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,253 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { parseContextSettings, ContextEngineError } from '../../context-engine/src/contracts.mjs';
3
+ import { importLegacySession } from '../../context-engine/src/node/legacy-import.mjs';
4
+
5
+ const fail = (code, message) => { throw new ContextEngineError(message, code); };
6
+ const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
7
+ const terminal = new Set(['committed', 'failed', 'cancelled']);
8
+ const validId = value => typeof value === 'string' && value.length > 0 && value.length <= 256 && !value.includes('\0');
9
+
10
+ /** Desktop ownership and transport adaptation; archive paths and model credentials
11
+ * never originate in the HTTP request. Every durable mutation has a receipt. */
12
+ export function createDesktopContextService({ engine, store, loadLegacy, resolveSessionModel, isSessionEnabled, readSession, onEvent, runInference, registraAncora, clock = () => new Date().toISOString() }) {
13
+ if (!engine || !store || typeof readSession !== 'function' || typeof resolveSessionModel !== 'function' || typeof isSessionEnabled !== 'function') fail('CTX_PORT_MISSING', 'Servizi desktop del contesto incompleti.');
14
+ const queues = new Map();
15
+ const deliveries = new Map();
16
+ const ownedJobs = new Map();
17
+ /* 24/09 — F4: l'ultima proiezione preparata per sessione (ciò che è andato al modello) è la base
18
+ dell'ancora del fornitore: `captureProviderResponse` la sposa con `usage.prompt_tokens`. */
19
+ const ultimeProiezioni = new Map();
20
+ /* 24/09 — F4: le sessioni per cui questo processo ha già cercato job orfani (basta una volta: in
21
+ questo processo un job attivo è sempre nella mappa `running` del motore). */
22
+ const recuperate = new Set();
23
+ let closed = false;
24
+ function serial(sessionId, task) {
25
+ const previous = queues.get(sessionId) ?? Promise.resolve();
26
+ const next = previous.catch(() => {}).then(task);
27
+ queues.set(sessionId, next);
28
+ next.finally(() => { if (queues.get(sessionId) === next) queues.delete(sessionId); }).catch(() => {});
29
+ return next;
30
+ }
31
+ async function ensure(sessionId) {
32
+ if (closed) fail('CTX_SERVICE_CLOSED', 'Il servizio del contesto è chiuso.');
33
+ if (!validId(sessionId) || !await readSession(sessionId)) fail('CTX_SESSION_NOT_FOUND', 'Conversazione non trovata.');
34
+ if (!await isSessionEnabled(sessionId)) fail('CTX_NOT_ENABLED', 'Il motore del contesto non è attivo per questa conversazione di prova.');
35
+ let snapshot = await store.readContextSnapshot({ sessionId });
36
+ if (!snapshot) {
37
+ const jsonl = await loadLegacy?.({ sessionId });
38
+ snapshot = jsonl ? await importLegacySession({ sessionId, jsonl, settings: parseContextSettings({}), metadata: {} }, { store }) : await store.initSession({ sessionId, settings: parseContextSettings({}) });
39
+ } else if (!recuperate.has(sessionId) && typeof engine.recoverInterruptedJobs === 'function') {
40
+ /*
41
+ * 24/09/2026 — F4, CTX-RESTART-ACTIVE-JOB-RECOVERY: al primo tocco della sessione in questo processo un job
42
+ * lasciato «in corso» da un processo morto viene dichiarato interrotto (`paused` + `CTX_JOB_INTERRUPTED`,
43
+ * vedi `engine.mjs::recoverInterrupted`), così il pannello non mostra un avanzamento che non avanza e la
44
+ * sessione può compattare di nuovo. Prima di oggi `GET /` rileggeva la riga com'era, per sempre.
45
+ */
46
+ snapshot = await engine.recoverInterruptedJobs({ sessionId });
47
+ recuperate.add(sessionId);
48
+ }
49
+ return snapshot;
50
+ }
51
+ async function allRecords(sessionId) {
52
+ const records = []; let afterSequence = 0;
53
+ for (;;) {
54
+ const page = await store.readOriginals({ sessionId, afterSequence, limit: 1000 });
55
+ records.push(...page);
56
+ if (page.length < 1000) return records;
57
+ afterSequence = page.at(-1).sequence;
58
+ }
59
+ }
60
+ function deliver(sessionId) {
61
+ if (!onEvent) return Promise.resolve();
62
+ if (deliveries.has(sessionId)) return deliveries.get(sessionId);
63
+ const pending = (async () => {
64
+ for (;;) {
65
+ const events = await store.readContextOutbox({ sessionId });
66
+ if (!events.length) return;
67
+ for (const event of events) {
68
+ await onEvent({ sessionId, event });
69
+ await store.ackContextEvent({ sessionId, eventId: event.id });
70
+ }
71
+ }
72
+ })();
73
+ deliveries.set(sessionId, pending);
74
+ pending.finally(() => { if (deliveries.get(sessionId) === pending) deliveries.delete(sessionId); }).catch(() => {});
75
+ return pending;
76
+ }
77
+ async function modelFor(sessionId) { return resolveSessionModel({ sessionId, session: await readSession(sessionId) }); }
78
+ function common(body, fields = []) {
79
+ if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(k => !['expectedRevision', 'idempotencyKey', ...fields].includes(k))) fail('CTX_INVALID_INPUT', 'Richiesta del contesto non valida.');
80
+ if (!Number.isSafeInteger(body.expectedRevision) || body.expectedRevision < 0 || !validId(body.idempotencyKey)) fail('CTX_INVALID_INPUT', 'Revisione e identità della richiesta sono necessarie.');
81
+ }
82
+ async function mutation(sessionId, method, path, body, operation, args) {
83
+ const requestFingerprint = digest({ method, path, body });
84
+ return store[operation]({ ...args, sessionId, idempotencyKey: body.idempotencyKey, requestFingerprint });
85
+ }
86
+ const api = {
87
+ async createKernelHooks({ sessionId, runId }) {
88
+ if (!await isSessionEnabled(sessionId)) return undefined;
89
+ if (!validId(runId)) fail('CTX_INVALID_INPUT', 'Identità del giro non valida.');
90
+ await serial(sessionId, () => ensure(sessionId));
91
+ return Object.freeze({
92
+ capture: ({ messages }) => api.syncOriginals({ sessionId, messages }),
93
+ /* 24/09 — F4, contratto con l'adapter (F1): `messages` = proiezione corretta, `originali` = grezzo. */
94
+ prepare: ({ messages, originali, tools, signal }) => api.prepare({ sessionId, messages, originali, tools, signal }),
95
+ infer: ({ signal }, operation) => runInference ? runInference({ sessionId, priority: 'chat', signal }, operation) : operation(signal),
96
+ captureProviderResponse: async ({ response, giro, usage }) => {
97
+ if (!response || typeof response !== 'object' || !Number.isSafeInteger(giro) || giro < 0) fail('CTX_INVALID_INPUT', 'Risposta del modello non archiviabile.');
98
+ const bytes = Buffer.from(JSON.stringify({ schema: 'talos.context.provider-response.v1', runId, giro, response }), 'utf8');
99
+ return serial(sessionId, async () => {
100
+ await ensure(sessionId);
101
+ const receipt = await store.putBlob({ sessionId, id: `provider-response-${digest({ runId, giro })}`, bytes, mimeType: 'application/json' });
102
+ /*
103
+ * 24/09/2026 — F4, punto 5: il numero VERO del fornitore (`usage.prompt_tokens`) diventa l'ancora della
104
+ * misura successiva, sposato alla proiezione appena inviata; il contatore separato (una chiamata in più,
105
+ * e per OpenRouter una stima) si usa solo quando il prefisso non combacia. Come Hermes
106
+ * `agent/usage_anchor.py:46-60`. ⛔ Il kernel oggi passa `{ response, giro }` senza `usage`
107
+ * (`talosHarness.mjs:8281`): finché quella riga non porta `usage`, qui non arriva nulla e si conta come prima.
108
+ */
109
+ if (registraAncora && usage && typeof usage === 'object' && ultimeProiezioni.has(sessionId)) {
110
+ const profile = await modelFor(sessionId);
111
+ registraAncora({ provider: profile.provider, model: profile.model, messages: ultimeProiezioni.get(sessionId), usage });
112
+ }
113
+ return receipt;
114
+ });
115
+ },
116
+ });
117
+ },
118
+ async compact({ sessionId, messages }) {
119
+ if (!await isSessionEnabled(sessionId)) return undefined;
120
+ const session = await readSession(sessionId);
121
+ if (Array.isArray(messages) && session?.conclusa !== false) await api.syncOriginals({ sessionId, messages });
122
+ const snapshot = await serial(sessionId, () => ensure(sessionId));
123
+ const { job } = await api.request({ sessionId, method: 'POST', path: '/jobs', body: { kind: 'compact', expectedRevision: snapshot.revision, idempotencyKey: randomUUID() } });
124
+ const finished = await engine.waitForCompaction({ sessionId, jobId: job.id });
125
+ await deliver(sessionId);
126
+ return { ok: true, compattato: finished.state === 'committed', jobId: job.id, ...(finished.error ? { error: finished.error } : {}) };
127
+ },
128
+ async request({ sessionId, method, path = '/', body, signal }) {
129
+ signal?.throwIfAborted();
130
+ let snapshot = await serial(sessionId, () => ensure(sessionId));
131
+ path = path.replace(/\/$/u, '') || '/';
132
+ if (method === 'GET') {
133
+ if (path === '/') { await deliver(sessionId); return { ...snapshot, usage: await store.readUsage({ sessionId }), semanticStatus: 'not-qualified' }; }
134
+ if (path === '/versions') return { versions: await engine.listContextVersions({ sessionId }) };
135
+ if (path === '/facts') return { facts: snapshot.facts };
136
+ if (path === '/export') return engine.exportContext({ sessionId });
137
+ let match = /^\/jobs\/([^/]+)$/u.exec(path);
138
+ if (match) {
139
+ const job = await store.readContextJob({ sessionId, jobId: match[1] });
140
+ if (!job) fail('CTX_JOB_NOT_FOUND', 'Compattazione non trovata.');
141
+ await deliver(sessionId); return { job };
142
+ }
143
+ match = /^\/sources\/([^/]+)$/u.exec(path);
144
+ if (match) return { source: await engine.readContextSource({ sessionId, sourceId: match[1] }) };
145
+ fail('CTX_ROUTE_NOT_FOUND', 'Operazione del contesto non trovata.');
146
+ }
147
+ const fields = path === '/settings' ? ['patch'] : path === '/jobs' ? ['kind'] : path.endsWith('/resolve') ? ['accept'] : path.startsWith('/facts') ? ['fact'] : [];
148
+ common(body, fields);
149
+ const requestFingerprint = digest({ method, path, body });
150
+ const receipt = await store.readContextMutation({ sessionId, idempotencyKey: body.idempotencyKey, requestFingerprint });
151
+ const wrap = result => path.startsWith('/facts') ? { fact: result } : path.startsWith('/versions') ? { version: result } : path.startsWith('/jobs') ? { job: result } : result;
152
+ if (receipt) return wrap(receipt.result);
153
+ if (method === 'POST' && path === '/jobs') {
154
+ const existing = snapshot.jobs.find(job => job.idempotencyKey === body.idempotencyKey);
155
+ if (existing) return { job: await engine.startCompaction({ sessionId, idempotencyKey: body.idempotencyKey, kind: body.kind ?? 'compact', sessionModel: await modelFor(sessionId) }) };
156
+ }
157
+ if (snapshot.revision !== body.expectedRevision) fail('CTX_STALE_REVISION', 'La conversazione è cambiata. Aggiorna il pannello prima di modificare il contesto.');
158
+ signal?.throwIfAborted();
159
+ if (method === 'PATCH' && path === '/settings') {
160
+ const settings = parseContextSettings({ ...snapshot.settings, ...body.patch });
161
+ return mutation(sessionId, method, path, body, 'updateSessionSettings', { settings, expectedRevision: body.expectedRevision });
162
+ }
163
+ if (method === 'POST' && path === '/jobs') {
164
+ if (!['compact', 'regenerate'].includes(body.kind ?? 'compact')) fail('CTX_INVALID_INPUT', 'Tipo di compattazione non valido.');
165
+ const job = await engine.startCompaction({ sessionId, idempotencyKey: body.idempotencyKey, kind: body.kind ?? 'compact', sessionModel: await modelFor(sessionId) });
166
+ ownedJobs.set(`${sessionId}:${job.id}`, { sessionId, jobId: job.id });
167
+ return { job };
168
+ }
169
+ let match = /^\/jobs\/([^/]+)(\/resume)?$/u.exec(path);
170
+ if (match && (method === 'DELETE' && !match[2] || method === 'POST' && match[2])) {
171
+ const jobId = match[1];
172
+ const job = method === 'DELETE' ? await engine.cancelCompaction({ sessionId, jobId }) : await engine.resumeCompaction({ sessionId, jobId, sessionModel: await modelFor(sessionId) });
173
+ const result = await mutation(sessionId, method, path, body, 'saveJobProgress', { job });
174
+ return { job: result };
175
+ }
176
+ match = /^\/versions\/([^/]+)\/restore$/u.exec(path);
177
+ if (method === 'POST' && match) {
178
+ const version = await mutation(sessionId, method, path, body, 'restoreContextVersion', { versionId: match[1], expectedRevision: body.expectedRevision, newVersionId: randomUUID(), createdAt: clock() });
179
+ await deliver(sessionId); return { version };
180
+ }
181
+ match = /^\/facts(?:\/([^/]+))?(\/resolve)?$/u.exec(path);
182
+ if (match) {
183
+ const factId = match[1];
184
+ if (method === 'DELETE' && factId && !match[2]) return { fact: await mutation(sessionId, method, path, body, 'removeProtectedFact', { factId, expectedRevision: body.expectedRevision }) };
185
+ let input = body.fact;
186
+ const prior = snapshot.facts.find(f => f.id === factId || f.id === input?.id);
187
+ if (method === 'POST' && match[2]) {
188
+ if (typeof body.accept !== 'boolean' || prior?.status !== 'conflict') fail('CTX_FACT_CONFLICT_NOT_FOUND', 'Nessun conflitto da risolvere.');
189
+ input = { id: factId, text: body.accept ? prior.conflict.proposedText : prior.text, sources: body.accept ? prior.conflict.sources : prior.sources };
190
+ } else if (!(method === 'POST' && !factId || method === 'PATCH' && factId)) fail('CTX_ROUTE_NOT_FOUND', 'Operazione del contesto non trovata.');
191
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(k => !['id', 'text', 'sources'].includes(k)) || (factId && input.id && input.id !== factId)) fail('CTX_INVALID_INPUT', 'Informazione protetta non valida.');
192
+ const fact = { id: factId ?? input.id ?? randomUUID(), text: input.text, sources: input.sources ?? [], status: 'active', revision: (prior?.revision ?? 0) + 1 };
193
+ return { fact: await mutation(sessionId, method, path, body, 'upsertProtectedFact', { fact, expectedRevision: body.expectedRevision }) };
194
+ }
195
+ fail('CTX_ROUTE_NOT_FOUND', 'Operazione del contesto non trovata.');
196
+ },
197
+ syncOriginals({ sessionId, messages }) {
198
+ return serial(sessionId, async () => {
199
+ const snapshot = await ensure(sessionId);
200
+ if (!Array.isArray(messages)) fail('CTX_INVALID_INPUT', 'Cronologia non valida.');
201
+ const saved = await allRecords(sessionId);
202
+ if (messages.length < saved.length || saved.some((record, index) => record.sha256 !== digest(messages[index]))) fail('CTX_HISTORY_DIVERGED', 'La cronologia attiva differisce dall’archivio. Gli originali sono conservati; occorre recuperare la versione completa.');
203
+ const records = messages.slice(saved.length).map((message, offset) => ({ id: `message-${saved.length + offset + 1}`, message, createdAt: clock(), origin: 'desktop-kernel' }));
204
+ if (records.length) await store.appendOriginalBatch({ sessionId, records, expectedRevision: snapshot.revision });
205
+ return store.readContextSnapshot({ sessionId });
206
+ });
207
+ },
208
+ /*
209
+ * 24/09/2026 — F4: ARCHIVIO ≠ PROIEZIONE (T1/T2 della ricognizione). L'adapter desktop corregge gli esiti
210
+ * degli attrezzi PRIMA della richiesta; archiviare quella proiezione faceva divergere l'archivio (grezzo, da
211
+ * `capture`) alla richiesta dopo: `CTX_HISTORY_DIVERGED` alla seconda richiesta, riprodotto dalla sonda T2.
212
+ * Ora: si ARCHIVIA da `originali` quando c'è (ripiego: `messages`, come prima per chi non lo passa), si
213
+ * PROIETTA e si RIASSUME da `messages`. Una proiezione non allineata (lunghezza o ruoli diversi) si rifiuta
214
+ * prima di toccare l'archivio. Come Hermes (`hermes_state_messages.py:735-741`, clone `65ad529`) e Claude
215
+ * Code (issue #26125, 16/02/2026: «The full transcript is preserved in transcript.jsonl on disk» mentre al
216
+ * modello arrivano i «compacted summaries»): la trascrizione grezza e il contesto inviato sono due cose.
217
+ */
218
+ async prepare({ sessionId, messages, originali, tools, signal }) {
219
+ let projection;
220
+ if (originali !== undefined) {
221
+ if (!Array.isArray(originali) || !Array.isArray(messages) || messages.length !== originali.length || messages.some((message, index) => message?.role !== originali[index]?.role)) fail('CTX_INVALID_INPUT', 'La proiezione della richiesta non è allineata agli originali (lunghezza o ruoli diversi).');
222
+ projection = messages;
223
+ }
224
+ await api.syncOriginals({ sessionId, messages: originali === undefined ? messages : originali });
225
+ let result;
226
+ try { result = await engine.prepareForRequest({ sessionId, projection, tools, sessionModel: await modelFor(sessionId), signal }); }
227
+ catch (error) {
228
+ /* 25/09 — l'avviso «compattazione automatica in pausa» nasce anche quando la richiesta MUORE (contesto che non entra,
229
+ riassunto rifiutato mentre si aspettava): senza questa consegna restava nella coda fino alla richiesta dopo. Una
230
+ consegna fallita non copre l'errore vero della richiesta. */
231
+ await deliver(sessionId).catch(() => {});
232
+ throw error;
233
+ }
234
+ if (Array.isArray(result?.messages)) ultimeProiezioni.set(sessionId, result.messages);
235
+ await deliver(sessionId);
236
+ return result;
237
+ },
238
+ async append({ sessionId, record }) { await serial(sessionId, () => ensure(sessionId)); return engine.appendOriginal({ sessionId, record }); },
239
+ async close() {
240
+ if (closed) return;
241
+ closed = true;
242
+ await Promise.allSettled([...queues.values()]);
243
+ await Promise.allSettled([...deliveries.values()]);
244
+ for (const options of ownedJobs.values()) {
245
+ const job = await store.readContextJob(options);
246
+ if (job && !terminal.has(job.state)) await engine.cancelCompaction(options);
247
+ await engine.waitForCompaction(options);
248
+ }
249
+ ownedJobs.clear();
250
+ },
251
+ };
252
+ return Object.freeze(api);
253
+ }
@@ -0,0 +1,252 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { stat } from 'node:fs/promises';
4
+ import { createServer } from 'node:net';
5
+ import { dirname, isAbsolute } from 'node:path';
6
+
7
+ const MODEL_ID = 'qwen3-embedding-0.6b-q8_0';
8
+ const MODEL = Object.freeze({
9
+ id: MODEL_ID, repo: 'Qwen/Qwen3-Embedding-0.6B-GGUF', revision: '370f27d7550e0def9b39c1f16d3fbaa13aa67728',
10
+ bytes: 639150592, sha256: '06507c7b42688469c4e7298b0a1e16deff06caf291cf0a5b278c308249c3e439',
11
+ license: 'Apache-2.0', path: MODEL_ID,
12
+ files: [{ path: 'Qwen3-Embedding-0.6B-Q8_0.gguf', bytes: 639150592, sha256: '06507c7b42688469c4e7298b0a1e16deff06caf291cf0a5b278c308249c3e439' }],
13
+ });
14
+ const QUERY_INSTRUCTION = 'Given a conversation search query, retrieve relevant passages from the same conversation';
15
+ const PROFILE = Object.freeze({ runtime: 'llama.cpp', runtimeVersion: 'b10517', runtimeCommit: 'dc72703fc69698b1ea68ece8d2dd8a96e6a4e1fe', dimensions: 1024, pooling: 'last', queryInstruction: QUERY_INSTRUCTION });
16
+ const HEALTH_TIMEOUT = 30000;
17
+ const REQUEST_TIMEOUT = 30000;
18
+ const MAX_RESPONSE = 2 * 1024 * 1024;
19
+ const fail = (message, code) => { throw Object.assign(new Error(message), { code }); };
20
+
21
+ async function allocateLoopbackPort() {
22
+ const server = createServer();
23
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
24
+ const { port } = server.address();
25
+ await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
26
+ return port;
27
+ }
28
+
29
+ // A promise race also bounds injected transports that do not honor AbortSignal.
30
+ async function abortable(action, signal) {
31
+ if (signal.aborted) throw signal.reason;
32
+ let listener;
33
+ const stopped = new Promise((_, reject) => { listener = () => reject(signal.reason); signal.addEventListener('abort', listener, { once: true }); });
34
+ try { return await Promise.race([Promise.resolve().then(action), stopped]); }
35
+ finally { signal.removeEventListener('abort', listener); }
36
+ }
37
+
38
+ async function pause(ms, signal) {
39
+ let timer;
40
+ try { await abortable(() => new Promise(resolve => { timer = setTimeout(resolve, ms); }), signal); }
41
+ finally { clearTimeout(timer); }
42
+ }
43
+
44
+ export function createContextEmbeddingRuntime({ binaryPath, modelPath, modelSha256, processPolicy, fetchFn, downloadModel, modelStore, clock = () => new Date().toISOString() } = {}) {
45
+ if (typeof binaryPath !== 'string' || !isAbsolute(binaryPath) || typeof modelPath !== 'string' || !isAbsolute(modelPath)
46
+ || typeof modelSha256 !== 'string' || !/^[a-f0-9]{64}$/iu.test(modelSha256) || !processPolicy || typeof processPolicy.spawn !== 'function'
47
+ || typeof fetchFn !== 'function' || (downloadModel !== undefined && typeof downloadModel !== 'function') || typeof clock !== 'function') fail('Embedding dependencies are invalid', 'CTX_EMBEDDING_CONFIG');
48
+ const digest = modelSha256.toLowerCase();
49
+ const profile = { ...PROFILE, modelId: MODEL_ID, modelSha256: digest, revision: MODEL.revision };
50
+ const profileId = createHash('sha256').update(JSON.stringify(profile)).digest('hex');
51
+ const lifetime = new AbortController();
52
+ let closed = false; let active = null; let running = false; let downloadFlight = null; let verified = null; let state = 'unavailable';
53
+
54
+ const manifest = (status, extra = {}) => ({ ...structuredClone(MODEL), ...profile, profileId, sha256: digest, state: status, observedAt: clock(), ...extra });
55
+ function check(signal) {
56
+ if (closed) fail('Embedding runtime is closed', 'CTX_EMBEDDING_CLOSED');
57
+ if (signal?.aborted) fail('Embedding operation cancelled', 'CTX_CANCELLED');
58
+ }
59
+ function scopedSignal(signal, timeout = REQUEST_TIMEOUT) {
60
+ return AbortSignal.any([lifetime.signal, AbortSignal.timeout(timeout), ...(signal ? [signal] : [])]);
61
+ }
62
+ function translate(error, signal, defaultCode = 'CTX_EMBEDDING_FAILED') {
63
+ check(signal);
64
+ if (error?.code?.startsWith('CTX_')) return error;
65
+ if (error instanceof SyntaxError) return Object.assign(new Error('Local embedding endpoint returned malformed JSON'), { code: 'CTX_EMBEDDING_RESPONSE' });
66
+ return Object.assign(new Error(error?.name === 'TimeoutError' ? 'Embedding operation timed out' : 'Local embedding runtime failed'), { code: error?.name === 'TimeoutError' ? 'CTX_EMBEDDING_TIMEOUT' : defaultCode });
67
+ }
68
+
69
+ async function verifyWeights(signal) {
70
+ check(signal);
71
+ let info;
72
+ try { info = await stat(modelPath); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
73
+ if (!info.isFile() || info.size === 0) fail('Embedding model is not a regular nonempty file', 'CTX_EMBEDDING_HASH');
74
+ const key = `${info.dev}:${info.ino}:${info.size}:${info.mtimeMs}:${info.ctimeMs}`;
75
+ if (verified?.key === key) return verified;
76
+ const hash = createHash('sha256');
77
+ const stream = createReadStream(modelPath, { signal: scopedSignal(signal, 60000) });
78
+ for await (const chunk of stream) { check(signal); hash.update(chunk); }
79
+ if (hash.digest('hex') !== digest) fail('Embedding model SHA-256 mismatch', 'CTX_EMBEDDING_HASH');
80
+ if (digest === MODEL.sha256 && info.size !== MODEL.bytes) fail('Embedding model size mismatch', 'CTX_EMBEDDING_HASH');
81
+ verified = { key, byteLength: info.size };
82
+ return verified;
83
+ }
84
+
85
+ async function ensureEmbeddingModel({ approved = false, signal } = {}) {
86
+ check(signal);
87
+ if (typeof approved !== 'boolean') fail('Download approval must be explicit', 'CTX_EMBEDDING_INVALID');
88
+ try {
89
+ const local = await verifyWeights(signal);
90
+ check(signal);
91
+ if (local) return manifest('ready', { byteLength: local.byteLength });
92
+ if (approved !== true) return manifest('approval-required');
93
+ if (!downloadModel) fail('Guided embedding download is unavailable', 'CTX_EMBEDDING_DOWNLOAD_UNAVAILABLE');
94
+ if (!downloadFlight) {
95
+ // The owner binds this port to the existing HF transfer/model store.
96
+ downloadFlight = Promise.resolve().then(() => downloadModel({ ...structuredClone(MODEL), approved: true, signal: scopedSignal(signal, 600000) }));
97
+ downloadFlight.finally(() => { downloadFlight = null; }).catch(() => {});
98
+ }
99
+ const result = await abortable(() => downloadFlight, scopedSignal(signal, 600000));
100
+ check(signal);
101
+ const ready = await verifyWeights(signal);
102
+ if (ready) return manifest('ready', { byteLength: ready.byteLength });
103
+ const pending = result?.state;
104
+ if (!['queued', 'downloading', 'incomplete', 'paused'].includes(pending)) fail('Embedding download did not produce verified model bytes', 'CTX_EMBEDDING_DOWNLOAD_FAILED');
105
+ return manifest(pending);
106
+ } catch (error) { throw translate(error, signal); }
107
+ }
108
+
109
+ async function jsonRequest(entry, path, options, signal, timeout = REQUEST_TIMEOUT) {
110
+ const requestSignal = scopedSignal(signal, timeout);
111
+ return abortable(async () => {
112
+ const response = await fetchFn(`${entry.baseURL}${path}`, {
113
+ ...options, redirect: 'error', signal: requestSignal,
114
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${entry.token}` },
115
+ });
116
+ if (!response?.ok) fail('Local embedding endpoint is unavailable', path === '/health' ? 'CTX_EMBEDDING_HEALTH' : 'CTX_EMBEDDING_HTTP');
117
+ const declaredSize = response.headers?.get?.('content-length');
118
+ if (declaredSize && Number(declaredSize) > MAX_RESPONSE) fail('Embedding response exceeds limit', 'CTX_EMBEDDING_RESPONSE');
119
+ if (response.body?.getReader) {
120
+ const reader = response.body.getReader(); const chunks = []; let size = 0;
121
+ try {
122
+ for (;;) {
123
+ const { value, done } = await abortable(() => reader.read(), requestSignal);
124
+ if (done) break;
125
+ size += value.byteLength;
126
+ if (size > MAX_RESPONSE) fail('Embedding response exceeds limit', 'CTX_EMBEDDING_RESPONSE');
127
+ chunks.push(Buffer.from(value));
128
+ }
129
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
130
+ } finally { void reader.cancel().catch(() => {}); }
131
+ }
132
+ const data = await response.json();
133
+ if (Buffer.byteLength(JSON.stringify(data), 'utf8') > MAX_RESPONSE) fail('Embedding response exceeds limit', 'CTX_EMBEDDING_RESPONSE');
134
+ return data;
135
+ }, requestSignal);
136
+ }
137
+
138
+ async function stopEntry(entry) {
139
+ if (!entry) return;
140
+ if (entry.stopping) return entry.stopping;
141
+ entry.stopping = (async () => {
142
+ const waitForExit = async () => {
143
+ let timer;
144
+ try { await Promise.race([entry.completion, new Promise(resolve => { timer = setTimeout(resolve, 1000); })]); }
145
+ finally { clearTimeout(timer); }
146
+ };
147
+ if (entry.child && !entry.closed) {
148
+ try { entry.child.kill('SIGTERM'); } catch { /* child can exit concurrently */ }
149
+ await waitForExit();
150
+ if (!entry.closed) {
151
+ try { entry.child.kill('SIGKILL'); } catch { /* child can exit concurrently */ }
152
+ await waitForExit();
153
+ }
154
+ if (!entry.closed) fail('Embedding process did not confirm shutdown', 'CTX_EMBEDDING_STOP_FAILED');
155
+ }
156
+ if (entry.locked) { await modelStore.unlock(MODEL_ID); entry.locked = false; }
157
+ if (active === entry) active = null;
158
+ if (!closed) state = 'unavailable';
159
+ })();
160
+ try { await entry.stopping; } catch (error) { entry.stopping = null; throw error; }
161
+ }
162
+
163
+ async function start(signal) {
164
+ if (active && state === 'ready' && !active.closed) return active;
165
+ if (active) await stopEntry(active);
166
+ const local = await ensureEmbeddingModel({ approved: false, signal });
167
+ if (local.state !== 'ready') fail('Download the local embedding model to enable semantic search', 'CTX_EMBEDDING_MODEL_REQUIRED');
168
+ const port = await allocateLoopbackPort(); check(signal);
169
+ const entry = { baseURL: `http://127.0.0.1:${port}`, token: randomBytes(32).toString('hex'), child: null, closed: false, failure: null, locked: false };
170
+ try {
171
+ if (modelStore) {
172
+ const stored = await modelStore.inspect(MODEL_ID);
173
+ if (stored) {
174
+ if (stored.sha256 !== digest || stored.state !== 'ready') fail('Registered embedding model is not ready or has another digest', 'CTX_EMBEDDING_HASH');
175
+ await modelStore.lock(MODEL_ID); entry.locked = true;
176
+ }
177
+ }
178
+ check(signal);
179
+ entry.child = processPolicy.spawn(binaryPath, [
180
+ '-m', modelPath, '--alias', MODEL_ID, '--host', '127.0.0.1', '--port', String(port), '--api-key', entry.token,
181
+ '--embedding', '--pooling', 'last', '-ngl', '0', '--device', 'none', '-c', '8192', '-b', '8192', '-ub', '8192',
182
+ '--parallel', '1', '--threads', '2', '--threads-batch', '2', '--no-webui',
183
+ ], { cwd: dirname(binaryPath), shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
184
+ if (!entry.child || typeof entry.child.once !== 'function') fail('Embedding process did not start', 'CTX_EMBEDDING_PROCESS');
185
+ entry.completion = new Promise(resolve => entry.child.once('close', () => { entry.closed = true; if (active === entry) state = 'failed'; resolve(); }));
186
+ entry.child.on('error', () => { entry.failure = true; if (active === entry) state = 'failed'; });
187
+ // Drain diagnostic output without persisting prompt text or credentials.
188
+ entry.child.stdout?.resume?.(); entry.child.stderr?.resume?.();
189
+ active = entry; state = 'loading';
190
+ const startupSignal = scopedSignal(signal, HEALTH_TIMEOUT);
191
+ while (!startupSignal.aborted) {
192
+ if (entry.closed || entry.failure) fail('Embedding process exited before becoming ready', 'CTX_EMBEDDING_PROCESS');
193
+ try {
194
+ const result = await jsonRequest(entry, '/health', { method: 'GET' }, startupSignal, 2000);
195
+ if (result?.status !== 'ok') fail('Embedding health response is invalid', 'CTX_EMBEDDING_HEALTH');
196
+ if (entry.closed || entry.failure) fail('Embedding process exited during health probe', 'CTX_EMBEDDING_PROCESS');
197
+ check(signal); state = 'ready'; return entry;
198
+ } catch (error) {
199
+ if (!['CTX_EMBEDDING_HEALTH', 'CTX_EMBEDDING_HTTP'].includes(error?.code) && error?.name !== 'TypeError' && error?.name !== 'TimeoutError') throw error;
200
+ }
201
+ await pause(100, startupSignal);
202
+ }
203
+ throw startupSignal.reason;
204
+ } catch (error) { await stopEntry(entry); throw translate(error, signal); }
205
+ }
206
+
207
+ async function embedContextBatch({ texts, kind, signal } = {}) {
208
+ check(signal);
209
+ if (!Array.isArray(texts) || texts.length > 32 || !['query', 'document'].includes(kind)
210
+ || [...texts].some(text => typeof text !== 'string' || !text.trim() || !text.isWellFormed() || Buffer.byteLength(text, 'utf8') > 7680)
211
+ || texts.reduce((sum, text) => sum + Buffer.byteLength(text, 'utf8'), 0) > 32768) fail('Embedding input must contain at most 32 bounded text passages', 'CTX_EMBEDDING_INVALID');
212
+ if (texts.length === 0) return [];
213
+ if (running) fail('Another embedding request is active', 'CTX_EMBEDDING_BUSY');
214
+ check(signal); running = true;
215
+ try {
216
+ if (typeof processPolicy.isChatBusy === 'function' && await processPolicy.isChatBusy()) fail('Embedding yields to active chat', 'CTX_EMBEDDING_BUSY');
217
+ const entry = await start(signal);
218
+ if (typeof processPolicy.isChatBusy === 'function' && await processPolicy.isChatBusy()) fail('Embedding yields to active chat', 'CTX_EMBEDDING_BUSY');
219
+ const input = texts.map(text => kind === 'query' ? `Instruct: ${QUERY_INSTRUCTION}\nQuery: ${text}` : text);
220
+ const response = await jsonRequest(entry, '/v1/embeddings', { method: 'POST', body: JSON.stringify({ model: MODEL_ID, input, encoding_format: 'float' }) }, signal);
221
+ check(signal);
222
+ if (!Array.isArray(response?.data) || response.data.length !== texts.length) fail('Embedding response has the wrong batch shape', 'CTX_EMBEDDING_RESPONSE');
223
+ const vectors = new Array(texts.length);
224
+ for (const row of response.data) {
225
+ if (!Number.isSafeInteger(row?.index) || row.index < 0 || row.index >= texts.length || vectors[row.index] !== undefined
226
+ || !Array.isArray(row.embedding) || row.embedding.length !== PROFILE.dimensions || [...row.embedding].some(value => typeof value !== 'number' || !Number.isFinite(value))) fail('Embedding response has invalid indexes, dimensions or values', 'CTX_EMBEDDING_RESPONSE');
227
+ vectors[row.index] = [...row.embedding];
228
+ }
229
+ return vectors;
230
+ } catch (error) {
231
+ if (signal?.aborted || closed || error?.name === 'TimeoutError') await stopEntry(active);
232
+ throw translate(error, signal);
233
+ } finally { running = false; }
234
+ }
235
+
236
+ async function health() {
237
+ if (closed) return { ready: false, state: 'closed', profileId, profile: { ...profile } };
238
+ let ready = false;
239
+ if (active && state === 'ready' && !active.closed && !active.failure) {
240
+ try { ready = (await jsonRequest(active, '/health', { method: 'GET' }, undefined, 2000))?.status === 'ok'; } catch { ready = false; }
241
+ }
242
+ return { ready, state: ready ? 'ready' : state === 'ready' ? 'unavailable' : state, profileId, profile: { ...profile } };
243
+ }
244
+
245
+ async function close() {
246
+ if (closed && !active) return;
247
+ closed = true; lifetime.abort(); state = 'closed'; verified = null;
248
+ await stopEntry(active);
249
+ }
250
+
251
+ return Object.freeze({ ensureEmbeddingModel, embedContextBatch, health, close });
252
+ }
@@ -0,0 +1,81 @@
1
+ const fault = (code, message) => Object.assign(new Error(message), { code });
2
+
3
+ /** Serializes inference, not process/tool execution. A lease lasts until the
4
+ * operation settles, including complete stream consumption by its caller. */
5
+ export function createContextInferenceScheduler() {
6
+ const resources = new Map();
7
+ let closed = false, closing;
8
+ function pump(resource, queue) {
9
+ if (queue.active || closed) return;
10
+ const index = queue.pending.findIndex(entry => entry.priority === 'chat');
11
+ const entry = queue.pending.splice(index < 0 ? 0 : index, 1)[0];
12
+ if (!entry) { resources.delete(resource); return; }
13
+ queue.active = entry;
14
+ entry.done = (async () => {
15
+ try {
16
+ entry.controller.signal.throwIfAborted();
17
+ const result = await entry.operation(entry.controller.signal);
18
+ if (entry.controller.signal.aborted) {
19
+ const reason = entry.controller.signal.reason;
20
+ if (reason && result?.usage !== undefined) reason.usage = result.usage;
21
+ throw reason;
22
+ }
23
+ entry.resolve(result);
24
+ } catch (error) {
25
+ const reason = entry.controller.signal.aborted ? entry.controller.signal.reason : error;
26
+ if (reason && error?.usage !== undefined) reason.usage = error.usage;
27
+ entry.reject(reason);
28
+ } finally {
29
+ entry.signal?.removeEventListener('abort', entry.abort);
30
+ queue.active = null;
31
+ if (closed) resources.delete(resource);
32
+ else pump(resource, queue);
33
+ }
34
+ })();
35
+ }
36
+ return Object.freeze({
37
+ run({ resource, priority, signal } = {}, operation) {
38
+ if (closed) return Promise.reject(fault('CTX_SCHEDULER_CLOSED', 'Il pianificatore delle inferenze è chiuso.'));
39
+ if (typeof resource !== 'string' || !resource.trim() || !['chat', 'background'].includes(priority) || typeof operation !== 'function') return Promise.reject(fault('CTX_RESOURCE_INVALID', 'Risorsa o priorità di inferenza non valida.'));
40
+ if (signal?.aborted) return Promise.reject(signal.reason);
41
+ let queue = resources.get(resource);
42
+ if (!queue) { queue = { active: null, pending: [] }; resources.set(resource, queue); }
43
+ return new Promise((resolve, reject) => {
44
+ const entry = { priority, operation, signal, resolve, reject, controller: new AbortController() };
45
+ entry.abort = () => {
46
+ entry.controller.abort(signal.reason);
47
+ if (queue.active !== entry) {
48
+ const index = queue.pending.indexOf(entry);
49
+ if (index >= 0) queue.pending.splice(index, 1);
50
+ signal.removeEventListener('abort', entry.abort);
51
+ reject(signal.reason);
52
+ }
53
+ };
54
+ signal?.addEventListener('abort', entry.abort, { once: true });
55
+ queue.pending.push(entry);
56
+ if (priority === 'chat' && queue.active?.priority === 'background') queue.active.controller.abort(fault('CTX_RESOURCE_BUSY', 'La sintesi lascia la risorsa alla chat. I segmenti già verificati restano salvati.'));
57
+ pump(resource, queue);
58
+ });
59
+ },
60
+ getState() {
61
+ return [...resources].map(([resource, queue]) => ({ resource, active: queue.active?.priority ?? null, queuedChat: queue.pending.filter(e => e.priority === 'chat').length, queuedBackground: queue.pending.filter(e => e.priority === 'background').length }));
62
+ },
63
+ close() {
64
+ if (closing) return closing;
65
+ closed = true;
66
+ const active = [];
67
+ for (const [resource, queue] of resources) {
68
+ for (const entry of queue.pending.splice(0)) {
69
+ entry.signal?.removeEventListener('abort', entry.abort);
70
+ entry.reject(fault('CTX_SCHEDULER_CLOSED', 'Il pianificatore delle inferenze è chiuso.'));
71
+ }
72
+ if (queue.active) {
73
+ if (queue.active.priority === 'background') queue.active.controller.abort(fault('CTX_SCHEDULER_CLOSED', 'Sintesi fermata durante la chiusura.'));
74
+ active.push(queue.active.done);
75
+ } else resources.delete(resource);
76
+ }
77
+ closing = Promise.allSettled(active).then(() => undefined);
78
+ return closing;
79
+ },
80
+ });
81
+ }