mixdog 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -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,
@@ -197,6 +173,16 @@ function resolveContextUsedPct({
197
173
  autoCompactTokenLimit,
198
174
  compact,
199
175
  });
176
+ // Trigger-as-denominator: when a sub-boundary compaction trigger
177
+ // (boundary - buffer) is known, context % is measured against IT so the
178
+ // gauge reads 100% exactly when auto-compact fires instead of stalling at
179
+ // ~90% of the boundary window. The gateway's own pct is computed against
180
+ // the boundary denominator, so it is bypassed in that case.
181
+ const trigger = num(autoCompactTokenLimit);
182
+ const triggerDenominator = trigger > 0 && (!(boundary > 0) || trigger < boundary);
183
+ if (triggerDenominator && numerator > 0) {
184
+ return (numerator / trigger) * 100;
185
+ }
200
186
  const gatewayRawPct = gatewayStatus?.contextUsedPct;
201
187
  if (
202
188
  gatewayStatus
@@ -210,88 +196,6 @@ function resolveContextUsedPct({
210
196
  return 0;
211
197
  }
212
198
 
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
199
  /**
296
200
  * Render the L1/L2 statusline footer from CLI state.
297
201
  *
@@ -380,9 +284,23 @@ function renderNativeStatusline({
380
284
  const quotaStatus = usageReady
381
285
  ? mergeQuotaStatus(gatewayStatus, fallbackQuotaStatus({ provider, model }))
382
286
  : null;
383
- const quotaSegments = quotaStatus
287
+ let quotaSegments = quotaStatus
384
288
  ? formatGatewayLimitSegments(quotaStatus, { COLS: cols, D, R, GRN, YLW, RED, colourPct, epochMsToHHMM })
385
289
  : [];
290
+ // Only apply the hold to providers that actually armed the OAuth boot latch.
291
+ // oauthUsageSegmentReady() also returns true for non-OAuth providers (they
292
+ // are never gated), so gate the hold itself on _oauthUsageArmedProviders to
293
+ // keep non-OAuth empty/null behavior byte-for-byte unchanged.
294
+ const normalizedHoldProvider = String(provider || '').trim().toLowerCase();
295
+ if (usageReady && _oauthUsageArmedProviders.has(normalizedHoldProvider)) {
296
+ const holdKey = quotaSegmentsHoldKey({ provider, model, effort, fast, sessionId, clientHostPid });
297
+ if (quotaSegments.length) {
298
+ rememberNonEmptyQuotaSegments(holdKey, quotaSegments);
299
+ } else {
300
+ const held = _lastNonEmptyQuotaSegmentsByKey.get(holdKey);
301
+ if (held && held.length) quotaSegments = held;
302
+ }
303
+ }
386
304
  for (const seg of quotaSegments) addL1(seg);
387
305
 
388
306
  const agentPayload = agentStatuslinePayload([
@@ -440,15 +358,6 @@ function renderNativeStatusline({
440
358
  return l2 ? `${l1}\n${l2}` : l1;
441
359
  }
442
360
 
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
361
  function loadGatewayQuotaStatus({
453
362
  provider, model, effort, fast, contextWindow, rawContextWindow, autoCompactTokenLimit = 0,
454
363
  sessionId, activeContextTokens, clientHostPid,
@@ -615,317 +524,6 @@ function mergeQuotaStatus(primary, fallback) {
615
524
  };
616
525
  }
617
526
 
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
527
  /** Minimal one-line footer used when the vendored renderer is unavailable. */
930
528
  export function fallbackStatusline({
931
529
  provider = '', model = '', effort = '', fast = false, cwd = '', stats, contextWindow = 0,
@@ -962,25 +560,3 @@ function fallbackLine({
962
560
  if (!parts.length) return statusSubtle('> mixdog');
963
561
  return parts.join(sep);
964
562
  }
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
  /**