mixdog 0.9.151 → 0.9.153

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 (307) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +108 -68
  4. package/package.json +17 -4
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/scripts/tool-stress.mjs +0 -2
  8. package/src/agents/front-worker/AGENT.md +13 -0
  9. package/src/agents/front-worker/agent.json +6 -0
  10. package/src/agents/heavy-worker/AGENT.md +10 -11
  11. package/src/agents/heavy-worker/agent.json +1 -1
  12. package/src/agents/maintainer/AGENT.md +0 -2
  13. package/src/agents/reviewer/AGENT.md +9 -13
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/agents/security/AGENT.md +14 -0
  16. package/src/agents/security/agent.json +6 -0
  17. package/src/agents/worker/AGENT.md +8 -8
  18. package/src/agents/worker/agent.json +1 -1
  19. package/src/agents/writer/AGENT.md +14 -0
  20. package/src/agents/writer/agent.json +6 -0
  21. package/src/defaults/skills/setup/SKILL.md +18 -27
  22. package/src/headless-exec.mjs +3 -10
  23. package/src/headless-exec.test.mjs +2 -7
  24. package/src/rules/agent/00-common.md +4 -7
  25. package/src/rules/lead/01-general.md +0 -3
  26. package/src/rules/shared/10-tool-workflow.md +10 -2
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  31. package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
  32. package/src/runtime/agent/orchestrator/config.mjs +13 -68
  33. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
  34. package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
  35. package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
  36. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
  37. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
  38. package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
  39. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  43. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
  44. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
  45. package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
  47. package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
  48. package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
  49. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  50. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
  51. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
  52. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
  60. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  64. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
  66. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
  67. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
  68. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
  69. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
  70. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
  71. package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
  72. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  73. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
  74. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
  76. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
  77. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
  78. package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
  79. package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  81. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
  82. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  83. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
  84. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  85. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  86. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
  87. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  90. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
  92. package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
  93. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
  94. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
  96. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
  97. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
  98. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  100. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
  101. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
  102. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
  103. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
  104. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
  105. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  106. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
  107. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  108. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  109. package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
  110. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  111. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  112. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
  115. package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
  116. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
  119. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
  122. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
  123. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
  124. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
  125. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
  126. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  129. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
  130. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
  132. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
  135. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
  136. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
  137. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  138. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  139. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
  140. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  141. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  142. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  143. package/src/runtime/bridge-clients.test.mjs +827 -0
  144. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  145. package/src/runtime/browser-bridge/client.mjs +152 -0
  146. package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
  147. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  148. package/src/runtime/computer-bridge/client.mjs +231 -0
  149. package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
  150. package/src/runtime/media/store.mjs +18 -8
  151. package/src/runtime/media/store.test.mjs +14 -0
  152. package/src/runtime/memory/index.mjs +1 -1
  153. package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
  154. package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
  155. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  156. package/src/runtime/office/assurance.mjs +833 -0
  157. package/src/runtime/office/benchmark.mjs +496 -0
  158. package/src/runtime/office/capabilities.mjs +551 -0
  159. package/src/runtime/office/com-adapter.mjs +283 -0
  160. package/src/runtime/office/content-model.mjs +183 -0
  161. package/src/runtime/office/contract-benchmark.mjs +105 -0
  162. package/src/runtime/office/design-library.mjs +1562 -0
  163. package/src/runtime/office/design-system.mjs +1966 -0
  164. package/src/runtime/office/index.mjs +2413 -0
  165. package/src/runtime/office/journal.mjs +82 -0
  166. package/src/runtime/office/office-assurance.test.mjs +371 -0
  167. package/src/runtime/office/office-com-host.ps1 +3781 -0
  168. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  169. package/src/runtime/office/office-live-runtime.test.mjs +819 -0
  170. package/src/runtime/office/office-runtime.test.mjs +2070 -0
  171. package/src/runtime/office/ooxml-validator.mjs +271 -0
  172. package/src/runtime/office/pagination.mjs +132 -0
  173. package/src/runtime/office/pdf-adapter.mjs +591 -0
  174. package/src/runtime/office/pdf-analysis.mjs +372 -0
  175. package/src/runtime/office/pdf-render.mjs +167 -0
  176. package/src/runtime/office/pdf-security.mjs +63 -0
  177. package/src/runtime/office/polish-benchmark.mjs +190 -0
  178. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  179. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  180. package/src/runtime/office/quality-pipeline.mjs +136 -0
  181. package/src/runtime/office/tabular.mjs +302 -0
  182. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  183. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  184. package/src/runtime/office/tool-defs.mjs +113 -0
  185. package/src/runtime/office/visual-diff.mjs +89 -0
  186. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  187. package/src/runtime/office/xlsx-contract.mjs +147 -0
  188. package/src/runtime/shared/agent-route-config.mjs +9 -0
  189. package/src/runtime/shared/child-spawn-gate.mjs +17 -0
  190. package/src/runtime/shared/child-spawn-remote.mjs +69 -7
  191. package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
  192. package/src/runtime/shared/pristine-execution-contract.json +2 -1
  193. package/src/runtime/shared/provider-api-key.mjs +1 -0
  194. package/src/runtime/shared/session-runtime-health.mjs +91 -0
  195. package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
  196. package/src/runtime/shared/skill-document.mjs +86 -0
  197. package/src/runtime/shared/skill-document.test.mjs +81 -0
  198. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  199. package/src/runtime/shared/tool-surface.mjs +27 -0
  200. package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
  201. package/src/runtime/shared/turn-snapshot.mjs +32 -9
  202. package/src/runtime/shared/user-cwd.mjs +33 -14
  203. package/src/runtime/shared/user-cwd.test.mjs +43 -0
  204. package/src/session-runtime/agent-disable.test.mjs +20 -1
  205. package/src/session-runtime/context-status.mjs +9 -3
  206. package/src/session-runtime/cwd-plugins.mjs +26 -26
  207. package/src/session-runtime/cwd-plugins.test.mjs +75 -0
  208. package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
  209. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  210. package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
  211. package/src/session-runtime/global-extensions.mjs +26 -0
  212. package/src/session-runtime/global-extensions.test.mjs +28 -0
  213. package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
  214. package/src/session-runtime/goal-runtime.mjs +1141 -0
  215. package/src/session-runtime/goal-runtime.test.mjs +645 -0
  216. package/src/session-runtime/lifecycle-api.mjs +58 -11
  217. package/src/session-runtime/lifecycle-api.test.mjs +183 -0
  218. package/src/session-runtime/mcp-glue.mjs +49 -47
  219. package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
  220. package/src/session-runtime/plugin-mcp.mjs +96 -22
  221. package/src/session-runtime/prewarm.mjs +1 -10
  222. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  223. package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
  224. package/src/session-runtime/resource-api-global.test.mjs +109 -0
  225. package/src/session-runtime/resource-api.mjs +168 -116
  226. package/src/session-runtime/runtime-core.mjs +208 -29
  227. package/src/session-runtime/runtime-tunables.mjs +6 -8
  228. package/src/session-runtime/session-lifecycle.mjs +34 -12
  229. package/src/session-runtime/session-title.mjs +2 -2
  230. package/src/session-runtime/session-turn-api.mjs +37 -25
  231. package/src/session-runtime/skills-api.mjs +79 -25
  232. package/src/session-runtime/skills-api.test.mjs +99 -0
  233. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  234. package/src/session-runtime/tool-catalog-schema.mjs +0 -5
  235. package/src/session-runtime/tool-catalog.mjs +42 -112
  236. package/src/session-runtime/tool-defs.mjs +6 -1
  237. package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
  238. package/src/session-runtime/tool-surface.mjs +30 -8
  239. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  240. package/src/session-runtime/workflow.mjs +8 -6
  241. package/src/standalone/agent-dispatch-broker.mjs +5 -9
  242. package/src/standalone/agent-tool/helpers.mjs +2 -2
  243. package/src/standalone/agent-tool/job-views.mjs +3 -1
  244. package/src/standalone/agent-tool/notify.mjs +11 -9
  245. package/src/standalone/agent-tool/notify.test.mjs +37 -0
  246. package/src/standalone/agent-tool/render.mjs +3 -0
  247. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  248. package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
  249. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  250. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  251. package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
  252. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  253. package/src/standalone/agent-tool.mjs +57 -10
  254. package/src/standalone/daemon.mjs +113 -15
  255. package/src/standalone/hook-bus/config.mjs +1 -0
  256. package/src/standalone/plugin-admin.mjs +17 -0
  257. package/src/standalone/plugin-admin.test.mjs +7 -0
  258. package/src/standalone/provider-admin.mjs +1 -0
  259. package/src/standalone/session-protocol.mjs +5 -0
  260. package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
  261. package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
  262. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
  263. package/src/standalone/session-runtime-host-factory.mjs +17 -0
  264. package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
  265. package/src/standalone/session-runtime-host-health.test.mjs +5 -0
  266. package/src/standalone/session-runtime-host.mjs +709 -58
  267. package/src/standalone/session-runtime-inline-host.mjs +258 -0
  268. package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
  269. package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
  270. package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
  271. package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
  272. package/src/standalone/session-runtime-shard-router.mjs +149 -0
  273. package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
  274. package/src/standalone/session-runtime-worker.mjs +302 -26
  275. package/src/standalone/session-service-agent-tree.test.mjs +984 -0
  276. package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
  277. package/src/standalone/session-service.mjs +711 -11
  278. package/src/tui/app/app-view.jsx +1 -0
  279. package/src/tui/app/core-memory-picker.mjs +2 -0
  280. package/src/tui/app/extension-pickers.mjs +1 -1
  281. package/src/tui/app/maintenance-pickers.mjs +3 -3
  282. package/src/tui/app/model-options.mjs +2 -0
  283. package/src/tui/app/model-picker.mjs +14 -4
  284. package/src/tui/app/panel-handoff.test.mjs +311 -0
  285. package/src/tui/app/project-picker.mjs +1 -0
  286. package/src/tui/app/route-pickers.mjs +21 -6
  287. package/src/tui/app/settings-picker.mjs +26 -3
  288. package/src/tui/app/slash-commands.mjs +1 -0
  289. package/src/tui/app/slash-dispatch.mjs +73 -22
  290. package/src/tui/app/theme-effort-pickers.mjs +7 -2
  291. package/src/tui/components/Picker.jsx +10 -1
  292. package/src/tui/dist/index.mjs +187 -45
  293. package/src/tui/lib/voice-setup.mjs +4 -1
  294. package/src/tui/session/agent-message-source.test.mjs +37 -0
  295. package/src/tui/session/completion-card-restore.test.mjs +50 -0
  296. package/src/tui/session/context-state.mjs +3 -13
  297. package/src/tui/session/goal-continuation.mjs +141 -0
  298. package/src/tui/session/goal-continuation.test.mjs +203 -0
  299. package/src/tui/session/queue-helpers.mjs +5 -2
  300. package/src/tui/session/session-action-surface.test.mjs +190 -0
  301. package/src/tui/session/session-api-ext.mjs +48 -8
  302. package/src/tui/session/session-api.mjs +73 -1
  303. package/src/tui/session/session-flow.mjs +31 -1
  304. package/src/tui/session/turn.mjs +88 -15
  305. package/src/tui/session-local.mjs +39 -3
  306. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  307. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
