mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -20,12 +20,9 @@ export const MEASURED_TOOL_USAGE = Object.freeze({
20
20
  agent: 330,
21
21
  shell: 81,
22
22
  cwd: 2,
23
- diagnostics: 2,
24
23
  recall: 2,
25
24
  search: 2,
26
25
  web_fetch: 2,
27
- provider_status: 2,
28
- channel_status: 2,
29
26
  });
30
27
  const MEASURED_TOOL_ORDER = Object.freeze(Object.keys(MEASURED_TOOL_USAGE));
31
28
  export const DEFERRED_DEFAULT_FULL_TOOLS = Object.freeze([
@@ -81,9 +78,6 @@ const READONLY_TOOL_NAMES = new Set([
81
78
  'web_fetch',
82
79
  'recall',
83
80
  'memory',
84
- 'provider_status',
85
- 'channel_status',
86
- 'schedule_status',
87
81
  'fetch',
88
82
  'Skill',
89
83
  ]);
@@ -92,13 +86,8 @@ const DEFERRED_SELECT_ALIASES = {
92
86
  search: ['search', 'web_fetch'],
93
87
  web: ['web_fetch', 'search'],
94
88
  memory: ['memory', 'recall'],
95
- channels: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment', 'schedule_status', 'trigger_schedule', 'schedule_control', 'reload_config'],
96
- discord: ['reply', 'fetch', 'react', 'edit_message', 'download_attachment'],
97
- providers: ['provider_status'],
98
- provider: ['provider_status'],
99
- status: ['provider_status', 'channel_status', 'schedule_status'],
100
- schedule: ['schedule_status', 'trigger_schedule', 'schedule_control'],
101
- channel: ['channel_status'],
89
+ channels: ['reply', 'fetch'],
90
+ discord: ['reply', 'fetch'],
102
91
  explore: ['explore'],
103
92
  discovery: ['explore'],
104
93
  agent: ['agent'],
@@ -111,10 +100,6 @@ const TOOL_SEARCH_SAFE_AUTO_ALIASES = new Set([
111
100
  'web',
112
101
  'search',
113
102
  'agent',
114
- 'provider',
115
- 'providers',
116
- 'channel',
117
- 'schedule',
118
103
  'memory',
119
104
  ]);
120
105
  const TOOL_SEARCH_AMBIGUOUS_AUTO_QUERIES = new Set([
@@ -152,12 +137,9 @@ const TOOL_SEARCH_STOP_WORDS = new Set([
152
137
  ]);
153
138
  const TOOL_SEARCH_ROW_ALIASES = Object.freeze({
154
139
  agent: ['delegate', 'subagent', 'worker', 'parallel agent', 'background agent', 'reviewer', 'explorer'],
155
- channel_status: ['channel status', 'discord status', 'channel config'],
156
140
  cwd: ['cwd', 'working directory', 'current directory', 'project root', 'folder'],
157
141
  memory: ['save memory', 'store memory', 'delete memory', 'forget memory', 'memory status'],
158
- provider_status: ['provider status', 'auth status', 'model status', 'oauth status', 'provider config'],
159
142
  recall: ['recall', 'previous work', 'past work', 'prior context', 'history', 'resume context'],
160
- schedule_status: ['schedule status', 'cron status'],
161
143
  search: ['web search', 'internet search', 'current info', 'latest info', 'online search', 'docs search'],
162
144
  shell: ['run command', 'execute command', 'terminal', 'powershell', 'bash', 'run tests', 'test command', 'build command', 'npm', 'node', 'git'],
163
145
  task: ['background task', 'async task', 'wait task', 'cancel task', 'task status'],
@@ -690,15 +672,33 @@ export function renderToolSearch(args = {}, session, mode = 'full') {
690
672
  ? autoToolSelectionNames(query, initialRows)
691
673
  : [];
692
674
  const selectedNames = forcedSelectedNames.length ? forcedSelectedNames : autoSelectedNames;
693
- const toolSelection = selectedNames.length ? selectDeferredTools(session, selectedNames, mode) : null;
694
- const selectionMode = forcedSelectedNames.length ? 'select' : (autoSelectedNames.length ? 'auto' : null);
695
- const nextActiveNames = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
696
- const rows = [
697
- ...catalog.map((tool) => toolRow(tool, nextActiveNames)),
698
- ].filter((row) => row.name);
699
- const matches = query
675
+ let toolSelection = selectedNames.length ? selectDeferredTools(session, selectedNames, mode) : null;
676
+ let selectionMode = forcedSelectedNames.length ? 'select' : (autoSelectedNames.length ? 'auto' : null);
677
+ let nextActiveNames = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
678
+ let rows = catalog.map((tool) => toolRow(tool, nextActiveNames)).filter((row) => row.name);
679
+ let matches = query
700
680
  ? rankedToolSearchRows(rows, query)
701
681
  : rows;
682
+ // Query hits must become loadable without a second explicit select turn:
683
+ // if the query produced ranked matches but the forced/auto path above
684
+ // loaded nothing (no exact-name/alias/category hit), fall back to
685
+ // auto-loading the ranked match names themselves (deferred/non-active
686
+ // only, capped at `limit`). Ambiguous queries stay excluded so a guess
687
+ // never silently loads a wrong category.
688
+ const STRONG_MATCH_REASONS = new Set(['exact-name', 'exact-alias', 'alias-phrase', 'phrase', 'all-tokens']);
689
+ const isStrongMatch = (row) => row.score >= 24 || (row.reasons || []).some((reason) => STRONG_MATCH_REASONS.has(reason));
690
+ const queryLoadedNothing = !toolSelection
691
+ || (!toolSelection.added.length && !toolSelection.already.length);
692
+ if (query && !TOOL_SEARCH_AMBIGUOUS_AUTO_QUERIES.has(query) && matches.length && queryLoadedNothing) {
693
+ const fallbackNames = matches.filter((row) => !row.active && isStrongMatch(row)).map((row) => row.name).slice(0, limit);
694
+ if (fallbackNames.length) {
695
+ toolSelection = selectDeferredTools(session, fallbackNames, mode);
696
+ selectionMode = 'query';
697
+ nextActiveNames = new Set((session?.tools || []).map((tool) => clean(tool?.name)).filter(Boolean));
698
+ rows = catalog.map((tool) => toolRow(tool, nextActiveNames)).filter((row) => row.name);
699
+ matches = query ? rankedToolSearchRows(rows, query) : rows;
700
+ }
701
+ }
702
702
  const selected = toolSelection
703
703
  ? {
704
704
  mode: selectionMode,
@@ -706,7 +706,7 @@ export function renderToolSearch(args = {}, session, mode = 'full') {
706
706
  }
707
707
  : null;
708
708
  const nativeToolSearch = toolSelection?.native
709
- ? toolSearchNativePayload(catalog, toolSelection.added, session?.provider)
709
+ ? toolSearchNativePayload(catalog, [...new Set([...toolSelection.added, ...(toolSelection.already || [])])], session?.provider)
710
710
  : null;
711
711
  return JSON.stringify({
712
712
  selected,
@@ -715,6 +715,6 @@ export function renderToolSearch(args = {}, session, mode = 'full') {
715
715
  matches: matches.slice(0, limit),
716
716
  activeTools: sortedNamesByMeasuredUsage(nextActiveNames),
717
717
  discoveredTools: sortedNamesByMeasuredUsage(session?.deferredDiscoveredTools || []),
718
- note: 'query ranks matches and auto-loads high-confidence deferred tools; select exact tool names, or query select:a,b, to force load.',
718
+ note: 'query ranks matches and auto-loads high-confidence deferred tools, including ranked search hits (deferred, non-active) up to limit; already-loaded/discovered tools are re-surfaced on re-selection; select exact tool names, or query select:a,b, to force load.',
719
719
  }, null, 2);
720
720
  }
@@ -0,0 +1,84 @@
1
+ // Standalone session tool definitions (wrapper tools surfaced by the runtime).
2
+ // Pure, self-contained schemas + the agent-hidden default helper. Extracted
3
+ // from mixdog-session-runtime.mjs; no runtime closure dependencies.
4
+
5
+ export const TOOL_SEARCH_TOOL = {
6
+ name: 'tool_search',
7
+ title: 'Tool Search',
8
+ annotations: {
9
+ title: 'Tool Search',
10
+ readOnlyHint: true,
11
+ destructiveHint: false,
12
+ idempotentHint: true,
13
+ openWorldHint: false,
14
+ agentHidden: true,
15
+ },
16
+ description: 'Search deferred tools; confident queries load matches.',
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ query: { type: 'string', description: 'Search text; confident hits load. select:a,b forces exact names.' },
21
+ select: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Force exact tool names.' },
22
+ limit: { type: 'number', description: 'Max matches.' },
23
+ },
24
+ additionalProperties: false,
25
+ },
26
+ };
27
+
28
+ export const CWD_TOOL = {
29
+ name: 'cwd',
30
+ title: 'Work Project',
31
+ annotations: {
32
+ title: 'Work Project',
33
+ readOnlyHint: false,
34
+ destructiveHint: false,
35
+ idempotentHint: false,
36
+ openWorldHint: false,
37
+ agentHidden: true,
38
+ },
39
+ description: 'Show or set the session work project for tool execution.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ action: { type: 'string', enum: ['get', 'set'], description: 'Default get.' },
44
+ path: { type: 'string', description: 'Project directory for set.' },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ };
49
+
50
+ export const SKILL_TOOL = {
51
+ name: 'Skill',
52
+ title: 'Skill',
53
+ annotations: {
54
+ title: 'Skill',
55
+ readOnlyHint: true,
56
+ destructiveHint: false,
57
+ idempotentHint: true,
58
+ openWorldHint: false,
59
+ agentHidden: false,
60
+ },
61
+ description: 'Load a named SKILL.md into context.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ name: { type: 'string', description: 'Skill name.' },
66
+ },
67
+ required: ['name'],
68
+ additionalProperties: false,
69
+ },
70
+ };
71
+
72
+ export const LEAD_DISALLOWED_TOOLS = Object.freeze([]);
73
+ export const AGENT_HIDDEN_WRAPPER_TOOLS = new Set([]);
74
+
75
+ export function applyStandaloneToolDefaults(tool) {
76
+ if (!tool || !AGENT_HIDDEN_WRAPPER_TOOLS.has(tool.name)) return tool;
77
+ return {
78
+ ...tool,
79
+ annotations: {
80
+ ...(tool.annotations || {}),
81
+ agentHidden: true,
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,201 @@
1
+ // Background warmup/refresh schedulers, extracted from
2
+ // mixdog-session-runtime.mjs. Dependency-injected factory: the timer handles
3
+ // live in a caller-owned `timers` object (so the facade's clearTimeout teardown
4
+ // still sees them) and all route/config/state reads go through supplied
5
+ // accessors. Returns the schedule* functions plus a clearAll() teardown helper.
6
+ import { performance } from 'node:perf_hooks';
7
+ import { clean } from './session-text.mjs';
8
+
9
+ export function createWarmupSchedulers({
10
+ timers,
11
+ bootProfile,
12
+ getRoute,
13
+ getConfig,
14
+ isCloseRequested,
15
+ getActiveTurnCount,
16
+ getSessionCreatePromise,
17
+ getProviderModelsCache,
18
+ getProviderModelsPromise,
19
+ reloadFullConfig,
20
+ ensureConfigForRouteProvider,
21
+ ensureProvidersReady,
22
+ ensureProviderEnabled,
23
+ refreshStatuslineUsageSnapshot,
24
+ warmProviderModelCache,
25
+ cachedProviderSetup,
26
+ warmCatalogsInBackground,
27
+ isFirstTurnCompleted,
28
+ envFlag,
29
+ delays,
30
+ flags,
31
+ }) {
32
+ const {
33
+ providerWarmupDelayMs,
34
+ providerSetupWarmupDelayMs,
35
+ providerModelWarmupDelayMs,
36
+ modelCatalogWarmupDelayMs,
37
+ statuslineUsageWarmupDelayMs,
38
+ statuslineUsageRefreshDelayMs,
39
+ backgroundBusyRetryMs,
40
+ } = delays;
41
+ const {
42
+ providerWarmupEnabled,
43
+ modelPrefetchEnabled,
44
+ modelCatalogWarmupEnabled,
45
+ } = flags;
46
+
47
+ function scheduleProviderWarmup(delayMs = providerWarmupDelayMs) {
48
+ if (!providerWarmupEnabled) {
49
+ bootProfile('providers:warm-skipped');
50
+ return;
51
+ }
52
+ if (timers.providerWarmupTimer || isCloseRequested()) return;
53
+ timers.providerWarmupTimer = setTimeout(() => {
54
+ timers.providerWarmupTimer = null;
55
+ if (isCloseRequested()) return;
56
+ if (!isFirstTurnCompleted() && !envFlag('MIXDOG_PROVIDER_WARMUP_BEFORE_FIRST_TURN')) {
57
+ bootProfile('providers:warm-deferred', { reason: 'first-turn-pending' });
58
+ return;
59
+ }
60
+ if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
61
+ bootProfile('providers:warm-deferred', { reason: getActiveTurnCount() > 0 ? 'turn-active' : 'session-create' });
62
+ scheduleProviderWarmup(backgroundBusyRetryMs);
63
+ return;
64
+ }
65
+ const providersStartedAt = performance.now();
66
+ try {
67
+ reloadFullConfig();
68
+ } catch (error) {
69
+ bootProfile('config:full-failed', { error: error?.message || String(error) });
70
+ }
71
+ void ensureProvidersReady(getConfig().providers || {})
72
+ .then(() => {
73
+ bootProfile('providers:init:ready', { ms: (performance.now() - providersStartedAt).toFixed(1) });
74
+ if (isCloseRequested()) return null;
75
+ return true;
76
+ })
77
+ .catch((error) => bootProfile('providers:warm-failed', { error: error?.message || String(error) }));
78
+ }, delayMs);
79
+ timers.providerWarmupTimer.unref?.();
80
+ }
81
+
82
+ function scheduleProviderSetupWarmup(delayMs = providerSetupWarmupDelayMs) {
83
+ if (timers.providerSetupWarmupTimer || isCloseRequested()) return;
84
+ timers.providerSetupWarmupTimer = setTimeout(() => {
85
+ timers.providerSetupWarmupTimer = null;
86
+ if (isCloseRequested()) return;
87
+ void cachedProviderSetup()
88
+ .then(() => bootProfile('provider-setup:warm-ready'))
89
+ .catch((error) => bootProfile('provider-setup:warm-failed', { error: error?.message || String(error) }));
90
+ }, delayMs);
91
+ timers.providerSetupWarmupTimer.unref?.();
92
+ }
93
+
94
+ function scheduleProviderModelWarmup(delayMs = providerModelWarmupDelayMs) {
95
+ if (!modelPrefetchEnabled) return;
96
+ if (timers.providerModelWarmupTimer || isCloseRequested()) return;
97
+ timers.providerModelWarmupTimer = setTimeout(() => {
98
+ timers.providerModelWarmupTimer = null;
99
+ if (isCloseRequested() || Array.isArray(getProviderModelsCache().models) || getProviderModelsPromise()) return;
100
+ if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
101
+ bootProfile('provider-models:warm-deferred', { reason: getActiveTurnCount() > 0 ? 'turn-active' : 'session-create' });
102
+ scheduleProviderModelWarmup(backgroundBusyRetryMs);
103
+ return;
104
+ }
105
+ warmProviderModelCache();
106
+ }, delayMs);
107
+ timers.providerModelWarmupTimer.unref?.();
108
+ }
109
+
110
+ function scheduleModelCatalogWarmup(delayMs = modelCatalogWarmupDelayMs) {
111
+ if (!modelCatalogWarmupEnabled) {
112
+ bootProfile('model-catalog:warm-skipped', { reason: 'disabled' });
113
+ return;
114
+ }
115
+ if (timers.modelCatalogWarmupTimer || isCloseRequested()) return;
116
+ timers.modelCatalogWarmupTimer = setTimeout(() => {
117
+ timers.modelCatalogWarmupTimer = null;
118
+ if (isCloseRequested()) return;
119
+ if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
120
+ bootProfile('model-catalog:warm-deferred', { reason: getActiveTurnCount() > 0 ? 'turn-active' : 'session-create' });
121
+ scheduleModelCatalogWarmup(backgroundBusyRetryMs);
122
+ return;
123
+ }
124
+ void warmCatalogsInBackground()
125
+ .then(() => bootProfile('model-catalog:warm-ready'))
126
+ .catch((error) => bootProfile('model-catalog:warm-failed', { error: error?.message || String(error) }));
127
+ }, delayMs);
128
+ timers.modelCatalogWarmupTimer.unref?.();
129
+ }
130
+
131
+ function scheduleStatuslineUsageWarmup(delayMs = statuslineUsageWarmupDelayMs) {
132
+ const route = getRoute();
133
+ const providerId = clean(route?.provider);
134
+ if (!providerId || !providerId.includes('oauth')) {
135
+ bootProfile('statusline-usage:warm-skipped', { provider: providerId || null });
136
+ return;
137
+ }
138
+ if (timers.statuslineUsageWarmupTimer || isCloseRequested()) return;
139
+ timers.statuslineUsageWarmupTimer = setTimeout(async () => {
140
+ timers.statuslineUsageWarmupTimer = null;
141
+ if (isCloseRequested()) return;
142
+ if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
143
+ bootProfile('statusline-usage:warm-deferred', { reason: getActiveTurnCount() > 0 ? 'turn-active' : 'session-create' });
144
+ scheduleStatuslineUsageWarmup(backgroundBusyRetryMs);
145
+ return;
146
+ }
147
+ try {
148
+ ensureConfigForRouteProvider();
149
+ await ensureProvidersReady(ensureProviderEnabled(getConfig(), getRoute().provider));
150
+ if (isCloseRequested()) return;
151
+ refreshStatuslineUsageSnapshot(getRoute());
152
+ bootProfile('statusline-usage:warm-ready', { provider: clean(getRoute()?.provider) });
153
+ } catch (error) {
154
+ bootProfile('statusline-usage:warm-failed', { error: error?.message || String(error) });
155
+ } finally {
156
+ scheduleStatuslineUsageRefresh();
157
+ }
158
+ }, delayMs);
159
+ timers.statuslineUsageWarmupTimer.unref?.();
160
+ }
161
+
162
+ // Idle keep-alive loop: periodically re-fetch the OAuth usage snapshot so its
163
+ // cachedAt stays "live-fresh" and the statusline usage segment does not vanish
164
+ // after LIVE_USAGE_SNAPSHOT_MAX_AGE_MS while the session is idle. Turn-driven
165
+ // refreshes (recordStandaloneStatusTelemetry) already cover active sessions.
166
+ function scheduleStatuslineUsageRefresh(delayMs = statuslineUsageRefreshDelayMs) {
167
+ const route = getRoute();
168
+ const providerId = clean(route?.provider);
169
+ if (!providerId || !providerId.includes('oauth')) return;
170
+ if (timers.statuslineUsageRefreshTimer || isCloseRequested()) return;
171
+ timers.statuslineUsageRefreshTimer = setTimeout(async () => {
172
+ timers.statuslineUsageRefreshTimer = null;
173
+ if (isCloseRequested()) return;
174
+ if (getActiveTurnCount() > 0 || getSessionCreatePromise()) {
175
+ // Active turns refresh usage on their own; just re-arm the idle loop.
176
+ scheduleStatuslineUsageRefresh();
177
+ return;
178
+ }
179
+ try {
180
+ ensureConfigForRouteProvider();
181
+ await ensureProvidersReady(ensureProviderEnabled(getConfig(), getRoute().provider));
182
+ if (isCloseRequested()) return;
183
+ refreshStatuslineUsageSnapshot(getRoute());
184
+ } catch {
185
+ // Usage display must never affect the session runtime.
186
+ } finally {
187
+ scheduleStatuslineUsageRefresh();
188
+ }
189
+ }, delayMs);
190
+ timers.statuslineUsageRefreshTimer.unref?.();
191
+ }
192
+
193
+ return {
194
+ scheduleProviderWarmup,
195
+ scheduleProviderSetupWarmup,
196
+ scheduleProviderModelWarmup,
197
+ scheduleModelCatalogWarmup,
198
+ scheduleStatuslineUsageWarmup,
199
+ scheduleStatuslineUsageRefresh,
200
+ };
201
+ }
@@ -0,0 +1,237 @@
1
+ // Pure, dependency-light helpers extracted from the agent-tool facade. No
2
+ // module-level mutable state lives here (the frontmatter perm cache and all
3
+ // timers stay in the facade / their owner modules). Behavior-preserving:
4
+ // function bodies are identical to the originals.
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { isAbsolute, join, resolve } from 'node:path';
7
+ import {
8
+ normalizeAgentPermissionOrNone,
9
+ parseMarkdownFrontmatter,
10
+ } from '../../runtime/shared/markdown-frontmatter.mjs';
11
+ import { clearGatewaySessionRoute, writeGatewaySessionRoute } from '../../vendor/statusline/src/gateway/session-routes.mjs';
12
+ import { PRESET_ALIASES } from './tool-def.mjs';
13
+
14
+ export function envTimeoutMs(name, fallback) {
15
+ const raw = process.env[name];
16
+ if (raw === undefined || raw === '') return fallback;
17
+ const n = Number(raw);
18
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
19
+ }
20
+
21
+ export function clean(value) {
22
+ return String(value ?? '').trim();
23
+ }
24
+
25
+ export function agentTagOf(session) {
26
+ return clean(session?.agentTag);
27
+ }
28
+
29
+ export function normalizeAgentName(value) {
30
+ const id = clean(value).toLowerCase().replace(/[\s_]+/g, '-');
31
+ if (id === 'explorer') return 'explore';
32
+ if (id === 'maint' || id === 'maintenance' || id === 'memory') return 'maintainer';
33
+ if (id === 'heavy' || id === 'heavyworker') return 'heavy-worker';
34
+ if (id === 'review') return 'reviewer';
35
+ if (id === 'debug') return 'debugger';
36
+ return id;
37
+ }
38
+
39
+ export function positiveInt(value) {
40
+ const n = Number(value);
41
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
42
+ }
43
+
44
+ export function terminalPidForContext(context = {}) {
45
+ return positiveInt(context?.clientHostPid);
46
+ }
47
+
48
+ export function agentScope(args = {}, context = {}) {
49
+ const scope = clean(args.scope || args.terminal || args.term).toLowerCase();
50
+ if (args.allTerminals === true || scope === 'all' || scope === 'global') return {};
51
+ return context || {};
52
+ }
53
+
54
+ export function sessionMatchesContext(session, context = {}) {
55
+ const wantedPid = terminalPidForContext(context);
56
+ if (!wantedPid) return true;
57
+ const sessionPid = positiveInt(session?.clientHostPid);
58
+ return !!sessionPid && sessionPid === wantedPid;
59
+ }
60
+
61
+ export function rowMatchesContext(row, context = {}) {
62
+ const wantedPid = terminalPidForContext(context);
63
+ if (!wantedPid) return true;
64
+ const rowPid = positiveInt(row?.clientHostPid);
65
+ return !!rowPid && rowPid === wantedPid;
66
+ }
67
+
68
+ export function nonNegativeInt(value) {
69
+ if (value === undefined || value === null || value === '') return null;
70
+ const n = Number(value);
71
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
72
+ }
73
+
74
+ export function presetKey(preset) {
75
+ return clean(preset?.id || preset?.name);
76
+ }
77
+
78
+ export function bridgeRouteForStatusline(preset = {}) {
79
+ const provider = clean(preset.provider);
80
+ const model = clean(preset.model);
81
+ if (!provider || !model) return null;
82
+ const out = {
83
+ mode: 'fixed',
84
+ defaultProvider: provider,
85
+ defaultModel: model,
86
+ };
87
+ const id = clean(preset.id);
88
+ const name = clean(preset.name);
89
+ const modelDisplay = clean(preset.modelDisplay || preset.display || preset.displayName);
90
+ const effort = clean(preset.effort);
91
+ if (id) out.presetId = id;
92
+ if (name) out.presetName = name;
93
+ if (modelDisplay) out.modelDisplay = modelDisplay;
94
+ if (effort) {
95
+ out.effort = effort;
96
+ out.displayEffort = effort;
97
+ }
98
+ if (preset.fast === true || preset.fast === false) out.fast = preset.fast;
99
+ return out;
100
+ }
101
+
102
+ export function writeAgentStatuslineRoute(sessionId, preset) {
103
+ const route = bridgeRouteForStatusline(preset);
104
+ if (!sessionId || !route) return false;
105
+ try { return writeGatewaySessionRoute(sessionId, route); } catch { return false; }
106
+ }
107
+
108
+ export function clearAgentStatuslineRoute(sessionId) {
109
+ if (!sessionId) return false;
110
+ try { return clearGatewaySessionRoute(sessionId); } catch { return false; }
111
+ }
112
+
113
+ export function findPreset(config, key) {
114
+ const wanted = clean(key).toLowerCase();
115
+ if (!wanted) return null;
116
+ const presets = Array.isArray(config?.presets) ? config.presets : [];
117
+ return presets.find((p) => {
118
+ return clean(p?.id).toLowerCase() === wanted || clean(p?.name).toLowerCase() === wanted;
119
+ }) || null;
120
+ }
121
+
122
+ export function synthesizePreset(config, key) {
123
+ const alias = PRESET_ALIASES.get(clean(key).toLowerCase());
124
+ if (!alias) return null;
125
+ const base = findPreset(config, alias.base);
126
+ if (!base) return null;
127
+ return {
128
+ ...base,
129
+ id: alias.id,
130
+ name: alias.name,
131
+ effort: alias.effort,
132
+ };
133
+ }
134
+
135
+ export function normalizeAgentRoute(routeLike, fallbackProvider = '') {
136
+ const provider = clean(routeLike?.provider) || clean(fallbackProvider);
137
+ const model = clean(routeLike?.model);
138
+ if (!provider || !model) return null;
139
+ return {
140
+ provider,
141
+ model,
142
+ effort: clean(routeLike?.effort) || undefined,
143
+ fast: routeLike?.fast === true,
144
+ };
145
+ }
146
+
147
+ export function agentPresetName(agent) {
148
+ return `AGENT ${String(agent || '').toUpperCase()}`;
149
+ }
150
+
151
+ export async function resolvePrompt(args, cwd) {
152
+ const prompt = clean(args.prompt || args.message);
153
+ const file = clean(args.file);
154
+ if (prompt && file) throw new Error('agent: provide only one of prompt/message or file');
155
+ if (prompt) return prompt;
156
+ if (file) {
157
+ const target = isAbsolute(file) ? file : resolve(cwd || process.cwd(), file);
158
+ return readFileSync(target, 'utf8');
159
+ }
160
+ throw new Error('agent: prompt/message/file is required');
161
+ }
162
+
163
+ export function withCwdHeader(prompt, cwd) {
164
+ if (!cwd) return prompt;
165
+ if (String(prompt).startsWith('[effective-cwd]')) return prompt;
166
+ return `[effective-cwd] ${cwd}\n\n${prompt}`;
167
+ }
168
+
169
+ export function compactIso(value) {
170
+ const text = clean(value);
171
+ if (!text) return '';
172
+ return text.replace('T', ' ').replace(/\.\d+Z$/, 'Z');
173
+ }
174
+
175
+ export function formatElapsedMs(ms) {
176
+ const n = Number(ms);
177
+ if (!Number.isFinite(n) || n < 0) return '';
178
+ const totalSec = Math.floor(n / 1000);
179
+ if (totalSec < 60) return `${totalSec}s`;
180
+ const min = Math.floor(totalSec / 60);
181
+ const sec = totalSec % 60;
182
+ if (min < 60) return sec ? `${min}m${sec}s` : `${min}m`;
183
+ const hr = Math.floor(min / 60);
184
+ const remMin = min % 60;
185
+ return remMin ? `${hr}h${remMin}m` : `${hr}h`;
186
+ }
187
+
188
+ export function elapsedFromStamps(startedAt, finishedAt, status) {
189
+ const start = Date.parse(clean(startedAt));
190
+ if (!Number.isFinite(start)) return null;
191
+ const finish = Date.parse(clean(finishedAt));
192
+ const end = Number.isFinite(finish) ? finish : Date.now();
193
+ const label = formatElapsedMs(end - start);
194
+ if (!label) return null;
195
+ return Number.isFinite(finish) ? label : `${label} (running)`;
196
+ }
197
+
198
+ export function stripFinalAnswerWrapper(value) {
199
+ const text = String(value ?? '').trim();
200
+ const match = /^<final-answer\b[^>]*>([\s\S]*?)<\/final-answer>\s*$/i.exec(text);
201
+ return match ? match[1].trim() : text;
202
+ }
203
+
204
+ // Process-wide TTL cache for agent AGENT.md frontmatter permission. The file
205
+ // rarely changes, but this otherwise pays several existsSync()+readFileSync()
206
+ // calls on EVERY spawn — multiplied across a parallel fanout that is pure
207
+ // redundant synchronous I/O on the event loop, which blocks every other
208
+ // concurrent spawn. Short TTL keeps edits picked up quickly while collapsing
209
+ // burst reads. Cache + TTL are owned here alongside the sole reader.
210
+ const _frontmatterPermCache = new Map(); // key -> { value, atMs }
211
+ const FRONTMATTER_PERM_CACHE_TTL_MS = envTimeoutMs('MIXDOG_AGENT_FRONTMATTER_TTL_MS', 5_000);
212
+
213
+ export function readAgentFrontmatterPermission(agent, dataDir, standaloneSourceRoot) {
214
+ const cleanAgent = clean(agent);
215
+ if (!cleanAgent) return null;
216
+ const cacheKey = `${dataDir || ''}\u0000${cleanAgent}`;
217
+ const cached = _frontmatterPermCache.get(cacheKey);
218
+ if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) {
219
+ return cached.value;
220
+ }
221
+ const candidates = [];
222
+ if (dataDir) {
223
+ candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
224
+ candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
225
+ }
226
+ candidates.push(join(standaloneSourceRoot, 'agents', cleanAgent, 'AGENT.md'));
227
+ candidates.push(join(standaloneSourceRoot, 'agents', `${cleanAgent}.md`));
228
+ let resolved = null;
229
+ for (const file of candidates) {
230
+ if (!existsSync(file)) continue;
231
+ const fm = parseMarkdownFrontmatter(readFileSync(file, 'utf8'));
232
+ const permission = normalizeAgentPermissionOrNone(fm.permission);
233
+ if (permission) { resolved = permission; break; }
234
+ }
235
+ _frontmatterPermCache.set(cacheKey, { value: resolved, atMs: Date.now() });
236
+ return resolved;
237
+ }