mixdog 0.9.3 → 0.9.4

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 (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -11,9 +11,7 @@
11
11
  import React, { useEffect, useState } from 'react';
12
12
  import { Box, Text } from 'ink';
13
13
  import stringWidth from 'string-width';
14
- import stripAnsi from 'strip-ansi';
15
- import { displayWidth } from '../display-width.mjs';
16
- import { theme, TURN_MARKER, RESULT_GUTTER, RESULT_GUTTER_CONT } from '../theme.mjs';
14
+ import { theme, TURN_MARKER, AGENT_CALL_MARKER, AGENT_RESPONSE_MARKER } from '../theme.mjs';
17
15
  import { formatElapsed } from '../time-format.mjs';
18
16
  import { BULLET_OPERATOR } from '../figures.mjs';
19
17
  import {
@@ -22,23 +20,52 @@ import {
22
20
  summarizeToolResult as surfaceSummarizeToolResult,
23
21
  formatAggregateHeader,
24
22
  formatToolActionHeader,
25
- displayModelName,
26
23
  summarizeAgentSurfaceBrief,
27
24
  AGENT_SURFACE_BRIEF_MAX,
28
25
  } from '../../runtime/shared/tool-surface.mjs';
29
- import { backgroundTaskFailureStatusLabel, isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
30
- import { formatExpandedResult, wrapExpandedResultLines } from './tool-output-format.mjs';
31
-
32
- const MIN_RESULT_LINE_CHARS = 24;
33
- // Hard cap for the collapsed result detail row (the second line under the ⎿
34
- // gutter). Independent of terminal width so a wide terminal never lets a long
35
- // line (e.g. an agent response brief) stretch the whole row — anything past
36
- // this is truncated with an ellipsis. ctrl+o expand still shows the full body.
37
- const RESULT_LINE_HARD_MAX = 80;
38
- // Hard cap for the parenthesized header arg summary so a long path/query does
39
- // not eat the whole header line; anything longer is truncated with an ellipsis.
40
- const SUMMARY_MAX_CHARS = 48;
41
- const HEADER_FAILURE_STATUS_MAX = 32;
26
+ import { isBackgroundErrorOnlyBody, backgroundTaskFailureStatusLabel } from '../../runtime/shared/err-text.mjs';
27
+ import {
28
+ MIN_RESULT_LINE_CHARS,
29
+ RESULT_LINE_HARD_MAX,
30
+ SUMMARY_MAX_CHARS,
31
+ HEADER_FAILURE_STATUS_MAX,
32
+ safeInlineText,
33
+ normalizeCountMap,
34
+ truncateToWidth,
35
+ shellResultElapsed,
36
+ normalizeTerminalStatus,
37
+ resultTerminalStatus,
38
+ stripLeadingStatusMarkerLines,
39
+ stripLeadingStatusMarkerFromText,
40
+ } from './tool-execution/text-format.mjs';
41
+ import {
42
+ SKILL_SURFACE_NAMES,
43
+ isShellTool,
44
+ shellDisplayStatus,
45
+ shellHeader,
46
+ isAgentTool,
47
+ isBackgroundTaskTool,
48
+ agentResponseTitle,
49
+ agentActionTitle,
50
+ agentActionSummary,
51
+ hasAgentResponseResult,
52
+ resolveBackgroundTaskMeta,
53
+ backgroundTaskElapsed,
54
+ prefixElapsed,
55
+ mergeTerminalDetail,
56
+ shouldPrefixSyncElapsed,
57
+ backgroundTaskResultTitle,
58
+ backgroundTaskActionTitle,
59
+ backgroundTaskFailureDetail,
60
+ backgroundTaskDetail,
61
+ isBackgroundTaskResponseArgs,
62
+ genericCompletedDetail,
63
+ toolSearchLoadedSummary,
64
+ agentTerminalDetail,
65
+ clampFailureCount,
66
+ toolStatusColor,
67
+ } from './tool-execution/surface-detail.mjs';
68
+ import { ResultBody } from './tool-execution/ResultBody.jsx';
42
69
 
43
70
  export function displayToolName(name, args) {
44
71
  return surfaceDisplayToolName(name, args);
@@ -47,193 +74,6 @@ export function displayToolName(name, args) {
47
74
  const TOOL_BLINK_MS = 500;
48
75
  const TOOL_BLINK_LIMIT_MS = 3000;
49
76
  const TOOL_PENDING_SHOW_DELAY_MS = 1000;
50
- // Read `theme.subtle` at use-time (not captured here) so a live `/theme`
51
- // switch re-tones the tool hints. `theme` is mutated in-place on switch.
52
- // Collapsed tool headers/details are laid out as single terminal rows. Never let
53
- // raw C0/control bytes (CR, tabs, cursor escapes, etc.) reach those rows: a
54
- // terminal can apply them after Ink has already clipped/measured the row, which
55
- // makes a scrolled tool card appear to write through the prompt/statusline.
56
- const INLINE_CONTROL_RE = /[\u0000-\u001F\u007F]/g;
57
-
58
- function safeInlineText(value) {
59
- return stripAnsi(String(value ?? ''))
60
- .replace(/\r\n?/g, '\n')
61
- .replace(/\t/g, ' ')
62
- .replace(/\n+/g, ' ')
63
- .replace(INLINE_CONTROL_RE, ' ')
64
- .replace(/\s+/g, ' ')
65
- .trim();
66
- }
67
-
68
- function normalizeCount(value) {
69
- const n = Number(value || 0);
70
- return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
71
- }
72
-
73
- function normalizeCountMap(value = {}) {
74
- const out = {};
75
- for (const [key, raw] of Object.entries(value || {})) {
76
- if (raw && typeof raw === 'object') {
77
- out[key] = { ...raw, count: normalizeCount(raw.count) };
78
- } else {
79
- out[key] = normalizeCount(raw);
80
- }
81
- }
82
- return out;
83
- }
84
-
85
- function deltaColor(token) {
86
- return String(token || '').startsWith('+') ? theme.success : theme.error;
87
- }
88
-
89
- function deltaTextParts(text) {
90
- const value = String(text ?? '');
91
- const parts = [];
92
- const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
93
- let last = 0;
94
- let match;
95
- while ((match = re.exec(value))) {
96
- const prefix = match[1] || '';
97
- const token = (match[2] || '').replace(/\s+/g, '');
98
- const tokenStart = match.index + prefix.length;
99
- if (match.index > last) parts.push({ text: value.slice(last, match.index) });
100
- if (prefix) parts.push({ text: prefix });
101
- if (token) parts.push({ text: token, color: deltaColor(token) });
102
- last = tokenStart + (match[2] || '').length;
103
- }
104
- if (last < value.length) parts.push({ text: value.slice(last) });
105
- return parts;
106
- }
107
-
108
- function renderDeltaText(text) {
109
- return deltaTextParts(text).map((part, index) => (
110
- part.color ? <Text key={index} color={part.color}>{part.text}</Text> : part.text
111
- ));
112
- }
113
-
114
- // Shared multi-line result body: `└` on the first row, `│` continuation rail on
115
- // every following row, body text in one flex column so wrapping stays aligned
116
- // under the head gutter.
117
- //
118
- // Two render paths:
119
- // - COLLAPSED (raw=false): a single fitted summary line, diff(+/-) colored via
120
- // renderDeltaText.
121
- // - EXPANDED (raw=true): formatExpandedResult then wrapExpandedResultLines so
122
- // each physical row fits the body width before render (rail rows stay 1:1;
123
- // ink does not re-wrap). Physical row mount cap: MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES
124
- // (default 600; 0 disables). Shell/script bodies keep the newest tail when capped.
125
- function ResultBody({ lines, rawText, pathArg = '', isShell = false, columns, color, raw }) {
126
- const renderLines = raw
127
- ? wrapExpandedResultLines(
128
- formatExpandedResult(rawText, { pathArg, isShell }),
129
- columns,
130
- { isShell },
131
- )
132
- : (lines || []);
133
- if (!renderLines || renderLines.length === 0) return null;
134
- return (
135
- <Box flexDirection="row">
136
- <Box flexShrink={0} flexDirection="column">
137
- {renderLines.map((_, i) => (
138
- <Text key={i} color={theme.subtle}>{i === 0 ? RESULT_GUTTER : RESULT_GUTTER_CONT}</Text>
139
- ))}
140
- </Box>
141
- <Box flexDirection="column" flexShrink={1} flexGrow={1}>
142
- {renderLines.map((line, i) => (
143
- <Text key={i} color={raw ? undefined : color} wrap="truncate">
144
- {raw
145
- ? (line || ' ')
146
- : renderDeltaText(fitResultLine(line || ' ', columns))}
147
- </Text>
148
- ))}
149
- </Box>
150
- </Box>
151
- );
152
- }
153
-
154
- function plural(count, singular, pluralText = `${singular}s`) {
155
- return count === 1 ? singular : pluralText;
156
- }
157
-
158
- function isShellTool(normalizedName, label = '') {
159
- const n = String(normalizedName || '').toLowerCase();
160
- const l = String(label || '').toLowerCase();
161
- return n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait' || l === 'run';
162
- }
163
-
164
- function shellResultStatus(value) {
165
- const match = String(value || '').match(/(?:^|\b)status:\s*(running|pending|queued|completed|failed|cancelled|canceled)\b/im);
166
- return match ? String(match[1] || '').toLowerCase() : '';
167
- }
168
-
169
- function normalizeTerminalStatus(value) {
170
- const raw = String(value || '').trim().toLowerCase();
171
- if (!raw) return '';
172
- if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return 'running';
173
- if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return 'completed';
174
- if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return 'failed';
175
- if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
176
- return '';
177
- }
178
-
179
- function displayTerminalStatus(value) {
180
- const status = normalizeTerminalStatus(value);
181
- if (status === 'running') return 'Running';
182
- if (status === 'completed') return 'Finished';
183
- if (status === 'failed') return 'Failed';
184
- if (status === 'cancelled') return 'Cancelled';
185
- return '';
186
- }
187
-
188
- function resultTerminalStatus(value) {
189
- const text = String(value || '');
190
- const tagged = text.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
191
- if (tagged) return normalizeTerminalStatus(tagged);
192
- const bracketed = text.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
193
- if (bracketed) return normalizeTerminalStatus(bracketed);
194
- // Loose inline `status: x` / `state: x` matches are a last-resort fallback —
195
- // prefer the engine-controlled `<status>` tag or `[status: …]` marker above.
196
- // A loose match can false-positive on prose that happens to start with
197
- // "status:" (rare, but shellResultStatus below already owns real shell
198
- // output parsing; this fallback stays narrow and unchanged in behavior).
199
- const inline = text.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
200
- return normalizeTerminalStatus(inline);
201
- }
202
-
203
- const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
204
-
205
- function stripLeadingStatusMarkerLines(lines) {
206
- const out = Array.isArray(lines) ? lines.slice() : [];
207
- if (out.length > 0 && LEADING_STATUS_MARKER_LINE_RE.test(String(out[0] ?? '').trim())) out.shift();
208
- return out;
209
- }
210
-
211
- function stripLeadingStatusMarkerFromText(text) {
212
- return stripLeadingStatusMarkerLines(String(text || '').split('\n')).join('\n');
213
- }
214
-
215
- function shellDisplayStatus({ pending = false, failedCount = 0, isError = false, result = '' } = {}) {
216
- const status = shellResultStatus(result);
217
- if (pending || /^(running|pending|queued)$/.test(status)) return 'running';
218
- if (/^cancel/.test(status)) return 'cancelled';
219
- if (/^(failed|error|killed|timeout)$/.test(status) || isError || failedCount > 0) return 'failed';
220
- return 'completed';
221
- }
222
-
223
- function shellHeader(status, count = 1) {
224
- const n = Math.max(1, Number(count) || 1);
225
- const object = `${n} ${plural(n, 'command')}`;
226
- if (status === 'running') return `Running ${object}`;
227
- return `Ran ${object}`;
228
- }
229
-
230
- function shellResultElapsed(value) {
231
- const match = String(value || '').match(/^\[elapsed:\s*(\d+)\s*ms\]/mi);
232
- if (!match) return '';
233
- const elapsedMs = Number(match[1]);
234
- return Number.isFinite(elapsedMs) && elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
235
- }
236
-
237
77
  function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
238
78
  // No stableVerbWidth padding: it padded the done verb to the active ("-ing")
239
79
  // width, which Ink trims at the line END (vendor output trimEnd) so it never
@@ -243,395 +83,6 @@ function statusCopy(name, label, count, doneCount, pending, isError, args = {})
243
83
  // dropping the pad just normalizes the spacing.
244
84
  return formatToolActionHeader(name, args, { pending, count });
245
85
  }
246
-
247
- function fitResultLine(line, columns) {
248
- const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
249
- const text = safeInlineText(line);
250
- return displayWidth(text) > max ? truncateToWidth(text, max) : text;
251
- }
252
-
253
- /** Trim text from the end (by display width) so it fits maxWidth, appending '…'. */
254
- function truncateToWidth(text, maxWidth) {
255
- const str = safeInlineText(text);
256
- if (maxWidth < 1) return '';
257
- if (displayWidth(str) <= maxWidth) return str;
258
- const chars = Array.from(str);
259
- let out = '';
260
- for (const ch of chars) {
261
- if (displayWidth(out + ch + '…') > maxWidth) break;
262
- out += ch;
263
- }
264
- return `${out}…`;
265
- }
266
-
267
- function isAgentTool(normalizedName) {
268
- return normalizedName === 'agent';
269
- }
270
-
271
- const SKILL_SURFACE_NAMES = new Set([
272
- 'skill', 'skill_execute', 'skill_view', 'skills_list', 'use_skill',
273
- ]);
274
-
275
- function isBackgroundTaskTool(normalizedName) {
276
- return new Set(['explore', 'search', 'shell', 'bash', 'bash_session', 'shell_command', 'task']).has(String(normalizedName || '').toLowerCase());
277
- }
278
-
279
- const AGENT_DISPLAY_NAMES = new Map([
280
- ['explore', 'Explore'],
281
- ['maintainer', 'Maintainer'],
282
- ['worker', 'Worker'],
283
- ['heavy-worker', 'Heavy Worker'],
284
- ['reviewer', 'Reviewer'],
285
- ['debugger', 'Debugger'],
286
- ]);
287
-
288
- function titleizeAgentName(value) {
289
- const text = String(value || '').trim();
290
- if (!text) return '';
291
- const key = text.toLowerCase().replace(/[\s_]+/g, '-');
292
- if (AGENT_DISPLAY_NAMES.has(key)) return AGENT_DISPLAY_NAMES.get(key);
293
- return text
294
- .replace(/[_-]+/g, ' ')
295
- .split(/\s+/)
296
- .filter(Boolean)
297
- .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`)
298
- .join(' ');
299
- }
300
-
301
- function agentModelLabel(args) {
302
- const a = args && typeof args === 'object' ? args : {};
303
- const provider = String(a.provider || a.providerId || a.provider_id || '').trim();
304
- const model = String(a.model || '').trim();
305
- const displayHint = String(a.modelDisplay || a.model_display || a.displayModel || '').trim();
306
- return displayModelName(model, provider, displayHint);
307
- }
308
-
309
- function agentTagLabel(args) {
310
- // The real spawn tag (engine fills parsedArgs.tag from the envelope target).
311
- // Never fall back to task_id — only the human-meaningful spawn tag belongs in
312
- // the header parentheses.
313
- return String(args?.tag || '').trim();
314
- }
315
-
316
- function withModelAndTag(label, args) {
317
- const model = agentModelLabel(args);
318
- const tag = agentTagLabel(args);
319
- const inner = [model, tag].filter(Boolean).join(', ');
320
- return inner ? `${label} (${inner})` : label;
321
- }
322
-
323
- // Append an agent name to a base action word without leaving a trailing space
324
- // when the agent is unknown (no generic "Agent" fallback).
325
- function joinActionAgent(action, agent) {
326
- return agent ? `${action} ${agent}` : action;
327
- }
328
-
329
- function agentResponseTitle(args) {
330
- const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
331
- // The agent + model identify the responder; the response summary itself
332
- // is hidden in the collapsed card (ctrl+o expand still shows the full body).
333
- // No generic "Agent" fallback — render just "Response" when the agent is empty.
334
- return withModelAndTag(joinActionAgent('Response', name), args);
335
- }
336
-
337
- function agentActionTitle(args) {
338
- const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
339
- // Runtime treats an omitted type/action as "spawn" (see agent-tool.mjs default),
340
- // so mirror that contract here instead of falling through to the generic
341
- // "Called agent" status copy.
342
- const action = String(args?.type || args?.action || 'spawn').toLowerCase();
343
- // Fixed action verbs regardless of running/completed status. No generic
344
- // "Agent" fallback for the agent: when the agent is unknown render the action
345
- // word alone ("Spawn") instead of "Spawn Agent".
346
- if (action === 'spawn') return withModelAndTag(joinActionAgent('Spawn', name), args);
347
- if (action === 'send') return withModelAndTag(joinActionAgent('Send', name), args);
348
- if (action === 'list') return 'Agent status';
349
- if (action === 'cancel') return withModelAndTag(joinActionAgent('Cancel', name), args);
350
- if (action === 'close') return withModelAndTag(joinActionAgent('Close', name), args);
351
- if (action === 'cleanup') return withModelAndTag(joinActionAgent('Cleanup', name), args);
352
- if (action === 'read' || action === 'status') return withModelAndTag(joinActionAgent('Status', name), args);
353
- return '';
354
- }
355
-
356
- function agentActionSummary(args, summary) {
357
- const text = String(summary || '').trim();
358
- if (!text) return '';
359
- const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
360
- if (name && text === name) return '';
361
- let rest = name && text.startsWith(`${name} · `) ? text.slice(name.length + 3).trim() : text;
362
- // The agent/model/tag surface summary ("Heavy Worker · Opus 4.8") is now folded
363
- // into the header label itself ("Spawn Heavy Worker (Opus 4.8, tag)"), so drop
364
- // the model and tag tokens from the parenthesized summary to avoid showing
365
- // them twice.
366
- const model = agentModelLabel(args);
367
- if (model && rest === model) return '';
368
- const tag = agentTagLabel(args);
369
- if (tag && rest === tag) return '';
370
- return rest;
371
- }
372
-
373
- function hasAgentResponseResult(value) {
374
- const text = String(value || '').trim();
375
- if (!text) return false;
376
- if (/^(?:undefined|null)$/i.test(text)) return false;
377
- if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(text)) return false;
378
- const isBridgeEnvelope = /^(?:agent task:|background task\b|agent message queued\b|agent close:)/i.test(text)
379
- || /^(?:agents|tasks):\s*\d/i.test(text)
380
- || /^\(no agents or tasks\)$/i.test(text)
381
- || (/^task_id:\s*\S+/mi.test(text) && /^(?:surface|operation|status):\s*/mi.test(text));
382
- if (!isBridgeEnvelope) return true;
383
- let sawBlank = false;
384
- for (const line of text.split('\n')) {
385
- const trimmed = line.trim();
386
- if (!trimmed) {
387
- sawBlank = true;
388
- continue;
389
- }
390
- if (/^agent result\b/i.test(trimmed)) continue;
391
- if (/^(?:undefined|null)$/i.test(trimmed)) continue;
392
- if (/^<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>$/i.test(trimmed)) continue;
393
- if (!sawBlank && /^(?:agent task|background task|agent message queued\b|agent close:|task_id|surface|operation|label|status|type|target|agent|preset|model|effort|fast|limits|started|finished|error|notification|queueDepth):?\s*/i.test(trimmed)) continue;
394
- if (!sawBlank && /^(?:agents|tasks):\s*/i.test(trimmed)) continue;
395
- if (/^\(no agents or tasks\)$/i.test(trimmed)) continue;
396
- if (!sawBlank && /^-\s+\S+/i.test(trimmed)) continue;
397
- return true;
398
- }
399
- return false;
400
- }
401
-
402
- function parseBackgroundTaskResult(value) {
403
- const text = String(value || '').trim();
404
- if (!text) return null;
405
- const allLines = text.split('\n');
406
- const start = allLines.findIndex((line) => line.trim() === 'background task');
407
- if (start < 0) return null;
408
- const rest = allLines.slice(start + 1);
409
- const blank = rest.findIndex((line) => !line.trim());
410
- const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
411
- const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
412
- const fields = {};
413
- for (const line of headLines) {
414
- const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
415
- if (match) fields[match[1].toLowerCase()] = match[2].trim();
416
- }
417
- const status = String(fields.status || '').toLowerCase();
418
- const error = fields.error || '';
419
- return {
420
- taskId: fields.task_id || fields.taskid || '',
421
- surface: fields.surface || '',
422
- operation: fields.operation || '',
423
- label: fields.label || '',
424
- status,
425
- startedAt: fields.started || fields.startedat || '',
426
- finishedAt: fields.finished || fields.finishedat || '',
427
- body,
428
- error,
429
- hasResponse: Boolean(body) && !isBackgroundErrorOnlyBody(body, error) && !/^(running|pending|queued)$/i.test(status),
430
- };
431
- }
432
-
433
- function backgroundTaskMetaFromArgs(args = {}) {
434
- const taskId = String(args.task_id || args.taskId || '').trim();
435
- if (!taskId) return null;
436
- return {
437
- taskId,
438
- surface: args.surface || '',
439
- operation: args.operation || '',
440
- label: args.label || '',
441
- status: String(args.status || '').toLowerCase(),
442
- startedAt: args.startedAt || args.started || '',
443
- finishedAt: args.finishedAt || args.finished || '',
444
- error: args.error || '',
445
- body: '',
446
- hasResponse: false,
447
- };
448
- }
449
-
450
- function resolveBackgroundTaskMeta(parsedArgs = {}, resultText = '') {
451
- const parsed = parseBackgroundTaskResult(resultText);
452
- if (parsed) {
453
- if (!parsed.error && parsedArgs?.error) parsed.error = parsedArgs.error;
454
- if (!parsed.status && parsedArgs?.status) parsed.status = String(parsedArgs.status).toLowerCase();
455
- if (!parsed.surface && parsedArgs?.surface) parsed.surface = parsedArgs.surface;
456
- return parsed;
457
- }
458
- return backgroundTaskMetaFromArgs(parsedArgs);
459
- }
460
-
461
- function backgroundTaskElapsed(meta = {}, fallback = '') {
462
- const startedMs = Date.parse(meta.startedAt || '');
463
- const finishedMs = Date.parse(meta.finishedAt || '');
464
- if (Number.isFinite(startedMs) && Number.isFinite(finishedMs) && finishedMs >= startedMs) {
465
- const elapsedMs = finishedMs - startedMs;
466
- return elapsedMs >= 1000 ? formatElapsed(elapsedMs) : '';
467
- }
468
- return fallback || '';
469
- }
470
-
471
- function prefixElapsed(detail, elapsed = '') {
472
- const text = String(detail || '').trim();
473
- const time = String(elapsed || '').trim();
474
- if (!time) return text;
475
- // Unified convention: the elapsed time ALWAYS goes at the END, ` · ` separated.
476
- // Guard against a double-append when the text already ends with the same time.
477
- if (text && text.endsWith(`· ${time}`)) return text;
478
- return text ? `${text} · ${time}` : time;
479
- }
480
-
481
- function mergeTerminalDetail(status, detail = '') {
482
- const label = displayTerminalStatus(status);
483
- const text = String(detail || '').trim();
484
- if (!label) return text;
485
- if (label === 'Finished' && text) return text;
486
- if (!text) return label;
487
- if (text.toLowerCase().startsWith(label.toLowerCase())) return text;
488
- return `${label} · ${text}`;
489
- }
490
-
491
- function shouldPrefixSyncElapsed(normalizedName, label) {
492
- const n = String(normalizedName || '').toLowerCase();
493
- const l = String(label || '').toLowerCase();
494
- return n === 'explore' || l === 'explore' || n === 'search' || l === 'search' || l === 'web search';
495
- }
496
-
497
- function backgroundTaskDisplayName(normalizedName, meta = {}) {
498
- const surface = String(meta.surface || normalizedName || '').toLowerCase();
499
- if (surface === 'explore') return 'Explore';
500
- if (surface === 'search') return 'Search';
501
- if (surface === 'shell' || surface === 'bash' || surface === 'bash_session' || surface === 'shell_command' || surface === 'task') return 'Shell';
502
- return titleizeAgentName(surface || normalizedName || 'Task');
503
- }
504
-
505
- function backgroundTaskResultTitle(normalizedName, meta = {}) {
506
- const display = backgroundTaskDisplayName(normalizedName, meta);
507
- if (display === 'Shell') return 'Shell output';
508
- if (display === 'Search') return 'Search results';
509
- return `${display} response`;
510
- }
511
-
512
- function backgroundTaskActionTitle(normalizedName, meta = {}) {
513
- const display = backgroundTaskDisplayName(normalizedName, meta);
514
- if (/^(running|pending|queued)$/i.test(meta.status || '')) return `Started ${display}`;
515
- if (meta.hasResponse) return backgroundTaskResultTitle(normalizedName, meta);
516
- return `${display} status`;
517
- }
518
-
519
- function backgroundTaskFailureDetail(meta = {}, parsedArgs = {}) {
520
- const status = meta.status || parsedArgs?.status;
521
- const error = meta.error || parsedArgs?.error;
522
- if (!error) return '';
523
- const surface = meta.surface || parsedArgs?.surface || '';
524
- return backgroundTaskFailureStatusLabel(status, error, { surface });
525
- }
526
-
527
- function backgroundTaskDetail(meta = {}, elapsed = '', parsedArgs = {}) {
528
- const parts = [];
529
- const status = displayTerminalStatus(meta.status);
530
- if (status) parts.push(status);
531
- if (meta.taskId) parts.push(`task_id: ${meta.taskId}`);
532
- const firstBodyLine = String(meta.body || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
533
- if (firstBodyLine && /^(running|pending|queued)$/i.test(meta.status || '')) parts.push(firstBodyLine);
534
- return prefixElapsed(parts.join(' · '), elapsed);
535
- }
536
-
537
- function isBackgroundTaskResponseArgs(normalizedName, args = {}) {
538
- if (!isBackgroundTaskTool(normalizedName)) return false;
539
- const type = String(args?.type || args?.action || '').toLowerCase();
540
- const status = String(args?.status || '').toLowerCase();
541
- return type === 'result' || type === 'completion' || (/^(completed|cancelled|canceled)$/i.test(status) && Boolean(args?.task_id));
542
- }
543
-
544
- function isOutputDetailTool(normalizedName, label) {
545
- const n = String(normalizedName || '').toLowerCase();
546
- const l = String(label || '').toLowerCase();
547
- return new Set([
548
- 'shell', 'bash', 'bash_session', 'shell_command', 'job_wait',
549
- 'read', 'view_image', 'read_mcp_resource',
550
- 'grep', 'glob', 'search', 'search_query', 'image_query', 'web_search', 'web_search_call', 'explore', 'web_fetch', 'fetch', 'download_attachment',
551
- 'list', 'ls', 'code_graph',
552
- 'recall', 'recall_memory', 'search_memories', 'remember', 'save_memory', 'update_memory',
553
- ]).has(n) || l === 'read' || l === 'search' || l === 'web search' || l === 'run';
554
- }
555
-
556
- function genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) {
557
- const n = String(normalizedName || '').toLowerCase();
558
- const l = String(label || '').toLowerCase();
559
- if (isError) return hasResult ? firstResultLine : 'Failed';
560
- if (n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait') {
561
- return '';
562
- }
563
- if (isOutputDetailTool(n, l)) {
564
- return hasResult ? firstResultLine : '';
565
- }
566
- return '';
567
- }
568
-
569
- function toolSearchLoadedSummary(resultText) {
570
- let parsed;
571
- try {
572
- parsed = JSON.parse(String(resultText || ''));
573
- } catch {
574
- const text = String(resultText || '');
575
- const match = /^Loaded deferred tools:\s*(.+)$/m.exec(text);
576
- if (!match) return '';
577
- return [...new Set(match[1].split(',').map((name) => name.trim()).filter(Boolean))].join(', ');
578
- }
579
- const tools = parsed?.selected?.tools;
580
- if (!tools || typeof tools !== 'object') return '';
581
- const names = [
582
- ...(Array.isArray(tools.added) ? tools.added : []),
583
- ...(Array.isArray(tools.already) ? tools.already : []),
584
- ]
585
- .map((name) => String(name || '').trim())
586
- .filter(Boolean);
587
- return [...new Set(names)].join(', ');
588
- }
589
-
590
- function agentTerminalDetail(status, isError, elapsed, error = '') {
591
- const failureDetail = isError && error
592
- ? backgroundTaskFailureStatusLabel(status, error, { surface: 'agent' })
593
- : '';
594
- if (failureDetail) return failureDetail;
595
- const s = String(status || '').toLowerCase();
596
- const word = /cancel/.test(s)
597
- ? 'Cancelled'
598
- : /error|fail|killed|timeout/.test(s) || isError
599
- ? 'Failed'
600
- : /done|success|complete|closed/.test(s)
601
- ? 'Finished'
602
- : '';
603
- // Unified ` · <time>` convention (previously "Finished after 12s").
604
- return word ? `${word}${elapsed ? ` · ${elapsed}` : ''}` : '';
605
- }
606
-
607
- function clampFailureCount(errorCount, groupCount, isError) {
608
- const explicit = Number(errorCount);
609
- if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
610
- return isError ? groupCount : 0;
611
- }
612
-
613
- // Single source of truth for the tool-card dot (●) color. Both the aggregate
614
- // and normal (single-tool) render paths must call this with a resolved
615
- // `terminalStatus` — do not recompute color inline elsewhere.
616
- // running/pending -> theme.text (white; blink handled by caller)
617
- // success -> theme.success
618
- // partial failure -> mixdogOrange || warning (some, not all, of the group failed)
619
- // all failed -> theme.error
620
- // cancelled -> theme.warning
621
- // Note: callers resolve terminalStatus to 'failed' whenever failedCount > 0
622
- // (they cannot tell partial from total failure), so a 'failed' status must
623
- // still fall through to the failedCount check below rather than short-circuit
624
- // to error — otherwise a mixed-success batch (e.g. 1 of 3 failed) renders as
625
- // full-failure red instead of partial orange.
626
- function toolStatusColor({ pending, groupCount, failedCount, terminalStatus = '' }) {
627
- if (pending) return theme.text;
628
- const status = normalizeTerminalStatus(terminalStatus);
629
- if (status === 'cancelled') return theme.warning;
630
- if (failedCount <= 0) return status === 'failed' ? theme.error : theme.success;
631
- if (groupCount > 1 && failedCount < groupCount) return theme.mixdogOrange || theme.warning;
632
- return theme.error;
633
- }
634
-
635
86
  export function ToolExecution({ name, args, result, rawResult, isError, errorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, headerFinalized = true, deferredDisplayReady = false }) {
636
87
  const rowWidth = Math.max(1, Number(columns || 80));
637
88
  const [blinkOn, setBlinkOn] = useState(true);
@@ -988,7 +439,14 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
988
439
  }
989
440
  const finalStatusColor = toolStatusColor({ pending, groupCount, failedCount, terminalStatus });
990
441
  const dotColor = finalStatusColor;
991
- const dotText = pending && !blinkExpired && !blinkOn ? ' ' : TURN_MARKER;
442
+ // Agent surface cards use directional markers: `←` for requests going OUT
443
+ // (spawn/send/etc.) and `→` for the response coming back IN. Background
444
+ // task cards (shell async / explore / search / task) and every other tool
445
+ // keep the BLACK_CIRCLE turn marker. Blink behavior is shared.
446
+ const markerGlyph = isAgentResponse
447
+ ? AGENT_RESPONSE_MARKER
448
+ : (isAgentSurfaceCard ? AGENT_CALL_MARKER : TURN_MARKER);
449
+ const dotText = pending && !blinkExpired && !blinkOn ? ' ' : markerGlyph;
992
450
  let labelText;
993
451
  if (isAgentResponse) labelText = agentResponseTitle(parsedArgs);
994
452
  else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);