@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.1

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 (718) hide show
  1. package/README.md +398 -45
  2. package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
  3. package/THIRD_PARTY_NOTICES.md +273 -12
  4. package/bin/zmux-darwin-arm64 +0 -0
  5. package/bin/ztc-audio-darwin-arm64 +0 -0
  6. package/bin/ztc-capture-darwin-arm64 +0 -0
  7. package/bin/ztc-computer-darwin-arm64 +0 -0
  8. package/bin/ztc-llm-darwin-arm64 +0 -0
  9. package/bin/ztc-tts-darwin-arm64 +0 -0
  10. package/bin/ztc-tui-darwin-arm64 +0 -0
  11. package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
  12. package/bin/zterm-darwin-arm64 +0 -0
  13. package/dist/App.js +1206 -209
  14. package/dist/agent/agent.d.ts +74 -8
  15. package/dist/agent/agent.js +1073 -184
  16. package/dist/agent/backends/account_rotation.d.ts +4 -0
  17. package/dist/agent/backends/account_rotation.js +8 -2
  18. package/dist/agent/backends/anthropic.d.ts +13 -0
  19. package/dist/agent/backends/anthropic.js +169 -28
  20. package/dist/agent/backends/claude_cli.js +18 -8
  21. package/dist/agent/backends/codex_app_server.d.ts +1 -0
  22. package/dist/agent/backends/codex_app_server.js +55 -3
  23. package/dist/agent/backends/codex_auth.d.ts +1 -0
  24. package/dist/agent/backends/codex_auth.js +67 -13
  25. package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
  26. package/dist/agent/backends/codex_buffered_recovery.js +293 -0
  27. package/dist/agent/backends/gemini.js +3 -1
  28. package/dist/agent/backends/http_error.d.ts +26 -0
  29. package/dist/agent/backends/http_error.js +221 -0
  30. package/dist/agent/backends/index.d.ts +9 -2
  31. package/dist/agent/backends/index.js +22 -2
  32. package/dist/agent/backends/openai.d.ts +0 -6
  33. package/dist/agent/backends/openai.js +5 -2
  34. package/dist/agent/backends/openai_compatible.d.ts +5 -4
  35. package/dist/agent/backends/openai_compatible.js +26 -19
  36. package/dist/agent/backends/openai_responses.d.ts +8 -3
  37. package/dist/agent/backends/openai_responses.js +77 -33
  38. package/dist/agent/backends/provider_ids.d.ts +1 -0
  39. package/dist/agent/backends/provider_ids.js +4 -0
  40. package/dist/agent/backends/request_metrics.js +12 -2
  41. package/dist/agent/backends/types.d.ts +109 -6
  42. package/dist/agent/backends/types.js +5 -1
  43. package/dist/agent/backends/zrouter_stream.d.ts +6 -1
  44. package/dist/agent/backends/zrouter_stream.js +160 -42
  45. package/dist/agent/background_registry.d.ts +2 -0
  46. package/dist/agent/background_registry.js +1 -0
  47. package/dist/agent/claim_observation.d.ts +6 -0
  48. package/dist/agent/claim_observation.js +54 -0
  49. package/dist/agent/commands/accounts.js +46 -177
  50. package/dist/agent/commands/attention.d.ts +2 -0
  51. package/dist/agent/commands/attention.js +67 -0
  52. package/dist/agent/commands/auth.js +18 -2
  53. package/dist/agent/commands/board.d.ts +2 -0
  54. package/dist/agent/commands/board.js +169 -0
  55. package/dist/agent/commands/clipboard.js +50 -1
  56. package/dist/agent/commands/compact.js +21 -13
  57. package/dist/agent/commands/config.js +4 -1
  58. package/dist/agent/commands/context.js +6 -1
  59. package/dist/agent/commands/desktop.d.ts +2 -0
  60. package/dist/agent/commands/desktop.js +29 -0
  61. package/dist/agent/commands/effort.js +14 -6
  62. package/dist/agent/commands/environment.d.ts +3 -0
  63. package/dist/agent/commands/environment.js +109 -0
  64. package/dist/agent/commands/evidence.d.ts +2 -0
  65. package/dist/agent/commands/evidence.js +9 -0
  66. package/dist/agent/commands/fork.js +4 -3
  67. package/dist/agent/commands/goal.d.ts +33 -1
  68. package/dist/agent/commands/goal.js +174 -14
  69. package/dist/agent/commands/goal_exit.d.ts +36 -0
  70. package/dist/agent/commands/goal_exit.js +120 -0
  71. package/dist/agent/commands/help.js +3 -1
  72. package/dist/agent/commands/history.js +3 -1
  73. package/dist/agent/commands/inbox.d.ts +2 -0
  74. package/dist/agent/commands/inbox.js +148 -0
  75. package/dist/agent/commands/index.js +16 -1
  76. package/dist/agent/commands/input_mode.js +8 -6
  77. package/dist/agent/commands/job.d.ts +2 -0
  78. package/dist/agent/commands/job.js +93 -0
  79. package/dist/agent/commands/keybindings.js +4 -1
  80. package/dist/agent/commands/label.d.ts +2 -0
  81. package/dist/agent/commands/label.js +77 -0
  82. package/dist/agent/commands/layout.d.ts +2 -0
  83. package/dist/agent/commands/layout.js +20 -0
  84. package/dist/agent/commands/mirror.js +16 -1
  85. package/dist/agent/commands/model.js +12 -2
  86. package/dist/agent/commands/permissions.d.ts +3 -1
  87. package/dist/agent/commands/permissions.js +79 -89
  88. package/dist/agent/commands/plan.js +5 -15
  89. package/dist/agent/commands/profile.js +21 -2
  90. package/dist/agent/commands/provider_selection.js +20 -1
  91. package/dist/agent/commands/rename.js +1 -1
  92. package/dist/agent/commands/resources.d.ts +2 -0
  93. package/dist/agent/commands/resources.js +67 -0
  94. package/dist/agent/commands/rewind.d.ts +3 -0
  95. package/dist/agent/commands/rewind.js +16 -0
  96. package/dist/agent/commands/runbooks.d.ts +2 -0
  97. package/dist/agent/commands/runbooks.js +73 -0
  98. package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
  99. package/dist/agent/commands/runbooks_lazy.js +8 -0
  100. package/dist/agent/commands/sessions.d.ts +1 -1
  101. package/dist/agent/commands/sessions.js +33 -5
  102. package/dist/agent/commands/settings.js +185 -39
  103. package/dist/agent/commands/setup.d.ts +2 -0
  104. package/dist/agent/commands/setup.js +78 -0
  105. package/dist/agent/commands/share.d.ts +2 -0
  106. package/dist/agent/commands/share.js +64 -0
  107. package/dist/agent/commands/status.js +14 -2
  108. package/dist/agent/commands/types.d.ts +99 -6
  109. package/dist/agent/commands/update.js +8 -0
  110. package/dist/agent/commands/usage.d.ts +2 -0
  111. package/dist/agent/commands/usage.js +8 -0
  112. package/dist/agent/factory.d.ts +11 -0
  113. package/dist/agent/factory.js +113 -88
  114. package/dist/agent/loop_policy.d.ts +6 -3
  115. package/dist/agent/loop_policy.js +12 -3
  116. package/dist/agent/measurement_guidance.d.ts +2 -0
  117. package/dist/agent/measurement_guidance.js +5 -0
  118. package/dist/agent/request_encoder.d.ts +8 -1
  119. package/dist/agent/request_encoder.js +95 -23
  120. package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
  121. package/dist/agent/runtime/benchmark_comparison.js +94 -0
  122. package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
  123. package/dist/agent/runtime/benchmark_contract.js +90 -0
  124. package/dist/agent/runtime/checked_commands.d.ts +32 -0
  125. package/dist/agent/runtime/checked_commands.js +119 -0
  126. package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
  127. package/dist/agent/runtime/command_execution_policy.js +91 -0
  128. package/dist/agent/runtime/command_jobs.d.ts +36 -18
  129. package/dist/agent/runtime/command_jobs.js +151 -13
  130. package/dist/agent/runtime/command_panes.d.ts +59 -0
  131. package/dist/agent/runtime/command_panes.js +398 -0
  132. package/dist/agent/runtime/command_wait.d.ts +19 -0
  133. package/dist/agent/runtime/command_wait.js +167 -0
  134. package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
  135. package/dist/agent/runtime/cpu_benchmark.js +126 -0
  136. package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
  137. package/dist/agent/runtime/delegated_evidence.js +80 -0
  138. package/dist/agent/runtime/evidence.d.ts +127 -0
  139. package/dist/agent/runtime/evidence.js +468 -0
  140. package/dist/agent/runtime/execution_context.d.ts +7 -0
  141. package/dist/agent/runtime/execution_context.js +39 -3
  142. package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
  143. package/dist/agent/runtime/json_verifier_recipe.js +66 -0
  144. package/dist/agent/runtime/measurements.d.ts +110 -0
  145. package/dist/agent/runtime/measurements.js +317 -0
  146. package/dist/agent/runtime/policy.d.ts +4 -0
  147. package/dist/agent/runtime/policy.js +31 -20
  148. package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
  149. package/dist/agent/runtime/provider_native_evidence.js +86 -0
  150. package/dist/agent/runtime/recovery.d.ts +2 -0
  151. package/dist/agent/runtime/recovery.js +45 -2
  152. package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
  153. package/dist/agent/runtime/remote_broker_transport.js +84 -0
  154. package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
  155. package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
  156. package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
  157. package/dist/agent/runtime/remote_journal_replay.js +233 -0
  158. package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
  159. package/dist/agent/runtime/remote_mutation_journal.js +208 -0
  160. package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
  161. package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
  162. package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
  163. package/dist/agent/runtime/remote_resource_errors.js +9 -0
  164. package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
  165. package/dist/agent/runtime/remote_resource_script.js +1009 -0
  166. package/dist/agent/runtime/remote_resources.d.ts +29 -0
  167. package/dist/agent/runtime/remote_resources.js +160 -0
  168. package/dist/agent/runtime/source_verification.d.ts +12 -0
  169. package/dist/agent/runtime/source_verification.js +210 -0
  170. package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
  171. package/dist/agent/runtime/source_verification_contract.js +203 -0
  172. package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
  173. package/dist/agent/runtime/tool_checkpoints.js +26 -0
  174. package/dist/agent/runtime/tool_effects.d.ts +5 -0
  175. package/dist/agent/runtime/tool_progress.d.ts +12 -0
  176. package/dist/agent/runtime/tool_progress.js +76 -0
  177. package/dist/agent/runtime/tool_runtime.d.ts +16 -1
  178. package/dist/agent/runtime/tool_runtime.js +204 -10
  179. package/dist/agent/runtime/tracing.d.ts +1 -1
  180. package/dist/agent/runtime/verifier_capture.d.ts +9 -0
  181. package/dist/agent/runtime/verifier_capture.js +100 -0
  182. package/dist/agent/runtime/verifier_contract.d.ts +24 -0
  183. package/dist/agent/runtime/verifier_contract.js +41 -0
  184. package/dist/agent/subagent.d.ts +20 -10
  185. package/dist/agent/subagent.js +266 -29
  186. package/dist/agent/tool_response_directory.d.ts +24 -0
  187. package/dist/agent/tool_response_directory.js +61 -0
  188. package/dist/agent/tool_response_source.d.ts +8 -0
  189. package/dist/agent/tool_response_source.js +49 -0
  190. package/dist/agent/tool_response_store.d.ts +75 -4
  191. package/dist/agent/tool_response_store.js +583 -64
  192. package/dist/agent/tools/ask_user.d.ts +3 -1
  193. package/dist/agent/tools/ask_user.js +30 -11
  194. package/dist/agent/tools/background.js +1 -0
  195. package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
  196. package/dist/agent/tools/benchmark_cpu.js +102 -0
  197. package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
  198. package/dist/agent/tools/compare_benchmarks.js +53 -0
  199. package/dist/agent/tools/deferred.js +1 -0
  200. package/dist/agent/tools/delegate.js +18 -2
  201. package/dist/agent/tools/helper_requirements.d.ts +7 -0
  202. package/dist/agent/tools/helper_requirements.js +19 -0
  203. package/dist/agent/tools/index.d.ts +1 -0
  204. package/dist/agent/tools/index.js +3 -0
  205. package/dist/agent/tools/measurement_report.d.ts +22 -0
  206. package/dist/agent/tools/measurement_report.js +190 -0
  207. package/dist/agent/tools/pane.d.ts +0 -7
  208. package/dist/agent/tools/pane.js +5 -4
  209. package/dist/agent/tools/project_context.d.ts +3 -0
  210. package/dist/agent/tools/project_context.js +45 -0
  211. package/dist/agent/tools/recall_tool_response.d.ts +3 -5
  212. package/dist/agent/tools/recall_tool_response.js +19 -29
  213. package/dist/agent/tools/registry.d.ts +1 -0
  214. package/dist/agent/tools/registry.js +23 -0
  215. package/dist/agent/tools/related_sessions.d.ts +2 -0
  216. package/dist/agent/tools/related_sessions.js +156 -0
  217. package/dist/agent/tools/routine.js +13 -3
  218. package/dist/agent/tools/session_evidence.d.ts +3 -0
  219. package/dist/agent/tools/session_evidence.js +72 -0
  220. package/dist/agent/tools/shell.d.ts +4 -3
  221. package/dist/agent/tools/shell.js +174 -47
  222. package/dist/agent/tools/tool_search.js +3 -2
  223. package/dist/agent/tools/types.d.ts +28 -0
  224. package/dist/agent/tools/verify_artifact.d.ts +2 -0
  225. package/dist/agent/tools/verify_artifact.js +60 -0
  226. package/dist/agent/tools/workflow.js +1 -0
  227. package/dist/app/activity.d.ts +6 -1
  228. package/dist/app/activity.js +81 -12
  229. package/dist/app/agent_runner.d.ts +6 -1
  230. package/dist/app/agent_runner.js +34 -19
  231. package/dist/app/command_context.js +106 -10
  232. package/dist/app/controller.d.ts +15 -4
  233. package/dist/app/controller.js +29 -3
  234. package/dist/app/escape_stop.d.ts +11 -0
  235. package/dist/app/escape_stop.js +15 -0
  236. package/dist/app/liveness.d.ts +6 -4
  237. package/dist/app/liveness.js +13 -3
  238. package/dist/app/model_catalog_cache.d.ts +5 -0
  239. package/dist/app/model_catalog_cache.js +138 -0
  240. package/dist/app/ordered_transcript.js +43 -4
  241. package/dist/app/prompt_turn.d.ts +14 -0
  242. package/dist/app/prompt_turn.js +53 -0
  243. package/dist/app/recap.d.ts +3 -0
  244. package/dist/app/recap.js +9 -3
  245. package/dist/app/restart.d.ts +3 -2
  246. package/dist/app/restart.js +22 -7
  247. package/dist/app/rewind.d.ts +3 -3
  248. package/dist/app/rewind.js +3 -3
  249. package/dist/app/selection_completion.d.ts +17 -2
  250. package/dist/app/selection_completion.js +84 -9
  251. package/dist/app/session_manager.d.ts +38 -6
  252. package/dist/app/session_manager.js +263 -60
  253. package/dist/app/session_messages.d.ts +3 -0
  254. package/dist/app/session_messages.js +60 -0
  255. package/dist/app/task_scope_router.d.ts +20 -0
  256. package/dist/app/task_scope_router.js +173 -0
  257. package/dist/app/transcript_window.d.ts +2 -0
  258. package/dist/app/transcript_window.js +4 -0
  259. package/dist/app/workspace_admission_details.d.ts +3 -0
  260. package/dist/app/workspace_admission_details.js +26 -0
  261. package/dist/app/workspace_layout_controller.d.ts +11 -0
  262. package/dist/app/workspace_layout_controller.js +88 -0
  263. package/dist/app/workspace_monitor.d.ts +1 -0
  264. package/dist/app/workspace_monitor.js +216 -0
  265. package/dist/app/workspace_presets.d.ts +29 -0
  266. package/dist/app/workspace_presets.js +192 -0
  267. package/dist/attention/queue.d.ts +37 -0
  268. package/dist/attention/queue.js +183 -0
  269. package/dist/bench/rf_shared.d.ts +2 -0
  270. package/dist/board/binding.d.ts +42 -0
  271. package/dist/board/binding.js +38 -0
  272. package/dist/board/client.d.ts +42 -0
  273. package/dist/board/client.js +60 -0
  274. package/dist/cli.js +1 -1
  275. package/dist/cli_args.js +19 -17
  276. package/dist/cli_main.js +110 -13
  277. package/dist/cli_ratatui.d.ts +183 -16
  278. package/dist/cli_ratatui.js +3989 -1255
  279. package/dist/components/ActivityLine.d.ts +3 -2
  280. package/dist/components/ActivityLine.js +2 -2
  281. package/dist/components/InputArea.d.ts +19 -1
  282. package/dist/components/InputArea.js +264 -190
  283. package/dist/components/MessageList.d.ts +2 -0
  284. package/dist/components/MessageList.js +3 -1
  285. package/dist/components/SingleMessage.d.ts +2 -0
  286. package/dist/components/SingleMessage.js +4 -2
  287. package/dist/components/StatusBar.d.ts +4 -2
  288. package/dist/components/StatusBar.js +3 -1
  289. package/dist/conductor_branch.js +3 -0
  290. package/dist/config/api_retry.d.ts +6 -1
  291. package/dist/config/api_retry.js +19 -1
  292. package/dist/config/defaults.d.ts +2 -1
  293. package/dist/config/defaults.js +9 -2
  294. package/dist/config/message_display.d.ts +1 -0
  295. package/dist/config/message_display.js +17 -2
  296. package/dist/config/permissions.d.ts +21 -0
  297. package/dist/config/permissions.js +89 -0
  298. package/dist/config/resolved.d.ts +2 -2
  299. package/dist/config/types.d.ts +32 -5
  300. package/dist/config.d.ts +38 -4
  301. package/dist/config.js +290 -42
  302. package/dist/context/project_intelligence.d.ts +44 -0
  303. package/dist/context/project_intelligence.js +246 -0
  304. package/dist/desktop/cli_launch.d.ts +8 -0
  305. package/dist/desktop/cli_launch.js +6 -0
  306. package/dist/desktop/cli_shim.d.ts +8 -0
  307. package/dist/desktop/cli_shim.js +120 -0
  308. package/dist/desktop/runtime.d.ts +16 -0
  309. package/dist/desktop/runtime.js +161 -0
  310. package/dist/desktop/worker_launch.d.ts +7 -0
  311. package/dist/desktop/worker_launch.js +7 -0
  312. package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
  313. package/dist/diagnostics/cargo_mutants_selection.js +48 -0
  314. package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
  315. package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
  316. package/dist/diagnostics/session_evidence_package.d.ts +40 -0
  317. package/dist/diagnostics/session_evidence_package.js +234 -0
  318. package/dist/diagnostics/session_export.d.ts +93 -0
  319. package/dist/diagnostics/session_export.js +634 -0
  320. package/dist/emulation/tool_adapters.js +48 -18
  321. package/dist/emulation/ztc_addendum.d.ts +1 -0
  322. package/dist/emulation/ztc_addendum.js +13 -12
  323. package/dist/environment/service_runner.d.ts +1 -0
  324. package/dist/environment/service_runner.js +68 -0
  325. package/dist/environment/store.d.ts +120 -0
  326. package/dist/environment/store.js +387 -0
  327. package/dist/events.d.ts +40 -1
  328. package/dist/events.js +10 -17
  329. package/dist/goal_contract.d.ts +25 -0
  330. package/dist/goal_contract.js +44 -0
  331. package/dist/headless.d.ts +31 -0
  332. package/dist/headless.js +578 -70
  333. package/dist/headless_cli.js +8 -0
  334. package/dist/headless_cli_args.d.ts +1 -0
  335. package/dist/headless_cli_args.js +1 -0
  336. package/dist/intake/cli.d.ts +1 -0
  337. package/dist/intake/cli.js +33 -0
  338. package/dist/intake/format.d.ts +2 -0
  339. package/dist/intake/format.js +14 -0
  340. package/dist/intake/notifications.d.ts +16 -0
  341. package/dist/intake/notifications.js +132 -0
  342. package/dist/intake/store.d.ts +56 -0
  343. package/dist/intake/store.js +238 -0
  344. package/dist/library.d.ts +8 -0
  345. package/dist/library.js +19 -10
  346. package/dist/mcp/client.d.ts +1 -0
  347. package/dist/mcp/client.js +20 -0
  348. package/dist/modes/builtin/plan.yaml +2 -2
  349. package/dist/modes/handlers.js +1 -1
  350. package/dist/onboarding/first_run.d.ts +3 -0
  351. package/dist/onboarding/first_run.js +33 -25
  352. package/dist/onboarding/setup.d.ts +32 -0
  353. package/dist/onboarding/setup.js +133 -0
  354. package/dist/onboarding/setup_agent_health.d.ts +17 -0
  355. package/dist/onboarding/setup_agent_health.js +79 -0
  356. package/dist/onboarding/setup_environment.d.ts +66 -0
  357. package/dist/onboarding/setup_environment.js +219 -0
  358. package/dist/onboarding/setup_workspace.d.ts +17 -0
  359. package/dist/onboarding/setup_workspace.js +129 -0
  360. package/dist/optimize/adapters/index.d.ts +4 -1
  361. package/dist/optimize/adapters/index.js +7 -0
  362. package/dist/optimize/adapters/kernelbench.js +14 -5
  363. package/dist/optimize/adapters/popcorn.js +3 -2
  364. package/dist/optimize/adapters/profile_benchmark.js +13 -7
  365. package/dist/optimize/adapters/rfbench.js +25 -14
  366. package/dist/optimize/adapters/types.d.ts +3 -0
  367. package/dist/optimize/adapters/zergpod.js +5 -2
  368. package/dist/optimize/baseline.js +10 -21
  369. package/dist/optimize/branch.js +9 -12
  370. package/dist/optimize/combine.js +2 -3
  371. package/dist/optimize/executor.js +34 -48
  372. package/dist/optimize/measurements.d.ts +55 -0
  373. package/dist/optimize/measurements.js +173 -0
  374. package/dist/optimize/results.d.ts +1 -0
  375. package/dist/optimize/results.js +11 -33
  376. package/dist/optimize/tge.js +2 -1
  377. package/dist/panes/initial_input.d.ts +19 -0
  378. package/dist/panes/initial_input.js +206 -0
  379. package/dist/panes/labels.d.ts +16 -0
  380. package/dist/panes/labels.js +42 -0
  381. package/dist/panes/model_catalog.d.ts +3 -3
  382. package/dist/panes/model_catalog.js +28 -4
  383. package/dist/panes/pane_registry.js +6 -2
  384. package/dist/panes/types.d.ts +3 -0
  385. package/dist/panes/ztc_launch.js +19 -0
  386. package/dist/plugin/optimization.d.ts +2 -0
  387. package/dist/plugin/optimization.js +1 -0
  388. package/dist/plugin/runtime.d.ts +2 -0
  389. package/dist/plugin/simulation.d.ts +6 -1
  390. package/dist/plugin/simulation_contract.d.ts +836 -1
  391. package/dist/plugin/simulation_documents.d.ts +22 -0
  392. package/dist/plugin/simulation_documents.js +1 -0
  393. package/dist/plugin/simulation_interventions.d.ts +103 -0
  394. package/dist/plugin/simulation_interventions.js +1 -0
  395. package/dist/plugin/simulation_living.d.ts +1 -0
  396. package/dist/plugin/simulation_living.js +3 -0
  397. package/dist/plugin/simulation_meetings.d.ts +121 -0
  398. package/dist/plugin/simulation_meetings.js +1 -0
  399. package/dist/plugin/simulation_story_review.d.ts +111 -0
  400. package/dist/plugin/simulation_story_review.js +1 -0
  401. package/dist/plugin/simulation_storyteller.d.ts +91 -0
  402. package/dist/plugin/simulation_storyteller.js +1 -0
  403. package/dist/plugin/simulation_structured.d.ts +2 -0
  404. package/dist/plugin/simulation_structured.js +2 -0
  405. package/dist/plugin/simulation_toolkit.d.ts +21 -0
  406. package/dist/plugin/simulation_toolkit.js +24 -0
  407. package/dist/prompts/tool_delegate_task.j2 +1 -1
  408. package/dist/remote-broker/Dockerfile +10 -0
  409. package/dist/remote-broker/archive.py +1227 -0
  410. package/dist/remote-broker/cutover.py +761 -0
  411. package/dist/remote-broker/requirements.txt +5 -0
  412. package/dist/remote-broker/resources.json +6 -0
  413. package/dist/remote-broker/resources.write-staging.example.json +18 -0
  414. package/dist/remote-broker/retained.py +686 -0
  415. package/dist/remote-broker/retained_cleanup.py +550 -0
  416. package/dist/remote-broker/server.py +648 -0
  417. package/dist/remote-broker/worker.py +1007 -0
  418. package/dist/router_auth.d.ts +31 -0
  419. package/dist/router_auth.js +155 -9
  420. package/dist/runbooks/catalog.d.ts +3 -0
  421. package/dist/runbooks/catalog.js +26 -0
  422. package/dist/runbooks/library.d.ts +11 -0
  423. package/dist/runbooks/library.js +95 -0
  424. package/dist/runbooks/package.d.ts +43 -0
  425. package/dist/runbooks/package.js +102 -0
  426. package/dist/session.d.ts +65 -13
  427. package/dist/session.js +548 -43
  428. package/dist/session_handoff.d.ts +25 -0
  429. package/dist/session_handoff.js +322 -0
  430. package/dist/session_handoff_contract.d.ts +89 -0
  431. package/dist/session_handoff_contract.js +179 -0
  432. package/dist/session_media.d.ts +41 -0
  433. package/dist/session_media.js +357 -0
  434. package/dist/session_ownership.d.ts +16 -0
  435. package/dist/session_ownership.js +221 -0
  436. package/dist/session_public.d.ts +10 -2
  437. package/dist/session_public.js +6 -1
  438. package/dist/simulation/dynamic_history.d.ts +42 -0
  439. package/dist/simulation/dynamic_history.js +173 -0
  440. package/dist/simulation/engines/swarm/pane_manager.js +7 -6
  441. package/dist/simulation/inference.d.ts +3 -1
  442. package/dist/simulation/inference.js +19 -1
  443. package/dist/simulation/intervention_generation.d.ts +27 -0
  444. package/dist/simulation/intervention_generation.js +246 -0
  445. package/dist/simulation/intervention_material.d.ts +4 -0
  446. package/dist/simulation/intervention_material.js +81 -0
  447. package/dist/simulation/living/context.d.ts +3130 -0
  448. package/dist/simulation/living/context.js +944 -0
  449. package/dist/simulation/living/definer.d.ts +12 -0
  450. package/dist/simulation/living/definer.js +223 -0
  451. package/dist/simulation/living/definition.d.ts +257 -0
  452. package/dist/simulation/living/definition.js +341 -0
  453. package/dist/simulation/living/frame.d.ts +259 -0
  454. package/dist/simulation/living/frame.js +223 -0
  455. package/dist/simulation/living/identifiers.d.ts +2 -0
  456. package/dist/simulation/living/identifiers.js +12 -0
  457. package/dist/simulation/living/index.d.ts +15 -0
  458. package/dist/simulation/living/index.js +15 -0
  459. package/dist/simulation/living/inference.d.ts +219 -0
  460. package/dist/simulation/living/inference.js +576 -0
  461. package/dist/simulation/living/mechanics.d.ts +54 -0
  462. package/dist/simulation/living/mechanics.js +588 -0
  463. package/dist/simulation/living/model-input.d.ts +25 -0
  464. package/dist/simulation/living/model-input.js +215 -0
  465. package/dist/simulation/living/open-world.d.ts +257 -0
  466. package/dist/simulation/living/open-world.js +102 -0
  467. package/dist/simulation/living/projection.d.ts +7 -0
  468. package/dist/simulation/living/projection.js +31 -0
  469. package/dist/simulation/living/recall.d.ts +17 -0
  470. package/dist/simulation/living/recall.js +25 -0
  471. package/dist/simulation/living/reduce.d.ts +15 -0
  472. package/dist/simulation/living/reduce.js +1853 -0
  473. package/dist/simulation/living/replay.d.ts +6 -0
  474. package/dist/simulation/living/replay.js +22 -0
  475. package/dist/simulation/living/runtime.d.ts +140 -0
  476. package/dist/simulation/living/runtime.js +3020 -0
  477. package/dist/simulation/living/shared-kinds.d.ts +24 -0
  478. package/dist/simulation/living/shared-kinds.js +96 -0
  479. package/dist/simulation/living/types.d.ts +2785 -0
  480. package/dist/simulation/living/types.js +669 -0
  481. package/dist/simulation/llm.d.ts +3 -1
  482. package/dist/simulation/llm.js +90 -30
  483. package/dist/simulation/memory/embedding.d.ts +1 -1
  484. package/dist/simulation/memory/embedding.js +9 -3
  485. package/dist/simulation/memory/filesystem.d.ts +2 -0
  486. package/dist/simulation/memory/filesystem.js +5 -1
  487. package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
  488. package/dist/simulation/memory/filesystem_file_tree.js +41 -10
  489. package/dist/simulation/memory/filesystem_invariants.js +1 -2
  490. package/dist/simulation/memory/filesystem_types.d.ts +1 -0
  491. package/dist/simulation/memory/memory.d.ts +2 -0
  492. package/dist/simulation/memory/memory.js +5 -1
  493. package/dist/simulation/orchestration/intent.js +0 -2
  494. package/dist/simulation/orchestration/lifecycle.js +0 -2
  495. package/dist/simulation/plugin_host.d.ts +25 -1
  496. package/dist/simulation/plugin_host.js +334 -17
  497. package/dist/simulation/session_documents.d.ts +20 -0
  498. package/dist/simulation/session_documents.js +62 -0
  499. package/dist/simulation/session_input_validation.d.ts +43 -0
  500. package/dist/simulation/session_input_validation.js +364 -0
  501. package/dist/simulation/session_inputs.d.ts +32 -0
  502. package/dist/simulation/session_inputs.js +154 -0
  503. package/dist/simulation/telemetry.d.ts +11 -14
  504. package/dist/simulation/telemetry.js +177 -54
  505. package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
  506. package/dist/simulation/toolkit/bounded_turns.js +80 -0
  507. package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
  508. package/dist/simulation/toolkit/dynamic_agents.js +218 -0
  509. package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
  510. package/dist/simulation/toolkit/dynamic_digest.js +8 -0
  511. package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
  512. package/dist/simulation/toolkit/dynamic_documents.js +71 -0
  513. package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
  514. package/dist/simulation/toolkit/dynamic_driver.js +269 -0
  515. package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
  516. package/dist/simulation/toolkit/dynamic_game.js +805 -0
  517. package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
  518. package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
  519. package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
  520. package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
  521. package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
  522. package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
  523. package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
  524. package/dist/simulation/toolkit/dynamic_progress.js +214 -0
  525. package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
  526. package/dist/simulation/toolkit/dynamic_projection.js +219 -0
  527. package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
  528. package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
  529. package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
  530. package/dist/simulation/toolkit/dynamic_state.js +1967 -0
  531. package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
  532. package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
  533. package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
  534. package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
  535. package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
  536. package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
  537. package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
  538. package/dist/simulation/toolkit/dynamic_world.js +377 -0
  539. package/dist/simulation/toolkit/structured_output.d.ts +5 -0
  540. package/dist/simulation/toolkit/structured_output.js +321 -0
  541. package/dist/simulation/types/world.d.ts +2 -0
  542. package/dist/simulation/world/artifact_store.js +1 -1
  543. package/dist/simulation/world/memory_manager.d.ts +5 -0
  544. package/dist/simulation/world/memory_manager.js +17 -2
  545. package/dist/simulation/world/state.d.ts +3 -2
  546. package/dist/simulation/world/state.js +15 -5
  547. package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
  548. package/dist/terminal_attach.d.ts +1 -0
  549. package/dist/terminal_attach.js +113 -0
  550. package/dist/tool_progress.d.ts +3 -0
  551. package/dist/tool_progress.js +27 -0
  552. package/dist/types.d.ts +145 -10
  553. package/dist/types.js +3 -0
  554. package/dist/ui/core/input.d.ts +3 -0
  555. package/dist/ui/core/input_segments.d.ts +18 -1
  556. package/dist/ui/core/input_segments.js +131 -47
  557. package/dist/ui/core/input_state.d.ts +12 -0
  558. package/dist/ui/core/layout_yoga.d.ts +1 -1
  559. package/dist/ui/core/turn_metadata.d.ts +5 -0
  560. package/dist/ui/core/turn_metadata.js +22 -0
  561. package/dist/ui/ink/render.d.ts +2 -1
  562. package/dist/ui/ink/render.js +6 -5
  563. package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
  564. package/dist/ui/ratatui/headless_pane_manager.js +293 -104
  565. package/dist/ui/ratatui/index.d.ts +1 -0
  566. package/dist/ui/ratatui/input.d.ts +4 -0
  567. package/dist/ui/ratatui/input.js +15 -0
  568. package/dist/ui/ratatui/ipc_path.d.ts +2 -0
  569. package/dist/ui/ratatui/ipc_path.js +29 -1
  570. package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
  571. package/dist/ui/ratatui/mux_persistence.js +139 -1
  572. package/dist/ui/ratatui/pane_relay.d.ts +1 -1
  573. package/dist/ui/ratatui/pane_relay.js +55 -27
  574. package/dist/ui/ratatui/process.d.ts +17 -1
  575. package/dist/ui/ratatui/process.js +80 -7
  576. package/dist/ui/ratatui/protocol.d.ts +236 -1
  577. package/dist/ui/ratatui/protocol.js +90 -0
  578. package/dist/ui/scrollback.d.ts +2 -1
  579. package/dist/ui/scrollback.js +9 -4
  580. package/dist/ui/stream_typewriter.d.ts +8 -3
  581. package/dist/ui/stream_typewriter.js +30 -1
  582. package/dist/ui/streaming_scrollback.d.ts +1 -0
  583. package/dist/ui/streaming_scrollback.js +1 -1
  584. package/dist/ui/transcript_render_cache.js +10 -0
  585. package/dist/ui/views/activity_line.d.ts +5 -3
  586. package/dist/ui/views/activity_line.js +65 -7
  587. package/dist/ui/views/app.d.ts +6 -3
  588. package/dist/ui/views/app.js +17 -6
  589. package/dist/ui/views/completion_menu.js +10 -5
  590. package/dist/ui/views/input_area.d.ts +7 -3
  591. package/dist/ui/views/input_area.js +15 -9
  592. package/dist/ui/views/message_list.d.ts +4 -1
  593. package/dist/ui/views/message_list.js +89 -19
  594. package/dist/ui/views/queue_panel.d.ts +3 -1
  595. package/dist/ui/views/queue_panel.js +9 -2
  596. package/dist/ui/views/status_bar.d.ts +4 -2
  597. package/dist/ui/views/status_bar.js +28 -13
  598. package/dist/ui/views/theme.d.ts +2 -0
  599. package/dist/ui/views/theme.js +2 -0
  600. package/dist/ui/widgets/color_panel.js +18 -0
  601. package/dist/ui/widgets/form.js +8 -0
  602. package/dist/ui/widgets/log_view.js +7 -0
  603. package/dist/ui/widgets/navigator_panel.js +8 -0
  604. package/dist/ui/widgets/prompt_viewer.js +2 -0
  605. package/dist/ui/widgets/questionnaire.d.ts +2 -0
  606. package/dist/ui/widgets/questionnaire.js +79 -16
  607. package/dist/ui/widgets/resume_panel.d.ts +2 -0
  608. package/dist/ui/widgets/resume_panel.js +26 -15
  609. package/dist/ui/widgets/rewind_panel.d.ts +2 -2
  610. package/dist/ui/widgets/rewind_panel.js +4 -2
  611. package/dist/ui/widgets/select.js +16 -27
  612. package/dist/ui/widgets/settings_panel.js +16 -0
  613. package/dist/ui/widgets/status_line_panel.d.ts +1 -0
  614. package/dist/ui/widgets/status_line_panel.js +30 -4
  615. package/dist/ui/widgets/text_input.d.ts +2 -0
  616. package/dist/ui/widgets/text_input.js +15 -1
  617. package/dist/ui/widgets/types.d.ts +2 -0
  618. package/dist/usage_projection.d.ts +24 -0
  619. package/dist/usage_projection.js +130 -0
  620. package/dist/usage_snapshot.d.ts +36 -0
  621. package/dist/usage_snapshot.js +131 -0
  622. package/dist/utils/assistant_message.d.ts +2 -0
  623. package/dist/utils/assistant_message.js +5 -0
  624. package/dist/utils/atomic_write.d.ts +6 -2
  625. package/dist/utils/atomic_write.js +29 -4
  626. package/dist/utils/checked_shell.d.ts +25 -0
  627. package/dist/utils/checked_shell.js +107 -0
  628. package/dist/utils/compaction.d.ts +4 -1
  629. package/dist/utils/compaction.js +31 -7
  630. package/dist/utils/context.d.ts +4 -4
  631. package/dist/utils/context.js +14 -12
  632. package/dist/utils/deepseek.d.ts +2 -2
  633. package/dist/utils/deepseek.js +13 -5
  634. package/dist/utils/ephemeral.js +4 -0
  635. package/dist/utils/ephemeral_tools.d.ts +18 -0
  636. package/dist/utils/ephemeral_tools.js +97 -0
  637. package/dist/utils/jev_client.d.ts +4 -0
  638. package/dist/utils/jev_client.js +15 -0
  639. package/dist/utils/model_cache.d.ts +15 -0
  640. package/dist/utils/model_cache.js +143 -0
  641. package/dist/utils/models.d.ts +4 -2
  642. package/dist/utils/models.js +12 -16
  643. package/dist/utils/reasoning.d.ts +8 -1
  644. package/dist/utils/reasoning.js +31 -11
  645. package/dist/utils/router_catalog.d.ts +49 -0
  646. package/dist/utils/router_catalog.js +271 -0
  647. package/dist/utils/sandbox.d.ts +9 -0
  648. package/dist/utils/sandbox.js +118 -15
  649. package/dist/utils/shell.d.ts +7 -0
  650. package/dist/utils/shell.js +98 -34
  651. package/dist/utils/subprocess_env.d.ts +3 -0
  652. package/dist/utils/subprocess_env.js +27 -1
  653. package/dist/web/accounts_mirror.d.ts +3 -1
  654. package/dist/web/accounts_mirror.js +13 -9
  655. package/dist/web/child_publication.d.ts +17 -0
  656. package/dist/web/child_publication.js +45 -0
  657. package/dist/web/mirror_authenticator.d.ts +31 -11
  658. package/dist/web/mirror_authenticator.js +67 -17
  659. package/dist/web/mirror_client.d.ts +31 -4
  660. package/dist/web/mirror_client.js +189 -23
  661. package/dist/web/mirror_stream.d.ts +84 -22
  662. package/dist/web/mirror_stream.js +676 -95
  663. package/dist/web/observer_query_redaction.d.ts +6 -0
  664. package/dist/web/observer_query_redaction.js +58 -0
  665. package/dist/web/pane_registry.d.ts +2 -0
  666. package/dist/web/pane_registry.js +86 -22
  667. package/dist/web/tool_image.d.ts +6 -0
  668. package/dist/web/tool_image.js +17 -0
  669. package/dist/web/view_plugin.d.ts +14 -0
  670. package/dist/web/view_plugin.js +23 -1
  671. package/dist/worker/executor.js +31 -12
  672. package/dist/worker/index.d.ts +1 -0
  673. package/dist/worker/toolsets.js +3 -1
  674. package/dist/worker/types.d.ts +12 -1
  675. package/dist/workflows/registry.js +1 -0
  676. package/dist/workflows/registry_handle.d.ts +2 -0
  677. package/dist/workflows/runtime.d.ts +2 -0
  678. package/dist/workflows/runtime.js +1 -0
  679. package/dist/workspace_agent.d.ts +7 -0
  680. package/dist/workspace_agent.js +36 -0
  681. package/dist/workspace_collision.d.ts +76 -0
  682. package/dist/workspace_collision.js +688 -0
  683. package/dist/workspace_layout.d.ts +18 -0
  684. package/dist/workspace_layout.js +121 -0
  685. package/dist/workspace_runtime.d.ts +44 -0
  686. package/dist/workspace_runtime.js +82 -0
  687. package/dist/workspace_session_context.d.ts +62 -0
  688. package/dist/workspace_session_context.js +160 -0
  689. package/dist/zlm-embed.mjs +32518 -9615
  690. package/dist/zlm.js +1 -0
  691. package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
  692. package/node_modules/@zerg/model-contracts/README.md +90 -0
  693. package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
  694. package/node_modules/@zerg/model-contracts/index.js +223 -0
  695. package/node_modules/@zerg/model-contracts/package.json +8 -0
  696. package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
  697. package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
  698. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  699. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  700. package/package.json +45 -24
  701. package/packages/model-contracts/LICENSE.md +375 -0
  702. package/packages/model-contracts/README.md +90 -0
  703. package/packages/model-contracts/index.d.ts +20 -0
  704. package/packages/model-contracts/index.js +223 -0
  705. package/packages/model-contracts/package.json +8 -0
  706. package/remote-broker/Dockerfile +10 -0
  707. package/remote-broker/README.md +308 -0
  708. package/remote-broker/archive.py +1227 -0
  709. package/remote-broker/cutover.py +761 -0
  710. package/remote-broker/requirements.txt +5 -0
  711. package/remote-broker/resources.example.json +6 -0
  712. package/remote-broker/resources.write-staging.example.json +18 -0
  713. package/remote-broker/retained.py +686 -0
  714. package/remote-broker/retained_cleanup.py +550 -0
  715. package/remote-broker/server.py +648 -0
  716. package/scripts/build-remote-broker.mjs +13 -0
  717. package/scripts/ensure-local-packages.mjs +6 -0
  718. package/scripts/node-pty-helpers.mjs +103 -0