@@ -0,0 +1,258 @@
1
+ /**
2
+ * In-process session runtime host.
3
+ *
4
+ * Sessions remain independent async actors, but share the daemon's V8 isolate
5
+ * and ESM module cache. Native helpers and explicitly bounded worker pools keep
6
+ * CPU-heavy parsing/search work off the daemon loop.
7
+ */
8
+ export function createInlineSessionRuntimeHost({
9
+ cwd = process.cwd(),
10
+ log = () => {},
11
+ loadLocalModule = () => import('../tui/session-local.mjs'),
12
+ loadAgentGraph = () => Promise.all([
13
+ import('../runtime/agent/orchestrator/config.mjs'),
14
+ import('../runtime/agent/orchestrator/providers/registry.mjs'),
15
+ import('../runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs'),
16
+ ]).then(([config, registry, dispatch]) => ({ config, registry, dispatch })),
17
+ executeAgentControl = null,
18
+ } = {}) {
19
+ const records = new Map();
20
+ const recordsBySessionId = new Map();
21
+ const agentDispatchers = new Map();
22
+ const agentDispatchRuns = new Map();
23
+ let nextRuntimeId = 0;
24
+ let closed = false;
25
+ let localModulePromise = null;
26
+ let prewarmPromise = null;
27
+ let agentGraphPromise = null;
28
+ let preparedProviderSignature = null;
29
+ let providerPreparePromise = null;
30
+
31
+ function localModule() {
32
+ localModulePromise ??= Promise.resolve().then(loadLocalModule).catch((error) => {
33
+ localModulePromise = null;
34
+ throw error;
35
+ });
36
+ return localModulePromise;
37
+ }
38
+
39
+ function agentGraph() {
40
+ agentGraphPromise ??= Promise.resolve().then(loadAgentGraph).then(
41
+ (graph) => graph,
42
+ (error) => {
43
+ agentGraphPromise = null;
44
+ throw error;
45
+ },
46
+ );
47
+ return agentGraphPromise;
48
+ }
49
+
50
+ async function prepareAgentProviders() {
51
+ const { config, registry } = await agentGraph();
52
+ const providers = config.loadConfig()?.providers || {};
53
+ let signature = null;
54
+ try { signature = JSON.stringify(providers); } catch { /* re-prepare each call */ }
55
+ if (signature !== null && preparedProviderSignature === signature) return;
56
+ if (providerPreparePromise) {
57
+ await providerPreparePromise;
58
+ if (signature !== null && preparedProviderSignature === signature) return;
59
+ return prepareAgentProviders();
60
+ }
61
+ const pending = Promise.resolve()
62
+ .then(() => registry.initProviders(providers))
63
+ .then(() => { preparedProviderSignature = signature; });
64
+ const tracked = pending.finally(() => {
65
+ if (providerPreparePromise === tracked) providerPreparePromise = null;
66
+ });
67
+ providerPreparePromise = tracked;
68
+ await tracked;
69
+ }
70
+
71
+ function forget(record) {
72
+ if (!record || !records.delete(record.id)) return;
73
+ for (const [sessionId, owner] of recordsBySessionId) {
74
+ if (owner === record) recordsBySessionId.delete(sessionId);
75
+ }
76
+ }
77
+
78
+ function ownerRuntime(sessionId) {
79
+ const id = String(sessionId || '').trim();
80
+ const known = recordsBySessionId.get(id);
81
+ if (known) return known.runtime;
82
+ for (const record of records.values()) {
83
+ const current = String(record.runtime.getState?.()?.sessionId || record.runtime.id || '');
84
+ if (!current || current !== id) continue;
85
+ recordsBySessionId.set(id, record);
86
+ return record.runtime;
87
+ }
88
+ return null;
89
+ }
90
+
91
+ async function create(options = {}) {
92
+ if (closed) throw new Error('session runtime host is closed');
93
+ const module = await localModule();
94
+ const runtime = await module.createLocalSessionRuntime({
95
+ ...options,
96
+ ...(options.cwd ? {} : { cwd }),
97
+ ...(typeof executeAgentControl === 'function' ? { executeAgentControl } : {}),
98
+ });
99
+ const record = {
100
+ id: `inline-${process.pid}-${++nextRuntimeId}`,
101
+ runtime,
102
+ };
103
+ records.set(record.id, record);
104
+ const hintedSessionId = String(options.sessionId || '').trim();
105
+ if (hintedSessionId) recordsBySessionId.set(hintedSessionId, record);
106
+
107
+ const originalDispose = typeof runtime.dispose === 'function'
108
+ ? runtime.dispose.bind(runtime)
109
+ : null;
110
+ runtime.dispose = async (...args) => {
111
+ try {
112
+ return await originalDispose?.(...args);
113
+ } finally {
114
+ forget(record);
115
+ }
116
+ };
117
+ return runtime;
118
+ }
119
+
120
+ function prewarm() {
121
+ if (closed) return Promise.reject(new Error('session runtime host is closed'));
122
+ prewarmPromise ??= localModule().then((module) => Promise.allSettled([
123
+ module.preloadSessionRuntimeModule?.(),
124
+ module.preloadAgentLoopRuntime?.(),
125
+ module.preloadKeychainSecrets?.(),
126
+ ])).then(() => ({ ready: true }));
127
+ return prewarmPromise;
128
+ }
129
+
130
+ async function agentDispatch(payload = {}, { signal = null } = {}) {
131
+ if (closed) throw new Error('session runtime host is closed');
132
+ const dispatchId = String(payload?.dispatchId || '');
133
+ if (!dispatchId) throw new Error('agent dispatch id is required');
134
+ if (agentDispatchRuns.has(dispatchId)) {
135
+ throw new Error(`agent dispatch ${dispatchId} is already running`);
136
+ }
137
+ const controller = new AbortController();
138
+ const abort = () => {
139
+ if (controller.signal.aborted) return;
140
+ try { controller.abort(signal?.reason); } catch { try { controller.abort(); } catch {} }
141
+ };
142
+ if (signal?.aborted) abort();
143
+ else signal?.addEventListener?.('abort', abort, { once: true });
144
+ agentDispatchRuns.set(dispatchId, controller);
145
+ try {
146
+ if (controller.signal.aborted) throw controller.signal.reason || new Error('agent dispatch canceled');
147
+ const agent = String(payload.agent || '');
148
+ const { dispatch } = await agentGraph();
149
+ await prepareAgentProviders();
150
+ if (controller.signal.aborted) throw controller.signal.reason || new Error('agent dispatch canceled');
151
+ let dispatcher = agentDispatchers.get(agent);
152
+ if (!dispatcher) {
153
+ dispatcher = dispatch.makeAgentDispatch({
154
+ agent,
155
+ ...(payload.options && typeof payload.options === 'object' ? payload.options : {}),
156
+ });
157
+ agentDispatchers.set(agent, dispatcher);
158
+ }
159
+ const params = payload.params && typeof payload.params === 'object' ? payload.params : {};
160
+ const prompt = String(params.prompt ?? '');
161
+ return await dispatcher({
162
+ prompt,
163
+ preset: params.preset || undefined,
164
+ cwd: typeof params.cwd === 'string' && params.cwd ? params.cwd : undefined,
165
+ parentSignal: controller.signal,
166
+ ...(Number.isFinite(Number(params.idleTimeoutMs)) && Number(params.idleTimeoutMs) > 0
167
+ ? { idleTimeoutMs: Number(params.idleTimeoutMs) }
168
+ : {}),
169
+ });
170
+ } finally {
171
+ signal?.removeEventListener?.('abort', abort);
172
+ agentDispatchRuns.delete(dispatchId);
173
+ }
174
+ }
175
+
176
+ return {
177
+ create,
178
+ prewarm,
179
+ agentDispatch,
180
+ async agentControl(args = {}, context = {}) {
181
+ if (closed) throw new Error('session runtime host is closed');
182
+ if (typeof executeAgentControl === 'function') {
183
+ return await executeAgentControl(args, context);
184
+ }
185
+ throw new Error('canonical Agent control is unavailable');
186
+ },
187
+ notifySessionCompletion(ownerSessionId, text, meta = {}) {
188
+ const runtime = ownerRuntime(ownerSessionId);
189
+ return runtime?.deliverToolCompletion?.(
190
+ String(ownerSessionId || ''),
191
+ String(text || ''),
192
+ meta,
193
+ ) === true;
194
+ },
195
+ async agentSessionAction(sessionId, action, args = []) {
196
+ void sessionId;
197
+ void action;
198
+ void args;
199
+ throw new Error('Agent sessions are owned by the canonical session service');
200
+ },
201
+ refreshRuntimeWorkload() {
202
+ return Promise.resolve(this.workloads);
203
+ },
204
+ subscribeAgentSessionStates(listener) {
205
+ void listener;
206
+ return () => {};
207
+ },
208
+ agentSessionState(sessionId) {
209
+ void sessionId;
210
+ return null;
211
+ },
212
+ get workloads() {
213
+ const memory = process.memoryUsage();
214
+ return {
215
+ mode: 'in-process',
216
+ refreshedAt: Date.now(),
217
+ shardCount: 0,
218
+ shards: [],
219
+ worker: {
220
+ pid: process.pid,
221
+ runtimes: records.size,
222
+ memory: {
223
+ rss: memory.rss,
224
+ heapTotal: memory.heapTotal,
225
+ heapUsed: memory.heapUsed,
226
+ external: memory.external,
227
+ },
228
+ },
229
+ };
230
+ },
231
+ async close(reason = 'session runtime host closed') {
232
+ if (closed) return;
233
+ closed = true;
234
+ for (const controller of agentDispatchRuns.values()) {
235
+ try { controller.abort(new Error(reason)); } catch {}
236
+ }
237
+ agentDispatchRuns.clear();
238
+ const active = [...records.values()];
239
+ await Promise.allSettled(active.map((record) => record.runtime.dispose?.(reason)));
240
+ records.clear();
241
+ recordsBySessionId.clear();
242
+ },
243
+ get status() {
244
+ return {
245
+ mode: 'in-process',
246
+ active: !closed,
247
+ worker: {
248
+ pid: process.pid,
249
+ pids: [process.pid],
250
+ runtimes: records.size,
251
+ },
252
+ shards: [],
253
+ shardCount: 0,
254
+ providerCooldown: null,
255
+ };
256
+ },
257
+ };
258
+ }
@@ -0,0 +1,385 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import {
8
+ drainSessionStore,
9
+ saveSessionAsync,
10
+ saveSessionAsyncDeferred,
11
+ } from '../runtime/agent/orchestrator/session/store.mjs';
12
+ import { createInlineSessionRuntimeHost } from './session-runtime-inline-host.mjs';
13
+
14
+ async function waitFor(predicate, label, timeoutMs = 5_000) {
15
+ const deadline = Date.now() + timeoutMs;
16
+ while (Date.now() < deadline) {
17
+ if (predicate()) return;
18
+ await new Promise((resolve) => setTimeout(resolve, 10));
19
+ }
20
+ throw new Error(`timed out waiting for ${label}`);
21
+ }
22
+
23
+ function createFakeLocalModule(events) {
24
+ return {
25
+ async createLocalSessionRuntime(options = {}) {
26
+ events.push(['create', options]);
27
+ const listeners = new Set();
28
+ const state = { sessionId: options.sessionId || null };
29
+ return {
30
+ get id() { return state.sessionId; },
31
+ getState: () => state,
32
+ subscribe(listener) {
33
+ listeners.add(listener);
34
+ return () => listeners.delete(listener);
35
+ },
36
+ async resume(sessionId) {
37
+ events.push(['resume', sessionId]);
38
+ state.sessionId = sessionId;
39
+ return true;
40
+ },
41
+ async submitAsync(prompt, options) {
42
+ events.push(['submit', prompt, options]);
43
+ return true;
44
+ },
45
+ deliverToolCompletion(sessionId, text, meta) {
46
+ events.push(['completion', sessionId, text, meta]);
47
+ return state.sessionId === sessionId;
48
+ },
49
+ async dispose(reason) {
50
+ events.push(['dispose', reason]);
51
+ },
52
+ async agentControl(args) {
53
+ return JSON.stringify(args);
54
+ },
55
+ };
56
+ },
57
+ async preloadSessionRuntimeModule() {
58
+ events.push(['prewarm', 'runtime']);
59
+ },
60
+ async preloadAgentLoopRuntime() {
61
+ events.push(['prewarm', 'agent-loop']);
62
+ },
63
+ async preloadKeychainSecrets() {
64
+ events.push(['prewarm', 'keychain']);
65
+ },
66
+ };
67
+ }
68
+
69
+ function deferred() {
70
+ let resolve;
71
+ const promise = new Promise((accept) => { resolve = accept; });
72
+ return { promise, resolve };
73
+ }
74
+
75
+ test('inline host keeps session actors in the daemon process and releases them', async () => {
76
+ const events = [];
77
+ const host = createInlineSessionRuntimeHost({
78
+ cwd: 'C:\\project',
79
+ loadLocalModule: async () => createFakeLocalModule(events),
80
+ executeAgentControl: async (args) => JSON.stringify(args),
81
+ });
82
+
83
+ const first = await host.create({ sessionId: 'session-a' });
84
+ const second = await host.create({ sessionId: 'session-b', cwd: 'D:\\other' });
85
+
86
+ assert.equal(host.status.mode, 'in-process');
87
+ assert.equal(host.status.worker.pid, process.pid);
88
+ assert.equal(host.status.worker.runtimes, 2);
89
+ assert.equal(events[0][1].cwd, 'C:\\project');
90
+ assert.equal(events[1][1].cwd, 'D:\\other');
91
+ assert.equal(
92
+ await host.agentControl({ type: 'list' }, { callerSessionId: 'session-a' }),
93
+ '{"type":"list"}',
94
+ );
95
+ const completionMeta = { type: 'agent_task_result', execution_id: 'task-agent-1' };
96
+ assert.equal(host.notifySessionCompletion(
97
+ 'session-a',
98
+ 'Agent handoff',
99
+ completionMeta,
100
+ ), true);
101
+ assert.deepEqual(
102
+ events.find(([type]) => type === 'completion'),
103
+ ['completion', 'session-a', 'Agent handoff', completionMeta],
104
+ );
105
+
106
+ await first.dispose('idle');
107
+ assert.equal(host.status.worker.runtimes, 1);
108
+ await host.close('done');
109
+ assert.equal(host.status.active, false);
110
+ assert.equal(host.status.worker.runtimes, 0);
111
+ assert.equal(events.some(([type, reason]) => type === 'dispose' && reason === 'done'), true);
112
+ void second;
113
+ });
114
+
115
+ test('inline host prewarm deliberately leaves the memory process cold', async () => {
116
+ const events = [];
117
+ const host = createInlineSessionRuntimeHost({
118
+ loadLocalModule: async () => createFakeLocalModule(events),
119
+ });
120
+
121
+ await host.prewarm();
122
+ await host.prewarm();
123
+
124
+ assert.deepEqual(
125
+ events.filter(([type]) => type === 'prewarm').map(([, target]) => target).sort(),
126
+ ['agent-loop', 'keychain', 'runtime'],
127
+ );
128
+ await host.close('done');
129
+ });
130
+
131
+ test.skip('retired Agent session surface is not part of the canonical session host', async () => {
132
+ const events = [];
133
+ const session = {
134
+ id: 'agent-a',
135
+ provider: 'test-provider',
136
+ model: 'test-model',
137
+ cwd: 'E:\\agent-project',
138
+ };
139
+ const host = createInlineSessionRuntimeHost({
140
+ loadLocalModule: async () => createFakeLocalModule(events),
141
+ loadAgentSessionManager: async () => ({
142
+ getSession: (sessionId) => sessionId === session.id ? session : null,
143
+ }),
144
+ });
145
+
146
+ assert.equal(host.agentSessionState(session.id), null);
147
+ assert.equal(await host.agentSessionAction(session.id, 'submitAsync', [
148
+ 'hello agent',
149
+ { id: 'desktop-agent-message' },
150
+ ]), true);
151
+ assert.equal(host.agentSessionState(session.id)?.sessionId, session.id);
152
+ assert.deepEqual(events[0], ['create', {
153
+ provider: session.provider,
154
+ model: session.model,
155
+ cwd: session.cwd,
156
+ toolMode: 'full',
157
+ desktopSession: null,
158
+ }]);
159
+ assert.deepEqual(events[1], ['resume', session.id]);
160
+ assert.deepEqual(events[2], [
161
+ 'submit',
162
+ 'hello agent',
163
+ { id: 'desktop-agent-message' },
164
+ ]);
165
+
166
+ await host.close('done');
167
+ assert.equal(events.some(([type, reason]) => type === 'dispose' && reason === 'done'), true);
168
+ });
169
+
170
+ test.skip('retired Agent store projection is not part of the canonical session host', async () => {
171
+ const root = mkdtempSync(join(tmpdir(), 'mixdog-inline-agent-publication-'));
172
+ const previous = process.env.MIXDOG_DATA_DIR;
173
+ process.env.MIXDOG_DATA_DIR = root;
174
+ mkdirSync(join(root, 'sessions'));
175
+ const host = createInlineSessionRuntimeHost();
176
+ const unsubscribe = host.subscribeAgentSessionStates(() => {});
177
+ const session = {
178
+ id: `sess_inline_agent_${process.pid}_${Date.now()}`,
179
+ owner: 'agent',
180
+ agent: 'worker',
181
+ provider: 'test-provider',
182
+ model: 'test-model',
183
+ cwd: 'C:\\project',
184
+ status: 'running',
185
+ messages: [{ role: 'user', content: 'Reply exactly DONE' }],
186
+ generation: 0,
187
+ closed: false,
188
+ };
189
+
190
+ try {
191
+ await new Promise((resolve) => setImmediate(resolve));
192
+ await saveSessionAsync(session, { expectedGeneration: session.generation });
193
+ await waitFor(
194
+ () => host.agentSessionState(session.id)?.items?.length === 1,
195
+ 'initial Agent projection',
196
+ );
197
+
198
+ session.status = 'idle';
199
+ session.messages.push({ role: 'assistant', content: 'DONE' });
200
+ await saveSessionAsyncDeferred(session, { expectedGeneration: session.generation });
201
+ await waitFor(
202
+ () => host.agentSessionState(session.id)?.items?.length === 2,
203
+ 'terminal Agent projection',
204
+ );
205
+
206
+ const snapshot = host.agentSessionState(session.id);
207
+ assert.equal(Boolean(snapshot.busy), false);
208
+ assert.equal(snapshot.items[1].kind, 'assistant');
209
+ assert.equal(snapshot.items[1].text, 'DONE');
210
+ } finally {
211
+ unsubscribe();
212
+ await host.close('done');
213
+ drainSessionStore();
214
+ if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
215
+ else process.env.MIXDOG_DATA_DIR = previous;
216
+ rmSync(root, { recursive: true, force: true });
217
+ }
218
+ });
219
+
220
+ test.skip('retired Agent dispatch projector is not part of the canonical session host', async () => {
221
+ const allowRequesting = deferred();
222
+ const allowReasoning = deferred();
223
+ const allowReset = deferred();
224
+ const allowTerminal = deferred();
225
+ const allowCommit = deferred();
226
+ const resetAcks = [];
227
+ const session = {
228
+ id: `sess_inline_agent_stream_${process.pid}_${Date.now()}`,
229
+ owner: 'agent',
230
+ agent: 'worker',
231
+ provider: 'test-provider',
232
+ model: 'test-model',
233
+ cwd: 'C:\\project',
234
+ messages: [{ role: 'user', content: 'Reply exactly DONE' }],
235
+ };
236
+ const host = createInlineSessionRuntimeHost({
237
+ loadAgentGraph: async () => ({
238
+ config: { loadConfig: () => ({ providers: {} }) },
239
+ registry: { initProviders: async () => {} },
240
+ dispatch: {
241
+ makeAgentDispatch: () => async (call) => {
242
+ const requiredCallbacks = [
243
+ 'onSessionStart',
244
+ 'onStageChange',
245
+ 'onReasoningDelta',
246
+ 'onTextDelta',
247
+ 'onTextReset',
248
+ 'onAssistantMessageCommitted',
249
+ ];
250
+ for (const name of requiredCallbacks) {
251
+ if (typeof call?.[name] !== 'function') {
252
+ throw new TypeError(`missing formal Agent projection callback ${name}`);
253
+ }
254
+ }
255
+
256
+ call.onSessionStart(session);
257
+ call.onStageChange(session, 'requesting');
258
+ await allowRequesting.promise;
259
+
260
+ call.onStageChange(session, 'streaming');
261
+ call.onReasoningDelta(session, 'inspect first');
262
+ await allowReasoning.promise;
263
+
264
+ call.onTextDelta(session, 'PARTIAL');
265
+ await allowReset.promise;
266
+ resetAcks.push(call.onTextReset(session, { chars: 99 }));
267
+ resetAcks.push(call.onTextReset(session, { chars: 3 }));
268
+ await allowTerminal.promise;
269
+ resetAcks.push(call.onTextReset(session, { chars: 4 }));
270
+ call.onTextDelta(session, 'DONE');
271
+ session.messages.push({ role: 'assistant', content: 'DONE' });
272
+ call.onAssistantMessageCommitted(session);
273
+ await allowCommit.promise;
274
+ return 'DONE';
275
+ },
276
+ },
277
+ }),
278
+ });
279
+
280
+ try {
281
+ const dispatch = host.agentDispatch({
282
+ dispatchId: `dispatch_${session.id}`,
283
+ agent: 'worker',
284
+ params: { prompt: 'Reply exactly DONE' },
285
+ });
286
+ await waitFor(
287
+ () => {
288
+ const snapshot = host.agentSessionState(session.id);
289
+ return snapshot?.busy === true
290
+ && snapshot?.spinner?.mode === 'requesting'
291
+ && snapshot?.agentStage === 'requesting';
292
+ },
293
+ 'requesting Agent stage',
294
+ );
295
+ const requesting = host.agentSessionState(session.id);
296
+ assert.equal(requesting.thinking, null);
297
+ assert.equal(requesting.streamingTail, null);
298
+ assert.equal(requesting.items[0].kind, 'user');
299
+ assert.equal(requesting.items[0].text, 'Reply exactly DONE');
300
+
301
+ allowRequesting.resolve();
302
+ await waitFor(
303
+ () => {
304
+ const snapshot = host.agentSessionState(session.id);
305
+ return snapshot?.thinking === 'inspect first'
306
+ && snapshot?.spinner?.mode === 'thinking'
307
+ && snapshot?.spinner?.thinking === true
308
+ && snapshot?.agentStage === 'streaming';
309
+ },
310
+ 'reasoning Agent frame',
311
+ );
312
+ const reasoning = host.agentSessionState(session.id);
313
+ assert.equal(reasoning.busy, true);
314
+ assert.equal(reasoning.streamingTail, null);
315
+
316
+ allowReasoning.resolve();
317
+ await waitFor(
318
+ () => host.agentSessionState(session.id)?.streamingTail?.text === 'PARTIAL',
319
+ 'partial Agent assistant frame',
320
+ );
321
+ const partial = host.agentSessionState(session.id);
322
+ assert.equal(partial.busy, true);
323
+ assert.equal(partial.thinking, null);
324
+ assert.equal(partial.spinner.mode, 'responding');
325
+ assert.equal(partial.agentStage, 'streaming');
326
+ assert.equal(partial.items[0].kind, 'user');
327
+ assert.equal(partial.items[0].text, 'Reply exactly DONE');
328
+
329
+ allowReset.resolve();
330
+ await waitFor(
331
+ () => host.agentSessionState(session.id)?.streamingTail?.text === 'PART',
332
+ 'exact Agent text retraction',
333
+ );
334
+ assert.deepEqual(resetAcks, [false, true]);
335
+
336
+ allowTerminal.resolve();
337
+ await waitFor(
338
+ () => {
339
+ const snapshot = host.agentSessionState(session.id);
340
+ const assistant = snapshot?.items
341
+ ?.filter((item) => item.kind === 'assistant')
342
+ .map((item) => item.text);
343
+ return snapshot?.busy === true
344
+ && snapshot?.streamingTail == null
345
+ && assistant?.length === 1
346
+ && assistant[0] === 'DONE';
347
+ },
348
+ 'committed Agent assistant transcript before settle',
349
+ );
350
+ const committed = host.agentSessionState(session.id);
351
+ assert.equal(committed.busy, true);
352
+ assert.equal(committed.streamingTail, null);
353
+ assert.equal(committed.thinking, null);
354
+ assert.equal(committed.spinner?.mode, 'responding');
355
+ assert.deepEqual(
356
+ committed.items.filter((item) => item.kind === 'assistant').map((item) => item.text),
357
+ ['DONE'],
358
+ );
359
+
360
+ allowCommit.resolve();
361
+ assert.equal(await dispatch, 'DONE');
362
+ await waitFor(
363
+ () => host.agentSessionState(session.id)?.busy === false,
364
+ 'terminal Agent callback projection',
365
+ );
366
+ const terminal = host.agentSessionState(session.id);
367
+ assert.deepEqual(resetAcks, [false, true, true]);
368
+ assert.equal(terminal.spinner, null);
369
+ assert.equal(terminal.streamingTail, null);
370
+ assert.equal(terminal.thinking, null);
371
+ assert.equal(Object.hasOwn(terminal, 'agentStage'), false);
372
+ assert.deepEqual(
373
+ terminal.items.filter((item) => item.kind === 'assistant').map((item) => item.text),
374
+ ['DONE'],
375
+ );
376
+ } finally {
377
+ allowRequesting.resolve();
378
+ allowReasoning.resolve();
379
+ allowReset.resolve();
380
+ allowTerminal.resolve();
381
+ allowCommit.resolve();
382
+ await host.close('done');
383
+ }
384
+ });
385
+