talos-code 0.0.1 → 0.1.0

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 (401) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +111 -3
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  4. package/dist/archive/zip.js +73 -0
  5. package/dist/args.js +97 -0
  6. package/dist/automations/history-store.js +34 -0
  7. package/dist/automations/policy-store.js +43 -0
  8. package/dist/automations/runner.js +107 -0
  9. package/dist/automations/schedule.js +85 -0
  10. package/dist/automations/windows-task.js +53 -0
  11. package/dist/commands/advanced-cli.js +233 -0
  12. package/dist/commands/checkpoint-cli.js +45 -0
  13. package/dist/commands/config-cli.js +107 -0
  14. package/dist/commands/context.js +17 -0
  15. package/dist/commands/extensions-cli.js +207 -0
  16. package/dist/commands/project-cli.js +74 -0
  17. package/dist/commands/project-commands.js +143 -0
  18. package/dist/commands/provider-cli.js +185 -0
  19. package/dist/commands/services-cli.js +248 -0
  20. package/dist/commands/session-cli.js +171 -0
  21. package/dist/commands/system-cli.js +405 -0
  22. package/dist/config/commands.js +60 -0
  23. package/dist/config/load.js +382 -0
  24. package/dist/config/migrations.js +48 -0
  25. package/dist/config/types.js +11 -0
  26. package/dist/diagnostics/development-log.js +154 -0
  27. package/dist/diagnostics/doctor.js +104 -0
  28. package/dist/diagnostics/redact.js +80 -0
  29. package/dist/diagnostics/zip.js +52 -0
  30. package/dist/errors.js +156 -0
  31. package/dist/events/bridge.js +130 -0
  32. package/dist/extensions/installer.js +178 -0
  33. package/dist/extensions/package-schema.js +24 -0
  34. package/dist/headless/result.js +84 -0
  35. package/dist/headless/run.js +230 -0
  36. package/dist/i18n/en/approval.js +43 -0
  37. package/dist/i18n/en/common.js +9 -0
  38. package/dist/i18n/en/credentials.js +82 -0
  39. package/dist/i18n/en/errors.js +246 -0
  40. package/dist/i18n/en/firstrun.js +89 -0
  41. package/dist/i18n/en/providers.js +37 -0
  42. package/dist/i18n/en/screen.js +736 -0
  43. package/dist/i18n/en/tools.js +127 -0
  44. package/dist/i18n/en.js +14 -0
  45. package/dist/i18n/error-view.js +307 -0
  46. package/dist/i18n/index.js +19 -0
  47. package/dist/i18n/kernel-map.js +139 -0
  48. package/dist/io.js +45 -0
  49. package/dist/main.js +330 -0
  50. package/dist/paths.js +41 -0
  51. package/dist/protocol/v2/codec.js +9 -0
  52. package/dist/protocol/v2/events.js +555 -0
  53. package/dist/protocol/v2/index.js +4 -0
  54. package/dist/protocol/v2/replay.js +28 -0
  55. package/dist/protocol/v2/types.js +1 -0
  56. package/dist/provider/control-plane.js +135 -0
  57. package/dist/provider/environment-keys.js +275 -0
  58. package/dist/provider/health.js +110 -0
  59. package/dist/provider/missing-key.js +48 -0
  60. package/dist/provider/model-catalog.js +168 -0
  61. package/dist/provider/provider-text.js +13 -0
  62. package/dist/provider/store.js +95 -0
  63. package/dist/provider/system-keyring.js +214 -0
  64. package/dist/runtime/active-run.js +63 -0
  65. package/dist/runtime/agent-tree.js +74 -0
  66. package/dist/runtime/attachments.js +84 -0
  67. package/dist/runtime/brokered-executor.js +395 -0
  68. package/dist/runtime/context-status.js +76 -0
  69. package/dist/runtime/create-runtime.js +111 -0
  70. package/dist/runtime/model-profile.js +361 -0
  71. package/dist/runtime/output-store.js +137 -0
  72. package/dist/runtime/provider-attempts.js +185 -0
  73. package/dist/runtime/replay-buffer.js +153 -0
  74. package/dist/runtime/repo.js +95 -0
  75. package/dist/runtime/session-facade.js +135 -0
  76. package/dist/runtime/session-summary.js +125 -0
  77. package/dist/runtime/supervisor.js +262 -0
  78. package/dist/runtime/talos-composition.js +1006 -0
  79. package/dist/runtime/types.js +5 -0
  80. package/dist/runtime/usage-snapshot.js +82 -0
  81. package/dist/security/auto-classifier.js +38 -0
  82. package/dist/security/credential-free-environment.js +54 -0
  83. package/dist/security/evaluate.js +243 -0
  84. package/dist/security/execution-backends.js +236 -0
  85. package/dist/security/execution-broker.js +102 -0
  86. package/dist/security/forge-scan.js +74 -0
  87. package/dist/security/from-approval.js +39 -0
  88. package/dist/security/mxc-execution-backend.js +281 -0
  89. package/dist/security/permission-engine.js +138 -0
  90. package/dist/security/permission-explanation.js +54 -0
  91. package/dist/security/persist.js +179 -0
  92. package/dist/security/plugin-guard.js +230 -0
  93. package/dist/security/process-tree-evidence-store.js +74 -0
  94. package/dist/security/process-tree-probe.js +554 -0
  95. package/dist/security/project-resource-inventory.js +186 -0
  96. package/dist/security/project-trust-gate.js +38 -0
  97. package/dist/security/project-trust.js +367 -0
  98. package/dist/security/rule-parser.js +201 -0
  99. package/dist/security/shell-segmentation.js +68 -0
  100. package/dist/security/trust-authority.js +324 -0
  101. package/dist/security/types.js +1 -0
  102. package/dist/security/workspace-identity.js +102 -0
  103. package/dist/services/automation-facade.js +59 -0
  104. package/dist/services/forge-facade.js +77 -0
  105. package/dist/services/hook-facade.js +240 -0
  106. package/dist/services/index.js +16 -0
  107. package/dist/services/library-facade.js +174 -0
  108. package/dist/services/mcp-facade.js +348 -0
  109. package/dist/services/memory-facade.js +126 -0
  110. package/dist/services/notes-facade.js +157 -0
  111. package/dist/services/plugin-facade.js +308 -0
  112. package/dist/services/research-facade.js +110 -0
  113. package/dist/services/task-board-facade.js +236 -0
  114. package/dist/services/workflow-catalog.js +246 -0
  115. package/dist/sessions/export-format.js +98 -0
  116. package/dist/sessions/metadata-store.js +160 -0
  117. package/dist/sessions/share.js +122 -0
  118. package/dist/sessions/transfer.js +16 -0
  119. package/dist/subcommands.js +62 -0
  120. package/dist/tui/agent-roster.js +36 -0
  121. package/dist/tui/app.js +3761 -0
  122. package/dist/tui/approval.js +36 -0
  123. package/dist/tui/boot/boot-sequence.js +76 -0
  124. package/dist/tui/boot/cinematic.js +243 -0
  125. package/dist/tui/boot/desktop-logo.js +82 -0
  126. package/dist/tui/boot.js +3 -0
  127. package/dist/tui/busy-input.js +58 -0
  128. package/dist/tui/catalog-service.js +559 -0
  129. package/dist/tui/components/assistant-stream.js +1 -0
  130. package/dist/tui/components/command-menu.js +68 -0
  131. package/dist/tui/components/composer.js +219 -0
  132. package/dist/tui/components/diff.js +35 -0
  133. package/dist/tui/components/footer.js +48 -0
  134. package/dist/tui/components/header.js +6 -0
  135. package/dist/tui/components/markdown.js +305 -0
  136. package/dist/tui/components/status-indicator.js +32 -0
  137. package/dist/tui/components/terminal-shell.js +205 -0
  138. package/dist/tui/components/thinking-row.js +19 -0
  139. package/dist/tui/components/tool-row.js +97 -0
  140. package/dist/tui/components/transcript-virtualizer.js +165 -0
  141. package/dist/tui/components/transcript.js +43 -0
  142. package/dist/tui/diff-model.js +77 -0
  143. package/dist/tui/editor-history.js +21 -0
  144. package/dist/tui/editor.js +210 -0
  145. package/dist/tui/event-adapter.js +436 -0
  146. package/dist/tui/exit-output.js +58 -0
  147. package/dist/tui/external-editor.js +53 -0
  148. package/dist/tui/file-completion.js +89 -0
  149. package/dist/tui/focus-manager.js +5 -0
  150. package/dist/tui/highlight.js +30 -0
  151. package/dist/tui/input-router.js +18 -0
  152. package/dist/tui/interrupt.js +99 -0
  153. package/dist/tui/keybindings.js +194 -0
  154. package/dist/tui/keymap-resolver.js +91 -0
  155. package/dist/tui/launch-state.js +139 -0
  156. package/dist/tui/line-diff.js +57 -0
  157. package/dist/tui/live-activity.js +45 -0
  158. package/dist/tui/metrics.js +123 -0
  159. package/dist/tui/onboarding.js +36 -0
  160. package/dist/tui/overlays/agent-tree.js +39 -0
  161. package/dist/tui/overlays/approval-dialog.js +640 -0
  162. package/dist/tui/overlays/automation-center.js +51 -0
  163. package/dist/tui/overlays/checkpoint-picker.js +47 -0
  164. package/dist/tui/overlays/context-inspector.js +36 -0
  165. package/dist/tui/overlays/effort-line.js +110 -0
  166. package/dist/tui/overlays/forge-center.js +46 -0
  167. package/dist/tui/overlays/help-dialog.js +16 -0
  168. package/dist/tui/overlays/history-picker.js +37 -0
  169. package/dist/tui/overlays/hook-center.js +70 -0
  170. package/dist/tui/overlays/library-center.js +55 -0
  171. package/dist/tui/overlays/mcp-center.js +60 -0
  172. package/dist/tui/overlays/memory-center.js +63 -0
  173. package/dist/tui/overlays/model-picker.js +72 -0
  174. package/dist/tui/overlays/notes-center.js +53 -0
  175. package/dist/tui/overlays/overlay-host.js +8 -0
  176. package/dist/tui/overlays/plugin-center.js +76 -0
  177. package/dist/tui/overlays/provider-picker.js +145 -0
  178. package/dist/tui/overlays/queue-editor.js +32 -0
  179. package/dist/tui/overlays/research-center.js +59 -0
  180. package/dist/tui/overlays/scroll-window.js +234 -0
  181. package/dist/tui/overlays/session-picker.js +115 -0
  182. package/dist/tui/overlays/tasks-center.js +83 -0
  183. package/dist/tui/overlays/theme-picker.js +21 -0
  184. package/dist/tui/overlays/transcript-search.js +58 -0
  185. package/dist/tui/overlays/trust-center.js +29 -0
  186. package/dist/tui/overlays/workflow-center.js +46 -0
  187. package/dist/tui/project-file-index.js +214 -0
  188. package/dist/tui/project-references.js +85 -0
  189. package/dist/tui/project-trust-prompt.js +287 -0
  190. package/dist/tui/prompt-history-store.js +116 -0
  191. package/dist/tui/queue-store.js +110 -0
  192. package/dist/tui/regions/budget.js +59 -0
  193. package/dist/tui/regions/views.js +96 -0
  194. package/dist/tui/render-coordinator.js +148 -0
  195. package/dist/tui/render-scheduler.js +4 -0
  196. package/dist/tui/run.js +69 -0
  197. package/dist/tui/selection-list.js +29 -0
  198. package/dist/tui/session-controller.js +778 -0
  199. package/dist/tui/session-export.js +54 -0
  200. package/dist/tui/shell-input.js +35 -0
  201. package/dist/tui/shell-layout.js +48 -0
  202. package/dist/tui/shell-model.js +98 -0
  203. package/dist/tui/slash-commands.js +80 -0
  204. package/dist/tui/state.js +151 -0
  205. package/dist/tui/status-bar.js +125 -0
  206. package/dist/tui/status-view.js +40 -0
  207. package/dist/tui/terminal-capabilities.js +9 -0
  208. package/dist/tui/terminal-session.js +11 -0
  209. package/dist/tui/text-width.js +66 -0
  210. package/dist/tui/theme-catalog.js +25 -0
  211. package/dist/tui/theme-store.js +23 -0
  212. package/dist/tui/theme.js +23 -0
  213. package/dist/tui/tool-display.js +135 -0
  214. package/dist/tui/tool-facts.js +1 -0
  215. package/dist/tui/tools/bash-renderer.js +32 -0
  216. package/dist/tui/tools/change.js +61 -0
  217. package/dist/tui/tools/edit-renderer.js +17 -0
  218. package/dist/tui/tools/generic-renderer.js +13 -0
  219. package/dist/tui/tools/list-renderer.js +14 -0
  220. package/dist/tui/tools/read-renderer.js +13 -0
  221. package/dist/tui/tools/registry.js +20 -0
  222. package/dist/tui/tools/row-format.js +187 -0
  223. package/dist/tui/tools/search-renderer.js +38 -0
  224. package/dist/tui/tools/shared.js +97 -0
  225. package/dist/tui/tools/write-renderer.js +15 -0
  226. package/dist/tui/transcript-model.js +441 -0
  227. package/dist/tui/ui-preferences.js +79 -0
  228. package/dist/tui/usage-view.js +77 -0
  229. package/dist/tui/vim-mode.js +99 -0
  230. package/dist/update/check.js +15 -0
  231. package/dist/update/npm.js +51 -0
  232. package/dist/update/run.js +129 -0
  233. package/dist/version.js +2 -0
  234. package/dist/workspace/checkpoint-store.js +210 -0
  235. package/dist/workspace/checkpoint.js +413 -0
  236. package/dist/workspace/restore.js +232 -0
  237. package/package.json +63 -5
  238. package/vendor/context-engine/package.json +11 -0
  239. package/vendor/context-engine/src/compaction-planner.mjs +57 -0
  240. package/vendor/context-engine/src/contracts.mjs +48 -0
  241. package/vendor/context-engine/src/engine.mjs +445 -0
  242. package/vendor/context-engine/src/node/context-export.mjs +164 -0
  243. package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
  244. package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
  245. package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
  246. package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
  247. package/vendor/context-engine/src/profiles.mjs +10 -0
  248. package/vendor/context-engine/src/retrieval.mjs +104 -0
  249. package/vendor/context-engine/src/summary.mjs +97 -0
  250. package/vendor/context-engine/src/usage.mjs +34 -0
  251. package/vendor/harness-ui/package.json +38 -0
  252. package/vendor/harness-ui/src/acp-agent.mjs +350 -0
  253. package/vendor/harness-ui/src/agent-service.mjs +2188 -0
  254. package/vendor/harness-ui/src/agui-events.mjs +452 -0
  255. package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
  256. package/vendor/harness-ui/src/artifact-store.mjs +39 -0
  257. package/vendor/harness-ui/src/assistenza.mjs +123 -0
  258. package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
  259. package/vendor/harness-ui/src/automation-store.mjs +145 -0
  260. package/vendor/harness-ui/src/browser-annota.mjs +639 -0
  261. package/vendor/harness-ui/src/browser-frame.mjs +211 -0
  262. package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
  263. package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
  264. package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
  265. package/vendor/harness-ui/src/browser-stream.mjs +445 -0
  266. package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
  267. package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
  268. package/vendor/harness-ui/src/config.mjs +697 -0
  269. package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
  270. package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
  271. package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
  272. package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
  273. package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
  274. package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
  275. package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
  276. package/vendor/harness-ui/src/context-runtime.mjs +118 -0
  277. package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
  278. package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
  279. package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
  280. package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
  281. package/vendor/harness-ui/src/custom-task.mjs +171 -0
  282. package/vendor/harness-ui/src/doctor.mjs +142 -0
  283. package/vendor/harness-ui/src/document-filename.mjs +97 -0
  284. package/vendor/harness-ui/src/document-generator.mjs +493 -0
  285. package/vendor/harness-ui/src/document-report.mjs +331 -0
  286. package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
  287. package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
  288. package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
  289. package/vendor/harness-ui/src/forge-contract.mjs +221 -0
  290. package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
  291. package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
  292. package/vendor/harness-ui/src/generation-idle.mjs +332 -0
  293. package/vendor/harness-ui/src/gguf-header.mjs +207 -0
  294. package/vendor/harness-ui/src/git-service.mjs +626 -0
  295. package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
  296. package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
  297. package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
  298. package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
  299. package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
  300. package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
  301. package/vendor/harness-ui/src/hook-registry.mjs +186 -0
  302. package/vendor/harness-ui/src/http-app.mjs +6259 -0
  303. package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
  304. package/vendor/harness-ui/src/id-archivio.mjs +27 -0
  305. package/vendor/harness-ui/src/image-generator.mjs +143 -0
  306. package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
  307. package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
  308. package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
  309. package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
  310. package/vendor/harness-ui/src/library-store.mjs +652 -0
  311. package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
  312. package/vendor/harness-ui/src/local-model-store.mjs +339 -0
  313. package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
  314. package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
  315. package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
  316. package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
  317. package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
  318. package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
  319. package/vendor/harness-ui/src/mcp-client.mjs +98 -0
  320. package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
  321. package/vendor/harness-ui/src/mcp-session.mjs +177 -0
  322. package/vendor/harness-ui/src/memory-store.mjs +227 -0
  323. package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
  324. package/vendor/harness-ui/src/model-catalog.mjs +129 -0
  325. package/vendor/harness-ui/src/model-destination.mjs +189 -0
  326. package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
  327. package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
  328. package/vendor/harness-ui/src/notes-store.mjs +250 -0
  329. package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
  330. package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
  331. package/vendor/harness-ui/src/path-policy.mjs +442 -0
  332. package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
  333. package/vendor/harness-ui/src/plugin-session.mjs +180 -0
  334. package/vendor/harness-ui/src/process-policy.mjs +345 -0
  335. package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
  336. package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
  337. package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
  338. package/vendor/harness-ui/src/provider-probe.mjs +582 -0
  339. package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
  340. package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
  341. package/vendor/harness-ui/src/public-problem.mjs +109 -0
  342. package/vendor/harness-ui/src/research/card.mjs +235 -0
  343. package/vendor/harness-ui/src/research/citations.mjs +142 -0
  344. package/vendor/harness-ui/src/research/collector.mjs +275 -0
  345. package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
  346. package/vendor/harness-ui/src/research/dossier.mjs +114 -0
  347. package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
  348. package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
  349. package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
  350. package/vendor/harness-ui/src/research/independence.mjs +159 -0
  351. package/vendor/harness-ui/src/research/ledger.mjs +166 -0
  352. package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
  353. package/vendor/harness-ui/src/research/narration.mjs +181 -0
  354. package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
  355. package/vendor/harness-ui/src/research/opposing.mjs +305 -0
  356. package/vendor/harness-ui/src/research/outline.mjs +111 -0
  357. package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
  358. package/vendor/harness-ui/src/research/pdf.mjs +291 -0
  359. package/vendor/harness-ui/src/research/plan.mjs +301 -0
  360. package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
  361. package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
  362. package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
  363. package/vendor/harness-ui/src/research/recheck.mjs +194 -0
  364. package/vendor/harness-ui/src/research/report.mjs +203 -0
  365. package/vendor/harness-ui/src/research/run.mjs +527 -0
  366. package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
  367. package/vendor/harness-ui/src/research/verification.mjs +572 -0
  368. package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
  369. package/vendor/harness-ui/src/research-store.mjs +1133 -0
  370. package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
  371. package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
  372. package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
  373. package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
  374. package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
  375. package/vendor/harness-ui/src/search-source-store.mjs +172 -0
  376. package/vendor/harness-ui/src/session-registry.mjs +6095 -0
  377. package/vendor/harness-ui/src/session-store.mjs +220 -0
  378. package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
  379. package/vendor/harness-ui/src/setup-stato.mjs +31 -0
  380. package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
  381. package/vendor/harness-ui/src/skill-registry.mjs +120 -0
  382. package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
  383. package/vendor/harness-ui/src/static-files.mjs +96 -0
  384. package/vendor/harness-ui/src/stream-partition.mjs +123 -0
  385. package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
  386. package/vendor/harness-ui/src/task-catalog.mjs +65 -0
  387. package/vendor/harness-ui/src/tasks-store.mjs +220 -0
  388. package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
  389. package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
  390. package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
  391. package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
  392. package/vendor/harness-ui/src/usage-cache.mjs +315 -0
  393. package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
  394. package/vendor/harness-ui/src/workspace-context.mjs +124 -0
  395. package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
  396. package/vendor/harness-ui/src/workspace-files.mjs +589 -0
  397. package/vendor/harness-ui/src/workspace-info.mjs +189 -0
  398. package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
  399. package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
  400. package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
  401. package/vendor/manifest.json +170 -0
