diff --git a/src/agents/cortex-history.test.ts b/src/agents/cortex-history.test.ts index 6a6002486f5..a6c23cae00a 100644 --- a/src/agents/cortex-history.test.ts +++ b/src/agents/cortex-history.test.ts @@ -86,4 +86,47 @@ describe("cortex capture history", () => { expect(syncEntry?.timestamp).toBe(2_000); expect(syncEntry?.syncPlatforms).toEqual(["claude-code", "cursor", "copilot"]); }); + + it("finds an older matching conversation entry even when newer unrelated entries exceed 100", async () => { + const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cortex-history-scan-")); + vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); + + await appendCortexCaptureHistory({ + agentId: "main", + sessionId: "session-target", + channelId: "channel-target", + captured: true, + score: 0.8, + reason: "target conversation capture", + timestamp: 1_000, + }); + + for (let index = 0; index < 150; index += 1) { + await appendCortexCaptureHistory({ + agentId: "main", + sessionId: `session-${index}`, + channelId: `channel-${index}`, + captured: true, + score: 0.5, + reason: `other capture ${index}`, + timestamp: 2_000 + index, + }); + } + + const asyncEntry = await getLatestCortexCaptureHistoryEntry({ + agentId: "main", + sessionId: "session-target", + channelId: "channel-target", + }); + const syncEntry = getLatestCortexCaptureHistoryEntrySync({ + agentId: "main", + sessionId: "session-target", + channelId: "channel-target", + }); + + expect(asyncEntry?.reason).toBe("target conversation capture"); + expect(asyncEntry?.timestamp).toBe(1_000); + expect(syncEntry?.reason).toBe("target conversation capture"); + expect(syncEntry?.timestamp).toBe(1_000); + }); }); diff --git a/src/agents/cortex-history.ts b/src/agents/cortex-history.ts index ead0d0e5b41..eeba39eaffb 100644 --- a/src/agents/cortex-history.ts +++ b/src/agents/cortex-history.ts @@ -18,6 +18,50 @@ export type CortexCaptureHistoryEntry = { const latestCortexCaptureHistoryByKey = new Map(); +function matchesHistoryEntry( + entry: CortexCaptureHistoryEntry, + params: { + agentId: string; + sessionId?: string; + channelId?: string; + }, +): boolean { + return ( + entry.agentId === params.agentId && + (params.sessionId ? entry.sessionId === params.sessionId : true) && + (params.channelId ? entry.channelId === params.channelId : true) + ); +} + +function parseLatestMatchingHistoryEntry( + raw: string, + params: { + agentId: string; + sessionId?: string; + channelId?: string; + }, +): CortexCaptureHistoryEntry | null { + const lines = raw + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + for (let index = lines.length - 1; index >= 0; index -= 1) { + const line = lines[index]; + if (!line) { + continue; + } + try { + const entry = JSON.parse(line) as CortexCaptureHistoryEntry; + if (matchesHistoryEntry(entry, params)) { + return entry; + } + } catch { + continue; + } + } + return null; +} + function buildHistoryCacheKey(params: { agentId: string; sessionId?: string; @@ -91,29 +135,7 @@ export function getLatestCortexCaptureHistoryEntrySync(params: { } catch { return null; } - const lines = raw - .split("\n") - .map((line) => line.trim()) - .filter(Boolean); - for (let index = lines.length - 1; index >= 0; index -= 1) { - const line = lines[index]; - if (!line) { - continue; - } - try { - const entry = JSON.parse(line) as CortexCaptureHistoryEntry; - if ( - entry.agentId === params.agentId && - (params.sessionId ? entry.sessionId === params.sessionId : true) && - (params.channelId ? entry.channelId === params.channelId : true) - ) { - return entry; - } - } catch { - continue; - } - } - return null; + return parseLatestMatchingHistoryEntry(raw, params); } export function getCachedLatestCortexCaptureHistoryEntry(params: { @@ -138,14 +160,14 @@ export async function getLatestCortexCaptureHistoryEntry(params: { channelId?: string; env?: NodeJS.ProcessEnv; }): Promise { - const recent = await readRecentCortexCaptureHistory({ limit: 100, env: params.env }); - const match = - recent.find( - (entry) => - entry.agentId === params.agentId && - (params.sessionId ? entry.sessionId === params.sessionId : true) && - (params.channelId ? entry.channelId === params.channelId : true), - ) ?? null; + const historyPath = resolveHistoryPath(params.env); + let raw: string; + try { + raw = await fsp.readFile(historyPath, "utf8"); + } catch { + return null; + } + const match = parseLatestMatchingHistoryEntry(raw, params); if (match) { cacheHistoryEntry(match); }