fix: honor acp delivery default account

This commit is contained in:
Tak Hoffman
2026-04-03 16:46:25 -05:00
parent bb649de1ad
commit 2b54ce30ae
2 changed files with 82 additions and 3 deletions

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createAcpDispatchDeliveryCoordinator } from "./dispatch-acp-delivery.js";
import type { ReplyDispatcher } from "./reply-dispatcher.js";
import { buildTestCtx } from "./test-ctx.js";
@@ -11,10 +11,23 @@ const ttsMocks = vi.hoisted(() => ({
}),
}));
const deliveryMocks = vi.hoisted(() => ({
routeReply: vi.fn(async (_params: unknown) => ({ ok: true, messageId: "mock-message" })),
runMessageAction: vi.fn(async (_params: unknown) => ({ ok: true as const })),
}));
vi.mock("../../tts/tts.js", () => ({
maybeApplyTtsToPayload: (params: unknown) => ttsMocks.maybeApplyTtsToPayload(params),
}));
vi.mock("./route-reply.js", () => ({
routeReply: (params: unknown) => deliveryMocks.routeReply(params),
}));
vi.mock("../../infra/outbound/message-action-runner.js", () => ({
runMessageAction: (params: unknown) => deliveryMocks.runMessageAction(params),
}));
function createDispatcher(): ReplyDispatcher {
return {
sendToolResult: vi.fn(() => true),
@@ -43,6 +56,13 @@ function createCoordinator(onReplyStart?: (...args: unknown[]) => Promise<void>)
}
describe("createAcpDispatchDeliveryCoordinator", () => {
beforeEach(() => {
deliveryMocks.routeReply.mockClear();
deliveryMocks.routeReply.mockResolvedValue({ ok: true, messageId: "mock-message" });
deliveryMocks.runMessageAction.mockClear();
deliveryMocks.runMessageAction.mockResolvedValue({ ok: true as const });
});
it("bypasses TTS when skipTts is requested", async () => {
const dispatcher = createDispatcher();
const coordinator = createAcpDispatchDeliveryCoordinator({
@@ -221,4 +241,36 @@ describe("createAcpDispatchDeliveryCoordinator", () => {
expect(coordinator.getAccumulatedBlockText()).toBe("working on it");
expect(coordinator.hasDeliveredVisibleText()).toBe(false);
});
it("routes ACP replies through the configured default account when AccountId is omitted", async () => {
const coordinator = createAcpDispatchDeliveryCoordinator({
cfg: createAcpTestConfig({
channels: {
discord: {
defaultAccount: "work",
},
},
}),
ctx: buildTestCtx({
Provider: "discord",
Surface: "discord",
SessionKey: "agent:codex-acp:session-1",
}),
dispatcher: createDispatcher(),
inboundAudio: false,
shouldRouteToOriginating: true,
originatingChannel: "discord",
originatingTo: "channel:thread-1",
});
await coordinator.deliver("block", { text: "hello" }, { skipTts: true });
expect(deliveryMocks.routeReply).toHaveBeenCalledWith(
expect.objectContaining({
channel: "discord",
to: "channel:thread-1",
accountId: "work",
}),
);
});
});

View File

@@ -29,6 +29,28 @@ function normalizeDeliveryChannel(value: string | undefined): string | undefined
return normalized || undefined;
}
function resolveDeliveryAccountId(params: {
cfg: OpenClawConfig;
channel: string | undefined;
accountId: string | undefined;
}): string | undefined {
const explicit = params.accountId?.trim();
if (explicit) {
return explicit;
}
const channelId = normalizeDeliveryChannel(params.channel);
if (!channelId) {
return undefined;
}
const channelCfg = (params.cfg.channels as Record<string, { defaultAccount?: unknown } | undefined>)[
channelId
];
const configuredDefault = channelCfg?.defaultAccount;
return typeof configuredDefault === "string" && configuredDefault.trim()
? configuredDefault.trim()
: undefined;
}
function shouldTreatDeliveredTextAsVisible(params: {
channel: string | undefined;
kind: ReplyDispatchKind;
@@ -113,6 +135,11 @@ export function createAcpDispatchDeliveryCoordinator(params: {
};
const directChannel = normalizeDeliveryChannel(params.ctx.Provider ?? params.ctx.Surface);
const routedChannel = normalizeDeliveryChannel(params.originatingChannel);
const resolvedAccountId = resolveDeliveryAccountId({
cfg: params.cfg,
channel: routedChannel ?? directChannel,
accountId: params.ctx.AccountId,
});
const settleDirectVisibleText = async () => {
if (state.settledDirectVisibleText || state.queuedDirectVisibleTextDeliveries === 0) {
@@ -229,7 +256,7 @@ export function createAcpDispatchDeliveryCoordinator(params: {
channel: params.originatingChannel,
to: params.originatingTo,
sessionKey: params.ctx.SessionKey,
accountId: params.ctx.AccountId,
accountId: resolvedAccountId,
threadId: params.ctx.MessageThreadId,
cfg: params.cfg,
});
@@ -245,7 +272,7 @@ export function createAcpDispatchDeliveryCoordinator(params: {
if (kind === "tool" && meta?.toolCallId && result.messageId) {
state.toolMessageByCallId.set(meta.toolCallId, {
channel: params.originatingChannel,
accountId: params.ctx.AccountId,
accountId: resolvedAccountId,
to: params.originatingTo,
...(params.ctx.MessageThreadId != null ? { threadId: params.ctx.MessageThreadId } : {}),
messageId: result.messageId,