mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 23:01:33 +00:00
* 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
98 lines
2.8 KiB
TypeScript
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);
|
|
}
|