package/dist/headless.js CHANGED
@@ -1,4 +1,9 @@
1
- import { shutdownCommandJobs, subscribeCommandJobs } from './agent/runtime/command_jobs.js';
1
+ import { environmentOperationInProgress } from './agent/commands/environment.js';
2
+ import { getEnvironment, openEnvironment, releaseEnvironment, describeEnvironmentForAgent, isEnvironmentWorkspacePath, EnvironmentRecoveryError } from './environment/store.js';
3
+ import { inferAgentPaneTitle, readPaneLabels } from './panes/labels.js';
4
+ import { listCommandJobs, shutdownCommandJobs, subscribeCommandJobs } from './agent/runtime/command_jobs.js';
5
+ import { ToolResponseStore } from './agent/tool_response_store.js';
6
+ import { projectGoal } from './goal_contract.js';
2
7
  /**
3
8
  * Headless Mode for ZTC
4
9
  *
@@ -17,14 +22,17 @@ import { shutdownCommandJobs, subscribeCommandJobs } from './agent/runtime/comma
17
22
  * echo "build a JIRA connector" | ztc --headless --mode ~/dcaig/ztc/modes/dcaig.yaml
18
23
  * echo '{"prompt": "...", "session_id": "..."}' | ztc --headless --json
19
24
  */
