talos-code 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +661 -0
  3. package/README.md +116 -3
  4. package/THIRD_PARTY_NOTICES.md +40 -0
  5. package/dist/archive/zip.js +73 -0
  6. package/dist/args.js +100 -0
  7. package/dist/automations/history-store.js +34 -0
  8. package/dist/automations/policy-store.js +43 -0
  9. package/dist/automations/runner.js +107 -0
  10. package/dist/automations/schedule.js +85 -0
  11. package/dist/automations/windows-task.js +53 -0
  12. package/dist/commands/advanced-cli.js +233 -0
  13. package/dist/commands/checkpoint-cli.js +45 -0
  14. package/dist/commands/config-cli.js +107 -0
  15. package/dist/commands/context.js +17 -0
  16. package/dist/commands/extensions-cli.js +207 -0
  17. package/dist/commands/project-cli.js +74 -0
  18. package/dist/commands/project-commands.js +143 -0
  19. package/dist/commands/provider-cli.js +244 -0
  20. package/dist/commands/services-cli.js +248 -0
  21. package/dist/commands/session-cli.js +171 -0
  22. package/dist/commands/system-cli.js +405 -0
  23. package/dist/config/commands.js +60 -0
  24. package/dist/config/load.js +382 -0
  25. package/dist/config/migrations.js +48 -0
  26. package/dist/config/types.js +11 -0
  27. package/dist/diagnostics/development-log.js +154 -0
  28. package/dist/diagnostics/doctor.js +104 -0
  29. package/dist/diagnostics/redact.js +80 -0
  30. package/dist/diagnostics/zip.js +52 -0
  31. package/dist/errors.js +156 -0
  32. package/dist/events/bridge.js +130 -0
  33. package/dist/extensions/installer.js +178 -0
  34. package/dist/extensions/package-schema.js +24 -0
  35. package/dist/headless/result.js +84 -0
  36. package/dist/headless/run.js +230 -0
  37. package/dist/i18n/en/approval.js +46 -0
  38. package/dist/i18n/en/common.js +9 -0
  39. package/dist/i18n/en/credentials.js +92 -0
  40. package/dist/i18n/en/errors.js +267 -0
  41. package/dist/i18n/en/firstrun.js +101 -0
  42. package/dist/i18n/en/providers.js +37 -0
  43. package/dist/i18n/en/screen.js +774 -0
  44. package/dist/i18n/en/tools.js +127 -0
  45. package/dist/i18n/en.js +14 -0
  46. package/dist/i18n/error-view.js +307 -0
  47. package/dist/i18n/index.js +19 -0
  48. package/dist/i18n/kernel-map.js +139 -0
  49. package/dist/io.js +45 -0
  50. package/dist/main.js +331 -0
  51. package/dist/paths.js +41 -0
  52. package/dist/protocol/v2/codec.js +9 -0
  53. package/dist/protocol/v2/events.js +555 -0
  54. package/dist/protocol/v2/index.js +4 -0
  55. package/dist/protocol/v2/replay.js +28 -0
  56. package/dist/protocol/v2/types.js +1 -0
  57. package/dist/provider/control-plane.js +135 -0
  58. package/dist/provider/environment-keys.js +275 -0
  59. package/dist/provider/health.js +110 -0
  60. package/dist/provider/missing-key.js +48 -0
  61. package/dist/provider/model-catalog.js +168 -0
  62. package/dist/provider/openrouter-login.js +172 -0
  63. package/dist/provider/provider-text.js +13 -0
  64. package/dist/provider/store.js +95 -0
  65. package/dist/provider/system-keyring.js +214 -0
  66. package/dist/runtime/active-run.js +63 -0
  67. package/dist/runtime/agent-tree.js +74 -0
  68. package/dist/runtime/attachments.js +84 -0
  69. package/dist/runtime/brokered-executor.js +395 -0
  70. package/dist/runtime/context-archive.js +69 -0
  71. package/dist/runtime/context-status.js +76 -0
  72. package/dist/runtime/create-runtime.js +111 -0
  73. package/dist/runtime/model-profile.js +361 -0
  74. package/dist/runtime/output-store.js +137 -0
  75. package/dist/runtime/provider-attempts.js +185 -0
  76. package/dist/runtime/replay-buffer.js +153 -0
  77. package/dist/runtime/repo.js +95 -0
  78. package/dist/runtime/session-facade.js +135 -0
  79. package/dist/runtime/session-summary.js +125 -0
  80. package/dist/runtime/supervisor.js +265 -0
  81. package/dist/runtime/talos-composition.js +1011 -0
  82. package/dist/runtime/types.js +5 -0
  83. package/dist/runtime/usage-snapshot.js +82 -0
  84. package/dist/security/auto-classifier.js +38 -0
  85. package/dist/security/credential-free-environment.js +54 -0
  86. package/dist/security/evaluate.js +243 -0
  87. package/dist/security/execution-backends.js +236 -0
  88. package/dist/security/execution-broker.js +102 -0
  89. package/dist/security/forge-scan.js +74 -0
  90. package/dist/security/from-approval.js +39 -0
  91. package/dist/security/mxc-execution-backend.js +281 -0
  92. package/dist/security/permission-engine.js +138 -0
  93. package/dist/security/permission-explanation.js +54 -0
  94. package/dist/security/persist.js +179 -0
  95. package/dist/security/plugin-guard.js +230 -0
  96. package/dist/security/process-tree-evidence-store.js +74 -0
  97. package/dist/security/process-tree-probe.js +554 -0
  98. package/dist/security/project-resource-inventory.js +186 -0
  99. package/dist/security/project-trust-gate.js +38 -0
  100. package/dist/security/project-trust.js +367 -0
  101. package/dist/security/rule-parser.js +201 -0
  102. package/dist/security/shell-segmentation.js +68 -0
  103. package/dist/security/trust-authority.js +324 -0
  104. package/dist/security/types.js +1 -0
  105. package/dist/security/workspace-identity.js +102 -0
  106. package/dist/services/automation-facade.js +59 -0
  107. package/dist/services/forge-facade.js +77 -0
  108. package/dist/services/hook-facade.js +240 -0
  109. package/dist/services/index.js +16 -0
  110. package/dist/services/library-facade.js +174 -0
  111. package/dist/services/mcp-facade.js +348 -0
  112. package/dist/services/memory-facade.js +126 -0
  113. package/dist/services/notes-facade.js +157 -0
  114. package/dist/services/plugin-facade.js +308 -0
  115. package/dist/services/research-facade.js +110 -0
  116. package/dist/services/task-board-facade.js +236 -0
  117. package/dist/services/workflow-catalog.js +246 -0
  118. package/dist/sessions/export-format.js +98 -0
  119. package/dist/sessions/metadata-store.js +160 -0
  120. package/dist/sessions/share.js +122 -0
  121. package/dist/sessions/transfer.js +16 -0
  122. package/dist/subcommands.js +62 -0
  123. package/dist/tui/agent-roster.js +36 -0
  124. package/dist/tui/agent-view.js +51 -0
  125. package/dist/tui/app.js +4098 -0
  126. package/dist/tui/approval.js +36 -0
  127. package/dist/tui/boot/boot-sequence.js +76 -0
  128. package/dist/tui/boot/cinematic.js +243 -0
  129. package/dist/tui/boot/desktop-logo.js +82 -0
  130. package/dist/tui/boot.js +3 -0
  131. package/dist/tui/busy-input.js +58 -0
  132. package/dist/tui/catalog-service.js +560 -0
  133. package/dist/tui/components/assistant-stream.js +1 -0
  134. package/dist/tui/components/command-menu.js +68 -0
  135. package/dist/tui/components/composer.js +219 -0
  136. package/dist/tui/components/diff.js +35 -0
  137. package/dist/tui/components/footer.js +48 -0
  138. package/dist/tui/components/header.js +6 -0
  139. package/dist/tui/components/markdown.js +305 -0
  140. package/dist/tui/components/status-indicator.js +38 -0
  141. package/dist/tui/components/terminal-shell.js +214 -0
  142. package/dist/tui/components/thinking-row.js +19 -0
  143. package/dist/tui/components/tool-row.js +97 -0
  144. package/dist/tui/components/transcript-virtualizer.js +165 -0
  145. package/dist/tui/components/transcript.js +43 -0
  146. package/dist/tui/descendant-approvals.js +104 -0
  147. package/dist/tui/diff-model.js +77 -0
  148. package/dist/tui/editor-history.js +21 -0
  149. package/dist/tui/editor.js +210 -0
  150. package/dist/tui/event-adapter.js +436 -0
  151. package/dist/tui/exit-output.js +58 -0
  152. package/dist/tui/external-editor.js +53 -0
  153. package/dist/tui/file-completion.js +89 -0
  154. package/dist/tui/focus-manager.js +5 -0
  155. package/dist/tui/highlight.js +30 -0
  156. package/dist/tui/input-router.js +18 -0
  157. package/dist/tui/interrupt.js +99 -0
  158. package/dist/tui/keybindings.js +194 -0
  159. package/dist/tui/keymap-resolver.js +91 -0
  160. package/dist/tui/launch-state.js +148 -0
  161. package/dist/tui/line-diff.js +57 -0
  162. package/dist/tui/live-activity.js +56 -0
  163. package/dist/tui/metrics.js +123 -0
  164. package/dist/tui/onboarding.js +36 -0
  165. package/dist/tui/overlays/agent-tree.js +43 -0
  166. package/dist/tui/overlays/approval-dialog.js +642 -0
  167. package/dist/tui/overlays/automation-center.js +51 -0
  168. package/dist/tui/overlays/checkpoint-picker.js +47 -0
  169. package/dist/tui/overlays/context-inspector.js +36 -0
  170. package/dist/tui/overlays/effort-line.js +110 -0
  171. package/dist/tui/overlays/forge-center.js +46 -0
  172. package/dist/tui/overlays/help-dialog.js +16 -0
  173. package/dist/tui/overlays/history-picker.js +37 -0
  174. package/dist/tui/overlays/hook-center.js +70 -0
  175. package/dist/tui/overlays/library-center.js +55 -0
  176. package/dist/tui/overlays/mcp-center.js +60 -0
  177. package/dist/tui/overlays/memory-center.js +63 -0
  178. package/dist/tui/overlays/model-picker.js +77 -0
  179. package/dist/tui/overlays/notes-center.js +53 -0
  180. package/dist/tui/overlays/overlay-host.js +8 -0
  181. package/dist/tui/overlays/plugin-center.js +76 -0
  182. package/dist/tui/overlays/provider-picker.js +171 -0
  183. package/dist/tui/overlays/queue-editor.js +32 -0
  184. package/dist/tui/overlays/research-center.js +59 -0
  185. package/dist/tui/overlays/scroll-window.js +234 -0
  186. package/dist/tui/overlays/session-picker.js +115 -0
  187. package/dist/tui/overlays/tasks-center.js +83 -0
  188. package/dist/tui/overlays/theme-picker.js +21 -0
  189. package/dist/tui/overlays/transcript-search.js +58 -0
  190. package/dist/tui/overlays/trust-center.js +29 -0
  191. package/dist/tui/overlays/workflow-center.js +46 -0
  192. package/dist/tui/project-file-index.js +214 -0
  193. package/dist/tui/project-references.js +85 -0
  194. package/dist/tui/project-trust-prompt.js +289 -0
  195. package/dist/tui/prompt-history-store.js +116 -0
  196. package/dist/tui/queue-store.js +110 -0
  197. package/dist/tui/regions/budget.js +59 -0
  198. package/dist/tui/regions/views.js +96 -0
  199. package/dist/tui/render-coordinator.js +148 -0
  200. package/dist/tui/render-scheduler.js +4 -0
  201. package/dist/tui/run.js +69 -0
  202. package/dist/tui/selection-list.js +29 -0
  203. package/dist/tui/session-controller.js +791 -0
  204. package/dist/tui/session-export.js +54 -0
  205. package/dist/tui/setup-wizard.js +46 -0
  206. package/dist/tui/shell-input.js +35 -0
  207. package/dist/tui/shell-layout.js +48 -0
  208. package/dist/tui/shell-model.js +98 -0
  209. package/dist/tui/slash-commands.js +82 -0
  210. package/dist/tui/state.js +151 -0
  211. package/dist/tui/status-bar.js +125 -0
  212. package/dist/tui/status-view.js +40 -0
  213. package/dist/tui/terminal-capabilities.js +9 -0
  214. package/dist/tui/terminal-session.js +11 -0
  215. package/dist/tui/text-width.js +66 -0
  216. package/dist/tui/theme-catalog.js +27 -0
  217. package/dist/tui/theme-store.js +23 -0
  218. package/dist/tui/theme.js +23 -0
  219. package/dist/tui/tool-display.js +135 -0
  220. package/dist/tui/tool-facts.js +1 -0
  221. package/dist/tui/tools/bash-renderer.js +32 -0
  222. package/dist/tui/tools/change.js +61 -0
  223. package/dist/tui/tools/edit-renderer.js +17 -0
  224. package/dist/tui/tools/generic-renderer.js +13 -0
  225. package/dist/tui/tools/list-renderer.js +14 -0
  226. package/dist/tui/tools/read-renderer.js +13 -0
  227. package/dist/tui/tools/registry.js +20 -0
  228. package/dist/tui/tools/row-format.js +187 -0
  229. package/dist/tui/tools/search-renderer.js +38 -0
  230. package/dist/tui/tools/shared.js +97 -0
  231. package/dist/tui/tools/write-renderer.js +15 -0
  232. package/dist/tui/transcript-model.js +441 -0
  233. package/dist/tui/ui-preferences.js +79 -0
  234. package/dist/tui/usage-view.js +77 -0
  235. package/dist/tui/vim-mode.js +99 -0
  236. package/dist/update/check.js +15 -0
  237. package/dist/update/npm.js +51 -0
  238. package/dist/update/run.js +129 -0
  239. package/dist/version.js +2 -0
  240. package/dist/workspace/checkpoint-store.js +210 -0
  241. package/dist/workspace/checkpoint.js +413 -0
  242. package/dist/workspace/restore.js +232 -0
  243. package/package.json +64 -5
  244. package/vendor/context-engine/package.json +11 -0
  245. package/vendor/context-engine/src/compaction-planner.mjs +57 -0
  246. package/vendor/context-engine/src/contracts.mjs +48 -0
  247. package/vendor/context-engine/src/engine.mjs +445 -0
  248. package/vendor/context-engine/src/node/context-export.mjs +164 -0
  249. package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
  250. package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
  251. package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
  252. package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
  253. package/vendor/context-engine/src/profiles.mjs +10 -0
  254. package/vendor/context-engine/src/retrieval.mjs +104 -0
  255. package/vendor/context-engine/src/summary.mjs +97 -0
  256. package/vendor/context-engine/src/usage.mjs +34 -0
  257. package/vendor/harness-ui/package.json +38 -0
  258. package/vendor/harness-ui/src/acp-agent.mjs +350 -0
  259. package/vendor/harness-ui/src/agent-service.mjs +2188 -0
  260. package/vendor/harness-ui/src/agui-events.mjs +452 -0
  261. package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
  262. package/vendor/harness-ui/src/artifact-store.mjs +39 -0
  263. package/vendor/harness-ui/src/assistenza.mjs +123 -0
  264. package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
  265. package/vendor/harness-ui/src/automation-store.mjs +145 -0
  266. package/vendor/harness-ui/src/browser-annota.mjs +639 -0
  267. package/vendor/harness-ui/src/browser-frame.mjs +211 -0
  268. package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
  269. package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
  270. package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
  271. package/vendor/harness-ui/src/browser-stream.mjs +445 -0
  272. package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
  273. package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
  274. package/vendor/harness-ui/src/config.mjs +697 -0
  275. package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
  276. package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
  277. package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
  278. package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
  279. package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
  280. package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
  281. package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
  282. package/vendor/harness-ui/src/context-runtime.mjs +118 -0
  283. package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
  284. package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
  285. package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
  286. package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
  287. package/vendor/harness-ui/src/custom-task.mjs +171 -0
  288. package/vendor/harness-ui/src/doctor.mjs +142 -0
  289. package/vendor/harness-ui/src/document-filename.mjs +97 -0
  290. package/vendor/harness-ui/src/document-generator.mjs +493 -0
  291. package/vendor/harness-ui/src/document-report.mjs +331 -0
  292. package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
  293. package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
  294. package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
  295. package/vendor/harness-ui/src/forge-contract.mjs +221 -0
  296. package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
  297. package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
  298. package/vendor/harness-ui/src/generation-idle.mjs +332 -0
  299. package/vendor/harness-ui/src/gguf-header.mjs +207 -0
  300. package/vendor/harness-ui/src/git-service.mjs +626 -0
  301. package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
  302. package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
  303. package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
  304. package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
  305. package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
  306. package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
  307. package/vendor/harness-ui/src/hook-registry.mjs +186 -0
  308. package/vendor/harness-ui/src/http-app.mjs +6259 -0
  309. package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
  310. package/vendor/harness-ui/src/id-archivio.mjs +27 -0
  311. package/vendor/harness-ui/src/image-generator.mjs +143 -0
  312. package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
  313. package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
  314. package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
  315. package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
  316. package/vendor/harness-ui/src/library-store.mjs +652 -0
  317. package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
  318. package/vendor/harness-ui/src/local-model-store.mjs +339 -0
  319. package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
  320. package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
  321. package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
  322. package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
  323. package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
  324. package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
  325. package/vendor/harness-ui/src/mcp-client.mjs +98 -0
  326. package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
  327. package/vendor/harness-ui/src/mcp-session.mjs +177 -0
  328. package/vendor/harness-ui/src/memory-store.mjs +227 -0
  329. package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
  330. package/vendor/harness-ui/src/model-catalog.mjs +129 -0
  331. package/vendor/harness-ui/src/model-destination.mjs +189 -0
  332. package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
  333. package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
  334. package/vendor/harness-ui/src/notes-store.mjs +250 -0
  335. package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
  336. package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
  337. package/vendor/harness-ui/src/path-policy.mjs +442 -0
  338. package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
  339. package/vendor/harness-ui/src/plugin-session.mjs +180 -0
  340. package/vendor/harness-ui/src/process-policy.mjs +345 -0
  341. package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
  342. package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
  343. package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
  344. package/vendor/harness-ui/src/provider-probe.mjs +582 -0
  345. package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
  346. package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
  347. package/vendor/harness-ui/src/public-problem.mjs +109 -0
  348. package/vendor/harness-ui/src/research/card.mjs +235 -0
  349. package/vendor/harness-ui/src/research/citations.mjs +142 -0
  350. package/vendor/harness-ui/src/research/collector.mjs +275 -0
  351. package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
  352. package/vendor/harness-ui/src/research/dossier.mjs +114 -0
  353. package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
  354. package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
  355. package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
  356. package/vendor/harness-ui/src/research/independence.mjs +159 -0
  357. package/vendor/harness-ui/src/research/ledger.mjs +166 -0
  358. package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
  359. package/vendor/harness-ui/src/research/narration.mjs +181 -0
  360. package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
  361. package/vendor/harness-ui/src/research/opposing.mjs +305 -0
  362. package/vendor/harness-ui/src/research/outline.mjs +111 -0
  363. package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
  364. package/vendor/harness-ui/src/research/pdf.mjs +291 -0
  365. package/vendor/harness-ui/src/research/plan.mjs +301 -0
  366. package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
  367. package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
  368. package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
  369. package/vendor/harness-ui/src/research/recheck.mjs +194 -0
  370. package/vendor/harness-ui/src/research/report.mjs +203 -0
  371. package/vendor/harness-ui/src/research/run.mjs +527 -0
  372. package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
  373. package/vendor/harness-ui/src/research/verification.mjs +572 -0
  374. package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
  375. package/vendor/harness-ui/src/research-store.mjs +1133 -0
  376. package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
  377. package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
  378. package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
  379. package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
  380. package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
  381. package/vendor/harness-ui/src/search-source-store.mjs +172 -0
  382. package/vendor/harness-ui/src/session-registry.mjs +6095 -0
  383. package/vendor/harness-ui/src/session-store.mjs +220 -0
  384. package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
  385. package/vendor/harness-ui/src/setup-stato.mjs +31 -0
  386. package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
  387. package/vendor/harness-ui/src/skill-registry.mjs +120 -0
  388. package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
  389. package/vendor/harness-ui/src/static-files.mjs +96 -0
  390. package/vendor/harness-ui/src/stream-partition.mjs +123 -0
  391. package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
  392. package/vendor/harness-ui/src/task-catalog.mjs +65 -0
  393. package/vendor/harness-ui/src/tasks-store.mjs +220 -0
  394. package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
  395. package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
  396. package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
  397. package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
  398. package/vendor/harness-ui/src/usage-cache.mjs +315 -0
  399. package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
  400. package/vendor/harness-ui/src/workspace-context.mjs +124 -0
  401. package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
  402. package/vendor/harness-ui/src/workspace-files.mjs +589 -0
  403. package/vendor/harness-ui/src/workspace-info.mjs +189 -0
  404. package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
  405. package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
  406. package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
  407. package/vendor/manifest.json +170 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * mcp-registry.mjs — FASE E del piano `elegant-spinning-dongarra.md`,
