@astralyn/pi 0.82.3 → 0.82.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/cli/config-selector.d.ts.map +1 -1
  3. package/dist/cli/config-selector.js +3 -2
  4. package/dist/cli/config-selector.js.map +1 -1
  5. package/dist/cli/startup-ui.d.ts.map +1 -1
  6. package/dist/cli/startup-ui.js +3 -2
  7. package/dist/cli/startup-ui.js.map +1 -1
  8. package/dist/core/agent-session.d.ts +2 -0
  9. package/dist/core/agent-session.d.ts.map +1 -1
  10. package/dist/core/agent-session.js +33 -14
  11. package/dist/core/agent-session.js.map +1 -1
  12. package/dist/core/extensions/index.d.ts +2 -2
  13. package/dist/core/extensions/index.d.ts.map +1 -1
  14. package/dist/core/extensions/index.js +1 -1
  15. package/dist/core/extensions/index.js.map +1 -1
  16. package/dist/core/extensions/loader.d.ts +1 -1
  17. package/dist/core/extensions/loader.d.ts.map +1 -1
  18. package/dist/core/extensions/loader.js +2 -3
  19. package/dist/core/extensions/loader.js.map +1 -1
  20. package/dist/core/extensions/types.d.ts +18 -0
  21. package/dist/core/extensions/types.d.ts.map +1 -1
  22. package/dist/core/extensions/types.js +11 -0
  23. package/dist/core/extensions/types.js.map +1 -1
  24. package/dist/extensions/index.d.ts.map +1 -1
  25. package/dist/extensions/index.js +2 -0
  26. package/dist/extensions/index.js.map +1 -1
  27. package/dist/extensions/plan/constants.d.ts +31 -0
  28. package/dist/extensions/plan/constants.d.ts.map +1 -0
  29. package/dist/extensions/plan/constants.js +57 -0
  30. package/dist/extensions/plan/constants.js.map +1 -0
  31. package/dist/extensions/plan/index.d.ts +21 -0
  32. package/dist/extensions/plan/index.d.ts.map +1 -0
  33. package/dist/extensions/plan/index.js +291 -0
  34. package/dist/extensions/plan/index.js.map +1 -0
  35. package/dist/extensions/plan/schema.d.ts +15 -0
  36. package/dist/extensions/plan/schema.d.ts.map +1 -0
  37. package/dist/extensions/plan/schema.js +13 -0
  38. package/dist/extensions/plan/schema.js.map +1 -0
  39. package/dist/extensions/plan/state.d.ts +29 -0
  40. package/dist/extensions/plan/state.d.ts.map +1 -0
  41. package/dist/extensions/plan/state.js +74 -0
  42. package/dist/extensions/plan/state.js.map +1 -0
  43. package/dist/extensions/plan/storage.d.ts +18 -0
  44. package/dist/extensions/plan/storage.d.ts.map +1 -0
  45. package/dist/extensions/plan/storage.js +58 -0
  46. package/dist/extensions/plan/storage.js.map +1 -0
  47. package/dist/extensions/question/dialog.d.ts +3 -1
  48. package/dist/extensions/question/dialog.d.ts.map +1 -1
  49. package/dist/extensions/question/dialog.js +106 -52
  50. package/dist/extensions/question/dialog.js.map +1 -1
  51. package/dist/extensions/question/index.d.ts.map +1 -1
  52. package/dist/extensions/question/index.js +2 -2
  53. package/dist/extensions/question/index.js.map +1 -1
  54. package/dist/extensions/question/render-cache.d.ts +9 -0
  55. package/dist/extensions/question/render-cache.d.ts.map +1 -1
  56. package/dist/extensions/question/render-cache.js +22 -0
  57. package/dist/extensions/question/render-cache.js.map +1 -1
  58. package/dist/extensions/question/results.d.ts.map +1 -1
  59. package/dist/extensions/question/results.js +6 -1
  60. package/dist/extensions/question/results.js.map +1 -1
  61. package/dist/extensions/question/schema.d.ts +4 -0
  62. package/dist/extensions/question/schema.d.ts.map +1 -1
  63. package/dist/extensions/question/schema.js +19 -28
  64. package/dist/extensions/question/schema.js.map +1 -1
  65. package/dist/extensions/question/types.d.ts +1 -1
  66. package/dist/extensions/question/types.d.ts.map +1 -1
  67. package/dist/extensions/question/types.js.map +1 -1
  68. package/dist/extensions/rewind/anchor.d.ts +29 -0
  69. package/dist/extensions/rewind/anchor.d.ts.map +1 -0
  70. package/dist/extensions/rewind/anchor.js +35 -0
  71. package/dist/extensions/rewind/anchor.js.map +1 -0
  72. package/dist/extensions/rewind/engine.d.ts +2 -6
  73. package/dist/extensions/rewind/engine.d.ts.map +1 -1
  74. package/dist/extensions/rewind/engine.js +41 -10
  75. package/dist/extensions/rewind/engine.js.map +1 -1
  76. package/dist/extensions/rewind/index.d.ts.map +1 -1
  77. package/dist/extensions/rewind/index.js +55 -21
  78. package/dist/extensions/rewind/index.js.map +1 -1
  79. package/dist/extensions/rewind/menu.d.ts.map +1 -1
  80. package/dist/extensions/rewind/menu.js +7 -2
  81. package/dist/extensions/rewind/menu.js.map +1 -1
  82. package/dist/extensions/rewind/restore.d.ts +26 -9
  83. package/dist/extensions/rewind/restore.d.ts.map +1 -1
  84. package/dist/extensions/rewind/restore.js +58 -16
  85. package/dist/extensions/rewind/restore.js.map +1 -1
  86. package/dist/extensions/subagent/activity.d.ts +1 -0
  87. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  88. package/dist/extensions/subagent/activity.js +20 -4
  89. package/dist/extensions/subagent/activity.js.map +1 -1
  90. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  91. package/dist/extensions/subagent/agents.js +13 -18
  92. package/dist/extensions/subagent/agents.js.map +1 -1
  93. package/dist/extensions/subagent/constants.d.ts +5 -4
  94. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  95. package/dist/extensions/subagent/constants.js +12 -5
  96. package/dist/extensions/subagent/constants.js.map +1 -1
  97. package/dist/extensions/subagent/index.d.ts.map +1 -1
  98. package/dist/extensions/subagent/index.js +12 -5
  99. package/dist/extensions/subagent/index.js.map +1 -1
  100. package/dist/extensions/subagent/render.d.ts.map +1 -1
  101. package/dist/extensions/subagent/render.js +166 -41
  102. package/dist/extensions/subagent/render.js.map +1 -1
  103. package/dist/extensions/subagent/resolve.d.ts +2 -5
  104. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  105. package/dist/extensions/subagent/resolve.js +8 -13
  106. package/dist/extensions/subagent/resolve.js.map +1 -1
  107. package/dist/extensions/subagent/runner.d.ts +3 -1
  108. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  109. package/dist/extensions/subagent/runner.js +115 -66
  110. package/dist/extensions/subagent/runner.js.map +1 -1
  111. package/dist/extensions/subagent/schema.d.ts +0 -6
  112. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  113. package/dist/extensions/subagent/schema.js +3 -4
  114. package/dist/extensions/subagent/schema.js.map +1 -1
  115. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  116. package/dist/extensions/subagent/sdk-runner.js +37 -16
  117. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  118. package/dist/extensions/subagent/types.d.ts +10 -2
  119. package/dist/extensions/subagent/types.d.ts.map +1 -1
  120. package/dist/extensions/subagent/types.js.map +1 -1
  121. package/dist/extensions/todo/constants.d.ts +1 -1
  122. package/dist/extensions/todo/constants.d.ts.map +1 -1
  123. package/dist/extensions/todo/constants.js +29 -4
  124. package/dist/extensions/todo/constants.js.map +1 -1
  125. package/dist/extensions/todo/index.d.ts +1 -1
  126. package/dist/extensions/todo/index.d.ts.map +1 -1
  127. package/dist/extensions/todo/index.js +22 -3
  128. package/dist/extensions/todo/index.js.map +1 -1
  129. package/dist/extensions/todo/schema.d.ts +6 -5
  130. package/dist/extensions/todo/schema.d.ts.map +1 -1
  131. package/dist/extensions/todo/schema.js +20 -9
  132. package/dist/extensions/todo/schema.js.map +1 -1
  133. package/dist/extensions/todo/state.d.ts +5 -1
  134. package/dist/extensions/todo/state.d.ts.map +1 -1
  135. package/dist/extensions/todo/state.js +184 -38
  136. package/dist/extensions/todo/state.js.map +1 -1
  137. package/dist/extensions/todo/view.d.ts.map +1 -1
  138. package/dist/extensions/todo/view.js +10 -6
  139. package/dist/extensions/todo/view.js.map +1 -1
  140. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  141. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  142. package/dist/modes/interactive/interactive-mode.js +36 -2
  143. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  144. package/dist/utils/coalescing-terminal.d.ts +116 -0
  145. package/dist/utils/coalescing-terminal.d.ts.map +1 -0
  146. package/dist/utils/coalescing-terminal.js +256 -0
  147. package/dist/utils/coalescing-terminal.js.map +1 -0
  148. package/docs/bundled/extensions/README.md +1 -0
  149. package/docs/bundled/extensions/plan.md +31 -0
  150. package/docs/bundled/extensions/question.md +9 -7
  151. package/docs/bundled/extensions/rewind.md +19 -8
  152. package/docs/bundled/extensions/subagent.md +16 -16
  153. package/docs/bundled/extensions/todo.md +5 -3
  154. package/docs/compaction.md +6 -2
  155. package/docs/docs.json +4 -0
  156. package/docs/extensions.md +4 -2
  157. package/npm-shrinkwrap.json +2 -2
  158. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/extensions/rewind/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * engine.ts — file-history backup engine for the rewind extension.\n *\n * Ported from Claude Code 2.1.88's src/utils/fileHistory.ts, adapted from its\n * React state-updater to module-level per-session maps. The core idea (and the\n * reason this exists): we back up ONLY the files Pi's edit/write tools are about\n * to modify — one copyFile before the edit — instead of snapshotting the whole\n * work tree. Cost is proportional to \"how many files Pi changed\", not project\n * size, so it never blocks the session-lifecycle critical path and storage stays\n * tiny.\n *\n * Per turn:\n * - beginTurn() (before_agent_start): open a working frame and re-record every\n * already-tracked file at its current (turn-start) state, reusing the latest\n * backup when unchanged (worktree lastSeen → mtime/size/content), creating a\n * new version when not. lastSeen skips backup-blob IO when the worktree has\n * not moved since the last confirm; content compare streams in 64 KiB chunks.\n * - trackEdit() (tool_call edit|write, before the write): back up a *newly*\n * edited file at its pre-edit state into the working frame (null marker when\n * the target does not exist yet, so rewind deletes the created file).\n * - endTurn() (agent_settled): if anything changed, stamp + return the frame\n * to persist; else discard it. agent_settled (not agent_end) so auto-retry /\n * overflow compact-retry / queued follow-ups share one logical turn.\n *\n * Rewind = applySnapshot(): restore every tracked file to the version recorded in\n * the target frame (copy back / delete for null), touching only files that differ.\n *\n * Backups live at <rewindBackupsDir(sessionId)>/<sha256(relpath)[:16]>@v<n>.\n */\nimport { createHash } from \"node:crypto\";\nimport { chmodSync, copyFileSync, mkdirSync, type Stats, statSync } from \"node:fs\";\nimport { chmod, copyFile, link, mkdir, open, readFile, stat, unlink } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative } from \"node:path\";\nimport type { FileBackup, FileHistorySnapshot } from \"./snapshot.ts\";\nimport { backupsDir } from \"./storage.ts\";\n\n/** Cap on retained snapshots per session (matches CC's MAX_SNAPSHOTS). */\nconst MAX_SNAPSHOTS = 100;\n/** Avoid reading very large files solely to compare backup content. */\nconst MAX_CONTENT_BYTES = 25 * 1024 * 1024;\n/** Cap concurrent backup/compare/restore IO so long tracked sets don't thrash handles. */\nconst IO_CONCURRENCY = 16;\n/** Cap bytes loaded for coarse restore line stats (confirm dialog only). */\nconst MAX_DIFF_BYTES = 1_048_576;\n/** Streaming compare chunk size (avoids buffering whole files up to MAX_CONTENT_BYTES). */\nconst COMPARE_CHUNK_BYTES = 64 * 1024;\n\n/** Worktree identity recorded when we last backed up / confirmed a tracking path. */\ninterface SeenStats {\n\tmtimeMs: number;\n\tsize: number;\n\tmode: number;\n}\n\nfunction seenFromStats(st: Stats): SeenStats {\n\treturn { mtimeMs: st.mtimeMs, size: st.size, mode: st.mode };\n}\n\nfunction sameSeen(a: SeenStats, b: Stats): boolean {\n\treturn a.mtimeMs === b.mtimeMs && a.size === b.size && a.mode === b.mode;\n}\n\nexport interface FileHistoryState {\n\t/** Finalized + persisted frames, oldest first. */\n\tsnapshots: FileHistorySnapshot[];\n\t/** All tracking-paths ever edited this session. */\n\ttrackedFiles: Set<string>;\n\t/** Latest backup per tracking path (finalized + in-progress pending versions). */\n\tlatestByTracking: Map<string, FileBackup>;\n\t/**\n\t * Worktree mtime/size/mode at the last backup or unchanged confirmation.\n\t * beginTurn uses this to skip backup-blob stats/content when the worktree\n\t * has not moved since we last recorded it (still stats the worktree once).\n\t */\n\tlastSeen: Map<string, SeenStats>;\n\t/** The current turn's working frame (built across the turn, persisted at endTurn). */\n\tpending: FileHistorySnapshot | null;\n\t/** Whether the pending frame differs from the last finalized frame. */\n\tdirty: boolean;\n\t/** Monotonic activity counter (incremented on every finalized frame). */\n\tseq: number;\n}\n\nexport interface ApplySnapshotOptions {\n\t/**\n\t * Absolute paths already known to differ (e.g. from collectChanges).\n\t * When set, only those paths are restored/deleted and originChanged is skipped.\n\t */\n\tonlyPaths?: ReadonlySet<string>;\n}\n\n// ---- per-session state ----------------------------------------------------\n\nconst states = new Map<string, FileHistoryState>();\nconst cwds = new Map<string, string>();\n\nfunction freshState(): FileHistoryState {\n\treturn {\n\t\tsnapshots: [],\n\t\ttrackedFiles: new Set(),\n\t\tlatestByTracking: new Map(),\n\t\tlastSeen: new Map(),\n\t\tpending: null,\n\t\tdirty: false,\n\t\tseq: 0,\n\t};\n}\n\n/** Run async work over items with a fixed concurrency cap. Preserves result order. */\nasync function mapPool<T, R>(items: readonly T[], concurrency: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n\tif (items.length === 0) return [];\n\tconst results = new Array<R>(items.length);\n\tlet next = 0;\n\tasync function worker(): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst i = next++;\n\t\t\tif (i >= items.length) return;\n\t\t\tresults[i] = await fn(items[i]!);\n\t\t}\n\t}\n\tconst n = Math.min(concurrency, items.length);\n\tawait Promise.all(Array.from({ length: n }, () => worker()));\n\treturn results;\n}\n\n/** Rebuild latestByTracking from finalized frames (oldest → newest, last write wins). */\nfunction rebuildLatestIndex(snapshots: FileHistorySnapshot[]): Map<string, FileBackup> {\n\tconst latest = new Map<string, FileBackup>();\n\tfor (const snap of snapshots) {\n\t\tfor (const [tracking, backup] of Object.entries(snap.trackedFileBackups)) {\n\t\t\tlatest.set(tracking, backup);\n\t\t}\n\t}\n\treturn latest;\n}\n\nfunction getState(sid: string): FileHistoryState {\n\tlet s = states.get(sid);\n\tif (!s) {\n\t\ts = freshState();\n\t\tstates.set(sid, s);\n\t}\n\treturn s;\n}\n\n/** Bind a session id to its cwd (for relative-path keying). Call at session_start. */\nexport function registerSession(sid: string, cwd: string): void {\n\tcwds.set(sid, cwd);\n\tgetState(sid);\n}\n\nexport function disposeSession(sid: string): void {\n\tstates.delete(sid);\n\tcwds.delete(sid);\n}\n\nexport function getSnapshots(sid: string): FileHistorySnapshot[] {\n\treturn states.get(sid)?.snapshots ?? [];\n}\n\nfunction cwdFor(sid: string): string {\n\treturn cwds.get(sid) ?? process.cwd();\n}\n\n// ---- path helpers ---------------------------------------------------------\n\n/** Use the cwd-relative path as the tracking key when inside cwd (shorter, portable). */\nfunction shorten(absPath: string, cwd: string): string {\n\tif (!isAbsolute(absPath)) return absPath;\n\tif (absPath === cwd) return absPath;\n\tconst rel = relative(cwd, absPath);\n\tif (!rel || rel.startsWith(\"..\") || isAbsolute(rel)) return absPath;\n\treturn rel;\n}\n\nfunction expand(tracking: string, cwd: string): string {\n\treturn isAbsolute(tracking) ? tracking : join(cwd, tracking);\n}\n\nfunction backupName(tracking: string, version: number): string {\n\tconst h = createHash(\"sha256\").update(tracking).digest(\"hex\").slice(0, 16);\n\treturn `${h}@v${version}`;\n}\n\nfunction backupPathFor(sid: string, name: string): string {\n\treturn join(backupsDir(sid), name);\n}\n\nfunction latestBackupOf(state: FileHistoryState, tracking: string): FileBackup | undefined {\n\treturn state.latestByTracking.get(tracking);\n}\n\n/** First-ever recorded backup for a file (used when rewinding before it was tracked). */\nfunction firstBackupName(state: FileHistoryState, tracking: string): string | null | undefined {\n\tfor (const snap of state.snapshots) {\n\t\tconst b = snap.trackedFileBackups[tracking];\n\t\tif (b && b.version === 1) return b.backupName;\n\t}\n\treturn undefined;\n}\n\n/** Resolve the backup blob name for `tracking` at `target` (null = did not exist). */\nfunction backupNameForTarget(\n\tstate: FileHistoryState,\n\ttarget: FileHistorySnapshot,\n\ttracking: string,\n): string | null | undefined {\n\tconst tb = target.trackedFileBackups[tracking];\n\tif (tb) return tb.backupName;\n\treturn firstBackupName(state, tracking);\n}\n\n// ---- change detection (ported fileHistory.ts compareStatsAndContent;\n// content path streams via filesEqualChunked instead of full-buffer reads)\n\nfunction isENOENT(e: unknown): boolean {\n\treturn !!e && typeof e === \"object\" && (e as { code?: string }).code === \"ENOENT\";\n}\n\nasync function statOrNull(p: string): Promise<Stats | null> {\n\ttry {\n\t\treturn await stat(p);\n\t} catch (e) {\n\t\tif (isENOENT(e)) return null;\n\t\tthrow e;\n\t}\n}\n\n/**\n * Raw-byte equality without buffering whole files. A utf-8 decode maps invalid\n * sequences to U+FFFD, which can make two DIFFERENT binary files compare equal —\n * and then rewind would silently skip both the re-backup and the restore.\n */\nasync function filesEqualChunked(aPath: string, bPath: string, size: number): Promise<boolean> {\n\tif (size === 0) return true;\n\tconst [aHandle, bHandle] = await Promise.all([open(aPath, \"r\"), open(bPath, \"r\")]);\n\ttry {\n\t\tconst aBuf = Buffer.allocUnsafe(COMPARE_CHUNK_BYTES);\n\t\tconst bBuf = Buffer.allocUnsafe(COMPARE_CHUNK_BYTES);\n\t\tlet remaining = size;\n\t\twhile (remaining > 0) {\n\t\t\tconst want = Math.min(COMPARE_CHUNK_BYTES, remaining);\n\t\t\tconst [aRead, bRead] = await Promise.all([\n\t\t\t\taHandle.read(aBuf, 0, want, null),\n\t\t\t\tbHandle.read(bBuf, 0, want, null),\n\t\t\t]);\n\t\t\tif (aRead.bytesRead !== bRead.bytesRead) return false;\n\t\t\tif (aRead.bytesRead === 0) return remaining === 0;\n\t\t\tif (!aBuf.subarray(0, aRead.bytesRead).equals(bBuf.subarray(0, bRead.bytesRead))) return false;\n\t\t\tremaining -= aRead.bytesRead;\n\t\t}\n\t\treturn true;\n\t} finally {\n\t\tawait Promise.allSettled([aHandle.close(), bHandle.close()]);\n\t}\n}\n\n/** True when the on-disk file differs from its backup blob. */\nasync function originChanged(sid: string, filePath: string, name: string, hint?: Stats): Promise<boolean> {\n\tconst backupPath = backupPathFor(sid, name);\n\tconst orig = hint ?? (await statOrNull(filePath).catch(() => null));\n\tconst back = await statOrNull(backupPath).catch(() => null);\n\n\t// One exists, one missing -> changed.\n\tif ((orig === null) !== (back === null)) return true;\n\tif (orig === null || back === null) return false;\n\tif (orig.mode !== back.mode || orig.size !== back.size) return true;\n\t// Original untouched since the backup was written -> unchanged (skip content read).\n\tif (orig.mtimeMs < back.mtimeMs) return false;\n\t// Memory guard: don't load huge files just to byte-compare; assume changed and\n\t// let createBackup stream a new version instead (copyFile never buffers whole files).\n\tif (orig.size > MAX_CONTENT_BYTES) return true;\n\ttry {\n\t\treturn !(await filesEqualChunked(filePath, backupPath, orig.size));\n\t} catch {\n\t\treturn true;\n\t}\n}\n\n// ---- backup / restore IO --------------------------------------------------\n\n/** Copy the file's current contents into a backup blob; null backup when absent. */\nasync function createBackup(\n\tsid: string,\n\tfilePath: string,\n\ttracking: string,\n\tversion: number,\n\tknown?: Stats | null,\n): Promise<FileBackup> {\n\tlet src: Stats;\n\tif (known === null) return { backupName: null, version };\n\tif (known) {\n\t\tsrc = known;\n\t} else {\n\t\ttry {\n\t\t\tsrc = await stat(filePath);\n\t\t} catch (e) {\n\t\t\tif (isENOENT(e)) return { backupName: null, version };\n\t\t\tthrow e;\n\t\t}\n\t}\n\tconst name = backupName(tracking, version);\n\tconst dest = backupPathFor(sid, name);\n\ttry {\n\t\tawait copyFile(filePath, dest);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tawait mkdir(dirname(dest), { recursive: true });\n\t\tawait copyFile(filePath, dest);\n\t}\n\ttry {\n\t\tawait chmod(dest, src.mode);\n\t} catch {\n\t\t// best-effort (e.g. Windows); content is what matters\n\t}\n\treturn { backupName: name, version };\n}\n\n/**\n * Synchronous backup, used on the edit critical path (tool_call). Doing the copy\n * synchronously guarantees it completes before the hook returns control to the\n * host — so the backup always captures the file's PRE-edit contents, regardless\n * of whether the host awaits the hook before running the tool.\n *\n * `known` reuses a pre-stat from the caller (`null` = known ENOENT) so trackEdit\n * can record lastSeen without a second statSync on the critical path.\n */\nfunction createBackupSync(\n\tsid: string,\n\tfilePath: string,\n\ttracking: string,\n\tversion: number,\n\tknown?: Stats | null,\n): FileBackup {\n\tlet src: Stats;\n\tif (known === null) return { backupName: null, version };\n\tif (known) {\n\t\tsrc = known;\n\t} else {\n\t\ttry {\n\t\t\tsrc = statSync(filePath);\n\t\t} catch (e) {\n\t\t\tif (isENOENT(e)) return { backupName: null, version };\n\t\t\tthrow e;\n\t\t}\n\t}\n\tconst name = backupName(tracking, version);\n\tconst dest = backupPathFor(sid, name);\n\ttry {\n\t\tcopyFileSync(filePath, dest);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tmkdirSync(dirname(dest), { recursive: true });\n\t\tcopyFileSync(filePath, dest);\n\t}\n\ttry {\n\t\tchmodSync(dest, src.mode);\n\t} catch {\n\t\t// best-effort\n\t}\n\treturn { backupName: name, version };\n}\n\nasync function restoreBackup(sid: string, filePath: string, name: string): Promise<void> {\n\tconst backupPath = backupPathFor(sid, name);\n\tlet back: Stats;\n\ttry {\n\t\tback = await stat(backupPath);\n\t} catch {\n\t\treturn; // backup vanished; leave the file untouched\n\t}\n\ttry {\n\t\tawait copyFile(backupPath, filePath);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait copyFile(backupPath, filePath);\n\t}\n\ttry {\n\t\tawait chmod(filePath, back.mode);\n\t} catch {\n\t\t// best-effort\n\t}\n}\n\n// ---- per-turn lifecycle ---------------------------------------------------\n\n/**\n * Open the working frame for a turn and re-record every tracked file at its\n * current state (reuse latest backup when unchanged, new version when changed).\n * Sets state.dirty when any file produced a new version.\n */\nexport async function beginTurn(sid: string): Promise<void> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst backups: Record<string, FileBackup> = {};\n\tlet dirty = false;\n\n\tawait mapPool(Array.from(state.trackedFiles), IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst latest = latestBackupOf(state, tracking);\n\t\t\tconst nextVersion = latest ? latest.version + 1 : 1;\n\t\t\tconst st = await statOrNull(filePath);\n\t\t\tif (!st) {\n\t\t\t\t// Already recorded absent in the latest frame -> reuse it. Allocating a\n\t\t\t\t// fresh null version every turn would pin dirty=true forever once a\n\t\t\t\t// tracked file is deleted, defeating the \"skip unchanged turns\" check\n\t\t\t\t// and flushing real checkpoints out of the capped ring.\n\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\tif (latest && latest.backupName === null) {\n\t\t\t\t\tbackups[tracking] = latest;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst absent: FileBackup = { backupName: null, version: nextVersion };\n\t\t\t\tbackups[tracking] = absent;\n\t\t\t\tstate.latestByTracking.set(tracking, absent);\n\t\t\t\tdirty = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Worktree identity unchanged since last backup/confirm → reuse without\n\t\t\t// opening the backup blob (still paid one worktree stat above).\n\t\t\tconst seen = state.lastSeen.get(tracking);\n\t\t\tif (latest && latest.backupName !== null && seen && sameSeen(seen, st)) {\n\t\t\t\tbackups[tracking] = latest;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (latest && latest.backupName !== null && !(await originChanged(sid, filePath, latest.backupName, st))) {\n\t\t\t\tbackups[tracking] = latest; // unchanged -> reuse\n\t\t\t\tstate.lastSeen.set(tracking, seenFromStats(st));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst created = await createBackup(sid, filePath, tracking, nextVersion, st);\n\t\t\tbackups[tracking] = created;\n\t\t\tstate.latestByTracking.set(tracking, created);\n\t\t\t// Record the pre-backup worktree identity (copyFile does not change it).\n\t\t\tstate.lastSeen.set(tracking, seenFromStats(st));\n\t\t\tdirty = true;\n\t\t} catch {\n\t\t\t// skip this file; never break the turn\n\t\t}\n\t});\n\n\tstate.pending = { v: 1, userEntryId: \"\", turnId: \"\", prompt: \"\", trackedFileBackups: backups, timestamp: \"\" };\n\tstate.dirty = dirty;\n}\n\n/**\n * Back up a file about to be edited/written, if not already captured this turn.\n * Call from the tool_call hook BEFORE the edit lands. ENOENT target -> null\n * marker (rewind deletes the created file). Synchronous so the backup is on disk\n * before the hook returns (see createBackupSync).\n */\nexport function trackEdit(sid: string, absPath: string): void {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst tracking = shorten(absPath, cwd);\n\n\tif (!state.pending) {\n\t\tstate.pending = { v: 1, userEntryId: \"\", turnId: \"\", prompt: \"\", trackedFileBackups: {}, timestamp: \"\" };\n\t}\n\tif (state.pending.trackedFileBackups[tracking]) return; // already captured this turn\n\n\tconst latest = latestBackupOf(state, tracking);\n\tconst version = latest ? latest.version + 1 : 1;\n\tconst filePath = expand(tracking, cwd);\n\t// One pre-edit stat: drives the backup and lastSeen (null = new-file marker).\n\tlet preEdit: Stats | null = null;\n\ttry {\n\t\tpreEdit = statSync(filePath);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t}\n\tconst backup = createBackupSync(sid, filePath, tracking, version, preEdit);\n\tstate.pending.trackedFileBackups[tracking] = backup;\n\tstate.trackedFiles.add(tracking);\n\tstate.latestByTracking.set(tracking, backup);\n\tif (preEdit) state.lastSeen.set(tracking, seenFromStats(preEdit));\n\telse state.lastSeen.delete(tracking);\n\tstate.dirty = true;\n}\n\n/**\n * Finalize the turn. Returns the snapshot to persist (caller appendEntry's it),\n * or null when nothing changed this turn. Pushes finalized frames into the\n * in-memory ring (capped at MAX_SNAPSHOTS).\n */\nexport function endTurn(\n\tsid: string,\n\tuserEntryId: string,\n\tturnId: string,\n\tprompt: string,\n\ttimestamp: string,\n\tmaxSnapshots = MAX_SNAPSHOTS,\n): FileHistorySnapshot | null {\n\tconst state = getState(sid);\n\tconst pending = state.pending;\n\tstate.pending = null;\n\tif (!pending || !state.dirty) {\n\t\tstate.dirty = false;\n\t\treturn null;\n\t}\n\tstate.dirty = false;\n\tconst frame: FileHistorySnapshot = { ...pending, userEntryId, turnId, prompt, timestamp };\n\tstate.snapshots.push(frame);\n\tif (state.snapshots.length > maxSnapshots) {\n\t\tconst dropped = state.snapshots.slice(0, state.snapshots.length - maxSnapshots);\n\t\tstate.snapshots = state.snapshots.slice(-maxSnapshots);\n\t\tvoid pruneDroppedBlobs(sid, dropped, state.snapshots);\n\t}\n\tstate.seq++;\n\treturn frame;\n}\n\n/**\n * Best-effort deletion of backup blobs referenced ONLY by frames dropped from\n * the capped ring. Backups are reused across frames (an unchanged file keeps\n * pointing at the same version), so a blob is unlinked only when no retained\n * frame still references it. Without this, the cap trims the in-memory index\n * while the blob files accumulate for the whole session — gc.ts only reclaims\n * whole session directories.\n */\nasync function pruneDroppedBlobs(\n\tsid: string,\n\tdropped: FileHistorySnapshot[],\n\tretained: FileHistorySnapshot[],\n): Promise<void> {\n\tconst live = new Set<string>();\n\tfor (const snap of retained) {\n\t\tfor (const b of Object.values(snap.trackedFileBackups)) {\n\t\t\tif (b.backupName) live.add(b.backupName);\n\t\t}\n\t}\n\tconst doomed = new Set<string>();\n\tfor (const snap of dropped) {\n\t\tfor (const b of Object.values(snap.trackedFileBackups)) {\n\t\t\tif (b.backupName && !live.has(b.backupName)) doomed.add(b.backupName);\n\t\t}\n\t}\n\tif (doomed.size === 0) return;\n\tawait Promise.allSettled(Array.from(doomed, (name) => unlink(backupPathFor(sid, name))));\n}\n\n// ---- rewind ---------------------------------------------------------------\n\n/**\n * Absolute paths that restoring to `target` would change on disk (empty =\n * nothing to do). Same walk applySnapshot performs, without writing — the\n * caller can both count and preview the files from one pass. Concurrent with\n * IO_CONCURRENCY; result order matches trackedFiles insertion order.\n */\nexport async function collectChanges(sid: string, target: FileHistorySnapshot): Promise<string[]> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst trackings = Array.from(state.trackedFiles);\n\tconst results = await mapPool(trackings, IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return null;\n\t\t\tif (name === null) {\n\t\t\t\treturn (await statOrNull(filePath)) ? filePath : null;\n\t\t\t}\n\t\t\treturn (await originChanged(sid, filePath, name)) ? filePath : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t});\n\treturn results.filter((p): p is string => p !== null);\n}\n\nexport interface CoarseDiffStats {\n\t/** Absolute paths that would change (same as collectChanges). */\n\tpaths: string[];\n\t/**\n\t * Coarse line insertions when restoring (lines present in backup, not in\n\t * current). Bag-of-lines, not a true Myers diff — confirm UI only.\n\t */\n\tinsertions: number;\n\t/** Coarse line deletions when restoring (lines present in current, not in backup). */\n\tdeletions: number;\n}\n\n/**\n * Coarse +N/−M line stats for a restore confirm dialog. Only loads text for\n * paths already known to differ (from collectChanges). Oversized files still\n * appear in `paths` but contribute 0 to line totals. No extra dependency.\n */\nexport async function collectChangeDiffStats(\n\tsid: string,\n\ttarget: FileHistorySnapshot,\n\tchangedPaths: readonly string[],\n): Promise<CoarseDiffStats> {\n\tif (changedPaths.length === 0) {\n\t\treturn { paths: [], insertions: 0, deletions: 0 };\n\t}\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\t// Reverse map abs path → tracking key for the known changed set.\n\tconst trackingByAbs = new Map<string, string>();\n\tfor (const tracking of state.trackedFiles) {\n\t\ttrackingByAbs.set(expand(tracking, cwd), tracking);\n\t}\n\n\tconst results = await mapPool([...changedPaths], IO_CONCURRENCY, async (filePath) => {\n\t\ttry {\n\t\t\tconst tracking = trackingByAbs.get(filePath);\n\t\t\tif (!tracking) return { insertions: 0, deletions: 0 };\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return { insertions: 0, deletions: 0 };\n\t\t\treturn await coarseLineDelta(sid, filePath, name);\n\t\t} catch {\n\t\t\treturn { insertions: 0, deletions: 0 };\n\t\t}\n\t});\n\n\tlet insertions = 0;\n\tlet deletions = 0;\n\tfor (const r of results) {\n\t\tinsertions += r.insertions;\n\t\tdeletions += r.deletions;\n\t}\n\treturn { paths: [...changedPaths], insertions, deletions };\n}\n\n/**\n * Bag-of-lines delta for current worktree → backup target (restore direction).\n * insertions = lines that appear after restore; deletions = lines removed.\n */\nasync function coarseLineDelta(\n\tsid: string,\n\tfilePath: string,\n\tbackupName: string | null,\n): Promise<{ insertions: number; deletions: number }> {\n\tconst currentText = await readTextCapped(filePath, MAX_DIFF_BYTES);\n\tif (backupName === null) {\n\t\t// Snapshot: file did not exist → restore deletes it.\n\t\tif (currentText === null) return { insertions: 0, deletions: 0 };\n\t\treturn { insertions: 0, deletions: countLines(currentText) };\n\t}\n\tconst backupText = await readTextCapped(backupPathFor(sid, backupName), MAX_DIFF_BYTES);\n\tif (currentText === null && backupText === null) return { insertions: 0, deletions: 0 };\n\tif (currentText === null) return { insertions: countLines(backupText ?? \"\"), deletions: 0 };\n\tif (backupText === null) return { insertions: 0, deletions: countLines(currentText) };\n\treturn bagOfLinesDelta(currentText, backupText);\n}\n\n/** null = missing or oversize/unreadable (skip line contrib). */\nasync function readTextCapped(filePath: string, maxBytes: number): Promise<string | null> {\n\ttry {\n\t\tconst st = await statOrNull(filePath);\n\t\tif (!st) return null;\n\t\tif (st.size > maxBytes) return null;\n\t\tconst buf = await readFile(filePath);\n\t\t// Skip obvious binary (NUL in the first 8K).\n\t\tconst sample = buf.subarray(0, Math.min(buf.length, 8192));\n\t\tif (sample.includes(0)) return null;\n\t\treturn buf.toString(\"utf8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction countLines(text: string): number {\n\tif (text.length === 0) return 0;\n\tlet n = 1;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text.charCodeAt(i) === 10 /* \\n */) n++;\n\t}\n\t// Trailing newline does not add an extra empty \"line\" for display counts.\n\tif (text.endsWith(\"\\n\")) n--;\n\treturn Math.max(n, 0);\n}\n\n/**\n * Multiset frequency delta: from → to.\n * deletions = lines over-represented in `from`; insertions = over-represented in `to`.\n */\nfunction bagOfLinesDelta(from: string, to: string): { insertions: number; deletions: number } {\n\tconst fromCounts = lineFrequency(from);\n\tconst toCounts = lineFrequency(to);\n\tlet insertions = 0;\n\tlet deletions = 0;\n\tconst keys = new Set([...fromCounts.keys(), ...toCounts.keys()]);\n\tfor (const key of keys) {\n\t\tconst a = fromCounts.get(key) ?? 0;\n\t\tconst b = toCounts.get(key) ?? 0;\n\t\tif (b > a) insertions += b - a;\n\t\telse if (a > b) deletions += a - b;\n\t}\n\treturn { insertions, deletions };\n}\n\nfunction lineFrequency(text: string): Map<string, number> {\n\tconst map = new Map<string, number>();\n\t// Preserve empty file as zero lines; otherwise split including last empty.\n\tif (text.length === 0) return map;\n\tconst lines = text.split(\"\\n\");\n\tif (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop();\n\tfor (const line of lines) {\n\t\tmap.set(line, (map.get(line) ?? 0) + 1);\n\t}\n\treturn map;\n}\n\n/**\n * Restore the work tree to `target`. Returns the list of changed file paths.\n * Pass `onlyPaths` (from a prior collectChanges) to skip a second originChanged\n * pass and only touch those absolute paths.\n */\nexport async function applySnapshot(\n\tsid: string,\n\ttarget: FileHistorySnapshot,\n\topts?: ApplySnapshotOptions,\n): Promise<string[]> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst only = opts?.onlyPaths;\n\tconst trackings = Array.from(state.trackedFiles).filter((tracking) => {\n\t\tif (!only) return true;\n\t\treturn only.has(expand(tracking, cwd));\n\t});\n\n\tconst results = await mapPool(trackings, IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return null; // can't resolve -> leave file alone\n\t\t\tif (name === null) {\n\t\t\t\ttry {\n\t\t\t\t\tawait unlink(filePath);\n\t\t\t\t\t// Worktree no longer matches any recorded identity.\n\t\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\t\treturn filePath;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (!isENOENT(e)) throw e;\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// onlyPaths already proven different at confirm time — skip re-compare.\n\t\t\tif (only || (await originChanged(sid, filePath, name))) {\n\t\t\t\tawait restoreBackup(sid, filePath, name);\n\t\t\t\t// Drop stale lastSeen: worktree now matches the target blob, not the\n\t\t\t\t// latest backup identity beginTurn compares against.\n\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\treturn filePath;\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t});\n\treturn results.filter((p): p is string => p !== null);\n}\n\n// ---- persistence rebuild + resume migration -------------------------------\n\n/** Rebuild in-memory state from snapshots persisted in the session JSONL. */\nexport function restoreStateFromSnapshots(\n\tsid: string,\n\tcwd: string,\n\tsnapshots: FileHistorySnapshot[],\n\tmaxSnapshots = MAX_SNAPSHOTS,\n): void {\n\tcwds.set(sid, cwd);\n\t// Apply the same cap on reload so a long session's JSONL can't reinflate the\n\t// in-memory ring past the limit. trackedFiles is rebuilt from the retained\n\t// frames only (older frames are unreachable for rewind anyway); blobs only\n\t// those frames referenced are pruned best-effort, mirroring endTurn.\n\tconst retained = snapshots.length > maxSnapshots ? snapshots.slice(-maxSnapshots) : snapshots;\n\tif (retained.length < snapshots.length) {\n\t\tvoid pruneDroppedBlobs(sid, snapshots.slice(0, snapshots.length - retained.length), retained);\n\t}\n\tconst trackedFiles = new Set<string>();\n\tfor (const snap of retained) {\n\t\tfor (const key of Object.keys(snap.trackedFileBackups)) trackedFiles.add(key);\n\t}\n\tstates.set(sid, {\n\t\tsnapshots: [...retained],\n\t\ttrackedFiles,\n\t\tlatestByTracking: rebuildLatestIndex(retained),\n\t\t// lastSeen is process-local; after reload we re-establish it on the first\n\t\t// beginTurn via originChanged / createBackup.\n\t\tlastSeen: new Map(),\n\t\tpending: null,\n\t\tdirty: false,\n\t\tseq: retained.length,\n\t});\n}\n\n/**\n * Hard-link this session's backup blobs from a previous session's directory\n * (resume/fork carries the conversation + its snapshot index, but the blobs live\n * under the old session id). Falls back to copy. No-op when ids match.\n */\nexport async function migrateBackupsFromSession(\n\tprevSid: string,\n\tsid: string,\n\tsnapshots: FileHistorySnapshot[],\n): Promise<void> {\n\tif (!prevSid || prevSid === sid) return;\n\tconst destDir = backupsDir(sid);\n\tawait mkdir(destDir, { recursive: true });\n\tawait Promise.allSettled(\n\t\tsnapshots.flatMap((snap) =>\n\t\t\tObject.values(snap.trackedFileBackups)\n\t\t\t\t.filter((b): b is FileBackup & { backupName: string } => b.backupName !== null)\n\t\t\t\t.map(async ({ backupName: name }) => {\n\t\t\t\t\tconst from = join(backupsDir(prevSid), name);\n\t\t\t\t\tconst to = join(destDir, name);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait link(from, to);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconst code = (e as { code?: string }).code;\n\t\t\t\t\t\tif (code === \"EEXIST\") return;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait copyFile(from, to);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// best-effort; a missing blob just means that version can't restore\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t),\n\t);\n}\n"]}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/extensions/rewind/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * engine.ts — file-history backup engine for the rewind extension.\n *\n * Ported from Claude Code 2.1.88's src/utils/fileHistory.ts, adapted from its\n * React state-updater to module-level per-session maps. The core idea (and the\n * reason this exists): we back up ONLY the files Pi's edit/write tools are about\n * to modify — one copyFile before the edit — instead of snapshotting the whole\n * work tree. Cost is proportional to \"how many files Pi changed\", not project\n * size, so it never blocks the session-lifecycle critical path and storage stays\n * tiny.\n *\n * Per turn:\n * - beginTurn() (before_agent_start): open a working frame and re-record every\n * already-tracked file at its current (turn-start) state, reusing the latest\n * backup when unchanged (worktree lastSeen → mtime/size/content), creating a\n * new version when not. lastSeen skips backup-blob IO when the worktree has\n * not moved since the last confirm; content compare streams in 64 KiB chunks.\n * - trackEdit() (tool_call edit|write, before the write): back up a *newly*\n * edited file at its pre-edit state into the working frame (null marker when\n * the target does not exist yet, so rewind deletes the created file).\n * - endTurn() (agent_settled): if anything changed AND the caller resolved a\n * turn anchor, stamp + return the frame to persist; else discard it (an\n * unanchored frame could never be matched by /tree and would exist only in\n * memory). agent_settled (not agent_end) so auto-retry / overflow\n * compact-retry / queued follow-ups share one logical turn.\n *\n * Rewind = applySnapshot(): restore every tracked file to the version recorded in\n * the target frame (copy back / delete for null), touching only files that differ.\n *\n * Backups live at <rewindBackupsDir(sessionId)>/<sha256(relpath)[:16]>@v<n>.\n */\nimport { createHash } from \"node:crypto\";\nimport { chmodSync, copyFileSync, mkdirSync, type Stats, statSync } from \"node:fs\";\nimport { chmod, copyFile, link, mkdir, open, readFile, stat, unlink } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative } from \"node:path\";\nimport type { FileBackup, FileHistorySnapshot } from \"./snapshot.ts\";\nimport { backupsDir } from \"./storage.ts\";\n\n/** Cap on retained snapshots per session (matches CC's MAX_SNAPSHOTS). */\nconst MAX_SNAPSHOTS = 100;\n/** Avoid reading very large files solely to compare backup content. */\nconst MAX_CONTENT_BYTES = 25 * 1024 * 1024;\n/** Cap concurrent backup/compare/restore IO so long tracked sets don't thrash handles. */\nconst IO_CONCURRENCY = 16;\n/** Cap bytes loaded for coarse restore line stats (confirm dialog only). */\nconst MAX_DIFF_BYTES = 1_048_576;\n/** Streaming compare chunk size (avoids buffering whole files up to MAX_CONTENT_BYTES). */\nconst COMPARE_CHUNK_BYTES = 64 * 1024;\n\n/** Worktree identity recorded when we last backed up / confirmed a tracking path. */\ninterface SeenStats {\n\tmtimeMs: number;\n\tsize: number;\n\tmode: number;\n}\n\nfunction seenFromStats(st: Stats): SeenStats {\n\treturn { mtimeMs: st.mtimeMs, size: st.size, mode: st.mode };\n}\n\nfunction sameSeen(a: SeenStats, b: Stats): boolean {\n\treturn a.mtimeMs === b.mtimeMs && a.size === b.size && a.mode === b.mode;\n}\n\nexport interface FileHistoryState {\n\t/** Finalized + persisted frames, oldest first. */\n\tsnapshots: FileHistorySnapshot[];\n\t/** All tracking-paths ever edited this session. */\n\ttrackedFiles: Set<string>;\n\t/** Latest backup per tracking path (finalized + in-progress pending versions). */\n\tlatestByTracking: Map<string, FileBackup>;\n\t/**\n\t * Worktree mtime/size/mode at the last backup or unchanged confirmation.\n\t * beginTurn uses this to skip backup-blob stats/content when the worktree\n\t * has not moved since we last recorded it (still stats the worktree once).\n\t */\n\tlastSeen: Map<string, SeenStats>;\n\t/** The current turn's working frame (built across the turn, persisted at endTurn). */\n\tpending: FileHistorySnapshot | null;\n\t/** Whether the pending frame differs from the last finalized frame. */\n\tdirty: boolean;\n\t/** Monotonic activity counter (incremented on every finalized frame). */\n\tseq: number;\n}\n\nexport interface ApplySnapshotOptions {\n\t/**\n\t * Absolute paths already known to differ (e.g. from collectChanges).\n\t * When set, only those paths are restored/deleted and originChanged is skipped.\n\t */\n\tonlyPaths?: ReadonlySet<string>;\n}\n\n// ---- per-session state ----------------------------------------------------\n\nconst states = new Map<string, FileHistoryState>();\nconst cwds = new Map<string, string>();\n\nfunction freshState(): FileHistoryState {\n\treturn {\n\t\tsnapshots: [],\n\t\ttrackedFiles: new Set(),\n\t\tlatestByTracking: new Map(),\n\t\tlastSeen: new Map(),\n\t\tpending: null,\n\t\tdirty: false,\n\t\tseq: 0,\n\t};\n}\n\n/** Run async work over items with a fixed concurrency cap. Preserves result order. */\nasync function mapPool<T, R>(items: readonly T[], concurrency: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n\tif (items.length === 0) return [];\n\tconst results = new Array<R>(items.length);\n\tlet next = 0;\n\tasync function worker(): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst i = next++;\n\t\t\tif (i >= items.length) return;\n\t\t\tresults[i] = await fn(items[i]!);\n\t\t}\n\t}\n\tconst n = Math.min(concurrency, items.length);\n\tawait Promise.all(Array.from({ length: n }, () => worker()));\n\treturn results;\n}\n\n/** Rebuild latestByTracking from finalized frames (oldest → newest, last write wins). */\nfunction rebuildLatestIndex(snapshots: FileHistorySnapshot[]): Map<string, FileBackup> {\n\tconst latest = new Map<string, FileBackup>();\n\tfor (const snap of snapshots) {\n\t\tfor (const [tracking, backup] of Object.entries(snap.trackedFileBackups)) {\n\t\t\tlatest.set(tracking, backup);\n\t\t}\n\t}\n\treturn latest;\n}\n\nfunction getState(sid: string): FileHistoryState {\n\tlet s = states.get(sid);\n\tif (!s) {\n\t\ts = freshState();\n\t\tstates.set(sid, s);\n\t}\n\treturn s;\n}\n\n/** Bind a session id to its cwd (for relative-path keying). Call at session_start. */\nexport function registerSession(sid: string, cwd: string): void {\n\tcwds.set(sid, cwd);\n\tgetState(sid);\n}\n\nexport function disposeSession(sid: string): void {\n\tstates.delete(sid);\n\tcwds.delete(sid);\n}\n\nexport function getSnapshots(sid: string): FileHistorySnapshot[] {\n\treturn states.get(sid)?.snapshots ?? [];\n}\n\nfunction cwdFor(sid: string): string {\n\treturn cwds.get(sid) ?? process.cwd();\n}\n\n// ---- path helpers ---------------------------------------------------------\n\n/** Use the cwd-relative path as the tracking key when inside cwd (shorter, portable). */\nfunction shorten(absPath: string, cwd: string): string {\n\tif (!isAbsolute(absPath)) return absPath;\n\tif (absPath === cwd) return absPath;\n\tconst rel = relative(cwd, absPath);\n\tif (!rel || rel.startsWith(\"..\") || isAbsolute(rel)) return absPath;\n\treturn rel;\n}\n\nfunction expand(tracking: string, cwd: string): string {\n\treturn isAbsolute(tracking) ? tracking : join(cwd, tracking);\n}\n\nfunction backupName(tracking: string, version: number): string {\n\tconst h = createHash(\"sha256\").update(tracking).digest(\"hex\").slice(0, 16);\n\treturn `${h}@v${version}`;\n}\n\nfunction backupPathFor(sid: string, name: string): string {\n\treturn join(backupsDir(sid), name);\n}\n\nfunction latestBackupOf(state: FileHistoryState, tracking: string): FileBackup | undefined {\n\treturn state.latestByTracking.get(tracking);\n}\n\n/** First-ever recorded backup for a file (used when rewinding before it was tracked). */\nfunction firstBackupName(state: FileHistoryState, tracking: string): string | null | undefined {\n\tfor (const snap of state.snapshots) {\n\t\tconst b = snap.trackedFileBackups[tracking];\n\t\tif (b && b.version === 1) return b.backupName;\n\t}\n\treturn undefined;\n}\n\n/** Resolve the backup blob name for `tracking` at `target` (null = did not exist). */\nfunction backupNameForTarget(\n\tstate: FileHistoryState,\n\ttarget: FileHistorySnapshot,\n\ttracking: string,\n): string | null | undefined {\n\tconst tb = target.trackedFileBackups[tracking];\n\tif (tb) return tb.backupName;\n\treturn firstBackupName(state, tracking);\n}\n\n// ---- change detection (ported fileHistory.ts compareStatsAndContent;\n// content path streams via filesEqualChunked instead of full-buffer reads)\n\nfunction isENOENT(e: unknown): boolean {\n\treturn !!e && typeof e === \"object\" && (e as { code?: string }).code === \"ENOENT\";\n}\n\nasync function statOrNull(p: string): Promise<Stats | null> {\n\ttry {\n\t\treturn await stat(p);\n\t} catch (e) {\n\t\tif (isENOENT(e)) return null;\n\t\tthrow e;\n\t}\n}\n\n/**\n * Raw-byte equality without buffering whole files. A utf-8 decode maps invalid\n * sequences to U+FFFD, which can make two DIFFERENT binary files compare equal —\n * and then rewind would silently skip both the re-backup and the restore.\n */\nasync function filesEqualChunked(aPath: string, bPath: string, size: number): Promise<boolean> {\n\tif (size === 0) return true;\n\tconst [aHandle, bHandle] = await Promise.all([open(aPath, \"r\"), open(bPath, \"r\")]);\n\ttry {\n\t\tconst aBuf = Buffer.allocUnsafe(COMPARE_CHUNK_BYTES);\n\t\tconst bBuf = Buffer.allocUnsafe(COMPARE_CHUNK_BYTES);\n\t\tlet remaining = size;\n\t\twhile (remaining > 0) {\n\t\t\tconst want = Math.min(COMPARE_CHUNK_BYTES, remaining);\n\t\t\tconst [aRead, bRead] = await Promise.all([\n\t\t\t\taHandle.read(aBuf, 0, want, null),\n\t\t\t\tbHandle.read(bBuf, 0, want, null),\n\t\t\t]);\n\t\t\tif (aRead.bytesRead !== bRead.bytesRead) return false;\n\t\t\tif (aRead.bytesRead === 0) return remaining === 0;\n\t\t\tif (!aBuf.subarray(0, aRead.bytesRead).equals(bBuf.subarray(0, bRead.bytesRead))) return false;\n\t\t\tremaining -= aRead.bytesRead;\n\t\t}\n\t\treturn true;\n\t} finally {\n\t\tawait Promise.allSettled([aHandle.close(), bHandle.close()]);\n\t}\n}\n\n/** True when the on-disk file differs from its backup blob. */\nasync function originChanged(\n\tsid: string,\n\tfilePath: string,\n\tname: string,\n\topts?: { hint?: Stats; trustMtime?: boolean },\n): Promise<boolean> {\n\tconst backupPath = backupPathFor(sid, name);\n\tconst orig = opts?.hint ?? (await statOrNull(filePath).catch(() => null));\n\tconst back = await statOrNull(backupPath).catch(() => null);\n\n\t// One exists, one missing -> changed.\n\tif ((orig === null) !== (back === null)) return true;\n\tif (orig === null || back === null) return false;\n\tif (orig.mode !== back.mode || orig.size !== back.size) return true;\n\t// Original untouched since the backup was written -> unchanged (skip content\n\t// read). Trusted only on the beginTurn path (worst case there: one redundant\n\t// version). Restore-side change detection must not be fooled by a same-size\n\t// content swap that carries an older mtime (archive extraction, touch -d).\n\tif (opts?.trustMtime && orig.mtimeMs < back.mtimeMs) return false;\n\t// Memory guard: don't load huge files just to byte-compare; assume changed and\n\t// let createBackup stream a new version instead (copyFile never buffers whole files).\n\tif (orig.size > MAX_CONTENT_BYTES) return true;\n\ttry {\n\t\treturn !(await filesEqualChunked(filePath, backupPath, orig.size));\n\t} catch {\n\t\treturn true;\n\t}\n}\n\n// ---- backup / restore IO --------------------------------------------------\n\n/** Copy the file's current contents into a backup blob; null backup when absent. */\nasync function createBackup(\n\tsid: string,\n\tfilePath: string,\n\ttracking: string,\n\tversion: number,\n\tknown?: Stats | null,\n): Promise<FileBackup> {\n\tlet src: Stats;\n\tif (known === null) return { backupName: null, version };\n\tif (known) {\n\t\tsrc = known;\n\t} else {\n\t\ttry {\n\t\t\tsrc = await stat(filePath);\n\t\t} catch (e) {\n\t\t\tif (isENOENT(e)) return { backupName: null, version };\n\t\t\tthrow e;\n\t\t}\n\t}\n\tconst name = backupName(tracking, version);\n\tconst dest = backupPathFor(sid, name);\n\ttry {\n\t\tawait copyFile(filePath, dest);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tawait mkdir(dirname(dest), { recursive: true });\n\t\tawait copyFile(filePath, dest);\n\t}\n\ttry {\n\t\tawait chmod(dest, src.mode);\n\t} catch {\n\t\t// best-effort (e.g. Windows); content is what matters\n\t}\n\treturn { backupName: name, version };\n}\n\n/**\n * Synchronous backup, used on the edit critical path (tool_call). Doing the copy\n * synchronously guarantees it completes before the hook returns control to the\n * host — so the backup always captures the file's PRE-edit contents, regardless\n * of whether the host awaits the hook before running the tool.\n *\n * `known` reuses a pre-stat from the caller (`null` = known ENOENT) so trackEdit\n * can record lastSeen without a second statSync on the critical path.\n */\nfunction createBackupSync(\n\tsid: string,\n\tfilePath: string,\n\ttracking: string,\n\tversion: number,\n\tknown?: Stats | null,\n): FileBackup {\n\tlet src: Stats;\n\tif (known === null) return { backupName: null, version };\n\tif (known) {\n\t\tsrc = known;\n\t} else {\n\t\ttry {\n\t\t\tsrc = statSync(filePath);\n\t\t} catch (e) {\n\t\t\tif (isENOENT(e)) return { backupName: null, version };\n\t\t\tthrow e;\n\t\t}\n\t}\n\tconst name = backupName(tracking, version);\n\tconst dest = backupPathFor(sid, name);\n\ttry {\n\t\tcopyFileSync(filePath, dest);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tmkdirSync(dirname(dest), { recursive: true });\n\t\tcopyFileSync(filePath, dest);\n\t}\n\ttry {\n\t\tchmodSync(dest, src.mode);\n\t} catch {\n\t\t// best-effort\n\t}\n\treturn { backupName: name, version };\n}\n\nasync function restoreBackup(sid: string, filePath: string, name: string): Promise<void> {\n\tconst backupPath = backupPathFor(sid, name);\n\tlet back: Stats;\n\ttry {\n\t\tback = await stat(backupPath);\n\t} catch {\n\t\treturn; // backup vanished; leave the file untouched\n\t}\n\ttry {\n\t\tawait copyFile(backupPath, filePath);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait copyFile(backupPath, filePath);\n\t}\n\ttry {\n\t\tawait chmod(filePath, back.mode);\n\t} catch {\n\t\t// best-effort\n\t}\n}\n\n// ---- per-turn lifecycle ---------------------------------------------------\n\n/**\n * Open the working frame for a turn and re-record every tracked file at its\n * current state (reuse latest backup when unchanged, new version when changed).\n * Sets state.dirty when any file produced a new version.\n */\nexport async function beginTurn(sid: string): Promise<void> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst backups: Record<string, FileBackup> = {};\n\tlet dirty = false;\n\n\tawait mapPool(Array.from(state.trackedFiles), IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst latest = latestBackupOf(state, tracking);\n\t\t\tconst nextVersion = latest ? latest.version + 1 : 1;\n\t\t\tconst st = await statOrNull(filePath);\n\t\t\tif (!st) {\n\t\t\t\t// Already recorded absent in the latest frame -> reuse it. Allocating a\n\t\t\t\t// fresh null version every turn would pin dirty=true forever once a\n\t\t\t\t// tracked file is deleted, defeating the \"skip unchanged turns\" check\n\t\t\t\t// and flushing real checkpoints out of the capped ring.\n\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\tif (latest && latest.backupName === null) {\n\t\t\t\t\tbackups[tracking] = latest;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst absent: FileBackup = { backupName: null, version: nextVersion };\n\t\t\t\tbackups[tracking] = absent;\n\t\t\t\tstate.latestByTracking.set(tracking, absent);\n\t\t\t\tdirty = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Worktree identity unchanged since last backup/confirm → reuse without\n\t\t\t// opening the backup blob (still paid one worktree stat above).\n\t\t\tconst seen = state.lastSeen.get(tracking);\n\t\t\tif (latest && latest.backupName !== null && seen && sameSeen(seen, st)) {\n\t\t\t\tbackups[tracking] = latest;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tlatest &&\n\t\t\t\tlatest.backupName !== null &&\n\t\t\t\t!(await originChanged(sid, filePath, latest.backupName, { hint: st, trustMtime: true }))\n\t\t\t) {\n\t\t\t\tbackups[tracking] = latest; // unchanged -> reuse\n\t\t\t\tstate.lastSeen.set(tracking, seenFromStats(st));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst created = await createBackup(sid, filePath, tracking, nextVersion, st);\n\t\t\tbackups[tracking] = created;\n\t\t\tstate.latestByTracking.set(tracking, created);\n\t\t\t// Record the pre-backup worktree identity (copyFile does not change it).\n\t\t\tstate.lastSeen.set(tracking, seenFromStats(st));\n\t\t\tdirty = true;\n\t\t} catch {\n\t\t\t// skip this file; never break the turn\n\t\t}\n\t});\n\n\tstate.pending = { v: 1, userEntryId: \"\", turnId: \"\", prompt: \"\", trackedFileBackups: backups, timestamp: \"\" };\n\tstate.dirty = dirty;\n}\n\n/**\n * Back up a file about to be edited/written, if not already captured this turn.\n * Call from the tool_call hook BEFORE the edit lands. ENOENT target -> null\n * marker (rewind deletes the created file). Synchronous so the backup is on disk\n * before the hook returns (see createBackupSync).\n */\n/**\n * Reuse an existing tracking key that differs only by case (Windows paths are\n * case-insensitive): two keys for one physical file would race each other in\n * applySnapshot's concurrent restore.\n */\nfunction canonicalTracking(state: FileHistoryState, tracking: string): string {\n\tif (process.platform !== \"win32\" || state.trackedFiles.has(tracking)) return tracking;\n\tconst lower = tracking.toLowerCase();\n\tfor (const existing of state.trackedFiles) {\n\t\tif (existing.toLowerCase() === lower) return existing;\n\t}\n\treturn tracking;\n}\n\nexport function trackEdit(sid: string, absPath: string): void {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst tracking = canonicalTracking(state, shorten(absPath, cwd));\n\n\tif (!state.pending) {\n\t\tstate.pending = { v: 1, userEntryId: \"\", turnId: \"\", prompt: \"\", trackedFileBackups: {}, timestamp: \"\" };\n\t}\n\tif (state.pending.trackedFileBackups[tracking]) return; // already captured this turn\n\n\tconst latest = latestBackupOf(state, tracking);\n\tconst version = latest ? latest.version + 1 : 1;\n\tconst filePath = expand(tracking, cwd);\n\t// One pre-edit stat: drives the backup and lastSeen (null = new-file marker).\n\tlet preEdit: Stats | null = null;\n\ttry {\n\t\tpreEdit = statSync(filePath);\n\t} catch (e) {\n\t\tif (!isENOENT(e)) throw e;\n\t}\n\tconst backup = createBackupSync(sid, filePath, tracking, version, preEdit);\n\tstate.pending.trackedFileBackups[tracking] = backup;\n\tstate.trackedFiles.add(tracking);\n\tstate.latestByTracking.set(tracking, backup);\n\tif (preEdit) state.lastSeen.set(tracking, seenFromStats(preEdit));\n\telse state.lastSeen.delete(tracking);\n\tstate.dirty = true;\n}\n\n/**\n * Finalize the turn. Returns the snapshot to persist (caller appendEntry's it),\n * or null when nothing changed this turn. Pushes finalized frames into the\n * in-memory ring (capped at MAX_SNAPSHOTS).\n */\nexport function endTurn(\n\tsid: string,\n\tuserEntryId: string,\n\tturnId: string,\n\tprompt: string,\n\ttimestamp: string,\n\tmaxSnapshots = MAX_SNAPSHOTS,\n): FileHistorySnapshot | null {\n\tconst state = getState(sid);\n\tconst pending = state.pending;\n\tstate.pending = null;\n\tif (!pending || !state.dirty) {\n\t\tstate.dirty = false;\n\t\treturn null;\n\t}\n\tstate.dirty = false;\n\t// No resolvable anchor (e.g. a custom-triggered run that appended no user\n\t// entry): discard the frame. Pushing it into the ring without persisting it\n\t// would diverge memory from the JSONL, and /tree could never match it anyway.\n\tif (!userEntryId) return null;\n\tconst frame: FileHistorySnapshot = { ...pending, userEntryId, turnId, prompt, timestamp };\n\tstate.snapshots.push(frame);\n\tif (state.snapshots.length > maxSnapshots) {\n\t\tconst dropped = state.snapshots.slice(0, state.snapshots.length - maxSnapshots);\n\t\tstate.snapshots = state.snapshots.slice(-maxSnapshots);\n\t\tvoid pruneDroppedBlobs(sid, dropped, state.snapshots);\n\t}\n\tstate.seq++;\n\treturn frame;\n}\n\n/**\n * Best-effort deletion of backup blobs referenced ONLY by frames dropped from\n * the capped ring. Backups are reused across frames (an unchanged file keeps\n * pointing at the same version), so a blob is unlinked only when no retained\n * frame still references it. Without this, the cap trims the in-memory index\n * while the blob files accumulate for the whole session — gc.ts only reclaims\n * whole session directories.\n */\nasync function pruneDroppedBlobs(\n\tsid: string,\n\tdropped: FileHistorySnapshot[],\n\tretained: FileHistorySnapshot[],\n): Promise<void> {\n\tconst live = new Set<string>();\n\tfor (const snap of retained) {\n\t\tfor (const b of Object.values(snap.trackedFileBackups)) {\n\t\t\tif (b.backupName) live.add(b.backupName);\n\t\t}\n\t}\n\tconst doomed = new Set<string>();\n\tfor (const snap of dropped) {\n\t\tfor (const b of Object.values(snap.trackedFileBackups)) {\n\t\t\tif (b.backupName && !live.has(b.backupName)) doomed.add(b.backupName);\n\t\t}\n\t}\n\tif (doomed.size === 0) return;\n\tawait Promise.allSettled(Array.from(doomed, (name) => unlink(backupPathFor(sid, name))));\n}\n\n// ---- rewind ---------------------------------------------------------------\n\n/**\n * Absolute paths that restoring to `target` would change on disk (empty =\n * nothing to do). Same walk applySnapshot performs, without writing — the\n * caller can both count and preview the files from one pass. Concurrent with\n * IO_CONCURRENCY; result order matches trackedFiles insertion order.\n */\nexport async function collectChanges(sid: string, target: FileHistorySnapshot): Promise<string[]> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst trackings = Array.from(state.trackedFiles);\n\tconst results = await mapPool(trackings, IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return null;\n\t\t\tif (name === null) {\n\t\t\t\treturn (await statOrNull(filePath)) ? filePath : null;\n\t\t\t}\n\t\t\treturn (await originChanged(sid, filePath, name)) ? filePath : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t});\n\treturn results.filter((p): p is string => p !== null);\n}\n\nexport interface CoarseDiffStats {\n\t/** Absolute paths that would change (same as collectChanges). */\n\tpaths: string[];\n\t/**\n\t * Coarse line insertions when restoring (lines present in backup, not in\n\t * current). Bag-of-lines, not a true Myers diff — confirm UI only.\n\t */\n\tinsertions: number;\n\t/** Coarse line deletions when restoring (lines present in current, not in backup). */\n\tdeletions: number;\n}\n\n/**\n * Coarse +N/−M line stats for a restore confirm dialog. Only loads text for\n * paths already known to differ (from collectChanges). Oversized files still\n * appear in `paths` but contribute 0 to line totals. No extra dependency.\n */\nexport async function collectChangeDiffStats(\n\tsid: string,\n\ttarget: FileHistorySnapshot,\n\tchangedPaths: readonly string[],\n): Promise<CoarseDiffStats> {\n\tif (changedPaths.length === 0) {\n\t\treturn { paths: [], insertions: 0, deletions: 0 };\n\t}\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\t// Reverse map abs path → tracking key for the known changed set.\n\tconst trackingByAbs = new Map<string, string>();\n\tfor (const tracking of state.trackedFiles) {\n\t\ttrackingByAbs.set(expand(tracking, cwd), tracking);\n\t}\n\n\tconst results = await mapPool([...changedPaths], IO_CONCURRENCY, async (filePath) => {\n\t\ttry {\n\t\t\tconst tracking = trackingByAbs.get(filePath);\n\t\t\tif (!tracking) return { insertions: 0, deletions: 0 };\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return { insertions: 0, deletions: 0 };\n\t\t\treturn await coarseLineDelta(sid, filePath, name);\n\t\t} catch {\n\t\t\treturn { insertions: 0, deletions: 0 };\n\t\t}\n\t});\n\n\tlet insertions = 0;\n\tlet deletions = 0;\n\tfor (const r of results) {\n\t\tinsertions += r.insertions;\n\t\tdeletions += r.deletions;\n\t}\n\treturn { paths: [...changedPaths], insertions, deletions };\n}\n\n/**\n * Bag-of-lines delta for current worktree → backup target (restore direction).\n * insertions = lines that appear after restore; deletions = lines removed.\n */\nasync function coarseLineDelta(\n\tsid: string,\n\tfilePath: string,\n\tbackupName: string | null,\n): Promise<{ insertions: number; deletions: number }> {\n\tconst currentText = await readTextCapped(filePath, MAX_DIFF_BYTES);\n\tif (backupName === null) {\n\t\t// Snapshot: file did not exist → restore deletes it.\n\t\tif (currentText === null) return { insertions: 0, deletions: 0 };\n\t\treturn { insertions: 0, deletions: countLines(currentText) };\n\t}\n\tconst backupText = await readTextCapped(backupPathFor(sid, backupName), MAX_DIFF_BYTES);\n\tif (currentText === null && backupText === null) return { insertions: 0, deletions: 0 };\n\tif (currentText === null) return { insertions: countLines(backupText ?? \"\"), deletions: 0 };\n\tif (backupText === null) return { insertions: 0, deletions: countLines(currentText) };\n\treturn bagOfLinesDelta(currentText, backupText);\n}\n\n/** null = missing or oversize/unreadable (skip line contrib). */\nasync function readTextCapped(filePath: string, maxBytes: number): Promise<string | null> {\n\ttry {\n\t\tconst st = await statOrNull(filePath);\n\t\tif (!st) return null;\n\t\tif (st.size > maxBytes) return null;\n\t\tconst buf = await readFile(filePath);\n\t\t// Skip obvious binary (NUL in the first 8K).\n\t\tconst sample = buf.subarray(0, Math.min(buf.length, 8192));\n\t\tif (sample.includes(0)) return null;\n\t\treturn buf.toString(\"utf8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction countLines(text: string): number {\n\tif (text.length === 0) return 0;\n\tlet n = 1;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text.charCodeAt(i) === 10 /* \\n */) n++;\n\t}\n\t// Trailing newline does not add an extra empty \"line\" for display counts.\n\tif (text.endsWith(\"\\n\")) n--;\n\treturn Math.max(n, 0);\n}\n\n/**\n * Multiset frequency delta: from → to.\n * deletions = lines over-represented in `from`; insertions = over-represented in `to`.\n */\nfunction bagOfLinesDelta(from: string, to: string): { insertions: number; deletions: number } {\n\tconst fromCounts = lineFrequency(from);\n\tconst toCounts = lineFrequency(to);\n\tlet insertions = 0;\n\tlet deletions = 0;\n\tconst keys = new Set([...fromCounts.keys(), ...toCounts.keys()]);\n\tfor (const key of keys) {\n\t\tconst a = fromCounts.get(key) ?? 0;\n\t\tconst b = toCounts.get(key) ?? 0;\n\t\tif (b > a) insertions += b - a;\n\t\telse if (a > b) deletions += a - b;\n\t}\n\treturn { insertions, deletions };\n}\n\nfunction lineFrequency(text: string): Map<string, number> {\n\tconst map = new Map<string, number>();\n\t// Preserve empty file as zero lines; otherwise split including last empty.\n\tif (text.length === 0) return map;\n\tconst lines = text.split(\"\\n\");\n\tif (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop();\n\tfor (const line of lines) {\n\t\tmap.set(line, (map.get(line) ?? 0) + 1);\n\t}\n\treturn map;\n}\n\n/**\n * Restore the work tree to `target`. Returns the list of changed file paths.\n * Pass `onlyPaths` (from a prior collectChanges) to skip a second originChanged\n * pass and only touch those absolute paths.\n */\nexport async function applySnapshot(\n\tsid: string,\n\ttarget: FileHistorySnapshot,\n\topts?: ApplySnapshotOptions,\n): Promise<string[]> {\n\tconst state = getState(sid);\n\tconst cwd = cwdFor(sid);\n\tconst only = opts?.onlyPaths;\n\tconst trackings = Array.from(state.trackedFiles).filter((tracking) => {\n\t\tif (!only) return true;\n\t\treturn only.has(expand(tracking, cwd));\n\t});\n\n\tconst results = await mapPool(trackings, IO_CONCURRENCY, async (tracking) => {\n\t\ttry {\n\t\t\tconst filePath = expand(tracking, cwd);\n\t\t\tconst name = backupNameForTarget(state, target, tracking);\n\t\t\tif (name === undefined) return null; // can't resolve -> leave file alone\n\t\t\tif (name === null) {\n\t\t\t\ttry {\n\t\t\t\t\tawait unlink(filePath);\n\t\t\t\t\t// Worktree no longer matches any recorded identity.\n\t\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\t\treturn filePath;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (!isENOENT(e)) throw e;\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// onlyPaths already proven different at confirm time — skip re-compare.\n\t\t\tif (only || (await originChanged(sid, filePath, name))) {\n\t\t\t\tawait restoreBackup(sid, filePath, name);\n\t\t\t\t// Drop stale lastSeen: worktree now matches the target blob, not the\n\t\t\t\t// latest backup identity beginTurn compares against.\n\t\t\t\tstate.lastSeen.delete(tracking);\n\t\t\t\treturn filePath;\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t});\n\treturn results.filter((p): p is string => p !== null);\n}\n\n// ---- persistence rebuild + resume migration -------------------------------\n\n/** The trailing window of `snapshots` retained under the cap (endTurn's ring). */\nexport function capSnapshots(snapshots: FileHistorySnapshot[], maxSnapshots = MAX_SNAPSHOTS): FileHistorySnapshot[] {\n\treturn snapshots.length > maxSnapshots ? snapshots.slice(-maxSnapshots) : snapshots;\n}\n\n/** Rebuild in-memory state from snapshots persisted in the session JSONL. */\nexport function restoreStateFromSnapshots(\n\tsid: string,\n\tcwd: string,\n\tsnapshots: FileHistorySnapshot[],\n\tmaxSnapshots = MAX_SNAPSHOTS,\n): void {\n\tcwds.set(sid, cwd);\n\t// Apply the same cap on reload so a long session's JSONL can't reinflate the\n\t// in-memory ring past the limit. trackedFiles is rebuilt from the retained\n\t// frames only (older frames are unreachable for rewind anyway); blobs only\n\t// those frames referenced are pruned best-effort, mirroring endTurn.\n\tconst retained = capSnapshots(snapshots, maxSnapshots);\n\tif (retained.length < snapshots.length) {\n\t\tvoid pruneDroppedBlobs(sid, snapshots.slice(0, snapshots.length - retained.length), retained);\n\t}\n\tconst trackedFiles = new Set<string>();\n\tfor (const snap of retained) {\n\t\tfor (const key of Object.keys(snap.trackedFileBackups)) trackedFiles.add(key);\n\t}\n\tstates.set(sid, {\n\t\tsnapshots: [...retained],\n\t\ttrackedFiles,\n\t\tlatestByTracking: rebuildLatestIndex(retained),\n\t\t// lastSeen is process-local; after reload we re-establish it on the first\n\t\t// beginTurn via originChanged / createBackup.\n\t\tlastSeen: new Map(),\n\t\tpending: null,\n\t\tdirty: false,\n\t\tseq: retained.length,\n\t});\n}\n\n/**\n * Hard-link this session's backup blobs from a previous session's directory\n * (resume/fork carries the conversation + its snapshot index, but the blobs live\n * under the old session id). Falls back to copy. No-op when ids match.\n */\nexport async function migrateBackupsFromSession(\n\tprevSid: string,\n\tsid: string,\n\tsnapshots: FileHistorySnapshot[],\n): Promise<void> {\n\tif (!prevSid || prevSid === sid) return;\n\tconst destDir = backupsDir(sid);\n\tawait mkdir(destDir, { recursive: true });\n\tawait Promise.allSettled(\n\t\tsnapshots.flatMap((snap) =>\n\t\t\tObject.values(snap.trackedFileBackups)\n\t\t\t\t.filter((b): b is FileBackup & { backupName: string } => b.backupName !== null)\n\t\t\t\t.map(async ({ backupName: name }) => {\n\t\t\t\t\tconst from = join(backupsDir(prevSid), name);\n\t\t\t\t\tconst to = join(destDir, name);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait link(from, to);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconst code = (e as { code?: string }).code;\n\t\t\t\t\t\tif (code === \"EEXIST\") return;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait copyFile(from, to);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// best-effort; a missing blob just means that version can't restore\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t),\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/rewind/index.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gCAAgC,CAAC;AA2FrF,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAuJrD","sourcesContent":["/**\n * rewind — file-backed checkpoint & rewind for Pi.\n *\n * Engine: instead of snapshotting the whole work tree into a per-session shadow\n * git repo (the old design, which froze the UI in large directories like a\n * multi-project parent folder and grew storage without bound), we back up ONLY\n * the files Pi's edit/write tools are about to modify — one copyFile before each\n * edit (see engine.ts, ported from Claude Code's file-history). Cost is\n * proportional to how many files Pi changed, never to project size.\n *\n * Per turn: before_agent_start opens a snapshot frame (re-recording tracked files\n * at their turn-start state); tool_call(edit|write) backs up each newly edited\n * file before it lands; agent_settled persists the frame to the session JSONL\n * (custom \"pi-rewind-snapshot\" entry) when it changed. agent_settled is used\n * instead of agent_end so auto-retry, overflow compaction-retry, and queued\n * follow-ups stay in one logical turn (agent_end can fire mid-continuation).\n * On session_start the index is rebuilt from those entries; resume/fork\n * hard-links the prior session's blobs.\n *\n * Time-travel is fused into /tree: navigating to a node whose turn changed files\n * offers to sync the work tree to that point (session_before_tree/session_tree).\n * /rewind itself is a settings + storage menu (menu.ts), not a restore picker.\n *\n * Restore safety: applySnapshot only rewrites files that differ and never throws\n * out — a broken backup degrades to \"leave the file alone\", so it can never abort\n * the user's session.\n *\n * Architecture informed by oh-my-pi (GPL-3.0) and Claude Code's file-history;\n * independent implementation. No ANSI: all UI is native (ctx.ui.* + theme).\n */\nimport path from \"node:path\";\n\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../core/extensions/types.ts\";\n\nimport { loadRewindConfig, type RewindConfig, reloadRewindConfig } from \"./config.ts\";\nimport {\n\tbeginTurn,\n\tdisposeSession,\n\tendTurn,\n\tgetSnapshots,\n\tmigrateBackupsFromSession,\n\tregisterSession,\n\trestoreStateFromSnapshots,\n\ttrackEdit,\n} from \"./engine.ts\";\nimport { runGc, sessionIdFromFile } from \"./gc.ts\";\nimport { runRewindMenu } from \"./menu.ts\";\nimport { rewindBackupsRoot, sessionsDirectory } from \"./paths.ts\";\nimport { restoreToSnapshot, snapshotChangeDiffStats, snapshotChangedPaths, snapshotForEntry } from \"./restore.ts\";\nimport { type FileHistorySnapshot, isSnapshot, SNAPSHOT_ENTRY_TYPE } from \"./snapshot.ts\";\nimport { configureStorage } from \"./storage.ts\";\nimport { truncateText } from \"./text.ts\";\nimport { editWriteTargetPath, resolveToolPath } from \"./tool-path.ts\";\n\n// Bind the engine/gc storage roots to the real on-disk locations (they avoid\n// importing paths.ts directly so they stay node-testable). Safe to call at load.\nconfigureStorage({ backupsRoot: rewindBackupsRoot(), sessionsRoot: sessionsDirectory() });\n\n// Global (one config.json). Reloaded from disk at session_start; /rewind menu\n// saveRewindConfig updates the in-memory cache so the next turn sees changes\n// without re-reading the file every before_agent_start.\nlet config: RewindConfig = loadRewindConfig();\n\n/** /tree confirm → apply payload (changedPaths skips a second originChanged pass). */\ninterface PendingTreeRestore {\n\tsnapshot: FileHistorySnapshot;\n\tchangedPaths: string[];\n}\n\n// Per-session transient state held by the integration layer.\nconst pendingPrompt = new Map<string, string>(); // turn prompt, captured for the snapshot label\nconst pendingTreeRestore = new Map<string, PendingTreeRestore | null>(); // /tree sync intent\n\n// ---- helpers --------------------------------------------------------------\n\n/** The id of the last user message in the current branch (the turn's anchor). */\nfunction lastUserEntryId(ctx: ExtensionContext): string | undefined {\n\tconst branch = ctx.sessionManager.getBranch();\n\tfor (let i = branch.length - 1; i >= 0; i--) {\n\t\tconst e = branch[i];\n\t\tif (e.type === \"message\" && (e.message as AgentMessage).role === \"user\") return e.id;\n\t}\n\treturn undefined;\n}\n\n/** Rebuild the snapshot list for a session from its persisted custom entries. */\nfunction rebuildSnapshots(ctx: ExtensionContext): FileHistorySnapshot[] {\n\tconst out: FileHistorySnapshot[] = [];\n\tfor (const entry of ctx.sessionManager.getEntries()) {\n\t\tif (entry.type === \"custom\" && entry.customType === SNAPSHOT_ENTRY_TYPE && isSnapshot(entry.data)) {\n\t\t\tout.push(entry.data);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Max files listed in the /tree restore confirmation before \"+N more\". */\nconst RESTORE_PREVIEW_LIMIT = 8;\n/** Max characters per previewed path (leading-truncated: the filename matters most). */\nconst RESTORE_PREVIEW_PATH_MAX = 64;\n\n/**\n * Bounded cwd-relative file list shown under the /tree restore question, so an\n * irreversible work-tree rewrite is confirmed against WHICH files, not just a\n * count. Multi-line select titles are upstream-sanctioned (ui.confirm joins\n * title + message with \\n through the same selector).\n */\nfunction formatRestorePreview(changedPaths: string[], cwd: string): string {\n\tconst lines = changedPaths.slice(0, RESTORE_PREVIEW_LIMIT).map((p) => {\n\t\tconst rel = path.relative(cwd, p);\n\t\tconst display = rel && !rel.startsWith(\"..\") && !path.isAbsolute(rel) ? rel : p;\n\t\tconst bounded =\n\t\t\tdisplay.length > RESTORE_PREVIEW_PATH_MAX ? `…${display.slice(-(RESTORE_PREVIEW_PATH_MAX - 1))}` : display;\n\t\treturn ` ${bounded}`;\n\t});\n\tif (changedPaths.length > RESTORE_PREVIEW_LIMIT) {\n\t\tlines.push(` … +${changedPaths.length - RESTORE_PREVIEW_LIMIT} more`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n// ---- extension entry ------------------------------------------------------\n\nexport default function rewind(pi: ExtensionAPI): void {\n\t// session_start: rebuild the index, migrate fork/resume blobs, reclaim storage.\n\tpi.on(\"session_start\", async (event, ctx) => {\n\t\tconfig = reloadRewindConfig();\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tregisterSession(sid, ctx.cwd);\n\n\t\tconst snapshots = rebuildSnapshots(ctx);\n\t\trestoreStateFromSnapshots(sid, ctx.cwd, snapshots, config.maxSnapshots);\n\n\t\tif ((event.reason === \"resume\" || event.reason === \"fork\") && event.previousSessionFile) {\n\t\t\tconst prevSid = sessionIdFromFile(event.previousSessionFile);\n\t\t\tif (prevSid) {\n\t\t\t\ttry {\n\t\t\t\t\tawait migrateBackupsFromSession(prevSid, sid, snapshots);\n\t\t\t\t} catch {\n\t\t\t\t\t// best-effort; a missing blob just means that version can't restore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\trunGc(config.retentionDays, sid);\n\t\t} catch {\n\t\t\t// GC is best-effort; never block startup\n\t\t}\n\t});\n\n\t// tool_call: back up the target file BEFORE the edit/write lands. Synchronous\n\t// so the backup is on disk before the hook returns control to the agent loop.\n\tpi.on(\"tool_call\", (event, ctx) => {\n\t\tif (event.toolName !== \"edit\" && event.toolName !== \"write\") return;\n\t\tif (!config.enabled) return;\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tconst rawPath = editWriteTargetPath(event.input);\n\t\tif (!rawPath) return;\n\t\tconst abs = resolveToolPath(rawPath, ctx.cwd);\n\t\ttry {\n\t\t\ttrackEdit(sid, abs);\n\t\t} catch {\n\t\t\t// never block the edit on a backup failure\n\t\t}\n\t});\n\n\t// before_agent_start: open the turn's snapshot frame.\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\t// Memory-cached; session_start reloads disk, /rewind save updates cache.\n\t\tconfig = loadRewindConfig();\n\t\tif (!config.enabled) return;\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tpendingPrompt.set(sid, event.prompt ?? \"\");\n\t\ttry {\n\t\t\tawait beginTurn(sid);\n\t\t} catch {\n\t\t\t// non-fatal\n\t\t}\n\t});\n\n\t// agent_settled: finalize + persist after Pi will not auto-continue (retry /\n\t// compact-retry / follow-up). agent_end alone is too early for that.\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!config.enabled) return;\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tconst userEntryId = lastUserEntryId(ctx) ?? \"\";\n\t\tconst turnId = ctx.sessionManager.getLeafId() ?? userEntryId;\n\t\tconst prompt = truncateText(pendingPrompt.get(sid) ?? \"\", 120, { collapseWhitespace: true });\n\t\tpendingPrompt.delete(sid);\n\t\tconst frame = endTurn(sid, userEntryId, turnId, prompt, new Date().toISOString(), config.maxSnapshots);\n\t\tif (frame && userEntryId) {\n\t\t\tpi.appendEntry(SNAPSHOT_ENTRY_TYPE, frame);\n\t\t}\n\t});\n\n\t// session_before_tree: offer to sync files when navigating to a changed point.\n\tpi.on(\"session_before_tree\", async (event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tpendingTreeRestore.set(sid, null);\n\t\tif (!config.enabled) return;\n\n\t\tconst target = snapshotForEntry(getSnapshots(sid), ctx.sessionManager, event.preparation.targetId);\n\t\tif (!target) return;\n\t\tconst changed = await snapshotChangedPaths(sid, target);\n\t\tif (changed.length === 0) return; // silent nav, like native /tree\n\t\t// Lifecycle handler: silent-return without UI. (hasUI is the guard for\n\t\t// ctx.ui.*; checking ctx.mode here would wrongly proceed when a TUI\n\t\t// session has no usable UI.)\n\t\tif (!ctx.hasUI) return;\n\n\t\tconst n = changed.length;\n\t\tlet lineStats = \"\";\n\t\ttry {\n\t\t\tconst stats = await snapshotChangeDiffStats(sid, target, changed);\n\t\t\tif (stats.insertions > 0 || stats.deletions > 0) {\n\t\t\t\tlineStats = ` (+${stats.insertions} / −${stats.deletions})`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Coarse stats are best-effort; still offer path preview.\n\t\t}\n\t\tconst choice = await ctx.ui.select(\n\t\t\t`Restore ${n} file${n === 1 ? \"\" : \"s\"} to this point?${lineStats}\\n${formatRestorePreview(changed, ctx.cwd)}`,\n\t\t\t[\"Yes, restore files\", \"No, conversation only\"],\n\t\t);\n\t\tif (choice?.startsWith(\"Yes\")) {\n\t\t\t// Cache paths so session_tree can apply without a second full compare.\n\t\t\tpendingTreeRestore.set(sid, { snapshot: target, changedPaths: changed });\n\t\t}\n\t});\n\n\t// session_tree: execute any sync intent recorded above.\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tconst pending = pendingTreeRestore.get(sid);\n\t\tpendingTreeRestore.set(sid, null);\n\t\tif (!pending) return;\n\t\ttry {\n\t\t\tconst changed = await restoreToSnapshot(sid, pending.snapshot, {\n\t\t\t\tonlyPaths: new Set(pending.changedPaths),\n\t\t\t});\n\t\t\tif (changed.length > 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Restored ${changed.length} file${changed.length === 1 ? \"\" : \"s\"} to this checkpoint.`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tctx.ui.notify(`Rewind restore failed: ${String(e)}`, \"warning\");\n\t\t}\n\t});\n\n\t// session_shutdown: drop this session's in-memory state.\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tdisposeSession(sid);\n\t\tpendingTreeRestore.delete(sid);\n\t\tpendingPrompt.delete(sid);\n\t});\n\n\t// /rewind: settings + storage menu (time-travel itself is via /tree).\n\tpi.registerCommand(\"rewind\", {\n\t\tdescription: \"Rewind settings and backup storage (restore is via /tree)\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tawait runRewindMenu(ctx);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/rewind/index.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gCAAgC,CAAC;AA8GrF,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA6KrD","sourcesContent":["/**\n * rewind — file-backed checkpoint & rewind for Pi.\n *\n * Engine: instead of snapshotting the whole work tree into a per-session shadow\n * git repo (the old design, which froze the UI in large directories like a\n * multi-project parent folder and grew storage without bound), we back up ONLY\n * the files Pi's edit/write tools are about to modify — one copyFile before each\n * edit (see engine.ts, ported from Claude Code's file-history). Cost is\n * proportional to how many files Pi changed, never to project size.\n *\n * Per turn: before_agent_start opens a snapshot frame (re-recording tracked files\n * at their turn-start state); tool_call(edit|write) backs up each newly edited\n * file before it lands; agent_settled persists the frame to the session JSONL\n * (custom \"pi-rewind-snapshot\" entry) when it changed. agent_settled is used\n * instead of agent_end so auto-retry, overflow compaction-retry, and queued\n * follow-ups stay in one logical turn (agent_end can fire mid-continuation).\n * The frame anchors to the FIRST user entry the run appended (tracked via\n * anchorScanStart + anchor.ts) — the entry whose start the frame recorded;\n * steering/follow-up messages append later user entries that must not steal\n * the anchor. On session_start the index is rebuilt from those entries;\n * resume/fork hard-links the prior session's blobs.\n *\n * Time-travel is fused into /tree: navigating to a node whose turn changed files\n * offers to sync the work tree to that point (session_before_tree/session_tree).\n * Snapshot selection mirrors navigateTree's leaf rules (see restore.ts).\n * /rewind itself is a settings + storage menu (menu.ts), not a restore picker.\n *\n * Restore safety: applySnapshot only rewrites files that differ and never throws\n * out — a broken backup degrades to \"leave the file alone\", so it can never abort\n * the user's session.\n *\n * Architecture informed by oh-my-pi (GPL-3.0) and Claude Code's file-history;\n * independent implementation. No ANSI: all UI is native (ctx.ui.* + theme).\n */\nimport path from \"node:path\";\n\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../core/extensions/types.ts\";\n\nimport { firstUserEntryIdAfter } from \"./anchor.ts\";\nimport { loadRewindConfig, type RewindConfig, reloadRewindConfig } from \"./config.ts\";\nimport {\n\tbeginTurn,\n\tcapSnapshots,\n\tdisposeSession,\n\tendTurn,\n\tgetSnapshots,\n\tmigrateBackupsFromSession,\n\tregisterSession,\n\trestoreStateFromSnapshots,\n\ttrackEdit,\n} from \"./engine.ts\";\nimport { runGc, sessionIdFromFile } from \"./gc.ts\";\nimport { runRewindMenu } from \"./menu.ts\";\nimport { rewindBackupsRoot, sessionsDirectory } from \"./paths.ts\";\nimport {\n\ttype EntryTreeView,\n\trestoreToSnapshot,\n\tsnapshotChangeDiffStats,\n\tsnapshotChangedPaths,\n\tsnapshotForEntry,\n} from \"./restore.ts\";\nimport { type FileHistorySnapshot, isSnapshot, SNAPSHOT_ENTRY_TYPE } from \"./snapshot.ts\";\nimport { configureStorage } from \"./storage.ts\";\nimport { truncateText } from \"./text.ts\";\nimport { editWriteTargetPath, resolveToolPath } from \"./tool-path.ts\";\n\n// Bind the engine/gc storage roots to the real on-disk locations (they avoid\n// importing paths.ts directly so they stay node-testable). Safe to call at load.\nconfigureStorage({ backupsRoot: rewindBackupsRoot(), sessionsRoot: sessionsDirectory() });\n\n// Global (one config.json). Reloaded from disk at session_start; /rewind menu\n// saveRewindConfig updates the in-memory cache so the next turn sees changes\n// without re-reading the file every before_agent_start.\nlet config: RewindConfig = loadRewindConfig();\n\n/** /tree confirm → apply payload (changedPaths skips a second originChanged pass). */\ninterface PendingTreeRestore {\n\tsnapshot: FileHistorySnapshot;\n\tchangedPaths: string[];\n}\n\n// Per-session transient state held by the integration layer.\nconst pendingPrompt = new Map<string, string>(); // turn prompt, captured for the snapshot label\nconst pendingTreeRestore = new Map<string, PendingTreeRestore | null>(); // /tree sync intent\n// Leaf id before the current run started (null = branch root). agent_settled\n// scans forward from here for the run's FIRST user entry — the frame's anchor.\nconst anchorScanStart = new Map<string, string | null>();\n\n// ---- helpers --------------------------------------------------------------\n\n/** Record the current leaf as the scan start for the next frame's anchor. */\nfunction markAnchorScanStart(sid: string, ctx: ExtensionContext): void {\n\tanchorScanStart.set(sid, ctx.sessionManager.getLeafId() ?? null);\n}\n\n/** Adapt the session manager to restore.ts's tree view (turn-anchor = navigateTree's \"leaf = parent\" entries). */\nfunction sessionTreeView(ctx: ExtensionContext): EntryTreeView {\n\treturn {\n\t\tgetEntry(id) {\n\t\t\tconst e = ctx.sessionManager.getEntry(id);\n\t\t\tif (!e) return undefined;\n\t\t\tconst isTurnAnchor =\n\t\t\t\t(e.type === \"message\" && (e.message as AgentMessage).role === \"user\") || e.type === \"custom_message\";\n\t\t\treturn { id: e.id, parentId: e.parentId, isTurnAnchor };\n\t\t},\n\t};\n}\n\n/** Rebuild the snapshot list for a session from its persisted custom entries. */\nfunction rebuildSnapshots(ctx: ExtensionContext): FileHistorySnapshot[] {\n\tconst out: FileHistorySnapshot[] = [];\n\tfor (const entry of ctx.sessionManager.getEntries()) {\n\t\tif (entry.type === \"custom\" && entry.customType === SNAPSHOT_ENTRY_TYPE && isSnapshot(entry.data)) {\n\t\t\tout.push(entry.data);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Max files listed in the /tree restore confirmation before \"+N more\". */\nconst RESTORE_PREVIEW_LIMIT = 8;\n/** Max characters per previewed path (leading-truncated: the filename matters most). */\nconst RESTORE_PREVIEW_PATH_MAX = 64;\n\n/**\n * Bounded cwd-relative file list shown under the /tree restore question, so an\n * irreversible work-tree rewrite is confirmed against WHICH files, not just a\n * count. Multi-line select titles are upstream-sanctioned (ui.confirm joins\n * title + message with \\n through the same selector).\n */\nfunction formatRestorePreview(changedPaths: string[], cwd: string): string {\n\tconst lines = changedPaths.slice(0, RESTORE_PREVIEW_LIMIT).map((p) => {\n\t\tconst rel = path.relative(cwd, p);\n\t\tconst display = rel && !rel.startsWith(\"..\") && !path.isAbsolute(rel) ? rel : p;\n\t\tconst bounded =\n\t\t\tdisplay.length > RESTORE_PREVIEW_PATH_MAX ? `…${display.slice(-(RESTORE_PREVIEW_PATH_MAX - 1))}` : display;\n\t\treturn ` ${bounded}`;\n\t});\n\tif (changedPaths.length > RESTORE_PREVIEW_LIMIT) {\n\t\tlines.push(` … +${changedPaths.length - RESTORE_PREVIEW_LIMIT} more`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n// ---- extension entry ------------------------------------------------------\n\nexport default function rewind(pi: ExtensionAPI): void {\n\t// session_start: rebuild the index, migrate fork/resume blobs, reclaim storage.\n\tpi.on(\"session_start\", async (event, ctx) => {\n\t\tconfig = reloadRewindConfig();\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tregisterSession(sid, ctx.cwd);\n\n\t\tconst snapshots = rebuildSnapshots(ctx);\n\n\t\t// Migrate BEFORE rebuilding in-memory state: restoreStateFromSnapshots\n\t\t// prunes over-cap blobs from THIS session's directory (fire-and-forget),\n\t\t// and linking only the retained frames first keeps the two steps disjoint\n\t\t// — no race, and dropped frames' blobs are never linked at all.\n\t\tif ((event.reason === \"resume\" || event.reason === \"fork\") && event.previousSessionFile) {\n\t\t\tconst prevSid = sessionIdFromFile(event.previousSessionFile);\n\t\t\tif (prevSid) {\n\t\t\t\ttry {\n\t\t\t\t\tawait migrateBackupsFromSession(prevSid, sid, capSnapshots(snapshots, config.maxSnapshots));\n\t\t\t\t} catch {\n\t\t\t\t\t// best-effort; a missing blob just means that version can't restore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trestoreStateFromSnapshots(sid, ctx.cwd, snapshots, config.maxSnapshots);\n\t\tmarkAnchorScanStart(sid, ctx);\n\n\t\ttry {\n\t\t\trunGc(config.retentionDays, sid);\n\t\t} catch {\n\t\t\t// GC is best-effort; never block startup\n\t\t}\n\t});\n\n\t// tool_call: back up the target file BEFORE the edit/write lands. Synchronous\n\t// so the backup is on disk before the hook returns control to the agent loop.\n\tpi.on(\"tool_call\", (event, ctx) => {\n\t\tif (event.toolName !== \"edit\" && event.toolName !== \"write\") return;\n\t\tif (!config.enabled) return;\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tconst rawPath = editWriteTargetPath(event.input);\n\t\tif (!rawPath) return;\n\t\tconst abs = resolveToolPath(rawPath, ctx.cwd);\n\t\ttry {\n\t\t\ttrackEdit(sid, abs);\n\t\t} catch {\n\t\t\t// never block the edit on a backup failure\n\t\t}\n\t});\n\n\t// before_agent_start: open the turn's snapshot frame.\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\t// Memory-cached; session_start reloads disk, /rewind save updates cache.\n\t\tconfig = loadRewindConfig();\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\t// The run's user entry is appended after this hook; the frame's anchor is\n\t\t// the first user entry that appears past this leaf.\n\t\tmarkAnchorScanStart(sid, ctx);\n\t\tif (!config.enabled) return;\n\t\tpendingPrompt.set(sid, event.prompt ?? \"\");\n\t\ttry {\n\t\t\tawait beginTurn(sid);\n\t\t} catch {\n\t\t\t// non-fatal\n\t\t}\n\t});\n\n\t// agent_settled: finalize + persist after Pi will not auto-continue (retry /\n\t// compact-retry / follow-up). agent_end alone is too early for that.\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!config.enabled) return;\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\t// Anchor = the run's FIRST appended user entry (whose start the frame\n\t\t// recorded). Steering/follow-up messages consumed in the same run append\n\t\t// later user entries and must not steal the anchor. Unresolvable (scan\n\t\t// start missing / off-branch / no user entry appended) -> endTurn discards.\n\t\tconst scanStart = anchorScanStart.get(sid);\n\t\tconst userEntryId =\n\t\t\tscanStart === undefined ? \"\" : (firstUserEntryIdAfter(ctx.sessionManager.getBranch(), scanStart) ?? \"\");\n\t\tconst turnId = ctx.sessionManager.getLeafId() ?? userEntryId;\n\t\tconst prompt = truncateText(pendingPrompt.get(sid) ?? \"\", 120, { collapseWhitespace: true });\n\t\tpendingPrompt.delete(sid);\n\t\tconst frame = endTurn(sid, userEntryId, turnId, prompt, new Date().toISOString(), config.maxSnapshots);\n\t\tif (frame) {\n\t\t\tpi.appendEntry(SNAPSHOT_ENTRY_TYPE, frame);\n\t\t}\n\t\t// Next run may start without before_agent_start (custom-triggered): scan\n\t\t// from the settled leaf (past this run's entries + the snapshot entry).\n\t\tmarkAnchorScanStart(sid, ctx);\n\t});\n\n\t// session_before_tree: offer to sync files when navigating to a changed point.\n\tpi.on(\"session_before_tree\", async (event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tpendingTreeRestore.set(sid, null);\n\t\tif (!config.enabled) return;\n\n\t\tconst target = snapshotForEntry(getSnapshots(sid), sessionTreeView(ctx), event.preparation.targetId);\n\t\tif (!target) return;\n\t\tconst changed = await snapshotChangedPaths(sid, target);\n\t\tif (changed.length === 0) return; // silent nav, like native /tree\n\t\t// Lifecycle handler: silent-return without UI. (hasUI is the guard for\n\t\t// ctx.ui.*; checking ctx.mode here would wrongly proceed when a TUI\n\t\t// session has no usable UI.)\n\t\tif (!ctx.hasUI) return;\n\n\t\tconst n = changed.length;\n\t\tlet lineStats = \"\";\n\t\ttry {\n\t\t\tconst stats = await snapshotChangeDiffStats(sid, target, changed);\n\t\t\tif (stats.insertions > 0 || stats.deletions > 0) {\n\t\t\t\tlineStats = ` (+${stats.insertions} / −${stats.deletions})`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Coarse stats are best-effort; still offer path preview.\n\t\t}\n\t\tconst choice = await ctx.ui.select(\n\t\t\t`Restore ${n} file${n === 1 ? \"\" : \"s\"} to this point?${lineStats}\\n${formatRestorePreview(changed, ctx.cwd)}\\n (covers edit/write changes only; files changed via bash are not tracked)`,\n\t\t\t[\"Yes, restore files\", \"No, conversation only\"],\n\t\t);\n\t\tif (choice?.startsWith(\"Yes\")) {\n\t\t\t// Cache paths so session_tree can apply without a second full compare.\n\t\t\tpendingTreeRestore.set(sid, { snapshot: target, changedPaths: changed });\n\t\t}\n\t});\n\n\t// session_tree: execute any sync intent recorded above.\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\t// The leaf moved; the old scan start may now be off-branch. Re-mark so a\n\t\t// custom-triggered run after navigation still anchors correctly.\n\t\tmarkAnchorScanStart(sid, ctx);\n\t\tconst pending = pendingTreeRestore.get(sid);\n\t\tpendingTreeRestore.set(sid, null);\n\t\tif (!pending) return;\n\t\ttry {\n\t\t\tconst changed = await restoreToSnapshot(sid, pending.snapshot, {\n\t\t\t\tonlyPaths: new Set(pending.changedPaths),\n\t\t\t});\n\t\t\tif (changed.length > 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Restored ${changed.length} file${changed.length === 1 ? \"\" : \"s\"} to this checkpoint.`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tctx.ui.notify(`Rewind restore failed: ${String(e)}`, \"warning\");\n\t\t}\n\t});\n\n\t// session_shutdown: drop this session's in-memory state.\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tconst sid = ctx.sessionManager.getSessionId();\n\t\tif (!sid) return;\n\t\tdisposeSession(sid);\n\t\tpendingTreeRestore.delete(sid);\n\t\tpendingPrompt.delete(sid);\n\t\tanchorScanStart.delete(sid);\n\t});\n\n\t// /rewind: settings + storage menu (time-travel itself is via /tree).\n\tpi.registerCommand(\"rewind\", {\n\t\tdescription: \"Rewind settings and backup storage (restore is via /tree)\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tawait runRewindMenu(ctx);\n\t\t},\n\t});\n}\n"]}