25
+ import { randomUUID } from 'node:crypto';
20
26
  import { writeFile } from 'fs/promises';
21
27
  import { configStore } from './config.js';
28
+ import { formatTaskHandoffContext, projectTaskHandoff } from './session_handoff_contract.js';
22
29
  import { RouterConfigurationError } from './router_auth.js';
23
30
  import { registerInstance, deregisterInstance } from './instance_registry.js';
24
31
  import { ContextOverflowError, isContextOverflowMessage } from './agent/agent.js';
25
32
  import { dumpContextOverflow } from './agent/contextDump.js';
26
33
  import { registerToolCallbacks } from './app/tool_callbacks.js';
27
34
  import { createAgentWithConfig } from './app/agent_runner.js';
35
+ import { mergeToolCheckpointMessages } from './agent/runtime/tool_checkpoints.js';
28
36
  import { clearWorkerExecutor } from './worker/index.js';
29
37
  import { commands, refreshPluginCommands } from './agent/commands/index.js';
30
38
  import { goalManager, goalPinTask, detectGoalCompletion, detectGoalBlocked, goalBlockedReason, goalFinalNote, turnProducedText, turnToolOutcomes, shouldAccountFinalTurn, snapshotGoalForTurn } from './agent/commands/goal.js';
@@ -33,13 +41,16 @@ import { allocatePlanArtifact, findPlanContent, readPlanArtifactSafely, } from '
33
41
  import { initModeRegistry, registerBuiltinHandlers, getModeRegistry } from './modes/index.js';
