diff --git a/src/config/sessions/session-key.ts b/src/config/sessions/session-key.ts index 70c1eba0a8b..1443be30630 100644 --- a/src/config/sessions/session-key.ts +++ b/src/config/sessions/session-key.ts @@ -1,5 +1,5 @@ import type { MsgContext } from "../../auto-reply/templating.js"; -import { normalizeChatType } from "../../channels/chat-type.js"; +import { normalizeExplicitDiscordSessionKey } from "../../discord/session-key-normalization.js"; import { buildAgentMainSessionKey, DEFAULT_AGENT_ID, @@ -29,24 +29,7 @@ export function deriveSessionKey(scope: SessionScope, ctx: MsgContext) { export function resolveSessionKey(scope: SessionScope, ctx: MsgContext, mainKey?: string) { const explicit = ctx.SessionKey?.trim(); if (explicit) { - let normalized = explicit.toLowerCase(); - if (normalizeChatType(ctx.ChatType) === "direct") { - normalized = normalized.replace(/^(agent:[^:]+:discord:)dm:/, "$1direct:"); - const match = normalized.match(/^((?:agent:[^:]+:)?)discord:channel:([^:]+)$/); - if (match) { - const from = (ctx.From ?? "").trim().toLowerCase(); - const senderId = (ctx.SenderId ?? "").trim().toLowerCase(); - const fromDiscordId = - from.startsWith("discord:") && !from.includes(":channel:") && !from.includes(":group:") - ? from.slice("discord:".length) - : ""; - const directId = senderId || fromDiscordId; - if (directId && directId === match[2]) { - normalized = `${match[1]}discord:direct:${match[2]}`; - } - } - } - return normalized; + return normalizeExplicitDiscordSessionKey(explicit, ctx); } const raw = deriveSessionKey(scope, ctx); if (scope === "global") { diff --git a/src/discord/session-key-normalization.test.ts b/src/discord/session-key-normalization.test.ts new file mode 100644 index 00000000000..22066c3da95 --- /dev/null +++ b/src/discord/session-key-normalization.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { normalizeExplicitDiscordSessionKey } from "./session-key-normalization.js"; + +describe("normalizeExplicitDiscordSessionKey", () => { + it("rewrites legacy discord:dm keys for direct chats", () => { + expect( + normalizeExplicitDiscordSessionKey("agent:fina:discord:dm:123456", { + ChatType: "direct", + From: "discord:123456", + SenderId: "123456", + }), + ).toBe("agent:fina:discord:direct:123456"); + }); + + it("rewrites phantom discord:channel keys when sender matches", () => { + expect( + normalizeExplicitDiscordSessionKey("discord:channel:123456", { + ChatType: "direct", + From: "discord:123456", + SenderId: "123456", + }), + ).toBe("discord:direct:123456"); + }); + + it("leaves non-direct channel keys unchanged", () => { + expect( + normalizeExplicitDiscordSessionKey("agent:fina:discord:channel:123456", { + ChatType: "channel", + From: "discord:channel:123456", + SenderId: "789", + }), + ).toBe("agent:fina:discord:channel:123456"); + }); +}); diff --git a/src/discord/session-key-normalization.ts b/src/discord/session-key-normalization.ts new file mode 100644 index 00000000000..05a48c7e06b --- /dev/null +++ b/src/discord/session-key-normalization.ts @@ -0,0 +1,27 @@ +import type { MsgContext } from "../auto-reply/templating.js"; +import { normalizeChatType } from "../channels/chat-type.js"; + +export function normalizeExplicitDiscordSessionKey( + sessionKey: string, + ctx: Pick, +): string { + let normalized = sessionKey.trim().toLowerCase(); + if (normalizeChatType(ctx.ChatType) !== "direct") { + return normalized; + } + + normalized = normalized.replace(/^(agent:[^:]+:discord:)dm:/, "$1direct:"); + const match = normalized.match(/^((?:agent:[^:]+:)?)discord:channel:([^:]+)$/); + if (!match) { + return normalized; + } + + const from = (ctx.From ?? "").trim().toLowerCase(); + const senderId = (ctx.SenderId ?? "").trim().toLowerCase(); + const fromDiscordId = + from.startsWith("discord:") && !from.includes(":channel:") && !from.includes(":group:") + ? from.slice("discord:".length) + : ""; + const directId = senderId || fromDiscordId; + return directId && directId === match[2] ? `${match[1]}discord:direct:${match[2]}` : normalized; +}