diff --git a/extensions/telegram/src/bot-message-context.session-recreate.test.ts b/extensions/telegram/src/bot-message-context.session-recreate.test.ts new file mode 100644 index 00000000000..85880dfbab5 --- /dev/null +++ b/extensions/telegram/src/bot-message-context.session-recreate.test.ts @@ -0,0 +1,99 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { + clearRuntimeConfigSnapshot, + setRuntimeConfigSnapshot, +} from "openclaw/plugin-sdk/config-runtime"; +import { + clearSessionStoreCacheForTest, + loadSessionStore, + updateSessionStore, +} from "openclaw/plugin-sdk/config-runtime"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { createSuiteTempRootTracker } from "../../../src/test-helpers/temp-dir.js"; +import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js"; + +const TELEGRAM_DIRECT_KEY = "agent:main:telegram:direct:7463849194"; + +describe("Telegram direct session recreation after delete", () => { + const suiteRootTracker = createSuiteTempRootTracker({ + prefix: "openclaw-telegram-context-recreate-", + }); + + beforeAll(async () => { + await suiteRootTracker.setup(); + }); + + afterEach(() => { + clearRuntimeConfigSnapshot(); + clearSessionStoreCacheForTest(); + }); + + afterAll(async () => { + await suiteRootTracker.cleanup(); + }); + + it("records a deleted direct session again when the next DM is processed", async () => { + const tempDir = await suiteRootTracker.make("direct"); + const storePath = path.join(tempDir, "sessions.json"); + const cfg = { + agents: { + defaults: { + model: "openai/gpt-5.4", + workspace: "/tmp/openclaw", + }, + }, + channels: { telegram: {} }, + messages: { groupChat: { mentionPatterns: [] } }, + session: { + dmScope: "per-channel-peer" as const, + store: storePath, + }, + }; + setRuntimeConfigSnapshot(cfg as never); + await fs.writeFile( + storePath, + JSON.stringify( + { + [TELEGRAM_DIRECT_KEY]: { + sessionId: "old-session", + updatedAt: 1_700_000_000_000, + chatType: "direct", + channel: "telegram", + }, + }, + null, + 2, + ), + "utf-8", + ); + await updateSessionStore(storePath, (store) => { + delete store[TELEGRAM_DIRECT_KEY]; + }); + + const context = await buildTelegramMessageContextForTest({ + cfg, + message: { + message_id: 2, + chat: { id: 7463849194, type: "private" }, + date: 1_700_000_001, + text: "hello again", + from: { id: 7463849194, first_name: "Alice" }, + }, + sessionRuntime: null, + }); + + const store = loadSessionStore(storePath, { skipCache: true }); + expect(context?.ctxPayload?.SessionKey).toBe(TELEGRAM_DIRECT_KEY); + expect(store[TELEGRAM_DIRECT_KEY]).toEqual( + expect.objectContaining({ + lastChannel: "telegram", + lastTo: "telegram:7463849194", + origin: expect.objectContaining({ + provider: "telegram", + chatType: "direct", + }), + }), + ); + }); +}); diff --git a/src/gateway/session-utils.telegram-recreate.test.ts b/src/gateway/session-utils.telegram-recreate.test.ts new file mode 100644 index 00000000000..423376aa929 --- /dev/null +++ b/src/gateway/session-utils.telegram-recreate.test.ts @@ -0,0 +1,126 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import type { MsgContext } from "../auto-reply/templating.js"; +import { + clearSessionStoreCacheForTest, + loadSessionStore, + recordSessionMetaFromInbound, + updateLastRoute, + updateSessionStore, +} from "../config/sessions.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js"; +import { listSessionsFromStore } from "./session-utils.js"; + +const TELEGRAM_DIRECT_KEY = "agent:main:telegram:direct:7463849194"; + +const cfg = { + agents: { + defaults: { + model: "openai/gpt-5.4", + workspace: "/tmp/openclaw", + }, + }, + session: { + dmScope: "per-channel-peer", + }, +} satisfies Partial as OpenClawConfig; + +function createTelegramDirectContext(): MsgContext { + return { + Provider: "telegram", + Surface: "telegram", + OriginatingChannel: "telegram", + OriginatingTo: "telegram:7463849194", + AccountId: "default", + ChatType: "direct", + ConversationLabel: "Alice id:7463849194", + From: "telegram:7463849194", + To: "telegram:7463849194", + SenderId: "7463849194", + SenderName: "Alice", + SessionKey: TELEGRAM_DIRECT_KEY, + }; +} + +describe("Telegram direct session recreation after delete", () => { + const suiteRootTracker = createSuiteTempRootTracker({ + prefix: "openclaw-telegram-session-recreate-", + }); + let tempDir = ""; + let storePath = ""; + + beforeAll(async () => { + await suiteRootTracker.setup(); + }); + + afterEach(() => { + clearSessionStoreCacheForTest(); + }); + + afterAll(async () => { + await suiteRootTracker.cleanup(); + }); + + it("surfaces a deleted Telegram direct session again after the next inbound message", async () => { + tempDir = await suiteRootTracker.make("direct"); + storePath = path.join(tempDir, "sessions.json"); + await fs.writeFile( + storePath, + JSON.stringify( + { + [TELEGRAM_DIRECT_KEY]: { + sessionId: "old-session", + updatedAt: 1_700_000_000_000, + chatType: "direct", + channel: "telegram", + }, + }, + null, + 2, + ), + "utf-8", + ); + + await updateSessionStore(storePath, (store) => { + delete store[TELEGRAM_DIRECT_KEY]; + }); + expect(loadSessionStore(storePath, { skipCache: true })[TELEGRAM_DIRECT_KEY]).toBeUndefined(); + + const ctx = createTelegramDirectContext(); + await recordSessionMetaFromInbound({ + storePath, + sessionKey: TELEGRAM_DIRECT_KEY, + ctx, + }); + await updateLastRoute({ + storePath, + sessionKey: TELEGRAM_DIRECT_KEY, + channel: "telegram", + to: "telegram:7463849194", + accountId: "default", + ctx, + }); + + const store = loadSessionStore(storePath, { skipCache: true }); + const listed = listSessionsFromStore({ + cfg, + storePath, + store, + opts: {}, + }); + + expect(store[TELEGRAM_DIRECT_KEY]).toEqual( + expect.objectContaining({ + lastChannel: "telegram", + lastTo: "telegram:7463849194", + origin: expect.objectContaining({ + chatType: "direct", + provider: "telegram", + }), + }), + ); + expect(listed.sessions.map((session) => session.key)).toContain(TELEGRAM_DIRECT_KEY); + }); +});