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
@@ -0,0 +1,406 @@
1
+ // ── Runtime liveness map ──────────────────────────────────────────────
2
+ // In-memory only. Tracks per-session stage + stream heartbeat so agent type=list
3
+ // can surface whether a session is actually alive vs stuck. Never persisted —
4
+ // heartbeats would otherwise churn the session JSON on every SSE delta.
5
+ //
6
+ // Extracted from manager.mjs (pass-3). The Map and its timer companion are
7
+ // module-level singletons — preserving the baseline's single-process shape.
8
+ // manager.mjs owns askSession's controller/generation lifecycle and calls the
9
+ // accessors below via imports; a small set of accessors that need the session
10
+ // store / provider registry are injected through configureRuntimeLiveness() to
11
+ // avoid a circular import back into manager.mjs.
12
+ //
13
+ // Entry shape: {
14
+ // stage, lastStreamDeltaAt, lastToolCall, lastError, updatedAt,
15
+ // controller?: AbortController, // set while an ask is in flight
16
+ // generation?: number, // snapshot taken at ask start
17
+ // closed?: boolean, // flipped by closeSession()
18
+ // }
19
+ import { createAbortController } from '../../../../shared/abort-controller.mjs';
20
+ import { publishHeartbeat, deleteHeartbeat } from '../store.mjs';
21
+ import { DEFAULT_ACTIVITY_HEARTBEAT_MS } from '../../stall-policy.mjs';
22
+ import {
23
+ configureUsageMetricsRuntime,
24
+ dropMetricSeenState,
25
+ bumpUsageMetricsTurnId,
26
+ } from './usage-metrics.mjs';
27
+
28
+ const HEARTBEAT_THROTTLE_MS = 60_000; // 60s
29
+
30
+ const _runtimeState = new Map();
31
+ const _toolActivityHeartbeats = new Map();
32
+ const VALID_STAGES = new Set([
33
+ 'connecting', 'requesting', 'streaming', 'tool_running', 'idle', 'error', 'done', 'cancelling',
34
+ ]);
35
+
36
+ // Injected deps that would otherwise pull manager.mjs's store/provider surface
37
+ // back into this module (circular). Wired once from manager.mjs at load time.
38
+ let _deps = {
39
+ loadSession: () => null,
40
+ saveSessionAsync: async () => {},
41
+ };
42
+ export function configureRuntimeLiveness(deps = {}) {
43
+ _deps = { ..._deps, ...deps };
44
+ }
45
+
46
+ export function _touchRuntime(id) {
47
+ let entry = _runtimeState.get(id);
48
+ if (!entry) {
49
+ entry = { stage: 'idle', lastStreamDeltaAt: null, lastToolCall: null, lastError: null, updatedAt: Date.now() };
50
+ _runtimeState.set(id, entry);
51
+ }
52
+ return entry;
53
+ }
54
+
55
+ export function _stopToolActivityHeartbeat(id) {
56
+ if (!id) return;
57
+ const timer = _toolActivityHeartbeats.get(id);
58
+ if (!timer) return;
59
+ try { clearInterval(timer); } catch { /* ignore */ }
60
+ _toolActivityHeartbeats.delete(id);
61
+ }
62
+
63
+ function _touchSessionActivityProgress(id) {
64
+ const entry = _runtimeState.get(id);
65
+ if (!entry || entry.closed || entry.controller?.signal?.aborted) return;
66
+ if (entry.stage !== 'tool_running') return;
67
+ const now = Date.now();
68
+ entry.lastProgressAt = now;
69
+ entry.updatedAt = now;
70
+ publishHeartbeat(id, now);
71
+ }
72
+
73
+ function _startToolActivityHeartbeat(id) {
74
+ _stopToolActivityHeartbeat(id);
75
+ if (!(DEFAULT_ACTIVITY_HEARTBEAT_MS > 0)) return;
76
+ const timer = setInterval(() => _touchSessionActivityProgress(id), DEFAULT_ACTIVITY_HEARTBEAT_MS);
77
+ if (typeof timer.unref === 'function') timer.unref();
78
+ _toolActivityHeartbeats.set(id, timer);
79
+ }
80
+
81
+ export function updateSessionStage(id, stage) {
82
+ if (!id || !VALID_STAGES.has(stage)) return;
83
+ const entry = _touchRuntime(id);
84
+ const now = Date.now();
85
+ entry.stage = stage;
86
+ if (stage === 'connecting' || stage === 'requesting') {
87
+ entry.modelRequestStartedAt = now;
88
+ }
89
+ entry.lastProgressAt = now;
90
+ entry.updatedAt = now;
91
+ if (stage !== 'tool_running') _stopToolActivityHeartbeat(id);
92
+ }
93
+
94
+ /**
95
+ * Reset heartbeat-visible fields for a new ask. Preserves controller/generation/
96
+ * closed (lifecycle) but clears the previous run's streaming state so stale
97
+ * lastToolCall / lastStreamDeltaAt from the previous ask don't leak into the
98
+ * new one.
99
+ */
100
+ export function markSessionAskStart(id) {
101
+ if (!id) return;
102
+ _stopToolActivityHeartbeat(id);
103
+ const entry = _touchRuntime(id);
104
+ entry.usageMetricsTurnIncremental = false;
105
+ const sessionForTurn = entry.session ?? _deps.loadSession(id);
106
+ if (sessionForTurn) bumpUsageMetricsTurnId(sessionForTurn);
107
+ entry.stage = 'connecting';
108
+ entry.lastStreamDeltaAt = null;
109
+ entry.lastToolCall = null;
110
+ entry.toolStartedAt = null;
111
+ entry.lastError = null;
112
+ // A new ask starts a fresh turn lifecycle — clear any stale empty-final
113
+ // classification from the prior turn so inspectBridgeEntry doesn't keep
114
+ // short-circuiting to 'empty-synthesis' (which would disable stall
115
+ // detection for the entire new turn).
116
+ entry.emptyFinal = false;
117
+ entry.emptyFinalAt = null;
118
+ // askStartedAt is the watchdog's fallback reference when a session
119
+ // hangs before any stream delta arrives. Without it, a provider that
120
+ // never returns a first token would stall forever because the watchdog
121
+ // keys solely on lastStreamDeltaAt.
122
+ const now = Date.now();
123
+ entry.askStartedAt = now;
124
+ entry.modelRequestStartedAt = now;
125
+ entry.lastProgressAt = now;
126
+ entry.updatedAt = now;
127
+ // Publish heartbeat immediately so the status aggregator picks the
128
+ // session up in the connecting / requesting window. Without this the
129
+ // .hb file only landed on the first stream chunk — producing a 3–10s
130
+ // (xhigh: 30s+) invisible gap where agent sessions ran but the CC
131
+ // statusline showed no maintenance/agent badge. STREAM_FRESH_MS (5 min)
132
+ // still drops a session whose provider truly never returns a chunk;
133
+ // markSessionStreamDelta keeps refreshing once chunks arrive.
134
+ publishHeartbeat(id, now);
135
+ }
136
+ export async function markSessionStreamDelta(id) {
137
+ if (!id) return;
138
+ // Non-creating lookup: a live ask ALWAYS has a runtime entry (markSessionAskStart
139
+ // creates it before streaming begins). _touchRuntime would instead resurrect a
140
+ // blank entry — and closeSession()/idle-sweep clear _runtimeState on a deferred
141
+ // tick while a detached provider stream may still be trickling deltas. A delta
142
+ // arriving after that clear must NOT re-create an entry or it would republish the
143
+ // .hb heartbeat that markSessionClosed deleted, orphaning a dead session's
144
+ // heartbeat indefinitely (the disk tombstone blocks ask resumption but not this
145
+ // path). Skip a missing, tombstoned, or aborted entry — never refresh liveness.
146
+ const entry = _runtimeState.get(id);
147
+ if (!entry || entry.closed || entry.controller?.signal?.aborted) return;
148
+ _stopToolActivityHeartbeat(id);
149
+ const now = Date.now();
150
+ entry.lastStreamDeltaAt = now;
151
+ entry.lastProgressAt = now;
152
+ // Only promote to 'streaming' if we were in a pre-stream stage; never downgrade
153
+ // mid-tool (tool_running has its own delta source if the tool streams back).
154
+ if (entry.stage === 'connecting' || entry.stage === 'requesting') {
155
+ entry.stage = 'streaming';
156
+ }
157
+ // Lightweight heartbeat (≤5s self-throttled) for the status aggregator.
158
+ // Disk-side session.lastHeartbeatAt below is the heavy 60s zombie-reaper
159
+ // signal; the .hb file is the fast fresh-session signal consumed by the
160
+ // status line.
161
+ publishHeartbeat(id, now);
162
+ const session = entry.session;
163
+ if (session && now - (session.lastHeartbeatAt || 0) > HEARTBEAT_THROTTLE_MS) {
164
+ session.lastHeartbeatAt = now;
165
+ await _deps.saveSessionAsync(session, { expectedGeneration: session.generation });
166
+ }
167
+ entry.updatedAt = now;
168
+ }
169
+ export function markSessionToolCall(id, toolName) {
170
+ if (!id) return;
171
+ const entry = _touchRuntime(id);
172
+ entry.stage = 'tool_running';
173
+ entry.lastToolCall = toolName || null;
174
+ entry.toolStartedAt = Date.now();
175
+ entry.lastProgressAt = entry.toolStartedAt;
176
+ entry.updatedAt = entry.toolStartedAt;
177
+ publishHeartbeat(id, entry.toolStartedAt);
178
+ _startToolActivityHeartbeat(id);
179
+ }
180
+ // Parent AbortSignal listeners are dropped on askSession unwind (finally /
181
+ // terminal return) and on error/cancel/close — not in markSessionDone, which
182
+ // also runs between queued follow-up turns within one ask.
183
+ export function markSessionDone(id, { empty = false } = {}) {
184
+ if (!id) return;
185
+ _stopToolActivityHeartbeat(id);
186
+ const entry = _touchRuntime(id);
187
+ entry.stage = 'done';
188
+ entry.lastError = null;
189
+ entry.askStartedAt = null;
190
+ entry.toolStartedAt = null;
191
+ // Non-empty completion: drop any stale empty-final flag so a subsequent
192
+ // ask on the same reusable runtime entry starts clean. Empty-final
193
+ // completions preserve the flag (set by markSessionEmptyFinal just prior).
194
+ if (!empty) {
195
+ entry.emptyFinal = false;
196
+ entry.emptyFinalAt = null;
197
+ }
198
+ const doneTs = Date.now();
199
+ entry.doneAt = doneTs;
200
+ entry.lastProgressAt = doneTs;
201
+ entry.updatedAt = doneTs;
202
+ // Terminal stage — drop the heartbeat so the status badge releases
203
+ // immediately. A subsequent ask on the same session re-publishes via
204
+ // markSessionStreamDelta on the first chunk.
205
+ deleteHeartbeat(id);
206
+ }
207
+ // Tag a session as having completed with empty final synthesis (no
208
+ // content/reasoning). Distinct from `markSessionDone`: still a success
209
+ // (no abort), but the stall watchdog and post-mortem tools can
210
+ // distinguish "finished empty" from "finished with content" without
211
+ // mistaking the silence for a stall.
212
+ export function markSessionEmptyFinal(id) {
213
+ if (!id) return;
214
+ const entry = _touchRuntime(id);
215
+ entry.emptyFinal = true;
216
+ entry.emptyFinalAt = Date.now();
217
+ }
218
+ export function markSessionError(id, msg) {
219
+ if (!id) return;
220
+ _stopToolActivityHeartbeat(id);
221
+ const entry = _touchRuntime(id);
222
+ entry.stage = 'error';
223
+ entry.lastError = msg ? String(msg).slice(0, 200) : null;
224
+ entry.askStartedAt = null;
225
+ entry.toolStartedAt = null;
226
+ // Error path is a non-empty completion (we have an error message, not a
227
+ // silent empty final). Clear the flag so the next ask starts clean.
228
+ entry.emptyFinal = false;
229
+ entry.emptyFinalAt = null;
230
+ const errTs = Date.now();
231
+ entry.doneAt = errTs;
232
+ entry.lastProgressAt = errTs;
233
+ entry.updatedAt = errTs;
234
+ deleteHeartbeat(id);
235
+ _unlinkParentAbortListener(entry);
236
+ }
237
+ export function markSessionCancelled(id) {
238
+ if (!id) return;
239
+ _stopToolActivityHeartbeat(id);
240
+ const entry = _touchRuntime(id);
241
+ entry.stage = 'done';
242
+ entry.lastError = null;
243
+ entry.askStartedAt = null;
244
+ entry.toolStartedAt = null;
245
+ entry.emptyFinal = false;
246
+ entry.emptyFinalAt = null;
247
+ const doneTs = Date.now();
248
+ entry.doneAt = doneTs;
249
+ entry.lastProgressAt = doneTs;
250
+ entry.updatedAt = doneTs;
251
+ deleteHeartbeat(id);
252
+ _unlinkParentAbortListener(entry);
253
+ }
254
+ export function getSessionRuntime(id) {
255
+ return id ? (_runtimeState.get(id) || null) : null;
256
+ }
257
+
258
+ const _COMPACTION_BLOCKED_STAGES = new Set([
259
+ 'connecting', 'requesting', 'streaming', 'tool_running', 'cancelling',
260
+ ]);
261
+
262
+ export function isSessionCompactionBlocked(sessionId) {
263
+ if (!sessionId) return false;
264
+ const entry = _runtimeState.get(sessionId);
265
+ if (!entry || entry.closed === true) return false;
266
+ if (entry.controller && !entry.controller.signal?.aborted) return true;
267
+ return _COMPACTION_BLOCKED_STAGES.has(entry.stage);
268
+ }
269
+
270
+ export function getSessionProgressSnapshot(sessionId) {
271
+ const entry = _runtimeState.get(sessionId);
272
+ if (!entry) return null;
273
+ const askStartedAt = entry.askStartedAt || 0;
274
+ const modelRequestStartedAt = entry.modelRequestStartedAt || askStartedAt;
275
+ const firstActivityAt = Math.max(
276
+ entry.lastStreamDeltaAt || 0,
277
+ entry.toolStartedAt || 0,
278
+ );
279
+ const stage = entry.stage || 'idle';
280
+ const waitingForFirstActivity = Boolean(
281
+ modelRequestStartedAt
282
+ && (stage === 'connecting' || stage === 'requesting')
283
+ && firstActivityAt <= modelRequestStartedAt
284
+ );
285
+ return {
286
+ stage,
287
+ askStartedAt,
288
+ modelRequestStartedAt,
289
+ firstActivityAt,
290
+ lastStreamDeltaAt: entry.lastStreamDeltaAt || 0,
291
+ toolStartedAt: entry.toolStartedAt || 0,
292
+ lastProgressAt: entry.lastProgressAt || 0,
293
+ updatedAt: entry.updatedAt || 0,
294
+ hasFirstActivity: Boolean(firstActivityAt && (!askStartedAt || firstActivityAt >= askStartedAt)),
295
+ waitingForFirstActivity,
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Iterate all active session runtimes. Used by the stream watchdog.
301
+ * Returns an iterable of [sessionId, entry] pairs; consumers should
302
+ * treat entries as read-only snapshots and avoid mutating them.
303
+ */
304
+ export function forEachSessionRuntime() {
305
+ return _runtimeState.entries();
306
+ }
307
+
308
+ // Wire the usage-metrics runtime accessor to this module's _runtimeState so
309
+ // persistIterationMetrics can read the live in-memory session and flag
310
+ // usageMetricsTurnIncremental. (Moved with the map from manager.mjs.)
311
+ configureUsageMetricsRuntime({ getRuntimeEntry: (id) => _runtimeState.get(id) });
312
+
313
+ /** Mark session hidden so listSessions() filters it out (runtime-only). */
314
+ export function hideSessionFromList(sessionId) {
315
+ if (!sessionId) return;
316
+ const entry = _runtimeState.get(sessionId);
317
+ if (entry) entry.listHidden = true;
318
+ }
319
+
320
+ export function getSessionAbortSignal(sessionId) {
321
+ return _runtimeState.get(sessionId)?.controller?.signal ?? null;
322
+ }
323
+
324
+ /**
325
+ * Return the most recent "session is making progress" timestamp.
326
+ *
327
+ * Combines three independent progress signals so an idle watchdog can stay
328
+ * alive across both streaming and long tool calls:
329
+ * - lastStreamDeltaAt: provider stream chunk landed
330
+ * - toolStartedAt: a tool call just kicked off (nested tool work may
331
+ * stall the outer stream for a while; this keeps the watchdog from
332
+ * killing legitimate sub-agent runs)
333
+ * - askStartedAt: ask just started; covers the pre-stream connect window
334
+ *
335
+ * Returns 0 when the runtime entry is unknown so callers can decide to
336
+ * either skip the watchdog or treat 0 as "no progress yet".
337
+ */
338
+ export function getSessionLastProgressAt(sessionId) {
339
+ const entry = _runtimeState.get(sessionId);
340
+ if (!entry) return 0;
341
+ return Math.max(
342
+ entry.lastProgressAt || 0,
343
+ entry.lastStreamDeltaAt || 0,
344
+ entry.toolStartedAt || 0,
345
+ entry.askStartedAt || 0,
346
+ );
347
+ }
348
+
349
+ /**
350
+ * Link a parent AbortSignal to a sub-session's controller so that aborting
351
+ * the parent (fan-out deadline or caller ESC) tears down the agent role's
352
+ * provider call promptly. Safe to call after prepareAgentSession but before
353
+ * askSession completes. No-op if the session runtime isn't found.
354
+ *
355
+ * @param {string} sessionId — the sub-session to abort
356
+ * @param {AbortSignal} parentSignal — upstream signal (from fan-out coordinator)
357
+ */
358
+ export function linkParentSignalToSession(sessionId, parentSignal) {
359
+ if (!(parentSignal instanceof AbortSignal)) return;
360
+ const entry = _touchRuntime(sessionId);
361
+ if (!entry.controller) entry.controller = createAbortController();
362
+ const abortReason = () => {
363
+ const reason = parentSignal.reason;
364
+ if (reason instanceof Error) return reason;
365
+ if (reason !== undefined && reason !== null && reason !== '') return new Error(String(reason));
366
+ return new Error('parent signal aborted');
367
+ };
368
+ if (parentSignal.aborted) {
369
+ _unlinkParentAbortListener(entry);
370
+ try { entry.controller.abort(abortReason()); } catch { /* ignore */ }
371
+ return;
372
+ }
373
+ _unlinkParentAbortListener(entry);
374
+ const onParentAbort = () => {
375
+ try { entry.controller?.abort(abortReason()); } catch { /* ignore */ }
376
+ };
377
+ parentSignal.addEventListener('abort', onParentAbort, { once: true });
378
+ entry.parentAbortLink = { signal: parentSignal, listener: onParentAbort };
379
+ }
380
+ export function _unlinkParentAbortListener(entry) {
381
+ const link = entry?.parentAbortLink;
382
+ if (!link) return;
383
+ try { link.signal.removeEventListener('abort', link.listener); } catch { /* ignore */ }
384
+ entry.parentAbortLink = null;
385
+ }
386
+ export function _clearSessionRuntime(id) {
387
+ if (id) {
388
+ _stopToolActivityHeartbeat(id);
389
+ _unlinkParentAbortListener(_runtimeState.get(id));
390
+ _runtimeState.delete(id);
391
+ // R15: also drop the per-session metric-idempotency Set; otherwise it
392
+ // grows O(sessions x iterations) for the whole server lifetime since
393
+ // nothing else deletes from _metricSeenIter on session close.
394
+ dropMetricSeenState(id);
395
+ }
396
+ }
397
+
398
+ // Direct-Map accessors for manager.mjs internals (askSession / closeSession /
399
+ // idle-sweep) that mutate entries in place. Kept as thin exports so the Map
400
+ // stays private to this module.
401
+ export function _getRuntimeEntry(id) {
402
+ return _runtimeState.get(id);
403
+ }
404
+ export function _runtimeEntries() {
405
+ return _runtimeState.entries();
406
+ }
@@ -0,0 +1,80 @@
1
+ // Standalone-status route info + gateway usage telemetry for sessions.
2
+ // Extracted verbatim from manager.mjs (behavior-preserving). Fire-and-forget
3
+ // statusline telemetry — must never affect the model turn.
4
+ import { getProvider } from '../../providers/registry.mjs';
5
+ import { fetchOAuthUsageSnapshot } from '../../providers/oauth-usage.mjs';
6
+ import {
7
+ buildGatewayLimits,
8
+ recordGatewayUsageEvent,
9
+ summarizeGatewayUsage,
10
+ } from '../../providers/statusline-route-meta.mjs';
11
+ import { estimateTranscriptContextUsage } from '../context-utils.mjs';
12
+
13
+ export function standaloneStatusRouteInfo(session) {
14
+ if (!session) return null;
15
+ // autoCompactTokenLimit is an EXPLICIT sub-boundary auto-compact limit only.
16
+ // Do NOT fall back to compactBoundaryTokens/contextWindow here — that
17
+ // labels a derived full-window value as an explicit limit, which the
18
+ // runtime would treat as the compaction trigger and collapse the buffer.
19
+ // The boundary/window stays available via contextWindow/rawContextWindow.
20
+ const _boundary = Number(session.compactBoundaryTokens || session.contextWindow || 0);
21
+ const _limit = Number(session.autoCompactTokenLimit || 0);
22
+ const explicitAutoCompactTokenLimit = _limit > 0 && (!_boundary || _limit < _boundary) ? _limit : null;
23
+ return {
24
+ provider: session.provider,
25
+ model: session.model,
26
+ modelDisplay: session.modelDisplay || session.displayName || session.model,
27
+ effort: session.effort || '',
28
+ fast: session.fast === true,
29
+ contextWindow: session.contextWindow || null,
30
+ rawContextWindow: session.rawContextWindow || session.contextWindow || null,
31
+ effectiveContextWindowPercent: session.effectiveContextWindowPercent || null,
32
+ autoCompactTokenLimit: explicitAutoCompactTokenLimit,
33
+ presetId: session.presetId || null,
34
+ presetName: session.presetName || null,
35
+ };
36
+ }
37
+
38
+ export function recordStandaloneStatusTelemetry(session, result, durationMs) {
39
+ if (!session || !result?.usage) return;
40
+ const routeInfo = standaloneStatusRouteInfo(session);
41
+ if (!routeInfo?.provider || !routeInfo?.model) return;
42
+ const providerOut = {
43
+ usage: result.usage,
44
+ model: result.model,
45
+ serviceTier: result.serviceTier,
46
+ };
47
+ // The transcript estimate is the SSOT for the displayed context footprint.
48
+ // agentLoop()'s result has no `compact` field, so build a synthetic compact
49
+ // arg carrying the live monotonic estimate (estimateMessagesTokens+reserve)
50
+ // as afterTokens. This lights up summarizeGatewayUsage's estimate-based
51
+ // contextUsedPct branch (provider input_tokens swing wildly / unbounded on
52
+ // e.g. OpenAI gpt-5.5), and lets a genuine >100% pass through.
53
+ const _estTokens = estimateTranscriptContextUsage(session.messages, session.tools || []);
54
+ const _compactArg = { ...(result.compact && typeof result.compact === 'object' ? result.compact : {}), afterTokens: _estTokens };
55
+ try {
56
+ const summary = {
57
+ ...summarizeGatewayUsage(routeInfo, providerOut, _compactArg, durationMs),
58
+ requestKind: 'chat',
59
+ sessionId: session.id || null,
60
+ toolCount: result.toolCallsTotal ?? null,
61
+ messageCount: Array.isArray(session.messages) ? session.messages.length : null,
62
+ cacheStrategy: session.providerCacheOpts?.cacheStrategy || null,
63
+ };
64
+ recordGatewayUsageEvent(summary);
65
+ } catch {
66
+ // Statusline telemetry must never affect the model turn.
67
+ }
68
+
69
+ const provider = getProvider(routeInfo.provider);
70
+ if (!provider) return;
71
+ fetchOAuthUsageSnapshot(routeInfo, provider, (message) => {
72
+ if (process.env.MIXDOG_STATUSLINE_TRACE) {
73
+ process.stderr.write(`[statusline] ${message}\n`);
74
+ }
75
+ })
76
+ .then((snapshot) => {
77
+ try { buildGatewayLimits(routeInfo, providerOut, snapshot); } catch {}
78
+ })
79
+ .catch(() => {});
80
+ }