diff --git a/src/config/sessions/delivery-info.test.ts b/src/config/sessions/delivery-info.test.ts index e2c6897d87..23717338ea 100644 --- a/src/config/sessions/delivery-info.test.ts +++ b/src/config/sessions/delivery-info.test.ts @@ -17,7 +17,7 @@ vi.mock("./store.js", () => ({ loadSessionStore: () => storeState.store, })); -import { extractDeliveryInfo } from "./delivery-info.js"; +import { extractDeliveryInfo, parseSessionThreadInfo } from "./delivery-info.js"; const buildEntry = (deliveryContext: SessionEntry["deliveryContext"]): SessionEntry => ({ sessionId: "session-1", @@ -30,6 +30,25 @@ beforeEach(() => { }); describe("extractDeliveryInfo", () => { + it("parses base session and thread/topic ids", () => { + expect(parseSessionThreadInfo("agent:main:telegram:group:1:topic:55")).toEqual({ + baseSessionKey: "agent:main:telegram:group:1", + threadId: "55", + }); + expect(parseSessionThreadInfo("agent:main:slack:channel:C1:thread:123.456")).toEqual({ + baseSessionKey: "agent:main:slack:channel:C1", + threadId: "123.456", + }); + expect(parseSessionThreadInfo("agent:main:telegram:dm:user-1")).toEqual({ + baseSessionKey: "agent:main:telegram:dm:user-1", + threadId: undefined, + }); + expect(parseSessionThreadInfo(undefined)).toEqual({ + baseSessionKey: undefined, + threadId: undefined, + }); + }); + it("returns deliveryContext for direct session keys", () => { const sessionKey = "agent:main:webchat:dm:user-123"; storeState.store[sessionKey] = buildEntry({ diff --git a/src/config/sessions/delivery-info.ts b/src/config/sessions/delivery-info.ts index 006f1db449..b8c5a637c1 100644 --- a/src/config/sessions/delivery-info.ts +++ b/src/config/sessions/delivery-info.ts @@ -6,12 +6,12 @@ import { loadSessionStore } from "./store.js"; * Extract deliveryContext and threadId from a sessionKey. * Supports both :thread: (most channels) and :topic: (Telegram). */ -export function extractDeliveryInfo(sessionKey: string | undefined): { - deliveryContext: { channel?: string; to?: string; accountId?: string } | undefined; +export function parseSessionThreadInfo(sessionKey: string | undefined): { + baseSessionKey: string | undefined; threadId: string | undefined; } { if (!sessionKey) { - return { deliveryContext: undefined, threadId: undefined }; + return { baseSessionKey: undefined, threadId: undefined }; } const topicIndex = sessionKey.lastIndexOf(":topic:"); const threadIndex = sessionKey.lastIndexOf(":thread:"); @@ -22,6 +22,17 @@ export function extractDeliveryInfo(sessionKey: string | undefined): { const threadIdRaw = markerIndex === -1 ? undefined : sessionKey.slice(markerIndex + marker.length); const threadId = threadIdRaw?.trim() || undefined; + return { baseSessionKey, threadId }; +} + +export function extractDeliveryInfo(sessionKey: string | undefined): { + deliveryContext: { channel?: string; to?: string; accountId?: string } | undefined; + threadId: string | undefined; +} { + const { baseSessionKey, threadId } = parseSessionThreadInfo(sessionKey); + if (!sessionKey || !baseSessionKey) { + return { deliveryContext: undefined, threadId }; + } let deliveryContext: { channel?: string; to?: string; accountId?: string } | undefined; try { @@ -29,7 +40,7 @@ export function extractDeliveryInfo(sessionKey: string | undefined): { const storePath = resolveStorePath(cfg.session?.store); const store = loadSessionStore(storePath); let entry = store[sessionKey]; - if (!entry?.deliveryContext && markerIndex !== -1 && baseSessionKey) { + if (!entry?.deliveryContext && baseSessionKey !== sessionKey) { entry = store[baseSessionKey]; } if (entry?.deliveryContext) { diff --git a/src/gateway/server-restart-sentinel.ts b/src/gateway/server-restart-sentinel.ts index 32bff530b6..44e246282a 100644 --- a/src/gateway/server-restart-sentinel.ts +++ b/src/gateway/server-restart-sentinel.ts @@ -3,6 +3,7 @@ import { resolveAnnounceTargetFromKey } from "../agents/tools/sessions-send-help import { normalizeChannelId } from "../channels/plugins/index.js"; import type { CliDeps } from "../cli/deps.js"; import { resolveMainSessionKeyFromConfig } from "../config/sessions.js"; +import { parseSessionThreadInfo } from "../config/sessions/delivery-info.js"; import { deliverOutboundPayloads } from "../infra/outbound/deliver.js"; import { resolveOutboundTarget } from "../infra/outbound/targets.js"; import { @@ -30,17 +31,7 @@ export async function scheduleRestartSentinelWake(_params: { deps: CliDeps }) { return; } - // Extract topic/thread ID from sessionKey (supports both :topic: and :thread:) - // Telegram uses :topic:, other platforms use :thread: - const topicIndex = sessionKey.lastIndexOf(":topic:"); - const threadIndex = sessionKey.lastIndexOf(":thread:"); - const markerIndex = Math.max(topicIndex, threadIndex); - const marker = topicIndex > threadIndex ? ":topic:" : ":thread:"; - - const baseSessionKey = markerIndex === -1 ? sessionKey : sessionKey.slice(0, markerIndex); - const threadIdRaw = - markerIndex === -1 ? undefined : sessionKey.slice(markerIndex + marker.length); - const sessionThreadId = threadIdRaw?.trim() || undefined; + const { baseSessionKey, threadId: sessionThreadId } = parseSessionThreadInfo(sessionKey); const { cfg, entry } = loadSessionEntry(sessionKey); const parsedTarget = resolveAnnounceTargetFromKey(baseSessionKey); @@ -49,7 +40,7 @@ export async function scheduleRestartSentinelWake(_params: { deps: CliDeps }) { // Handles race condition where store wasn't flushed before restart const sentinelContext = payload.deliveryContext; let sessionDeliveryContext = deliveryContextFromSession(entry); - if (!sessionDeliveryContext && markerIndex !== -1 && baseSessionKey) { + if (!sessionDeliveryContext && baseSessionKey && baseSessionKey !== sessionKey) { const { entry: baseEntry } = loadSessionEntry(baseSessionKey); sessionDeliveryContext = deliveryContextFromSession(baseEntry); }