@@ -14,11 +14,15 @@
14
14
  * (custom "pi-rewind-snapshot" entry) when it changed. agent_settled is used
15
15
  * instead of agent_end so auto-retry, overflow compaction-retry, and queued
16
16
  * follow-ups stay in one logical turn (agent_end can fire mid-continuation).
17
- * On session_start the index is rebuilt from those entries; resume/fork
18
- * hard-links the prior session's blobs.
17
+ * The frame anchors to the FIRST user entry the run appended (tracked via
18
+ * anchorScanStart + anchor.ts) — the entry whose start the frame recorded;
19
+ * steering/follow-up messages append later user entries that must not steal
20
+ * the anchor. On session_start the index is rebuilt from those entries;
21
+ * resume/fork hard-links the prior session's blobs.
19
22
  *
20
23
  * Time-travel is fused into /tree: navigating to a node whose turn changed files
21
24
  * offers to sync the work tree to that point (session_before_tree/session_tree).
25
+ * Snapshot selection mirrors navigateTree's leaf rules (see restore.ts).
22
26
  * /rewind itself is a settings + storage menu (menu.ts), not a restore picker.
23
27
  *
24
28
  * Restore safety: applySnapshot only rewrites files that differ and never throws
@@ -29,12 +33,13 @@
29
33
  * independent implementation. No ANSI: all UI is native (ctx.ui.* + theme).