34
42
  import { loadEmulationProfiles, getEmulationProfile } from './emulation/catalog.js';
35
43
  import { resolve } from 'path';
36
- import { loadSessionByIdOrName, loadSessionFromFile, generateSessionId, SessionLoadError, bindToolResponseStoreToSession, } from './session.js';
44
+ import { homedir } from 'node:os';
45
+ import { applyAccountOp } from './web/accounts_mirror.js';
46
+ import { loadSessionByIdOrName, loadSessionFromFile, generateSessionId, getSessionPathForCwd, SessionLoadError, bindToolResponseStoreToSession, } from './session.js';
37
47
  import { clearStaleTodosOnBoot } from './app/todo_state.js';
38
48
  import { runtimeDiag } from './utils/debug_log.js';
39
49
  import { rename as renameFile } from 'fs/promises';
40
50
  import { buildModelsController, buildSkillsController, buildShellController, buildStubShellController, buildAutoSelectWidgets, routeUserInput, getPlanModeCommandDenial } from './app/command_context.js';
41
51
  import { isToolAllowed } from './app/modes.js';
42
52
  import { SessionManager } from './app/session_manager.js';
53
+ import { LocalWorkspaceCoordinator } from './workspace_collision.js';
43
54
  import { HeadlessPaneManager } from './ui/ratatui/headless_pane_manager.js';
44
55
  import { setPaneToolsTuiProcess, clearPaneToolsTuiProcess, setBusTransport, clearBusTransport, getBackgroundRegistry, clearBackgroundRegistry, resetAskUserTurnFlag, setTodoUpdateCallback } from './agent/tools/index.js';
45
56
  import { PaneRelayServer, generateRelaySocketPath } from './ui/ratatui/pane_relay.js';
@@ -98,6 +109,11 @@ export function classifyPaneChildLine(line) {
98
109
  }
99
110
  case 'cancel':
100
111
  return { kind: 'cancel' };
112
+ case 'ask_user_answer':
113
+ if (typeof cmd.question_id !== 'string' || !cmd.question_id || typeof cmd.answer !== 'string' || !cmd.answer.trim()) {
114
+ return { kind: 'malformed_cmd', raw: trimmed };
115
+ }
116
+ return { kind: 'ask_user_answer', questionId: cmd.question_id, answer: cmd.answer };
101
117
  default:
102
118
  return { kind: 'unknown_cmd', cmd: typeof cmd.__ztc_cmd === 'string' ? cmd.__ztc_cmd : String(cmd.__ztc_cmd) };
103
119
  }
