mixdog 0.7.17 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (836) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/smoke-loop-report.mjs +221 -0
  10. package/scripts/smoke-loop.mjs +201 -0
  11. package/scripts/smoke.mjs +113 -0
  12. package/scripts/tool-failures.mjs +143 -0
  13. package/scripts/tool-smoke.mjs +456 -0
  14. package/src/agents/debugger/AGENT.md +3 -0
  15. package/src/agents/debugger/agent.json +6 -0
  16. package/src/agents/explore/AGENT.md +4 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/agents/heavy-worker/AGENT.md +3 -0
  19. package/src/agents/heavy-worker/agent.json +6 -0
  20. package/src/agents/maintainer/AGENT.md +3 -0
  21. package/src/agents/maintainer/agent.json +6 -0
  22. package/src/agents/reviewer/AGENT.md +3 -0
  23. package/src/agents/reviewer/agent.json +6 -0
  24. package/src/agents/scheduler-task.md +3 -0
  25. package/src/agents/web-researcher/AGENT.md +3 -0
  26. package/src/agents/web-researcher/agent.json +6 -0
  27. package/src/agents/webhook-handler.md +3 -0
  28. package/src/agents/worker/AGENT.md +3 -0
  29. package/src/agents/worker/agent.json +6 -0
  30. package/src/app.mjs +90 -0
  31. package/src/cli.mjs +11 -0
  32. package/src/defaults/hidden-roles.json +72 -0
  33. package/src/defaults/mixdog-config.template.json +15 -0
  34. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  35. package/src/hooks/lib/settings-loader.cjs +112 -0
  36. package/src/lib/keychain-cjs.cjs +332 -0
  37. package/src/lib/plugin-paths.cjs +28 -0
  38. package/src/lib/rules-builder.cjs +315 -0
  39. package/src/mixdog-session-runtime.mjs +3704 -0
  40. package/src/output-styles/default.md +38 -0
  41. package/src/output-styles/extreme-simple.md +17 -0
  42. package/src/output-styles/simple.md +17 -0
  43. package/src/repl.mjs +322 -0
  44. package/src/rules/bridge/00-common.md +5 -0
  45. package/src/rules/bridge/20-skip-protocol.md +11 -0
  46. package/src/rules/bridge/30-explorer.md +4 -0
  47. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  48. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  49. package/src/rules/lead/00-tool-lead.md +5 -0
  50. package/src/rules/lead/01-general.md +5 -0
  51. package/src/rules/lead/02-channels.md +3 -0
  52. package/src/rules/lead/04-workflow.md +12 -0
  53. package/src/rules/shared/00-language.md +3 -0
  54. package/src/rules/shared/01-tool.md +3 -0
  55. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  56. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  57. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  59. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  60. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  65. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  66. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  67. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  68. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  69. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  77. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  79. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  80. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  81. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  83. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  84. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  85. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  86. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  88. package/src/runtime/agent/orchestrator/session/loop.mjs +2320 -0
  89. package/src/runtime/agent/orchestrator/session/manager.mjs +2960 -0
  90. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  91. package/src/runtime/agent/orchestrator/session/store.mjs +663 -0
  92. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  93. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  94. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  95. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  96. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  97. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  118. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  119. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  120. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  121. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  122. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  123. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  124. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  125. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  129. package/src/runtime/channels/backends/discord.mjs +781 -0
  130. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  131. package/src/runtime/channels/index.mjs +3309 -0
  132. package/src/runtime/channels/lib/config.mjs +285 -0
  133. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  134. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  135. package/src/runtime/channels/lib/holidays.mjs +138 -0
  136. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  137. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  138. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  139. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  140. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  141. package/src/runtime/channels/lib/state-file.mjs +68 -0
  142. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  143. package/src/runtime/channels/lib/tool-format.mjs +124 -0
  144. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  146. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  147. package/src/runtime/channels/tool-defs.mjs +177 -0
  148. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  149. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  150. package/src/runtime/memory/index.mjs +3600 -0
  151. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  152. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  153. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  154. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  155. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  156. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  157. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  158. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  159. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  160. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  161. package/src/runtime/memory/lib/memory.mjs +418 -0
  162. package/src/runtime/memory/lib/pg/adapter.mjs +314 -0
  163. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  164. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  165. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  166. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  167. package/src/runtime/memory/tool-defs.mjs +79 -0
  168. package/src/runtime/search/index.mjs +925 -0
  169. package/src/runtime/search/lib/config.mjs +61 -0
  170. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  171. package/src/runtime/search/tool-defs.mjs +64 -0
  172. package/src/runtime/shared/atomic-file.mjs +435 -0
  173. package/src/runtime/shared/background-tasks.mjs +376 -0
  174. package/src/runtime/shared/child-guardian.mjs +98 -0
  175. package/src/runtime/shared/config.mjs +393 -0
  176. package/src/runtime/shared/err-text.mjs +121 -0
  177. package/src/runtime/shared/launcher-control.mjs +259 -0
  178. package/src/runtime/shared/llm/cost.mjs +66 -0
  179. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  180. package/src/runtime/shared/open-url.mjs +37 -0
  181. package/src/runtime/shared/plugin-paths.mjs +25 -0
  182. package/src/runtime/shared/process-shutdown.mjs +147 -0
  183. package/src/runtime/shared/schedules-store.mjs +70 -0
  184. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  185. package/src/runtime/shared/tool-surface.mjs +950 -0
  186. package/src/runtime/shared/user-cwd.mjs +221 -0
  187. package/src/runtime/shared/user-data-guard.mjs +232 -0
  188. package/src/runtime/shared/workspace-router.mjs +259 -0
  189. package/src/standalone/bridge-tool.mjs +1414 -0
  190. package/src/standalone/channel-admin.mjs +366 -0
  191. package/src/standalone/channel-worker-preload.cjs +3 -0
  192. package/src/standalone/channel-worker.mjs +353 -0
  193. package/src/standalone/explore-tool.mjs +233 -0
  194. package/src/standalone/hook-bus.mjs +246 -0
  195. package/src/standalone/plugin-admin.mjs +247 -0
  196. package/src/standalone/provider-admin.mjs +338 -0
  197. package/src/standalone/seeds.mjs +94 -0
  198. package/src/standalone/usage-dashboard.mjs +510 -0
  199. package/src/tui/App.jsx +5438 -0
  200. package/src/tui/components/AnsiText.jsx +199 -0
  201. package/src/tui/components/ContextPanel.jsx +217 -0
  202. package/src/tui/components/Markdown.jsx +205 -0
  203. package/src/tui/components/MarkdownTable.jsx +204 -0
  204. package/src/tui/components/Message.jsx +103 -0
  205. package/src/tui/components/Picker.jsx +317 -0
  206. package/src/tui/components/PromptInput.jsx +584 -0
  207. package/src/tui/components/QueuedCommands.jsx +47 -0
  208. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  209. package/src/tui/components/Spinner.jsx +317 -0
  210. package/src/tui/components/StatusLine.jsx +87 -0
  211. package/src/tui/components/TextEntryPanel.jsx +323 -0
  212. package/src/tui/components/ToolExecution.jsx +772 -0
  213. package/src/tui/components/TurnDone.jsx +78 -0
  214. package/src/tui/components/UsagePanel.jsx +331 -0
  215. package/src/tui/dist/index.mjs +12359 -0
  216. package/src/tui/engine.mjs +2410 -0
  217. package/src/tui/figures.mjs +50 -0
  218. package/src/tui/hooks/useEngine.mjs +16 -0
  219. package/src/tui/index.jsx +254 -0
  220. package/src/tui/input-editing.mjs +242 -0
  221. package/src/tui/markdown/format-token.mjs +194 -0
  222. package/src/tui/paste-attachments.mjs +198 -0
  223. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  224. package/src/tui/spinner-verbs.mjs +45 -0
  225. package/src/tui/theme.mjs +67 -0
  226. package/src/tui/time-format.mjs +53 -0
  227. package/src/ui/ansi.mjs +115 -0
  228. package/src/ui/markdown.mjs +195 -0
  229. package/src/ui/statusline.mjs +730 -0
  230. package/src/ui/tool-card.mjs +101 -0
  231. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  232. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  233. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  234. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  235. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  236. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  237. package/src/workflows/default/WORKFLOW.md +7 -0
  238. package/src/workflows/default/workflow.json +14 -0
  239. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  240. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  241. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  242. package/vendor/ink/build/colorize.d.ts +3 -0
  243. package/vendor/ink/build/colorize.js +48 -0
  244. package/vendor/ink/build/colorize.js.map +1 -0
  245. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  247. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  248. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  249. package/vendor/ink/build/components/AnimationContext.js +13 -0
  250. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  251. package/vendor/ink/build/components/App.d.ts +24 -0
  252. package/vendor/ink/build/components/App.js +554 -0
  253. package/vendor/ink/build/components/App.js.map +1 -0
  254. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  255. package/vendor/ink/build/components/AppContext.js +25 -0
  256. package/vendor/ink/build/components/AppContext.js.map +1 -0
  257. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  258. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  259. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  260. package/vendor/ink/build/components/Box.d.ts +130 -0
  261. package/vendor/ink/build/components/Box.js +34 -0
  262. package/vendor/ink/build/components/Box.js.map +1 -0
  263. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  264. package/vendor/ink/build/components/CursorContext.js +8 -0
  265. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  266. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  268. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  269. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  270. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  271. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  272. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  273. package/vendor/ink/build/components/FocusContext.js +17 -0
  274. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  275. package/vendor/ink/build/components/Newline.d.ts +13 -0
  276. package/vendor/ink/build/components/Newline.js +8 -0
  277. package/vendor/ink/build/components/Newline.js.map +1 -0
  278. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  279. package/vendor/ink/build/components/Spacer.js +11 -0
  280. package/vendor/ink/build/components/Spacer.js.map +1 -0
  281. package/vendor/ink/build/components/Static.d.ts +24 -0
  282. package/vendor/ink/build/components/Static.js +28 -0
  283. package/vendor/ink/build/components/Static.js.map +1 -0
  284. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  285. package/vendor/ink/build/components/StderrContext.js +13 -0
  286. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  287. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  288. package/vendor/ink/build/components/StdinContext.js +20 -0
  289. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  290. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  291. package/vendor/ink/build/components/StdoutContext.js +13 -0
  292. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  293. package/vendor/ink/build/components/Text.d.ts +55 -0
  294. package/vendor/ink/build/components/Text.js +50 -0
  295. package/vendor/ink/build/components/Text.js.map +1 -0
  296. package/vendor/ink/build/components/Transform.d.ts +16 -0
  297. package/vendor/ink/build/components/Transform.js +15 -0
  298. package/vendor/ink/build/components/Transform.js.map +1 -0
  299. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  300. package/vendor/ink/build/cursor-helpers.js +62 -0
  301. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  304. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  305. package/vendor/ink/build/devtools.d.ts +1 -0
  306. package/vendor/ink/build/devtools.js +36 -0
  307. package/vendor/ink/build/devtools.js.map +1 -0
  308. package/vendor/ink/build/dom.d.ts +62 -0
  309. package/vendor/ink/build/dom.js +143 -0
  310. package/vendor/ink/build/dom.js.map +1 -0
  311. package/vendor/ink/build/get-max-width.d.ts +3 -0
  312. package/vendor/ink/build/get-max-width.js +10 -0
  313. package/vendor/ink/build/get-max-width.js.map +1 -0
  314. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  315. package/vendor/ink/build/hooks/use-animation.js +87 -0
  316. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  317. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  318. package/vendor/ink/build/hooks/use-app.js +8 -0
  319. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  323. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  324. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  325. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  329. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  330. package/vendor/ink/build/hooks/use-focus.js +43 -0
  331. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  332. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  333. package/vendor/ink/build/hooks/use-input.js +126 -0
  334. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  338. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  339. package/vendor/ink/build/hooks/use-paste.js +62 -0
  340. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  341. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  342. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  343. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  344. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  345. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  346. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  347. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  348. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  349. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  350. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  351. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  352. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  353. package/vendor/ink/build/index.d.ts +42 -0
  354. package/vendor/ink/build/index.js +24 -0
  355. package/vendor/ink/build/index.js.map +1 -0
  356. package/vendor/ink/build/ink.d.ts +146 -0
  357. package/vendor/ink/build/ink.js +1022 -0
  358. package/vendor/ink/build/ink.js.map +1 -0
  359. package/vendor/ink/build/input-parser.d.ts +10 -0
  360. package/vendor/ink/build/input-parser.js +194 -0
  361. package/vendor/ink/build/input-parser.js.map +1 -0
  362. package/vendor/ink/build/instances.d.ts +3 -0
  363. package/vendor/ink/build/instances.js +8 -0
  364. package/vendor/ink/build/instances.js.map +1 -0
  365. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  366. package/vendor/ink/build/kitty-keyboard.js +32 -0
  367. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  368. package/vendor/ink/build/log-update.d.ts +20 -0
  369. package/vendor/ink/build/log-update.js +261 -0
  370. package/vendor/ink/build/log-update.js.map +1 -0
  371. package/vendor/ink/build/measure-element.d.ts +20 -0
  372. package/vendor/ink/build/measure-element.js +13 -0
  373. package/vendor/ink/build/measure-element.js.map +1 -0
  374. package/vendor/ink/build/measure-text.d.ts +6 -0
  375. package/vendor/ink/build/measure-text.js +21 -0
  376. package/vendor/ink/build/measure-text.js.map +1 -0
  377. package/vendor/ink/build/output.d.ts +35 -0
  378. package/vendor/ink/build/output.js +328 -0
  379. package/vendor/ink/build/output.js.map +1 -0
  380. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  381. package/vendor/ink/build/parse-keypress.js +495 -0
  382. package/vendor/ink/build/parse-keypress.js.map +1 -0
  383. package/vendor/ink/build/reconciler.d.ts +4 -0
  384. package/vendor/ink/build/reconciler.js +306 -0
  385. package/vendor/ink/build/reconciler.js.map +1 -0
  386. package/vendor/ink/build/render-background.d.ts +4 -0
  387. package/vendor/ink/build/render-background.js +25 -0
  388. package/vendor/ink/build/render-background.js.map +1 -0
  389. package/vendor/ink/build/render-border.d.ts +4 -0
  390. package/vendor/ink/build/render-border.js +84 -0
  391. package/vendor/ink/build/render-border.js.map +1 -0
  392. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  393. package/vendor/ink/build/render-node-to-output.js +162 -0
  394. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  395. package/vendor/ink/build/render-to-string.d.ts +38 -0
  396. package/vendor/ink/build/render-to-string.js +116 -0
  397. package/vendor/ink/build/render-to-string.js.map +1 -0
  398. package/vendor/ink/build/render.d.ts +176 -0
  399. package/vendor/ink/build/render.js +71 -0
  400. package/vendor/ink/build/render.js.map +1 -0
  401. package/vendor/ink/build/renderer.d.ts +8 -0
  402. package/vendor/ink/build/renderer.js +64 -0
  403. package/vendor/ink/build/renderer.js.map +1 -0
  404. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  405. package/vendor/ink/build/sanitize-ansi.js +27 -0
  406. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  407. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  408. package/vendor/ink/build/squash-text-nodes.js +36 -0
  409. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  410. package/vendor/ink/build/styles.d.ts +302 -0
  411. package/vendor/ink/build/styles.js +303 -0
  412. package/vendor/ink/build/styles.js.map +1 -0
  413. package/vendor/ink/build/utils.d.ts +9 -0
  414. package/vendor/ink/build/utils.js +19 -0
  415. package/vendor/ink/build/utils.js.map +1 -0
  416. package/vendor/ink/build/wrap-text.d.ts +3 -0
  417. package/vendor/ink/build/wrap-text.js +38 -0
  418. package/vendor/ink/build/wrap-text.js.map +1 -0
  419. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  420. package/vendor/ink/build/write-synchronized.js +9 -0
  421. package/vendor/ink/build/write-synchronized.js.map +1 -0
  422. package/vendor/ink/license +10 -0
  423. package/vendor/ink/package.json +137 -0
  424. package/.claude-plugin/marketplace.json +0 -34
  425. package/.claude-plugin/plugin.json +0 -20
  426. package/.gitattributes +0 -34
  427. package/.mcp.json +0 -14
  428. package/ARCHITECTURE.md +0 -77
  429. package/CHANGELOG.md +0 -30
  430. package/CONTRIBUTING.md +0 -45
  431. package/DATA-FLOW.md +0 -79
  432. package/LICENSE +0 -21
  433. package/SECURITY.md +0 -138
  434. package/UNINSTALL.md +0 -112
  435. package/agents/maintenance.md +0 -5
  436. package/agents/memory-classification.md +0 -30
  437. package/agents/scheduler-task.md +0 -18
  438. package/agents/webhook-handler.md +0 -27
  439. package/agents/worker.md +0 -24
  440. package/bin/bridge +0 -133
  441. package/bin/statusline-launcher.mjs +0 -82
  442. package/bin/statusline-lib.mjs +0 -558
  443. package/bin/statusline.mjs +0 -615
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/setup.md +0 -17
  448. package/defaults/hidden-roles.json +0 -68
  449. package/defaults/memory-chunk-prompt.md +0 -63
  450. package/defaults/mixdog-config.template.json +0 -27
  451. package/defaults/user-workflow.json +0 -8
  452. package/defaults/user-workflow.md +0 -17
  453. package/hooks/hooks.json +0 -73
  454. package/hooks/lib/active-instance.cjs +0 -77
  455. package/hooks/lib/permission-evaluator.cjs +0 -411
  456. package/hooks/lib/permission-route.cjs +0 -63
  457. package/hooks/lib/settings-loader.cjs +0 -117
  458. package/hooks/post-tool-use.cjs +0 -84
  459. package/hooks/pre-mcp-sandbox.cjs +0 -158
  460. package/hooks/pre-tool-subagent.cjs +0 -258
  461. package/hooks/session-start.cjs +0 -1479
  462. package/hooks/shim-launcher.cjs +0 -65
  463. package/hooks/turn-timer.cjs +0 -82
  464. package/lib/claude-md-writer.cjs +0 -386
  465. package/lib/keychain-cjs.cjs +0 -290
  466. package/lib/plugin-paths.cjs +0 -69
  467. package/lib/rules-builder.cjs +0 -241
  468. package/native/README.md +0 -117
  469. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  470. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  471. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  473. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  474. package/prompts/code-review.txt +0 -16
  475. package/prompts/security-audit.txt +0 -17
  476. package/rules/bridge/00-common.md +0 -39
  477. package/rules/bridge/20-skip-protocol.md +0 -18
  478. package/rules/bridge/30-explorer.md +0 -33
  479. package/rules/bridge/40-cycle1-agent.md +0 -52
  480. package/rules/bridge/41-cycle2-agent.md +0 -62
  481. package/rules/lead/00-tool-lead.md +0 -61
  482. package/rules/lead/01-general.md +0 -23
  483. package/rules/lead/02-channels.md +0 -49
  484. package/rules/lead/03-team.md +0 -27
  485. package/rules/lead/04-workflow.md +0 -20
  486. package/rules/shared/00-language.md +0 -14
  487. package/rules/shared/01-tool.md +0 -138
  488. package/scripts/bootstrap.mjs +0 -130
  489. package/scripts/bridge-unify-smoke.mjs +0 -308
  490. package/scripts/build-runtime-linux.sh +0 -348
  491. package/scripts/build-runtime-macos.sh +0 -217
  492. package/scripts/build-runtime-windows.ps1 +0 -242
  493. package/scripts/builtin-utils-smoke.mjs +0 -398
  494. package/scripts/bump.mjs +0 -80
  495. package/scripts/check-json.mjs +0 -45
  496. package/scripts/check-syntax-changed.mjs +0 -102
  497. package/scripts/check-syntax.mjs +0 -58
  498. package/scripts/code-graph-batch.test.mjs +0 -33
  499. package/scripts/config-preserve-smoke.mjs +0 -180
  500. package/scripts/doctor.mjs +0 -489
  501. package/scripts/edit-normalize-fuzz.mjs +0 -130
  502. package/scripts/edit-normalize-smoke.mjs +0 -401
  503. package/scripts/edit-operation-smoke.mjs +0 -369
  504. package/scripts/edit2-smoke.mjs +0 -63
  505. package/scripts/ensure-deps.mjs +0 -259
  506. package/scripts/fuzzy-e2e.mjs +0 -28
  507. package/scripts/fuzzy-smoke.mjs +0 -26
  508. package/scripts/generate-runtime-manifest.mjs +0 -166
  509. package/scripts/guard-smoke.mjs +0 -66
  510. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  511. package/scripts/hook-routing-smoke.mjs +0 -29
  512. package/scripts/inject-input.ps1 +0 -204
  513. package/scripts/io-complex-smoke.mjs +0 -667
  514. package/scripts/io-explore-bench.mjs +0 -424
  515. package/scripts/io-guardrails-smoke.mjs +0 -205
  516. package/scripts/io-mini-bench-baseline.json +0 -11
  517. package/scripts/io-mini-bench.mjs +0 -216
  518. package/scripts/io-route-harness.mjs +0 -933
  519. package/scripts/io-telemetry-report.mjs +0 -691
  520. package/scripts/mutation-bench.mjs +0 -564
  521. package/scripts/mutation-io-smoke.mjs +0 -1097
  522. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  523. package/scripts/native-patch-smoke.mjs +0 -304
  524. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  525. package/scripts/patch-interior-context-smoke.mjs +0 -49
  526. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  527. package/scripts/perf-hook-smoke.mjs +0 -71
  528. package/scripts/permission-eval-smoke.mjs +0 -443
  529. package/scripts/prep-patch.mjs +0 -53
  530. package/scripts/prep-shim.mjs +0 -96
  531. package/scripts/provider-cache-smoke.mjs +0 -687
  532. package/scripts/report-runtime-health.mjs +0 -132
  533. package/scripts/resolve-bun.mjs +0 -60
  534. package/scripts/run-mcp.mjs +0 -1448
  535. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  536. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  537. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  538. package/scripts/smoke-runtime-negative.ps1 +0 -100
  539. package/scripts/smoke-runtime-negative.sh +0 -95
  540. package/scripts/stall-policy-smoke.mjs +0 -50
  541. package/scripts/start-memory-worker.mjs +0 -23
  542. package/scripts/statusline-launcher-smoke.mjs +0 -82
  543. package/scripts/stress-atomic-write.mjs +0 -1028
  544. package/scripts/test-fault-inject.mjs +0 -164
  545. package/scripts/test-large-file.mjs +0 -174
  546. package/scripts/tool-edge-smoke.mjs +0 -209
  547. package/scripts/uninstall.mjs +0 -201
  548. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  549. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  550. package/server-main.mjs +0 -3109
  551. package/server.mjs +0 -468
  552. package/setup/config-merge.mjs +0 -246
  553. package/setup/install.mjs +0 -574
  554. package/setup/launch-core.mjs +0 -617
  555. package/setup/launch.mjs +0 -101
  556. package/setup/locate-claude.mjs +0 -56
  557. package/setup/mixdog-cli.mjs +0 -122
  558. package/setup/setup-server.mjs +0 -3305
  559. package/setup/setup.html +0 -3740
  560. package/setup/tui.mjs +0 -325
  561. package/skills/retro-skill-proposer/SKILL.md +0 -92
  562. package/skills/schedule-add/SKILL.md +0 -77
  563. package/skills/setup/SKILL.md +0 -356
  564. package/skills/webhook-add/SKILL.md +0 -81
  565. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  566. package/src/agent/index.mjs +0 -2138
  567. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  568. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  569. package/src/agent/orchestrator/bridge-trace.mjs +0 -583
  570. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  571. package/src/agent/orchestrator/config.mjs +0 -405
  572. package/src/agent/orchestrator/context/collect.mjs +0 -651
  573. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  574. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  575. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  576. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  577. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  578. package/src/agent/orchestrator/jobs.mjs +0 -116
  579. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  580. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1881
  581. package/src/agent/orchestrator/providers/anthropic.mjs +0 -594
  582. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  583. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  584. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  585. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  586. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  587. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  588. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  589. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  590. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  591. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  592. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  593. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  594. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  595. package/src/agent/orchestrator/session/loop.mjs +0 -1478
  596. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  597. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  598. package/src/agent/orchestrator/session/store.mjs +0 -632
  599. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  600. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  601. package/src/agent/orchestrator/session/trim.mjs +0 -491
  602. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  603. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  604. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  605. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  606. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  607. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  608. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  609. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  610. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  611. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  612. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -455
  613. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  614. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  615. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  616. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  617. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  618. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  619. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  620. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  621. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  622. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  623. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  624. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  625. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  626. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  627. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  628. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  629. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  630. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  631. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  632. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  633. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  634. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  635. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  636. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  637. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  638. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  639. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  640. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  641. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  642. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  643. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  644. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  645. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  646. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  647. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  648. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  649. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  650. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  651. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  652. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  653. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  654. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  655. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  656. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  657. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  658. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  659. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  660. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  661. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  662. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  663. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  664. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  665. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  666. package/src/agent/tool-defs.mjs +0 -103
  667. package/src/channels/backends/discord.mjs +0 -784
  668. package/src/channels/data/voice-runtime-manifest.json +0 -138
  669. package/src/channels/index.mjs +0 -3268
  670. package/src/channels/lib/config.mjs +0 -292
  671. package/src/channels/lib/drop-trace.mjs +0 -71
  672. package/src/channels/lib/event-pipeline.mjs +0 -81
  673. package/src/channels/lib/holidays.mjs +0 -138
  674. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  675. package/src/channels/lib/output-forwarder.mjs +0 -765
  676. package/src/channels/lib/runtime-paths.mjs +0 -517
  677. package/src/channels/lib/scheduler.mjs +0 -723
  678. package/src/channels/lib/session-discovery.mjs +0 -103
  679. package/src/channels/lib/state-file.mjs +0 -68
  680. package/src/channels/lib/status-snapshot.mjs +0 -219
  681. package/src/channels/lib/tool-format.mjs +0 -140
  682. package/src/channels/lib/transcript-discovery.mjs +0 -195
  683. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  684. package/src/channels/lib/webhook.mjs +0 -1318
  685. package/src/channels/tool-defs.mjs +0 -170
  686. package/src/daemon/host.mjs +0 -118
  687. package/src/daemon/mcp-transport.mjs +0 -47
  688. package/src/daemon/session.mjs +0 -100
  689. package/src/daemon/thin-client.mjs +0 -71
  690. package/src/daemon/transport.mjs +0 -163
  691. package/src/memory/data/runtime-manifest.json +0 -40
  692. package/src/memory/index.mjs +0 -3332
  693. package/src/memory/lib/core-memory-store.mjs +0 -330
  694. package/src/memory/lib/embedding-provider.mjs +0 -269
  695. package/src/memory/lib/embedding-worker.mjs +0 -323
  696. package/src/memory/lib/memory-cycle1.mjs +0 -645
  697. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  698. package/src/memory/lib/memory-cycle3.mjs +0 -540
  699. package/src/memory/lib/memory-embed.mjs +0 -299
  700. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  701. package/src/memory/lib/memory-recall-store.mjs +0 -638
  702. package/src/memory/lib/memory.mjs +0 -412
  703. package/src/memory/lib/pg/adapter.mjs +0 -308
  704. package/src/memory/lib/pg/process.mjs +0 -360
  705. package/src/memory/lib/pg/supervisor.mjs +0 -396
  706. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  707. package/src/memory/lib/trace-store.mjs +0 -728
  708. package/src/memory/tool-defs.mjs +0 -79
  709. package/src/search/index.mjs +0 -1173
  710. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  711. package/src/search/lib/backends/exa.mjs +0 -50
  712. package/src/search/lib/backends/firecrawl.mjs +0 -61
  713. package/src/search/lib/backends/gemini-api.mjs +0 -83
  714. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  715. package/src/search/lib/backends/index.mjs +0 -150
  716. package/src/search/lib/backends/openai-api.mjs +0 -144
  717. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  718. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  719. package/src/search/lib/backends/tavily.mjs +0 -55
  720. package/src/search/lib/backends/xai-api.mjs +0 -113
  721. package/src/search/lib/config.mjs +0 -192
  722. package/src/search/lib/provider-usage.mjs +0 -67
  723. package/src/search/lib/providers.mjs +0 -47
  724. package/src/search/lib/search-intent.mjs +0 -109
  725. package/src/search/lib/setup-handler.mjs +0 -261
  726. package/src/search/lib/web-tools.mjs +0 -1219
  727. package/src/search/tool-defs.mjs +0 -83
  728. package/src/setup/defender-exclusion.mjs +0 -183
  729. package/src/shared/atomic-file.mjs +0 -436
  730. package/src/shared/config.mjs +0 -372
  731. package/src/shared/daemon-recycle.mjs +0 -108
  732. package/src/shared/disable-claude-builtins.mjs +0 -91
  733. package/src/shared/err-text.mjs +0 -12
  734. package/src/shared/llm/cost.mjs +0 -66
  735. package/src/shared/llm/http-agent.mjs +0 -123
  736. package/src/shared/open-url.mjs +0 -62
  737. package/src/shared/plugin-paths.mjs +0 -58
  738. package/src/shared/schedules-store.mjs +0 -70
  739. package/src/shared/seed.mjs +0 -136
  740. package/src/shared/user-cwd.mjs +0 -225
  741. package/src/shared/user-data-guard.mjs +0 -244
  742. package/src/status/aggregator.mjs +0 -584
  743. package/src/status/server.mjs +0 -413
  744. package/tools.json +0 -1653
  745. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  746. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  747. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  748. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  749. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  750. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  751. /package/{lib → src/lib}/text-utils.cjs +0 -0
  752. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  753. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  754. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  755. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  756. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  757. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  758. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  759. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  805. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  806. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  807. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  808. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  809. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  810. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  811. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  815. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  816. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  817. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  818. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  819. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  820. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  821. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  829. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  830. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  831. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  832. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  833. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  834. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  835. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  836. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -0,0 +1,2410 @@
