mixdog 0.9.2 → 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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,292 +1,71 @@
1
- import { displayModelName as sharedDisplayModelName } from '../../ui/model-display.mjs';
2
-
3
- // Default cap for tool-arg summaries (header parenthetical, channel tool lines).
4
- // Unified at 80 so every tool surface line — header arg summary and collapsed
5
- // detail alike — shares one width ceiling; per-call sites still clamp lower
6
- // (header 48, channel 50) where a tighter line is wanted.
7
- const DEFAULT_SUMMARY_MAX = 80;
8
- // Semantic cap for collapsed tool/agent/task card one-liners — the second row
9
- // under the ⎿ gutter (spawn, send, response, generic/shell result summaries).
10
- // Kept at 80 so every collapsed detail line is truncated to the same width
11
- // regardless of terminal columns; ctrl+o expand still shows the full body.
12
- export const AGENT_SURFACE_BRIEF_MAX = 80;
13
- const STATUS_SEPARATOR = ' · ';
14
-
15
- export function stripToolPrefix(name) {
16
- const text = rawToolName(name);
17
- const mcp = parseMcpToolName(text);
18
- return mcp ? mcp.tool : text;
19
- }
20
-
21
- function rawToolName(name) {
22
- return String(name || 'tool').replace(/^functions\./, '');
23
- }
24
-
25
- export function parseMcpToolName(name) {
26
- const text = rawToolName(name);
27
- const match = /^mcp__(.+?)__(.+)$/.exec(text);
28
- if (!match) return null;
29
- return { server: match[1], tool: match[2] };
30
- }
31
-
32
- export function isMcpToolName(name) {
33
- return Boolean(parseMcpToolName(name));
34
- }
35
-
36
- export function normalizeToolName(name) {
37
- return stripToolPrefix(name).replace(/-/g, '_').toLowerCase();
38
- }
39
-
40
- export function truncateToolText(value, max = DEFAULT_SUMMARY_MAX) {
41
- const text = String(value ?? '').trim();
42
- return text.length > max ? `${text.slice(0, max)}...` : text;
43
- }
44
-
45
- function truncateSingleLine(value, max = DEFAULT_SUMMARY_MAX) {
46
- return truncateToolText(String(value ?? '').replace(/\s+/g, ' '), max);
47
- }
48
-
49
- function truncateCommand(value, max = DEFAULT_SUMMARY_MAX) {
50
- const text = String(value ?? '').trim();
51
- if (!text) return '';
52
- const lines = text.split('\n');
53
- let out = lines.length > 2 ? `${lines.slice(0, 2).join(' ')}...` : lines.join(' ');
54
- out = out.replace(/\s+/g, ' ').trim();
55
- return out.length > max ? `${out.slice(0, Math.max(1, max - 3))}...` : out;
56
- }
57
-
58
- export function parseToolArgs(args) {
59
- if (!args) return {};
60
- if (typeof args === 'string') {
61
- try {
62
- const parsed = JSON.parse(args);
63
- return parsed && typeof parsed === 'object' ? parsed : { value: args };
64
- } catch {
65
- return { value: args };
66
- }
67
- }
68
- if (typeof args === 'object') {
69
- if (args.input && typeof args.input === 'object') return args.input;
70
- return args;
71
- }
72
- return { value: args };
73
- }
74
-
75
- export function displayToolPath(path) {
76
- const text = String(path ?? '');
77
- return text.replace(/\\/g, '/').split('/').filter(Boolean).at(-1) || text;
78
- }
79
-
80
- function compactParts(parts) {
81
- return parts.filter((part) => part != null && String(part).trim()).map((part) => String(part).trim()).join(STATUS_SEPARATOR);
82
- }
83
-
84
- function compactSlash(left, right) {
85
- const a = String(left ?? '').trim();
86
- const b = String(right ?? '').trim();
87
- return a && b ? `${a}/${b}` : a || b;
88
- }
89
-
90
- function mcpToolTarget(name, max = DEFAULT_SUMMARY_MAX) {
91
- const mcp = parseMcpToolName(name);
92
- if (!mcp) return '';
93
- return truncateToolText(compactSlash(mcp.server, mcp.tool), max);
94
- }
95
-
96
- function quoted(value, max) {
97
- const text = truncateToolText(value || '', max);
98
- return text ? `"${text}"` : '';
99
- }
100
-
101
- function firstText(...values) {
102
- for (const value of values) {
103
- if (value != null && String(value).trim()) return String(value).trim();
104
- }
105
- return '';
106
- }
107
-
108
- function splitToolSearchSelection(value) {
109
- if (Array.isArray(value)) return value.map((item) => String(item || '').trim()).filter(Boolean);
110
- return String(value || '')
111
- .split(/[,\s]+/)
112
- .map((item) => item.trim())
113
- .filter(Boolean);
114
- }
115
-
116
- function toolSearchTargetKind(value) {
117
- const lower = String(value || '').trim().toLowerCase();
118
- if (!lower) return '';
119
- if (lower.startsWith('mcp__') || lower.includes('_mcp_') || lower.includes('mcp')) return 'MCP';
120
- if (lower === 'skill' || lower.startsWith('skill:') || lower.startsWith('skill_') || lower.startsWith('skills_') || lower.includes('skill')) return 'Skills';
121
- return 'Tools';
122
- }
123
-
124
- function orderedToolSearchKinds(kinds) {
125
- const set = new Set((kinds || []).filter(Boolean));
126
- const order = ['Tools', 'MCP', 'Skills'];
127
- const out = order.filter((kind) => set.has(kind));
128
- return out.length ? out : ['Tools'];
129
- }
130
-
131
- function inferToolSearchKinds(args = {}) {
132
- const selected = splitToolSearchSelection(args.select);
133
- if (selected.length) return orderedToolSearchKinds(selected.map(toolSearchTargetKind));
134
-
135
- const query = firstText(args.query, args.q, args.text).toLowerCase();
136
- const kinds = [];
137
- if (/\bmcp\b|mcp__|mcp[-_\s]?server/.test(query)) kinds.push('MCP');
138
- if (/\bskills?\b|skill_|use[-_\s]?skill/.test(query)) kinds.push('Skills');
139
- return orderedToolSearchKinds(kinds);
140
- }
141
-
142
- function toolSearchDisplayLabel(args = {}) {
143
- return `Load ${inferToolSearchKinds(args).join('/')}`;
144
- }
145
-
146
- function displayToolSearchTarget(value) {
147
- const text = String(value || '').trim();
148
- if (/^skill:/i.test(text)) return text.slice('skill:'.length);
149
- const mcp = /^mcp__(.*?)__(.+)$/.exec(text);
150
- if (mcp) return `${mcp[1]}.${mcp[2]}`;
151
- return stripToolPrefix(text);
152
- }
153
-
154
- function titleizeToolName(name) {
155
- return stripToolPrefix(name)
156
- .split(/[_\s-]+/)
157
- .filter(Boolean)
158
- .map((part) => {
159
- const lower = part.toLowerCase();
160
- if (lower === 'ui') return 'UI';
161
- if (lower === 'mcp') return 'MCP';
162
- if (lower === 'id') return 'ID';
163
- return `${lower.slice(0, 1).toUpperCase()}${lower.slice(1)}`;
164
- })
165
- .join(' ') || 'Tool';
166
- }
167
-
168
- const AGENT_DISPLAY_NAMES = new Map([
169
- ['explore', 'Explore'],
170
- ['maintainer', 'Maintainer'],
171
- ['worker', 'Worker'],
172
- ['heavy-worker', 'Heavy Worker'],
173
- ['reviewer', 'Reviewer'],
174
- ['debugger', 'Debugger'],
175
- ]);
176
-
177
- function titleizeDisplayName(value) {
178
- const text = String(value || '').trim();
179
- if (!text) return '';
180
- return text
181
- .replace(/[_-]+/g, ' ')
182
- .split(/\s+/)
183
- .filter(Boolean)
184
- .map((part) => {
185
- const lower = part.toLowerCase();
186
- if (lower === 'ui') return 'UI';
187
- if (lower === 'mcp') return 'MCP';
188
- if (lower === 'id') return 'ID';
189
- return `${lower.slice(0, 1).toUpperCase()}${lower.slice(1)}`;
190
- })
191
- .join(' ');
192
- }
193
-
194
- function displayAgentName(value) {
195
- const text = String(value || '').trim();
196
- if (!text) return '';
197
- const key = text.toLowerCase().replace(/[\s_]+/g, '-');
198
- return AGENT_DISPLAY_NAMES.get(key) || titleizeDisplayName(text);
199
- }
200
-
201
- export function displayModelName(model, provider, displayHint) {
202
- const text = String(model ?? '').trim();
203
- const modelId = text
204
- ? (text.includes('/') ? (text.split('/').filter(Boolean).at(-1) || text) : text)
205
- : '';
206
- const shown = sharedDisplayModelName(modelId, provider, displayHint);
207
- return shown || modelId;
208
- }
209
-
210
- function bridgeAgentModelSummary(args) {
211
- const provider = firstText(args.provider, args.providerId, args.provider_id);
212
- const modelId = firstText(args.model);
213
- const displayHint = firstText(args.modelDisplay, args.model_display, args.displayModel);
214
- return compactParts([
215
- displayAgentName(firstText(args.agent, args.name, args.subagent_type)),
216
- displayModelName(modelId, provider, displayHint),
217
- ]);
218
- }
219
-
220
- function summarizeLineWindow(a) {
221
- const offset = a.offset ?? a.start_line ?? a.startLine ?? a.line;
222
- const limit = a.limit ?? a.line_count ?? a.lineCount ?? a.lines;
223
- if (offset == null && limit == null) return '';
224
- const start = Number(offset);
225
- const count = Number(limit);
226
- if (Number.isFinite(start) && Number.isFinite(count) && count > 0) {
227
- return `lines ${start}-${Math.max(start, start + count - 1)}`;
228
- }
229
- if (Number.isFinite(start)) return `from line ${start}`;
230
- if (Number.isFinite(count)) return `${count} lines`;
231
- return '';
232
- }
233
-
234
- function summarizePatch(patch, basePath) {
235
- const text = String(patch ?? '');
236
- const files = [];
237
- for (const line of text.split('\n')) {
238
- const match = /^\*\*\*\s+(?:Update|Add|Delete) File:\s+(.+)\s*$/.exec(line);
239
- if (match) files.push(displayToolPath(match[1]));
240
- }
241
- if (files.length === 1) return files[0];
242
- if (files.length > 1) return `${files.length} files`;
243
- if (basePath) return displayToolPath(basePath);
244
- return text ? 'patch' : '';
245
- }
246
-
247
- function collectionCount(...values) {
248
- for (const value of values) {
249
- if (Array.isArray(value)) {
250
- const count = value.filter((item) => item != null && String(item).trim()).length;
251
- return count || value.length || 0;
252
- }
253
- if (value && typeof value === 'object') return 1;
254
- if (value != null && String(value).trim()) return 1;
255
- }
256
- return 0;
257
- }
258
-
259
- function formatCountedUnit(count, singular, pluralText = `${singular}s`) {
260
- const n = Math.max(0, Number(count || 0));
261
- return n > 0 ? `${n} ${pluralize(n, singular, pluralText)}` : '';
262
- }
263
-
264
- function patchFileCount(args = {}) {
265
- const patchText = String(args.patch ?? '');
266
- if (patchText) {
267
- const files = new Set();
268
- for (const line of patchText.split('\n')) {
269
- const match = /^\*\*\*\s+(?:Update|Add|Delete) File:\s+(.+)\s*$/.exec(line);
270
- if (match) files.add(match[1].trim());
271
- }
272
- if (files.size > 0) return files.size;
273
- }
274
- return collectionCount(args.path, args.file, args.file_path, args.base_path);
275
- }
276
-
277
- function codeGraphLabel(args) {
278
- const mode = String(args.mode || args.action || '').toLowerCase();
279
- if (mode === 'prewarm' || mode === 'index' || mode === 'build' || mode === 'refresh') return 'Setup';
280
- if (mode === 'search' || mode === 'find_symbol' || mode === 'references' || mode === 'callers' || mode === 'callees') return 'Search';
281
- return 'Read';
282
- }
283
-
284
- function codeGraphSummary(args, max) {
285
- return compactParts([
286
- args.mode || args.action || '',
287
- truncateToolText(firstText(args.symbol, Array.isArray(args.symbols) ? args.symbols.join(', ') : '', args.file, args.path, args.query), max),
288
- ]);
289
- }
1
+ // ── Facade: tool-surface primitives + result summaries are re-exported from
2
+ // their cohesive modules so every existing importer resolves unchanged. This
3
+ // file keeps the display-name / arg-summary and aggregate-card clusters.
4
+ import {
5
+ DEFAULT_SUMMARY_MAX,
6
+ AGENT_SURFACE_BRIEF_MAX,
7
+ STATUS_SEPARATOR,
8
+ stripToolPrefix,
9
+ parseMcpToolName,
10
+ isMcpToolName,
11
+ normalizeToolName,
12
+ truncateToolText,
13
+ truncateSingleLine,
14
+ truncateCommand,
15
+ parseToolArgs,
16
+ displayToolPath,
17
+ compactParts,
18
+ compactSlash,
19
+ mcpToolTarget,
20
+ quoted,
21
+ firstText,
22
+ splitToolSearchSelection,
23
+ toolSearchDisplayLabel,
24
+ displayToolSearchTarget,
25
+ titleizeToolName,
26
+ displayAgentName,
27
+ displayModelName,
28
+ bridgeAgentModelSummary,
29
+ summarizeLineWindow,
30
+ summarizePatch,
31
+ collectionCount,
32
+ formatCountedUnit,
33
+ patchFileCount,
34
+ codeGraphLabel,
35
+ codeGraphSummary,
36
+ pluralize,
37
+ titleWord,
38
+ titleStatus,
39
+ } from './tool-primitives.mjs';
40
+ import {
41
+ parseLineDelta,
42
+ formatLineDelta,
43
+ parseUpdateSummary,
44
+ extractErrorCause,
45
+ summarizeToolResult,
46
+ isExplorerSurface,
47
+ summarizeAgentSurfaceBrief,
48
+ isMemorySurface,
49
+ } from './tool-result-summary.mjs';
50
+
51
+ export {
52
+ AGENT_SURFACE_BRIEF_MAX,
53
+ DEFAULT_SUMMARY_MAX,
54
+ STATUS_SEPARATOR,
55
+ stripToolPrefix,
56
+ parseMcpToolName,
57
+ isMcpToolName,
58
+ normalizeToolName,
59
+ truncateToolText,
60
+ parseToolArgs,
61
+ displayToolPath,
62
+ displayModelName,
63
+ extractErrorCause,
64
+ summarizeToolResult,
65
+ isExplorerSurface,
66
+ summarizeAgentSurfaceBrief,
67
+ isMemorySurface,
68
+ };
290
69
 