30
34
  */
31
35
  import path from "node:path";
36
+ import { firstUserEntryIdAfter } from "./anchor.js";
32
37
  import { loadRewindConfig, reloadRewindConfig } from "./config.js";
33
- import { beginTurn, disposeSession, endTurn, getSnapshots, migrateBackupsFromSession, registerSession, restoreStateFromSnapshots, trackEdit, } from "./engine.js";
38
+ import { beginTurn, capSnapshots, disposeSession, endTurn, getSnapshots, migrateBackupsFromSession, registerSession, restoreStateFromSnapshots, trackEdit, } from "./engine.js";
34
39
  import { runGc, sessionIdFromFile } from "./gc.js";
35
40
  import { runRewindMenu } from "./menu.js";
36
41
  import { rewindBackupsRoot, sessionsDirectory } from "./paths.js";
37
- import { restoreToSnapshot, snapshotChangeDiffStats, snapshotChangedPaths, snapshotForEntry } from "./restore.js";
42
+ import { restoreToSnapshot, snapshotChangeDiffStats, snapshotChangedPaths, snapshotForEntry, } from "./restore.js";
38
43
  import { isSnapshot, SNAPSHOT_ENTRY_TYPE } from "./snapshot.js";
39
44
  import { configureStorage } from "./storage.js";
