talos-code 0.0.1 → 0.2.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 (407) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +661 -0
  3. package/README.md +116 -3
  4. package/THIRD_PARTY_NOTICES.md +40 -0
  5. package/dist/archive/zip.js +73 -0
  6. package/dist/args.js +100 -0
  7. package/dist/automations/history-store.js +34 -0
  8. package/dist/automations/policy-store.js +43 -0
  9. package/dist/automations/runner.js +107 -0
  10. package/dist/automations/schedule.js +85 -0
  11. package/dist/automations/windows-task.js +53 -0
  12. package/dist/commands/advanced-cli.js +233 -0
  13. package/dist/commands/checkpoint-cli.js +45 -0
  14. package/dist/commands/config-cli.js +107 -0
  15. package/dist/commands/context.js +17 -0
  16. package/dist/commands/extensions-cli.js +207 -0
  17. package/dist/commands/project-cli.js +74 -0
  18. package/dist/commands/project-commands.js +143 -0
  19. package/dist/commands/provider-cli.js +244 -0
  20. package/dist/commands/services-cli.js +248 -0
  21. package/dist/commands/session-cli.js +171 -0
  22. package/dist/commands/system-cli.js +405 -0
  23. package/dist/config/commands.js +60 -0
  24. package/dist/config/load.js +382 -0
  25. package/dist/config/migrations.js +48 -0
  26. package/dist/config/types.js +11 -0
  27. package/dist/diagnostics/development-log.js +154 -0
  28. package/dist/diagnostics/doctor.js +104 -0
  29. package/dist/diagnostics/redact.js +80 -0
  30. package/dist/diagnostics/zip.js +52 -0
  31. package/dist/errors.js +156 -0
  32. package/dist/events/bridge.js +130 -0
  33. package/dist/extensions/installer.js +178 -0
  34. package/dist/extensions/package-schema.js +24 -0
  35. package/dist/headless/result.js +84 -0
  36. package/dist/headless/run.js +230 -0
  37. package/dist/i18n/en/approval.js +46 -0
  38. package/dist/i18n/en/common.js +9 -0
  39. package/dist/i18n/en/credentials.js +92 -0
  40. package/dist/i18n/en/errors.js +267 -0
  41. package/dist/i18n/en/firstrun.js +101 -0
  42. package/dist/i18n/en/providers.js +37 -0
  43. package/dist/i18n/en/screen.js +774 -0
  44. package/dist/i18n/en/tools.js +127 -0
  45. package/dist/i18n/en.js +14 -0
  46. package/dist/i18n/error-view.js +307 -0
  47. package/dist/i18n/index.js +19 -0
  48. package/dist/i18n/kernel-map.js +139 -0
  49. package/dist/io.js +45 -0
  50. package/dist/main.js +331 -0
  51. package/dist/paths.js +41 -0
  52. package/dist/protocol/v2/codec.js +9 -0
  53. package/dist/protocol/v2/events.js +555 -0
  54. package/dist/protocol/v2/index.js +4 -0
  55. package/dist/protocol/v2/replay.js +28 -0
  56. package/dist/protocol/v2/types.js +1 -0
  57. package/dist/provider/control-plane.js +135 -0
  58. package/dist/provider/environment-keys.js +275 -0
  59. package/dist/provider/health.js +110 -0
  60. package/dist/provider/missing-key.js +48 -0
  61. package/dist/provider/model-catalog.js +168 -0
  62. package/dist/provider/openrouter-login.js +172 -0
  63. package/dist/provider/provider-text.js +13 -0
  64. package/dist/provider/store.js +95 -0
  65. package/dist/provider/system-keyring.js +214 -0
  66. package/dist/runtime/active-run.js +63 -0
  67. package/dist/runtime/agent-tree.js +74 -0
  68. package/dist/runtime/attachments.js +84 -0
  69. package/dist/runtime/brokered-executor.js +395 -0
  70. package/dist/runtime/context-archive.js +69 -0
  71. package/dist/runtime/context-status.js +76 -0
  72. package/dist/runtime/create-runtime.js +111 -0
  73. package/dist/runtime/model-profile.js +361 -0
  74. package/dist/runtime/output-store.js +137 -0
  75. package/dist/runtime/provider-attempts.js +185 -0
  76. package/dist/runtime/replay-buffer.js +153 -0
  77. package/dist/runtime/repo.js +95 -0
  78. package/dist/runtime/session-facade.js +135 -0
  79. package/dist/runtime/session-summary.js +125 -0
  80. package/dist/runtime/supervisor.js +265 -0
  81. package/dist/runtime/talos-composition.js +1011 -0
  82. package/dist/runtime/types.js +5 -0
  83. package/dist/runtime/usage-snapshot.js +82 -0
  84. package/dist/security/auto-classifier.js +38 -0
  85. package/dist/security/credential-free-environment.js +54 -0
  86. package/dist/security/evaluate.js +243 -0
  87. package/dist/security/execution-backends.js +236 -0
  88. package/dist/security/execution-broker.js +102 -0
  89. package/dist/security/forge-scan.js +74 -0
  90. package/dist/security/from-approval.js +39 -0
  91. package/dist/security/mxc-execution-backend.js +281 -0
  92. package/dist/security/permission-engine.js +138 -0
  93. package/dist/security/permission-explanation.js +54 -0
  94. package/dist/security/persist.js +179 -0
  95. package/dist/security/plugin-guard.js +230 -0
  96. package/dist/security/process-tree-evidence-store.js +74 -0
  97. package/dist/security/process-tree-probe.js +554 -0
  98. package/dist/security/project-resource-inventory.js +186 -0
  99. package/dist/security/project-trust-gate.js +38 -0
  100. package/dist/security/project-trust.js +367 -0
  101. package/dist/security/rule-parser.js +201 -0
  102. package/dist/security/shell-segmentation.js +68 -0
  103. package/dist/security/trust-authority.js +324 -0
  104. package/dist/security/types.js +1 -0
  105. package/dist/security/workspace-identity.js +102 -0
  106. package/dist/services/automation-facade.js +59 -0
  107. package/dist/services/forge-facade.js +77 -0
  108. package/dist/services/hook-facade.js +240 -0
  109. package/dist/services/index.js +16 -0
  110. package/dist/services/library-facade.js +174 -0
  111. package/dist/services/mcp-facade.js +348 -0
  112. package/dist/services/memory-facade.js +126 -0
  113. package/dist/services/notes-facade.js +157 -0
  114. package/dist/services/plugin-facade.js +308 -0
  115. package/dist/services/research-facade.js +110 -0
  116. package/dist/services/task-board-facade.js +236 -0
  117. package/dist/services/workflow-catalog.js +246 -0
  118. package/dist/sessions/export-format.js +98 -0
  119. package/dist/sessions/metadata-store.js +160 -0
  120. package/dist/sessions/share.js +122 -0
  121. package/dist/sessions/transfer.js +16 -0
  122. package/dist/subcommands.js +62 -0
  123. package/dist/tui/agent-roster.js +36 -0
  124. package/dist/tui/agent-view.js +51 -0
  125. package/dist/tui/app.js +4098 -0
  126. package/dist/tui/approval.js +36 -0
  127. package/dist/tui/boot/boot-sequence.js +76 -0
  128. package/dist/tui/boot/cinematic.js +243 -0
  129. package/dist/tui/boot/desktop-logo.js +82 -0
  130. package/dist/tui/boot.js +3 -0
  131. package/dist/tui/busy-input.js +58 -0
  132. package/dist/tui/catalog-service.js +560 -0
  133. package/dist/tui/components/assistant-stream.js +1 -0
  134. package/dist/tui/components/command-menu.js +68 -0
  135. package/dist/tui/components/composer.js +219 -0
  136. package/dist/tui/components/diff.js +35 -0
  137. package/dist/tui/components/footer.js +48 -0
  138. package/dist/tui/components/header.js +6 -0
  139. package/dist/tui/components/markdown.js +305 -0
  140. package/dist/tui/components/status-indicator.js +38 -0
  141. package/dist/tui/components/terminal-shell.js +214 -0
  142. package/dist/tui/components/thinking-row.js +19 -0
  143. package/dist/tui/components/tool-row.js +97 -0
  144. package/dist/tui/components/transcript-virtualizer.js +165 -0
  145. package/dist/tui/components/transcript.js +43 -0
  146. package/dist/tui/descendant-approvals.js +104 -0
  147. package/dist/tui/diff-model.js +77 -0
  148. package/dist/tui/editor-history.js +21 -0
  149. package/dist/tui/editor.js +210 -0
  150. package/dist/tui/event-adapter.js +436 -0
  151. package/dist/tui/exit-output.js +58 -0
  152. package/dist/tui/external-editor.js +53 -0
  153. package/dist/tui/file-completion.js +89 -0
  154. package/dist/tui/focus-manager.js +5 -0
  155. package/dist/tui/highlight.js +30 -0
  156. package/dist/tui/input-router.js +18 -0
  157. package/dist/tui/interrupt.js +99 -0
  158. package/dist/tui/keybindings.js +194 -0
  159. package/dist/tui/keymap-resolver.js +91 -0
  160. package/dist/tui/launch-state.js +148 -0
  161. package/dist/tui/line-diff.js +57 -0
  162. package/dist/tui/live-activity.js +56 -0
  163. package/dist/tui/metrics.js +123 -0
  164. package/dist/tui/onboarding.js +36 -0
  165. package/dist/tui/overlays/agent-tree.js +43 -0
  166. package/dist/tui/overlays/approval-dialog.js +642 -0
  167. package/dist/tui/overlays/automation-center.js +51 -0
  168. package/dist/tui/overlays/checkpoint-picker.js +47 -0
  169. package/dist/tui/overlays/context-inspector.js +36 -0
  170. package/dist/tui/overlays/effort-line.js +110 -0
  171. package/dist/tui/overlays/forge-center.js +46 -0
  172. package/dist/tui/overlays/help-dialog.js +16 -0
  173. package/dist/tui/overlays/history-picker.js +37 -0
  174. package/dist/tui/overlays/hook-center.js +70 -0
  175. package/dist/tui/overlays/library-center.js +55 -0
  176. package/dist/tui/overlays/mcp-center.js +60 -0
  177. package/dist/tui/overlays/memory-center.js +63 -0
  178. package/dist/tui/overlays/model-picker.js +77 -0
  179. package/dist/tui/overlays/notes-center.js +53 -0
  180. package/dist/tui/overlays/overlay-host.js +8 -0
  181. package/dist/tui/overlays/plugin-center.js +76 -0
  182. package/dist/tui/overlays/provider-picker.js +171 -0
  183. package/dist/tui/overlays/queue-editor.js +32 -0
  184. package/dist/tui/overlays/research-center.js +59 -0
  185. package/dist/tui/overlays/scroll-window.js +234 -0
  186. package/dist/tui/overlays/session-picker.js +115 -0
  187. package/dist/tui/overlays/tasks-center.js +83 -0
  188. package/dist/tui/overlays/theme-picker.js +21 -0
  189. package/dist/tui/overlays/transcript-search.js +58 -0
  190. package/dist/tui/overlays/trust-center.js +29 -0
  191. package/dist/tui/overlays/workflow-center.js +46 -0
  192. package/dist/tui/project-file-index.js +214 -0
  193. package/dist/tui/project-references.js +85 -0
  194. package/dist/tui/project-trust-prompt.js +289 -0
  195. package/dist/tui/prompt-history-store.js +116 -0
  196. package/dist/tui/queue-store.js +110 -0
  197. package/dist/tui/regions/budget.js +59 -0
  198. package/dist/tui/regions/views.js +96 -0
  199. package/dist/tui/render-coordinator.js +148 -0
  200. package/dist/tui/render-scheduler.js +4 -0
  201. package/dist/tui/run.js +69 -0
  202. package/dist/tui/selection-list.js +29 -0
  203. package/dist/tui/session-controller.js +791 -0
  204. package/dist/tui/session-export.js +54 -0
  205. package/dist/tui/setup-wizard.js +46 -0
  206. package/dist/tui/shell-input.js +35 -0
  207. package/dist/tui/shell-layout.js +48 -0
  208. package/dist/tui/shell-model.js +98 -0
  209. package/dist/tui/slash-commands.js +82 -0
  210. package/dist/tui/state.js +151 -0
  211. package/dist/tui/status-bar.js +125 -0
  212. package/dist/tui/status-view.js +40 -0
  213. package/dist/tui/terminal-capabilities.js +9 -0
  214. package/dist/tui/terminal-session.js +11 -0
  215. package/dist/tui/text-width.js +66 -0
  216. package/dist/tui/theme-catalog.js +27 -0
  217. package/dist/tui/theme-store.js +23 -0
  218. package/dist/tui/theme.js +23 -0
  219. package/dist/tui/tool-display.js +135 -0
  220. package/dist/tui/tool-facts.js +1 -0
  221. package/dist/tui/tools/bash-renderer.js +32 -0
  222. package/dist/tui/tools/change.js +61 -0
  223. package/dist/tui/tools/edit-renderer.js +17 -0
  224. package/dist/tui/tools/generic-renderer.js +13 -0
  225. package/dist/tui/tools/list-renderer.js +14 -0
  226. package/dist/tui/tools/read-renderer.js +13 -0
  227. package/dist/tui/tools/registry.js +20 -0
  228. package/dist/tui/tools/row-format.js +187 -0
  229. package/dist/tui/tools/search-renderer.js +38 -0
  230. package/dist/tui/tools/shared.js +97 -0
  231. package/dist/tui/tools/write-renderer.js +15 -0
  232. package/dist/tui/transcript-model.js +441 -0
  233. package/dist/tui/ui-preferences.js +79 -0
  234. package/dist/tui/usage-view.js +77 -0
  235. package/dist/tui/vim-mode.js +99 -0
  236. package/dist/update/check.js +15 -0
  237. package/dist/update/npm.js +51 -0
  238. package/dist/update/run.js +129 -0
  239. package/dist/version.js +2 -0
  240. package/dist/workspace/checkpoint-store.js +210 -0
  241. package/dist/workspace/checkpoint.js +413 -0
  242. package/dist/workspace/restore.js +232 -0
  243. package/package.json +64 -5
  244. package/vendor/context-engine/package.json +11 -0
  245. package/vendor/context-engine/src/compaction-planner.mjs +57 -0
  246. package/vendor/context-engine/src/contracts.mjs +48 -0
  247. package/vendor/context-engine/src/engine.mjs +445 -0
  248. package/vendor/context-engine/src/node/context-export.mjs +164 -0
  249. package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
  250. package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
  251. package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
  252. package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
  253. package/vendor/context-engine/src/profiles.mjs +10 -0
  254. package/vendor/context-engine/src/retrieval.mjs +104 -0
  255. package/vendor/context-engine/src/summary.mjs +97 -0
  256. package/vendor/context-engine/src/usage.mjs +34 -0
  257. package/vendor/harness-ui/package.json +38 -0
  258. package/vendor/harness-ui/src/acp-agent.mjs +350 -0
  259. package/vendor/harness-ui/src/agent-service.mjs +2188 -0
  260. package/vendor/harness-ui/src/agui-events.mjs +452 -0
  261. package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
  262. package/vendor/harness-ui/src/artifact-store.mjs +39 -0
  263. package/vendor/harness-ui/src/assistenza.mjs +123 -0
  264. package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
  265. package/vendor/harness-ui/src/automation-store.mjs +145 -0
  266. package/vendor/harness-ui/src/browser-annota.mjs +639 -0
  267. package/vendor/harness-ui/src/browser-frame.mjs +211 -0
  268. package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
  269. package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
  270. package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
  271. package/vendor/harness-ui/src/browser-stream.mjs +445 -0
  272. package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
  273. package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
  274. package/vendor/harness-ui/src/config.mjs +697 -0
  275. package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
  276. package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
  277. package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
  278. package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
  279. package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
  280. package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
  281. package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
  282. package/vendor/harness-ui/src/context-runtime.mjs +118 -0
  283. package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
  284. package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
  285. package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
  286. package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
  287. package/vendor/harness-ui/src/custom-task.mjs +171 -0
  288. package/vendor/harness-ui/src/doctor.mjs +142 -0
  289. package/vendor/harness-ui/src/document-filename.mjs +97 -0
  290. package/vendor/harness-ui/src/document-generator.mjs +493 -0
  291. package/vendor/harness-ui/src/document-report.mjs +331 -0
  292. package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
  293. package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
  294. package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
  295. package/vendor/harness-ui/src/forge-contract.mjs +221 -0
  296. package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
  297. package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
  298. package/vendor/harness-ui/src/generation-idle.mjs +332 -0
  299. package/vendor/harness-ui/src/gguf-header.mjs +207 -0
  300. package/vendor/harness-ui/src/git-service.mjs +626 -0
  301. package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
  302. package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
  303. package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
  304. package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
  305. package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
  306. package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
  307. package/vendor/harness-ui/src/hook-registry.mjs +186 -0
  308. package/vendor/harness-ui/src/http-app.mjs +6259 -0
  309. package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
  310. package/vendor/harness-ui/src/id-archivio.mjs +27 -0
  311. package/vendor/harness-ui/src/image-generator.mjs +143 -0
  312. package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
  313. package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
  314. package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
  315. package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
  316. package/vendor/harness-ui/src/library-store.mjs +652 -0
  317. package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
  318. package/vendor/harness-ui/src/local-model-store.mjs +339 -0
  319. package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
  320. package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
  321. package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
  322. package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
  323. package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
  324. package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
  325. package/vendor/harness-ui/src/mcp-client.mjs +98 -0
  326. package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
  327. package/vendor/harness-ui/src/mcp-session.mjs +177 -0
  328. package/vendor/harness-ui/src/memory-store.mjs +227 -0
  329. package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
  330. package/vendor/harness-ui/src/model-catalog.mjs +129 -0
  331. package/vendor/harness-ui/src/model-destination.mjs +189 -0
  332. package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
  333. package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
  334. package/vendor/harness-ui/src/notes-store.mjs +250 -0
  335. package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
  336. package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
  337. package/vendor/harness-ui/src/path-policy.mjs +442 -0
  338. package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
  339. package/vendor/harness-ui/src/plugin-session.mjs +180 -0
  340. package/vendor/harness-ui/src/process-policy.mjs +345 -0
  341. package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
  342. package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
  343. package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
  344. package/vendor/harness-ui/src/provider-probe.mjs +582 -0
  345. package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
  346. package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
  347. package/vendor/harness-ui/src/public-problem.mjs +109 -0
  348. package/vendor/harness-ui/src/research/card.mjs +235 -0
  349. package/vendor/harness-ui/src/research/citations.mjs +142 -0
  350. package/vendor/harness-ui/src/research/collector.mjs +275 -0
  351. package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
  352. package/vendor/harness-ui/src/research/dossier.mjs +114 -0
  353. package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
  354. package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
  355. package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
  356. package/vendor/harness-ui/src/research/independence.mjs +159 -0
  357. package/vendor/harness-ui/src/research/ledger.mjs +166 -0
  358. package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
  359. package/vendor/harness-ui/src/research/narration.mjs +181 -0
  360. package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
  361. package/vendor/harness-ui/src/research/opposing.mjs +305 -0
  362. package/vendor/harness-ui/src/research/outline.mjs +111 -0
  363. package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
  364. package/vendor/harness-ui/src/research/pdf.mjs +291 -0
  365. package/vendor/harness-ui/src/research/plan.mjs +301 -0
  366. package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
  367. package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
  368. package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
  369. package/vendor/harness-ui/src/research/recheck.mjs +194 -0
  370. package/vendor/harness-ui/src/research/report.mjs +203 -0
  371. package/vendor/harness-ui/src/research/run.mjs +527 -0
  372. package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
  373. package/vendor/harness-ui/src/research/verification.mjs +572 -0
  374. package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
  375. package/vendor/harness-ui/src/research-store.mjs +1133 -0
  376. package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
  377. package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
  378. package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
  379. package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
  380. package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
  381. package/vendor/harness-ui/src/search-source-store.mjs +172 -0
  382. package/vendor/harness-ui/src/session-registry.mjs +6095 -0
  383. package/vendor/harness-ui/src/session-store.mjs +220 -0
  384. package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
  385. package/vendor/harness-ui/src/setup-stato.mjs +31 -0
  386. package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
  387. package/vendor/harness-ui/src/skill-registry.mjs +120 -0
  388. package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
  389. package/vendor/harness-ui/src/static-files.mjs +96 -0
  390. package/vendor/harness-ui/src/stream-partition.mjs +123 -0
  391. package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
  392. package/vendor/harness-ui/src/task-catalog.mjs +65 -0
  393. package/vendor/harness-ui/src/tasks-store.mjs +220 -0
  394. package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
  395. package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
  396. package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
  397. package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
  398. package/vendor/harness-ui/src/usage-cache.mjs +315 -0
  399. package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
  400. package/vendor/harness-ui/src/workspace-context.mjs +124 -0
  401. package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
  402. package/vendor/harness-ui/src/workspace-files.mjs +589 -0
  403. package/vendor/harness-ui/src/workspace-info.mjs +189 -0
  404. package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
  405. package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
  406. package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
  407. package/vendor/manifest.json +170 -0
