From 037da3ce34422d536f65a2e7722ccd930a49cc69 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:26:36 -0500 Subject: [PATCH] fix: honor acp dispatch default account --- src/auto-reply/reply/dispatch-acp.test.ts | 84 +++++++++++++++++++++++ src/auto-reply/reply/dispatch-acp.ts | 14 ++-- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/auto-reply/reply/dispatch-acp.test.ts b/src/auto-reply/reply/dispatch-acp.test.ts index 0c04bf46f67..5cdbb4d094a 100644 --- a/src/auto-reply/reply/dispatch-acp.test.ts +++ b/src/auto-reply/reply/dispatch-acp.test.ts @@ -811,6 +811,90 @@ describe("tryDispatchAcpReply", () => { ); }); + it("honors the configured default account when checking bound-session identity notices", async () => { + const canonicalSessionKey = "agent:main:main"; + managerMocks.resolveSession.mockReturnValue({ + kind: "ready", + sessionKey: canonicalSessionKey, + meta: createAcpSessionMeta({ + identity: { + state: "pending", + source: "ensure", + lastUpdatedAt: Date.now(), + acpxRecordId: "rec-work", + }, + }), + }); + bindingServiceMocks.listBySession.mockImplementation((targetSessionKey: string) => + targetSessionKey === canonicalSessionKey + ? [ + { + bindingId: "discord:work:thread-1", + targetSessionKey: canonicalSessionKey, + targetKind: "session", + conversation: { + channel: "discord", + accountId: "work", + conversationId: "thread-1", + }, + status: "active", + boundAt: 0, + }, + ] + : [], + ); + sessionMetaMocks.readAcpSessionEntry.mockImplementation( + (params: { sessionKey: string; cfg?: OpenClawConfig }) => + params.sessionKey === canonicalSessionKey + ? { + cfg: params.cfg ?? createAcpTestConfig(), + storePath: "/tmp/openclaw-session-store.json", + sessionKey: canonicalSessionKey, + storeSessionKey: canonicalSessionKey, + acp: createAcpSessionMeta({ + identity: { + state: "resolved", + source: "status", + lastUpdatedAt: Date.now(), + acpxSessionId: "acpx-work", + }, + }), + } + : null, + ); + managerMocks.runTurn.mockResolvedValue(undefined); + const { dispatcher } = createDispatcher(); + + await runDispatch({ + bodyForAgent: "test", + dispatcher, + cfg: createAcpTestConfig({ + channels: { + discord: { + defaultAccount: "work", + }, + }, + }), + ctxOverrides: { + Provider: "discord", + Surface: "discord", + }, + sessionKeyOverride: canonicalSessionKey, + }); + + expect(bindingServiceMocks.listBySession).toHaveBeenCalledWith(canonicalSessionKey); + expect(dispatcher.sendFinalReply).toHaveBeenCalledWith( + expect.objectContaining({ + text: expect.stringContaining("Session ids resolved."), + }), + ); + expect(dispatcher.sendFinalReply).toHaveBeenCalledWith( + expect.objectContaining({ + text: expect.stringContaining("acpx session id: acpx-work"), + }), + ); + }); + it("does not deliver final fallback text when routed block text was already visible", 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 2073e76a4c2..a93dfcd8da3 100644 --- a/src/auto-reply/reply/dispatch-acp.ts +++ b/src/auto-reply/reply/dispatch-acp.ts @@ -163,6 +163,7 @@ function resolveAcpRequestId(ctx: FinalizedMsgContext): string { } function hasBoundConversationForSession(params: { + cfg: OpenClawConfig; sessionKey: string; channelRaw: string | undefined; accountIdRaw: string | undefined; @@ -173,10 +174,14 @@ function hasBoundConversationForSession(params: { if (!channel) { return false; } - const accountId = String(params.accountIdRaw ?? "") - .trim() - .toLowerCase(); - const normalizedAccountId = accountId || "default"; + const accountId = String(params.accountIdRaw ?? "").trim().toLowerCase(); + const channels = params.cfg.channels as Record; + const configuredDefaultAccountId = channels?.[channel]?.defaultAccount; + const normalizedAccountId = + accountId || + (typeof configuredDefaultAccountId === "string" && configuredDefaultAccountId.trim() + ? configuredDefaultAccountId.trim().toLowerCase() + : "default"); const bindingService = getSessionBindingService(); const bindings = bindingService.listBySession(params.sessionKey); return bindings.some((binding) => { @@ -375,6 +380,7 @@ export async function tryDispatchAcpReply(params: { identityPendingBeforeTurn && (Boolean(params.ctx.MessageThreadId != null && String(params.ctx.MessageThreadId).trim()) || hasBoundConversationForSession({ + cfg: params.cfg, sessionKey: canonicalSessionKey, channelRaw: params.ctx.OriginatingChannel ?? params.ctx.Surface ?? params.ctx.Provider, accountIdRaw: params.ctx.AccountId,