diff --git a/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts b/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts index da11d64fd22..a5f6f46896f 100644 --- a/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts +++ b/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts @@ -710,7 +710,7 @@ describe("thread binding lifecycle", () => { vi.setSystemTime(touchedAt); manager.touchThread({ threadId: "thread-1" }); - __testing.resetThreadBindingsForTests(); + __testing.resetThreadBindingsForTests({ clearStore: false }); const reloaded = createTestThreadBindingManager({ accountId: "default", persist: true, @@ -1384,7 +1384,7 @@ describe("thread binding lifecycle", () => { sessionKey: "agent:codex:acp:uncertain", storeSessionKey: "agent:codex:acp:uncertain", cfg: EMPTY_DISCORD_TEST_CONFIG, - storePath: "/tmp/mock-sessions.json", + storePath: "/tmp/openclaw-agent.sqlite", storeReadFailed: true, entry: undefined, acp: undefined, @@ -1741,45 +1741,32 @@ describe("thread binding lifecycle", () => { process.env.OPENCLAW_STATE_DIR = stateDir; try { __testing.resetThreadBindingsForTests(); - const bindingsPath = __testing.resolveThreadBindingsPath(); - fs.mkdirSync(path.dirname(bindingsPath), { recursive: true }); const boundAt = Date.now() - 10_000; const expiresAt = boundAt + 60_000; - fs.writeFileSync( - bindingsPath, - JSON.stringify( - { - version: 1, - bindings: { - "thread-legacy-active": { - accountId: "default", - channelId: "parent-1", - threadId: "thread-legacy-active", - targetKind: "subagent", - targetSessionKey: "agent:main:subagent:legacy-active", - agentId: "main", - boundBy: "system", - boundAt, - expiresAt, - }, - "thread-legacy-disabled": { - accountId: "default", - channelId: "parent-1", - threadId: "thread-legacy-disabled", - targetKind: "subagent", - targetSessionKey: "agent:main:subagent:legacy-disabled", - agentId: "main", - boundBy: "system", - boundAt, - expiresAt: 0, - }, - }, - }, - null, - 2, - ), - "utf-8", - ); + __testing.seedThreadBindingStoreForTests("default:thread-legacy-active", { + accountId: "default", + channelId: "parent-1", + threadId: "thread-legacy-active", + targetKind: "subagent", + targetSessionKey: "agent:main:subagent:legacy-active", + agentId: "main", + boundBy: "system", + boundAt, + lastActivityAt: boundAt, + expiresAt, + }); + __testing.seedThreadBindingStoreForTests("default:thread-legacy-disabled", { + accountId: "default", + channelId: "parent-1", + threadId: "thread-legacy-disabled", + targetKind: "subagent", + targetSessionKey: "agent:main:subagent:legacy-disabled", + agentId: "main", + boundBy: "system", + boundAt, + lastActivityAt: boundAt, + expiresAt: 0, + }); const manager = createTestThreadBindingManager({ accountId: "default", @@ -1843,45 +1830,27 @@ describe("thread binding lifecycle", () => { process.env.OPENCLAW_STATE_DIR = stateDir; try { __testing.resetThreadBindingsForTests(); - const bindingsPath = __testing.resolveThreadBindingsPath(); - fs.mkdirSync(path.dirname(bindingsPath), { recursive: true }); const now = Date.now(); - fs.writeFileSync( - bindingsPath, - JSON.stringify( - { - version: 1, - bindings: { - "thread-1": { - accountId: "default", - channelId: "parent-1", - threadId: "thread-1", - targetKind: "subagent", - targetSessionKey: "agent:main:subagent:child", - agentId: "main", - boundBy: "system", - boundAt: now, - lastActivityAt: now, - idleTimeoutMs: 60_000, - maxAgeMs: 0, - }, - }, - }, - null, - 2, - ), - "utf-8", - ); + __testing.seedThreadBindingStoreForTests("default:thread-1", { + accountId: "default", + channelId: "parent-1", + threadId: "thread-1", + targetKind: "subagent", + targetSessionKey: "agent:main:subagent:child", + agentId: "main", + boundBy: "system", + boundAt: now, + lastActivityAt: now, + idleTimeoutMs: 60_000, + maxAgeMs: 0, + }); const removed = unbindThreadBindingsBySessionKey({ targetSessionKey: "agent:main:subagent:child", }); expect(removed).toHaveLength(1); - const payload = JSON.parse(fs.readFileSync(bindingsPath, "utf-8")) as { - bindings?: Record; - }; - expect(Object.keys(payload.bindings ?? {})).toStrictEqual([]); + expect(Object.keys(__testing.readThreadBindingStoreForTests())).toStrictEqual([]); } finally { __testing.resetThreadBindingsForTests(); if (previousStateDir === undefined) { diff --git a/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test-support.ts b/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test-support.ts index 4a119cfbd47..a4ae2aea53f 100644 --- a/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test-support.ts +++ b/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test-support.ts @@ -156,7 +156,6 @@ describe("Feishu ACP-init failure lifecycle", () => { finalizeInboundContextMock, dispatchReplyFromConfigMock, withReplyDispatcherMock, - storePath: "/tmp/feishu-acp-failure-sessions.json", }); }); diff --git a/extensions/feishu/src/monitor.bot-menu.lifecycle.test-support.ts b/extensions/feishu/src/monitor.bot-menu.lifecycle.test-support.ts index d0f6998f59e..96470e9dc34 100644 --- a/extensions/feishu/src/monitor.bot-menu.lifecycle.test-support.ts +++ b/extensions/feishu/src/monitor.bot-menu.lifecycle.test-support.ts @@ -124,7 +124,6 @@ describe("Feishu bot-menu lifecycle", () => { finalizeInboundContextMock, dispatchReplyFromConfigMock, withReplyDispatcherMock, - storePath: "/tmp/feishu-bot-menu-sessions.json", }); }); diff --git a/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test-support.ts b/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test-support.ts index 75539db2a16..cde36956ddc 100644 --- a/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +++ b/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test-support.ts @@ -167,7 +167,6 @@ describe("Feishu broadcast reply-once lifecycle", () => { finalizeInboundContextMock, dispatchReplyFromConfigMock, withReplyDispatcherMock, - storePath: "/tmp/feishu-broadcast-sessions.json", }); }); diff --git a/extensions/feishu/src/monitor.card-action.lifecycle.test-support.ts b/extensions/feishu/src/monitor.card-action.lifecycle.test-support.ts index 04fcf56df70..4d906c97e3c 100644 --- a/extensions/feishu/src/monitor.card-action.lifecycle.test-support.ts +++ b/extensions/feishu/src/monitor.card-action.lifecycle.test-support.ts @@ -152,7 +152,6 @@ describe("Feishu card-action lifecycle", () => { finalizeInboundContextMock, dispatchReplyFromConfigMock, withReplyDispatcherMock, - storePath: "/tmp/feishu-card-action-sessions.json", }); }); diff --git a/extensions/feishu/src/test-support/lifecycle-test-support.ts b/extensions/feishu/src/test-support/lifecycle-test-support.ts index 76ff156c414..7af80600d83 100644 --- a/extensions/feishu/src/test-support/lifecycle-test-support.ts +++ b/extensions/feishu/src/test-support/lifecycle-test-support.ts @@ -97,7 +97,6 @@ function installFeishuLifecycleRuntime(params: { finalizeInboundContext: PluginRuntime["channel"]["reply"]["finalizeInboundContext"]; dispatchReplyFromConfig: PluginRuntime["channel"]["reply"]["dispatchReplyFromConfig"]; withReplyDispatcher: PluginRuntime["channel"]["reply"]["withReplyDispatcher"]; - resolveStorePath: PluginRuntime["channel"]["session"]["resolveStorePath"]; hasControlCommand?: PluginRuntime["channel"]["text"]["hasControlCommand"]; shouldComputeCommandAuthorized?: PluginRuntime["channel"]["commands"]["shouldComputeCommandAuthorized"]; resolveCommandAuthorizedFromAuthorizers?: PluginRuntime["channel"]["commands"]["resolveCommandAuthorizedFromAuthorizers"]; @@ -129,7 +128,6 @@ function installFeishuLifecycleRuntime(params: { }, session: { readSessionUpdatedAt: vi.fn(), - resolveStorePath: params.resolveStorePath, }, pairing: { readAllowFromStore: params.readAllowFromStore ?? vi.fn().mockResolvedValue([]), @@ -150,7 +148,6 @@ export function installFeishuLifecycleReplyRuntime(params: { finalizeInboundContextMock: unknown; dispatchReplyFromConfigMock: unknown; withReplyDispatcherMock: unknown; - storePath: string; }): PluginRuntime { return installFeishuLifecycleRuntime({ resolveAgentRoute: @@ -161,7 +158,6 @@ export function installFeishuLifecycleReplyRuntime(params: { params.dispatchReplyFromConfigMock as PluginRuntime["channel"]["reply"]["dispatchReplyFromConfig"], withReplyDispatcher: params.withReplyDispatcherMock as PluginRuntime["channel"]["reply"]["withReplyDispatcher"], - resolveStorePath: vi.fn(() => params.storePath), }); } diff --git a/extensions/line/src/bot-message-context.test.ts b/extensions/line/src/bot-message-context.test.ts index 9dd718f2e05..1684bb05bc2 100644 --- a/extensions/line/src/bot-message-context.test.ts +++ b/extensions/line/src/bot-message-context.test.ts @@ -28,7 +28,6 @@ const lineBindingsPlugin = { describe("buildLineMessageContext", () => { let tmpDir: string; - let storePath: string; let cfg: OpenClawConfig; const account: ResolvedLineAccount = { accountId: "default", @@ -83,8 +82,7 @@ describe("buildLineMessageContext", () => { ); sessionBindingTesting.resetSessionBindingAdaptersForTests(); tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-line-context-")); - storePath = path.join(tmpDir, "sessions.json"); - cfg = { session: { store: storePath } }; + cfg = { session: {} }; }); afterEach(async () => { @@ -249,7 +247,7 @@ describe("buildLineMessageContext", () => { it("group peer binding matches raw groupId without prefix (#21907)", async () => { const groupId = "Cc7e3bece1234567890abcdef"; // pragma: allowlist secret const bindingCfg: OpenClawConfig = { - session: { store: storePath }, + session: {}, agents: { list: [{ id: "main" }, { id: "line-group-agent" }], }, @@ -286,7 +284,7 @@ describe("buildLineMessageContext", () => { it("room peer binding matches raw roomId without prefix (#21907)", async () => { const roomId = "Rr1234567890abcdef"; const bindingCfg: OpenClawConfig = { - session: { store: storePath }, + session: {}, agents: { list: [{ id: "main" }, { id: "line-room-agent" }], }, diff --git a/extensions/telegram/src/bot-message-context.session-recreate.test-support.ts b/extensions/telegram/src/bot-message-context.session-recreate.test-support.ts index 9fa58581c84..87661a88bc1 100644 --- a/extensions/telegram/src/bot-message-context.session-recreate.test-support.ts +++ b/extensions/telegram/src/bot-message-context.session-recreate.test-support.ts @@ -58,7 +58,6 @@ describe("Telegram direct session recreation after delete", () => { 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: { diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index 0446018c518..fcab4d125e3 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -62,6 +62,10 @@ const resolveAndPersistSessionFile = vi.hoisted(() => sessionEntry: { sessionId: "s1", sessionFile: "/tmp/session.jsonl" }, })), ); +const sessionRows = vi.hoisted(() => ({ value: {} as Record> })); +const getSessionEntry = vi.hoisted(() => + vi.fn(({ sessionKey }: { sessionKey: string }) => sessionRows.value[sessionKey]), +); const generateTopicLabel = vi.hoisted(() => vi.fn()); const describeStickerImage = vi.hoisted(() => vi.fn(async () => null)); const loadModelCatalog = vi.hoisted(() => vi.fn(async () => ({}))); @@ -76,11 +80,6 @@ const getAgentScopedMediaLocalRoots = vi.hoisted(() => ); const resolveChunkMode = vi.hoisted(() => vi.fn(() => undefined)); const resolveMarkdownTableMode = vi.hoisted(() => vi.fn(() => "preserve")); -const resolveSessionStoreEntry = vi.hoisted(() => - vi.fn(({ store, sessionKey }: { store: Record; sessionKey: string }) => ({ - existing: store[sessionKey], - })), -); vi.mock("./draft-stream.js", () => ({ createTelegramDraftStream, @@ -129,11 +128,10 @@ vi.mock("./bot-message-dispatch.runtime.js", () => ({ getAgentScopedMediaLocalRoots, loadSessionStore, resolveAndPersistSessionFile, + getSessionEntry, resolveAutoTopicLabelConfig: resolveAutoTopicLabelConfigRuntime, resolveChunkMode, resolveMarkdownTableMode, - resolveSessionStoreEntry, - resolveStorePath, })); vi.mock("./bot-message-dispatch.agent.runtime.js", () => ({ @@ -158,8 +156,9 @@ let resetTelegramReplyFenceForTests: typeof import("./bot-message-dispatch.js"). const telegramDepsForTest: TelegramBotDeps = { getRuntimeConfig: loadConfig as TelegramBotDeps["getRuntimeConfig"], - resolveStorePath: resolveStorePath as TelegramBotDeps["resolveStorePath"], - loadSessionStore: loadSessionStore as TelegramBotDeps["loadSessionStore"], + getSessionEntry: getSessionEntry as unknown as TelegramBotDeps["getSessionEntry"], + listSessionEntries: vi.fn(() => []) as TelegramBotDeps["listSessionEntries"], + patchSessionEntry: vi.fn(async () => null) as TelegramBotDeps["patchSessionEntry"], readChannelAllowFromStore: readChannelAllowFromStore as TelegramBotDeps["readChannelAllowFromStore"], upsertChannelPairingRequest: @@ -219,11 +218,15 @@ describe("dispatchTelegramMessage draft streaming", () => { loadSessionStore.mockReset(); resolveStorePath.mockReset(); resolveAndPersistSessionFile.mockReset(); + sessionRows.value = {}; + getSessionEntry.mockReset(); + getSessionEntry.mockImplementation( + ({ sessionKey }: { sessionKey: string }) => sessionRows.value[sessionKey], + ); generateTopicLabel.mockReset(); getAgentScopedMediaLocalRoots.mockClear(); resolveChunkMode.mockClear(); resolveMarkdownTableMode.mockClear(); - resolveSessionStoreEntry.mockClear(); describeStickerImage.mockReset(); loadModelCatalog.mockReset(); findModelInCatalog.mockReset(); @@ -274,6 +277,7 @@ describe("dispatchTelegramMessage draft streaming", () => { sessionEntry: { sessionId: "s1", sessionFile: "/tmp/session.jsonl" }, }); loadSessionStore.mockReturnValue({}); + sessionRows.value = {}; generateTopicLabel.mockResolvedValue("Topic label"); describeStickerImage.mockResolvedValue(null); loadModelCatalog.mockResolvedValue({}); @@ -326,7 +330,7 @@ describe("dispatchTelegramMessage draft streaming", () => { removeAckAfterReply: false, } as unknown as TelegramMessageContext; base.turn = { - storePath: "/tmp/openclaw/telegram-sessions.json", + storePath: "/tmp/openclaw/telegram-agent.sqlite", recordInboundSession: vi.fn(async () => undefined), record: { onRecordError: vi.fn(), @@ -426,9 +430,9 @@ describe("dispatchTelegramMessage draft streaming", () => { } function createReasoningStreamContext(): TelegramMessageContext { - loadSessionStore.mockReturnValue({ + sessionRows.value = { s1: { reasoningLevel: "stream" }, - }); + }; return createContext({ ctxPayload: { SessionKey: "s1" } as unknown as TelegramMessageContext["ctxPayload"], }); @@ -1615,7 +1619,7 @@ describe("dispatchTelegramMessage draft streaming", () => { it("uses resolved DM config for auto-topic-label overrides", async () => { dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: true }); - loadSessionStore.mockReturnValue({ s1: {} }); + sessionRows.value = { s1: {} }; const bot = createBot(); await dispatchWithContext({