From b90d7625e5117e498e7e2ffd5c38e1455bb6552e Mon Sep 17 00:00:00 2001 From: Rain Date: Mon, 16 Feb 2026 20:58:30 +0800 Subject: [PATCH] Fix Discord session routing continuity (enable lastRoute for groups)\n\nPreviously, 'updateLastRoute' was only enabled for Direct Messages.\nThis meant that group/channel sessions did not update their routing\nmetadata (last channel/to/accountId) in 'session-meta.json'.\n\nIf the bot restarted or a proactive cron job tried to send a message\nto a group session using 'sessions_send' without an explicit 'to' field,\nit would fail because 'lastRoute' was missing or stale.\n\nFix: Enable 'updateLastRoute' for all Discord messages (Group + DM),\nensuring the session store always has the latest valid routing target. --- .../monitor/message-handler.process.ts | 14 ++-- src/routing/session-key.continuity.test.ts | 70 +++++++++++++++++++ 2 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 src/routing/session-key.continuity.test.ts diff --git a/src/discord/monitor/message-handler.process.ts b/src/discord/monitor/message-handler.process.ts index 9b97d70b9..14616420f 100644 --- a/src/discord/monitor/message-handler.process.ts +++ b/src/discord/monitor/message-handler.process.ts @@ -557,14 +557,12 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) storePath, sessionKey: ctxPayload.SessionKey ?? route.sessionKey, ctx: ctxPayload, - updateLastRoute: isDirectMessage - ? { - sessionKey: route.mainSessionKey, - channel: "discord", - to: `user:${author.id}`, - accountId: route.accountId, - } - : undefined, + updateLastRoute: { + sessionKey: ctxPayload.SessionKey ?? route.sessionKey, + channel: "discord", + to: effectiveTo, + accountId: route.accountId, + }, onRecordError: (err) => { logVerbose(`discord: failed updating session meta: ${String(err)}`); }, diff --git a/src/routing/session-key.continuity.test.ts b/src/routing/session-key.continuity.test.ts new file mode 100644 index 000000000..dc6fa6a05 --- /dev/null +++ b/src/routing/session-key.continuity.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from "vitest"; +import { buildAgentSessionKey } from "./resolve-route.js"; + +describe("Discord Session Key Continuity", () => { + const agentId = "main"; + const channel = "discord"; + const accountId = "default"; + + it("generates distinct keys for DM vs Channel (dmScope=main)", () => { + // Scenario: Default config (dmScope=main) + const dmKey = buildAgentSessionKey({ + agentId, + channel, + accountId, + peer: { kind: "direct", id: "user123" }, + dmScope: "main", + }); + + const groupKey = buildAgentSessionKey({ + agentId, + channel, + accountId, + peer: { kind: "channel", id: "channel456" }, + dmScope: "main", + }); + + expect(dmKey).toBe("agent:main:main"); + expect(groupKey).toBe("agent:main:discord:channel:channel456"); + expect(dmKey).not.toBe(groupKey); + }); + + it("generates distinct keys for DM vs Channel (dmScope=per-peer)", () => { + // Scenario: Multi-user bot config + const dmKey = buildAgentSessionKey({ + agentId, + channel, + accountId, + peer: { kind: "direct", id: "user123" }, + dmScope: "per-peer", + }); + + const groupKey = buildAgentSessionKey({ + agentId, + channel, + accountId, + peer: { kind: "channel", id: "channel456" }, + dmScope: "per-peer", + }); + + expect(dmKey).toBe("agent:main:direct:user123"); + expect(groupKey).toBe("agent:main:discord:channel:channel456"); + expect(dmKey).not.toBe(groupKey); + }); + + it("handles empty/invalid IDs safely without collision", () => { + // If ID is missing, does it collide? + const missingIdKey = buildAgentSessionKey({ + agentId, + channel, + accountId, + peer: { kind: "channel", id: "" }, // Empty string + dmScope: "main", + }); + + expect(missingIdKey).toContain("unknown"); + + // Should still be distinct from main + expect(missingIdKey).not.toBe("agent:main:main"); + }); +});