fix: harden discord ack auth and gate fallout (#60081) (thanks @FunJim)

This commit is contained in:
Peter Steinberger 2026-04-04 00:37:46 +09:00
parent c1741abc3c
commit bf6bd7432a
6 changed files with 44 additions and 12 deletions

View File

@ -71,6 +71,7 @@ Docs: https://docs.openclaw.ai
- Plugins/allowlists: let explicit bundled chat channel enablement bypass `plugins.allow`, while keeping auto-enabled channel activation and startup sidecars behind restrictive allowlists. (#60233) Thanks @dorukardahan.
- Allowlist/commands: require owner access for `/allowlist add` and `/allowlist remove` so command-authorized non-owners cannot mutate persisted allowlists. (#59836) Thanks @eleqtrizit.
- Control UI/skills: clear stale ClawHub results immediately when the search query changes, so debounced searches cannot keep outdated install targets visible. Related #60134.
- Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim.
## 2026.4.2

View File

@ -303,14 +303,31 @@ describe("processDiscordMessage ack reactions", () => {
it("sends ack reactions for mention-gated guild messages when mentioned", async () => {
const ctx = await createBaseContext({
accountId: "ops",
shouldRequireMention: true,
effectiveWasMentioned: true,
route: {
agentId: "main",
channel: "discord",
accountId: "ops",
sessionKey: "agent:main:discord:channel:c1",
mainSessionKey: "agent:main:main",
},
});
// oxlint-disable-next-line typescript/no-explicit-any
await processDiscordMessage(ctx as any);
expect(sendMocks.reactMessageDiscord.mock.calls[0]).toEqual(["c1", "m1", "👀", { rest: {}, cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }) }]);
expect(sendMocks.reactMessageDiscord.mock.calls[0]).toEqual([
"c1",
"m1",
"👀",
{
rest: {},
cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }),
accountId: "ops",
},
]);
});
it("uses preflight-resolved messageChannelId when message.channelId is missing", async () => {
@ -332,7 +349,11 @@ describe("processDiscordMessage ack reactions", () => {
"fallback-channel",
"m1",
"👀",
{ rest: {}, cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }) },
{
rest: {},
cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }),
accountId: "default",
},
]);
});
@ -488,7 +509,21 @@ describe("processDiscordMessage ack reactions", () => {
// oxlint-disable-next-line typescript/no-explicit-any
await processDiscordMessage(ctx as any);
expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith("c1", "m1", "👀", expect.objectContaining({ rest: {}, cfg: expect.objectContaining({ messages: expect.objectContaining({ ackReaction: "👀", removeAckAfterReply: true }) }) }));
expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith(
"c1",
"m1",
"👀",
expect.objectContaining({
rest: {},
cfg: expect.objectContaining({
messages: expect.objectContaining({
ackReaction: "👀",
removeAckAfterReply: true,
}),
}),
accountId: "default",
}),
);
});
it("removes the plain ack reaction when status reactions are disabled and removeAckAfterReply is enabled", async () => {

View File

@ -26,14 +26,14 @@ type DiscordSubagentSpawningEvent = {
threadId?: string | number;
};
childSessionKey: string;
agentId?: string;
agentId: string;
label?: string;
};
type DiscordSubagentEndedEvent = {
targetSessionKey: string;
accountId?: string;
targetKind?: string;
targetKind?: ThreadBindingTargetKind;
reason?: string;
sendFarewell?: boolean;
};

View File

@ -119,7 +119,7 @@ const resolveTelegramApproveCommandBehavior: NonNullable<
isTelegramExecApprovalAuthorizedSender({ cfg, accountId, senderId }) &&
!isTelegramExecApprovalApprover({ cfg, accountId, senderId })
) {
return { kind: "ignore" };
return undefined;
}
return {
kind: "reply",

View File

@ -503,7 +503,7 @@ const telegramCommandTestPlugin: ChannelPlugin = {
isTelegramExecApprovalAuthorizedSender({ cfg, accountId, senderId }) &&
!getTelegramExecApprovalApprovers({ cfg, accountId }).includes(senderId?.trim() ?? "")
) {
return { kind: "ignore" } as const;
return undefined;
}
return {
kind: "reply",

View File

@ -1217,7 +1217,7 @@ describe("secrets runtime snapshot", () => {
const ignoredInactiveWarnings = snapshot.warnings.filter(
(warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
);
expect(ignoredInactiveWarnings).toHaveLength(10);
expect(ignoredInactiveWarnings).toHaveLength(6);
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining([
"agents.defaults.memorySearch.remote.apiKey",
@ -1226,10 +1226,6 @@ describe("secrets runtime snapshot", () => {
"channels.telegram.accounts.disabled.botToken",
"plugins.entries.brave.config.webSearch.apiKey",
"plugins.entries.google.config.webSearch.apiKey",
"plugins.entries.xai.config.webSearch.apiKey",
"plugins.entries.moonshot.config.webSearch.apiKey",
"plugins.entries.perplexity.config.webSearch.apiKey",
"plugins.entries.firecrawl.config.webSearch.apiKey",
]),
);
});