Skip to content

Commit 43de6ae

Browse files
committed
perf(gateway): avoid extra session-list store work
1 parent ea098bc commit 43de6ae

6 files changed

Lines changed: 99 additions & 4 deletions

File tree

src/config/sessions.cache.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,30 @@ describe("Session Store Cache", () => {
108108
expect(loaded2["session:1"].skillsSnapshot?.skills?.[0]?.name).toBe("alpha");
109109
});
110110

111+
it("honors explicit clone:false on cache hits", async () => {
112+
const testStore = createSingleSessionStore(
113+
createSessionEntry({
114+
origin: { provider: "openai" },
115+
}),
116+
);
117+
118+
await saveSessionStore(storePath, testStore);
119+
120+
const parseSpy = vi.spyOn(JSON, "parse");
121+
122+
const loaded1 = loadSessionStore(storePath, { clone: false });
123+
expect(parseSpy).not.toHaveBeenCalled();
124+
125+
loaded1["session:1"].origin = { provider: "mutated" };
126+
127+
const loaded2 = loadSessionStore(storePath, { clone: false });
128+
expect(loaded2).toBe(loaded1);
129+
expect(loaded2["session:1"].origin?.provider).toBe("mutated");
130+
expect(parseSpy).not.toHaveBeenCalled();
131+
132+
parseSpy.mockRestore();
133+
});
134+
111135
it("does not cache pre-migration or pre-normalization disk JSON", () => {
112136
fs.writeFileSync(
113137
storePath,

src/config/sessions/combined-store-gateway.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ function mergeSessionEntryIntoCombined(params: {
4343
}
4444
}
4545

46-
export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
46+
export function loadCombinedSessionStoreForGateway(
47+
cfg: OpenClawConfig,
48+
opts: { agentId?: string } = {},
49+
): {
4750
storePath: string;
4851
store: Record<string, SessionEntry>;
4952
} {
@@ -70,7 +73,13 @@ export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
7073
return { storePath, store: combined };
7174
}
7275

73-
const targets = resolveAllAgentSessionStoreTargetsSync(cfg);
76+
const requestedAgentId =
77+
typeof opts.agentId === "string" && opts.agentId.trim()
78+
? normalizeAgentId(opts.agentId)
79+
: undefined;
80+
const targets = resolveAllAgentSessionStoreTargetsSync(cfg).filter(
81+
(target) => !requestedAgentId || normalizeAgentId(target.agentId) === requestedAgentId,
82+
);
7483
const combined: Record<string, SessionEntry> = {};
7584
for (const target of targets) {
7685
const agentId = target.agentId;
@@ -93,6 +102,10 @@ export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
93102
}
94103

95104
const storePath =
96-
typeof storeConfig === "string" && storeConfig.trim() ? storeConfig.trim() : "(multiple)";
105+
targets.length === 1
106+
? targets[0].storePath
107+
: typeof storeConfig === "string" && storeConfig.trim()
108+
? storeConfig.trim()
109+
: "(multiple)";
97110
return { storePath, store: combined };
98111
}

src/config/sessions/store-cache.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export function readSessionStoreCache(params: {
6363
storePath: string;
6464
mtimeMs?: number;
6565
sizeBytes?: number;
66+
clone?: boolean;
6667
}): Record<string, SessionEntry> | null {
6768
const cached = SESSION_STORE_CACHE.get(params.storePath);
6869
if (!cached) {
@@ -72,6 +73,9 @@ export function readSessionStoreCache(params: {
7273
invalidateSessionStoreCache(params.storePath);
7374
return null;
7475
}
76+
if (params.clone === false) {
77+
return cached.store;
78+
}
7579
return cloneSessionStoreRecord(cached.store, cached.serialized);
7680
}
7781

src/config/sessions/store-load.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export function loadSessionStore(
107107
storePath,
108108
mtimeMs: currentFileStat?.mtimeMs,
109109
sizeBytes: currentFileStat?.sizeBytes,
110+
clone: opts.clone,
110111
});
111112
if (cached) {
112113
return cached;

src/gateway/server-methods/sessions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
668668
}
669669
const p = params;
670670
const cfg = context.getRuntimeConfig();
671-
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
671+
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { agentId: p.agentId });
672672
const modelCatalog = await loadOptionalSessionsListModelCatalog(context);
673673
const result = await listSessionsFromStoreAsync({
674674
cfg,

src/gateway/session-utils.subagent.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,4 +1155,57 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)"
11551155
expect(store["agent:codex:acp-task"]).toBeDefined();
11561156
});
11571157
});
1158+
1159+
test("agent-scoped loads read only matching agent stores", async () => {
1160+
await withStateDirEnv("openclaw-acp-scoped-", async ({ stateDir }) => {
1161+
const customRoot = path.join(stateDir, "custom-state");
1162+
const agentsDir = path.join(customRoot, "agents");
1163+
const mainDir = path.join(agentsDir, "main", "sessions");
1164+
const codexDir = path.join(agentsDir, "codex", "sessions");
1165+
fs.mkdirSync(mainDir, { recursive: true });
1166+
fs.mkdirSync(codexDir, { recursive: true });
1167+
1168+
const mainStorePath = path.join(mainDir, "sessions.json");
1169+
const codexStorePath = path.join(codexDir, "sessions.json");
1170+
fs.writeFileSync(
1171+
mainStorePath,
1172+
JSON.stringify({
1173+
"agent:main:main": { sessionId: "s-main", updatedAt: 100 },
1174+
}),
1175+
"utf8",
1176+
);
1177+
fs.writeFileSync(
1178+
codexStorePath,
1179+
JSON.stringify({
1180+
"agent:codex:acp-task": { sessionId: "s-codex", updatedAt: 200 },
1181+
}),
1182+
"utf8",
1183+
);
1184+
1185+
const cfg = {
1186+
session: {
1187+
mainKey: "main",
1188+
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
1189+
},
1190+
agents: {
1191+
list: [{ id: "main", default: true }],
1192+
},
1193+
} as OpenClawConfig;
1194+
1195+
const readSpy = vi.spyOn(fs, "readFileSync");
1196+
1197+
const { store, storePath } = loadCombinedSessionStoreForGateway(cfg, { agentId: "codex" });
1198+
1199+
expect(storePath).toBe(fs.realpathSync.native(codexStorePath));
1200+
expect(store["agent:codex:acp-task"]).toBeDefined();
1201+
expect(store["agent:main:main"]).toBeUndefined();
1202+
const readPaths = readSpy.mock.calls
1203+
.map((call) => call[0])
1204+
.filter((arg): arg is string => typeof arg === "string");
1205+
expect(readPaths).toContain(fs.realpathSync.native(codexStorePath));
1206+
expect(readPaths).not.toContain(fs.realpathSync.native(mainStorePath));
1207+
1208+
readSpy.mockRestore();
1209+
});
1210+
});
11581211
});

0 commit comments

Comments
 (0)