mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
perf(gateway): avoid extra session-list store work
This commit is contained in:
@@ -108,6 +108,30 @@ describe("Session Store Cache", () => {
|
||||
expect(loaded2["session:1"].skillsSnapshot?.skills?.[0]?.name).toBe("alpha");
|
||||
});
|
||||
|
||||
it("honors explicit clone:false on cache hits", async () => {
|
||||
const testStore = createSingleSessionStore(
|
||||
createSessionEntry({
|
||||
origin: { provider: "openai" },
|
||||
}),
|
||||
);
|
||||
|
||||
await saveSessionStore(storePath, testStore);
|
||||
|
||||
const parseSpy = vi.spyOn(JSON, "parse");
|
||||
|
||||
const loaded1 = loadSessionStore(storePath, { clone: false });
|
||||
expect(parseSpy).not.toHaveBeenCalled();
|
||||
|
||||
loaded1["session:1"].origin = { provider: "mutated" };
|
||||
|
||||
const loaded2 = loadSessionStore(storePath, { clone: false });
|
||||
expect(loaded2).toBe(loaded1);
|
||||
expect(loaded2["session:1"].origin?.provider).toBe("mutated");
|
||||
expect(parseSpy).not.toHaveBeenCalled();
|
||||
|
||||
parseSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("does not cache pre-migration or pre-normalization disk JSON", () => {
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
|
||||
@@ -43,7 +43,10 @@ function mergeSessionEntryIntoCombined(params: {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
|
||||
export function loadCombinedSessionStoreForGateway(
|
||||
cfg: OpenClawConfig,
|
||||
opts: { agentId?: string } = {},
|
||||
): {
|
||||
storePath: string;
|
||||
store: Record<string, SessionEntry>;
|
||||
} {
|
||||
@@ -70,7 +73,13 @@ export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
|
||||
return { storePath, store: combined };
|
||||
}
|
||||
|
||||
const targets = resolveAllAgentSessionStoreTargetsSync(cfg);
|
||||
const requestedAgentId =
|
||||
typeof opts.agentId === "string" && opts.agentId.trim()
|
||||
? normalizeAgentId(opts.agentId)
|
||||
: undefined;
|
||||
const targets = resolveAllAgentSessionStoreTargetsSync(cfg).filter(
|
||||
(target) => !requestedAgentId || normalizeAgentId(target.agentId) === requestedAgentId,
|
||||
);
|
||||
const combined: Record<string, SessionEntry> = {};
|
||||
for (const target of targets) {
|
||||
const agentId = target.agentId;
|
||||
@@ -93,6 +102,10 @@ export function loadCombinedSessionStoreForGateway(cfg: OpenClawConfig): {
|
||||
}
|
||||
|
||||
const storePath =
|
||||
typeof storeConfig === "string" && storeConfig.trim() ? storeConfig.trim() : "(multiple)";
|
||||
targets.length === 1
|
||||
? targets[0].storePath
|
||||
: typeof storeConfig === "string" && storeConfig.trim()
|
||||
? storeConfig.trim()
|
||||
: "(multiple)";
|
||||
return { storePath, store: combined };
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export function readSessionStoreCache(params: {
|
||||
storePath: string;
|
||||
mtimeMs?: number;
|
||||
sizeBytes?: number;
|
||||
clone?: boolean;
|
||||
}): Record<string, SessionEntry> | null {
|
||||
const cached = SESSION_STORE_CACHE.get(params.storePath);
|
||||
if (!cached) {
|
||||
@@ -72,6 +73,9 @@ export function readSessionStoreCache(params: {
|
||||
invalidateSessionStoreCache(params.storePath);
|
||||
return null;
|
||||
}
|
||||
if (params.clone === false) {
|
||||
return cached.store;
|
||||
}
|
||||
return cloneSessionStoreRecord(cached.store, cached.serialized);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export function loadSessionStore(
|
||||
storePath,
|
||||
mtimeMs: currentFileStat?.mtimeMs,
|
||||
sizeBytes: currentFileStat?.sizeBytes,
|
||||
clone: opts.clone,
|
||||
});
|
||||
if (cached) {
|
||||
return cached;
|
||||
|
||||
@@ -668,7 +668,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
const p = params;
|
||||
const cfg = context.getRuntimeConfig();
|
||||
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
|
||||
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { agentId: p.agentId });
|
||||
const modelCatalog = await loadOptionalSessionsListModelCatalog(context);
|
||||
const result = await listSessionsFromStoreAsync({
|
||||
cfg,
|
||||
|
||||
@@ -1155,4 +1155,57 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)"
|
||||
expect(store["agent:codex:acp-task"]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test("agent-scoped loads read only matching agent stores", async () => {
|
||||
await withStateDirEnv("openclaw-acp-scoped-", async ({ stateDir }) => {
|
||||
const customRoot = path.join(stateDir, "custom-state");
|
||||
const agentsDir = path.join(customRoot, "agents");
|
||||
const mainDir = path.join(agentsDir, "main", "sessions");
|
||||
const codexDir = path.join(agentsDir, "codex", "sessions");
|
||||
fs.mkdirSync(mainDir, { recursive: true });
|
||||
fs.mkdirSync(codexDir, { recursive: true });
|
||||
|
||||
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",
|
||||
);
|
||||
|
||||
const cfg = {
|
||||
session: {
|
||||
mainKey: "main",
|
||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
||||
},
|
||||
agents: {
|
||||
list: [{ id: "main", default: true }],
|
||||
},
|
||||
} 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"]).toBeDefined();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user