From 9ac91cdf0bfd46c6b4aef41ece86676878df4255 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 6 May 2026 19:07:00 +0100 Subject: [PATCH] refactor: simplify session store loads --- extensions/active-memory/index.ts | 6 +- .../monitor/native-command-model-picker-ui.ts | 4 +- .../native-command.model-picker.test.ts | 4 +- extensions/feishu/src/reasoning-preview.ts | 2 +- .../mattermost/src/mattermost/model-picker.ts | 2 +- .../mattermost/src/mattermost/monitor.ts | 1 - ...e-context.session-recreate.test-support.ts | 2 +- .../telegram/src/bot-message-dispatch.ts | 8 +- .../bot.create-telegram-bot.test-harness.ts | 4 +- extensions/telegram/src/bot.test.ts | 14 ++-- .../monitor/group-activation.test.ts | 2 +- src/agents/command/session-store.test.ts | 14 ++-- .../agent-runner.misc.runreplyagent.test.ts | 8 +- .../reply/commands-export-session.test.ts | 4 +- src/auto-reply/reply/followup-runner.test.ts | 4 +- src/commands/agents.delete.test.ts | 2 +- src/config/sessions.test.ts | 2 +- src/config/sessions/sessions.test.ts | 12 +-- .../sessions/store-backend.sqlite.test.ts | 8 +- src/config/sessions/store-load.ts | 10 +-- .../store.session-key-normalization.test.ts | 8 +- .../sessions/store.skills-stripping.test.ts | 6 +- src/gateway/boot.test.ts | 4 +- src/gateway/session-utils.subagent.test.ts | 80 ++++++------------- .../session-utils.telegram-recreate.test.ts | 4 +- .../contracts/host-hooks.contract.test.ts | 10 +-- .../run-context-lifecycle.contract.test.ts | 2 +- .../session-entry-projection.contract.test.ts | 46 +++++------ 28 files changed, 110 insertions(+), 163 deletions(-) diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index 8bb40fcc138..02dc88bb6f6 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -546,7 +546,7 @@ function resolveCanonicalSessionKeyFromSessionId(params: { agentId: params.agentId, }, ); - const store = params.api.runtime.agent.session.loadSessionStore(storePath, { clone: false }); + const store = params.api.runtime.agent.session.loadSessionStore(storePath); let bestMatch: | { sessionKey: string; @@ -673,7 +673,7 @@ function resolveRecallRunChannelContext(params: { agentId: params.agentId, }, ); - const store = params.api.runtime.agent.session.loadSessionStore(storePath, { clone: false }); + const store = params.api.runtime.agent.session.loadSessionStore(storePath); const sessionEntry = resolveSessionStoreEntry({ store, sessionKey: resolvedSessionKey, @@ -1554,7 +1554,7 @@ async function persistPluginStatusLines(params: { agentId ? { agentId } : undefined, ); if (!params.statusLine && !debugLine) { - const store = params.api.runtime.agent.session.loadSessionStore(storePath, { clone: false }); + const store = params.api.runtime.agent.session.loadSessionStore(storePath); const existingEntry = resolveSessionStoreEntry({ store, sessionKey }).existing; const hasActiveMemoryEntry = Array.isArray(existingEntry?.pluginDebugEntries) ? existingEntry.pluginDebugEntries.some((entry) => entry?.pluginId === "active-memory") diff --git a/extensions/discord/src/monitor/native-command-model-picker-ui.ts b/extensions/discord/src/monitor/native-command-model-picker-ui.ts index e991dbf60e3..f4ff31e9602 100644 --- a/extensions/discord/src/monitor/native-command-model-picker-ui.ts +++ b/extensions/discord/src/monitor/native-command-model-picker-ui.ts @@ -241,7 +241,7 @@ export function resolveDiscordModelPickerCurrentModel(params: { const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.route.agentId, }); - const sessionStore = loadSessionStore(storePath, { skipCache: true }); + const sessionStore = loadSessionStore(storePath); const sessionEntry = sessionStore[params.route.sessionKey]; const override = resolveStoredModelOverride({ sessionEntry, @@ -270,7 +270,7 @@ export function resolveDiscordModelPickerCurrentRuntime(params: { const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.route.agentId, }); - const sessionStore = loadSessionStore(storePath, { skipCache: true }); + const sessionStore = loadSessionStore(storePath); const sessionRuntime = normalizeOptionalString( sessionStore[params.route.sessionKey]?.agentRuntimeOverride, ); diff --git a/extensions/discord/src/monitor/native-command.model-picker.test.ts b/extensions/discord/src/monitor/native-command.model-picker.test.ts index 00b7ce01187..ff2d43b39f6 100644 --- a/extensions/discord/src/monitor/native-command.model-picker.test.ts +++ b/extensions/discord/src/monitor/native-command.model-picker.test.ts @@ -672,7 +672,7 @@ describe("Discord model picker interactions", () => { mi: "1", }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(store["agent:worker:subagent:bound"]?.providerOverride).toBe("lmstudio"); expect(store["agent:worker:subagent:bound"]?.modelOverride).toBe( "unsloth/gemma-4-26b-a4b-it@iq4_xs", @@ -723,7 +723,7 @@ describe("Discord model picker interactions", () => { createModelsViewSubmitData(), ); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(store["agent:worker:subagent:bound"]?.providerOverride).toBeUndefined(); expect(store["agent:worker:subagent:bound"]?.modelOverride).toBeUndefined(); expect(JSON.stringify(submitInteraction.followUp.mock.calls[0]?.[0])).toContain( diff --git a/extensions/feishu/src/reasoning-preview.ts b/extensions/feishu/src/reasoning-preview.ts index 93ecccc4591..e219a1d38b7 100644 --- a/extensions/feishu/src/reasoning-preview.ts +++ b/extensions/feishu/src/reasoning-preview.ts @@ -15,7 +15,7 @@ export function resolveFeishuReasoningPreviewEnabled(params: { } try { - const store = loadSessionStore(params.storePath, { skipCache: true }); + const store = loadSessionStore(params.storePath); const level = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey }).existing ?.reasoningLevel; if (level === "on" || level === "stream" || level === "off") { diff --git a/extensions/mattermost/src/mattermost/model-picker.ts b/extensions/mattermost/src/mattermost/model-picker.ts index 20d2206f564..d19c9b40648 100644 --- a/extensions/mattermost/src/mattermost/model-picker.ts +++ b/extensions/mattermost/src/mattermost/model-picker.ts @@ -244,7 +244,7 @@ export function resolveMattermostModelPickerCurrentModel(params: { agentId: params.route.agentId, }); const sessionStore = params.skipCache - ? loadSessionStore(storePath, { skipCache: true }) + ? loadSessionStore(storePath) : loadSessionStore(storePath); const sessionEntry = sessionStore[params.route.sessionKey]; const override = resolveStoredModelOverride({ diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 61494e8b226..c382db9f8d3 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -1199,7 +1199,6 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} cfg, route: modelSessionRoute, data, - skipCache: true, }); const view = renderMattermostModelsPickerView({ ownerUserId: pickerState.ownerUserId, 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 b2f1748b05f..2ab161d807c 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 @@ -119,7 +119,7 @@ describe("Telegram direct session recreation after delete", () => { onRecordError: context.turn.record.onRecordError, }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(context?.ctxPayload?.SessionKey).toBe(TELEGRAM_DIRECT_KEY); expect(store[TELEGRAM_DIRECT_KEY]).toEqual( expect.objectContaining({ diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index d27460123a2..0e320cd6f6b 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -221,9 +221,7 @@ function resolveTelegramReasoningLevel(params: { } try { const storePath = telegramDeps.resolveStorePath(cfg.session?.store, { agentId }); - const store = (telegramDeps.loadSessionStore ?? loadSessionStore)(storePath, { - skipCache: true, - }); + const store = (telegramDeps.loadSessionStore ?? loadSessionStore)(storePath); const entry = resolveSessionStoreEntry({ store, sessionKey }).existing; const level = entry?.reasoningLevel; if (level === "on" || level === "stream" || level === "off") { @@ -950,9 +948,7 @@ export const dispatchTelegramMessage = async ({ const storePath = telegramDeps.resolveStorePath(cfg.session?.store, { agentId: route.agentId, }); - const store = (telegramDeps.loadSessionStore ?? loadSessionStore)(storePath, { - skipCache: true, - }); + const store = (telegramDeps.loadSessionStore ?? loadSessionStore)(storePath); const sessionKey = ctxPayload.SessionKey; if (sessionKey) { const entry = resolveSessionStoreEntry({ store, sessionKey }).existing; diff --git a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts index d98a784f8d1..389d516feb5 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts @@ -55,9 +55,7 @@ const { getRuntimeConfig, loadSessionStoreMock, resolveStorePathMock, sessionSto sessionStoreEntries: { value: SessionStore }; } => ({ getRuntimeConfig: vi.fn(() => ({})), - loadSessionStoreMock: vi.fn( - (_storePath, _opts) => sessionStoreEntries.value, - ), + loadSessionStoreMock: vi.fn(() => sessionStoreEntries.value), resolveStorePathMock: vi.fn( (storePath?: string) => storePath ?? sessionStorePath, ), diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index b31f9584c3e..3ca39a8485c 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -226,7 +226,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).not.toHaveBeenCalled(); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({}); + expect(loadSessionStore(storePath)).toEqual({}); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-authz-bypass-1"); } finally { await rm(storePath, { force: true }); @@ -299,7 +299,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).not.toHaveBeenCalled(); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({}); + expect(loadSessionStore(storePath)).toEqual({}); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-group-model-authz-1"); } finally { await rm(storePath, { force: true }); @@ -381,7 +381,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).not.toHaveBeenCalled(); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({}); + expect(loadSessionStore(storePath)).toEqual({}); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-group-model-authz-runtime-1"); } finally { loadConfig.mockReset(); @@ -1104,7 +1104,7 @@ describe("createTelegramBot", () => { "Session selection cleared. Runtime unchanged. New replies use the agent's configured default.", ); - const entry = Object.values(loadSessionStore(storePath, { skipCache: true }))[0]; + const entry = Object.values(loadSessionStore(storePath))[0]; expect(entry?.providerOverride).toBeUndefined(); expect(entry?.modelOverride).toBeUndefined(); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-compact-1"); @@ -1255,7 +1255,7 @@ describe("createTelegramBot", () => { "Session selection cleared. Runtime unchanged. New replies use the agent's configured default.", ); - const entry = Object.values(loadSessionStore(storePath, { skipCache: true }))[0]; + const entry = Object.values(loadSessionStore(storePath))[0]; expect(entry?.providerOverride).toBeUndefined(); expect(entry?.modelOverride).toBeUndefined(); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-default-1"); @@ -1330,7 +1330,7 @@ describe("createTelegramBot", () => { expect.objectContaining({ parse_mode: "HTML" }), ); - const entry = Object.values(loadSessionStore(storePath, { skipCache: true }))[0]; + const entry = Object.values(loadSessionStore(storePath))[0]; expect(entry?.providerOverride).toBe("openai"); expect(entry?.modelOverride).toBe("gpt-5.4"); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-html-1"); @@ -1420,7 +1420,7 @@ describe("createTelegramBot", () => { // Override must be persisted (not cleared) because openai/gpt-5.4 is // NOT the default in the fresh config. - const entry = Object.values(loadSessionStore(storePath, { skipCache: true }))[0]; + const entry = Object.values(loadSessionStore(storePath))[0]; expect(entry?.providerOverride).toBe("openai"); expect(entry?.modelOverride).toBe("gpt-5.4"); } finally { diff --git a/extensions/whatsapp/src/auto-reply/monitor/group-activation.test.ts b/extensions/whatsapp/src/auto-reply/monitor/group-activation.test.ts index 1d4597d014a..a38a0d95fb9 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/group-activation.test.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/group-activation.test.ts @@ -52,7 +52,7 @@ const expectWorkGroupActivationEntry = async ( assertEntry?: (entry: SessionStoreEntry | undefined) => void, ) => { await vi.waitFor(() => { - const scopedEntry = loadSessionStore(storePath, { skipCache: true })[WORK_GROUP_SESSION_KEY]; + const scopedEntry = loadSessionStore(storePath)[WORK_GROUP_SESSION_KEY]; expect(scopedEntry?.groupActivation).toBe("always"); assertEntry?.(scopedEntry); }); diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts index 7d93eaf4baf..9660481641e 100644 --- a/src/agents/command/session-store.test.ts +++ b/src/agents/command/session-store.test.ts @@ -382,7 +382,7 @@ describe("updateSessionStoreAfterAgentRun", () => { } as never, }); - const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const persisted = loadSessionStore(storePath)[sessionKey]; expect(persisted?.acp).toMatchObject({ backend: "acpx", agent: "codex", @@ -438,7 +438,7 @@ describe("updateSessionStoreAfterAgentRun", () => { } as never, }); - const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const persisted = loadSessionStore(storePath)[sessionKey]; expect(persisted).toMatchObject({ status: "done", startedAt: 1_000, @@ -502,7 +502,7 @@ describe("updateSessionStoreAfterAgentRun", () => { } as never, }); - const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const persisted = loadSessionStore(storePath)[sessionKey]; expect(persisted?.systemPromptReport?.bootstrapTruncation?.warningSignaturesSeen).toEqual([ "sig-a", "sig-b", @@ -572,7 +572,7 @@ describe("updateSessionStoreAfterAgentRun", () => { authEpoch: "auth-epoch-1", }); - const persisted = loadSessionStore(storePath, { skipCache: true })[first.sessionKey!]; + const persisted = loadSessionStore(storePath)[first.sessionKey!]; expect(persisted?.cliSessionBindings?.["claude-cli"]).toEqual({ sessionId: "claude-cli-session-1", authEpoch: "auth-epoch-1", @@ -1225,7 +1225,7 @@ describe("clearCliSessionInStore", () => { expect(cleared?.claudeCliSessionId).toBeUndefined(); expect(sessionStore[sessionKey]).toEqual(cleared); - const persisted = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const persisted = loadSessionStore(storePath)[sessionKey]; expect(persisted?.cliSessionBindings?.["claude-cli"]).toBeUndefined(); expect(persisted?.cliSessionBindings?.["codex-cli"]).toEqual({ sessionId: "codex-session-1", @@ -1257,9 +1257,7 @@ describe("clearCliSessionInStore", () => { expect(cleared).toBeUndefined(); expect(sessionStore[existingKey]?.claudeCliSessionId).toBe("claude-session-1"); - expect( - loadSessionStore(storePath, { skipCache: true })[existingKey]?.claudeCliSessionId, - ).toBe("claude-session-1"); + expect(loadSessionStore(storePath)[existingKey]?.claudeCliSessionId).toBe("claude-session-1"); }); }); }); diff --git a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts index 465c3034a60..b1c3b345c3c 100644 --- a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts +++ b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts @@ -662,7 +662,7 @@ describe("runReplyAgent Active Memory inline debug", () => { ); runEmbeddedPiAgentMock.mockImplementationOnce(async () => { - const latest = loadSessionStore(storePath, { skipCache: true }); + const latest = loadSessionStore(storePath); latest[sessionKey] = { ...latest[sessionKey], pluginDebugEntries: [ @@ -774,7 +774,7 @@ describe("runReplyAgent Active Memory inline debug", () => { ); runEmbeddedPiAgentMock.mockImplementationOnce(async () => { - const latest = loadSessionStore(storePath, { skipCache: true }); + const latest = loadSessionStore(storePath); latest[sessionKey] = { ...latest[sessionKey], pluginDebugEntries: [ @@ -885,7 +885,7 @@ describe("runReplyAgent Active Memory inline debug", () => { ); runEmbeddedPiAgentMock.mockImplementationOnce(async () => { - const latest = loadSessionStore(storePath, { skipCache: true }); + const latest = loadSessionStore(storePath); latest[sessionKey] = { ...latest[sessionKey], pluginDebugEntries: [ @@ -1612,7 +1612,7 @@ describe("runReplyAgent Active Memory inline debug", () => { typingMode: "instant", }); - expect(loadSessionStoreSpy).not.toHaveBeenCalledWith(storePath, { skipCache: true }); + expect(loadSessionStoreSpy).not.toHaveBeenCalledWith(storePath); expect(result).toMatchObject({ text: "Normal reply" }); }); }); diff --git a/src/auto-reply/reply/commands-export-session.test.ts b/src/auto-reply/reply/commands-export-session.test.ts index 762d1a7b583..91e09c80042 100644 --- a/src/auto-reply/reply/commands-export-session.test.ts +++ b/src/auto-reply/reply/commands-export-session.test.ts @@ -202,9 +202,7 @@ describe("buildExportSessionReply", () => { }); expect(hoisted.resolveDefaultSessionStorePathMock).not.toHaveBeenCalled(); - expect(hoisted.loadSessionStoreMock).toHaveBeenCalledWith("/tmp/custom-store/sessions.json", { - skipCache: true, - }); + expect(hoisted.loadSessionStoreMock).toHaveBeenCalledWith("/tmp/custom-store/sessions.json"); expect(hoisted.resolveSessionFilePathOptionsMock).toHaveBeenCalledWith({ agentId: "target", storePath: "/tmp/custom-store/sessions.json", diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index 0fe4602165e..a39076215a3 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -218,7 +218,7 @@ async function persistRunSessionUsageForFollowupTest( return; } const registeredStore = FOLLOWUP_TEST_SESSION_STORES.get(storePath); - const store = registeredStore ?? loadSessionStore(storePath, { skipCache: true }); + const store = registeredStore ?? loadSessionStore(storePath); const entry = store[sessionKey]; if (!entry) { return; @@ -962,7 +962,7 @@ describe("createFollowupRunner compaction", () => { if (registeredStore) { registeredStore[params.sessionKey] = updatedEntry; } else { - const store = loadSessionStore(params.storePath, { skipCache: true }); + const store = loadSessionStore(params.storePath); store[params.sessionKey] = updatedEntry; await saveSessionStore(params.storePath, store); } diff --git a/src/commands/agents.delete.test.ts b/src/commands/agents.delete.test.ts index 57a9c2fe2e3..39854ce3547 100644 --- a/src/commands/agents.delete.test.ts +++ b/src/commands/agents.delete.test.ts @@ -68,7 +68,7 @@ function expectSessionStore( storePath: string, sessions: Record, ) { - expect(loadSessionStore(storePath, { skipCache: true })).toEqual(sessions); + expect(loadSessionStore(storePath)).toEqual(sessions); } function readJsonLogs(): Array> { diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index 4666009674f..003cd7201a6 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -879,7 +879,7 @@ describe("sessions", () => { parseSpy.mockRestore(); } - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(store[mainSessionKey]?.thinkingLevel).toBe("high"); }); diff --git a/src/config/sessions/sessions.test.ts b/src/config/sessions/sessions.test.ts index b339f69498f..6ae38383a7c 100644 --- a/src/config/sessions/sessions.test.ts +++ b/src/config/sessions/sessions.test.ts @@ -529,7 +529,7 @@ describe("resolveAndPersistSessionFile", () => { }, }; fs.writeFileSync(fixture.storePath(), JSON.stringify(store), "utf-8"); - const sessionStore = loadSessionStore(fixture.storePath(), { skipCache: true }); + const sessionStore = loadSessionStore(fixture.storePath()); const fallbackSessionFile = resolveSessionTranscriptPathInDir( sessionId, fixture.sessionsDir(), @@ -547,7 +547,7 @@ describe("resolveAndPersistSessionFile", () => { expect(result.sessionFile).toBe(fallbackSessionFile); - const saved = loadSessionStore(fixture.storePath(), { skipCache: true }); + const saved = loadSessionStore(fixture.storePath()); expect(saved[sessionKey]?.sessionFile).toBe(fallbackSessionFile); }); @@ -555,7 +555,7 @@ describe("resolveAndPersistSessionFile", () => { const sessionId = "new-session-id"; const sessionKey = "agent:main:telegram:group:123"; fs.writeFileSync(fixture.storePath(), JSON.stringify({}), "utf-8"); - const sessionStore = loadSessionStore(fixture.storePath(), { skipCache: true }); + const sessionStore = loadSessionStore(fixture.storePath()); const fallbackSessionFile = resolveSessionTranscriptPathInDir(sessionId, fixture.sessionsDir()); const result = await resolveAndPersistSessionFile({ @@ -568,7 +568,7 @@ describe("resolveAndPersistSessionFile", () => { expect(result.sessionFile).toBe(fallbackSessionFile); expect(result.sessionEntry.sessionId).toBe(sessionId); - const saved = loadSessionStore(fixture.storePath(), { skipCache: true }); + const saved = loadSessionStore(fixture.storePath()); expect(saved[sessionKey]?.sessionFile).toBe(fallbackSessionFile); }); @@ -592,7 +592,7 @@ describe("resolveAndPersistSessionFile", () => { }, }; fs.writeFileSync(fixture.storePath(), JSON.stringify(store), "utf-8"); - const sessionStore = loadSessionStore(fixture.storePath(), { skipCache: true }); + const sessionStore = loadSessionStore(fixture.storePath()); const result = await resolveAndPersistSessionFile({ sessionId: nextSessionId, @@ -607,7 +607,7 @@ describe("resolveAndPersistSessionFile", () => { expect(result.sessionFile).not.toBe(previousSessionFile); expect(result.sessionEntry.sessionFile).toBe(expectedNextSessionFile); - const saved = loadSessionStore(fixture.storePath(), { skipCache: true }); + const saved = loadSessionStore(fixture.storePath()); expect(saved[sessionKey]?.sessionFile).toBe(expectedNextSessionFile); }); }); diff --git a/src/config/sessions/store-backend.sqlite.test.ts b/src/config/sessions/store-backend.sqlite.test.ts index 1dc05b3c544..dbd64c08b89 100644 --- a/src/config/sessions/store-backend.sqlite.test.ts +++ b/src/config/sessions/store-backend.sqlite.test.ts @@ -123,7 +123,7 @@ describe("SQLite session store backend", () => { }); expect(fs.existsSync(storePath)).toBe(false); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({ + expect(loadSessionStore(storePath)).toEqual({ "discord:ops": { ...entry, updatedAt: 200, @@ -145,7 +145,7 @@ describe("SQLite session store backend", () => { await saveSessionStore(storePath, { "discord:ops": entry }); expect(fs.existsSync(storePath)).toBe(false); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({ + expect(loadSessionStore(storePath)).toEqual({ "discord:ops": entry, }); }); @@ -170,7 +170,7 @@ describe("SQLite session store backend", () => { ), ); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({}); + expect(loadSessionStore(storePath)).toEqual({}); await saveSessionStore(storePath, { "discord:ops": { @@ -179,7 +179,7 @@ describe("SQLite session store backend", () => { updatedAt: 200, }, }); - expect(loadSessionStore(storePath, { skipCache: true })).toEqual({ + expect(loadSessionStore(storePath)).toEqual({ "discord:ops": { ...legacyEntry, sessionId: "sqlite-session", diff --git a/src/config/sessions/store-load.ts b/src/config/sessions/store-load.ts index b9e0cc693c4..a8b770cb16f 100644 --- a/src/config/sessions/store-load.ts +++ b/src/config/sessions/store-load.ts @@ -9,19 +9,11 @@ import type { SessionEntry } from "./types.js"; export { normalizeSessionStore } from "./store-normalize.js"; -export type LoadSessionStoreOptions = { - skipCache?: boolean; - clone?: boolean; -}; - function isSessionStoreRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } -export function loadSessionStore( - storePath: string, - _opts: LoadSessionStoreOptions = {}, -): Record { +export function loadSessionStore(storePath: string): Record { const sqliteOptions = resolveSqliteSessionStoreOptionsForPath(storePath); if (sqliteOptions) { return loadSqliteSessionStore(sqliteOptions); diff --git a/src/config/sessions/store.session-key-normalization.test.ts b/src/config/sessions/store.session-key-normalization.test.ts index 3f30af03ea4..a9c9216006f 100644 --- a/src/config/sessions/store.session-key-normalization.test.ts +++ b/src/config/sessions/store.session-key-normalization.test.ts @@ -57,7 +57,7 @@ describe("session store key normalization", () => { ctx: createInboundContext(), }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(Object.keys(store)).toEqual([CANONICAL_KEY]); expect(store[CANONICAL_KEY]?.origin?.provider).toBe("webchat"); }); @@ -76,7 +76,7 @@ describe("session store key normalization", () => { to: "webchat:user-1", }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(Object.keys(store)).toEqual([CANONICAL_KEY]); expect(store[CANONICAL_KEY]).toEqual( expect.objectContaining({ @@ -112,7 +112,7 @@ describe("session store key normalization", () => { to: "webchat:user-2", }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(store[CANONICAL_KEY]?.sessionId).toBe("legacy-session"); expect(store[MIXED_CASE_KEY]).toBeUndefined(); }); @@ -149,7 +149,7 @@ describe("session store key normalization", () => { ctx: createInboundContext(), }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); expect(store[CANONICAL_KEY]?.sessionId).toBe("existing-session"); expect(store[CANONICAL_KEY]?.updatedAt).toBe(existingUpdatedAt); expect(store[CANONICAL_KEY]?.origin?.provider).toBe("webchat"); diff --git a/src/config/sessions/store.skills-stripping.test.ts b/src/config/sessions/store.skills-stripping.test.ts index ffa3f2e4571..59284eb9e0b 100644 --- a/src/config/sessions/store.skills-stripping.test.ts +++ b/src/config/sessions/store.skills-stripping.test.ts @@ -100,7 +100,7 @@ describe("session store strips resolvedSkills from persistence", () => { }; await saveSessionStore(storePath, store); - const loaded = loadSessionStore(storePath, { skipCache: true }); + const loaded = loadSessionStore(storePath); const persistedSnapshot = loaded["agent:main:test:1"]?.skillsSnapshot; expect(persistedSnapshot).toMatchObject({ @@ -122,7 +122,7 @@ describe("session store strips resolvedSkills from persistence", () => { expect(rawLegacy).toContain("resolvedSkills"); await fs.writeFile(storePath, rawLegacy, "utf-8"); - const loaded = loadSessionStore(storePath, { skipCache: true }); + const loaded = loadSessionStore(storePath); expect(loaded["agent:main:test:1"]?.skillsSnapshot?.resolvedSkills).toBeUndefined(); expect(loaded["agent:main:test:1"]?.skillsSnapshot?.prompt).toBe( legacy["agent:main:test:1"].skillsSnapshot?.prompt, @@ -143,7 +143,7 @@ describe("session store strips resolvedSkills from persistence", () => { const raw = await fs.readFile(storePath, "utf-8"); expect(raw).not.toContain("resolvedSkills"); - const reloaded = loadSessionStore(storePath, { skipCache: true }); + const reloaded = loadSessionStore(storePath); expect(reloaded["agent:main:test:1"]?.skillsSnapshot?.resolvedSkills).toBeUndefined(); expect(reloaded["agent:main:test:1"]?.skillsSnapshot?.skills).toHaveLength(6); }); diff --git a/src/gateway/boot.test.ts b/src/gateway/boot.test.ts index 279880b41ea..e2b0f9dd7c4 100644 --- a/src/gateway/boot.test.ts +++ b/src/gateway/boot.test.ts @@ -82,7 +82,7 @@ describe("runBootOnce", () => { const mockAgentUpdatesMainSession = (storePath: string, sessionKey: string) => { agentCommand.mockImplementation(async (opts: { sessionId?: string }) => { - const current = loadSessionStore(storePath, { skipCache: true }); + const current = loadSessionStore(storePath); current[sessionKey] = { sessionId: String(opts.sessionId), updatedAt: Date.now(), @@ -96,7 +96,7 @@ describe("runBootOnce", () => { sessionKey: string; expectedSessionId?: string; }) => { - const restored = loadSessionStore(params.storePath, { skipCache: true }); + const restored = loadSessionStore(params.storePath); if (params.expectedSessionId === undefined) { expect(restored[params.sessionKey]).toBeUndefined(); return; diff --git a/src/gateway/session-utils.subagent.test.ts b/src/gateway/session-utils.subagent.test.ts index b59dc677b3b..75180f12988 100644 --- a/src/gateway/session-utils.subagent.test.ts +++ b/src/gateway/session-utils.subagent.test.ts @@ -7,7 +7,7 @@ import { resetSubagentRegistryForTests, } from "../agents/subagent-registry.js"; import type { OpenClawConfig } from "../config/config.js"; -import { loadSessionStore, type SessionEntry } from "../config/sessions.js"; +import { loadSessionStore, saveSessionStore, type SessionEntry } from "../config/sessions.js"; import { registerAgentRunContext, resetAgentRunContextForTest } from "../infra/agent-events.js"; import { withStateDirEnv } from "../test-helpers/state-dir-env.js"; import { withEnv } from "../test-utils/env.js"; @@ -1252,21 +1252,12 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" fs.mkdirSync(mainDir, { recursive: true }); fs.mkdirSync(codexDir, { recursive: true }); - fs.writeFileSync( - path.join(mainDir, "sessions.json"), - JSON.stringify({ - "agent:main:main": { sessionId: "s-main", updatedAt: 100 }, - }), - "utf8", - ); - - fs.writeFileSync( - path.join(codexDir, "sessions.json"), - JSON.stringify({ - "agent:codex:acp-task": { sessionId: "s-codex", updatedAt: 200 }, - }), - "utf8", - ); + await saveSessionStore(path.join(mainDir, "sessions.json"), { + "agent:main:main": { sessionId: "s-main", updatedAt: 100 }, + }); + await saveSessionStore(path.join(codexDir, "sessions.json"), { + "agent:codex:acp-task": { sessionId: "s-codex", updatedAt: 200 }, + }); const cfg = { session: { @@ -1295,20 +1286,12 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" const mainStorePath = path.join(mainDir, "sessions.json"); const codexStorePath = path.join(codexDir, "sessions.json"); - fs.writeFileSync( - mainStorePath, - JSON.stringify({ - "agent:main:main": { sessionId: "s-main", updatedAt: 100 }, - }), - "utf8", - ); - fs.writeFileSync( - codexStorePath, - JSON.stringify({ - "agent:codex:acp-task": { sessionId: "s-codex", updatedAt: 200 }, - }), - "utf8", - ); + await saveSessionStore(mainStorePath, { + "agent:main:main": { sessionId: "s-main", updatedAt: 100 }, + }); + await saveSessionStore(codexStorePath, { + "agent:codex:acp-task": { sessionId: "s-codex", updatedAt: 200 }, + }); const cfg = { session: { @@ -1320,41 +1303,28 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }, } as OpenClawConfig; - const readSpy = vi.spyOn(fs, "readFileSync"); - const { store, storePath } = loadCombinedSessionStoreForGateway(cfg, { agentId: "codex" }); expect(storePath).toBe(fs.realpathSync.native(codexStorePath)); expect(store["agent:codex:acp-task"]).toMatchObject({ sessionId: "s-codex" }); expect(store["agent:main:main"]).toBeUndefined(); - const readPaths = readSpy.mock.calls - .map((call) => call[0]) - .filter((arg): arg is string => typeof arg === "string"); - expect(readPaths).toContain(fs.realpathSync.native(codexStorePath)); - expect(readPaths).not.toContain(fs.realpathSync.native(mainStorePath)); - - readSpy.mockRestore(); }); }); - test("keeps canonical single-target entries by reference", async () => { + test("keeps canonical single-target entries intact", async () => { await withStateDirEnv("openclaw-acp-canonical-", async ({ stateDir }) => { const customRoot = path.join(stateDir, "custom-state"); const codexDir = path.join(customRoot, "agents", "codex", "sessions"); fs.mkdirSync(codexDir, { recursive: true }); const codexStorePath = path.join(codexDir, "sessions.json"); - fs.writeFileSync( - codexStorePath, - JSON.stringify({ - "agent:codex:acp-task": { - sessionId: "s-codex", - updatedAt: 200, - spawnedBy: "agent:codex:main", - }, - }), - "utf8", - ); + await saveSessionStore(codexStorePath, { + "agent:codex:acp-task": { + sessionId: "s-codex", + updatedAt: 200, + spawnedBy: "agent:codex:main", + }, + }); const cfg = { session: { @@ -1366,12 +1336,12 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }, } as OpenClawConfig; - const cachedStore = loadSessionStore(fs.realpathSync.native(codexStorePath), { - clone: false, - }); + const savedStore = loadSessionStore( + path.join(fs.realpathSync.native(codexDir), "sessions.json"), + ); const { store } = loadCombinedSessionStoreForGateway(cfg, { agentId: "codex" }); - expect(store["agent:codex:acp-task"]).toBe(cachedStore["agent:codex:acp-task"]); + expect(store["agent:codex:acp-task"]).toEqual(savedStore["agent:codex:acp-task"]); }); }); }); diff --git a/src/gateway/session-utils.telegram-recreate.test.ts b/src/gateway/session-utils.telegram-recreate.test.ts index 423376aa929..15c55e8860e 100644 --- a/src/gateway/session-utils.telegram-recreate.test.ts +++ b/src/gateway/session-utils.telegram-recreate.test.ts @@ -86,7 +86,7 @@ describe("Telegram direct session recreation after delete", () => { await updateSessionStore(storePath, (store) => { delete store[TELEGRAM_DIRECT_KEY]; }); - expect(loadSessionStore(storePath, { skipCache: true })[TELEGRAM_DIRECT_KEY]).toBeUndefined(); + expect(loadSessionStore(storePath)[TELEGRAM_DIRECT_KEY]).toBeUndefined(); const ctx = createTelegramDirectContext(); await recordSessionMetaFromInbound({ @@ -103,7 +103,7 @@ describe("Telegram direct session recreation after delete", () => { ctx, }); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const listed = listSessionsFromStore({ cfg, storePath, diff --git a/src/plugins/contracts/host-hooks.contract.test.ts b/src/plugins/contracts/host-hooks.contract.test.ts index 04b6458220c..e2ded45b521 100644 --- a/src/plugins/contracts/host-hooks.contract.test.ts +++ b/src/plugins/contracts/host-hooks.contract.test.ts @@ -1099,7 +1099,7 @@ describe("host-hook fixture plugin contract", () => { id: first.id, sessionKey: "agent:main:main", }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect( stored["agent:main:main"]?.pluginNextTurnInjections?.["approval-fixture"], ).toHaveLength(1); @@ -1205,7 +1205,7 @@ describe("host-hook fixture plugin contract", () => { text: "active prompt contribution", }), ]); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect(stored["agent:main:main"]?.pluginNextTurnInjections).toBeUndefined(); }, }); @@ -2183,7 +2183,7 @@ describe("host-hook fixture plugin contract", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect(stored["agent:main:main"]).toEqual( expect.objectContaining({ pluginExtensions: { @@ -2333,7 +2333,7 @@ describe("host-hook fixture plugin contract", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect(stored["agent:main:main"]?.pluginExtensions).toEqual({ "restart-state-fixture": { workflow: { state: "waiting" } }, }); @@ -2409,7 +2409,7 @@ describe("host-hook fixture plugin contract", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect(stored["agent:main:main"]?.pluginNextTurnInjections).toBeUndefined(); }, }); diff --git a/src/plugins/contracts/run-context-lifecycle.contract.test.ts b/src/plugins/contracts/run-context-lifecycle.contract.test.ts index b1c9a77f86d..e761cdee3f5 100644 --- a/src/plugins/contracts/run-context-lifecycle.contract.test.ts +++ b/src/plugins/contracts/run-context-lifecycle.contract.test.ts @@ -711,7 +711,7 @@ describe("plugin run context lifecycle", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); expect(stored["agent:main:main"]?.pluginExtensions).toEqual({ "restart-state-fixture": { workflow: { state: "waiting" } }, }); diff --git a/src/plugins/contracts/session-entry-projection.contract.test.ts b/src/plugins/contracts/session-entry-projection.contract.test.ts index 63de77be1a6..76b3bb63807 100644 --- a/src/plugins/contracts/session-entry-projection.contract.test.ts +++ b/src/plugins/contracts/session-entry-projection.contract.test.ts @@ -78,7 +78,7 @@ describe("plugin session extension SessionEntry projection", () => { value: { state: "executing", title: "Deploy approval", internal: 7 }, }); expect(patchResult.ok).toBe(true); - const afterPatch = loadSessionStore(storePath, { skipCache: true }); + const afterPatch = loadSessionStore(storePath); expect( (afterPatch["agent:main:main"] as unknown as Record).approvalSnapshot, ).toEqual({ state: "executing", title: "Deploy approval" }); @@ -91,7 +91,7 @@ describe("plugin session extension SessionEntry projection", () => { unset: true, }); expect(unsetResult.ok).toBe(true); - const afterUnset = loadSessionStore(storePath, { skipCache: true }); + const afterUnset = loadSessionStore(storePath); expect( (afterUnset["agent:main:main"] as unknown as Record).approvalSnapshot, ).toBeUndefined(); @@ -162,11 +162,8 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ ok: true }); expect( - ( - loadSessionStore(storePath, { skipCache: true })[ - "agent:main:main" - ] as unknown as Record - ).approvalSnapshot, + (loadSessionStore(storePath)["agent:main:main"] as unknown as Record) + .approvalSnapshot, ).toEqual({ state: "ready" }); await expect( @@ -178,9 +175,10 @@ describe("plugin session extension SessionEntry projection", () => { value: { state: "bad", fail: "throw" }, }), ).resolves.toMatchObject({ ok: true }); - const afterThrow = loadSessionStore(storePath, { skipCache: true })[ - "agent:main:main" - ] as unknown as Record; + const afterThrow = loadSessionStore(storePath)["agent:main:main"] as unknown as Record< + string, + unknown + >; expect(afterThrow.approvalSnapshot).toBeUndefined(); expect(afterThrow.pluginExtensions).toMatchObject({ "failing-promoted-plugin": { @@ -198,11 +196,8 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ ok: true }); expect( - ( - loadSessionStore(storePath, { skipCache: true })[ - "agent:main:main" - ] as unknown as Record - ).approvalSnapshot, + (loadSessionStore(storePath)["agent:main:main"] as unknown as Record) + .approvalSnapshot, ).toEqual({ state: "ready-again" }); await expect( @@ -214,9 +209,10 @@ describe("plugin session extension SessionEntry projection", () => { value: { state: "async-bad", fail: "promise" }, }), ).resolves.toMatchObject({ ok: true }); - const afterPromise = loadSessionStore(storePath, { skipCache: true })[ - "agent:main:main" - ] as unknown as Record; + const afterPromise = loadSessionStore(storePath)["agent:main:main"] as unknown as Record< + string, + unknown + >; expect(afterPromise.approvalSnapshot).toBeUndefined(); expect(afterPromise.pluginExtensions).toMatchObject({ "failing-promoted-plugin": { @@ -416,7 +412,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.pluginExtensions).toBeUndefined(); expect(entry.approvalSnapshot).toBeUndefined(); @@ -483,7 +479,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.pluginExtensions).toBeUndefined(); expect(entry.approvalSnapshot).toBeUndefined(); @@ -562,7 +558,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.approvalSnapshot).toBeUndefined(); expect(entry.pluginExtensionSlotKeys).toBeUndefined(); @@ -665,7 +661,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.approvalSnapshot).toEqual({ state: "waiting" }); expect(entry.legacyApprovalSnapshot).toBeUndefined(); @@ -756,7 +752,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.approvalSnapshot).toEqual({ state: "waiting" }); expect(entry.pluginExtensionSlotKeys).toEqual({ @@ -820,7 +816,7 @@ describe("plugin session extension SessionEntry projection", () => { }), ).resolves.toMatchObject({ failures: [] }); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.approvalSnapshot).toBeUndefined(); expect(entry.pluginExtensionSlotKeys).toBeUndefined(); @@ -991,7 +987,7 @@ describe("plugin session extension SessionEntry projection", () => { value: { state: "executing" }, }); expect(result.ok).toBe(true); - const stored = loadSessionStore(storePath, { skipCache: true }); + const stored = loadSessionStore(storePath); const entry = stored["agent:main:main"] as unknown as Record; expect(entry.approvalSnapshot).toBeUndefined(); },