talos-code 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +111 -3
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  4. package/dist/archive/zip.js +73 -0
  5. package/dist/args.js +97 -0
  6. package/dist/automations/history-store.js +34 -0
  7. package/dist/automations/policy-store.js +43 -0
  8. package/dist/automations/runner.js +107 -0
  9. package/dist/automations/schedule.js +85 -0
  10. package/dist/automations/windows-task.js +53 -0
  11. package/dist/commands/advanced-cli.js +233 -0
  12. package/dist/commands/checkpoint-cli.js +45 -0
  13. package/dist/commands/config-cli.js +107 -0
  14. package/dist/commands/context.js +17 -0
  15. package/dist/commands/extensions-cli.js +207 -0
  16. package/dist/commands/project-cli.js +74 -0
  17. package/dist/commands/project-commands.js +143 -0
  18. package/dist/commands/provider-cli.js +185 -0
  19. package/dist/commands/services-cli.js +248 -0
  20. package/dist/commands/session-cli.js +171 -0
  21. package/dist/commands/system-cli.js +405 -0
  22. package/dist/config/commands.js +60 -0
  23. package/dist/config/load.js +382 -0
  24. package/dist/config/migrations.js +48 -0
  25. package/dist/config/types.js +11 -0
  26. package/dist/diagnostics/development-log.js +154 -0
  27. package/dist/diagnostics/doctor.js +104 -0
  28. package/dist/diagnostics/redact.js +80 -0
  29. package/dist/diagnostics/zip.js +52 -0
  30. package/dist/errors.js +156 -0
  31. package/dist/events/bridge.js +130 -0
  32. package/dist/extensions/installer.js +178 -0
  33. package/dist/extensions/package-schema.js +24 -0
  34. package/dist/headless/result.js +84 -0
  35. package/dist/headless/run.js +230 -0
  36. package/dist/i18n/en/approval.js +43 -0
  37. package/dist/i18n/en/common.js +9 -0
  38. package/dist/i18n/en/credentials.js +82 -0
  39. package/dist/i18n/en/errors.js +246 -0
  40. package/dist/i18n/en/firstrun.js +89 -0
  41. package/dist/i18n/en/providers.js +37 -0
  42. package/dist/i18n/en/screen.js +736 -0
  43. package/dist/i18n/en/tools.js +127 -0
  44. package/dist/i18n/en.js +14 -0
  45. package/dist/i18n/error-view.js +307 -0
  46. package/dist/i18n/index.js +19 -0
  47. package/dist/i18n/kernel-map.js +139 -0
  48. package/dist/io.js +45 -0
  49. package/dist/main.js +330 -0
  50. package/dist/paths.js +41 -0
  51. package/dist/protocol/v2/codec.js +9 -0
  52. package/dist/protocol/v2/events.js +555 -0
  53. package/dist/protocol/v2/index.js +4 -0
  54. package/dist/protocol/v2/replay.js +28 -0
  55. package/dist/protocol/v2/types.js +1 -0
  56. package/dist/provider/control-plane.js +135 -0
  57. package/dist/provider/environment-keys.js +275 -0
  58. package/dist/provider/health.js +110 -0
  59. package/dist/provider/missing-key.js +48 -0
  60. package/dist/provider/model-catalog.js +168 -0
  61. package/dist/provider/provider-text.js +13 -0
  62. package/dist/provider/store.js +95 -0
  63. package/dist/provider/system-keyring.js +214 -0
  64. package/dist/runtime/active-run.js +63 -0
  65. package/dist/runtime/agent-tree.js +74 -0
  66. package/dist/runtime/attachments.js +84 -0
  67. package/dist/runtime/brokered-executor.js +395 -0
  68. package/dist/runtime/context-status.js +76 -0
  69. package/dist/runtime/create-runtime.js +111 -0
  70. package/dist/runtime/model-profile.js +361 -0
  71. package/dist/runtime/output-store.js +137 -0
  72. package/dist/runtime/provider-attempts.js +185 -0
  73. package/dist/runtime/replay-buffer.js +153 -0
  74. package/dist/runtime/repo.js +95 -0
  75. package/dist/runtime/session-facade.js +135 -0
  76. package/dist/runtime/session-summary.js +125 -0
  77. package/dist/runtime/supervisor.js +262 -0
  78. package/dist/runtime/talos-composition.js +1006 -0
  79. package/dist/runtime/types.js +5 -0
  80. package/dist/runtime/usage-snapshot.js +82 -0
  81. package/dist/security/auto-classifier.js +38 -0
  82. package/dist/security/credential-free-environment.js +54 -0
  83. package/dist/security/evaluate.js +243 -0
  84. package/dist/security/execution-backends.js +236 -0
  85. package/dist/security/execution-broker.js +102 -0
  86. package/dist/security/forge-scan.js +74 -0
  87. package/dist/security/from-approval.js +39 -0
  88. package/dist/security/mxc-execution-backend.js +281 -0
  89. package/dist/security/permission-engine.js +138 -0
  90. package/dist/security/permission-explanation.js +54 -0
  91. package/dist/security/persist.js +179 -0
  92. package/dist/security/plugin-guard.js +230 -0
  93. package/dist/security/process-tree-evidence-store.js +74 -0
  94. package/dist/security/process-tree-probe.js +554 -0
  95. package/dist/security/project-resource-inventory.js +186 -0
  96. package/dist/security/project-trust-gate.js +38 -0
  97. package/dist/security/project-trust.js +367 -0
  98. package/dist/security/rule-parser.js +201 -0
  99. package/dist/security/shell-segmentation.js +68 -0
  100. package/dist/security/trust-authority.js +324 -0
  101. package/dist/security/types.js +1 -0
  102. package/dist/security/workspace-identity.js +102 -0
  103. package/dist/services/automation-facade.js +59 -0
  104. package/dist/services/forge-facade.js +77 -0
  105. package/dist/services/hook-facade.js +240 -0
  106. package/dist/services/index.js +16 -0
  107. package/dist/services/library-facade.js +174 -0
  108. package/dist/services/mcp-facade.js +348 -0
  109. package/dist/services/memory-facade.js +126 -0
  110. package/dist/services/notes-facade.js +157 -0
  111. package/dist/services/plugin-facade.js +308 -0
  112. package/dist/services/research-facade.js +110 -0
  113. package/dist/services/task-board-facade.js +236 -0
  114. package/dist/services/workflow-catalog.js +246 -0
  115. package/dist/sessions/export-format.js +98 -0
  116. package/dist/sessions/metadata-store.js +160 -0
  117. package/dist/sessions/share.js +122 -0
  118. package/dist/sessions/transfer.js +16 -0
  119. package/dist/subcommands.js +62 -0
  120. package/dist/tui/agent-roster.js +36 -0
  121. package/dist/tui/app.js +3761 -0
  122. package/dist/tui/approval.js +36 -0
  123. package/dist/tui/boot/boot-sequence.js +76 -0
  124. package/dist/tui/boot/cinematic.js +243 -0
  125. package/dist/tui/boot/desktop-logo.js +82 -0
  126. package/dist/tui/boot.js +3 -0
  127. package/dist/tui/busy-input.js +58 -0
  128. package/dist/tui/catalog-service.js +559 -0
  129. package/dist/tui/components/assistant-stream.js +1 -0
  130. package/dist/tui/components/command-menu.js +68 -0
  131. package/dist/tui/components/composer.js +219 -0
  132. package/dist/tui/components/diff.js +35 -0
  133. package/dist/tui/components/footer.js +48 -0
  134. package/dist/tui/components/header.js +6 -0
  135. package/dist/tui/components/markdown.js +305 -0
  136. package/dist/tui/components/status-indicator.js +32 -0
  137. package/dist/tui/components/terminal-shell.js +205 -0
  138. package/dist/tui/components/thinking-row.js +19 -0
  139. package/dist/tui/components/tool-row.js +97 -0
  140. package/dist/tui/components/transcript-virtualizer.js +165 -0
  141. package/dist/tui/components/transcript.js +43 -0
  142. package/dist/tui/diff-model.js +77 -0
  143. package/dist/tui/editor-history.js +21 -0
  144. package/dist/tui/editor.js +210 -0
  145. package/dist/tui/event-adapter.js +436 -0
  146. package/dist/tui/exit-output.js +58 -0
  147. package/dist/tui/external-editor.js +53 -0
  148. package/dist/tui/file-completion.js +89 -0
  149. package/dist/tui/focus-manager.js +5 -0
  150. package/dist/tui/highlight.js +30 -0
  151. package/dist/tui/input-router.js +18 -0
  152. package/dist/tui/interrupt.js +99 -0
  153. package/dist/tui/keybindings.js +194 -0
  154. package/dist/tui/keymap-resolver.js +91 -0
  155. package/dist/tui/launch-state.js +139 -0
  156. package/dist/tui/line-diff.js +57 -0
  157. package/dist/tui/live-activity.js +45 -0
  158. package/dist/tui/metrics.js +123 -0
  159. package/dist/tui/onboarding.js +36 -0
  160. package/dist/tui/overlays/agent-tree.js +39 -0
  161. package/dist/tui/overlays/approval-dialog.js +640 -0
  162. package/dist/tui/overlays/automation-center.js +51 -0
  163. package/dist/tui/overlays/checkpoint-picker.js +47 -0
  164. package/dist/tui/overlays/context-inspector.js +36 -0
  165. package/dist/tui/overlays/effort-line.js +110 -0
  166. package/dist/tui/overlays/forge-center.js +46 -0
  167. package/dist/tui/overlays/help-dialog.js +16 -0
  168. package/dist/tui/overlays/history-picker.js +37 -0
  169. package/dist/tui/overlays/hook-center.js +70 -0
  170. package/dist/tui/overlays/library-center.js +55 -0
  171. package/dist/tui/overlays/mcp-center.js +60 -0
  172. package/dist/tui/overlays/memory-center.js +63 -0
  173. package/dist/tui/overlays/model-picker.js +72 -0
  174. package/dist/tui/overlays/notes-center.js +53 -0
  175. package/dist/tui/overlays/overlay-host.js +8 -0
  176. package/dist/tui/overlays/plugin-center.js +76 -0
  177. package/dist/tui/overlays/provider-picker.js +145 -0
  178. package/dist/tui/overlays/queue-editor.js +32 -0
  179. package/dist/tui/overlays/research-center.js +59 -0
  180. package/dist/tui/overlays/scroll-window.js +234 -0
  181. package/dist/tui/overlays/session-picker.js +115 -0
  182. package/dist/tui/overlays/tasks-center.js +83 -0
  183. package/dist/tui/overlays/theme-picker.js +21 -0
  184. package/dist/tui/overlays/transcript-search.js +58 -0
  185. package/dist/tui/overlays/trust-center.js +29 -0
  186. package/dist/tui/overlays/workflow-center.js +46 -0
  187. package/dist/tui/project-file-index.js +214 -0
  188. package/dist/tui/project-references.js +85 -0
  189. package/dist/tui/project-trust-prompt.js +287 -0
  190. package/dist/tui/prompt-history-store.js +116 -0
  191. package/dist/tui/queue-store.js +110 -0
  192. package/dist/tui/regions/budget.js +59 -0
  193. package/dist/tui/regions/views.js +96 -0
  194. package/dist/tui/render-coordinator.js +148 -0
  195. package/dist/tui/render-scheduler.js +4 -0
  196. package/dist/tui/run.js +69 -0
  197. package/dist/tui/selection-list.js +29 -0
  198. package/dist/tui/session-controller.js +778 -0
  199. package/dist/tui/session-export.js +54 -0
  200. package/dist/tui/shell-input.js +35 -0
  201. package/dist/tui/shell-layout.js +48 -0
  202. package/dist/tui/shell-model.js +98 -0
  203. package/dist/tui/slash-commands.js +80 -0
  204. package/dist/tui/state.js +151 -0
  205. package/dist/tui/status-bar.js +125 -0
  206. package/dist/tui/status-view.js +40 -0
  207. package/dist/tui/terminal-capabilities.js +9 -0
  208. package/dist/tui/terminal-session.js +11 -0
  209. package/dist/tui/text-width.js +66 -0
  210. package/dist/tui/theme-catalog.js +25 -0
  211. package/dist/tui/theme-store.js +23 -0
  212. package/dist/tui/theme.js +23 -0
  213. package/dist/tui/tool-display.js +135 -0
  214. package/dist/tui/tool-facts.js +1 -0
  215. package/dist/tui/tools/bash-renderer.js +32 -0
  216. package/dist/tui/tools/change.js +61 -0
  217. package/dist/tui/tools/edit-renderer.js +17 -0
  218. package/dist/tui/tools/generic-renderer.js +13 -0
  219. package/dist/tui/tools/list-renderer.js +14 -0
  220. package/dist/tui/tools/read-renderer.js +13 -0
  221. package/dist/tui/tools/registry.js +20 -0
  222. package/dist/tui/tools/row-format.js +187 -0
  223. package/dist/tui/tools/search-renderer.js +38 -0
  224. package/dist/tui/tools/shared.js +97 -0
  225. package/dist/tui/tools/write-renderer.js +15 -0
  226. package/dist/tui/transcript-model.js +441 -0
  227. package/dist/tui/ui-preferences.js +79 -0
  228. package/dist/tui/usage-view.js +77 -0
  229. package/dist/tui/vim-mode.js +99 -0
  230. package/dist/update/check.js +15 -0
  231. package/dist/update/npm.js +51 -0
  232. package/dist/update/run.js +129 -0
  233. package/dist/version.js +2 -0
  234. package/dist/workspace/checkpoint-store.js +210 -0
  235. package/dist/workspace/checkpoint.js +413 -0
  236. package/dist/workspace/restore.js +232 -0
  237. package/package.json +63 -5
  238. package/vendor/context-engine/package.json +11 -0
  239. package/vendor/context-engine/src/compaction-planner.mjs +57 -0
  240. package/vendor/context-engine/src/contracts.mjs +48 -0
  241. package/vendor/context-engine/src/engine.mjs +445 -0
  242. package/vendor/context-engine/src/node/context-export.mjs +164 -0
  243. package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
  244. package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
  245. package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
  246. package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
  247. package/vendor/context-engine/src/profiles.mjs +10 -0
  248. package/vendor/context-engine/src/retrieval.mjs +104 -0
  249. package/vendor/context-engine/src/summary.mjs +97 -0
  250. package/vendor/context-engine/src/usage.mjs +34 -0
  251. package/vendor/harness-ui/package.json +38 -0
  252. package/vendor/harness-ui/src/acp-agent.mjs +350 -0
  253. package/vendor/harness-ui/src/agent-service.mjs +2188 -0
  254. package/vendor/harness-ui/src/agui-events.mjs +452 -0
  255. package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
  256. package/vendor/harness-ui/src/artifact-store.mjs +39 -0
  257. package/vendor/harness-ui/src/assistenza.mjs +123 -0
  258. package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
  259. package/vendor/harness-ui/src/automation-store.mjs +145 -0
  260. package/vendor/harness-ui/src/browser-annota.mjs +639 -0
  261. package/vendor/harness-ui/src/browser-frame.mjs +211 -0
  262. package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
  263. package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
  264. package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
  265. package/vendor/harness-ui/src/browser-stream.mjs +445 -0
  266. package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
  267. package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
  268. package/vendor/harness-ui/src/config.mjs +697 -0
  269. package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
  270. package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
  271. package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
  272. package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
  273. package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
  274. package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
  275. package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
  276. package/vendor/harness-ui/src/context-runtime.mjs +118 -0
  277. package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
  278. package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
  279. package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
  280. package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
  281. package/vendor/harness-ui/src/custom-task.mjs +171 -0
  282. package/vendor/harness-ui/src/doctor.mjs +142 -0
  283. package/vendor/harness-ui/src/document-filename.mjs +97 -0
  284. package/vendor/harness-ui/src/document-generator.mjs +493 -0
  285. package/vendor/harness-ui/src/document-report.mjs +331 -0
  286. package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
  287. package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
  288. package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
  289. package/vendor/harness-ui/src/forge-contract.mjs +221 -0
  290. package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
  291. package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
  292. package/vendor/harness-ui/src/generation-idle.mjs +332 -0
  293. package/vendor/harness-ui/src/gguf-header.mjs +207 -0
  294. package/vendor/harness-ui/src/git-service.mjs +626 -0
  295. package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
  296. package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
  297. package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
  298. package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
  299. package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
  300. package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
  301. package/vendor/harness-ui/src/hook-registry.mjs +186 -0
  302. package/vendor/harness-ui/src/http-app.mjs +6259 -0
  303. package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
  304. package/vendor/harness-ui/src/id-archivio.mjs +27 -0
  305. package/vendor/harness-ui/src/image-generator.mjs +143 -0
  306. package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
  307. package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
  308. package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
  309. package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
  310. package/vendor/harness-ui/src/library-store.mjs +652 -0
  311. package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
  312. package/vendor/harness-ui/src/local-model-store.mjs +339 -0
  313. package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
  314. package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
  315. package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
  316. package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
  317. package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
  318. package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
  319. package/vendor/harness-ui/src/mcp-client.mjs +98 -0
  320. package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
  321. package/vendor/harness-ui/src/mcp-session.mjs +177 -0
  322. package/vendor/harness-ui/src/memory-store.mjs +227 -0
  323. package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
  324. package/vendor/harness-ui/src/model-catalog.mjs +129 -0
  325. package/vendor/harness-ui/src/model-destination.mjs +189 -0
  326. package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
  327. package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
  328. package/vendor/harness-ui/src/notes-store.mjs +250 -0
  329. package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
  330. package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
  331. package/vendor/harness-ui/src/path-policy.mjs +442 -0
  332. package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
  333. package/vendor/harness-ui/src/plugin-session.mjs +180 -0
  334. package/vendor/harness-ui/src/process-policy.mjs +345 -0
  335. package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
  336. package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
  337. package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
  338. package/vendor/harness-ui/src/provider-probe.mjs +582 -0
  339. package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
  340. package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
  341. package/vendor/harness-ui/src/public-problem.mjs +109 -0
  342. package/vendor/harness-ui/src/research/card.mjs +235 -0
  343. package/vendor/harness-ui/src/research/citations.mjs +142 -0
  344. package/vendor/harness-ui/src/research/collector.mjs +275 -0
  345. package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
  346. package/vendor/harness-ui/src/research/dossier.mjs +114 -0
  347. package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
  348. package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
  349. package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
  350. package/vendor/harness-ui/src/research/independence.mjs +159 -0
  351. package/vendor/harness-ui/src/research/ledger.mjs +166 -0
  352. package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
  353. package/vendor/harness-ui/src/research/narration.mjs +181 -0
  354. package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
  355. package/vendor/harness-ui/src/research/opposing.mjs +305 -0
  356. package/vendor/harness-ui/src/research/outline.mjs +111 -0
  357. package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
  358. package/vendor/harness-ui/src/research/pdf.mjs +291 -0
  359. package/vendor/harness-ui/src/research/plan.mjs +301 -0
  360. package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
  361. package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
  362. package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
  363. package/vendor/harness-ui/src/research/recheck.mjs +194 -0
  364. package/vendor/harness-ui/src/research/report.mjs +203 -0
  365. package/vendor/harness-ui/src/research/run.mjs +527 -0
  366. package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
  367. package/vendor/harness-ui/src/research/verification.mjs +572 -0
  368. package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
  369. package/vendor/harness-ui/src/research-store.mjs +1133 -0
  370. package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
  371. package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
  372. package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
  373. package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
  374. package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
  375. package/vendor/harness-ui/src/search-source-store.mjs +172 -0
  376. package/vendor/harness-ui/src/session-registry.mjs +6095 -0
  377. package/vendor/harness-ui/src/session-store.mjs +220 -0
  378. package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
  379. package/vendor/harness-ui/src/setup-stato.mjs +31 -0
  380. package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
  381. package/vendor/harness-ui/src/skill-registry.mjs +120 -0
  382. package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
  383. package/vendor/harness-ui/src/static-files.mjs +96 -0
  384. package/vendor/harness-ui/src/stream-partition.mjs +123 -0
  385. package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
  386. package/vendor/harness-ui/src/task-catalog.mjs +65 -0
  387. package/vendor/harness-ui/src/tasks-store.mjs +220 -0
  388. package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
  389. package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
  390. package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
  391. package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
  392. package/vendor/harness-ui/src/usage-cache.mjs +315 -0
  393. package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
  394. package/vendor/harness-ui/src/workspace-context.mjs +124 -0
  395. package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
  396. package/vendor/harness-ui/src/workspace-files.mjs +589 -0
  397. package/vendor/harness-ui/src/workspace-info.mjs +189 -0
  398. package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
  399. package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
  400. package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
  401. package/vendor/manifest.json +170 -0
