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
@@ -9,47 +9,28 @@
9
9
  * reused as read-only data sources, but display identity always belongs to the
10
10
  * CLI route passed here.
11
11
  */
12
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
13
- import { homedir } from 'node:os';
14
- import { join } from 'node:path';
15
- import { bold, colorEnabled, rgb } from './ansi.mjs';
16
- import { displayModelName, shortenModelName } from './model-display.mjs';
17
- import { createSessionStats } from './session-stats.mjs';
18
- import { forEachSessionRuntime } from '../runtime/agent/orchestrator/session/manager.mjs';
19
- import { listHiddenAgentNames } from '../runtime/agent/orchestrator/internal-agents.mjs';
20
- import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
21
12
  import { readCachedOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
22
13
  import { readCachedOpenCodeGoUsageSnapshot } from '../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
23
14
  import { buildGatewayLimits } from '../runtime/agent/orchestrator/providers/statusline-route-meta.mjs';
24
15
  import { compactBoundaryForStatus, formatGatewayLimitSegments, loadGatewayStatus } from '../vendor/statusline/bin/statusline-route.mjs';
16
+ import { createSessionStats } from './session-stats.mjs';
17
+ import {
18
+ FALLBACK_CONTEXT_WINDOW, statusSubtle,
19
+ R, B, D, GRN, YLW, RED,
20
+ terminalColumns, modelContextWindow, formatModelSegment,
21
+ formatContextSegment, colourPct, epochMsToHHMM,
22
+ num, formatElapsed,
23
+ } from './statusline-format.mjs';
24
+ import { shellJobsStatus, memoryCycleStatus } from './statusline-segments.mjs';
25
+ import {
26
+ summarizeWorkerTags, agentStatuslinePayload, classifyAgentWorkers, activeHiddenAgentWorkers,
27
+ } from './statusline-agents.mjs';
25
28
  export { createSessionStats, applyUsageDelta } from './session-stats.mjs';
29
+ // Facade re-exports: keep these public symbols resolving from statusline.mjs.
30
+ export { contextPctDisplayLabel } from './statusline-format.mjs';
26
31
 
27
- // Token window used to compute a fallback context% from our own session usage.
28
- // The live gateway (when up) overrides this with the real route's window. This
29
- // is only the last resort for unknown local models.
30
- const FALLBACK_CONTEXT_WINDOW = 200000;
31
- const statusText = rgb(198, 198, 198);
32
- const statusSubtle = rgb(136, 136, 136);
33
- const statusAccent = rgb(215, 119, 87);
34
- const DEFAULT_MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
35
- const DEFAULT_STANDALONE_DATA_DIR = join(DEFAULT_MIXDOG_HOME, 'data');
36
-
37
- function sgr(code) {
38
- return colorEnabled() ? `\x1b[${code}m` : '';
39
- }
40
-
41
- const R = sgr('0');
42
- const B = sgr('1');
43
- const D = sgr('38;2;136;136;136');
44
- const GRN = sgr('38;2;0;170;75');
45
- const YLW = sgr('38;2;255;193;7');
46
- const RED = sgr('38;2;220;70;88');
47
- const CYN = sgr('38;2;136;136;136');
48
- const GREY = sgr('38;2;136;136;136');
49
- const SHELL_JOBS_SEGMENT_CACHE_MS = 1000;
50
32
  const GATEWAY_QUOTA_STATUS_CACHE_MS = 500;
51
33
  const WORKER_SPINNER_FRAMES = Object.freeze(['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']);
52
- const WORKER_SPINNER_FRAME_MS = 160;
53
34
  // L2 segment spinner: reuses the original WORKER_SPINNER_FRAMES dot glyphs (no
54
35
  // separate glyph list) but spins them at a faster 120ms step than the worker
55
36
  // spinner's 160ms. l2SpinnerFrame() indexes straight into WORKER_SPINNER_FRAMES.
@@ -61,11 +42,46 @@ const L2_SPINNER_FRAME_MS = 120;
61
42
  // replaces them. Snapshots from a previous launch stay hidden during boot so the
62
43
  // statusline starts empty until the current session captures usage once.
63
44
  const STATUSLINE_PROCESS_STARTED_AT_MS = Date.now() - Math.floor((Number(process.uptime?.()) || 0) * 1000);
64
- const DEFAULT_HIDDEN_STATUSLINE_AGENTS = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
65
- let _shellJobsSegmentCache = { ownerPid: 0, at: 0, value: { count: 0, elapsedLabel: '' } };
66
45
  let _gatewayQuotaStatusCache = { key: '', at: 0, value: null };
67
46
  let _fallbackQuotaStatusCache = { key: '', at: 0, value: null };
68
- let _hiddenStatuslineAgents = null;
47
+ // Holds the last non-empty rendered L1 quota/usage segments per provider+route
48
+ // key, but ONLY for providers that have actually armed the OAuth boot latch
49
+ // (_oauthUsageArmedProviders) — non-OAuth providers (which oauthUsageSegmentReady
50
+ // also returns true for, since they are never gated) must keep their exact
51
+ // prior empty/null behavior, unaffected by this hold. Once armed and at least
52
+ // one non-empty quota segment set has been rendered for a route, a transient
53
+ // resolution failure (cache miss / gateway hiccup) that would otherwise
54
+ // collapse quotaSegments to [] instead re-uses the last held segments, so the
55
+ // L1 segment does not blink out. Replaced only when a newer non-empty result
56
+ // lands for the SAME key. The key includes sessionId + clientHostPid because
57
+ // rendered segments can embed session-scoped routeSpend — without those, a new
58
+ // session sharing provider/model/effort/fast could reuse a stale prior
59
+ // session's spend. Capped at a small LRU size so long-running processes that
60
+ // cycle through many sessions/routes don't grow this map unbounded.
61
+ const _lastNonEmptyQuotaSegmentsByKey = new Map();
62
+ const LAST_NON_EMPTY_QUOTA_SEGMENTS_CACHE_MAX = 8;
63
+
64
+ function quotaSegmentsHoldKey({ provider, model, effort, fast, sessionId, clientHostPid } = {}) {
65
+ return [
66
+ String(provider || '').trim().toLowerCase(),
67
+ String(model || '').trim(),
68
+ String(effort || '').trim(),
69
+ fast === true ? 'fast' : '',
70
+ String(sessionId || ''),
71
+ String(clientHostPid || ''),
72
+ ].join('\0');
73
+ }
74
+
75
+ function rememberNonEmptyQuotaSegments(key, segments) {
76
+ // Delete-then-set to bump this key to most-recently-used position (Map
77
+ // iterates in insertion order), then evict the oldest entry if over cap.
78
+ _lastNonEmptyQuotaSegmentsByKey.delete(key);
79
+ _lastNonEmptyQuotaSegmentsByKey.set(key, segments);
80
+ if (_lastNonEmptyQuotaSegmentsByKey.size > LAST_NON_EMPTY_QUOTA_SEGMENTS_CACHE_MAX) {
81
+ const oldestKey = _lastNonEmptyQuotaSegmentsByKey.keys().next().value;
82
+ _lastNonEmptyQuotaSegmentsByKey.delete(oldestKey);
83
+ }
84
+ }
69
85
  // Option A boot gate: the L1 usage/quota segment stays fully empty until THIS
70
86
  // process has captured its FIRST confirmed (current-process) OAuth usage
71
87
  // snapshot for THAT provider. The latch is monotonic PER PROVIDER — once a
@@ -85,19 +101,6 @@ function isConfirmedCurrentProcessSnapshot(snapshot) {
85
101
  return cachedAt > 0 && cachedAt >= STATUSLINE_PROCESS_STARTED_AT_MS;
86
102
  }
87
103
 
88
- function summarizeWorkerTags(workers, limit = 3) {
89
- const cleanLabels = [...new Set((Array.isArray(workers) ? workers : [])
90
- .map((worker) => String(worker?.tag || '').trim())
91
- .filter(Boolean))];
92
- if (cleanLabels.length <= limit) return cleanLabels.join(', ');
93
- return `${cleanLabels.slice(0, limit).join(', ')}, +${cleanLabels.length - limit}`;
94
- }
95
-
96
- function workerSpinnerFrame(now = Date.now()) {
97
- const index = Math.floor(now / WORKER_SPINNER_FRAME_MS) % WORKER_SPINNER_FRAMES.length;
98
- return WORKER_SPINNER_FRAMES[index] || WORKER_SPINNER_FRAMES[0];
99
- }
100
-
101
104
  function l2SpinnerFrame(now = Date.now()) {
102
105
  const index = Math.floor(now / L2_SPINNER_FRAME_MS) % WORKER_SPINNER_FRAMES.length;
103
106
  return WORKER_SPINNER_FRAMES[index] || WORKER_SPINNER_FRAMES[0];
@@ -114,21 +117,6 @@ function l2SpinnerFrame(now = Date.now()) {
114
117
  * model / context% / effort / 5H-7D are overridden by the live gateway via
115
118
  * loadGatewayStatus() when it's running; these are the standalone fallbacks.
116
119
  */
117
- function providerInputExcludesCache(provider) {
118
- return String(provider || '').toLowerCase().includes('anthropic');
119
- }
120
-
121
- function promptFootprintTokens(provider, stats) {
122
- const s = stats || createSessionStats();
123
- const explicit = num(s.promptTokens);
124
- if (explicit > 0) return explicit;
125
- const input = num(s.inputTokens);
126
- if (providerInputExcludesCache(provider)) {
127
- return input + num(s.cachedTokens) + num(s.cacheWriteTokens);
128
- }
129
- return input;
130
- }
131
-
132
120
  function activeContextNumerator(provider, stats) {
133
121
  const s = stats || createSessionStats();
134
122
  const source = String(s.currentContextSource || '').toLowerCase();
@@ -144,18 +132,6 @@ function activeContextNumerator(provider, stats) {
144
132
  return 0;
145
133
  }
146
134
 
147
- function currentContextTokens(provider, stats) {
148
- return activeContextNumerator(provider, stats);
149
- }
150
-
151
- function modelContextWindow(provider, model, explicitContextWindow = 0) {
152
- const explicit = num(explicitContextWindow);
153
- if (explicit > 0) return explicit;
154
- const metaWindow = num(getModelMetadataSync(model, provider)?.contextWindow);
155
- if (metaWindow > 0) return metaWindow;
156
- return FALLBACK_CONTEXT_WINDOW;
157
- }
158
-
159
135
  function displayContextBoundary({
160
136
  contextWindow = 0,
161
137
  displayContextWindow = 0,
@@ -210,88 +186,6 @@ function resolveContextUsedPct({
210
186
  return 0;
211
187
  }
212
188
 
213
- function normalizeAgentWorkerForStatusline(worker = {}) {
214
- const tag = String(worker.tag || worker.agent || worker.name || '').trim();
215
- if (!tag) return null;
216
- const statusText = String(worker.stage || worker.status || '').toLowerCase();
217
- const status = isTerminalBridgeStatus(statusText) ? 'idle' : 'running';
218
- return {
219
- tag,
220
- status,
221
- startedAtMs: timeMs(worker.startedAt || worker.startTime || worker.createdAt),
222
- agent: worker.agent || null,
223
- stage: worker.stage || worker.status || null,
224
- sessionId: worker.sessionId || null,
225
- provider: worker.provider || null,
226
- model: worker.model || null,
227
- };
228
- }
229
-
230
- function normalizeAgentJobForStatusline(job = {}) {
231
- const statusText = String(job.status || job.stage || '').toLowerCase();
232
- if (!statusText) return null;
233
- const taskId = String(job.task_id || job.taskId || '').trim();
234
- const tag = String(job.tag || job.agent || job.type || taskId || '').trim();
235
- if (!tag && !taskId) return null;
236
- const startedAtMs = timeMs(job.startedAt);
237
- const finishedAtMs = timeMs(job.finishedAt);
238
- if (isTerminalBridgeStatus(statusText) && finishedAtMs > 0) {
239
- return {
240
- tag,
241
- taskId,
242
- status: 'finished',
243
- finalStatus: statusText,
244
- startedAtMs,
245
- finishedAtMs,
246
- agent: job.agent || null,
247
- stage: job.stage || job.workerStatus || job.status || null,
248
- sessionId: job.sessionId || null,
249
- provider: job.provider || null,
250
- model: job.model || null,
251
- };
252
- }
253
- if (!/running/.test(statusText)) return null;
254
- return {
255
- tag,
256
- taskId,
257
- status: 'running',
258
- startedAtMs,
259
- agent: job.agent || null,
260
- stage: job.stage || job.workerStatus || job.status || null,
261
- sessionId: job.sessionId || null,
262
- provider: job.provider || null,
263
- model: job.model || null,
264
- };
265
- }
266
-
267
- function agentStatuslinePayload(agentWorkers = [], agentJobs = []) {
268
- const byTag = new Map();
269
- const finishedJobs = [];
270
- for (const worker of Array.isArray(agentWorkers) ? agentWorkers : []) {
271
- const row = normalizeAgentWorkerForStatusline(worker);
272
- if (row) byTag.set(row.tag, row);
273
- }
274
- for (const job of Array.isArray(agentJobs) ? agentJobs : []) {
275
- const row = normalizeAgentJobForStatusline(job);
276
- if (!row) continue;
277
- if (row.status === 'finished') {
278
- finishedJobs.push(row);
279
- continue;
280
- }
281
- const prev = byTag.get(row.tag);
282
- byTag.set(row.tag, { ...(prev || {}), ...row, status: 'running' });
283
- }
284
- const workers = [...byTag.values()];
285
- return {
286
- workers,
287
- finishedJobs: finishedJobs.sort((a, b) => (b.finishedAtMs || 0) - (a.finishedAtMs || 0)),
288
- sessions: {
289
- roles: workers.filter((w) => w.status !== 'idle').map((w) => w.tag),
290
- workers,
291
- },
292
- };
293
- }
294
-
295
189
  /**
296
190
  * Render the L1/L2 statusline footer from CLI state.
297
191
  *
@@ -380,9 +274,23 @@ function renderNativeStatusline({
380
274
  const quotaStatus = usageReady
381
275
  ? mergeQuotaStatus(gatewayStatus, fallbackQuotaStatus({ provider, model }))
382
276
  : null;
383
- const quotaSegments = quotaStatus
277
+ let quotaSegments = quotaStatus
384
278
  ? formatGatewayLimitSegments(quotaStatus, { COLS: cols, D, R, GRN, YLW, RED, colourPct, epochMsToHHMM })
385
279
  : [];
280
+ // Only apply the hold to providers that actually armed the OAuth boot latch.
281
+ // oauthUsageSegmentReady() also returns true for non-OAuth providers (they
282
+ // are never gated), so gate the hold itself on _oauthUsageArmedProviders to
283
+ // keep non-OAuth empty/null behavior byte-for-byte unchanged.
284
+ const normalizedHoldProvider = String(provider || '').trim().toLowerCase();
285
+ if (usageReady && _oauthUsageArmedProviders.has(normalizedHoldProvider)) {
286
+ const holdKey = quotaSegmentsHoldKey({ provider, model, effort, fast, sessionId, clientHostPid });
287
+ if (quotaSegments.length) {
288
+ rememberNonEmptyQuotaSegments(holdKey, quotaSegments);
289
+ } else {
290
+ const held = _lastNonEmptyQuotaSegmentsByKey.get(holdKey);
291
+ if (held && held.length) quotaSegments = held;
292
+ }
293
+ }
386
294
  for (const seg of quotaSegments) addL1(seg);
387
295
 
388
296
  const agentPayload = agentStatuslinePayload([
@@ -440,15 +348,6 @@ function renderNativeStatusline({
440
348
  return l2 ? `${l1}\n${l2}` : l1;
441
349
  }
442
350
 
443
- function terminalColumns() {
444
- const cols = Number(process.stdout?.columns);
445
- return Number.isFinite(cols) && cols > 0 ? Math.floor(cols) : 120;
446
- }
447
-
448
- function dataDir() {
449
- return process.env.MIXDOG_DATA_DIR || DEFAULT_STANDALONE_DATA_DIR;
450
- }
451
-
452
351
  function loadGatewayQuotaStatus({
453
352
  provider, model, effort, fast, contextWindow, rawContextWindow, autoCompactTokenLimit = 0,
454
353
  sessionId, activeContextTokens, clientHostPid,
@@ -615,317 +514,6 @@ function mergeQuotaStatus(primary, fallback) {
615
514
  };
616
515
  }
617
516
 
618
- function formatModelSegment({ provider, model, effort, fast, cols }) {
619
- const raw = String(model || '').trim();
620
- const meta = getModelMetadataSync(raw, provider) || {};
621
- const displayHint = String(meta.displayName || meta.display || meta.name || '').trim();
622
- const modelName = shortenModelName(displayModelName(raw, provider, displayHint), cols);
623
- const bits = [`${B}${modelName}${R}`];
624
- if (effort) bits.push(`${B}${String(effort).toUpperCase()}${R}`);
625
- if (fast === true) bits.push(`${B}FAST${R}`);
626
- return bits.join(` ${D}·${R} `);
627
- }
628
-
629
- /** Display label for context % (clamped to 100); raw pct still drives bar/color thresholds. */
630
- export function contextPctDisplayLabel(ctxPct) {
631
- const pct = Number(ctxPct);
632
- if (!Number.isFinite(pct) || pct <= 0) return '0';
633
- if (pct > 0 && pct < 1) return String(Math.round(pct * 10) / 10);
634
- return String(Math.floor(Math.min(100, pct)));
635
- }
636
-
637
- function formatContextSegment(ctxPct, cols) {
638
- const raw = Number(ctxPct);
639
- const pct = Number.isFinite(raw) ? Math.max(0, raw) : 0;
640
- const barPct = clampPct(pct);
641
- const fill = pct >= 90 ? RED : pct >= 70 ? YLW : GRN;
642
- const label = contextPctDisplayLabel(pct);
643
- // Keep a full-width bar wherever there is room for one. Below 80 cols the bar
644
- // is dropped (label-only) so the footer never overflows a narrow terminal;
645
- // at 80+ it stays a fixed 14 cells instead of shrinking to 8, which read as
646
- // too small/cramped on mid-width terminals.
647
- const cells = cols >= 80 ? 14 : 0;
648
- if (!cells) return `${fill}${label}%${R}`;
649
- const bar = makeBar(barPct, cells);
650
- const filled = bar.replace(/░/g, '');
651
- const empty = bar.replace(/▓/g, '');
652
- return `${fill}${filled}${R}${D}${empty}${R} ${label}%`;
653
- }
654
-
655
- function makeBar(pct, cells) {
656
- let filled = Math.floor((Number(pct) || 0) * cells / 100);
657
- if (filled < 0) filled = 0;
658
- if (filled > cells) filled = cells;
659
- if (pct >= 1 && filled === 0) filled = 1;
660
- return '▓'.repeat(filled) + '░'.repeat(cells - filled);
661
- }
662
-
663
- function colourPct(p) {
664
- if (p >= 90) return `${RED}${p}%${R}`;
665
- if (p >= 70) return `${YLW}${p}%${R}`;
666
- return `${GRN}${p}%${R}`;
667
- }
668
-
669
- function epochMsToHHMM(ms) {
670
- const d = new Date(Number(ms));
671
- if (!Number.isFinite(d.getTime())) return '';
672
- return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
673
- }
674
-
675
- function classifyAgentWorkers(workers = []) {
676
- const maintenance = [];
677
- const runningWorkers = [];
678
- const seenMaintenance = new Set();
679
- const seenRunning = new Set();
680
- for (const w of Array.isArray(workers) ? workers : []) {
681
- const tag = String(w?.tag || '').trim();
682
- if (!tag) continue;
683
- const maint = hiddenWorkerLabel(w);
684
- if (maint) {
685
- if (w.status !== 'idle' && !seenMaintenance.has(maint)) {
686
- seenMaintenance.add(maint);
687
- maintenance.push(`${GRN}↻${R} ${B}${maint}${R}`);
688
- }
689
- continue;
690
- }
691
- if (w.status !== 'idle' && !seenRunning.has(tag)) {
692
- seenRunning.add(tag);
693
- runningWorkers.push(w);
694
- }
695
- }
696
- return { maintenance, runningWorkers };
697
- }
698
-
699
- function hiddenWorkerLabel(worker = {}) {
700
- const agent = String(worker?.agent || '').trim();
701
- const tag = String(worker?.tag || '').trim();
702
- return maintenanceLabel(agent) || (!agent ? maintenanceLabel(tag) : '');
703
- }
704
-
705
- function hiddenStatuslineAgents() {
706
- if (_hiddenStatuslineAgents) return _hiddenStatuslineAgents;
707
- const agents = new Set(DEFAULT_HIDDEN_STATUSLINE_AGENTS);
708
- try {
709
- for (const agent of listHiddenAgentNames()) {
710
- const clean = String(agent || '').trim();
711
- if (clean) agents.add(clean);
712
- }
713
- } catch {}
714
- _hiddenStatuslineAgents = agents;
715
- return agents;
716
- }
717
-
718
- function isActiveHiddenStatus(statusText) {
719
- return /^(connecting|requesting|streaming|tool_running|running)$/i.test(String(statusText || '').trim());
720
- }
721
-
722
- function activeHiddenAgentWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
723
- const agents = hiddenStatuslineAgents();
724
- const ownerPid = positiveInt(clientHostPid);
725
- const ownerSessionId = String(sessionId || '').trim();
726
- const rows = [];
727
- try {
728
- for (const [runtimeSessionId, entry] of forEachSessionRuntime() || []) {
729
- if (!entry || entry.closed === true) continue;
730
- const session = entry.session || null;
731
- if (!session || session.closed === true) continue;
732
- const agent = String(session?.agent || '').trim();
733
- if (!agent || !agents.has(agent)) continue;
734
- const id = session?.id || runtimeSessionId || null;
735
- if (ownerSessionId && id === ownerSessionId) continue;
736
- const sessionOwnerId = String(session?.ownerSessionId || '').trim();
737
- if (sessionOwnerId && ownerSessionId && sessionOwnerId !== ownerSessionId) continue;
738
- const pid = positiveInt(session?.clientHostPid);
739
- if (ownerPid && pid && pid !== ownerPid) continue;
740
- const stage = String(entry.stage || session?.stage || session?.status || '').trim().toLowerCase();
741
- const status = String(session?.status || stage || '').trim().toLowerCase();
742
- if (!isActiveHiddenStatus(stage || status)) continue;
743
- rows.push({
744
- tag: String(session?.agentTag || `${agent}:${id || rows.length}`).trim(),
745
- agent,
746
- status: 'running',
747
- stage: stage || status || 'running',
748
- sessionId: id,
749
- provider: session?.provider || null,
750
- model: session?.model || null,
751
- });
752
- }
753
- } catch {}
754
- return rows;
755
- }
756
-
757
- function isTerminalBridgeStatus(statusText) {
758
- return /idle|done|complete|success|closed|error|fail|cancel|killed|timeout/.test(String(statusText || '').toLowerCase());
759
- }
760
-
761
- function timeMs(value) {
762
- if (typeof value === 'number' && Number.isFinite(value) && value > 0) return value;
763
- const n = Date.parse(String(value || ''));
764
- return Number.isFinite(n) ? n : 0;
765
- }
766
-
767
- function maintenanceLabel(tag) {
768
- switch (tag) {
769
- case 'cycle1-agent': return 'cycle1';
770
- case 'cycle2-agent': return 'cycle2';
771
- case 'cycle3-agent': return 'cycle3';
772
- case 'scheduler-task': return 'scheduler';
773
- case 'webhook-handler': return 'webhook';
774
- case 'explorer': return 'explorer';
775
- default: return '';
776
- }
777
- }
778
-
779
- function shellJobsStatus({ clientHostPid } = {}) {
780
- return _shellJobsStatusImpl({ clientHostPid });
781
- }
782
-
783
- // Memory cycle L2 segment source. The memory daemon writes
784
- // data/memory-cycle-state.json on every cycle start/finish (index.mjs
785
- // _writeCycleStateFile) — no HTTP call from the statusline path. Cached at
786
- // the same 1s cadence as the shell segment. Shows a single unified "Memory"
787
- // segment: running (spinner + elapsed) or backlog warning (yellow count).
788
- let _memoryCycleSegmentCache = { at: 0, value: null };
789
- const MEMORY_CYCLE_SEGMENT_CACHE_MS = 1000;
790
- const MEMORY_CYCLE_BACKLOG_WARN = 500;
791
- function memoryCycleStatus() {
792
- const now = Date.now();
793
- if (now - _memoryCycleSegmentCache.at < MEMORY_CYCLE_SEGMENT_CACHE_MS) {
794
- return _memoryCycleSegmentCache.value;
795
- }
796
- let value = null;
797
- try {
798
- const p = join(dataDir(), 'memory-cycle-state.json');
799
- if (existsSync(p)) {
800
- const state = JSON.parse(readFileSync(p, 'utf-8'));
801
- const running = state?.running || null;
802
- const backlog = state?.backlog || {};
803
- // Stale-file guard: a daemon that died mid-run leaves running set —
804
- // ignore anything not refreshed in the last 10 minutes.
805
- const fresh = Number(state?.updatedAt) > now - 10 * 60_000;
806
- if (fresh && running?.cycle && Number(running.started_at) > 0) {
807
- value = { kind: 'running', startedAt: Number(running.started_at) };
808
- } else if (fresh) {
809
- const pending = Math.max(Number(backlog?.unchunked) || 0, Number(backlog?.cycle2_pending) || 0);
810
- if (pending > MEMORY_CYCLE_BACKLOG_WARN) value = { kind: 'backlog', pending };
811
- }
812
- }
813
- } catch { value = null; }
814
- _memoryCycleSegmentCache = { at: now, value };
815
- return value;
816
- }
817
-
818
- function _shellJobsStatusImpl({ clientHostPid } = {}) {
819
- const ownerPid = positiveInt(clientHostPid);
820
- const empty = { count: 0, elapsedLabel: '' };
821
- if (!ownerPid) return empty;
822
- const now = Date.now();
823
- if (_shellJobsSegmentCache.ownerPid === ownerPid && now - _shellJobsSegmentCache.at < SHELL_JOBS_SEGMENT_CACHE_MS) {
824
- return _shellJobsSegmentCache.value || empty;
825
- }
826
- let value = empty;
827
- try {
828
- const dir = join(dataDir(), 'shell-jobs');
829
- if (!existsSync(dir)) {
830
- _shellJobsSegmentCache = { ownerPid, at: now, value };
831
- return value;
832
- }
833
- const names = readdirSync(dir);
834
- const done = new Set(names.filter((n) => n.endsWith('.done')).map((n) => n.slice(0, -5)));
835
- const ownerByJob = new Map();
836
- for (const n of names) {
837
- const i = n.lastIndexOf('.owner-');
838
- if (i > 0) {
839
- const pid = positiveInt(n.slice(i + 7));
840
- if (pid) ownerByJob.set(n.slice(0, i), pid);
841
- }
842
- }
843
- const ids = names
844
- .filter((n) => n.endsWith('.json'))
845
- .map((n) => n.slice(0, -5))
846
- .filter((id) => !done.has(id) && ownerByJob.get(id) === ownerPid)
847
- .sort((a, b) => jobStampMs(b) - jobStampMs(a))
848
- .slice(0, 30);
849
- let count = 0;
850
- let oldestMs = Infinity;
851
- for (const id of ids) {
852
- const p = join(dir, `${id}.json`);
853
- let detail;
854
- try { detail = JSON.parse(readFileSync(p, 'utf-8')); } catch { continue; }
855
- if (!isShellJobAlive(detail, p, dir, id)) continue;
856
- count++;
857
- try {
858
- const st = statSync(p);
859
- if (st.mtimeMs < oldestMs) oldestMs = st.mtimeMs;
860
- } catch {}
861
- }
862
- if (!count) {
863
- _shellJobsSegmentCache = { ownerPid, at: now, value };
864
- return value;
865
- }
866
- const elapsedLabel = Number.isFinite(oldestMs) ? formatElapsed(Date.now() - oldestMs) : '';
867
- value = { count, elapsedLabel };
868
- } catch {
869
- value = empty;
870
- }
871
- _shellJobsSegmentCache = { ownerPid, at: now, value };
872
- return value;
873
- }
874
-
875
- function isShellJobAlive(detail, detailPath, dir, id) {
876
- const pid = positiveInt(detail?.pid);
877
- if (!pid) return false;
878
- try {
879
- const st = statSync(detailPath);
880
- const timeoutMs = Number(detail?.timeoutMs);
881
- const enforced = detail?.timeoutEnforced === true || existsSync(join(dir, `${id}.enforced`));
882
- if (enforced && Number.isFinite(timeoutMs) && timeoutMs > 0 && Date.now() - st.mtimeMs > timeoutMs + 30 * 60_000) {
883
- return false;
884
- }
885
- } catch {
886
- return false;
887
- }
888
- try {
889
- process.kill(pid, 0);
890
- return true;
891
- } catch (e) {
892
- return e?.code === 'EPERM';
893
- }
894
- }
895
-
896
- function jobStampMs(id) {
897
- const m = /^job_(\d+)/.exec(String(id || ''));
898
- return m ? Number(m[1]) : 0;
899
- }
900
-
901
- function positiveInt(value) {
902
- const n = Number(value);
903
- return Number.isInteger(n) && n > 0 ? n : 0;
904
- }
905
-
906
- // Byte-identical replica of src/tui/time-format.mjs formatDuration() with
907
- // DEFAULT options (no mostSignificantOnly / hideTrailingZeros), wrapped to drop
908
- // sub-1s like formatElapsed there. Output shape: '' (<1s), `Xs`, `Xm Ys`,
909
- // `Xh Ym Zs`, `Xd Yh Zm`. statusline.mjs is a standalone UI module that should
910
- // not depend on the React/ink TUI tree, so the algorithm is replicated rather
911
- // than imported. Used for ALL L2 elapsed (Agents/Explore/Search/Shell).
912
- function formatElapsed(ms) {
913
- if (!Number.isFinite(Number(ms))) return '';
914
- const value = Math.max(0, Number(ms) || 0);
915
- if (value < 60_000) {
916
- if (value < 1_000) return '';
917
- return `${Math.floor(value / 1000)}s`;
918
- }
919
- const days = Math.floor(value / 86_400_000);
920
- const hours = Math.floor((value % 86_400_000) / 3_600_000);
921
- const minutes = Math.floor((value % 3_600_000) / 60_000);
922
- const seconds = Math.floor((value % 60_000) / 1000);
923
- if (days > 0) return `${days}d ${hours}h ${minutes}m`;
924
- if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
925
- if (minutes > 0) return `${minutes}m ${seconds}s`;
926
- return `${seconds}s`;
927
- }
928
-
929
517
  /** Minimal one-line footer used when the vendored renderer is unavailable. */
930
518
  export function fallbackStatusline({
931
519
  provider = '', model = '', effort = '', fast = false, cwd = '', stats, contextWindow = 0,
@@ -962,25 +550,3 @@ function fallbackLine({
962
550
  if (!parts.length) return statusSubtle('> mixdog');
963
551
  return parts.join(sep);
964
552
  }
965
-
966
- function num(v) {
967
- const n = Number(v);
968
- return Number.isFinite(n) ? n : 0;
969
- }
970
-
971
- function clampPct(v) {
972
- const n = Number(v);
973
- if (!Number.isFinite(n)) return 0;
974
- return Math.max(0, Math.min(100, n));
975
- }
976
-
977
- /** Human-friendly token count: 1234 -> 1.2k. */
978
- function fmt(n) {
979
- const v = Number(n) || 0;
980
- if (v < 1000) return String(Math.round(v));
981
- if (v < 1_000_000) {
982
- const k = Math.round(v / 1000);
983
- return k >= 1000 ? '1M' : `${k}k`;
984
- }
985
- return (v / 1_000_000).toFixed(1) + 'M';
986
- }
@@ -21,7 +21,6 @@ const SUMMARIZERS = {
21
21
  glob: (a) => joinMaybe(a.pattern) ?? a.path,
22
22
  grep: (a) => joinMaybe(a.pattern),
23
23
  shell: (a) => a.command,
24
- diagnostics: (a) => a.path,
25
24
  };
26
25
 
27
26
  /**
@@ -539,7 +539,14 @@ export function loadGatewayStatus(options = {}) {
539
539
  : true;
540
540
  const metricsOwnCurrentSession = !!(sessionStatus || gatewayCcSessionMatch || gatewayTranscriptMatch || activeTranscriptMatch);
541
541
  const metricsMatch = gatewayClientHostMatch && metricsOwnCurrentSession && routeMatchesConfigured;
542
- const lastUsage = gatewayClientHostMatch && (sessionStatus || gatewayCcSessionMatch || gatewayTranscriptMatch) && active.gateway_last_usage && typeof active.gateway_last_usage === 'object'
542
+ // Strong ownership = this session's own status file or an explicit session/
543
+ // transcript id match from the gateway advert. `activeTranscriptMatch` (the
544
+ // global active-instance transcript merely naming the current session) is NOT
545
+ // strong: right after /new the global record can already point at the new
546
+ // session while its stored gateway metrics (context pct, last usage) still
547
+ // belong to the previous one.
548
+ const strongSessionOwnership = !!(sessionStatus || gatewayCcSessionMatch || gatewayTranscriptMatch);
549
+ const lastUsage = gatewayClientHostMatch && strongSessionOwnership && active.gateway_last_usage && typeof active.gateway_last_usage === 'object'
543
550
  ? active.gateway_last_usage
544
551
  : null;
545
552
  const lastCompact = lastUsage?.compact && typeof lastUsage.compact === 'object'
@@ -593,7 +600,13 @@ export function loadGatewayStatus(options = {}) {
593
600
  rawContextWindow: configured?.rawContextWindow ?? num(active.gateway_raw_context_window, null),
594
601
  effectiveContextWindowPercent: configured?.effectiveContextWindowPercent ?? num(active.gateway_effective_context_window_percent, null),
595
602
  autoCompactTokenLimit: statusAutoCompactTokenLimit,
596
- contextUsedPct: metricsMatch ? recomputedContextUsedPct ?? num(active.gateway_context_used_pct, null) : null,
603
+ // Live recompute (from the caller's own context tokens) may use the weaker
604
+ // metricsMatch gate, but the STORED gateway_context_used_pct fallback needs
605
+ // strong ownership — otherwise a fresh /new session with a not-yet-updated
606
+ // active-instance record would keep showing the old session's context %.
607
+ contextUsedPct: metricsMatch
608
+ ? recomputedContextUsedPct ?? (strongSessionOwnership ? num(active.gateway_context_used_pct, null) : null)
609
+ : null,
597
610
  lastUsage,
598
611
  quotaWindows: activeQuotaWindows.length ? activeQuotaWindows : (configuredStatus?.quotaWindows || []),
599
612
  balance: metricsMatch && active.gateway_balance && typeof active.gateway_balance === 'object' ? active.gateway_balance : (configuredStatus?.balance || null),