291
70
  export function displayToolName(name, args = {}) {
292
71
  if (isMcpToolName(name)) return 'MCP';
@@ -315,8 +94,6 @@ export function displayToolName(name, args = {}) {
315
94
  case 'list':
316
95
  case 'ls':
317
96
  return 'Search';
318
- case 'trigger_schedule':
319
- return 'Schedule';
320
97
  case 'tool_search':
321
98
  return toolSearchDisplayLabel(parseToolArgs(args));
322
99
  case 'search':
@@ -329,15 +106,7 @@ export function displayToolName(name, args = {}) {
329
106
  return 'Explore';
330
107
  case 'web_fetch':
331
108
  case 'fetch':
332
- case 'download_attachment':
333
109
  return 'Fetch';
334
- case 'diagnostics':
335
- case 'open_config':
336
- case 'provider_status':
337
- case 'channel_status':
338
- case 'schedule_status':
339
- case 'schedule_control':
340
- case 'reload_config':
341
110
  case 'list_mcp_resources':
342
111
  case 'list_mcp_resource_templates':
343
112
  case 'cwd':
@@ -366,10 +135,6 @@ export function displayToolName(name, args = {}) {
366
135
  case 'code_graph':
367
136
  return codeGraphLabel(parseToolArgs(args));
368
137
  case 'reply':
369
- case 'react':
370
- case 'edit_message':
371
- case 'activate_channel_bridge':
372
- case 'inject_command':
373
138
  return 'Channel';
374
139
  default:
375
140
  return titleizeToolName(name);
@@ -472,27 +237,11 @@ export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}
472
237
  return formatCountedUnit(collectionCount(a.url, a.uri), 'URL', 'URLs');
473
238
  }
474
239
  return truncateToolText(a.url || a.uri || '', max);
475
- case 'download_attachment':
476
- return displayToolPath(a.filename || a.name || a.url || '');
477
240
  case 'read_mcp_resource':
478
241
  return truncateToolText(a.uri || '', max);
479
242
  case 'list_mcp_resources':
480
243
  case 'list_mcp_resource_templates':
481
244
  return a.server ? `server "${truncateToolText(a.server, max)}"` : 'all servers';
482
- case 'diagnostics':
483
- return truncateToolText(a.scope || a.mode || 'runtime', max);
484
- case 'open_config':
485
- return truncateToolText(a.section || a.tab || 'config', max);
486
- case 'provider_status':
487
- return truncateToolText(a.provider || 'providers', max);
488
- case 'channel_status':
489
- return truncateToolText(a.channel || a.name || 'channels', max);
490
- case 'schedule_status':
491
- case 'schedule_control':
492
- case 'trigger_schedule':
493
- return compactParts([a.action || a.type || 'schedule', a.name || a.id || '']);
494
- case 'reload_config':
495
- return truncateToolText(a.scope || 'config', max);
496
245
  case 'cwd':
497
246
  return truncateToolText(firstText(a.path, a.cwd, a.dir), max);
498
247
  case 'memory':
@@ -524,13 +273,7 @@ export function summarizeToolArgs(name, args, { max = DEFAULT_SUMMARY_MAX } = {}
524
273
  case 'code_graph':
525
274
  return codeGraphSummary(a, max);
526
275
  case 'reply':
527
- case 'react':
528
- case 'edit_message':
529
276
  return truncateToolText(a.channel || a.channelId || a.messageId || a.emoji || '', max);
530
- case 'activate_channel_bridge':
531
- return a.active === false ? 'deactivate' : 'activate';
532
- case 'inject_command':
533
- return truncateToolText(firstText(a.command, a.text, a.name), max);
534
277
  case 'skill':
535
278
  case 'skill_execute':
536
279
  case 'skill_view':
@@ -566,537 +309,6 @@ export function formatToolSurface(name, args, opts = {}) {
566
309
  };
567
310
  }
568
311
 
569
- function pluralize(count, singular, pluralText = `${singular}s`) {
570
- return count === 1 ? singular : pluralText;
571
- }
572
-
573
- function titleWord(value) {
574
- const text = String(value || '');
575
- return text ? `${text.charAt(0).toUpperCase()}${text.slice(1).toLowerCase()}` : '';
576
- }
577
-
578
- function titleStatus(value) {
579
- const text = String(value || '').trim();
580
- if (!text) return '';
581
- if (/^ok$/i.test(text)) return 'Ok';
582
- if (/^(done|success|completed)$/i.test(text)) return 'Finished';
583
- if (/^(error|failed|fail|killed|timeout)$/i.test(text)) return 'Failed';
584
- if (/^(cancelled|canceled|cancel)$/i.test(text)) return 'Cancelled';
585
- return titleWord(text);
586
- }
587
-
588
- function countNonEmptyLines(text) {
589
- return String(text ?? '')
590
- .split('\n')
591
- .filter((line) => line.trim()).length;
592
- }
593
-
594
- // Zero-result recognizer (audit HIGH): result text that SAYS "nothing found"
595
- // must summarize as an explicit zero, not be line-counted into "1 match".
596
- // Matches the shapes emitted by grep/glob/find/list/recall backends:
597
- // "(no matches)", "no matches found", "(no results)", "No results",
598
- // "(no fuzzy match for \"...\")", "0 matches", "(empty)", "(no entries)".
599
- function looksLikeZeroResultText(text) {
600
- const trimmed = String(text ?? '').trim();
601
- if (!trimmed) return false;
602
- // Only trust short, single-line-ish payloads — a real listing that merely
603
- // CONTAINS the words "no matches" somewhere must not be zeroed.
604
- if (trimmed.length > 200 || trimmed.includes('\n')) return false;
605
- return /^\(?\s*(?:no|0)\s+(?:fuzzy\s+)?(?:match(?:es)?|results?|files?|entries|candidates?|hits?)\b/i.test(trimmed)
606
- || /^\(?\s*(?:empty|none)\s*\)?$/i.test(trimmed)
607
- || /^no\s+\S+\s+(?:found|matched)\b/i.test(trimmed);
608
- }
609
-
610
- function splitPathAndDelta(value, explicitDelta = '') {
611
- let path = String(value ?? '').trim();
612
- let delta = String(explicitDelta ?? '').trim();
613
- if (!delta) {
614
- const sep = path.lastIndexOf(' — ');
615
- if (sep !== -1) {
616
- delta = path.slice(sep + 3).trim();
617
- path = path.slice(0, sep).trim();
618
- }
619
- }
620
- return { path, delta };
621
- }
622
-
623
- function parseLineDelta(delta) {
624
- const totals = { added: 0, removed: 0, seen: false };
625
- for (const match of String(delta ?? '').matchAll(/([+-])\s*(\d+)\s*(?:line|lines)?/gi)) {
626
- const n = Number(match[2]) || 0;
627
- totals.seen = true;
628
- if (match[1] === '+') totals.added += n;
629
- else totals.removed += n;
630
- }
631
- return totals;
632
- }
633
-
634
- function formatLineDelta(totals) {
635
- if (!totals?.seen) return '';
636
- const added = Number(totals.added) || 0;
637
- const removed = Number(totals.removed) || 0;
638
- if (added === 0 && removed === 0) return '';
639
- const parts = [];
640
- if (added > 0) parts.push(`+${added} ${pluralize(added, 'line')}`);
641
- if (removed > 0) parts.push(`-${removed} ${pluralize(removed, 'line')}`);
642
- return parts.join(STATUS_SEPARATOR);
643
- }
644
-
645
- function parseUpdateSummary(text) {
646
- const match = /^(Updated|Created|Deleted)\s+(.+?)(?:\s+·\s+|$)/i.exec(String(text || '').trim());
647
- if (!match) return null;
648
- const action = titleWord(match[1]);
649
- const target = match[2].trim();
650
- const fileCountMatch = /^(\d+)\s+Files?$/i.exec(target);
651
- const totals = parseLineDelta(text);
652
- return {
653
- action,
654
- file: fileCountMatch ? '' : target,
655
- fileCount: fileCountMatch ? Number(fileCountMatch[1]) || 0 : 0,
656
- added: totals.added,
657
- removed: totals.removed,
658
- seen: totals.seen,
659
- };
660
- }
661
-
662
- /** Heuristic: does the text look like a line-oriented listing rather than prose? */
663
- function looksLineOriented(text) {
664
- const lines = String(text ?? '').split('\n').filter((line) => line.trim());
665
- if (lines.length === 0) return false;
666
- // Prose tends to be a few long sentences; listings are many shorter rows.
667
- const longLines = lines.filter((line) => line.trim().length > 200).length;
668
- return longLines === 0;
669
- }
670
-
671
- function summarizeUpdateResult(text, args) {
672
- // A dry_run patch validates without writing, so the collapsed detail must not
673
- // claim a real mutation ("Updated/Created/Deleted foo.js"). Map every action
674
- // to "Checked" wording, matching the dry-run header (Checking/Checked).
675
- const isDryRun = parseToolArgs(args)?.dry_run === true;
676
- const changed = [];
677
- for (const line of String(text ?? '').split('\n')) {
678
- const ok = /^\s*OK\s+(modify|add|delete|create)\s+(.+?)\s*$/i.exec(line);
679
- if (ok) {
680
- const { path, delta } = splitPathAndDelta(ok[2]);
681
- changed.push({ action: ok[1].toLowerCase(), path, delta });
682
- continue;
683
- }
684
- const edited = /^\s*(Edited|Created|Updated|Wrote):\s+(.+?)(?:\s+\(([^)]+)\))?\s*$/i.exec(line);
685
- if (edited) {
686
- const { path, delta } = splitPathAndDelta(edited[2], edited[3] || '');
687
- changed.push({ action: edited[1].toLowerCase(), path, delta });
688
- }
689
- }
690
- if (changed.length === 1) {
691
- const item = changed[0];
692
- const action = isDryRun
693
- ? 'Checked'
694
- : item.action === 'delete' ? 'Deleted' : item.action === 'add' || item.action === 'create' || item.action === 'created' ? 'Created' : 'Updated';
695
- return compactParts([`${action} ${displayToolPath(item.path)}`, formatLineDelta(parseLineDelta(item.delta))]);
696
- }
697
- if (changed.length > 1) {
698
- const totals = changed.reduce((acc, item) => {
699
- const delta = parseLineDelta(item.delta);
700
- acc.added += delta.added;
701
- acc.removed += delta.removed;
702
- acc.seen = acc.seen || delta.seen;
703
- return acc;
704
- }, { added: 0, removed: 0, seen: false });
705
- return compactParts([`${isDryRun ? 'Checked' : 'Updated'} ${changed.length} Files`, formatLineDelta(totals)]);
706
- }
707
-
708
- const parsedArgs = parseToolArgs(args);
709
- const target = parsedArgs.path ?? parsedArgs.file ?? parsedArgs.file_path ?? '';
710
- if (target) return `${isDryRun ? 'Checked' : 'Updated'} ${displayToolPath(target)}`;
711
- return null;
712
- }
713
-
714
- function textBetweenTag(text, tag) {
715
- const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
716
- const match = re.exec(String(text ?? ''));
717
- return match ? match[1].trim() : '';
718
- }
719
-
720
- // Strip the most common inline-markdown markers so a one-line card summary
721
- // reads as plain prose ("**not clean**" → "not clean", "`x`" → "x"). Block
722
- // markers (#, >, -, 1.) at line start are dropped too. Whitespace is collapsed
723
- // by the caller's truncateSingleLine.
724
- function stripInlineMarkdown(value) {
725
- return String(value ?? '')
726
- .replace(/^\s{0,3}(?:#{1,6}\s+|>\s+|[-*+]\s+|\d+\.\s+)/, '')
727
- .replace(/\*\*([^*]+)\*\*/g, '$1')
728
- .replace(/__([^_]+)__/g, '$1')
729
- .replace(/(?<!\*)\*(?!\*)([^*]+)\*(?!\*)/g, '$1')
730
- .replace(/`([^`]+)`/g, '$1')
731
- .trim();
732
- }
733
-
734
- function firstAgentResultLine(text) {
735
- const finalAnswer = textBetweenTag(text, 'final-answer') || textBetweenTag(text, 'result');
736
- const raw = finalAnswer || text;
737
- for (const line of String(raw ?? '').split('\n')) {
738
- const trimmed = line.trim();
739
- if (!trimmed) continue;
740
- if (/^agent result\b/i.test(trimmed)) continue;
741
- 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;
742
- if (/^(?:agent task|status|type|target|role|agent|preset|model|effort|fast|limits|session|task-id|task_id|notification|queueDepth):\s*/i.test(trimmed)) continue;
743
- if (/^\[[a-z-]+:\s*[^\]]*\]$/i.test(trimmed)) continue;
744
- return truncateSingleLine(trimmed, AGENT_SURFACE_BRIEF_MAX);
745
- }
746
- return '';
747
- }
748
-
749
- function summarizeGenericResult(text) {
750
- const trimmed = String(text ?? '').trim();
751
- if (!trimmed) return null;
752
- if (/^(?:undefined|null)$/i.test(trimmed)) return null;
753
-
754
- if (/^[\[{]/.test(trimmed)) {
755
- try {
756
- const parsed = JSON.parse(trimmed);
757
- if (Array.isArray(parsed)) return `${parsed.length} ${pluralize(parsed.length, 'item')}`;
758
- if (parsed && typeof parsed === 'object') {
759
- const status = firstText(parsed.status, parsed.state, parsed.result, parsed.message);
760
- if (status) return truncateSingleLine(titleStatus(status), AGENT_SURFACE_BRIEF_MAX);
761
- if (parsed.cwd) return truncateSingleLine(parsed.cwd, AGENT_SURFACE_BRIEF_MAX);
762
- if (typeof parsed.ok === 'boolean') return parsed.ok ? 'Ok' : 'Failed';
763
- for (const key of ['items', 'results', 'resources', 'templates', 'providers', 'schedules', 'channels', 'tools']) {
764
- if (Array.isArray(parsed[key])) return `${parsed[key].length} ${pluralize(parsed[key].length, (key.slice(0, -1) || 'item').toLowerCase())}`;
765
- }
766
- }
767
- } catch {
768
- return null;
769
- }
770
- }
771
-
772
- const line = firstAgentResultLine(text) || trimmed.split('\n').map((item) => item.trim()).find(Boolean) || '';
773
- if (!line || line === '{' || line === '[') return null;
774
- if (/^(ok|done|success|saved|sent|updated|reloaded|connected|enabled|disabled|active|inactive)$/i.test(line)) {
775
- return titleStatus(line);
776
- }
777
- return truncateSingleLine(line, AGENT_SURFACE_BRIEF_MAX);
778
- }
779
-
780
- // Error-cause extractor (audit HIGH): when a tool result is an error, surface
781
- // the actual cause line instead of a bare "Failed"/raw first line. Handles
782
- // JSON error envelopes ({error|message|cause|detail}) and plain-text bodies
783
- // (first line that carries error-ish signal, else first non-empty line).
784
- // Exported for ToolExecution's collapsed detail row.
785
- export function extractErrorCause(resultText) {
786
- const text = String(resultText ?? '').trim();
787
- if (!text) return '';
788
- // JSON envelope: prefer explicit error-ish keys.
789
- if (text.startsWith('{') || text.startsWith('[')) {
790
- try {
791
- const parsed = JSON.parse(text);
792
- const obj = Array.isArray(parsed) ? parsed[0] : parsed;
793
- if (obj && typeof obj === 'object') {
794
- const cause = firstText(
795
- typeof obj.error === 'string' ? obj.error : obj.error?.message,
796
- obj.message, obj.cause, obj.detail, obj.reason, obj.status,
797
- );
798
- if (cause) return truncateSingleLine(String(cause), AGENT_SURFACE_BRIEF_MAX);
799
- }
800
- } catch { /* fall through to text scan */ }
801
- }
802
- const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
803
- // Prefer the first line that looks like an error statement.
804
- const errorish = lines.find((l) => /\b(error|failed|failure|denied|refused|timed?\s*out|timeout|not\s+found|missing|invalid|cannot|can't|exception|exit\s+(?:code\s+)?[1-9])\b/i.test(l));
805
- const picked = errorish || lines[0] || '';
806
- return truncateSingleLine(stripInlineMarkdown(picked), AGENT_SURFACE_BRIEF_MAX);
807
- }
808
-
809
- /**
810
- * Derive a short semantic one-liner for a completed tool call using only the
811
- * tool name, parsed args, and the raw result text. Returns null when nothing
812
- * reliable can be derived, so the caller falls back to the raw result block.
813
- */
814
- export function summarizeToolResult(name, args, resultText, isError = false) {
815
- if (isError) {
816
- // Audit HIGH: errors used to disable semantic summaries entirely, leaving
817
- // the UI with a raw first line or a bare "Failed". Surface the extracted
818
- // cause so the collapsed card answers "why" without ctrl+o.
819
- const cause = extractErrorCause(resultText);
820
- return cause || null;
821
- }
822
- const text = String(resultText ?? '');
823
- const trimmed = text.trim();
824
- if (/^(?:undefined|null)$/i.test(trimmed)) return null;
825
- if (isMcpToolName(name)) return trimmed ? firstAgentResultLine(text) || null : null;
826
- const normalized = normalizeToolName(name);
827
-
828
- switch (normalized) {
829
- case 'read':
830
- case 'view_image':
831
- case 'read_mcp_resource': {
832
- if (/^\[image:/i.test(trimmed)) return 'Image';
833
- if (!trimmed) return null;
834
- const n = text.split('\n').length;
835
- return `${n} ${pluralize(n, 'line')}`;
836
- }
837
- case 'apply_patch': {
838
- const updateSummary = summarizeUpdateResult(text, args);
839
- if (updateSummary) return updateSummary;
840
- // Prefer explicit additions/removals hints if the result text states them.
841
- const add = /(\d+)\s+addition/i.exec(text);
842
- const rem = /(\d+)\s+removal/i.exec(text);
843
- if (add || rem) {
844
- const a = add ? Number(add[1]) : 0;
845
- const r = rem ? Number(rem[1]) : 0;
846
- return `+${a} -${r}`;
847
- }
848
- // Else count unified-diff style +/- lines (ignore +++/--- file headers).
849
- let a = 0;
850
- let r = 0;
851
- for (const line of text.split('\n')) {
852
- if (/^\+\+\+|^---/.test(line)) continue;
853
- if (/^\+/.test(line)) a += 1;
854
- else if (/^-/.test(line)) r += 1;
855
- }
856
- if (a > 0 || r > 0) return `+${a} -${r}`;
857
- return null;
858
- }
859
- case 'grep': {
860
- if (!trimmed || !looksLineOriented(text)) return null;
861
- if (looksLikeZeroResultText(text)) return '0 matches';
862
- const n = countNonEmptyLines(text);
863
- if (n === 0) return null;
864
- return `${n} ${pluralize(n, 'match', 'matches')}`;
865
- }
866
- case 'glob': {
867
- if (!trimmed || !looksLineOriented(text)) return null;
868
- if (looksLikeZeroResultText(text)) return '0 files';
869
- const n = countNonEmptyLines(text);
870
- if (n === 0) return null;
871
- return `${n} ${pluralize(n, 'file')}`;
872
- }
873
- case 'find': {
874
- if (!trimmed || !looksLineOriented(text)) return null;
875
- if (looksLikeZeroResultText(text)) return '0 candidates';
876
- const n = countNonEmptyLines(text);
877
- if (n === 0) return null;
878
- return `${n} ${pluralize(n, 'candidate')}`;
879
- }
880
- case 'list':
881
- case 'ls': {
882
- if (!trimmed || !looksLineOriented(text)) return null;
883
- if (looksLikeZeroResultText(text)) return '0 entries';
884
- const n = countNonEmptyLines(text);
885
- if (n === 0) return null;
886
- return `${n} ${pluralize(n, 'entry', 'entries')}`;
887
- }
888
- case 'shell':
889
- case 'bash':
890
- case 'bash_session':
891
- case 'shell_command':
892
- case 'job_wait': {
893
- if (!trimmed) return '(No Output)';
894
- const job = /^\[(?:task_id|job):\s*([^\]]+)\]/mi.exec(text);
895
- const status = /^\[status:\s*([^\]]+)\]/mi.exec(text);
896
- const exit = /^\[exit:\s*([^\]]+)\]/mi.exec(text);
897
- if (job || status || exit) {
898
- return compactParts([
899
- job ? job[1] : '',
900
- status ? titleStatus(status[1]) : '',
901
- exit ? `Exit ${exit[1]}` : '',
902
- ]);
903
- }
904
- const firstLine = trimmed.split('\n').map((line) => line.trim()).find(Boolean) || trimmed;
905
- return truncateSingleLine(firstLine, AGENT_SURFACE_BRIEF_MAX);
906
- }
907
- case 'code_graph': {
908
- const match = /(\d+)\s+(references|definitions|symbols|callers|callees|results|matches)/i.exec(text);
909
- if (match) return `${match[1]} ${String(match[2]).toLowerCase()}`;
910
- if (looksLikeZeroResultText(text)) return 'No results';
911
- return null;
912
- }
913
- case 'web_fetch':
914
- case 'fetch': {
915
- // Audit HIGH: channel `fetch` (Discord message fetch — args carry
916
- // channel/messageId/limit, never url/uri) was summarized as a WEB fetch,
917
- // so its result missed both the status/size probes and fell to raw JSON.
918
- // Route it to the generic JSON/text summarizer instead.
919
- if (normalized === 'fetch') {
920
- const a = parseToolArgs(args);
921
- const isChannelFetch = !firstText(a.url, a.uri)
922
- && Boolean(firstText(a.channel, a.channelId, a.chatId, a.messageId) || a.limit != null);
923
- if (isChannelFetch) {
924
- const n = countNonEmptyLines(text);
925
- if (trimmed && looksLineOriented(text) && n > 0) return `${n} ${pluralize(n, 'message')}`;
926
- return summarizeGenericResult(text);
927
- }
928
- }
929
- // Status: require a status-like context (HTTP NNN, "Status: NNN",
930
- // or "NNN OK"/"NNN Not Found") rather than any bare 3-digit number.
931
- const status = /(?:HTTP[\s/]*\d?\.?\d?\s*|status[:\s]+)([1-5]\d{2})\b/i.exec(text)
932
- || /\b([1-5]\d{2})\s+(?:OK|Not\s+Found|Forbidden|Moved|Found|Created|No\s+Content|Bad\s+Request|Unauthorized|Internal)/.exec(text);
933
- const size = /\b(\d+(?:\.\d+)?\s?(?:[KMGT]?B|bytes))\b/i.exec(text);
934
- if (size && status) return `${size[1]} · HTTP ${status[1]}`;
935
- if (size) return size[1];
936
- if (status) return `HTTP ${status[1]}`;
937
- return null;
938
- }
939
- case 'download_attachment':
940
- return summarizeGenericResult(text);
941
- case 'search': {
942
- const match = /(\d+)\s+results?/i.exec(text);
943
- if (match) {
944
- const n = Number(match[1]);
945
- return `${n} ${pluralize(n, 'result')}`;
946
- }
947
- return null;
948
- }
949
- case 'search_query':
950
- case 'image_query':
951
- case 'web_search':
952
- case 'web_search_call': {
953
- const match = /(\d+)\s+results?/i.exec(text);
954
- if (match) {
955
- const n = Number(match[1]);
956
- return `${n} ${pluralize(n, 'result')}`;
957
- }
958
- return trimmed ? firstAgentResultLine(text) || null : null;
959
- }
960
- case 'explore': {
961
- return trimmed ? firstAgentResultLine(text) || null : null;
962
- }
963
- case 'recall':
964
- case 'search_memories':
965
- case 'memory': {
966
- if (!trimmed || trimmed === '(no results)' || looksLikeZeroResultText(text)) return 'No Results';
967
- let n = 0;
968
- for (const line of text.split('\n')) {
969
- if (/#\d+\s*$/.test(line)) n += 1;
970
- }
971
- if (n > 0) return `${n} ${pluralize(n, 'Memory', 'Memories')}`;
972
- return summarizeGenericResult(text);
973
- }
974
- case 'remember':
975
- case 'save_memory':
976
- case 'update_memory':
977
- case 'reply':
978
- case 'react':
979
- case 'edit_message':
980
- case 'activate_channel_bridge':
981
- case 'inject_command':
982
- case 'request_user_input':
983
- case 'update_plan':
984
- case 'diagnostics':
985
- case 'open_config':
986
- case 'provider_status':
987
- case 'channel_status':
988
- case 'schedule_status':
989
- case 'schedule_control':
990
- case 'trigger_schedule':
991
- case 'reload_config':
992
- case 'cwd':
993
- case 'list_mcp_resources':
994
- case 'list_mcp_resource_templates':
995
- return summarizeGenericResult(text);
996
- case 'skill':
997
- case 'skill_execute':
998
- case 'skill_view':
999
- case 'skills_list':
1000
- case 'use_skill': {
1001
- const parsedArgs = parseToolArgs(args);
1002
- const target = firstText(parsedArgs.name, parsedArgs.skill, parsedArgs.skill_name);
1003
- if (normalized === 'skills_list') {
1004
- const count = /(\d+)\s+skills?/i.exec(text);
1005
- if (count) return `${Number(count[1]) || count[1]} ${pluralize(Number(count[1]) || 0, 'skill')}`;
1006
- const lines = countNonEmptyLines(text);
1007
- return lines > 0 ? `${lines} ${pluralize(lines, 'skill')}` : null;
1008
- }
1009
- if (target) {
1010
- const verb = normalized === 'skill' || normalized === 'skill_view' ? 'Loaded' : 'Used';
1011
- return `${verb} ${truncateToolText(target, 80)}`;
1012
- }
1013
- return trimmed ? firstAgentResultLine(text) || null : null;
1014
- }
1015
- case 'agent':
1016
- case 'task': {
1017
- // Status-check (list/status) envelopes start with "agents: N" / "tasks: M"
1018
- // (or "(no agents or tasks)"). Collapse them to a tight count summary
1019
- // instead of leaking the raw "agents: 3 …" worker dump into the card.
1020
- if (/^\(no agents or tasks\)$/im.test(text)) return 'No agents or tasks';
1021
- const agentsCount = /^agents:\s*(\d+)/im.exec(text);
1022
- const tasksCount = /^tasks:\s*(\d+)/im.exec(text);
1023
- if (agentsCount || tasksCount) {
1024
- const a = agentsCount ? Number(agentsCount[1]) : 0;
1025
- const t = tasksCount ? Number(tasksCount[1]) : 0;
1026
- const parts = [];
1027
- if (agentsCount) parts.push(`${a} ${pluralize(a, 'agent')}`);
1028
- if (tasksCount && t > 0) parts.push(`${t} ${pluralize(t, 'task')}`);
1029
- return compactParts(parts) || 'No agents or tasks';
1030
- }
1031
- const answerLine = firstAgentResultLine(text);
1032
- // Agent/task result cards show only a one-liner; full report via ctrl+o.
1033
- if (answerLine) return truncateSingleLine(stripInlineMarkdown(answerLine), AGENT_SURFACE_BRIEF_MAX);
1034
- const task = /^agent task:\s*(\S+)/mi.exec(text);
1035
- const status = /^status:\s*([^\s(]+)/mi.exec(text);
1036
- const agent = /^agent:\s*(.+)$/mi.exec(text);
1037
- const preset = /^preset:\s*(.+)$/mi.exec(text);
1038
- const model = /^model:\s*(.+)$/mi.exec(text);
1039
- const limits = /^limits:\s*(.+)$/mi.exec(text);
1040
- const agentModel = compactParts([
1041
- displayAgentName(agent ? agent[1] : ''),
1042
- displayModelName(model ? model[1] : ''),
1043
- ]);
1044
- if (agentModel) return agentModel;
1045
- const parts = [
1046
- task ? task[1] : '',
1047
- agent ? agent[1] : '',
1048
- preset ? preset[1] : '',
1049
- model ? model[1] : '',
1050
- status ? titleStatus(status[1]) : '',
1051
- limits ? limits[1] : '',
1052
- ].filter(Boolean);
1053
- if (parts.length) return compactParts(parts);
1054
- if (task) return status ? `${task[1]} ${titleStatus(status[1])}` : task[1];
1055
- return null;
1056
- }
1057
- default:
1058
- return null;
1059
- }
1060
- }
1061
-
1062
- export function isExplorerSurface(label) {
1063
- return label === 'Read' || label === 'Search';
1064
- }
1065
-
1066
- function truncateAgentSurfaceBrief(value, max = AGENT_SURFACE_BRIEF_MAX) {
1067
- const text = String(value ?? '').replace(/\s+/g, ' ').trim();
1068
- if (!text) return '';
1069
- if (text.length <= max) return text;
1070
- return `${text.slice(0, Math.max(1, max - 1))}\u2026`;
1071
- }
1072
-
1073
- // Tight one-liner for an agent/task card: prefer the inbound prompt/message for
1074
- // spawn/send, or a stripped first line of the result when a response landed.
1075
- export function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, isResponse = false } = {}) {
1076
- const a = parseToolArgs(args);
1077
- const action = String(a?.type || a?.action || '').toLowerCase();
1078
- const text = String(resultText ?? '').trim();
1079
- if (isResponse && text) {
1080
- const fromResult = summarizeToolResult(name, args, text, isError);
1081
- if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
1082
- const line = firstAgentResultLine(text);
1083
- if (line) return truncateAgentSurfaceBrief(stripInlineMarkdown(line));
1084
- }
1085
- const outbound = firstText(a?.prompt, a?.message);
1086
- if (outbound && (action === 'spawn' || action === 'send' || !action)) {
1087
- return truncateAgentSurfaceBrief(outbound);
1088
- }
1089
- if ((action === 'spawn' || action === 'send') && text && !isResponse) {
1090
- const fromResult = summarizeToolResult(name, args, text, isError);
1091
- if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
1092
- }
1093
- return '';
1094
- }
1095
-
1096
- export function isMemorySurface(label) {
1097
- return label === 'Memory';
1098
- }
1099
-
1100
312
  // ── Aggregate tool-card classification & formatting ──────────────
1101
313
 
1102
314
  export const CATEGORY_ORDER = [
@@ -1121,7 +333,6 @@ const TOOL_CATEGORY = new Map([
1121
333
  ['web_search_call', 'Web Research'],
1122
334
  ['web_fetch', 'Web Research'],
1123
335
  ['fetch', 'Web Research'],
1124
- ['download_attachment', 'Web Research'],
1125
336
  ['recall', 'Memory'],
1126
337
  ['recall_memory', 'Memory'],
1127
338
  ['search_memories', 'Memory'],
@@ -1139,23 +350,11 @@ const TOOL_CATEGORY = new Map([
1139
350
  ['task', 'Task'],
1140
351
  ['agent', 'Agent'],
1141
352
  ['reply', 'Channel'],
1142
- ['react', 'Channel'],
1143
- ['edit_message', 'Channel'],
1144
- ['activate_channel_bridge', 'Channel'],
1145
- ['inject_command', 'Channel'],
1146
- ['diagnostics', 'Setup'],
1147
- ['open_config', 'Setup'],
1148
- ['provider_status', 'Setup'],
1149
- ['channel_status', 'Setup'],
1150
- ['schedule_status', 'Setup'],
1151
- ['schedule_control', 'Setup'],
1152
- ['reload_config', 'Setup'],
1153
353
  ['list_mcp_resources', 'Setup'],
1154
354
  ['list_mcp_resource_templates', 'Setup'],
1155
355
  ['cwd', 'Setup'],
1156
356
  ['request_user_input', 'Setup'],
1157
357
  ['update_plan', 'Setup'],
1158
- ['trigger_schedule', 'Schedule'],
1159
358
  ['skill', 'Skill'],
1160
359
  ['skill_execute', 'Skill'],
1161
360
  ['skill_view', 'Skill'],
@@ -1285,8 +484,6 @@ export function toolWorkUnit(name, args = {}, category = '') {
1285
484
  case 'web_fetch':
1286
485
  case 'fetch':
1287
486
  return unitDescriptor('Web Research', { count: queryCount(a, 'url', 'urls', 'uri', 'uris') || 1, active: 'Fetching', done: 'Fetched', noun: 'URL', pluralNoun: 'URLs' });
1288
- case 'download_attachment':
1289
- return unitDescriptor('Web Research', { count: queryCount(a, 'url', 'urls', 'filename', 'name') || 1, active: 'Downloading', done: 'Downloaded', noun: 'attachment' });
1290
487
  case 'recall':
1291
488
  case 'recall_memory':
1292
489
  case 'search_memories':
@@ -1317,16 +514,6 @@ export function toolWorkUnit(name, args = {}, category = '') {
1317
514
  return unitDescriptor('Skill', { count: queryCount(a, 'name', 'skill', 'skill_name', 'query', 'q') || 1, noun: 'skill' });
1318
515
  case 'reply':
1319
516
  return unitDescriptor('Channel', { count: queryCount(a, 'messages', 'messageId', 'text') || 1, noun: 'message' });
1320
- case 'react':
1321
- return unitDescriptor('Channel', { count: queryCount(a, 'emoji', 'messageId') || 1, active: 'Reacting', done: 'Reacted', noun: 'reaction' });
1322
- case 'edit_message':
1323
- return unitDescriptor('Channel', { count: queryCount(a, 'messageId', 'text') || 1, active: 'Editing', done: 'Edited', noun: 'message' });
1324
- case 'activate_channel_bridge':
1325
- return unitDescriptor('Channel', { active: 'Toggling', done: 'Toggled', noun: 'channel bridge' });
1326
- case 'inject_command':
1327
- return unitDescriptor('Channel', { count: queryCount(a, 'command', 'text', 'name') || 1, active: 'Injecting', done: 'Injected', noun: 'command' });
1328
- case 'trigger_schedule':
1329
- return unitDescriptor('Schedule', { count: queryCount(a, 'name', 'id') || 1, noun: 'schedule' });
1330
517
  case 'code_graph': {
1331
518
  const mode = String(a.mode || a.action || '').toLowerCase();
1332
519
  const searching = mode === 'search' || mode === 'find_symbol' || mode === 'references' || mode === 'callers' || mode === 'callees';
@@ -1341,20 +528,6 @@ export function toolWorkUnit(name, args = {}, category = '') {
1341
528
  return unitDescriptor('Setup', { active: 'Asking', done: 'Asked', noun: 'user' });
1342
529
  case 'update_plan':
1343
530
  return unitDescriptor('Setup', { active: 'Updating', done: 'Updated', noun: 'plan' });
1344
- case 'diagnostics':
1345
- return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'diagnostic' });
1346
- case 'open_config':
1347
- return unitDescriptor('Setup', { active: 'Opening', done: 'Opened', noun: 'config' });
1348
- case 'provider_status':
1349
- return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'provider' });
1350
- case 'channel_status':
1351
- return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'channel' });
1352
- case 'schedule_status':
1353
- return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'schedule' });
1354
- case 'schedule_control':
1355
- return unitDescriptor('Setup', { active: 'Updating', done: 'Updated', noun: 'schedule' });
1356
- case 'reload_config':
1357
- return unitDescriptor('Setup', { active: 'Reloading', done: 'Reloaded', noun: 'config' });
1358
531
  case 'list_mcp_resources':
1359
532
  return unitDescriptor('Setup', { active: 'Listing', done: 'Listed', noun: 'MCP resource' });
1360
533
  case 'list_mcp_resource_templates':
@@ -1504,9 +677,18 @@ export function formatAggregateDetail(summaries) {
1504
677
  match = /^(?:Found\s+)?(\d+)\s+([a-z]+)$/i.exec(text);
1505
678
  if (match) {
1506
679
  const nounRaw = match[2].toLowerCase();
1507
- const singular = nounRaw.endsWith('ies') ? `${nounRaw.slice(0, -3)}y` : nounRaw.endsWith('s') ? nounRaw.slice(0, -1) : nounRaw;
1508
- const plural = nounRaw.endsWith('s') ? nounRaw : `${nounRaw}s`;
1509
- const key = `found_${plural}`;
680
+ // Normalize to a canonical singular so singular/plural variants of the
681
+ // SAME noun merge into one metric. Previously "48 matches" keyed as
682
+ // found_matches while "1 match" keyed as found_matchs (naive +s), so the
683
+ // detail row showed "48 matches, 1 match" instead of "49 matches".
684
+ const singular = nounRaw.endsWith('ies') ? `${nounRaw.slice(0, -3)}y`
685
+ : /(?:ch|sh|x|z|s)es$/.test(nounRaw) ? nounRaw.slice(0, -2)
686
+ : nounRaw.endsWith('s') ? nounRaw.slice(0, -1)
687
+ : nounRaw;
688
+ const plural = singular.endsWith('y') ? `${singular.slice(0, -1)}ies`
689
+ : /(?:ch|sh|x|z|s)$/.test(singular) ? `${singular}es`
690
+ : `${singular}s`;
691
+ const key = `found_${singular}`;
1510
692
  const metric = addMetric(key, { count: 0, singular, plural, render: (m) => `${m.count} ${pluralize(m.count, m.singular, m.plural)}` });
1511
693
  metric.count += Number(match[1]);
1512
694
  continue;