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,652 @@
1
+ /**
2
+ * library-store.mjs — FASE N del piano `elegant-spinning-dongarra.md`
3
+ * (29/8): port dal mobile, owner testuale: "iniziamo da library,
4
+ * ricordati che sul mobile esistono già da tempo tutte queste
5
+ * funzioni... è tutto già fatto". Stesso CONTRATTO (schema/semantica)
6
+ * degli 8 tool mobile — letti alla fonte il 29/8, non presunti:
7
+ * `mobile/src/lib/tools/readTools.ts` righe 320-650 (i 4 tool di
8
+ * lettura) e `mobile/src/lib/vaultLibrary.ts` (il modello dati) —
9
+ * storage DIVERSO: file su disco per-progetto (decisione già presa
10
+ * nel ledger FASE N: "storage per-progetto dentro il workspace"), non
11
+ * SQLCipher cross-chat come sul mobile.
12
+ *
13
+ * Prima fetta: SOLO i 4 tool di lettura (list/search/read/file_origin)
14
+ * — come sul mobile, dove sono arrivati per primi (domanda 2 del
15
+ * ledger FASE N, risolta con la convenzione già stabilita per
16
+ * E/F/G/H: registry+tool prima, mutazioni/UI dopo). `salvaVoce` esiste
17
+ * già qui — serve per seminare/testare questa stessa fetta dal vivo
18
+ * (senza scrittura non c'è nulla da leggere) ed è la base pronta per
19
+ * la seconda fetta — ma non è ancora esposta come tool al modello.
20
+ *
21
+ * ⛔⛔⛔ Deliberatamente SENZA il concetto mobile di `contentOrigin`/A8
22
+ * (la provenienza dinamica "peggiore fra N risultati", usata sul
23
+ * mobile per la difesa da prompt injection): verificato il 29/8
24
+ * leggendo `talosHarness.mjs` per intero — non ha NESSUN equivalente,
25
+ * in nessun nome. La sua trifecta (`SICUREZZA_PER_ATTREZZO`) è un
26
+ * vocabolario DIVERSO — booleano, STATICO per nome di attrezzo, e
27
+ * limitato ai soli 5 "ATTREZZI_CON_RICEVUTA" (scrivi/prova/shell/
28
+ * document_create/generate_image); perfino `naviga` (che legge
29
+ * contenuto web non fidato) ne è fuori oggi. I 4 tool di lettura qui
30
+ * sotto ricevono lo STESSO trattamento non censito di
31
+ * elenca/cerca/leggi/naviga: nessuna voce in SICUREZZA_PER_ATTREZZO,
32
+ * il default `{readsPrivateData:false, readsUntrustedContent:false,
33
+ * canTransmit:false}` si applica — coerente col precedente stabilito,
34
+ * non un buco nuovo introdotto da questa fase.
35
+ *
36
+ * Stile DI: stesso pattern di `skill-registry.mjs`/`frequent-dirs.mjs`
37
+ * (letti come precedenti diretti) — funzioni async con `deps` opzionali
38
+ * per i test, mai un vero filesystem mockato altrove.
39
+ */
40
+ import { promises as fsp } from 'node:fs';
41
+ import { join } from 'node:path';
42
+ import { randomUUID } from 'node:crypto';
43
+
44
+ // ⛔ Il tetto dello scarico è UNO SOLO per tutto il prodotto: vedi `leggiBytesVoce` in fondo.
45
+ import { DIMENSIONE_MASSIMA_SCARICO } from './workspace-files.mjs';
46
+
47
+ export class LibraryStoreError extends Error {
48
+ constructor(message, code = 'LIBRARY_INVALID') {
49
+ super(message);
50
+ this.name = 'LibraryStoreError';
51
+ this.code = code;
52
+ }
53
+ }
54
+
55
+ export const CARTELLA_LIBRERIA = '.harness-ui-library';
56
+ const NOME_FILE_META = 'meta.json';
57
+ const NOME_FILE_CONTENUTO = 'contenuto';
58
+ const MAX_VOCI_PAGINA = 20;
59
+ const MAX_RISULTATI_RICERCA = 20;
60
+ // Stesso ordine di grandezza di MAX_LIBRARY_LIST_CURSORS mobile (readTools.ts riga 247, valore 128).
61
+ const MAX_CURSORI_VIVI = 128;
62
+
63
+ /**
64
+ * @returns {'image'|'document'} — derivato dal mediaType, come
65
+ * `talosLibraryFileType` mobile (vaultLibrary.ts righe 66-71). Niente
66
+ * `'link'`: l'archiviazione di pagine web nella Libreria è una
67
+ * capacità mobile (ricerca approfondita che salva le fonti) che il
68
+ * desktop non ha ancora — il valore resta nello schema del filtro
69
+ * (per parità di contratto) ma non produce mai risultati, onestamente.
70
+ */
71
+ export function tipoFileLibreria(mediaType) {
72
+ return String(mediaType || '').startsWith('image/') ? 'image' : 'document';
73
+ }
74
+
75
+ /**
76
+ * Una Map nuova per sessione — stessa vita di `libraryListCursors`
77
+ * mobile (un'istanza per chat, chiusa nella factory di `defineTalosTool`).
78
+ * Qui il chiamante (agent-service.mjs, una volta per sessione) la crea
79
+ * e la passa a `impaginaVoci` ad ogni chiamata di `library_list`.
80
+ */
81
+ export function creaCursoriLibreria() {
82
+ return new Map();
83
+ }
84
+
85
+ /*
86
+ * ⛔⛔⛔ 10/09/2026 — L'ID DI UNA VOCE NON È PIÙ SOLO UNA COSA DEL MODELLO.
87
+ *
88
+ * Fino a oggi ogni `id` arrivava da `impaginaVoci`/`cercaVoci`, cioè da un elenco che questo
89
+ * file aveva appena letto dal disco. Da oggi arriva anche da un SEGMENTO DI INDIRIZZO HTTP
90
+ * (`/api/v1/sessions/:id/library/:voceId/...`), cioè da fuori: un id `..%2f..` diventerebbe
91
+ * `join(cartella, '.harness-ui-library', '..', '..')` e uscirebbe dalla cartella del progetto,
92
+ * e `leggiVoce`/`rinominaVoce`/`eliminaVoce` lo avrebbero seguito senza fiatare. La stessa porta
93
+ * era già aperta dalla parte del modello (`agent-service.mjs` passa `argomenti?.id ?? ''` così
94
+ * com'è): la difesa sta QUI, nel magazzino, così vale per tutti e due i chiamanti e non c'è una
95
+ * seconda copia che un giorno diverge.
96
+ *
97
+ * Ricerca 10/09/2026, prima di scrivere (Microsoft Learn, «Naming Files, Paths, and Namespaces»;
98
+ * npm `sanitize-filename` e la sua tabella di casi): un nome di file sicuro non è «una stringa
99
+ * senza `..`» — sono vietati anche i separatori, i caratteri di controllo, i punti e gli spazi
100
+ * in coda, e i nomi riservati di Windows (CON, PRN, AUX, NUL, COM1-9, LPT1-9) perfino con
101
+ * un'estensione appiccicata dietro. Qui il tetto è più stretto di quell'elenco, e per questo non
102
+ * serve ricopiarlo: un id di Libreria lo genera SEMPRE `salvaVoce` nella forma `lib-<uuid>` (mai
103
+ * il chiamante), e `randomUUID` produce solo cifre esadecimali e trattini ⇒ un'allowlist di
104
+ * lettere, cifre, punto, trattino e trattino basso accetta tutto ciò che esiste davvero sul disco
105
+ * e rifiuta tutto il resto.
106
+ *
107
+ * ⛔ Un id fuori grammatica torna `null` — «questa voce non c'è», non un'eccezione: è vero (un id
108
+ * con una barra dentro non può nominare nessuna voce) e non cambia il contratto delle funzioni
109
+ * qui sotto, che per un id sconosciuto rispondono già `null`. Un'eccezione nuova, invece,
110
+ * sarebbe arrivata in faccia al modello per una chiamata che oggi si chiude con un «non trovato».
111
+ */
112
+ export function idVoceLibreriaValido(id) {
113
+ return typeof id === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id);
114
+ }
115
+
116
+ /**
117
+ * Il percorso RELATIVO (dalla cartella del progetto) del file di una voce — la sola forma in cui
118
+ * la disposizione interna della Libreria esce da questo file. `null` se l'id non è valido.
119
+ * Serve a «rivela in Esplora file», che vuole un percorso dentro il workspace: farlo comporre al
120
+ * chiamante vorrebbe dire scrivere `.harness-ui-library/<id>/contenuto` in due posti, cioè tenere
121
+ * due mappe della stessa cartella — e quando la Libreria cambierà forma se ne aggiornerà una sola.
122
+ */
123
+ export function percorsoContenutoVoce(id) {
124
+ if (!idVoceLibreriaValido(id)) return null;
125
+ return `${CARTELLA_LIBRERIA}/${id}/${NOME_FILE_CONTENUTO}`;
126
+ }
127
+
128
+ /**
129
+ * ⭐⭐⭐ BC-38 (12/09/2026), owner: «mettere il percorso dei file nella Libreria… nel dettaglio
130
+ * anche da chi sono stati creati e da quale sessione».
131
+ *
132
+ * Il percorso ASSOLUTO del file di contenuto — la sola forma che si può mettere davanti a una
133
+ * persona, perché è quella che si incolla in Esplora file o in un terminale. Gemello di
134
+ * `percorsoContenutoVoce` (che resta relativo perché è ciò che vuole `rivelaInEsploraFile`), e
135
+ * come lui vive QUI: la disposizione della cartella di Libreria ha un solo proprietario.
136
+ * `null` se l'id non è in grammatica — «questa voce non c'è», mai un percorso inventato.
137
+ */
138
+ export function percorsoAssolutoVoce(cartella, id) {
139
+ const relativo = percorsoContenutoVoce(id);
140
+ if (!relativo || typeof cartella !== 'string' || cartella.length === 0) return null;
141
+ return join(cartella, relativo);
142
+ }
143
+
144
+ /**
145
+ * CHI ha prodotto una voce, letto dal `meta.json` e mai dedotto dal NOME del file.
146
+ *
147
+ * ⛔ Due valori, non tre: `persona` (un file caricato) e `modello` (un file generato). La terza
148
+ * categoria che si potrebbe volere — «l'ha scritto una ricerca approfondita» — NON è
149
+ * distinguibile oggi: `research-orchestrator.mjs` chiama `salvaVoce` con lo stesso
150
+ * `origine:'generated'` di un artefatto o di un `document_create`, e l'unico segno che resta
151
+ * sul disco sarebbe il prefisso «Research - » nel nome. Un prefisso nel nome NON è un dato:
152
+ * è una stringa che chiunque rinomina. ⇒ si dice «il modello», che è vero, invece di indovinare.
153
+ * ⛔ `modello`/`provider` restano `null` per tutte le voci scritte prima di oggi: nessuna li
154
+ * portava (misurato il 12/09/2026 sulla Libreria del Desktop dell owner: 14 voci su 14
155
+ * senza `modello`). Un `null` qui vuol dire «non registrato», e a schermo si legge così.
156
+ */
157
+ function creatoDaDiMeta(meta, origineEffettiva) {
158
+ if (origineEffettiva !== 'generated') return { tipo: 'persona' };
159
+ return {
160
+ tipo: 'modello',
161
+ modello: typeof meta.modello === 'string' && meta.modello.trim() ? meta.modello.trim() : null,
162
+ provider: typeof meta.provider === 'string' && meta.provider.trim() ? meta.provider.trim() : null,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * DA QUALE sessione arriva una voce. `null` quando il `meta.json` non lo dice — ed è il caso di
168
+ * ogni voce scritta prima del 12/09/2026, perché `salvaVoce` non lo salvava.
169
+ * ⛔ `null` NON si riempie con un ripiego: «sessione non registrata» è la verità, un id inventato
170
+ * o l'id del progetto al posto suo sarebbero una bugia che porta a cliccare su una chat che non
171
+ * esiste. Il `nome` è quello congelato al momento della scrittura: può essere `null` da solo
172
+ * (l'id c'è, il nome no) e chi disegna lo risolve con l'elenco vivo delle sessioni.
173
+ */
174
+ function sessioneDiMeta(meta) {
175
+ const id = typeof meta.sessionId === 'string' && meta.sessionId.trim() ? meta.sessionId.trim() : null;
176
+ if (!id) return null;
177
+ const nome = typeof meta.sessionNome === 'string' && meta.sessionNome.trim() ? meta.sessionNome.trim() : null;
178
+ return { id, nome };
179
+ }
180
+
181
+ /** La cartella di UNA voce, o `null` se l'id non è un nome piatto (vedi `idVoceLibreriaValido`). */
182
+ function cartellaDellaVoce(cartella, id) {
183
+ if (!idVoceLibreriaValido(id)) return null;
184
+ return join(cartella, CARTELLA_LIBRERIA, id);
185
+ }
186
+
187
+ function meta2voce(id, meta) {
188
+ const creatoIl = typeof meta.creatoIl === 'string' ? meta.creatoIl : new Date(0).toISOString();
189
+ return {
190
+ id,
191
+ nome: meta.nome,
192
+ mediaType: typeof meta.mediaType === 'string' ? meta.mediaType : 'application/octet-stream',
193
+ fileType: tipoFileLibreria(meta.mediaType),
194
+ // Fail-closed come `parseVaultOrigin` mobile: qualunque cosa non sia esplicitamente 'generated' è trattata come un upload.
195
+ origine: meta.origine === 'generated' ? 'generated' : 'uploaded',
196
+ creatoIl,
197
+ aggiornatoIl: typeof meta.aggiornatoIl === 'string' ? meta.aggiornatoIl : creatoIl,
198
+ modello: typeof meta.modello === 'string' ? meta.modello : null,
199
+ provider: typeof meta.provider === 'string' ? meta.provider : null,
200
+ };
201
+ }
202
+
203
+ async function leggiMeta(percorsoMeta, readFileFn, id) {
204
+ let testo;
205
+ try {
206
+ testo = await readFileFn(percorsoMeta, 'utf8');
207
+ } catch (errore) {
208
+ if (errore?.code === 'ENOENT') return undefined; // assente: non un errore, un "non c'è" onesto
209
+ throw new LibraryStoreError(`Impossibile leggere ${id}/${NOME_FILE_META}: ${errore.message}`, 'LIBRARY_READ_FAILED');
210
+ }
211
+ let meta;
212
+ try {
213
+ meta = JSON.parse(testo);
214
+ } catch {
215
+ throw new LibraryStoreError(`${id}/${NOME_FILE_META} non è JSON valido`, 'LIBRARY_MALFORMED');
216
+ }
217
+ if (typeof meta.nome !== 'string' || meta.nome.length === 0) {
218
+ throw new LibraryStoreError(`${id}/${NOME_FILE_META} manca di "nome"`, 'LIBRARY_MALFORMED');
219
+ }
220
+ return meta;
221
+ }
222
+
223
+ /**
224
+ * Legge OGNI voce (solo metadata, MAI il contenuto) sotto
225
+ * `<cartella>/.harness-ui-library/`. Cartella assente ⇒ `[]`, mai un
226
+ * errore — un progetto senza Libreria è uno stato valido, stesso
227
+ * principio "gli stati sono tre" di caricaHooks/caricaSkill. Una
228
+ * sottocartella senza `meta.json` è saltata (non è una voce, non è un
229
+ * errore); un `meta.json` malformato FERMA il caricamento con un
230
+ * errore dichiarato — mai una voce fantasma letta a metà.
231
+ */
232
+ /*
233
+ * ⭐ BC-38 — `conProvenienza` è SPENTO di default, e non è una cautela generica: `elencaVoci` è
234
+ * anche l'attrezzo `library_list` del MODELLO (`agent-service.mjs:1398` → `impaginaVoci`, che
235
+ * restituisce le voci intere). Accendere sempre i quattro campi nuovi vorrebbe dire mettere un
236
+ * percorso assoluto e una provenienza dentro il contesto di OGNI giro, cioè cambiare in silenzio
237
+ * il contratto verso il modello e il conto dei token. ⇒ li chiede solo chi disegna per la
238
+ * PERSONA (`session-registry.elencaLibreria`); per il modello questa funzione resta identica
239
+ * bit per bit.
240
+ */
241
+ export async function elencaVoci({ cartella, conProvenienza = false }, deps = {}) {
242
+ const readdirFn = deps.readdirFn ?? fsp.readdir;
243
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
244
+ const cartellaLibreria = join(cartella, CARTELLA_LIBRERIA);
245
+ let voci;
246
+ try {
247
+ voci = await readdirFn(cartellaLibreria, { withFileTypes: true });
248
+ } catch (errore) {
249
+ if (errore?.code === 'ENOENT') return [];
250
+ throw new LibraryStoreError(`Impossibile leggere ${CARTELLA_LIBRERIA}: ${errore.message}`, 'LIBRARY_READ_FAILED');
251
+ }
252
+ const entries = [];
253
+ for (const voce of voci) {
254
+ if (!voce.isDirectory()) continue; // un file sciolto dentro .harness-ui-library/ non è una voce
255
+ const meta = await leggiMeta(join(cartellaLibreria, voce.name, NOME_FILE_META), readFileFn, voce.name);
256
+ if (meta === undefined) continue;
257
+ const base = meta2voce(voce.name, meta);
258
+ entries.push(conProvenienza
259
+ ? {
260
+ ...base,
261
+ cartella,
262
+ percorso: percorsoAssolutoVoce(cartella, voce.name),
263
+ creatoDa: creatoDaDiMeta(meta, base.origine),
264
+ sessione: sessioneDiMeta(meta),
265
+ }
266
+ : base);
267
+ }
268
+ return entries;
269
+ }
270
+
271
+ /**
272
+ * Come `elencaVoci`, ma legge ANCHE il testo estratto di ogni voce
273
+ * non-immagine — stessa separazione deliberata di mobile
274
+ * (`listLibraryEntries` "metadata-only", `listLibraryDocs` "complete
275
+ * extracted text, loaded only by an explicit search call", doc in
276
+ * readTools.ts riga 85-88): un `library_list` non paga il costo I/O
277
+ * di leggere ogni contenuto, solo `library_search` lo fa.
278
+ */
279
+ export async function elencaVociConTesto({ cartella }, deps = {}) {
280
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
281
+ const entries = await elencaVoci({ cartella }, deps);
282
+ const conTesto = [];
283
+ for (const entry of entries) {
284
+ if (entry.fileType === 'image') {
285
+ conTesto.push({ ...entry, testoEstratto: '' });
286
+ continue;
287
+ }
288
+ let testoEstratto = '';
289
+ try {
290
+ testoEstratto = await readFileFn(join(cartella, CARTELLA_LIBRERIA, entry.id, NOME_FILE_CONTENUTO), 'utf8');
291
+ } catch {
292
+ // ⛔ contenuto mancante/illeggibile: la voce resta cercabile per nome, mai un errore che blocca l'intera ricerca per una voce sola.
293
+ }
294
+ conTesto.push({ ...entry, testoEstratto });
295
+ }
296
+ return conTesto;
297
+ }
298
+
299
+ function confrontaVoci(a, b) {
300
+ // Più recente per aggiornamento, poi creazione, poi id — stesso ordine di `compareLibraryListEntries` mobile (readTools.ts righe 250-257).
301
+ return b.aggiornatoIl.localeCompare(a.aggiornatoIl)
302
+ || b.creatoIl.localeCompare(a.creatoIl)
303
+ || b.id.localeCompare(a.id);
304
+ }
305
+
306
+ function primaDelCursore(voce, dopo) {
307
+ if (voce.aggiornatoIl !== dopo.aggiornatoIl) return voce.aggiornatoIl < dopo.aggiornatoIl;
308
+ if (voce.creatoIl !== dopo.creatoIl) return voce.creatoIl < dopo.creatoIl;
309
+ return voce.id < dopo.id;
310
+ }
311
+
312
+ /**
313
+ * Pagina l'elenco — PURA (nessun I/O): filtro origine/tipo, ordine
314
+ * più-recente-prima, cursore opaco (token → stato, nella Map passata
315
+ * dal chiamante — vedi `creaCursoriLibreria`). Un `pageToken`
316
+ * sconosciuto/scaduto torna `{errore:'CURSOR_INVALID'}`; un
317
+ * `pageToken` con filtri diversi da quelli della pagina precedente
318
+ * torna `{errore:'FILTER_DRIFT'}` — stessi due esiti onesti di
319
+ * `library_list` mobile (readTools.ts righe 339-365), mai un salto
320
+ * silenzioso a una pagina qualunque.
321
+ */
322
+ export function impaginaVoci(voci, { origine = 'all', fileType = 'all', pageSize = 10, pageToken } = {}, cursori) {
323
+ const taglia = Math.max(1, Math.min(MAX_VOCI_PAGINA, pageSize));
324
+ let cursore;
325
+ if (pageToken) {
326
+ cursore = cursori.get(pageToken);
327
+ if (!cursore) return { errore: 'CURSOR_INVALID' };
328
+ if (cursore.origine !== origine || cursore.fileType !== fileType) return { errore: 'FILTER_DRIFT' };
329
+ }
330
+ const filtrate = voci
331
+ .filter((v) => origine === 'all' || v.origine === origine)
332
+ .filter((v) => fileType === 'all' || v.fileType === fileType)
333
+ .slice()
334
+ .sort(confrontaVoci);
335
+ const candidate = cursore ? filtrate.filter((v) => primaDelCursore(v, cursore.dopo)) : filtrate;
336
+ const pagina = candidate.slice(0, taglia);
337
+ const vistiPrima = cursore?.visti ?? 0;
338
+ const vistiDopo = vistiPrima + pagina.length;
339
+ const altre = candidate.length > pagina.length;
340
+ const ultima = pagina.at(-1);
341
+ let nextPageToken = null;
342
+ if (altre && ultima) {
343
+ while (cursori.size >= MAX_CURSORI_VIVI) {
344
+ const piuVecchio = cursori.keys().next().value;
345
+ if (piuVecchio === undefined) break;
346
+ cursori.delete(piuVecchio); // FIFO — stesso schema di eviction di issueLibraryListCursor mobile (readTools.ts righe 308-318)
347
+ }
348
+ nextPageToken = randomUUID();
349
+ cursori.set(nextPageToken, {
350
+ origine, fileType, visti: vistiDopo,
351
+ dopo: { aggiornatoIl: ultima.aggiornatoIl, creatoIl: ultima.creatoIl, id: ultima.id },
352
+ });
353
+ }
354
+ return { pagina, totale: filtrate.length, vistiPrima, vistiDopo, nextPageToken };
355
+ }
356
+
357
+ function punteggioRicerca(voce, query) {
358
+ const parole = query.toLowerCase().split(/\s+/).filter(Boolean);
359
+ if (parole.length === 0) return 0;
360
+ const nome = voce.nome.toLowerCase();
361
+ const corpo = (voce.testoEstratto || '').toLowerCase();
362
+ let punti = 0;
363
+ for (const parola of parole) {
364
+ if (nome.includes(parola)) punti += 3;
365
+ if (corpo.includes(parola)) punti += 1;
366
+ }
367
+ return punti;
368
+ }
369
+
370
+ /**
371
+ * Cerca fra le voci (richiede `testoEstratto`, cioè il risultato di
372
+ * `elencaVociConTesto` — mai `elencaVoci`, che non lo porta). PURA.
373
+ *
374
+ * ⛔ Punteggio semplice per la prima fetta — non porta l'euristica
375
+ * esatta di `rankLibraryDocs` mobile: fuori scala per un porting
376
+ * "stesso contratto, storage diverso", dove ciò che conta per la
377
+ * parità è lo SCHEMA (solo punteggio>0 è un match, offset paginato),
378
+ * non l'algoritmo di ranking interno. Nome pesa più del corpo, parole
379
+ * della query, case-insensitive.
380
+ */
381
+ export function cercaVoci(vociConTesto, { query, limit = 5, offset = 0 } = {}) {
382
+ const taglia = Math.max(1, Math.min(MAX_RISULTATI_RICERCA, limit));
383
+ const puntati = vociConTesto
384
+ .map((v) => ({ voce: v, punteggio: punteggioRicerca(v, query) }))
385
+ // Un punteggio zero non è entrato: dire il contrario contaminerebbe la ricerca con voci a caso — stesso principio di `matching.filter(({score}) => score > 0)` mobile.
386
+ .filter(({ punteggio }) => punteggio > 0)
387
+ .sort((a, b) => b.punteggio - a.punteggio || b.voce.aggiornatoIl.localeCompare(a.voce.aggiornatoIl));
388
+ const pagina = puntati.slice(offset, offset + taglia);
389
+ const nextOffset = offset + pagina.length < puntati.length ? offset + pagina.length : null;
390
+ return { pagina: pagina.map((r) => r.voce), totale: puntati.length, nextOffset };
391
+ }
392
+
393
+ /**
394
+ * Legge UNA voce per intero (metadata + contenuto). Mai un id
395
+ * indovinato dal chiamante: sempre ottenuto da `impaginaVoci`/
396
+ * `cercaVoci` prima — stesso principio di `library_read` mobile.
397
+ * `null` se l'id non esiste (mai un'eccezione per un id sbagliato:
398
+ * è un esito onesto, non un guasto).
399
+ */
400
+ export async function leggiVoce({ cartella, id }, deps = {}) {
401
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
402
+ const cartellaVoce = cartellaDellaVoce(cartella, id);
403
+ if (!cartellaVoce) return null;
404
+ const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
405
+ if (meta === undefined) return null;
406
+ const voce = meta2voce(id, meta);
407
+ const percorsoContenuto = join(cartellaVoce, NOME_FILE_CONTENUTO);
408
+ if (voce.fileType === 'image') {
409
+ let bytes;
410
+ try {
411
+ bytes = await readFileFn(percorsoContenuto);
412
+ } catch (errore) {
413
+ throw new LibraryStoreError(`${id}: metadata presente ma contenuto illeggibile: ${errore.message}`, 'LIBRARY_READ_FAILED');
414
+ }
415
+ return { nome: voce.nome, mediaType: voce.mediaType, origine: voce.origine, immagineBase64: bytes.toString('base64') };
416
+ }
417
+ let testo;
418
+ try {
419
+ testo = await readFileFn(percorsoContenuto, 'utf8');
420
+ } catch (errore) {
421
+ throw new LibraryStoreError(`${id}: metadata presente ma contenuto illeggibile: ${errore.message}`, 'LIBRARY_READ_FAILED');
422
+ }
423
+ return { nome: voce.nome, mediaType: voce.mediaType, origine: voce.origine, testo };
424
+ }
425
+
426
+
427
+ /**
428
+ * I BYTE di una voce, per essere SCARICATA — sorella di `leggiVoce` qui sopra, con una differenza
429
+ * che non è un dettaglio: `leggiVoce` legge in `utf8` tutto ciò che non è un'immagine, e un `.docx`
430
+ * (che è uno zip) o un `.pdf` passati da lì tornano CORROTTI in modo irreversibile — ogni byte non
431
+ * valido diventa U+FFFD e non torna più indietro. Quella porta serve al modello, che vuole del
432
+ * testo; questa serve alla persona che clicca «scarica», e qui i byte restano byte.
433
+ *
434
+ * ⛔ Il tetto dei 64 MB non è un numero nuovo: è `DIMENSIONE_MASSIMA_SCARICO`, lo stesso identico
435
+ * dello scarico di un file del workspace (`workspace-files.mjs`) — importato, non ricopiato,
436
+ * perché due tetti scritti in due posti sono due tetti che un giorno diranno numeri diversi.
437
+ * Serve davvero: qui i byte finiscono TUTTI in memoria prima di partire.
438
+ * @returns {Promise<{bytes:Buffer, dimensione:number, nome:string, mediaType:string}|null>} `null`
439
+ * se l'id non nomina nessuna voce (id sconosciuto o fuori grammatica).
440
+ */
441
+ export async function leggiBytesVoce({ cartella, id }, deps = {}) {
442
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
443
+ const statFn = deps.statFn ?? fsp.stat;
444
+ const cartellaVoce = cartellaDellaVoce(cartella, id);
445
+ if (!cartellaVoce) return null;
446
+ const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
447
+ if (meta === undefined) return null;
448
+ const voce = meta2voce(id, meta);
449
+ const percorsoContenuto = join(cartellaVoce, NOME_FILE_CONTENUTO);
450
+ let stat;
451
+ try {
452
+ stat = await statFn(percorsoContenuto);
453
+ } catch (errore) {
454
+ // ⛔ La scheda c'è e il file no: uno stato ROTTO, non un «non trovato» — dirlo è il solo modo perché qualcuno lo ripari.
455
+ throw new LibraryStoreError(`${id}: la scheda c'è, il file no (${errore.message})`, 'LIBRARY_READ_FAILED');
456
+ }
457
+ if (stat.size > DIMENSIONE_MASSIMA_SCARICO) {
458
+ throw new LibraryStoreError(
459
+ `File troppo grande da scaricare (${Math.round(stat.size / 1024 / 1024)} MB, tetto ${DIMENSIONE_MASSIMA_SCARICO / 1024 / 1024} MB)`,
460
+ 'LIBRARY_TOO_LARGE',
461
+ );
462
+ }
463
+ const bytes = await readFileFn(percorsoContenuto);
464
+ return { bytes, dimensione: stat.size, nome: voce.nome, mediaType: voce.mediaType };
465
+ }
466
+
467
+ /**
468
+ * Solo la provenienza — MAI il contenuto: la stessa "seconda porta"
469
+ * di `library_file_origin` mobile (owner: "ogni funzione ha DUE
470
+ * porte, la stazione e l'attrezzo" — qui l'attrezzo che risponde "chi
471
+ * ha fatto questo file" senza dover leggerlo). `null` se l'id non
472
+ * esiste.
473
+ */
474
+ export async function origineVoce({ cartella, id }, deps = {}) {
475
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
476
+ const cartellaVoce = cartellaDellaVoce(cartella, id);
477
+ if (!cartellaVoce) return null;
478
+ const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
479
+ if (meta === undefined) return null;
480
+ const voce = meta2voce(id, meta);
481
+ return { nome: voce.nome, origine: voce.origine, modello: voce.modello, provider: voce.provider, creatoIl: voce.creatoIl };
482
+ }
483
+
484
+ /**
485
+ * Un nome di Libreria non è testo libero: è un'etichetta. Porto diretto
486
+ * di `talosSafeLibraryName` mobile (`libraryWriteTools.ts` righe 48-58)
487
+ * — tolti separatori di percorso e caratteri di controllo, perché
488
+ * questo nome finisce in un file VERO su disco (`library_export`,
489
+ * seconda fetta) dove una barra smetterebbe di essere una lettera e
490
+ * diventerebbe una cartella. ⛔ Non porta `talosStripPromptEnvelope`
491
+ * (mobile la applica prima: rimuove un involucro di prompt-injection
492
+ * specifico del suo formato di messaggio) — il desktop non ha quel
493
+ * concetto, e sanificare qui i soli caratteri strutturalmente
494
+ * pericolosi resta sufficiente per un nome file.
495
+ */
496
+ export function sanificaNomeLibreria(valore) {
497
+ return String(valore ?? '')
498
+ .normalize('NFKC')
499
+ .replace(/[--Ÿ]/g, ' ')
500
+ .replace(/[/\\:*?"<>|]/g, ' ')
501
+ .replace(/\s+/g, ' ')
502
+ .trim()
503
+ .replace(/^\.+\s*/, '')
504
+ .trim();
505
+ }
506
+
507
+ /**
508
+ * Risolve un riferimento del modello (id ESATTO o nome visibile
509
+ * ESATTO) a UNA voce — porto diretto della logica di `library_export`
510
+ * mobile (`libraryExportTools.ts` righe 62-79): mai un fuzzy-match sul
511
+ * nome (case/spazi normalizzati sì, un sinonimo o un troncamento no).
512
+ * Più di un nome uguale ⇒ `{ambiguo:true}` (mai una scelta a caso: il
513
+ * modello deve chiedere); nessuna corrispondenza ⇒ `null`.
514
+ */
515
+ export function trovaVoce(voci, reference) {
516
+ const perId = voci.find((v) => v.id === reference);
517
+ if (perId) return perId;
518
+ const cercato = String(reference ?? '').normalize('NFKC').trim().toLowerCase();
519
+ const perNome = voci.filter((v) => v.nome.normalize('NFKC').trim().toLowerCase() === cercato);
520
+ if (perNome.length > 1) return { ambiguo: true };
521
+ return perNome[0] ?? null;
522
+ }
523
+
524
+ /**
525
+ * Rinomina una voce — SOLO i metadata, mai il contenuto. `null` se
526
+ * l'id non esiste; il nome nuovo è sanificato QUI (non fidato dal
527
+ * chiamante), e un nome vuoto DOPO la sanificazione è un errore
528
+ * dichiarato — mai un file rinominato al vuoto.
529
+ */
530
+ export async function rinominaVoce({ cartella, id, nome }, deps = {}) {
531
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
532
+ const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
533
+ const nomeSicuro = sanificaNomeLibreria(nome);
534
+ if (!nomeSicuro) {
535
+ throw new LibraryStoreError('Il nome è vuoto una volta tolti i caratteri di percorso — scegline uno semplice.', 'LIBRARY_NAME_EMPTY');
536
+ }
537
+ const cartellaVoce = cartellaDellaVoce(cartella, id);
538
+ if (!cartellaVoce) return null;
539
+ const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
540
+ if (meta === undefined) return null;
541
+ const nomePrima = meta.nome;
542
+ const aggiornato = { ...meta, nome: nomeSicuro, aggiornatoIl: new Date().toISOString() };
543
+ await writeFileFn(join(cartellaVoce, NOME_FILE_META), JSON.stringify(aggiornato, null, 2), 'utf8');
544
+ return { id, nomePrima, nomeDopo: nomeSicuro };
545
+ }
546
+
547
+ /**
548
+ * Elimina una voce PER INTERO (metadata + contenuto) — la cartella
549
+ * `<id>/` sparisce dal disco. `null` se l'id non esiste (mai
550
+ * un'eccezione per un id già sparito: è un esito onesto, "già andato",
551
+ * non un guasto — stesso principio di `library_delete` mobile,
552
+ * `libraryWriteTools.ts` riga 191 "It may already be gone").
553
+ */
554
+ export async function eliminaVoce({ cartella, id }, deps = {}) {
555
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
556
+ const rmFn = deps.rmFn ?? fsp.rm;
557
+ const cartellaVoce = cartellaDellaVoce(cartella, id);
558
+ if (!cartellaVoce) return null;
559
+ const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
560
+ if (meta === undefined) return null;
561
+ await rmFn(cartellaVoce, { recursive: true, force: true });
562
+ return { id, nome: meta.nome };
563
+ }
564
+
565
+ /*
566
+ * ⭐ (16/09/2026) — ELIMINAZIONE A LOTTI (richiesta owner): una sola chiamata
567
+ * `library_delete` con `ids` ripulisce N voci senza N giri del modello. L'iterazione
568
+ * riusa `eliminaVoce` COSÌ COM'È (mai una copia della sua logica: un credito futuro
569
+ * qui deve valere anche lì) e distingue l'eliminato dall'assente — un id già sparito
570
+ * NON è un guasto (stesso principio di `eliminaVoce`), finisce in `assenti` e l'esito
571
+ * resta onesto. Limiti spessi: mai un lotto che svuota la Libreria per sbaglio
572
+ * (tetto 100, una chiamata extra quando serve di più). `eliminaVoce` NON cambia.
573
+ */
574
+ export async function eliminaVoci({ cartella, ids }, deps = {}) {
575
+ if (!Array.isArray(ids) || ids.length === 0) {
576
+ throw new LibraryStoreError('Per eliminare a lotti serve un array `ids` con almeno un id.', 'LIBRARY_INVALID');
577
+ }
578
+ if (ids.length > 100) {
579
+ throw new LibraryStoreError('Massimo 100 id per chiamata: spegni la richiesta in più lotti.', 'LIBRARY_INVALID');
580
+ }
581
+ for (const id of ids) {
582
+ if (typeof id !== 'string' || !id.trim()) {
583
+ throw new LibraryStoreError('Ogni elemento di `ids` deve essere un id non vuoto (stringa).', 'LIBRARY_INVALID');
584
+ }
585
+ }
586
+ const eliminate = [];
587
+ const assenti = [];
588
+ for (const id of ids) {
589
+ const risultato = await eliminaVoce({ cartella, id }, deps);
590
+ if (risultato) eliminate.push(risultato);
591
+ else assenti.push(id);
592
+ }
593
+ return { eliminate, assenti };
594
+ }
595
+
596
+ /**
597
+ * ⛔ NON un tool del modello — primitivo interno per seminare/testare
598
+ * (e per la futura auto-archiviazione di document_create/generate_image
599
+ * nella Libreria, non ancora decisa). Un id non si passa mai dal
600
+ * chiamante: è sempre generato qui, stesso principio di ogni id in
601
+ * questo progetto.
602
+ */
603
+ /*
604
+ * ⭐⭐⭐ BC-38 (12/09/2026) — `sessionId`/`sessionNome`: DA QUALE conversazione nasce un file.
605
+ *
606
+ * ⛔ Si SCRIVONO, non si deducono. Fino a stamattina il `meta.json` non portava nessun legame con
607
+ * la sessione, e non c'era modo di ricostruirlo dopo: la Libreria è per CARTELLA di progetto,
608
+ * quindi la stessa voce compare in TUTTE le sessioni che lavorano lì (verificato il 12/09/2026:
609
+ * `c8e9b07b` ed `efc9559d` mostrano le stesse 14 voci) — l'elenco in cui una voce compare non
610
+ * dice chi l'ha prodotta.
611
+ * ⛔ Il NOME viaggia congelato accanto all'id e non al posto suo: una sessione si rinomina, e una
612
+ * voce scritta ieri deve continuare a nominare la sessione giusta anche se oggi si chiama in un
613
+ * altro modo. Chi disegna preferisce il nome VIVO quando l'elenco delle sessioni ce l'ha.
614
+ * ⛔ Assenti ⇒ il `meta.json` esce identico a ieri, campo per campo: nessun chiamante che non li
615
+ * passa cambia di un byte, e le voci vecchie restano leggibili con `sessione: null`.
616
+ */
617
+ export async function salvaVoce({ cartella, nome, mediaType, origine = 'uploaded', testo, base64, modello, provider, sessionId, sessionNome }, deps = {}) {
618
+ const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
619
+ const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
620
+ const randomUUIDFn = deps.randomUUIDFn ?? randomUUID;
621
+ if (typeof nome !== 'string' || nome.trim().length === 0) {
622
+ throw new LibraryStoreError('Un file di Libreria vuole un nome', 'LIBRARY_INVALID');
623
+ }
624
+ if (typeof testo !== 'string' && typeof base64 !== 'string') {
625
+ throw new LibraryStoreError('Un file di Libreria vuole un contenuto (testo o base64)', 'LIBRARY_INVALID');
626
+ }
627
+ const id = `lib-${randomUUIDFn()}`;
628
+ const cartellaVoce = join(cartella, CARTELLA_LIBRERIA, id);
629
+ await mkdirFn(cartellaVoce, { recursive: true });
630
+ const adesso = new Date().toISOString();
631
+ const origineEffettiva = origine === 'generated' ? 'generated' : 'uploaded';
632
+ const meta = {
633
+ nome: nome.trim(),
634
+ mediaType: mediaType || 'text/plain',
635
+ origine: origineEffettiva,
636
+ creatoIl: adesso,
637
+ aggiornatoIl: adesso,
638
+ ...(origineEffettiva === 'generated' && modello ? { modello } : {}),
639
+ ...(origineEffettiva === 'generated' && provider ? { provider } : {}),
640
+ ...(typeof sessionId === 'string' && sessionId.trim() ? { sessionId: sessionId.trim() } : {}),
641
+ ...(typeof sessionId === 'string' && sessionId.trim() && typeof sessionNome === 'string' && sessionNome.trim()
642
+ ? { sessionNome: sessionNome.trim() }
643
+ : {}),
644
+ };
645
+ await writeFileFn(join(cartellaVoce, NOME_FILE_META), JSON.stringify(meta, null, 2), 'utf8');
646
+ if (typeof base64 === 'string') {
647
+ await writeFileFn(join(cartellaVoce, NOME_FILE_CONTENUTO), Buffer.from(base64, 'base64'));
648
+ } else {
649
+ await writeFileFn(join(cartellaVoce, NOME_FILE_CONTENUTO), testo, 'utf8');
650
+ }
651
+ return id;
652
+ }