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
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import React, { useEffect, useLayoutEffect, useState, useRef } from 'react';
19
19
  import { Box, Text, useInput, usePaste, useStdin } from 'ink';
20
- import stringWidth from 'string-width';
20
+ import { displayWidth } from '../display-width.mjs';
21
21
  import { theme, surfaceBackground } from '../theme.mjs';
22
22
  import {
23
23
  caretPosition,
@@ -40,48 +40,29 @@ import {
40
40
  verticalOffset,
41
41
  wordRangeAt,
42
42
  } from '../input-editing.mjs';
43
-
44
- function hintStyle(tone) {
45
- if (tone === 'error') return { textColor: theme.error };
46
- if (tone === 'warn' || tone === 'cancel') return { textColor: theme.warning };
47
- if (tone === 'plain') return { textColor: theme.subtle };
48
- return { textColor: theme.inactive };
49
- }
43
+ import {
44
+ hintStyle,
45
+ insertText,
46
+ normalizePastedText,
47
+ singleTrailingLineBreakPrefix,
48
+ draftStateEqual,
49
+ isModifiedEnterSequence,
50
+ isAnyModifiedEnterSequence,
51
+ } from './prompt-input/edit-helpers.mjs';
52
+ import {
53
+ VOICE_TICK_MS,
54
+ voiceIndicatorTextFor,
55
+ } from './prompt-input/voice-indicator.mjs';
50
56
 
51
57
  // Windows Terminal IME composition can clip a glyph that starts exactly at the
52
58
  // left edge of the editable text node. The rounded prompt box already adds a
53
59
  // paddingX of 1, so the typing start can sit directly against that padding
54
60
  // without an extra guard column.
55
61
  const IME_LEFT_GUARD_COLUMNS = 0;
56
-
57
- // Monotone voice indicator glyphs — right end of the prompt box. All frames
58
- // are 1-cell-wide (no emoji, no string-width special-casing needed). Modes:
59
- // 'off' — voice disabled and no install in flight: renders nothing,
60
- // zero reserved columns.
61
- // 'installing' — voice-runtime download in progress (progressHint active):
62
- // braille spinner only.
63
- // 'idle' — voice enabled, recorder idle: a slow "breathing" pulse
64
- // (◎ ◍ ◉ ◍) so the indicator isn't a dead static glyph.
65
- // 'recording' — steady ◉ + a braille spinner riding next to it (2 cells).
66
- // 'transcribing' — braille spinner only (recording just stopped, awaiting
67
- // the whisper-server round-trip).
68
- // A single ~120ms interval drives BOTH the fast braille cycle and the slower
69
- // idle pulse (idle advances one frame every VOICE_IDLE_FRAME_TICKS ticks) —
70
- // one timer instead of two, and it only exists while the indicator is
71
- // visible (mode !== 'off'); it's torn down otherwise, never a permanent timer.
72
- const VOICE_BRAILLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧'];
73
- const VOICE_IDLE_FRAMES = ['◎', '◍', '◉', '◍'];
74
- const VOICE_TICK_MS = 120;
75
- const VOICE_IDLE_FRAME_TICKS = 5; // ~600ms per idle frame (5 * 120ms)
76
62
  // Coalesce prompt mouse-drag extend commits (SGR motion can fire faster than ink
77
63
  // needs to immediate-render). Matches transcript selection paint cadence.
78
64
  const MOUSE_EXTEND_COALESCE_MS = 24;
79
65
 
80
- function insertText(draft, input) {
81
- if (!input) return draft;
82
- return replaceSelection(draft, input);
83
- }
84
-
85
66
  function renderSelectedText(displayValue, range, trailingSpace = false) {
86
67
  if (!range) return trailingSpace ? `${displayValue} ` : displayValue;
87
68
  const start = Math.max(0, Math.min(displayValue.length, range.start));
@@ -98,57 +79,6 @@ function renderSelectedText(displayValue, range, trailingSpace = false) {
98
79
  );
99
80
  }
100
81
 
101
- function normalizePastedText(text) {
102
- return String(text ?? '').replace(/\r\n?/g, '\n');
103
- }
104
-
105
- function singleTrailingLineBreakPrefix(text) {
106
- const normalized = normalizePastedText(text);
107
- if (!normalized.endsWith('\n')) return null;
108
- const prefix = normalized.slice(0, -1);
109
- return prefix.includes('\n') ? null : prefix;
110
- }
111
-
112
- function draftStateEqual(a, b) {
113
- return (
114
- a.value === b.value
115
- && a.cursor === b.cursor
116
- && a.selectionAnchor === b.selectionAnchor
117
- );
118
- }
119
-
120
- // Recognize a MODIFIED Enter delivered via the kitty keyboard protocol
121
- // (\x1b[13;<mod>u) or modifyOtherKeys (\x1b[27;<mod>;13~). The xterm modifier
122
- // param is (1 + bitmask) where the bitmask bits are shift=1, alt=2, ctrl=4. We
123
- // treat Ctrl+Enter AND Shift+Enter (the two common "insert newline" chords) as a
124
- // newline, so we match when the shift OR ctrl bit is set. Ctrl+J is handled
125
- // separately as the protocol-independent fallback.
126
- const MODIFIED_ENTER_SHIFT_OR_CTRL = 1 | 4;
127
- function isModifiedEnterSequence(input) {
128
- const text = String(input ?? '');
129
- const body = text.startsWith('\x1b[') ? text.slice(2) : text.startsWith('[') ? text.slice(1) : '';
130
- if (!body) return false;
131
- const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
132
- if (kitty) return ((Number(kitty[1]) - 1) & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0;
133
- const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
134
- return Boolean(modifyOtherKeys && (((Number(modifyOtherKeys[1]) - 1) & MODIFIED_ENTER_SHIFT_OR_CTRL) !== 0));
135
- }
136
-
137
- // Recognize ANY modified Enter (any modifier bitmask, e.g. Alt+Enter \x1b[13;3u
138
- // / \x1b[27;3;13~). Used to CONSUME modified-Enter sequences we don't map to a
139
- // newline (Alt-only, etc.) so they aren't typed into the prompt as raw CSI text
140
- // under modifyOtherKeys. Plain Enter (mod param = 1, bitmask 0) is intentionally
141
- // NOT matched, so it still submits.
142
- function isAnyModifiedEnterSequence(input) {
143
- const text = String(input ?? '');
144
- const body = text.startsWith('\x1b[') ? text.slice(2) : text.startsWith('[') ? text.slice(1) : '';
145
- if (!body) return false;
146
- const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
147
- if (kitty) return (Number(kitty[1]) - 1) !== 0;
148
- const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
149
- return Boolean(modifyOtherKeys && ((Number(modifyOtherKeys[1]) - 1) !== 0));
150
- }
151
-
152
82
  export function PromptInput({
153
83
  onSubmit,
154
84
  disabled = false,
@@ -204,12 +134,13 @@ export function PromptInput({
204
134
  const preferredColumnRef = useRef(null);
205
135
  const mouseExtendCoalesceRef = useRef({ pendingNext: null, timer: null, t: 0 });
206
136
  // Voice indicator animation tick. Runs a single ~120ms interval ONLY while
207
- // voiceIndicatorMode !== 'off' (installing/idle/recording/transcribing) —
208
- // torn down the instant the mode goes 'off', so no permanent timer exists
209
- // when voice is disabled and no install is in flight.
137
+ // an ANIMATED mode is active (installing/recording/transcribing) — 'idle'
138
+ // renders a static glyph and 'off' renders nothing, so neither keeps a
139
+ // timer alive (the old idle "breathing" pulse spun constantly whenever
140
+ // voice was merely enabled, reading as phantom activity).
210
141
  const [voiceTick, setVoiceTick] = useState(0);
211
142
  useEffect(() => {
212
- if (voiceIndicatorMode === 'off') return undefined;
143
+ if (voiceIndicatorMode === 'off' || voiceIndicatorMode === 'idle') return undefined;
213
144
  const timer = setInterval(() => setVoiceTick((t) => (t + 1) % 1000000), VOICE_TICK_MS);
214
145
  timer.unref?.();
215
146
  return () => clearInterval(timer);
@@ -308,7 +239,7 @@ export function PromptInput({
308
239
  // following cell — pass hasTrailingContent=true so it rolls to row N+1
309
240
  // exactly as ink wraps the trailing space.
310
241
  ? caretPosition(d.value, d.cursor, contentWidth, d.cursor >= d.value.length ? true : undefined)
311
- : { row: 0, col: stringWidth(d.value.slice(0, d.cursor)) };
242
+ : { row: 0, col: displayWidth(d.value.slice(0, d.cursor)) };
312
243
  return w > 0
313
244
  ? { ...caret, col: caret.col + guardColumns }
314
245
  : caret;
@@ -1064,19 +995,10 @@ export function PromptInput({
1064
995
  const hintMeta = hintStyle(hintTone);
1065
996
  // Monotone voice indicator glyph text — zero-width (rendered as null) when
1066
997
  // mode is 'off'. Braille frame drives installing/recording/transcribing;
1067
- // the idle pulse advances one frame every VOICE_IDLE_FRAME_TICKS ticks of
1068
- // the SAME interval (slower "breathing" cadence off one shared timer).
1069
- const voiceBrailleFrame = VOICE_BRAILLE_FRAMES[voiceTick % VOICE_BRAILLE_FRAMES.length];
1070
- const voiceIdleFrame = VOICE_IDLE_FRAMES[Math.floor(voiceTick / VOICE_IDLE_FRAME_TICKS) % VOICE_IDLE_FRAMES.length];
1071
- const voiceIndicatorText = voiceIndicatorMode === 'installing' || voiceIndicatorMode === 'transcribing'
1072
- ? voiceBrailleFrame
1073
- : voiceIndicatorMode === 'recording'
1074
- ? `◉${voiceBrailleFrame}`
1075
- : voiceIndicatorMode === 'idle'
1076
- ? voiceIdleFrame
1077
- : '';
998
+ // 'idle' is a static (no animation, no timer).
999
+ const voiceIndicatorText = voiceIndicatorTextFor(voiceIndicatorMode, voiceTick);
1078
1000
  // stringWidth is unnecessary here — every glyph above is a single
1079
- // 1-column codepoint (◎◍◉ and the braille frames), and 'recording'
1001
+ // 1-column codepoint ( and the braille frames), and 'recording'
1080
1002
  // concatenates exactly two 1-cell glyphs, so plain .length gives the
1081
1003
  // correct cell count without pulling in emoji-width special-casing.
1082
1004
  voiceIndicatorWidthRef.current = voiceIndicatorText.length;
@@ -60,7 +60,11 @@ export function SlashCommandPalette({ commands, selectedIndex = 0, title = 'Comm
60
60
  start = Math.max(0, end - MAX_VISIBLE);
61
61
  }
62
62
  const visible = commands.slice(start, end);
63
- const blankRows = Math.max(0, MAX_VISIBLE - visible.length);
63
+ // No-match frames keep the palette mounted at the SAME fixed height so the
64
+ // slash session never unmounts/remounts the 14-row panel per keystroke
65
+ // (Windows fullscreen repaint made that oscillation a hard flicker).
66
+ const noMatches = total === 0;
67
+ const blankRows = Math.max(0, MAX_VISIBLE - visible.length - (noMatches ? 1 : 0));
64
68
  const labelWidth = Math.max(12, Math.min(COMMAND_LABEL_WIDTH, Math.max(12, Math.floor(columns * 0.45))));
65
69
  const descriptionWidth = Math.max(0, columns - labelWidth - 12);
66
70
  // Standard panel rhythm: title row, blank, description/hint row, blank, content.
@@ -84,6 +88,9 @@ export function SlashCommandPalette({ commands, selectedIndex = 0, title = 'Comm
84
88
  <Text> </Text>
85
89
  <Text color={theme.subtle}>{description || ' '}</Text>
86
90
  <Text> </Text>
91
+ {noMatches ? (
92
+ <Text color={theme.subtle} wrap="truncate">No matching commands</Text>
93
+ ) : null}
87
94
  {visible.map((item, index) => (
88
95
  <CommandRow
89
96
  key={item.name}
@@ -482,6 +482,29 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
482
482
  Number(activeTools.search?.startedAt) || 0,
483
483
  ].join('|')
484
484
  : '';
485
+ // Stable primitive signatures so the render effect's dep array doesn't
486
+ // churn on new array/object identities carrying unchanged content — a new
487
+ // `agentWorkers`/`agentJobs` array (or `stats` object) reference on every
488
+ // render was clearing the pending 150ms debounce timer before it could
489
+ // fire, starving the boot full-render. Only tag|stage per worker/job and
490
+ // the numeric stats fields actually consumed here are covered.
491
+ const agentWorkersSignature = Array.isArray(agentWorkers)
492
+ ? agentWorkers.map((w) => `${w?.tag || w?.agent || w?.name || ''}|${w?.stage || w?.status || ''}|${localTimeMs(w?.startedAt || w?.startTime || w?.createdAt)}`).join(',')
493
+ : '';
494
+ const agentJobsSignature = Array.isArray(agentJobs)
495
+ ? agentJobs.map((j) => `${j?.tag || j?.agent || j?.name || ''}|${j?.status || j?.stage || ''}|${localTimeMs(j?.startedAt || j?.startTime || j?.createdAt)}`).join(',')
496
+ : '';
497
+ const statsForSignature = stats && typeof stats === 'object' ? stats : {};
498
+ const statsSignature = [
499
+ localNum(statsForSignature.currentContextTokens),
500
+ localNum(statsForSignature.currentEstimatedContextTokens),
501
+ localNum(statsForSignature.inputTokens),
502
+ localNum(statsForSignature.latestInputTokens),
503
+ localNum(statsForSignature.promptTokens),
504
+ localNum(statsForSignature.turns),
505
+ localNum(statsForSignature.contextTokens),
506
+ String(statsForSignature.currentContextSource || ''),
507
+ ].join('|');
485
508
  const refreshMs = hasActiveStatuslineWork(line, agentWorkers, agentJobs, activeTools) ? STATUSLINE_ACTIVE_REFRESH_MS : STATUSLINE_REFRESH_MS;
486
509
 
487
510
  useEffect(() => {
@@ -498,6 +521,7 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
498
521
 
499
522
  useEffect(() => {
500
523
  let alive = true;
524
+ let bootRetryTimer = null;
501
525
  const effectId = renderEffectIdRef.current + 1;
502
526
  renderEffectIdRef.current = effectId;
503
527
  const isCurrentEffect = () => alive && renderEffectIdRef.current === effectId;
@@ -508,23 +532,27 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
508
532
  lastImmediateArgsRef.current,
509
533
  );
510
534
  // ROUTE identity = the subset that actually changes the async full line's L1
511
- // usage/quota segment (provider/model/session/effort/fast + context windows).
512
- // agentRevision, compactBoundaryTokens, autoCompactTokenLimit and stats-reset
513
- // are NON-ROUTE: they churn while several agents run but DON'T invalidate the
514
- // L1 usage windows. Only a true route/session switch may wipe the cached full
515
- // line and snap to the usage-less local line; non-route churn must keep the
516
- // last good full line so `5H …/7D …` never blinks. (lastImmediateArgsRef holds
517
- // exactly these route fields, captured at the end of the previous effect run.)
535
+ // usage/quota segment (provider/model/session/effort/fast). agentRevision,
536
+ // compactBoundaryTokens, autoCompactTokenLimit, stats-reset AND the context
537
+ // window fields (contextWindow/displayContextWindow/rawContextWindow) are
538
+ // NON-ROUTE: the first prompt's API response publishes displayContextWindow
539
+ // 0→N (engine syncContextDisplayFields), which only moves the ctx% bar
540
+ // never the `5H …/7D …` usage windows. Treating it as route wiped the cached
541
+ // full line and snapped to the usage-less local line for one frame (the
542
+ // "usage blinks once on first prompt" bug). A real route switch that changes
543
+ // usage windows always changes provider/model/session anyway. Only a true
544
+ // route/session switch may wipe the cached full line; non-route churn keeps
545
+ // the last good full line (identityChanged still snaps, but via the cached
546
+ // graft path below, so usage survives and the async render refreshes ctx%
547
+ // within ~150ms). (lastImmediateArgsRef holds these fields, captured at the
548
+ // end of the previous effect run.)
518
549
  const prevImmediate = lastImmediateArgsRef.current;
519
550
  const routeChanged = !prevImmediate
520
551
  || prevImmediate.sessionId !== args.sessionId
521
552
  || prevImmediate.provider !== args.provider
522
553
  || prevImmediate.model !== args.model
523
554
  || prevImmediate.effort !== args.effort
524
- || prevImmediate.fast !== args.fast
525
- || prevImmediate.contextWindow !== args.contextWindow
526
- || prevImmediate.displayContextWindow !== args.displayContextWindow
527
- || prevImmediate.rawContextWindow !== args.rawContextWindow;
555
+ || prevImmediate.fast !== args.fast;
528
556
  // A theme switch must re-tone the footer immediately: the stored `line`
529
557
  // holds already-normalized ANSI with the OLD palette, so re-running
530
558
  // normalizeStatusLine on it is a no-op. Force a fresh local rebuild (new
@@ -607,9 +635,31 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
607
635
  const timer = setTimeout(() => {
608
636
  if (bootFullDoneRef.current !== true) {
609
637
  if (!bootFullRenderEligible(mountAtRef.current, lineRef.current, args.agentWorkers, args.agentJobs, args.activeTools)) {
638
+ // Not eligible yet (boot delay not elapsed). Don't just bail and wait
639
+ // for the next refreshTick interval (250ms/2000ms) — arm a follow-up
640
+ // timeout for the REMAINING boot delay so the first full render still
641
+ // fires on schedule instead of stalling/flickering until the next tick.
642
+ const active = hasActiveStatuslineWork(lineRef.current, args.agentWorkers, args.agentJobs, args.activeTools);
643
+ const bootDelay = active ? STATUSLINE_BOOT_FULL_DELAY_ACTIVE_MS : STATUSLINE_BOOT_FULL_DELAY_MS;
644
+ const elapsed = Date.now() - mountAtRef.current;
645
+ const remaining = Math.max(150, bootDelay - elapsed);
646
+ bootRetryTimer = setTimeout(() => {
647
+ if (!isCurrentEffect()) return;
648
+ setRefreshTick((tick) => (tick + 1) % 1_000_000);
649
+ }, remaining);
650
+ bootRetryTimer.unref?.();
610
651
  return;
611
652
  }
612
653
  if (!canAttemptBootFullRender(bootFullNextAttemptAtRef.current)) {
654
+ // Backoff window not elapsed yet — arm a follow-up timeout for exactly
655
+ // the remaining backoff so the retry fires on time instead of waiting
656
+ // for the next refreshTick interval.
657
+ const remaining = Math.max(150, bootFullNextAttemptAtRef.current - Date.now());
658
+ bootRetryTimer = setTimeout(() => {
659
+ if (!isCurrentEffect()) return;
660
+ setRefreshTick((tick) => (tick + 1) % 1_000_000);
661
+ }, remaining);
662
+ bootRetryTimer.unref?.();
613
663
  return;
614
664
  }
615
665
  }
@@ -644,8 +694,9 @@ function StatusLineView({ sessionId, clientHostPid, provider, model, effort, fas
644
694
  return () => {
645
695
  alive = false;
646
696
  clearTimeout(timer);
697
+ clearTimeout(bootRetryTimer);
647
698
  };
648
- }, [sessionId, clientHostPid, provider, model, effort, fast, cwd, stats, contextWindow, displayContextWindow, compactBoundaryTokens, autoCompactTokenLimit, rawContextWindow, resizeEpoch, agentRevision, agentWorkers, agentJobs, activeToolsSignature, refreshTick, themeEpoch]);
699
+ }, [sessionId, clientHostPid, provider, model, effort, fast, cwd, statsSignature, contextWindow, displayContextWindow, compactBoundaryTokens, autoCompactTokenLimit, rawContextWindow, resizeEpoch, agentRevision, agentWorkersSignature, agentJobsSignature, activeToolsSignature, refreshTick, themeEpoch]);
649
700
 
650
701
  const lines = line ? line.split('\n').slice(0, 2) : [' ', ' '];
651
702
  const workflowLabel = workflowModeLabel(workflow, remoteEnabled);
@@ -159,6 +159,7 @@ function isAnyModifiedEnterSequence(input) {
159
159
  export function TextEntryPanel({
160
160
  title,
161
161
  hint = '',
162
+ detail = '',
162
163
  initialValue = '',
163
164
  mask = false,
164
165
  columns = 80,
@@ -465,6 +466,10 @@ export function TextEntryPanel({
465
466
  // The hint is collapsed to one line and width-truncated so a long manual
466
467
  // OAuth URL can never wrap and push the bordered title off the top.
467
468
  const hintText = truncateText(singleLine(hint), Math.max(0, columns - 4));
469
+ // Optional wrapped detail block (e.g. the manual OAuth URL). Lives INSIDE the
470
+ // live panel — never pushed to the transcript — so it disappears completely
471
+ // when the panel closes instead of lingering in terminal scrollback.
472
+ const detailText = String(detail || '').trim();
468
473
 
469
474
  return (
470
475
  <Box flexDirection="column" flexShrink={0} width="100%">
@@ -475,6 +480,12 @@ export function TextEntryPanel({
475
480
  </Box>
476
481
  <Text> </Text>
477
482
  <Text color={theme.subtle}>{hintText || ' '}</Text>
483
+ {detailText ? (
484
+ <Box flexDirection="column" width="100%">
485
+ <Text> </Text>
486
+ <Text color={theme.subtle} wrap="wrap">{detailText}</Text>
487
+ </Box>
488
+ ) : null}
478
489
  <Text> </Text>
479
490
  <Box ref={boxRef} flexDirection="row" width="100%" backgroundColor={surfaceBackground()}>
480
491
  <Text color={theme.inactive}>{promptLabel}</Text>