@@ -0,0 +1,116 @@
1
+ /*
2
+ * R5b D5 (owner decision Q-R5-15): the prompts a person sends, kept across sessions. One file per project under the data
3
+ * root (`<dataRoot>/history/<projectId>.jsonl`, never in the project), the last 1,000 (Hermes, the Node REPL); each line
4
+ * `{v:1,text,collapsedPastes,at}`, the newest last. Before a prompt reaches the disk its secret-shaped values and the
5
+ * environment's secret values are masked (diagnostics/redact.ts, the same masks as the logs), piece by piece so a collapsed
6
+ * paste still covers its own text afterwards. `ui.history.persist: false` turns it off: nothing is read and nothing is
7
+ * written. Before R5b the history lived in memory only (R4 E17).
8
+ *
9
+ * Writes: one `appendFile` per prompt of one short line (a prompt over 64 KiB stays in memory only: long interleaved appends
10
+ * are how a jsonl file breaks, 2026-09-04), and a rewrite to the last 1,000 through a temporary file and a rename once the
11
+ * file passes 1,200 lines. A line that does not parse is skipped, never fatal.
12
+ */
13
+ import { randomUUID } from 'node:crypto';
14
+ import { appendFile, chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
15
+ import { dirname, join } from 'node:path';
16
+ import { loadEffectiveConfig } from "../config/load.js";
17
+ import { createRedactor } from "../diagnostics/redact.js";
18
+ import { projectId } from "../paths.js";
19
+ export const PROMPT_HISTORY_LIMIT = 1000;
20
+ const PRUNE_AT = 1200;
21
+ const MAX_RECORD_CHARS = 64 * 1024;
22
+ export function promptHistoryFile(paths, projectRoot) { return join(paths.dataRoot, 'history', `${projectId(projectRoot)}.jsonl`); }
23
+ /** The draft with its secrets masked; each paste block is masked on its own and its offsets follow the new text. */
24
+ export function maskDraft(draft, redactor) {
25
+ const pastes = [...draft.collapsedPastes].sort((a, b) => a.start - b.start);
26
+ let text = '', cursor = 0;
27
+ const kept = [];
28
+ for (const paste of pastes) {
29
+ if (paste.start < cursor || paste.end > draft.text.length || paste.end <= paste.start)
30
+ continue;
31
+ text += redactor.text(draft.text.slice(cursor, paste.start));
32
+ const inner = redactor.text(draft.text.slice(paste.start, paste.end)), start = text.length;
33
+ text += inner;
34
+ kept.push({ start, end: text.length, chars: inner.length, lines: paste.lines });
35
+ cursor = paste.end;
36
+ }
37
+ text += redactor.text(draft.text.slice(cursor));
38
+ return { text, collapsedPastes: kept };
39
+ }
40
+ function parseLine(line) {
41
+ try {
42
+ const row = JSON.parse(line);
43
+ if (row?.v !== 1 || typeof row.text !== 'string')
44
+ return null;
45
+ const pastes = Array.isArray(row.collapsedPastes) ? row.collapsedPastes.filter((p) => [p?.start, p?.end, p?.chars, p?.lines].every(Number.isSafeInteger) && p.start >= 0 && p.end <= row.text.length && p.start < p.end) : [];
46
+ return { text: row.text, collapsedPastes: pastes.map((p) => ({ start: p.start, end: p.end, chars: p.chars, lines: p.lines })) };
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ export function createPromptHistoryStore({ paths, projectRoot, secrets }) {
53
+ const file = promptHistoryFile(paths, projectRoot);
54
+ const redactor = createRedactor(secrets);
55
+ let lines = null;
56
+ const readRows = async () => { try {
57
+ return (await readFile(file, 'utf8')).split('\n').filter(Boolean);
58
+ }
59
+ catch (error) {
60
+ if (error?.code === 'ENOENT')
61
+ return [];
62
+ throw error;
63
+ } };
64
+ const newestFirst = (raw) => {
65
+ const seen = new Set(), rows = [];
66
+ for (let index = raw.length - 1; index >= 0 && rows.length < PROMPT_HISTORY_LIMIT; index -= 1) {
67
+ const row = parseLine(raw[index]);
68
+ if (!row || seen.has(row.text))
69
+ continue;
70
+ seen.add(row.text);
71
+ rows.push(row);
72
+ }
73
+ return rows;
74
+ };
75
+ return {
76
+ file,
77
+ async load() { const raw = await readRows(); lines = raw.length; return newestFirst(raw); },
78
+ async append(draft) {
79
+ if (!draft.text.trim() || draft.text.length > MAX_RECORD_CHARS)
80
+ return;
81
+ const masked = maskDraft(draft, redactor);
82
+ await mkdir(dirname(file), { recursive: true, mode: 0o700 });
83
+ await appendFile(file, `${JSON.stringify({ v: 1, text: masked.text, collapsedPastes: masked.collapsedPastes, at: new Date().toISOString() })}\n`, { encoding: 'utf8', mode: 0o600 });
84
+ if (lines === null)
85
+ lines = (await readRows()).length;
86
+ else
87
+ lines += 1;
88
+ if (lines > PRUNE_AT) {
89
+ const kept = newestFirst(await readRows()).reverse().map(row => JSON.stringify({ v: 1, text: row.text, collapsedPastes: row.collapsedPastes, at: new Date().toISOString() }));
90
+ const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
91
+ try {
92
+ await writeFile(temp, kept.map(line => line + '\n').join(''), { encoding: 'utf8', mode: 0o600 });
93
+ await rename(temp, file);
94
+ }
95
+ catch (error) {
96
+ await rm(temp, { force: true }).catch(() => { });
97
+ throw error;
98
+ }
99
+ try {
100
+ await chmod(file, 0o600);
101
+ }
102
+ catch { /* Windows */ }
103
+ lines = kept.length;
104
+ }
105
+ },
106
+ };
107
+ }
108
+ /** On unless `ui.history.persist` is false; a configuration that cannot be read keeps it off (nothing written by doubt). */
109
+ export async function promptHistoryEnabled({ paths, projectRoot }) {
110
+ try {
111
+ return (await loadEffectiveConfig({ paths, projectRoot })).value.ui?.history?.persist !== false;
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
@@ -0,0 +1,110 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, open, readFile, rename, rm, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { validateCliAttachments } from "../runtime/attachments.js";
5
+ export const QUEUE_SCHEMA = 'talos.cli.queue.v1';
6
+ export const QUEUE_MAX_ENTRIES = 64;
7
+ export const QUEUE_TEXT_MAX_BYTES = 1024 * 1024;
8
+ export const QUEUE_STORE_MAX_BYTES = 64 * 1024 * 1024;
9
+ export class QueueStoreError extends Error {
10
+ code;
11
+ constructor(code, message = code) { super(message); this.name = 'QueueStoreError'; this.code = code; }
12
+ }
13
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
14
+ function session(value) { if (typeof value !== 'string' || !value.trim() || value.length > 512 || /[\0\r\n]/u.test(value))
15
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Session id is invalid.'); return value; }
16
+ function cloneEntry(entry) { return { ...entry, ...(entry.attachments ? { attachments: entry.attachments.map(row => ({ ...row })) } : {}) }; }
17
+ function validateEntry(value) {
18
+ if (!value || typeof value !== 'object')
19
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue entry is invalid.');
20
+ const row = value;
21
+ if (typeof row.id !== 'string' || !UUID.test(row.id) || !(row.kind === 'prompt' || row.kind === 'command') || !(row.status === 'pending' || row.status === 'dispatching') || typeof row.text !== 'string')
22
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue entry shape is invalid.');
23
+ if (!row.text.trim() || Buffer.byteLength(row.text, 'utf8') > QUEUE_TEXT_MAX_BYTES)
24
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue entry text is invalid.');
25
+ if (row.kind === 'command' && row.attachments !== undefined)
26
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Commands cannot carry attachments.');
27
+ let attachments;
28
+ if (row.attachments !== undefined) {
29
+ try {
30
+ attachments = validateCliAttachments(row.attachments);
31
+ }
32
+ catch {
33
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue attachment snapshot is invalid.');
34
+ }
35
+ }
36
+ return { id: row.id, kind: row.kind, text: row.text, status: row.status, ...(attachments?.length ? { attachments } : {}) };
37
+ }
38
+ function validated(entries) {
39
+ if (!Array.isArray(entries) || entries.length > QUEUE_MAX_ENTRIES)
40
+ throw new QueueStoreError('QUEUE_STORE_LIMIT', `A session queue can retain at most ${QUEUE_MAX_ENTRIES} entries.`);
41
+ return entries.map(validateEntry);
42
+ }
43
+ export function createQueueStore({ rootDir }) {
44
+ if (typeof rootDir !== 'string' || !rootDir.trim())
45
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue root is required.');
46
+ const fileForSession = (sessionId) => path.join(rootDir, createHash('sha256').update(session(sessionId)).digest('hex') + '.json');
47
+ async function syncDirectory() { try {
48
+ const handle = await open(rootDir, 'r');
49
+ try {
50
+ await handle.sync();
51
+ }
52
+ finally {
53
+ await handle.close();
54
+ }
55
+ }
56
+ catch { /* Not available on every platform. */ } }
57
+ async function save(sessionId, entries) {
58
+ const id = session(sessionId), rows = validated(entries).map(cloneEntry);
59
+ const body = JSON.stringify({ schema: QUEUE_SCHEMA, sessionId: id, entries: rows }) + '\n';
60
+ if (Buffer.byteLength(body, 'utf8') > QUEUE_STORE_MAX_BYTES)
61
+ throw new QueueStoreError('QUEUE_STORE_LIMIT', 'Queue store exceeds its byte budget.');
62
+ await mkdir(rootDir, { recursive: true, mode: 0o700 });
63
+ const file = fileForSession(id), temp = path.join(rootDir, '.' + path.basename(file) + '.' + process.pid + '.' + randomUUID() + '.tmp');
64
+ let handle = null;
65
+ try {
66
+ handle = await open(temp, 'wx', 0o600);
67
+ await handle.writeFile(body, 'utf8');
68
+ await handle.sync();
69
+ await handle.close();
70
+ handle = null;
71
+ await rename(temp, file);
72
+ await syncDirectory();
73
+ }
74
+ catch (error) {
75
+ try {
76
+ await handle?.close();
77
+ }
78
+ catch { }
79
+ await rm(temp, { force: true }).catch(() => { });
80
+ if (error instanceof QueueStoreError)
81
+ throw error;
82
+ throw Object.assign(new QueueStoreError('QUEUE_STORE_WRITE_FAILED', 'Queue state could not be written atomically.'), { cause: error });
83
+ }
84
+ }
85
+ async function load(sessionId) {
86
+ const id = session(sessionId), file = fileForSession(id);
87
+ let info;
88
+ try {
89
+ info = await stat(file);
90
+ }
91
+ catch (error) {
92
+ if (error.code === 'ENOENT')
93
+ return [];
94
+ throw Object.assign(new QueueStoreError('QUEUE_STORE_READ_FAILED'), { cause: error });
95
+ }
96
+ if (!info.isFile() || info.size > QUEUE_STORE_MAX_BYTES)
97
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue store file is invalid.');
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(await readFile(file, 'utf8'));
101
+ }
102
+ catch {
103
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue store is not valid JSON.');
104
+ }
105
+ if (parsed?.schema !== QUEUE_SCHEMA || parsed?.sessionId !== id || !Array.isArray(parsed?.entries))
106
+ throw new QueueStoreError('QUEUE_STORE_INVALID', 'Queue store schema/session mismatch.');
107
+ return validated(parsed.entries).map(cloneEntry);
108
+ }
109
+ return { fileForSession, save, load };
110
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * R1 lane L — ONE height budget per frame (gate E1, E3, E13; owner decisions Q-R1-3, Q-R1-8).
3
+ *
4
+ * Pure: it receives the frame height and the exact row counts of the content (composer lines, chrome lines, the rows a
5
+ * panel asks for) and returns the height of every region. The regions are then rendered as boxes of EXACTLY these
6
+ * heights with their overflow hidden, so the sum of the rendered rows equals `frameRows` by construction and a region
7
+ * whose content is taller is clipped instead of overwriting its neighbours (review D-02). No row count here is an
8
+ * estimate: every input is counted by the same width function Ink lays text out with (text-width.ts, E4).
9
+ */
10
+ import { t } from "../../i18n/index.js";
11
+ export const MIN_COLUMNS = 40;
12
+ export const MIN_ROWS = 12;
13
+ /* R2 (Q-R2-10): the notice is on acceptance frames (s12), so its words are the dictionary's; the bytes did not change. */
14
+ export const TOO_SMALL_TEXT = t('screen.layout.tooSmall', { columns: MIN_COLUMNS, rows: MIN_ROWS });
15
+ /* A panel whose budget is below this many rows takes the whole frame instead of a sliver (Q-R1-3). */
16
+ export const PANEL_FULL_SCREEN_BELOW = 6;
17
+ export const COMPOSER_MAX_ROWS = 10;
18
+ const whole = (value) => { const n = Number(value); return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0; };
19
+ const request = (value) => value === null || value === undefined ? 0 : value === Infinity ? Infinity : whole(value);
20
+ export function composerRowsCap(rows) { return Math.max(3, Math.min(COMPOSER_MAX_ROWS, Math.floor(whole(rows) / 3))); }
21
+ export function budget(input) {
22
+ const rows = whole(input.rows), columns = whole(input.columns), frameRows = Math.max(1, Math.min(rows, whole(input.frameRows)));
23
+ const zero = { header: 0, transcript: 0, diff: 0, overlay: 0, aux: 0, menu: 0, status: 0, composer: 0, composerContent: 0, composerHints: 0, shortcut: 0, roster: 0, extras: 0, footer: 0, overlayMaxRows: 0 };
24
+ if (columns < MIN_COLUMNS || rows < MIN_ROWS)
25
+ return { tooSmall: true, frameRows, fullScreenPanel: false, ...zero };
26
+ /* Composer: border + text + hints, capped at min(10, ⌊rows/3⌋) (E3). Hints go first when the cap bites; the text
27
+ keeps at least one row, and a longer text scrolls inside the composer to the cursor. */
28
+ const cap = composerRowsCap(rows);
29
+ const composerHints = Math.min(whole(input.composerHints), Math.max(0, cap - 3));
30
+ const composerContent = Math.max(1, Math.min(Math.max(1, whole(input.composerLines)), cap - 2 - composerHints));
31
+ const composer = 2 + composerContent + composerHints;
32
+ const status = input.status === undefined ? 1 : Math.min(1, whole(input.status));
33
+ const overlayMaxRows = Math.max(0, frameRows - composer - status);
34
+ const wants = { overlay: request(input.overlay), menu: request(input.menu), diff: request(input.diff), aux: request(input.aux) };
35
+ const anyPanel = wants.overlay > 0 || wants.menu > 0 || wants.diff > 0 || wants.aux > 0;
36
+ if (anyPanel && overlayMaxRows < PANEL_FULL_SCREEN_BELOW) {
37
+ /* Q-R1-3: a panel squeezed below six rows is unusable; it takes the frame. Unreachable at ≥ 40×12 with the
38
+ composer cap above (frame ≥ 11, composer ≤ 4, status 1 → ≥ 6), kept for a frame policy that changes. */
39
+ const first = wants.overlay > 0 ? 'overlay' : wants.menu > 0 ? 'menu' : wants.diff > 0 ? 'diff' : 'aux';
40
+ return { tooSmall: false, frameRows, fullScreenPanel: true, ...zero, [first]: frameRows, overlayMaxRows: frameRows };
41
+ }
42
+ let room = overlayMaxRows;
43
+ const take = (want) => { const got = Math.min(want, room); room -= got; return got; };
44
+ const overlay = take(wants.overlay), menu = take(wants.menu), diff = take(wants.diff), aux = take(wants.aux);
45
+ /* Chrome after the panels, kept in this order while it fits, so it is DROPPED in the reverse order the owner set:
46
+ roster (and its queue/reasoning lines) first, then the shortcut strip, then the header, the footer last (E13).
47
+ Without a panel the transcript keeps at least one row. */
48
+ const transcriptMin = overlay + menu + diff + aux > 0 ? 0 : 1;
49
+ let spare = room - transcriptMin;
50
+ const keep = (want) => { const n = whole(want); if (n <= spare) {
51
+ spare -= n;
52
+ return n;
53
+ } return 0; };
54
+ const footer = keep(input.footer), header = keep(input.header), shortcut = keep(input.shortcut), roster = keep(input.roster), extras = roster ? keep(input.extras) : 0;
55
+ const transcript = spare + transcriptMin;
56
+ return { tooSmall: false, frameRows, fullScreenPanel: false, header, transcript, diff, overlay, aux, menu, status, composer, composerContent, composerHints, shortcut, roster, extras, footer, overlayMaxRows };
57
+ }
58
+ /* The sum of every region; equals `frameRows` for every budget that is not "too small" (asserted by the grid test). */
59
+ export function budgetRows(b) { return b.header + b.transcript + b.diff + b.overlay + b.aux + b.menu + b.status + b.composer + b.shortcut + b.roster + b.extras + b.footer; }
@@ -0,0 +1,96 @@
1
+ /*
2
+ * R1 lane L, gate E11 step 3: the screen's regions as small Ink views, split out of `app.ts` behind `budget()`.
3
+ *
4
+ * Every region is a box of EXACTLY the height `budget()` gave it, `flexShrink:0`, overflow hidden (E1/E2). The frame
5
+ * is therefore the sum of its regions whatever their content does: a region whose content is taller is clipped inside
6
+ * its own rows instead of pushing the composer off the screen or overwriting the footer (review D-02/D-03). Single
7
+ * lines of chrome are pre-truncated to the width with the width function Ink uses and rendered with
8
+ * `wrap:'truncate-end'`, so a line can never become two rows (E4).
9
+ */
10
+ import { t } from "../../i18n/index.js";
11
+ import { composerLayout } from "../components/composer.js";
12
+ import { composerDisplay } from "../components/composer.js";
13
+ import { truncateDisplay } from "../text-width.js";
14
+ import { TOO_SMALL_TEXT } from "./budget.js";
15
+ export function createRegionViews(React, Ink) {
16
+ const h = React.createElement;
17
+ /* A region: fixed height, never shrinks, never overflows. `bottom` anchors the content to the last row (transcript). */
18
+ function Region({ rows, bottom = false, children, ...rest }) {
19
+ if (rows <= 0)
20
+ return null;
21
+ /* The inner box keeps the content at its natural height: a bordered panel squeezed by Yoga would draw its text
22
+ over its own border (the D-02 trace); taller content is clipped by the region instead. */
23
+ return h(Ink.Box, { flexDirection: 'column', height: rows, flexShrink: 0, overflowY: 'hidden', ...(bottom ? { justifyContent: 'flex-end' } : {}), ...rest }, h(Ink.Box, { flexDirection: 'column', flexShrink: 0 }, children));
24
+ }
25
+ /* One row of chrome, whatever the text. */
26
+ function Line({ text, columns, ...style }) {
27
+ return h(Ink.Text, { wrap: 'truncate-end', ...style }, truncateDisplay(String(text ?? ''), Math.max(1, columns)) || ' ');
28
+ }
29
+ /* Q-R1-8: below 40×12 the frame is the notice and nothing else: one line whenever it fits. Narrower than the notice
30
+ itself (31 columns) it wraps at a word instead of being cut, so "need 40×12" — the one thing the line is for — is
31
+ never lost; a person can still read it at 20 columns. */
32
+ function TooSmall({ columns }) {
33
+ return h(Ink.Box, { flexDirection: 'column', width: Math.max(1, columns) }, h(Ink.Text, { wrap: 'wrap' }, TOO_SMALL_TEXT));
34
+ }
35
+ /*
36
+ * The transcript region (E9, Q-R1-2/9): the dim announcement row (latest prompt above the view, messages above it),
37
+ * then the items, bottom-anchored. `clipBelow` shifts the items down by the rows scrolled up inside the last one;
38
+ * the rows that fall below the region are clipped by the region itself.
39
+ */
40
+ function TranscriptRegion({ rows, announcement, clipBelow, color, children }) {
41
+ if (rows <= 0)
42
+ return null;
43
+ const itemRows = announcement ? rows - 1 : rows;
44
+ return h(Region, { rows }, announcement ? h(Line, { key: 'announce', text: announcement, columns: Number.MAX_SAFE_INTEGER, dimColor: true, color }) : null, itemRows > 0 ? h(Region, { key: 'items', rows: itemRows, bottom: true }, h(Ink.Box, { flexDirection: 'column', flexShrink: 0, marginBottom: -Math.max(0, clipBelow) }, children)) : null);
45
+ }
46
+ /*
47
+ * The composer (E3): a bordered box whose text rows are laid out by `composerLayout` (exact row count, scrolled to
48
+ * the cursor) and capped at `contentRows`. It subscribes to the composer store itself, so a keystroke re-renders the
49
+ * composer and not the whole app (the fast input path of B1); the app re-renders only when the ROW COUNT changes.
50
+ */
51
+ const ComposerRegion = React.memo(function ComposerRegion({ store, vimHint, contentRows, hintRows, columns }) {
52
+ const editor = React.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
53
+ const contentWidth = composerContentWidth(columns);
54
+ const layout = composerLayout(composerDisplay(editor), { contentWidth, maxRows: contentRows });
55
+ const hints = [vimHint, editor.text.includes('\n') ? t('screen.composer.multilineHint') : ''].filter(Boolean).slice(0, hintRows);
56
+ return h(Ink.Box, { borderStyle: 'single', flexDirection: 'column', flexShrink: 0, height: 2 + contentRows + hints.length, overflowY: 'hidden' }, ...layout.rows.map((row, index) => {
57
+ /* The marker `>` leads the first visible row; an arrow beside it says the text continues above or below. */
58
+ const marker = index === 0 ? (layout.hiddenAbove ? '>↑' : '> ') : index === layout.rows.length - 1 && layout.hiddenBelow ? ' ↓' : ' ';
59
+ return h(Ink.Text, { key: `composer-row-${layout.start + index}`, wrap: 'truncate-end' }, marker, ...row.map((piece, pieceIndex) => h(Ink.Text, { key: pieceIndex, inverse: piece.kind === 'cursor' || piece.selected, dimColor: piece.kind === 'paste' }, piece.text)));
60
+ }), ...hints.map((hint, index) => h(Ink.Text, { key: `composer-hint-${index}`, dimColor: true, wrap: 'truncate-end' }, truncateDisplay(hint, Math.max(1, columns - 2)))));
61
+ }, (previous, next) => previous.store === next.store && previous.vimHint === next.vimHint && previous.contentRows === next.contentRows && previous.hintRows === next.hintRows && previous.columns === next.columns);
62
+ /* The header row (dropped third when rows run out, E13). */
63
+ function Header({ rows, text, color }) {
64
+ return h(Region, { rows }, h(Ink.Text, { bold: true, color, wrap: 'truncate-end' }, text));
65
+ }
66
+ /* The overlay host wiring (Q-R1-3): diff, overlay, completions and command menu, each in the rows the budget gave it,
67
+ in the order they stack above the status row. An overlay taller than its rows is clipped by its region, never drawn
68
+ over the composer; lane O fits every overlay to `maxRows` so that clip does not happen in practice. */
69
+ function Panels({ budget, diff, overlay, aux, menu }) {
70
+ return h(React.Fragment, null, h(Region, { key: 'diff', rows: budget.diff }, diff), h(Region, { key: 'overlay', rows: budget.overlay }, overlay), h(Region, { key: 'aux', rows: budget.aux }, ...aux), h(Region, { key: 'menu', rows: budget.menu }, menu));
71
+ }
72
+ /* The rows under the composer, each one row and only if the budget kept it: dropped roster first, then the shortcut
73
+ strip; the footer stays longest (E13). */
74
+ function StatusBar({ budget, shortcutLine, rosterLines, queueLine, reasoningLine, footerLine, color, statusSegments, toneColors }) {
75
+ const line = (key, text) => h(Ink.Text, { key, dimColor: true, color, wrap: 'truncate-end' }, text || ' ');
76
+ return h(React.Fragment, null, budget.shortcut ? line('shortcut', shortcutLine) : null, ...(budget.roster ? rosterLines.slice(0, budget.roster) : []), budget.extras && queueLine ? line('queue', queueLine) : null, budget.extras && reasoningLine ? line('reasoning', reasoningLine) : null, budget.footer ? (statusSegments?.length ? h(StatusRow, { key: 'footer', segments: statusSegments, color, toneColors }) : line('footer', footerLine)) : null);
77
+ }
78
+ /* R5a S1 (Q-R5-2): the status row, one Text; the context percentage takes Hermes's colours at 50 / 80 / 95 (bold at 95)
79
+ and every other segment stays dim like the chrome. The segments already fit the width (status-bar.ts). */
80
+ function StatusRow({ segments, color, toneColors }) {
81
+ return h(Ink.Text, { wrap: 'truncate-end' }, ...segments.flatMap((segment, index) => {
82
+ const toned = segment.tone !== 'normal';
83
+ const piece = h(Ink.Text, { key: segment.id, dimColor: !toned, color: toned ? (toneColors?.[segment.tone] ?? color) : color, bold: segment.tone === 'critical' }, segment.text);
84
+ return index ? [h(Ink.Text, { key: `sep-${segment.id}`, dimColor: true, color }, ' · '), piece] : [piece];
85
+ }));
86
+ }
87
+ return { Region, Line, TooSmall, TranscriptRegion, ComposerRegion, Header, Panels, StatusBar };
88
+ }
89
+ /* Text columns inside the composer: the two border cells and the two-cell marker (`> `) are not text. */
90
+ export function composerContentWidth(columns) { return Math.max(1, Math.floor(columns) - 4); }
91
+ /* What the budget needs from the composer: its text rows at this width and its hint rows. The app subscribes to this
92
+ PRIMITIVE through the store, so it re-renders when the composer grows or shrinks by a row, not on every key. */
93
+ export function composerMeasure(editor, columns, vimHint) {
94
+ const rows = composerLayout(composerDisplay(editor), { contentWidth: composerContentWidth(columns), maxRows: Number.MAX_SAFE_INTEGER }).total;
95
+ return { lines: rows, hints: (vimHint ? 1 : 0) + (editor.text.includes('\n') ? 1 : 0) };
96
+ }
@@ -0,0 +1,148 @@
1
+ import { createTuiMetricsProfiler } from "./metrics.js";
2
+ import { COALESCE_WINDOW_MS, MOTION_FRAME_MS } from "./render-scheduler.js";
3
+ function defaultTimers() {
4
+ return {
5
+ clock: () => performance.now(),
6
+ setTimer: (fn, delayMs) => setTimeout(fn, delayMs),
7
+ clearTimer: handle => clearTimeout(handle),
8
+ };
9
+ }
10
+ export function createRenderCoordinator(options = {}) {
11
+ const defaults = defaultTimers();
12
+ const clock = options.clock ?? defaults.clock;
13
+ const setTimer = options.setTimer ?? defaults.setTimer;
14
+ const clearTimer = options.clearTimer ?? defaults.clearTimer;
15
+ const windowMs = Math.max(1, Math.min(COALESCE_WINDOW_MS, options.windowMs ?? COALESCE_WINDOW_MS));
16
+ const motionPeriodMs = Math.max(1, options.motionPeriodMs ?? MOTION_FRAME_MS);
17
+ const profiler = options.profiler ?? createTuiMetricsProfiler({ clock });
18
+ const listeners = new Set();
19
+ const awaitingVisible = new Map();
20
+ const pending = [];
21
+ let pendingSince = null;
22
+ let flushTimer = null;
23
+ let epoch = 0;
24
+ const motionTimers = new Set();
25
+ let disposed = false;
26
+ function cancelFlushTimer() {
27
+ if (flushTimer !== null) {
28
+ clearTimer(flushTimer);
29
+ flushTimer = null;
30
+ }
31
+ }
32
+ function scheduleFlush() {
33
+ if (disposed || flushTimer !== null)
34
+ return;
35
+ flushTimer = setTimer(() => { flushTimer = null; flushPending(); }, windowMs);
36
+ }
37
+ function flushPending(override) {
38
+ if (disposed)
39
+ throw new Error('RENDER_COORDINATOR_DISPOSED');
40
+ cancelFlushTimer();
41
+ const started = pendingSince ?? clock();
42
+ const rows = pending.splice(0, pending.length);
43
+ pendingSince = null;
44
+ for (const row of rows)
45
+ row.apply();
46
+ epoch += 1;
47
+ const batch = {
48
+ epoch,
49
+ reason: override ?? strongestReasonFrom(rows),
50
+ eventIds: rows.flatMap(row => row.eventId ? [row.eventId] : []),
51
+ coalescedMs: Math.max(0, clock() - started),
52
+ };
53
+ profiler.renderCoalesced(batch.coalescedMs);
54
+ awaitingVisible.set(epoch, batch);
55
+ for (const listener of listeners)
56
+ listener(batch);
57
+ return batch;
58
+ }
59
+ function strongestReasonFrom(rows) {
60
+ if (rows.some(row => row.reason === 'event'))
61
+ return 'event';
62
+ if (rows.some(row => row.reason === 'resize'))
63
+ return 'resize';
64
+ return 'motion';
65
+ }
66
+ function queue(row) {
67
+ if (disposed)
68
+ throw new Error('RENDER_COORDINATOR_DISPOSED');
69
+ if (pending.length === 0)
70
+ pendingSince = clock();
71
+ pending.push(row);
72
+ scheduleFlush();
73
+ }
74
+ const enqueueMotion = (apply) => queue({ reason: 'motion', apply });
75
+ return {
76
+ subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
77
+ enqueueEvent(eventId, apply) { profiler.eventReceived(eventId); queue({ reason: 'event', apply, eventId }); },
78
+ enqueueMotion,
79
+ immediate(reason, apply) {
80
+ if (disposed)
81
+ throw new Error('RENDER_COORDINATOR_DISPOSED');
82
+ if (pending.length === 0)
83
+ pendingSince = clock();
84
+ pending.push({ reason: reason === 'resize' ? 'resize' : 'event', apply });
85
+ return flushPending(reason);
86
+ },
87
+ startMotion(apply) {
88
+ if (disposed)
89
+ throw new Error('RENDER_COORDINATOR_DISPOSED');
90
+ let active = true;
91
+ let handle = null;
92
+ const owner = {};
93
+ const tick = () => {
94
+ if (!active || disposed)
95
+ return;
96
+ motionTimers.delete(handle);
97
+ handle = null;
98
+ queue({ reason: 'motion', apply, motion: owner });
99
+ handle = setTimer(tick, motionPeriodMs);
100
+ motionTimers.add(handle);
101
+ };
102
+ handle = setTimer(tick, motionPeriodMs);
103
+ motionTimers.add(handle);
104
+ return () => {
105
+ active = false;
106
+ if (handle !== null) {
107
+ clearTimer(handle);
108
+ motionTimers.delete(handle);
109
+ handle = null;
110
+ }
111
+ /* E5: a spinner frame queued just before the stop would commit after the session went idle, and on Ink 7.1.1 +
112
+ win32 every commit of a full-height frame is a full clear and repaint. Drop it; queued events still flush. */
113
+ for (let i = pending.length - 1; i >= 0; i--)
114
+ if (pending[i].motion === owner)
115
+ pending.splice(i, 1);
116
+ if (pending.length === 0) {
117
+ cancelFlushTimer();
118
+ pendingSince = null;
119
+ }
120
+ };
121
+ },
122
+ markVisible(visibleEpoch) {
123
+ const visible = [...awaitingVisible.entries()]
124
+ .filter(([candidate]) => candidate <= visibleEpoch)
125
+ .sort((a, b) => a[0] - b[0]);
126
+ if (visible.length === 0)
127
+ return;
128
+ for (const [candidate] of visible)
129
+ awaitingVisible.delete(candidate);
130
+ const batches = visible.map(([, batch]) => batch);
131
+ const latest = batches.at(-1);
132
+ profiler.renderCommitted({ eventIds: batches.flatMap(batch => batch.eventIds), reason: latest.reason });
133
+ },
134
+ dispose() {
135
+ if (disposed)
136
+ return;
137
+ disposed = true;
138
+ cancelFlushTimer();
139
+ for (const timer of motionTimers)
140
+ clearTimer(timer);
141
+ motionTimers.clear();
142
+ pending.length = 0;
143
+ pendingSince = null;
144
+ listeners.clear();
145
+ awaitingVisible.clear();
146
+ },
147
+ };
148
+ }
@@ -0,0 +1,4 @@
1
+ export const COALESCE_WINDOW_MS = 16;
2
+ export const MOTION_FRAME_MS = 120;
3
+ export function frameIndex(elapsedMs, periodMs, frameCount) { return frameCount <= 1 ? 0 : Math.floor(Math.max(0, elapsedMs) / Math.max(1, periodMs)) % frameCount; }
4
+ export function stableElapsedLabel(elapsedMs) { return elapsedMs < 1000 ? ' ' : `${(elapsedMs / 1000).toFixed(1).padStart(4, ' ')}s`; }
@@ -0,0 +1,69 @@
1
+ import { createTuiMetricsCollector, createTuiMetricsProfiler, createTuiRenderMetricsTap, summarizeTuiMetrics } from "./metrics.js";
2
+ import { createRenderCoordinator } from "./render-coordinator.js";
3
+ import { detectTerminalCapabilities } from "./terminal-capabilities.js";
4
+ import { createTerminalSessionPlan } from "./terminal-session.js";
5
+ import { DEFAULT_THEME_ACCENT } from "./theme-catalog.js";
6
+ import { createThemeStore } from "./theme-store.js";
7
+ import { createTerminalSizeStore, terminalFrameProps } from "./components/terminal-shell.js";
8
+ import { developmentLogError } from "../diagnostics/development-log.js";
9
+ import { formatExitOutput } from "./exit-output.js";
10
+ async function loadTuiModules() { return { React: await import('react'), Ink: await import('ink'), createTuiAppComponent: (await import("./app.js")).createTuiAppComponent }; }
11
+ export async function runInkTui({ exitOutput = 'transcript', ...props }, io = {}) {
12
+ let React, Ink, createTuiAppComponent;
13
+ try {
14
+ ({ React, Ink, createTuiAppComponent } = await (io.load ?? loadTuiModules)());
15
+ }
16
+ catch {
17
+ throw new Error('TUI_DEPENDENCY_UNAVAILABLE');
18
+ }
19
+ const stdout = io.stdout ?? process.stdout, env = io.env ?? process.env;
20
+ const capabilities = detectTerminalCapabilities({ stdinIsTTY: Boolean((io.stdin ?? process.stdin).isTTY), stdoutIsTTY: Boolean(stdout.isTTY), color: props.invocation.color !== false, env });
21
+ /* R5b D4 (Q-R5-14): the theme is live — one store, read here for the painted background and by the app for its colours,
22
+ so /theme can preview and change it without a restart. mono still means no colour and no motion (theme-store.ts).
23
+ The session is planned on the terminal's own capabilities, so leaving mono for a colour theme paints the background. */
24
+ const themeStore = createThemeStore(capabilities, props.uiTheme ?? DEFAULT_THEME_ACCENT);
25
+ const effectiveCapabilities = themeStore.getSnapshot().capabilities;
26
+ const session = createTerminalSessionPlan({ capabilities, env });
27
+ const collector = createTuiMetricsCollector();
28
+ const coordinator = createRenderCoordinator({ profiler: createTuiMetricsProfiler({ sink: collector.sink }) });
29
+ const TuiApp = createTuiAppComponent(React, Ink, capabilities, undefined, themeStore);
30
+ const terminalSize = createTerminalSizeStore(stdout, { rows: 30, columns: 100 }, error => developmentLogError('tui.resize.failure', error, {}, 'tui-run'));
31
+ /* R1 lane contract: the app reports its last transcript once, as it exits (TuiAppProps.onExit, lane L). */
32
+ let exitSnapshot = null;
33
+ const onExit = (snapshot) => { exitSnapshot = snapshot; };
34
+ const Root = () => {
35
+ const size = React.useSyncExternalStore(terminalSize.subscribe, terminalSize.getSnapshot, terminalSize.getSnapshot);
36
+ const background = React.useSyncExternalStore(themeStore.subscribe, themeStore.getSnapshot, themeStore.getSnapshot).theme.colors.background;
37
+ const frameProps = terminalFrameProps({ session, ...(background ? { background } : {}), rows: size.rows, columns: size.columns });
38
+ return React.createElement(Ink.Box, frameProps, React.createElement(TuiApp, { ...props, renderCoordinator: coordinator, terminalSize: size, onExit }));
39
+ };
40
+ /* E6 (lane I): with TALOS_TUI_RENDER_METRICS=1 count bytes, DEC 2026 frames, clears and Ink renders in-process. */
41
+ const metricsTap = env.TALOS_TUI_RENDER_METRICS === '1' ? createTuiRenderMetricsTap({ stdout, sink: collector.sink }) : null;
42
+ const rendered = Ink.render(React.createElement(Root), {
43
+ ...(metricsTap ? metricsTap.inkOptions : {}),
44
+ stdout,
45
+ exitOnCtrlC: false,
46
+ interactive: session.mode === 'plain' ? false : effectiveCapabilities.interactive,
47
+ alternateScreen: session.alternateScreen,
48
+ });
49
+ try {
50
+ await rendered.waitUntilExit();
51
+ metricsTap?.dispose(); /* the exit output below is not TUI rendering */
52
+ /* E10 / Q-R1-4: Ink resolves waitUntilExit only after it has written exitAlternativeScreen (ink.js 7.1.1:548-552,
53
+ 572-583), so this lands on the primary screen and survives in scrollback. Plain mode never had an alternate
54
+ screen and already left everything append-only (E14). */
55
+ if (session.alternateScreen) {
56
+ const text = formatExitOutput(exitSnapshot, { mode: exitOutput, color: themeStore.getSnapshot().capabilities.color });
57
+ if (text)
58
+ stdout.write(text);
59
+ }
60
+ }
61
+ finally {
62
+ terminalSize.dispose();
63
+ coordinator.dispose();
64
+ metricsTap?.dispose();
65
+ if (env.TALOS_TUI_RENDER_METRICS === '1') {
66
+ process.stderr.write(`${JSON.stringify({ schema: 'talos.cli.tui-render-metrics.v1', ...summarizeTuiMetrics(collector.snapshot()) })}\n`);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,29 @@
1
+ function score(text, query) {
2
+ const t = text.toLowerCase(), q = query.toLowerCase();
3
+ if (!q)
4
+ return 0;
5
+ if (t.startsWith(q))
6
+ return 3000 - q.length;
7
+ if (t.includes(q))
8
+ return 2000 - t.indexOf(q);
9
+ let at = 0, gaps = 0;
10
+ for (const ch of q) {
11
+ const next = t.indexOf(ch, at);
12
+ if (next < 0)
13
+ return -1;
14
+ gaps += next - at;
15
+ at = next + 1;
16
+ }
17
+ return 1000 - gaps;
18
+ }
19
+ export function filterItems(items, query) {
20
+ return items.map(item => ({ item, score: score(item.searchText, query) })).filter(x => x.score >= 0)
21
+ .sort((a, b) => b.score - a.score || (a.item.group ?? '').localeCompare(b.item.group ?? '') || a.item.label.localeCompare(b.item.label) || a.item.id.localeCompare(b.item.id))
22
+ .map(x => x.item);
23
+ }
24
+ export function moveSelection(state, count, action) {
25
+ const max = Math.max(0, count - 1);
26
+ const delta = action === 'up' ? -1 : action === 'down' ? 1 : action === 'page-up' ? -state.pageSize : action === 'page-down' ? state.pageSize : 0;
27
+ const selected = action === 'home' ? 0 : action === 'end' ? max : Math.max(0, Math.min(max, state.selected + delta));
28
+ return { ...state, selected };
29
+ }