From 01534d9bd563200d2d86e3dee761e6a6df266e91 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:49:12 -0500 Subject: [PATCH] fix: honor acp projector default account --- src/auto-reply/reply/dispatch-acp.test.ts | 42 +++++++++++++++++++++++ src/auto-reply/reply/dispatch-acp.ts | 29 +++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/auto-reply/reply/dispatch-acp.test.ts b/src/auto-reply/reply/dispatch-acp.test.ts index 5cdbb4d094a..734f0965e49 100644 --- a/src/auto-reply/reply/dispatch-acp.test.ts +++ b/src/auto-reply/reply/dispatch-acp.test.ts @@ -1033,6 +1033,48 @@ describe("tryDispatchAcpReply", () => { expect(result?.queuedFinal).toBe(true); }); + it("honors the configured default account for ACP projector chunking when AccountId is omitted", async () => { + setReadyAcpResolution(); + const cfg = createAcpTestConfig({ + channels: { + discord: { + defaultAccount: "work", + accounts: { + work: { + textChunkLimit: 5, + }, + }, + }, + }, + }); + managerMocks.runTurn.mockImplementation( + async ({ onEvent }: { onEvent: (event: unknown) => Promise }) => { + await onEvent({ type: "text_delta", text: "abcdef", tag: "agent_message_chunk" }); + await onEvent({ type: "done" }); + }, + ); + + const { dispatcher } = createDispatcher(); + await runDispatch({ + bodyForAgent: "reply", + cfg, + dispatcher, + ctxOverrides: { + Provider: "discord", + Surface: "discord", + }, + }); + + expect(dispatcher.sendBlockReply).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ text: "abcde" }), + ); + expect(dispatcher.sendBlockReply).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ text: "f" }), + ); + }); + it("does not add text fallback when final TTS already delivered audio", async () => { setReadyAcpResolution(); ttsMocks.resolveTtsConfig.mockReturnValue({ mode: "final" }); diff --git a/src/auto-reply/reply/dispatch-acp.ts b/src/auto-reply/reply/dispatch-acp.ts index a93dfcd8da3..769da13947f 100644 --- a/src/auto-reply/reply/dispatch-acp.ts +++ b/src/auto-reply/reply/dispatch-acp.ts @@ -200,6 +200,28 @@ function hasBoundConversationForSession(params: { }); } +function resolveDispatchAccountId(params: { + cfg: OpenClawConfig; + channelRaw: string | undefined; + accountIdRaw: string | undefined; +}): string | undefined { + const channel = String(params.channelRaw ?? "") + .trim() + .toLowerCase(); + if (!channel) { + return params.accountIdRaw?.trim() || undefined; + } + const explicit = params.accountIdRaw?.trim(); + if (explicit) { + return explicit; + } + const channels = params.cfg.channels as Record; + const configuredDefaultAccountId = channels?.[channel]?.defaultAccount; + return typeof configuredDefaultAccountId === "string" && configuredDefaultAccountId.trim() + ? configuredDefaultAccountId.trim() + : undefined; +} + export type AcpDispatchAttemptResult = { queuedFinal: boolean; counts: Record; @@ -394,12 +416,17 @@ export async function tryDispatchAcpReply(params: { resolveAgentIdFromSessionKey(canonicalSessionKey) ).trim() : resolveAgentIdFromSessionKey(canonicalSessionKey); + const effectiveDispatchAccountId = resolveDispatchAccountId({ + cfg: params.cfg, + channelRaw: params.ctx.OriginatingChannel ?? params.ctx.Surface ?? params.ctx.Provider, + accountIdRaw: params.ctx.AccountId, + }); const projector = createAcpReplyProjector({ cfg: params.cfg, shouldSendToolSummaries: params.shouldSendToolSummaries, deliver: delivery.deliver, provider: params.ctx.Surface ?? params.ctx.Provider, - accountId: params.ctx.AccountId, + accountId: effectiveDispatchAccountId, }); const acpDispatchStartedAt = Date.now();