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,62 @@
1
+ /**
2
+ * Accesso locale minimale al filesystem di un workspace.
3
+ *
4
+ * Questo adapter sostituisce l'import da repository esterni usato in
5
+ * precedenza dall'albero file. Conosce soltanto l'elenco di un livello:
6
+ * nessuna ricorsione, nessun comando di sistema e nessuna trasformazione del
7
+ * contenuto. Le policy di percorso restano esplicite e verificabili qui.
8
+ */
9
+ import { readdir } from 'node:fs/promises';
10
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
11
+
12
+ export class WorkspaceDiskError extends Error {
13
+ constructor(message, code = 'DIRECTORY_UNREADABLE') {
14
+ super(message);
15
+ this.name = 'WorkspaceDiskError';
16
+ this.code = code;
17
+ }
18
+ }
19
+
20
+ function percorsoRelativoValido(percorso) {
21
+ return typeof percorso === 'string'
22
+ && !percorso.includes('\0')
23
+ && !isAbsolute(percorso);
24
+ }
25
+
26
+ function dentroRadice(radice, candidato) {
27
+ const differenza = relative(radice, candidato);
28
+ return differenza === ''
29
+ || (differenza !== '..' && !differenza.startsWith(`..${sep}`) && !isAbsolute(differenza));
30
+ }
31
+
32
+ /**
33
+ * @param {{rootDir?:string, root?:string, fsImpl?:{readdir?:typeof readdir}, readdirFn?:typeof readdir}} input
34
+ * @returns {{elenca:(percorso?:string)=>Promise<Array<{nome:string, cartella:boolean}>>}}
35
+ */
36
+ export function createWorkspaceDisk({ rootDir, root = rootDir, fsImpl = {}, readdirFn = fsImpl.readdir ?? readdir } = {}) {
37
+ if (typeof root !== 'string' || root.length === 0 || root.includes('\0') || !isAbsolute(root)) {
38
+ throw new WorkspaceDiskError('Cartella workspace non valida', 'QUERY_INVALID');
39
+ }
40
+
41
+ const radice = resolve(root);
42
+ return {
43
+ async elenca(percorso = '') {
44
+ if (!percorsoRelativoValido(percorso)) {
45
+ throw new WorkspaceDiskError('Percorso non valido', 'QUERY_INVALID');
46
+ }
47
+ const candidato = resolve(radice, percorso);
48
+ if (!dentroRadice(radice, candidato)) {
49
+ throw new WorkspaceDiskError('Percorso fuori dalla cartella della sessione', 'QUERY_INVALID');
50
+ }
51
+ let voci;
52
+ try {
53
+ voci = await readdirFn(candidato, { withFileTypes: true });
54
+ } catch {
55
+ throw new WorkspaceDiskError('Cartella non leggibile');
56
+ }
57
+ return voci
58
+ .map((voce) => ({ nome: voce.name, cartella: voce.isDirectory() }))
59
+ .sort((a, b) => (a.cartella === b.cartella ? a.nome.localeCompare(b.nome, 'en') : a.cartella ? -1 : 1));
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,589 @@
1
+ /**
2
+ * workspace-files.mjs — le azioni sul singolo file dell'albero workspace
3
+ * che oggi mancano (piano `elegant-spinning-dongarra.md`, owner 27/8:
4
+ * "Non ha nessun'opzione per rinominare i file, per aprire i file, per
5
+ * aprirli nel visualizza file explorer di Windows. Non ha opzioni per
6
+ * eliminarlo, per allegarlo nella chat").
7
+ *
8
+ * ⛔ Deliberatamente FUORI da `talosHarness.mjs`: queste sono azioni
9
+ * dell'OWNER sull'albero (mai un tool-call del modello), quindi non
10
+ * hanno bisogno di passare dal kernel benchmarkato — `discoNode` espone
11
+ * solo `elenca`/`leggi`/`scrivi` (verificato leggendo il sorgente prima
12
+ * di scrivere questo file, non presunto), niente rinomina/elimina.
13
+ * Aggiungerle lì avrebbe richiesto un'altra ri-misura TALOS-BANCO per
14
+ * un bisogno che non tocca affatto il banco.
15
+ *
16
+ * ⛔⛔ Stessa disciplina di path-policy.mjs/workspace-tree.mjs: ogni
17
+ * percorso arriva da una richiesta HTTP, non da un modello cooperativo
18
+ * — `isPathInside` (path-policy.mjs, riusato non duplicato) valida
19
+ * SEMPRE il percorso REALE (dopo aver risolto eventuali symlink) contro
20
+ * la radice reale della sessione, mai la stringa grezza.
21
+ */
22
+ import { promises as fsp, realpathSync } from 'node:fs';
23
+ import { dirname, isAbsolute, join, relative, sep } from 'node:path';
24
+
25
+ import { isPathInside } from './path-policy.mjs';
26
+ import { createProcessPolicy } from './process-policy.mjs';
27
+
28
+ /* BC-64 (17/09): Esplora file è l'UNICO programma che lanciamo perché la persona lo VEDA — con la finestra
29
+ nascosta «Rivela in Esplora file» riusciva e non mostrava niente (misura in `process-policy.mjs`). */
30
+ export const EXPLORER_PROCESS_POLICY = createProcessPolicy({ allowedExecutables: ['explorer.exe', 'explorer'], finestreVisibili: true });
31
+
32
+ export class WorkspaceFileError extends Error {
33
+ constructor(message, code = 'QUERY_INVALID') {
34
+ super(message);
35
+ this.name = 'WorkspaceFileError';
36
+ this.code = code;
37
+ }
38
+ }
39
+
40
+ /** ⛔ 512 KB: un'anteprima, non un editor — file più grandi si dichiarano troppo grandi invece di essere troncati in silenzio. */
41
+ const DIMENSIONE_MASSIMA_ANTEPRIMA = 512 * 1024;
42
+
43
+ /*
44
+ * ⛔⛔ 11/09/2026 — `ammettiRadice` nasce dall'unica azione che sulla RADICE ha senso: «Apri in
45
+ * Esplora file» (owner, dal vivo: «se faccio tasto destro sulla ROOT deve poter spuntare "Apri",
46
+ * cioè devo poterla aprire su Windows»). Tutte le altre — rinomina, elimina, copia, sposta —
47
+ * restano vietate sulla radice, ed è per questo che il divieto NON è stato tolto ma reso una
48
+ * scelta di chi chiama: il valore di default è ancora `false`, quindi nessuna chiamata già
49
+ * scritta cambia comportamento.
50
+ * ⛔ Quello che NON si allenta mai: `realpath` prima del confronto (un collegamento simbolico che
51
+ * punta fuori viene canonicalizzato e respinto da `isPathInside`), niente percorsi assoluti,
52
+ * niente `\0`. Un `..` che risale sopra la radice cade su `isPathInside`, esattamente come prima.
53
+ */
54
+ function risolviPercorsoEsistente(cartella, percorso, { realpathSyncFn = realpathSync } = {}, ammettiRadice = false) {
55
+ const vuoto = percorso === '';
56
+ if (typeof percorso !== 'string' || percorso.includes('\0') || isAbsolute(percorso) || (vuoto && !ammettiRadice)) {
57
+ throw new WorkspaceFileError('Percorso non valido');
58
+ }
59
+ const radiceReale = realpathSyncFn(cartella);
60
+ let reale;
61
+ try {
62
+ reale = vuoto ? radiceReale : realpathSyncFn(join(cartella, percorso));
63
+ } catch {
64
+ throw new WorkspaceFileError('File non trovato', 'FILE_NOT_FOUND');
65
+ }
66
+ // ⛔ `isPathInside` dice `true` anche quando i due percorsi COINCIDONO (`relative()` torna ''): il confine vale comunque, ed è il controllo sotto a decidere se la radice stessa è ammessa.
67
+ if (!isPathInside(radiceReale, reale)) {
68
+ throw new WorkspaceFileError('Percorso fuori dalla cartella della sessione');
69
+ }
70
+ if (reale === radiceReale && !ammettiRadice) {
71
+ // ⛔ nessuna delle azioni che passano di qui con `ammettiRadice=false` ha senso sulla RADICE della sessione stessa (rinominarla/eliminarla è un disastro diverso, fuori scope qui)
72
+ throw new WorkspaceFileError('Percorso fuori dalla cartella della sessione, o è la radice stessa');
73
+ }
74
+ return { radiceReale, reale };
75
+ }
76
+
77
+ /**
78
+ * "Apri" — il contenuto di un file, in sola lettura. Non passa da
79
+ * `disco.leggi` del kernel (che tronca a MAX_BYTE_LETTI per un motivo
80
+ * diverso, il contesto del modello): qui l'anteprima ha il suo proprio
81
+ * tetto, dichiarato esplicitamente se superato.
82
+ */
83
+ export async function leggiContenutoFile({ cartella, percorso }, deps = {}) {
84
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
85
+ const stat = await (deps.statFn ?? fsp.stat)(reale);
86
+ if (!stat.isFile()) throw new WorkspaceFileError('Non è un file — apri una cartella dall\'albero, non da qui');
87
+ if (stat.size > DIMENSIONE_MASSIMA_ANTEPRIMA) {
88
+ throw new WorkspaceFileError(`File troppo grande per l'anteprima (${Math.round(stat.size / 1024)} KB, tetto ${DIMENSIONE_MASSIMA_ANTEPRIMA / 1024} KB)`, 'FILE_TOO_LARGE');
89
+ }
90
+ const contenuto = await (deps.readFileFn ?? fsp.readFile)(reale, 'utf8');
91
+ return { contenuto, dimensione: stat.size };
92
+ }
93
+
94
+ /*
95
+ * ⛔⛔ PO-05, owner: «ogni file generato deve avere un collegamento diretto per scaricarlo con un
96
+ * clic; nome, formato, dimensione e disponibilità REALI». Fino a oggi un documento generato finiva nel
97
+ * workspace e in Libreria, e in chat arrivava la riga `[binary docx file, 7714 bytes]`: vera, ma non
98
+ * cliccabile. Questa è la metà del server.
99
+ *
100
+ * ⛔ Perché non basta `leggiContenutoFile`, che è qui sopra: quella legge in **utf8** — un `.docx` è
101
+ * uno zip, e passarlo da `readFile(…, 'utf8')` lo corrompe irreparabilmente (ogni byte non valido
102
+ * diventa U+FFFD e non torna più indietro) — e ha il tetto dell'ANTEPRIMA (512 KB), che per un
103
+ * allegato è la misura sbagliata. Qui i byte restano byte.
104
+ * ⛔ La difesa NON si riscrive: è la stessa `risolviPercorsoEsistente` delle sorelle (canonicalizza
105
+ * con `realpath` e confina dentro la cartella della sessione, quindi né `..` né un collegamento
106
+ * simbolico portano fuori). Una difesa copiata è una difesa che un giorno diverge.
107
+ *
108
+ * Ricerca 09-10/09/2026, prima di scrivere:
109
+ * · IETF RFC 6266 (`Use of the Content-Disposition Header Field in HTTP`): «attachment» dice al
110
+ * destinatario di proporre il salvataggio; il parametro `filename*` estende `filename` alle
111
+ * codifiche oltre l'ASCII e, quando ci sono entrambi, **`filename*` ha la precedenza** ⇒ si
112
+ * mandano TUTTI E DUE: `filename` come ripiego ASCII e `filename*` in UTF-8. UTF-8 è la codifica
113
+ * raccomandata perché almeno un'implementazione diffusa non ne conosce altre.
114
+ * · MDN, `Content-Disposition` (letto 10/09/2026): stessa regola, e l'avvertenza che un agente che
115
+ * non conosce RFC 5987 mostrerebbe la sequenza percent-encoded — da cui il doppio parametro.
116
+ * ⛔ Il nome che finisce nell'intestazione è il **nome del file sul disco**, non un testo scelto da
117
+ * chi chiama: niente a capo né virgolette che possano spezzare l'intestazione (una risposta HTTP
118
+ * con un'intestazione spezzata è una vulnerabilità, non un nome brutto).
119
+ */
120
+ /* ⛔ Esportato il 10/09/2026: lo scarico di una voce di Libreria (`library-store.mjs`) ha lo
121
+ STESSO tetto, e due numeri scritti in due file un giorno dicono due cose diverse. */
122
+ export const DIMENSIONE_MASSIMA_SCARICO = 64 * 1024 * 1024;
123
+
124
+ /** Il nome, ridotto a ciò che può stare in un'intestazione HTTP senza spezzarla. Puro. */
125
+ export function nomeSicuroPerIntestazione(nome) {
126
+ const pulito = String(nome ?? '').replace(/[\r\n\0]/g, '').trim();
127
+ return pulito.length > 0 ? pulito : 'file';
128
+ }
129
+
130
+ /**
131
+ * Le due forme del nome per `Content-Disposition`, secondo RFC 6266: `ascii` è il ripiego (i
132
+ * caratteri fuori ASCII diventano `_`, le virgolette e la barra rovescia spariscono), `utf8` è il
133
+ * valore percent-encoded per `filename*`. Puro, così si prova senza un server.
134
+ */
135
+ export function nomiPerContentDisposition(nome) {
136
+ const sicuro = nomeSicuroPerIntestazione(nome);
137
+ const ascii = sicuro.replace(/["\\]/g, '').replace(/[^\x20-\x7E]/g, '_');
138
+ return { ascii: ascii.trim() || 'file', utf8: encodeURIComponent(sicuro) };
139
+ }
140
+
141
+ /**
142
+ * Un file del workspace, in BYTE, per essere scaricato. Stessa difesa delle sorelle.
143
+ * @returns {Promise<{bytes:Buffer, dimensione:number, nome:string}>}
144
+ */
145
+ export async function leggiFilePerScarico({ cartella, percorso }, deps = {}) {
146
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
147
+ const stat = await (deps.statFn ?? fsp.stat)(reale);
148
+ if (!stat.isFile()) throw new WorkspaceFileError('Non è un file: una cartella non si scarica');
149
+ if (stat.size > DIMENSIONE_MASSIMA_SCARICO) {
150
+ throw new WorkspaceFileError(
151
+ `File troppo grande da scaricare (${Math.round(stat.size / 1024 / 1024)} MB, tetto ${DIMENSIONE_MASSIMA_SCARICO / 1024 / 1024} MB)`,
152
+ 'FILE_TOO_LARGE',
153
+ );
154
+ }
155
+ const bytes = await (deps.readFileFn ?? fsp.readFile)(reale);
156
+ return { bytes, dimensione: stat.size, nome: reale.split(/[\\/]/).pop() };
157
+ }
158
+
159
+ /**
160
+ * "Rinomina" — `nuovoNome` è un NOME, non un percorso: niente `/`, `\`,
161
+ * `..` — sposta il file nella STESSA cartella, non altrove (rinominare
162
+ * ≠ spostare, stessa distinzione che fa ogni file manager reale).
163
+ */
164
+ export async function rinominaFile({ cartella, percorso, nuovoNome }, deps = {}) {
165
+ const { radiceReale, reale } = risolviPercorsoEsistente(cartella, percorso, deps);
166
+ if (
167
+ typeof nuovoNome !== 'string' || nuovoNome.length === 0 || nuovoNome.length > 255
168
+ || nuovoNome.includes('/') || nuovoNome.includes('\\') || nuovoNome.includes('\0')
169
+ || nuovoNome === '.' || nuovoNome === '..'
170
+ ) {
171
+ throw new WorkspaceFileError('Nuovo nome non valido — un nome di file, non un percorso');
172
+ }
173
+ const destinazione = join(dirname(reale), nuovoNome);
174
+ if (!isPathInside(radiceReale, destinazione)) throw new WorkspaceFileError('Destinazione fuori dalla cartella della sessione');
175
+ const accessFn = deps.accessFn ?? fsp.access;
176
+ const esisteGia = await accessFn(destinazione).then(() => true, () => false);
177
+ if (esisteGia) throw new WorkspaceFileError('Esiste già un file con questo nome', 'FILE_EXISTS');
178
+ await (deps.renameFn ?? fsp.rename)(reale, destinazione);
179
+ const nuovoPercorso = relative(radiceReale, destinazione).split(sep).join('/');
180
+ return { nuovoPercorso };
181
+ }
182
+
183
+ /**
184
+ * "Elimina" — DISTRUTTIVA. La conferma vive nel frontend (un dialogo
185
+ * vero, non un `confirm()` del browser — stessa disciplina "hard to
186
+ * reverse actions get confirmed" del resto del prodotto): questa
187
+ * funzione esegue e basta, non chiede nulla lei stessa.
188
+ */
189
+ export async function eliminaFile({ cartella, percorso }, deps = {}) {
190
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
191
+ const stat = await (deps.statFn ?? fsp.stat)(reale);
192
+ await (deps.rmFn ?? fsp.rm)(reale, { recursive: stat.isDirectory(), force: false });
193
+ return { eliminato: true };
194
+ }
195
+
196
+ /*
197
+ * ⛔⛔⛔ BC-11, 11/09/2026 — IL TETTO DI UNA SCRITTURA SI DICHIARA, non lo si scopre sbattendoci.
198
+ *
199
+ * Misurato sulla sessione `8dde6bff` dell'owner («genera un file html di almeno 1000 righe»):
200
+ * `creaFileWorkspace` accettava **41.943.040 byte in 12 ms senza un solo controllo**, e con
201
+ * `bytes: undefined` non rispondeva niente di utile — rilanciava il `TypeError` di Node
202
+ * (`ERR_INVALID_ARG_TYPE: The "data" argument must be of type string or an instance of Buffer…`),
203
+ * cioè un errore di sistema operativo consegnato a un modello che non ha modo di agirci sopra.
204
+ * ⇒ Regola dell'owner per BC-11: «ogni attrezzo deve DIRE al modello i suoi limiti quando li
205
+ * supera, invece di restituire un errore di sistema operativo».
206
+ *
207
+ * ⛔ Perché 32 MB e non di più: è la stessa taglia d'ordine dello scarico (64 MB, qui sopra) ma
208
+ * dimezzata, perché QUESTI byte hanno attraversato una risposta del modello — nessun documento
209
+ * generato da un giro è legittimamente più grande, e un numero più alto non proteggerebbe da
210
+ * niente. È un tetto DICHIARATO nel messaggio, non una soglia muta.
211
+ *
212
+ * Ricerca 11/09/2026, prima di scrivere (regola owner: si cerca PRIMA, soprattutto quando sembra
213
+ * ovvio — quello che manca non è la soluzione, sono i vincoli):
214
+ * · Anthropic, «Text editor tool» (platform.claude.com, letto 11/09/2026): i comandi sono
215
+ * `view`/`create`/`str_replace`/`insert`/`undo_edit` — `insert` esiste APPOSTA per aggiungere
216
+ * testo dopo una riga, cioè per non dover riscrivere un file intero a ogni aggiunta.
217
+ * · anthropics/claude-quickstarts#348 (letto 11/09/2026): «The `EditTool20250728` class expects
218
+ * `new_str` for the insert command, but Claude actually outputs `insert_text`. This causes
219
+ * insert commands to fail» ⇒ il nome sbagliato di un argomento è una classe di errore REALE
220
+ * anche nell'implementazione di riferimento del fornitore, non una stranezza del nostro
221
+ * modello: un attrezzo che accetta un solo nome trasforma un errore di forma in un guasto.
222
+ * · Nous Research, hermes-agent v0.21, `tools/file_tools.py:2729` (WRITE_FILE_SCHEMA): «Use this
223
+ * instead of echo/cat heredoc in terminal… OVERWRITES the entire file — use 'patch' for
224
+ * targeted edits», e `registry.register(…, max_result_size_chars=100_000)` — il tetto è un dato
225
+ * dichiarato accanto all'attrezzo, non un comportamento scoperto a valle.
226
+ * · cline, `apps/vscode/src/sdk/sdk-diff-edit-coordinator.ts:401` — l'errore stesso insegna il
227
+ * valore giusto: «insert_line must be a positive one-based boundary line in the range 1-N.
228
+ * **Use N to append at EOF.**»
229
+ * · Node.js `fs` (nodejs.org, letto 11/09/2026): `appendFile` usa il flag `'a'`; su Windows
230
+ * `flock` non c'è, e `writeFile` con `'w'` / `appendFile` con `'a'` sono la via per una
231
+ * scrittura o un'aggiunta atomica per singola chiamata. ⇒ qui si accoda con `appendFile`, MAI
232
+ * leggendo-concatenando-riscrivendo (che perderebbe la scrittura di chiunque altro in mezzo).
233
+ */
234
+ export const DIMENSIONE_MASSIMA_CREAZIONE = 32 * 1024 * 1024;
235
+
236
+ /**
237
+ * "Crea" — scrive BYTE nuovi alla radice del workspace (`document_create`,
238
+ * piano elegant-spinning-dongarra.md, 28/8). Diversa dalle altre azioni
239
+ * di questo file: qui il file NON esiste ancora, quindi
240
+ * `risolviPercorsoEsistente` (che richiede `realpathSync` sul
241
+ * bersaglio) non si applica — si valida solo che `nome` sia un NOME
242
+ * piatto (stessa grammatica di `nuovoNome` in `rinominaFile`: niente
243
+ * `/`, `\`, `..`), mai un percorso con sottocartelle. Scope
244
+ * deliberatamente stretto (radice del workspace, non un percorso
245
+ * arbitrario) — un generatore di documenti scrive dove l'utente lo
246
+ * vede subito nell'albero, non in una sottocartella indovinata.
247
+ *
248
+ * ⛔⛔⛔ BC-11 — `modalita` è la seconda metà della cura, e nasce da un numero: nella sessione
249
+ * `8dde6bff` il modello ha speso **119 chiamate `shell` contro 23 `scrivi`** (e nella `37e10d21`
250
+ * **99 contro 6**) per scrivere UN file, perché nessun attrezzo sa AGGIUNGERE: un file più lungo
251
+ * di una risposta obbligava a inventarsi `_p2.html`, `_p3.html`, `_p4.html`, `_p5.html` e poi un
252
+ * passo di «assemblaggio» — cioè almeno due giri in più per ogni pezzo.
253
+ * · `'nuovo'` (default): ESATTAMENTE il comportamento di sempre — un nome già preso viene
254
+ * rifiutato, mai sovrascritto in silenzio (prova `workspace-files.test.mjs:240`, che resta vera).
255
+ * · `'accoda'`: aggiunge in coda se il file c'è, lo crea se non c'è. È un'OPZIONE ESPLICITA di chi
256
+ * chiama, non un ripiego automatico: una sovrascrittura involontaria e un'aggiunta involontaria
257
+ * sono due danni diversi, e nessuno dei due deve poter succedere per distrazione.
258
+ * ⛔ Accodare a una CARTELLA è esattamente il guasto che BC-11 è venuto a curare (`EISDIR:
259
+ * illegal operation on a directory`): qui si guarda cosa c'è PRIMA e si risponde a parole.
260
+ *
261
+ * @param {{cartella:string, nome:string, bytes:Uint8Array|Buffer|string, modalita?:'nuovo'|'accoda'}} input
262
+ */
263
+ export async function creaFileWorkspace({ cartella, nome, bytes, modalita = 'nuovo' }, deps = {}) {
264
+ if (modalita !== 'nuovo' && modalita !== 'accoda') {
265
+ throw new WorkspaceFileError('Modalità non valida: "nuovo" (rifiuta un nome già preso) o "accoda" (aggiunge in coda)');
266
+ }
267
+ if (
268
+ typeof nome !== 'string' || nome.length === 0 || nome.length > 255
269
+ || nome.includes('/') || nome.includes('\\') || nome.includes('\0')
270
+ || nome === '.' || nome === '..'
271
+ ) {
272
+ throw new WorkspaceFileError('Nome file non valido — un nome, non un percorso');
273
+ }
274
+ /*
275
+ * ⛔ BC-11 — il TIPO prima del tetto, e detto a parole. Senza questo controllo `fsp.writeFile`
276
+ * lancia `ERR_INVALID_ARG_TYPE` (riprodotto l'11/09/2026), che per chi legge il risultato
277
+ * dell'attrezzo è indistinguibile da un guasto del disco.
278
+ */
279
+ const byteValidi = typeof bytes === 'string' || ArrayBuffer.isView(bytes) || bytes instanceof ArrayBuffer;
280
+ if (!byteValidi) {
281
+ throw new WorkspaceFileError(
282
+ 'Contenuto mancante o non valido: servono byte o testo, e ne è arrivato '
283
+ + `${bytes === undefined ? 'nessuno' : typeof bytes}. Rimanda la chiamata con il contenuto.`,
284
+ 'CONTENT_INVALID',
285
+ );
286
+ }
287
+ const dimensione = typeof bytes === 'string' ? Buffer.byteLength(bytes, 'utf8') : bytes.byteLength;
288
+ if (dimensione > DIMENSIONE_MASSIMA_CREAZIONE) {
289
+ throw new WorkspaceFileError(
290
+ `Contenuto troppo grande (${Math.round(dimensione / 1024 / 1024)} MB, tetto `
291
+ + `${DIMENSIONE_MASSIMA_CREAZIONE / 1024 / 1024} MB). Scrivilo in più pezzi, aggiungendo ogni pezzo in coda.`,
292
+ 'CONTENT_TOO_LARGE',
293
+ );
294
+ }
295
+ const radiceReale = (deps.realpathSyncFn ?? realpathSync)(cartella);
296
+ const destinazione = join(radiceReale, nome);
297
+ if (!isPathInside(radiceReale, destinazione)) throw new WorkspaceFileError('Destinazione fuori dalla cartella del workspace');
298
+ const accessFn = deps.accessFn ?? fsp.access;
299
+ const esisteGia = await accessFn(destinazione).then(() => true, () => false);
300
+ if (esisteGia && modalita === 'nuovo') throw new WorkspaceFileError('Esiste già un file con questo nome', 'FILE_EXISTS');
301
+ if (esisteGia) {
302
+ /*
303
+ * ⛔ La domanda «è un file?» si fa PRIMA di aprire: `appendFile` su una cartella darebbe
304
+ * `EISDIR` — lo stesso errore di sistema operativo che l'owner ha visto cinque volte nella
305
+ * sessione `8dde6bff` e che nessuno poteva leggere.
306
+ */
307
+ const stat = await (deps.statFn ?? fsp.stat)(destinazione);
308
+ if (!stat.isFile()) throw new WorkspaceFileError('Esiste già una cartella con questo nome: non ci si può accodare', 'NOT_A_FILE');
309
+ if (stat.size + dimensione > DIMENSIONE_MASSIMA_CREAZIONE) {
310
+ throw new WorkspaceFileError(
311
+ `Il file arriverebbe a ${Math.round((stat.size + dimensione) / 1024 / 1024)} MB, oltre il tetto di `
312
+ + `${DIMENSIONE_MASSIMA_CREAZIONE / 1024 / 1024} MB.`,
313
+ 'CONTENT_TOO_LARGE',
314
+ );
315
+ }
316
+ // ⛔ `appendFile` (flag 'a'), MAI leggi-concatena-riscrivi: quest'ultima perderebbe in silenzio
317
+ // ciò che qualcun altro ha scritto fra la lettura e la riscrittura.
318
+ await (deps.appendFileFn ?? fsp.appendFile)(destinazione, bytes);
319
+ return { percorso: nome, accodato: true, byteTotali: stat.size + dimensione };
320
+ }
321
+ await (deps.writeFileFn ?? fsp.writeFile)(destinazione, bytes);
322
+ return { percorso: nome, ...(modalita === 'accoda' ? { accodato: false, byteTotali: dimensione } : {}) };
323
+ }
324
+
325
+ /**
326
+ * "Sposta" — drag&drop (piano `elegant-spinning-dongarra.md`, owner:
327
+ * "nella lista files devo poter draggare i file"). Sposta un file/
328
+ * cartella in un'ALTRA cartella dello stesso workspace, mantenendo il
329
+ * nome — `cartellaDestinazione` è un percorso relativo ('' = radice)
330
+ * che deve esistere ED essere una cartella. Non sovrascrive mai un
331
+ * nome già occupato nella destinazione. `isPathInside(reale,
332
+ * destinazioneCartellaReale)` copre da sola sia "dentro se stessa" sia
333
+ * "dentro un proprio discendente" (torna true anche a parità di
334
+ * percorso — verificato leggendo path-policy.mjs, non presunto): senza
335
+ * questa guardia una cartella spostata dentro se stessa lascerebbe
336
+ * `fs.rename` fallire a metà con un errore di sistema operativo
337
+ * travestito da azione riuscita.
338
+ */
339
+ export async function spostaFile({ cartella, percorso, cartellaDestinazione }, deps = {}) {
340
+ const { radiceReale, reale } = risolviPercorsoEsistente(cartella, percorso, deps);
341
+ const realpathSyncFn = deps.realpathSyncFn ?? realpathSync;
342
+ let destinazioneCartellaReale;
343
+ if (cartellaDestinazione === '') {
344
+ destinazioneCartellaReale = radiceReale;
345
+ } else {
346
+ if (typeof cartellaDestinazione !== 'string' || isAbsolute(cartellaDestinazione)) {
347
+ throw new WorkspaceFileError('Cartella di destinazione non valida');
348
+ }
349
+ try {
350
+ destinazioneCartellaReale = realpathSyncFn(join(cartella, cartellaDestinazione));
351
+ } catch {
352
+ throw new WorkspaceFileError('Cartella di destinazione non trovata', 'FILE_NOT_FOUND');
353
+ }
354
+ if (!isPathInside(radiceReale, destinazioneCartellaReale)) {
355
+ throw new WorkspaceFileError('Destinazione fuori dalla cartella della sessione');
356
+ }
357
+ }
358
+ const statDestinazione = await (deps.statFn ?? fsp.stat)(destinazioneCartellaReale).catch(() => null);
359
+ if (!statDestinazione || !statDestinazione.isDirectory()) {
360
+ throw new WorkspaceFileError('La destinazione non è una cartella');
361
+ }
362
+ if (isPathInside(reale, destinazioneCartellaReale)) {
363
+ throw new WorkspaceFileError('Non puoi spostare un elemento dentro se stesso o un suo discendente');
364
+ }
365
+ const nome = reale.split(sep).pop();
366
+ const destinazione = join(destinazioneCartellaReale, nome);
367
+ const accessFn = deps.accessFn ?? fsp.access;
368
+ const esisteGia = await accessFn(destinazione).then(() => true, () => false);
369
+ if (esisteGia) throw new WorkspaceFileError('Esiste già un elemento con questo nome nella destinazione', 'FILE_EXISTS');
370
+ await (deps.renameFn ?? fsp.rename)(reale, destinazione);
371
+ const nuovoPercorso = relative(radiceReale, destinazione).split(sep).join('/');
372
+ return { nuovoPercorso };
373
+ }
374
+
375
+ /**
376
+ * "Copia" — owner: "non esiste il comando copia". Duplica un file/
377
+ * cartella nella STESSA posizione, con un nome tipo "nome (copia).ext"
378
+ * (pattern Explorer/Finder — verificato via ricerca web, non inventato:
379
+ * è la convenzione standard di ogni file manager desktop). Se anche
380
+ * quello esiste già, prova "nome (copia 2).ext" e così via, fino a un
381
+ * tetto dichiarato — mai un ciclo infinito su un caso patologico.
382
+ */
383
+ export async function copiaFile({ cartella, percorso }, deps = {}) {
384
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
385
+ const accessFn = deps.accessFn ?? fsp.access;
386
+ const cartellaGenitore = dirname(reale);
387
+ const nomeOriginale = reale.split(sep).pop();
388
+ const stat = await (deps.statFn ?? fsp.stat)(reale);
389
+ const puntoEstensione = nomeOriginale.lastIndexOf('.');
390
+ // niente estensione separata per una cartella, o per un file che INIZIA con un punto (es. ".gitignore" resta intero in "base")
391
+ const haEstensione = !stat.isDirectory() && puntoEstensione > 0;
392
+ const base = haEstensione ? nomeOriginale.slice(0, puntoEstensione) : nomeOriginale;
393
+ const estensione = haEstensione ? nomeOriginale.slice(puntoEstensione) : '';
394
+
395
+ const TETTO_TENTATIVI = 1000;
396
+ let destinazione = null;
397
+ for (let n = 1; n <= TETTO_TENTATIVI; n += 1) {
398
+ const suffisso = n === 1 ? ' (copia)' : ` (copia ${n})`;
399
+ const candidato = join(cartellaGenitore, `${base}${suffisso}${estensione}`);
400
+ const esisteGia = await accessFn(candidato).then(() => true, () => false);
401
+ if (!esisteGia) { destinazione = candidato; break; }
402
+ }
403
+ if (!destinazione) throw new WorkspaceFileError('Troppe copie già esistenti con questo nome');
404
+
405
+ await (deps.cpFn ?? fsp.cp)(reale, destinazione, { recursive: true, errorOnExist: true });
406
+ const radiceReale = realpathSync(cartella);
407
+ const nuovoPercorso = relative(radiceReale, destinazione).split(sep).join('/');
408
+ return { nuovoPercorso };
409
+ }
410
+
411
+ /**
412
+ * "Nuovo file"/"Nuova cartella" — CRUD manuale dell'owner ("comandi
413
+ * crud in generale"), in QUALSIASI punto dell'albero — a differenza di
414
+ * `creaFileWorkspace` sopra, che resta vincolata alla radice per
415
+ * `document_create` (un bisogno diverso, dell'AGENTE, non toccato qui).
416
+ * `percorsoBase` è la cartella dove creare ('' = radice); deve
417
+ * esistere ED essere una cartella se non vuoto.
418
+ */
419
+ export async function creaVoceWorkspace({ cartella, percorsoBase, nome, tipo }, deps = {}) {
420
+ if (tipo !== 'file' && tipo !== 'cartella') {
421
+ throw new WorkspaceFileError('Tipo non valido: "file" o "cartella"');
422
+ }
423
+ if (
424
+ typeof nome !== 'string' || nome.length === 0 || nome.length > 255
425
+ || nome.includes('/') || nome.includes('\\') || nome.includes('\0')
426
+ || nome === '.' || nome === '..'
427
+ ) {
428
+ throw new WorkspaceFileError('Nome non valido — un nome, non un percorso');
429
+ }
430
+ const realpathSyncFn = deps.realpathSyncFn ?? realpathSync;
431
+ const radiceReale = realpathSyncFn(cartella);
432
+ let cartellaBaseReale = radiceReale;
433
+ if (percorsoBase) {
434
+ if (typeof percorsoBase !== 'string' || isAbsolute(percorsoBase)) {
435
+ throw new WorkspaceFileError('Cartella base non valida');
436
+ }
437
+ try {
438
+ cartellaBaseReale = realpathSyncFn(join(cartella, percorsoBase));
439
+ } catch {
440
+ throw new WorkspaceFileError('Cartella base non trovata', 'FILE_NOT_FOUND');
441
+ }
442
+ if (!isPathInside(radiceReale, cartellaBaseReale)) {
443
+ throw new WorkspaceFileError('Cartella base fuori dal workspace della sessione');
444
+ }
445
+ const statBase = await (deps.statFn ?? fsp.stat)(cartellaBaseReale).catch(() => null);
446
+ if (!statBase || !statBase.isDirectory()) throw new WorkspaceFileError('La cartella base non è una cartella');
447
+ }
448
+ const destinazione = join(cartellaBaseReale, nome);
449
+ if (!isPathInside(radiceReale, destinazione)) throw new WorkspaceFileError('Destinazione fuori dal workspace della sessione');
450
+ const accessFn = deps.accessFn ?? fsp.access;
451
+ const esisteGia = await accessFn(destinazione).then(() => true, () => false);
452
+ if (esisteGia) throw new WorkspaceFileError('Esiste già un elemento con questo nome', 'FILE_EXISTS');
453
+ if (tipo === 'cartella') await (deps.mkdirFn ?? fsp.mkdir)(destinazione);
454
+ else await (deps.writeFileFn ?? fsp.writeFile)(destinazione, '');
455
+ const percorso = relative(radiceReale, destinazione).split(sep).join('/');
456
+ return { percorso };
457
+ }
458
+
459
+ /**
460
+ * "Rivela in Esplora File" — SOLO Windows (`explorer.exe`), dichiarato
461
+ * non simulato altrove. Un SOLO argomento argv (`/select,<percorso>`,
462
+ * verificato via ricerca web la sintassi esatta — niente spazio dopo la
463
+ * virgola): `execFile` non passa da una shell, quindi zero rischio di
464
+ * injection anche con un percorso pieno di caratteri strani.
465
+ *
466
+ * ⛔ `explorer.exe` torna quasi sempre un codice di uscita diverso da
467
+ * zero ANCHE quando ha aperto la finestra correttamente (comportamento
468
+ * noto, non un guasto di questo codice) — l'unico fallimento vero da
469
+ * segnalare è `execFile` che non trova l'eseguibile affatto.
470
+ */
471
+ /*
472
+ * ⛔⛔ 10/09/2026 — «APRI», l'azione Windows che mancava. Owner: «con CRUD completo e AZIONI
473
+ * WINDOWS», e poi «fai in modo che le azioni e i pulsanti della libreria funzionino a schermo».
474
+ *
475
+ * Il problema, misurato: la rotta che serve i byte manda `Content-Disposition: attachment` e
476
+ * `application/octet-stream` — giusto per uno SCARICO, ma vuol dire che un pulsante «Apri» agganciato
477
+ * lì farebbe scaricare il file una seconda volta invece di aprirlo. Su Windows «aprire» ha un
478
+ * significato preciso e diverso: lo apre il programma associato all'estensione (Word per un .docx,
479
+ * il visualizzatore foto per un .jpg).
480
+ *
481
+ * ⛔ `explorer.exe <percorso>` è la stessa porta già usata da `rivelaInEsploraFile` qui sotto, senza
482
+ * `/select`: con il percorso di un FILE, Explorer lo apre col programma associato. Riusa la stessa
483
+ * politica di processo (`EXPLORER_PROCESS_POLICY`), che ammette solo `explorer.exe`: nessun
484
+ * eseguibile nuovo, nessuna shell, e il percorso resta un ARGOMENTO — mai una stringa di comando,
485
+ * che con un nome contenente `&` o `"` sarebbe un'iniezione.
486
+ * ⛔ E come la sorella: fuori da Windows non finge, dichiara che non è disponibile.
487
+ */
488
+ /*
489
+ * ⛔⛔ 11/09/2026 — L'UNICA PORTA verso `explorer.exe`, estratta dalle tre azioni che la usavano con
490
+ * lo stesso identico preambolo copiato tre volte. Non è un riordino estetico: il difetto del
491
+ * 10/09 (il richiamo finito nel posto delle OPZIONI, la promessa mai risolta, la richiesta HTTP
492
+ * appesa per sempre) viveva in DUE copie e andava curato in due posti. «Una difesa copiata è una
493
+ * difesa che un giorno diverge» — qui la forma del richiamo, la politica di processo e la regola
494
+ * «un codice d'uscita non-zero non è un guasto» stanno scritte una volta sola.
495
+ * ⛔ Gli argomenti restano ARGOMENTI (`execFile`, mai una shell): un nome con `&`, `"` o uno spazio
496
+ * non può diventare un comando.
497
+ */
498
+ function lanciaExplorer(argomenti, cartella, deps) {
499
+ const politica = (comando, args, opzioni, callback) => EXPLORER_PROCESS_POLICY.execFile(comando, args, {
500
+ ...opzioni,
501
+ cwd: opzioni?.cwd ?? cartella,
502
+ }, callback);
503
+ const grezza = deps.execFileFn ?? politica;
504
+ /* Chi inietta un finto a tre parametri continua a funzionare: l'ultimo argomento è il richiamo. */
505
+ const execFileFn = (comando, args, opzioni, callback) => (
506
+ grezza.length <= 3
507
+ ? grezza(comando, args, callback)
508
+ : grezza(comando, args, opzioni, callback)
509
+ );
510
+ return new Promise((ok, no) => {
511
+ execFileFn('explorer.exe', argomenti, {}, (errore) => {
512
+ if (errore && errore.code === 'ENOENT') { no(errore); return; }
513
+ ok(); // ⛔ explorer.exe esce con codici non-zero anche quando ha funzionato: è noto, e non è un guasto
514
+ });
515
+ });
516
+ }
517
+
518
+ /*
519
+ * ⛔⛔⛔ 11/09/2026 — «APRI IN ESPLORA FILE», owner dal vivo sulla foto dell'albero: «se faccio tasto
520
+ * destro sulla ROOT deve poter spuntare "Apri", cioè devo poterla aprire su Windows».
521
+ *
522
+ * Questa è la sorella di `apriFileConProgrammaPredefinito` che sa dire di sì anche a una CARTELLA e
523
+ * alla RADICE della sessione (`percorso: ''`). Perché non si è semplicemente allargata quella:
524
+ * · la Libreria la usa per i FILE e il suo rifiuto su una cartella è un contratto provato
525
+ * («⛔ APRI, AL CONTRARIO: una cartella non si apre col programma») — un rifiuto che passa a un sì
526
+ * non è un allargamento, è un'altra funzione;
527
+ * · qui invece «aprire» una cartella ha un significato Windows preciso e diverso.
528
+ * ⛔ Ma la PORTA è la stessa (`lanciaExplorer`) e la DIFESA è la stessa (`risolviPercorsoEsistente`,
529
+ * con l'unica deroga dichiarata: la radice è ammessa) — mai una seconda validazione da tenere
530
+ * allineata alla prima.
531
+ *
532
+ * Ricerca 11/09/2026, PRIMA di scrivere (WebSearch esaurito per la sessione ⇒ fonti primarie via
533
+ * WebFetch, dichiarato nel rapporto):
534
+ * · Microsoft Learn, «Launching Applications (ShellExecute, ShellExecuteEx, SHELLEXECUTEINFO)»
535
+ * (learn.microsoft.com/en-us/windows/win32/shell/launch, pagina datata 2025-07-02, letta
536
+ * 11/09/2026): il verbo predefinito di un oggetto della Shell è `open`, e vale per «file or
537
+ * folder objects» — cioè aprire una CARTELLA è la stessa operazione di aprire un file, con un
538
+ * gestore diverso (la finestra di Esplora file invece del programma associato all'estensione).
539
+ * ⇒ `explorer.exe <cartella>` è la stessa porta già in uso per i file, senza `/select`.
540
+ * · Stessa pagina: `/select` non compare fra i verbi — è lo switch di Explorer che apre la
541
+ * cartella GENITORE con l'elemento evidenziato. ⇒ sulla radice sarebbe la cosa sbagliata: aprirebbe
542
+ * il genitore del workspace, cioè un posto FUORI dal workspace. «Apri» e «Rivela» restano due
543
+ * azioni diverse, e questa è quella che l'owner ha chiesto.
544
+ */
545
+ export async function apriInEsploraFile({ cartella, percorso }, deps = {}) {
546
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps, true);
547
+ if ((deps.platform ?? process.platform) !== 'win32') {
548
+ throw new WorkspaceFileError('Disponibile solo su Windows', 'PLATFORM_UNSUPPORTED');
549
+ }
550
+ await lanciaExplorer([reale], cartella, deps);
551
+ return { aperto: true };
552
+ }
553
+
554
+ export async function apriFileConProgrammaPredefinito({ cartella, percorso }, deps = {}) {
555
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
556
+ if ((deps.platform ?? process.platform) !== 'win32') {
557
+ throw new WorkspaceFileError('Disponibile solo su Windows', 'PLATFORM_UNSUPPORTED');
558
+ }
559
+ const stat = await (deps.statFn ?? fsp.stat)(reale);
560
+ /* ⛔ Una cartella si «rivela», non si «apre col programma»: due azioni diverse, due bottoni diversi. */
561
+ if (!stat.isFile()) throw new WorkspaceFileError('Non è un file: usa «Mostra nella cartella»');
562
+ /*
563
+ * ⛔⛔ 10/09/2026 — QUESTA RIGA NON HA MAI FUNZIONATO CON LA POLITICA VERA, e nessun test se n'era
564
+ * accorto. Il wrapper dichiarava `(comando, argomenti, opzioni, callback)` ma qui sotto veniva
565
+ * chiamato con TRE argomenti: la funzione di richiamo finiva nel posto delle OPZIONI, e
566
+ * `callback` restava `undefined` ⇒ `execFile` non richiamava nessuno, la promessa non si
567
+ * risolveva mai, e la richiesta HTTP restava appesa per sempre.
568
+ * ⛔ Misurato dal vivo il 10/09 premendo il bottone sul 4174: la rotta non rispondeva entro 15
569
+ * secondi, e `curl` chiudeva con «0 bytes received». Fuori dal server, la funzione restava appesa
570
+ * oltre gli 8 secondi. Con `execFile` diretto, invece, il richiamo arriva in **126 ms**.
571
+ * ⛔ Perché i test erano verdi: iniettavano un finto a TRE parametri `(comando, argomenti, cb)`,
572
+ * cioè con la forma sbagliata — un finto che non imita il vero misura il finto. Ora il wrapper
573
+ * accetta entrambe le forme, e una prova nuova chiama con QUATTRO argomenti come fa il codice.
574
+ */
575
+ await lanciaExplorer([reale], cartella, deps);
576
+ return { aperto: true };
577
+ }
578
+
579
+ export async function rivelaInEsploraFile({ cartella, percorso }, deps = {}) {
580
+ const { reale } = risolviPercorsoEsistente(cartella, percorso, deps);
581
+ if ((deps.platform ?? process.platform) !== 'win32') {
582
+ throw new WorkspaceFileError('Disponibile solo su Windows', 'PLATFORM_UNSUPPORTED');
583
+ }
584
+ /* ⛔ Stessa porta della sorella qui sopra (`lanciaExplorer`), e per lo stesso motivo: chiamato con
585
+ tre argomenti il richiamo finiva nel posto delle opzioni e la promessa non si risolveva mai.
586
+ Vedi là la storia — e ora quella cura vive in UN posto solo, non in tre copie. */
587
+ await lanciaExplorer([`/select,${reale}`], cartella, deps);
588
+ return { rivelato: true };
589
+ }