@ashx-j/lunr 0.2.7 → 0.2.9

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 (164) hide show
  1. package/dist/builtin-extensions/ashxj-spinners.d.ts +7 -0
  2. package/dist/builtin-extensions/ashxj-spinners.d.ts.map +1 -1
  3. package/dist/builtin-extensions/ashxj-spinners.js +40 -3
  4. package/dist/builtin-extensions/ashxj-spinners.js.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.d.ts +4 -0
  6. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  7. package/dist/builtin-extensions/ashxj-tui.js +44 -0
  8. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  9. package/dist/builtin-extensions/lunr-behavior.d.ts +2 -1
  10. package/dist/builtin-extensions/lunr-behavior.d.ts.map +1 -1
  11. package/dist/builtin-extensions/lunr-behavior.js +26 -14
  12. package/dist/builtin-extensions/lunr-behavior.js.map +1 -1
  13. package/dist/builtin-extensions/lunr-todos.d.ts +4 -7
  14. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  15. package/dist/builtin-extensions/lunr-todos.js +18 -10
  16. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +3 -3
  19. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js +2 -2
  22. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +1 -7
  25. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +2 -2
  28. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  29. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +2 -2
  30. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +3 -4
  32. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +21 -0
  35. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +6 -18
  38. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +13 -5
  41. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts +21 -0
  43. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts.map +1 -0
  44. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js +22 -0
  45. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js.map +1 -0
  46. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
  47. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +2 -1
  51. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +10 -18
  54. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +3 -0
  56. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +9 -11
  58. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  59. package/dist/builtin-extensions/pi-web-access/render-search-error.d.ts.map +1 -1
  60. package/dist/builtin-extensions/pi-web-access/render-search-error.js +1 -1
  61. package/dist/builtin-extensions/pi-web-access/render-search-error.js.map +1 -1
  62. package/dist/catalog/models.json +1 -1
  63. package/dist/catalog/providers/github-copilot.json +1 -1
  64. package/dist/catalog/providers/google-vertex.json +1 -1
  65. package/dist/catalog/providers/google.json +1 -1
  66. package/dist/catalog/providers/nvidia.json +1 -1
  67. package/dist/catalog/providers/opencode-go.json +1 -1
  68. package/dist/catalog/providers/opencode.json +1 -1
  69. package/dist/catalog/providers/openrouter.json +1 -1
  70. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  71. package/dist/catalog/publication.json +3 -3
  72. package/dist/cli/update-cli.d.ts +16 -0
  73. package/dist/cli/update-cli.d.ts.map +1 -0
  74. package/dist/cli/update-cli.js +83 -0
  75. package/dist/cli/update-cli.js.map +1 -0
  76. package/dist/cli.d.ts.map +1 -1
  77. package/dist/cli.js +6 -0
  78. package/dist/cli.js.map +1 -1
  79. package/dist/core/behavior-preset.d.ts +60 -0
  80. package/dist/core/behavior-preset.d.ts.map +1 -0
  81. package/dist/core/behavior-preset.js +257 -0
  82. package/dist/core/behavior-preset.js.map +1 -0
  83. package/dist/core/customize.d.ts +3 -0
  84. package/dist/core/customize.d.ts.map +1 -1
  85. package/dist/core/customize.js +9 -0
  86. package/dist/core/customize.js.map +1 -1
  87. package/dist/core/footer-data-provider.d.ts +12 -1
  88. package/dist/core/footer-data-provider.d.ts.map +1 -1
  89. package/dist/core/footer-data-provider.js +70 -5
  90. package/dist/core/footer-data-provider.js.map +1 -1
  91. package/dist/core/keybindings.d.ts +2 -2
  92. package/dist/core/keybindings.d.ts.map +1 -1
  93. package/dist/core/keybindings.js +1 -1
  94. package/dist/core/keybindings.js.map +1 -1
  95. package/dist/core/model-tiers.d.ts +9 -0
  96. package/dist/core/model-tiers.d.ts.map +1 -1
  97. package/dist/core/model-tiers.js +16 -0
  98. package/dist/core/model-tiers.js.map +1 -1
  99. package/dist/core/rollback.d.ts +1 -1
  100. package/dist/core/rollback.d.ts.map +1 -1
  101. package/dist/core/rollback.js +1 -1
  102. package/dist/core/rollback.js.map +1 -1
  103. package/dist/core/settings-manager.d.ts +23 -0
  104. package/dist/core/settings-manager.d.ts.map +1 -1
  105. package/dist/core/settings-manager.js +66 -0
  106. package/dist/core/settings-manager.js.map +1 -1
  107. package/dist/core/slash-commands.d.ts.map +1 -1
  108. package/dist/core/slash-commands.js +2 -1
  109. package/dist/core/slash-commands.js.map +1 -1
  110. package/dist/core/tools/bash.d.ts.map +1 -1
  111. package/dist/core/tools/bash.js +1 -3
  112. package/dist/core/tools/bash.js.map +1 -1
  113. package/dist/core/update-check.d.ts +32 -0
  114. package/dist/core/update-check.d.ts.map +1 -0
  115. package/dist/core/update-check.js +112 -0
  116. package/dist/core/update-check.js.map +1 -0
  117. package/dist/core/usage-service.d.ts +24 -1
  118. package/dist/core/usage-service.d.ts.map +1 -1
  119. package/dist/core/usage-service.js +91 -14
  120. package/dist/core/usage-service.js.map +1 -1
  121. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  122. package/dist/gateway/agent-bridge.js +5 -1
  123. package/dist/gateway/agent-bridge.js.map +1 -1
  124. package/dist/gateway/cron.d.ts.map +1 -1
  125. package/dist/gateway/cron.js +5 -1
  126. package/dist/gateway/cron.js.map +1 -1
  127. package/dist/main.d.ts.map +1 -1
  128. package/dist/main.js +13 -1
  129. package/dist/main.js.map +1 -1
  130. package/dist/modes/interactive/components/assistant-message.d.ts +10 -1
  131. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/assistant-message.js +154 -68
  133. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  134. package/dist/modes/interactive/components/settings-selector.d.ts +13 -0
  135. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  136. package/dist/modes/interactive/components/settings-selector.js +157 -10
  137. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  138. package/dist/modes/interactive/components/thinking-summary.d.ts +4 -4
  139. package/dist/modes/interactive/components/thinking-summary.d.ts.map +1 -1
  140. package/dist/modes/interactive/components/thinking-summary.js +7 -5
  141. package/dist/modes/interactive/components/thinking-summary.js.map +1 -1
  142. package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
  143. package/dist/modes/interactive/components/thinking-tail.js +5 -3
  144. package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
  145. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  146. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  147. package/dist/modes/interactive/components/tool-execution.js +9 -0
  148. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  149. package/dist/modes/interactive/interactive-mode.d.ts +6 -1
  150. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  151. package/dist/modes/interactive/interactive-mode.js +124 -43
  152. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  153. package/docs/keybindings.md +1 -1
  154. package/docs/providers.md +2 -0
  155. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  156. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  157. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  158. package/examples/extensions/gondolin/package-lock.json +2 -2
  159. package/examples/extensions/gondolin/package.json +1 -1
  160. package/examples/extensions/sandbox/package-lock.json +2 -2
  161. package/examples/extensions/sandbox/package.json +1 -1
  162. package/examples/extensions/with-deps/package-lock.json +2 -2
  163. package/examples/extensions/with-deps/package.json +1 -1
  164. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@ashx-j/lunr-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.mode.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.chat.pageUp\": true;\n\t\"app.chat.pageDown\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@ashx-j/lunr-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: [],\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.mode.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle permission mode\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.chat.pageUp\": {\n\t\tdefaultKeys: [\"shift+pageUp\", \"ctrl+up\"],\n\t\tdescription: \"Scroll chat up\",\n\t},\n\t\"app.chat.pageDown\": {\n\t\tdefaultKeys: [\"shift+pageDown\", \"ctrl+down\"],\n\t\tdescription: \"Scroll chat down\",\n\t},\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcyclePermissionMode: \"app.mode.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