@@ -0,0 +1,281 @@
1
+ /*
2
+ * ⭐⭐⭐ R0 lane S — THE CONTAINER'S ENVIRONMENT IS ONE ALLOWLIST, FOR THE PROBE AND FOR EVERY COMMAND.
3
+ *
4
+ * Measured 2026-09-23 on this host (Windows 11 26200.9457, Node 24.18.0, SDK 0.8.0, the policy below):
5
+ * · SDK 0.8.0 uses a supplied environment VERBATIM (microsoft/mxc docs/schema.md, 2026-09-21) and serialises it into
6
+ * `--config-base64` on the `wxc-exec.exe` command line, which process auditing records (B1 slice 19). So it is an
7
+ * allowlist of system paths: never `process.env`, never a denylist of it.
8
+ * · Without LOCALAPPDATA every sandboxed process dies before it starts: `CreateProcessW(PROC_THREAD_ATTRIBUTE_SECURITY_
9
+ * ENVIRONMENT) failed: WIN32_ERROR(203)` (microsoft/mxc#1130, closed 2026-09-14, "SystemRoot + LOCALAPPDATA").
10
+ * Without SystemRoot node, npm and powershell fail; without PATH npm, git, powershell and pwsh are not found
11
+ * (microsoft/mxc#1102: no PATH floor). Without APPDATA npm's prefix becomes `<cwd>\${APPDATA}\npm`; without
12
+ * USERPROFILE git finds no global configuration. windir, SystemDrive, TEMP, TMP, PATHEXT and ComSpec are not needed
13
+ * by those six workloads but are the gate E1 floor (Gemini CLI's win32 list; the container overrides TEMP with its
14
+ * own AppContainer folder). NUMBER_OF_PROCESSORS and PROCESSOR_ARCHITECTURE stay from B1 slice 19.
15
+ * · A command given NO environment gets the backend default, which is the user's profile block: 56 variables here,
16
+ * three of them secret-shaped names from the persistent user environment. So a command always gets this list.
17
+ * ⛔ A name is added only when a measurement in the real container shows a workload needs it, and only if neither its
18
+ * name nor its value can hold a credential.
19
+ */
20
+ export const MXC_CHILD_ENVIRONMENT_NAMES = Object.freeze(['SystemRoot', 'windir', 'SystemDrive', 'LOCALAPPDATA', 'APPDATA', 'USERPROFILE', 'TEMP', 'TMP', 'PATH', 'PATHEXT', 'ComSpec', 'NUMBER_OF_PROCESSORS', 'PROCESSOR_ARCHITECTURE']);
21
+ function lookup(source, name) {
22
+ if (source[name] !== undefined)
23
+ return source[name];
24
+ const upper = name.toUpperCase();
25
+ for (const [key, value] of Object.entries(source))
26
+ if (key.toUpperCase() === upper && value !== undefined)
27
+ return value;
28
+ return undefined;
29
+ }
30
+ function systemRootOf(source) { return lookup(source, 'SystemRoot') ?? lookup(source, 'windir') ?? 'C:\\Windows'; }
31
+ /**
32
+ * The host PATH with the Windows system folders first. ⛔ A relative entry (`.`, `bin`) resolves against whatever the
33
+ * working directory is — a planting vector — and is dropped, as are empty entries and duplicates (case-insensitive,
34
+ * trailing separator ignored). Git's `usr/bin` before System32 is what made the SDK's `whoami` print GNU usage.
35
+ */
36
+ export function systemFirstPath(source = process.env) {
37
+ const root = systemRootOf(source).replace(/[\\/]+$/u, '');
38
+ const front = [`${root}\\System32`, root, `${root}\\System32\\Wbem`, `${root}\\System32\\WindowsPowerShell\\v1.0\\`];
39
+ const seen = new Set();
40
+ return [...front, ...String(lookup(source, 'PATH') ?? '').split(';')].filter(entry => {
41
+ if (!entry || !/^(?:[A-Za-z]:[\\/]|\\\\)/u.test(entry))
42
+ return false;
43
+ const key = entry.replace(/[\\/]+$/u, '').toLowerCase();
44
+ if (seen.has(key))
45
+ return false;
46
+ seen.add(key);
47
+ return true;
48
+ }).join(';');
49
+ }
50
+ /** Only the allowlisted names this host defines, PATH always (System32 at least). A fresh object, never `process.env`. */
51
+ export function mxcChildEnvironment(source = process.env) {
52
+ const environment = {};
53
+ for (const name of MXC_CHILD_ENVIRONMENT_NAMES) {
54
+ if (name === 'PATH') {
55
+ environment.PATH = systemFirstPath(source);
56
+ continue;
57
+ }
58
+ const value = lookup(source, name);
59
+ if (value !== undefined)
60
+ environment[name] = value;
61
+ }
62
+ return environment;
63
+ }
64
+ /*
65
+ * R0 lane S, decision 4 (owner, 2026-09-23; gate E2): Win32k is allowed. `allowWindows:false` becomes
66
+ * `DisallowWin32kSystemCalls`, and measured here node.exe and powershell.exe then exit 0xC0000142
67
+ * (STATUS_DLL_INIT_FAILED) before their first instruction; Codex keeps `ui.disable:false` because "PowerShell needs
68
+ * Win32k and desktop handles during DLL startup" (codex-rs/mxc-sandbox policy.rs, 2026-09-22). Clipboard and input
69
+ * injection stay blocked; filesystem and network grants are unchanged.
70
+ */
71
+ export const MXC_UI_POLICY = Object.freeze({ allowWindows: true, clipboard: 'none', allowInputInjection: false });
72
+ /*
73
+ * ⭐⭐ R0 lane S — S4, the SDK's import-time `whoami` (review E-08, gate E5).
74
+ * `@microsoft/mxc-sdk@0.8.0` `dist/diagnostic.js` runs `execSync('whoami /user /fo csv /nh')` WHEN THE MODULE LOADS,
75
+ * through cmd, the inherited PATH and the current directory, with stderr inherited. Measured: with Git's `usr/bin`
76
+ * before System32 every load printed `whoami: extra operand '/user'` on TALOS's stderr; with a `whoami.bat` in the
77
+ * working directory and NoDefaultCurrentDirectoryInExePath unset (the Windows default) that batch file RAN on the
78
+ * host. So the SDK is loaded once per process, only when a sandbox is asked for, with PATH System32-first and the
79
+ * current-directory search off for the length of the import; both are put back exactly afterwards, and only if nobody
80
+ * else changed them meanwhile.
81
+ */
82
+ let sdkModule;
83
+ async function importSdkWithSystemSearch() {
84
+ if (process.platform !== 'win32')
85
+ return import('@microsoft/mxc-sdk');
86
+ const FLAG = 'NoDefaultCurrentDirectoryInExePath';
87
+ const previousPath = process.env.PATH;
88
+ const previousFlag = process.env[FLAG];
89
+ const guardedPath = systemFirstPath(process.env);
90
+ process.env.PATH = guardedPath;
91
+ process.env[FLAG] = '1';
92
+ try {
93
+ return await import('@microsoft/mxc-sdk');
94
+ }
95
+ finally {
96
+ if (process.env.PATH === guardedPath) {
97
+ if (previousPath === undefined)
98
+ delete process.env.PATH;
99
+ else
100
+ process.env.PATH = previousPath;
101
+ }
102
+ if (process.env[FLAG] === '1') {
103
+ if (previousFlag === undefined)
104
+ delete process.env[FLAG];
105
+ else
106
+ process.env[FLAG] = previousFlag;
107
+ }
108
+ }
109
+ }
110
+ export async function loadMxcSdk() {
111
+ const sdk = await (sdkModule ??= importSdkWithSystemSearch().catch(error => { sdkModule = undefined; throw error; }));
112
+ return {
113
+ getPlatformSupport: () => sdk.getPlatformSupport(),
114
+ createConfigFromPolicy: (policy, containment, name) => sdk.createConfigFromPolicy(policy, containment, name),
115
+ spawnSandboxFromConfig: (config, options, workingDirectory, environment) => sdk.spawnSandboxFromConfig(config, options, workingDirectory, environment)
116
+ };
117
+ }
118
+ function quoteWindowsArgument(value) {
119
+ if (value.length > 0 && !/[\s"]/u.test(value))
120
+ return value;
121
+ let quoted = '"';
122
+ let slashes = 0;
123
+ for (const character of value) {
124
+ if (character === '\\') {
125
+ slashes++;
126
+ continue;
127
+ }
128
+ if (character === '"') {
129
+ quoted += '\\'.repeat(slashes * 2 + 1) + '"';
130
+ slashes = 0;
131
+ continue;
132
+ }
133
+ quoted += '\\'.repeat(slashes) + character;
134
+ slashes = 0;
135
+ }
136
+ return quoted + '\\'.repeat(slashes * 2) + '"';
137
+ }
138
+ /** Shared so the probe's sandboxed executor is quoted by the same rule as a brokered command. */
139
+ export function windowsCommandLine(parts) { return parts.map(quoteWindowsArgument).join(' '); }
140
+ /*
141
+ * ⛔⛔ R0 lane S — cmd.exe does NOT parse MSVCRT quoting. Measured 2026-09-23 on the host, no sandbox involved:
142
+ * `windowsCommandLine(['/d','/s','/c','node -e "console.log(1)"'])` gives `/d /s /c "node -e \"console.log(1)\""`,
143
+ * cmd strips the outer quotes and hands node `\"console.log(1)\"` — node evaluates a string literal, prints nothing,
144
+ * exits 0. Every brokered command with a double quote in it (a `git commit -m "…"`) ran CHANGED. Node's own
145
+ * `spawn(cmd,{shell:true})` on Windows builds `<ComSpec> /d /s /c "<command>"` verbatim (lib/child_process.js),
146
+ * and with `/s` cmd removes exactly the first and the last quote (`cmd /?`). That is the form used here for the
147
+ * brokered shell invocation, and only for it: the request keeps its argv shape.
148
+ */
149
+ function isCmdInvocation(input) {
150
+ const { executable, args } = input.request;
151
+ return /(?:^|[\\/])cmd(?:\.exe)?$/iu.test(executable) && args.length === 4 && args[0] === '/d' && args[1] === '/s' && args[2] === '/c';
152
+ }
153
+ function commandLine(input) {
154
+ if (isCmdInvocation(input))
155
+ return `${windowsCommandLine([input.request.executable])} /d /s /c "${input.request.args[3]}"`;
156
+ return windowsCommandLine([input.request.executable, ...input.request.args]);
157
+ }
158
+ function policyFor(input) {
159
+ const policy = input.windowsSandboxPolicy;
160
+ if (!policy)
161
+ throw new Error('MXC_WINDOWS_SANDBOX_POLICY_REQUIRED');
162
+ const networkDefault = input.request.network === 'internet-client' ? 'allow' : 'deny';
163
+ return {
164
+ version: '0.8.0-alpha',
165
+ filesystem: { readwritePaths: policy.fsReadWrite, readonlyPaths: policy.fsReadOnly, clearPolicyOnExit: true },
166
+ network: { egress: { default: networkDefault }, ingress: { default: 'deny', hostLoopback: 'deny' } },
167
+ ui: { ...MXC_UI_POLICY },
168
+ ...(input.request.timeoutMs === undefined ? {} : { timeoutMs: input.request.timeoutMs })
169
+ };
170
+ }
171
+ function collectChild(child, input, maxOutputBytes, applied) {
172
+ return new Promise((resolve, reject) => {
173
+ let stdout = Buffer.alloc(0);
174
+ let stderr = Buffer.alloc(0);
175
+ let outputBytes = 0;
176
+ let settled = false;
177
+ const signal = input.request.signal;
178
+ const cleanup = () => {
179
+ if (timer)
180
+ clearTimeout(timer);
181
+ signal?.removeEventListener('abort', onAbort);
182
+ child.stdout?.off('data', onStdout);
183
+ child.stderr?.off('data', onStderr);
184
+ child.off('error', onError);
185
+ child.off('close', onClose);
186
+ };
187
+ const finishReject = (error) => { if (settled)
188
+ return; settled = true; cleanup(); try {
189
+ child.kill();
190
+ }
191
+ catch { } reject(error); };
192
+ const append = (target, chunk) => {
193
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
194
+ if (outputBytes + buffer.length > maxOutputBytes) {
195
+ finishReject(new Error('MXC_OUTPUT_LIMIT_EXCEEDED'));
196
+ return;
197
+ }
198
+ outputBytes += buffer.length;
199
+ if (target === 'stdout')
200
+ stdout = Buffer.concat([stdout, buffer]);
201
+ else
202
+ stderr = Buffer.concat([stderr, buffer]);
203
+ };
204
+ const onStdout = (chunk) => append('stdout', chunk);
205
+ const onStderr = (chunk) => append('stderr', chunk);
206
+ const onError = (cause) => finishReject(cause);
207
+ const onClose = (code) => { if (settled)
208
+ return; settled = true; cleanup(); resolve({ exitCode: code ?? -1, stdout: stdout.toString('utf8'), stderr: stderr.toString('utf8'), applied }); };
209
+ const onAbort = () => finishReject(new Error('MXC_EXECUTION_CANCELLED'));
210
+ const timer = input.request.timeoutMs === undefined ? undefined : setTimeout(() => finishReject(new Error('MXC_EXECUTION_TIMEOUT')), input.request.timeoutMs);
211
+ timer?.unref();
212
+ child.stdout?.on('data', onStdout);
213
+ child.stderr?.on('data', onStderr);
214
+ child.once('error', onError);
215
+ child.once('close', onClose);
216
+ if (signal?.aborted)
217
+ onAbort();
218
+ else
219
+ signal?.addEventListener('abort', onAbort, { once: true });
220
+ });
221
+ }
222
+ export function createMxcExecutionBackend(options = {}) {
223
+ const platform = options.platform ?? process.platform;
224
+ const maxOutputBytes = options.maxOutputBytes ?? 8 * 1024 * 1024;
225
+ if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1)
226
+ throw new Error('MXC_OUTPUT_LIMIT_INVALID');
227
+ let sdkPromise;
228
+ // inspect() runs for both available() and execute(); without this memo every command would spawn a probe tree.
229
+ let probePromise;
230
+ const getSdk = () => sdkPromise ??= options.sdk ? Promise.resolve(options.sdk) : (options.sdkLoader ?? loadMxcSdk)();
231
+ const inspect = async () => {
232
+ if (platform !== 'win32')
233
+ return { available: false, reason: 'MXC BaseContainer enforcement requires Windows' };
234
+ let sdk;
235
+ try {
236
+ sdk = await getSdk();
237
+ }
238
+ catch (cause) {
239
+ return { available: false, reason: `MXC SDK failed to load: ${cause instanceof Error ? cause.message : String(cause)}` };
240
+ }
241
+ const evidence = sdk.getPlatformSupport();
242
+ if (!evidence.isSupported)
243
+ return { available: false, reason: evidence.reason ?? 'MXC is unsupported on this host' };
244
+ if (!evidence.availableMethods.includes('processcontainer'))
245
+ return { available: false, reason: 'MXC ProcessContainer is unavailable' };
246
+ if (evidence.isolationTier !== 'base-container')
247
+ return { available: false, reason: `MXC isolation tier ${evidence.isolationTier ?? 'unknown'} does not prove Experimental_CreateProcessInSandbox` };
248
+ if (!options.verifyProcessTreeKillOnClose)
249
+ return { available: false, reason: 'MXC process-tree kill-on-close has not been verified on this host' };
250
+ const verify = options.verifyProcessTreeKillOnClose;
251
+ let probe;
252
+ try {
253
+ probe = await (probePromise ??= Promise.resolve(verify(evidence)));
254
+ }
255
+ catch (cause) {
256
+ return { available: false, reason: `MXC process-tree probe failed: ${cause instanceof Error ? cause.message : String(cause)}` };
257
+ }
258
+ if (typeof probe !== 'object' || probe === null || typeof probe.verified !== 'boolean' || typeof probe.evidence !== 'object' || probe.evidence === null)
259
+ return { available: false, reason: 'MXC process-tree probe returned an unrecognized result: the seam is a measurement, not a boolean' };
260
+ if (!probe.verified)
261
+ return { available: false, reason: probe.reason ?? 'MXC process-tree kill-on-close probe failed' };
262
+ return { available: true, probe };
263
+ };
264
+ return {
265
+ name: 'microsoft-mxc-0.8.0', level: 'windows-sandbox', available: inspect,
266
+ async execute(input) {
267
+ const availability = await inspect();
268
+ if (!availability.available)
269
+ throw Object.assign(new Error(availability.reason), { code: 'MXC_BACKEND_UNAVAILABLE' }); /* R2 (gate E6): code on error.code, printed once */
270
+ const sdk = await getSdk();
271
+ const sandboxPolicy = policyFor(input);
272
+ const identity = input.windowsSandboxPolicy?.identity;
273
+ const config = sdk.createConfigFromPolicy(sandboxPolicy, 'process', identity);
274
+ config.process = { ...(config.process ?? { commandLine: '' }), commandLine: commandLine(input), cwd: input.request.cwd };
275
+ /* ⛔ R0 lane S: never the backend default (the user profile block, secret-shaped names included, measured): the allowlist. */
276
+ const child = sdk.spawnSandboxFromConfig(config, { usePty: false, experimental: true }, input.request.cwd, input.request.environment ?? mxcChildEnvironment());
277
+ const applied = { enforcement: 'windows-sandbox', appContainer: true, filesystem: 'bfs', network: input.request.network, processTree: 'job-object-kill-on-close', processTreeEvidence: availability.probe.evidence, sandboxSpecVersion: '0.1.0' };
278
+ return collectChild(child, input, maxOutputBytes, applied);
279
+ }
280
+ };
281
+ }
@@ -0,0 +1,138 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { compileRuleSet, evaluateRules } from "./evaluate.js";
4
+ import { ruleToPersist, unreadableRulesError } from "./persist.js";
5
+ import { isReadOnlyShellCommand, parseRule, tokenizeCommand } from "./rule-parser.js";
6
+ const FS_EDIT_OPTIONS = {
7
+ mkdir: new Set(['-p', '-v']), touch: new Set(['-a', '-m', '-c']), rm: new Set(['-r', '-R', '-f', '-rf', '-fr', '-d']), rmdir: new Set(['-p']), cp: new Set(['-r', '-R', '-f', '-n', '-p']), mv: new Set(['-f', '-n']),
8
+ };
9
+ function insideProject(root, candidate) { const rel = path.relative(path.resolve(root), path.resolve(candidate)); return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel)); }
10
+ export function acceptEditsFilesystemCommand(command, projectRoot) {
11
+ const tokens = tokenizeCommand(command);
12
+ if (tokens.length < 2)
13
+ return false;
14
+ const head = tokens[0];
15
+ const allowed = FS_EDIT_OPTIONS[head];
16
+ if (!allowed)
17
+ return false;
18
+ const operands = [];
19
+ let options = true;
20
+ for (const token of tokens.slice(1)) {
21
+ if (options && token === '--') {
22
+ options = false;
23
+ continue;
24
+ }
25
+ if (options && token.startsWith('-')) {
26
+ if (!allowed.has(token))
27
+ return false;
28
+ continue;
29
+ }
30
+ options = false;
31
+ operands.push(token);
32
+ }
33
+ if ((head === 'cp' || head === 'mv') ? operands.length < 2 : operands.length < 1)
34
+ return false;
35
+ for (const operand of operands) {
36
+ const abs = path.isAbsolute(operand) ? path.resolve(operand) : path.resolve(projectRoot, operand);
37
+ if (!insideProject(projectRoot, abs) || abs === path.resolve(projectRoot))
38
+ return false;
39
+ const rel = path.relative(projectRoot, abs).split(path.sep);
40
+ if (rel[0] === '.git' || rel[0] === '.talos-cli')
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+ const PROTECTED_PROJECT_ROOTS = new Set(['.git', '.talos-cli', '.github', '.vscode', '.idea', '.husky']);
46
+ function protectedProjectMutation(action, root) {
47
+ if (!['Write', 'Edit'].includes(action.tool) || !action.resource)
48
+ return false;
49
+ const abs = path.isAbsolute(action.resource) ? path.resolve(action.resource) : path.resolve(root, action.resource);
50
+ if (!insideProject(root, abs))
51
+ return false;
52
+ const first = path.relative(path.resolve(root), abs).split(path.sep)[0] ?? '';
53
+ return PROTECTED_PROJECT_ROOTS.has(first);
54
+ }
55
+ function outsideWrite(action, root) { if (!['Write', 'Edit'].includes(action.tool) || !action.resource)
56
+ return false; const rel = path.relative(path.resolve(root), path.resolve(root, action.resource)); return rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel); }
57
+ export function createPermissionEngine({ projectRoot, rules, mode, classifier, persistRule }) {
58
+ if (mode === 'auto' && !classifier)
59
+ throw new Error('PERMISSION_AUTO_UNAVAILABLE');
60
+ // B1 slice 21. ⛔ Fail closed and by name: one unreadable entry, in any list, and there is no engine.
61
+ const { rules: compiled, invalid } = compileRuleSet(rules);
62
+ if (invalid.length)
63
+ throw unreadableRulesError(projectRoot, invalid);
64
+ const pending = new Map();
65
+ const grants = [];
66
+ return { async decide(action, context) {
67
+ if (outsideWrite(action, projectRoot))
68
+ return { kind: 'deny', code: 'PATH_NOT_ALLOWED', message: 'Operation targets a path outside the project' };
69
+ let base = evaluateRules(compiled, action, { projectRoot });
70
+ if (base.effect === 'deny')
71
+ return { kind: 'deny', code: 'PERMISSION_DENIED', message: base.reason };
72
+ const m = context.mode;
73
+ if (m === 'plan' && (['Write', 'Edit', 'Hook', 'Plugin'].includes(action.tool) || (action.tool === 'Bash' && !isReadOnlyShellCommand(action.command ?? ''))))
74
+ return { kind: 'deny', code: 'PLAN_MODE_READ_ONLY', message: 'Plan mode permits only read-only operations' };
75
+ if (grants.some(g => JSON.stringify(g) === JSON.stringify(action)))
76
+ return { kind: 'allow', source: 'session' };
77
+ if (base.effect === 'allow')
78
+ return { kind: 'allow', source: base.rule ?? 'rule' };
79
+ if (m === 'acceptEdits' && base.effect === 'ask' && !base.rule && !protectedProjectMutation(action, projectRoot) && (['Write', 'Edit'].includes(action.tool) || (action.tool === 'Bash' && acceptEditsFilesystemCommand(action.command ?? '', projectRoot))))
80
+ return { kind: 'allow', source: 'acceptEdits' };
81
+ if (m === 'bypassPermissions' && base.effect === 'ask' && !base.rule)
82
+ return { kind: 'allow', source: 'bypassPermissions' };
83
+ if (m === 'auto' && base.effect === 'ask' && !base.rule && !protectedProjectMutation(action, projectRoot)) {
84
+ if (!classifier)
85
+ throw new Error('PERMISSION_AUTO_UNAVAILABLE');
86
+ const c = await classifier({ action, projectRoot });
87
+ if (c === 'allow')
88
+ return { kind: 'allow', source: 'auto' };
89
+ if (c === 'deny')
90
+ return { kind: 'deny', code: 'PERMISSION_AUTO_DENIED', message: 'Automatic classifier denied operation' };
91
+ base = { effect: 'ask', reason: 'automatic classifier requested confirmation' };
92
+ }
93
+ if (m === 'dontAsk' || !context.interactive)
94
+ return { kind: 'deny', code: 'PERMISSION_REQUIRED', message: 'Operation requires approval in non-interactive mode' };
95
+ const requestId = randomUUID();
96
+ pending.set(requestId, action);
97
+ return { kind: 'ask', requestId, action, reason: base.reason, ...(base.rule ? { rule: base.rule } : {}) };
98
+ }, async resolve(requestId, decision) {
99
+ const action = pending.get(requestId);
100
+ if (!action)
101
+ throw new Error('APPROVAL_NOT_PENDING');
102
+ pending.delete(requestId);
103
+ if (decision === 'allow-session')
104
+ grants.push(action);
105
+ if ((decision === 'allow-always' || decision === 'deny-always') && persistRule) {
106
+ const effect = decision === 'allow-always' ? 'allow' : 'deny';
107
+ // B1 slice 21. A rule the writing path refuses — a compound command, a line break, a rule that would
108
+ // not match its own request, an allow that narrows nothing — is known before the key is pressed (the
109
+ // full review previews the same refusal). Nothing is written, and the approval is still answered:
110
+ // measured at 805c89a0, the refusal threw out of resolve and the runtime's request was never answered.
111
+ // "Allow always" then answers for this session only, for the identical request, which is what Hermes
112
+ // does when "always" cannot be honoured; "deny always" denies this request and asks again next time.
113
+ let rule;
114
+ try {
115
+ rule = parseRule(ruleToPersist(effect, action, projectRoot), effect);
116
+ }
117
+ catch {
118
+ if (effect === 'allow')
119
+ grants.push(action);
120
+ return;
121
+ }
122
+ await persistRule(effect, action);
123
+ // ⛔ The rule is obeyed from now on, not from the next start: before this, the engine compiled its rules
124
+ // once and "allow always" asked again for the identical command in the same session.
125
+ compiled[effect].push(rule);
126
+ // ⛔ And the session's rule set shows it. The approval dialog explains each request from the rule set
127
+ // this engine was built from (`rules`, the same object); without the rule there, measured: after
128
+ // "allow always" on `npm test`, `npm test && npm publish` asked for segment 2 while the account
129
+ // said segment 1, and the dialog withheld every row as a "possible race". A frozen set is left alone.
130
+ try {
131
+ const list = rules[effect];
132
+ if (Array.isArray(list) && !list.includes(rule.raw))
133
+ list.push(rule.raw);
134
+ }
135
+ catch { }
136
+ }
137
+ } };
138
+ }
@@ -0,0 +1,54 @@
1
+ import { compileRules, evaluateRules } from "./evaluate.js";
2
+ import { segmentShellCommand } from "./shell-segmentation.js";
3
+ function explanationForDecision(decision, segments, decisiveSegment) {
4
+ return { effect: decision.effect, reason: decision.reason, ...(decision.rule ? { rule: decision.rule } : {}), precedence: 'deny > ask > allow', decisiveSegment, segments };
5
+ }
6
+ export function explainPermission(rules, action, { projectRoot }) {
7
+ if (action.tool !== 'Bash')
8
+ return explanationForDecision(evaluateRules(rules, action, { projectRoot }), [], null);
9
+ let commandSegments;
10
+ try {
11
+ commandSegments = segmentShellCommand(action.command ?? '');
12
+ }
13
+ catch {
14
+ return explanationForDecision({ effect: 'deny', reason: 'unsupported shell composition' }, [], null);
15
+ }
16
+ const segments = commandSegments.map(segment => {
17
+ const decision = evaluateRules(rules, { ...action, command: segment.command }, { projectRoot });
18
+ return { command: segment.command, operatorBefore: segment.operatorBefore, effect: decision.effect, reason: decision.reason, ...(decision.rule ? { rule: decision.rule } : {}) };
19
+ });
20
+ for (const effect of ['deny', 'ask', 'allow']) {
21
+ const decisiveSegment = segments.findIndex(segment => segment.effect === effect);
22
+ if (decisiveSegment >= 0) {
23
+ const decisive = segments[decisiveSegment];
24
+ return explanationForDecision({ effect: decisive.effect, reason: decisive.reason, ...(decisive.rule ? { rule: decisive.rule } : {}) }, segments, decisiveSegment);
25
+ }
26
+ }
27
+ return explanationForDecision({ effect: 'deny', reason: 'unsupported shell composition' }, segments, null);
28
+ }
29
+ export function simulatePermission({ rules, action, projectRoot }) {
30
+ const explanation = explainPermission(compileRules(rules), action, { projectRoot });
31
+ return { effect: explanation.effect, requiresApproval: explanation.effect === 'ask', explanation };
32
+ }
33
+ export function formatPermissionSimulation(simulation) {
34
+ const explanation = simulation.explanation;
35
+ const lines = [
36
+ `Permission dry-run: ${simulation.effect}`,
37
+ `Precedence: ${explanation.precedence}`,
38
+ ];
39
+ if (explanation.decisiveSegment !== null) {
40
+ const segment = explanation.segments[explanation.decisiveSegment];
41
+ lines.push(`Decisive: segment ${explanation.decisiveSegment + 1}${segment ? ` · ${segment.command}` : ''} · ${explanation.reason}`);
42
+ }
43
+ else
44
+ lines.push(`Decisive: ${explanation.reason}`);
45
+ if (explanation.rule)
46
+ lines.push(`Rule: ${explanation.rule}`);
47
+ if (explanation.segments.length) {
48
+ lines.push('Segments:');
49
+ explanation.segments.forEach((segment, index) => {
50
+ lines.push(` ${index + 1}. ${segment.effect} · ${segment.command}${segment.rule ? ` · ${segment.rule}` : ''} · ${segment.reason}`);
51
+ });
52
+ }
53
+ return lines.join('\n') + '\n';
54
+ }
@@ -0,0 +1,179 @@
1
+ import fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { projectId, resolveCliPaths } from "../paths.js";
5
+ import { readConfigScope, writeConfigScope } from "../config/load.js";
6
+ import { patternNamesOnlyAVerb, ruleMatchesAction } from "./evaluate.js";
7
+ import { parseRule, ruleProblem } from "./rule-parser.js";
8
+ import { segmentShellCommand } from "./shell-segmentation.js";
9
+ function normalizeResource(resource, root) { const abs = isAbsolute(resource) ? resolve(resource) : resolve(root, resource); const rel = relative(resolve(root), abs); if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
10
+ throw new Error('PATH_NOT_ALLOWED'); return rel.split(sep).join('/'); }
11
+ function spelledRule(a, root) { if (a.tool === 'Bash')
12
+ return `Bash(${a.command ?? ''})`; if (['Read', 'Write', 'Edit'].includes(a.tool))
13
+ return `${a.tool}(${normalizeResource(a.resource ?? '', root)})`; if (a.tool === 'Mcp')
14
+ return `Mcp(${a.serverId ?? ''}:${a.operation ?? '*'})`; if (a.tool === 'TalosService')
15
+ return `TalosService(${a.resource ?? ''}:${a.operation ?? '*'})`; return `${a.tool}(${a.operation ?? a.resource ?? '*'})`; }
16
+ /*
17
+ * B1 slice 21 — A RULE IS WRITTEN ONLY IF IT CAN BE READ BACK AND DECIDES THE REQUEST IT WAS WRITTEN FOR.
18
+ *
19
+ * Recorded at e5d197e7 and re-measured at 805c89a0: "allow always" on `ls<LF>rm -rf /` wrote `Bash(ls<LF>rm -rf /)`, which the
20
+ * parser cannot read, and the next start threw; on `git status && npm test; npm publish` it wrote the
21
+ * whole command, which matches none of the segments the engine judges one at a time.
22
+ *
23
+ * ⛔ A compound command is not remembered at all. Measured in the two competitors before deciding:
24
+ * - Claude Code 2.1.274 (claude.exe) offers one rule per subcommand that still asks, capped at five, each
25
+ * the exact subcommand or a prefix ending in ` *` (`git status *`), and labels the row "don't ask again
26
+ * for A and B commands" from exactly the rules it writes: shown, but a prefix is broader than the ask.
27
+ * - Hermes Agent (0.20.0 and main f5d19261) persists the dangerous-pattern CATEGORY ("recursive
28
+ * delete"), and its command-text allowlist refuses any command with a shell operator; when "always"
29
+ * cannot be honoured it answers "session" instead.
30
+ * A rule per segment here would allow `rm -rf build` inside any other command, and the approval dialog
31
+ * previews exactly one rule, so it could not show what was written. Declining is the only form that
32
+ * neither widens nor hides, and the engine then answers for this session only, as Hermes does.
33
+ */
34
+ const ALWAYS_ANSWERS = '"allow always" answers for this session only';
35
+ /* R2 (gate E6): the code travels on `error.code`; the message is the English sentence alone, so the failure line prints the
36
+ code once (review D-06). `codeForError` read the code from a `CODE: ` prefix before; the property keeps it identical. */
37
+ function ruleError(code, message) { return Object.assign(new Error(message), { code }); }
38
+ export function permissionActionToRule(a, root) {
39
+ if (a.tool === 'Bash') {
40
+ let segments;
41
+ try {
42
+ segments = segmentShellCommand(a.command ?? '');
43
+ }
44
+ catch {
45
+ throw ruleError('RULE_COMMAND_UNSUPPORTED', `this command cannot be split into the segments the engine judges, so no rule is written for it; ${ALWAYS_ANSWERS}`);
46
+ }
47
+ if (segments.length > 1)
48
+ throw ruleError('RULE_COMPOUND_COMMAND', `a command of ${segments.length} segments is not remembered, because a rule for the whole command matches none of its segments and a rule for each segment would allow it inside any other command; ${ALWAYS_ANSWERS}`);
49
+ }
50
+ const rule = spelledRule(a, root);
51
+ let compiled;
52
+ try {
53
+ compiled = parseRule(rule, 'allow');
54
+ }
55
+ catch {
56
+ throw ruleError('RULE_UNREADABLE', `the rule for this request ${ruleProblem(rule) ?? 'cannot be parsed'}, so it could not be read back and is not written; ${ALWAYS_ANSWERS}`);
57
+ }
58
+ // A verb-only pattern (`cat:*`, `*`) is slice 11's case and keeps slice 11's answer: an allow is refused as
59
+ // RULE_NOT_NARROWED by the writer, and a deny that covers the verb is the safe direction and is written.
60
+ if (!(a.tool === 'Bash' && patternNamesOnlyAVerb(compiled.pattern)) && !ruleMatchesAction(compiled, a, root))
61
+ throw ruleError('RULE_MATCHES_NOTHING', `the rule for this request would not match the request itself, so it is not written; ${ALWAYS_ANSWERS}`);
62
+ return rule;
63
+ }
64
+ // B1 slice 11. Gemini CLI refuses to *construct* a persistent allow rule for its shell, read, write
65
+ // and search tools unless the rule carries an argument-narrowing pattern — a property of the
66
+ // writing interface, so it binds a hand-written administrator rule too and not only the
67
+ // "don't ask again" button. Measured on this host before the change: this path could produce
68
+ // `Bash(*)`, `Bash(**)` and `Bash(cat:*)`, because the rule is the command text and a command whose
69
+ // own text is `*` or ends in `:*` becomes exactly the blanket form. A deny that covers everything
70
+ // is the safe direction and is still written; only an `allow` that narrows nothing is refused.
71
+ function bashRuleNarrowsNothing(rule) { const m = /^Bash\((.*)\)$/u.exec(rule); if (!m)
72
+ return false; const pattern = m[1]; return pattern === '' || pattern === '*' || pattern === '**' || /:\*$/u.test(pattern); }
73
+ // B1 slice 21. The rule "always" would write, or the refusal that writes nothing. One function, so the engine answers
74
+ // exactly the refusals the writer makes: before this slice an allow that narrows nothing threw out of the
75
+ // engine's resolve, and the runtime's approval was never answered.
76
+ export function ruleToPersist(effect, action, root) {
77
+ const rule = permissionActionToRule(action, root);
78
+ if (effect === 'allow' && bashRuleNarrowsNothing(rule))
79
+ throw new Error(`RULE_NOT_NARROWED:${rule}`);
80
+ return rule;
81
+ }
82
+ // ⛔ B1 slice 21. Only the list being changed is written. Measured at 805c89a0: this function wrote all
83
+ // three lists into the project-user file, and a list in a later configuration file REPLACES the list of
84
+ // an earlier one, so one "allow always" on `npm test` wrote `deny: []` and switched off the deny rules of
85
+ // the project's own `.talos-cli/config.json` (`curl evil.example` went from deny to ask).
86
+ export async function persistPermissionRule({ paths, projectRoot, effect, action }) {
87
+ const rule = ruleToPersist(effect, action, projectRoot);
88
+ const current = await readConfigScope({ paths, projectRoot, scope: 'project-user' });
89
+ const list = current.permissions?.[effect] ?? [];
90
+ if (!Array.isArray(list))
91
+ throw ruleError('RULE_STORE_INVALID', `"permissions.${effect}" in the project-user configuration is not a list`);
92
+ // The file keeps whatever other lists it already had; a partial `permissions` object is what the loader
93
+ // merges over the lower scopes, so the type's three required lists do not describe a patch.
94
+ await writeConfigScope({ paths, projectRoot, scope: 'project-user', patch: { permissions: { [effect]: [...new Set([...list, rule])] } } });
95
+ }
96
+ /*
97
+ * B1 slice 21 — A RULE THAT CANNOT BE READ STOPS THE START, BY NAME.
98
+ *
99
+ * Measured at 805c89a0, starting the interactive CLI with that rule on disk: Ink printed
100
+ * `ERROR PERMISSION_RULE_INVALID:Bash(ls` with the rule's own line break splitting the line, then a
101
+ * stack through parseRule, compileRules, createPermissionEngine and the React reconciler — and nothing
102
+ * said which file held the rule or what to do about it.
103
+ * ⛔ Skipping the rule is not the cure: a skipped deny rule silently allows what it denied (Claude Code
104
+ * 2.1.274 does skip an invalid rule with a warning, deny rules included). The engine refuses to exist.
105
+ */
106
+ export class PermissionRulesUnreadableError extends Error {
107
+ code = 'PERMISSION_RULE_INVALID';
108
+ constructor(message) {
109
+ super(message);
110
+ this.name = 'PermissionRulesUnreadableError';
111
+ // A configuration error is described completely by its message. Its frames point at this constructor,
112
+ // not at anything a person can fix, and the interactive screen prints every frame it is given.
113
+ this.stack = `${this.name}: ${message}`;
114
+ }
115
+ }
116
+ // Highest precedence first: the loader overlays user, then project, then project-user, and a list in a
117
+ // later file replaces the list of an earlier one. So the first file that defines the list is the file the
118
+ // engine's list came from. The layout mirrors `files()` in config/load.ts; a test writes through the real
119
+ // writer into each scope and asserts this names that exact file, so a drift turns red.
120
+ function permissionConfigFiles(projectRoot) {
121
+ let paths = null;
122
+ try {
123
+ paths = resolveCliPaths(process.env, process.platform, homedir());
124
+ }
125
+ catch {
126
+ paths = null;
127
+ }
128
+ return [...(paths ? [join(paths.projectOverridesRoot, projectId(projectRoot), 'config.json')] : []), join(projectRoot, '.talos-cli', 'config.json'), ...(paths ? [join(paths.configRoot, 'config.json')] : [])];
129
+ }
130
+ // `undefined` when the file is absent, unreadable or has no `permissions`; `null` when `permissions` is there
131
+ // but is not an object of lists, which replaces every lower file's lists at once.
132
+ function storedPermissions(file) {
133
+ let value;
134
+ try {
135
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
136
+ }
137
+ catch {
138
+ return undefined;
139
+ }
140
+ if (!value || typeof value !== 'object' || !Object.hasOwn(value, 'permissions'))
141
+ return undefined;
142
+ const permissions = value.permissions;
143
+ return permissions && typeof permissions === 'object' && !Array.isArray(permissions) ? permissions : null;
144
+ }
145
+ function locate(projectRoot, invalid) {
146
+ const files = permissionConfigFiles(projectRoot);
147
+ for (const file of files) {
148
+ const permissions = storedPermissions(file);
149
+ if (permissions === null)
150
+ return invalid.index === null ? { file, whole: true } : { searched: files };
151
+ if (!permissions || !Object.hasOwn(permissions, invalid.effect))
152
+ continue;
153
+ const list = permissions[invalid.effect];
154
+ const found = invalid.index === null ? JSON.stringify(list) === JSON.stringify(invalid.value) : Array.isArray(list) && JSON.stringify(list[invalid.index]) === JSON.stringify(invalid.value);
155
+ return found ? { file } : { searched: files };
156
+ }
157
+ return { searched: files };
158
+ }
159
+ // JSON spells the rule the way it appears in the file (a line break as the two characters `\n`), and
160
+ // what JSON leaves raw — DEL, C1, line and paragraph separators, bidi controls, invisible characters —
161
+ // becomes a visible `\uXXXX`, so nothing in the message can move a terminal's cursor or hide text.
162
+ const BACKSLASH = String.fromCharCode(92);
163
+ const TERMINAL_UNSAFE = /[\x00-\x1f\x7f-\x9f\p{Zl}\p{Zp}\p{Bidi_Control}\p{Default_Ignorable_Code_Point}]/gu;
164
+ function inert(text) { return text.replace(TERMINAL_UNSAFE, character => { const code = character.codePointAt(0); return code > 0xffff ? `${BACKSLASH}u{${code.toString(16)}}` : `${BACKSLASH}u${code.toString(16).padStart(4, '0')}`; }); }
165
+ function spelled(value) { return JSON.stringify(value) ?? String(value); }
166
+ export function unreadableRulesError(projectRoot, invalid) {
167
+ const first = invalid[0];
168
+ const where = locate(projectRoot, first);
169
+ const found = 'file' in where;
170
+ const whole = found && where.whole === true;
171
+ const list = whole ? '"permissions"' : `"permissions.${first.effect}"`;
172
+ const problem = whole ? 'is not an object holding the allow, ask and deny lists' : first.problem;
173
+ const subject = first.index === null ? list : `the ${first.effect} rule ${spelled(first.value)} in ${list}`;
174
+ const place = found ? ` of ${where.file}` : '';
175
+ const origin = found ? '' : ` It is not in ${where.searched.join(', ')}: these rules were handed to the engine directly.`;
176
+ const fix = !found ? 'Remove it where these rules come from, then start TALOS again.' : whole ? `Make ${list} in that file an object such as {"allow":[],"ask":[],"deny":[]}, then start TALOS again.` : first.index === null ? `Make ${list} in that file a list of rules, for example [], then start TALOS again.` : `Delete that entry from ${list} in that file, then start TALOS again.`;
177
+ const more = invalid.length > 1 ? ` ${invalid.length - 1} more permission ${invalid.length === 2 ? 'entry is' : 'entries are'} unreadable too.` : '';
178
+ return new PermissionRulesUnreadableError(inert(`Permission rules cannot be loaded: ${subject}${place} ${problem}.${origin} No rule is skipped, because a skipped deny rule would allow what it denies. ${fix}${more}`));
179
+ }