refactor: simplify session store loads

This commit is contained in:
Peter Steinberger
2026-05-06 19:07:00 +01:00
parent 78948d4e32
commit 9ac91cdf0b
28 changed files with 110 additions and 163 deletions

View File

@@ -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")

View File

@@ -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,
);

View File

@@ -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(

View File

@@ -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") {

View File

@@ -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({

View File

@@ -1199,7 +1199,6 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
cfg,
route: modelSessionRoute,
data,
skipCache: true,
});
const view = renderMattermostModelsPickerView({
ownerUserId: pickerState.ownerUserId,

View File

@@ -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({

View File

@@ -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;

View File

@@ -55,9 +55,7 @@ const { getRuntimeConfig, loadSessionStoreMock, resolveStorePathMock, sessionSto
sessionStoreEntries: { value: SessionStore };
} => ({
getRuntimeConfig: vi.fn<GetRuntimeConfigFn>(() => ({})),
loadSessionStoreMock: vi.fn<LoadSessionStoreFn>(
(_storePath, _opts) => sessionStoreEntries.value,
),
loadSessionStoreMock: vi.fn<LoadSessionStoreFn>(() => sessionStoreEntries.value),
resolveStorePathMock: vi.fn<ResolveStorePathFn>(
(storePath?: string) => storePath ?? sessionStorePath,
),

View File

@@ -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 {

View File

@@ -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);
});

View File

@@ -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");
});
});
});

View File

@@ -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" });
});
});

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -68,7 +68,7 @@ function expectSessionStore(
storePath: string,
sessions: Record<string, { sessionId: string; updatedAt: number }>,
) {
expect(loadSessionStore(storePath, { skipCache: true })).toEqual(sessions);
expect(loadSessionStore(storePath)).toEqual(sessions);
}
function readJsonLogs(): Array<Record<string, unknown>> {

View File

@@ -879,7 +879,7 @@ describe("sessions", () => {
parseSpy.mockRestore();
}
const store = loadSessionStore(storePath, { skipCache: true });
const store = loadSessionStore(storePath);
expect(store[mainSessionKey]?.thinkingLevel).toBe("high");
});

View File

@@ -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);
});
});

View File

@@ -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",

View File

@@ -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<string, SessionEntry> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
export function loadSessionStore(
storePath: string,
_opts: LoadSessionStoreOptions = {},
): Record<string, SessionEntry> {
export function loadSessionStore(storePath: string): Record<string, SessionEntry> {
const sqliteOptions = resolveSqliteSessionStoreOptionsForPath(storePath);
if (sqliteOptions) {
return loadSqliteSessionStore(sqliteOptions);

View File

@@ -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");

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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"]);
});
});
});

View File

@@ -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,

View File

@@ -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();
},
});

View File

@@ -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" } },
});

View File

@@ -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<string, unknown>).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<string, unknown>).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<string, unknown>
).approvalSnapshot,
(loadSessionStore(storePath)["agent:main:main"] as unknown as Record<string, unknown>)
.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<string, unknown>;
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<string, unknown>
).approvalSnapshot,
(loadSessionStore(storePath)["agent:main:main"] as unknown as Record<string, unknown>)
.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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
expect(entry.approvalSnapshot).toBeUndefined();
},