1
+ {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@ashx-j/lunr-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.mode.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.chat.pageUp\": true;\n\t\"app.chat.pageDown\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@ashx-j/lunr-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: [],\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.mode.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle permission mode\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: [], description: \"Toggle tool output (unbound; click a card instead)\" },\n\t\"app.chat.pageUp\": {\n\t\tdefaultKeys: [\"shift+pageUp\", \"ctrl+up\"],\n\t\tdescription: \"Scroll chat up\",\n\t},\n\t\"app.chat.pageDown\": {\n\t\tdefaultKeys: [\"shift+pageDown\", \"ctrl+down\"],\n\t\tdescription: \"Scroll chat down\",\n\t},\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcyclePermissionMode: \"app.mode.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
@@ -1,3 +1,4 @@
1
+ import type { ThinkingLevel } from "@ashx-j/lunr-agent";
1
2
  import type { ModelTierName, SettingsManager } from "./settings-manager.ts";
2
3
  /**
3
4
  * 3-tier subagent model routing (light / standard / heavy).
@@ -11,6 +12,12 @@ export declare const MODEL_TIERS_BRIDGE_SYMBOL: unique symbol;
11
12
  export interface ModelTiersBridge {
12
13
  /** Resolve a tier to its configured "provider/model" string, or undefined if unset/unknown. */
13
14
  getTierModel(tier: string): string | undefined;
15
+ /** Per-tier thinking level, or undefined to inherit the parent session. */
16
+ getTierThinking(tier: string): ThinkingLevel | undefined;
17
+ /** Parent session thinking level (live). Undefined when no session is bound. */
18
+ getParentThinking(): ThinkingLevel | undefined;
19
+ /** Bind (or clear) the live parent-session thinking reader. */
20
+ setParentThinkingProvider(provider: (() => ThinkingLevel | undefined) | undefined): void;
14
21
  /** Whether tier-based subagent routing is enabled in settings. */
15
22
  isTierModeEnabled(): boolean;
16
23
  /** Ask the pi-subagents extension to rebuild its tool description from current settings. */
@@ -31,4 +38,6 @@ export declare function getModelTiersBridge(): ModelTiersBridge | undefined;
31
38
  export declare function getTierModel(tier: ModelTierName): string | undefined;
32
39
  /** Whether tier-based subagent routing is enabled in settings. */
33
40
  export declare function isTierModeEnabled(): boolean;
41
+ /** Per-tier thinking, or undefined to inherit the parent session. */
42
+ export declare function getTierThinking(tier: ModelTierName): ThinkingLevel | undefined;
34
43
  //# sourceMappingURL=model-tiers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-tiers.d.ts","sourceRoot":"","sources":["../../src/core/model-tiers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;GAMG;AAEH,eAAO,MAAM,UAAU,EAAE,SAAS,aAAa,EAAmC,CAAC;AAEnF,eAAO,MAAM,yBAAyB,eAAkC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,+FAA+F;IAC/F,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/C,kEAAkE;IAClE,iBAAiB,IAAI,OAAO,CAAC;IAC7B,4FAA4F;IAC5F,sBAAsB,IAAI,IAAI,CAAC;IAC/B,qHAAqH;IACrH,gCAAgC,CAAC,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;CAC5E;AAkCD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAG9E;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE;AAED,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C","sourcesContent":["import type { ModelTierName, SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * 3-tier subagent model routing (light / standard / heavy).\n *\n * The bridge is exposed on `globalThis` under `Symbol.for(\"@lunr/model-tiers\")` so\n * builtin extensions (pi-subagents) can resolve `tier` params without importing core\n * code (avoids import cycles and keeps upstream diffs minimal).\n */\n\nexport const TIER_NAMES: readonly ModelTierName[] = [\"light\", \"standard\", \"heavy\"];\n\nexport const MODEL_TIERS_BRIDGE_SYMBOL = Symbol.for(\"@lunr/model-tiers\");\n\nexport interface ModelTiersBridge {\n\t/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\n\tgetTierModel(tier: string): string | undefined;\n\t/** Whether tier-based subagent routing is enabled in settings. */\n\tisTierModeEnabled(): boolean;\n\t/** Ask the pi-subagents extension to rebuild its tool description from current settings. */\n\trefreshToolDescription(): void;\n\t/** Register (or clear) the callback used by {@link refreshToolDescription}. Called by the extension once at load. */\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void;\n}\n\nfunction isModelTierName(tier: string): tier is ModelTierName {\n\treturn tier === \"light\" || tier === \"standard\" || tier === \"heavy\";\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\nlet toolDescriptionRefresher: (() => void) | undefined;\n\nconst bridge: ModelTiersBridge = {\n\tgetTierModel(tier: string): string | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierModel(tier);\n\t},\n\tisTierModeEnabled(): boolean {\n\t\treturn activeSettingsManager?.getModelTiersEnabled() ?? false;\n\t},\n\trefreshToolDescription(): void {\n\t\tif (!toolDescriptionRefresher) return;\n\t\ttry {\n\t\t\ttoolDescriptionRefresher();\n\t\t} catch (error) {\n\t\t\t// Settings UI is on the uncaughtException path; a stale/broken refresher\n\t\t\t// must not kill the process. persist of the toggle already happened.\n\t\t\tconsole.warn(\n\t\t\t\t`[model-tiers] refreshToolDescription failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t},\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void {\n\t\ttoolDescriptionRefresher = refresher;\n\t},\n};\n\n/**\n * Register (or re-point) the global model-tier bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerModelTierBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getModelTiersBridge(): ModelTiersBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] as ModelTiersBridge | undefined;\n}\n\n/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\nexport function getTierModel(tier: ModelTierName): string | undefined {\n\treturn bridge.getTierModel(tier);\n}\n\n/** Whether tier-based subagent routing is enabled in settings. */\nexport function isTierModeEnabled(): boolean {\n\treturn bridge.isTierModeEnabled();\n}\n"]}
1
+ {"version":3,"file":"model-tiers.d.ts","sourceRoot":"","sources":["../../src/core/model-tiers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;GAMG;AAEH,eAAO,MAAM,UAAU,EAAE,SAAS,aAAa,EAAmC,CAAC;AAEnF,eAAO,MAAM,yBAAyB,eAAkC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,+FAA+F;IAC/F,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/C,2EAA2E;IAC3E,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IACzD,gFAAgF;IAChF,iBAAiB,IAAI,aAAa,GAAG,SAAS,CAAC;IAC/C,+DAA+D;IAC/D,yBAAyB,CAAC,QAAQ,EAAE,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IACzF,kEAAkE;IAClE,iBAAiB,IAAI,OAAO,CAAC;IAC7B,4FAA4F;IAC5F,sBAAsB,IAAI,IAAI,CAAC;IAC/B,qHAAqH;IACrH,gCAAgC,CAAC,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;CAC5E;AA6CD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAG9E;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE;AAED,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,SAAS,CAE9E","sourcesContent":["import type { ThinkingLevel } from \"@ashx-j/lunr-agent\";\nimport type { ModelTierName, SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * 3-tier subagent model routing (light / standard / heavy).\n *\n * The bridge is exposed on `globalThis` under `Symbol.for(\"@lunr/model-tiers\")` so\n * builtin extensions (pi-subagents) can resolve `tier` params without importing core\n * code (avoids import cycles and keeps upstream diffs minimal).\n */\n\nexport const TIER_NAMES: readonly ModelTierName[] = [\"light\", \"standard\", \"heavy\"];\n\nexport const MODEL_TIERS_BRIDGE_SYMBOL = Symbol.for(\"@lunr/model-tiers\");\n\nexport interface ModelTiersBridge {\n\t/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\n\tgetTierModel(tier: string): string | undefined;\n\t/** Per-tier thinking level, or undefined to inherit the parent session. */\n\tgetTierThinking(tier: string): ThinkingLevel | undefined;\n\t/** Parent session thinking level (live). Undefined when no session is bound. */\n\tgetParentThinking(): ThinkingLevel | undefined;\n\t/** Bind (or clear) the live parent-session thinking reader. */\n\tsetParentThinkingProvider(provider: (() => ThinkingLevel | undefined) | undefined): void;\n\t/** Whether tier-based subagent routing is enabled in settings. */\n\tisTierModeEnabled(): boolean;\n\t/** Ask the pi-subagents extension to rebuild its tool description from current settings. */\n\trefreshToolDescription(): void;\n\t/** Register (or clear) the callback used by {@link refreshToolDescription}. Called by the extension once at load. */\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void;\n}\n\nfunction isModelTierName(tier: string): tier is ModelTierName {\n\treturn tier === \"light\" || tier === \"standard\" || tier === \"heavy\";\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\nlet toolDescriptionRefresher: (() => void) | undefined;\nlet parentThinkingProvider: (() => ThinkingLevel | undefined) | undefined;\n\nconst bridge: ModelTiersBridge = {\n\tgetTierModel(tier: string): string | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierModel(tier);\n\t},\n\tgetTierThinking(tier: string): ThinkingLevel | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierThinking(tier);\n\t},\n\tgetParentThinking(): ThinkingLevel | undefined {\n\t\treturn parentThinkingProvider?.();\n\t},\n\tsetParentThinkingProvider(provider: (() => ThinkingLevel | undefined) | undefined): void {\n\t\tparentThinkingProvider = provider;\n\t},\n\tisTierModeEnabled(): boolean {\n\t\treturn activeSettingsManager?.getModelTiersEnabled() ?? false;\n\t},\n\trefreshToolDescription(): void {\n\t\tif (!toolDescriptionRefresher) return;\n\t\ttry {\n\t\t\ttoolDescriptionRefresher();\n\t\t} catch (error) {\n\t\t\t// Settings UI is on the uncaughtException path; a stale/broken refresher\n\t\t\t// must not kill the process. persist of the toggle already happened.\n\t\t\tconsole.warn(\n\t\t\t\t`[model-tiers] refreshToolDescription failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t},\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void {\n\t\ttoolDescriptionRefresher = refresher;\n\t},\n};\n\n/**\n * Register (or re-point) the global model-tier bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerModelTierBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getModelTiersBridge(): ModelTiersBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] as ModelTiersBridge | undefined;\n}\n\n/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\nexport function getTierModel(tier: ModelTierName): string | undefined {\n\treturn bridge.getTierModel(tier);\n}\n\n/** Whether tier-based subagent routing is enabled in settings. */\nexport function isTierModeEnabled(): boolean {\n\treturn bridge.isTierModeEnabled();\n}\n\n/** Per-tier thinking, or undefined to inherit the parent session. */\nexport function getTierThinking(tier: ModelTierName): ThinkingLevel | undefined {\n\treturn bridge.getTierThinking(tier);\n}\n"]}
@@ -12,12 +12,24 @@ function isModelTierName(tier) {
12
12
  }
13
13
  let activeSettingsManager;
14
14
  let toolDescriptionRefresher;
15
+ let parentThinkingProvider;
15
16
  const bridge = {
16
17
  getTierModel(tier) {
17
18
  if (!activeSettingsManager || !isModelTierName(tier))
18
19
  return undefined;
19
20
  return activeSettingsManager.getTierModel(tier);
20
21
  },
22
+ getTierThinking(tier) {
23
+ if (!activeSettingsManager || !isModelTierName(tier))
24
+ return undefined;
25
+ return activeSettingsManager.getTierThinking(tier);
26
+ },
27
+ getParentThinking() {
28
+ return parentThinkingProvider?.();
29
+ },
30
+ setParentThinkingProvider(provider) {
31
+ parentThinkingProvider = provider;
32
+ },
21
33
  isTierModeEnabled() {
22
34
  return activeSettingsManager?.getModelTiersEnabled() ?? false;
23
35
  },
@@ -59,4 +71,8 @@ export function getTierModel(tier) {
59
71
  export function isTierModeEnabled() {
60
72
  return bridge.isTierModeEnabled();
61
73
  }
74
+ /** Per-tier thinking, or undefined to inherit the parent session. */
75
+ export function getTierThinking(tier) {
76
+ return bridge.getTierThinking(tier);
77
+ }
62
78
  //# sourceMappingURL=model-tiers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-tiers.js","sourceRoot":"","sources":["../../src/core/model-tiers.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,UAAU,GAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAEnF,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AAazE,SAAS,eAAe,CAAC,IAAY,EAAyB;IAC7D,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,OAAO,CAAC;AAAA,CACnE;AAED,IAAI,qBAAkD,CAAC;AACvD,IAAI,wBAAkD,CAAC;AAEvD,MAAM,MAAM,GAAqB;IAChC,YAAY,CAAC,IAAY,EAAsB;QAC9C,IAAI,CAAC,qBAAqB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QACvE,OAAO,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAAA,CAChD;IACD,iBAAiB,GAAY;QAC5B,OAAO,qBAAqB,EAAE,oBAAoB,EAAE,IAAI,KAAK,CAAC;IAAA,CAC9D;IACD,sBAAsB,GAAS;QAC9B,IAAI,CAAC,wBAAwB;YAAE,OAAO;QACtC,IAAI,CAAC;YACJ,wBAAwB,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,yEAAyE;YACzE,qEAAqE;YACrE,OAAO,CAAC,IAAI,CACX,gDAAgD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACxG,CAAC;QACH,CAAC;IAAA,CACD;IACD,gCAAgC,CAAC,SAAmC,EAAQ;QAC3E,wBAAwB,GAAG,SAAS,CAAC;IAAA,CACrC;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAgC,EAAQ;IAC/E,qBAAqB,GAAG,eAAe,CAAC;IACvC,UAAsC,CAAC,yBAAyB,CAAC,GAAG,MAAM,CAAC;AAAA,CAC5E;AAED,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,GAAiC;IACnE,OAAQ,UAAsC,CAAC,yBAAyB,CAAiC,CAAC;AAAA,CAC1G;AAED,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,IAAmB,EAAsB;IACrE,OAAO,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAAA,CACjC;AAED,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,GAAY;IAC5C,OAAO,MAAM,CAAC,iBAAiB,EAAE,CAAC;AAAA,CAClC","sourcesContent":["import type { ModelTierName, SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * 3-tier subagent model routing (light / standard / heavy).\n *\n * The bridge is exposed on `globalThis` under `Symbol.for(\"@lunr/model-tiers\")` so\n * builtin extensions (pi-subagents) can resolve `tier` params without importing core\n * code (avoids import cycles and keeps upstream diffs minimal).\n */\n\nexport const TIER_NAMES: readonly ModelTierName[] = [\"light\", \"standard\", \"heavy\"];\n\nexport const MODEL_TIERS_BRIDGE_SYMBOL = Symbol.for(\"@lunr/model-tiers\");\n\nexport interface ModelTiersBridge {\n\t/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\n\tgetTierModel(tier: string): string | undefined;\n\t/** Whether tier-based subagent routing is enabled in settings. */\n\tisTierModeEnabled(): boolean;\n\t/** Ask the pi-subagents extension to rebuild its tool description from current settings. */\n\trefreshToolDescription(): void;\n\t/** Register (or clear) the callback used by {@link refreshToolDescription}. Called by the extension once at load. */\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void;\n}\n\nfunction isModelTierName(tier: string): tier is ModelTierName {\n\treturn tier === \"light\" || tier === \"standard\" || tier === \"heavy\";\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\nlet toolDescriptionRefresher: (() => void) | undefined;\n\nconst bridge: ModelTiersBridge = {\n\tgetTierModel(tier: string): string | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierModel(tier);\n\t},\n\tisTierModeEnabled(): boolean {\n\t\treturn activeSettingsManager?.getModelTiersEnabled() ?? false;\n\t},\n\trefreshToolDescription(): void {\n\t\tif (!toolDescriptionRefresher) return;\n\t\ttry {\n\t\t\ttoolDescriptionRefresher();\n\t\t} catch (error) {\n\t\t\t// Settings UI is on the uncaughtException path; a stale/broken refresher\n\t\t\t// must not kill the process. persist of the toggle already happened.\n\t\t\tconsole.warn(\n\t\t\t\t`[model-tiers] refreshToolDescription failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t},\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void {\n\t\ttoolDescriptionRefresher = refresher;\n\t},\n};\n\n/**\n * Register (or re-point) the global model-tier bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerModelTierBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getModelTiersBridge(): ModelTiersBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] as ModelTiersBridge | undefined;\n}\n\n/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\nexport function getTierModel(tier: ModelTierName): string | undefined {\n\treturn bridge.getTierModel(tier);\n}\n\n/** Whether tier-based subagent routing is enabled in settings. */\nexport function isTierModeEnabled(): boolean {\n\treturn bridge.isTierModeEnabled();\n}\n"]}
1
+ {"version":3,"file":"model-tiers.js","sourceRoot":"","sources":["../../src/core/model-tiers.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,UAAU,GAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAEnF,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AAmBzE,SAAS,eAAe,CAAC,IAAY,EAAyB;IAC7D,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,OAAO,CAAC;AAAA,CACnE;AAED,IAAI,qBAAkD,CAAC;AACvD,IAAI,wBAAkD,CAAC;AACvD,IAAI,sBAAqE,CAAC;AAE1E,MAAM,MAAM,GAAqB;IAChC,YAAY,CAAC,IAAY,EAAsB;QAC9C,IAAI,CAAC,qBAAqB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QACvE,OAAO,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAAA,CAChD;IACD,eAAe,CAAC,IAAY,EAA6B;QACxD,IAAI,CAAC,qBAAqB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QACvE,OAAO,qBAAqB,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAAA,CACnD;IACD,iBAAiB,GAA8B;QAC9C,OAAO,sBAAsB,EAAE,EAAE,CAAC;IAAA,CAClC;IACD,yBAAyB,CAAC,QAAuD,EAAQ;QACxF,sBAAsB,GAAG,QAAQ,CAAC;IAAA,CAClC;IACD,iBAAiB,GAAY;QAC5B,OAAO,qBAAqB,EAAE,oBAAoB,EAAE,IAAI,KAAK,CAAC;IAAA,CAC9D;IACD,sBAAsB,GAAS;QAC9B,IAAI,CAAC,wBAAwB;YAAE,OAAO;QACtC,IAAI,CAAC;YACJ,wBAAwB,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,yEAAyE;YACzE,qEAAqE;YACrE,OAAO,CAAC,IAAI,CACX,gDAAgD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACxG,CAAC;QACH,CAAC;IAAA,CACD;IACD,gCAAgC,CAAC,SAAmC,EAAQ;QAC3E,wBAAwB,GAAG,SAAS,CAAC;IAAA,CACrC;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAgC,EAAQ;IAC/E,qBAAqB,GAAG,eAAe,CAAC;IACvC,UAAsC,CAAC,yBAAyB,CAAC,GAAG,MAAM,CAAC;AAAA,CAC5E;AAED,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,GAAiC;IACnE,OAAQ,UAAsC,CAAC,yBAAyB,CAAiC,CAAC;AAAA,CAC1G;AAED,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,IAAmB,EAAsB;IACrE,OAAO,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAAA,CACjC;AAED,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,GAAY;IAC5C,OAAO,MAAM,CAAC,iBAAiB,EAAE,CAAC;AAAA,CAClC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe,CAAC,IAAmB,EAA6B;IAC/E,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AAAA,CACpC","sourcesContent":["import type { ThinkingLevel } from \"@ashx-j/lunr-agent\";\nimport type { ModelTierName, SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * 3-tier subagent model routing (light / standard / heavy).\n *\n * The bridge is exposed on `globalThis` under `Symbol.for(\"@lunr/model-tiers\")` so\n * builtin extensions (pi-subagents) can resolve `tier` params without importing core\n * code (avoids import cycles and keeps upstream diffs minimal).\n */\n\nexport const TIER_NAMES: readonly ModelTierName[] = [\"light\", \"standard\", \"heavy\"];\n\nexport const MODEL_TIERS_BRIDGE_SYMBOL = Symbol.for(\"@lunr/model-tiers\");\n\nexport interface ModelTiersBridge {\n\t/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\n\tgetTierModel(tier: string): string | undefined;\n\t/** Per-tier thinking level, or undefined to inherit the parent session. */\n\tgetTierThinking(tier: string): ThinkingLevel | undefined;\n\t/** Parent session thinking level (live). Undefined when no session is bound. */\n\tgetParentThinking(): ThinkingLevel | undefined;\n\t/** Bind (or clear) the live parent-session thinking reader. */\n\tsetParentThinkingProvider(provider: (() => ThinkingLevel | undefined) | undefined): void;\n\t/** Whether tier-based subagent routing is enabled in settings. */\n\tisTierModeEnabled(): boolean;\n\t/** Ask the pi-subagents extension to rebuild its tool description from current settings. */\n\trefreshToolDescription(): void;\n\t/** Register (or clear) the callback used by {@link refreshToolDescription}. Called by the extension once at load. */\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void;\n}\n\nfunction isModelTierName(tier: string): tier is ModelTierName {\n\treturn tier === \"light\" || tier === \"standard\" || tier === \"heavy\";\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\nlet toolDescriptionRefresher: (() => void) | undefined;\nlet parentThinkingProvider: (() => ThinkingLevel | undefined) | undefined;\n\nconst bridge: ModelTiersBridge = {\n\tgetTierModel(tier: string): string | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierModel(tier);\n\t},\n\tgetTierThinking(tier: string): ThinkingLevel | undefined {\n\t\tif (!activeSettingsManager || !isModelTierName(tier)) return undefined;\n\t\treturn activeSettingsManager.getTierThinking(tier);\n\t},\n\tgetParentThinking(): ThinkingLevel | undefined {\n\t\treturn parentThinkingProvider?.();\n\t},\n\tsetParentThinkingProvider(provider: (() => ThinkingLevel | undefined) | undefined): void {\n\t\tparentThinkingProvider = provider;\n\t},\n\tisTierModeEnabled(): boolean {\n\t\treturn activeSettingsManager?.getModelTiersEnabled() ?? false;\n\t},\n\trefreshToolDescription(): void {\n\t\tif (!toolDescriptionRefresher) return;\n\t\ttry {\n\t\t\ttoolDescriptionRefresher();\n\t\t} catch (error) {\n\t\t\t// Settings UI is on the uncaughtException path; a stale/broken refresher\n\t\t\t// must not kill the process. persist of the toggle already happened.\n\t\t\tconsole.warn(\n\t\t\t\t`[model-tiers] refreshToolDescription failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t},\n\tregisterToolDescriptionRefresher(refresher: (() => void) | undefined): void {\n\t\ttoolDescriptionRefresher = refresher;\n\t},\n};\n\n/**\n * Register (or re-point) the global model-tier bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerModelTierBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getModelTiersBridge(): ModelTiersBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MODEL_TIERS_BRIDGE_SYMBOL] as ModelTiersBridge | undefined;\n}\n\n/** Resolve a tier to its configured \"provider/model\" string, or undefined if unset/unknown. */\nexport function getTierModel(tier: ModelTierName): string | undefined {\n\treturn bridge.getTierModel(tier);\n}\n\n/** Whether tier-based subagent routing is enabled in settings. */\nexport function isTierModeEnabled(): boolean {\n\treturn bridge.isTierModeEnabled();\n}\n\n/** Per-tier thinking, or undefined to inherit the parent session. */\nexport function getTierThinking(tier: ModelTierName): ThinkingLevel | undefined {\n\treturn bridge.getTierThinking(tier);\n}\n"]}
@@ -4,7 +4,7 @@
4
4
  * Captures pre-write file content before edit/write tools mutate files, keyed
5
5
  * by user-turn and session id. `/rollback` restores the newest non-empty turn's
6
6
  * snapshots and then rewinds the conversation via a session fork (persistent,
7
- * unlike /undo).
7
+ * unlike /undo and /edit, which stay in the same session file).
8
8
  *
9
9
  * Capture modes (setting `rollbackCapture`):
10
10
  * - copies: snapshot pre-write content; restore = write back + delete files
@@ -1 +1 @@
1
- {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/core/rollback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAkBH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACvB;AAsCD,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAEhG;AAwBD,wBAAgB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAOnE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,yFAAyF;AACzF,wBAAgB,6BAA6B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAEjE;AAED,wBAAgB,yBAAyB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAUD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAyBrF;AA6BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA2DxE;AA6ID,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yFAAyF;IACzF,aAAa,EAAE,MAAM,CAAC;CACtB;AA0CD,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CA2DnE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOxG;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAwBvF;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA6BtD","sourcesContent":["/**\n * lunR: rollback service — pre-edit file snapshots for /rollback.\n *\n * Captures pre-write file content before edit/write tools mutate files, keyed\n * by user-turn and session id. `/rollback` restores the newest non-empty turn's\n * snapshots and then rewinds the conversation via a session fork (persistent,\n * unlike /undo).\n *\n * Capture modes (setting `rollbackCapture`):\n * - copies: snapshot pre-write content; restore = write back + delete files\n * the agent created with edit/write.\n * - hybrid: copies + also delete files created OUTSIDE tools (tree scope).\n * - shadow-git: hidden git repo (deferred — behaves like copies).\n *\n * Scope (setting `rollbackScope`):\n * - tools: only edit/write tool changes.\n * - tree: also catches bash side-effects. Baseline = `git status --porcelain`\n * at turn START (pre-change content); at agent_end, files that are\n * dirty but were clean/absent at baseline are captured too (untracked\n * = created during the turn; tracked = restored from HEAD). Skipped\n * with a one-time warning when cwd is not a repo.\n *\n * Snapshots persist on disk under `~/.lunr/rollback/<session-id>/<turn-n>/`\n * (manifest.json + sha1-named .snap payloads) and are reloaded on init, so\n * rollback survives restarts. Turn granularity = one user message → agent_end.\n * Retention prunes beyond `rollbackTurns` turns.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, normalize, resolve } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface Snapshot {\n\texisted: boolean;\n\tcontent?: Buffer;\n\tcreatedByTool: boolean;\n}\n\ninterface TurnSnapshots {\n\tturnIndex: number;\n\tcwd?: string;\n\tfiles: Map<string, Snapshot>;\n}\n\ninterface ManifestEntry {\n\texisted: boolean;\n\tcreatedByTool: boolean;\n\tsnapFile?: string;\n}\n\ninterface TurnManifest {\n\tcwd?: string;\n\tfiles: Record<string, ManifestEntry>;\n}\n\nconst ROLLBACK_BASE = join(homedir(), \".lunr\", \"rollback\");\n\ninterface RollbackContext {\n\tsessionId: string;\n\tsettingsManager: SettingsManager | undefined;\n\tsessionForceEnabled: boolean;\n\tturns: TurnSnapshots[];\n\tcurrentTurnIndex: number;\n\trepoWarningShown: boolean;\n\texternalModWarningShown: boolean;\n\t/** Last cwd seen by beginTurn — reused by auto-started turns (mid-turn enable). */\n\tlastCwd?: string;\n}\n\nconst contexts = new Map<string, RollbackContext>();\nlet lastSessionId: string | undefined;\nlet globalSettingsManager: SettingsManager | undefined;\nlet warningHandler: ((message: string) => void) | undefined;\n\n/** Register a sink for one-time user-facing warnings (e.g. tree scope outside a git repo). */\nexport function setRollbackWarningHandler(handler: ((message: string) => void) | undefined): void {\n\twarningHandler = handler;\n}\n\nfunction createContext(sessionId: string): RollbackContext {\n\treturn {\n\t\tsessionId,\n\t\tsettingsManager: globalSettingsManager,\n\t\tsessionForceEnabled: false,\n\t\tturns: [],\n\t\tcurrentTurnIndex: -1,\n\t\trepoWarningShown: false,\n\t\texternalModWarningShown: false,\n\t};\n}\n\nfunction getContext(sessionId?: string): RollbackContext {\n\tconst sid = sessionId ?? lastSessionId ?? \"default\";\n\tlet ctx = contexts.get(sid);\n\tif (!ctx) {\n\t\tctx = createContext(sid);\n\t\tcontexts.set(sid, ctx);\n\t}\n\treturn ctx;\n}\n\nexport function initRollback(sm: SettingsManager, sid: string): void {\n\tglobalSettingsManager = sm;\n\tlastSessionId = sid;\n\tcontexts.set(sid, createContext(sid));\n\tconst ctx = contexts.get(sid)!;\n\tctx.settingsManager = sm;\n\tloadPersistedTurns(ctx);\n}\n\nexport function isRollbackEnabled(sessionId?: string): boolean {\n\tconst ctx = getContext(sessionId);\n\treturn (ctx.settingsManager?.getRollbackEnabled() ?? false) || ctx.sessionForceEnabled;\n}\n\n/** Whether rollback is force-enabled for this session (e.g. by auto permission mode). */\nexport function isRollbackSessionForceEnabled(sessionId?: string): boolean {\n\treturn getContext(sessionId).sessionForceEnabled;\n}\n\nexport function enableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = true;\n}\n\nexport function disableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = false;\n}\n\nfunction pruneTurns(ctx: RollbackContext): void {\n\tconst maxTurns = ctx.settingsManager?.getRollbackTurns() ?? 2;\n\twhile (ctx.turns.length > maxTurns) {\n\t\tconst old = ctx.turns.shift();\n\t\tif (old) cleanupTurnFiles(ctx, old);\n\t}\n}\n\n/**\n * Begin a new turn: push a new snapshot map, prune old turns beyond retention.\n * When `cwd` is given and scope is `tree`, also capture the pre-turn baseline\n * (content of all currently dirty/untracked files) so bash side-effects can be\n * restored later.\n */\nexport function beginTurn(cwd?: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: remember the session cwd so auto-started turns (mid-turn enable) can\n\t// still be constrained by the allowed-roots check.\n\tif (cwd) ctx.lastCwd = cwd;\n\tctx.currentTurnIndex++;\n\tconst turn: TurnSnapshots = { turnIndex: ctx.currentTurnIndex, cwd, files: new Map() };\n\tctx.turns.push(turn);\n\tpruneTurns(ctx);\n\tif (cwd && ctx.settingsManager?.getRollbackScope() === \"tree\") {\n\t\tcaptureTreeBaseline(ctx, cwd);\n\t}\n}\n\n/** Capture pre-write content for a file path, recording under the current turn. */\nexport function rollbackSnapshotBeforeWrite(absPath: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: auto-started turn inherits the last known cwd so restores stay\n\t// root-checked; without any recorded cwd the allowed roots still apply.\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) beginTurn(ctx.lastCwd, ctx.sessionId);\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tif (turn.files.has(absPath)) return; // already snapshotted this turn\n\n\ttry {\n\t\tconst existed = existsSync(absPath);\n\t\tlet content: Buffer | undefined;\n\t\tif (existed) {\n\t\t\tcontent = readFileSync(absPath);\n\t\t}\n\t\tturn.files.set(absPath, {\n\t\t\texisted,\n\t\t\tcontent,\n\t\t\tcreatedByTool: true,\n\t\t});\n\t\tpersistSnapshot(ctx, turn, absPath);\n\t} catch {\n\t\t// snapshot failures are non-fatal\n\t}\n}\n\n/**\n * Tree scope, turn START: snapshot the current (pre-turn) content of every\n * dirty/untracked file so modifications made during the turn can be restored.\n */\nfunction captureTreeBaseline(ctx: RollbackContext, cwd: string): void {\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { path } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue;\n\t\ttry {\n\t\t\tconst existed = existsSync(abs);\n\t\t\tturn.files.set(abs, {\n\t\t\t\texisted,\n\t\t\t\tcontent: existed ? readFileSync(abs) : undefined,\n\t\t\t\tcreatedByTool: false,\n\t\t\t});\n\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Tree scope, turn END: pick up files that became dirty during the turn but\n * were clean/absent at baseline. Untracked = created during the turn (deleted\n * on restore under hybrid); tracked-modified = restore from HEAD content.\n */\nexport function captureTreeChanges(cwd: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\tif (ctx.settingsManager?.getRollbackScope() !== \"tree\") return;\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) return;\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { status, path, source } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue; // already captured (baseline or tool snapshot)\n\t\ttry {\n\t\t\tif (status === \"??\" || status.includes(\"R\")) {\n\t\t\t\t// lunr: untracked now (or rename dest) and not in the baseline — created\n\t\t\t\t// during this turn (deleted on restore under hybrid/shadow-git).\n\t\t\t\tturn.files.set(abs, { existed: false, createdByTool: false });\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t\t// lunr: rename source existed at HEAD and is gone now — restore its\n\t\t\t\t// HEAD content on rollback, like a tracked file deleted mid-turn.\n\t\t\t\tif (status.includes(\"R\") && source) {\n\t\t\t\t\tconst sourceAbs = join(cwd, source);\n\t\t\t\t\tif (!turn.files.has(sourceAbs)) {\n\t\t\t\t\t\tconst headSrc = spawnSync(\"git\", [\"show\", `HEAD:${source}`], {\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!headSrc.error && headSrc.status === 0 && headSrc.stdout) {\n\t\t\t\t\t\t\tturn.files.set(sourceAbs, {\n\t\t\t\t\t\t\t\texisted: true,\n\t\t\t\t\t\t\t\tcontent: headSrc.stdout as Buffer,\n\t\t\t\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tpersistSnapshot(ctx, turn, sourceAbs);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tracked file that was clean at turn start; original = HEAD content.\n\t\t\t\tconst head = spawnSync(\"git\", [\"show\", `HEAD:${path}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tif (head.error || head.status !== 0 || !head.stdout) continue;\n\t\t\t\tturn.files.set(abs, {\n\t\t\t\t\texisted: true,\n\t\t\t\t\tcontent: head.stdout as Buffer,\n\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t});\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t}\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Run `git status --porcelain -z` and parse entries, or return undefined (with\n * a one-time warning) when cwd is not a repo / git fails. Rename/copy entries\n * carry their source path in a separate bare field — parsed into `source`.\n */\nfunction gitPorcelain(\n\tctx: RollbackContext,\n\tcwd: string,\n): Array<{ status: string; path: string; source?: string }> | undefined {\n\ttry {\n\t\tconst result = spawnSync(\"git\", [\"status\", \"--porcelain\", \"-z\"], { cwd, encoding: \"utf8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\twarnOnce(\n\t\t\t\tctx,\n\t\t\t\t\"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\",\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst fields = (result.stdout ?? \"\").split(\"\\0\").filter((f) => f.length > 0);\n\t\tconst entries: Array<{ status: string; path: string; source?: string }> = [];\n\t\tfor (let i = 0; i < fields.length; i++) {\n\t\t\tconst field = fields[i];\n\t\t\tconst status = field.slice(0, 2);\n\t\t\tconst path = field.slice(3);\n\t\t\tlet source: string | undefined;\n\t\t\tif (status.includes(\"R\") || status.includes(\"C\")) {\n\t\t\t\t// Rename/copy: the bare field after this entry is the source path.\n\t\t\t\tsource = fields[i + 1];\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tentries.push({ status, path, source });\n\t\t}\n\t\treturn entries;\n\t} catch {\n\t\twarnOnce(ctx, \"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\");\n\t\treturn undefined;\n\t}\n}\n\nfunction warnOnce(ctx: RollbackContext, message: string): void {\n\tif (ctx.repoWarningShown) return;\n\tctx.repoWarningShown = true;\n\ttry {\n\t\twarningHandler?.(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\nfunction persistSnapshot(ctx: RollbackContext, turn: TurnSnapshots, absPath: string): void {\n\ttry {\n\t\tconst snap = turn.files.get(absPath);\n\t\tif (!snap) return;\n\t\tconst turnDir = getTurnDir(ctx, turn.turnIndex);\n\t\tmkdirSync(turnDir, { recursive: true });\n\t\tconst manifestPath = join(turnDir, \"manifest.json\");\n\t\tconst manifest = readManifest(manifestPath);\n\t\tconst entry: ManifestEntry = { existed: snap.existed, createdByTool: snap.createdByTool };\n\t\tif (snap.content) {\n\t\t\tconst snapFile = snapFileName(absPath);\n\t\t\twriteFileSync(join(turnDir, snapFile), snap.content);\n\t\t\tentry.snapFile = snapFile;\n\t\t}\n\t\tmanifest.files[absPath] = entry;\n\t\tif (turn.cwd) manifest.cwd = turn.cwd;\n\t\twriteFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n\t} catch {\n\t\t// disk persistence failures are non-fatal\n\t}\n}\n\nfunction readManifest(path: string): TurnManifest {\n\tif (!existsSync(path)) return { files: {} };\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\tconst files = (parsed as Record<string, unknown>).files;\n\t\t\treturn {\n\t\t\t\tcwd: typeof parsed.cwd === \"string\" ? parsed.cwd : undefined,\n\t\t\t\tfiles:\n\t\t\t\t\tfiles && typeof files === \"object\" && !Array.isArray(files)\n\t\t\t\t\t\t? (files as Record<string, ManifestEntry>)\n\t\t\t\t\t\t: {},\n\t\t\t};\n\t\t}\n\t\treturn { files: {} };\n\t} catch {\n\t\treturn { files: {} };\n\t}\n}\n\n/** Reload persisted turns from disk (called by initRollback) so rollback survives restarts. */\nfunction loadPersistedTurns(ctx: RollbackContext): void {\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\tif (!existsSync(sessionDir)) return;\n\t\tconst loaded: TurnSnapshots[] = [];\n\t\tfor (const dirName of readdirSync(sessionDir)) {\n\t\t\tconst match = /^turn-(\\d+)$/.exec(dirName);\n\t\t\tif (!match) continue;\n\t\t\tconst turnIndex = Number(match[1]);\n\t\t\tconst turnDir = join(sessionDir, dirName);\n\t\t\tconst manifest = readManifest(join(turnDir, \"manifest.json\"));\n\t\t\tconst files = new Map<string, Snapshot>();\n\t\t\tfor (const [absPath, meta] of Object.entries(manifest.files)) {\n\t\t\t\tlet content: Buffer | undefined;\n\t\t\t\tif (meta.snapFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(join(turnDir, meta.snapFile));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontent = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfiles.set(absPath, { existed: meta.existed, content, createdByTool: meta.createdByTool });\n\t\t\t}\n\t\t\tloaded.push({ turnIndex, cwd: manifest.cwd, files });\n\t\t}\n\t\tloaded.sort((a, b) => a.turnIndex - b.turnIndex);\n\t\tctx.turns = loaded;\n\t\tctx.currentTurnIndex = loaded.length > 0 ? loaded[loaded.length - 1].turnIndex : -1;\n\t\tpruneTurns(ctx);\n\t} catch {\n\t\t// reload failures are non-fatal — start empty\n\t}\n}\n\nfunction getTurnDir(ctx: RollbackContext, turnIndex: number): string {\n\treturn join(ROLLBACK_BASE, ctx.sessionId, `turn-${turnIndex}`);\n}\n\nfunction cleanupTurnFiles(ctx: RollbackContext, turn: TurnSnapshots): void {\n\ttry {\n\t\tconst dir = getTurnDir(ctx, turn.turnIndex);\n\t\tif (existsSync(dir)) rmSync(dir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n}\n\nexport interface RollbackResult {\n\trestored: string[];\n\tdeleted: string[];\n\t/** Number of turns consumed: the restored turn plus any empty turns skipped above it. */\n\tturnsConsumed: number;\n}\n\n/**\n * Restore the newest NON-EMPTY turn's snapshots, drop it (and any empty turns\n * on top of it), and return what actually happened. Files the agent created\n * with edit/write are deleted in every capture mode; files created outside\n * tools (tree scope) are deleted only under hybrid/shadow-git.\n */\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tlet normRoot = normalize(root).replace(/\\\\$/g, \"\");\n\tlet normPath = normalize(absPath).replace(/\\\\$/g, \"\");\n\t// lunr: Windows drive-letter/directory casing differs by launch context and\n\t// resolve() preserves input casing — compare case-insensitively there.\n\tif (process.platform === \"win32\") {\n\t\tnormRoot = normRoot.toLowerCase();\n\t\tnormPath = normPath.toLowerCase();\n\t}\n\treturn normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\\\`);\n}\n\nfunction isWithinAllowedRoots(absPath: string, turn: TurnSnapshots): boolean {\n\tif (!turn.cwd) return true; // no recorded cwd: cannot validate, allow (tests / legacy)\n\tconst roots = [\n\t\tROLLBACK_BASE,\n\t\tjoin(homedir(), CONFIG_DIR_NAME), // lunr: behavior.md / cron jobs.json live under the config dir\n\t\t// lunr: simple-memory lives next to the lunr agent dir (~/.lunr/simple-memory)\n\t\tjoin(dirname(getAgentDir()), \"simple-memory\"),\n\t\tresolve(turn.cwd),\n\t];\n\treturn roots.some((root) => isPathUnderRoot(absPath, root));\n}\n\nfunction warnExternalModification(ctx: RollbackContext, message: string): void {\n\tif (ctx.externalModWarningShown || !warningHandler) return;\n\tctx.externalModWarningShown = true;\n\ttry {\n\t\twarningHandler(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\n/** Count how many turns a rollback would consume (trailing empty turns + the newest non-empty one). */\nexport function peekRollbackTurnsConsumed(sessionId?: string): number {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return 0;\n\tif (ctx.turns.length === 0) return 0;\n\tlet consumed = 0;\n\tfor (let i = ctx.turns.length - 1; i >= 0; i--) {\n\t\tconsumed++;\n\t\tif (ctx.turns[i].files.size > 0) break;\n\t}\n\treturn consumed;\n}\n\nexport function rollbackLastTurn(sessionId?: string): RollbackResult {\n\tconst ctx = getContext(sessionId);\n\tconst empty: RollbackResult = { restored: [], deleted: [], turnsConsumed: 0 };\n\tif (!isRollbackEnabled(ctx.sessionId)) return empty;\n\n\tlet turnsConsumed = 0;\n\twhile (ctx.turns.length > 0 && ctx.turns[ctx.turns.length - 1].files.size === 0) {\n\t\tconst skipped = ctx.turns.pop();\n\t\tif (skipped) {\n\t\t\tcleanupTurnFiles(ctx, skipped);\n\t\t\tturnsConsumed++;\n\t\t}\n\t}\n\tif (ctx.turns.length === 0) return empty;\n\tturnsConsumed++;\n\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tconst capture = ctx.settingsManager?.getRollbackCapture() ?? \"copies\";\n\tconst restored: string[] = [];\n\tconst deleted: string[] = [];\n\n\tfor (const [absPath, snap] of turn.files) {\n\t\ttry {\n\t\t\tif (!isWithinAllowedRoots(absPath, turn)) {\n\t\t\t\twarnExternalModification(\n\t\t\t\t\tctx,\n\t\t\t\t\t`Rollback skipped ${absPath}: outside the session working directory or lunR config dir.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (snap.existed && snap.content) {\n\t\t\t\t// For tree-scope baseline snapshots, skip the write if the file is\n\t\t\t\t// already identical to the snapshot (no actual change occurred during the\n\t\t\t\t// turn). Tool snapshots are always written so /rollback reports them.\n\t\t\t\tif (!snap.createdByTool && existsSync(absPath)) {\n\t\t\t\t\tconst current = readFileSync(absPath);\n\t\t\t\t\tif (current.equals(snap.content)) continue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absPath), { recursive: true });\n\t\t\t\twriteFileSync(absPath, snap.content);\n\t\t\t\trestored.push(absPath);\n\t\t\t} else if (!snap.existed) {\n\t\t\t\tconst shouldDelete = snap.createdByTool || capture === \"hybrid\" || capture === \"shadow-git\";\n\t\t\t\tif (shouldDelete && existsSync(absPath)) {\n\t\t\t\t\tunlinkSync(absPath);\n\t\t\t\t\tdeleted.push(absPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// existed but content unreadable — skip\n\t\t} catch {\n\t\t\t// individual file restore failures are non-fatal\n\t\t}\n\t}\n\n\tcleanupTurnFiles(ctx, turn);\n\tctx.turns.pop();\n\n\treturn { restored, deleted, turnsConsumed };\n}\n\nexport function getRollbackStatus(sessionId?: string): { enabled: boolean; turns: number; files: number } {\n\tconst ctx = getContext(sessionId);\n\treturn {\n\t\tenabled: isRollbackEnabled(ctx.sessionId),\n\t\tturns: ctx.turns.length,\n\t\tfiles: ctx.turns.reduce((sum, t) => sum + t.files.size, 0),\n\t};\n}\n\n/**\n * lunr: carry a session's rollback state across a fork — re-key the in-memory\n * context and move the on-disk dir to the forked session's id. No-op when the\n * old session has no state. Fork teardown deliberately skips clearRollback so\n * this can run right after (see interactive-mode beforeSessionInvalidate).\n */\nexport function migrateRollbackSession(oldSessionId: string, newSessionId: string): void {\n\tif (oldSessionId === newSessionId) return;\n\tconst ctx = contexts.get(oldSessionId);\n\tif (ctx) {\n\t\tcontexts.delete(oldSessionId);\n\t\tctx.sessionId = newSessionId;\n\t\tcontexts.set(newSessionId, ctx);\n\t}\n\ttry {\n\t\tconst oldDir = join(ROLLBACK_BASE, oldSessionId);\n\t\tconst newDir = join(ROLLBACK_BASE, newSessionId);\n\t\tif (existsSync(oldDir)) {\n\t\t\tif (existsSync(newDir)) rmSync(newDir, { recursive: true, force: true });\n\t\t\ttry {\n\t\t\t\trenameSync(oldDir, newDir);\n\t\t\t} catch {\n\t\t\t\t// Cross-device or locked-dir fallback: copy then delete.\n\t\t\t\tcpSync(oldDir, newDir, { recursive: true });\n\t\t\t\trmSync(oldDir, { recursive: true, force: true });\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// migration failures are non-fatal — in-memory state was still re-keyed\n\t}\n}\n\n/** Clear rollback state for a session (or all sessions when no id is passed). */\nexport function clearRollback(sessionId?: string): void {\n\tif (sessionId === undefined) {\n\t\tfor (const ctx of contexts.values()) {\n\t\t\tfor (const turn of ctx.turns) {\n\t\t\t\tcleanupTurnFiles(ctx, turn);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\t\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t}\n\t\tcontexts.clear();\n\t\treturn;\n\t}\n\n\tconst ctx = contexts.get(sessionId);\n\tif (!ctx) return;\n\tfor (const turn of ctx.turns) {\n\t\tcleanupTurnFiles(ctx, turn);\n\t}\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, sessionId);\n\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n\tcontexts.delete(sessionId);\n}\n\nfunction snapFileName(absPath: string): string {\n\treturn `${createHash(\"sha1\").update(absPath).digest(\"hex\")}.snap`;\n}\n"]}
1
+ {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/core/rollback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAkBH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACvB;AAsCD,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAEhG;AAwBD,wBAAgB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAOnE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,yFAAyF;AACzF,wBAAgB,6BAA6B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAEjE;AAED,wBAAgB,yBAAyB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAUD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAyBrF;AA6BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA2DxE;AA6ID,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yFAAyF;IACzF,aAAa,EAAE,MAAM,CAAC;CACtB;AA0CD,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CA2DnE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOxG;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAwBvF;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA6BtD","sourcesContent":["/**\n * lunR: rollback service — pre-edit file snapshots for /rollback.\n *\n * Captures pre-write file content before edit/write tools mutate files, keyed\n * by user-turn and session id. `/rollback` restores the newest non-empty turn's\n * snapshots and then rewinds the conversation via a session fork (persistent,\n * unlike /undo and /edit, which stay in the same session file).\n *\n * Capture modes (setting `rollbackCapture`):\n * - copies: snapshot pre-write content; restore = write back + delete files\n * the agent created with edit/write.\n * - hybrid: copies + also delete files created OUTSIDE tools (tree scope).\n * - shadow-git: hidden git repo (deferred — behaves like copies).\n *\n * Scope (setting `rollbackScope`):\n * - tools: only edit/write tool changes.\n * - tree: also catches bash side-effects. Baseline = `git status --porcelain`\n * at turn START (pre-change content); at agent_end, files that are\n * dirty but were clean/absent at baseline are captured too (untracked\n * = created during the turn; tracked = restored from HEAD). Skipped\n * with a one-time warning when cwd is not a repo.\n *\n * Snapshots persist on disk under `~/.lunr/rollback/<session-id>/<turn-n>/`\n * (manifest.json + sha1-named .snap payloads) and are reloaded on init, so\n * rollback survives restarts. Turn granularity = one user message → agent_end.\n * Retention prunes beyond `rollbackTurns` turns.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, normalize, resolve } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface Snapshot {\n\texisted: boolean;\n\tcontent?: Buffer;\n\tcreatedByTool: boolean;\n}\n\ninterface TurnSnapshots {\n\tturnIndex: number;\n\tcwd?: string;\n\tfiles: Map<string, Snapshot>;\n}\n\ninterface ManifestEntry {\n\texisted: boolean;\n\tcreatedByTool: boolean;\n\tsnapFile?: string;\n}\n\ninterface TurnManifest {\n\tcwd?: string;\n\tfiles: Record<string, ManifestEntry>;\n}\n\nconst ROLLBACK_BASE = join(homedir(), \".lunr\", \"rollback\");\n\ninterface RollbackContext {\n\tsessionId: string;\n\tsettingsManager: SettingsManager | undefined;\n\tsessionForceEnabled: boolean;\n\tturns: TurnSnapshots[];\n\tcurrentTurnIndex: number;\n\trepoWarningShown: boolean;\n\texternalModWarningShown: boolean;\n\t/** Last cwd seen by beginTurn — reused by auto-started turns (mid-turn enable). */\n\tlastCwd?: string;\n}\n\nconst contexts = new Map<string, RollbackContext>();\nlet lastSessionId: string | undefined;\nlet globalSettingsManager: SettingsManager | undefined;\nlet warningHandler: ((message: string) => void) | undefined;\n\n/** Register a sink for one-time user-facing warnings (e.g. tree scope outside a git repo). */\nexport function setRollbackWarningHandler(handler: ((message: string) => void) | undefined): void {\n\twarningHandler = handler;\n}\n\nfunction createContext(sessionId: string): RollbackContext {\n\treturn {\n\t\tsessionId,\n\t\tsettingsManager: globalSettingsManager,\n\t\tsessionForceEnabled: false,\n\t\tturns: [],\n\t\tcurrentTurnIndex: -1,\n\t\trepoWarningShown: false,\n\t\texternalModWarningShown: false,\n\t};\n}\n\nfunction getContext(sessionId?: string): RollbackContext {\n\tconst sid = sessionId ?? lastSessionId ?? \"default\";\n\tlet ctx = contexts.get(sid);\n\tif (!ctx) {\n\t\tctx = createContext(sid);\n\t\tcontexts.set(sid, ctx);\n\t}\n\treturn ctx;\n}\n\nexport function initRollback(sm: SettingsManager, sid: string): void {\n\tglobalSettingsManager = sm;\n\tlastSessionId = sid;\n\tcontexts.set(sid, createContext(sid));\n\tconst ctx = contexts.get(sid)!;\n\tctx.settingsManager = sm;\n\tloadPersistedTurns(ctx);\n}\n\nexport function isRollbackEnabled(sessionId?: string): boolean {\n\tconst ctx = getContext(sessionId);\n\treturn (ctx.settingsManager?.getRollbackEnabled() ?? false) || ctx.sessionForceEnabled;\n}\n\n/** Whether rollback is force-enabled for this session (e.g. by auto permission mode). */\nexport function isRollbackSessionForceEnabled(sessionId?: string): boolean {\n\treturn getContext(sessionId).sessionForceEnabled;\n}\n\nexport function enableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = true;\n}\n\nexport function disableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = false;\n}\n\nfunction pruneTurns(ctx: RollbackContext): void {\n\tconst maxTurns = ctx.settingsManager?.getRollbackTurns() ?? 2;\n\twhile (ctx.turns.length > maxTurns) {\n\t\tconst old = ctx.turns.shift();\n\t\tif (old) cleanupTurnFiles(ctx, old);\n\t}\n}\n\n/**\n * Begin a new turn: push a new snapshot map, prune old turns beyond retention.\n * When `cwd` is given and scope is `tree`, also capture the pre-turn baseline\n * (content of all currently dirty/untracked files) so bash side-effects can be\n * restored later.\n */\nexport function beginTurn(cwd?: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: remember the session cwd so auto-started turns (mid-turn enable) can\n\t// still be constrained by the allowed-roots check.\n\tif (cwd) ctx.lastCwd = cwd;\n\tctx.currentTurnIndex++;\n\tconst turn: TurnSnapshots = { turnIndex: ctx.currentTurnIndex, cwd, files: new Map() };\n\tctx.turns.push(turn);\n\tpruneTurns(ctx);\n\tif (cwd && ctx.settingsManager?.getRollbackScope() === \"tree\") {\n\t\tcaptureTreeBaseline(ctx, cwd);\n\t}\n}\n\n/** Capture pre-write content for a file path, recording under the current turn. */\nexport function rollbackSnapshotBeforeWrite(absPath: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: auto-started turn inherits the last known cwd so restores stay\n\t// root-checked; without any recorded cwd the allowed roots still apply.\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) beginTurn(ctx.lastCwd, ctx.sessionId);\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tif (turn.files.has(absPath)) return; // already snapshotted this turn\n\n\ttry {\n\t\tconst existed = existsSync(absPath);\n\t\tlet content: Buffer | undefined;\n\t\tif (existed) {\n\t\t\tcontent = readFileSync(absPath);\n\t\t}\n\t\tturn.files.set(absPath, {\n\t\t\texisted,\n\t\t\tcontent,\n\t\t\tcreatedByTool: true,\n\t\t});\n\t\tpersistSnapshot(ctx, turn, absPath);\n\t} catch {\n\t\t// snapshot failures are non-fatal\n\t}\n}\n\n/**\n * Tree scope, turn START: snapshot the current (pre-turn) content of every\n * dirty/untracked file so modifications made during the turn can be restored.\n */\nfunction captureTreeBaseline(ctx: RollbackContext, cwd: string): void {\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { path } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue;\n\t\ttry {\n\t\t\tconst existed = existsSync(abs);\n\t\t\tturn.files.set(abs, {\n\t\t\t\texisted,\n\t\t\t\tcontent: existed ? readFileSync(abs) : undefined,\n\t\t\t\tcreatedByTool: false,\n\t\t\t});\n\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Tree scope, turn END: pick up files that became dirty during the turn but\n * were clean/absent at baseline. Untracked = created during the turn (deleted\n * on restore under hybrid); tracked-modified = restore from HEAD content.\n */\nexport function captureTreeChanges(cwd: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\tif (ctx.settingsManager?.getRollbackScope() !== \"tree\") return;\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) return;\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { status, path, source } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue; // already captured (baseline or tool snapshot)\n\t\ttry {\n\t\t\tif (status === \"??\" || status.includes(\"R\")) {\n\t\t\t\t// lunr: untracked now (or rename dest) and not in the baseline — created\n\t\t\t\t// during this turn (deleted on restore under hybrid/shadow-git).\n\t\t\t\tturn.files.set(abs, { existed: false, createdByTool: false });\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t\t// lunr: rename source existed at HEAD and is gone now — restore its\n\t\t\t\t// HEAD content on rollback, like a tracked file deleted mid-turn.\n\t\t\t\tif (status.includes(\"R\") && source) {\n\t\t\t\t\tconst sourceAbs = join(cwd, source);\n\t\t\t\t\tif (!turn.files.has(sourceAbs)) {\n\t\t\t\t\t\tconst headSrc = spawnSync(\"git\", [\"show\", `HEAD:${source}`], {\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!headSrc.error && headSrc.status === 0 && headSrc.stdout) {\n\t\t\t\t\t\t\tturn.files.set(sourceAbs, {\n\t\t\t\t\t\t\t\texisted: true,\n\t\t\t\t\t\t\t\tcontent: headSrc.stdout as Buffer,\n\t\t\t\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tpersistSnapshot(ctx, turn, sourceAbs);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tracked file that was clean at turn start; original = HEAD content.\n\t\t\t\tconst head = spawnSync(\"git\", [\"show\", `HEAD:${path}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tif (head.error || head.status !== 0 || !head.stdout) continue;\n\t\t\t\tturn.files.set(abs, {\n\t\t\t\t\texisted: true,\n\t\t\t\t\tcontent: head.stdout as Buffer,\n\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t});\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t}\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Run `git status --porcelain -z` and parse entries, or return undefined (with\n * a one-time warning) when cwd is not a repo / git fails. Rename/copy entries\n * carry their source path in a separate bare field — parsed into `source`.\n */\nfunction gitPorcelain(\n\tctx: RollbackContext,\n\tcwd: string,\n): Array<{ status: string; path: string; source?: string }> | undefined {\n\ttry {\n\t\tconst result = spawnSync(\"git\", [\"status\", \"--porcelain\", \"-z\"], { cwd, encoding: \"utf8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\twarnOnce(\n\t\t\t\tctx,\n\t\t\t\t\"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\",\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst fields = (result.stdout ?? \"\").split(\"\\0\").filter((f) => f.length > 0);\n\t\tconst entries: Array<{ status: string; path: string; source?: string }> = [];\n\t\tfor (let i = 0; i < fields.length; i++) {\n\t\t\tconst field = fields[i];\n\t\t\tconst status = field.slice(0, 2);\n\t\t\tconst path = field.slice(3);\n\t\t\tlet source: string | undefined;\n\t\t\tif (status.includes(\"R\") || status.includes(\"C\")) {\n\t\t\t\t// Rename/copy: the bare field after this entry is the source path.\n\t\t\t\tsource = fields[i + 1];\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tentries.push({ status, path, source });\n\t\t}\n\t\treturn entries;\n\t} catch {\n\t\twarnOnce(ctx, \"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\");\n\t\treturn undefined;\n\t}\n}\n\nfunction warnOnce(ctx: RollbackContext, message: string): void {\n\tif (ctx.repoWarningShown) return;\n\tctx.repoWarningShown = true;\n\ttry {\n\t\twarningHandler?.(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\nfunction persistSnapshot(ctx: RollbackContext, turn: TurnSnapshots, absPath: string): void {\n\ttry {\n\t\tconst snap = turn.files.get(absPath);\n\t\tif (!snap) return;\n\t\tconst turnDir = getTurnDir(ctx, turn.turnIndex);\n\t\tmkdirSync(turnDir, { recursive: true });\n\t\tconst manifestPath = join(turnDir, \"manifest.json\");\n\t\tconst manifest = readManifest(manifestPath);\n\t\tconst entry: ManifestEntry = { existed: snap.existed, createdByTool: snap.createdByTool };\n\t\tif (snap.content) {\n\t\t\tconst snapFile = snapFileName(absPath);\n\t\t\twriteFileSync(join(turnDir, snapFile), snap.content);\n\t\t\tentry.snapFile = snapFile;\n\t\t}\n\t\tmanifest.files[absPath] = entry;\n\t\tif (turn.cwd) manifest.cwd = turn.cwd;\n\t\twriteFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n\t} catch {\n\t\t// disk persistence failures are non-fatal\n\t}\n}\n\nfunction readManifest(path: string): TurnManifest {\n\tif (!existsSync(path)) return { files: {} };\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\tconst files = (parsed as Record<string, unknown>).files;\n\t\t\treturn {\n\t\t\t\tcwd: typeof parsed.cwd === \"string\" ? parsed.cwd : undefined,\n\t\t\t\tfiles:\n\t\t\t\t\tfiles && typeof files === \"object\" && !Array.isArray(files)\n\t\t\t\t\t\t? (files as Record<string, ManifestEntry>)\n\t\t\t\t\t\t: {},\n\t\t\t};\n\t\t}\n\t\treturn { files: {} };\n\t} catch {\n\t\treturn { files: {} };\n\t}\n}\n\n/** Reload persisted turns from disk (called by initRollback) so rollback survives restarts. */\nfunction loadPersistedTurns(ctx: RollbackContext): void {\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\tif (!existsSync(sessionDir)) return;\n\t\tconst loaded: TurnSnapshots[] = [];\n\t\tfor (const dirName of readdirSync(sessionDir)) {\n\t\t\tconst match = /^turn-(\\d+)$/.exec(dirName);\n\t\t\tif (!match) continue;\n\t\t\tconst turnIndex = Number(match[1]);\n\t\t\tconst turnDir = join(sessionDir, dirName);\n\t\t\tconst manifest = readManifest(join(turnDir, \"manifest.json\"));\n\t\t\tconst files = new Map<string, Snapshot>();\n\t\t\tfor (const [absPath, meta] of Object.entries(manifest.files)) {\n\t\t\t\tlet content: Buffer | undefined;\n\t\t\t\tif (meta.snapFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(join(turnDir, meta.snapFile));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontent = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfiles.set(absPath, { existed: meta.existed, content, createdByTool: meta.createdByTool });\n\t\t\t}\n\t\t\tloaded.push({ turnIndex, cwd: manifest.cwd, files });\n\t\t}\n\t\tloaded.sort((a, b) => a.turnIndex - b.turnIndex);\n\t\tctx.turns = loaded;\n\t\tctx.currentTurnIndex = loaded.length > 0 ? loaded[loaded.length - 1].turnIndex : -1;\n\t\tpruneTurns(ctx);\n\t} catch {\n\t\t// reload failures are non-fatal — start empty\n\t}\n}\n\nfunction getTurnDir(ctx: RollbackContext, turnIndex: number): string {\n\treturn join(ROLLBACK_BASE, ctx.sessionId, `turn-${turnIndex}`);\n}\n\nfunction cleanupTurnFiles(ctx: RollbackContext, turn: TurnSnapshots): void {\n\ttry {\n\t\tconst dir = getTurnDir(ctx, turn.turnIndex);\n\t\tif (existsSync(dir)) rmSync(dir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n}\n\nexport interface RollbackResult {\n\trestored: string[];\n\tdeleted: string[];\n\t/** Number of turns consumed: the restored turn plus any empty turns skipped above it. */\n\tturnsConsumed: number;\n}\n\n/**\n * Restore the newest NON-EMPTY turn's snapshots, drop it (and any empty turns\n * on top of it), and return what actually happened. Files the agent created\n * with edit/write are deleted in every capture mode; files created outside\n * tools (tree scope) are deleted only under hybrid/shadow-git.\n */\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tlet normRoot = normalize(root).replace(/\\\\$/g, \"\");\n\tlet normPath = normalize(absPath).replace(/\\\\$/g, \"\");\n\t// lunr: Windows drive-letter/directory casing differs by launch context and\n\t// resolve() preserves input casing — compare case-insensitively there.\n\tif (process.platform === \"win32\") {\n\t\tnormRoot = normRoot.toLowerCase();\n\t\tnormPath = normPath.toLowerCase();\n\t}\n\treturn normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\\\`);\n}\n\nfunction isWithinAllowedRoots(absPath: string, turn: TurnSnapshots): boolean {\n\tif (!turn.cwd) return true; // no recorded cwd: cannot validate, allow (tests / legacy)\n\tconst roots = [\n\t\tROLLBACK_BASE,\n\t\tjoin(homedir(), CONFIG_DIR_NAME), // lunr: behavior.md / cron jobs.json live under the config dir\n\t\t// lunr: simple-memory lives next to the lunr agent dir (~/.lunr/simple-memory)\n\t\tjoin(dirname(getAgentDir()), \"simple-memory\"),\n\t\tresolve(turn.cwd),\n\t];\n\treturn roots.some((root) => isPathUnderRoot(absPath, root));\n}\n\nfunction warnExternalModification(ctx: RollbackContext, message: string): void {\n\tif (ctx.externalModWarningShown || !warningHandler) return;\n\tctx.externalModWarningShown = true;\n\ttry {\n\t\twarningHandler(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\n/** Count how many turns a rollback would consume (trailing empty turns + the newest non-empty one). */\nexport function peekRollbackTurnsConsumed(sessionId?: string): number {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return 0;\n\tif (ctx.turns.length === 0) return 0;\n\tlet consumed = 0;\n\tfor (let i = ctx.turns.length - 1; i >= 0; i--) {\n\t\tconsumed++;\n\t\tif (ctx.turns[i].files.size > 0) break;\n\t}\n\treturn consumed;\n}\n\nexport function rollbackLastTurn(sessionId?: string): RollbackResult {\n\tconst ctx = getContext(sessionId);\n\tconst empty: RollbackResult = { restored: [], deleted: [], turnsConsumed: 0 };\n\tif (!isRollbackEnabled(ctx.sessionId)) return empty;\n\n\tlet turnsConsumed = 0;\n\twhile (ctx.turns.length > 0 && ctx.turns[ctx.turns.length - 1].files.size === 0) {\n\t\tconst skipped = ctx.turns.pop();\n\t\tif (skipped) {\n\t\t\tcleanupTurnFiles(ctx, skipped);\n\t\t\tturnsConsumed++;\n\t\t}\n\t}\n\tif (ctx.turns.length === 0) return empty;\n\tturnsConsumed++;\n\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tconst capture = ctx.settingsManager?.getRollbackCapture() ?? \"copies\";\n\tconst restored: string[] = [];\n\tconst deleted: string[] = [];\n\n\tfor (const [absPath, snap] of turn.files) {\n\t\ttry {\n\t\t\tif (!isWithinAllowedRoots(absPath, turn)) {\n\t\t\t\twarnExternalModification(\n\t\t\t\t\tctx,\n\t\t\t\t\t`Rollback skipped ${absPath}: outside the session working directory or lunR config dir.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (snap.existed && snap.content) {\n\t\t\t\t// For tree-scope baseline snapshots, skip the write if the file is\n\t\t\t\t// already identical to the snapshot (no actual change occurred during the\n\t\t\t\t// turn). Tool snapshots are always written so /rollback reports them.\n\t\t\t\tif (!snap.createdByTool && existsSync(absPath)) {\n\t\t\t\t\tconst current = readFileSync(absPath);\n\t\t\t\t\tif (current.equals(snap.content)) continue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absPath), { recursive: true });\n\t\t\t\twriteFileSync(absPath, snap.content);\n\t\t\t\trestored.push(absPath);\n\t\t\t} else if (!snap.existed) {\n\t\t\t\tconst shouldDelete = snap.createdByTool || capture === \"hybrid\" || capture === \"shadow-git\";\n\t\t\t\tif (shouldDelete && existsSync(absPath)) {\n\t\t\t\t\tunlinkSync(absPath);\n\t\t\t\t\tdeleted.push(absPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// existed but content unreadable — skip\n\t\t} catch {\n\t\t\t// individual file restore failures are non-fatal\n\t\t}\n\t}\n\n\tcleanupTurnFiles(ctx, turn);\n\tctx.turns.pop();\n\n\treturn { restored, deleted, turnsConsumed };\n}\n\nexport function getRollbackStatus(sessionId?: string): { enabled: boolean; turns: number; files: number } {\n\tconst ctx = getContext(sessionId);\n\treturn {\n\t\tenabled: isRollbackEnabled(ctx.sessionId),\n\t\tturns: ctx.turns.length,\n\t\tfiles: ctx.turns.reduce((sum, t) => sum + t.files.size, 0),\n\t};\n}\n\n/**\n * lunr: carry a session's rollback state across a fork — re-key the in-memory\n * context and move the on-disk dir to the forked session's id. No-op when the\n * old session has no state. Fork teardown deliberately skips clearRollback so\n * this can run right after (see interactive-mode beforeSessionInvalidate).\n */\nexport function migrateRollbackSession(oldSessionId: string, newSessionId: string): void {\n\tif (oldSessionId === newSessionId) return;\n\tconst ctx = contexts.get(oldSessionId);\n\tif (ctx) {\n\t\tcontexts.delete(oldSessionId);\n\t\tctx.sessionId = newSessionId;\n\t\tcontexts.set(newSessionId, ctx);\n\t}\n\ttry {\n\t\tconst oldDir = join(ROLLBACK_BASE, oldSessionId);\n\t\tconst newDir = join(ROLLBACK_BASE, newSessionId);\n\t\tif (existsSync(oldDir)) {\n\t\t\tif (existsSync(newDir)) rmSync(newDir, { recursive: true, force: true });\n\t\t\ttry {\n\t\t\t\trenameSync(oldDir, newDir);\n\t\t\t} catch {\n\t\t\t\t// Cross-device or locked-dir fallback: copy then delete.\n\t\t\t\tcpSync(oldDir, newDir, { recursive: true });\n\t\t\t\trmSync(oldDir, { recursive: true, force: true });\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// migration failures are non-fatal — in-memory state was still re-keyed\n\t}\n}\n\n/** Clear rollback state for a session (or all sessions when no id is passed). */\nexport function clearRollback(sessionId?: string): void {\n\tif (sessionId === undefined) {\n\t\tfor (const ctx of contexts.values()) {\n\t\t\tfor (const turn of ctx.turns) {\n\t\t\t\tcleanupTurnFiles(ctx, turn);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\t\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t}\n\t\tcontexts.clear();\n\t\treturn;\n\t}\n\n\tconst ctx = contexts.get(sessionId);\n\tif (!ctx) return;\n\tfor (const turn of ctx.turns) {\n\t\tcleanupTurnFiles(ctx, turn);\n\t}\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, sessionId);\n\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n\tcontexts.delete(sessionId);\n}\n\nfunction snapFileName(absPath: string): string {\n\treturn `${createHash(\"sha1\").update(absPath).digest(\"hex\")}.snap`;\n}\n"]}
@@ -4,7 +4,7 @@
4
4
  * Captures pre-write file content before edit/write tools mutate files, keyed
5
5
  * by user-turn and session id. `/rollback` restores the newest non-empty turn's
6
6
  * snapshots and then rewinds the conversation via a session fork (persistent,
7
- * unlike /undo).
7
+ * unlike /undo and /edit, which stay in the same session file).
8
8
  *
9
9
  * Capture modes (setting `rollbackCapture`):
10
10
  * - copies: snapshot pre-write content; restore = write back + delete files
@@ -1 +1 @@
1
- {"version":3,"file":"rollback.js","sourceRoot":"","sources":["../../src/core/rollback.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: rollback service — pre-edit file snapshots for /rollback.\n *\n * Captures pre-write file content before edit/write tools mutate files, keyed\n * by user-turn and session id. `/rollback` restores the newest non-empty turn's\n * snapshots and then rewinds the conversation via a session fork (persistent,\n * unlike /undo).\n *\n * Capture modes (setting `rollbackCapture`):\n * - copies: snapshot pre-write content; restore = write back + delete files\n * the agent created with edit/write.\n * - hybrid: copies + also delete files created OUTSIDE tools (tree scope).\n * - shadow-git: hidden git repo (deferred — behaves like copies).\n *\n * Scope (setting `rollbackScope`):\n * - tools: only edit/write tool changes.\n * - tree: also catches bash side-effects. Baseline = `git status --porcelain`\n * at turn START (pre-change content); at agent_end, files that are\n * dirty but were clean/absent at baseline are captured too (untracked\n * = created during the turn; tracked = restored from HEAD). Skipped\n * with a one-time warning when cwd is not a repo.\n *\n * Snapshots persist on disk under `~/.lunr/rollback/<session-id>/<turn-n>/`\n * (manifest.json + sha1-named .snap payloads) and are reloaded on init, so\n * rollback survives restarts. Turn granularity = one user message → agent_end.\n * Retention prunes beyond `rollbackTurns` turns.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, normalize, resolve } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface Snapshot {\n\texisted: boolean;\n\tcontent?: Buffer;\n\tcreatedByTool: boolean;\n}\n\ninterface TurnSnapshots {\n\tturnIndex: number;\n\tcwd?: string;\n\tfiles: Map<string, Snapshot>;\n}\n\ninterface ManifestEntry {\n\texisted: boolean;\n\tcreatedByTool: boolean;\n\tsnapFile?: string;\n}\n\ninterface TurnManifest {\n\tcwd?: string;\n\tfiles: Record<string, ManifestEntry>;\n}\n\nconst ROLLBACK_BASE = join(homedir(), \".lunr\", \"rollback\");\n\ninterface RollbackContext {\n\tsessionId: string;\n\tsettingsManager: SettingsManager | undefined;\n\tsessionForceEnabled: boolean;\n\tturns: TurnSnapshots[];\n\tcurrentTurnIndex: number;\n\trepoWarningShown: boolean;\n\texternalModWarningShown: boolean;\n\t/** Last cwd seen by beginTurn — reused by auto-started turns (mid-turn enable). */\n\tlastCwd?: string;\n}\n\nconst contexts = new Map<string, RollbackContext>();\nlet lastSessionId: string | undefined;\nlet globalSettingsManager: SettingsManager | undefined;\nlet warningHandler: ((message: string) => void) | undefined;\n\n/** Register a sink for one-time user-facing warnings (e.g. tree scope outside a git repo). */\nexport function setRollbackWarningHandler(handler: ((message: string) => void) | undefined): void {\n\twarningHandler = handler;\n}\n\nfunction createContext(sessionId: string): RollbackContext {\n\treturn {\n\t\tsessionId,\n\t\tsettingsManager: globalSettingsManager,\n\t\tsessionForceEnabled: false,\n\t\tturns: [],\n\t\tcurrentTurnIndex: -1,\n\t\trepoWarningShown: false,\n\t\texternalModWarningShown: false,\n\t};\n}\n\nfunction getContext(sessionId?: string): RollbackContext {\n\tconst sid = sessionId ?? lastSessionId ?? \"default\";\n\tlet ctx = contexts.get(sid);\n\tif (!ctx) {\n\t\tctx = createContext(sid);\n\t\tcontexts.set(sid, ctx);\n\t}\n\treturn ctx;\n}\n\nexport function initRollback(sm: SettingsManager, sid: string): void {\n\tglobalSettingsManager = sm;\n\tlastSessionId = sid;\n\tcontexts.set(sid, createContext(sid));\n\tconst ctx = contexts.get(sid)!;\n\tctx.settingsManager = sm;\n\tloadPersistedTurns(ctx);\n}\n\nexport function isRollbackEnabled(sessionId?: string): boolean {\n\tconst ctx = getContext(sessionId);\n\treturn (ctx.settingsManager?.getRollbackEnabled() ?? false) || ctx.sessionForceEnabled;\n}\n\n/** Whether rollback is force-enabled for this session (e.g. by auto permission mode). */\nexport function isRollbackSessionForceEnabled(sessionId?: string): boolean {\n\treturn getContext(sessionId).sessionForceEnabled;\n}\n\nexport function enableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = true;\n}\n\nexport function disableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = false;\n}\n\nfunction pruneTurns(ctx: RollbackContext): void {\n\tconst maxTurns = ctx.settingsManager?.getRollbackTurns() ?? 2;\n\twhile (ctx.turns.length > maxTurns) {\n\t\tconst old = ctx.turns.shift();\n\t\tif (old) cleanupTurnFiles(ctx, old);\n\t}\n}\n\n/**\n * Begin a new turn: push a new snapshot map, prune old turns beyond retention.\n * When `cwd` is given and scope is `tree`, also capture the pre-turn baseline\n * (content of all currently dirty/untracked files) so bash side-effects can be\n * restored later.\n */\nexport function beginTurn(cwd?: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: remember the session cwd so auto-started turns (mid-turn enable) can\n\t// still be constrained by the allowed-roots check.\n\tif (cwd) ctx.lastCwd = cwd;\n\tctx.currentTurnIndex++;\n\tconst turn: TurnSnapshots = { turnIndex: ctx.currentTurnIndex, cwd, files: new Map() };\n\tctx.turns.push(turn);\n\tpruneTurns(ctx);\n\tif (cwd && ctx.settingsManager?.getRollbackScope() === \"tree\") {\n\t\tcaptureTreeBaseline(ctx, cwd);\n\t}\n}\n\n/** Capture pre-write content for a file path, recording under the current turn. */\nexport function rollbackSnapshotBeforeWrite(absPath: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: auto-started turn inherits the last known cwd so restores stay\n\t// root-checked; without any recorded cwd the allowed roots still apply.\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) beginTurn(ctx.lastCwd, ctx.sessionId);\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tif (turn.files.has(absPath)) return; // already snapshotted this turn\n\n\ttry {\n\t\tconst existed = existsSync(absPath);\n\t\tlet content: Buffer | undefined;\n\t\tif (existed) {\n\t\t\tcontent = readFileSync(absPath);\n\t\t}\n\t\tturn.files.set(absPath, {\n\t\t\texisted,\n\t\t\tcontent,\n\t\t\tcreatedByTool: true,\n\t\t});\n\t\tpersistSnapshot(ctx, turn, absPath);\n\t} catch {\n\t\t// snapshot failures are non-fatal\n\t}\n}\n\n/**\n * Tree scope, turn START: snapshot the current (pre-turn) content of every\n * dirty/untracked file so modifications made during the turn can be restored.\n */\nfunction captureTreeBaseline(ctx: RollbackContext, cwd: string): void {\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { path } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue;\n\t\ttry {\n\t\t\tconst existed = existsSync(abs);\n\t\t\tturn.files.set(abs, {\n\t\t\t\texisted,\n\t\t\t\tcontent: existed ? readFileSync(abs) : undefined,\n\t\t\t\tcreatedByTool: false,\n\t\t\t});\n\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Tree scope, turn END: pick up files that became dirty during the turn but\n * were clean/absent at baseline. Untracked = created during the turn (deleted\n * on restore under hybrid); tracked-modified = restore from HEAD content.\n */\nexport function captureTreeChanges(cwd: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\tif (ctx.settingsManager?.getRollbackScope() !== \"tree\") return;\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) return;\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { status, path, source } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue; // already captured (baseline or tool snapshot)\n\t\ttry {\n\t\t\tif (status === \"??\" || status.includes(\"R\")) {\n\t\t\t\t// lunr: untracked now (or rename dest) and not in the baseline — created\n\t\t\t\t// during this turn (deleted on restore under hybrid/shadow-git).\n\t\t\t\tturn.files.set(abs, { existed: false, createdByTool: false });\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t\t// lunr: rename source existed at HEAD and is gone now — restore its\n\t\t\t\t// HEAD content on rollback, like a tracked file deleted mid-turn.\n\t\t\t\tif (status.includes(\"R\") && source) {\n\t\t\t\t\tconst sourceAbs = join(cwd, source);\n\t\t\t\t\tif (!turn.files.has(sourceAbs)) {\n\t\t\t\t\t\tconst headSrc = spawnSync(\"git\", [\"show\", `HEAD:${source}`], {\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!headSrc.error && headSrc.status === 0 && headSrc.stdout) {\n\t\t\t\t\t\t\tturn.files.set(sourceAbs, {\n\t\t\t\t\t\t\t\texisted: true,\n\t\t\t\t\t\t\t\tcontent: headSrc.stdout as Buffer,\n\t\t\t\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tpersistSnapshot(ctx, turn, sourceAbs);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tracked file that was clean at turn start; original = HEAD content.\n\t\t\t\tconst head = spawnSync(\"git\", [\"show\", `HEAD:${path}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tif (head.error || head.status !== 0 || !head.stdout) continue;\n\t\t\t\tturn.files.set(abs, {\n\t\t\t\t\texisted: true,\n\t\t\t\t\tcontent: head.stdout as Buffer,\n\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t});\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t}\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Run `git status --porcelain -z` and parse entries, or return undefined (with\n * a one-time warning) when cwd is not a repo / git fails. Rename/copy entries\n * carry their source path in a separate bare field — parsed into `source`.\n */\nfunction gitPorcelain(\n\tctx: RollbackContext,\n\tcwd: string,\n): Array<{ status: string; path: string; source?: string }> | undefined {\n\ttry {\n\t\tconst result = spawnSync(\"git\", [\"status\", \"--porcelain\", \"-z\"], { cwd, encoding: \"utf8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\twarnOnce(\n\t\t\t\tctx,\n\t\t\t\t\"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\",\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst fields = (result.stdout ?? \"\").split(\"\\0\").filter((f) => f.length > 0);\n\t\tconst entries: Array<{ status: string; path: string; source?: string }> = [];\n\t\tfor (let i = 0; i < fields.length; i++) {\n\t\t\tconst field = fields[i];\n\t\t\tconst status = field.slice(0, 2);\n\t\t\tconst path = field.slice(3);\n\t\t\tlet source: string | undefined;\n\t\t\tif (status.includes(\"R\") || status.includes(\"C\")) {\n\t\t\t\t// Rename/copy: the bare field after this entry is the source path.\n\t\t\t\tsource = fields[i + 1];\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tentries.push({ status, path, source });\n\t\t}\n\t\treturn entries;\n\t} catch {\n\t\twarnOnce(ctx, \"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\");\n\t\treturn undefined;\n\t}\n}\n\nfunction warnOnce(ctx: RollbackContext, message: string): void {\n\tif (ctx.repoWarningShown) return;\n\tctx.repoWarningShown = true;\n\ttry {\n\t\twarningHandler?.(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\nfunction persistSnapshot(ctx: RollbackContext, turn: TurnSnapshots, absPath: string): void {\n\ttry {\n\t\tconst snap = turn.files.get(absPath);\n\t\tif (!snap) return;\n\t\tconst turnDir = getTurnDir(ctx, turn.turnIndex);\n\t\tmkdirSync(turnDir, { recursive: true });\n\t\tconst manifestPath = join(turnDir, \"manifest.json\");\n\t\tconst manifest = readManifest(manifestPath);\n\t\tconst entry: ManifestEntry = { existed: snap.existed, createdByTool: snap.createdByTool };\n\t\tif (snap.content) {\n\t\t\tconst snapFile = snapFileName(absPath);\n\t\t\twriteFileSync(join(turnDir, snapFile), snap.content);\n\t\t\tentry.snapFile = snapFile;\n\t\t}\n\t\tmanifest.files[absPath] = entry;\n\t\tif (turn.cwd) manifest.cwd = turn.cwd;\n\t\twriteFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n\t} catch {\n\t\t// disk persistence failures are non-fatal\n\t}\n}\n\nfunction readManifest(path: string): TurnManifest {\n\tif (!existsSync(path)) return { files: {} };\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\tconst files = (parsed as Record<string, unknown>).files;\n\t\t\treturn {\n\t\t\t\tcwd: typeof parsed.cwd === \"string\" ? parsed.cwd : undefined,\n\t\t\t\tfiles:\n\t\t\t\t\tfiles && typeof files === \"object\" && !Array.isArray(files)\n\t\t\t\t\t\t? (files as Record<string, ManifestEntry>)\n\t\t\t\t\t\t: {},\n\t\t\t};\n\t\t}\n\t\treturn { files: {} };\n\t} catch {\n\t\treturn { files: {} };\n\t}\n}\n\n/** Reload persisted turns from disk (called by initRollback) so rollback survives restarts. */\nfunction loadPersistedTurns(ctx: RollbackContext): void {\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\tif (!existsSync(sessionDir)) return;\n\t\tconst loaded: TurnSnapshots[] = [];\n\t\tfor (const dirName of readdirSync(sessionDir)) {\n\t\t\tconst match = /^turn-(\\d+)$/.exec(dirName);\n\t\t\tif (!match) continue;\n\t\t\tconst turnIndex = Number(match[1]);\n\t\t\tconst turnDir = join(sessionDir, dirName);\n\t\t\tconst manifest = readManifest(join(turnDir, \"manifest.json\"));\n\t\t\tconst files = new Map<string, Snapshot>();\n\t\t\tfor (const [absPath, meta] of Object.entries(manifest.files)) {\n\t\t\t\tlet content: Buffer | undefined;\n\t\t\t\tif (meta.snapFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(join(turnDir, meta.snapFile));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontent = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfiles.set(absPath, { existed: meta.existed, content, createdByTool: meta.createdByTool });\n\t\t\t}\n\t\t\tloaded.push({ turnIndex, cwd: manifest.cwd, files });\n\t\t}\n\t\tloaded.sort((a, b) => a.turnIndex - b.turnIndex);\n\t\tctx.turns = loaded;\n\t\tctx.currentTurnIndex = loaded.length > 0 ? loaded[loaded.length - 1].turnIndex : -1;\n\t\tpruneTurns(ctx);\n\t} catch {\n\t\t// reload failures are non-fatal — start empty\n\t}\n}\n\nfunction getTurnDir(ctx: RollbackContext, turnIndex: number): string {\n\treturn join(ROLLBACK_BASE, ctx.sessionId, `turn-${turnIndex}`);\n}\n\nfunction cleanupTurnFiles(ctx: RollbackContext, turn: TurnSnapshots): void {\n\ttry {\n\t\tconst dir = getTurnDir(ctx, turn.turnIndex);\n\t\tif (existsSync(dir)) rmSync(dir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n}\n\nexport interface RollbackResult {\n\trestored: string[];\n\tdeleted: string[];\n\t/** Number of turns consumed: the restored turn plus any empty turns skipped above it. */\n\tturnsConsumed: number;\n}\n\n/**\n * Restore the newest NON-EMPTY turn's snapshots, drop it (and any empty turns\n * on top of it), and return what actually happened. Files the agent created\n * with edit/write are deleted in every capture mode; files created outside\n * tools (tree scope) are deleted only under hybrid/shadow-git.\n */\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tlet normRoot = normalize(root).replace(/\\\\$/g, \"\");\n\tlet normPath = normalize(absPath).replace(/\\\\$/g, \"\");\n\t// lunr: Windows drive-letter/directory casing differs by launch context and\n\t// resolve() preserves input casing — compare case-insensitively there.\n\tif (process.platform === \"win32\") {\n\t\tnormRoot = normRoot.toLowerCase();\n\t\tnormPath = normPath.toLowerCase();\n\t}\n\treturn normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\\\`);\n}\n\nfunction isWithinAllowedRoots(absPath: string, turn: TurnSnapshots): boolean {\n\tif (!turn.cwd) return true; // no recorded cwd: cannot validate, allow (tests / legacy)\n\tconst roots = [\n\t\tROLLBACK_BASE,\n\t\tjoin(homedir(), CONFIG_DIR_NAME), // lunr: behavior.md / cron jobs.json live under the config dir\n\t\t// lunr: simple-memory lives next to the lunr agent dir (~/.lunr/simple-memory)\n\t\tjoin(dirname(getAgentDir()), \"simple-memory\"),\n\t\tresolve(turn.cwd),\n\t];\n\treturn roots.some((root) => isPathUnderRoot(absPath, root));\n}\n\nfunction warnExternalModification(ctx: RollbackContext, message: string): void {\n\tif (ctx.externalModWarningShown || !warningHandler) return;\n\tctx.externalModWarningShown = true;\n\ttry {\n\t\twarningHandler(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\n/** Count how many turns a rollback would consume (trailing empty turns + the newest non-empty one). */\nexport function peekRollbackTurnsConsumed(sessionId?: string): number {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return 0;\n\tif (ctx.turns.length === 0) return 0;\n\tlet consumed = 0;\n\tfor (let i = ctx.turns.length - 1; i >= 0; i--) {\n\t\tconsumed++;\n\t\tif (ctx.turns[i].files.size > 0) break;\n\t}\n\treturn consumed;\n}\n\nexport function rollbackLastTurn(sessionId?: string): RollbackResult {\n\tconst ctx = getContext(sessionId);\n\tconst empty: RollbackResult = { restored: [], deleted: [], turnsConsumed: 0 };\n\tif (!isRollbackEnabled(ctx.sessionId)) return empty;\n\n\tlet turnsConsumed = 0;\n\twhile (ctx.turns.length > 0 && ctx.turns[ctx.turns.length - 1].files.size === 0) {\n\t\tconst skipped = ctx.turns.pop();\n\t\tif (skipped) {\n\t\t\tcleanupTurnFiles(ctx, skipped);\n\t\t\tturnsConsumed++;\n\t\t}\n\t}\n\tif (ctx.turns.length === 0) return empty;\n\tturnsConsumed++;\n\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tconst capture = ctx.settingsManager?.getRollbackCapture() ?? \"copies\";\n\tconst restored: string[] = [];\n\tconst deleted: string[] = [];\n\n\tfor (const [absPath, snap] of turn.files) {\n\t\ttry {\n\t\t\tif (!isWithinAllowedRoots(absPath, turn)) {\n\t\t\t\twarnExternalModification(\n\t\t\t\t\tctx,\n\t\t\t\t\t`Rollback skipped ${absPath}: outside the session working directory or lunR config dir.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (snap.existed && snap.content) {\n\t\t\t\t// For tree-scope baseline snapshots, skip the write if the file is\n\t\t\t\t// already identical to the snapshot (no actual change occurred during the\n\t\t\t\t// turn). Tool snapshots are always written so /rollback reports them.\n\t\t\t\tif (!snap.createdByTool && existsSync(absPath)) {\n\t\t\t\t\tconst current = readFileSync(absPath);\n\t\t\t\t\tif (current.equals(snap.content)) continue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absPath), { recursive: true });\n\t\t\t\twriteFileSync(absPath, snap.content);\n\t\t\t\trestored.push(absPath);\n\t\t\t} else if (!snap.existed) {\n\t\t\t\tconst shouldDelete = snap.createdByTool || capture === \"hybrid\" || capture === \"shadow-git\";\n\t\t\t\tif (shouldDelete && existsSync(absPath)) {\n\t\t\t\t\tunlinkSync(absPath);\n\t\t\t\t\tdeleted.push(absPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// existed but content unreadable — skip\n\t\t} catch {\n\t\t\t// individual file restore failures are non-fatal\n\t\t}\n\t}\n\n\tcleanupTurnFiles(ctx, turn);\n\tctx.turns.pop();\n\n\treturn { restored, deleted, turnsConsumed };\n}\n\nexport function getRollbackStatus(sessionId?: string): { enabled: boolean; turns: number; files: number } {\n\tconst ctx = getContext(sessionId);\n\treturn {\n\t\tenabled: isRollbackEnabled(ctx.sessionId),\n\t\tturns: ctx.turns.length,\n\t\tfiles: ctx.turns.reduce((sum, t) => sum + t.files.size, 0),\n\t};\n}\n\n/**\n * lunr: carry a session's rollback state across a fork — re-key the in-memory\n * context and move the on-disk dir to the forked session's id. No-op when the\n * old session has no state. Fork teardown deliberately skips clearRollback so\n * this can run right after (see interactive-mode beforeSessionInvalidate).\n */\nexport function migrateRollbackSession(oldSessionId: string, newSessionId: string): void {\n\tif (oldSessionId === newSessionId) return;\n\tconst ctx = contexts.get(oldSessionId);\n\tif (ctx) {\n\t\tcontexts.delete(oldSessionId);\n\t\tctx.sessionId = newSessionId;\n\t\tcontexts.set(newSessionId, ctx);\n\t}\n\ttry {\n\t\tconst oldDir = join(ROLLBACK_BASE, oldSessionId);\n\t\tconst newDir = join(ROLLBACK_BASE, newSessionId);\n\t\tif (existsSync(oldDir)) {\n\t\t\tif (existsSync(newDir)) rmSync(newDir, { recursive: true, force: true });\n\t\t\ttry {\n\t\t\t\trenameSync(oldDir, newDir);\n\t\t\t} catch {\n\t\t\t\t// Cross-device or locked-dir fallback: copy then delete.\n\t\t\t\tcpSync(oldDir, newDir, { recursive: true });\n\t\t\t\trmSync(oldDir, { recursive: true, force: true });\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// migration failures are non-fatal — in-memory state was still re-keyed\n\t}\n}\n\n/** Clear rollback state for a session (or all sessions when no id is passed). */\nexport function clearRollback(sessionId?: string): void {\n\tif (sessionId === undefined) {\n\t\tfor (const ctx of contexts.values()) {\n\t\t\tfor (const turn of ctx.turns) {\n\t\t\t\tcleanupTurnFiles(ctx, turn);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\t\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t}\n\t\tcontexts.clear();\n\t\treturn;\n\t}\n\n\tconst ctx = contexts.get(sessionId);\n\tif (!ctx) return;\n\tfor (const turn of ctx.turns) {\n\t\tcleanupTurnFiles(ctx, turn);\n\t}\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, sessionId);\n\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n\tcontexts.delete(sessionId);\n}\n\nfunction snapFileName(absPath: string): string {\n\treturn `${createHash(\"sha1\").update(absPath).digest(\"hex\")}.snap`;\n}\n"]}
1
+ {"version":3,"file":"rollback.js","sourceRoot":"","sources":["../../src/core/rollback.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: rollback service — pre-edit file snapshots for /rollback.\n *\n * Captures pre-write file content before edit/write tools mutate files, keyed\n * by user-turn and session id. `/rollback` restores the newest non-empty turn's\n * snapshots and then rewinds the conversation via a session fork (persistent,\n * unlike /undo and /edit, which stay in the same session file).\n *\n * Capture modes (setting `rollbackCapture`):\n * - copies: snapshot pre-write content; restore = write back + delete files\n * the agent created with edit/write.\n * - hybrid: copies + also delete files created OUTSIDE tools (tree scope).\n * - shadow-git: hidden git repo (deferred — behaves like copies).\n *\n * Scope (setting `rollbackScope`):\n * - tools: only edit/write tool changes.\n * - tree: also catches bash side-effects. Baseline = `git status --porcelain`\n * at turn START (pre-change content); at agent_end, files that are\n * dirty but were clean/absent at baseline are captured too (untracked\n * = created during the turn; tracked = restored from HEAD). Skipped\n * with a one-time warning when cwd is not a repo.\n *\n * Snapshots persist on disk under `~/.lunr/rollback/<session-id>/<turn-n>/`\n * (manifest.json + sha1-named .snap payloads) and are reloaded on init, so\n * rollback survives restarts. Turn granularity = one user message → agent_end.\n * Retention prunes beyond `rollbackTurns` turns.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, normalize, resolve } from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface Snapshot {\n\texisted: boolean;\n\tcontent?: Buffer;\n\tcreatedByTool: boolean;\n}\n\ninterface TurnSnapshots {\n\tturnIndex: number;\n\tcwd?: string;\n\tfiles: Map<string, Snapshot>;\n}\n\ninterface ManifestEntry {\n\texisted: boolean;\n\tcreatedByTool: boolean;\n\tsnapFile?: string;\n}\n\ninterface TurnManifest {\n\tcwd?: string;\n\tfiles: Record<string, ManifestEntry>;\n}\n\nconst ROLLBACK_BASE = join(homedir(), \".lunr\", \"rollback\");\n\ninterface RollbackContext {\n\tsessionId: string;\n\tsettingsManager: SettingsManager | undefined;\n\tsessionForceEnabled: boolean;\n\tturns: TurnSnapshots[];\n\tcurrentTurnIndex: number;\n\trepoWarningShown: boolean;\n\texternalModWarningShown: boolean;\n\t/** Last cwd seen by beginTurn — reused by auto-started turns (mid-turn enable). */\n\tlastCwd?: string;\n}\n\nconst contexts = new Map<string, RollbackContext>();\nlet lastSessionId: string | undefined;\nlet globalSettingsManager: SettingsManager | undefined;\nlet warningHandler: ((message: string) => void) | undefined;\n\n/** Register a sink for one-time user-facing warnings (e.g. tree scope outside a git repo). */\nexport function setRollbackWarningHandler(handler: ((message: string) => void) | undefined): void {\n\twarningHandler = handler;\n}\n\nfunction createContext(sessionId: string): RollbackContext {\n\treturn {\n\t\tsessionId,\n\t\tsettingsManager: globalSettingsManager,\n\t\tsessionForceEnabled: false,\n\t\tturns: [],\n\t\tcurrentTurnIndex: -1,\n\t\trepoWarningShown: false,\n\t\texternalModWarningShown: false,\n\t};\n}\n\nfunction getContext(sessionId?: string): RollbackContext {\n\tconst sid = sessionId ?? lastSessionId ?? \"default\";\n\tlet ctx = contexts.get(sid);\n\tif (!ctx) {\n\t\tctx = createContext(sid);\n\t\tcontexts.set(sid, ctx);\n\t}\n\treturn ctx;\n}\n\nexport function initRollback(sm: SettingsManager, sid: string): void {\n\tglobalSettingsManager = sm;\n\tlastSessionId = sid;\n\tcontexts.set(sid, createContext(sid));\n\tconst ctx = contexts.get(sid)!;\n\tctx.settingsManager = sm;\n\tloadPersistedTurns(ctx);\n}\n\nexport function isRollbackEnabled(sessionId?: string): boolean {\n\tconst ctx = getContext(sessionId);\n\treturn (ctx.settingsManager?.getRollbackEnabled() ?? false) || ctx.sessionForceEnabled;\n}\n\n/** Whether rollback is force-enabled for this session (e.g. by auto permission mode). */\nexport function isRollbackSessionForceEnabled(sessionId?: string): boolean {\n\treturn getContext(sessionId).sessionForceEnabled;\n}\n\nexport function enableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = true;\n}\n\nexport function disableRollbackForSession(sessionId?: string): void {\n\tgetContext(sessionId).sessionForceEnabled = false;\n}\n\nfunction pruneTurns(ctx: RollbackContext): void {\n\tconst maxTurns = ctx.settingsManager?.getRollbackTurns() ?? 2;\n\twhile (ctx.turns.length > maxTurns) {\n\t\tconst old = ctx.turns.shift();\n\t\tif (old) cleanupTurnFiles(ctx, old);\n\t}\n}\n\n/**\n * Begin a new turn: push a new snapshot map, prune old turns beyond retention.\n * When `cwd` is given and scope is `tree`, also capture the pre-turn baseline\n * (content of all currently dirty/untracked files) so bash side-effects can be\n * restored later.\n */\nexport function beginTurn(cwd?: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: remember the session cwd so auto-started turns (mid-turn enable) can\n\t// still be constrained by the allowed-roots check.\n\tif (cwd) ctx.lastCwd = cwd;\n\tctx.currentTurnIndex++;\n\tconst turn: TurnSnapshots = { turnIndex: ctx.currentTurnIndex, cwd, files: new Map() };\n\tctx.turns.push(turn);\n\tpruneTurns(ctx);\n\tif (cwd && ctx.settingsManager?.getRollbackScope() === \"tree\") {\n\t\tcaptureTreeBaseline(ctx, cwd);\n\t}\n}\n\n/** Capture pre-write content for a file path, recording under the current turn. */\nexport function rollbackSnapshotBeforeWrite(absPath: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\t// lunr: auto-started turn inherits the last known cwd so restores stay\n\t// root-checked; without any recorded cwd the allowed roots still apply.\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) beginTurn(ctx.lastCwd, ctx.sessionId);\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tif (turn.files.has(absPath)) return; // already snapshotted this turn\n\n\ttry {\n\t\tconst existed = existsSync(absPath);\n\t\tlet content: Buffer | undefined;\n\t\tif (existed) {\n\t\t\tcontent = readFileSync(absPath);\n\t\t}\n\t\tturn.files.set(absPath, {\n\t\t\texisted,\n\t\t\tcontent,\n\t\t\tcreatedByTool: true,\n\t\t});\n\t\tpersistSnapshot(ctx, turn, absPath);\n\t} catch {\n\t\t// snapshot failures are non-fatal\n\t}\n}\n\n/**\n * Tree scope, turn START: snapshot the current (pre-turn) content of every\n * dirty/untracked file so modifications made during the turn can be restored.\n */\nfunction captureTreeBaseline(ctx: RollbackContext, cwd: string): void {\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { path } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue;\n\t\ttry {\n\t\t\tconst existed = existsSync(abs);\n\t\t\tturn.files.set(abs, {\n\t\t\t\texisted,\n\t\t\t\tcontent: existed ? readFileSync(abs) : undefined,\n\t\t\t\tcreatedByTool: false,\n\t\t\t});\n\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Tree scope, turn END: pick up files that became dirty during the turn but\n * were clean/absent at baseline. Untracked = created during the turn (deleted\n * on restore under hybrid); tracked-modified = restore from HEAD content.\n */\nexport function captureTreeChanges(cwd: string, sessionId?: string): void {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return;\n\tif (ctx.settingsManager?.getRollbackScope() !== \"tree\") return;\n\tif (ctx.currentTurnIndex < 0 || ctx.turns.length === 0) return;\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tif (!turn) return;\n\n\tconst entries = gitPorcelain(ctx, cwd);\n\tif (!entries) return;\n\tfor (const { status, path, source } of entries) {\n\t\tif (!path) continue;\n\t\tconst abs = join(cwd, path);\n\t\tif (turn.files.has(abs)) continue; // already captured (baseline or tool snapshot)\n\t\ttry {\n\t\t\tif (status === \"??\" || status.includes(\"R\")) {\n\t\t\t\t// lunr: untracked now (or rename dest) and not in the baseline — created\n\t\t\t\t// during this turn (deleted on restore under hybrid/shadow-git).\n\t\t\t\tturn.files.set(abs, { existed: false, createdByTool: false });\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t\t// lunr: rename source existed at HEAD and is gone now — restore its\n\t\t\t\t// HEAD content on rollback, like a tracked file deleted mid-turn.\n\t\t\t\tif (status.includes(\"R\") && source) {\n\t\t\t\t\tconst sourceAbs = join(cwd, source);\n\t\t\t\t\tif (!turn.files.has(sourceAbs)) {\n\t\t\t\t\t\tconst headSrc = spawnSync(\"git\", [\"show\", `HEAD:${source}`], {\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!headSrc.error && headSrc.status === 0 && headSrc.stdout) {\n\t\t\t\t\t\t\tturn.files.set(sourceAbs, {\n\t\t\t\t\t\t\t\texisted: true,\n\t\t\t\t\t\t\t\tcontent: headSrc.stdout as Buffer,\n\t\t\t\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tpersistSnapshot(ctx, turn, sourceAbs);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tracked file that was clean at turn start; original = HEAD content.\n\t\t\t\tconst head = spawnSync(\"git\", [\"show\", `HEAD:${path}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\tmaxBuffer: 32 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tif (head.error || head.status !== 0 || !head.stdout) continue;\n\t\t\t\tturn.files.set(abs, {\n\t\t\t\t\texisted: true,\n\t\t\t\t\tcontent: head.stdout as Buffer,\n\t\t\t\t\tcreatedByTool: false,\n\t\t\t\t});\n\t\t\t\tpersistSnapshot(ctx, turn, abs);\n\t\t\t}\n\t\t} catch {\n\t\t\t// skip unreadable\n\t\t}\n\t}\n}\n\n/**\n * Run `git status --porcelain -z` and parse entries, or return undefined (with\n * a one-time warning) when cwd is not a repo / git fails. Rename/copy entries\n * carry their source path in a separate bare field — parsed into `source`.\n */\nfunction gitPorcelain(\n\tctx: RollbackContext,\n\tcwd: string,\n): Array<{ status: string; path: string; source?: string }> | undefined {\n\ttry {\n\t\tconst result = spawnSync(\"git\", [\"status\", \"--porcelain\", \"-z\"], { cwd, encoding: \"utf8\", timeout: 5000 });\n\t\tif (result.error || result.status !== 0) {\n\t\t\twarnOnce(\n\t\t\t\tctx,\n\t\t\t\t\"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\",\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst fields = (result.stdout ?? \"\").split(\"\\0\").filter((f) => f.length > 0);\n\t\tconst entries: Array<{ status: string; path: string; source?: string }> = [];\n\t\tfor (let i = 0; i < fields.length; i++) {\n\t\t\tconst field = fields[i];\n\t\t\tconst status = field.slice(0, 2);\n\t\t\tconst path = field.slice(3);\n\t\t\tlet source: string | undefined;\n\t\t\tif (status.includes(\"R\") || status.includes(\"C\")) {\n\t\t\t\t// Rename/copy: the bare field after this entry is the source path.\n\t\t\t\tsource = fields[i + 1];\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tentries.push({ status, path, source });\n\t\t}\n\t\treturn entries;\n\t} catch {\n\t\twarnOnce(ctx, \"Rollback tree scope: not a git repository (or git failed) — bash side-effects won't be captured.\");\n\t\treturn undefined;\n\t}\n}\n\nfunction warnOnce(ctx: RollbackContext, message: string): void {\n\tif (ctx.repoWarningShown) return;\n\tctx.repoWarningShown = true;\n\ttry {\n\t\twarningHandler?.(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\nfunction persistSnapshot(ctx: RollbackContext, turn: TurnSnapshots, absPath: string): void {\n\ttry {\n\t\tconst snap = turn.files.get(absPath);\n\t\tif (!snap) return;\n\t\tconst turnDir = getTurnDir(ctx, turn.turnIndex);\n\t\tmkdirSync(turnDir, { recursive: true });\n\t\tconst manifestPath = join(turnDir, \"manifest.json\");\n\t\tconst manifest = readManifest(manifestPath);\n\t\tconst entry: ManifestEntry = { existed: snap.existed, createdByTool: snap.createdByTool };\n\t\tif (snap.content) {\n\t\t\tconst snapFile = snapFileName(absPath);\n\t\t\twriteFileSync(join(turnDir, snapFile), snap.content);\n\t\t\tentry.snapFile = snapFile;\n\t\t}\n\t\tmanifest.files[absPath] = entry;\n\t\tif (turn.cwd) manifest.cwd = turn.cwd;\n\t\twriteFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n\t} catch {\n\t\t// disk persistence failures are non-fatal\n\t}\n}\n\nfunction readManifest(path: string): TurnManifest {\n\tif (!existsSync(path)) return { files: {} };\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\tconst files = (parsed as Record<string, unknown>).files;\n\t\t\treturn {\n\t\t\t\tcwd: typeof parsed.cwd === \"string\" ? parsed.cwd : undefined,\n\t\t\t\tfiles:\n\t\t\t\t\tfiles && typeof files === \"object\" && !Array.isArray(files)\n\t\t\t\t\t\t? (files as Record<string, ManifestEntry>)\n\t\t\t\t\t\t: {},\n\t\t\t};\n\t\t}\n\t\treturn { files: {} };\n\t} catch {\n\t\treturn { files: {} };\n\t}\n}\n\n/** Reload persisted turns from disk (called by initRollback) so rollback survives restarts. */\nfunction loadPersistedTurns(ctx: RollbackContext): void {\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\tif (!existsSync(sessionDir)) return;\n\t\tconst loaded: TurnSnapshots[] = [];\n\t\tfor (const dirName of readdirSync(sessionDir)) {\n\t\t\tconst match = /^turn-(\\d+)$/.exec(dirName);\n\t\t\tif (!match) continue;\n\t\t\tconst turnIndex = Number(match[1]);\n\t\t\tconst turnDir = join(sessionDir, dirName);\n\t\t\tconst manifest = readManifest(join(turnDir, \"manifest.json\"));\n\t\t\tconst files = new Map<string, Snapshot>();\n\t\t\tfor (const [absPath, meta] of Object.entries(manifest.files)) {\n\t\t\t\tlet content: Buffer | undefined;\n\t\t\t\tif (meta.snapFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(join(turnDir, meta.snapFile));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontent = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfiles.set(absPath, { existed: meta.existed, content, createdByTool: meta.createdByTool });\n\t\t\t}\n\t\t\tloaded.push({ turnIndex, cwd: manifest.cwd, files });\n\t\t}\n\t\tloaded.sort((a, b) => a.turnIndex - b.turnIndex);\n\t\tctx.turns = loaded;\n\t\tctx.currentTurnIndex = loaded.length > 0 ? loaded[loaded.length - 1].turnIndex : -1;\n\t\tpruneTurns(ctx);\n\t} catch {\n\t\t// reload failures are non-fatal — start empty\n\t}\n}\n\nfunction getTurnDir(ctx: RollbackContext, turnIndex: number): string {\n\treturn join(ROLLBACK_BASE, ctx.sessionId, `turn-${turnIndex}`);\n}\n\nfunction cleanupTurnFiles(ctx: RollbackContext, turn: TurnSnapshots): void {\n\ttry {\n\t\tconst dir = getTurnDir(ctx, turn.turnIndex);\n\t\tif (existsSync(dir)) rmSync(dir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n}\n\nexport interface RollbackResult {\n\trestored: string[];\n\tdeleted: string[];\n\t/** Number of turns consumed: the restored turn plus any empty turns skipped above it. */\n\tturnsConsumed: number;\n}\n\n/**\n * Restore the newest NON-EMPTY turn's snapshots, drop it (and any empty turns\n * on top of it), and return what actually happened. Files the agent created\n * with edit/write are deleted in every capture mode; files created outside\n * tools (tree scope) are deleted only under hybrid/shadow-git.\n */\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tlet normRoot = normalize(root).replace(/\\\\$/g, \"\");\n\tlet normPath = normalize(absPath).replace(/\\\\$/g, \"\");\n\t// lunr: Windows drive-letter/directory casing differs by launch context and\n\t// resolve() preserves input casing — compare case-insensitively there.\n\tif (process.platform === \"win32\") {\n\t\tnormRoot = normRoot.toLowerCase();\n\t\tnormPath = normPath.toLowerCase();\n\t}\n\treturn normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\\\`);\n}\n\nfunction isWithinAllowedRoots(absPath: string, turn: TurnSnapshots): boolean {\n\tif (!turn.cwd) return true; // no recorded cwd: cannot validate, allow (tests / legacy)\n\tconst roots = [\n\t\tROLLBACK_BASE,\n\t\tjoin(homedir(), CONFIG_DIR_NAME), // lunr: behavior.md / cron jobs.json live under the config dir\n\t\t// lunr: simple-memory lives next to the lunr agent dir (~/.lunr/simple-memory)\n\t\tjoin(dirname(getAgentDir()), \"simple-memory\"),\n\t\tresolve(turn.cwd),\n\t];\n\treturn roots.some((root) => isPathUnderRoot(absPath, root));\n}\n\nfunction warnExternalModification(ctx: RollbackContext, message: string): void {\n\tif (ctx.externalModWarningShown || !warningHandler) return;\n\tctx.externalModWarningShown = true;\n\ttry {\n\t\twarningHandler(message);\n\t} catch {\n\t\t// warning delivery is best-effort\n\t}\n}\n\n/** Count how many turns a rollback would consume (trailing empty turns + the newest non-empty one). */\nexport function peekRollbackTurnsConsumed(sessionId?: string): number {\n\tconst ctx = getContext(sessionId);\n\tif (!isRollbackEnabled(ctx.sessionId)) return 0;\n\tif (ctx.turns.length === 0) return 0;\n\tlet consumed = 0;\n\tfor (let i = ctx.turns.length - 1; i >= 0; i--) {\n\t\tconsumed++;\n\t\tif (ctx.turns[i].files.size > 0) break;\n\t}\n\treturn consumed;\n}\n\nexport function rollbackLastTurn(sessionId?: string): RollbackResult {\n\tconst ctx = getContext(sessionId);\n\tconst empty: RollbackResult = { restored: [], deleted: [], turnsConsumed: 0 };\n\tif (!isRollbackEnabled(ctx.sessionId)) return empty;\n\n\tlet turnsConsumed = 0;\n\twhile (ctx.turns.length > 0 && ctx.turns[ctx.turns.length - 1].files.size === 0) {\n\t\tconst skipped = ctx.turns.pop();\n\t\tif (skipped) {\n\t\t\tcleanupTurnFiles(ctx, skipped);\n\t\t\tturnsConsumed++;\n\t\t}\n\t}\n\tif (ctx.turns.length === 0) return empty;\n\tturnsConsumed++;\n\n\tconst turn = ctx.turns[ctx.turns.length - 1];\n\tconst capture = ctx.settingsManager?.getRollbackCapture() ?? \"copies\";\n\tconst restored: string[] = [];\n\tconst deleted: string[] = [];\n\n\tfor (const [absPath, snap] of turn.files) {\n\t\ttry {\n\t\t\tif (!isWithinAllowedRoots(absPath, turn)) {\n\t\t\t\twarnExternalModification(\n\t\t\t\t\tctx,\n\t\t\t\t\t`Rollback skipped ${absPath}: outside the session working directory or lunR config dir.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (snap.existed && snap.content) {\n\t\t\t\t// For tree-scope baseline snapshots, skip the write if the file is\n\t\t\t\t// already identical to the snapshot (no actual change occurred during the\n\t\t\t\t// turn). Tool snapshots are always written so /rollback reports them.\n\t\t\t\tif (!snap.createdByTool && existsSync(absPath)) {\n\t\t\t\t\tconst current = readFileSync(absPath);\n\t\t\t\t\tif (current.equals(snap.content)) continue;\n\t\t\t\t}\n\t\t\t\tmkdirSync(dirname(absPath), { recursive: true });\n\t\t\t\twriteFileSync(absPath, snap.content);\n\t\t\t\trestored.push(absPath);\n\t\t\t} else if (!snap.existed) {\n\t\t\t\tconst shouldDelete = snap.createdByTool || capture === \"hybrid\" || capture === \"shadow-git\";\n\t\t\t\tif (shouldDelete && existsSync(absPath)) {\n\t\t\t\t\tunlinkSync(absPath);\n\t\t\t\t\tdeleted.push(absPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// existed but content unreadable — skip\n\t\t} catch {\n\t\t\t// individual file restore failures are non-fatal\n\t\t}\n\t}\n\n\tcleanupTurnFiles(ctx, turn);\n\tctx.turns.pop();\n\n\treturn { restored, deleted, turnsConsumed };\n}\n\nexport function getRollbackStatus(sessionId?: string): { enabled: boolean; turns: number; files: number } {\n\tconst ctx = getContext(sessionId);\n\treturn {\n\t\tenabled: isRollbackEnabled(ctx.sessionId),\n\t\tturns: ctx.turns.length,\n\t\tfiles: ctx.turns.reduce((sum, t) => sum + t.files.size, 0),\n\t};\n}\n\n/**\n * lunr: carry a session's rollback state across a fork — re-key the in-memory\n * context and move the on-disk dir to the forked session's id. No-op when the\n * old session has no state. Fork teardown deliberately skips clearRollback so\n * this can run right after (see interactive-mode beforeSessionInvalidate).\n */\nexport function migrateRollbackSession(oldSessionId: string, newSessionId: string): void {\n\tif (oldSessionId === newSessionId) return;\n\tconst ctx = contexts.get(oldSessionId);\n\tif (ctx) {\n\t\tcontexts.delete(oldSessionId);\n\t\tctx.sessionId = newSessionId;\n\t\tcontexts.set(newSessionId, ctx);\n\t}\n\ttry {\n\t\tconst oldDir = join(ROLLBACK_BASE, oldSessionId);\n\t\tconst newDir = join(ROLLBACK_BASE, newSessionId);\n\t\tif (existsSync(oldDir)) {\n\t\t\tif (existsSync(newDir)) rmSync(newDir, { recursive: true, force: true });\n\t\t\ttry {\n\t\t\t\trenameSync(oldDir, newDir);\n\t\t\t} catch {\n\t\t\t\t// Cross-device or locked-dir fallback: copy then delete.\n\t\t\t\tcpSync(oldDir, newDir, { recursive: true });\n\t\t\t\trmSync(oldDir, { recursive: true, force: true });\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// migration failures are non-fatal — in-memory state was still re-keyed\n\t}\n}\n\n/** Clear rollback state for a session (or all sessions when no id is passed). */\nexport function clearRollback(sessionId?: string): void {\n\tif (sessionId === undefined) {\n\t\tfor (const ctx of contexts.values()) {\n\t\t\tfor (const turn of ctx.turns) {\n\t\t\t\tcleanupTurnFiles(ctx, turn);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst sessionDir = join(ROLLBACK_BASE, ctx.sessionId);\n\t\t\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t}\n\t\tcontexts.clear();\n\t\treturn;\n\t}\n\n\tconst ctx = contexts.get(sessionId);\n\tif (!ctx) return;\n\tfor (const turn of ctx.turns) {\n\t\tcleanupTurnFiles(ctx, turn);\n\t}\n\ttry {\n\t\tconst sessionDir = join(ROLLBACK_BASE, sessionId);\n\t\tif (existsSync(sessionDir)) rmSync(sessionDir, { recursive: true, force: true });\n\t} catch {\n\t\t// ignore\n\t}\n\tcontexts.delete(sessionId);\n}\n\nfunction snapFileName(absPath: string): string {\n\treturn `${createHash(\"sha1\").update(absPath).digest(\"hex\")}.snap`;\n}\n"]}