mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 08:02:54 +00:00
Fix cross-worktree chat leak: key native-chat transcript cache by resolved file path (#7341)
This commit is contained in:
@@ -4,13 +4,18 @@ import { resolveSessionFilePath } from './session-file-resolver'
|
||||
import { readNativeChatTranscript, type ReadTranscriptResult } from './transcript-reader'
|
||||
|
||||
// Why: both the desktop IPC handler and the runtime RPC handler read the same
|
||||
// host-filesystem transcript, so a single process-global cache keyed by
|
||||
// agent:sessionId maximizes the hit rate across desktop + every paired
|
||||
// web/mobile client. Keying by connection instead would defeat the multi-client
|
||||
// case this feature targets and multiply memory by the connection count.
|
||||
// The cache stores ONE canonical, unwindowed parse; windowing and per-surface
|
||||
// truncation stay in the callers so the same parse is reused across all `limit`
|
||||
// values and every client kind.
|
||||
// host-filesystem transcript, so a single process-global cache keyed by the
|
||||
// RESOLVED transcript file path maximizes the hit rate across desktop + every
|
||||
// paired web/mobile client (all clients of one session resolve the same path
|
||||
// against this runtime's home). Keying by connection instead would defeat the
|
||||
// multi-client case this feature targets and multiply memory by the connection
|
||||
// count. The key is the resolved file path, NOT `agent:sessionId`: two panes can
|
||||
// share one sessionId yet resolve to DIFFERENT files (the same session resumed
|
||||
// into a second worktree, which writes a new transcript file), and a
|
||||
// sessionId-only key let one worktree's cached parse be served to another when
|
||||
// their file mtimes momentarily coincided (#7326). The cache stores ONE
|
||||
// canonical, unwindowed parse; windowing and per-surface truncation stay in the
|
||||
// callers so the same parse is reused across all `limit` values and every client kind.
|
||||
|
||||
type CachedTranscript = {
|
||||
result: ReadTranscriptResult
|
||||
@@ -40,8 +45,8 @@ function setCached(key: string, value: CachedTranscript): void {
|
||||
}
|
||||
}
|
||||
|
||||
function cacheKey(agent: AgentType, sessionId: string): string {
|
||||
return `${agent}:${sessionId}`
|
||||
function cacheKey(agent: AgentType, filePath: string): string {
|
||||
return `${agent}:${filePath}`
|
||||
}
|
||||
|
||||
async function fileMtimeMs(filePath: string): Promise<number> {
|
||||
@@ -68,7 +73,7 @@ export async function readNativeChatTranscriptCached(
|
||||
return { error: `No transcript found for ${agent} session ${sessionId}` }
|
||||
}
|
||||
|
||||
const key = cacheKey(agent, sessionId)
|
||||
const key = cacheKey(agent, filePath)
|
||||
const mtimeMs = await fileMtimeMs(filePath)
|
||||
const cached = cache.get(key)
|
||||
if (cached && Number.isFinite(mtimeMs) && cached.mtimeMs === mtimeMs) {
|
||||
@@ -84,7 +89,7 @@ export async function readNativeChatTranscriptCached(
|
||||
return result
|
||||
}
|
||||
|
||||
/** Test-only: drop the per-session transcript cache between runs. */
|
||||
/** Test-only: drop the transcript parse cache between runs. */
|
||||
export function clearNativeChatTranscriptCache(): void {
|
||||
cache.clear()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user