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,220 @@
1
+ /**
2
+ * session-store.mjs — FASE L (30/8), piano `elegant-spinning-dongarra.md`.
3
+ * Owner: "ricerca web competitor" sulla domanda "un riavvio del
4
+ * processo perde una sessione in corso, è mai successo per davvero?".
5
+ *
6
+ * ⛔⛔⛔ La ricerca ha RIDEFINITO la fase, non solo risposto alla domanda.
7
+ * Verificato alla fonte (non presunto): `session-registry.mjs` dichiara
8
+ * fin dalla sua prima riga "solo in memoria, deliberato... non ancora
9
+ * aperto" — un riavvio del server perde OGNI sessione, non solo quelle
10
+ * in corso, e non è mai stato diversamente (`.sessions/` su disco che
11
+ * un piano precedente citava non esiste in questo file — verificato
12
+ * con una ricerca diretta, zero riscontri).
13
+ *
14
+ * Cosa fa davvero lo stato dell'arte in questo spazio: transcript JSONL
15
+ * append-only come fonte di verità per il replay
16
+ * ("rollout files"), un indice separato (SQLite, solo per liste
17
+ * veloci — non necessario alla scala di questo prodotto). ⭐⭐⭐ Onestamente
18
+ * ammesso altrove: "if a transport failure occurs early enough...
19
+ * no resumable artefacts may be written" — un ripristino a metà turno
20
+ * non è mai garantito, nemmeno lì. Il ripristino VERO che si offre in
21
+ * questi casi è
22
+ * "rilettura della trascrizione", non "il modello riprende da dove
23
+ * stava" — lo stesso confine onesto che questo modulo dichiara.
24
+ *
25
+ * ⛔ Trovato durante la ricerca, non ipotetico: riscrivere l'intero file
26
+ * invece di solo accodare è una classe di bug nota (perdita di dati su
27
+ * crash per una riscrittura non atomica della trascrizione) — la
28
+ * classe di errore che questo modulo evita per costruzione: MAI un
29
+ * `writeFile` che sostituisce il file intero, solo `appendFile`. Una
30
+ * riga JSONL è atomica sui filesystem POSIX: un crash a metà riga
31
+ * lascia al più UNA riga corrotta, mai le precedenti.
32
+ *
33
+ * ⛔ Nessun fsync esplicito dopo ogni riga (differenza dichiarata dal
34
+ * pattern "production-hardened" trovato in ricerca): a differenza di
35
+ * un ledger finanziario, questo è uno strumento locale owner-only — la
36
+ * finestra di perdita è quella del buffer del sistema operativo (tipicamente
37
+ * pochi millisecondi), non l'intera sessione. Scelta esplicita, non
38
+ * un taglio silenzioso.
39
+ */
40
+ import { promises as fsp, mkdirSync, appendFileSync } from 'node:fs';
41
+ import { join } from 'node:path';
42
+
43
+ export class SessionStoreError extends Error {
44
+ constructor(message, code = 'SESSION_STORE_FAILED') {
45
+ super(message);
46
+ this.name = 'SessionStoreError';
47
+ this.code = code;
48
+ }
49
+ }
50
+
51
+ const ESTENSIONE = '.jsonl';
52
+
53
+ function percorsoDi(cartellaStore, sessionId) {
54
+ // ⛔ sessionId è sempre un randomUUID() generato da questo stesso processo (mai testo esterno) — nessuna sanificazione di percorso richiesta, a differenza di un nome file scelto dal modello (vedi library-store.mjs).
55
+ return join(cartellaStore, `${sessionId}${ESTENSIONE}`);
56
+ }
57
+
58
+ /**
59
+ * Accoda UNA riga — mai una riscrittura del file intero (la classe di
60
+ * bug vista in ricerca, vedi la testa del file). `record` è già serializzabile (un evento AG-UI,
61
+ * o l'intestazione, o il record `messaggiFinali`) — questo modulo non
62
+ * sa cosa contiene, solo che va in coda.
63
+ */
64
+ /*
65
+ * ⭐⭐⭐ 04/9 — W0-07: UNA CODA PER FILE. `fsp.appendFile` non è atomica per
66
+ * un record più grande di una singola scrittura di sistema: il contenuto
67
+ * viene spezzato, e un secondo append concorrente sullo stesso file si
68
+ * infila in mezzo. Il risultato è una riga JSONL illeggibile — cioè una
69
+ * sessione che `ripristina()` scarta per sempre, in silenzio.
70
+ *
71
+ * ⛔ Non è teoria: nello store dell'owner il file `b7b1b7d2…` (31/08) aveva
72
+ * 4 righe rotte, la prima spezzata a **1.572.866 byte, esattamente 1,5
73
+ * MiB**, e le successive iniziavano a metà percorso pur finendo con
74
+ * `_sequenza` coerenti. Erano `WorkspaceChanged` da 1-1,5 MB l'uno (il
75
+ * workspace era `C:\` intero). Quella conversazione — vera, conclusa con
76
+ * successo — è stata invisibile per quattro giorni; l'ho recuperata a mano
77
+ * il 04/09 buttando solo le righe illeggibili.
78
+ *
79
+ * ⇒ Le scritture dello STESSO file si mettono in fila. File diversi non si
80
+ * aspettano fra loro (la chiave della coda è il percorso), quindi una
81
+ * sessione lenta non rallenta le altre. Un errore su una scrittura non
82
+ * blocca la coda: la successiva parte comunque.
83
+ *
84
+ * ⭐ Ricerca web del 04/09 (obbligo dell'owner, fatta PRIMA di considerare
85
+ * chiusa questa cura): `O_APPEND` è atomico **per singola chiamata di
86
+ * scrittura** — due scrittori non si sovrappongono finché ogni record entra
87
+ * in UNA chiamata; un payload grande viene però spezzato in più chiamate, ed
88
+ * è lì che si intrecciano. La forma raccomandata è esattamente questa: «le
89
+ * scritture concorrenti sullo stesso file si mettono in coda e si
90
+ * serializzano con le Promise, mentre file diversi restano in parallelo».
91
+ * Due cose che la ricerca aggiunge e che qui NON sono risolte, registrate
92
+ * come debito nel ledger: (a) per un log si consiglia uno stream persistente
93
+ * invece di aprire e chiudere il file a ogni evento; (b) una scrittura
94
+ * riuscita vive nella cache del kernel finché non c'è un `fsync`, quindi un
95
+ * crash può perdere gli ultimi eventi — l'invariante da tenere è non
96
+ * trattare mai come record dei byte di coda non validati, e `ripristina()`
97
+ * già distingue l'ultima riga spezzata (crash a metà append) da una riga
98
+ * rotta in mezzo (danno vero).
99
+ */
100
+ const codeDiScrittura = new Map();
101
+
102
+ export async function registraRiga({ cartellaStore, sessionId, record, durable = false }, deps = {}) {
103
+ const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
104
+ const appendFileFn = deps.appendFileFn ?? fsp.appendFile;
105
+ const percorso = percorsoDi(cartellaStore, sessionId);
106
+ // ⛔ Serializzato SUBITO, non dentro la coda: `record` potrebbe cambiare mentre questa scrittura aspetta il suo turno.
107
+ const riga = `${JSON.stringify(record)}\n`;
108
+ const precedente = codeDiScrittura.get(percorso) ?? Promise.resolve();
109
+ const corrente = precedente.catch(() => {}).then(async () => {
110
+ await mkdirFn(cartellaStore, { recursive: true });
111
+ await appendFileFn(percorso, riga, durable ? { encoding: 'utf8', flush: true } : 'utf8');
112
+ });
113
+ codeDiScrittura.set(percorso, corrente);
114
+ // La mappa non deve crescere per sempre: chi è l'ultimo della fila la ripulisce.
115
+ corrente.catch(() => {}).finally(() => {
116
+ if (codeDiScrittura.get(percorso) === corrente) codeDiScrittura.delete(percorso);
117
+ });
118
+ return corrente;
119
+ }
120
+
121
+ /**
122
+ * ⭐⭐⭐ FASE L, trovato dalla verifica DAL VIVO (30/8) — non ipotizzato: un
123
+ * server VERO, una sessione VERA, uccisa (`child.kill()`, TerminateProcess
124
+ * su Windows — un crash vero, nessun handler di spegnimento gira) **6 ms**
125
+ * dopo aver ricevuto il sessionId dal client. `ripristina()` sul riavvio:
126
+ * "114/115" — la sessione appena creata non c'era proprio, perché la SUA
127
+ * riga di intestazione (`registraRiga`, fire-and-forget) non aveva ancora
128
+ * toccato il disco quando il processo è morto. Non un dato corrotto: la
129
+ * sessione non è mai esistita per `ripristina()`, un buco onesto ma
130
+ * evitabile — un crash nella manciata di millisecondi dopo la creazione
131
+ * perde l'intera sessione, in silenzio.
132
+ *
133
+ * ⛔ La cura NON è rendere `avvia()`/`avviaLibero()` asincrone (~110
134
+ * call-site di test, l'intero contratto sincrono di `session-registry.mjs`
135
+ * — sproporzionato per una finestra di pochi millisecondi, stessa strada
136
+ * già scartata per lo stesso motivo in FASE E). La cura è che la SOLA
137
+ * intestazione — una scrittura piccola, una volta per sessione, mai per
138
+ * ogni evento — usi l'API fs SINCRONA: `avvia()` può restare sincrona e
139
+ * il client non riceve MAI un sessionId la cui intestazione non sia già
140
+ * durevole sul disco. Gli eventi/messaggiFinali successivi restano
141
+ * fire-and-forget (`registraRiga` sopra, invariata) — quella finestra
142
+ * resta (dichiarata nel commento di testa del modulo, "pochi
143
+ * millisecondi... scelta esplicita"), ma non può più cancellare
144
+ * l'ESISTENZA stessa della sessione.
145
+ */
146
+ export function registraRigaSync({ cartellaStore, sessionId, record }, deps = {}) {
147
+ const mkdirSyncFn = deps.mkdirSyncFn ?? mkdirSync;
148
+ const appendFileSyncFn = deps.appendFileSyncFn ?? appendFileSync;
149
+ mkdirSyncFn(cartellaStore, { recursive: true });
150
+ appendFileSyncFn(percorsoDi(cartellaStore, sessionId), `${JSON.stringify(record)}\n`, 'utf8');
151
+ }
152
+
153
+ /**
154
+ * Elenca gli id delle sessioni persistite — per la ricostruzione
155
+ * all'avvio del server. Cartella assente ⇒ `[]`, mai un errore (un
156
+ * primo avvio non ha ancora nessuna sessione salvata).
157
+ */
158
+ export async function elencaSessioniPersistite({ cartellaStore }, deps = {}) {
159
+ const readdirFn = deps.readdirFn ?? fsp.readdir;
160
+ let voci;
161
+ try {
162
+ voci = await readdirFn(cartellaStore, { withFileTypes: true });
163
+ } catch (errore) {
164
+ if (errore?.code === 'ENOENT') return [];
165
+ throw new SessionStoreError(`Impossibile leggere ${cartellaStore}: ${errore.message}`, 'SESSION_STORE_READ_FAILED');
166
+ }
167
+ return voci
168
+ .filter((v) => v.isFile() && v.name.endsWith(ESTENSIONE))
169
+ .map((v) => v.name.slice(0, -ESTENSIONE.length));
170
+ }
171
+
172
+ /**
173
+ * ⭐⭐⭐ 30/8, QA visiva (Task 14) — trovato dal vivo: nessun modo di
174
+ * eliminare una sessione, né qui né lato client. 144+ sessioni
175
+ * accumulate in un solo giro di QA senza possibilità di pulizia.
176
+ * Rimuove il file persistito — `ENOENT` è un esito onesto (già
177
+ * cancellato, o mai persistito perché senza `cartellaStore`), non un
178
+ * errore: stesso principio di `leggiRegistro` sopra.
179
+ */
180
+ export async function eliminaSessionePersistita({ cartellaStore, sessionId }, deps = {}) {
181
+ const unlinkFn = deps.unlinkFn ?? fsp.unlink;
182
+ try {
183
+ await unlinkFn(percorsoDi(cartellaStore, sessionId));
184
+ } catch (errore) {
185
+ if (errore?.code === 'ENOENT') return;
186
+ throw new SessionStoreError(`Impossibile eliminare la sessione ${sessionId}: ${errore.message}`, 'SESSION_STORE_DELETE_FAILED');
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Legge un registro per intero — una riga JSON per riga del file.
192
+ * ⭐⭐⭐ L'ULTIMA riga, se non è JSON valido, viene SCARTATA in silenzio
193
+ * (mai un errore che perde l'intero file): è esattamente il caso di
194
+ * un crash a metà scrittura, l'unica corruzione che l'append-only
195
+ * ammette per costruzione — "una riga rotta non invalida le
196
+ * precedenti", verificato in ricerca, non presunto. Una riga NON
197
+ * ultima malformata è invece un errore dichiarato: quello indica un
198
+ * file danneggiato in un altro modo, non un crash a metà append.
199
+ */
200
+ export async function leggiRegistro({ cartellaStore, sessionId }, deps = {}) {
201
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
202
+ let testo;
203
+ try {
204
+ testo = await readFileFn(percorsoDi(cartellaStore, sessionId), 'utf8');
205
+ } catch (errore) {
206
+ if (errore?.code === 'ENOENT') return null;
207
+ throw new SessionStoreError(`Impossibile leggere la sessione ${sessionId}: ${errore.message}`, 'SESSION_STORE_READ_FAILED');
208
+ }
209
+ const righe = testo.split('\n').filter((r) => r.trim() !== '');
210
+ const record = [];
211
+ for (let i = 0; i < righe.length; i++) {
212
+ try {
213
+ record.push(JSON.parse(righe[i]));
214
+ } catch {
215
+ if (i === righe.length - 1) break; // ultima riga, possibile crash a metà scrittura: scartata, non fatale
216
+ throw new SessionStoreError(`La sessione ${sessionId} ha una riga corrotta (non l'ultima): il file non è un crash a metà append, è danneggiato altrove.`, 'SESSION_STORE_CORRUPT');
217
+ }
218
+ }
219
+ return record;
220
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * sessione-pronta.mjs — «questa sessione può partire con QUESTO modello?» (CLI-REQ-05, punto 1).
3
+ *
4
+ * Estratta da `server.mjs` il 17/09/2026 perché lì dentro era una chiusura che nessuna prova poteva
5
+ * chiamare: i revisori l'avevano misurata ricopiandone i byte. Una regola che decide se un giro parte
6
+ * merita un nome e delle prove sue.
7
+ *
8
+ * ⛔⛔⛔ 17/09/2026, difetto trovato dalla corsia della CLI DOPO la fusione (`ba420a95`) e riprodotto qui
9
+ * con lo store VERO prima di toccare una riga (`scratchpad/sonda-panchina.mjs`): una chiave messa in
10
+ * PANCHINA (rifiutata dal fornitore, o in attesa dopo un 429) rispondeva ancora «pronto».
11
+ * hasKey('deepseek') → true (conta OGNI chiave del pool, panchina compresa)
12
+ * getKey('deepseek') → null (passa da `scegliChiave`, che salta chi è in panchina)
13
+ * Il ramo OpenRouter usava già `getKey`, tutti gli altri `hasKey`: la sessione partiva, e il giro moriva
14
+ * subito dopo senza una chiave utilizzabile. L'owner l'ha incontrato nella CLI lo stesso giorno.
15
+ * ⇒ «Pronto» vuol dire UNA CHIAVE UTILIZZABILE ADESSO, per ogni fornitore. E quando le chiavi ci sono ma
16
+ * stanno tutte in panchina, il rifiuto lo DICE — con la causa in parole umane e fino a quando — invece
17
+ * di «Manca la chiave», che manderebbe la persona a incollare una chiave che ha già.
18
+ *
19
+ * ⛔ SINCRONA per contratto: `avviaESegui` nel registro non è `async` e non lo diventa (un solo tick fa
20
+ * cadere 148 prove, misurato il 10/09). Lo store risponde in memoria.
21
+ */
22
+ import { REGISTRO_FORNITORI } from './provider-registry.mjs';
23
+ import { separaFonteModello } from './model-destination.mjs';
24
+
25
+ /** La causa della panchina, detta a una persona. Mai la classe tecnica a schermo. */
26
+ const CAUSA_UMANA = Object.freeze({
27
+ credenziale: 'il fornitore l\'ha rifiutata',
28
+ credito: 'il credito è esaurito',
29
+ traffico: 'il fornitore ha chiesto di aspettare per troppo traffico',
30
+ });
31
+
32
+ function fraseDellaPanchina(etichetta, pool, adesso) {
33
+ const ferme = pool.filter((v) => Number.isFinite(v?.inPanchinaFino) && v.inPanchinaFino > adesso);
34
+ if (ferme.length === 0) return null;
35
+ // La prima a tornare disponibile è quella che interessa: è quando si può riprovare.
36
+ const prima = ferme.reduce((a, b) => (a.inPanchinaFino <= b.inPanchinaFino ? a : b));
37
+ const causa = CAUSA_UMANA[prima.causa] ?? 'il fornitore non l\'ha accettata';
38
+ const minuti = Math.max(1, Math.ceil((prima.inPanchinaFino - adesso) / 60_000));
39
+ const quando = minuti >= 120 ? `fra circa ${Math.round(minuti / 60)} ore` : `fra circa ${minuti} min`;
40
+ const quante = ferme.length === 1 ? `La chiave di ${etichetta} è in pausa` : `Le ${ferme.length} chiavi di ${etichetta} sono in pausa`;
41
+ return `${quante}: ${causa}. Si riprova da sola ${quando}; per non aspettare, collega un'altra chiave da Fornitori e accessi.`;
42
+ }
43
+
44
+ /**
45
+ * @param {{providerStore: {getKey: Function, elencaPool?: Function}, chiaveApi?: string|null, adessoFn?: () => number}} deps
46
+ * @returns {(modello: unknown) => {pronto: boolean, fornitore?: string, codice?: string, messaggio?: string}}
47
+ */
48
+ export function creaProntoFn({ providerStore, chiaveApi = null, adessoFn = Date.now }) {
49
+ return function prontoFn(modello) {
50
+ /* ⛔ D2 (17/09): con un modello vuoto rispondeva «pronto» e la sessione partiva. Un modello che non si sa
51
+ leggere NON è pronto, e lo dice. */
52
+ if (typeof modello !== 'string' || modello.trim() === '') {
53
+ return { pronto: false, codice: 'CONFIG_INVALID', messaggio: 'Scegli un modello prima di avviare la sessione.' };
54
+ }
55
+ let fonte;
56
+ try { ({ fonte } = separaFonteModello(modello)); }
57
+ catch { return { pronto: false, codice: 'CONFIG_INVALID', messaggio: 'Questo modello non è riconosciuto: scegline uno dall\'elenco.' }; }
58
+ const record = REGISTRO_FORNITORI[fonte];
59
+ /* ⛔ `codice` e `messaggio` solo quando c'è qualcosa da dire: un «pronto» non porta un codice d'errore. */
60
+ if (!record || record.chiaveObbligatoria !== true) return { pronto: true, fornitore: record?.etichetta ?? fonte };
61
+ // UNA regola per tutti: una chiave utilizzabile ADESSO. La chiave d'ambiente di avvio vale solo per OpenRouter, com'era.
62
+ const utilizzabile = providerStore.getKey(fonte) ?? (fonte === 'openrouter' ? chiaveApi : null);
63
+ if (utilizzabile) return { pronto: true, fornitore: record.etichetta };
64
+ const pool = typeof providerStore.elencaPool === 'function' ? (providerStore.elencaPool(fonte) ?? []) : [];
65
+ const panchina = fraseDellaPanchina(record.etichetta, pool, adessoFn());
66
+ return {
67
+ pronto: false,
68
+ fornitore: record.etichetta,
69
+ codice: 'CONFIG_INVALID',
70
+ messaggio: panchina ?? `Manca la chiave di ${record.etichetta}: collegala da Fornitori e accessi.`,
71
+ };
72
+ };
73
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ⭐⭐⭐ 04/9 — R-02, intro al primo avvio (Fascia R, owner: «abbia un intro
3
+ * stile mobile»). Lo stato che dice al browser QUALI passi del primo avvio
4
+ * sono già fatti — letto dalla realtà, come sul telefono
5
+ * (`mobile/src/lib/onboarding/setupProgress.ts`: «a step is done when the
6
+ * thing it asks for EXISTS», non da un cursore salvato che può invecchiare).
7
+ *
8
+ * ⛔ Nessun segreto passa di qui: della chiave si dice solo per QUALI provider
9
+ * esiste (`conChiave`), mai il valore. È la stessa disciplina di
10
+ * `providerStore.listPublic()`, e il test lo prova con `JSON.stringify`.
11
+ *
12
+ * `TALOS_INTRO=0` è il rollback dichiarato nel ledger: l'intro non si apre,
13
+ * e il lanciatore torna al comportamento di R-01 (schermata Doctor se manca
14
+ * la chiave).
15
+ */
16
+ export function statoPrimoAvvio({ providerStore = null, localeConfigurato = false, introDisattivato = false, cartelleProgetto = 0 } = {}) {
17
+ let conChiave = [];
18
+ if (providerStore && typeof providerStore.listPublic === 'function') {
19
+ try {
20
+ conChiave = providerStore.listPublic().filter((riga) => riga.keyConfigured === true).map((riga) => riga.id);
21
+ } catch {
22
+ conChiave = []; // un portachiavi rotto non è una chiave: si dichiara «non pronto», mai un'ipotesi
23
+ }
24
+ }
25
+ const locale = localeConfigurato === true;
26
+ return Object.freeze({
27
+ introDisattivato: introDisattivato === true,
28
+ provider: Object.freeze({ pronto: conChiave.length > 0 || locale, conChiave: Object.freeze(conChiave), localeConfigurato: locale }),
29
+ cartelleProgetto: Number.isInteger(cartelleProgetto) && cartelleProgetto >= 0 ? cartelleProgetto : 0,
30
+ });
31
+ }
@@ -0,0 +1,204 @@
1
+ // BC-48 A · lettore di sezioni Markdown e selezione deterministica, senza chiamate al modello.
2
+ // Riferimenti: CommonMark 0.31.2; node:path del runtime verificato v24.18.0.
3
+ import { posix, win32 } from 'node:path';
4
+
5
+ export const SOGLIA_RIGHE_INDICE = 60;
6
+ const byte = testo => Buffer.byteLength(testo, 'utf8');
7
+ const slash = testo => String(testo).replace(/\\/g, '/');
8
+
9
+ /** Conserva righe e terminatori originali: le coordinate appartengono al disco. */
10
+ function scandisci(contenuto) {
11
+ const righe = String(contenuto).match(/[^\n]*\n|[^\n]+$/g) ?? [];
12
+ const commenti = [];
13
+ let recinto = null;
14
+ let commento = null;
15
+ const lette = righe.map((originale, indice) => {
16
+ const riga = originale.replace(/\r?\n$/, '');
17
+ const citazione = riga.match(/^(?: {0,3}>[ \t]?)+/)?.[0] ?? '';
18
+ const interna = riga.slice(citazione.length);
19
+ if (recinto?.citato && !citazione) recinto = null;
20
+ if (!commento && recinto) {
21
+ const chiusura = interna.match(/^ {0,3}(`{3,}|~{3,})[ \t]*$/);
22
+ if (chiusura && chiusura[1][0] === recinto.delimitatore[0] && chiusura[1].length >= recinto.delimitatore.length) recinto = null;
23
+ return { originale, pulita: originale, codice: true };
24
+ }
25
+ if (!commento) {
26
+ const apertura = interna.replace(/^ {0,3}(?:[-+*]|\d+[.)])[ \t]+/, '').match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
27
+ if (apertura && !(apertura[1][0] === '`' && apertura[2].includes('`'))) {
28
+ recinto = { delimitatore: apertura[1], citato: Boolean(citazione) };
29
+ return { originale, pulita: originale, codice: true };
30
+ }
31
+ if (/^( {4}|\t)/.test(interna)) return { originale, pulita: originale, codice: true };
32
+ }
33
+ let pulita = '';
34
+ for (let i = 0; i < originale.length;) {
35
+ if (commento) {
36
+ if (originale.startsWith('-->', i)) {
37
+ commenti.push(commento); commento = null; i += 3;
38
+ } else {
39
+ commento.testo += originale[i];
40
+ if (originale[i] === '\n' || originale[i] === '\r') pulita += originale[i];
41
+ i++;
42
+ }
43
+ } else if (originale.startsWith('<!--', i)) {
44
+ commento = { da: indice + 1, testo: '' }; i += 4;
45
+ } else if (originale[i] === '`') {
46
+ const delimitatore = originale.slice(i).match(/^`+/)[0];
47
+ const fine = originale.indexOf(delimitatore, i + delimitatore.length);
48
+ const dopo = fine < 0 ? i + delimitatore.length : fine + delimitatore.length;
49
+ pulita += originale.slice(i, dopo); i = dopo;
50
+ } else { pulita += originale[i]; i++; }
51
+ }
52
+ return { originale, pulita, codice: false };
53
+ });
54
+ return { righe: lette, commenti };
55
+ }
56
+
57
+ export function rimuoviCommentiHtml(contenuto) {
58
+ return scandisci(contenuto).righe.map(r => r.pulita).join('');
59
+ }
60
+
61
+ function primaFrase(testo) {
62
+ const righe = testo.split(/\r?\n/).slice(1);
63
+ const paragrafo = [];
64
+ for (const riga of righe) {
65
+ if (!riga.trim()) { if (paragrafo.length) break; else continue; }
66
+ if (paragrafo.length && /^\s*(?:[-*+] |\d+[.)] |#|```|~~~)/.test(riga)) break;
67
+ paragrafo.push(riga.trim().replace(/^(?:[-*+] |\d+[.)] )/, ''));
68
+ }
69
+ const frase = paragrafo.join(' ').replace(/\s+/g, ' ').trim();
70
+ return frase.match(/^.*?[.!?](?=\s|$)/)?.[0] ?? frase;
71
+ }
72
+
73
+ export function analizzaSezioniIstruzioni(contenuto) {
74
+ const { righe, commenti } = scandisci(contenuto);
75
+ const inizi = righe.flatMap((r, i) => {
76
+ const titolo = !r.codice && r.pulita.match(/^ {0,3}##[ \t]+(.+?)[ \t]*(?:\r?\n)?$/);
77
+ return titolo ? [{ titolo: titolo[1].replace(/[ \t]+#+[ \t]*$/, '').trim(), da: i + 1 }] : [];
78
+ });
79
+ const sezioni = inizi.map((inizio, i) => {
80
+ const a = (inizi[i + 1]?.da ?? righe.length + 1) - 1;
81
+ const parte = righe.slice(inizio.da - 1, a);
82
+ const testo = parte.map(r => r.pulita).join('');
83
+ const metadati = commenti.filter(c => c.da > inizio.da && c.da <= a);
84
+ // Una lista separata da virgole; i gruppi {a,b} non vengono divisi.
85
+ const paths = metadati.flatMap(c => {
86
+ const m = c.testo.trim().match(/^talos:\s*paths:\s*([^\r\n]+)$/);
87
+ if (!m) return [];
88
+ return m[1].split(/,(?![^{}]*\})/).map(p => p.trim()).filter(Boolean);
89
+ });
90
+ return { ...inizio, a, byte: byte(parte.map(r => r.originale).join('')), testo, primaFrase: primaFrase(testo), paths,
91
+ sempre: (i === 0 && /^Non-Negotiable(?:\s|$)/i.test(inizio.titolo)) || metadati.some(c => /^talos:\s*sempre$/.test(c.testo.trim())) };
92
+ });
93
+ return { righe: righe.length, testo: righe.map(r => r.pulita).join(''),
94
+ premessa: righe.slice(0, (inizi[0]?.da ?? righe.length + 1) - 1).map(r => r.pulita).join(''), sezioni };
95
+ }
96
+
97
+ export function rendiFileIstruzioni(file, { sogliaRighe = SOGLIA_RIGHE_INDICE } = {}) {
98
+ const analisi = analizzaSezioniIstruzioni(file.contenuto);
99
+ const indicizzato = analisi.sezioni.length > 0 && (!slash(file.etichetta).includes('/') || analisi.righe > sogliaRighe);
100
+ if (!indicizzato) return { ...analisi, indicizzato, sezioniSempre: [], testo: analisi.testo };
101
+ const sempre = analisi.sezioni.filter(s => s.sempre);
102
+ const indice = analisi.sezioni.filter(s => !s.sempre).map(s => `## ${s.titolo} · righe ${s.da}-${s.a} · ${s.byte} byte · ${s.primaFrase}`).join('\n');
103
+ const lettura = file.lettura ?? file.etichetta;
104
+ const guida = `Prima di lavorare su un'area, leggi la sua sezione con \`leggi\` su \`${lettura}\` alle righe indicate (righe del file su disco). Le sezioni con un percorso si aprono da sole quando tocchi quei file, se la gestione del contesto è attiva.\n`;
105
+ return { ...analisi, indicizzato, sezioniSempre: sempre.map(({ titolo, da, a, byte }) => ({ etichetta: file.etichetta, titolo, da, a, byte })),
106
+ testo: analisi.premessa + sempre.map(s => s.testo).join('') + guida + indice + (indice ? '\n' : '') };
107
+ }
108
+
109
+ function percorsoNelProgetto(percorso, cartella, radice) {
110
+ if (typeof percorso !== 'string' || !percorso.trim() || /[\r\n\0]/.test(percorso)) return null;
111
+ const api = /^[A-Za-z]:\/|^\/{2}/.test(slash(radice)) ? win32 : posix;
112
+ const pieno = api.resolve(cartella, slash(percorso));
113
+ const rel = slash(api.relative(radice, pieno));
114
+ if (rel === '..' || rel.startsWith('../') || api.isAbsolute(rel)) return null;
115
+ return rel || '.';
116
+ }
117
+
118
+ function percorsiToccati(messages, cartella, radice) {
119
+ const chiamate = new Map();
120
+ const pendenti = new Set();
121
+ const percorsi = new Set();
122
+ for (const messaggio of messages) {
123
+ if (messaggio?.role === 'assistant') for (const c of messaggio.tool_calls ?? []) {
124
+ if (typeof c?.id !== 'string') continue;
125
+ chiamate.set(c.id, c); pendenti.add(c.id);
126
+ }
127
+ if (messaggio?.role !== 'tool' || !pendenti.has(messaggio.tool_call_id)) continue;
128
+ pendenti.delete(messaggio.tool_call_id);
129
+ const chiamata = chiamate.get(messaggio.tool_call_id);
130
+ const nome = chiamata?.function?.name;
131
+ // ⭐ PO-12 (13/09/2026) — `file_edit` tocca un file per `percorso` esattamente come `scrivi`: senza questo nome le istruzioni di progetto della cartella toccata non si accendono su una MODIFICA, cioe' proprio sull'attrezzo con cui il modello lavora su codice che gia' esiste.
132
+ if (!['leggi', 'scrivi', 'file_edit', 'cerca', 'elenca'].includes(nome)) continue;
133
+ let argomenti;
134
+ try { argomenti = JSON.parse(chiamata.function.arguments); } catch { continue; }
135
+ if (!argomenti || typeof argomenti !== 'object' || Array.isArray(argomenti)) continue;
136
+ const candidati = [];
137
+ if (nome !== 'cerca') {
138
+ const percorso = ['percorso', 'path', 'file_path', 'filePath', 'file', 'filename'].map(k => argomenti[k]).find(v => typeof v === 'string' && v.trim());
139
+ if (percorso) candidati.push(percorso);
140
+ else if (nome === 'elenca') candidati.push('.');
141
+ }
142
+ // Questi due attrezzi restituiscono percorsi, uno per riga. Le code sono avvisi,
143
+ // non risultati: non leggere mai percorsi dal contenuto di leggi o di una shell.
144
+ if (['cerca', 'elenca'].includes(nome) && typeof messaggio.content === 'string') {
145
+ for (const riga of messaggio.content.split(/\r?\n/)) {
146
+ if (/^(?:⚠|…|no file matches\.|give at least|".*not a readable folder)/.test(riga)) break;
147
+ if (riga.trim()) candidati.push(riga);
148
+ }
149
+ }
150
+ for (const candidato of candidati) {
151
+ const percorso = percorsoNelProgetto(candidato, cartella, radice);
152
+ if (percorso !== null) percorsi.add(percorso);
153
+ }
154
+ }
155
+ return { percorsi: [...percorsi], pendenti: pendenti.size };
156
+ }
157
+
158
+ function combacia(percorso, glob) {
159
+ const modello = slash(glob).replace(/^\.\//, '');
160
+ // Pattern confinati, espansione limitata: non permettere esplosioni di brace.
161
+ if (modello.length > 1024 || modello.startsWith('/') || /(^|\/)\.\.(\/|$)|^[A-Za-z]:/.test(modello) || (modello.match(/\{/g)?.length ?? 0) > 1) return false;
162
+ try { return posix.matchesGlob(percorso, modello); } catch { return false; }
163
+ }
164
+
165
+ /** Accoda agli originali. Stato locale più marcatori nello storico: nessun registro globale. */
166
+ export function creaIniettoreSezioni({ file = [], cartella, radice, tetto = 24_000 } = {}) {
167
+ const catalogo = file.flatMap(f => {
168
+ const resa = rendiFileIstruzioni(f);
169
+ const occorrenze = new Map();
170
+ return resa.indicizzato ? resa.sezioni.map(s => {
171
+ const occorrenza = (occorrenze.get(s.titolo) ?? 0) + 1;
172
+ occorrenze.set(s.titolo, occorrenza);
173
+ return { ...s, occorrenza, etichetta: f.etichetta, lettura: f.lettura ?? f.etichetta };
174
+ }).filter(s => !s.sempre && s.paths.length) : [];
175
+ });
176
+ const viste = new Set();
177
+ return messages => {
178
+ if (!Array.isArray(messages) || !catalogo.length) return;
179
+ const { percorsi, pendenti } = percorsiToccati(messages, cartella, radice);
180
+ if (pendenti) return; // Preserva l'abbinamento anche nei batch di tool.
181
+ const aggiunte = [];
182
+ for (const sezione of catalogo) {
183
+ const marca = `Sezione di \`${sezione.etichetta}\` che vale per questa cartella: ${sezione.titolo} (${sezione.occorrenza}).\n`;
184
+ if (viste.has(marca)) continue;
185
+ if (messages.some(m => m?.role === 'user' && typeof m.content === 'string' && m.content.startsWith(marca))) { viste.add(marca); continue; }
186
+ if (!sezione.paths.some(glob => percorsi.some(p => combacia(p, glob)))) continue;
187
+ const intera = marca + `Righe ${sezione.da}-${sezione.a} del file su disco.\n` + sezione.testo;
188
+ const avviso = marca + `⚠ Tetto delle istruzioni (${tetto} byte): sezione omessa intera. Leggila con \`leggi\` su \`${sezione.lettura}\`, righe ${sezione.da}-${sezione.a}.`;
189
+ aggiunte.push({ marca, content: intera, avviso });
190
+ }
191
+ let totale = aggiunte.reduce((n, a) => n + byte(a.content), 0);
192
+ for (let i = aggiunte.length - 1; totale > tetto && i >= 0; i--) {
193
+ const a = aggiunte[i];
194
+ if (byte(a.avviso) >= byte(a.content)) continue;
195
+ totale += byte(a.avviso) - byte(a.content);
196
+ a.content = a.avviso;
197
+ }
198
+ if (totale > tetto) throw new RangeError('Il tetto delle istruzioni è insufficiente anche per gli avvisi delle sezioni.');
199
+ for (const { marca, content } of aggiunte) {
200
+ messages.push({ role: 'user', content });
201
+ viste.add(marca);
202
+ }
203
+ };
204
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * skill-registry.mjs — FASE F del piano `elegant-spinning-dongarra.md`
3
+ * (29/8): cartelle di istruzioni caricate on-demand, dichiarate per
4
+ * workspace. Stesso posto (`hook-registry.mjs`/`mcp-registry.mjs`
5
+ * letti come precedenti diretti) — `.harness-ui-skills/<nome>/SKILL.md`
6
+ * DENTRO il workspace, mai fuori: una skill è una proprietà del
7
+ * progetto, come `AGENTS.md` o `.harness-ui-hooks.json`.
8
+ *
9
+ * ⛔⛔ Nessun trust/hash gate, a differenza di hook (eseguono un
10
+ * comando) e server MCP (connettono un processo) — decisione
11
+ * deliberata: una skill è TESTO INERTE, la stessa categoria di rischio
12
+ * di `task.consegna` o di qualunque altro contenuto già nel system
13
+ * prompt, mai gated in questo progetto. Un cancello qui sarebbe un
14
+ * bluff di sicurezza, non una protezione vera — vedi la doc nel piano
15
+ * madre, FASE F, per il ragionamento completo.
16
+ *
17
+ * Formato verificato EMPIRICAMENTE il 29/8 leggendo quattro `SKILL.md`
18
+ * reali (`~/.claude/plugins/cache/.../SKILL.md`), non da una spec
19
+ * assunta: frontmatter YAML fra due righe `---`, tutte le istanze
20
+ * osservate a chiave singola per riga (`name: ...`, `description: ...`
21
+ * mai spezzato su più righe) — questo modulo parsa quella forma, non
22
+ * lo YAML generale (blocchi multi-riga, liste, nesting). Un file che
23
+ * usa una di quelle forme più ricche è un errore dichiarato
24
+ * (`SkillRegistryError`), mai un parsing silenziosamente sbagliato.
25
+ */
26
+ import { promises as fsp } from 'node:fs';
27
+ import { join } from 'node:path';
28
+
29
+ export class SkillRegistryError extends Error {
30
+ constructor(message, code = 'SKILL_INVALID') {
31
+ super(message);
32
+ this.name = 'SkillRegistryError';
33
+ this.code = code;
34
+ }
35
+ }
36
+
37
+ const NOME_CARTELLA_SKILLS = '.harness-ui-skills';
38
+ const NOME_FILE_SKILL = 'SKILL.md';
39
+
40
+ /**
41
+ * Parsa il frontmatter minimo (`chiave: valore`, una riga per coppia,
42
+ * fra due delimitatori `---`) di un SKILL.md. Torna
43
+ * `{campi, corpo}` — `campi` è un oggetto piatto stringa→stringa,
44
+ * `corpo` è tutto ciò che segue il secondo `---` (il markdown vero).
45
+ */
46
+ function analizzaSkillMd(testo, skillId) {
47
+ const righe = testo.split('\n');
48
+ if (righe[0]?.trim() !== '---') {
49
+ throw new SkillRegistryError(`${skillId}/SKILL.md deve iniziare con un frontmatter "---"`, 'SKILL_MALFORMED');
50
+ }
51
+ const fineFrontmatter = righe.findIndex((r, i) => i > 0 && r.trim() === '---');
52
+ if (fineFrontmatter === -1) {
53
+ throw new SkillRegistryError(`${skillId}/SKILL.md ha un frontmatter mai chiuso (manca il secondo "---")`, 'SKILL_MALFORMED');
54
+ }
55
+ const campi = {};
56
+ for (const riga of righe.slice(1, fineFrontmatter)) {
57
+ if (riga.trim() === '') continue;
58
+ const indice = riga.indexOf(':');
59
+ if (indice === -1) {
60
+ throw new SkillRegistryError(`${skillId}/SKILL.md ha una riga di frontmatter non valida (attesa "chiave: valore"): "${riga}"`, 'SKILL_MALFORMED');
61
+ }
62
+ const chiave = riga.slice(0, indice).trim();
63
+ const valore = riga.slice(indice + 1).trim();
64
+ campi[chiave] = valore;
65
+ }
66
+ /*
67
+ * ⛔ `/^[\r\n]+/`, non `/^\n+/`: un file con terminatori CRLF (comune
68
+ * su Windows — stesso difetto già visto altrove in questo progetto,
69
+ * `scrivere-un-file-da-python-lo-converte-in-crlf.md` in memoria)
70
+ * lascerebbe un `\r` isolato davanti al corpo altrimenti — verificato
71
+ * dal vivo il 29/8 leggendo un vero SKILL.md con CRLF, non presunto.
72
+ */
73
+ const corpo = righe.slice(fineFrontmatter + 1).join('\n').replace(/^[\r\n]+/, '');
74
+ return { campi, corpo };
75
+ }
76
+
77
+ /**
78
+ * Legge `<cartella>/.harness-ui-skills/*\/SKILL.md`. Un progetto senza
79
+ * skill dichiarate è uno stato valido — mai un errore, torna
80
+ * `{skills: []}`. Una singola skill malformata FERMA l'intero
81
+ * caricamento con un errore dichiarato (mai una skill fantasma
82
+ * ignorata in silenzio, stesso principio di caricaHooks/caricaServerMcp)
83
+ * — l'owner che ha sbagliato a scrivere un file deve saperlo.
84
+ */
85
+ export async function caricaSkill({ cartella }, deps = {}) {
86
+ const readdirFn = deps.readdirFn ?? fsp.readdir;
87
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
88
+ const cartellaSkills = join(cartella, NOME_CARTELLA_SKILLS);
89
+ let voci;
90
+ try {
91
+ voci = await readdirFn(cartellaSkills, { withFileTypes: true });
92
+ } catch (errore) {
93
+ if (errore?.code === 'ENOENT') return { skills: [] };
94
+ throw new SkillRegistryError(`Impossibile leggere ${NOME_CARTELLA_SKILLS}: ${errore.message}`, 'SKILL_READ_FAILED');
95
+ }
96
+ const skills = [];
97
+ for (const voce of voci) {
98
+ if (!voce.isDirectory()) continue; // un file sciolto dentro .harness-ui-skills/ non è una skill
99
+ const skillId = voce.name;
100
+ const percorso = join(cartellaSkills, skillId, NOME_FILE_SKILL);
101
+ let testo;
102
+ try {
103
+ testo = await readFileFn(percorso, 'utf8');
104
+ } catch (errore) {
105
+ if (errore?.code === 'ENOENT') continue; // una sottocartella senza SKILL.md non è una skill, non è un errore
106
+ throw new SkillRegistryError(`Impossibile leggere ${skillId}/${NOME_FILE_SKILL}: ${errore.message}`, 'SKILL_READ_FAILED');
107
+ }
108
+ const { campi, corpo } = analizzaSkillMd(testo, skillId);
109
+ if (typeof campi.name !== 'string' || campi.name.length === 0) {
110
+ throw new SkillRegistryError(`${skillId}/SKILL.md manca di "name" nel frontmatter`, 'SKILL_MALFORMED');
111
+ }
112
+ if (typeof campi.description !== 'string' || campi.description.length === 0) {
113
+ throw new SkillRegistryError(`${skillId}/SKILL.md manca di "description" nel frontmatter`, 'SKILL_MALFORMED');
114
+ }
115
+ skills.push({ id: skillId, name: campi.name, description: campi.description, corpo });
116
+ }
117
+ // ⭐ ordine deterministico — mai dipendere dall'ordine che il filesystem restituisce (varia per OS).
118
+ skills.sort((a, b) => a.id.localeCompare(b.id));
119
+ return { skills };
120
+ }