3
+ * seconda fetta (29/8): dove vive la configurazione dei server MCP
4
+ * per-workspace, e il trust che decide se un server dichiarato può
5
+ * davvero connettersi. `hook-registry.mjs` letto per intero come
6
+ * precedente diretto prima di scrivere questo file — stesso design,
7
+ * per lo stesso motivo: un comando esterno arbitrario (qui: un intero
8
+ * server a lunga vita, con i SUOI tool, non solo un controllo rapido)
9
+ * non deve MAI partire senza un consenso esplicito dell'owner, e quel
10
+ * consenso resta legato al CONTENUTO della dichiarazione (hash), non
11
+ * al nome.
12
+ *
13
+ * ⛔⛔ Due cartelle DIVERSE, stesso motivo di hook-registry.mjs:
14
+ * - `.harness-ui-mcp.json` vive DENTRO il workspace (come hooks.json,
15
+ * AGENTS.md) — "quali server MCP servono questo progetto" è una
16
+ * proprietà del progetto, non della macchina.
17
+ * - il trust vive FUORI, accanto a server.mjs (stesso pattern di
18
+ * `.automations/` e del trust degli hook).
19
+ *
20
+ * ⛔⛔⛔ L'`allowlist` per server è OBBLIGATORIA nello schema, non
21
+ * opzionale — a differenza di `filtraToolMcp` in mcp-client.mjs (che
22
+ * accetta qualunque array, anche vuoto, perché non è compito suo
23
+ * imporre una policy). Qui SÌ: un server dichiarato senza allowlist è
24
+ * un file MALFORMATO, rifiutato prima ancora di poter connettersi —
25
+ * "mai un tool MCP remoto esposto senza filtro" (piano madre, §2.2)
26
+ * è applicato al punto più a monte
27
+ * possibile, non lasciato alla disciplina di chi integra dopo.
28
+ *
29
+ * Il PROTOCOLLO (connettersi, elencare, chiamare) resta interamente
30
+ * in mcp-client.mjs — questo file non lo importa e non lo usa: solo
31
+ * configurazione e trust, stesso confine "qui solo X, mai Y" già
32
+ * documentato nella testa di mcp-client.mjs stesso.
33
+ */
34
+ import { createHash } from 'node:crypto';
35
+ import { promises as fsp } from 'node:fs';
36
+ import { join } from 'node:path';
37
+
38
+ export class McpRegistryError extends Error {
39
+ constructor(message, code = 'MCP_CONFIG_INVALID') {
40
+ super(message);
41
+ this.name = 'McpRegistryError';
42
+ this.code = code;
43
+ }
44
+ }
45
+
46
+ const NOME_FILE_MCP = '.harness-ui-mcp.json';
47
+
48
+ /**
49
+ * Legge `<cartella>/.harness-ui-mcp.json`. Un progetto senza server
50
+ * dichiarati è uno stato valido — mai un errore, torna `{ server: [] }`.
51
+ * Uno schema malformato invece È un errore dichiarato (mai un server
52
+ * fantasma silenziosamente ignorato, stesso principio di caricaHooks).
53
+ */
54
+ export async function caricaServerMcp({ cartella }, deps = {}) {
55
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
56
+ const percorso = join(cartella, NOME_FILE_MCP);
57
+ let testo;
58
+ try {
59
+ testo = await readFileFn(percorso, 'utf8');
60
+ } catch (errore) {
61
+ if (errore?.code === 'ENOENT') return { server: [] };
62
+ throw new McpRegistryError(`Impossibile leggere ${NOME_FILE_MCP}: ${errore.message}`, 'MCP_CONFIG_READ_FAILED');
63
+ }
64
+ let dati;
65
+ try {
66
+ dati = JSON.parse(testo);
67
+ } catch {
68
+ throw new McpRegistryError(`${NOME_FILE_MCP} non è un JSON valido`, 'MCP_CONFIG_MALFORMED');
69
+ }
70
+ if (!dati || !Array.isArray(dati.server)) {
71
+ throw new McpRegistryError(`${NOME_FILE_MCP} deve avere un campo "server" (array)`, 'MCP_CONFIG_MALFORMED');
72
+ }
73
+ const server = dati.server.map((voce, indice) => {
74
+ if (typeof voce?.id !== 'string' || voce.id.length === 0) {
75
+ throw new McpRegistryError(`server[${indice}] manca di "id" (stringa non vuota)`, 'MCP_CONFIG_MALFORMED');
76
+ }
77
+ if (typeof voce.comando !== 'string' || voce.comando.length === 0) {
78
+ throw new McpRegistryError(`server[${indice}] ("${voce.id}") manca di "comando" (stringa non vuota)`, 'MCP_CONFIG_MALFORMED');
79
+ }
80
+ if (voce.argomenti !== undefined && (!Array.isArray(voce.argomenti) || !voce.argomenti.every((a) => typeof a === 'string'))) {
81
+ throw new McpRegistryError(`server[${indice}] ("${voce.id}") ha "argomenti" non valido — atteso un array di stringhe`, 'MCP_CONFIG_MALFORMED');
82
+ }
83
+ if (!Array.isArray(voce.allowlist) || voce.allowlist.length === 0 || !voce.allowlist.every((n) => typeof n === 'string' && n.length > 0)) {
84
+ throw new McpRegistryError(`server[${indice}] ("${voce.id}") manca di "allowlist" (array non vuoto di nomi tool) — un server MCP non si dichiara senza dire quali tool sono ammessi`, 'MCP_CONFIG_MALFORMED');
85
+ }
86
+ const argomenti = voce.argomenti ?? [];
87
+ const hash = createHash('sha256').update(JSON.stringify({ comando: voce.comando, argomenti, allowlist: voce.allowlist })).digest('hex');
88
+ return { id: voce.id, comando: voce.comando, argomenti, allowlist: voce.allowlist, hash };
89
+ });
90
+ return { server };
91
+ }
92
+
93
+ function percorsoTrust(cartellaTrust, serverId) {
94
+ // ⛔ serverId arriva da un file scritto dall'owner (.harness-ui-mcp.json),
95
+ // ma è comunque un input esterno: mai costruire un percorso da una
96
+ // stringa non validata come nome file — stesso principio già in uso
97
+ // in hook-registry.mjs/workspace-files.mjs.
98
+ if (typeof serverId !== 'string' || serverId.length === 0 || /[\\/]|\.\./.test(serverId)) {
99
+ throw new McpRegistryError('serverId non valido — un nome, non un percorso', 'MCP_SERVER_ID_INVALID');
100
+ }
101
+ return join(cartellaTrust, `${serverId}.json`);
102
+ }
103
+
104
+ /**
105
+ * Un server è fidato SOLO se il registro persistito porta lo STESSO
106
+ * hash della dichiarazione attuale (comando+argomenti+allowlist) — un
107
+ * server il cui contenuto è cambiato torna automaticamente "non
108
+ * fidato", senza bisogno di un'azione esplicita di sfiducia: il trust
109
+ * è legato al CONTENUTO, non al nome. Stesso comportamento di
110
+ * verificaTrust in hook-registry.mjs.
111
+ */
112
+ export async function verificaTrustMcp({ cartellaTrust, serverId, hash }, deps = {}) {
113
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
114
+ let testo;
115
+ try {
116
+ testo = await readFileFn(percorsoTrust(cartellaTrust, serverId), 'utf8');
117
+ } catch {
118
+ return false; // mai fidato per default: assente = non fidato, non un errore
119
+ }
120
+ try {
121
+ const dati = JSON.parse(testo);
122
+ return dati?.hash === hash;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Registra il trust — chiamata SOLO da un'azione owner esplicita (la
130
+ * rotta HTTP dedicata, mai automatica dal ciclo dell'agente o
131
+ * dall'avvio di una sessione).
132
+ */
133
+ export async function fidaServerMcp({ cartellaTrust, serverId, hash }, deps = {}) {
134
+ const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
135
+ const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
136
+ await mkdirFn(cartellaTrust, { recursive: true });
137
+ await writeFileFn(percorsoTrust(cartellaTrust, serverId), JSON.stringify({ hash, fidatoIl: new Date().toISOString() }), 'utf8');
138
+ return { fidato: true };
139
+ }
140
+
141
+ /**
142
+ * Il punto d'ingresso per chi avvia una sessione (session-registry.mjs):
143
+ * carica i server dichiarati, tiene SOLO quelli fidati (stesso
144
+ * fail-closed di hook-registry.mjs — un server non fidato semplicemente
145
+ * non compare, mai un errore che blocca l'avvio della sessione: la
146
+ * sessione parte comunque, solo senza quel server), e torna l'elenco
147
+ * pronto per essere connesso da mcp-client.mjs.
148
+ */
149
+ export async function serverMcpFidati({ cartella, cartellaTrust }, deps = {}) {
150
+ const { server } = await caricaServerMcp({ cartella }, deps);
151
+ const fidati = [];
152
+ for (const s of server) {
153
+ const fidato = await verificaTrustMcp({ cartellaTrust, serverId: s.id, hash: s.hash }, deps);
154
+ if (fidato) fidati.push(s);
155
+ }
156
+ return { fidati, nonFidati: server.filter((s) => !fidati.includes(s)) };
157
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * mcp-session.mjs — FASE E del piano `elegant-spinning-dongarra.md`,
3
+ * terza fetta (29/8): il punto che UNISCE `mcp-registry.mjs` (quali
4
+ * server sono dichiarati e fidati) e `mcp-client.mjs` (il protocollo)
5
+ * in quello che una sessione REALE offre al kernel — `toolMcp` +
6
+ * `chiamaToolMcpFn`, esattamente la forma che `talosLavora` accetta
7
+ * (vedi `AVM-harness/.../talosHarness.mjs`, commit `a902af7`).
8
+ *
9
+ * ⛔ A differenza degli hook (`costruisciHookFn`, letto per intero
10
+ * prima di scrivere questo file): un hook si legge PIGRAMENTE, alla
11
+ * prima tool-call, perché serve solo quando un evento capita. I tool
12
+ * MCP invece devono essere NOTI PRIMA della prima chiamata al modello
13
+ * (finiscono nello schema `tools` della richiesta) — questo file
14
+ * connette tutti i server fidati EAGERMENTE, una volta, prima che
15
+ * `talosLavora` parta.
16
+ *
17
+ * ⛔⛔⛔ Piu' server possono esporre un tool con lo STESSO nome (`read_file`
18
+ * su due server diversi non e' un caso raro — sono processi
19
+ * indipendenti, non sanno l'uno dell'altro). Il nome esposto al
20
+ * modello e' quindi SEMPRE prefissato `mcp__<serverId>__<toolName>`
21
+ * (stessa convenzione dell'ecosistema MCP, non inventata qui) — un
22
+ * dispatcher interno lo demux nel client/nome originali giusti,
23
+ * mai un'ambiguita' silenziosa fra due server.
24
+ *
25
+ * Un server che non parte (comando sbagliato, dipendenza mancante)
26
+ * non impedisce MAI alla sessione di avviarsi — stesso principio
27
+ * "mai un blocco silenzioso, degrada" gia' in uso per gli hook: viene
28
+ * saltato, e il suo fallimento e' registrato per chi chiama (mai
29
+ * inghiottito senza traccia).
30
+ */
31
+ import {
32
+ caricaServerMcp as caricaServerMcpReale,
33
+ serverMcpFidati as serverMcpFidatiReale,
34
+ } from './mcp-registry.mjs';
35
+ import { schemaIngressoAttrezzo } from './tool-schema-normalize.mjs';
36
+ import {
37
+ chiamaToolMcp as chiamaToolMcpReale,
38
+ connettiServerMcp as connettiServerMcpReale,
39
+ elencaToolMcp as elencaToolMcpReale,
40
+ filtraToolMcp as filtraToolMcpReale,
41
+ } from './mcp-client.mjs';
42
+
43
+ const SEPARATORE = '__';
44
+ const PREFISSO = `mcp${SEPARATORE}`;
45
+
46
+ /** Pura: costruisce il nome esposto al modello. Un test la esercita senza connettere nulla. */
47
+ export function nomeEspostoMcp(serverId, nomeTool) {
48
+ return `${PREFISSO}${serverId}${SEPARATORE}${nomeTool}`;
49
+ }
50
+
51
+ /** Tetto della partenza in parallelo: oltre, si aprirebbero troppi processi figli in un colpo all'avvio di una sessione. */
52
+ export const CONCORRENZA_AVVIO_MCP_MASSIMA = 8;
53
+
54
+ /*
55
+ * ⭐ 14/09 (F07 della review) — la scoperta dei tool era SERIALE: N server fidati costavano la SOMMA dei loro avvii,
56
+ * e ogni avvio è un processo figlio che fa handshake, cioè attesa, non calcolo. Misurato su quattro server d'eco:
57
+ * mediana **121 ms** in fila contro **46 ms** a quattro per volta.
58
+ * ⛔ Resta SPENTA di serie (`1` = il comportamento di prima, byte per byte): un server MCP di terzi può avere un
59
+ * avvio pesante, e far partire più processi insieme sulla macchina di qualcun altro non è una scelta che prendo io.
60
+ * Chi la vuole la accende con `TALOS_MCP_STARTUP_CONCURRENCY`.
61
+ * ⛔ Qualunque valore storto — vuoto, «due», 0, -3, 2.5 — torna **1**, mai un errore: una variabile d'ambiente scritta
62
+ * male non deve impedire a una sessione di partire.
63
+ */
64
+ export function concorrenzaAvvioMcp(env = process.env) {
65
+ const grezzo = env?.TALOS_MCP_STARTUP_CONCURRENCY;
66
+ if (typeof grezzo !== 'string' && typeof grezzo !== 'number') return 1;
67
+ const numero = Number(String(grezzo).trim());
68
+ if (!Number.isInteger(numero) || numero < 1) return 1;
69
+ return Math.min(numero, CONCORRENZA_AVVIO_MCP_MASSIMA);
70
+ }
71
+
72
+ /**
73
+ * Connette OGNI server fidato del workspace, scopre e filtra i suoi
74
+ * tool con la sua allowlist (mai "tutto ciò che offre" — la stessa
75
+ * garanzia che `filtraToolMcp` già dà, applicata qui a ogni server
76
+ * dichiarato), e torna `{toolMcp, chiamaToolMcpFn, falliti, chiudiTutti}`
77
+ * pronto per `talosLavora`.
78
+ *
79
+ * `falliti`: `{serverId, errore}[]` — i server che NON sono partiti,
80
+ * mai inghiottiti in silenzio (chi chiama decide se avvisare l'owner).
81
+ * `chiudiTutti()`: chiude OGNI connessione aperta — va chiamato quando
82
+ * la sessione finisce, altrimenti il processo figlio del server resta
83
+ * vivo (stesso rischio già noto per ogni sottoprocesso spawnato).
84
+ */
85
+ export async function preparaToolMcpPerSessione({ cartella, cartellaTrust }, deps = {}) {
86
+ const serverMcpFidatiFn = deps.serverMcpFidatiFn ?? serverMcpFidatiReale;
87
+ const connettiServerMcpFn = deps.connettiServerMcpFn ?? connettiServerMcpReale;
88
+ const elencaToolMcpFn = deps.elencaToolMcpFn ?? elencaToolMcpReale;
89
+ const filtraToolMcpFn = deps.filtraToolMcpFn ?? filtraToolMcpReale;
90
+ const chiamaToolMcpFnReale = deps.chiamaToolMcpFn ?? chiamaToolMcpReale;
91
+
92
+ let fidati = [];
93
+ try {
94
+ ({ fidati } = await serverMcpFidatiFn({ cartella, cartellaTrust }));
95
+ } catch {
96
+ // ⭐ stesso principio degli hook: un registro che non si legge non blocca la sessione, degrada a "nessun server".
97
+ return { toolMcp: [], chiamaToolMcpFn: null, falliti: [], chiudiTutti: async () => {} };
98
+ }
99
+
100
+ const toolMcp = [];
101
+ const instradamento = new Map(); // nomeEsposto -> { client, nomeOriginale }
102
+ const connessioni = []; // { client, chiudi } — per chiudiTutti
103
+ const falliti = [];
104
+
105
+ /** L'avvio di UN server: non lancia mai — un fallimento è un esito come un altro, da comporre in ordine più sotto. */
106
+ async function avviaUnServer(server) {
107
+ let client;
108
+ let chiudi;
109
+ try {
110
+ ({ client, chiudi } = await connettiServerMcpFn({ comando: server.comando, argomenti: server.argomenti, nome: `talos-harness-${server.id}` }));
111
+ } catch (errore) {
112
+ // un server che non parte non ferma gli altri, ne' la sessione
113
+ return { server, errore: errore instanceof Error ? errore.message : String(errore) };
114
+ }
115
+ let tuttiITool;
116
+ try {
117
+ tuttiITool = await elencaToolMcpFn({ client });
118
+ } catch (errore) {
119
+ // la connessione resta aperta (chiusa comunque da chiudiTutti), ma zero tool da questo server
120
+ return { server, client, chiudi, errore: `elenco tool fallito: ${errore instanceof Error ? errore.message : String(errore)}` };
121
+ }
122
+ return { server, client, chiudi, tool: filtraToolMcpFn(tuttiITool, server.allowlist) };
123
+ }
124
+
125
+ /*
126
+ * ⛔ L'ORDINE del risultato non dipende da chi finisce prima: gli esiti si depositano nella casella del loro server e
127
+ * si compongono dopo, in fila. Senza, `toolMcp` cambierebbe ordine a ogni avvio a seconda di quale processo figlio
128
+ * è stato più svelto — e con due server che espongono lo stesso attrezzo cambierebbe anche quale vince a schermo.
129
+ */
130
+ const concorrenza = Math.max(1, Math.min(deps.concorrenza ?? concorrenzaAvvioMcp(deps.env), CONCORRENZA_AVVIO_MCP_MASSIMA));
131
+ const esiti = new Array(fidati.length).fill(null);
132
+ let prossimo = 0;
133
+ async function unLavoratore() {
134
+ while (prossimo < fidati.length) {
135
+ const indice = prossimo;
136
+ prossimo += 1;
137
+ esiti[indice] = await avviaUnServer(fidati[indice]);
138
+ }
139
+ }
140
+ await Promise.all(Array.from({ length: Math.min(concorrenza, fidati.length) }, () => unLavoratore()));
141
+
142
+ for (const esito of esiti) {
143
+ if (!esito) continue;
144
+ if (esito.chiudi) connessioni.push({ client: esito.client, chiudi: esito.chiudi });
145
+ if (esito.errore) {
146
+ falliti.push({ serverId: esito.server.id, errore: esito.errore });
147
+ continue;
148
+ }
149
+ for (const t of esito.tool) {
150
+ const nomeEsposto = nomeEspostoMcp(esito.server.id, t.name);
151
+ /*
152
+ * ⛔ Stessa cura degli attrezzi Forge, e qui serve ancora di più: questo
153
+ * schema arriva da un server MCP di TERZI, che non abbiamo scritto noi e
154
+ * non possiamo correggere. Passarlo intatto a un motore che ne costruisce
155
+ * una grammatica fa fallire l'intera richiesta — non solo quell'attrezzo.
156
+ */
157
+ toolMcp.push({ name: nomeEsposto, description: t.description, inputSchema: schemaIngressoAttrezzo(t.inputSchema) });
158
+ instradamento.set(nomeEsposto, { client: esito.client, nomeOriginale: t.name });
159
+ }
160
+ }
161
+
162
+ const chiamaToolMcpFn = toolMcp.length > 0
163
+ ? async (nomeEsposto, argomenti) => {
164
+ const voce = instradamento.get(nomeEsposto);
165
+ if (!voce) throw new Error(`tool MCP "${nomeEsposto}" non è fra quelli connessi in questa sessione`);
166
+ return chiamaToolMcpFnReale({ client: voce.client }, voce.nomeOriginale, argomenti);
167
+ }
168
+ : null;
169
+
170
+ const chiudiTutti = async () => {
171
+ for (const { chiudi } of connessioni) {
172
+ try { await chiudi(); } catch { /* un server che non chiude pulito non deve bloccare la chiusura degli altri */ }
173
+ }
174
+ };
175
+
176
+ return { toolMcp, chiamaToolMcpFn, falliti, chiudiTutti };
177
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * memory-store.mjs — FASE N, sesto sistema (30/8), piano
3
+ * `elegant-spinning-dongarra.md`. Owner: "TUTTI i 5 sistemi rimasti, uno
4
+ * dopo l'altro".
5
+ *
6
+ * ⛔⛔⛔ GLOBALE, non per-progetto — stesso principio di notes-store.mjs/
7
+ * tasks-store.mjs. Letto alla fonte: `mobile/src/lib/tools/memoryWriteTools.ts`
8
+ * + `readTools.ts` + `chatDatabaseSchema.ts` (`talos_memories`) +
9
+ * `stationFacades.ts`/`chatController.ts` (l'implementazione VERA dietro
10
+ * l'interfaccia del tool, non presunta). Lo schema mobile ha uno
11
+ * `scope_type` a tre livelli (global/project/session), ma il CHIAMANTE
12
+ * del tool lo fissa a `'global'` SEMPRE, verificato in
13
+ * `chatController.ts`: "«ricordati che preferisco le risposte brevi»
14
+ * non vale solo in questa conversazione" — questo porto riflette solo
15
+ * ciò che il tool espone davvero, non l'intero schema del deposito.
16
+ *
17
+ * ⛔ `status` (active/disabled/quarantined/rejected nello schema mobile)
18
+ * NON portato: quella macchina a stati è per la STAZIONE mobile
19
+ * (moderazione manuale) — verificato in `stationFacades.ts`,
20
+ * `TalosMemoryWriteSources` non ha un `setStatus`. `memory_delete`
21
+ * desktop fa una cancellazione VERA (mobile stesso chiama
22
+ * `deleteMemory`, un hard delete — confermato alla fonte, non un
23
+ * cambio di stato), stesso schema di notes_delete/tasks_delete.
24
+ *
25
+ * ⭐⭐⭐ L'UNICA logica genuinamente nuova rispetto a Notes/Tasks: la
26
+ * DEDUPLICAZIONE per titolo. Owner mobile, 2026-08-07: "«no, ricordati
27
+ * invece che...» produceva una seconda memoria accanto alla prima" — la
28
+ * memoria è l'unica superficie in cui un duplicato non è disordine, è
29
+ * una contraddizione che si autoalimenta a ogni conversazione futura.
30
+ * `creaMemoria` cerca PRIMA per titolo (case-insensitive, spazi
31
+ * ignorati) e torna la voce ESISTENTE con `duplicato:true` invece di
32
+ * scriverne una seconda.
33
+ *
34
+ * ⛔ Diverso da mobile in UN dettaglio, dichiarato: mobile `create()`
35
+ * NON torna un id ("l'ha detto il typecheck, non io" — un vincolo
36
+ * accidentale della sua implementazione, non una scelta di design,
37
+ * verificato leggendo il commento originale). Questo store non ha
38
+ * quel vincolo: `creaMemoria` torna sempre l'id, per coerenza con
39
+ * notes-store.mjs/tasks-store.mjs (il messaggio di successo desktop
40
+ * include l'id, come "Saved the note «X» (id Y)." — mobile non lo fa
41
+ * per un accidente della sua repository, non per principio).
42
+ *
43
+ * ⛔ Stessa CONVENZIONE DI CHIAMATA di notes-store.mjs/tasks-store.mjs
44
+ * (`funzione({cartella, ...}, deps={})`). Un file JSON per memoria
45
+ * (stessa classe di bug — riscrittura invece di accodamento — già
46
+ * evitata altrove, session-store.mjs).
47
+ */
48
+ import { randomUUID } from 'node:crypto';
49
+ import { promises as fsp } from 'node:fs';
50
+ import { join } from 'node:path';
51
+ import { idArchivioValido } from './id-archivio.mjs';
52
+
53
+ const TITOLO_MASSIMO = 80;
54
+ const CONTENUTO_MASSIMO = 600;
55
+ const GENERI = Object.freeze(['preference', 'project_fact', 'procedure', 'policy_note']);
56
+
57
+ /** ⭐ Stesso pattern REALE di `.automations/`/`.notes-store/`/`.tasks-store/`: il nome che `server.mjs` userà per il percorso di default. */
58
+ export const CARTELLA_MEMORIA = '.memory-store';
59
+
60
+ export class MemoryStoreError extends Error {
61
+ constructor(message, code = 'MEMORY_INVALID') {
62
+ super(message);
63
+ this.name = 'MemoryStoreError';
64
+ this.code = code;
65
+ }
66
+ }
67
+
68
+ function percorsoDi(cartella, id) {
69
+ // ⛔ 14/09: l'id arriva da fuori (indirizzo HTTP, attrezzo del modello) — un id fuori grammatica non nomina nessun file e
70
+ // torna `null`, mai un `join` che con `..` uscirebbe dalla cartella. Vedi id-archivio.mjs.
71
+ if (!idArchivioValido(id)) return null;
72
+ return join(cartella, `${id}.json`);
73
+ }
74
+
75
+ function normalizzaTitoloPerConfronto(titolo) {
76
+ return titolo.trim().toLowerCase();
77
+ }
78
+
79
+ function validaTitolo(title) {
80
+ if (typeof title !== 'string' || title.trim().length === 0 || title.length > TITOLO_MASSIMO) {
81
+ throw new MemoryStoreError(`title deve avere 1-${TITOLO_MASSIMO} caratteri`, 'MEMORY_INVALID');
82
+ }
83
+ return title.trim();
84
+ }
85
+
86
+ function validaContenuto(content) {
87
+ if (typeof content !== 'string' || content.trim().length === 0 || content.length > CONTENUTO_MASSIMO) {
88
+ throw new MemoryStoreError(`content deve avere 1-${CONTENUTO_MASSIMO} caratteri`, 'MEMORY_INVALID');
89
+ }
90
+ return content.trim();
91
+ }
92
+
93
+ function validaGenere(kind) {
94
+ if (!GENERI.includes(kind)) {
95
+ throw new MemoryStoreError(`kind deve essere uno fra ${GENERI.join('/')}`, 'MEMORY_INVALID');
96
+ }
97
+ return kind;
98
+ }
99
+
100
+ /* ⛔ 11/09 — CHI ha scritto questa memoria: stesso principio e stesso default di notes-store.mjs/tasks-store.mjs. */
101
+ const ORIGINI = Object.freeze(['persona', 'modello']);
102
+
103
+ function validaOrigine(origine) {
104
+ if (!ORIGINI.includes(origine)) {
105
+ throw new MemoryStoreError(`origine deve essere una fra ${ORIGINI.join('/')}`, 'MEMORY_INVALID');
106
+ }
107
+ return origine;
108
+ }
109
+
110
+ /** La forma PUBBLICA di una memoria — l'unica che esce da questo prodotto, uguale per ogni superficie che la mostra. */
111
+ export function formaPubblicaMemoria(voce) {
112
+ return {
113
+ id: voce.id,
114
+ titolo: voce.titolo,
115
+ contenuto: voce.contenuto,
116
+ genere: voce.genere,
117
+ creataAlle: voce.creataAlle ?? null,
118
+ aggiornataAlle: voce.aggiornataAlle ?? null,
119
+ origine: ORIGINI.includes(voce.origine) ? voce.origine : 'modello',
120
+ };
121
+ }
122
+
123
+ /** Più recentemente aggiornate per prime. Cartella assente ⇒ `[]`, mai un errore. */
124
+ export async function elencaMemorie({ cartella }, deps = {}) {
125
+ const readdirFn = deps.readdirFn ?? fsp.readdir;
126
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
127
+ let nomi;
128
+ try {
129
+ nomi = await readdirFn(cartella);
130
+ } catch {
131
+ return [];
132
+ }
133
+ const voci = [];
134
+ for (const nome of nomi) {
135
+ if (!nome.endsWith('.json')) continue;
136
+ try {
137
+ voci.push(JSON.parse(await readFileFn(join(cartella, nome), 'utf8')));
138
+ } catch { /* una voce corrotta non deve nascondere le altre */ }
139
+ }
140
+ return voci.sort((a, b) => b.aggiornataAlle.localeCompare(a.aggiornataAlle) || a.id.localeCompare(b.id));
141
+ }
142
+
143
+ /** Un id assente torna `null`, mai un'eccezione. */
144
+ export async function leggiMemoria({ cartella, id }, deps = {}) {
145
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
146
+ const percorso = percorsoDi(cartella, id);
147
+ if (!percorso) return null; // ⛔ 14/09: id fuori grammatica = «non c'è», mai una lettura fuori dalla cartella
148
+ try {
149
+ return JSON.parse(await readFileFn(percorso, 'utf8'));
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /** Cerca per titolo esatto (case/spazi-insensitive) — usata dalla deduplicazione, mai per il modello direttamente. */
156
+ export async function trovaMemoriaPerTitolo({ cartella, title }, deps = {}) {
157
+ const cercato = normalizzaTitoloPerConfronto(title);
158
+ const tutte = await elencaMemorie({ cartella }, deps);
159
+ return tutte.find((m) => normalizzaTitoloPerConfronto(m.titolo) === cercato) ?? null;
160
+ }
161
+
162
+ /**
163
+ * PURA — stesso confine di `cercaVoci` in library-store.mjs: chi
164
+ * chiama carica l'elenco (I/O), questa funzione filtra (nessun I/O,
165
+ * un test la esercita con un array letterale). Sottostringa
166
+ * case-insensitive su titolo O contenuto — `memory_search` mobile non
167
+ * documenta un algoritmo più sofisticato di questo (nessuna fonte
168
+ * trovata per un ranking/fuzzy match), quindi non se ne inventa uno.
169
+ */
170
+ export function cercaMemorie(memorie, { query, limit = 5 } = {}) {
171
+ const cercato = String(query ?? '').trim().toLowerCase();
172
+ const trovate = cercato === ''
173
+ ? []
174
+ : memorie.filter((m) => m.titolo.toLowerCase().includes(cercato) || m.contenuto.toLowerCase().includes(cercato));
175
+ return { memorie: trovate.slice(0, limit), totale: trovate.length };
176
+ }
177
+
178
+ /**
179
+ * @returns `{voce, duplicato}` — `duplicato:true` quando esiste già una
180
+ * memoria con lo STESSO titolo: torna quella esistente, MAI ne scrive
181
+ * una seconda (la dedup è la ragion d'essere di questa funzione, non
182
+ * un dettaglio).
183
+ * @throws {MemoryStoreError} MEMORY_INVALID PRIMA di ogni I/O.
184
+ */
185
+ export async function creaMemoria({ cartella, title, content, kind = 'preference', origine = 'modello' }, deps = {}) {
186
+ const titolo = validaTitolo(title);
187
+ const contenuto = validaContenuto(content);
188
+ const genere = validaGenere(kind);
189
+ const daChi = validaOrigine(origine);
190
+ const readdirFn = deps.readdirFn ?? fsp.readdir;
191
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
192
+ const gemello = await trovaMemoriaPerTitolo({ cartella, title: titolo }, { readdirFn, readFileFn });
193
+ if (gemello) return { voce: gemello, duplicato: true };
194
+ const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
195
+ const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
196
+ const clockFn = deps.clockFn ?? (() => new Date());
197
+ const ora = clockFn().toISOString();
198
+ const voce = { id: randomUUID(), titolo, contenuto, genere, origine: daChi, creataAlle: ora, aggiornataAlle: ora };
199
+ await mkdirFn(cartella, { recursive: true });
200
+ await writeFileFn(percorsoDi(cartella, voce.id), JSON.stringify(voce, null, 2), 'utf8');
201
+ return { voce, duplicato: false };
202
+ }
203
+
204
+ /** Solo i campi passati cambiano. @throws {MemoryStoreError} MEMORY_NOT_FOUND se l'id non esiste. */
205
+ export async function aggiornaMemoria({ cartella, id, title, content, kind }, deps = {}) {
206
+ const readFileFn = deps.readFileFn ?? fsp.readFile;
207
+ const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
208
+ const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
209
+ const clockFn = deps.clockFn ?? (() => new Date());
210
+ const voce = await leggiMemoria({ cartella, id }, { readFileFn });
211
+ if (!voce) throw new MemoryStoreError(`nessuna memoria con id ${id}`, 'MEMORY_NOT_FOUND');
212
+ if (title !== undefined) voce.titolo = validaTitolo(title);
213
+ if (content !== undefined) voce.contenuto = validaContenuto(content);
214
+ if (kind !== undefined) voce.genere = validaGenere(kind);
215
+ voce.aggiornataAlle = clockFn().toISOString();
216
+ await mkdirFn(cartella, { recursive: true });
217
+ await writeFileFn(percorsoDi(cartella, id), JSON.stringify(voce, null, 2), 'utf8');
218
+ return voce;
219
+ }
220
+
221
+ /** Cancellazione VERA (mobile stesso: `deleteMemory`, un hard delete) — idempotente, un id già assente non è un errore. */
222
+ export async function eliminaMemoria({ cartella, id }, deps = {}) {
223
+ const rmFn = deps.rmFn ?? fsp.rm;
224
+ const percorso = percorsoDi(cartella, id);
225
+ if (!percorso) return; // ⛔ 14/09: un id che non può nominare un file è già «assente» — no-op idempotente
226
+ await rmFn(percorso, { force: true });
227
+ }