mixdog 0.7.18 → 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 (844) 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/http-agent.mjs +129 -0
  179. package/src/runtime/shared/open-url.mjs +37 -0
  180. package/src/runtime/shared/plugin-paths.mjs +25 -0
  181. package/src/runtime/shared/process-shutdown.mjs +147 -0
  182. package/src/runtime/shared/schedules-store.mjs +70 -0
  183. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  184. package/src/runtime/shared/tool-surface.mjs +950 -0
  185. package/src/runtime/shared/user-cwd.mjs +221 -0
  186. package/src/runtime/shared/user-data-guard.mjs +232 -0
  187. package/src/runtime/shared/workspace-router.mjs +259 -0
  188. package/src/standalone/bridge-tool.mjs +1414 -0
  189. package/src/standalone/channel-admin.mjs +366 -0
  190. package/src/standalone/channel-worker-preload.cjs +3 -0
  191. package/src/standalone/channel-worker.mjs +353 -0
  192. package/src/standalone/explore-tool.mjs +233 -0
  193. package/src/standalone/hook-bus.mjs +246 -0
  194. package/src/standalone/plugin-admin.mjs +247 -0
  195. package/src/standalone/provider-admin.mjs +338 -0
  196. package/src/standalone/seeds.mjs +94 -0
  197. package/src/standalone/usage-dashboard.mjs +510 -0
  198. package/src/tui/App.jsx +5438 -0
  199. package/src/tui/components/AnsiText.jsx +199 -0
  200. package/src/tui/components/ContextPanel.jsx +217 -0
  201. package/src/tui/components/Markdown.jsx +205 -0
  202. package/src/tui/components/MarkdownTable.jsx +204 -0
  203. package/src/tui/components/Message.jsx +103 -0
  204. package/src/tui/components/Picker.jsx +317 -0
  205. package/src/tui/components/PromptInput.jsx +584 -0
  206. package/src/tui/components/QueuedCommands.jsx +47 -0
  207. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  208. package/src/tui/components/Spinner.jsx +317 -0
  209. package/src/tui/components/StatusLine.jsx +87 -0
  210. package/src/tui/components/TextEntryPanel.jsx +323 -0
  211. package/src/tui/components/ToolExecution.jsx +772 -0
  212. package/src/tui/components/TurnDone.jsx +78 -0
  213. package/src/tui/components/UsagePanel.jsx +331 -0
  214. package/src/tui/dist/index.mjs +12359 -0
  215. package/src/tui/engine.mjs +2410 -0
  216. package/src/tui/figures.mjs +50 -0
  217. package/src/tui/hooks/useEngine.mjs +16 -0
  218. package/src/tui/index.jsx +254 -0
  219. package/src/tui/input-editing.mjs +242 -0
  220. package/src/tui/markdown/format-token.mjs +194 -0
  221. package/src/tui/paste-attachments.mjs +198 -0
  222. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  223. package/src/tui/spinner-verbs.mjs +45 -0
  224. package/src/tui/theme.mjs +67 -0
  225. package/src/tui/time-format.mjs +53 -0
  226. package/src/ui/ansi.mjs +115 -0
  227. package/src/ui/markdown.mjs +195 -0
  228. package/src/ui/statusline.mjs +730 -0
  229. package/src/ui/tool-card.mjs +101 -0
  230. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  231. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  232. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  233. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  234. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  235. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  236. package/src/workflows/default/WORKFLOW.md +7 -0
  237. package/src/workflows/default/workflow.json +14 -0
  238. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  239. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  240. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  241. package/vendor/ink/build/colorize.d.ts +3 -0
  242. package/vendor/ink/build/colorize.js +48 -0
  243. package/vendor/ink/build/colorize.js.map +1 -0
  244. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  245. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  246. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  247. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  248. package/vendor/ink/build/components/AnimationContext.js +13 -0
  249. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  250. package/vendor/ink/build/components/App.d.ts +24 -0
  251. package/vendor/ink/build/components/App.js +554 -0
  252. package/vendor/ink/build/components/App.js.map +1 -0
  253. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  254. package/vendor/ink/build/components/AppContext.js +25 -0
  255. package/vendor/ink/build/components/AppContext.js.map +1 -0
  256. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  257. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  258. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  259. package/vendor/ink/build/components/Box.d.ts +130 -0
  260. package/vendor/ink/build/components/Box.js +34 -0
  261. package/vendor/ink/build/components/Box.js.map +1 -0
  262. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  263. package/vendor/ink/build/components/CursorContext.js +8 -0
  264. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  265. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  266. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  267. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  268. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  269. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  270. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  271. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  272. package/vendor/ink/build/components/FocusContext.js +17 -0
  273. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  274. package/vendor/ink/build/components/Newline.d.ts +13 -0
  275. package/vendor/ink/build/components/Newline.js +8 -0
  276. package/vendor/ink/build/components/Newline.js.map +1 -0
  277. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  278. package/vendor/ink/build/components/Spacer.js +11 -0
  279. package/vendor/ink/build/components/Spacer.js.map +1 -0
  280. package/vendor/ink/build/components/Static.d.ts +24 -0
  281. package/vendor/ink/build/components/Static.js +28 -0
  282. package/vendor/ink/build/components/Static.js.map +1 -0
  283. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  284. package/vendor/ink/build/components/StderrContext.js +13 -0
  285. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  286. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  287. package/vendor/ink/build/components/StdinContext.js +20 -0
  288. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  289. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  290. package/vendor/ink/build/components/StdoutContext.js +13 -0
  291. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  292. package/vendor/ink/build/components/Text.d.ts +55 -0
  293. package/vendor/ink/build/components/Text.js +50 -0
  294. package/vendor/ink/build/components/Text.js.map +1 -0
  295. package/vendor/ink/build/components/Transform.d.ts +16 -0
  296. package/vendor/ink/build/components/Transform.js +15 -0
  297. package/vendor/ink/build/components/Transform.js.map +1 -0
  298. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  299. package/vendor/ink/build/cursor-helpers.js +62 -0
  300. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  301. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  302. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  303. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  304. package/vendor/ink/build/devtools.d.ts +1 -0
  305. package/vendor/ink/build/devtools.js +36 -0
  306. package/vendor/ink/build/devtools.js.map +1 -0
  307. package/vendor/ink/build/dom.d.ts +62 -0
  308. package/vendor/ink/build/dom.js +143 -0
  309. package/vendor/ink/build/dom.js.map +1 -0
  310. package/vendor/ink/build/get-max-width.d.ts +3 -0
  311. package/vendor/ink/build/get-max-width.js +10 -0
  312. package/vendor/ink/build/get-max-width.js.map +1 -0
  313. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  314. package/vendor/ink/build/hooks/use-animation.js +87 -0
  315. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  316. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  317. package/vendor/ink/build/hooks/use-app.js +8 -0
  318. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  319. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  320. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  321. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  322. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  323. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  324. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  325. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  326. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  327. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  328. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  329. package/vendor/ink/build/hooks/use-focus.js +43 -0
  330. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  331. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  332. package/vendor/ink/build/hooks/use-input.js +126 -0
  333. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  334. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  335. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  336. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  337. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  338. package/vendor/ink/build/hooks/use-paste.js +62 -0
  339. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  340. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  341. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  342. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  343. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  344. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  345. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  346. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  347. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  348. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  349. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  350. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  351. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  352. package/vendor/ink/build/index.d.ts +42 -0
  353. package/vendor/ink/build/index.js +24 -0
  354. package/vendor/ink/build/index.js.map +1 -0
  355. package/vendor/ink/build/ink.d.ts +146 -0
  356. package/vendor/ink/build/ink.js +1022 -0
  357. package/vendor/ink/build/ink.js.map +1 -0
  358. package/vendor/ink/build/input-parser.d.ts +10 -0
  359. package/vendor/ink/build/input-parser.js +194 -0
  360. package/vendor/ink/build/input-parser.js.map +1 -0
  361. package/vendor/ink/build/instances.d.ts +3 -0
  362. package/vendor/ink/build/instances.js +8 -0
  363. package/vendor/ink/build/instances.js.map +1 -0
  364. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  365. package/vendor/ink/build/kitty-keyboard.js +32 -0
  366. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  367. package/vendor/ink/build/log-update.d.ts +20 -0
  368. package/vendor/ink/build/log-update.js +261 -0
  369. package/vendor/ink/build/log-update.js.map +1 -0
  370. package/vendor/ink/build/measure-element.d.ts +20 -0
  371. package/vendor/ink/build/measure-element.js +13 -0
  372. package/vendor/ink/build/measure-element.js.map +1 -0
  373. package/vendor/ink/build/measure-text.d.ts +6 -0
  374. package/vendor/ink/build/measure-text.js +21 -0
  375. package/vendor/ink/build/measure-text.js.map +1 -0
  376. package/vendor/ink/build/output.d.ts +35 -0
  377. package/vendor/ink/build/output.js +328 -0
  378. package/vendor/ink/build/output.js.map +1 -0
  379. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  380. package/vendor/ink/build/parse-keypress.js +495 -0
  381. package/vendor/ink/build/parse-keypress.js.map +1 -0
  382. package/vendor/ink/build/reconciler.d.ts +4 -0
  383. package/vendor/ink/build/reconciler.js +306 -0
  384. package/vendor/ink/build/reconciler.js.map +1 -0
  385. package/vendor/ink/build/render-background.d.ts +4 -0
  386. package/vendor/ink/build/render-background.js +25 -0
  387. package/vendor/ink/build/render-background.js.map +1 -0
  388. package/vendor/ink/build/render-border.d.ts +4 -0
  389. package/vendor/ink/build/render-border.js +84 -0
  390. package/vendor/ink/build/render-border.js.map +1 -0
  391. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  392. package/vendor/ink/build/render-node-to-output.js +162 -0
  393. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  394. package/vendor/ink/build/render-to-string.d.ts +38 -0
  395. package/vendor/ink/build/render-to-string.js +116 -0
  396. package/vendor/ink/build/render-to-string.js.map +1 -0
  397. package/vendor/ink/build/render.d.ts +176 -0
  398. package/vendor/ink/build/render.js +71 -0
  399. package/vendor/ink/build/render.js.map +1 -0
  400. package/vendor/ink/build/renderer.d.ts +8 -0
  401. package/vendor/ink/build/renderer.js +64 -0
  402. package/vendor/ink/build/renderer.js.map +1 -0
  403. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  404. package/vendor/ink/build/sanitize-ansi.js +27 -0
  405. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  406. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  407. package/vendor/ink/build/squash-text-nodes.js +36 -0
  408. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  409. package/vendor/ink/build/styles.d.ts +302 -0
  410. package/vendor/ink/build/styles.js +303 -0
  411. package/vendor/ink/build/styles.js.map +1 -0
  412. package/vendor/ink/build/utils.d.ts +9 -0
  413. package/vendor/ink/build/utils.js +19 -0
  414. package/vendor/ink/build/utils.js.map +1 -0
  415. package/vendor/ink/build/wrap-text.d.ts +3 -0
  416. package/vendor/ink/build/wrap-text.js +38 -0
  417. package/vendor/ink/build/wrap-text.js.map +1 -0
  418. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  419. package/vendor/ink/build/write-synchronized.js +9 -0
  420. package/vendor/ink/build/write-synchronized.js.map +1 -0
  421. package/vendor/ink/license +10 -0
  422. package/vendor/ink/package.json +137 -0
  423. package/.claude-plugin/marketplace.json +0 -34
  424. package/.claude-plugin/plugin.json +0 -20
  425. package/.gitattributes +0 -34
  426. package/.mcp.json +0 -14
  427. package/ARCHITECTURE.md +0 -77
  428. package/CHANGELOG.md +0 -30
  429. package/CONTRIBUTING.md +0 -45
  430. package/DATA-FLOW.md +0 -79
  431. package/LICENSE +0 -21
  432. package/SECURITY.md +0 -138
  433. package/UNINSTALL.md +0 -115
  434. package/agents/maintenance.md +0 -5
  435. package/agents/memory-classification.md +0 -30
  436. package/agents/scheduler-task.md +0 -18
  437. package/agents/webhook-handler.md +0 -27
  438. package/agents/worker.md +0 -24
  439. package/bin/bridge +0 -133
  440. package/bin/statusline-launcher.mjs +0 -82
  441. package/bin/statusline-lib.mjs +0 -581
  442. package/bin/statusline-route.mjs +0 -273
  443. package/bin/statusline.mjs +0 -638
  444. package/bun.lock +0 -927
  445. package/commands/config.md +0 -16
  446. package/commands/doctor.md +0 -13
  447. package/commands/model.md +0 -61
  448. package/commands/setup.md +0 -17
  449. package/defaults/hidden-roles.json +0 -68
  450. package/defaults/memory-chunk-prompt.md +0 -63
  451. package/defaults/mixdog-config.template.json +0 -27
  452. package/defaults/user-workflow.json +0 -8
  453. package/defaults/user-workflow.md +0 -17
  454. package/hooks/hooks.json +0 -73
  455. package/hooks/lib/active-instance.cjs +0 -77
  456. package/hooks/lib/permission-evaluator.cjs +0 -411
  457. package/hooks/lib/permission-route.cjs +0 -63
  458. package/hooks/lib/settings-loader.cjs +0 -117
  459. package/hooks/post-tool-use.cjs +0 -84
  460. package/hooks/pre-mcp-sandbox.cjs +0 -158
  461. package/hooks/pre-tool-subagent.cjs +0 -258
  462. package/hooks/session-start.cjs +0 -1493
  463. package/hooks/shim-launcher.cjs +0 -65
  464. package/hooks/turn-timer.cjs +0 -82
  465. package/lib/claude-md-writer.cjs +0 -386
  466. package/lib/keychain-cjs.cjs +0 -290
  467. package/lib/plugin-paths.cjs +0 -69
  468. package/lib/rules-builder.cjs +0 -241
  469. package/native/README.md +0 -117
  470. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  471. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  472. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  473. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  474. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  475. package/prompts/code-review.txt +0 -16
  476. package/prompts/security-audit.txt +0 -17
  477. package/rules/bridge/00-common.md +0 -39
  478. package/rules/bridge/20-skip-protocol.md +0 -18
  479. package/rules/bridge/30-explorer.md +0 -33
  480. package/rules/bridge/40-cycle1-agent.md +0 -52
  481. package/rules/bridge/41-cycle2-agent.md +0 -62
  482. package/rules/lead/00-tool-lead.md +0 -61
  483. package/rules/lead/01-general.md +0 -26
  484. package/rules/lead/02-channels.md +0 -49
  485. package/rules/lead/03-team.md +0 -27
  486. package/rules/lead/04-workflow.md +0 -20
  487. package/rules/shared/00-language.md +0 -14
  488. package/rules/shared/01-tool.md +0 -138
  489. package/scripts/bootstrap.mjs +0 -130
  490. package/scripts/bridge-unify-smoke.mjs +0 -308
  491. package/scripts/build-runtime-linux.sh +0 -348
  492. package/scripts/build-runtime-macos.sh +0 -217
  493. package/scripts/build-runtime-windows.ps1 +0 -242
  494. package/scripts/builtin-utils-smoke.mjs +0 -398
  495. package/scripts/bump.mjs +0 -80
  496. package/scripts/check-json.mjs +0 -45
  497. package/scripts/check-syntax-changed.mjs +0 -102
  498. package/scripts/check-syntax.mjs +0 -58
  499. package/scripts/code-graph-batch.test.mjs +0 -33
  500. package/scripts/config-preserve-smoke.mjs +0 -180
  501. package/scripts/doctor.mjs +0 -489
  502. package/scripts/edit-normalize-fuzz.mjs +0 -130
  503. package/scripts/edit-normalize-smoke.mjs +0 -401
  504. package/scripts/edit-operation-smoke.mjs +0 -369
  505. package/scripts/edit2-smoke.mjs +0 -63
  506. package/scripts/ensure-deps.mjs +0 -259
  507. package/scripts/fuzzy-e2e.mjs +0 -28
  508. package/scripts/fuzzy-smoke.mjs +0 -26
  509. package/scripts/gateway-model.mjs +0 -596
  510. package/scripts/generate-runtime-manifest.mjs +0 -166
  511. package/scripts/guard-smoke.mjs +0 -66
  512. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  513. package/scripts/hook-routing-smoke.mjs +0 -29
  514. package/scripts/inject-input.ps1 +0 -204
  515. package/scripts/io-complex-smoke.mjs +0 -667
  516. package/scripts/io-explore-bench.mjs +0 -424
  517. package/scripts/io-guardrails-smoke.mjs +0 -205
  518. package/scripts/io-mini-bench-baseline.json +0 -11
  519. package/scripts/io-mini-bench.mjs +0 -216
  520. package/scripts/io-route-harness.mjs +0 -933
  521. package/scripts/io-telemetry-report.mjs +0 -691
  522. package/scripts/lib/gateway-inventory.mjs +0 -178
  523. package/scripts/lib/gateway-settings.mjs +0 -78
  524. package/scripts/mutation-bench.mjs +0 -564
  525. package/scripts/mutation-io-smoke.mjs +0 -1097
  526. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  527. package/scripts/native-patch-smoke.mjs +0 -304
  528. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  529. package/scripts/patch-interior-context-smoke.mjs +0 -49
  530. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  531. package/scripts/perf-hook-smoke.mjs +0 -71
  532. package/scripts/permission-eval-smoke.mjs +0 -443
  533. package/scripts/prep-patch.mjs +0 -53
  534. package/scripts/prep-shim.mjs +0 -96
  535. package/scripts/provider-cache-smoke.mjs +0 -687
  536. package/scripts/report-runtime-health.mjs +0 -132
  537. package/scripts/resolve-bun.mjs +0 -60
  538. package/scripts/run-mcp.mjs +0 -1473
  539. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  540. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  541. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  542. package/scripts/smoke-runtime-negative.ps1 +0 -100
  543. package/scripts/smoke-runtime-negative.sh +0 -95
  544. package/scripts/stall-policy-smoke.mjs +0 -50
  545. package/scripts/start-memory-worker.mjs +0 -23
  546. package/scripts/statusline-launcher-smoke.mjs +0 -235
  547. package/scripts/stress-atomic-write.mjs +0 -1028
  548. package/scripts/test-fault-inject.mjs +0 -164
  549. package/scripts/test-large-file.mjs +0 -174
  550. package/scripts/tool-edge-smoke.mjs +0 -209
  551. package/scripts/uninstall.mjs +0 -238
  552. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  553. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  554. package/server-main.mjs +0 -3350
  555. package/server.mjs +0 -468
  556. package/setup/config-merge.mjs +0 -246
  557. package/setup/install.mjs +0 -574
  558. package/setup/launch-core.mjs +0 -617
  559. package/setup/launch.mjs +0 -101
  560. package/setup/locate-claude.mjs +0 -56
  561. package/setup/mixdog-cli.mjs +0 -122
  562. package/setup/setup-server.mjs +0 -3305
  563. package/setup/setup.html +0 -3740
  564. package/setup/tui.mjs +0 -325
  565. package/skills/retro-skill-proposer/SKILL.md +0 -92
  566. package/skills/schedule-add/SKILL.md +0 -77
  567. package/skills/setup/SKILL.md +0 -356
  568. package/skills/webhook-add/SKILL.md +0 -81
  569. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  570. package/src/agent/index.mjs +0 -2229
  571. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  572. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  573. package/src/agent/orchestrator/bridge-trace.mjs +0 -601
  574. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  575. package/src/agent/orchestrator/config.mjs +0 -405
  576. package/src/agent/orchestrator/context/collect.mjs +0 -651
  577. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  578. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  579. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  580. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  581. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  582. package/src/agent/orchestrator/jobs.mjs +0 -116
  583. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  584. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1884
  585. package/src/agent/orchestrator/providers/anthropic.mjs +0 -598
  586. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  587. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  588. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  589. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  590. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  591. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  592. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  593. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  594. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  595. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  596. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  597. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  598. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  599. package/src/agent/orchestrator/session/loop.mjs +0 -1619
  600. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  601. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  602. package/src/agent/orchestrator/session/store.mjs +0 -632
  603. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  604. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  605. package/src/agent/orchestrator/session/trim.mjs +0 -491
  606. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  607. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  608. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  609. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  610. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  611. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  612. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  613. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  614. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  615. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  616. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -511
  617. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  618. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  619. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  620. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  621. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  622. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  623. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  624. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  625. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  626. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  627. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  628. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  629. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  630. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  631. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  632. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  633. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  634. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  635. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  636. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  637. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  638. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  639. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  640. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  641. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  642. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  643. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  644. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  645. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  646. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  647. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  648. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  649. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  650. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  651. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  652. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  653. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  654. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  655. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  656. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  657. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  658. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  659. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  660. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  661. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  662. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  663. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  664. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  665. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  666. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  667. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  668. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  669. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  670. package/src/agent/tool-defs.mjs +0 -110
  671. package/src/channels/backends/discord.mjs +0 -784
  672. package/src/channels/data/voice-runtime-manifest.json +0 -138
  673. package/src/channels/index.mjs +0 -3268
  674. package/src/channels/lib/config.mjs +0 -292
  675. package/src/channels/lib/drop-trace.mjs +0 -71
  676. package/src/channels/lib/event-pipeline.mjs +0 -81
  677. package/src/channels/lib/holidays.mjs +0 -138
  678. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  679. package/src/channels/lib/output-forwarder.mjs +0 -765
  680. package/src/channels/lib/runtime-paths.mjs +0 -552
  681. package/src/channels/lib/scheduler.mjs +0 -723
  682. package/src/channels/lib/session-discovery.mjs +0 -103
  683. package/src/channels/lib/state-file.mjs +0 -68
  684. package/src/channels/lib/status-snapshot.mjs +0 -219
  685. package/src/channels/lib/tool-format.mjs +0 -140
  686. package/src/channels/lib/transcript-discovery.mjs +0 -195
  687. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  688. package/src/channels/lib/webhook.mjs +0 -1318
  689. package/src/channels/tool-defs.mjs +0 -170
  690. package/src/daemon/host.mjs +0 -118
  691. package/src/daemon/mcp-transport.mjs +0 -47
  692. package/src/daemon/session.mjs +0 -100
  693. package/src/daemon/thin-client.mjs +0 -71
  694. package/src/daemon/transport.mjs +0 -163
  695. package/src/gateway/claude-current.mjs +0 -255
  696. package/src/gateway/oauth-usage.mjs +0 -598
  697. package/src/gateway/route-meta.mjs +0 -629
  698. package/src/gateway/server.mjs +0 -713
  699. package/src/memory/data/runtime-manifest.json +0 -40
  700. package/src/memory/index.mjs +0 -3332
  701. package/src/memory/lib/core-memory-store.mjs +0 -330
  702. package/src/memory/lib/embedding-provider.mjs +0 -269
  703. package/src/memory/lib/embedding-worker.mjs +0 -323
  704. package/src/memory/lib/memory-cycle1.mjs +0 -645
  705. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  706. package/src/memory/lib/memory-cycle3.mjs +0 -540
  707. package/src/memory/lib/memory-embed.mjs +0 -299
  708. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  709. package/src/memory/lib/memory-recall-store.mjs +0 -638
  710. package/src/memory/lib/memory.mjs +0 -412
  711. package/src/memory/lib/pg/adapter.mjs +0 -308
  712. package/src/memory/lib/pg/process.mjs +0 -360
  713. package/src/memory/lib/pg/supervisor.mjs +0 -396
  714. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  715. package/src/memory/lib/trace-store.mjs +0 -728
  716. package/src/memory/tool-defs.mjs +0 -79
  717. package/src/search/index.mjs +0 -1173
  718. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  719. package/src/search/lib/backends/exa.mjs +0 -50
  720. package/src/search/lib/backends/firecrawl.mjs +0 -61
  721. package/src/search/lib/backends/gemini-api.mjs +0 -83
  722. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  723. package/src/search/lib/backends/index.mjs +0 -150
  724. package/src/search/lib/backends/openai-api.mjs +0 -144
  725. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  726. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  727. package/src/search/lib/backends/tavily.mjs +0 -55
  728. package/src/search/lib/backends/xai-api.mjs +0 -113
  729. package/src/search/lib/config.mjs +0 -192
  730. package/src/search/lib/provider-usage.mjs +0 -67
  731. package/src/search/lib/providers.mjs +0 -47
  732. package/src/search/lib/search-intent.mjs +0 -109
  733. package/src/search/lib/setup-handler.mjs +0 -261
  734. package/src/search/lib/web-tools.mjs +0 -1219
  735. package/src/search/tool-defs.mjs +0 -83
  736. package/src/setup/defender-exclusion.mjs +0 -183
  737. package/src/shared/atomic-file.mjs +0 -436
  738. package/src/shared/config.mjs +0 -372
  739. package/src/shared/daemon-recycle.mjs +0 -108
  740. package/src/shared/disable-claude-builtins.mjs +0 -91
  741. package/src/shared/err-text.mjs +0 -12
  742. package/src/shared/llm/http-agent.mjs +0 -123
  743. package/src/shared/open-url.mjs +0 -62
  744. package/src/shared/plugin-paths.mjs +0 -58
  745. package/src/shared/schedules-store.mjs +0 -70
  746. package/src/shared/seed.mjs +0 -161
  747. package/src/shared/user-cwd.mjs +0 -225
  748. package/src/shared/user-data-guard.mjs +0 -244
  749. package/src/status/aggregator.mjs +0 -584
  750. package/src/status/server.mjs +0 -413
  751. package/tools.json +0 -1671
  752. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  753. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  754. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  755. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  756. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  757. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  758. /package/{lib → src/lib}/text-utils.cjs +0 -0
  759. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  760. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  761. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  762. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  763. /package/src/{agent → runtime/agent}/orchestrator/session/cache/post-edit-marks.mjs +0 -0
  764. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/advisory-lock.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  805. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  806. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  807. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  808. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  809. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  810. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  811. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  812. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  813. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  814. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  815. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  816. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  817. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  818. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  819. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  820. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  821. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  822. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  823. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  824. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  829. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  830. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  831. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  832. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  833. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  834. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  835. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  836. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  837. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  838. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  839. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  840. /package/src/{shared → runtime/shared}/llm/cost.mjs +0 -0
  841. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  842. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  843. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  844. /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
+ }