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,519 @@
1
+ /**
2
+ * browser-proxy-universale.mjs — M3 (07/09/2026): la CORSIA VELOCE del Browser.
3
+ * Fa vedere dentro TALOS anche le pagine che vietano la cornice (github risponde
4
+ * `X-Frame-Options: DENY` e nella cornice resta un rettangolo grigio), senza
5
+ * dover avviare un browser vero. Il browser vero (la corsia B, CDP) resta per
6
+ * quello che il proxy non può dare: accesso già fatto, pagine che si difendono
7
+ * davvero, JavaScript che parla solo col suo stesso server.
8
+ *
9
+ * ⛔ PERCHÉ UN'ORIGINE SEPARATA, E NON LA NOSTRA — è il punto di tutto il file.
10
+ * Una pagina servita da noi diventa, per il browser, CODICE DELLA NOSTRA
11
+ * ORIGINE: la same-origin policy le apre `localStorage`, `IndexedDB`, i cookie e
12
+ * ogni rotta `/api/*` di TALOS come se fossero roba sua (MDN «Same-origin
13
+ * policy», letto il 07/09/2026: l'origine è schema+host+PORTA, e ciò che
14
+ * condivide l'origine condivide lo storage). Un proxy che serve siti di terzi
15
+ * dalla propria origine mette per giunta tutti i siti proxati nello STESSO
16
+ * barattolo: lo script di un sito legge quello che ci ha lasciato un altro
17
+ * (proxyorb.com, «Same-Origin Policy and Web Proxies», letto il 07/09/2026). E
18
+ * la pagina di terzi è SEMPRE contenuto non affidabile — l'indirect prompt
19
+ * injection su Comet dimostrata da Brave.
20
+ * ⇒ Il proxy vive su una PORTA SUA (origine diversa) e non sa niente di TALOS.
21
+ *
22
+ * ⛔ La porta separa l'origine, NON i cookie. I cookie si scelgono per HOST, la
23
+ * porta non entra nel confronto: una pagina servita da `127.0.0.1:4301` sta nello
24
+ * stesso barattolo di TALOS su `127.0.0.1:4174` — e `talos_token`
25
+ * (`src/http-app.mjs:1193`, HttpOnly SameSite=Strict) resta illeggibile da JS ma
26
+ * VIENE SPEDITO, perché per SameSite due porte dello stesso host sono lo stesso
27
+ * sito. Per questo il proxy si lega di default a un OSPITE diverso, `127.0.0.2`:
28
+ * host diverso ⇒ barattolo diverso ⇒ da lì non parte nessun cookie di TALOS.
29
+ * Dove quell'indirizzo non si può legare (macOS vuole un alias esplicito su
30
+ * `lo0`) si ripiega su `127.0.0.1` e lo si DICHIARA (`cookieCondiviso: true`),
31
+ * invece di fingere un isolamento che non c'è.
32
+ *
33
+ * ⛔ SSRF: il proxy va a prendere pagine per conto di chi chiede, quindi è la
34
+ * classica leva verso l'interno. Indirizzi privati, loopback, link-local (dentro
35
+ * cui sta `169.254.169.254`, le credenziali IAM di una macchina cloud) e
36
+ * multicast si rifiutano, in IPv4 e in IPv6 — comprese le forme mascherate
37
+ * `[::ffff:169.254.169.254]`, che alcune librerie Node lasciano passare
38
+ * (appsecbrief.com «SSRF in 2026» e vulnsy.com «SSRF Cheat Sheet 2026», letti il
39
+ * 07/09/2026). Il controllo si ripete a OGNI salto di reindirizzamento, perché il
40
+ * primo indirizzo può essere pubblico e il secondo no. Resta scoperto il DNS
41
+ * rebinding (un nome pubblico che risolve a 10.x): la cura vera è guardare
42
+ * l'indirizzo del socket, ed è lavoro della corsia B.
43
+ *
44
+ * ⛔ La CSP si RISCRIVE, non si butta. Via `X-Frame-Options` (intero: dice solo
45
+ * chi può incorniciare) e via la SOLA direttiva `frame-ancestors`; `script-src`,
46
+ * `object-src` e le altre restano in piedi. Buttare tutta la CSP toglierebbe a
47
+ * una pagina non affidabile ogni difesa contro i suoi stessi XSS proprio mentre
48
+ * la ospitiamo noi (OWASP «Clickjacking Defense Cheat Sheet» e usamaejaz.com
49
+ * «Securely bypassing X-Frame-Options», letti il 07/09/2026: si modifica la
50
+ * direttiva, non si cancella l'intestazione).
51
+ *
52
+ * ⛔ `integrity` non si tocca. Con `<base>` sull'origine vera una sottorisorsa
53
+ * che prima era same-origin diventa cross-origin: e una risorsa cross-origin con
54
+ * `integrity` DEVE viaggiare in CORS, cioè vuole `crossorigin` nel markup,
55
+ * altrimenti il browser riceve una risposta opaca, non può calcolare l'impronta e
56
+ * blocca lo script (MDN «Subresource Integrity», letto il 07/09/2026). Quindi si
57
+ * AGGIUNGE `crossorigin="anonymous"` dove manca, e non si toglie mai l'impronta:
58
+ * toglierla spegnerebbe in silenzio una difesa sulla catena di fornitura. Per lo
59
+ * stesso motivo il proxy passa SOLO il documento: se riscrivessimo anche le
60
+ * sottorisorse, ogni byte cambiato sarebbe un'impronta che non torna.
61
+ */
62
+ import http from 'node:http';
63
+ import { randomBytes } from 'node:crypto';
64
+
65
+ import { urlAmmesso, MILLISECONDI_MASSIMI } from './browser-frame.mjs';
66
+
67
+ export const BYTE_MASSIMI = 5 * 1024 * 1024;
68
+ export const SALTI_MASSIMI = 5;
69
+ export const OSPITE_SEPARATO = '127.0.0.2';
70
+ export const OSPITE_RIPIEGO = '127.0.0.1';
71
+ const AGENTE = 'TALOS-Harness-Desktop/0.1 (proxy universale)';
72
+
73
+ /* Mai inoltrate al browser. `set-cookie`: i cookie del sito non entrano nel
74
+ * barattolo del proxy. `strict-transport-security` e `clear-site-data`
75
+ * parlerebbero dell'origine NOSTRA. `cross-origin-resource-policy` e i due
76
+ * `cross-origin-*-policy` bloccherebbero proprio la cornice che stiamo
77
+ * costruendo. `content-encoding`/`content-length`/gli hop-by-hop descrivono un
78
+ * corpo che abbiamo già decodificato e riscritto. `x-frame-options` è il motivo
79
+ * per cui questo file esiste. */
80
+ export const INTESTAZIONI_MAI_INOLTRATE = [
81
+ 'set-cookie', 'set-cookie2', 'strict-transport-security', 'clear-site-data', 'public-key-pins',
82
+ 'cross-origin-resource-policy', 'cross-origin-opener-policy', 'cross-origin-embedder-policy',
83
+ 'content-encoding', 'content-length', 'transfer-encoding', 'connection', 'keep-alive', 'upgrade',
84
+ 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'x-frame-options',
85
+ ];
86
+
87
+ const NOMI_SONDATI = ['content-type', 'content-security-policy', 'content-security-policy-report-only', 'x-frame-options', 'set-cookie', 'cache-control', 'content-length', 'content-encoding', 'strict-transport-security', 'cross-origin-resource-policy', 'cross-origin-opener-policy', 'cross-origin-embedder-policy', 'x-content-type-options', 'location'];
88
+
89
+ /* Lo script che entra nella pagina proxata. Vive sull'origine del PROXY — mai su
90
+ * quella di TALOS: una richiesta verso 4174 partirebbe col cookie di sessione. */
91
+ export const SCRIPT_OVERLAY_PREDEFINITO = `(() => {
92
+ const dove = (document.currentScript && document.currentScript.dataset.talosUrl) || location.href;
93
+ const manda = (tipo, dati) => { try { parent.postMessage(Object.assign({ talos: 'browser-proxy', tipo, url: dove }, dati), '*'); } catch { /* il genitore puo' essere andato via */ } };
94
+ addEventListener('error', (e) => manda('errore', { messaggio: String((e && e.message) || 'errore'), riga: (e && e.lineno) || null }), true);
95
+ addEventListener('unhandledrejection', (e) => manda('errore', { messaggio: String((e && e.reason && e.reason.message) || (e && e.reason) || 'promessa rifiutata') }));
96
+ addEventListener('DOMContentLoaded', () => manda('pronta', { titolo: document.title || null }));
97
+ })();`;
98
+
99
+ /* ─────────────── gli indirizzi che il proxy non tocca ─────────────── */
100
+
101
+ function gruppiIpv6(host) {
102
+ const testo = String(host).replace(/^\[|\]$/g, '');
103
+ if (!testo.includes(':')) return null;
104
+ let corpo = testo;
105
+ const conV4 = /^(.*:)((?:\d{1,3}\.){3}\d{1,3})$/.exec(testo);
106
+ if (conV4) {
107
+ const ottetti = conV4[2].split('.').map(Number);
108
+ if (ottetti.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
109
+ corpo = `${conV4[1]}${((ottetti[0] << 8) | ottetti[1]).toString(16)}:${((ottetti[2] << 8) | ottetti[3]).toString(16)}`;
110
+ }
111
+ const parti = corpo.split('::');
112
+ if (parti.length > 2) return null;
113
+ const sinistra = parti[0] ? parti[0].split(':').filter(Boolean) : [];
114
+ const destra = parti.length === 2 ? (parti[1] ? parti[1].split(':').filter(Boolean) : []) : null;
115
+ const buchi = destra === null ? 0 : 8 - sinistra.length - destra.length;
116
+ if (buchi < 0) return null;
117
+ const pezzi = destra === null ? sinistra : [...sinistra, ...Array(buchi).fill('0'), ...destra];
118
+ if (pezzi.length !== 8) return null;
119
+ const gruppi = pezzi.map((p) => (/^[0-9a-f]{1,4}$/i.test(p) ? parseInt(p, 16) : NaN));
120
+ return gruppi.some((n) => Number.isNaN(n)) ? null : gruppi;
121
+ }
122
+
123
+ function ottettiIpv4(host) {
124
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(String(host));
125
+ if (!m) return null;
126
+ const o = m.slice(1).map(Number);
127
+ return o.some((n) => n > 255) ? null : o;
128
+ }
129
+
130
+ function motivoIpv4(ottetti) {
131
+ const [a, b, c] = ottetti;
132
+ if (a === 127) return 'Indirizzo di loopback (127.0.0.0/8)';
133
+ if (a === 0) return 'Indirizzo «questa rete» (0.0.0.0/8)';
134
+ if (a === 10) return 'Indirizzo di rete privata (10.0.0.0/8)';
135
+ if (a === 172 && b >= 16 && b <= 31) return 'Indirizzo di rete privata (172.16.0.0/12)';
136
+ if (a === 192 && b === 168) return 'Indirizzo di rete privata (192.168.0.0/16)';
137
+ if (a === 169 && b === 254) return 'Indirizzo link-local (169.254.0.0/16: è lì che risponde il servizio metadati di una macchina cloud)';
138
+ if (a === 100 && b >= 64 && b <= 127) return 'Indirizzo condiviso dell\'operatore (100.64.0.0/10)';
139
+ if (a === 192 && b === 0 && c === 0) return 'Indirizzo riservato IETF (192.0.0.0/24)';
140
+ if (a === 198 && (b === 18 || b === 19)) return 'Indirizzo per prove di rete (198.18.0.0/15)';
141
+ if (a >= 224) return 'Indirizzo multicast o riservato (224.0.0.0/4 e oltre)';
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Dice PERCHÉ un host non si può proxare, o `null` se è un indirizzo pubblico.
147
+ * @param {string} host l'hostname come l'ha normalizzato `new URL` (IPv6 fra parentesi)
148
+ * @returns {string|null}
149
+ */
150
+ export function indirizzoLocale(host) {
151
+ const nudo = String(host || '').toLowerCase().replace(/^\[|\]$/g, '');
152
+ if (!nudo) return 'Indirizzo senza host';
153
+ if (nudo === 'localhost' || nudo.endsWith('.localhost') || nudo.endsWith('.local') || nudo.endsWith('.internal') || nudo.endsWith('.home.arpa')) {
154
+ return 'Nome che punta al tuo computer o alla tua rete di casa';
155
+ }
156
+ const v4 = ottettiIpv4(nudo);
157
+ if (v4) return motivoIpv4(v4);
158
+ const g = gruppiIpv6(nudo);
159
+ if (g) {
160
+ if (g.every((n) => n === 0)) return 'Indirizzo non specificato (::)';
161
+ if (g.slice(0, 7).every((n) => n === 0) && g[7] === 1) return 'Indirizzo di loopback (::1)';
162
+ /* IPv4 travestito da IPv6: `::ffff:169.254.169.254` è lo stesso bersaglio, scritto come molti controlli non lo riconoscono */
163
+ if (g.slice(0, 5).every((n) => n === 0) && g[5] === 0xffff) return motivoIpv4([g[6] >> 8, g[6] & 0xff, g[7] >> 8, g[7] & 0xff]);
164
+ if ((g[0] & 0xfe00) === 0xfc00) return 'Indirizzo IPv6 di rete locale (fc00::/7)';
165
+ if ((g[0] & 0xffc0) === 0xfe80) return 'Indirizzo IPv6 link-local (fe80::/10)';
166
+ }
167
+ return null;
168
+ }
169
+
170
+ /**
171
+ * Chi il proxy può andare a prendere. Riusa `urlAmmesso` di `browser-frame.mjs`
172
+ * (solo http/https, mai credenziali nell'indirizzo) e ne tiene la FORMA
173
+ * `{ok:true} | {ok:false, motivo}`, aggiungendoci il filtro SSRF.
174
+ * `locale:true` distingue «non lo tocca il proxy» da «non lo tocca nessuno»: un
175
+ * dev server sul computer lo apre benissimo il browser vero, che gira lì.
176
+ * @param {URL} url
177
+ * @returns {{ok:true}|{ok:false, motivo:string, locale?:boolean}}
178
+ */
179
+ export function bersaglioAmmesso(url) {
180
+ const base = urlAmmesso(url);
181
+ if (!base.ok) return base;
182
+ const motivo = indirizzoLocale(url.hostname);
183
+ if (motivo) return { ok: false, motivo, locale: true };
184
+ return { ok: true };
185
+ }
186
+
187
+ /* ─────────────── quale delle tre vie ─────────────── */
188
+
189
+ const SEGNI_NEL_PERCORSO = /(^|\/)(login|log-in|signin|sign-in|accedi|auth|oauth|oauth2|sso|session|account|accounts)(\/|$)/i;
190
+ const SEGNI_NEL_MOTIVO = /(401|403|login|accesso|autentic|credenzial|sessione|cookie|non autorizzat)/i;
191
+
192
+ /**
193
+ * Riconosce una pagina che senza cookie non mostrerebbe niente di utile. Il
194
+ * proxy non inoltra credenziali per costruzione, quindi lì farebbe vedere solo
195
+ * il muro dell'accesso: quella pagina è del browser vero, che ha un profilo suo.
196
+ * @param {{url?:string, motivo?:string|null}} dati
197
+ */
198
+ export function dietroLogin({ url = '', motivo = null } = {}) {
199
+ if (motivo && SEGNI_NEL_MOTIVO.test(String(motivo))) return true;
200
+ let indirizzo;
201
+ try { indirizzo = new URL(String(url)); } catch { return false; }
202
+ if (SEGNI_NEL_PERCORSO.test(indirizzo.pathname)) return true;
203
+ return /^(accounts?|login|auth|signin|sso|my)\./i.test(indirizzo.hostname);
204
+ }
205
+
206
+ /**
207
+ * La scelta della corsia. Regola d'oro: la via più economica che funziona.
208
+ * @param {{incorniciabile?:boolean, motivo?:string|null, url?:string, proxyDisponibile?:boolean, vivoDisponibile?:boolean}} dati
209
+ * @returns {{via:'cornice'|'proxy'|'vivo', perche:string}}
210
+ */
211
+ export function decidiVia({ incorniciabile, motivo = null, url = '', proxyDisponibile = true, vivoDisponibile = false } = {}) {
212
+ if (incorniciabile === true) {
213
+ return { via: 'cornice', perche: 'La pagina si lascia incorniciare: è la via più economica — nessun proxy da attraversare, nessun browser da avviare' };
214
+ }
215
+ let bersaglio = null;
216
+ try { bersaglio = new URL(String(url)); } catch { bersaglio = null; }
217
+ const ammesso = bersaglio ? bersaglioAmmesso(bersaglio) : { ok: false, motivo: 'Indirizzo non valido' };
218
+ if (!ammesso.ok) {
219
+ if (ammesso.locale && vivoDisponibile) return { via: 'vivo', perche: `${ammesso.motivo}: il proxy non tocca gli indirizzi privati, ma il browser vero gira sul tuo computer e ci arriva` };
220
+ if (ammesso.locale) return { via: 'cornice', perche: `${ammesso.motivo}: il proxy non lo tocca e il browser vero non è disponibile` };
221
+ return { via: 'cornice', perche: `${ammesso.motivo}: nessuna corsia lo migliora` };
222
+ }
223
+ if (dietroLogin({ url, motivo })) {
224
+ if (vivoDisponibile) return { via: 'vivo', perche: 'Sembra una pagina dietro accesso: il proxy non inoltra i tuoi cookie, quindi mostrerebbe solo il modulo di accesso — il browser vero ha un profilo suo dove puoi entrare' };
225
+ if (proxyDisponibile) return { via: 'proxy', perche: 'Sembra dietro accesso e il browser vero non è disponibile: il proxy la mostrerà come la vede chi l\'accesso non l\'ha fatto' };
226
+ return { via: 'cornice', perche: 'Sembra dietro accesso e non c\'è né il proxy né il browser vero' };
227
+ }
228
+ if (proxyDisponibile) return { via: 'proxy', perche: `${motivo || 'La pagina vieta la cornice'}: la passiamo dal proxy, su un'origine separata dalla nostra` };
229
+ if (vivoDisponibile) return { via: 'vivo', perche: `${motivo || 'La pagina vieta la cornice'}: il proxy non è disponibile, la apre il browser vero` };
230
+ return { via: 'cornice', perche: `${motivo || 'La pagina vieta la cornice'}: senza proxy né browser vero la cornice resterà quasi certamente vuota` };
231
+ }
232
+
233
+ /* ─────────────── le intestazioni ─────────────── */
234
+
235
+ function coppieDa(intestazioni) {
236
+ if (!intestazioni) return [];
237
+ if (typeof intestazioni.entries === 'function') return [...intestazioni.entries()];
238
+ if (typeof intestazioni.get === 'function') return NOMI_SONDATI.map((n) => [n, intestazioni.get(n)]).filter(([, v]) => v != null && v !== '');
239
+ return Object.entries(intestazioni).filter(([, v]) => v != null && v !== '').map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : String(v)]);
240
+ }
241
+
242
+ /** Legge una singola intestazione da una qualunque delle tre forme (Headers, `{get}`, oggetto). */
243
+ export function prendiIntestazione(intestazioni, nome) {
244
+ const cercato = String(nome).toLowerCase();
245
+ const trovata = coppieDa(intestazioni).find(([k]) => String(k).toLowerCase() === cercato);
246
+ return trovata ? String(trovata[1]) : null;
247
+ }
248
+
249
+ /**
250
+ * Toglie la SOLA direttiva `frame-ancestors`, lasciando in piedi tutto il resto.
251
+ * Una intestazione CSP può portare più politiche separate da virgola: si
252
+ * trattano una per una, e nel browser continuano a intersecarsi.
253
+ * @returns {{csp:string, tolta:boolean}}
254
+ */
255
+ export function senzaFrameAncestors(valore) {
256
+ const testo = String(valore || '');
257
+ let tolta = false;
258
+ const csp = testo.split(',').map((politica) => {
259
+ const direttive = politica.split(';').map((d) => d.trim()).filter(Boolean);
260
+ const restano = direttive.filter((d) => {
261
+ const eLei = /^frame-ancestors\b/i.test(d);
262
+ if (eLei) tolta = true;
263
+ return !eLei;
264
+ });
265
+ return restano.join('; ');
266
+ }).filter(Boolean).join(', ');
267
+ return { csp, tolta };
268
+ }
269
+
270
+ /**
271
+ * Aggiunge UNA origine esatta alle sorgenti di script, così l'overlay può
272
+ * partire anche dentro una pagina con una CSP severa. È un allargamento minimo e
273
+ * dichiarato — l'opposto di cancellare la politica.
274
+ * ⛔ Con `strict-dynamic` in politica il browser ignora le sorgenti per host: lì
275
+ * l'overlay non parte, e quella pagina è del browser vero.
276
+ */
277
+ export function permettiScriptDa(csp, origine) {
278
+ const testo = String(csp || '').trim();
279
+ if (!testo) return testo;
280
+ let org = String(origine || '').trim();
281
+ try { org = new URL(org).origin; } catch { /* accettiamo anche un'origine già nuda */ }
282
+ if (!org) return testo;
283
+ return testo.split(',').map((politica) => {
284
+ const direttive = politica.split(';').map((d) => d.trim()).filter(Boolean);
285
+ let toccata = false;
286
+ for (let i = 0; i < direttive.length; i += 1) {
287
+ if (/^script-src(-elem)?\b/i.test(direttive[i])) {
288
+ if (!direttive[i].toLowerCase().includes(org.toLowerCase())) direttive[i] = `${direttive[i]} ${org}`;
289
+ toccata = true;
290
+ }
291
+ }
292
+ if (!toccata) {
293
+ const predefinita = direttive.find((d) => /^default-src\b/i.test(d));
294
+ /* `script-src` non eredita da `default-src` una volta che esiste: si copia il valore e ci si aggiunge la nostra origine, così nient'altro cambia */
295
+ if (predefinita) direttive.push(`script-src ${predefinita.replace(/^default-src\s*/i, '').trim()} ${org}`.trim());
296
+ }
297
+ return direttive.join('; ');
298
+ }).join(', ');
299
+ }
300
+
301
+ /**
302
+ * @param {Headers|{get:Function}|Record<string,string>} headers
303
+ * @returns {{tolte:string[], tenute:Record<string,string>}}
304
+ */
305
+ export function intestazioniDaTogliere(headers) {
306
+ const tolte = [];
307
+ const tenute = {};
308
+ for (const [nome0, valore0] of coppieDa(headers)) {
309
+ const nome = String(nome0).toLowerCase();
310
+ const valore = Array.isArray(valore0) ? valore0.join(', ') : String(valore0);
311
+ if (INTESTAZIONI_MAI_INOLTRATE.includes(nome)) { tolte.push(nome); continue; }
312
+ if (nome === 'content-security-policy' || nome === 'content-security-policy-report-only') {
313
+ const { csp, tolta } = senzaFrameAncestors(valore);
314
+ if (tolta) tolte.push(`${nome}: frame-ancestors`);
315
+ if (csp) tenute[nome] = csp;
316
+ else if (tolta) tolte.push(nome); /* c'era solo frame-ancestors: non resta niente da mandare */
317
+ else tenute[nome] = valore;
318
+ continue;
319
+ }
320
+ tenute[nome] = valore;
321
+ }
322
+ return { tolte, tenute };
323
+ }
324
+
325
+ /* ─────────────── il documento ─────────────── */
326
+
327
+ const virgolette = (s) => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
328
+
329
+ /* ⛔ `String.replace` con una stringa mangia i `$` (lezione 02/09): qui i sostituti sono SEMPRE funzioni. */
330
+ function riscriviTagConIntegrity(html) {
331
+ return html.replace(/<(script|link)\b[^>]*>/gi, (tag) => {
332
+ if (!/\sintegrity\s*=/i.test(tag)) return tag;
333
+ if (/\scrossorigin(\s*=|[\s/>])/i.test(tag)) return tag;
334
+ return tag.replace(/\s*(\/?)>$/, (_fine, barra) => ` crossorigin="anonymous"${barra ? ' /' : ''}>`);
335
+ });
336
+ }
337
+
338
+ function riscriviMetaCsp(html, origineScript) {
339
+ return html.replace(/<meta\b[^>]*http-equiv\s*=\s*["']?content-security-policy["']?[^>]*>/gi, (tag) => tag.replace(/(content\s*=\s*)("([^"]*)"|'([^']*)')/i, (_intero, prefisso, _tutto, dop, sing) => {
340
+ const valore = dop !== undefined ? dop : (sing || '');
341
+ const nuovo = permettiScriptDa(senzaFrameAncestors(valore).csp, origineScript);
342
+ return `${prefisso}"${virgolette(nuovo)}"`;
343
+ }));
344
+ }
345
+
346
+ /**
347
+ * Prepara il documento per la cornice: `<base>` sull'origine vera (così le
348
+ * sottorisorse continuano a caricarsi dal sito, e nessun byte loro passa da noi
349
+ * a rompere le impronte `integrity`), l'overlay in testa, `frame-ancestors`
350
+ * fuori anche dal `<meta>` — dove per giunta il browser la ignora già (MDN «CSP
351
+ * frame-ancestors», letto il 07/09/2026: in un meta tag la direttiva non ha
352
+ * effetto, la decisione sulla cornice è già stata presa) — e `crossorigin`
353
+ * aggiunto dove c'è `integrity`.
354
+ * @param {string} html
355
+ * @param {{urlPagina:string, origineProxy?:string}} dati
356
+ */
357
+ export function riscriviHtml(html, { urlPagina, origineProxy = '' } = {}) {
358
+ let s = String(html);
359
+ s = riscriviMetaCsp(s, origineProxy);
360
+ s = riscriviTagConIntegrity(s);
361
+ const base = /<base\b[^>]*href\s*=/i.test(s) ? '' : `<base href="${virgolette(urlPagina)}">`;
362
+ const overlay = origineProxy ? `<script src="${virgolette(String(origineProxy).replace(/\/+$/, ''))}/annota.js" data-talos-url="${virgolette(urlPagina)}"></script>` : '';
363
+ const iniezione = `${base}${overlay}`;
364
+ if (!iniezione) return s;
365
+ if (/<head\b[^>]*>/i.test(s)) return s.replace(/<head\b[^>]*>/i, (apertura) => `${apertura}${iniezione}`);
366
+ if (/<html\b[^>]*>/i.test(s)) return s.replace(/<html\b[^>]*>/i, (apertura) => `${apertura}<head>${iniezione}</head>`);
367
+ return `${iniezione}${s}`;
368
+ }
369
+
370
+ /** L'indirizzo con cui la cornice chiede una pagina al proxy. Un bersaglio che il proxy non tocca non deve nemmeno avere un link: qui si alza un errore. */
371
+ export function urlProxato(origineProxy, url) {
372
+ const base = String(origineProxy || '').replace(/\/+$/, '');
373
+ if (!/^https?:\/\/[^/]+/i.test(base)) throw new Error('Origine del proxy non valida');
374
+ let bersaglio;
375
+ try { bersaglio = new URL(String(url)); } catch { throw new Error('Indirizzo non valido'); }
376
+ const ammesso = bersaglioAmmesso(bersaglio);
377
+ if (!ammesso.ok) throw new Error(ammesso.motivo);
378
+ return `${base}/vai?u=${encodeURIComponent(bersaglio.href)}`;
379
+ }
380
+
381
+ /* ─────────────── chi va a prendere la pagina ─────────────── */
382
+
383
+ /**
384
+ * Il lettore predefinito. Segue i reindirizzamenti A MANO, un salto per volta,
385
+ * ricontrollando l'indirizzo a ogni salto: con `redirect: 'follow'` vedremmo solo
386
+ * la destinazione finale, e un 302 verso `169.254.169.254` sarebbe già partito.
387
+ * Niente cookie e niente credenziali: `fetch` di Node non ha un barattolo, e lo
388
+ * diciamo comunque con `credentials: 'omit'`.
389
+ */
390
+ export async function leggiPagina(indirizzo, { fetchFn = globalThis.fetch, millisecondi = MILLISECONDI_MASSIMI, saltiMassimi = SALTI_MASSIMI, ammesso = bersaglioAmmesso } = {}) {
391
+ let url;
392
+ try { url = new URL(String(indirizzo)); } catch { return { ok: false, motivo: 'Indirizzo non valido' }; }
393
+ const controller = new AbortController();
394
+ const timer = setTimeout(() => controller.abort(), millisecondi);
395
+ try {
396
+ for (let salto = 0; salto <= saltiMassimi; salto += 1) {
397
+ const via = ammesso(url);
398
+ if (!via.ok) return { ok: false, motivo: via.motivo, locale: via.locale === true };
399
+ const risposta = await fetchFn(url.href, { method: 'GET', redirect: 'manual', credentials: 'omit', signal: controller.signal, headers: { accept: 'text/html,*/*;q=0.5', 'user-agent': AGENTE } });
400
+ const stato = Number(risposta.status || 0);
401
+ if (stato >= 300 && stato < 400) {
402
+ const dove = prendiIntestazione(risposta.headers, 'location');
403
+ try { await risposta.body?.cancel?.(); } catch { /* niente da liberare */ }
404
+ if (!dove) return { ok: false, motivo: `Reindirizzamento senza destinazione (${stato})` };
405
+ try { url = new URL(dove, url); } catch { return { ok: false, motivo: 'Reindirizzamento verso un indirizzo non valido' }; }
406
+ continue;
407
+ }
408
+ const tipo = String(prendiIntestazione(risposta.headers, 'content-type') || '');
409
+ if (!/text\/html/i.test(tipo)) {
410
+ try { await risposta.body?.cancel?.(); } catch { /* niente da liberare */ }
411
+ return { ok: true, url: url.href, stato, intestazioni: risposta.headers, corpo: '', tipo };
412
+ }
413
+ const testo = await risposta.text();
414
+ if (testo.length > BYTE_MASSIMI) return { ok: false, motivo: 'La pagina supera i 5 MB' };
415
+ return { ok: true, url: url.href, stato, intestazioni: risposta.headers, corpo: testo, tipo };
416
+ }
417
+ return { ok: false, motivo: `Più di ${saltiMassimi} reindirizzamenti` };
418
+ } catch (errore) {
419
+ return { ok: false, motivo: errore?.name === 'AbortError' ? `Nessuna risposta entro ${Math.round(millisecondi / 1000)} secondi` : 'La pagina non risponde' };
420
+ } finally {
421
+ clearTimeout(timer);
422
+ }
423
+ }
424
+
425
+ /* ─────────────── il server sulla porta sua ─────────────── */
426
+
427
+ async function ascolta(server, porta, ospite, ripiego) {
428
+ const prova = (indirizzo) => new Promise((risolvi, rifiuta) => {
429
+ const suErrore = (e) => { server.removeListener('listening', suAscolto); rifiuta(e); };
430
+ const suAscolto = () => { server.removeListener('error', suErrore); risolvi(indirizzo); };
431
+ server.once('error', suErrore);
432
+ server.once('listening', suAscolto);
433
+ server.listen(porta, indirizzo);
434
+ });
435
+ try {
436
+ return { ospite: await prova(ospite), cookieCondiviso: false };
437
+ } catch (errore) {
438
+ if (ospite === ripiego) throw errore;
439
+ /* macOS non lega 127.0.0.2 senza un alias su lo0: si ripiega, e lo si DICE a chi chiama */
440
+ return { ospite: await prova(ripiego), cookieCondiviso: true };
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Accende il proxy su una porta sua. `leggi` è iniettato: i test non toccano la
446
+ * rete. La chiave nel percorso non cambia l'origine (quella la fa la porta): serve
447
+ * solo perché un altro programma sul computer non possa usarci come proxy aperto.
448
+ * @param {{porta?:number, leggi:Function, ammesso?:Function, ospite?:string, origineOspite?:string|null, scriptOverlay?:string, chiave?:string}} opzioni
449
+ * @returns {Promise<{porta:number, origine:string, ospite:string, cookieCondiviso:boolean, chiave:string, chiudi:()=>Promise<void>}>}
450
+ */
451
+ export async function creaServerProxy({ porta = 0, leggi, ammesso = bersaglioAmmesso, ospite = OSPITE_SEPARATO, origineOspite = null, scriptOverlay = SCRIPT_OVERLAY_PREDEFINITO, chiave = randomBytes(16).toString('hex') } = {}) {
452
+ if (typeof leggi !== 'function') throw new TypeError('creaServerProxy vuole «leggi»: chi va a prendere la pagina');
453
+ const prefisso = `/s/${chiave}`;
454
+ let origine = '';
455
+
456
+ const server = http.createServer((req, res) => {
457
+ const rispondi = (stato, testo) => {
458
+ res.writeHead(stato, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' });
459
+ res.end(testo);
460
+ };
461
+ (async () => {
462
+ if (req.method !== 'GET') return rispondi(405, 'Il proxy risponde solo a GET');
463
+ let percorso;
464
+ try { percorso = new URL(req.url, 'http://proxy.invalido'); } catch { return rispondi(400, 'Richiesta non valida'); }
465
+ if (!percorso.pathname.startsWith(`${prefisso}/`)) return rispondi(404, 'Qui non c\'è niente');
466
+ const resto = percorso.pathname.slice(prefisso.length);
467
+ if (resto === '/annota.js') {
468
+ res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8', 'cache-control': 'no-store' });
469
+ res.end(scriptOverlay);
470
+ return undefined;
471
+ }
472
+ if (resto !== '/vai') return rispondi(404, 'Qui non c\'è niente');
473
+ const chiesto = percorso.searchParams.get('u');
474
+ if (!chiesto) return rispondi(400, 'Manca l\'indirizzo da mostrare');
475
+ let bersaglio;
476
+ try { bersaglio = new URL(chiesto); } catch { return rispondi(400, 'Indirizzo non valido'); }
477
+ const via = ammesso(bersaglio);
478
+ if (!via.ok) return rispondi(403, via.motivo);
479
+ let pagina;
480
+ try { pagina = await leggi(bersaglio.href); } catch { pagina = { ok: false, motivo: 'La pagina non risponde' }; }
481
+ if (!pagina || pagina.ok === false) return rispondi(502, pagina?.motivo || 'La pagina non risponde');
482
+ const tipo = String(pagina.tipo || prendiIntestazione(pagina.intestazioni, 'content-type') || '');
483
+ if (!/text\/html/i.test(tipo)) return rispondi(415, 'Il proxy passa solo il documento HTML: script, stili e immagini si caricano dal sito vero grazie a <base>');
484
+ const { tolte, tenute } = intestazioniDaTogliere(pagina.intestazioni);
485
+ const html = riscriviHtml(String(pagina.corpo || ''), { urlPagina: pagina.url || bersaglio.href, origineProxy: origine });
486
+ const intestazioni = {
487
+ ...tenute,
488
+ 'content-type': 'text/html; charset=utf-8',
489
+ 'cache-control': 'no-store',
490
+ 'referrer-policy': 'no-referrer',
491
+ 'x-talos-tolte': tolte.join(', ') || 'nessuna',
492
+ 'x-talos-origine': pagina.url || bersaglio.href,
493
+ };
494
+ const delSito = tenute['content-security-policy'] ? permettiScriptDa(tenute['content-security-policy'], origine) : null;
495
+ /* due politiche CSP si INTERSECANO nel browser: quella del sito (senza frame-ancestors) resta in piedi, la nostra dice soltanto chi può incorniciare */
496
+ const nostra = origineOspite ? `frame-ancestors 'self' ${origineOspite}` : null;
497
+ if (delSito && nostra) intestazioni['content-security-policy'] = [delSito, nostra];
498
+ else if (delSito) intestazioni['content-security-policy'] = delSito;
499
+ else if (nostra) intestazioni['content-security-policy'] = nostra;
500
+ res.writeHead(200, intestazioni);
501
+ res.end(html);
502
+ return undefined;
503
+ })().catch(() => {
504
+ try { rispondi(500, 'Errore del proxy'); } catch { /* risposta già chiusa */ }
505
+ });
506
+ });
507
+
508
+ const legato = await ascolta(server, porta, ospite, OSPITE_RIPIEGO);
509
+ const portaVera = server.address().port;
510
+ origine = `http://${legato.ospite}:${portaVera}${prefisso}`;
511
+ return {
512
+ porta: portaVera,
513
+ origine,
514
+ ospite: legato.ospite,
515
+ cookieCondiviso: legato.cookieCondiviso,
516
+ chiave,
517
+ chiudi: () => new Promise((risolvi) => { server.close(() => risolvi()); }),
518
+ };
519
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * browser-proxy.mjs — Il proxy locale (06/09) che rende una pagina di un
3
+ * dev server LOCALE «della nostra origine», così dentro la cornice si può iniettare l'overlay di
4
+ * annotazione e leggere il DOM (same-origin policy). Una webview Electron eviterebbe il problema;
5
+ * in un guscio browser la strada è il proxy, come VS Code
6
+ * Live Preview.
7
+ *
8
+ * ⛔ SOLO bersagli locali (localhost, 127.0.0.1, ::1): una pagina passata dal proxy gira nella
9
+ * NOSTRA origine, quindi i suoi script vedono localStorage e cookie di TALOS. Per il dev server
10
+ * dello sviluppatore è il suo stesso codice; per un sito remoto sarebbe consegnargli TALOS.
11
+ * Un sito remoto resta nella cornice normale (altra origine, senza annotazione) o bloccato.
12
+ *
13
+ * Riscrittura (fonti lette il 06/09/2026: gist cprima «PHP proxy for iframe embedding», niutech
14
+ * x-frame-bypass, usamaejaz «bypassing X-Frame-Options»): via le intestazioni X-Frame-Options e
15
+ * Content-Security-Policy della pagina e il `<meta http-equiv="Content-Security-Policy">`;
16
+ * `<base href>` sull'origine vera, così script, stili e immagini relativi si caricano dal dev
17
+ * server; il nostro script in testa al `<head>` (cattura gli errori di console PRIMA degli script
18
+ * della pagina). Al documento proxato NON si applica la CSP di TALOS (bloccherebbe gli script del
19
+ * dev server): resta `frame-ancestors 'self'` — nessun altro può incorniciarlo.
20
+ */
21
+ import { urlAmmesso, MILLISECONDI_MASSIMI, classificaGuasto } from './browser-frame.mjs'; // 16/09: il guasto si nomina in UN posto solo
22
+
23
+ export const BYTE_MASSIMI = 5 * 1024 * 1024;
24
+ export const SCRIPT_OVERLAY = '/talos/browser-annota.js';
25
+
26
+ /** Solo il computer dello sviluppatore: la pagina proxata gira nella nostra origine. */
27
+ export function bersaglioLocale(url) {
28
+ const host = String(url?.hostname || '').toLowerCase().replace(/^\[|\]$/g, '');
29
+ return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
30
+ }
31
+
32
+ /**
33
+ * Riscrive il documento HTML per la cornice: niente CSP in pagina, `<base>` sull'origine vera, il
34
+ * nostro script per primo nel `<head>`.
35
+ * @param {string} html
36
+ * @param {string} urlVero l'indirizzo della pagina (per `<base>` e per il segnale al genitore)
37
+ * @param {string} origineNostra l'origine di TALOS: con `<base>` sul dev server, `/talos/…` andrebbe a finire LÀ — lo script si carica con l'indirizzo assoluto nostro (trovato dal vivo il 06/09: overlay assente)
38
+ */
39
+ export function riscriviHtml(html, urlVero, origineNostra = '') {
40
+ let s = String(html);
41
+ s = s.replace(/<meta[^>]+http-equiv=["']?content-security-policy["']?[^>]*>/gi, '');
42
+ const haBase = /<base\b[^>]*href=/i.test(s);
43
+ const iniezione = `${haBase ? '' : `<base href="${urlVero.replace(/"/g, '&quot;')}">`}<script src="${origineNostra.replace(/\/$/, '')}${SCRIPT_OVERLAY}" data-talos-url="${urlVero.replace(/"/g, '&quot;')}"></script>`;
44
+ if (/<head[^>]*>/i.test(s)) s = s.replace(/<head[^>]*>/i, (m) => `${m}${iniezione}`);
45
+ else if (/<html[^>]*>/i.test(s)) s = s.replace(/<html[^>]*>/i, (m) => `${m}<head>${iniezione}</head>`);
46
+ else s = `${iniezione}${s}`;
47
+ return s;
48
+ }
49
+
50
+ /**
51
+ * @param {string} indirizzo
52
+ * @param {{fetchFn?:typeof fetch, millisecondi?:number}} deps
53
+ * @returns {Promise<{ok:true, html:string, url:string, stato:number}|{ok:false, codice:string, motivo:string, stato?:number}>}
54
+ */
55
+ export async function proxyPagina(indirizzo, { fetchFn = globalThis.fetch, millisecondi = MILLISECONDI_MASSIMI, origineNostra = '' } = {}) {
56
+ let url;
57
+ try { url = new URL(String(indirizzo)); } catch { return { ok: false, codice: 'QUERY_INVALID', motivo: 'URL non valido' }; }
58
+ const ammesso = urlAmmesso(url);
59
+ if (!ammesso.ok) return { ok: false, codice: 'QUERY_INVALID', motivo: ammesso.motivo };
60
+ if (!bersaglioLocale(url)) return { ok: false, codice: 'BROWSER_PROXY_SOLO_LOCALE', motivo: 'Il proxy con annotazione vale solo per un dev server sul tuo computer (localhost, 127.0.0.1)' };
61
+ const controller = new AbortController();
62
+ const timer = setTimeout(() => controller.abort(), millisecondi);
63
+ try {
64
+ const risposta = await fetchFn(url.href, { method: 'GET', redirect: 'follow', signal: controller.signal, headers: { accept: 'text/html,*/*;q=0.5', 'user-agent': 'TALOS-Harness-Desktop/0.1 (proxy locale)' } });
65
+ const finale = (() => { try { return new URL(risposta.url || url.href); } catch { return url; } })();
66
+ if (!bersaglioLocale(finale)) { try { await risposta.body?.cancel?.(); } catch { /* niente */ } return { ok: false, codice: 'BROWSER_PROXY_SOLO_LOCALE', motivo: 'La pagina ha reindirizzato fuori dal tuo computer' }; }
67
+ const tipo = String(risposta.headers.get('content-type') || '');
68
+ if (!/text\/html/i.test(tipo)) { try { await risposta.body?.cancel?.(); } catch { /* niente */ } return { ok: false, codice: 'BROWSER_PROXY_NON_HTML', motivo: `Non è una pagina HTML (${tipo.split(';')[0] || 'tipo ignoto'})`, stato: risposta.status }; }
69
+ const lunghezza = Number(risposta.headers.get('content-length') || 0);
70
+ if (lunghezza > BYTE_MASSIMI) { try { await risposta.body?.cancel?.(); } catch { /* niente */ } return { ok: false, codice: 'BROWSER_PROXY_TROPPO_GRANDE', motivo: 'La pagina supera i 5 MB', stato: risposta.status }; }
71
+ const testo = await risposta.text();
72
+ if (testo.length > BYTE_MASSIMI) return { ok: false, codice: 'BROWSER_PROXY_TROPPO_GRANDE', motivo: 'La pagina supera i 5 MB', stato: risposta.status };
73
+ return { ok: true, html: riscriviHtml(testo, finale.href, origineNostra), url: finale.href, stato: risposta.status };
74
+ } catch (errore) {
75
+ /* ⛔ 16/09 — stessa cura di `browser-frame.mjs`, stessa tabella: su un dev server «non l'hai
76
+ acceso» e «ci ho messo troppo» sono due gesti diversi per chi programma, e prima uscivano
77
+ con la stessa frase. La classificazione è importata, non ricopiata. */
78
+ /* ⛔ 16/09, giro di riparazione — passano anche i `dettagli` (i secondi di un timeout): il
79
+ motivo qui è italiano e composto dal server, quindi chi disegna deve poter riscrivere la
80
+ frase nella lingua di chi guarda invece di provare a tradurre una chiave che contiene un
81
+ numero. Stessa cura del percorso della cornice, stesso contratto. */
82
+ const { genere, motivo, dettagli } = classificaGuasto(errore, { millisecondi });
83
+ return { ok: false, codice: 'BROWSER_PROXY_IRRAGGIUNGIBILE', motivo, genere, dettagli };
84
+ } finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }