mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  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/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,399 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- formatExpandedResult,
5
- wrapExpandedResultLines,
6
- expandedResultBodyWidth,
7
- inferLangFamily,
8
- stripUnderlineAnsi,
9
- linkifyUrls,
10
- tryFormatJson,
11
- } from './tool-output-format.mjs';
12
- import stringWidth from 'string-width';
13
- import { displayWidthWith } from '../display-width.mjs';
14
-
15
- const stripAnsi = (s) => String(s).replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\]8;;[^\x07]*\x07/g, '');
16
-
17
- function withRenderLineCap(cap, fn) {
18
- const prev = process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES;
19
- const legacy = process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
20
- process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES = String(cap);
21
- delete process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
22
- try {
23
- return fn();
24
- } finally {
25
- if (prev === undefined) delete process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES;
26
- else process.env.MIXDOG_TUI_TOOL_OUTPUT_MAX_RENDER_LINES = prev;
27
- if (legacy === undefined) delete process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS;
28
- else process.env.MIXDOG_TUI_EXPANDED_MAX_ROWS = legacy;
29
- }
30
- }
31
-
32
- test('inferLangFamily maps known extensions, null otherwise', () => {
33
- assert.equal(inferLangFamily('src/x.mjs'), 'js');
34
- assert.equal(inferLangFamily('a.py'), 'py');
35
- assert.equal(inferLangFamily('x.json'), 'json');
36
- assert.equal(inferLangFamily('run.sh'), 'sh');
37
- assert.equal(inferLangFamily('readme'), null);
38
- assert.equal(inferLangFamily(''), null);
39
- });
40
-
41
- test('read line-number gutter is split into a dim column and body is highlighted', () => {
42
- const read = '900\u2192function f() {\n901\u2192 return 1;\n902\u2192}';
43
- const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
44
- assert.equal(out.length, 3);
45
- // gutter text survives, body keyword colored (output carries SGR escapes).
46
- assert.ok(stripAnsi(out[0]).startsWith('900\u2192function f() {'));
47
- assert.ok(out[0].includes('\x1b['), 'first line carries color escapes');
48
- });
49
-
50
- test('non-ANSI tool output expands tabs so width math matches the terminal', () => {
51
- // A raw \t is ZERO-width to string-width, so wrapExpandedResultLines would
52
- // under-wrap a tab-bearing line and the terminal-expanded tab would bleed
53
- // the row through the bottom prompt box on scroll. formatExpandedResult must
54
- // normalize tabs to spaces for non-ANSI output.
55
- const out = formatExpandedResult('\tindented line', { pathArg: 'a.txt' });
56
- const plain = stripAnsi(out.join('\n'));
57
- assert.ok(!plain.includes('\t'), 'no raw tab survives non-ANSI tool output');
58
- const body = plain.split('\n').find((l) => l.includes('indented line'));
59
- assert.ok(body, 'body line present');
60
- assert.equal(stringWidth(body), body.length, 'visible width equals char length');
61
- });
62
-
63
- test('shell ANSI output keeps raw control bytes (not normalized)', () => {
64
- // ANSI/shell output legitimately carries control bytes in escape sequences;
65
- // normalization must be skipped so those survive verbatim.
66
- const sh = '\x1b[31mred\x1b[0m';
67
- const out = formatExpandedResult(sh, { isShell: true });
68
- assert.ok(out.join('').includes('\x1b['), 'escape sequences preserved');
69
- });
70
-
71
- test('colored shell output still expands visible tabs (ANSI-aware normalize)', () => {
72
- // A colored line with a raw \t in its VISIBLE text: the SGR escapes must be
73
- // preserved verbatim while the tab is expanded, so the visible width matches
74
- // the terminal and the row cannot bleed through the prompt box on scroll.
75
- const sh = '\x1b[32m\tgreen tabbed\x1b[0m';
76
- const out = formatExpandedResult(sh, { isShell: true });
77
- const joined = out.join('\n');
78
- assert.ok(joined.includes('\x1b['), 'color escapes preserved');
79
- assert.ok(!joined.includes('\t'), 'visible tab expanded even in colored output');
80
- const body = stripAnsi(joined).split('\n').find((l) => l.includes('green tabbed'));
81
- assert.ok(body, 'body line present');
82
- assert.equal(stringWidth(body), body.length, 'visible width equals char length');
83
- });
84
-
85
- test('block syntax highlight skips tokenizer for over-long lines (index aligned)', () => {
86
- // MAX_HIGHLIGHT_LINE_CHARS is 2000 in tool-output-format.mjs
87
- const giant = 'x'.repeat(2001);
88
- const read = `1\u2192const a = 1;\n2\u2192${giant}\n3\u2192const b = 2;`;
89
- const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
90
- assert.equal(out.length, 3);
91
- assert.ok(out[0].includes('\x1b['), 'short line before giant is highlighted');
92
- assert.ok(out[2].includes('\x1b['), 'short line after giant is highlighted');
93
- const plainMid = stripAnsi(out[1]);
94
- assert.ok(plainMid.startsWith('2\u2192'), 'gutter preserved on giant line');
95
- assert.ok(plainMid.endsWith(giant), 'giant body preserved');
96
- const hljsSpans = (out[1].match(/\x1b\[[0-9;]*m/g) || []).length;
97
- const shortSpans = (out[0].match(/\x1b\[[0-9;]*m/g) || []).length;
98
- assert.ok(hljsSpans <= shortSpans + 2, 'giant line not fully tokenized like normal code');
99
- });
100
-
101
- test('grep file:line: gutter is split too', () => {
102
- const grep = 'src/a.mjs:5581: value: foo,';
103
- const out = formatExpandedResult(grep, { pathArg: 'src/a.mjs' });
104
- assert.equal(out.length, 1);
105
- assert.ok(stripAnsi(out[0]).startsWith('src/a.mjs:5581:'));
106
- });
107
-
108
- test('grep Windows absolute path:line: gutter is split', () => {
109
- const grep = 'C:\\Project\\mixdog\\src\\App.jsx:12: const x = 1;';
110
- const out = formatExpandedResult(grep, { pathArg: 'src/App.jsx' });
111
- assert.equal(out.length, 1);
112
- const plain = stripAnsi(out[0]);
113
- assert.ok(plain.startsWith('C:\\Project\\mixdog\\src\\App.jsx:12:'));
114
- assert.ok(plain.includes('const x = 1;'), 'body preserved after gutter');
115
- assert.ok(out[0].includes('\x1b['), 'body carries highlight escapes');
116
- });
117
-
118
- test('grep bare line-number gutter still works', () => {
119
- const grep = '12: return null;';
120
- const out = formatExpandedResult(grep, { pathArg: 'a.mjs' });
121
- assert.equal(out.length, 1);
122
- assert.ok(stripAnsi(out[0]).startsWith('12:'));
123
- });
124
-
125
- test('https URLs are not treated as grep path:line: gutters', () => {
126
- const line = 'see https://example.com:443/path for docs';
127
- const out = formatExpandedResult(line, {});
128
- assert.equal(out.length, 1);
129
- assert.equal(stripAnsi(out[0]), line);
130
- });
131
-
132
- test('JSON result is auto pretty-printed (size-capped, precision-safe)', () => {
133
- const out = formatExpandedResult('{"a":1,"b":[2,3]}', {});
134
- assert.ok(out.length > 1, 'single-line JSON expands to multiple rows');
135
- assert.equal(stripAnsi(out[0]), '{');
136
- // precision loss → keep original line
137
- const big = '{"id":123456789012345678901234567890}';
138
- assert.equal(tryFormatJson(big), big);
139
- });
140
-
141
- test('JSON with markdown-like string values stays JSON (no markdown lexer)', () => {
142
- const raw = '["**literal**","`code`"]';
143
- const out = formatExpandedResult(raw, {});
144
- const visible = out.map(stripAnsi).join('\n');
145
- assert.ok(visible.includes('**literal**'), 'array string keeps ** delimiters');
146
- assert.ok(visible.includes('`code`'), 'array string keeps backticks');
147
- });
148
-
149
- test('JSON object with markdown-like values is not rendered as markdown', () => {
150
- const raw = '{"msg":"**bold**","x":"`y`"}';
151
- const out = formatExpandedResult(raw, {});
152
- const visible = out.map(stripAnsi).join('\n');
153
- assert.ok(visible.includes('**bold**'));
154
- assert.ok(visible.includes('`y`'));
155
- });
156
-
157
- test('shell output keeps color ANSI but strips underline and never highlights', () => {
158
- const sh = 'PASS \x1b[32mok\x1b[0m\n\x1b[4mfile.js\x1b[24m done';
159
- const out = formatExpandedResult(sh, { isShell: true });
160
- assert.ok(out[0].includes('\x1b[32m'), 'color ANSI preserved');
161
- assert.ok(!out[1].includes('\x1b[4m'), 'underline stripped');
162
- assert.equal(stripAnsi(out[1]), 'file.js done');
163
- });
164
-
165
- test('unified diff is colored by line class', () => {
166
- const diff = 'diff --git a/x b/x\n@@ -1 +1 @@\n-old\n+new\n ctx';
167
- const out = formatExpandedResult(diff, {});
168
- assert.equal(out.length, 5);
169
- assert.ok(out.every((l) => l.includes('\x1b[')), 'every diff line colored');
170
- });
171
-
172
- test('oversize output is capped with a truncation marker', () => {
173
- const many = Array.from({ length: 5000 }, (_, i) => `line${i}`).join('\n');
174
- const out = formatExpandedResult(many, {});
175
- assert.ok(out.length <= 4001, 'logical cap keeps at most MAX_EXPANDED_LINES plus marker');
176
- assert.ok(/truncated/.test(stripAnsi(out[out.length - 1])));
177
-
178
- const sixHundred = Array.from({ length: 600 }, (_, i) => `row${i}`).join('\n');
179
- const out600 = formatExpandedResult(sixHundred, {});
180
- assert.equal(out600.length, 600, 'normal expanded output is not truncated at 80 logical lines');
181
-
182
- const huge = 'x'.repeat(300 * 1024);
183
- const out2 = formatExpandedResult(huge, {});
184
- assert.ok(/truncated/.test(stripAnsi(out2[out2.length - 1])));
185
- });
186
-
187
- test('stripUnderlineAnsi removes only underline SGR', () => {
188
- assert.equal(stripUnderlineAnsi('a\x1b[4mu\x1b[24mb'), 'aub');
189
- assert.equal(stripUnderlineAnsi('a\x1b[32mg\x1b[0mb'), 'a\x1b[32mg\x1b[0mb');
190
- assert.equal(stripUnderlineAnsi('x\x1b[4;31my'), 'x\x1b[31my');
191
- assert.equal(stripUnderlineAnsi('x\x1b[42my'), 'x\x1b[42my');
192
- assert.equal(stripUnderlineAnsi('x\x1b[31;4my'), 'x\x1b[31my');
193
- });
194
-
195
- test('linkifyUrls wraps bare URLs in OSC 8', () => {
196
- const out = linkifyUrls('see https://x.com/y here');
197
- assert.ok(out.includes('\x1b]8;;https://x.com/y\x07'));
198
- assert.equal(linkifyUrls('no url here'), 'no url here');
199
- });
200
-
201
- test('linkifyUrls keeps ANSI out of OSC 8 URL targets', () => {
202
- const colored = 'go \x1b[31mhttps://example.com/a\x1b[0m end';
203
- const out = linkifyUrls(colored);
204
- const m = /\x1b]8;;([^\x07]+)\x07/.exec(out);
205
- assert.ok(m, 'OSC 8 opener present');
206
- assert.equal(m[1], 'https://example.com/a');
207
- assert.ok(!m[1].includes('\x1b'), 'target URL has no escape bytes');
208
- });
209
-
210
- function osc8UrlTargets(text) {
211
- const targets = [];
212
- const bel = /\x1b]8;;([^\x07]+)\x07/g;
213
- const st = /\x1b]8;;((?:[^\x1b]|\x1b(?![\\]))+)\x1b\\/g;
214
- let m;
215
- while ((m = bel.exec(text)) !== null) targets.push(m[1]);
216
- while ((m = st.exec(text)) !== null) targets.push(m[1]);
217
- return targets;
218
- }
219
-
220
- test('linkifyUrls does not re-linkify existing BEL OSC 8 hyperlinks', () => {
221
- const belLink = '\x1b]8;;https://a.test\x07label\x1b]8;;\x07';
222
- const out = linkifyUrls(belLink);
223
- assert.equal(out, belLink);
224
- assert.equal((out.match(/\x1b]8;;/g) || []).length, 2, 'single open/close pair');
225
- for (const target of osc8UrlTargets(out)) {
226
- assert.ok(!target.includes('\x1b'), 'target has no escape bytes');
227
- assert.ok(!target.includes('\x1b]8;;'), 'no nested OSC in target');
228
- }
229
- });
230
-
231
- test('linkifyUrls does not re-linkify existing ST OSC 8 hyperlinks', () => {
232
- const stLink = '\x1b]8;;https://a.test\x1b\\label\x1b]8;;\x1b\\';
233
- const out = linkifyUrls(stLink);
234
- assert.equal(out, stLink);
235
- assert.equal((out.match(/\x1b]8;;/g) || []).length, 2, 'single open/close pair');
236
- for (const target of osc8UrlTargets(out)) {
237
- assert.ok(!target.includes('\x1b'), 'target has no escape bytes');
238
- assert.ok(!target.includes('\x1b]8;;'), 'no nested OSC in target');
239
- }
240
- });
241
-
242
- test('empty input yields no lines', () => {
243
- assert.deepEqual(formatExpandedResult('', {}), []);
244
- });
245
-
246
- test('wrapExpandedResultLines splits long logical rows to body width', () => {
247
- const columns = 48;
248
- const maxW = expandedResultBodyWidth(columns);
249
- const longBody = 'alpha_beta_gamma_delta '.repeat(6).trimEnd();
250
- const logical = formatExpandedResult(`900\u2192${longBody}`, { pathArg: 'a.mjs' });
251
- assert.equal(logical.length, 1);
252
- const physical = wrapExpandedResultLines(logical, columns);
253
- assert.ok(physical.length > 1, 'long read line becomes multiple physical rows');
254
- for (const row of physical) {
255
- assert.ok(
256
- stringWidth(stripAnsi(row)) <= maxW,
257
- `row width ${stringWidth(stripAnsi(row))} exceeds budget ${maxW}`,
258
- );
259
- }
260
- for (let i = 1; i < physical.length; i++) {
261
- assert.ok(/^\s+/.test(stripAnsi(physical[i])), 'wrapped continuations are indented');
262
- }
263
- });
264
-
265
- test('expanded markdown prose renders headings and emphasis (not raw #/**)', () => {
266
- const md = '# Title\n\nSome **bold** and `inline`.\n';
267
- const out = formatExpandedResult(md, {});
268
- const visible = out.map(stripAnsi).join('\n');
269
- assert.ok(!visible.includes('# Title'), 'ATX heading marker not shown raw');
270
- assert.ok(visible.includes('Title'), 'heading text preserved');
271
- assert.ok(visible.includes('bold'), 'strong text preserved');
272
- assert.ok(!visible.includes('**'), 'emphasis delimiters stripped');
273
- assert.ok(out.some((l) => l.includes('\x1b[')), 'markdown path applies theme colors');
274
- });
275
-
276
- test('expanded fenced code in markdown path is highlighted', () => {
277
- const md = '```js\nconst x = 1;\n```\n';
278
- const out = formatExpandedResult(md, {});
279
- const visible = out.map(stripAnsi).join('\n');
280
- assert.ok(visible.includes('const x = 1;'), 'fence body preserved');
281
- assert.ok(out.some((l) => l.includes('\x1b[')), 'fenced block carries ANSI');
282
- });
283
-
284
- test('read gutter lines skip markdown mode and keep syntax highlight', () => {
285
- const read = '1\u2192# not a heading\n2\u2192const y = 2;\n';
286
- const out = formatExpandedResult(read, { pathArg: 'a.mjs' });
287
- const visible = out.map(stripAnsi);
288
- assert.ok(visible.some((l) => l.includes('1\u2192# not a heading')), 'source # kept on read lines');
289
- assert.ok(visible.some((l) => l.includes('const y = 2;')), 'read body preserved');
290
- assert.ok(out.some((l) => stripAnsi(l).includes('const y = 2;') && l.includes('\x1b[')), 'code line still highlighted');
291
- });
292
-
293
- test('shell physical cap keeps newest rows across logical lines', () => {
294
- withRenderLineCap(5, () => {
295
- const long = 'W'.repeat(240);
296
- const logical = formatExpandedResult(`${long}\nNEWEST_TAIL_LINE`, { isShell: true });
297
- const physical = wrapExpandedResultLines(logical, 32, { isShell: true });
298
- const visible = physical.map(stripAnsi);
299
- assert.ok(visible.some((l) => l.includes('NEWEST_TAIL_LINE')), 'newest logical line survives rolling cap');
300
- assert.ok(physical.length <= 5);
301
- assert.ok(visible.some((l) => /omitted above/i.test(l)));
302
- });
303
- });
304
-
305
- test('exact physical cap row count is not truncated', () => {
306
- withRenderLineCap(10, () => {
307
- const logical = Array.from({ length: 10 }, (_, i) => `row-${i}`).map((l) => formatExpandedResult(l, { isShell: true })[0]);
308
- const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
309
- assert.equal(physical.length, 10);
310
- assert.ok(!physical.some((l) => /omitted above/i.test(stripAnsi(l))));
311
- });
312
- });
313
-
314
- test('physical cap of 1 never returns more than one row', () => {
315
- withRenderLineCap(1, () => {
316
- const physical = wrapExpandedResultLines(formatExpandedResult('a\nb\nc', { isShell: true }), 80, { isShell: true });
317
- assert.ok(physical.length <= 1);
318
- const physicalNonShell = wrapExpandedResultLines(formatExpandedResult('a\nb\nc', {}), 80, { isShell: false });
319
- assert.ok(physicalNonShell.length <= 1);
320
- });
321
- });
322
-
323
- test('shell physical cap omitted count includes marker slot', () => {
324
- withRenderLineCap(5, () => {
325
- const logical = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5'].map((l) => formatExpandedResult(l, { isShell: true })[0]);
326
- const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
327
- const visible = physical.map(stripAnsi);
328
- assert.equal(physical.length, 5);
329
- assert.match(visible[0], /2 lines omitted above/);
330
- assert.deepEqual(visible.slice(1), ['r2', 'r3', 'r4', 'r5']);
331
- });
332
- withRenderLineCap(1, () => {
333
- const logical = Array.from({ length: 6 }, (_, i) => `row${i}`).map((l) => formatExpandedResult(l, { isShell: true })[0]);
334
- const physical = wrapExpandedResultLines(logical, 80, { isShell: true });
335
- assert.equal(physical.length, 1);
336
- assert.match(stripAnsi(physical[0]), /6 lines omitted above/);
337
- });
338
- });
339
-
340
- test('shell logical truncation marker precedes retained tail', () => {
341
- const many = Array.from({ length: 5000 }, (_, i) => `line${i}`).join('\n');
342
- const out = formatExpandedResult(many, { isShell: true });
343
- const visible = out.map(stripAnsi);
344
- assert.ok(visible.length <= 4001);
345
- assert.ok(/omitted above/i.test(visible[0]), 'shell marker is first row');
346
- assert.ok(visible[visible.length - 1].includes('line4999'), 'newest logical line is last');
347
- assert.ok(!visible[visible.length - 1].includes('re-read a narrower range'));
348
- });
349
-
350
- test('wide policy OFF: displayWidth is byte-for-byte identical to string-width for arrow lines', () => {
351
- // Guards the "non-WT terminals unchanged" invariant: with the policy OFF
352
- // (default in this test process — no WT_SESSION, no override), the arrow is
353
- // still 1 cell, so wrap output must be identical to the string-width world.
354
- const arrowLine = ' 12\u2192const x = 1; \u2190 done';
355
- assert.equal(displayWidthWith(arrowLine, false), stringWidth(arrowLine),
356
- 'policy OFF must equal plain string-width');
357
- const logical = formatExpandedResult(arrowLine, { pathArg: 'a.mjs' });
358
- const physical = wrapExpandedResultLines(logical, 40);
359
- const maxW = expandedResultBodyWidth(40);
360
- for (const row of physical) {
361
- assert.ok(stringWidth(stripAnsi(row)) <= maxW,
362
- `row width ${stringWidth(stripAnsi(row))} exceeds ${maxW} (policy OFF)`);
363
- }
364
- });
365
-
366
- test('wide policy ON (forced): every emitted arrow row fits the display-width budget', async () => {
367
- // The policy is resolved ONCE at module load, so forcing it requires a fresh
368
- // process. Spawn a child with MIXDOG_TUI_AMBIGUOUS_WIDE=1 and assert that
369
- // every wrapped read row (containing U+2192) measures <= expandedResultBodyWidth
370
- // under the SAME wide policy the terminal will use. Without the clamp,
371
- // string-width accepts arrow rows as fitting while the terminal renders them
372
- // one cell wider, bleeding into the prompt box.
373
- const { execFileSync } = await import('node:child_process');
374
- const { fileURLToPath } = await import('node:url');
375
- const modUrl = new URL('./tool-output-format.mjs', import.meta.url).href;
376
- const dwUrl = new URL('../display-width.mjs', import.meta.url).href;
377
- const script = `
378
- import { formatExpandedResult, wrapExpandedResultLines, expandedResultBodyWidth } from ${JSON.stringify(modUrl)};
379
- import { displayWidth, AMBIGUOUS_WIDE } from ${JSON.stringify(dwUrl)};
380
- const strip = (s) => String(s).replace(/\\x1b\\[[0-9;]*m/g, '').replace(/\\x1b\\]8;;[^\\x07]*\\x07/g, '');
381
- if (AMBIGUOUS_WIDE !== true) { console.log('POLICY_OFF'); process.exit(2); }
382
- const columns = 30;
383
- // Many arrows so a naive string-width fit accepts an over-wide row.
384
- const body = 'x'.repeat(24) + ' ' + '\\u2192'.repeat(12) + ' tail';
385
- const logical = formatExpandedResult('42\\u2192' + body, { pathArg: 'a.mjs' });
386
- const physical = wrapExpandedResultLines(logical, columns);
387
- const maxW = expandedResultBodyWidth(columns);
388
- let bad = 0;
389
- for (const row of physical) {
390
- if (displayWidth(strip(row)) > maxW) bad++;
391
- }
392
- console.log(bad === 0 ? 'OK ' + physical.length : 'BAD ' + bad);
393
- `;
394
- const out = execFileSync(process.execPath, ['--input-type=module', '-e', script], {
395
- env: { ...process.env, MIXDOG_TUI_AMBIGUOUS_WIDE: '1', WT_SESSION: '' },
396
- encoding: 'utf8',
397
- }).trim();
398
- assert.ok(out.startsWith('OK'), `expected all rows within budget, got: ${out}`);
399
- });
@@ -1,35 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import stringWidth from 'string-width';
4
- import { displayWidthWith, isProblemCodePoint } from './display-width.mjs';
5
-
6
- // Policy ON: the two problem ranges become 2 cells; everything else unchanged.
7
- test('policy ON widens circled digits and arrows only', () => {
8
- assert.equal(displayWidthWith('\u2460', true), 2); // ① circled digit
9
- assert.equal(displayWidthWith('\u2192', true), 2); // → rightwards arrow
10
- assert.equal(displayWidthWith('a', true), 1); // ASCII unchanged
11
- // Box-drawing / figures must NOT be widened by the policy.
12
- assert.equal(displayWidthWith('\u2514', true), stringWidth('\u2514')); // └
13
- assert.equal(displayWidthWith('\u2502', true), stringWidth('\u2502')); // │
14
- assert.equal(displayWidthWith('\u2500', true), stringWidth('\u2500')); // ─
15
- assert.equal(displayWidthWith('\u25cf', true), stringWidth('\u25cf')); // ●
16
- // Mixed string: base + one +1 per problem code point.
17
- assert.equal(displayWidthWith('a\u2460b\u2192', true), stringWidth('a\u2460b\u2192') + 2);
18
- });
19
-
20
- // Policy OFF: byte-for-byte identical to plain string-width.
21
- test('policy OFF equals string-width', () => {
22
- for (const s of ['\u2460', '\u2192', 'a', '\u2514', 'hello \u2192 world', '']) {
23
- assert.equal(displayWidthWith(s, false), stringWidth(s));
24
- }
25
- });
26
-
27
- test('problem range membership', () => {
28
- assert.equal(isProblemCodePoint(0x2460), true);
29
- assert.equal(isProblemCodePoint(0x24ff), true);
30
- assert.equal(isProblemCodePoint(0x2190), true);
31
- assert.equal(isProblemCodePoint(0x21ff), true);
32
- assert.equal(isProblemCodePoint(0x2500), false); // box-drawing
33
- assert.equal(isProblemCodePoint(0x245f), false); // just below range
34
- assert.equal(isProblemCodePoint(0x2200), false); // just above arrows
35
- });
@@ -1,115 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { resolveTuiRuntimeNotificationDelivery } from "./engine.mjs";
4
- import { toolCompletionMeta } from "../runtime/shared/tool-execution-contract.mjs";
5
-
6
- const completedBackgroundTask = [
7
- "background task",
8
- "task_id: task_agent_abc123",
9
- "surface: agent",
10
- "operation: run",
11
- "status: completed",
12
- "",
13
- "worker finished successfully",
14
- ].join("\n");
15
-
16
- const runningBackgroundTask = [
17
- "background task",
18
- "task_id: task_agent_abc123",
19
- "surface: agent",
20
- "operation: run",
21
- "status: running",
22
- ].join("\n");
23
-
24
- const statusOnlyCompletedBackgroundTask = [
25
- "background task",
26
- "task_id: task_agent_abc123",
27
- "surface: agent",
28
- "operation: run",
29
- "status: completed",
30
- ].join("\n");
31
-
32
- const agentTaskEnvelopeWithBody = [
33
- "agent task: task_agent_xyz",
34
- "status: completed",
35
- "",
36
- "done",
37
- ].join("\n");
38
-
39
- const nestedAgentTaskInBackgroundTask = [
40
- "background task",
41
- "task_id: task_agent_abc123",
42
- "surface: agent",
43
- "operation: run",
44
- "status: completed",
45
- "",
46
- "agent task: task_agent_nested",
47
- "status: completed",
48
- "",
49
- "nested worker result",
50
- ].join("\n");
51
-
52
- test("execution completion uses wrapper model content not raw enqueue text", () => {
53
- const meta = toolCompletionMeta({
54
- surface: "agent",
55
- id: "task_agent_abc123",
56
- status: "completed",
57
- context: { callerSessionId: "sess_owner" },
58
- });
59
- const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, completedBackgroundTask);
60
- assert.equal(delivery.action, "execution-ui");
61
- assert.equal(delivery.displayText, completedBackgroundTask);
62
- assert.ok(delivery.modelContent);
63
- assert.match(delivery.modelContent, /worker finished successfully/);
64
- assert.doesNotMatch(delivery.modelContent, /^background task\b/m);
65
- assert.notEqual(delivery.modelContent, completedBackgroundTask);
66
- });
67
-
68
- test("running execution notification is UI-only with empty model content", () => {
69
- const meta = toolCompletionMeta({
70
- surface: "agent",
71
- id: "task_agent_abc123",
72
- status: "running",
73
- context: { callerSessionId: "sess_owner" },
74
- });
75
- const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, runningBackgroundTask);
76
- assert.equal(delivery.action, "execution-ui");
77
- assert.equal(delivery.modelContent, "");
78
- });
79
-
80
- test("nested agent task body inside completed background task is not status-only", () => {
81
- const meta = toolCompletionMeta({
82
- surface: "agent",
83
- id: "task_agent_abc123",
84
- status: "completed",
85
- context: { callerSessionId: "sess_owner" },
86
- });
87
- const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, nestedAgentTaskInBackgroundTask);
88
- assert.equal(delivery.action, "execution-ui");
89
- assert.ok(String(delivery.modelContent || "").trim());
90
- assert.match(delivery.modelContent, /nested worker result/);
91
- });
92
-
93
- test("agent task envelope with blank-line body is not status-only", () => {
94
- const meta = toolCompletionMeta({
95
- surface: "agent",
96
- id: "task_agent_xyz",
97
- status: "completed",
98
- context: { callerSessionId: "sess_owner" },
99
- });
100
- const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, agentTaskEnvelopeWithBody);
101
- assert.equal(delivery.action, "execution-ui");
102
- assert.ok(String(delivery.modelContent || "").trim());
103
- });
104
-
105
- test("header-only completed background task is status-only", () => {
106
- const meta = toolCompletionMeta({
107
- surface: "agent",
108
- id: "task_agent_abc123",
109
- status: "completed",
110
- context: { callerSessionId: "sess_owner" },
111
- });
112
- const delivery = resolveTuiRuntimeNotificationDelivery({ meta }, statusOnlyCompletedBackgroundTask);
113
- assert.equal(delivery.action, "status-only");
114
- assert.equal(delivery.modelContent, "");
115
- });
@@ -1,75 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- toolAggregateDetailFallback,
5
- toolGroupedDisplayFallback,
6
- toolResultText,
7
- } from './engine.mjs';
8
-
9
- test('toolResultText prefers nested content over empty scalar fields', () => {
10
- assert.equal(
11
- toolResultText({ text: '', content: [{ type: 'text', text: 'nested body' }] }),
12
- 'nested body',
13
- );
14
- });
15
-
16
- test('toolResultText unwraps nested tool_result blocks', () => {
17
- assert.equal(
18
- toolResultText({
19
- type: 'tool_result',
20
- content: [{ type: 'tool_result', content: 'inner payload' }],
21
- }),
22
- 'inner payload',
23
- );
24
- });
25
-
26
- test('toolResultText preserves explicit empty tool_result clears', () => {
27
- assert.equal(toolResultText({ type: 'tool_result', content: '' }), '');
28
- });
29
-
30
- test('toolResultText preserves empty tool_result content arrays', () => {
31
- assert.equal(toolResultText({ type: 'tool_result', content: [] }), '');
32
- assert.notEqual(toolResultText({ type: 'tool_result', content: [] }), '{"type":"tool_result","content":[]}');
33
- });
34
-
35
- test('toolResultText preserves empty typed text parts inside tool_result', () => {
36
- assert.equal(
37
- toolResultText({ type: 'tool_result', content: [{ type: 'text', text: '' }] }),
38
- '',
39
- );
40
- });
41
-
42
- test('toolResultText preserves empty object-shaped tool_result inner content', () => {
43
- assert.equal(toolResultText({ type: 'tool_result', content: {} }), '');
44
- assert.equal(
45
- toolResultText({ type: 'tool_result', content: { type: 'text', text: '' } }),
46
- '',
47
- );
48
- });
49
-
50
- test('toolResultText still extracts plain strings and structured bodies', () => {
51
- assert.equal(toolResultText('plain body'), 'plain body');
52
- assert.equal(
53
- toolResultText([{ type: 'text', text: 'line one' }, { type: 'text', text: 'line two' }]),
54
- 'line one\nline two',
55
- );
56
- });
57
-
58
- test('toolResultText maps every top-level array element without using index as depth', () => {
59
- const parts = Array.from({ length: 14 }, (_, i) => ({ type: 'text', text: `line-${i}` }));
60
- const out = toolResultText(parts);
61
- assert.match(out, /^line-0\n/);
62
- assert.match(out, /\nline-13$/);
63
- assert.equal(out.split('\n').length, 14);
64
- });
65
-
66
- test('toolAggregateDetailFallback uses raw body when summary detail is empty', () => {
67
- assert.equal(toolAggregateDetailFallback('', 'file contents here'), 'file contents here');
68
- });
69
-
70
- test('toolGroupedDisplayFallback uses later extracted body when grouped text is empty', () => {
71
- assert.equal(toolGroupedDisplayFallback('', '', 'recovered body'), 'recovered body');
72
- assert.equal(toolGroupedDisplayFallback('group summary', '', 'later body'), 'group summary');
73
- });
74
-
75
-