diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index f33bae2b2..a89caeae7 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -121,8 +121,10 @@ describe("sessions", () => { await updateLastRoute({ storePath, sessionKey: mainSessionKey, - channel: "telegram", - to: " 12345 ", + deliveryContext: { + channel: "telegram", + to: " 12345 ", + }, }); const store = loadSessionStore(storePath); @@ -142,6 +144,36 @@ describe("sessions", () => { expect(store[mainSessionKey]?.compactionCount).toBe(2); }); + it("updateLastRoute prefers explicit deliveryContext", async () => { + const mainSessionKey = "agent:main:main"; + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-")); + const storePath = path.join(dir, "sessions.json"); + await fs.writeFile(storePath, "{}", "utf-8"); + + await updateLastRoute({ + storePath, + sessionKey: mainSessionKey, + channel: "whatsapp", + to: "111", + accountId: "legacy", + deliveryContext: { + channel: "telegram", + to: "222", + accountId: "primary", + }, + }); + + const store = loadSessionStore(storePath); + expect(store[mainSessionKey]?.lastChannel).toBe("telegram"); + expect(store[mainSessionKey]?.lastTo).toBe("222"); + expect(store[mainSessionKey]?.lastAccountId).toBe("primary"); + expect(store[mainSessionKey]?.deliveryContext).toEqual({ + channel: "telegram", + to: "222", + accountId: "primary", + }); + }); + it("updateSessionStore preserves concurrent additions", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-")); const storePath = path.join(dir, "sessions.json"); diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 3544cdcf0..3c8ead721 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -4,7 +4,13 @@ import path from "node:path"; import JSON5 from "json5"; import { getFileMtimeMs, isCacheEnabled, resolveCacheTtlMs } from "../cache-utils.js"; -import { normalizeSessionDeliveryFields } from "../../utils/delivery-context.js"; +import { + deliveryContextFromSession, + mergeDeliveryContext, + normalizeDeliveryContext, + normalizeSessionDeliveryFields, + type DeliveryContext, +} from "../../utils/delivery-context.js"; import { mergeSessionEntry, type SessionEntry } from "./types.js"; // ============================================================================ @@ -323,20 +329,29 @@ export async function updateSessionStoreEntry(params: { export async function updateLastRoute(params: { storePath: string; sessionKey: string; - channel: SessionEntry["lastChannel"]; + channel?: SessionEntry["lastChannel"]; to?: string; accountId?: string; + deliveryContext?: DeliveryContext; }) { const { storePath, sessionKey, channel, to, accountId } = params; return await withSessionStoreLock(storePath, async () => { const store = loadSessionStore(storePath); const existing = store[sessionKey]; const now = Date.now(); + const explicitContext = normalizeDeliveryContext(params.deliveryContext); + const inlineContext = normalizeDeliveryContext({ + channel, + to, + accountId, + }); + const mergedInput = mergeDeliveryContext(explicitContext, inlineContext); + const merged = mergeDeliveryContext(mergedInput, deliveryContextFromSession(existing)); const normalized = normalizeSessionDeliveryFields({ deliveryContext: { - channel: channel ?? existing?.lastChannel ?? existing?.deliveryContext?.channel, - to: to ?? existing?.lastTo ?? existing?.deliveryContext?.to, - accountId: accountId ?? existing?.lastAccountId ?? existing?.deliveryContext?.accountId, + channel: merged?.channel, + to: merged?.to, + accountId: merged?.accountId, }, }); const next = mergeSessionEntry(existing, { diff --git a/src/discord/monitor/message-handler.process.ts b/src/discord/monitor/message-handler.process.ts index 46abc522d..d669cf4dd 100644 --- a/src/discord/monitor/message-handler.process.ts +++ b/src/discord/monitor/message-handler.process.ts @@ -272,9 +272,11 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) await updateLastRoute({ storePath, sessionKey: route.mainSessionKey, - channel: "discord", - to: `user:${author.id}`, - accountId: route.accountId, + deliveryContext: { + channel: "discord", + to: `user:${author.id}`, + accountId: route.accountId, + }, }); } diff --git a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts index 37bb277dd..7fe151561 100644 --- a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts +++ b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts @@ -121,8 +121,10 @@ describe("monitorIMessageProvider", () => { expect(updateLastRouteMock).toHaveBeenCalledWith( expect.objectContaining({ - channel: "imessage", - to: "+15550004444", + deliveryContext: expect.objectContaining({ + channel: "imessage", + to: "+15550004444", + }), }), ); }); diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 099b0779e..6593d0bb1 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -453,9 +453,11 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P await updateLastRoute({ storePath, sessionKey: route.mainSessionKey, - channel: "imessage", - to, - accountId: route.accountId, + deliveryContext: { + channel: "imessage", + to, + accountId: route.accountId, + }, }); } } diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index 754fd3584..8a86ef945 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -143,9 +143,11 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { await updateLastRoute({ storePath, sessionKey: route.mainSessionKey, - channel: "signal", - to: entry.senderRecipient, - accountId: route.accountId, + deliveryContext: { + channel: "signal", + to: entry.senderRecipient, + accountId: route.accountId, + }, }); } diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index 9f8d3ba7e..d50abacf5 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -32,9 +32,11 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag await updateLastRoute({ storePath, sessionKey: route.mainSessionKey, - channel: "slack", - to: `user:${message.user}`, - accountId: route.accountId, + deliveryContext: { + channel: "slack", + to: `user:${message.user}`, + accountId: route.accountId, + }, }); } diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 5d068df4c..046ea3b6b 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -489,9 +489,11 @@ export const buildTelegramMessageContext = async ({ await updateLastRoute({ storePath, sessionKey: route.mainSessionKey, - channel: "telegram", - to: String(chatId), - accountId: route.accountId, + deliveryContext: { + channel: "telegram", + to: String(chatId), + accountId: route.accountId, + }, }); } diff --git a/src/web/auto-reply/monitor/last-route.ts b/src/web/auto-reply/monitor/last-route.ts index 692a2cfa0..6e52628d3 100644 --- a/src/web/auto-reply/monitor/last-route.ts +++ b/src/web/auto-reply/monitor/last-route.ts @@ -28,9 +28,11 @@ export function updateLastRouteInBackground(params: { const task = updateLastRoute({ storePath, sessionKey: params.sessionKey, - channel: params.channel, - to: params.to, - accountId: params.accountId, + deliveryContext: { + channel: params.channel, + to: params.to, + accountId: params.accountId, + }, }).catch((err) => { params.warn( {