1
+ /**
2
+ * src/tui/engine.mjs - the engine<->React bridge (React-free).
3
+ *
4
+ * Runs mixdog's session manager outside React and exposes a tiny subscribable
5
+ * store. The React/ink layer consumes it via useSyncExternalStore
6
+ * (see hooks/useEngine.mjs).
7
+ */
8
+ import { performance } from 'node:perf_hooks';
9
+ import { SPINNER_VERBS } from './spinner-verbs.mjs';
10
+ import {
11
+ classifyToolCategory,
12
+ formatAggregateDetail,
13
+ summarizeToolResult,
14
+ } from '../runtime/shared/tool-surface.mjs';
15
+ import { presentErrorText } from '../runtime/shared/err-text.mjs';
16
+
17
+ const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
18
+ const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
19
+
20
+ function bootProfile(event, fields = {}) {
21
+ if (!BOOT_PROFILE_ENABLED) return;
22
+ const elapsedMs = performance.now() - BOOT_PROFILE_START;
23
+ const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
24
+ for (const [key, value] of Object.entries(fields || {})) {
25
+ if (value === undefined || value === null || value === '') continue;
26
+ parts.push(`${key}=${String(value).replace(/\s+/g, '_')}`);
27
+ }
28
+ try { process.stderr.write(`${parts.join(' ')}\n`); } catch {}
29
+ }
30
+
31
+ // Session-usage accumulator - inlined (not imported from ui/statusline.mjs) so
32
+ // engine.mjs has no static dependency on the vendored statusline closure.
33
+ function createSessionStats() {
34
+ return {
35
+ inputTokens: 0,
36
+ outputTokens: 0,
37
+ cachedTokens: 0,
38
+ cacheWriteTokens: 0,
39
+ promptTokens: 0,
40
+ latestInputTokens: 0,
41
+ latestOutputTokens: 0,
42
+ latestCachedTokens: 0,
43
+ latestCacheWriteTokens: 0,
44
+ latestPromptTokens: 0,
45
+ currentContextTokens: 0,
46
+ costUsd: 0,
47
+ turns: 0,
48
+ };
49
+ }
50
+ function num(v) { const n = Number(v); return Number.isFinite(n) ? n : 0; }
51
+ function applyUsageDelta(stats, delta = {}) {
52
+ if (!stats || !delta) return stats;
53
+ const inputTokens = num(delta.deltaInput);
54
+ const outputTokens = num(delta.deltaOutput);
55
+ const cachedTokens = num(delta.deltaCachedRead);
56
+ const cacheWriteTokens = num(delta.deltaCacheWrite);
57
+ const promptTokens = num(delta.deltaPrompt);
58
+ stats.inputTokens += inputTokens;
59
+ stats.outputTokens += outputTokens;
60
+ stats.cachedTokens += cachedTokens;
61
+ stats.cacheWriteTokens += cacheWriteTokens;
62
+ stats.promptTokens += promptTokens;
63
+ stats.latestInputTokens = inputTokens;
64
+ stats.latestOutputTokens = outputTokens;
65
+ stats.latestCachedTokens = cachedTokens;
66
+ stats.latestCacheWriteTokens = cacheWriteTokens;
67
+ stats.latestPromptTokens = promptTokens;
68
+ stats.costUsd += num(delta.costUsd);
69
+ return stats;
70
+ }
71
+
72
+ // Source tests resolve from src/tui/engine.mjs; the built bundle resolves from
73
+ // src/tui/dist/index.mjs.
74
+ const SESSION_RUNTIME_MODULE = import.meta.url.replace(/\\/g, '/').includes('/tui/dist/')
75
+ ? '../../mixdog-session-runtime.mjs'
76
+ : '../mixdog-session-runtime.mjs';
77
+
78
+ let _idSeq = 0;
79
+ const nextId = () => `it_${++_idSeq}`;
80
+
81
+ function pickVerb(turn) {
82
+ return SPINNER_VERBS[(turn * 7 + 3) % SPINNER_VERBS.length];
83
+ }
84
+
85
+ const TURN_DONE_VERBS = [
86
+ 'Thought',
87
+ 'Reasoned',
88
+ 'Mapped',
89
+ 'Checked',
90
+ 'Solved',
91
+ 'Composed',
92
+ 'Synthesized',
93
+ 'Wrapped',
94
+ ];
95
+
96
+ function pickDoneVerb(turn) {
97
+ return TURN_DONE_VERBS[(turn * 5 + 2) % TURN_DONE_VERBS.length];
98
+ }
99
+
100
+ function formatIdleDuration(ms) {
101
+ const value = Math.max(0, Number(ms) || 0);
102
+ if (value >= 3_600_000 && value % 3_600_000 === 0) return `${value / 3_600_000}h`;
103
+ if (value >= 60_000) return `${Math.round(value / 60_000)}m`;
104
+ if (value < 1_000) return '';
105
+ return `${Math.floor(value / 1000)}s`;
106
+ }
107
+
108
+ function formatTokenCount(value) {
109
+ const n = Number(value || 0);
110
+ if (!Number.isFinite(n) || n <= 0) return '0';
111
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}m`;
112
+ if (n >= 10_000) return `${Math.round(n / 1000)}k`;
113
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
114
+ return `${Math.round(n)}`;
115
+ }
116
+
117
+ const FAILED_NOTICE_ACTIONS = new Map([
118
+ ['api key save', 'save API key'],
119
+ ['auth-forget', 'forget auth'],
120
+ ['auto-clear', 'update auto-clear'],
121
+ ['autoclear', 'update auto-clear'],
122
+ ['bridge', 'run bridge command'],
123
+ ['channels', 'load channels'],
124
+ ['channels update', 'update channels'],
125
+ ['clear', 'clear chat'],
126
+ ['compact', 'compact context'],
127
+ ['copy', 'copy'],
128
+ ['core memory', 'load core memory'],
129
+ ['cwd', 'update working directory'],
130
+ ['effort switch', 'switch effort'],
131
+ ['fast', 'update fast mode'],
132
+ ['hook rule update', 'update hook rule'],
133
+ ['hook toggle', 'toggle hook'],
134
+ ['hook update', 'update hook'],
135
+ ['hooks status', 'load hooks'],
136
+ ['local provider update', 'update local provider'],
137
+ ['mcp add', 'add MCP server'],
138
+ ['mcp reconnect', 'reconnect MCP server'],
139
+ ['mcp status', 'load MCP status'],
140
+ ['mcp toggle', 'toggle MCP server'],
141
+ ['memory', 'run memory command'],
142
+ ['memory status', 'load memory status'],
143
+ ['model save', 'save model'],
144
+ ['model switch', 'switch model'],
145
+ ['oauth code', 'finish OAuth login'],
146
+ ['oauth login', 'start OAuth login'],
147
+ ['output style switch', 'switch output style'],
148
+ ['OpenAI usage auth save', 'save OpenAI usage auth'],
149
+ ['OpenCode Go usage auth save', 'save OpenCode Go usage auth'],
150
+ ['plugin add', 'add plugin'],
151
+ ['plugin MCP enable', 'enable plugin MCP'],
152
+ ['plugin uninstall', 'uninstall plugin'],
153
+ ['plugin update', 'update plugin'],
154
+ ['plugins status', 'load plugins'],
155
+ ['providers', 'load providers'],
156
+ ['recall', 'run recall'],
157
+ ['resume', 'resume chat'],
158
+ ['schedule toggle', 'toggle schedule'],
159
+ ['setup save', 'save setup'],
160
+ ['settings update', 'update settings'],
161
+ ['skill add', 'add skill'],
162
+ ['skills status', 'load skills'],
163
+ ['tools status', 'load tool status'],
164
+ ['usage', 'load usage'],
165
+ ['webhook toggle', 'toggle webhook'],
166
+ ['workflow switch', 'switch workflow'],
167
+ ]);
168
+
169
+ function polishNoticeAction(action) {
170
+ const value = String(action || '').trim();
171
+ if (!value) return 'finish';
172
+ const key = value.toLowerCase();
173
+ for (const [candidate, replacement] of FAILED_NOTICE_ACTIONS.entries()) {
174
+ if (candidate.toLowerCase() === key) return replacement;
175
+ }
176
+ const suffixes = [
177
+ [' save', 'save'],
178
+ [' switch', 'switch'],
179
+ [' update', 'update'],
180
+ [' toggle', 'toggle'],
181
+ [' reconnect', 'reconnect'],
182
+ [' enable', 'enable'],
183
+ [' uninstall', 'uninstall'],
184
+ [' add', 'add'],
185
+ ];
186
+ for (const [suffix, verb] of suffixes) {
187
+ if (!key.endsWith(suffix)) continue;
188
+ const subject = value.slice(0, -suffix.length).trim();
189
+ return subject ? `${verb} ${subject}` : verb;
190
+ }
191
+ return value;
192
+ }
193
+
194
+ function sentenceStart(text) {
195
+ const value = String(text || '').trim();
196
+ return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
197
+ }
198
+
199
+ function polishNoticeText(text) {
200
+ let value = String(text ?? '').trim().replace(/^✓\s*/, '');
201
+ if (!value) return '';
202
+ const error = /^error\s*:\s*(.+)$/i.exec(value);
203
+ if (error?.[1]) value = error[1].trim();
204
+ const couldNot = /^could not\s+(.+?)(?::\s*(.+))?$/i.exec(value);
205
+ if (couldNot) {
206
+ return couldNot[2]
207
+ ? `Couldn’t ${couldNot[1]}: ${couldNot[2]}`
208
+ : `Couldn’t ${couldNot[1]}.`;
209
+ }
210
+ const failed = /^(.+?)\s+failed(?::\s*(.+))?$/i.exec(value);
211
+ if (failed) {
212
+ const action = polishNoticeAction(failed[1]);
213
+ return failed[2] ? `Couldn’t ${action}: ${failed[2]}` : `Couldn’t ${action}.`;
214
+ }
215
+ const busy = /^(.+?)\s+already in progress\.?$/i.exec(value);
216
+ if (busy) return `${sentenceStart(polishNoticeAction(busy[1]))} is already running.`;
217
+ const required = /^(.+?)\s+is required(?:\s+for\s+(.+))?\.?$/i.exec(value);
218
+ if (required) {
219
+ const subject = required[1].trim();
220
+ const target = required[2]?.trim();
221
+ return `${subject}${target ? ` required for ${target}` : ' required'}.`;
222
+ }
223
+ return value;
224
+ }
225
+
226
+ function toolResultText(content) {
227
+ if (content == null) return '';
228
+ if (typeof content === 'string') return content;
229
+ const parts = Array.isArray(content)
230
+ ? content
231
+ : (content && typeof content === 'object' && Array.isArray(content.content) ? content.content : null);
232
+ if (parts) {
233
+ return parts.map((c) => {
234
+ if (typeof c === 'string') return c;
235
+ if (c?.type === 'image') return `[image: ${c.mimeType || c.mediaType || c.source?.media_type || 'image'}]`;
236
+ return c?.text ?? '';
237
+ }).filter(Boolean).join('\n');
238
+ }
239
+ if (Array.isArray(content)) {
240
+ return content.map((c) => (typeof c === 'string' ? c : c?.text ?? '')).filter(Boolean).join('\n');
241
+ }
242
+ if (typeof content === 'object' && typeof content.text === 'string') return content.text;
243
+ try { return JSON.stringify(content); } catch { return String(content); }
244
+ }
245
+
246
+ function toolErrorDisplay(value, surface = 'tool') {
247
+ const text = presentErrorText(value, { surface });
248
+ if (/^(?:Search failed|Fetch failed|No first response|The .+ went stale|(?:Web search agent|Agent|Tool) (?:stopped|was cancelled))/i.test(text)) {
249
+ return text;
250
+ }
251
+ return /^error\s*:/i.test(text) ? text : `Error: ${text}`;
252
+ }
253
+
254
+ function toolCallId(call) {
255
+ return call?.id ?? call?.toolCallId ?? call?.tool_call_id ?? call?.call_id;
256
+ }
257
+
258
+ function toolResultCallId(message) {
259
+ return message?.toolCallId
260
+ ?? message?.tool_call_id
261
+ ?? message?.tool_use_id
262
+ ?? message?.call_id
263
+ ?? message?.id;
264
+ }
265
+
266
+ function toolCallName(call) {
267
+ return call?.name ?? call?.function?.name ?? call?.toolName ?? call?.tool_name ?? 'tool';
268
+ }
269
+
270
+ function toolCallArgs(call) {
271
+ return call?.arguments ?? call?.args ?? call?.input ?? call?.function?.arguments;
272
+ }
273
+
274
+ function textBetweenTag(text, tag) {
275
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
276
+ const match = re.exec(String(text ?? ''));
277
+ return match ? match[1].trim() : '';
278
+ }
279
+
280
+ function stripSyntheticAgentTags(text) {
281
+ const value = String(text ?? '').trim();
282
+ const finalAnswer = textBetweenTag(value, 'final-answer');
283
+ if (finalAnswer) return finalAnswer;
284
+ const taskResult = textBetweenTag(value, 'result');
285
+ if (taskResult) return taskResult;
286
+ return value
287
+ .replace(/^bridge result[^\n]*(?:\n|$)/i, '')
288
+ .replace(/<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>/gi, '')
289
+ .trim();
290
+ }
291
+
292
+ function splitBridgeEnvelope(text) {
293
+ const value = String(text ?? '').trim();
294
+ if (!value) return { head: '', body: '' };
295
+ const match = /\n\s*\n/.exec(value);
296
+ if (!match) return { head: value, body: '' };
297
+ return {
298
+ head: value.slice(0, match.index).trim(),
299
+ body: value.slice(match.index + match[0].length).trim(),
300
+ };
301
+ }
302
+
303
+ function bridgeJobStatusText(parsed) {
304
+ if (!parsed) return '';
305
+ const parts = [];
306
+ if (parsed.status) parts.push(`status: ${parsed.status}`);
307
+ if (parsed.taskId) parts.push(`task_id: ${parsed.taskId}`);
308
+ return parts.join(' · ');
309
+ }
310
+
311
+ function bridgeJobResultText(text, parsed = parseBridgeJob(text)) {
312
+ const value = String(text ?? '').trim();
313
+ if (!value) return '';
314
+ if (parsed?.taskId) {
315
+ const { body } = splitBridgeEnvelope(value);
316
+ const cleanBody = stripSyntheticAgentTags(body);
317
+ if (cleanBody) return cleanBody;
318
+ return bridgeJobStatusText(parsed);
319
+ }
320
+ return stripSyntheticAgentTags(value) || value;
321
+ }
322
+
323
+ function parseBackgroundTaskEnvelope(text) {
324
+ const value = String(text ?? '').trim();
325
+ if (!/^background task\b/i.test(value)) return null;
326
+ const allLines = value.split('\n');
327
+ const rest = allLines.slice(1);
328
+ const blank = rest.findIndex((line) => !line.trim());
329
+ const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
330
+ const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
331
+ const fields = {};
332
+ for (const line of headLines) {
333
+ const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
334
+ if (match) fields[match[1].toLowerCase()] = match[2].trim();
335
+ }
336
+ const surface = String(fields.surface || fields.operation || 'task').toLowerCase();
337
+ const name = surface === 'explore' || surface === 'search' || surface === 'shell' ? surface : 'task';
338
+ const status = String(fields.status || '').toLowerCase();
339
+ const taskId = fields.task_id || fields.taskid || '';
340
+ return {
341
+ name,
342
+ label: status || 'notification',
343
+ args: {
344
+ type: body ? 'result' : (fields.operation || 'status'),
345
+ status,
346
+ task_id: taskId || undefined,
347
+ surface,
348
+ label: fields.label || undefined,
349
+ },
350
+ result: body || [status ? `status: ${status}` : '', taskId ? `task_id: ${taskId}` : ''].filter(Boolean).join(' · ') || 'background task',
351
+ isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status) || /^error:/i.test(body),
352
+ };
353
+ }
354
+
355
+ function bracketField(text, name) {
356
+ const re = new RegExp(`^\\[${name}:\\s*([^\\]]*)\\]`, 'mi');
357
+ return re.exec(String(text ?? ''))?.[1]?.trim() || '';
358
+ }
359
+
360
+ function parseSyntheticAgentMessage(text) {
361
+ const value = String(text ?? '').trim();
362
+ if (!value) return null;
363
+ const finalAnswer = textBetweenTag(value, 'final-answer');
364
+ if (finalAnswer) {
365
+ return {
366
+ name: 'bridge',
367
+ label: 'final',
368
+ args: { type: 'read', description: 'agent result' },
369
+ result: finalAnswer,
370
+ };
371
+ }
372
+ const backgroundTask = parseBackgroundTaskEnvelope(value);
373
+ if (backgroundTask) return backgroundTask;
374
+ const shellTaskId = bracketField(value, 'task_id');
375
+ if (shellTaskId) {
376
+ const status = bracketField(value, 'status') || 'done';
377
+ const exit = bracketField(value, 'exit');
378
+ const command = bracketField(value, 'command');
379
+ return {
380
+ name: 'shell',
381
+ label: status,
382
+ args: { type: 'result', task_id: shellTaskId, command },
383
+ result: value,
384
+ isError: /^(failed|error|timeout|cancelled|killed)$/i.test(status) || (exit && exit !== '0' && exit !== 'n/a'),
385
+ };
386
+ }
387
+ const bridgeJob = parseBridgeJob(value);
388
+ if (bridgeJob?.taskId) {
389
+ const label = bridgeJob.status || 'notification';
390
+ const result = bridgeJobResultText(value, bridgeJob);
391
+ return {
392
+ name: 'bridge',
393
+ label,
394
+ args: bridgeArgsWithResultMetadata({ type: bridgeJob.type || 'notification', description: 'agent notification' }, bridgeJob),
395
+ result: result || bridgeJobStatusText(bridgeJob) || 'agent notification',
396
+ isError: /^(failed|error|timeout|cancelled|killed)$/i.test(label),
397
+ };
398
+ }
399
+ if (/<task-notification\b/i.test(value)) {
400
+ const status = textBetweenTag(value, 'status') || 'completed';
401
+ const summary = textBetweenTag(value, 'summary') || `Agent ${status}`;
402
+ const taskId = textBetweenTag(value, 'task-id');
403
+ const result = stripSyntheticAgentTags(value);
404
+ return {
405
+ name: 'bridge',
406
+ label: status,
407
+ taskId,
408
+ summary,
409
+ result: result || summary,
410
+ };
411
+ }
412
+ return null;
413
+ }
414
+
415
+ function normalizeToolName(name) {
416
+ return String(name || 'tool')
417
+ .replace(/^mcp__.*__/, '')
418
+ .replace(/^functions\./, '')
419
+ .replace(/-/g, '_')
420
+ .toLowerCase();
421
+ }
422
+
423
+ function parseToolArgs(args) {
424
+ if (!args) return {};
425
+ if (typeof args === 'string') {
426
+ try {
427
+ const parsed = JSON.parse(args);
428
+ return parsed && typeof parsed === 'object' ? parsed : {};
429
+ } catch {
430
+ return {};
431
+ }
432
+ }
433
+ return typeof args === 'object' ? args : {};
434
+ }
435
+
436
+ const yieldToRenderer = () => new Promise((resolve) => setImmediate(resolve));
437
+
438
+ function parseBridgeJob(text) {
439
+ const value = String(text || '');
440
+ const idMatch = /^bridge task:\s*([^\s]+)/m.exec(value) || /^task_id:\s*([^\s]+)/m.exec(value);
441
+ if (!idMatch) return null;
442
+ const statusMatch = /^status:\s*([^\s(]+)/m.exec(value);
443
+ const typeMatch = /^type:\s*(.+)$/m.exec(value);
444
+ const targetMatch = /^target:\s*(.+)$/m.exec(value);
445
+ const roleMatch = /^(?:agent|role):\s*(.+)$/m.exec(value);
446
+ const presetMatch = /^preset:\s*(.+)$/m.exec(value);
447
+ const modelMatch = /^model:\s*([^/\s]+)\/(.+)$/m.exec(value);
448
+ const effortMatch = /^effort:\s*(.+)$/m.exec(value);
449
+ const fastMatch = /^fast:\s*(on|off|true|false)$/m.exec(value);
450
+ return {
451
+ taskId: idMatch[1],
452
+ status: (statusMatch?.[1] || '').toLowerCase(),
453
+ type: (typeMatch?.[1] || '').trim(),
454
+ target: (targetMatch?.[1] || '').trim(),
455
+ role: (roleMatch?.[1] || '').trim(),
456
+ preset: (presetMatch?.[1] || '').trim(),
457
+ provider: (modelMatch?.[1] || '').trim(),
458
+ model: (modelMatch?.[2] || '').trim(),
459
+ effort: (effortMatch?.[1] || '').trim(),
460
+ fast: fastMatch ? /^(on|true)$/i.test(fastMatch[1]) : undefined,
461
+ };
462
+ }
463
+
464
+ const QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
465
+
466
+ function queuePriorityValue(value) {
467
+ return QUEUE_PRIORITY[String(value || 'next')] ?? QUEUE_PRIORITY.next;
468
+ }
469
+
470
+ function defaultQueuePriority(mode) {
471
+ return mode === 'task-notification' ? 'next' : 'later';
472
+ }
473
+
474
+ function isQueuedEntryEditable(entry) {
475
+ return (entry?.mode || 'prompt') !== 'task-notification';
476
+ }
477
+
478
+ function firstQueueLine(text) {
479
+ return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
480
+ }
481
+
482
+ function notificationDisplayText(text) {
483
+ const parsed = parseBridgeJob(text);
484
+ const result = bridgeJobResultText(text, parsed);
485
+ const synthetic = parseSyntheticAgentMessage(text);
486
+ return firstQueueLine(synthetic?.result || result || text) || 'agent notification';
487
+ }
488
+
489
+ function promptContentText(content) {
490
+ if (typeof content === 'string') return content;
491
+ if (Array.isArray(content)) {
492
+ return content.map((part) => {
493
+ if (typeof part === 'string') return part;
494
+ if (part?.type === 'text') return part.text || '';
495
+ if (part?.type === 'image') return '[Image]';
496
+ return part?.text || '';
497
+ }).filter(Boolean).join('\n');
498
+ }
499
+ return String(content ?? '');
500
+ }
501
+
502
+ function promptDisplayText(content, options = {}) {
503
+ if (typeof options.displayText === 'string') return options.displayText;
504
+ return promptContentText(content);
505
+ }
506
+
507
+ function mergePromptContents(entries) {
508
+ const batch = Array.isArray(entries) ? entries : [];
509
+ if (batch.every((entry) => typeof entry?.content === 'string')) {
510
+ return batch.map((entry) => entry.content).filter((text) => String(text || '').trim()).join('\n');
511
+ }
512
+ const parts = [];
513
+ for (const entry of batch) {
514
+ const content = entry?.content;
515
+ if (typeof content === 'string') {
516
+ if (content.trim()) parts.push({ type: 'text', text: content });
517
+ } else if (Array.isArray(content)) {
518
+ parts.push(...content);
519
+ }
520
+ parts.push({ type: 'text', text: '\n' });
521
+ }
522
+ while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
523
+ return parts.length === 1 && parts[0]?.type === 'text' ? parts[0].text : parts;
524
+ }
525
+
526
+ function mergePastedImages(entries) {
527
+ const out = {};
528
+ for (const entry of entries || []) {
529
+ const images = entry?.pastedImages;
530
+ if (!images || typeof images !== 'object') continue;
531
+ for (const [id, image] of Object.entries(images)) {
532
+ if (image) out[id] = image;
533
+ }
534
+ }
535
+ return Object.keys(out).length > 0 ? out : null;
536
+ }
537
+
538
+ function callCommitCallbacks(entries) {
539
+ for (const entry of entries || []) {
540
+ try { entry?.onCommitted?.(); } catch {}
541
+ }
542
+ }
543
+
544
+ function notificationQueueKey(event, text, parsed) {
545
+ const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
546
+ const id = String(meta.execution_id || parsed?.taskId || '').trim();
547
+ if (!id) return '';
548
+ const type = String(meta.type || '').trim();
549
+ const status = String(meta.status || parsed?.status || '').trim();
550
+ const fallbackKind = String(text || '').split('\n', 1)[0]?.trim() || 'notification';
551
+ return [id, type || fallbackKind, status].filter(Boolean).join(':');
552
+ }
553
+
554
+ function bridgeArgsWithResultMetadata(args, parsed) {
555
+ if (!parsed) return args;
556
+ const next = { ...(args && typeof args === 'object' ? args : {}) };
557
+ if (parsed.type) next.type = parsed.type;
558
+ if (parsed.status) next.status = parsed.status;
559
+ if (parsed.taskId) next.task_id = parsed.taskId;
560
+ if (parsed.role) next.role = parsed.role;
561
+ if (parsed.preset) next.preset = parsed.preset;
562
+ if (parsed.provider) next.provider = parsed.provider;
563
+ if (parsed.model) next.model = parsed.model;
564
+ if (parsed.effort) next.effort = parsed.effort;
565
+ if (parsed.fast !== undefined) next.fast = parsed.fast;
566
+ if (!next.tag && parsed.target) {
567
+ const target = parsed.target.split(/\s+/)[0];
568
+ if (target && !target.startsWith('sess_')) next.tag = target;
569
+ }
570
+ return next;
571
+ }
572
+
573
+ export async function createEngineSession({
574
+ provider: providerName,
575
+ model,
576
+ toolMode = 'full',
577
+ } = {}) {
578
+ const startedAt = performance.now();
579
+ bootProfile('engine:create:start', { provider: providerName, model, toolMode });
580
+ // Silence provider/session diagnostics so they cannot tear through the
581
+ // alternate-screen React/ink render.
582
+ process.env.MIXDOG_QUIET_PROVIDER_LOG = '1';
583
+ process.env.MIXDOG_QUIET_SESSION_LOG = '1';
584
+ process.env.MIXDOG_QUIET_MCP_LOG = '1';
585
+ process.env.MIXDOG_QUIET_MEMORY_LOG = '1';
586
+ process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= '0';
587
+
588
+ const importStartedAt = performance.now();
589
+ const { createMixdogSessionRuntime } = await import(SESSION_RUNTIME_MODULE);
590
+ bootProfile('session-runtime:imported', { ms: (performance.now() - importStartedAt).toFixed(1) });
591
+ const runtime = await createMixdogSessionRuntime({ provider: providerName, model, toolMode });
592
+ bootProfile('engine:create:runtime-ready', { ms: (performance.now() - startedAt).toFixed(1) });
593
+ const cwd = runtime.cwd || process.cwd();
594
+ const stateStartedAt = performance.now();
595
+ const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000 };
596
+ const BRIDGE_STATUS_CACHE_MS = 250;
597
+ let bridgeStatusCache = null;
598
+ let bridgeStatusCacheAt = 0;
599
+ const bridgeStatusState = ({ force = false } = {}) => {
600
+ const now = Date.now();
601
+ if (!force && bridgeStatusCache && now - bridgeStatusCacheAt < BRIDGE_STATUS_CACHE_MS) return bridgeStatusCache;
602
+ const status = runtime.bridgeStatus?.() || {};
603
+ bridgeStatusCache = {
604
+ bridgeMode: runtime.bridgeMode || status.bridgeMode || 'async',
605
+ bridgeWorkers: Array.isArray(status.bridgeWorkers) ? status.bridgeWorkers : [],
606
+ bridgeJobs: Array.isArray(status.bridgeJobs) ? status.bridgeJobs : [],
607
+ bridgeScope: status.bridgeScope || null,
608
+ };
609
+ bridgeStatusCacheAt = now;
610
+ return bridgeStatusCache;
611
+ };
612
+ const routeState = () => ({
613
+ sessionId: runtime.id,
614
+ clientHostPid: runtime.clientHostPid || null,
615
+ model: runtime.model,
616
+ provider: runtime.provider,
617
+ effort: runtime.effort,
618
+ effortOptions: runtime.effortOptions,
619
+ fast: runtime.fast,
620
+ fastCapable: runtime.fastCapable,
621
+ contextWindow: runtime.contextWindow,
622
+ rawContextWindow: runtime.rawContextWindow,
623
+ effectiveContextWindowPercent: runtime.effectiveContextWindowPercent,
624
+ cwd: runtime.cwd || process.cwd(),
625
+ systemShell: runtime.systemShell || { source: 'auto', command: '', effective: '' },
626
+ autoClear: autoClearState(),
627
+ workflow: runtime.workflow || null,
628
+ });
629
+
630
+ const routeStateStartedAt = performance.now();
631
+ const initialRouteState = routeState();
632
+ bootProfile('engine:route-state-ready', { ms: (performance.now() - routeStateStartedAt).toFixed(1) });
633
+ const initialBridgeState = {
634
+ bridgeMode: runtime.bridgeMode || 'async',
635
+ bridgeWorkers: [],
636
+ bridgeJobs: [],
637
+ bridgeScope: null,
638
+ };
639
+ let state = {
640
+ items: [],
641
+ toasts: [],
642
+ busy: false,
643
+ commandBusy: false,
644
+ commandStatus: null,
645
+ spinner: null,
646
+ queued: [],
647
+ thinking: null,
648
+ lastTurn: null,
649
+ stats: createSessionStats(),
650
+ ...initialRouteState,
651
+ ...initialBridgeState,
652
+ toolMode: runtime.toolMode,
653
+ cwd,
654
+ };
655
+ bootProfile('engine:state-ready', { ms: (performance.now() - stateStartedAt).toFixed(1) });
656
+ const syncContextStats = ({ allowEstimated = false } = {}) => {
657
+ const ctx = runtime.contextStatus?.() || null;
658
+ const hasProviderUsage = Number(state.stats.latestPromptTokens || state.stats.latestInputTokens || state.stats.inputTokens || 0) > 0;
659
+ const lastApiTokens = Number(ctx?.lastApiRequestStale ? 0 : (ctx?.lastApiRequestTokens || ctx?.usage?.lastContextTokens || 0));
660
+ const estimatedTokens = Number(ctx?.currentEstimatedTokens || 0);
661
+ const used = lastApiTokens;
662
+ if (!allowEstimated && !hasProviderUsage && ctx?.usedSource !== 'last_api_request') return ctx;
663
+ if (Number.isFinite(used) && used > 0) state.stats.currentContextTokens = Math.max(0, used);
664
+ else state.stats.currentContextTokens = 0;
665
+ state.stats.currentEstimatedContextTokens = Number.isFinite(estimatedTokens) ? Math.max(0, estimatedTokens) : 0;
666
+ state.stats.currentContextSource = used > 0 ? 'last_api_request' : (estimatedTokens > 0 ? 'estimated' : null);
667
+ state.stats.currentContextUpdatedAt = Date.now();
668
+ return ctx;
669
+ };
670
+ const contextStartedAt = performance.now();
671
+ syncContextStats({ allowEstimated: true });
672
+ bootProfile('engine:context-ready', { ms: (performance.now() - contextStartedAt).toFixed(1) });
673
+ const listeners = new Set();
674
+ const emit = () => { for (const l of listeners) l(); };
675
+ const set = (patch) => {
676
+ if (!patch || typeof patch !== 'object') return false;
677
+ let changed = false;
678
+ for (const [key, value] of Object.entries(patch)) {
679
+ if (!Object.is(state[key], value)) {
680
+ changed = true;
681
+ break;
682
+ }
683
+ }
684
+ if (!changed) return false;
685
+ state = { ...state, ...patch };
686
+ emit();
687
+ return true;
688
+ };
689
+
690
+ const itemIndexById = new Map();
691
+ const replaceItems = (items) => {
692
+ const nextItems = Array.isArray(items) ? items : [];
693
+ itemIndexById.clear();
694
+ for (let i = 0; i < nextItems.length; i++) {
695
+ const id = nextItems[i]?.id;
696
+ if (id != null) itemIndexById.set(id, i);
697
+ }
698
+ return nextItems;
699
+ };
700
+ const pushItem = (item) => {
701
+ const index = state.items.length;
702
+ const items = [...state.items, item];
703
+ if (item?.id != null) itemIndexById.set(item.id, index);
704
+ set({ items });
705
+ };
706
+ const removeItemsByIds = (ids) => {
707
+ const idSet = new Set((ids || []).filter((id) => id != null));
708
+ if (idSet.size === 0) return false;
709
+ const items = state.items.filter((item) => !idSet.has(item?.id));
710
+ if (items.length === state.items.length) return false;
711
+ set({ items: replaceItems(items) });
712
+ return true;
713
+ };
714
+ const pushUserOrSyntheticItem = (text, id = nextId()) => {
715
+ const synthetic = parseSyntheticAgentMessage(text);
716
+ if (!synthetic) {
717
+ pushItem({ kind: 'user', id, text });
718
+ return;
719
+ }
720
+ const label = synthetic.label || 'notification';
721
+ pushItem({
722
+ kind: 'tool',
723
+ id,
724
+ name: synthetic.name || 'bridge',
725
+ args: synthetic.args || {
726
+ type: label,
727
+ task_id: synthetic.taskId || undefined,
728
+ description: synthetic.summary || 'agent notification',
729
+ },
730
+ result: synthetic.result,
731
+ isError: synthetic.isError ?? /^(failed|error|killed|cancelled)$/i.test(label),
732
+ expanded: false,
733
+ count: 1,
734
+ completedCount: 1,
735
+ startedAt: Date.now(),
736
+ completedAt: Date.now(),
737
+ });
738
+ };
739
+ const pushToast = (text, tone = 'info', ttlMs = 3000) => {
740
+ const id = nextId();
741
+ const value = String(text ?? '').trim();
742
+ if (!value) return null;
743
+ set({ toasts: [...state.toasts.filter((toast) => toast.id !== id), { id, text: value, tone }] });
744
+ const timer = setTimeout(() => {
745
+ toastTimers.delete(timer);
746
+ if (disposed) return;
747
+ set({ toasts: state.toasts.filter((toast) => toast.id !== id) });
748
+ }, ttlMs);
749
+ toastTimers.add(timer);
750
+ timer.unref?.();
751
+ return id;
752
+ };
753
+ const pushNotice = (text, tone = 'info', options = {}) => {
754
+ const value = polishNoticeText(text);
755
+ if (!value) return null;
756
+ const forceTranscript = options.transcript === true;
757
+ if (!forceTranscript) return pushToast(value, tone, options.ttlMs);
758
+ const id = nextId();
759
+ pushItem({ kind: 'notice', id, text: value, tone });
760
+ return id;
761
+ };
762
+ const patchItem = (id, patch) => {
763
+ let index = itemIndexById.get(id);
764
+ if (!Number.isInteger(index) || state.items[index]?.id !== id) {
765
+ index = state.items.findIndex((it) => it.id === id);
766
+ if (index >= 0) itemIndexById.set(id, index);
767
+ }
768
+ if (index < 0) return false;
769
+ const current = state.items[index];
770
+ let changed = false;
771
+ for (const [key, value] of Object.entries(patch || {})) {
772
+ if (!Object.is(current[key], value)) {
773
+ changed = true;
774
+ break;
775
+ }
776
+ }
777
+ if (!changed) return false;
778
+ const items = state.items.slice();
779
+ items[index] = { ...current, ...patch };
780
+ set({ items });
781
+ return true;
782
+ };
783
+ const toastTimers = new Set();
784
+ let disposed = false;
785
+
786
+ function clearToastTimers() {
787
+ for (const timer of toastTimers) {
788
+ clearTimeout(timer);
789
+ }
790
+ toastTimers.clear();
791
+ }
792
+
793
+ let unsubscribeRuntimeNotifications = null;
794
+ let lastUserActivityAt = Date.now();
795
+ let autoClearRunning = false;
796
+ const pendingNotificationKeys = new Set();
797
+
798
+ function updateBridgeJobCard(itemId, text, isError = false) {
799
+ const parsed = parseBridgeJob(text);
800
+ const current = state.items.find((it) => it.id === itemId);
801
+ const rawDisplayText = bridgeJobResultText(text, parsed) || String(text || '').trim();
802
+ const displayText = isError ? toolErrorDisplay(rawDisplayText, 'bridge') : rawDisplayText;
803
+ patchItem(itemId, {
804
+ result: displayText,
805
+ text: displayText,
806
+ isError,
807
+ errorCount: isError ? 1 : 0,
808
+ ...(parsed ? { args: bridgeArgsWithResultMetadata(current?.args, parsed) } : {}),
809
+ });
810
+ }
811
+
812
+ if (typeof runtime.onNotification === 'function') {
813
+ unsubscribeRuntimeNotifications = runtime.onNotification((event) => {
814
+ if (disposed) return;
815
+ const text = String(event?.content ?? event?.text ?? event ?? '').trim();
816
+ if (!text) return;
817
+ const parsed = parseBridgeJob(text);
818
+ const notificationKey = notificationQueueKey(event, text, parsed);
819
+ if (parsed?.taskId) {
820
+ const existing = [...state.items].reverse().find((item) => {
821
+ if (!item || item.kind !== 'tool' || item.name !== 'bridge') return false;
822
+ const args = parseToolArgs(item.args);
823
+ return args.task_id === parsed.taskId;
824
+ });
825
+ if (existing) {
826
+ updateBridgeJobCard(existing.id, text, /^(failed|error|timeout|cancelled|killed)$/i.test(parsed.status));
827
+ }
828
+ }
829
+ enqueue(text, {
830
+ mode: 'task-notification',
831
+ priority: 'next',
832
+ key: notificationKey || undefined,
833
+ });
834
+ });
835
+ }
836
+
837
+ function groupedToolResultText(group) {
838
+ const completed = Math.min(group.count, group.completed);
839
+ if (group.count <= 1) return group.results.at(-1)?.text ?? '';
840
+ if (group.errors > 0) {
841
+ const succeeded = Math.max(0, completed - group.errors);
842
+ const reasons = group.results
843
+ .filter((result) => result?.isError)
844
+ .map((result) => firstErrorLine(result?.text))
845
+ .filter(Boolean);
846
+ const uniqueReasons = [...new Set(reasons)].slice(0, 2);
847
+ const base = succeeded > 0
848
+ ? `${succeeded} Ok · ${group.errors} Failed`
849
+ : `${group.errors} Failed`;
850
+ return [
851
+ `${base}${uniqueReasons[0] ? ` · ${uniqueReasons[0]}` : ''}`,
852
+ ...uniqueReasons.slice(1),
853
+ ].join('\n');
854
+ }
855
+ return '';
856
+ }
857
+
858
+ function firstErrorLine(text) {
859
+ const clean = toolErrorDisplay(text, 'tool');
860
+ if (clean) return clean;
861
+ for (const line of String(text || '').split('\n')) {
862
+ const trimmed = line.trim();
863
+ if (!trimmed) continue;
864
+ if (/^(Error|\[?error|FAIL\b)/i.test(trimmed)) return trimmed;
865
+ }
866
+ return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
867
+ }
868
+
869
+ function aggregateRawResult(calls) {
870
+ const chunks = [];
871
+ for (const rec of calls || []) {
872
+ const text = String(rec?.resultText || '').replace(/\s+$/, '');
873
+ if (!text.trim()) continue;
874
+ const label = String(rec?.name || rec?.category || 'tool').trim() || 'tool';
875
+ chunks.push(`${chunks.length + 1}. ${label}\n${text}`);
876
+ }
877
+ return chunks.join('\n\n');
878
+ }
879
+
880
+ function aggregateBucketForCategory(category) {
881
+ switch (category) {
882
+ case 'Read':
883
+ case 'Search':
884
+ return 'local-discovery';
885
+ case 'Web Research':
886
+ return 'web-research';
887
+ case 'Memory':
888
+ return 'memory';
889
+ case 'Explore':
890
+ return 'explore';
891
+ case 'Patch':
892
+ return 'patch';
893
+ default:
894
+ // Shell/Agent/Channel/Setup/Other stay as their own cards so risky or
895
+ // semantically distinct actions do not disappear inside a discovery log.
896
+ return null;
897
+ }
898
+ }
899
+
900
+ function aggregateSummaries(aggregate) {
901
+ return [...(aggregate?.calls?.values?.() || [])]
902
+ .filter((r) => r.summary)
903
+ .sort((a, b) => Number(a.summarySeq ?? 0) - Number(b.summarySeq ?? 0))
904
+ .map((r) => r.summary);
905
+ }
906
+
907
+ function assignAggregateSummaryOrder(aggregate, callRec) {
908
+ if (!aggregate || !callRec?.summary || callRec.summarySeq != null) return;
909
+ const next = Math.max(0, Number(aggregate.nextSummarySeq || 0));
910
+ callRec.summarySeq = next;
911
+ aggregate.nextSummarySeq = next + 1;
912
+ }
913
+
914
+ function patchToolCardResult(card, message, toolGroups, done) {
915
+ if (!card || card.done) return false;
916
+ const callId = toolResultCallId(message) || card.callId;
917
+ if (callId && done.has(callId)) return false;
918
+ const rawText = toolResultText(message?.content);
919
+ const isError = message?.isError === true || message?.toolKind === 'error' || /^\s*\[?error/i.test(rawText);
920
+ const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
921
+
922
+ // Aggregate card handling — collect semantic summaries per call
923
+ const aggregate = card.aggregate;
924
+ if (aggregate && card.itemId === aggregate.itemId) {
925
+ const callRec = callId ? aggregate.calls.get(callId) : null;
926
+ if (callRec) {
927
+ callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
928
+ assignAggregateSummaryOrder(aggregate, callRec);
929
+ callRec.isError = isError;
930
+ callRec.resultText = text;
931
+ callRec.resolved = true;
932
+ }
933
+ const allCalls = [...aggregate.calls.values()];
934
+ const completed = allCalls.filter((r) => r.resolved).length;
935
+ const errors = allCalls.filter((r) => r.isError).length;
936
+ const summaries = aggregateSummaries(aggregate);
937
+ let detailText;
938
+ if (errors > 0 && summaries.length === 0) {
939
+ const succeeded = completed - errors;
940
+ detailText = succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`;
941
+ } else {
942
+ detailText = formatAggregateDetail(summaries) || '';
943
+ }
944
+ const currentItem = state.items.find((it) => it.id === card.itemId);
945
+ const visualCompleted = Math.max(completed, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
946
+ const rawResult = aggregateRawResult(allCalls);
947
+ patchItem(card.itemId, {
948
+ result: detailText,
949
+ text: detailText,
950
+ rawResult: rawResult || null,
951
+ isError: errors > 0,
952
+ errorCount: errors,
953
+ count: allCalls.length,
954
+ completedCount: visualCompleted,
955
+ completedAt: Date.now(),
956
+ });
957
+ card.done = true;
958
+ if (callId) done.add(callId);
959
+ return true;
960
+ }
961
+
962
+ // Non-aggregate (legacy bridge-job cards, etc.)
963
+ const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
964
+ group.completed = Math.min(group.count, group.completed + 1);
965
+ group.errors += isError ? 1 : 0;
966
+ group.results.push({ text, isError });
967
+ toolGroups.set(card.itemId, group);
968
+ const resultText = groupedToolResultText(group);
969
+ const patch = {
970
+ result: resultText,
971
+ text: resultText,
972
+ isError: group.errors > 0,
973
+ errorCount: group.errors,
974
+ count: group.count,
975
+ completedCount: group.completed,
976
+ completedAt: Date.now(),
977
+ };
978
+ if (group.count <= 1) {
979
+ const parsedBridge = parseBridgeJob(rawText);
980
+ if (parsedBridge) patch.args = bridgeArgsWithResultMetadata(state.items.find((it) => it.id === card.itemId)?.args, parsedBridge);
981
+ }
982
+ patchItem(card.itemId, patch);
983
+ if (group.count <= 1) updateBridgeJobCard(card.itemId, rawText, isError);
984
+ card.done = true;
985
+ if (callId) done.add(callId);
986
+ return true;
987
+ }
988
+
989
+ const flushToolResults = (messages, toolCards, cardByCallId, toolGroups, done, { finalize = false } = {}) => {
990
+ const results = [];
991
+ for (const m of messages || []) {
992
+ if (!m || m.role !== 'tool') continue;
993
+ const callId = toolResultCallId(m);
994
+ results.push({ message: m, callId, used: false });
995
+ if (!callId || done.has(callId)) continue;
996
+ const card = cardByCallId.get(callId);
997
+ if (patchToolCardResult(card, m, toolGroups, done)) {
998
+ results[results.length - 1].used = true;
999
+ }
1000
+ }
1001
+
1002
+ const openCards = (toolCards || []).filter((card) => !card.done);
1003
+ if (openCards.length === 0) return;
1004
+
1005
+ const unusedResults = results.filter((result) => !result.used);
1006
+ const fallbackResults = unusedResults.slice(-openCards.length);
1007
+ for (let i = 0; i < fallbackResults.length; i++) {
1008
+ const card = openCards[i];
1009
+ const result = fallbackResults[i];
1010
+ if (!card || !result || card.done) continue;
1011
+ if (patchToolCardResult(card, result.message, toolGroups, done)) {
1012
+ if (result.callId) done.add(result.callId);
1013
+ result.used = true;
1014
+ }
1015
+ }
1016
+
1017
+ if (!finalize) return;
1018
+ for (const card of toolCards || []) {
1019
+ if (card.done) continue;
1020
+ // Aggregate finalize — mark any remaining calls as done
1021
+ const aggregate = card.aggregate;
1022
+ if (aggregate && card.itemId === aggregate.itemId) {
1023
+ const allCalls = [...aggregate.calls.values()];
1024
+ const completed = allCalls.filter((r) => r.resolved).length;
1025
+ const remaining = allCalls.length - completed;
1026
+ const totalCompleted = remaining > 0 ? completed + remaining : completed;
1027
+ const errors = allCalls.filter((r) => r.isError).length;
1028
+ const summaries = aggregateSummaries(aggregate);
1029
+ const detailText = formatAggregateDetail(summaries) || '';
1030
+ const rawResult = aggregateRawResult(allCalls);
1031
+ patchItem(card.itemId, {
1032
+ result: detailText,
1033
+ text: detailText,
1034
+ rawResult: rawResult || null,
1035
+ isError: errors > 0,
1036
+ errorCount: errors,
1037
+ count: allCalls.length,
1038
+ completedCount: totalCompleted,
1039
+ completedAt: Date.now(),
1040
+ });
1041
+ for (const sibling of toolCards || []) {
1042
+ if (sibling.itemId !== card.itemId) continue;
1043
+ sibling.done = true;
1044
+ if (sibling.callId) done.add(sibling.callId);
1045
+ }
1046
+ continue;
1047
+ }
1048
+ // Non-aggregate finalize
1049
+ const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
1050
+ group.completed = Math.min(group.count, group.completed + 1);
1051
+ toolGroups.set(card.itemId, group);
1052
+ const resultText = groupedToolResultText(group);
1053
+ patchItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, count: group.count, completedCount: group.completed, completedAt: Date.now() });
1054
+ card.done = true;
1055
+ if (card.callId) done.add(card.callId);
1056
+ }
1057
+ };
1058
+
1059
+ async function runTurn(userText, options = {}) {
1060
+ const turnIndex = state.stats.turns || 0;
1061
+ const startedAt = Date.now();
1062
+ const inputBaseline = state.stats.inputTokens;
1063
+ const outputBaseline = state.stats.outputTokens;
1064
+ const submittedIds = Array.isArray(options.submittedIds) ? options.submittedIds : [];
1065
+ const displayText = promptDisplayText(userText, options);
1066
+ let promptCommittedCallbackCalled = false;
1067
+ activePromptRestore = {
1068
+ text: String(displayText || '').trim(),
1069
+ pastedImages: options.pastedImages && typeof options.pastedImages === 'object' ? options.pastedImages : null,
1070
+ onCommitted: typeof options.onCommitted === 'function' ? options.onCommitted : null,
1071
+ restorable: options.restorable !== false,
1072
+ submittedIds,
1073
+ reclaimed: false,
1074
+ committed: false,
1075
+ requeueEntries: Array.isArray(options.requeueOnAbort) ? options.requeueOnAbort.slice() : [],
1076
+ };
1077
+ set({ busy: true, lastTurn: null, spinner: { active: true, verb: pickVerb(turnIndex), startedAt, responseLength: 0, inputTokens: 0, outputTokens: 0, mode: 'requesting' } });
1078
+
1079
+ let assistantText = '';
1080
+ let currentAssistantId = null;
1081
+ let currentAssistantText = '';
1082
+ let thinkingText = '';
1083
+ let thinkingStartedAt = 0;
1084
+ let thinkingSegmentStartedAt = 0;
1085
+ let accumulatedThinkingMs = 0;
1086
+ let cancelled = false;
1087
+ const cardByCallId = new Map();
1088
+ const toolCards = [];
1089
+ const toolGroups = new Map();
1090
+ const resultsDone = new Set();
1091
+ const aggregateCards = []; // active aggregate cards in the current consecutive tool block
1092
+ let openAggregateCard = null;
1093
+ let autoCompactStatus = null;
1094
+
1095
+ const markPromptCommitted = () => {
1096
+ if (activePromptRestore) {
1097
+ if (!promptCommittedCallbackCalled && typeof activePromptRestore.onCommitted === 'function') {
1098
+ promptCommittedCallbackCalled = true;
1099
+ try { activePromptRestore.onCommitted(); } catch {}
1100
+ }
1101
+ activePromptRestore.restorable = false;
1102
+ activePromptRestore.committed = true;
1103
+ activePromptRestore.requeueEntries = [];
1104
+ activePromptRestore.pastedImages = null;
1105
+ }
1106
+ };
1107
+
1108
+ const finalizeToolHeaders = () => {
1109
+ const ids = new Set();
1110
+ for (const card of toolCards || []) {
1111
+ if (card?.itemId) ids.add(card.itemId);
1112
+ }
1113
+ for (const aggregate of aggregateCards || []) {
1114
+ if (aggregate?.itemId) ids.add(aggregate.itemId);
1115
+ }
1116
+ if (ids.size === 0) return false;
1117
+ let changed = false;
1118
+ const items = state.items.map((item) => {
1119
+ if (!ids.has(item?.id) || item.kind !== 'tool' || item.headerFinalized !== false) return item;
1120
+ changed = true;
1121
+ return { ...item, headerFinalized: true };
1122
+ });
1123
+ if (changed) set({ items });
1124
+ return changed;
1125
+ };
1126
+
1127
+ const completeAggregateVisual = () => {
1128
+ for (const aggregate of aggregateCards) {
1129
+ const allCalls = [...aggregate.calls.values()];
1130
+ if (allCalls.length === 0) continue;
1131
+ const errors = allCalls.filter((r) => r.isError).length;
1132
+ const summaries = aggregateSummaries(aggregate);
1133
+ const detailText = formatAggregateDetail(summaries) || '';
1134
+ const rawResult = aggregateRawResult(allCalls);
1135
+ patchItem(aggregate.itemId, {
1136
+ result: detailText,
1137
+ text: detailText,
1138
+ rawResult: rawResult || null,
1139
+ isError: errors > 0,
1140
+ count: allCalls.length,
1141
+ completedCount: allCalls.length,
1142
+ completedAt: Date.now(),
1143
+ });
1144
+ }
1145
+ };
1146
+
1147
+ const clearAggregateContinuation = () => {
1148
+ completeAggregateVisual();
1149
+ finalizeToolHeaders();
1150
+ aggregateCards.length = 0;
1151
+ openAggregateCard = null;
1152
+ };
1153
+
1154
+ const ensureAggregateCard = (bucket) => {
1155
+ if (openAggregateCard?.bucket === bucket) return openAggregateCard;
1156
+ const itemId = nextId();
1157
+ const aggregate = {
1158
+ itemId,
1159
+ bucket,
1160
+ categories: new Map(),
1161
+ categoryOrder: [],
1162
+ calls: new Map(),
1163
+ nextSummarySeq: 0,
1164
+ pushed: false,
1165
+ startedAt: Date.now(),
1166
+ };
1167
+ aggregateCards.push(aggregate);
1168
+ openAggregateCard = aggregate;
1169
+ return aggregate;
1170
+ };
1171
+
1172
+ const syncAggregateHeader = (aggregate) => {
1173
+ if (!aggregate?.itemId) return;
1174
+ const patch = {
1175
+ args: { categoryOrder: aggregate.categoryOrder.slice() },
1176
+ count: aggregate.calls.size,
1177
+ completedCount: [...aggregate.calls.values()].filter((r) => r.resolved).length,
1178
+ categories: Object.fromEntries(aggregate.categories),
1179
+ };
1180
+ if (aggregate.pushed) {
1181
+ patchItem(aggregate.itemId, patch);
1182
+ return;
1183
+ }
1184
+ aggregate.pushed = true;
1185
+ pushItem({
1186
+ kind: 'tool',
1187
+ id: aggregate.itemId,
1188
+ name: '__aggregate__',
1189
+ ...patch,
1190
+ aggregate: true,
1191
+ result: null,
1192
+ rawResult: null,
1193
+ isError: false,
1194
+ expanded: false,
1195
+ headerFinalized: false,
1196
+ startedAt: aggregate.startedAt || Date.now(),
1197
+ });
1198
+ };
1199
+
1200
+ const ensureAssistant = () => {
1201
+ if (!currentAssistantId) {
1202
+ currentAssistantId = nextId();
1203
+ currentAssistantText = '';
1204
+ pushItem({ kind: 'assistant', id: currentAssistantId, text: '', streaming: true });
1205
+ }
1206
+ return currentAssistantId;
1207
+ };
1208
+
1209
+ const closeAssistantSegment = () => {
1210
+ currentAssistantId = null;
1211
+ currentAssistantText = '';
1212
+ };
1213
+
1214
+ const startThinkingSegment = () => {
1215
+ const now = Date.now();
1216
+ if (!thinkingStartedAt) thinkingStartedAt = now;
1217
+ if (!thinkingSegmentStartedAt) thinkingSegmentStartedAt = now;
1218
+ return now;
1219
+ };
1220
+
1221
+ const closeThinkingSegment = () => {
1222
+ if (!thinkingSegmentStartedAt) return;
1223
+ const now = Date.now();
1224
+ accumulatedThinkingMs += Math.max(0, now - thinkingSegmentStartedAt);
1225
+ thinkingSegmentStartedAt = 0;
1226
+ return now;
1227
+ };
1228
+
1229
+ // --- Streaming-delta batcher ---
1230
+ // onTextDelta and onReasoningDelta fire on every tiny chunk (often <10 chars).
1231
+ // Each call previously called set() → emit() → full React reconcile. We
1232
+ // batch accumulated text and flush at most once per STREAM_BATCH_INTERVAL_MS
1233
+ // (≈16ms / 60fps cap). A forced flush happens before any tool call,
1234
+ // finalization, or error so those code paths see the correct text state.
1235
+ const STREAM_BATCH_INTERVAL_MS = 16;
1236
+ let _batchTimer = null;
1237
+ let _pendingTextFlush = false; // true when a text/spinner update is queued
1238
+ let _pendingThinkFlush = false; // true when a thinking update is queued
1239
+ let _pendingThinkingLastEndedAt = 0;
1240
+
1241
+ const flushStreamBatch = () => {
1242
+ if (_batchTimer !== null) {
1243
+ clearTimeout(_batchTimer);
1244
+ _batchTimer = null;
1245
+ }
1246
+ if (_pendingTextFlush) {
1247
+ _pendingTextFlush = false;
1248
+ const id = ensureAssistant();
1249
+ // Emit the accumulated assistant text and spinner update together so a
1250
+ // streaming batch costs one set() → one emit() → one React reconcile.
1251
+ const patch = {};
1252
+ const index = state.items.findIndex((it) => it.id === id);
1253
+ if (index >= 0) {
1254
+ const current = state.items[index];
1255
+ if (!Object.is(current.text, currentAssistantText) || current.streaming !== true) {
1256
+ const items = state.items.slice();
1257
+ items[index] = { ...current, text: currentAssistantText, streaming: true };
1258
+ patch.items = items;
1259
+ }
1260
+ }
1261
+ const responseLengthVal = assistantText.length + thinkingText.length;
1262
+ if (state.spinner) {
1263
+ patch.spinner = { ...state.spinner, responseLength: responseLengthVal, thinking: false, thinkingLastEndedAt: _pendingThinkingLastEndedAt || state.spinner.thinkingLastEndedAt, mode: 'responding' };
1264
+ }
1265
+ if (Object.keys(patch).length > 0) set(patch);
1266
+ _pendingThinkingLastEndedAt = 0;
1267
+ }
1268
+ if (_pendingThinkFlush) {
1269
+ _pendingThinkFlush = false;
1270
+ const responseLengthVal = assistantText.length + thinkingText.length;
1271
+ const thinkingElapsedMs = accumulatedThinkingMs + (thinkingSegmentStartedAt ? Math.max(0, Date.now() - thinkingSegmentStartedAt) : 0);
1272
+ const patch = { thinking: thinkingText };
1273
+ if (state.spinner) {
1274
+ patch.spinner = { ...state.spinner, responseLength: responseLengthVal, thinking: true, thinkingStartedAt, thinkingSegmentStartedAt, thinkingAccumulatedMs: accumulatedThinkingMs, thinkingElapsedMs, thinkingLastEndedAt: 0, mode: 'thinking' };
1275
+ }
1276
+ set(patch);
1277
+ }
1278
+ };
1279
+
1280
+ const scheduleStreamFlush = () => {
1281
+ if (_batchTimer !== null) return; // already scheduled; do not re-arm
1282
+ _batchTimer = setTimeout(flushStreamBatch, STREAM_BATCH_INTERVAL_MS);
1283
+ if (_batchTimer?.unref) _batchTimer.unref(); // don't prevent process exit
1284
+ };
1285
+
1286
+ try {
1287
+ const { result, session } = await runtime.ask(userText, {
1288
+ drainSteering: () => drainPendingSteering(),
1289
+ onSteerMessage: (text) => {
1290
+ const value = String(text || '').trim();
1291
+ if (value) {
1292
+ finalizeToolHeaders();
1293
+ pushUserOrSyntheticItem(value);
1294
+ }
1295
+ },
1296
+ onToolCall: async (_iter, calls) => {
1297
+ markPromptCommitted();
1298
+ if (thinkingText && state.thinking) {
1299
+ const thinkingLastEndedAt = closeThinkingSegment();
1300
+ flushStreamBatch(); // flush any buffered text/thinking before the tool card appears
1301
+ set({ thinking: null, spinner: state.spinner ? { ...state.spinner, thinking: false, thinkingAccumulatedMs: accumulatedThinkingMs, thinkingLastEndedAt, mode: 'tool-use' } : state.spinner });
1302
+ } else if (state.spinner) {
1303
+ flushStreamBatch(); // flush any buffered text before the tool card appears
1304
+ set({ spinner: { ...state.spinner, mode: 'tool-use' } });
1305
+ }
1306
+ const batchCalls = (calls || []).filter(Boolean);
1307
+ if (batchCalls.length === 0) return;
1308
+ closeAssistantSegment();
1309
+
1310
+ const touchedAggregates = new Set();
1311
+ for (let i = 0; i < batchCalls.length; i++) {
1312
+ const c = batchCalls[i];
1313
+ const name = toolCallName(c);
1314
+ const args = toolCallArgs(c);
1315
+ const category = classifyToolCategory(name, args);
1316
+ const bucket = aggregateBucketForCategory(category);
1317
+ const callId = toolCallId(c);
1318
+ const callKey = callId || `__tool_${toolCards.length}_${i}`;
1319
+
1320
+ if (!bucket) {
1321
+ openAggregateCard = null;
1322
+ const itemId = nextId();
1323
+ pushItem({
1324
+ kind: 'tool',
1325
+ id: itemId,
1326
+ name,
1327
+ args,
1328
+ result: null,
1329
+ isError: false,
1330
+ expanded: false,
1331
+ headerFinalized: false,
1332
+ count: 1,
1333
+ completedCount: 0,
1334
+ startedAt: Date.now(),
1335
+ });
1336
+ const card = { itemId, callId: callKey, done: false };
1337
+ if (callId) {
1338
+ cardByCallId.set(callId, card);
1339
+ }
1340
+ toolCards.push(card);
1341
+ continue;
1342
+ }
1343
+
1344
+ const aggregateCard = ensureAggregateCard(bucket);
1345
+ if (!aggregateCard.categories.has(category)) aggregateCard.categoryOrder.push(category);
1346
+ aggregateCard.categories.set(category, (aggregateCard.categories.get(category) || 0) + 1);
1347
+ aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, resultText: null, resolved: false });
1348
+ touchedAggregates.add(aggregateCard);
1349
+ const card = { itemId: aggregateCard.itemId, callId: callKey, done: false, aggregate: aggregateCard };
1350
+ if (callId) {
1351
+ cardByCallId.set(callId, card);
1352
+ }
1353
+ toolCards.push(card);
1354
+ }
1355
+
1356
+ for (const aggregateCard of touchedAggregates) {
1357
+ syncAggregateHeader(aggregateCard);
1358
+ }
1359
+ await yieldToRenderer();
1360
+ },
1361
+ onToolResult: (message) => {
1362
+ flushToolResults([message], toolCards, cardByCallId, toolGroups, resultsDone);
1363
+ },
1364
+ onStageChange: (stage) => {
1365
+ if (!state.spinner) return;
1366
+ const value = String(stage || '');
1367
+ const mode = value === 'requesting'
1368
+ ? 'requesting'
1369
+ : value === 'streaming'
1370
+ ? (state.spinner.thinking ? 'thinking' : 'responding')
1371
+ : value === 'compacting'
1372
+ ? 'compacting'
1373
+ : null;
1374
+ if (!mode || state.spinner.mode === mode) return;
1375
+ set({ spinner: { ...state.spinner, mode } });
1376
+ },
1377
+ onTextDelta: (chunk) => {
1378
+ const textChunk = String(chunk ?? '');
1379
+ if (!textChunk) return;
1380
+ markPromptCommitted();
1381
+ const thinkingLastEndedAt = closeThinkingSegment();
1382
+ if (state.thinking) set({ thinking: null }); // collapse thinking panel immediately, no batch delay
1383
+ if (textChunk.trim()) clearAggregateContinuation();
1384
+ assistantText += textChunk;
1385
+ ensureAssistant(); // create the assistant item in state immediately so the slot exists
1386
+ currentAssistantText += textChunk;
1387
+ // Accumulate text; fire at most one render per STREAM_BATCH_INTERVAL_MS.
1388
+ _pendingTextFlush = true;
1389
+ if (thinkingLastEndedAt) _pendingThinkingLastEndedAt = thinkingLastEndedAt;
1390
+ scheduleStreamFlush();
1391
+ },
1392
+ onReasoningDelta: (chunk) => {
1393
+ if (String(chunk ?? '')) markPromptCommitted();
1394
+ startThinkingSegment();
1395
+ thinkingText += String(chunk ?? '');
1396
+ // Accumulate reasoning text; fire at most one render per STREAM_BATCH_INTERVAL_MS.
1397
+ _pendingThinkFlush = true;
1398
+ scheduleStreamFlush();
1399
+ },
1400
+ onUsageDelta: (delta) => {
1401
+ applyUsageDelta(state.stats, delta);
1402
+ syncContextStats();
1403
+ const currentTurnInput = Math.max(0, state.stats.inputTokens - inputBaseline);
1404
+ const currentTurnOutput = Math.max(0, state.stats.outputTokens - outputBaseline);
1405
+ if (state.spinner) {
1406
+ set({ stats: { ...state.stats }, spinner: { ...state.spinner, inputTokens: currentTurnInput, outputTokens: currentTurnOutput } });
1407
+ } else {
1408
+ set({ stats: { ...state.stats } });
1409
+ }
1410
+ },
1411
+ });
1412
+ markPromptCommitted();
1413
+ if (result?.postTurnCompact?.changed) autoCompactStatus = result.postTurnCompact;
1414
+
1415
+ flushToolResults(session?.messages || [], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true });
1416
+ finalizeToolHeaders();
1417
+ flushStreamBatch(); // force-flush any batched streaming text before finalization writes
1418
+ syncContextStats();
1419
+
1420
+ const finalText = (result?.content != null && String(result.content)) || assistantText;
1421
+ if (assistantText.trim() && currentAssistantId) {
1422
+ patchItem(currentAssistantId, { text: currentAssistantText || assistantText, streaming: false });
1423
+ }
1424
+ if (finalText && !assistantText.trim()) {
1425
+ const id = ensureAssistant();
1426
+ currentAssistantText = finalText;
1427
+ patchItem(id, { text: finalText, streaming: false });
1428
+ }
1429
+ state.stats.turns = (state.stats.turns || 0) + 1;
1430
+ } catch (error) {
1431
+ flushStreamBatch(); // ensure any batched text lands before the error notice
1432
+ if (error?.name === 'SessionClosedError') {
1433
+ cancelled = true;
1434
+ if (assistantText.trim() && currentAssistantId) {
1435
+ patchItem(currentAssistantId, { text: currentAssistantText || assistantText, streaming: false });
1436
+ }
1437
+ // Finalize pending tool cards so they don't stay "Running..." forever
1438
+ // after cancellation. Without this, the spinner vanishes and TurnDone
1439
+ // shows "cancelled", but in-flight tool cards remain in a perpetual
1440
+ // pending/blinking state because the normal finalize path (line 992)
1441
+ // was skipped when the error interrupted the try block.
1442
+ flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true });
1443
+ finalizeToolHeaders();
1444
+ } else {
1445
+ finalizeToolHeaders();
1446
+ pushNotice(toolErrorDisplay(error, 'turn'), 'error');
1447
+ }
1448
+ } finally {
1449
+ const reclaimed = cancelled && activePromptRestore?.reclaimed === true;
1450
+ activePromptRestore = null;
1451
+ closeThinkingSegment();
1452
+ const elapsedMs = Date.now() - startedAt;
1453
+ const thinkingElapsedMs = thinkingStartedAt ? accumulatedThinkingMs : 0;
1454
+ const finalOutputTokens = Math.max(0, Number(state.spinner?.outputTokens || 0), Math.round(Number(state.spinner?.responseLength || 0) / 4));
1455
+ const turnStatus = cancelled ? 'cancelled' : 'done';
1456
+ // Pin the post-think summary into the transcript right after this turn's
1457
+ // output so it scrolls up with the answer and stays in the scrollback,
1458
+ // mirroring Claude Code. (Previously TurnDone rendered only in the
1459
+ // bottom-fixed live-status slot and vanished on the next turn.)
1460
+ if (!reclaimed) {
1461
+ pushItem({ kind: 'turndone', id: nextId(), elapsedMs, status: turnStatus, outputTokens: finalOutputTokens, thinkingElapsedMs, verb: pickDoneVerb(turnIndex) });
1462
+ if (!cancelled && autoCompactStatus?.changed) {
1463
+ const before = formatTokenCount(autoCompactStatus.beforeTokens || autoCompactStatus.pressureTokens || 0);
1464
+ const after = formatTokenCount(autoCompactStatus.afterTokens || 0);
1465
+ const trigger = formatTokenCount(autoCompactStatus.triggerTokens || 0);
1466
+ pushItem({
1467
+ kind: 'statusdone',
1468
+ id: nextId(),
1469
+ label: 'Auto-compact complete',
1470
+ detail: [`context ${before}→${after}`, trigger !== '0' ? `trigger ${trigger}` : '']
1471
+ .filter(Boolean)
1472
+ .join(' · '),
1473
+ });
1474
+ }
1475
+ }
1476
+ set({
1477
+ busy: false,
1478
+ spinner: null,
1479
+ thinking: null,
1480
+ lastTurn: null,
1481
+ stats: { ...state.stats },
1482
+ ...routeState(),
1483
+ toolMode: runtime.toolMode,
1484
+ ...bridgeStatusState(),
1485
+ });
1486
+ }
1487
+ return cancelled ? 'cancelled' : 'done';
1488
+ }
1489
+
1490
+ const pending = [];
1491
+ let draining = false;
1492
+ let activePromptRestore = null;
1493
+
1494
+ function makeQueueEntry(text, options = {}) {
1495
+ const mode = options.mode || 'prompt';
1496
+ const priority = options.priority || defaultQueuePriority(mode);
1497
+ const displayText = promptDisplayText(text, options);
1498
+ return {
1499
+ id: options.id || nextId(),
1500
+ text: displayText,
1501
+ content: text,
1502
+ pastedImages: options.pastedImages && typeof options.pastedImages === 'object' ? options.pastedImages : null,
1503
+ onCommitted: typeof options.onCommitted === 'function' ? options.onCommitted : null,
1504
+ mode,
1505
+ priority,
1506
+ key: options.key || null,
1507
+ displayText: mode === 'task-notification' ? notificationDisplayText(displayText) : String(displayText || ''),
1508
+ };
1509
+ }
1510
+
1511
+ function removeQueuedEntries(entries) {
1512
+ const ids = new Set(entries.map((entry) => entry.id));
1513
+ const keys = entries.map((entry) => entry.key).filter(Boolean);
1514
+ for (const key of keys) pendingNotificationKeys.delete(key);
1515
+ set({ queued: state.queued.filter((q) => !ids.has(q.id)) });
1516
+ }
1517
+
1518
+ function requeueEntriesFront(entries) {
1519
+ const restored = [];
1520
+ for (const entry of entries || []) {
1521
+ if (!entry || !String(entry.text || '').trim()) continue;
1522
+ const next = {
1523
+ ...entry,
1524
+ displayText: entry.displayText || (entry.mode === 'task-notification' ? notificationDisplayText(entry.text) : String(entry.text || '')),
1525
+ };
1526
+ if (next.mode === 'task-notification' && next.key) {
1527
+ if (pendingNotificationKeys.has(next.key)) continue;
1528
+ pendingNotificationKeys.add(next.key);
1529
+ }
1530
+ restored.push(next);
1531
+ }
1532
+ if (restored.length === 0) return false;
1533
+ pending.unshift(...restored);
1534
+ set({ queued: [...restored, ...state.queued] });
1535
+ return true;
1536
+ }
1537
+
1538
+ function dequeueQueueBatch(maxPriority = 'later') {
1539
+ if (pending.length === 0) return [];
1540
+ const max = queuePriorityValue(maxPriority);
1541
+ let bestPriority = Infinity;
1542
+ let targetMode = null;
1543
+ for (const entry of pending) {
1544
+ const p = queuePriorityValue(entry.priority);
1545
+ if (p > max) continue;
1546
+ if (p < bestPriority) {
1547
+ bestPriority = p;
1548
+ targetMode = entry.mode || 'prompt';
1549
+ }
1550
+ }
1551
+ if (!targetMode) return [];
1552
+ const batch = [];
1553
+ for (let i = 0; i < pending.length;) {
1554
+ const entry = pending[i];
1555
+ if ((entry.mode || 'prompt') === targetMode && queuePriorityValue(entry.priority) === bestPriority) {
1556
+ batch.push(entry);
1557
+ pending.splice(i, 1);
1558
+ } else {
1559
+ i += 1;
1560
+ }
1561
+ }
1562
+ removeQueuedEntries(batch);
1563
+ return batch;
1564
+ }
1565
+
1566
+ async function drain() {
1567
+ if (draining) return;
1568
+ draining = true;
1569
+ try {
1570
+ while (pending.length > 0) {
1571
+ // Drain one priority/mode bucket at a time, matching Claude Code's
1572
+ // unified command queue semantics: prompt steering stays editable and
1573
+ // task notifications stay non-editable but model-visible.
1574
+ const batch = dequeueQueueBatch('later');
1575
+ if (batch.length === 0) break;
1576
+ const ids = new Set(batch.map((e) => e.id));
1577
+ const merged = mergePromptContents(batch);
1578
+ for (const entry of batch) {
1579
+ pushUserOrSyntheticItem(entry.text, entry.id);
1580
+ }
1581
+ const nonEditable = batch.filter((entry) => !isQueuedEntryEditable(entry));
1582
+ const batchPastedImages = mergePastedImages(batch);
1583
+ const turnStatus = await runTurn(merged, {
1584
+ displayText: batch.map((entry) => entry.text).filter((text) => String(text || '').trim()).join('\n'),
1585
+ pastedImages: batchPastedImages,
1586
+ onCommitted: () => callCommitCallbacks(batch),
1587
+ submittedIds: [...ids],
1588
+ restorable: nonEditable.length === 0,
1589
+ requeueOnAbort: nonEditable,
1590
+ });
1591
+ // If the user re-submits the reclaimed prompt while the cancelled turn
1592
+ // is still unwinding, enqueue() cannot start another drain because this
1593
+ // drain loop is still active. Continue when pending work appeared during
1594
+ // cancellation so the fresh submit does not get stuck in queued state.
1595
+ if (turnStatus === 'cancelled' && pending.length === 0) break;
1596
+ }
1597
+ } finally {
1598
+ draining = false;
1599
+ }
1600
+ }
1601
+ function enqueue(text, options = {}) {
1602
+ const entry = makeQueueEntry(text, options);
1603
+ if (entry.mode === 'task-notification' && entry.key) {
1604
+ if (pendingNotificationKeys.has(entry.key)) return false;
1605
+ pendingNotificationKeys.add(entry.key);
1606
+ }
1607
+ pending.push(entry);
1608
+ set({ queued: [...state.queued, entry] });
1609
+ void drain();
1610
+ return true;
1611
+ }
1612
+
1613
+ function drainPendingSteering() {
1614
+ const batch = dequeueQueueBatch('next');
1615
+ if (batch.length === 0) return [];
1616
+ const out = batch
1617
+ .map((entry) => {
1618
+ const content = entry.content;
1619
+ if (typeof content === 'string') return content.trim();
1620
+ return { text: String(entry.text || '').trim(), content };
1621
+ })
1622
+ .filter((entry) => {
1623
+ if (typeof entry === 'string') return entry.length > 0;
1624
+ if (Array.isArray(entry?.content)) return entry.content.length > 0;
1625
+ return String(entry?.content ?? '').trim().length > 0;
1626
+ });
1627
+ callCommitCallbacks(batch);
1628
+ return out;
1629
+ }
1630
+
1631
+ async function autoClearBeforeSubmit() {
1632
+ const cfg = autoClearState();
1633
+ const now = Date.now();
1634
+ const idleMs = now - lastUserActivityAt;
1635
+ if (!cfg.enabled || state.busy || pending.length > 0 || autoClearRunning || idleMs < cfg.idleMs) {
1636
+ lastUserActivityAt = now;
1637
+ return false;
1638
+ }
1639
+ autoClearRunning = true;
1640
+ try {
1641
+ const beforeContext = runtime.contextStatus?.() || null;
1642
+ await runtime.clear();
1643
+ resetStats();
1644
+ const afterContext = syncContextStats({ allowEstimated: true }) || runtime.contextStatus?.() || null;
1645
+ const idleLabel = formatIdleDuration(idleMs);
1646
+ const thresholdLabel = formatIdleDuration(cfg.idleMs);
1647
+ const beforeTokens = Number(beforeContext?.usedTokens || beforeContext?.currentEstimatedTokens || beforeContext?.usage?.lastContextTokens || 0);
1648
+ const afterTokens = Number(afterContext?.usedTokens || afterContext?.currentEstimatedTokens || afterContext?.usage?.lastContextTokens || 0);
1649
+ const contextDetail = beforeTokens > 0 || afterTokens > 0
1650
+ ? `context ${formatTokenCount(beforeTokens)}→${formatTokenCount(afterTokens)}`
1651
+ : 'context reset';
1652
+ set({
1653
+ items: replaceItems([]),
1654
+ toasts: [],
1655
+ queued: [],
1656
+ thinking: null,
1657
+ spinner: null,
1658
+ lastTurn: null,
1659
+ ...routeState(),
1660
+ stats: { ...state.stats },
1661
+ });
1662
+ pushItem({
1663
+ kind: 'statusdone',
1664
+ id: nextId(),
1665
+ label: 'Auto-clear complete',
1666
+ detail: [idleLabel ? `idle ${idleLabel}` : '', contextDetail, thresholdLabel ? `threshold ${thresholdLabel}` : '']
1667
+ .filter(Boolean)
1668
+ .join(' · '),
1669
+ });
1670
+ return true;
1671
+ } catch (error) {
1672
+ pushNotice(`auto-clear failed: ${error?.message || error}`, 'error');
1673
+ return false;
1674
+ } finally {
1675
+ lastUserActivityAt = Date.now();
1676
+ autoClearRunning = false;
1677
+ }
1678
+ }
1679
+
1680
+ function restoreQueued(currentText = '') {
1681
+ const queued = [];
1682
+ for (let i = 0; i < pending.length;) {
1683
+ const entry = pending[i];
1684
+ if (isQueuedEntryEditable(entry)) {
1685
+ queued.push(entry);
1686
+ pending.splice(i, 1);
1687
+ } else {
1688
+ i += 1;
1689
+ }
1690
+ }
1691
+ removeQueuedEntries(queued);
1692
+ const queuedText = queued.map((item) => item.text).filter((text) => String(text || '').trim()).join('\n');
1693
+ const combinedText = [queuedText, String(currentText || '')].filter((text) => text.trim()).join('\n');
1694
+ return { count: queued.length, text: combinedText, pastedImages: mergePastedImages(queued) };
1695
+ }
1696
+
1697
+ const resetStats = () => {
1698
+ state.stats = createSessionStats();
1699
+ return state.stats;
1700
+ };
1701
+ const resetStatsAndSyncContext = () => {
1702
+ resetStats();
1703
+ syncContextStats({ allowEstimated: true });
1704
+ return state.stats;
1705
+ };
1706
+
1707
+ return {
1708
+ getState: () => state,
1709
+ patchItem,
1710
+ subscribe: (listener) => {
1711
+ listeners.add(listener);
1712
+ return () => listeners.delete(listener);
1713
+ },
1714
+ submit: (text, options = {}) => {
1715
+ const t = promptDisplayText(text, options).trim();
1716
+ if (!t || state.commandBusy) return false;
1717
+ const mode = options.mode || 'prompt';
1718
+ // Plain user input entered while a turn is busy is a post-turn follow-up
1719
+ // by default, so it lands after any auto-compact/save boundary instead of
1720
+ // being swallowed as mid-turn steering. Task notifications still opt into
1721
+ // priority: 'next' and are injected at the next tool boundary.
1722
+ const priority = options.priority;
1723
+ const queueOptions = {
1724
+ ...options,
1725
+ mode,
1726
+ displayText: promptDisplayText(text, options),
1727
+ priority,
1728
+ };
1729
+ if (state.busy) {
1730
+ enqueue(text, queueOptions);
1731
+ return true;
1732
+ }
1733
+ void autoClearBeforeSubmit().then(() => enqueue(text, queueOptions));
1734
+ return true;
1735
+ },
1736
+ restoreQueued,
1737
+ setModel: async (m) => {
1738
+ if (state.commandBusy) return false;
1739
+ set({ commandBusy: true });
1740
+ try {
1741
+ await runtime.setRoute({ model: m });
1742
+ resetStatsAndSyncContext();
1743
+ set({ ...routeState(), stats: { ...state.stats } });
1744
+ return true;
1745
+ } finally {
1746
+ set({ commandBusy: false });
1747
+ }
1748
+ },
1749
+ setEffort: async (value) => {
1750
+ if (state.commandBusy) return false;
1751
+ set({ commandBusy: true });
1752
+ try {
1753
+ await runtime.setEffort(value);
1754
+ set({ ...routeState() });
1755
+ return runtime.effort || 'auto';
1756
+ } finally {
1757
+ set({ commandBusy: false });
1758
+ }
1759
+ },
1760
+ setFast: async (value) => {
1761
+ if (state.commandBusy) return null;
1762
+ set({ commandBusy: true });
1763
+ try {
1764
+ const enabled = await runtime.setFast(value);
1765
+ set({ ...routeState() });
1766
+ return enabled;
1767
+ } finally {
1768
+ set({ commandBusy: false });
1769
+ }
1770
+ },
1771
+ toggleFast: async () => {
1772
+ if (state.commandBusy) return null;
1773
+ set({ commandBusy: true });
1774
+ try {
1775
+ const enabled = await runtime.toggleFast();
1776
+ set({ ...routeState() });
1777
+ return enabled;
1778
+ } finally {
1779
+ set({ commandBusy: false });
1780
+ }
1781
+ },
1782
+ setToolMode: (m) => {
1783
+ void runtime.setToolMode(m)
1784
+ .then(() => {
1785
+ resetStatsAndSyncContext();
1786
+ set({ ...routeState(), toolMode: runtime.toolMode, stats: { ...state.stats } });
1787
+ })
1788
+ .catch((error) => pushNotice(toolErrorDisplay(error, 'tool'), 'error'));
1789
+ },
1790
+ toggleBridgeMode: () => {
1791
+ const mode = runtime.toggleBridgeMode();
1792
+ set(bridgeStatusState({ force: true }));
1793
+ pushNotice(`bridge mode -> ${mode}`, 'info');
1794
+ return mode;
1795
+ },
1796
+ setBridgeMode: (mode) => {
1797
+ const next = runtime.setBridgeMode(mode);
1798
+ set(bridgeStatusState({ force: true }));
1799
+ return next;
1800
+ },
1801
+ getAutoClear: () => autoClearState(),
1802
+ setAutoClear: (input = {}) => {
1803
+ const next = runtime.setAutoClear?.(input) || autoClearState();
1804
+ set({ autoClear: next });
1805
+ return next;
1806
+ },
1807
+ bridgeControl: async (args = {}) => {
1808
+ if (state.commandBusy) return null;
1809
+ set({ commandBusy: true });
1810
+ try {
1811
+ const result = await runtime.bridgeControl(args);
1812
+ const text = String(result || '').trim() || '(empty bridge result)';
1813
+ const itemId = nextId();
1814
+ pushItem({
1815
+ kind: 'tool',
1816
+ id: itemId,
1817
+ name: 'bridge',
1818
+ args,
1819
+ result: null,
1820
+ isError: false,
1821
+ expanded: false,
1822
+ count: 1,
1823
+ completedCount: 0,
1824
+ startedAt: Date.now(),
1825
+ });
1826
+ updateBridgeJobCard(itemId, text, /^error:/i.test(text));
1827
+ set(bridgeStatusState({ force: true }));
1828
+ return result;
1829
+ } finally {
1830
+ set({ commandBusy: false });
1831
+ }
1832
+ },
1833
+ toolsStatus: (query = '') => {
1834
+ return runtime.toolsStatus?.(query) || { mode: state.toolMode, count: 0, activeCount: 0, tools: [] };
1835
+ },
1836
+ selectTools: (names) => {
1837
+ const result = runtime.selectTools?.(names) || { added: [], already: [], blocked: [], missing: [] };
1838
+ const added = result.added?.length ? `added ${result.added.join(', ')}` : '';
1839
+ const already = result.already?.length ? `already ${result.already.join(', ')}` : '';
1840
+ const blocked = result.blocked?.length ? `blocked ${result.blocked.map((row) => row.name).join(', ')}` : '';
1841
+ const missing = result.missing?.length ? `missing ${result.missing.join(', ')}` : '';
1842
+ pushNotice(
1843
+ [added, already, blocked, missing].filter(Boolean).join(' - ') || 'no tool changes',
1844
+ result.blocked?.length || result.missing?.length ? 'warn' : 'info',
1845
+ );
1846
+ return result;
1847
+ },
1848
+ setCwd: (path) => {
1849
+ const next = runtime.setCwd(path);
1850
+ set({ cwd: next });
1851
+ pushNotice(`cwd -> ${next}`, 'info');
1852
+ return next;
1853
+ },
1854
+ getSystemShell: () => {
1855
+ return runtime.getSystemShell?.() || runtime.systemShell || { source: 'auto', command: '', effective: '' };
1856
+ },
1857
+ setSystemShell: (command) => {
1858
+ const next = runtime.setSystemShell?.(command) || { source: 'auto', command: '', effective: '' };
1859
+ set({ ...routeState(), systemShell: next });
1860
+ pushNotice(`system shell -> ${next.effective || 'auto'}`, 'info');
1861
+ return next;
1862
+ },
1863
+ mcpStatus: () => {
1864
+ return runtime.mcpStatus?.() || { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
1865
+ },
1866
+ reconnectMcp: async () => {
1867
+ if (state.commandBusy) return null;
1868
+ set({ commandBusy: true });
1869
+ try {
1870
+ const status = await runtime.reconnectMcp?.();
1871
+ resetStatsAndSyncContext();
1872
+ set({ ...routeState(), stats: { ...state.stats } });
1873
+ pushNotice(
1874
+ `mcp reconnect: ${status?.connectedCount || 0}/${status?.configuredCount || 0} connected${status?.failedCount ? ` - ${status.failedCount} failed` : ''}`,
1875
+ status?.failedCount ? 'warn' : 'info',
1876
+ );
1877
+ return status;
1878
+ } finally {
1879
+ set({ commandBusy: false });
1880
+ }
1881
+ },
1882
+ addMcpServer: async (input) => {
1883
+ if (state.commandBusy) return null;
1884
+ set({ commandBusy: true });
1885
+ try {
1886
+ const result = await runtime.addMcpServer?.(input);
1887
+ resetStatsAndSyncContext();
1888
+ set({ ...routeState(), stats: { ...state.stats } });
1889
+ pushNotice(`mcp added: ${result?.name || input?.name || 'server'}`, 'info');
1890
+ return result;
1891
+ } finally {
1892
+ set({ commandBusy: false });
1893
+ }
1894
+ },
1895
+ removeMcpServer: async (name) => {
1896
+ if (state.commandBusy) return null;
1897
+ set({ commandBusy: true });
1898
+ try {
1899
+ const status = await runtime.removeMcpServer?.(name);
1900
+ resetStatsAndSyncContext();
1901
+ set({ ...routeState(), stats: { ...state.stats } });
1902
+ pushNotice(`mcp removed: ${name}`, 'info');
1903
+ return status;
1904
+ } finally {
1905
+ set({ commandBusy: false });
1906
+ }
1907
+ },
1908
+ setMcpServerEnabled: async (name, enabled) => {
1909
+ if (state.commandBusy) return null;
1910
+ set({ commandBusy: true });
1911
+ try {
1912
+ const status = await runtime.setMcpServerEnabled?.(name, enabled);
1913
+ resetStatsAndSyncContext();
1914
+ set({ ...routeState(), stats: { ...state.stats } });
1915
+ pushNotice(`mcp ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
1916
+ return status;
1917
+ } finally {
1918
+ set({ commandBusy: false });
1919
+ }
1920
+ },
1921
+ skillsStatus: () => {
1922
+ return runtime.skillsStatus?.() || { cwd: state.cwd, count: 0, skills: [] };
1923
+ },
1924
+ skillContent: (name) => {
1925
+ return runtime.skillContent?.(name);
1926
+ },
1927
+ addSkill: async (input) => {
1928
+ if (state.commandBusy) return null;
1929
+ set({ commandBusy: true });
1930
+ try {
1931
+ const result = await runtime.addSkill?.(input);
1932
+ resetStatsAndSyncContext();
1933
+ set({ ...routeState(), stats: { ...state.stats } });
1934
+ pushNotice(`skill added: ${result?.skill?.name || input?.name || 'skill'}`, 'info');
1935
+ return result;
1936
+ } finally {
1937
+ set({ commandBusy: false });
1938
+ }
1939
+ },
1940
+ reloadSkills: async () => {
1941
+ if (state.commandBusy) return null;
1942
+ set({ commandBusy: true });
1943
+ try {
1944
+ const status = await runtime.reloadSkills?.();
1945
+ resetStatsAndSyncContext();
1946
+ set({ ...routeState(), stats: { ...state.stats } });
1947
+ pushNotice(`skills reload: ${status?.count || 0} available`, 'info');
1948
+ return status;
1949
+ } finally {
1950
+ set({ commandBusy: false });
1951
+ }
1952
+ },
1953
+ pluginsStatus: () => {
1954
+ return runtime.pluginsStatus?.() || { count: 0, plugins: [] };
1955
+ },
1956
+ reloadPlugins: async () => {
1957
+ if (state.commandBusy) return null;
1958
+ set({ commandBusy: true });
1959
+ try {
1960
+ const status = await runtime.reloadPlugins?.();
1961
+ resetStatsAndSyncContext();
1962
+ set({ ...routeState(), stats: { ...state.stats } });
1963
+ pushNotice(`plugins reload: ${status?.count || 0} detected`, 'info');
1964
+ return status;
1965
+ } finally {
1966
+ set({ commandBusy: false });
1967
+ }
1968
+ },
1969
+ addPlugin: async (source) => {
1970
+ if (state.commandBusy) return null;
1971
+ set({ commandBusy: true });
1972
+ try {
1973
+ const result = await runtime.addPlugin?.(source);
1974
+ resetStatsAndSyncContext();
1975
+ set({ ...routeState(), stats: { ...state.stats } });
1976
+ pushNotice(`plugin added: ${result?.plugin?.title || result?.plugin?.name || source}`, 'info');
1977
+ return result;
1978
+ } finally {
1979
+ set({ commandBusy: false });
1980
+ }
1981
+ },
1982
+ updatePlugin: async (plugin) => {
1983
+ if (state.commandBusy) return null;
1984
+ set({ commandBusy: true });
1985
+ try {
1986
+ const result = await runtime.updatePlugin?.(plugin);
1987
+ resetStatsAndSyncContext();
1988
+ set({ ...routeState(), stats: { ...state.stats } });
1989
+ pushNotice(`plugin updated: ${result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin}`, 'info');
1990
+ return result;
1991
+ } finally {
1992
+ set({ commandBusy: false });
1993
+ }
1994
+ },
1995
+ removePlugin: async (plugin) => {
1996
+ if (state.commandBusy) return null;
1997
+ set({ commandBusy: true });
1998
+ try {
1999
+ const result = await runtime.removePlugin?.(plugin);
2000
+ resetStatsAndSyncContext();
2001
+ set({ ...routeState(), stats: { ...state.stats } });
2002
+ pushNotice(`plugin uninstalled: ${result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin}`, 'info');
2003
+ return result;
2004
+ } finally {
2005
+ set({ commandBusy: false });
2006
+ }
2007
+ },
2008
+ enablePluginMcp: async (plugin) => {
2009
+ if (state.commandBusy) return null;
2010
+ set({ commandBusy: true });
2011
+ try {
2012
+ const result = await runtime.enablePluginMcp?.(plugin);
2013
+ resetStatsAndSyncContext();
2014
+ set({ ...routeState(), stats: { ...state.stats } });
2015
+ pushNotice(`plugin MCP enabled: ${result?.serverName || plugin?.name || 'plugin'}`, 'info');
2016
+ return result;
2017
+ } finally {
2018
+ set({ commandBusy: false });
2019
+ }
2020
+ },
2021
+ hooksStatus: () => {
2022
+ return runtime.hooksStatus?.() || { enabled: false, events: [], recent: [] };
2023
+ },
2024
+ contextStatus: () => {
2025
+ return runtime.contextStatus?.() || null;
2026
+ },
2027
+ addHookRule: (rule) => {
2028
+ const rules = runtime.addHookRule?.(rule) || [];
2029
+ pushNotice(`hook rule added (${rules.length} total)`, 'info');
2030
+ return rules;
2031
+ },
2032
+ setHookRuleEnabled: (index, enabled) => {
2033
+ const rules = runtime.setHookRuleEnabled?.(index, enabled) || [];
2034
+ pushNotice(`hook rule ${index + 1} ${enabled ? 'enabled' : 'disabled'}`, 'info');
2035
+ return rules;
2036
+ },
2037
+ deleteHookRule: (index) => {
2038
+ const rules = runtime.deleteHookRule?.(index) || [];
2039
+ pushNotice(`hook rule ${index + 1} deleted`, 'info');
2040
+ return rules;
2041
+ },
2042
+ memoryControl: async (args = {}, options = {}) => {
2043
+ if (state.commandBusy) return null;
2044
+ set({ commandBusy: true });
2045
+ try {
2046
+ const result = await runtime.memoryControl(args);
2047
+ const text = String(result || '').trim() || '(empty memory result)';
2048
+ if (!options.silent) pushNotice(text, 'info');
2049
+ return result;
2050
+ } finally {
2051
+ set({ commandBusy: false });
2052
+ }
2053
+ },
2054
+ recall: async (query, args = {}) => {
2055
+ if (state.commandBusy) return null;
2056
+ const startedAt = Date.now();
2057
+ set({ commandBusy: true, commandStatus: { active: true, verb: 'Recalling memory', startedAt, mode: 'recalling' } });
2058
+ try {
2059
+ const result = await runtime.recall(query, args);
2060
+ pushNotice(String(result || '').trim() || '(empty recall result)', 'info');
2061
+ return result;
2062
+ } finally {
2063
+ set({ commandBusy: false, commandStatus: null });
2064
+ }
2065
+ },
2066
+ compact: async () => {
2067
+ if (state.commandBusy) return null;
2068
+ const startedAt = Date.now();
2069
+ set({ commandBusy: true, commandStatus: { active: true, verb: 'Compacting conversation', startedAt, mode: 'compacting' } });
2070
+ try {
2071
+ const result = await runtime.compact({ recoverBridge: true });
2072
+ syncContextStats({ allowEstimated: true });
2073
+ set({ ...routeState(), stats: { ...state.stats } });
2074
+ if (result) {
2075
+ pushItem({ kind: 'turndone', id: nextId(), elapsedMs: Date.now() - startedAt, status: 'done', outputTokens: 0, thinkingElapsedMs: 0, verb: 'Compacted' });
2076
+ }
2077
+ return result;
2078
+ } finally {
2079
+ set({ commandBusy: false, commandStatus: null });
2080
+ }
2081
+ },
2082
+ abort: () => {
2083
+ if (!state.busy) return false;
2084
+ const restoreState = activePromptRestore;
2085
+ const restoreText = restoreState?.restorable ? restoreState.text : '';
2086
+ const restorePastedImages = restoreState?.restorable && restoreState?.pastedImages ? restoreState.pastedImages : null;
2087
+ const requeueEntries = restoreState && !restoreState.committed && Array.isArray(restoreState.requeueEntries)
2088
+ ? restoreState.requeueEntries.slice()
2089
+ : [];
2090
+ const aborted = runtime.abort('cli-react-abort');
2091
+ if (restoreState) {
2092
+ if ((restoreText || requeueEntries.length > 0) && aborted !== false) {
2093
+ restoreState.reclaimed = true;
2094
+ const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
2095
+ const patch = { spinner: null, thinking: null, lastTurn: null };
2096
+ if (idSet.size > 0) {
2097
+ const items = state.items.filter((item) => !idSet.has(item?.id));
2098
+ if (items.length !== state.items.length) {
2099
+ patch.items = replaceItems(items);
2100
+ }
2101
+ }
2102
+ set(patch);
2103
+ if (requeueEntries.length > 0) requeueEntriesFront(requeueEntries);
2104
+ }
2105
+ restoreState.restorable = false;
2106
+ restoreState.requeueEntries = [];
2107
+ }
2108
+ return { aborted, restoreText, pastedImages: restorePastedImages };
2109
+ },
2110
+ listPresets: () => {
2111
+ return runtime.listPresets();
2112
+ },
2113
+ listProviderModels: (options = {}) => {
2114
+ return runtime.listProviderModels(options);
2115
+ },
2116
+ listAgents: () => {
2117
+ return runtime.listAgents?.() || [];
2118
+ },
2119
+ listWorkflows: () => {
2120
+ return runtime.listWorkflows?.() || [];
2121
+ },
2122
+ getOutputStyle: () => {
2123
+ return runtime.getOutputStyle?.() || runtime.listOutputStyles?.() || null;
2124
+ },
2125
+ listOutputStyles: () => {
2126
+ return runtime.listOutputStyles?.() || runtime.getOutputStyle?.() || { styles: [], current: null, configured: 'default' };
2127
+ },
2128
+ setOutputStyle: async (styleId) => {
2129
+ if (state.commandBusy) return null;
2130
+ set({ commandBusy: true });
2131
+ try {
2132
+ const result = await runtime.setOutputStyle?.(styleId);
2133
+ resetStatsAndSyncContext();
2134
+ set({ ...routeState(), stats: { ...state.stats } });
2135
+ return result;
2136
+ } finally {
2137
+ set({ commandBusy: false });
2138
+ }
2139
+ },
2140
+ setWorkflow: async (workflowId) => {
2141
+ if (state.commandBusy) return null;
2142
+ set({ commandBusy: true });
2143
+ try {
2144
+ const result = runtime.setWorkflow?.(workflowId);
2145
+ set({ ...routeState(), stats: { ...state.stats } });
2146
+ return result;
2147
+ } finally {
2148
+ set({ commandBusy: false });
2149
+ }
2150
+ },
2151
+ setAgentRoute: async (agentId, opts) => {
2152
+ return await runtime.setAgentRoute?.(agentId, opts);
2153
+ },
2154
+ listProviders: () => {
2155
+ return runtime.listProviders();
2156
+ },
2157
+ getProviderSetup: () => {
2158
+ return runtime.getProviderSetup();
2159
+ },
2160
+ getUsageDashboard: async (options = {}) => {
2161
+ return await runtime.getUsageDashboard?.(options);
2162
+ },
2163
+ getOnboardingStatus: () => {
2164
+ return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
2165
+ },
2166
+ completeOnboarding: async (payload = {}) => {
2167
+ if (state.commandBusy) return null;
2168
+ set({ commandBusy: true });
2169
+ try {
2170
+ const result = await runtime.completeOnboarding?.(payload);
2171
+ resetStatsAndSyncContext();
2172
+ set({ ...routeState(), stats: { ...state.stats } });
2173
+ pushNotice('first-run setup saved', 'info');
2174
+ return result;
2175
+ } finally {
2176
+ set({ commandBusy: false });
2177
+ }
2178
+ },
2179
+ loginOAuthProvider: async (provider) => {
2180
+ if (state.commandBusy) return false;
2181
+ set({ commandBusy: true });
2182
+ try {
2183
+ const result = await runtime.loginOAuthProvider(provider);
2184
+ pushNotice(`provider oauth ok: ${result.provider}`, 'info');
2185
+ return true;
2186
+ } finally {
2187
+ set({ commandBusy: false });
2188
+ }
2189
+ },
2190
+ saveProviderApiKey: (provider, secret) => {
2191
+ const result = runtime.saveProviderApiKey(provider, secret);
2192
+ pushNotice(`provider api key saved: ${result.provider}`, 'info');
2193
+ return true;
2194
+ },
2195
+ saveOpenCodeGoUsageAuth: (opts) => {
2196
+ const result = runtime.saveOpenCodeGoUsageAuth(opts);
2197
+ pushNotice(result.workspaceId
2198
+ ? `OpenCode Go usage auth saved: ${result.workspaceId}`
2199
+ : 'OpenCode Go usage auth saved',
2200
+ 'info');
2201
+ return true;
2202
+ },
2203
+ saveOpenAIUsageSessionKey: (secret) => {
2204
+ runtime.saveOpenAIUsageSessionKey(secret);
2205
+ pushNotice('OpenAI usage auth saved', 'info');
2206
+ return true;
2207
+ },
2208
+ setLocalProvider: (provider, opts) => {
2209
+ const result = runtime.setLocalProvider(provider, opts);
2210
+ pushNotice(`local provider ${result.enabled ? 'enabled' : 'disabled'}: ${result.provider}`, 'info');
2211
+ return true;
2212
+ },
2213
+ authenticateProvider: async (provider, secret) => {
2214
+ if (state.commandBusy) return false;
2215
+ set({ commandBusy: true });
2216
+ try {
2217
+ const result = await runtime.authenticateProvider(provider, secret);
2218
+ pushNotice(`provider auth ok: ${result.provider} (${result.type})`, 'info');
2219
+ return true;
2220
+ } finally {
2221
+ set({ commandBusy: false });
2222
+ }
2223
+ },
2224
+ forgetProviderAuth: (provider) => {
2225
+ const result = runtime.forgetProviderAuth(provider);
2226
+ pushNotice(`provider auth forgotten: ${result.provider}`, 'info');
2227
+ return true;
2228
+ },
2229
+ getChannelSetup: () => {
2230
+ return runtime.getChannelSetup();
2231
+ },
2232
+ saveDiscordToken: (token) => {
2233
+ const result = runtime.saveDiscordToken(token);
2234
+ pushNotice('discord token saved', 'info');
2235
+ return result;
2236
+ },
2237
+ forgetDiscordToken: () => {
2238
+ const result = runtime.forgetDiscordToken();
2239
+ pushNotice('discord token forgotten', 'info');
2240
+ return result;
2241
+ },
2242
+ saveWebhookAuthtoken: (token) => {
2243
+ const result = runtime.saveWebhookAuthtoken(token);
2244
+ pushNotice('webhook/ngrok authtoken saved', 'info');
2245
+ return result;
2246
+ },
2247
+ forgetWebhookAuthtoken: () => {
2248
+ const result = runtime.forgetWebhookAuthtoken();
2249
+ pushNotice('webhook/ngrok authtoken forgotten', 'info');
2250
+ return result;
2251
+ },
2252
+ saveChannel: (entry) => {
2253
+ const result = runtime.saveChannel(entry);
2254
+ pushNotice(`channel saved: ${entry.name}`, 'info');
2255
+ return result;
2256
+ },
2257
+ deleteChannel: (name) => {
2258
+ const result = runtime.deleteChannel(name);
2259
+ pushNotice(`channel deleted: ${name}`, 'info');
2260
+ return result;
2261
+ },
2262
+ setWebhookConfig: (patch) => {
2263
+ const result = runtime.setWebhookConfig(patch);
2264
+ pushNotice('webhook config updated', 'info');
2265
+ return result;
2266
+ },
2267
+ saveSchedule: (entry) => {
2268
+ const result = runtime.saveSchedule(entry);
2269
+ pushNotice(`schedule saved: ${result.name}`, 'info');
2270
+ return result;
2271
+ },
2272
+ deleteSchedule: (name) => {
2273
+ const result = runtime.deleteSchedule(name);
2274
+ pushNotice(`schedule deleted: ${name}`, 'info');
2275
+ return result;
2276
+ },
2277
+ setScheduleEnabled: (name, enabled) => {
2278
+ const result = runtime.setScheduleEnabled(name, enabled);
2279
+ pushNotice(`schedule ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
2280
+ return result;
2281
+ },
2282
+ saveWebhook: (entry) => {
2283
+ const result = runtime.saveWebhook(entry);
2284
+ pushNotice(`webhook saved: ${result.name}`, 'info');
2285
+ return result;
2286
+ },
2287
+ deleteWebhook: (name) => {
2288
+ const result = runtime.deleteWebhook(name);
2289
+ pushNotice(`webhook deleted: ${name}`, 'info');
2290
+ return result;
2291
+ },
2292
+ setWebhookEnabled: (name, enabled) => {
2293
+ const result = runtime.setWebhookEnabled(name, enabled);
2294
+ pushNotice(`webhook ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
2295
+ return result;
2296
+ },
2297
+ setRoute: async (opts) => {
2298
+ if (state.commandBusy) return false;
2299
+ set({ commandBusy: true });
2300
+ try {
2301
+ await runtime.setRoute(opts);
2302
+ resetStatsAndSyncContext();
2303
+ set({ ...routeState(), stats: { ...state.stats } });
2304
+ return true;
2305
+ } finally {
2306
+ set({ commandBusy: false });
2307
+ }
2308
+ },
2309
+ pushNotice,
2310
+ clear: async () => {
2311
+ if (state.commandBusy) return false;
2312
+ set({ commandBusy: true });
2313
+ clearToastTimers();
2314
+ try {
2315
+ await runtime.clear({ recoverBridge: true });
2316
+ resetStatsAndSyncContext();
2317
+ set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...state.stats } });
2318
+ lastUserActivityAt = Date.now();
2319
+ return true;
2320
+ } finally {
2321
+ set({ commandBusy: false });
2322
+ }
2323
+ },
2324
+ listSessions: () => {
2325
+ return runtime.listSessions();
2326
+ },
2327
+ newSession: async () => {
2328
+ if (state.commandBusy) return false;
2329
+ set({ commandBusy: true });
2330
+ clearToastTimers();
2331
+ try {
2332
+ await runtime.newSession();
2333
+ resetStatsAndSyncContext();
2334
+ set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...state.stats } });
2335
+ return true;
2336
+ } finally {
2337
+ set({ commandBusy: false });
2338
+ }
2339
+ },
2340
+ resume: async (id) => {
2341
+ if (state.commandBusy) return false;
2342
+ set({ commandBusy: true, commandStatus: { active: true, verb: 'Resuming conversation', startedAt: Date.now(), mode: 'resuming' } });
2343
+ clearToastTimers();
2344
+ try {
2345
+ const r = await runtime.resume(id);
2346
+ if (!r) return false;
2347
+ resetStatsAndSyncContext();
2348
+ const items = [];
2349
+ for (const m of r.messages || []) {
2350
+ if (m.role === 'user') {
2351
+ // content may be a string OR an array of parts (text/tool-call
2352
+ // interleaving) — toolResultText coerces both to readable text so
2353
+ // array-content messages aren't silently dropped.
2354
+ const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
2355
+ if (text) {
2356
+ const synthetic = parseSyntheticAgentMessage(text);
2357
+ if (synthetic) {
2358
+ const label = synthetic.label || 'notification';
2359
+ items.push({
2360
+ kind: 'tool',
2361
+ id: nextId(),
2362
+ name: synthetic.name || 'bridge',
2363
+ args: synthetic.args || {
2364
+ type: label,
2365
+ task_id: synthetic.taskId || undefined,
2366
+ description: synthetic.summary || 'agent notification',
2367
+ },
2368
+ result: synthetic.result,
2369
+ isError: synthetic.isError ?? /^(failed|error|killed|cancelled)$/i.test(label),
2370
+ expanded: false,
2371
+ count: 1,
2372
+ completedCount: 1,
2373
+ startedAt: Date.now(),
2374
+ completedAt: Date.now(),
2375
+ });
2376
+ } else {
2377
+ items.push({ kind: 'user', id: nextId(), text });
2378
+ }
2379
+ }
2380
+ } else if (m.role === 'assistant') {
2381
+ const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
2382
+ if (text) items.push({ kind: 'assistant', id: nextId(), text });
2383
+ }
2384
+ }
2385
+ set({
2386
+ items: replaceItems(items),
2387
+ toasts: [],
2388
+ queued: [],
2389
+ thinking: null,
2390
+ spinner: null,
2391
+ lastTurn: null,
2392
+ ...routeState(),
2393
+ stats: { ...state.stats },
2394
+ });
2395
+ return true;
2396
+ } finally {
2397
+ set({ commandBusy: false, commandStatus: null });
2398
+ }
2399
+ },
2400
+ dispose: async (reason = 'cli-react-exit', options = {}) => {
2401
+ if (disposed) return;
2402
+ disposed = true;
2403
+ clearToastTimers();
2404
+ try { unsubscribeRuntimeNotifications?.(); } catch {}
2405
+ unsubscribeRuntimeNotifications = null;
2406
+ await runtime.close(reason, options);
2407
+ listeners.clear();
2408
+ },
2409
+ };
2410
+ }