fix: preserve discord announce account routing

This commit is contained in:
Peter Steinberger
2026-05-02 12:50:19 +01:00
parent 3ee41deba9
commit cd84e9bfb0
6 changed files with 78 additions and 4 deletions

View File

@@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai
- Hooks/doctor: warn when `hooks.transformsDir` points outside the canonical hooks transform directory, so invalid workspace skill paths get a direct recovery hint before the Gateway crash-loops. Fixes #75853. Thanks @midobk.
- Proxy/audio: convert standard `FormData` bodies before proxy-backed undici fetches, so audio transcription and multipart uploads no longer send `[object FormData]` when `HTTP_PROXY` or `HTTPS_PROXY` is configured. Fixes #48554. Thanks @dco5.
- Discord: allow explicitly configured ack reactions in tool-only guild channels while keeping automatic lifecycle/status reactions suppressed. Fixes #74922. Thanks @samvilian and @BlueBirdBack.
- Discord: enable session-backed A2A announce target lookup so `sessions_send` uses the target session's `deliveryContext.accountId` or `lastAccountId` instead of falling back to the default bot in multi-account setups. Fixes #42652; refs #51626 and #44773; supersedes #73975. Thanks @irchelper, @dpalfox, and @Lanfei.
- Discord: treat abort-time Carbon reconnect-exhausted events as expected shutdown during stale-socket restarts, so health-monitor restarts no longer reject the monitor lifecycle. Carries forward #58216; supersedes #73949. Thanks @Perttulands.
- Discord/native commands: return an explicit warning when slash command dispatch or direct plugin execution produces no visible reply instead of a success-style completion ack. Fixes #58986; supersedes #62057. Thanks @jb510.
- Discord: keep typing indicators alive during long tool runs and auto-compaction while keepalive ticks continue, so active sessions do not appear stalled before the final reply. Thanks @Squirbie.

View File

@@ -43,6 +43,7 @@
"blurb": "very well supported right now.",
"systemImage": "bubble.left.and.bubble.right",
"markdownCapable": true,
"preferSessionLookupForAnnounceTarget": true,
"commands": {
"nativeCommandsAutoEnabled": true,
"nativeSkillsAutoEnabled": true

View File

@@ -18,6 +18,7 @@ const DISCORD_CHANNEL_META = {
blurb: "very well supported right now.",
systemImage: "bubble.left.and.bubble.right",
markdownCapable: true,
preferSessionLookupForAnnounceTarget: true,
} as const;
export function getChatChannelMeta(id: string) {

View File

@@ -32,6 +32,12 @@ describe("createDiscordPluginBase", () => {
expect(plugin.security?.collectAuditFindings).toBeTypeOf("function");
});
it("hydrates announce delivery targets from stored session routing", () => {
const plugin = createDiscordPluginBase({ setup: {} as never });
expect(plugin.meta.preferSessionLookupForAnnounceTarget).toBe(true);
});
it("reports duplicate-token accounts as disabled to gateway startup", () => {
vi.stubEnv("DISCORD_BOT_TOKEN", "same-token");
const plugin = createDiscordPluginBase({ setup: {} as never });

View File

@@ -3,8 +3,15 @@ import type { CallGatewayOptions } from "../../gateway/call.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js";
import { runAgentStep } from "./agent-step.js";
import type { SessionListRow } from "./sessions-helpers.js";
import { runSessionsSendA2AFlow, __testing } from "./sessions-send-tool.a2a.js";
const callGatewayMock = vi.hoisted(() => vi.fn());
vi.mock("../../gateway/call.js", () => ({
callGateway: (opts: unknown) => callGatewayMock(opts),
}));
vi.mock("../run-wait.js", () => ({
waitForAgentRun: vi.fn().mockResolvedValue({ status: "ok" }),
readLatestAssistantReply: vi.fn().mockResolvedValue("Test announce reply"),
@@ -16,17 +23,25 @@ vi.mock("./agent-step.js", () => ({
describe("runSessionsSendA2AFlow announce delivery", () => {
let gatewayCalls: CallGatewayOptions[];
let sessionListRows: SessionListRow[];
beforeEach(() => {
setActivePluginRegistry(createSessionConversationTestRegistry());
gatewayCalls = [];
sessionListRows = [];
callGatewayMock.mockReset();
const callGateway = async <T = Record<string, unknown>>(opts: CallGatewayOptions) => {
gatewayCalls.push(opts);
if (opts.method === "sessions.list") {
return { sessions: sessionListRows } as T;
}
return {} as T;
};
callGatewayMock.mockImplementation(callGateway);
vi.clearAllMocks();
vi.mocked(runAgentStep).mockResolvedValue("Test announce reply");
__testing.setDepsForTest({
callGateway: async <T = Record<string, unknown>>(opts: CallGatewayOptions) => {
gatewayCalls.push(opts);
return {} as T;
},
callGateway,
});
});
@@ -70,6 +85,55 @@ describe("runSessionsSendA2AFlow announce delivery", () => {
expect(sendParams.threadId).toBeUndefined();
});
it.each([
{
source: "deliveryContext.accountId",
accountId: "thinker",
session: {
key: "agent:main:discord:channel:target-room",
kind: "group",
channel: "discord",
deliveryContext: {
channel: "discord",
to: "channel:target-room",
accountId: "thinker",
},
} satisfies SessionListRow,
},
{
source: "lastAccountId",
accountId: "scout",
session: {
key: "agent:main:discord:channel:target-room",
kind: "group",
channel: "discord",
lastChannel: "discord",
lastTo: "channel:target-room",
lastAccountId: "scout",
} satisfies SessionListRow,
},
])("uses Discord session $source for announce accountId", async ({ accountId, session }) => {
sessionListRows = [session];
await runSessionsSendA2AFlow({
targetSessionKey: session.key,
displayKey: session.key,
message: "Test message",
announceTimeoutMs: 10_000,
maxPingPongTurns: 0,
roundOneReply: "Worker completed successfully",
});
expect(gatewayCalls.some((call) => call.method === "sessions.list")).toBe(true);
const sendCall = gatewayCalls.find((call) => call.method === "send");
expect(sendCall).toBeDefined();
expect(sendCall?.params).toMatchObject({
channel: "discord",
to: "channel:target-room",
accountId,
});
});
it.each(["NO_REPLY", "HEARTBEAT_OK", "ANNOUNCE_SKIP", "REPLY_SKIP"])(
"does not re-inject exact control reply %s into agent-to-agent flow",
async (roundOneReply) => {

View File

@@ -62,6 +62,7 @@ export function createSessionConversationTestRegistry() {
selectionLabel: "Discord",
docsPath: "/channels/discord",
blurb: "Discord test stub.",
preferSessionLookupForAnnounceTarget: true,
},
capabilities: { chatTypes: ["direct", "channel", "thread"] },
messaging: {