Files
openclaw/src/agents/bootstrap-cache.ts
Peter Steinberger 9ae9fcb1cd fix: preserve transcript cursors across session resets (#112988)
* feat: add reset transcript boundaries

* feat: project reset boundaries in history

* refactor: reset sessions in place

* fix: fence reset runtime caches by lifecycle

* fix: preserve reset filtering during compaction

* fix: anchor compaction after reset boundaries

* fix: budget reset preludes during compaction

* fix: honor reset visibility in bounded history

* fix: couple reset boundaries to lifecycle commits

* fix: commit reset boundaries atomically

* fix: preserve reset boundary transition states

* fix: preserve concurrent reset lifecycle owners

* fix: preserve reset boundary storage compatibility

* fix: preserve reset windows across persisted histories

* fix: fence legacy reset lifecycle state

* fix: keep stale appends behind reset boundaries

* perf: index reset descendants linearly

* fix: bound reset history projection reads

* fix: narrow invalid leaf control types

* test: use typed cron lifecycle revision

* style: keep acp lifecycle helper within limit

* style: satisfy reset boundary lint rules

* test: split reset concurrency coverage

* style: satisfy compaction fixture lint

* refactor: remove retired reset replay exports

* test: assert in-place reply session resets

* test: assert reset boundary persistence

* test: mock session boundary counts
2026-07-23 06:22:43 -04:00

98 lines
2.8 KiB
TypeScript

/**
* Per-session workspace bootstrap snapshot cache.
* Reuses unchanged bootstrap file arrays while refreshing each turn so edits
* become visible to long-lived agent sessions.
*/
import { loadWorkspaceBootstrapFiles, type WorkspaceBootstrapFile } from "./workspace.js";
type BootstrapSnapshot = {
workspaceDir: string;
files: WorkspaceBootstrapFile[];
};
const MAX_BOOTSTRAP_SNAPSHOTS = 64;
const cache = new Map<string, BootstrapSnapshot>();
function bootstrapFilesEqual(
previous: WorkspaceBootstrapFile[],
next: WorkspaceBootstrapFile[],
): boolean {
if (previous.length !== next.length) {
return false;
}
return previous.every((file, index) => {
const updated = next[index];
return (
updated !== undefined &&
file.name === updated.name &&
file.path === updated.path &&
file.content === updated.content &&
file.missing === updated.missing
);
});
}
function pruneOldestBootstrapSnapshots(): void {
while (cache.size > MAX_BOOTSTRAP_SNAPSHOTS) {
const oldestKey = cache.keys().next().value;
if (typeof oldestKey !== "string") {
return;
}
cache.delete(oldestKey);
}
}
/** Load bootstrap files for a session, reusing the prior snapshot when content is unchanged. */
export async function getOrLoadBootstrapFiles(params: {
workspaceDir: string;
sessionKey: string;
}): Promise<WorkspaceBootstrapFile[]> {
pruneOldestBootstrapSnapshots();
const existing = cache.get(params.sessionKey);
// Refresh per turn so long-lived sessions pick up edits; loadWorkspaceBootstrapFiles
// handles unchanged file content through its guarded inode/mtime cache.
const files = await loadWorkspaceBootstrapFiles(params.workspaceDir);
if (
existing &&
existing.workspaceDir === params.workspaceDir &&
bootstrapFilesEqual(existing.files, files)
) {
cache.delete(params.sessionKey);
cache.set(params.sessionKey, existing);
return existing.files;
}
cache.set(params.sessionKey, { workspaceDir: params.workspaceDir, files });
pruneOldestBootstrapSnapshots();
return files;
}
/** Drop one cached bootstrap snapshot. */
export function clearBootstrapSnapshot(sessionKey: string): void {
cache.delete(sessionKey);
}
/** Clear bootstrap state when a visible session rolls over to a new backing session. */
export function clearBootstrapSnapshotOnSessionRollover(params: {
sessionKey?: string;
previousSessionId?: string;
}): void {
if (!params.sessionKey || !params.previousSessionId) {
return;
}
clearBootstrapSnapshot(params.sessionKey);
}
/** Clear bootstrap state after an in-log lifecycle boundary is durably appended. */
export function clearBootstrapSnapshotOnSessionBoundary(params: {
boundaryAppended: boolean;
sessionKey?: string;
}): void {
if (!params.boundaryAppended || !params.sessionKey) {
return;
}
clearBootstrapSnapshot(params.sessionKey);
}