@@ -291,10 +307,13 @@ export class HeadlessApp {
291
307
  messages = [];
292
308
  sessionId;
293
309
  sessionName;
310
+ sessionLabels = {};
294
311
  agentBuiltForMode = null;
295
312
  /** Plan artifact captured by the current agent's prompt/write guard. */
296
313
  agentBuiltForPlanFile = null;
297
314
  /** Binding barrier for agents created by synchronous mode-change callbacks. */
315
+ sessionToolResponseStore = new ToolResponseStore();
316
+ sessionToolResponseBinding = null;
298
317
  pendingAgentBind = null;
299
318
  /** Sticky failure for the current agent generation; a settled rejected bind
300
319
  * must not be forgotten before the next turn awaits it. */
@@ -327,13 +346,22 @@ export class HeadlessApp {
327
346
  lastResponse = null;
328
347
  taskCompleted = false;
329
348
  sessionManager;
349
+ workspaceCoordinator = null;
350
+ taskHandoff = null;
351
+ restoredHandoffCorrections = [];
330
352
  paneManager = null;
331
353
  askUserAutoAnswered = false;
354
+ pendingQuestionId = null;
332
355
  paneRelay = null;
333
356
  // Auto-started loopback mirror so this headless instance is reachable from
334
357
  // other ZTC navigators. Silent — no banner, no browser auto-open.
335
358
  autoMirror = null;
336
359
  shellCwd;
360
+ startupCatalogController = new AbortController();
361
+ environmentRecord = null;
362
+ environmentReturnCwd = process.cwd();
363
+ environmentSessionCwd = null;
364
+ environmentReleased = false;
337
365
  shellMode;
338
366
  backgroundEventUnsub = null;
339
367
  batchEventUnsub = null;
@@ -346,6 +374,9 @@ export class HeadlessApp {
346
374
  * AbortController()`, cleared in that run's finally.
347
375
  */
348
376
  activeRunController = null;
377
+ turnAdmissionCount = 0;
378
+ runtimeMutationDone = null;
379
+ accountReloadPending = false;
349
380
  /**
350
381
  * Set by cancelActiveRun so the run's catch can tell a user cancel apart from
351
382
  * the timeout path (which aborts the same controller) and from real errors —
@@ -364,6 +395,7 @@ export class HeadlessApp {
364
395
  activityState = { status: 'idle' };
365
396
  lastEmittedStatus = null;
366
397
  lastEmittedVerb = null;
398
+ lastEmittedWaiting = null;
367
399
  hookLifecycle;
368
400
  constructor(options) {
369
401
  this.options = {
@@ -383,12 +415,13 @@ export class HeadlessApp {
383
415
  this.shellMode = resolve_headless_shell_mode();
384
416
  this.sessionManager = new SessionManager({
385
417
  sessionId: this.sessionId,
418
+ getSessionCwd: () => this.environmentSessionCwd ?? process.cwd(),
386
419
  sessionFile: options.sessionFile,
387
420
  getMessages: () => this.messages,
388
421
  setMessages: (msgs) => { this.messages = msgs; },
389
422
  addMessage: (msg) => {
390
423
  this.messages.push({
391
- id: `msg_${Date.now()}`,
424
+ id: `msg_${randomUUID()}`,
392
425
  role: msg.role,
393
426
  content: msg.content,
394
427
  timestamp: new Date(),
@@ -403,11 +436,29 @@ export class HeadlessApp {
403
436
  // Without mode + artifact, a resumed headless plan silently becomes a
404
437
  // normal full-tool agent even though its transcript says it is planning.
405
438
  getRuntimeState: () => ({
439
+ environment: this.environmentRecord ? { id: this.environmentRecord.id, name: this.environmentRecord.name } : null,
440
+ evidence: this.agent?.getEvidenceSnapshot(),
441
+ usage: this.agent?.getUsageSessionId() === this.sessionId ? this.agent.getUsageSnapshot() : undefined,
442
+ diagnostics: this.agent?.getDiagnosticSessionId() === this.sessionId
443
+ ? this.agent.getDiagnosticSnapshot() : undefined,
406
444
  mode: this.currentMode,
407
445
  planFile: this.currentMode === 'plan' ? this.artifactPath : null,
408
446
  artifactPath: this.artifactPath,
409
447
  goal: goalManager.serialize(),
448
+ taskHandoffInput: { corrections: this.restoredHandoffCorrections.map(correction => {
449
+ const source = this.messages.find(message => message.id === correction.sourceMessageId);
450
+ const raw = source?.metadata?.taskHandoffCorrection;
451
+ const text = source?.role === 'user' ? source.content
452
+ : raw && typeof raw === 'object' && 'text' in raw && typeof raw.text === 'string' ? raw.text : correction.text;
453
+ return { ...correction, text };
454
+ }), owner: { pid: process.pid, paneId: null } },
410
455
  }),
456
+ flushToolResponses: async () => { await this.agent?.toolResponseStore.flush(); },
457
+ onCheckpoint: session => {
458
+ this.taskHandoff = session.task_handoff ? projectTaskHandoff(session.task_handoff) : null;
459
+ this.agent?.setTaskHandoff(this.taskHandoff);
460
+ this.publishTaskHandoff();
461
+ },
411
462
  debugLog: (msg) => {
412
463
  if (this.options.verbose) {
413
464
  this.output({ type: 'status', timestamp: new Date().toISOString(), data: { status: 'debug', message: msg } });
@@ -449,14 +500,16 @@ export class HeadlessApp {
449
500
  if (this.options.paneChild) {
450
501
  // A human is watching this pane in the web mirror. Surface the question
451
502
  // instead of auto-answering: emit an 'ask_user' event that the cli_ratatui
452
- // relay republishes as publishAskUser(paneId) so the browser renders the
453
- // widget. The turn then ends (no re-run flag), and the human's answer comes
454
- // back as a normal stdin prompt (onAskUserAnswer -> deliverBusMessage ->
455
- // pane-input callback), continuing the conversation.
503
+ // relay forwards with its original request ID. The turn then ends (no
504
+ // re-run flag), and the human's answer returns through the identified
505
+ // control channel, checked against this pane's pending question.
506
+ this.pendingQuestionId = randomUUID();
456
507
  this.output({
457
508
  type: 'ask_user',
458
509
  timestamp: new Date().toISOString(),
459
510
  data: {
511
+ question_id: this.pendingQuestionId,
512
+ origin_session_id: this.sessionId,
460
513
  questions: questions.map(q => ({
461
514
  question: q.question,
462
515
  options: q.options,
@@ -478,7 +531,7 @@ export class HeadlessApp {
478
531
  ? answers[0]
479
532
  : JSON.stringify(questions.map((q, i) => ({ question: q.question, answer: answers[i] })));
480
533
  this.messages.push({
481
- id: `msg_${Date.now()}`,
534
+ id: `msg_${randomUUID()}`,
482
535
  role: 'user',
483
536
  content: answerText,
484
537
  timestamp: new Date(),
@@ -486,19 +539,29 @@ export class HeadlessApp {
486
539
  this.askUserAutoAnswered = true;
487
540
  return null;
488
541
  }
542
+ dismissPendingQuestion() {
543
+ if (!this.pendingQuestionId)
544
+ return;
545
+ this.output({ type: 'question_dismiss', timestamp: new Date().toISOString(),
546
+ data: { question_id: this.pendingQuestionId, origin_session_id: this.sessionId } });
547
+ this.pendingQuestionId = null;
548
+ }
489
549
  emitHeadlessStatus() {
490
550
  const projected = buildActivityDisplayState(this.activityState, 'Thinking');
491
551
  const verb = projected.verb ?? null;
492
- if (projected.wireStatus === this.lastEmittedStatus && verb === this.lastEmittedVerb)
552
+ const waitingKey = projected.waiting ? JSON.stringify(projected.waiting) : null;
553
+ if (projected.wireStatus === this.lastEmittedStatus && verb === this.lastEmittedVerb && waitingKey === this.lastEmittedWaiting)
493
554
  return;
494
555
  this.lastEmittedStatus = projected.wireStatus;
495
556
  this.lastEmittedVerb = verb;
557
+ this.lastEmittedWaiting = waitingKey;
496
558
  this.output({
497
559
  type: 'status',
498
560
  timestamp: new Date().toISOString(),
499
561
  data: {
500
562
  status: projected.wireStatus,
501
563
  ...(verb ? { verb } : {}),
564
+ ...(projected.waiting ? { waiting: projected.waiting } : {}),
502
565
  },
503
566
  });
504
567
  }
@@ -531,11 +594,31 @@ export class HeadlessApp {
531
594
  subscribeAgentEvents() {
532
595
  if (!this.agent)
533
596
  throw new Error('Agent not initialized');
534
- this.activityState = { status: 'thinking', startedAt: new Date() };
597
+ this.activityState = { status: 'thinking', startedAt: new Date(), taskHandoff: this.activityState.taskHandoff,
598
+ workspaceCollision: this.activityState.workspaceCollision };
599
+ void this.workspaceCoordinator?.setActivity('running').catch(() => {
600
+ runtimeDiag('warn', '[ztc] Local workspace activity evidence could not be refreshed.');
601
+ });
535
602
  this.emitHeadlessStatus();
603
+ this.output({ type: 'evidence', timestamp: new Date().toISOString(),
604
+ data: { session_id: this.sessionId, snapshot: this.agent.getEvidenceSnapshot() } });
536
605
  const unsubscribe = this.agent.on((event) => {
537
606
  this.activityState = applyAgentEventToState(this.activityState, event);
538
607
  switch (event.type) {
608
+ case 'request_usage':
609
+ this.output({ type: 'request_usage', timestamp: new Date().toISOString(), data: { session_id: this.sessionId, receipt: event.receipt } });
610
+ this.autoMirror?.publish({ type: 'request_usage', data: { sessionId: this.sessionId, receipt: event.receipt } });
611
+ break;
612
+ case 'evidence':
613
+ this.output({ type: 'evidence', timestamp: new Date().toISOString(),
614
+ data: { session_id: event.sessionId, snapshot: event.snapshot } });
615
+ break;
616
+ case 'command_wait':
617
+ case 'command_wait_end':
618
+ goalManager.observeHostWait(event);
619
+ this.publishCurrentGoal();
620
+ this.emitHeadlessStatus();
621
+ break;
539
622
  case 'thinking_start':
540
623
  this.emitHeadlessStatus();
541
624
  break;
@@ -692,7 +775,11 @@ export class HeadlessApp {
692
775
  });
693
776
  return () => {
694
777
  unsubscribe();
695
- this.activityState = { status: 'idle' };
778
+ this.activityState = { status: 'idle', taskHandoff: this.activityState.taskHandoff,
779
+ workspaceCollision: this.activityState.workspaceCollision };
780
+ void this.workspaceCoordinator?.setActivity('idle').catch(() => {
781
+ runtimeDiag('warn', '[ztc] Local workspace activity evidence could not be refreshed.');
782
+ });
696
783
  this.emitHeadlessStatus();
697
784
  };
698
785
  }
@@ -711,6 +798,13 @@ export class HeadlessApp {
711
798
  /**
712
799
  * Output a message in the appropriate format
713
800
  */
801
+ publishTaskHandoff() {
802
+ this.publishCurrentGoal();
803
+ this.activityState = applyAgentEventToState(this.activityState, { type: 'task_handoff', sessionId: this.sessionId, projection: this.taskHandoff });
804
+ this.output({ type: 'task_handoff', timestamp: new Date().toISOString(),
805
+ data: { sessionId: this.sessionId, projection: this.taskHandoff } });
806
+ this.autoMirror?.publishTaskHandoff(this.taskHandoff, this.sessionId);
807
+ }
714
808
  output(data) {
715
809
  if (this.options.json) {
716
810
  console.log(JSON.stringify(data));
@@ -748,11 +842,19 @@ export class HeadlessApp {
748
842
  if (!this.options.quiet || this.options.verbose)
749
843
  console.log(`${ts}[commands] ${JSON.stringify(data.data.jobs)}`);
750
844
  break;
845
+ case 'evidence':
846
+ if (!this.options.quiet || this.options.verbose)
847
+ console.log(`${ts}[evidence] Retained source-linked observations; inspect with /evidence.`);
848
+ break;
849
+ case 'task_handoff':
850
+ if (this.taskHandoff && (!this.options.quiet || this.options.verbose))
851
+ console.log(formatTaskHandoffContext(this.taskHandoff.snapshot));
852
+ break;
751
853
  case 'status':
752
854
  // In quiet mode, skip status unless verbose
753
855
  if (this.options.quiet && !this.options.verbose)
754
856
  break;
755
- console.log(`${ts}[status] ${data.data.status}`);
857
+ console.log(`${ts}[status] ${data.data.waiting ? 'Waiting for command' : data.data.status}`);
756
858
  break;
757
859
  case 'artifact':
758
860
  console.log(`${ts}[artifact] ${data.data.path}`);
@@ -1008,8 +1110,9 @@ export class HeadlessApp {
1008
1110
  // whose real credential (for example a Codex subscription auth.json) is on
1009
1111
  // disk. `load()` is idempotent, so the CLI path is unaffected.
1010
1112
  await configStore.load();
1011
- // Require either direct provider key or platform auth token.
1012
- if (!configStore.hasApiKey() && !configStore.isAuthenticated()) {
1113
+ // Hosted native Messages supplies its own scoped transport bearer. The
1114
+ // backend factory validates it; provider-key discovery must not gate it.
1115
+ if (!configStore.get().nativeAnthropicMessagesRouter && !configStore.hasApiKey() && !configStore.isAuthenticated()) {
1013
1116
  this.output({
1014
1117
  type: 'error',
1015
1118
  timestamp: new Date().toISOString(),
@@ -1182,6 +1285,27 @@ export class HeadlessApp {
1182
1285
  });
1183
1286
  }
1184
1287
  }
1288
+ const savedEnvironment = loadedSession?.metadata.environment;
1289
+ if (savedEnvironment && this.options.detachEnvironment) {
1290
+ this.output({ type: 'status', timestamp: new Date().toISOString(), data: { status: 'environment_detached', message: `Environment ${savedEnvironment.name} detached by launch option; conversation resumed in ${this.shellCwd}.` } });
1291
+ }
1292
+ if (savedEnvironment && !this.options.detachEnvironment) {
1293
+ try {
1294
+ this.environmentSessionCwd = loadedSession.metadata.cwd;
1295
+ this.environmentReturnCwd = this.environmentSessionCwd;
1296
+ const before = await getEnvironment(savedEnvironment.name);
1297
+ if (before.id !== savedEnvironment.id)
1298
+ throw new Error('The saved environment was replaced; inspect it before recovery.');
1299
+ const record = await openEnvironment(savedEnvironment.name, this.sessionId);
1300
+ this.environmentReleased = false;
1301
+ this.environmentRecord = record;
1302
+ this.shellCwd = record.workspacePath;
1303
+ process.chdir(record.workspacePath);
1304
+ }
1305
+ catch (error) {
1306
+ throw new EnvironmentRecoveryError(`Environment ${savedEnvironment.name} could not resume: ${error instanceof Error ? error.message : String(error)}. To resume the conversation in the current project, add --detach-environment.`);
1307
+ }
1308
+ }
1185
1309
  if (explicitMode) {
1186
1310
  if (explicitMode.name === 'plan') {
1187
1311
  const resumePath = loadedSession?.metadata.mode === 'plan'
@@ -1217,6 +1341,7 @@ export class HeadlessApp {
1217
1341
  executeCommand: (cmd, args, origin) => this.executeCommand(cmd, args, origin),
1218
1342
  getMode: () => this.currentMode,
1219
1343
  setMode: (mode) => {
1344
+ this.sessionManager.recordHostDiagnostic('mode', { mode, previousMode: this.currentMode, source: 'host_mode_change' });
1220
1345
  this.currentMode = mode;
1221
1346
  this.hookLifecycle.onModeChange();
1222
1347
  },
@@ -1253,14 +1378,15 @@ export class HeadlessApp {
1253
1378
  // Set up pane tools for mux mode
1254
1379
  if (this.options.muxMode) {
1255
1380
  this.paneManager = new HeadlessPaneManager();
1256
- this.paneManager.setHostTitle('Overseer');
1381
+ this.paneManager.setHostTitle(inferAgentPaneTitle({ cwd: this.options.cwd ?? process.cwd(), sessionId: this.sessionId, paneId: 0 }));
1382
+ this.paneManager.send({ type: 'set_pane_labels', pane_id: 0, labels: this.sessionLabels, if_newer: true });
1257
1383
  await this.paneManager.start({ mux: true });
1258
1384
  setPaneToolsTuiProcess(this.paneManager);
1259
1385
  // Start pane relay server so child panes (--pane) can connect and use pane tools
1260
1386
  const relayPath = generateRelaySocketPath();
1261
1387
  process.env.ZTC_PANE_RELAY = relayPath;
1262
1388
  this.paneRelay = new PaneRelayServer(this.paneManager, relayPath);
1263
- this.paneRelay.start();
1389
+ await this.paneRelay.start();
1264
1390
  await runtimePluginRegistry.runMuxStartHooks({
1265
1391
  paneManager: this.paneManager,
1266
1392
  relayPath,
@@ -1290,6 +1416,13 @@ export class HeadlessApp {
1290
1416
  if (!this.options.deferJsonStdinStartup) {
1291
1417
  this.outputHeadlessShellMode();
1292
1418
  }
1419
+ // Discover current Router controls before the first backend snapshots them.
1420
+ try {
1421
+ if (configStore.resolveLLMBackendConfig().useProxy) {
1422
+ void buildModelsController(configStore).catalog(this.startupCatalogController.signal, { refresh: 'startup' }).catch(() => { });
1423
+ }
1424
+ }
1425
+ catch { /* Offline startup keeps the account-scoped cached catalog. */ }
1293
1426
  // Create agent
1294
1427
  this.agent = this.createAgent();
1295
1428
  if (!this.agent) {
@@ -1319,6 +1452,7 @@ export class HeadlessApp {
1319
1452
  // Web `use` (switch account) hot-swaps this headless instance's live
1320
1453
  // agent, matching the CLI /accounts use path.
1321
1454
  this.autoMirror.setAccountReloadHook(() => this.reloadAgentInPlace());
1455
+ this.autoMirror.setAccountOperationHook(body => this.applyAccountOperation(body));
1322
1456
  mirrorPort = this.autoMirror.getPort();
1323
1457
  }
1324
1458
  catch (err) {
@@ -1344,49 +1478,61 @@ export class HeadlessApp {
1344
1478
  hidden: false,
1345
1479
  cwd: process.cwd(),
1346
1480
  });
1347
- let cleanedUp = false;
1348
- const cleanup = () => {
1349
- if (cleanedUp)
1350
- return;
1351
- cleanedUp = true;
1481
+ }
1482
+ // Visibility only controls navigator registration, never process cleanup.
1483
+ let cleanedUp = false;
1484
+ const cleanup = () => {
1485
+ if (cleanedUp)
1486
+ return;
1487
+ cleanedUp = true;
1488
+ if (!this.options.navigatorHidden) {
1352
1489
  try {
1353
1490
  deregisterInstance(process.pid);
1354
1491
  }
1355
- catch { /* ignore */ }
1356
- try {
1357
- this.autoMirror?.stop();
1358
- }
1359
1492
  catch { /* best effort */ }
1360
- };
1361
- // Fatal-signal exit must also reap detached shell-tool children. They are
1362
- // spawned in their own process group, so an ordinary ztc exit orphans any
1363
- // `npm run dev` / test runner / install still running — the cell Stop /
1364
- // Archive / Quit and supervisor shutdown only SIGTERM this top-level process.
1365
- // SIGTERM the groups, give them a brief moment, then SIGKILL stragglers and
1366
- // exit. The 200ms is well under the supervisor's 2s SIGKILL-escalation on us,
1367
- // so we still exit promptly.
1368
- const fatalExit = (code) => {
1369
- cleanup();
1370
- const killed = terminateActiveShellChildren('SIGTERM');
1493
+ }
1494
+ try {
1495
+ this.autoMirror?.stop();
1496
+ }
1497
+ catch { /* best effort */ }
1498
+ this.workspaceCoordinator?.closeSync();
1499
+ };
1500
+ // Fatal-signal exit must also reap detached shell-tool children. They are
1501
+ // spawned in their own process group, so an ordinary ztc exit orphans any
1502
+ // `npm run dev` / test runner / install still running — the cell Stop /
1503
+ // Archive / Quit and supervisor shutdown only SIGTERM this top-level process.
1504
+ // SIGTERM the groups, give them a brief moment, then SIGKILL stragglers and
1505
+ // exit. The 200ms is well under the supervisor's 2s SIGKILL-escalation on us,
1506
+ // so we still exit promptly.
1507
+ let fatalExitRequested = false;
1508
+ const fatalExit = (code) => {
1509
+ if (fatalExitRequested)
1510
+ return;
1511
+ fatalExitRequested = true;
1512
+ cleanup();
1513
+ const killed = terminateActiveShellChildren('SIGTERM');
1514
+ const forceExit = setTimeout(() => {
1515
+ terminateActiveShellChildren('SIGKILL');
1516
+ process.exit(code);
1517
+ }, 200);
1518
+ // Wait for an already admitted presence publication to retire before
1519
+ // exit. The same bounded fallback covers a stuck filesystem request.
1520
+ void (this.workspaceCoordinator?.close() ?? Promise.resolve()).catch(() => { }).then(() => {
1371
1521
  if (killed.length === 0) {
1522
+ clearTimeout(forceExit);
1372
1523
  process.exit(code);
1373
- return;
1374
1524
  }
1375
- setTimeout(() => {
1376
- terminateActiveShellChildren('SIGKILL');
1377
- process.exit(code);
1378
- }, 200).unref();
1379
- };
1380
- process.on('exit', cleanup);
1381
- process.on('SIGINT', () => fatalExit(130));
1382
- process.on('SIGTERM', () => fatalExit(143));
1383
- process.on('SIGHUP', () => fatalExit(129));
1384
- process.on('uncaughtException', (err) => {
1385
- cleanup();
1386
- console.error(err);
1387
- process.exit(1);
1388
1525
  });
1389
- }
1526
+ };
1527
+ process.on('exit', cleanup);
1528
+ process.on('SIGINT', () => fatalExit(130));
1529
+ process.on('SIGTERM', () => fatalExit(143));
1530
+ process.on('SIGHUP', () => fatalExit(129));
1531
+ process.on('uncaughtException', (err) => {
1532
+ cleanup();
1533
+ console.error(err);
1534
+ process.exit(1);
1535
+ });
1390
1536
  // A pane child receives the web mirror's stop button as SIGUSR2 (see
1391
1537
  // HeadlessPaneManager.cancelPane): its stdin loop AWAITS the running turn, so
1392
1538
  // a signal is the only mid-turn cancel channel. SIGUSR2, not SIGUSR1 — Node
@@ -1412,21 +1558,44 @@ export class HeadlessApp {
1412
1558
  this.outputBackgroundEvent(event);
1413
1559
  });
1414
1560
  }
1561
+ // Navigator visibility does not change local workspace ownership evidence.
1562
+ // Hidden workers and pane children must remain discoverable by sibling UIs.
1563
+ this.workspaceCoordinator = new LocalWorkspaceCoordinator({
1564
+ sessionId: this.sessionId,
1565
+ getCwd: () => this.shellCwd,
1566
+ getWork: () => ({ sessionId: this.sessionId, messages: this.messages,
1567
+ goal: goalManager.get(), jobs: listCommandJobs(this.sessionId), taskHandoff: this.taskHandoff }),
1568
+ kind: process.env.ZTC_COLONY_CHILD ? 'colony_child' : 'headless',
1569
+ onSnapshot: (snapshot) => {
1570
+ this.activityState = { ...this.activityState, workspaceCollision: snapshot };
1571
+ },
1572
+ });
1573
+ await this.workspaceCoordinator.start();
1415
1574
  return true;
1416
1575
  }
1417
1576
  /**
1418
1577
  * Create the agent
1419
1578
  */
1420
1579
  commandJobsUnsubscribe;
1421
- createAgent() {
1580
+ createAgent(options = {}) {
1581
+ const diagnostics = this.sessionManager.getDiagnostics();
1582
+ this.sessionManager.recordHostDiagnostic('runtime', {
1583
+ renderer: 'headless', rendererVersion: null, nativeCapabilities: null, source: 'host_launch',
1584
+ });
1585
+ this.sessionManager.recordHostDiagnostic('mode', { mode: this.currentMode, source: 'agent_construction' });
1422
1586
  this.commandJobsUnsubscribe?.();
1423
1587
  this.commandJobsUnsubscribe = subscribeCommandJobs(this.sessionId, jobs => {
1588
+ for (const job of jobs)
1589
+ this.sessionManager.recordCommandJob(job);
1424
1590
  this.output({ type: 'command_jobs', timestamp: new Date().toISOString(), data: { jobs } });
1425
1591
  this.autoMirror?.publish({ type: 'command_jobs', data: { jobs } });
1592
+ void this.workspaceCoordinator?.refresh().catch(error => runtimeDiag('warn', `[ztc] Local workspace command evidence could not be refreshed: ${String(error)}`));
1426
1593
  });
1427
1594
  const isEvolveMode = this.currentMode === 'evolved-swarm';
1428
1595
  const hookDispatcher = this.hookLifecycle.refresh();
1429
1596
  const agent = createAgentWithConfig({
1597
+ allowUnavailableRouterAuth: options.allowUnavailableRouterAuth,
1598
+ environmentContext: this.environmentRecord ? describeEnvironmentForAgent(this.environmentRecord) : undefined,
1430
1599
  autoContinue: true, // Always auto-continue in headless mode
1431
1600
  maxConsecutiveAutoContinues: configStore.getMaxConsecutiveAutoContinues(),
1432
1601
  maxIterations: isEvolveMode ? 200 : (this.options.maxIterations ?? Infinity),
@@ -1443,8 +1612,25 @@ export class HeadlessApp {
1443
1612
  cwdProvider: () => this.shellCwd || process.cwd(),
1444
1613
  hookDispatcher,
1445
1614
  hookApprovalRequester: (request) => this.requestHookApproval(request),
1615
+ toolResponseStore: this.sessionToolResponseStore,
1616
+ onLocalFileChange: async (paths, cwd) => {
1617
+ await this.workspaceCoordinator?.recordObservedPaths(paths, cwd);
1618
+ },
1619
+ toolCheckpoint: async (checkpoint) => {
1620
+ if (checkpoint.sessionId !== this.sessionId) {
1621
+ throw new Error('Tool checkpoint session identity does not match the active headless session.');
1622
+ }
1623
+ const merged = mergeToolCheckpointMessages(this.messages, checkpoint.messages);
1624
+ // Agent.run shares this array. Preserve that identity and any accepted
1625
+ // live correction sources while applying the canonical tool outcome.
1626
+ this.messages.splice(0, this.messages.length, ...merged);
1627
+ await this.sessionManager.save({ required: true });
1628
+ },
1446
1629
  });
1447
1630
  if (agent) {
1631
+ agent.restoreDiagnosticSnapshot(diagnostics);
1632
+ agent.restoreUsageSnapshot(this.sessionManager.getUsageSnapshot());
1633
+ agent.setTaskHandoff(this.taskHandoff);
1448
1634
  this.agentBindFailure = null;
1449
1635
  const bind = this.bindAgentStore(agent);
1450
1636
  this.pendingAgentBind = bind;
@@ -1467,12 +1653,20 @@ export class HeadlessApp {
1467
1653
  return agent;
1468
1654
  }
1469
1655
  async bindAgentStore(agent) {
1470
- await bindToolResponseStoreToSession(agent.toolResponseStore, agent.discoveredTools, process.cwd(), this.sessionId, {
1656
+ const storeConfig = {
1657
+ offloadEnabled: configStore.getOffloadEnabled(),
1658
+ inlineSizeThresholdTokens: configStore.getInlineSizeThresholdTokens(),
1659
+ inlineAgeTurns: configStore.getInlineAgeTurns(),
1660
+ toolCompression: configStore.getToolCompression(),
1661
+ };
1662
+ agent.toolResponseStore.setConfig(storeConfig);
1663
+ this.sessionToolResponseBinding ??= bindToolResponseStoreToSession(agent.toolResponseStore, agent.discoveredTools, this.environmentSessionCwd ?? process.cwd(), this.sessionId, {
1471
1664
  offloadEnabled: configStore.getOffloadEnabled(),
1472
1665
  inlineSizeThresholdTokens: configStore.getInlineSizeThresholdTokens(),
1473
1666
  inlineAgeTurns: configStore.getInlineAgeTurns(),
1474
1667
  toolCompression: configStore.getToolCompression(),
1475
1668
  }, this.options.sessionFile);
1669
+ await this.sessionToolResponseBinding;
1476
1670
  agent.refreshSystemPrompt();
1477
1671
  }
1478
1672
  async awaitAgentBind() {
@@ -1497,6 +1691,11 @@ export class HeadlessApp {
1497
1691
  */
1498
1692
  replaceAgent(next) {
1499
1693
  const previous = this.agent;
1694
+ const diagnostics = this.sessionManager.getDiagnostics();
1695
+ if (next?.getDiagnosticSessionId() === this.sessionId)
1696
+ next.restoreDiagnosticSnapshot(diagnostics);
1697
+ if (next?.getUsageSessionId() === this.sessionId)
1698
+ next.restoreUsageSnapshot(this.sessionManager.getUsageSnapshot());
1500
1699
  this.agent = next;
1501
1700
  if (previous && previous !== next)
1502
1701
  previous.dispose();
@@ -1542,6 +1741,145 @@ export class HeadlessApp {
1542
1741
  this.replaceAgent(nextAgent);
1543
1742
  this.hookLifecycle.ensureWatcher();
1544
1743
  }
1744
+ /** Activate a config profile without replacing the conversation or its storage. */
1745
+ async switchConfigProfileInPlace(name, duplicateSource) {
1746
+ if (process.env.ZTC_HOSTED === '1' || process.env.ZTC_HOST_RUNTIME?.trim()) {
1747
+ throw new Error('Hosted thread settings cannot be replaced by a local config profile.');
1748
+ }
1749
+ if (this.activeRunController) {
1750
+ throw new Error('Wait for the current turn to finish before switching profiles.');
1751
+ }
1752
+ await this.awaitAgentBind();
1753
+ const previousMcp = JSON.stringify(configStore.get().mcpServers ?? {});
1754
+ const previousColonyMcp = process.env.ZTC_COLONY_MCP;
1755
+ const previousColonyOwned = process.env.ZTC_COLONY_MCP_CONFIG_OWNED;
1756
+ let runtimeTouched = false;
1757
+ let mcpChanged = false;
1758
+ const reloadMcp = async () => {
1759
+ const { closeMcpServers, colonyOptIn } = await import('./mcp/registry.js');
1760
+ await closeMcpServers();
1761
+ const servers = configStore.get().mcpServers;
1762
+ if ((servers && Object.keys(servers).length > 0) || colonyOptIn()) {
1763
+ const { initMcpServers } = await import('./mcp/client.js');
1764
+ await initMcpServers(servers);
1765
+ }
1766
+ };
1767
+ const reloadSkills = async () => {
1768
+ const { invalidateSkillsCache, preloadSkillsManifest } = await import('./skills/registry.js');
1769
+ invalidateSkillsCache();
1770
+ await preloadSkillsManifest();
1771
+ };
1772
+ const rebuild = async () => {
1773
+ const next = this.createAgent({ allowUnavailableRouterAuth: true });
1774
+ try {
1775
+ if (next)
1776
+ await this.awaitAgentBind();
1777
+ }
1778
+ catch (error) {
1779
+ next?.dispose();
1780
+ throw error;
1781
+ }
1782
+ this.replaceAgent(next);
1783
+ this.hookLifecycle.ensureWatcher();
1784
+ };
1785
+ try {
1786
+ const activate = async () => {
1787
+ runtimeTouched = true;
1788
+ if (process.env.ZTC_COLONY_MCP_CONFIG_OWNED === '1') {
1789
+ delete process.env.ZTC_COLONY_MCP;
1790
+ delete process.env.ZTC_COLONY_MCP_CONFIG_OWNED;
1791
+ }
1792
+ if (configStore.get().mcp?.colonyAutoAttach && !process.env.ZTC_COLONY_MCP) {
1793
+ process.env.ZTC_COLONY_MCP = '1';
1794
+ process.env.ZTC_COLONY_MCP_CONFIG_OWNED = '1';
1795
+ }
1796
+ mcpChanged = previousMcp !== JSON.stringify(configStore.get().mcpServers ?? {})
1797
+ || previousColonyMcp !== process.env.ZTC_COLONY_MCP;
1798
+ if (mcpChanged)
1799
+ await reloadMcp();
1800
+ await reloadSkills();
1801
+ await rebuild();
1802
+ await this.sessionManager.save({ persistEmpty: true, required: true });
1803
+ };
1804
+ if (duplicateSource === undefined)
1805
+ await configStore.switchProfile(name, activate);
1806
+ else
1807
+ await configStore.duplicateProfile(name, duplicateSource, activate);
1808
+ }
1809
+ catch (error) {
1810
+ if (previousColonyMcp === undefined)
1811
+ delete process.env.ZTC_COLONY_MCP;
1812
+ else
1813
+ process.env.ZTC_COLONY_MCP = previousColonyMcp;
1814
+ if (previousColonyOwned === undefined)
1815
+ delete process.env.ZTC_COLONY_MCP_CONFIG_OWNED;
1816
+ else
1817
+ process.env.ZTC_COLONY_MCP_CONFIG_OWNED = previousColonyOwned;
1818
+ if (runtimeTouched) {
1819
+ try {
1820
+ if (mcpChanged)
1821
+ await reloadMcp();
1822
+ await reloadSkills();
1823
+ await rebuild();
1824
+ }
1825
+ catch (restoreError) {
1826
+ throw new Error('Profile switch failed: ' + error.message
1827
+ + '. Restoring the previous runtime also failed: ' + restoreError.message);
1828
+ }
1829
+ }
1830
+ throw error;
1831
+ }
1832
+ this.autoMirror?.setSuperuser(configStore.isSuperuser());
1833
+ if (!this.agent) {
1834
+ let notice = 'This profile has no usable provider yet. Configure one with /config or /auth.';
1835
+ try {
1836
+ configStore.resolveLLMBackendConfig({ cwd: this.shellCwd });
1837
+ }
1838
+ catch (error) {
1839
+ if (error instanceof RouterConfigurationError && error.needsSignIn) {
1840
+ notice = 'This profile needs a Router sign-in. Use /auth login to sign in for this profile; your session and conversation are retained.';
1841
+ }
1842
+ }
1843
+ this.emitPaneChildNotice(notice);
1844
+ }
1845
+ }
1846
+ async reloadAccountAgent() {
1847
+ if (this.turnAdmissionCount > 0 || this.activeRunController || this.runtimeMutationDone) {
1848
+ this.accountReloadPending = true;
1849
+ return;
1850
+ }
1851
+ this.reloadAgentInPlace();
1852
+ await this.awaitAgentBind();
1853
+ }
1854
+ async applyAccountOperation(body) {
1855
+ if (this.turnAdmissionCount > 0 || this.activeRunController || this.runtimeMutationDone)
1856
+ return { ok: false, blocked: true, error: 'An agent turn or tool is active; wait before switching accounts.' };
1857
+ let release;
1858
+ this.runtimeMutationDone = new Promise(resolve => { release = resolve; });
1859
+ let operationOpen = true;
1860
+ try {
1861
+ return await applyAccountOp(body, configStore, async () => {
1862
+ if (!operationOpen) {
1863
+ await this.reloadAccountAgent();
1864
+ return;
1865
+ }
1866
+ this.reloadAgentInPlace();
1867
+ await this.awaitAgentBind();
1868
+ });
1869
+ }
1870
+ finally {
1871
+ operationOpen = false;
1872
+ this.runtimeMutationDone = null;
1873
+ release();
1874
+ }
1875
+ }
1876
+ async finishTurnAdmission() {
1877
+ this.turnAdmissionCount--;
1878
+ if (this.turnAdmissionCount === 0 && this.accountReloadPending) {
1879
+ this.accountReloadPending = false;
1880
+ await this.reloadAccountAgent();
1881
+ }
1882
+ }
1545
1883
  /** Rebuild a backend invalidated by logout before either headless turn path. */
1546
1884
  async ensureAgentForTurn() {
1547
1885
  if (this.agent)
@@ -1596,10 +1934,11 @@ export class HeadlessApp {
1596
1934
  addMessage: (msg) => {
1597
1935
  capturedMessages.push(msg.content);
1598
1936
  this.messages.push({
1599
- id: `msg_${Date.now()}`,
1937
+ id: `msg_${randomUUID()}`,
1600
1938
  role: msg.role,
1601
1939
  content: msg.content,
1602
1940
  timestamp: new Date(),
1941
+ ...(msg.metadata ? { metadata: msg.metadata } : {}),
1603
1942
  });
1604
1943
  this.output({
1605
1944
  type: 'message',
@@ -1608,11 +1947,24 @@ export class HeadlessApp {
1608
1947
  });
1609
1948
  },
1610
1949
  clearMessages: () => { this.messages = []; },
1950
+ setMessages: (messages) => { this.messages = messages; },
1611
1951
  // /clear-only reset (never /compact): drop the R7 pinned task so a fresh
1612
1952
  // conversation re-pins its own first message instead of re-running the
1613
1953
  // pre-clear prompt. clearMessages above is shared with /compact and must
1614
1954
  // keep the pin, so the reset lives here.
1615
- clearTransientState: () => { this.agent?.clearPinnedTask(); },
1955
+ clearTransientState: () => {
1956
+ this.taskHandoff = null;
1957
+ this.restoredHandoffCorrections = [];
1958
+ this.agent?.setTaskHandoff(null);
1959
+ this.publishTaskHandoff();
1960
+ this.agent?.clearPinnedTask();
1961
+ if (this.agent) {
1962
+ this.agent.toolResponseStore.evidence.clear();
1963
+ const snapshot = this.agent.getEvidenceSnapshot();
1964
+ this.activityState = applyAgentEventToState(this.activityState, { type: 'evidence', sessionId: this.sessionId, snapshot });
1965
+ this.output({ type: 'evidence', timestamp: new Date().toISOString(), data: { session_id: this.sessionId, snapshot } });
1966
+ }
1967
+ },
1616
1968
  getMessages: () => this.messages,
1617
1969
  setAgentState: () => { },
1618
1970
  // Authoritative context for /context + /compact (matches the bar): the
@@ -1624,6 +1976,13 @@ export class HeadlessApp {
1624
1976
  // and /context use the reduced size.
1625
1977
  setContextUsage: (used) => { this.lastContextTokens = used; },
1626
1978
  reloadAgent: (options) => this.reloadAgentInPlace(options?.defer),
1979
+ switchConfigProfile: (name) => this.switchConfigProfileInPlace(name),
1980
+ duplicateProfile: async (name, source) => {
1981
+ await this.switchConfigProfileInPlace(name, source);
1982
+ return name;
1983
+ },
1984
+ applyAccountOperation: body => this.applyAccountOperation(body),
1985
+ getUsageSnapshot: () => this.sessionManager.getUsageSnapshot(),
1627
1986
  setRuntimeModelOverride: (provider, model) => {
1628
1987
  configStore.setCliOverride('provider', provider);
1629
1988
  configStore.setCliOverride('model', model);
@@ -1631,16 +1990,57 @@ export class HeadlessApp {
1631
1990
  },
1632
1991
  setDebug: () => { },
1633
1992
  getSessionId: () => this.sessionId,
1993
+ getSessionCwd: () => this.environmentSessionCwd ?? this.shellCwd,
1994
+ environmentAvailable: () => this.shellMode === 'real',
1995
+ isAgentRunning: () => this.activeRunController !== null,
1996
+ getEnvironment: () => this.environmentRecord,
1997
+ environmentChanged: async (name, returnCwd) => {
1998
+ if (returnCwd) {
1999
+ this.environmentReturnCwd = returnCwd;
2000
+ this.environmentSessionCwd ??= returnCwd;
2001
+ }
2002
+ if (name) {
2003
+ this.environmentRecord = await getEnvironment(name);
2004
+ this.environmentReleased = false;
2005
+ }
2006
+ else {
2007
+ const previous = this.environmentRecord;
2008
+ this.environmentRecord = null;
2009
+ this.shellCwd = previous && isEnvironmentWorkspacePath(previous.workspacePath, this.environmentReturnCwd) ? homedir() : this.environmentReturnCwd;
2010
+ }
2011
+ process.chdir(this.shellCwd);
2012
+ await this.sessionManager.save({ persistEmpty: true, required: true });
2013
+ },
1634
2014
  getExecutionContext: () => this.agent?.getExecutionContext(),
2015
+ getOwnPaneId: () => this.paneManager ? 0 : null,
2016
+ getPaneLabels: this.paneManager ? async (paneId = 0) => readPaneLabels(this.paneManager, paneId) : undefined,
2017
+ setPaneLabels: async (labels, paneId) => {
2018
+ if (paneId !== undefined && !this.paneManager)
2019
+ throw new Error('Pane mux is unavailable.');
2020
+ if (paneId === undefined) {
2021
+ this.sessionName = labels.manual_title;
2022
+ this.sessionLabels = { ...labels };
2023
+ }
2024
+ this.paneManager?.send({ type: 'set_pane_labels', pane_id: paneId ?? 0, labels });
2025
+ },
2026
+ checkpointSession: async () => {
2027
+ await this.sessionManager.save({ required: true, persistEmpty: true });
2028
+ return this.options.sessionFile ?? getSessionPathForCwd(process.cwd(), this.sessionId);
2029
+ },
2030
+ getDiagnostics: () => this.sessionManager.getDiagnostics(),
2031
+ getEvidenceSnapshot: () => this.agent?.getEvidenceSnapshot(),
1635
2032
  getSessionName: () => this.sessionName,
1636
2033
  setSessionName: (name) => {
1637
2034
  this.sessionName = name && name.length > 0 ? name : undefined;
2035
+ this.sessionLabels.manual_title = this.sessionName;
2036
+ this.paneManager?.send({ type: 'set_pane_labels', pane_id: 0, labels: this.sessionLabels });
1638
2037
  },
1639
2038
  retry: () => { },
1640
2039
  exit: () => { process.exit(0); },
1641
2040
  config: configStore,
1642
2041
  emulation: { list: loadEmulationProfiles, get: getEmulationProfile },
1643
2042
  setMode: (mode) => {
2043
+ this.sessionManager.recordHostDiagnostic('mode', { mode, previousMode: this.currentMode, source: 'host_mode_change' });
1644
2044
  this.currentMode = mode;
1645
2045
  this.hookLifecycle.onModeChange();
1646
2046
  },
@@ -1677,7 +2077,7 @@ export class HeadlessApp {
1677
2077
  ...autoWidgets,
1678
2078
  embedded: this.embeddedController || undefined,
1679
2079
  bench: this.benchController || undefined,
1680
- getInputMode: () => 'queue',
2080
+ getInputMode: () => 'steer',
1681
2081
  setInputMode: () => { },
1682
2082
  triggerAgentTurn: () => {
1683
2083
  agentTurnPromise = this.runAgentOnCurrentMessages();
@@ -1699,6 +2099,8 @@ export class HeadlessApp {
1699
2099
  return denial;
1700
2100
  }
1701
2101
  // Global command lookup first — global commands may have richer behavior than mode handlers
2102
+ if (cmd.toLowerCase() === 'model' && (this.turnAdmissionCount > 0 || this.activeRunController || this.runtimeMutationDone))
2103
+ return 'Cannot switch model while an active agent turn or account change is running.';
1702
2104
  const command = commands.find(c => c.name === cmd.toLowerCase());
1703
2105
  if (command) {
1704
2106
  try {
@@ -1739,6 +2141,21 @@ export class HeadlessApp {
1739
2141
  * Run a single prompt through the agent
1740
2142
  */
1741
2143
  async runPrompt(prompt, opts = {}) {
2144
+ if (routeUserInput(prompt).type === 'command') {
2145
+ await this.runPromptAdmitted(prompt, opts);
2146
+ return;
2147
+ }
2148
+ if (this.runtimeMutationDone)
2149
+ await this.runtimeMutationDone;
2150
+ this.turnAdmissionCount++;
2151
+ try {
2152
+ await this.runPromptAdmitted(prompt, opts);
2153
+ }
2154
+ finally {
2155
+ await this.finishTurnAdmission();
2156
+ }
2157
+ }
2158
+ async runPromptAdmitted(prompt, opts = {}) {
1742
2159
  // Route input: slash command or regular message
1743
2160
  const route = routeUserInput(prompt);
1744
2161
  if (route.type === 'command') {
@@ -1753,6 +2170,11 @@ export class HeadlessApp {
1753
2170
  }
1754
2171
  return;
1755
2172
  }
2173
+ if (this.environmentRecord?.status === 'paused' || environmentOperationInProgress(this.sessionId)) {
2174
+ this.hadHeadlessError = true;
2175
+ this.output({ type: 'error', timestamp: new Date().toISOString(), data: { message: this.environmentRecord?.status === 'paused' ? 'Environment paused. Use /environment resume before starting an agent turn.' : 'Wait for the environment operation to finish before starting an agent turn.' } });
2176
+ return;
2177
+ }
1756
2178
  if (!await this.ensureAgentForTurn())
1757
2179
  return;
1758
2180
  if (route.type === 'shell') {
@@ -1770,12 +2192,19 @@ export class HeadlessApp {
1770
2192
  }
1771
2193
  // Add user message
1772
2194
  const userMsg = {
1773
- id: `msg_${Date.now()}`,
2195
+ id: `msg_${randomUUID()}`,
1774
2196
  role: 'user',
1775
2197
  content: prompt,
1776
2198
  timestamp: new Date(),
1777
2199
  };
1778
2200
  this.messages.push(userMsg);
2201
+ if (!opts.automaticGoalTurn) {
2202
+ const sustained = goalManager.adoptUserIntent(userMsg, { configuredMaxTurns: configStore.get().goalMaxTurns });
2203
+ if (sustained) {
2204
+ this.agent?.setPinnedTask(goalPinTask(sustained));
2205
+ this.emitGoalNote(`Sustained goal recorded: ${sustained.objective}`);
2206
+ }
2207
+ }
1779
2208
  this.output({
1780
2209
  type: 'message',
1781
2210
  timestamp: new Date().toISOString(),
@@ -1785,6 +2214,7 @@ export class HeadlessApp {
1785
2214
  try {
1786
2215
  this.ensureAgentMatchesMode();
1787
2216
  await this.awaitAgentBind();
2217
+ await this.sessionManager.save({ required: true });
1788
2218
  cleanup = this.subscribeAgentEvents();
1789
2219
  }
1790
2220
  catch (error) {
@@ -1844,11 +2274,12 @@ export class HeadlessApp {
1844
2274
  // `automatic`); a user turn taken while a goal is active resets it instead.
1845
2275
  this.lastTurnAutomaticGoal = opts.automaticGoalTurn === true;
1846
2276
  // Add assistant response
1847
- if (response.content) {
2277
+ if (response.content || response.reasoning?.trim()) {
1848
2278
  const assistantMsg = {
1849
- id: `msg_${Date.now()}`,
2279
+ id: `msg_${randomUUID()}`,
1850
2280
  role: 'assistant',
1851
2281
  content: response.content,
2282
+ ...(response.nativeContent ? { nativeContent: response.nativeContent, nativeModel: response.nativeModel, nativeContextId: response.nativeContextId } : {}),
1852
2283
  timestamp: new Date(),
1853
2284
  // Reasoning models expect their trace replayed with the assistant
1854
2285
  // message on later turns; dropping it strips the history.
@@ -1993,7 +2424,7 @@ export class HeadlessApp {
1993
2424
  return;
1994
2425
  }
1995
2426
  const userMsg = {
1996
- id: `msg_${Date.now()}`,
2427
+ id: `msg_${randomUUID()}`,
1997
2428
  role: 'user',
1998
2429
  content: prompt,
1999
2430
  timestamp: new Date(),
@@ -2029,7 +2460,7 @@ export class HeadlessApp {
2029
2460
  }
2030
2461
  const content = toolResult.content;
2031
2462
  const assistantMsg = {
2032
- id: `msg_${Date.now()}`,
2463
+ id: `msg_${randomUUID()}`,
2033
2464
  role: 'assistant',
2034
2465
  content,
2035
2466
  timestamp: new Date(),
@@ -2082,6 +2513,22 @@ export class HeadlessApp {
2082
2513
  * Used when commitPlan already added messages and we just need the agent to act on them.
2083
2514
  */
2084
2515
  async runAgentOnCurrentMessages() {
2516
+ if (this.environmentRecord?.status === 'paused' || environmentOperationInProgress(this.sessionId)) {
2517
+ this.hadHeadlessError = true;
2518
+ this.output({ type: 'error', timestamp: new Date().toISOString(), data: { message: this.environmentRecord?.status === 'paused' ? 'Environment paused. Use /environment resume before starting an agent turn.' : 'Wait for the environment operation to finish before starting an agent turn.' } });
2519
+ return;
2520
+ }
2521
+ if (this.runtimeMutationDone)
2522
+ await this.runtimeMutationDone;
2523
+ this.turnAdmissionCount++;
2524
+ try {
2525
+ await this.runAgentOnCurrentMessagesAdmitted();
2526
+ }
2527
+ finally {
2528
+ await this.finishTurnAdmission();
2529
+ }
2530
+ }
2531
+ async runAgentOnCurrentMessagesAdmitted() {
2085
2532
  if (!await this.ensureAgentForTurn())
2086
2533
  return;
2087
2534
  let cleanup;
@@ -2145,9 +2592,10 @@ export class HeadlessApp {
2145
2592
  }
2146
2593
  if (response.content) {
2147
2594
  const assistantMsg = {
2148
- id: `msg_${Date.now()}`,
2595
+ id: `msg_${randomUUID()}`,
2149
2596
  role: 'assistant',
2150
2597
  content: response.content,
2598
+ ...(response.nativeContent ? { nativeContent: response.nativeContent, nativeModel: response.nativeModel, nativeContextId: response.nativeContextId } : {}),
2151
2599
  timestamp: new Date(),
2152
2600
  // Reasoning models expect their trace replayed with the assistant
2153
2601
  // message on later turns; dropping it strips the history.
@@ -2228,6 +2676,7 @@ export class HeadlessApp {
2228
2676
  * Save the current session
2229
2677
  */
2230
2678
  async saveCurrentSession() {
2679
+ this.publishCurrentGoal();
2231
2680
  try {
2232
2681
  await this.sessionManager.save({ required: !!this.options.sessionFile });
2233
2682
  }
@@ -2347,6 +2796,11 @@ export class HeadlessApp {
2347
2796
  * Shut down background agents and batch orchestrator gracefully before exiting.
2348
2797
  */
2349
2798
  async shutdownBackgroundAgents() {
2799
+ this.startupCatalogController.abort(new Error('Runtime stopped'));
2800
+ if (this.environmentRecord && !this.environmentReleased) {
2801
+ await releaseEnvironment(this.environmentRecord.name, this.sessionId);
2802
+ this.environmentReleased = true;
2803
+ }
2350
2804
  await shutdownCommandJobs(this.sessionId);
2351
2805
  this.commandJobsUnsubscribe?.();
2352
2806
  this.commandJobsUnsubscribe = undefined;
@@ -2402,11 +2856,15 @@ export class HeadlessApp {
2402
2856
  }
2403
2857
  this.embeddedController = null;
2404
2858
  this.benchController = null;
2859
+ // Capture the producing agent before retiring its backend and identity.
2860
+ this.sessionManager.getDiagnostics();
2405
2861
  // Reap the live agent's backend (a codex child now outlives each turn).
2406
2862
  this.agent?.dispose();
2407
2863
  // The worker executor holds a SEPARATE backend instance — reap it too.
2408
2864
  clearWorkerExecutor();
2409
2865
  this.agent = null;
2866
+ await this.workspaceCoordinator?.close();
2867
+ this.workspaceCoordinator = null;
2410
2868
  }
2411
2869
  outputHeadlessShellMode() {
2412
2870
  this.output({
@@ -2541,6 +2999,7 @@ export class HeadlessApp {
2541
2999
  switch (classified.kind) {
2542
3000
  case 'prompt':
2543
3001
  assertPromptWithinLimit(classified.text);
3002
+ this.dismissPendingQuestion();
2544
3003
  await this.runInteractivePrompt(classified.text);
2545
3004
  this.outputPromptComplete();
2546
3005
  break;
@@ -2575,8 +3034,24 @@ export class HeadlessApp {
2575
3034
  this.outputPromptComplete();
2576
3035
  break;
2577
3036
  case 'cancel':
3037
+ this.dismissPendingQuestion();
2578
3038
  this.cancelActiveRun();
2579
3039
  break;
3040
+ case 'ask_user_answer':
3041
+ if (this.pendingQuestionId !== classified.questionId) {
3042
+ this.emitPaneChildNotice('Stale question answer ignored. No prompt was submitted.');
3043
+ break;
3044
+ }
3045
+ assertPromptWithinLimit(classified.answer);
3046
+ this.dismissPendingQuestion();
3047
+ if (classified.answer === '__SKIP__') {
3048
+ this.messages.push({ id: randomUUID(), role: 'user', content: '__SKIP__', timestamp: new Date() });
3049
+ }
3050
+ else {
3051
+ await this.runInteractivePrompt(classified.answer);
3052
+ this.outputPromptComplete();
3053
+ }
3054
+ break;
2580
3055
  case 'unknown_cmd':
2581
3056
  this.emitPaneChildNotice(`Unknown pane command "${classified.cmd}" — ignored.`);
2582
3057
  break;
@@ -2663,7 +3138,7 @@ export class HeadlessApp {
2663
3138
  async runSingle(prompt) {
2664
3139
  this.turnStartGoal = snapshotGoalForTurn();
2665
3140
  await this.runPrompt(prompt);
2666
- this.accountGoalFinalTurnAfterPrompt();
3141
+ await this.accountGoalFinalTurnAfterPrompt();
2667
3142
  // If the prompt set/resumed an autonomous /goal, drive it to a terminal
2668
3143
  // state before exiting (no-op when there is no armed goal).
2669
3144
  await this.runGoalLoopIfArmed();
@@ -2736,10 +3211,22 @@ export class HeadlessApp {
2736
3211
  * auto-starts; the user runs /goal resume. Shared by both load branches.
2737
3212
  */
2738
3213
  applyResumedSession(session, restoreRuntimeMode = true) {
3214
+ if (!session.hosted_settings)
3215
+ configStore.restoreSessionSelection(session.metadata);
3216
+ this.sessionToolResponseStore = new ToolResponseStore();
3217
+ this.sessionToolResponseBinding = null;
2739
3218
  this.sessionId = session.metadata.id;
2740
3219
  this.sessionManager.setSessionId(this.sessionId);
3220
+ this.sessionManager.restoreDiagnostics(session.diagnostics);
3221
+ this.sessionManager.restoreUsageSnapshot(session.usage);
2741
3222
  this.sessionName = session.metadata.name;
3223
+ this.sessionLabels = { ...session.metadata.labels, manual_title: this.sessionName };
2742
3224
  this.messages = session.messages;
3225
+ this.taskHandoff = session.task_handoff ? projectTaskHandoff(session.task_handoff) : null;
3226
+ this.restoredHandoffCorrections = session.task_handoff?.corrections.map(correction => ({
3227
+ ...correction, deliveryState: correction.deliveryState === 'pending' ? 'unknown' : correction.deliveryState,
3228
+ })) ?? [];
3229
+ this.publishTaskHandoff();
2743
3230
  goalManager.hydrate(session.metadata.goal);
2744
3231
  if (!restoreRuntimeMode)
2745
3232
  return true;
@@ -2777,7 +3264,18 @@ export class HeadlessApp {
2777
3264
  * makes a second call in the same terminal episode a no-op, so this cannot
2778
3265
  * double-count with the goal loop's own accounting.
2779
3266
  */
2780
- accountGoalFinalTurnAfterPrompt() {
3267
+ async accountGoalFinalTurnAfterPrompt() {
3268
+ const initial = goalManager.accountSustainedFirstTurn({ turnTokens: this.lastTurnTokens,
3269
+ ...(detectGoalBlocked(this.lastTurnText) ? { blockedReason: goalBlockedReason(this.lastTurnText) } : {}),
3270
+ toolCallCount: this.lastTurnToolCalls, hadTextualOutput: this.lastTurnHadTextualOutput,
3271
+ automaticGoalTurn: false, turnSummary: this.lastTurnText, ...this.lastTurnToolOutcome,
3272
+ evidence: this.agent?.getEvidenceSnapshot() });
3273
+ if (initial) {
3274
+ if (initial.kind !== 'idle')
3275
+ this.emitGoalNote(initial.note);
3276
+ await this.saveCurrentSession();
3277
+ return;
3278
+ }
2781
3279
  if (!shouldAccountFinalTurn(this.turnStartGoal, goalManager.get()))
2782
3280
  return;
2783
3281
  const accounted = goalManager.accountFinalTurn({
@@ -2785,8 +3283,10 @@ export class HeadlessApp {
2785
3283
  });
2786
3284
  // Only report when this call did the accounting, so the tallies include the
2787
3285
  // final turn (the `/goal complete` command message fires before it is known).
2788
- if (accounted)
3286
+ if (accounted) {
2789
3287
  this.emitGoalNote(goalFinalNote(accounted));
3288
+ await this.saveCurrentSession();
3289
+ }
2790
3290
  }
2791
3291
  /**
2792
3292
  * Run an interactive prompt, then drive any /goal it armed to a terminal state
@@ -2798,7 +3298,7 @@ export class HeadlessApp {
2798
3298
  async runInteractivePrompt(prompt) {
2799
3299
  this.turnStartGoal = snapshotGoalForTurn();
2800
3300
  await this.runPrompt(prompt);
2801
- this.accountGoalFinalTurnAfterPrompt();
3301
+ await this.accountGoalFinalTurnAfterPrompt();
2802
3302
  await this.runGoalLoopIfArmed();
2803
3303
  }
2804
3304
  /**
@@ -2847,7 +3347,7 @@ export class HeadlessApp {
2847
3347
  // them in would attribute stale numbers to this turn.
2848
3348
  if (this.goalTurnCancelled) {
2849
3349
  this.goalTurnCancelled = false;
2850
- goalManager.pause();
3350
+ goalManager.pause('user_cancelled');
2851
3351
  this.emitGoalNote(`⏸ Goal paused (cancelled): "${goal.objective}". /goal resume to continue.`);
2852
3352
  break;
2853
3353
  }
@@ -2868,7 +3368,7 @@ export class HeadlessApp {
2868
3368
  break;
2869
3369
  }
2870
3370
  // Provider-agnostic completion: the turn ended with the GOAL_COMPLETE sentinel.
2871
- if (detectGoalCompletion(this.lastTurnText)) {
3371
+ if (!afterTurn.plateau && !afterTurn.sustained && detectGoalCompletion(this.lastTurnText)) {
2872
3372
  const done = goalManager.complete({ turnTokens: this.lastTurnTokens });
2873
3373
  if (done)
2874
3374
  this.emitGoalNote(goalFinalNote(done));
@@ -2877,19 +3377,21 @@ export class HeadlessApp {
2877
3377
  // Provider-agnostic blocker: the turn ended with the GOAL_BLOCKED sentinel.
2878
3378
  // Terminal like completion — without it the loop keeps buying turns from an
2879
3379
  // agent that has already said it cannot get there.
2880
- if (detectGoalBlocked(this.lastTurnText)) {
3380
+ if (!afterTurn.plateau && !afterTurn.sustained && detectGoalBlocked(this.lastTurnText)) {
2881
3381
  const blocked = goalManager.block(goalBlockedReason(this.lastTurnText), { turnTokens: this.lastTurnTokens });
2882
3382
  if (blocked)
2883
3383
  this.emitGoalNote(goalFinalNote(blocked));
2884
3384
  break;
2885
3385
  }
2886
3386
  const decision = goalManager.onTurnComplete({
3387
+ ...(detectGoalBlocked(this.lastTurnText) ? { blockedReason: goalBlockedReason(this.lastTurnText) } : {}),
2887
3388
  turnTokens: this.lastTurnTokens,
2888
3389
  toolCallCount: this.lastTurnToolCalls,
2889
3390
  hadTextualOutput: this.lastTurnHadTextualOutput,
2890
3391
  automaticGoalTurn: this.lastTurnAutomaticGoal,
2891
3392
  turnSummary: this.lastTurnText,
2892
3393
  ...this.lastTurnToolOutcome,
3394
+ evidence: this.agent?.getEvidenceSnapshot(),
2893
3395
  });
2894
3396
  if (decision.kind === 'continue') {
2895
3397
  this.emitGoalNote(decision.note);
@@ -2907,7 +3409,13 @@ export class HeadlessApp {
2907
3409
  // goal as finished — not stale-'active', which would re-drive a done goal.
2908
3410
  await this.saveCurrentSession();
2909
3411
  }
3412
+ publishCurrentGoal() {
3413
+ const data = { sessionId: this.sessionId, goal: projectGoal(goalManager.get()) };
3414
+ this.output({ type: 'goal', timestamp: new Date().toISOString(), data });
3415
+ this.autoMirror?.publish({ type: 'goal', data });
3416
+ }
2910
3417
  emitGoalNote(content) {
3418
+ this.publishCurrentGoal();
2911
3419
  this.output({
2912
3420
  type: 'message',
2913
3421
  timestamp: new Date().toISOString(),
@@ -3050,7 +3558,7 @@ export async function runHeadless(options, initialPrompt) {
3050
3558
  initialized = await app.init();
3051
3559
  }
3052
3560
  catch (error) {
3053
- const message = error instanceof RouterConfigurationError ? error.message : 'Headless initialization failed.';
3561
+ const message = error instanceof RouterConfigurationError || error instanceof EnvironmentRecoveryError ? error.message : 'Headless initialization failed.';
3054
3562
  if (options.json) {
3055
3563
  process.stdout.write(`${JSON.stringify({
3056
3564
  type: 'error',