mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -26,7 +26,7 @@ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
26
26
  - SEQUENTIAL within a single complex scope — split it into ordered steps
27
27
  rather than handing it off in one shot, with a build/test-green gate
28
28
  between steps.
29
- - Write briefs per the lead-tool brief contract (token-optimized labeled
29
+ - Write briefs per the Lead brief contract (token-optimized labeled
30
30
  fragments).
31
31
  - After spawning async agent(s), END THE TURN — do not poll, guess, or start
32
32
  dependent work until the completion notification resumes you. Then wait
@@ -29,7 +29,7 @@ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
29
29
  - SEQUENTIAL within a single complex scope — split it into ordered steps
30
30
  rather than handing it off in one shot, with a build/test-green gate
31
31
  between steps.
32
- - Write briefs per the lead-tool brief contract (token-optimized labeled
32
+ - Write briefs per the Lead brief contract (token-optimized labeled
33
33
  fragments).
34
34
  - After spawning an async agent, END THE TURN — do not poll, guess, or start
35
35
  dependent or subsequent work until the completion notification resumes
@@ -16,11 +16,21 @@
16
16
  * overrides and wins. OFF ⇒ identical to plain string-width.
17
17
  */
18
18
  import stringWidth from 'string-width';
19
+ // [mixdog fork] Grapheme segmenter for cluster-aware width math (e.g. `↔️` =
20
+ // U+2194 U+FE0F is one cluster, counted once). Kept in sync with
21
+ // src/tui/display-width.mjs.
22
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
19
23
 
20
24
  function isProblemCodePoint(cp) {
21
25
  return (cp >= 0x2460 && cp <= 0x24ff) || (cp >= 0x2190 && cp <= 0x21ff);
22
26
  }
23
27
 
28
+ // [mixdog fork] Fast precheck for the problem ranges above. Lets the hot path
29
+ // bail before the per-grapheme segmenter loop when a string (the common
30
+ // ASCII/status-text case) contains no widenable glyph. Kept in sync with
31
+ // src/tui/display-width.mjs.
32
+ const PROBLEM_RE = /[\u2190-\u21ff\u2460-\u24ff]/;
33
+
24
34
  function resolveAmbiguousWidePolicy(env = process.env) {
25
35
  const override = env?.MIXDOG_TUI_AMBIGUOUS_WIDE;
26
36
  if (override === '1')
@@ -38,10 +48,16 @@ export function displayWidthWith(str, wide) {
38
48
  const base = stringWidth(s);
39
49
  if (!wide)
40
50
  return base;
51
+ // Fast bail: no problem glyph ⇒ segmenting can only return `base`.
52
+ if (!PROBLEM_RE.test(s))
53
+ return base;
41
54
  let extra = 0;
42
- for (const ch of s) {
43
- const cp = ch.codePointAt(0);
44
- if (isProblemCodePoint(cp) && stringWidth(ch) === 1)
55
+ // Widen per GRAPHEME CLUSTER, not per code point (kept in sync with
56
+ // src/tui/display-width.mjs): a clustered glyph like `↔️` (U+2194 U+FE0F)
57
+ // is counted once, and only when the whole cluster still measures 1 cell.
58
+ for (const { segment } of graphemeSegmenter.segment(s)) {
59
+ const cp = segment.codePointAt(0);
60
+ if (isProblemCodePoint(cp) && stringWidth(segment) === 1)
45
61
  extra += 1;
46
62
  }
47
63
  return base + extra;
@@ -86,16 +86,25 @@ const stripKittyQueryResponsesAndTrailingPartial = (buffer) => {
86
86
  // viewport, leaving stale copies of previous frames behind (#969). Keep the
87
87
  // pre-7.0 behavior of fully clearing between fullscreen frames there.
88
88
  const isWindowsConsole = process.platform === 'win32' || Boolean(process.env.WT_SESSION);
89
- const shouldClearTerminalForFrame = ({ isTty, viewportRows, previousOutputHeight, nextOutputHeight, isUnmounting, }) => {
89
+ const shouldClearTerminalForFrame = ({ isTty, viewportRows, previousViewportRows, previousOutputHeight, nextOutputHeight, isUnmounting, }) => {
90
90
  if (!isTty) {
91
91
  return false;
92
92
  }
93
+ const priorViewportRows = previousViewportRows ?? viewportRows;
93
94
  const hadPreviousFrame = previousOutputHeight > 0;
94
- const wasFullscreen = previousOutputHeight >= viewportRows;
95
- const wasOverflowing = previousOutputHeight > viewportRows;
95
+ const wasFullscreen = previousOutputHeight >= priorViewportRows;
96
+ const wasOverflowing = previousOutputHeight > priorViewportRows;
96
97
  const isOverflowing = nextOutputHeight > viewportRows;
97
98
  const isFullscreen = nextOutputHeight >= viewportRows;
98
99
  const isLeavingFullscreen = wasFullscreen && nextOutputHeight < viewportRows;
100
+ // [mixdog fork] A frame that SHRINKS by more than a line while at/over the
101
+ // viewport can leave stale wrapped rows below the new content on ANY
102
+ // terminal (not just Windows) — the incremental erase only clears the exact
103
+ // delta and a hardware wrap can desync it by a cell. Force a full clear on a
104
+ // meaningful height shrink near/at the viewport edge, cross-platform.
105
+ const isShrinkingAtViewport = hadPreviousFrame &&
106
+ nextOutputHeight < previousOutputHeight &&
107
+ (wasFullscreen || isFullscreen || wasOverflowing || isOverflowing);
99
108
  const shouldClearOnUnmount = isUnmounting && wasFullscreen;
100
109
  if (isWindowsConsole && (wasFullscreen || isFullscreen)) {
101
110
  return true;
@@ -106,6 +115,8 @@ const shouldClearTerminalForFrame = ({ isTty, viewportRows, previousOutputHeight
106
115
  (isOverflowing && hadPreviousFrame) ||
107
116
  // Clear when shrinking from fullscreen to non-fullscreen output.
108
117
  isLeavingFullscreen ||
118
+ // Clear on any height shrink at/over the viewport (cross-platform).
119
+ isShrinkingAtViewport ||
109
120
  // Preserve legacy unmount behavior for fullscreen frames: final teardown
110
121
  // render should clear once to avoid leaving a scrolled viewport state.
111
122
  shouldClearOnUnmount);
@@ -155,6 +166,7 @@ export default class Ink {
155
166
  lastOutputToRender;
156
167
  lastOutputHeight;
157
168
  lastTerminalWidth;
169
+ lastTerminalHeight;
158
170
  container;
159
171
  rootNode;
160
172
  // This variable is used only in debug mode to store full static output
@@ -165,6 +177,9 @@ export default class Ink {
165
177
  beforeExitHandler;
166
178
  restoreConsole;
167
179
  unsubscribeResize;
180
+ // [mixdog fork] Deferred repaint safety-net timer for resized(); cancelled
181
+ // by onRender when a real (App-driven) render lands first.
182
+ resizeRepaintTimer;
168
183
  throttledOnRender;
169
184
  hasPendingThrottledRender = false;
170
185
  kittyProtocolEnabled = false;
@@ -259,6 +274,7 @@ export default class Ink {
259
274
  this.lastOutputToRender = '';
260
275
  this.lastOutputHeight = 0;
261
276
  this.lastTerminalWidth = getWindowSize(this.options.stdout).columns;
277
+ this.lastTerminalHeight = getWindowSize(this.options.stdout).rows;
262
278
  // This variable is used only in debug mode to store full static output
263
279
  // so that it's rerendered every time, not just new static parts, like in non-debug mode
264
280
  this.fullStaticOutput = '';
@@ -293,16 +309,56 @@ export default class Ink {
293
309
  }
294
310
  resized = () => {
295
311
  const currentWidth = getWindowSize(this.options.stdout).columns;
296
- if (currentWidth < this.lastTerminalWidth) {
297
- // We clear the screen when decreasing terminal width to prevent duplicate overlapping re-renders.
312
+ const currentHeight = getWindowSize(this.options.stdout).rows;
313
+ if (currentWidth !== this.lastTerminalWidth || currentHeight !== this.lastTerminalHeight) {
314
+ // [mixdog fork] On viewport size change, full-clear the alt screen to
315
+ // drop the stale frame, but DO NOT immediately re-render with the old
316
+ // React tree at the new size. Ink's own onRender() here fires before
317
+ // App's debounced resizeState state update, so it paints a frame laid
318
+ // out for the PREVIOUS dimensions into the NEW viewport — that is the
319
+ // 2-frame jump/re-settle on resize. Clearing (no re-render) leaves a
320
+ // blank screen for the sub-frame until App's setResizeState commit
321
+ // drives a single, correctly-sized App-owned render. Layout listeners
322
+ // still fire so consumers observe the new size synchronously.
298
323
  this.log.clear();
324
+ this.log.reset?.();
325
+ // this.log.clear() only erases the PRIOR log frame's row span. On a
326
+ // fullscreen/alt-screen grow the newly exposed rows (below the old
327
+ // frame) are never touched by that erase, so stale scrollback can
328
+ // remain visible until the next full repaint — which, with the
329
+ // immediate onRender suppressed, may be shorter or delayed. Issue a
330
+ // true whole-screen clear so the viewport is uniformly blank until
331
+ // App's correctly-sized render lands.
332
+ this.writeBestEffort(this.options.stdout, ansiEscapes.clearTerminal);
299
333
  this.lastOutput = '';
300
334
  this.lastOutputToRender = '';
335
+ this.lastOutputHeight = 0;
336
+ this.calculateLayout();
337
+ dom.emitLayoutListeners(this.rootNode);
338
+ this.lastTerminalWidth = currentWidth;
339
+ this.lastTerminalHeight = currentHeight;
340
+ // Safety net: the early return delegates the repaint to App's stdout
341
+ // 'resize' listener (debounced setResizeState). If that render never
342
+ // arrives (listener not mounted yet, batched-away no-op, consumer
343
+ // without a resize handler), the screen would sit blank. Schedule a
344
+ // deferred onRender() that repaints with whatever tree is current;
345
+ // it is cancelled below whenever a real render happens first, so in
346
+ // the normal path it never fires and the immediate render stays
347
+ // suppressed.
348
+ if (this.resizeRepaintTimer) clearTimeout(this.resizeRepaintTimer);
349
+ this.resizeRepaintTimer = setTimeout(() => {
350
+ this.resizeRepaintTimer = undefined;
351
+ if (this.isUnmounted || this.isUnmounting) return;
352
+ this.onRender();
353
+ }, 200);
354
+ if (typeof this.resizeRepaintTimer.unref === 'function') this.resizeRepaintTimer.unref();
355
+ return;
301
356
  }
302
357
  this.calculateLayout();
303
358
  dom.emitLayoutListeners(this.rootNode);
304
359
  this.onRender();
305
360
  this.lastTerminalWidth = currentWidth;
361
+ this.lastTerminalHeight = currentHeight;
306
362
  };
307
363
  resolveExitPromise = () => { };
308
364
  rejectExitPromise = () => { };
@@ -508,6 +564,12 @@ export default class Ink {
508
564
  };
509
565
  onRender = () => {
510
566
  this.hasPendingThrottledRender = false;
567
+ // A real render is happening: cancel the resize safety-net timer so the
568
+ // deferred repaint never double-fires in the normal path.
569
+ if (this.resizeRepaintTimer) {
570
+ clearTimeout(this.resizeRepaintTimer);
571
+ this.resizeRepaintTimer = undefined;
572
+ }
511
573
  if (this.isUnmounted) {
512
574
  return;
513
575
  }
@@ -737,6 +799,12 @@ export default class Ink {
737
799
  if (typeof this.unsubscribeResize === 'function') {
738
800
  this.unsubscribeResize();
739
801
  }
802
+ // [mixdog fork] Drop the resize safety-net timer so a deferred
803
+ // repaint cannot fire against a torn-down tree.
804
+ if (this.resizeRepaintTimer) {
805
+ clearTimeout(this.resizeRepaintTimer);
806
+ this.resizeRepaintTimer = undefined;
807
+ }
740
808
  // Cancel any in-progress auto-detection before checking protocol state
741
809
  if (this.cancelKittyDetection) {
742
810
  this.cancelKittyDetection();
@@ -957,21 +1025,50 @@ export default class Ink {
957
1025
  // Detect fullscreen: output fills or exceeds terminal height.
958
1026
  // Only apply when writing to a real TTY — piped output always gets trailing newlines.
959
1027
  const viewportRows = isTty ? getWindowSize(this.options.stdout).rows : 24;
1028
+ const viewportColumns = isTty ? getWindowSize(this.options.stdout).columns : this.lastTerminalWidth;
1029
+ // [mixdog fork] Capture the previous viewport rows (as tracked at the
1030
+ // end of the last frame) before we update it below, so wasFullscreen/
1031
+ // wasOverflowing classify the PREVIOUS frame against the viewport it
1032
+ // was actually rendered into, not the current (possibly resized) one.
1033
+ const previousViewportRows = this.lastTerminalHeight ?? viewportRows;
960
1034
  const isFullscreen = isTty && outputHeight >= viewportRows;
961
1035
  const outputToRender = isFullscreen ? output : output + '\n';
962
1036
  const shouldClearTerminal = shouldClearTerminalForFrame({
963
1037
  isTty,
964
1038
  viewportRows,
1039
+ previousViewportRows,
965
1040
  previousOutputHeight: this.lastOutputHeight,
966
1041
  nextOutputHeight: outputHeight,
967
1042
  isUnmounting: this.isUnmounting,
968
1043
  });
1044
+ const viewportUnchanged = viewportRows === previousViewportRows && viewportColumns === this.lastTerminalWidth;
1045
+ if (isTty) {
1046
+ this.lastTerminalHeight = viewportRows;
1047
+ }
1048
+ // [mixdog fork] Skip redundant full clears: on Windows every fullscreen
1049
+ // frame takes the clearTerminal path, so a commit with IDENTICAL output
1050
+ // (e.g. post-mount tuiReady flip with no layout change) still blanked and
1051
+ // rewrote the whole screen — visible as boot flicker. Nothing changed →
1052
+ // nothing to write. [mixdog fork] Also require the viewport (rows AND
1053
+ // columns) to be unchanged since the last frame: a resize can arrive
1054
+ // with identical output text but must still force a full clear to
1055
+ // avoid ghosting stale cells left outside the new viewport.
1056
+ if (shouldClearTerminal &&
1057
+ !this.isUnmounting &&
1058
+ output === this.lastOutput &&
1059
+ staticOutput === '' &&
1060
+ !this.log.isCursorDirty() &&
1061
+ viewportUnchanged) {
1062
+ return;
1063
+ }
969
1064
  if (shouldClearTerminal) {
970
1065
  const sync = this.shouldSync();
971
1066
  if (sync) {
972
1067
  this.options.stdout.write(bsu);
973
1068
  }
974
- this.options.stdout.write(ansiEscapes.clearTerminal + this.fullStaticOutput + output);
1069
+ // [mixdog fork] Reset SGR before erasing so stale style/background
1070
+ // from the previous frame doesn't leak onto the cleared cells.
1071
+ this.options.stdout.write('\u001B[0m' + ansiEscapes.clearTerminal + this.fullStaticOutput + output);
975
1072
  this.lastOutput = output;
976
1073
  this.lastOutputToRender = outputToRender;
977
1074
  this.lastOutputHeight = outputHeight;
@@ -62,7 +62,9 @@ const createStandard = (stream, { showCursor = false } = {}) => {
62
62
  };
63
63
  render.clear = () => {
64
64
  const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition);
65
- stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
65
+ // [mixdog fork] Reset SGR before erasing so stale style/background from
66
+ // the previous frame doesn't leak onto the cleared cells.
67
+ stream.write(prefix + '\u001B[0m' + ansiEscapes.eraseLines(previousLineCount));
66
68
  previousOutput = '';
67
69
  previousLineCount = 0;
68
70
  previousCursorPosition = undefined;
@@ -171,7 +173,17 @@ const createIncremental = (stream, { showCursor = false } = {}) => {
171
173
  if (visibleCount < previousVisible) {
172
174
  const previousHadTrailingNewline = previousOutput.endsWith('\n');
173
175
  const extraSlot = previousHadTrailingNewline ? 1 : 0;
174
- buffer.push(ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot), ansiEscapes.cursorUp(visibleCount));
176
+ // [mixdog fork] Defensive erase on height shrink: clear ONE extra
177
+ // physical line beyond the counted delta. A hardware line-wrap (wide
178
+ // glyph at the viewport edge) can occupy a physical row the logical
179
+ // line count doesn't account for, leaving a ghost row below the new
180
+ // content. Not Windows-specific — the wrap desync happens on any
181
+ // terminal. We erase the extra line with eraseLine at the current
182
+ // (bottom) row FIRST — this does NOT move the cursor — then run the
183
+ // normal eraseLines walk. This keeps the post-erase cursor origin
184
+ // identical to upstream (cursorUp(visibleCount) still lands right),
185
+ // so only a stray trailing row is removed, layout is untouched.
186
+ buffer.push(ansiEscapes.eraseLine, ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot), ansiEscapes.cursorUp(visibleCount));
175
187
  }
176
188
  else {
177
189
  buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
@@ -205,7 +217,9 @@ const createIncremental = (stream, { showCursor = false } = {}) => {
205
217
  };
206
218
  render.clear = () => {
207
219
  const prefix = buildReturnToBottomPrefix(cursorWasShown, previousLines.length, previousCursorPosition);
208
- stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
220
+ // [mixdog fork] Reset SGR before erasing so stale style/background from
221
+ // the previous frame doesn't leak onto the cleared cells.
222
+ stream.write(prefix + '\u001B[0m' + ansiEscapes.eraseLines(previousLines.length));
209
223
  previousOutput = '';
210
224
  previousLines = [];
211
225
  previousCursorPosition = undefined;
@@ -1,6 +1,127 @@
1
1
  import wrapAnsi from 'wrap-ansi';
2
2
  import cliTruncate from 'cli-truncate';
3
+ import stripAnsi from 'strip-ansi';
4
+ import { AMBIGUOUS_WIDE, displayStringWidth } from './display-width.js';
3
5
  const cache = {};
6
+ // [mixdog fork] wrap-ansi / cli-truncate measure with plain string-width, which
7
+ // counts our problem glyphs (circled digits U+2460–U+24FF, arrows U+2190–U+21FF)
8
+ // as 1 cell while the terminal draws 2. When the policy is ON, their column math
9
+ // under-counts and a wrapped/truncated line can still overflow by one cell,
10
+ // pushing content past the box edge (the ghosting/overlap). We can't cleanly
11
+ // fork wrap-ansi, so we compensate at THIS boundary: re-check each produced line
12
+ // with displayStringWidth. For wrap/hard modes the overflow is REFLOWED onto a
13
+ // new line (no visible content is dropped); only truncate-end may cut.
14
+ const PROBLEM_RE = /[\u2190-\u21ff\u2460-\u24ff]/;
15
+ const ANSI_RE = /^\u001B(?:\[[0-9;]*m|\].*?(?:\u0007|\u001B\\))/;
16
+ const segmenter = new Intl.Segmenter();
17
+ // SGR reset — appended after a mid-line cut so an open style doesn't leak past
18
+ // the split point onto the reflowed remainder / following cells.
19
+ const SGR_RESET = '\u001B[0m';
20
+ // OSC 8 hyperlink open (with a URL) and universal close. Cutting inside a link
21
+ // must close it on head and re-open it on tail, exactly like an open SGR style.
22
+ const OSC8_CLOSE = '\u001B]8;;\u0007';
23
+ const OSC8_RE = /^\u001B\]8;;([^\u0007\u001B]*)(?:\u0007|\u001B\\)/;
24
+ // Split one ANSI line at the first grapheme boundary where VISIBLE displayWidth
25
+ // would exceed `max`. Walks grapheme CLUSTERS (not code points) so a clustered
26
+ // glyph (e.g. `↔️` = U+2194 U+FE0F) is measured/kept whole. Returns
27
+ // { head, tail }: head fits within max (with an SGR reset appended when any
28
+ // style is still open at the cut so styling doesn't bleed); tail is the
29
+ // remaining visible content, prefixed with the carried-over open SGR codes so
30
+ // it renders with the same style it was cut from. ANSI escapes cost 0 width.
31
+ const splitAtDisplayWidth = (line, max) => {
32
+ if (displayStringWidth(line) <= max)
33
+ return { head: line, tail: '' };
34
+ let head = '';
35
+ let width = 0;
36
+ let rest = line;
37
+ // Track SGR open codes seen so far so the tail can re-open them. An empty
38
+ // params reset ('' or '0') clears the carry; anything else accumulates.
39
+ let openSgr = '';
40
+ let sawOpenStyle = false;
41
+ // Track an open OSC 8 hyperlink (empty URL '...8;;\u0007' closes it).
42
+ let openLink = '';
43
+ while (rest.length > 0) {
44
+ const m = ANSI_RE.exec(rest);
45
+ if (m) {
46
+ head += m[0];
47
+ const sgr = /^\u001B\[([0-9;]*)m$/.exec(m[0]);
48
+ if (sgr) {
49
+ const params = sgr[1];
50
+ if (params === '' || params === '0') {
51
+ openSgr = '';
52
+ sawOpenStyle = false;
53
+ }
54
+ else {
55
+ openSgr += m[0];
56
+ sawOpenStyle = true;
57
+ }
58
+ }
59
+ else {
60
+ const osc = OSC8_RE.exec(m[0]);
61
+ if (osc)
62
+ openLink = osc[1] ? m[0] : '';
63
+ }
64
+ rest = rest.slice(m[0].length);
65
+ continue;
66
+ }
67
+ const { segment } = segmenter.segment(rest)[Symbol.iterator]().next().value;
68
+ const w = displayStringWidth(segment);
69
+ // Progress guarantee: if nothing visible has landed on head yet, force
70
+ // the first grapheme through even when it alone exceeds max (a 1-cluster
71
+ // overflow is unavoidable and preferable to a zero-progress loop).
72
+ if (width + w > max && width > 0)
73
+ break;
74
+ head += segment;
75
+ width += w;
76
+ rest = rest.slice(segment.length);
77
+ }
78
+ // Close any style / link still open at the cut, and re-open on the tail.
79
+ if (openLink)
80
+ head += OSC8_CLOSE;
81
+ if (sawOpenStyle)
82
+ head += SGR_RESET;
83
+ const carry = openSgr + openLink;
84
+ const tail = rest.length > 0 ? carry + rest : '';
85
+ return { head, tail };
86
+ };
87
+ // wrap/hard: no visible content may be dropped. Repeatedly split each produced
88
+ // line at the display-width budget and REFLOW the overflow onto following lines.
89
+ const reflowToDisplayWidth = (line, max) => {
90
+ const out = [];
91
+ let cur = line;
92
+ while (displayStringWidth(cur) > max) {
93
+ const { head, tail } = splitAtDisplayWidth(cur, max);
94
+ // splitAtDisplayWidth now always advances (forces >=1 grapheme onto
95
+ // head), so head carries visible content and tail shrinks each pass.
96
+ // Belt-and-braces: if tail failed to shrink, flush the remainder. Push
97
+ // `head` (not raw `cur`) — head carries the SGR/OSC8 close sequences, so
98
+ // even a single overwide styled/linked glyph doesn't leak its style.
99
+ if (tail === '' || tail === cur) {
100
+ out.push(head || cur);
101
+ return out;
102
+ }
103
+ out.push(head);
104
+ cur = tail;
105
+ }
106
+ out.push(cur);
107
+ return out;
108
+ };
109
+ // truncate-end: cutting is the intended behavior — drop the overflow but still
110
+ // cut on grapheme boundaries and close open styles.
111
+ const hardCutToDisplayWidth = (line, max) => splitAtDisplayWidth(line, max).head;
112
+ // Re-enforce the display-width budget on every produced line when the policy is
113
+ // ON and the source contains a problem glyph. No-op otherwise (byte-identical
114
+ // to upstream wrap-ansi output), so terminals without the wide policy are
115
+ // unaffected.
116
+ const enforceDisplayWidth = (wrapped, maxWidth, mode) => {
117
+ if (!AMBIGUOUS_WIDE || !PROBLEM_RE.test(stripAnsi(wrapped)))
118
+ return wrapped;
119
+ const lines = wrapped.split('\n');
120
+ if (mode === 'truncate')
121
+ return lines.map((line) => hardCutToDisplayWidth(line, maxWidth)).join('\n');
122
+ // wrap/hard: reflow overflow, never drop.
123
+ return lines.flatMap((line) => reflowToDisplayWidth(line, maxWidth)).join('\n');
124
+ };
4
125
  const wrapText = (text, maxWidth, wrapType) => {
5
126
  const cacheKey = text + String(maxWidth) + String(wrapType);
6
127
  const cachedText = cache[cacheKey];
@@ -13,6 +134,7 @@ const wrapText = (text, maxWidth, wrapType) => {
13
134
  trim: false,
14
135
  hard: true,
15
136
  });
137
+ wrappedText = enforceDisplayWidth(wrappedText, maxWidth, 'wrap');
16
138
  }
17
139
  if (wrapType === 'hard') {
18
140
  wrappedText = wrapAnsi(text, maxWidth, {
@@ -20,6 +142,7 @@ const wrapText = (text, maxWidth, wrapType) => {
20
142
  hard: true,
21
143
  wordWrap: false,
22
144
  });
145
+ wrappedText = enforceDisplayWidth(wrappedText, maxWidth, 'wrap');
23
146
  }
24
147
  if (wrapType.startsWith('truncate')) {
25
148
  let position = 'end';
@@ -30,6 +153,8 @@ const wrapText = (text, maxWidth, wrapType) => {
30
153
  position = 'start';
31
154
  }
32
155
  wrappedText = cliTruncate(text, maxWidth, { position });
156
+ if (position === 'end')
157
+ wrappedText = enforceDisplayWidth(wrappedText, maxWidth, 'truncate');
33
158
  }
34
159
  cache[cacheKey] = wrappedText;
35
160
  return wrappedText;
@@ -1,30 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { readFileSync } from 'node:fs';
3
-
4
- const src = readFileSync('src/standalone/folder-dialog.mjs', 'utf8');
5
- const fn = src.match(/function powershellScript[\s\S]*?return \[([\s\S]*?)\]\.join/);
6
- if (!fn) throw new Error('parse failed');
7
- // Re-eval powershellScript by importing - use dynamic compile
8
- const modUrl = new URL('../src/standalone/folder-dialog.mjs', import.meta.url);
9
- // Call pickFolder with a hook - instead duplicate minimal import
10
- const { pickFolder } = await import(modUrl.href);
11
-
12
- const child = spawn('powershell.exe', [
13
- '-NoLogo', '-NoProfile', '-STA', '-Command',
14
- [
15
- '$ErrorActionPreference = "Continue"',
16
- 'try {',
17
- ...readFileSync('src/standalone/folder-dialog.mjs', 'utf8')
18
- .split('\n')
19
- .filter((l) => l.includes('Add-Type -AssemblyName') || l.includes('Add-Type -TypeDefinition') || l.includes('GetDialogOwnerCenter')),
20
- '} catch { Write-Error $_; exit 1 }',
21
- ].join('; '),
22
- ], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
23
-
24
- let err = '';
25
- child.stderr.on('data', (d) => { err += d.toString(); });
26
- child.on('close', (code) => {
27
- console.log('code', code);
28
- console.log(err.slice(0, 2000));
29
- });
30
-
@@ -1,35 +0,0 @@
1
- import fs from "node:fs";
2
- const p = "src/runtime/shared/tool-surface.mjs";
3
- let s = fs.readFileSync(p, "utf8");
4
- const start = s.indexOf("export function summarizeAgentSurfaceBrief");
5
- const end = s.indexOf("function pluralize", start);
6
- if (start < 0 || end < 0) throw new Error("markers");
7
- const clean = `export function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, isResponse = false } = {}) {
8
- const a = parseToolArgs(args);
9
- const action = String(a?.type || a?.action || "").toLowerCase();
10
- const text = String(resultText ?? "").trim();
11
-
12
- if (isResponse && text) {
13
- const fromResult = summarizeToolResult(name, args, text, isError);
14
- if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
15
- const line = firstAgentResultLine(text);
16
- if (line) return truncateAgentSurfaceBrief(stripInlineMarkdown(line));
17
- }
18
-
19
- const outbound = firstText(a?.prompt, a?.message);
20
- if (outbound && (action === "spawn" || action === "send" || !action)) {
21
- return truncateAgentSurfaceBrief(outbound);
22
- }
23
-
24
- if ((action === "spawn" || action === "send") && text && !isResponse) {
25
- const fromResult = summarizeToolResult(name, args, text, isError);
26
- if (fromResult) return truncateAgentSurfaceBrief(stripInlineMarkdown(fromResult));
27
- }
28
-
29
- return "";
30
- }
31
-
32
- `;
33
- s = s.slice(0, start) + clean + s.slice(end);
34
- fs.writeFileSync(p, s);
35
- console.log("summarizeAgentSurfaceBrief cleaned");
@@ -1,24 +0,0 @@
1
- import fs from "node:fs";
2
- const p = "src/runtime/shared/tool-surface.mjs";
3
- let s = fs.readFileSync(p, "utf8");
4
- const block = `export function formatToolSurface(name, args, opts = {}) {
5
- const parsed = parseToolArgs(args);
6
- return {
7
- label: displayToolName(name, parsed),
8
- summary: summarizeToolArgs(name, parsed, opts),
9
- normalizedName: normalizeToolName(name),
10
- args: parsed,
11
- };
12
- }
13
-
14
- `;
15
- while (s.includes(block)) {
16
- s = s.replace(block, "");
17
- }
18
- const anchor = `/** Collapsed agent/worker surface card: one-line brief under the header (30-40 chars). */`;
19
- if (!s.includes(anchor)) throw new Error("anchor missing");
20
- if (!s.includes("export function formatToolSurface")) {
21
- s = s.replace(anchor, block + anchor);
22
- }
23
- fs.writeFileSync(p, s);
24
- console.log("fixed formatToolSurface");
@@ -1,42 +0,0 @@
1
- import fs from "node:fs";
2
- const p = "src/tui/components/ToolExecution.jsx";
3
- let s = fs.readFileSync(p, "utf8");
4
- const bad = ` let visibleDetailLines = detailLines;
5
- if (isSkillSurface && !showRawResult) {
6
- visibleDetailLines = [];
7
- } else if (isAgentSurfaceCard && !showRawResult) {
8
- if (agentSurfaceBrief) {
9
- visibleDetailLines = [agentSurfaceBrief];
10
- } else if (pending) {
11
- visibleDetailLines = [pendingDetailPlaceholder || 'Running'];
12
- } else {
13
- visibleDetailLines = [];
14
- }
15
- }
16
- } else if (isAgentSurfaceCard && !showRawResult) {
17
- } else if (isAgentSurfaceCard && !showRawResult) {
18
- const agentDetailFallback = collapsedDetail
19
- || (pending ? (pendingDetailPlaceholder || 'Running') : 'Finished');
20
- const agentDetailLine = agentSurfaceBrief
21
- || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
22
- visibleDetailLines = [agentDetailLine];
23
- }`;
24
- const good = ` let visibleDetailLines = detailLines;
25
- if (isSkillSurface && !showRawResult) {
26
- visibleDetailLines = [];
27
- } else if (isAgentSurfaceCard && !showRawResult) {
28
- const agentDetailFallback = collapsedDetail
29
- || (pending ? (pendingDetailPlaceholder || 'Running') : 'Finished');
30
- const agentDetailLine = agentSurfaceBrief
31
- || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
32
- visibleDetailLines = [agentDetailLine];
33
- }`;
34
- if (s.includes(bad)) {
35
- s = s.replace(bad, good);
36
- fs.writeFileSync(p, s);
37
- console.log("ToolExecution fixed");
38
- } else if (s.includes("agentDetailFallback")) {
39
- console.log("ToolExecution already ok");
40
- } else {
41
- throw new Error("ToolExecution pattern not found");
42
- }
@@ -1,48 +0,0 @@
1
- import fs from "node:fs";
2
- const p = "src/runtime/shared/tool-surface.mjs";
3
- let s = fs.readFileSync(p, "utf8");
4
- if (s.includes("summarizeAgentSurfaceBrief")) { console.log("skip"); process.exit(0); }
5
- const insert = `/** Collapsed agent/worker surface card: one-line brief under the header (30-40 chars). */
6
- export const AGENT_SURFACE_BRIEF_MAX = 40;
7
-
8
- export function summarizeAgentSurfaceBrief(name, args, resultText, { isError = false, isResponse = false } = {}) {
9
- const a = parseToolArgs(args);
10
- const action = String(a?.type || a?.action || "").toLowerCase();
11
- const text = String(resultText ?? "").trim();
12
-
13
- if (isResponse && text) {
14
- const fromResult = summarizeToolResult(name, args, text, isError);
15
- if (fromResult) return truncateSingleLine(stripInlineMarkdown(fromResult), AGENT_SURFACE_BRIEF_MAX);
16
- const line = firstAgentResultLine(text);
17
- if (line) return truncateSingleLine(stripInlineMarkdown(line), AGENT_SURFACE_BRIEF_MAX);
18
- }
19
-
20
- const outbound = firstText(a?.prompt, a?.message);
21
- if (outbound && (action === "spawn" || action === "send" || !action)) {
22
- return truncateSingleLine(outbound, AGENT_SURFACE_BRIEF_MAX);
23
- }
24
-
25
- if ((action === "spawn" || action === "send") && text && !isResponse) {
26
- const fromResult = summarizeToolResult(name, args, text, isError);
27
- if (fromResult) return truncateSingleLine(stripInlineMarkdown(fromResult), AGENT_SURFACE_BRIEF_MAX);
28
- }
29
-
30
- return "";
31
- }
32
-
33
- `;
34
- const needle = `export function formatToolSurface(name, args, opts = {}) {
35
- const parsed = parseToolArgs(args);
36
- return {
37
- label: displayToolName(name, parsed),
38
- summary: summarizeToolArgs(name, parsed, opts),
39
- normalizedName: normalizeToolName(name),
40
- args: parsed,
41
- };
42
- }
43
-
44
- function pluralize`;
45
- if (!s.includes(needle)) throw new Error("needle not found");
46
- s = s.replace(needle, insert + "function pluralize");
47
- fs.writeFileSync(p, s);
48
- console.log("tool-surface ok");
@@ -1,21 +0,0 @@
1
- import fs from "node:fs";
2
- const p = "src/tui/App.jsx";
3
- let s = fs.readFileSync(p, "utf8");
4
- const old = ` // EVERY agent card is a single header row whether pending or completed —
5
- // ToolExecution drops the ⎿ body for all of them AND the pending-delay
6
- // placeholder reserves only 1 row for agent surfaces — so reserve
7
- // exactly 1 row regardless of pending/hasResult. Otherwise the estimate
8
- // (2) and the real render (1) diverge and the viewport jumps.
9
- if (isSkillSurface || isAgentSurface) return 1;`;
10
- const neu = ` // Skill loads are a single header row; agent cards are header + brief.
11
- if (isSkillSurface) return 1;
12
- if (isAgentSurface) return 2;`;
13
- if (s.includes(old)) {
14
- s = s.replace(old, neu);
15
- fs.writeFileSync(p, s);
16
- console.log("App ok");
17
- } else if (s.includes("if (isAgentSurface) return 2")) {
18
- console.log("App already");
19
- } else {
20
- throw new Error("App block not found");
21
- }