test: expand accountId delivery coverage

Co-authored-by: Adam Holt <adam91holt@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-17 03:17:29 +00:00
parent 46015a3dd8
commit bc7d603867
3 changed files with 139 additions and 4 deletions

View File

@@ -191,4 +191,85 @@ describe("subagent announce formatting", () => {
expect(call?.params?.to).toBe("+1555");
expect(call?.params?.accountId).toBe("kev");
});
it("uses requester accountId for direct announce when not queued", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
const didAnnounce = await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",
childRunId: "run-direct",
requesterSessionKey: "agent:main:main",
requesterChannel: "whatsapp",
requesterAccountId: "acct-123",
requesterDisplayKey: "main",
task: "do thing",
timeoutMs: 1000,
cleanup: "keep",
waitForCompletion: false,
startedAt: 10,
endedAt: 20,
outcome: { status: "ok" },
});
expect(didAnnounce).toBe(true);
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
expect(call?.params?.channel).toBe("whatsapp");
expect(call?.params?.accountId).toBe("acct-123");
});
it("splits collect-mode announces when accountId differs", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
"agent:main:main": {
sessionId: "session-789",
lastChannel: "whatsapp",
lastTo: "+1555",
queueMode: "collect",
queueDebounceMs: 0,
},
};
await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",
childRunId: "run-a",
requesterSessionKey: "main",
requesterAccountId: "acct-a",
requesterDisplayKey: "main",
task: "do thing",
timeoutMs: 1000,
cleanup: "keep",
waitForCompletion: false,
startedAt: 10,
endedAt: 20,
outcome: { status: "ok" },
});
await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",
childRunId: "run-b",
requesterSessionKey: "main",
requesterAccountId: "acct-b",
requesterDisplayKey: "main",
task: "do thing",
timeoutMs: 1000,
cleanup: "keep",
waitForCompletion: false,
startedAt: 10,
endedAt: 20,
outcome: { status: "ok" },
});
await new Promise((r) => setTimeout(r, 5));
const accountIds = agentSpy.mock.calls.map(
(call) => (call[0] as { params?: Record<string, unknown> }).params?.accountId,
);
expect(accountIds).toContain("acct-a");
expect(accountIds).toContain("acct-b");
expect(agentSpy).toHaveBeenCalledTimes(2);
});
});

View File

@@ -351,6 +351,19 @@ function loadRequesterSessionEntry(requesterSessionKey: string) {
return { cfg, entry, canonicalKey };
}
function resolveAnnounceOrigin(params: {
channel?: string;
to?: string;
accountId?: string;
fallbackAccountId?: string;
}) {
return normalizeDeliveryContext({
channel: params.channel,
to: params.to,
accountId: params.accountId ?? params.fallbackAccountId,
});
}
async function maybeQueueSubagentAnnounce(params: {
requesterSessionKey: string;
triggerMessage: string;
@@ -381,10 +394,11 @@ async function maybeQueueSubagentAnnounce(params: {
queueSettings.mode === "steer-backlog" ||
queueSettings.mode === "interrupt";
if (isActive && (shouldFollowup || queueSettings.mode === "steer")) {
const origin = normalizeDeliveryContext({
const origin = resolveAnnounceOrigin({
channel: entry?.lastChannel,
to: entry?.lastTo,
accountId: entry?.lastAccountId ?? params.requesterAccountId,
accountId: entry?.lastAccountId,
fallbackAccountId: params.requesterAccountId,
});
enqueueAnnounce(
canonicalKey,
@@ -624,7 +638,7 @@ export async function runSubagentAnnounceFlow(params: {
}
// Send to main agent - it will respond in its own voice
const directOrigin = normalizeDeliveryContext({
const directOrigin = resolveAnnounceOrigin({
channel: params.requesterChannel,
accountId: params.requesterAccountId,
});

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { CliDeps } from "../cli/deps.js";
import type { ClawdbotConfig } from "../config/config.js";
@@ -25,6 +25,11 @@ vi.mock("../infra/outbound/targets.js", () => ({
}));
describe("deliverAgentCommandResult", () => {
beforeEach(() => {
mocks.deliverOutboundPayloads.mockClear();
mocks.resolveOutboundTarget.mockClear();
});
it("prefers explicit accountId for outbound delivery", async () => {
const cfg = {} as ClawdbotConfig;
const deps = {} as CliDeps;
@@ -61,4 +66,39 @@ describe("deliverAgentCommandResult", () => {
expect.objectContaining({ accountId: "kev" }),
);
});
it("falls back to session accountId for implicit delivery", async () => {
const cfg = {} as ClawdbotConfig;
const deps = {} as CliDeps;
const runtime = {
log: vi.fn(),
error: vi.fn(),
} as unknown as RuntimeEnv;
const sessionEntry = {
lastAccountId: "legacy",
} as SessionEntry;
const result = {
payloads: [{ text: "hi" }],
meta: {},
};
const { deliverAgentCommandResult } = await import("./agent/delivery.js");
await deliverAgentCommandResult({
cfg,
deps,
runtime,
opts: {
message: "hello",
deliver: true,
channel: "whatsapp",
},
sessionEntry,
result,
payloads: result.payloads,
});
expect(mocks.deliverOutboundPayloads).toHaveBeenCalledWith(
expect.objectContaining({ accountId: "legacy" }),
);
});
});