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
@@ -13,6 +13,11 @@ export const BULLET_OPERATOR = '\u00b7'; // · - inline separator
13
13
  export const UP_ARROW = '\u2191'; // ↑ - history/scroll up
14
14
  export const DOWN_ARROW = '\u2193'; // ↓ - history/scroll down
15
15
 
16
+ // Agent surface markers — dispatch (call out) vs response (result back).
17
+ // Basic Arrows block renders reliably in Cascadia/Consolas (same block as ↑↓).
18
+ export const RIGHT_ARROW = '\u2192'; // → - agent call/dispatch marker
19
+ export const LEFT_ARROW = '\u2190'; // ← - agent response marker
20
+
16
21
  // Tool-result tree gutter. \u2514 (└, BOX DRAWINGS LIGHT UP AND RIGHT) renders
17
22
  // in virtually every monospace font; the previous \u23bf (bracket extension)
18
23
  // had no glyph in common terminal fonts (Cascadia/Consolas) and fell back to a
package/src/tui/index.jsx CHANGED
@@ -16,6 +16,7 @@ import { installProcessSignalCleanup } from '../runtime/shared/process-shutdown.
16
16
  import { touchUiHeartbeat } from '../runtime/channels/lib/runtime-paths.mjs';
17
17
  import { emitTerminalBackground, loadThemeSettingFromConfig, theme } from './theme.mjs';
18
18
  import { POP_KITTY, DISABLE_MODIFY_OTHER_KEYS } from './keyboard-protocol.mjs';
19
+ import { displayWidth } from './display-width.mjs';
19
20
 
20
21
  const TERMINAL_MODE_RESET = '\x1b[?1006l\x1b[?1005l\x1b[?1015l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?2004l\x1b[?25h';
21
22
  const TERMINAL_OSC_RESET_BG = '\x1b]111\x07';
@@ -141,6 +142,59 @@ function resolveTuiStderrLogPath() {
141
142
  || join(process.env.MIXDOG_RUNTIME_ROOT || join(tmpdir(), 'mixdog'), 'mixdog-tui.stderr.log');
142
143
  }
143
144
 
145
+ /** `rgb(r,g,b)` → truecolor SGR prefix ('' when unparsable). */
146
+ function ansiFg(rgb) {
147
+ const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ''));
148
+ return m ? `\x1b[38;2;${m[1]};${m[2]};${m[3]}m` : '';
149
+ }
150
+
151
+ /**
152
+ * Paint a static boot splash into the (already-entered) alternate screen so
153
+ * the seconds between alt-screen entry and the first ink frame are not a blank
154
+ * void. The logo is drawn at the SAME rows/columns as App.jsx's welcome banner
155
+ * (marginTop 3 + 5 logo rows + blank + subtitle), so ink's first real frame
156
+ * overprints it in place with no visible jump. Do NOT pre-paint the bottom
157
+ * picker/prompt frame here: the first ink frame may hide or move those rows
158
+ * while the initial project picker settles, and stale raw borders look like a
159
+ * broken frame. Returns { stop } — call it right before ink mounts.
160
+ */
161
+ function paintBootSplash() {
162
+ try {
163
+ const cols = Math.max(1, Number(process.stdout.columns) || 80);
164
+ const rows = Math.max(1, Number(process.stdout.rows) || 24);
165
+ const windowsLikeTerminal = process.platform === 'win32' || Boolean(process.env.WT_SESSION);
166
+ const frameCols = Math.max(1, cols - (windowsLikeTerminal ? 1 : 0));
167
+ const center = (s) => `${' '.repeat(Math.max(0, Math.floor((frameCols - displayWidth(s)) / 2)))}${s}`;
168
+ const logo = [
169
+ '███╗ ███╗██╗██╗ ██╗██████╗ ██████╗ ██████╗ ',
170
+ '████╗ ████║██║╚██╗██╔╝██╔══██╗██╔═══██╗██╔════╝ ',
171
+ '██╔████╔██║██║ ╚███╔╝ ██║ ██║██║ ██║██║ ███╗',
172
+ '██║╚██╔╝██║██║ ██╔██╗ ██║ ██║██║ ██║██║ ██║',
173
+ '██║ ╚═╝ ██║██║██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝',
174
+ ];
175
+ const textFg = ansiFg(theme.text);
176
+ const logoFg = ansiFg(theme.logo ?? theme.claude) || textFg;
177
+ const subtleFg = ansiFg(theme.inactive);
178
+ const bold = '\x1b[1m';
179
+ const reset = '\x1b[0m';
180
+ let out = '\x1b[4;1H'; // row 4 == App banner's marginTop={3}
181
+ for (let i = 0; i < logo.length; i++) {
182
+ const fg = i < 2 ? textFg : logoFg;
183
+ out += `${bold}${fg}${center(logo[i])}${reset}\r\n`;
184
+ }
185
+ out += '\r\n';
186
+ out += `${subtleFg}${center(`mixdog coding agent · ${process.cwd()}`)}${reset}`;
187
+
188
+ // Park the cursor at home so ink's first frame paints top-down over the
189
+ // splash instead of starting at the bottom row and scrolling the screen.
190
+ out += '\x1b[H';
191
+ process.stdout.write(out);
192
+
193
+ return { stop: () => {} };
194
+ } catch { /* cosmetic only — never block boot */ }
195
+ return { stop: () => {} };
196
+ }
197
+
144
198
  /** Drain stdin so queued key/mouse bytes do not leak into the shell after exit. */