40
45
  import { truncateText } from "./text.js";
@@ -49,16 +54,25 @@ let config = loadRewindConfig();
49
54
  // Per-session transient state held by the integration layer.
50
55
  const pendingPrompt = new Map(); // turn prompt, captured for the snapshot label
51
56
  const pendingTreeRestore = new Map(); // /tree sync intent
57
+ // Leaf id before the current run started (null = branch root). agent_settled
58
+ // scans forward from here for the run's FIRST user entry — the frame's anchor.
59
+ const anchorScanStart = new Map();
52
60
  // ---- helpers --------------------------------------------------------------
53
- /** The id of the last user message in the current branch (the turn's anchor). */
54
- function lastUserEntryId(ctx) {
55
- const branch = ctx.sessionManager.getBranch();
56
- for (let i = branch.length - 1; i >= 0; i--) {
57
- const e = branch[i];
58
- if (e.type === "message" && e.message.role === "user")
59
- return e.id;
60
- }
61
- return undefined;
61
+ /** Record the current leaf as the scan start for the next frame's anchor. */
62
+ function markAnchorScanStart(sid, ctx) {
63
+ anchorScanStart.set(sid, ctx.sessionManager.getLeafId() ?? null);
64
+ }
65
+ /** Adapt the session manager to restore.ts's tree view (turn-anchor = navigateTree's "leaf = parent" entries). */
66
+ function sessionTreeView(ctx) {
67
+ return {
68
+ getEntry(id) {
69
+ const e = ctx.sessionManager.getEntry(id);
70
+ if (!e)
71
+ return undefined;
72
+ const isTurnAnchor = (e.type === "message" && e.message.role === "user") || e.type === "custom_message";
73
+ return { id: e.id, parentId: e.parentId, isTurnAnchor };
74
+ },
75
+ };
62
76
  }
63
77
  /** Rebuild the snapshot list for a session from its persisted custom entries. */
64
78
  function rebuildSnapshots(ctx) {
@@ -102,18 +116,23 @@ export default function rewind(pi) {
102
116
  return;
103
117
  registerSession(sid, ctx.cwd);
104
118
  const snapshots = rebuildSnapshots(ctx);
105
- restoreStateFromSnapshots(sid, ctx.cwd, snapshots, config.maxSnapshots);
119
+ // Migrate BEFORE rebuilding in-memory state: restoreStateFromSnapshots
120
+ // prunes over-cap blobs from THIS session's directory (fire-and-forget),
121
+ // and linking only the retained frames first keeps the two steps disjoint
122
+ // — no race, and dropped frames' blobs are never linked at all.
106
123
  if ((event.reason === "resume" || event.reason === "fork") && event.previousSessionFile) {
107
124
  const prevSid = sessionIdFromFile(event.previousSessionFile);
108
125
  if (prevSid) {
109
126
  try {
110
- await migrateBackupsFromSession(prevSid, sid, snapshots);
127
+ await migrateBackupsFromSession(prevSid, sid, capSnapshots(snapshots, config.maxSnapshots));
111
128
  }
112
129
  catch {
113
130
  // best-effort; a missing blob just means that version can't restore
114
131
  }
115
132
  }
116
133
  }
134
+ restoreStateFromSnapshots(sid, ctx.cwd, snapshots, config.maxSnapshots);
135
+ markAnchorScanStart(sid, ctx);
117
136
  try {
118
137
  runGc(config.retentionDays, sid);
119
138
  }
@@ -146,11 +165,14 @@ export default function rewind(pi) {
146
165
  pi.on("before_agent_start", async (event, ctx) => {
147
166
  // Memory-cached; session_start reloads disk, /rewind save updates cache.
148
167
  config = loadRewindConfig();
149
- if (!config.enabled)
150
- return;
151
168
  const sid = ctx.sessionManager.getSessionId();
152
169
  if (!sid)
153
170
  return;
171
+ // The run's user entry is appended after this hook; the frame's anchor is
172
+ // the first user entry that appears past this leaf.
173
+ markAnchorScanStart(sid, ctx);
174
+ if (!config.enabled)
175
+ return;
154
176
  pendingPrompt.set(sid, event.prompt ?? "");
155
177
  try {
156
178
  await beginTurn(sid);
@@ -167,14 +189,22 @@ export default function rewind(pi) {
167
189
  const sid = ctx.sessionManager.getSessionId();
168
190
  if (!sid)
169
191
  return;
170
- const userEntryId = lastUserEntryId(ctx) ?? "";
192
+ // Anchor = the run's FIRST appended user entry (whose start the frame
193
+ // recorded). Steering/follow-up messages consumed in the same run append
194
+ // later user entries and must not steal the anchor. Unresolvable (scan
195
+ // start missing / off-branch / no user entry appended) -> endTurn discards.
196
+ const scanStart = anchorScanStart.get(sid);
197
+ const userEntryId = scanStart === undefined ? "" : (firstUserEntryIdAfter(ctx.sessionManager.getBranch(), scanStart) ?? "");
171
198
  const turnId = ctx.sessionManager.getLeafId() ?? userEntryId;
172
199
  const prompt = truncateText(pendingPrompt.get(sid) ?? "", 120, { collapseWhitespace: true });
173
200
  pendingPrompt.delete(sid);
174
201
  const frame = endTurn(sid, userEntryId, turnId, prompt, new Date().toISOString(), config.maxSnapshots);
175
- if (frame && userEntryId) {
202
+ if (frame) {
176
203
  pi.appendEntry(SNAPSHOT_ENTRY_TYPE, frame);
177
204
  }
205
+ // Next run may start without before_agent_start (custom-triggered): scan
206
+ // from the settled leaf (past this run's entries + the snapshot entry).
207
+ markAnchorScanStart(sid, ctx);
178
208
  });
179
209
  // session_before_tree: offer to sync files when navigating to a changed point.
180
210
  pi.on("session_before_tree", async (event, ctx) => {
@@ -184,7 +214,7 @@ export default function rewind(pi) {
184
214
  pendingTreeRestore.set(sid, null);
185
215
  if (!config.enabled)
186
216
  return;
187
- const target = snapshotForEntry(getSnapshots(sid), ctx.sessionManager, event.preparation.targetId);
217
+ const target = snapshotForEntry(getSnapshots(sid), sessionTreeView(ctx), event.preparation.targetId);
188
218
  if (!target)
189
219
  return;
190
220
  const changed = await snapshotChangedPaths(sid, target);
@@ -206,7 +236,7 @@ export default function rewind(pi) {
206
236
  catch {
207
237
  // Coarse stats are best-effort; still offer path preview.
208
238
  }
209
- const choice = await ctx.ui.select(`Restore ${n} file${n === 1 ? "" : "s"} to this point?${lineStats}\n${formatRestorePreview(changed, ctx.cwd)}`, ["Yes, restore files", "No, conversation only"]);
239
+ const choice = await ctx.ui.select(`Restore ${n} file${n === 1 ? "" : "s"} to this point?${lineStats}\n${formatRestorePreview(changed, ctx.cwd)}\n (covers edit/write changes only; files changed via bash are not tracked)`, ["Yes, restore files", "No, conversation only"]);
210
240
  if (choice?.startsWith("Yes")) {
211
241
  // Cache paths so session_tree can apply without a second full compare.
212
242
  pendingTreeRestore.set(sid, { snapshot: target, changedPaths: changed });
@@ -217,6 +247,9 @@ export default function rewind(pi) {
217
247
  const sid = ctx.sessionManager.getSessionId();
218
248
  if (!sid)
219
249
  return;
250
+ // The leaf moved; the old scan start may now be off-branch. Re-mark so a
251
+ // custom-triggered run after navigation still anchors correctly.
252
+ markAnchorScanStart(sid, ctx);
220
253
  const pending = pendingTreeRestore.get(sid);
221
254
  pendingTreeRestore.set(sid, null);
222
255
  if (!pending)
@@ -241,6 +274,7 @@ export default function rewind(pi) {
241
274
  disposeSession(sid);
242
275
  pendingTreeRestore.delete(sid);
243
276
  pendingPrompt.delete(sid);
277
+ anchorScanStart.delete(sid);
244
278
  });
245
279
  // /rewind: settings + storage menu (time-travel itself is via /tree).
246
280
  pi.registerCommand("rewind", {