@@ -0,0 +1,413 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { chmod, copyFile, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import { resolveWorkspaceIdentity } from "../security/workspace-identity.js";
7
+ export const CHECKPOINT_LIMITS = { maxEntries: 50_000, maxStoredBytes: 256 * 1024 * 1024, maxStoredFileBytes: 64 * 1024 * 1024 };
8
+ function coded(code, message = code, details = {}) { return Object.assign(new Error(message === code ? code : `${code}: ${message}`), { code, details }); }
9
+ function sha256(data) { return createHash('sha256').update(data).digest('hex'); }
10
+ function slash(value) { return value.split(path.sep).join('/'); }
11
+ function safeRelative(value) {
12
+ const normalized = value.replace(/\\/gu, '/');
13
+ if (!normalized || normalized.startsWith('/') || normalized.split('/').some(part => !part || part === '.' || part === '..'))
14
+ throw coded('CHECKPOINT_PATH_INVALID');
15
+ return normalized;
16
+ }
17
+ function absoluteFromRelative(root, relative) { return path.resolve(root, ...safeRelative(relative).split('/')); }
18
+ function inside(root, candidate) { const relative = path.relative(root, candidate); return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)); }
19
+ /*
20
+ * R0 (2026-09-23). A file is made durable through a WRITABLE handle: FlushFileBuffers needs GENERIC_WRITE, so on Windows
21
+ * `open(path,'r')` + `sync()` is EPERM and every checkpoint undo/redo failed (exit 70) before restoring anything. POSIX
22
+ * accepts fsync on a read-only descriptor, which keeps a file already chmod-ed read-only syncable there. A file fsync
23
+ * error is never swallowed (gate E8/E9); only the directory step below is tolerated on win32.
24
+ */
25
+ export async function durableFile(pathname) {
26
+ let handle;
27
+ try {
28
+ handle = await open(pathname, 'r+');
29
+ }
30
+ catch (error) {
31
+ if (process.platform === 'win32' || (error?.code !== 'EACCES' && error?.code !== 'EPERM'))
32
+ throw error;
33
+ handle = await open(pathname, 'r');
34
+ }
35
+ try {
36
+ await handle.sync();
37
+ }
38
+ finally {
39
+ await handle.close();
40
+ }
41
+ }
42
+ async function durableDir(dirname) { try {
43
+ const handle = await open(dirname, 'r');
44
+ try {
45
+ await handle.sync();
46
+ }
47
+ finally {
48
+ await handle.close();
49
+ }
50
+ }
51
+ catch (error) {
52
+ if (process.platform !== 'win32')
53
+ throw error;
54
+ } }
55
+ async function ensurePrivateDir(dirname) { await mkdir(dirname, { recursive: true, mode: 0o700 }); try {
56
+ await chmod(dirname, 0o700);
57
+ }
58
+ catch (error) {
59
+ if (process.platform !== 'win32')
60
+ throw error;
61
+ } }
62
+ async function storeBlob(blobRoot, data) {
63
+ const hash = sha256(data), target = path.join(blobRoot, hash);
64
+ await ensurePrivateDir(blobRoot);
65
+ const temp = path.join(blobRoot, `.${hash}.${process.pid}.tmp`);
66
+ try {
67
+ const handle = await open(temp, 'wx', 0o600);
68
+ try {
69
+ await handle.writeFile(data);
70
+ await handle.sync();
71
+ }
72
+ finally {
73
+ await handle.close();
74
+ }
75
+ await rename(temp, target).catch(async (error) => { if (error?.code === 'EEXIST') {
76
+ await rm(temp, { force: true });
77
+ return;
78
+ } throw error; });
79
+ await durableDir(blobRoot);
80
+ }
81
+ catch (error) {
82
+ if (error?.code !== 'EEXIST')
83
+ throw error;
84
+ }
85
+ return hash;
86
+ }
87
+ function consumeFileBudget(budget, size) {
88
+ if (size > budget.limits.maxStoredFileBytes)
89
+ throw coded('CHECKPOINT_FILE_TOO_LARGE');
90
+ if (budget.storedBytes + size > budget.limits.maxStoredBytes)
91
+ throw coded('CHECKPOINT_SNAPSHOT_TOO_LARGE');
92
+ budget.storedBytes += size;
93
+ }
94
+ function consumeEntry(budget) { budget.entries += 1; if (budget.entries > budget.limits.maxEntries)
95
+ throw coded('CHECKPOINT_TOO_MANY_ENTRIES'); }
96
+ async function gitBytes(cwd, args, maxBytes = 64 * 1024 * 1024, allowedExitCodes = [0], stdin) {
97
+ return new Promise((resolve, reject) => {
98
+ const child = spawn('git', ['-c', 'core.quotepath=false', ...args], { cwd, stdio: [stdin ? 'pipe' : 'ignore', 'pipe', 'pipe'], windowsHide: true });
99
+ const out = [];
100
+ const err = [];
101
+ let total = 0;
102
+ const stdout = child.stdout, stderr = child.stderr;
103
+ if (!stdout || !stderr) {
104
+ reject(coded('CHECKPOINT_GIT_PIPE_UNAVAILABLE'));
105
+ return;
106
+ }
107
+ stdout.on('data', (chunk) => { total += chunk.length; if (total > maxBytes) {
108
+ child.kill();
109
+ reject(coded('CHECKPOINT_GIT_OUTPUT_TOO_LARGE'));
110
+ return;
111
+ } out.push(chunk); });
112
+ stderr.on('data', (chunk) => err.push(chunk));
113
+ child.on('error', reject);
114
+ if (stdin) {
115
+ child.stdin?.end(stdin);
116
+ }
117
+ child.on('close', code => { if (code !== null && allowedExitCodes.includes(code))
118
+ resolve(Buffer.concat(out));
119
+ else
120
+ reject(coded('CHECKPOINT_GIT_READ_FAILED', Buffer.concat(err).toString('utf8').trim() || `git exited ${code}`)); });
121
+ });
122
+ }
123
+ async function gitText(cwd, args, maxBytes = 64 * 1024 * 1024) { return (await gitBytes(cwd, args, maxBytes)).toString('utf8'); }
124
+ function nulList(buffer) { return buffer.toString('utf8').split('\0').filter(Boolean); }
125
+ async function gitHead(gitRoot) { try {
126
+ return (await gitText(gitRoot, ['rev-parse', '--verify', 'HEAD'], 1024)).trim() || null;
127
+ }
128
+ catch {
129
+ return null;
130
+ } }
131
+ async function gitIndexHash(gitRoot) {
132
+ let raw;
133
+ try {
134
+ raw = (await gitText(gitRoot, ['rev-parse', '--git-path', 'index'], 16 * 1024)).trim();
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ if (!raw)
140
+ return null;
141
+ const indexPath = path.isAbsolute(raw) ? raw : path.resolve(gitRoot, raw);
142
+ try {
143
+ return sha256(await readFile(indexPath));
144
+ }
145
+ catch (error) {
146
+ if (error?.code === 'ENOENT')
147
+ return null;
148
+ throw error;
149
+ }
150
+ }
151
+ async function assertExistingPathContained(workspace, absolute) {
152
+ const canonical = await realpath(absolute);
153
+ if (!inside(workspace.canonicalRoot, canonical))
154
+ throw coded('CHECKPOINT_PATH_ESCAPE', `Checkpoint path escapes workspace: ${absolute}`);
155
+ }
156
+ async function captureActualFile(workspace, absolute, relative, blobRoot, budget, indexExecutable = false) {
157
+ let stat;
158
+ try {
159
+ stat = await lstat(absolute);
160
+ }
161
+ catch (error) {
162
+ if (error?.code === 'ENOENT')
163
+ return null;
164
+ throw error;
165
+ }
166
+ consumeEntry(budget);
167
+ if (stat.isSymbolicLink()) {
168
+ let canonical;
169
+ try {
170
+ canonical = await realpath(absolute);
171
+ }
172
+ catch {
173
+ throw coded('CHECKPOINT_SYMLINK_ESCAPE', `Checkpoint symlink cannot be resolved safely: ${relative}`);
174
+ }
175
+ if (!inside(workspace.canonicalRoot, canonical))
176
+ throw coded('CHECKPOINT_SYMLINK_ESCAPE', `Checkpoint symlink escapes workspace: ${relative}`);
177
+ return { kind: 'symlink', target: await readlink(absolute) };
178
+ }
179
+ if (!stat.isFile())
180
+ throw coded('CHECKPOINT_SPECIAL_FILE_UNSUPPORTED', `Unsupported workspace entry: ${relative}`);
181
+ await assertExistingPathContained(workspace, absolute);
182
+ consumeFileBudget(budget, stat.size);
183
+ const data = await readFile(absolute);
184
+ const ref = await storeBlob(blobRoot, data);
185
+ const executable = process.platform === 'win32' ? indexExecutable : Boolean(stat.mode & 0o111);
186
+ return { kind: 'file', source: 'blob', ref, size: stat.size, mode: stat.mode & 0o777, executable };
187
+ }
188
+ async function captureGit(workspace, blobRoot, budget) {
189
+ const gitRoot = workspace.gitRoot;
190
+ const prefix = slash(path.relative(gitRoot, workspace.canonicalRoot));
191
+ const scope = prefix ? ['--', prefix] : [];
192
+ const [trackedRaw, dirtyRaw, untrackedRaw, flagsRaw, head, indexHash, conversionConfig] = await Promise.all([
193
+ gitBytes(gitRoot, ['ls-files', '-s', '-z', ...scope]),
194
+ gitBytes(gitRoot, ['diff', '--name-only', '-z', ...scope]),
195
+ gitBytes(gitRoot, ['ls-files', '--others', '--exclude-standard', '-z', ...scope]),
196
+ gitBytes(gitRoot, ['ls-files', '-v', '-z', ...scope]), gitHead(gitRoot), gitIndexHash(gitRoot),
197
+ gitBytes(gitRoot, ['config', '--null', '--get-regexp', '^(core\\.autocrlf|core\\.eol|filter\\.)'], 64 * 1024 * 1024, [0, 1]),
198
+ ]);
199
+ const trackedRows = nulList(trackedRaw);
200
+ const trackedPaths = trackedRows.map(row => { const tab = row.indexOf('\t'); if (tab < 0)
201
+ throw coded('CHECKPOINT_GIT_INDEX_INVALID'); return row.slice(tab + 1); });
202
+ const attributeInput = Buffer.from(trackedPaths.map(value => `${value}\0`).join(''), 'utf8');
203
+ const attributeState = await gitBytes(gitRoot, ['check-attr', '-a', '-z', '--stdin'], 64 * 1024 * 1024, [0], attributeInput);
204
+ const worktreeSemanticsHash = sha256(Buffer.concat([conversionConfig, Buffer.from([0]), attributeState]));
205
+ const dirty = new Set(nulList(dirtyRaw));
206
+ for (const row of nulList(flagsRaw)) {
207
+ const space = row.indexOf(' ');
208
+ if (space <= 0)
209
+ continue;
210
+ const flag = row.slice(0, space), name = row.slice(space + 1);
211
+ if (flag !== flag.toUpperCase() || flag === 'S')
212
+ dirty.add(name);
213
+ }
214
+ const entries = {};
215
+ const seen = new Set();
216
+ for (const row of trackedRows) {
217
+ const tab = row.indexOf('\t');
218
+ if (tab < 0)
219
+ throw coded('CHECKPOINT_GIT_INDEX_INVALID');
220
+ const meta = row.slice(0, tab).split(' ');
221
+ const gitPath = row.slice(tab + 1);
222
+ if (meta.length < 3 || meta[2] !== '0')
223
+ throw coded('CHECKPOINT_GIT_UNMERGED', 'Unmerged Git index state is not checkpointable safely.');
224
+ const mode = meta[0], oid = meta[1];
225
+ if (seen.has(gitPath))
226
+ throw coded('CHECKPOINT_GIT_UNMERGED');
227
+ seen.add(gitPath);
228
+ if (mode === '160000')
229
+ throw coded('CHECKPOINT_GITLINK_UNSUPPORTED', `Gitlink/submodule is outside checkpoint coverage: ${gitPath}`);
230
+ const relGit = prefix ? (gitPath === prefix ? '' : gitPath.startsWith(`${prefix}/`) ? gitPath.slice(prefix.length + 1) : null) : gitPath;
231
+ if (!relGit)
232
+ continue;
233
+ const relative = safeRelative(relGit);
234
+ const absolute = absoluteFromRelative(workspace.canonicalRoot, relative);
235
+ let stat;
236
+ try {
237
+ stat = await lstat(absolute);
238
+ }
239
+ catch (error) {
240
+ if (error?.code === 'ENOENT')
241
+ continue;
242
+ throw error;
243
+ }
244
+ const mustCapture = dirty.has(gitPath) || stat.isSymbolicLink() || !stat.isFile();
245
+ if (mustCapture) {
246
+ const actual = await captureActualFile(workspace, absolute, relative, blobRoot, budget, mode === '100755');
247
+ if (actual)
248
+ entries[relative] = actual;
249
+ continue;
250
+ }
251
+ consumeEntry(budget);
252
+ await assertExistingPathContained(workspace, absolute);
253
+ entries[relative] = { kind: 'file', source: 'git', ref: oid, size: stat.size, mode: stat.mode & 0o777, executable: mode === '100755', gitPath };
254
+ }
255
+ for (const gitPath of nulList(untrackedRaw)) {
256
+ const relGit = prefix ? (gitPath.startsWith(`${prefix}/`) ? gitPath.slice(prefix.length + 1) : null) : gitPath;
257
+ if (!relGit)
258
+ continue;
259
+ const relative = safeRelative(relGit);
260
+ if (entries[relative])
261
+ continue;
262
+ const actual = await captureActualFile(workspace, absoluteFromRelative(workspace.canonicalRoot, relative), relative, blobRoot, budget);
263
+ if (actual)
264
+ entries[relative] = actual;
265
+ }
266
+ return { capturedAt: new Date().toISOString(), workspace: { canonicalRoot: workspace.canonicalRoot, identityHash: workspace.identityHash, gitRoot }, entries, git: { root: gitRoot, head, indexHash, worktreeSemanticsHash }, coverage: { tracked: true, untrackedNonIgnored: true, ignoredUntracked: false, gitMetadataRestored: false }, exclusions: ['Untracked files ignored by Git are outside rollback coverage.', 'Empty-directory existence is outside rollback coverage.', 'Git refs, index and object database are observed for conflict evidence but are not restored.'] };
267
+ }
268
+ async function captureNonGit(workspace, blobRoot, budget) {
269
+ const entries = {};
270
+ async function preflight(dirname, relativeDir) {
271
+ const rows = await readdir(dirname, { withFileTypes: true });
272
+ rows.sort((a, b) => a.name.localeCompare(b.name));
273
+ for (const row of rows) {
274
+ const relative = safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name);
275
+ const absolute = path.join(dirname, row.name);
276
+ if (row.isDirectory()) {
277
+ await assertExistingPathContained(workspace, absolute);
278
+ await preflight(absolute, relative);
279
+ continue;
280
+ }
281
+ let stat;
282
+ try {
283
+ stat = await lstat(absolute);
284
+ }
285
+ catch (error) {
286
+ if (error?.code === 'ENOENT')
287
+ continue;
288
+ throw error;
289
+ }
290
+ consumeEntry(budget);
291
+ if (stat.isSymbolicLink()) {
292
+ let canonical;
293
+ try {
294
+ canonical = await realpath(absolute);
295
+ }
296
+ catch {
297
+ throw coded('CHECKPOINT_SYMLINK_ESCAPE', `Checkpoint symlink cannot be resolved safely: ${relative}`);
298
+ }
299
+ if (!inside(workspace.canonicalRoot, canonical))
300
+ throw coded('CHECKPOINT_SYMLINK_ESCAPE', `Checkpoint symlink escapes workspace: ${relative}`);
301
+ continue;
302
+ }
303
+ if (!stat.isFile())
304
+ throw coded('CHECKPOINT_SPECIAL_FILE_UNSUPPORTED', `Unsupported workspace entry: ${relative}`);
305
+ await assertExistingPathContained(workspace, absolute);
306
+ consumeFileBudget(budget, stat.size);
307
+ }
308
+ }
309
+ await preflight(workspace.canonicalRoot, '');
310
+ budget.entries = 0;
311
+ budget.storedBytes = 0;
312
+ async function walk(dirname, relativeDir) {
313
+ const rows = await readdir(dirname, { withFileTypes: true });
314
+ rows.sort((a, b) => a.name.localeCompare(b.name));
315
+ for (const row of rows) {
316
+ const relative = safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name);
317
+ const absolute = path.join(dirname, row.name);
318
+ if (row.isDirectory()) {
319
+ await assertExistingPathContained(workspace, absolute);
320
+ await walk(absolute, relative);
321
+ continue;
322
+ }
323
+ const actual = await captureActualFile(workspace, absolute, relative, blobRoot, budget);
324
+ if (actual)
325
+ entries[relative] = actual;
326
+ }
327
+ }
328
+ await walk(workspace.canonicalRoot, '');
329
+ return { capturedAt: new Date().toISOString(), workspace: { canonicalRoot: workspace.canonicalRoot, identityHash: workspace.identityHash, gitRoot: null }, entries, git: null, coverage: { tracked: false, untrackedNonIgnored: true, ignoredUntracked: true, gitMetadataRestored: false }, exclusions: ['Filesystem metadata other than file content, executable bit and symlink target is outside rollback coverage.', 'Empty-directory existence is outside rollback coverage.'] };
330
+ }
331
+ export async function captureWorkspaceSnapshot({ projectRoot, blobRoot, limits = CHECKPOINT_LIMITS }) {
332
+ const workspace = await resolveWorkspaceIdentity({ projectRoot });
333
+ await ensurePrivateDir(blobRoot);
334
+ const canonicalBlobRoot = await realpath(blobRoot);
335
+ if (inside(workspace.canonicalRoot, canonicalBlobRoot))
336
+ throw coded('CHECKPOINT_STORE_INSIDE_WORKSPACE');
337
+ const budget = { entries: 0, storedBytes: 0, limits };
338
+ return workspace.gitRoot ? captureGit(workspace, canonicalBlobRoot, budget) : captureNonGit(workspace, canonicalBlobRoot, budget);
339
+ }
340
+ export function checkpointMutationAllowed(mode) { return mode !== 'plan'; }
341
+ export function extractWorkspaceWriteEvidence(raw) {
342
+ if (!raw || typeof raw !== 'object')
343
+ return [];
344
+ const row = raw;
345
+ if (row.type !== 'StateDelta' || !Array.isArray(row.delta))
346
+ return [];
347
+ const out = [];
348
+ for (const item of row.delta) {
349
+ const pointer = typeof item?.path === 'string' ? item.path : '';
350
+ if (!pointer.startsWith('/file/'))
351
+ continue;
352
+ let relative;
353
+ try {
354
+ relative = safeRelative(pointer.slice('/file/'.length));
355
+ }
356
+ catch {
357
+ continue;
358
+ }
359
+ const before = item.prima === null ? null : typeof item.prima === 'string' ? sha256(item.prima) : null;
360
+ const after = typeof item.value === 'string' ? sha256(item.value) : null;
361
+ out.push({ path: relative, existedBefore: item.op === 'replace', beforeHash: before, afterHash: after });
362
+ }
363
+ return out;
364
+ }
365
+ export function sameSnapshotEntry(left, right) {
366
+ if (!left || !right)
367
+ return left === right;
368
+ if (left.kind !== right.kind)
369
+ return false;
370
+ if (left.kind === 'symlink' && right.kind === 'symlink')
371
+ return left.target === right.target;
372
+ if (left.kind === 'file' && right.kind === 'file')
373
+ return left.source === right.source && left.ref === right.ref && left.mode === right.mode && left.executable === right.executable;
374
+ return false;
375
+ }
376
+ export function sameGitSnapshot(left, right) { return left === null && right === null || Boolean(left && right && left.root === right.root && left.head === right.head && left.indexHash === right.indexHash && left.worktreeSemanticsHash === right.worktreeSemanticsHash); }
377
+ export async function materializeSnapshotFile(snapshot, entry, blobRoot, destination) {
378
+ await mkdir(path.dirname(destination), { recursive: true, mode: 0o700 });
379
+ if (entry.source === 'blob') {
380
+ const source = path.join(blobRoot, entry.ref);
381
+ const data = await readFile(source);
382
+ if (sha256(data) !== entry.ref)
383
+ throw coded('CHECKPOINT_BLOB_CORRUPT');
384
+ await copyFile(source, destination);
385
+ await durableFile(destination);
386
+ if (process.platform !== 'win32')
387
+ await chmod(destination, entry.mode);
388
+ return;
389
+ }
390
+ if (!snapshot.git || !entry.gitPath)
391
+ throw coded('CHECKPOINT_GIT_REFERENCE_INVALID');
392
+ await new Promise((resolve, reject) => {
393
+ const child = spawn('git', ['-c', 'core.quotepath=false', 'cat-file', '--filters', `--path=${entry.gitPath}`, entry.ref], { cwd: snapshot.git.root, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
394
+ const stream = createWriteStream(destination, { mode: entry.mode });
395
+ const errors = [];
396
+ let childDone = false, streamDone = false, failed = false;
397
+ const finish = () => { if (!failed && childDone && streamDone)
398
+ resolve(); };
399
+ child.stderr.on('data', (chunk) => errors.push(chunk));
400
+ child.on('error', error => { failed = true; reject(error); });
401
+ stream.on('error', error => { failed = true; reject(error); });
402
+ stream.on('close', () => { streamDone = true; finish(); });
403
+ child.stdout.pipe(stream);
404
+ child.on('close', code => { if (code !== 0) {
405
+ failed = true;
406
+ reject(coded('CHECKPOINT_GIT_OBJECT_UNAVAILABLE', Buffer.concat(errors).toString('utf8').trim()));
407
+ return;
408
+ } childDone = true; finish(); });
409
+ });
410
+ await durableFile(destination);
411
+ if (process.platform !== 'win32')
412
+ await chmod(destination, entry.mode);
413
+ }
@@ -0,0 +1,232 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmod, copyFile, mkdir, rename, rm, symlink } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { durableFile, materializeSnapshotFile, sameGitSnapshot, sameSnapshotEntry } from "./checkpoint.js";
5
+ function coded(code, message = code, details = {}) { return Object.assign(new Error(message === code ? code : `${code}: ${message}`), { code, details }); }
6
+ function stateAt(snapshot, relative) { return snapshot.entries[relative]; }
7
+ function changedPaths(left, right) { return [...new Set([...Object.keys(left.entries), ...Object.keys(right.entries)])].filter(relative => !sameSnapshotEntry(stateAt(left, relative), stateAt(right, relative))).sort(); }
8
+ function absolute(root, relative) { const candidate = path.resolve(root, ...relative.split('/')); const rel = path.relative(root, candidate); if (rel.startsWith(`..${path.sep}`) || rel === '..' || path.isAbsolute(rel))
9
+ throw coded('CHECKPOINT_PATH_INVALID'); return candidate; }
10
+ function assertNoStructuralTransition(left, right) {
11
+ const leftKeys = Object.keys(left.entries), rightKeys = Object.keys(right.entries);
12
+ for (const relative of leftKeys)
13
+ if (rightKeys.some(candidate => candidate.startsWith(`${relative}/`)))
14
+ throw coded('CHECKPOINT_STRUCTURAL_TRANSITION_UNSUPPORTED', `Cannot safely replace file/symlink ${relative} with a directory tree.`);
15
+ for (const relative of rightKeys)
16
+ if (leftKeys.some(candidate => candidate.startsWith(`${relative}/`)))
17
+ throw coded('CHECKPOINT_STRUCTURAL_TRANSITION_UNSUPPORTED', `Cannot safely replace directory tree ${relative} with a file/symlink.`);
18
+ }
19
+ function snapshotsFor(record, direction) {
20
+ if (record.state === 'open' || !record.post)
21
+ throw coded('CHECKPOINT_NOT_FINALIZED');
22
+ if (!record.reversible)
23
+ throw coded(record.nonReversibleReason ?? 'CHECKPOINT_NOT_REVERSIBLE');
24
+ if (direction === 'undo' && record.state !== 'complete')
25
+ throw coded('CHECKPOINT_STATE_INVALID');
26
+ if (direction === 'redo' && record.state !== 'undone')
27
+ throw coded('CHECKPOINT_STATE_INVALID');
28
+ const pair = direction === 'undo' ? { source: record.post, target: record.pre } : { source: record.pre, target: record.post };
29
+ assertNoStructuralTransition(pair.source, pair.target);
30
+ return pair;
31
+ }
32
+ function assertGitMetadata(current, expected) { if (!sameGitSnapshot(current.git, expected.git))
33
+ throw coded('CHECKPOINT_GIT_METADATA_CONFLICT', 'Git ref/index state changed since this checkpoint boundary.'); }
34
+ function assertCurrentMatches(current, expected, paths, code = 'CHECKPOINT_CONFLICT') {
35
+ for (const relative of paths)
36
+ if (!sameSnapshotEntry(stateAt(current, relative), stateAt(expected, relative)))
37
+ throw coded(code, `Workspace changed outside the checkpoint at ${relative}.`, { path: relative });
38
+ }
39
+ function assertCurrentMatchesEither(current, left, right, paths) {
40
+ for (const relative of paths) {
41
+ const now = stateAt(current, relative);
42
+ if (!sameSnapshotEntry(now, stateAt(left, relative)) && !sameSnapshotEntry(now, stateAt(right, relative)))
43
+ throw coded('CHECKPOINT_RECOVERY_CONFLICT', `Cannot recover interrupted restore because ${relative} changed externally.`, { path: relative });
44
+ }
45
+ }
46
+ async function stageTargets(store, snapshot, paths, token) {
47
+ const root = path.join(store.rootDir, 'restore-staging', token);
48
+ await mkdir(root, { recursive: true, mode: 0o700 });
49
+ const staged = new Map();
50
+ let index = 0;
51
+ try {
52
+ for (const relative of paths) {
53
+ const entry = stateAt(snapshot, relative);
54
+ if (!entry)
55
+ continue;
56
+ if (entry.kind === 'symlink') {
57
+ staged.set(relative, { kind: 'symlink', target: entry.target });
58
+ continue;
59
+ }
60
+ const temp = path.join(root, String(index++));
61
+ await materializeSnapshotFile(snapshot, entry, store.blobRoot, temp);
62
+ staged.set(relative, { kind: 'file', path: temp, entry });
63
+ }
64
+ return { root, staged };
65
+ }
66
+ catch (error) {
67
+ await rm(root, { recursive: true, force: true }).catch(() => { });
68
+ throw error;
69
+ }
70
+ }
71
+ async function placeTemp(temp, target) {
72
+ try {
73
+ await rename(temp, target);
74
+ }
75
+ catch (error) {
76
+ if (!['EEXIST', 'EPERM', 'ENOTEMPTY'].includes(String(error?.code)))
77
+ throw error;
78
+ await rm(target, { force: true });
79
+ await rename(temp, target);
80
+ }
81
+ }
82
+ async function applyStaged(snapshot, paths, staged, token) {
83
+ let index = 0;
84
+ for (const relative of paths) {
85
+ const target = absolute(snapshot.workspace.canonicalRoot, relative), entry = stateAt(snapshot, relative);
86
+ if (!entry) {
87
+ await rm(target, { force: true });
88
+ continue;
89
+ }
90
+ await mkdir(path.dirname(target), { recursive: true });
91
+ const temp = path.join(path.dirname(target), `.${path.basename(target)}.talos-checkpoint-${token}-${index++}.tmp`);
92
+ await rm(temp, { force: true });
93
+ const prepared = staged.get(relative);
94
+ if (!prepared)
95
+ throw coded('CHECKPOINT_RESTORE_STAGE_MISSING');
96
+ if (prepared.kind === 'symlink')
97
+ await symlink(prepared.target, temp);
98
+ else {
99
+ await copyFile(prepared.path, temp);
100
+ if (process.platform !== 'win32')
101
+ await chmod(temp, prepared.entry.mode);
102
+ await durableFile(temp);
103
+ }
104
+ await placeTemp(temp, target);
105
+ }
106
+ }
107
+ async function applySnapshot(store, snapshot, paths) { const token = randomUUID(), bundle = await stageTargets(store, snapshot, paths, token); try {
108
+ await applyStaged(snapshot, paths, bundle.staged, token);
109
+ }
110
+ finally {
111
+ await rm(bundle.root, { recursive: true, force: true }).catch(() => { });
112
+ } }
113
+ async function recoverPendingRestore(store) {
114
+ const tx = await store.readRestoreTransaction();
115
+ if (!tx)
116
+ return;
117
+ const record = await store.show(tx.checkpointId);
118
+ if (record.state === tx.expectedFinalState) {
119
+ await store.clearRestoreTransaction();
120
+ return;
121
+ }
122
+ if (!record.post)
123
+ throw coded('CHECKPOINT_RECOVERY_INVALID');
124
+ const source = tx.direction === 'undo' ? record.post : record.pre, target = tx.direction === 'undo' ? record.pre : record.post;
125
+ assertNoStructuralTransition(source, target);
126
+ const current = await store.capture();
127
+ assertGitMetadata(current, source);
128
+ assertCurrentMatchesEither(current, source, target, tx.paths);
129
+ await applySnapshot(store, source, tx.paths);
130
+ await store.clearRestoreTransaction();
131
+ }
132
+ async function restore({ store, id, direction }) {
133
+ return store.withRestoreLock(async () => {
134
+ await recoverPendingRestore(store);
135
+ const requested = id ? await store.show(id) : await store.latest(direction === 'undo' ? 'complete' : 'undone');
136
+ if (!requested)
137
+ throw coded('CHECKPOINT_NOT_FOUND');
138
+ const { source, target } = snapshotsFor(requested, direction);
139
+ const paths = changedPaths(source, target);
140
+ const current = await store.capture();
141
+ assertGitMetadata(current, source);
142
+ assertCurrentMatches(current, source, paths);
143
+ const token = randomUUID(), bundle = await stageTargets(store, target, paths, token);
144
+ try {
145
+ const currentAgain = await store.capture();
146
+ assertGitMetadata(currentAgain, source);
147
+ assertCurrentMatches(currentAgain, source, paths);
148
+ const expectedFinalState = direction === 'undo' ? 'undone' : 'complete';
149
+ const tx = { schema: 'talos.cli.checkpoint-restore.v1', checkpointId: requested.id, direction, expectedFinalState, paths, createdAt: new Date().toISOString() };
150
+ await store.writeRestoreTransaction(tx);
151
+ try {
152
+ await applyStaged(target, paths, bundle.staged, token);
153
+ }
154
+ catch (error) {
155
+ try {
156
+ await applySnapshot(store, source, paths);
157
+ await store.clearRestoreTransaction();
158
+ }
159
+ catch (rollback) {
160
+ throw coded('CHECKPOINT_RESTORE_ROLLBACK_FAILED', rollback instanceof Error ? rollback.message : String(rollback));
161
+ }
162
+ throw error;
163
+ }
164
+ const at = new Date().toISOString();
165
+ const updated = { ...requested, state: expectedFinalState, transitions: [...requested.transitions, { kind: direction, at }] };
166
+ await store.replace(updated);
167
+ await store.clearRestoreTransaction();
168
+ return updated;
169
+ }
170
+ finally {
171
+ await rm(bundle.root, { recursive: true, force: true }).catch(() => { });
172
+ }
173
+ });
174
+ }
175
+ export async function undoCheckpoint({ store, id }) { return restore({ store, ...(id ? { id } : {}), direction: 'undo' }); }
176
+ export async function redoCheckpoint({ store, id }) { return restore({ store, ...(id ? { id } : {}), direction: 'redo' }); }
177
+ /*
178
+ * R5b D3 (owner decision Q-R5-13 and the owner's answers of 2026-09-25): the /undo picker goes BACK TO A POINT — the chosen
179
+ * checkpoint and every newer one of the same session are undone, newest first (Claude Code's /rewind, Hermes's /rollback) —
180
+ * and re-applies an undone point: it and every older undone one of the session, oldest first. Every step is the checked,
181
+ * locked restore above; a step that fails puts back the steps already taken (in reverse), so the files never stay between two
182
+ * points, and the step's own error (the file that changed outside TALOS) is what the person reads. The whole range is checked
183
+ * first: a checkpoint that cannot be restored refuses the step before any file is touched.
184
+ */
185
+ function assertRestorable(rows) {
186
+ for (const row of rows) {
187
+ if (row.state === 'open' || !row.post)
188
+ throw coded('CHECKPOINT_NOT_FINALIZED');
189
+ if (!row.reversible)
190
+ throw coded(row.nonReversibleReason ?? 'CHECKPOINT_NOT_REVERSIBLE');
191
+ }
192
+ }
193
+ async function stepThrough(steps, forward, back) {
194
+ const done = [];
195
+ for (const step of steps) {
196
+ try {
197
+ done.push(await forward(step.id));
198
+ }
199
+ catch (error) {
200
+ for (const taken of [...done].reverse()) {
201
+ try {
202
+ await back(taken.id);
203
+ }
204
+ catch (rollback) {
205
+ throw coded('CHECKPOINT_RESTORE_ROLLBACK_FAILED', rollback instanceof Error ? rollback.message : String(rollback), { checkpointId: taken.id });
206
+ }
207
+ }
208
+ throw error;
209
+ }
210
+ }
211
+ return done;
212
+ }
213
+ export async function rewindToCheckpoint({ store, id }) {
214
+ const target = await store.show(id);
215
+ if (target.state !== 'complete')
216
+ throw coded('CHECKPOINT_STATE_INVALID');
217
+ const session = (await store.list()).filter(row => row.sessionId === target.sessionId);
218
+ const at = session.findIndex(row => row.id === target.id);
219
+ const range = session.slice(0, at + 1).filter(row => row.state !== 'undone');
220
+ assertRestorable(range);
221
+ return stepThrough(range, step => undoCheckpoint({ store, id: step }), step => redoCheckpoint({ store, id: step }));
222
+ }
223
+ export async function reapplyToCheckpoint({ store, id }) {
224
+ const target = await store.show(id);
225
+ if (target.state !== 'undone')
226
+ throw coded('CHECKPOINT_STATE_INVALID');
227
+ const session = (await store.list()).filter(row => row.sessionId === target.sessionId);
228
+ const at = session.findIndex(row => row.id === target.id);
229
+ const range = session.slice(at).filter(row => row.state === 'undone').reverse();
230
+ assertRestorable(range);
231
+ return stepThrough(range, step => redoCheckpoint({ store, id: step }), step => undoCheckpoint({ store, id: step }));
232
+ }