145
199
  function drainStdin(stdin = process.stdin) {
146
200
  if (!stdin.isTTY) return;
@@ -277,17 +331,27 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
277
331
  try { await loadThemeSettingFromConfig(); } catch { /* default theme stays */ }
278
332
  emitTerminalBackground(theme.background);
279
333
 
334
+ // Static splash + loading spinner while createEngineSession() warms up
335
+ // (config, providers, sessions). Without this the user stares at an empty
336
+ // alt screen for the whole boot, then the UI "pops in" — the splash makes
337
+ // the first ink frame an in-place repaint instead.
338
+ const splash = paintBootSplash() || { stop: () => {} };
339
+
280
340
  let store;
281
341
  try {
282
342
  store = await createEngineSession({ provider, model, toolMode, remote });
283
343
  bootProfile('store:ready', { ms: (performance.now() - startedAt).toFixed(1) });
284
344
  } catch (error) {
345
+ splash.stop();
285
346
  restoreTerminal();
286
347
  try { process.off('exit', restoreTerminal); } catch { /* ignore */ }
287
348
  restoreStderr();
288
349
  process.stderr.write(`mixdog: ${error?.message || error}\n`);
289
350
  return 1;
290
351
  }
352
+ // Stop the spinner BEFORE ink mounts so no stray splash write can land
353
+ // between (or after) ink's first frames.
354
+ splash.stop();
291
355
 
292
356
  // Keep mouse handling app-owned by default: native terminal selections are
293
357
  // cleared by the fullscreen redraws that happen while a turn streams. Users
@@ -367,7 +431,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
367
431
  // does not exit abruptly. Explicit exits go through /exit or /quit so teardown
368
432
  // still restores the cursor, mouse mode, and alternate screen cleanly.
369
433
  try {
370
- const instance = render(<App store={store} forceOnboarding={forceOnboarding === true} />, { exitOnCtrlC: false, maxFps: 120, onRender: makeRenderProfiler() });
434
+ const instance = render(<App store={store} forceOnboarding={forceOnboarding === true} />, { exitOnCtrlC: false, maxFps: 60, onRender: makeRenderProfiler() });
371
435
  bootProfile('render:mounted', { ms: (performance.now() - startedAt).toFixed(1) });
372
436
  const { waitUntilExit } = instance;
373
437
  // [mixdog fork] Hand the ink renderer's drag-selection setter to the store so
@@ -1,4 +1,4 @@
1
- import stringWidth from 'string-width';
1
+ import { displayWidth } from './display-width.mjs';
2
2
 
3
3
  const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
4
4
 
@@ -174,7 +174,7 @@ export function caretPosition(text, offset, width, hasTrailingContent = undefine
174
174
  continue;
175
175
  }
176
176
 
177
- const segmentWidth = stringWidth(segment);
177
+ const segmentWidth = displayWidth(segment);
178
178
  if (segmentWidth === 0) continue;
179
179
 
180
180
  // Mirror ink's wrap-ansi wrapWord (wrap="hard", wordWrap:false): a glyph
@@ -722,6 +722,9 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
722
722
  return `${OSC8_OPEN(href)}${ex.link(href)}${OSC8_CLOSE}`;
723
723
  }
724
724
  case 'list':
725
+ // Honor loose lists (blank line between items in the source markdown):
726
+ // items are joined with one extra EOL so multi-line items keep the
727
+ // author's breathing room. Tight lists render adjacent as before.
725
728
  return token.items
726
729
  .map((item, index) =>
727
730
  formatToken(
@@ -733,7 +736,7 @@ export function formatToken(token, listBaseIndent = 0, orderedListNumber = null,
733
736
  depth,
734
737
  ),
735
738
  )
736
- .join('');
739
+ .join(token.loose && depth === 0 ? EOL : '');
737
740
  case 'list_item':
738
741
  return formatListItem(token, listBaseIndent, orderedListNumber, parent, depth, width);
739
742
  case 'paragraph':
@@ -42,11 +42,16 @@ export function isResetStatsState(stats) {
42
42
  /**
43
43
  * True when the footer should snap local / invalidate cached async output.
44
44
  * Keep in sync with `shouldSnapLocalStatusline` in StatusLine.jsx.
45
+ * `agentRevision` is intentionally NOT checked here: agent worker/job churn
46
+ * must only kick off the async full-render refresh (the render effect already
47
+ * depends on `agentRevision`), never snap the footer back to the usage-less
48
+ * `localBootStatusLine` before/after the first full render — that was the
49
+ * source of the boot-entry flicker. `agentRevision` still participates in
50
+ * `statuslineFooterCacheKey` below so the async cache key churns correctly.
45
51
  */
46
52
  export function statuslineFooterIdentityChanged(args, lastArgs) {
47
53
  if (!args) return false;
48
54
  if (!lastArgs) return true;
49
- if (args.agentRevision !== lastArgs.agentRevision) return true;
50
55
  if (args.sessionId !== lastArgs.sessionId) return true;
51
56
  if (args.provider !== lastArgs.provider || args.model !== lastArgs.model) return true;
52
57
  if (args.effort !== lastArgs.effort || args.fast !== lastArgs.fast) return true;
@@ -60,8 +65,11 @@ export function statuslineFooterIdentityChanged(args, lastArgs) {
60
65
 
61
66
  /**
62
67
  * Cache key for the last async full statusline (identity-changing props only).
63
- * Must stay aligned with `shouldSnapLocalStatusline` in StatusLine.jsx.
64
- * Stats reset is not keyed here; `statuslineFooterIdentityChanged` clears cache instead.
68
+ * Includes `agentRevision` as a churn key (so a stale async result is not kept
69
+ * forever), but `agentRevision` is deliberately excluded from
70
+ * `statuslineFooterIdentityChanged` above — it must not itself force a local
71
+ * snap-back. Stats reset is not keyed here; `statuslineFooterIdentityChanged`
72
+ * clears cache instead.
65
73
  */
66
74
  export function statuslineFooterCacheKey({
67
75
  agentRevision = '',
package/src/tui/theme.mjs CHANGED
@@ -168,10 +168,16 @@ import {
168
168
  BLACK_CIRCLE,
169
169
  RESULT_GUTTER_GLYPH,
170
170
  RESULT_GUTTER_CONT_GLYPH,
171
+ RIGHT_ARROW,
172
+ LEFT_ARROW,
171
173
  } from './figures.mjs';
172
174
 
173
175
  /** Turn marker — BLACK_CIRCLE (`⏺` on macOS; `●` elsewhere). */
174
176
  export const TURN_MARKER = BLACK_CIRCLE;
177
+ /** Agent call marker — `←` (request going out to a sub-agent). */
178
+ export const AGENT_CALL_MARKER = LEFT_ARROW;
179
+ /** Agent response marker — `→` (result coming back in to the transcript). */
180
+ export const AGENT_RESPONSE_MARKER = RIGHT_ARROW;
175
181
  /** Result-tree gutter — `└` (U+2514) padded to a 2-col hanging indent. */
176
182
  export const RESULT_GUTTER = ` ${RESULT_GUTTER_GLYPH} `;
177
183
  /**
@@ -0,0 +1,213 @@
1
+ /**
2
+ * src/ui/statusline-agents.mjs — agent worker/job normalization for the L2 line.
3
+ *
4
+ * Extracted verbatim from statusline.mjs: normalize/merge REPL agent workers &
5
+ * bridge jobs into a statusline payload, classify running vs hidden-maintenance
6
+ * workers, and enumerate active hidden agents from live session runtimes. No
7
+ * behavior change.
8
+ */
9
+ import { forEachSessionRuntime } from '../runtime/agent/orchestrator/session/manager.mjs';
10
+ import { listHiddenAgentNames } from '../runtime/agent/orchestrator/internal-agents.mjs';
11
+ import { num, GRN, R, B } from './statusline-format.mjs';
12
+
13
+ const DEFAULT_HIDDEN_STATUSLINE_AGENTS = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
14
+ let _hiddenStatuslineAgents = null;
15
+
16
+ export function summarizeWorkerTags(workers, limit = 3) {
17
+ const cleanLabels = [...new Set((Array.isArray(workers) ? workers : [])
18
+ .map((worker) => String(worker?.tag || '').trim())
19
+ .filter(Boolean))];
20
+ if (cleanLabels.length <= limit) return cleanLabels.join(', ');
21
+ return `${cleanLabels.slice(0, limit).join(', ')}, +${cleanLabels.length - limit}`;
22
+ }
23
+
24
+ function normalizeAgentWorkerForStatusline(worker = {}) {
25
+ const tag = String(worker.tag || worker.agent || worker.name || '').trim();
26
+ if (!tag) return null;
27
+ const statusText = String(worker.stage || worker.status || '').toLowerCase();
28
+ const status = isTerminalBridgeStatus(statusText) ? 'idle' : 'running';
29
+ return {
30
+ tag,
31
+ status,
32
+ startedAtMs: timeMs(worker.startedAt || worker.startTime || worker.createdAt),
33
+ agent: worker.agent || null,
34
+ stage: worker.stage || worker.status || null,
35
+ sessionId: worker.sessionId || null,
36
+ provider: worker.provider || null,
37
+ model: worker.model || null,
38
+ };
39
+ }
40
+
41
+ function normalizeAgentJobForStatusline(job = {}) {
42
+ const statusText = String(job.status || job.stage || '').toLowerCase();
43
+ if (!statusText) return null;
44
+ const taskId = String(job.task_id || job.taskId || '').trim();
45
+ const tag = String(job.tag || job.agent || job.type || taskId || '').trim();
46
+ if (!tag && !taskId) return null;
47
+ const startedAtMs = timeMs(job.startedAt);
48
+ const finishedAtMs = timeMs(job.finishedAt);
49
+ if (isTerminalBridgeStatus(statusText) && finishedAtMs > 0) {
50
+ return {
51
+ tag,
52
+ taskId,
53
+ status: 'finished',
54
+ finalStatus: statusText,
55
+ startedAtMs,
56
+ finishedAtMs,
57
+ agent: job.agent || null,
58
+ stage: job.stage || job.workerStatus || job.status || null,
59
+ sessionId: job.sessionId || null,
60
+ provider: job.provider || null,
61
+ model: job.model || null,
62
+ };
63
+ }
64
+ if (!/running/.test(statusText)) return null;
65
+ return {
66
+ tag,
67
+ taskId,
68
+ status: 'running',
69
+ startedAtMs,
70
+ agent: job.agent || null,
71
+ stage: job.stage || job.workerStatus || job.status || null,
72
+ sessionId: job.sessionId || null,
73
+ provider: job.provider || null,
74
+ model: job.model || null,
75
+ };
76
+ }
77
+
78
+ export function agentStatuslinePayload(agentWorkers = [], agentJobs = []) {
79
+ const byTag = new Map();
80
+ const finishedJobs = [];
81
+ for (const worker of Array.isArray(agentWorkers) ? agentWorkers : []) {
82
+ const row = normalizeAgentWorkerForStatusline(worker);
83
+ if (row) byTag.set(row.tag, row);
84
+ }
85
+ for (const job of Array.isArray(agentJobs) ? agentJobs : []) {
86
+ const row = normalizeAgentJobForStatusline(job);
87
+ if (!row) continue;
88
+ if (row.status === 'finished') {
89
+ finishedJobs.push(row);
90
+ continue;
91
+ }
92
+ const prev = byTag.get(row.tag);
93
+ byTag.set(row.tag, { ...(prev || {}), ...row, status: 'running' });
94
+ }
95
+ const workers = [...byTag.values()];
96
+ return {
97
+ workers,
98
+ finishedJobs: finishedJobs.sort((a, b) => (b.finishedAtMs || 0) - (a.finishedAtMs || 0)),
99
+ sessions: {
100
+ roles: workers.filter((w) => w.status !== 'idle').map((w) => w.tag),
101
+ workers,
102
+ },
103
+ };
104
+ }
105
+
106
+ export function classifyAgentWorkers(workers = []) {
107
+ const maintenance = [];
108
+ const runningWorkers = [];
109
+ const seenMaintenance = new Set();
110
+ const seenRunning = new Set();
111
+ for (const w of Array.isArray(workers) ? workers : []) {
112
+ const tag = String(w?.tag || '').trim();
113
+ if (!tag) continue;
114
+ const maint = hiddenWorkerLabel(w);
115
+ if (maint) {
116
+ if (w.status !== 'idle' && !seenMaintenance.has(maint)) {
117
+ seenMaintenance.add(maint);
118
+ maintenance.push(`${GRN}↻${R} ${B}${maint}${R}`);
119
+ }
120
+ continue;
121
+ }
122
+ if (w.status !== 'idle' && !seenRunning.has(tag)) {
123
+ seenRunning.add(tag);
124
+ runningWorkers.push(w);
125
+ }
126
+ }
127
+ return { maintenance, runningWorkers };
128
+ }
129
+
130
+ function hiddenWorkerLabel(worker = {}) {
131
+ const agent = String(worker?.agent || '').trim();
132
+ const tag = String(worker?.tag || '').trim();
133
+ return maintenanceLabel(agent) || (!agent ? maintenanceLabel(tag) : '');
134
+ }
135
+
136
+ function hiddenStatuslineAgents() {
137
+ if (_hiddenStatuslineAgents) return _hiddenStatuslineAgents;
138
+ const agents = new Set(DEFAULT_HIDDEN_STATUSLINE_AGENTS);
139
+ try {
140
+ for (const agent of listHiddenAgentNames()) {
141
+ const clean = String(agent || '').trim();
142
+ if (clean) agents.add(clean);
143
+ }
144
+ } catch {}
145
+ _hiddenStatuslineAgents = agents;
146
+ return agents;
147
+ }
148
+
149
+ function isActiveHiddenStatus(statusText) {
150
+ return /^(connecting|requesting|streaming|tool_running|running)$/i.test(String(statusText || '').trim());
151
+ }
152
+
153
+ export function activeHiddenAgentWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
154
+ const agents = hiddenStatuslineAgents();
155
+ const ownerPid = positiveInt(clientHostPid);
156
+ const ownerSessionId = String(sessionId || '').trim();
157
+ const rows = [];
158
+ try {
159
+ for (const [runtimeSessionId, entry] of forEachSessionRuntime() || []) {
160
+ if (!entry || entry.closed === true) continue;
161
+ const session = entry.session || null;
162
+ if (!session || session.closed === true) continue;
163
+ const agent = String(session?.agent || '').trim();
164
+ if (!agent || !agents.has(agent)) continue;
165
+ const id = session?.id || runtimeSessionId || null;
166
+ if (ownerSessionId && id === ownerSessionId) continue;
167
+ const sessionOwnerId = String(session?.ownerSessionId || '').trim();
168
+ if (sessionOwnerId && ownerSessionId && sessionOwnerId !== ownerSessionId) continue;
169
+ const pid = positiveInt(session?.clientHostPid);
170
+ if (ownerPid && pid && pid !== ownerPid) continue;
171
+ const stage = String(entry.stage || session?.stage || session?.status || '').trim().toLowerCase();
172
+ const status = String(session?.status || stage || '').trim().toLowerCase();
173
+ if (!isActiveHiddenStatus(stage || status)) continue;
174
+ rows.push({
175
+ tag: String(session?.agentTag || `${agent}:${id || rows.length}`).trim(),
176
+ agent,
177
+ status: 'running',
178
+ stage: stage || status || 'running',
179
+ sessionId: id,
180
+ provider: session?.provider || null,
181
+ model: session?.model || null,
182
+ });
183
+ }
184
+ } catch {}
185
+ return rows;
186
+ }
187
+
188
+ function isTerminalBridgeStatus(statusText) {
189
+ return /idle|done|complete|success|closed|error|fail|cancel|killed|timeout/.test(String(statusText || '').toLowerCase());
190
+ }
191
+
192
+ function timeMs(value) {
193
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) return value;
194
+ const n = Date.parse(String(value || ''));
195
+ return Number.isFinite(n) ? n : 0;
196
+ }
197
+
198
+ function maintenanceLabel(tag) {
199
+ switch (tag) {
200
+ case 'cycle1-agent': return 'cycle1';
201
+ case 'cycle2-agent': return 'cycle2';
202
+ case 'cycle3-agent': return 'cycle3';
203
+ case 'scheduler-task': return 'scheduler';
204
+ case 'webhook-handler': return 'webhook';
205
+ case 'explorer': return 'explorer';
206
+ default: return '';
207
+ }
208
+ }
209
+
210
+ function positiveInt(value) {
211
+ const n = Number(value);
212
+ return Number.isInteger(n) && n > 0 ? n : 0;
213
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * src/ui/statusline-format.mjs — pure formatting primitives for the footer.
3
+ *
4
+ * Extracted verbatim from statusline.mjs: ANSI SGR constants, the context%
5
+ * bar/segment formatters, small numeric helpers, and the TUI-independent
6
+ * formatElapsed() replica. No behavior change — statusline.mjs re-imports these.
7
+ */
8
+ import { colorEnabled, rgb } from './ansi.mjs';
9
+ import { displayModelName, shortenModelName } from './model-display.mjs';
10
+ import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
11
+
12
+ // Token window used to compute a fallback context% from our own session usage.
13
+ // The live gateway (when up) overrides this with the real route's window. This
14
+ // is only the last resort for unknown local models.
15
+ export const FALLBACK_CONTEXT_WINDOW = 200000;
16
+ export const statusText = rgb(198, 198, 198);
17
+ export const statusSubtle = rgb(136, 136, 136);
18
+ export const statusAccent = rgb(215, 119, 87);
19
+
20
+ function sgr(code) {
21
+ return colorEnabled() ? `\x1b[${code}m` : '';
22
+ }
23
+
24
+ export const R = sgr('0');
25
+ export const B = sgr('1');
26
+ export const D = sgr('38;2;136;136;136');
27
+ export const GRN = sgr('38;2;0;170;75');
28
+ export const YLW = sgr('38;2;255;193;7');
29
+ export const RED = sgr('38;2;220;70;88');
30
+ export const CYN = sgr('38;2;136;136;136');
31
+ export const GREY = sgr('38;2;136;136;136');
32
+
33
+ export function terminalColumns() {
34
+ const cols = Number(process.stdout?.columns);
35
+ return Number.isFinite(cols) && cols > 0 ? Math.floor(cols) : 120;
36
+ }
37
+
38
+ export function modelContextWindow(provider, model, explicitContextWindow = 0) {
39
+ const explicit = num(explicitContextWindow);
40
+ if (explicit > 0) return explicit;
41
+ const metaWindow = num(getModelMetadataSync(model, provider)?.contextWindow);
42
+ if (metaWindow > 0) return metaWindow;
43
+ return FALLBACK_CONTEXT_WINDOW;
44
+ }
45
+
46
+ export function formatModelSegment({ provider, model, effort, fast, cols }) {
47
+ const raw = String(model || '').trim();
48
+ const meta = getModelMetadataSync(raw, provider) || {};
49
+ const displayHint = String(meta.displayName || meta.display || meta.name || '').trim();
50
+ const modelName = shortenModelName(displayModelName(raw, provider, displayHint), cols);
51
+ const bits = [`${B}${modelName}${R}`];
52
+ if (effort) bits.push(`${B}${String(effort).toUpperCase()}${R}`);
53
+ if (fast === true) bits.push(`${B}FAST${R}`);
54
+ return bits.join(` ${D}·${R} `);
55
+ }
56
+
57
+ /** Display label for context % (clamped to 100); raw pct still drives bar/color thresholds. */
58
+ export function contextPctDisplayLabel(ctxPct) {
59
+ const pct = Number(ctxPct);
60
+ if (!Number.isFinite(pct) || pct <= 0) return '0';
61
+ if (pct > 0 && pct < 1) return String(Math.round(pct * 10) / 10);
62
+ return String(Math.floor(Math.min(100, pct)));
63
+ }
64
+
65
+ export function formatContextSegment(ctxPct, cols) {
66
+ const raw = Number(ctxPct);
67
+ const pct = Number.isFinite(raw) ? Math.max(0, raw) : 0;
68
+ const barPct = clampPct(pct);
69
+ const fill = pct >= 90 ? RED : pct >= 70 ? YLW : GRN;
70
+ const label = contextPctDisplayLabel(pct);
71
+ // Keep a full-width bar wherever there is room for one. Below 80 cols the bar
72
+ // is dropped (label-only) so the footer never overflows a narrow terminal;
73
+ // at 80+ it stays a fixed 14 cells instead of shrinking to 8, which read as
74
+ // too small/cramped on mid-width terminals.
75
+ const cells = cols >= 80 ? 14 : 0;
76
+ if (!cells) return `${fill}${label}%${R}`;
77
+ const bar = makeBar(barPct, cells);
78
+ const filled = bar.replace(/░/g, '');
79
+ const empty = bar.replace(/▓/g, '');
80
+ return `${fill}${filled}${R}${D}${empty}${R} ${label}%`;
81
+ }
82
+
83
+ export function makeBar(pct, cells) {
84
+ let filled = Math.floor((Number(pct) || 0) * cells / 100);
85
+ if (filled < 0) filled = 0;
86
+ if (filled > cells) filled = cells;
87
+ if (pct >= 1 && filled === 0) filled = 1;
88
+ return '▓'.repeat(filled) + '░'.repeat(cells - filled);
89
+ }
90
+
91
+ export function colourPct(p) {
92
+ if (p >= 90) return `${RED}${p}%${R}`;
93
+ if (p >= 70) return `${YLW}${p}%${R}`;
94
+ return `${GRN}${p}%${R}`;
95
+ }
96
+
97
+ export function epochMsToHHMM(ms) {
98
+ const d = new Date(Number(ms));
99
+ if (!Number.isFinite(d.getTime())) return '';
100
+ return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
101
+ }
102
+
103
+ export function num(v) {
104
+ const n = Number(v);
105
+ return Number.isFinite(n) ? n : 0;
106
+ }
107
+
108
+ export function clampPct(v) {
109
+ const n = Number(v);
110
+ if (!Number.isFinite(n)) return 0;
111
+ return Math.max(0, Math.min(100, n));
112
+ }
113
+
114
+ /** Human-friendly token count: 1234 -> 1.2k. */
115
+ export function fmt(n) {
116
+ const v = Number(n) || 0;
117
+ if (v < 1000) return String(Math.round(v));
118
+ if (v < 1_000_000) {
119
+ const k = Math.round(v / 1000);
120
+ return k >= 1000 ? '1M' : `${k}k`;
121
+ }
122
+ return (v / 1_000_000).toFixed(1) + 'M';
123
+ }
124
+
125
+ // Byte-identical replica of src/tui/time-format.mjs formatDuration() with
126
+ // DEFAULT options (no mostSignificantOnly / hideTrailingZeros), wrapped to drop
127
+ // sub-1s like formatElapsed there. Output shape: '' (<1s), `Xs`, `Xm Ys`,
128
+ // `Xh Ym Zs`, `Xd Yh Zm`. statusline.mjs is a standalone UI module that should
129
+ // not depend on the React/ink TUI tree, so the algorithm is replicated rather
130
+ // than imported. Used for ALL L2 elapsed (Agents/Explore/Search/Shell).
131
+ export function formatElapsed(ms) {
132
+ if (!Number.isFinite(Number(ms))) return '';
133
+ const value = Math.max(0, Number(ms) || 0);
134
+ if (value < 60_000) {
135
+ if (value < 1_000) return '';
136
+ return `${Math.floor(value / 1000)}s`;
137
+ }
138
+ const days = Math.floor(value / 86_400_000);
139
+ const hours = Math.floor((value % 86_400_000) / 3_600_000);
140
+ const minutes = Math.floor((value % 3_600_000) / 60_000);
141
+ const seconds = Math.floor((value % 60_000) / 1000);
142
+ if (days > 0) return `${days}d ${hours}h ${minutes}m`;
143
+ if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
144
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
145
+ return `${seconds}s`;
146
+ }