fix: preserve account routing for explicit targets

Co-authored-by: adam91holt <adam91holt@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-17 04:24:47 +00:00
parent 780c811146
commit 3efc5e54fa
3 changed files with 95 additions and 3 deletions

View File

@@ -31,6 +31,7 @@
### Fixes
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
- Gateway: honor explicit delivery targets without implicit accountId fallback; preserve lastAccountId for implicit routing.
- Repo: fix oxlint config filename and move ignore pattern into config. (#1064) — thanks @connorshea.
- Messages: `/stop` now hard-aborts queued followups and sub-agent runs; suppress zero-count stop notes.
- Sessions: reset `compactionCount` on `/new` and `/reset`, and preserve `sessions.json` file mode (0600).

View File

@@ -155,6 +155,7 @@ export const agentHandlers: GatewayRequestHandlers = {
skillsSnapshot: entry?.skillsSnapshot,
lastChannel: entry?.lastChannel,
lastTo: entry?.lastTo,
lastAccountId: entry?.lastAccountId,
modelOverride: entry?.modelOverride,
providerOverride: entry?.providerOverride,
label: labelValue,
@@ -201,8 +202,11 @@ export const agentHandlers: GatewayRequestHandlers = {
const lastChannel = sessionEntry?.lastChannel;
const lastTo = typeof sessionEntry?.lastTo === "string" ? sessionEntry.lastTo.trim() : "";
const explicitTo =
typeof request.to === "string" && request.to.trim() ? request.to.trim() : undefined;
const resolvedAccountId =
normalizeAccountId(request.accountId) ?? normalizeAccountId(sessionEntry?.lastAccountId);
normalizeAccountId(request.accountId) ??
(explicitTo ? undefined : normalizeAccountId(sessionEntry?.lastAccountId));
const wantsDelivery = request.deliver === true;
@@ -224,8 +228,6 @@ export const agentHandlers: GatewayRequestHandlers = {
return wantsDelivery ? DEFAULT_CHAT_CHANNEL : INTERNAL_MESSAGE_CHANNEL;
})();
const explicitTo =
typeof request.to === "string" && request.to.trim() ? request.to.trim() : undefined;
const deliveryTargetMode = explicitTo
? "explicit"
: isDeliverableMessageChannel(resolvedChannel)

View File

@@ -152,6 +152,95 @@ describe("gateway server agent", () => {
testState.allowFrom = undefined;
});
test("agent avoids lastAccountId when explicit to is provided", async () => {
testState.allowFrom = ["+1555"];
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
testState.sessionStorePath = path.join(dir, "sessions.json");
await fs.writeFile(
testState.sessionStorePath,
JSON.stringify(
{
main: {
sessionId: "sess-main-explicit",
updatedAt: Date.now(),
lastChannel: "whatsapp",
lastTo: "+1555",
lastAccountId: "legacy",
},
},
null,
2,
),
"utf-8",
);
const { server, ws } = await startServerWithClient();
await connectOk(ws);
const res = await rpcReq(ws, "agent", {
message: "hi",
sessionKey: "main",
deliver: true,
to: "+1666",
idempotencyKey: "idem-agent-explicit",
});
expect(res.ok).toBe(true);
const spy = vi.mocked(agentCommand);
const call = spy.mock.calls.at(-1)?.[0] as Record<string, unknown>;
expectChannels(call, "whatsapp");
expect(call.to).toBe("+1666");
expect(call.accountId).toBeUndefined();
ws.close();
await server.close();
testState.allowFrom = undefined;
});
test("agent falls back to lastAccountId for implicit delivery", async () => {
testState.allowFrom = ["+1555"];
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
testState.sessionStorePath = path.join(dir, "sessions.json");
await fs.writeFile(
testState.sessionStorePath,
JSON.stringify(
{
main: {
sessionId: "sess-main-implicit",
updatedAt: Date.now(),
lastChannel: "whatsapp",
lastTo: "+1555",
lastAccountId: "kev",
},
},
null,
2,
),
"utf-8",
);
const { server, ws } = await startServerWithClient();
await connectOk(ws);
const res = await rpcReq(ws, "agent", {
message: "hi",
sessionKey: "main",
deliver: true,
idempotencyKey: "idem-agent-implicit-account",
});
expect(res.ok).toBe(true);
const spy = vi.mocked(agentCommand);
const call = spy.mock.calls.at(-1)?.[0] as Record<string, unknown>;
expectChannels(call, "whatsapp");
expect(call.to).toBe("+1555");
expect(call.accountId).toBe("kev");
ws.close();
await server.close();
testState.allowFrom = undefined;
});
test("agent forwards image attachments as images[]", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-"));
testState.sessionStorePath = path.join(dir, "sessions.json");