mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,255 @@
1
+ {
2
+ "round": "8",
3
+ "runner": "lead",
4
+ "opts": {
5
+ "provider": null,
6
+ "model": null,
7
+ "effort": null,
8
+ "fast": false,
9
+ "env": {
10
+ "MIXDOG_DATA_DIR": "C:\\Users\\tempe\\.mixdog\\bench-data"
11
+ }
12
+ },
13
+ "tasks": 3,
14
+ "completed": 3,
15
+ "completion_rate": 100,
16
+ "sessions": [
17
+ "sess_51468_1783039949771_1783039950019_986225c84c3411f10ae399d9084cae21",
18
+ "sess_51564_1783039949781_1783039950027_596c0770872549478e285dccd2390597",
19
+ "sess_19712_1783039949794_1783039950038_d4b96a9272d27d7f810e51341f8c58b7"
20
+ ],
21
+ "results": [
22
+ {
23
+ "id": "r4-debug-forwarding-gap",
24
+ "agent": "reviewer",
25
+ "ok": true,
26
+ "ms": 144598,
27
+ "sessionId": "sess_51468_1783039949771_1783039950019_986225c84c3411f10ae399d9084cae21",
28
+ "output": "sessionId: sess_51468_1783039949771_1783039950019_986225c84c3411f10ae399d9084cae21\n조사 완료: 활성화 직후 경로에는 “지연 후 복구”가 대부분이지만, 새 transcript가 이미 존재하는 상태로 잡히면 첫 출력이 실제로 조용히 스킵될 수 있습니다.\n\n**경로**: `start()`가 `channelBridgeActive=true`와 `bridge-state.json`을 먼저 씁니다(`src/runtime/channels/index.mjs:1734`), 이후 ownership/connect/bind를 거쳐 `forwarder.forwardNewText()`가 `extractNewText()`→queue→`backend.sendMessage()`로 첫 전송합니다(`index.mjs:801`, `lib/output-forwarder.mjs:351`, `index.mjs:284`).\n\n**최대 영향 — 새 transcript 기존 파일을 EOF로 바인딩**: `catchUpFromPersisted`가 켜져도 persisted path가 다르면 cursor를 `currentSize`로 둡니다(`lib/output-forwarder.mjs:117`), 그리고 그 값을 즉시 상태에 저장합니다(`index.mjs:326`); 바인딩 전에 이미 쓰인 assistant 출력은 backlog catch-up으로 복구되지 않습니다.\n\n**높은 영향 — discovery polling 중 파일이 생겨 이미 내용이 있는 경우**: `discoverSessionBoundTranscript()` 루프가 `exists`를 잡으면 초기 activate 경로는 `catchUpFromPersisted:true`만 넘기고 `replayFromStart`는 거의 false입니다(`index.mjs:433`, `index.mjs:441`, `index.mjs:625`); persisted가 같은 transcript일 때만 복구되고, 아니면 위 EOF 스킵으로 유실됩니다.\n\n**중간 영향 — not-yet-created transcript watch 실패**: `startWatch()`는 없는 파일에서 catch 후 watcher를 닫습니다(`lib/output-forwarder.mjs:647`, `lib/output-forwarder.mjs:667`); pending re-arm이 250ms polling으로 파일 생성 후 `startWatch()`와 `forwardNewText()`를 실행해 60초 안에는 backlog가 복구됩니다(`index.mjs:357`, `index.mjs:376`), 60초 timeout 후에는 자동 복구가 없습니다(`index.mjs:384`).\n\n**중간 영향 — bridge-state 이후 connect/ownership 전 flush**: watch flush가 먼저 떠도 `_isOwner()`는 `bridgeRuntimeConnected && currentOwnerState().owned`라서 false이면 읽지 않고 반환합니다(`index.mjs:316`, `lib/output-forwarder.mjs:351`); cursor가 전진하지 않아 post-connect `forwardNewText()` 또는 re-arm이 backlog를 복구합니다(`index.mjs:776`, `index.mjs:801`).\n\n**낮은 영향 — ownership probe 예외/일시 false**: `_isOwner()`는 예외를 non-owner로 처리합니다(`lib/output-forwarder.mjs:36`), 해당 flush는 재시도 예약 없이 끝납니다; bytes는 소비되지 않아 다음 fs.watch/turn-end/post-connect flush가 있으면 복구되지만, 추가 트리거가 없으면 지연될 수 있습니다.\n\n**낮은 영향 — 바인딩 전 channelId 없음**: persisted status나 main channel fallback이 없으면 바인딩 자체를 중단합니다(`index.mjs:610`, `index.mjs:624`), 이후 channel bind가 생길 때까지 전송은 지연되며, 그때 새 transcript를 EOF로 잡으면 위 유실 조건을 다시 탑니다.\n\nVerdict: not-yet-created 파일과 pre-connect ownership 창은 대체로 backlog로 복구되지만, “이미 존재하는 새 transcript를 persisted mismatch 상태로 바인딩”하는 경로는 첫 assistant 출력을 조용히 유실할 수 있습니다.",
29
+ "card": {
30
+ "session": "sess_51468_1783039949771_1783039950019_986225c84c3411f10ae399d9084cae21",
31
+ "provider": "openai-oauth",
32
+ "model": "gpt-5.5",
33
+ "wall_ms": 134065,
34
+ "turns": 9,
35
+ "tool_calls": 8,
36
+ "tools_per_turn": 0.9,
37
+ "total_tool_ms": 497,
38
+ "llm_stream_ms": 133367,
39
+ "cache_ratio": 0.8301802744876187,
40
+ "cached_tokens": 186368,
41
+ "prompt_tokens": 224491,
42
+ "uncached_tokens": 38123,
43
+ "output_tokens": 13835,
44
+ "cache_weighted_input_10": 56759.8,
45
+ "cache_weighted_input_25": 84715,
46
+ "prompt_growth": 35644,
47
+ "antipatterns": 1,
48
+ "issues": 15,
49
+ "issue_types": [
50
+ "cache",
51
+ "cache_break",
52
+ "readonly_stall",
53
+ "broad_tool_result",
54
+ "service_tier",
55
+ "missed_parallelism",
56
+ "tool_churn_cluster"
57
+ ],
58
+ "read_fragmentation_paths": [],
59
+ "grep_sweep_count": 0
60
+ }
61
+ },
62
+ {
63
+ "id": "r4-impl-usage-summary",
64
+ "agent": "worker",
65
+ "ok": true,
66
+ "ms": 90877,
67
+ "sessionId": "sess_51564_1783039949781_1783039950027_596c0770872549478e285dccd2390597",
68
+ "output": "sessionId: sess_51564_1783039949781_1783039950027_596c0770872549478e285dccd2390597\n요청 범위가 작고 실행 승인이 포함되어 있어 바로 확인·수정·검증까지 진행하겠습니다.구현 파일과 npm script가 이미 작업트리에 있어 요구사항 대비 검증하고 필요한 보정만 하겠습니다.출력이 비어 있는 날에도 `GRAND TOTAL`이 보이도록 보정하겠습니다.패치 도구가 환경 문제로 실패해 PowerShell로 동일 변경을 적용하겠습니다.검증을 실행하겠습니다.차이 요약을 확인하겠습니다.새 스크립트가 untracked라 `git diff`에 빠져 상태까지 확인합니다.재영님, 완료했습니다.\n\n- **추가 기능** `usage:today` npm script와 `scripts/usage-today.mjs`를 추가해 오늘 0시 이후 usage를 모델별로 집계합니다.\n- **집계 내용** calls, input/cached/output tokens, costUsd와 `GRAND TOTAL` 라인을 출력하며 `MIXDOG_DATA_DIR`와 `--trace`, `--json`을 지원합니다.\n- **검증** `node --check scripts/usage-today.mjs` 통과, `npm run usage:today` 실행 성공.\n- **Diff 요약** `package.json` 1줄 추가, `scripts/usage-today.mjs` 신규 파일 추가.",
69
+ "card": {
70
+ "session": "sess_51564_1783039949781_1783039950027_596c0770872549478e285dccd2390597",
71
+ "provider": "openai-oauth",
72
+ "model": "gpt-5.5",
73
+ "wall_ms": 80356,
74
+ "turns": 11,
75
+ "tool_calls": 16,
76
+ "tools_per_turn": 1.5,
77
+ "total_tool_ms": 3261,
78
+ "llm_stream_ms": 76951,
79
+ "cache_ratio": 0.7513836864762118,
80
+ "cached_tokens": 161280,
81
+ "prompt_tokens": 214644,
82
+ "uncached_tokens": 53364,
83
+ "output_tokens": 5943,
84
+ "cache_weighted_input_10": 69492,
85
+ "cache_weighted_input_25": 93684,
86
+ "prompt_growth": 23910,
87
+ "antipatterns": 1,
88
+ "issues": 15,
89
+ "issue_types": [
90
+ "cache",
91
+ "cache_break",
92
+ "tool_error",
93
+ "service_tier",
94
+ "missed_parallelism",
95
+ "tool_churn_cluster"
96
+ ],
97
+ "read_fragmentation_paths": [],
98
+ "grep_sweep_count": 0
99
+ }
100
+ },
101
+ {
102
+ "id": "r4-explain-compact-engine",
103
+ "agent": "explore",
104
+ "ok": true,
105
+ "ms": 74444,
106
+ "sessionId": "sess_19712_1783039949794_1783039950038_d4b96a9272d27d7f810e51341f8c58b7",
107
+ "output": "op.mjs:329`), then fires if `shouldCompactForSession(...)` is true (`src/runtime/agent/orchestrator/session/loop.mjs:353`).\n\n- **Trigger logic**: `shouldCompactForSession` returns false without `auto`/boundary, true on reactive overflow, true on unknown token estimate, otherwise compares `messageTokens + reserveTokens` to `triggerTokens || boundaryTokens` (`src/runtime/agent/orchestrator/session/loop/compact-policy.mjs:170`).\n\n- **Reactive path**: provider context overflow sets `reactiveOverflowRetryPending = true`, causing the next loop to force compaction with trigger `reactive` (`src/runtime/agent/orchestrator/session/loop.mjs:996`, `src/runtime/agent/orchestrator/session/loop.mjs:1002`, `src/runtime/agent/orchestrator/session/loop.mjs:352`).\n\n- **Budgeting**: compact target is capped to the boundary and includes reserve (`src/runtime/agent/orchestrator/session/loop/compact-policy.mjs:163`); manual runner uses the same boundary/trigger/reserve concepts and skips when not forced and below threshold (`src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:150`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:176`).\n\n- **Semantic flow**: `semanticCompactMessages` no-ops if sanitized messages already fit and not forced, otherwise redacts, selects a head/tail window, summarizes the head with provider.send, then emits `system + summary + tail` (`src/runtime/agent/orchestrator/session/compact/engine.mjs:278`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:289`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:312`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:367`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:392`).\n\n- **Semantic tail selection**: `selectCompactionWindow` splits protected context from live history, uses user-anchored recent turns when users exist, otherwise valid non-tool cut points, then sanitizes/reconciles the preserved tail (`src/runtime/agent/orchestrator/session/compact/engine.mjs:235`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:240`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:250`).\n\n- **User-tail policy**: user-anchored mode keeps up to `tailTurns` recent turns within `preserveRecentBudget`; if the newest turn alone is too large it keeps a fitting suffix or moves everything into the head (`src/runtime/agent/orchestrator/session/compact/engine.mjs:134`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:141`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:151`).\n\n- **No-user tail policy**: cut-point mode never starts a tail on a `tool` result, grows the newest valid suffix while it fits, and falls back to minimal/empty tail so there is still a compactable head (`src/runtime/agent/orchestrator/session/compact/engine.mjs:121`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:193`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:207`).\n\n- **Recall-fasttrack flow**: loop first builds bounded recall text, then calls `recallFastTrackCompactMessages` with force and tail options (`src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs:148`, `src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs:161`).\n\n- **Recall tail policy**: recall preserves newest structured turns verbatim, default max 2 user turns and 8k token cap, dropping oldest turns then truncating oldest surviving content while preserving newest content (`src/runtime/agent/orchestrator/session/compact/engine.mjs:449`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:467`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:601`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:610`).\n\n- **Recall assembly**: recall path redacts after no-op check, splits protected/live/previous summary, selects recall tail/head, requires compactable prior history or recall/prior text, then emits `system + recall summary + recallTail` (`src/runtime/agent/orchestrator/session/compact/engine.mjs:625`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:644`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:646`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:651`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:653`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:695`).\n\n- **Redaction hook**: sensitive tool-call argument values are redacted only when compaction proceeds, before window selection, so preserved system/tail are measured and emitted redacted; no-op returns sanitized but unredacted messages (`src/runtime/agent/orchestrator/session/compact/engine.mjs:286`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:306`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:312`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:389`).\n\n- **Budget fallback**: both semantic and recall raise budget to fit mandatory `system + tail` plus minimum summary room instead of failing when preserved content exceeds target (`src/runtime/agent/orchestrator/session/compact/engine.mjs:319`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:328`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:658`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:661`).\n\n- **Semantic failures**: provider missing, empty live window, no compactable head, prompt/summary fit failure, empty provider summary, or final budget overflow throw from `engine.mjs` (`src/runtime/agent/orchestrator/session/compact/engine.mjs:279`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:254`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:315`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:335`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:372`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:395`).\n\n- **Recall failures**: recall path throws on no compactable prior history, empty recall text when not explicitly allowed, summary fit failure, or final budget overflow (`src/runtime/agent/orchestrator/session/compact/engine.mjs:653`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:666`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:691`, `src/runtime/agent/orchestrator/session/compact/engine.mjs:698`).\n\n- **Loop fallback**: semantic auto failure can recover via `pruneToolOutputsUnanchored` if the pruned transcript fits; recall-fasttrack failure deliberately skips this prune and escalates overflow (`src/runtime/agent/orchestrator/session/loop.mjs:501`, `src/runtime/agent/orchestrator/session/loop.mjs:517`, `src/runtime/agent/orchestrator/session/loop.mjs:527`, `src/runtime/agent/orchestrator/session/loop.mjs:618`).\n\n- **Manual fallback**: `runSessionCompaction` falls back from recall-fasttrack to semantic when enabled/provider exists; if non-recall still fails, it tries the same unanchored prune, otherwise records `manual_failed`/`post_turn_failed` without replacing history (`src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:237`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:245`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:322`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:348`).\n\n- **Success side effects**: successful loop compaction mutates in-memory messages, clears provider state, resets iterations, and records compact telemetry/events (`src/runtime/agent/orchestrator/session/loop.mjs:630`, `src/runtime/agent/orchestrator/session/loop.mjs:639`, `src/runtime/agent/orchestrator/session/loop.mjs:644`, `src/runtime/agent/orchestrator/session/loop.mjs:651`); manual runner writes `session.messages`, clears provider state, and stores compaction metadata (`src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:410`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:411`, `src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs:412`).",
108
+ "card": {
109
+ "session": "sess_19712_1783039949794_1783039950038_d4b96a9272d27d7f810e51341f8c58b7",
110
+ "provider": "openai-oauth",
111
+ "model": "gpt-5.5",
112
+ "wall_ms": 63903,
113
+ "turns": 8,
114
+ "tool_calls": 12,
115
+ "tools_per_turn": 1.5,
116
+ "total_tool_ms": 1254,
117
+ "llm_stream_ms": 62907,
118
+ "cache_ratio": 0.5888853058153284,
119
+ "cached_tokens": 109568,
120
+ "prompt_tokens": 186060,
121
+ "uncached_tokens": 76492,
122
+ "output_tokens": 4420,
123
+ "cache_weighted_input_10": 87448.8,
124
+ "cache_weighted_input_25": 103884,
125
+ "prompt_growth": 36532,
126
+ "antipatterns": 0,
127
+ "issues": 10,
128
+ "issue_types": [
129
+ "cache",
130
+ "cache_break",
131
+ "service_tier"
132
+ ],
133
+ "read_fragmentation_paths": [],
134
+ "grep_sweep_count": 0
135
+ }
136
+ }
137
+ ],
138
+ "task_complete": true,
139
+ "task_errors": [],
140
+ "score_complete": true,
141
+ "score_errors": [],
142
+ "group": {
143
+ "n": 3,
144
+ "wall_ms": 92774.7,
145
+ "turns": 9.3,
146
+ "tool_calls": 12,
147
+ "tools_per_turn": 1.3,
148
+ "total_tool_ms": 1670.7,
149
+ "llm_stream_ms": 91075,
150
+ "cache_ratio": 0.7,
151
+ "cached_tokens": 152405.3,
152
+ "prompt_tokens": 208398.3,
153
+ "uncached_tokens": 55993,
154
+ "output_tokens": 8066,
155
+ "cache_weighted_input_10": 71233.5,
156
+ "cache_weighted_input_25": 94094.3,
157
+ "prompt_growth": 32028.7,
158
+ "antipatterns": 0.7,
159
+ "issues": 13.3,
160
+ "grep_sweep_count": 0
161
+ },
162
+ "cards": [
163
+ {
164
+ "session": "sess_51468_1783039949771_1783039950019_986225c84c3411f10ae399d9084cae21",
165
+ "provider": "openai-oauth",
166
+ "model": "gpt-5.5",
167
+ "wall_ms": 134065,
168
+ "turns": 9,
169
+ "tool_calls": 8,
170
+ "tools_per_turn": 0.9,
171
+ "total_tool_ms": 497,
172
+ "llm_stream_ms": 133367,
173
+ "cache_ratio": 0.8301802744876187,
174
+ "cached_tokens": 186368,
175
+ "prompt_tokens": 224491,
176
+ "uncached_tokens": 38123,
177
+ "output_tokens": 13835,
178
+ "cache_weighted_input_10": 56759.8,
179
+ "cache_weighted_input_25": 84715,
180
+ "prompt_growth": 35644,
181
+ "antipatterns": 1,
182
+ "issues": 15,
183
+ "issue_types": [
184
+ "cache",
185
+ "cache_break",
186
+ "readonly_stall",
187
+ "broad_tool_result",
188
+ "service_tier",
189
+ "missed_parallelism",
190
+ "tool_churn_cluster"
191
+ ],
192
+ "read_fragmentation_paths": [],
193
+ "grep_sweep_count": 0
194
+ },
195
+ {
196
+ "session": "sess_51564_1783039949781_1783039950027_596c0770872549478e285dccd2390597",
197
+ "provider": "openai-oauth",
198
+ "model": "gpt-5.5",
199
+ "wall_ms": 80356,
200
+ "turns": 11,
201
+ "tool_calls": 16,
202
+ "tools_per_turn": 1.5,
203
+ "total_tool_ms": 3261,
204
+ "llm_stream_ms": 76951,
205
+ "cache_ratio": 0.7513836864762118,
206
+ "cached_tokens": 161280,
207
+ "prompt_tokens": 214644,
208
+ "uncached_tokens": 53364,
209
+ "output_tokens": 5943,
210
+ "cache_weighted_input_10": 69492,
211
+ "cache_weighted_input_25": 93684,
212
+ "prompt_growth": 23910,
213
+ "antipatterns": 1,
214
+ "issues": 15,
215
+ "issue_types": [
216
+ "cache",
217
+ "cache_break",
218
+ "tool_error",
219
+ "service_tier",
220
+ "missed_parallelism",
221
+ "tool_churn_cluster"
222
+ ],
223
+ "read_fragmentation_paths": [],
224
+ "grep_sweep_count": 0
225
+ },
226
+ {
227
+ "session": "sess_19712_1783039949794_1783039950038_d4b96a9272d27d7f810e51341f8c58b7",
228
+ "provider": "openai-oauth",
229
+ "model": "gpt-5.5",
230
+ "wall_ms": 63903,
231
+ "turns": 8,
232
+ "tool_calls": 12,
233
+ "tools_per_turn": 1.5,
234
+ "total_tool_ms": 1254,
235
+ "llm_stream_ms": 62907,
236
+ "cache_ratio": 0.5888853058153284,
237
+ "cached_tokens": 109568,
238
+ "prompt_tokens": 186060,
239
+ "uncached_tokens": 76492,
240
+ "output_tokens": 4420,
241
+ "cache_weighted_input_10": 87448.8,
242
+ "cache_weighted_input_25": 103884,
243
+ "prompt_growth": 36532,
244
+ "antipatterns": 0,
245
+ "issues": 10,
246
+ "issue_types": [
247
+ "cache",
248
+ "cache_break",
249
+ "service_tier"
250
+ ],
251
+ "read_fragmentation_paths": [],
252
+ "grep_sweep_count": 0
253
+ }
254
+ ]
255
+ }
@@ -17,7 +17,7 @@
17
17
  // This is INTERNAL A/B only (mixdog vs its own prior round). Cross-CLI compare
18
18
  // vs codex/claude (tree-total vs solo) is a separate future mode; codex/claude
19
19
  // runners are left as slots.
20
- import { execFileSync } from 'node:child_process';
20
+ import { execFile, execFileSync, spawn } from 'node:child_process';
21
21
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
22
22
  import { fileURLToPath, pathToFileURL } from 'node:url';
23
23
  import { dirname, resolve } from 'node:path';
@@ -57,7 +57,105 @@ function extractSessionId(text) {
57
57
  }
58
58
 
59
59
  const RUNNERS = {
60
- mixdog(task, opts) {
60
+ // Full Lead session (workflow routing + agent tool fan-out). This is the
61
+ // same path the REPL drives: createMixdogSessionRuntime().ask(). Use it to
62
+ // measure the delegation/cost-routing design (explorer/worker on cheap
63
+ // models, lead/reviewer on frontier) instead of a single-role headless run.
64
+ async lead(task, opts) {
65
+ const RUNTIME_URL = pathToFileURL(resolve(__dir, '../src/mixdog-session-runtime.mjs')).href;
66
+ const driver = [
67
+ `import { createMixdogSessionRuntime } from ${JSON.stringify(RUNTIME_URL)};`,
68
+ `process.chdir(${JSON.stringify(task.cwd ? resolve(task.cwd) : process.cwd())});`,
69
+ `const rt = await createMixdogSessionRuntime({`,
70
+ ` provider: ${JSON.stringify(opts.provider || null)} || undefined,`,
71
+ ` model: ${JSON.stringify(opts.model || null)} || undefined,`,
72
+ `});`,
73
+ `let text = '';`,
74
+ `// Mirror the TUI auto-resume EXACTLY (engine/agent-job-feed.mjs):`,
75
+ `// runtime notifications enqueue the model-visible completion into the`,
76
+ `// session's pending queue; the TUI then kicks an EMPTY pending-resume`,
77
+ `// ask (no injected user text) via queueMicrotask — the pre-send drain`,
78
+ `// pulls the pending messages. No polling, no synthetic 'continue', no`,
79
+ `// verdict heuristic: resume immediately per kick, finish when no kick`,
80
+ `// arrived during the last turn.`,
81
+ `let busy = false;`,
82
+ `let kickDeferred = false;`,
83
+ `let wake = null;`,
84
+ `rt.onNotification(() => {`,
85
+ ` // Defer like scheduleExecutionPendingResumeKick: the model-visible`,
86
+ ` // body is enqueued after onNotification returns.`,
87
+ ` queueMicrotask(() => {`,
88
+ ` if (busy) { kickDeferred = true; return; }`,
89
+ ` kickDeferred = true;`,
90
+ ` if (wake) wake();`,
91
+ ` });`,
92
+ ` return false;`,
93
+ `});`,
94
+ `const askOnce = async (msg) => {`,
95
+ ` busy = true;`,
96
+ ` try {`,
97
+ ` let t = '';`,
98
+ ` const { result } = await rt.ask(msg, { onTextDelta: (c) => { t += c; } });`,
99
+ ` return String(result?.text ?? t ?? '');`,
100
+ ` } finally { busy = false; }`,
101
+ `};`,
102
+ `text = await askOnce(${JSON.stringify(task.prompt || '')});`,
103
+ `const deadline = Date.now() + 30 * 60_000;`,
104
+ `// Session is "settled" only when no delegated agent task is still`,
105
+ `// running/pending — the live TUI stays open while agents run, so the`,
106
+ `// bench must too. Idle grace only applies once the agent board is clear.`,
107
+ `const agentsBusy = () => {`,
108
+ ` try {`,
109
+ ` const st = rt.agentStatus?.() || {};`,
110
+ ` const jobs = Array.isArray(st.agentJobs) ? st.agentJobs : [];`,
111
+ ` return jobs.some((j) => /running|pending|spawn|queued/i.test(String(j?.status || j?.state || '')));`,
112
+ ` } catch { return false; }`,
113
+ `};`,
114
+ `while (Date.now() < deadline) {`,
115
+ ` if (!kickDeferred) {`,
116
+ ` // Wait for the next kick. While agent tasks are still running the`,
117
+ ` // wait is unbounded (like a live TUI left open); once the board is`,
118
+ ` // clear a short grace catches stragglers, then the session settles.`,
119
+ ` for (;;) {`,
120
+ ` const kicked = await new Promise((r) => {`,
121
+ ` wake = () => r(true);`,
122
+ ` setTimeout(() => r(false), 10_000);`,
123
+ ` });`,
124
+ ` wake = null;`,
125
+ ` if (kicked || kickDeferred) break;`,
126
+ ` if (!agentsBusy()) break;`,
127
+ ` if (Date.now() >= deadline) break;`,
128
+ ` }`,
129
+ ` if (!kickDeferred && !agentsBusy()) break;`,
130
+ ` }`,
131
+ ` kickDeferred = false;`,
132
+ ` const t = await askOnce('');`,
133
+ ` if (String(t || '').trim()) text = t;`,
134
+ `}`,
135
+ `const sid = rt.sessionId || rt.session?.id || '';`,
136
+ `process.stdout.write('sessionId: ' + sid + '\\n');`,
137
+ `process.stdout.write(text);`,
138
+ `await rt.close('bench-exit');`,
139
+ `process.exit(0);`,
140
+ ].join('\n');
141
+ const started = Date.now();
142
+ const { raw, ok } = await new Promise((resolveRun) => {
143
+ execFile('node', ['--input-type=module', '-e', driver], {
144
+ encoding: 'utf8',
145
+ maxBuffer: 64 * 1024 * 1024,
146
+ env: {
147
+ ...process.env,
148
+ ...(opts.effort ? { MIXDOG_AGENT_EFFORT: opts.effort } : {}),
149
+ ...(opts.env || {}),
150
+ },
151
+ }, (err, stdout, stderrOut) => {
152
+ if (err) resolveRun({ raw: String(stdout || '') + String(stderrOut || ''), ok: false });
153
+ else resolveRun({ raw: String(stdout || ''), ok: true });
154
+ });
155
+ });
156
+ return { sessionId: extractSessionId(raw), ok, ms: Date.now() - started, raw };
157
+ },
158
+ async mixdog(task, opts) {
61
159
  const driver = [
62
160
  `import { runHeadlessRole } from ${JSON.stringify(HEADLESS)};`,
63
161
  `const out = [];`,
@@ -74,10 +172,8 @@ const RUNNERS = {
74
172
  `process.exit(code);`,
75
173
  ].join('\n');
76
174
  const started = Date.now();
77
- let raw = '';
78
- let ok = false;
79
- try {
80
- raw = execFileSync('node', ['--input-type=module', '-e', driver], {
175
+ const { raw, ok } = await new Promise((resolveRun) => {
176
+ execFile('node', ['--input-type=module', '-e', driver], {
81
177
  encoding: 'utf8',
82
178
  maxBuffer: 64 * 1024 * 1024,
83
179
  env: {
@@ -86,16 +182,67 @@ const RUNNERS = {
86
182
  ...(opts.fast ? { MIXDOG_AGENT_FAST: '1' } : {}),
87
183
  ...(opts.env || {}),
88
184
  },
185
+ }, (err, stdout, stderrOut) => {
186
+ if (err) resolveRun({ raw: String(stdout || '') + String(stderrOut || ''), ok: false });
187
+ else resolveRun({ raw: String(stdout || ''), ok: true });
89
188
  });
90
- ok = true;
91
- } catch (e) {
92
- raw = String(e.stdout || '') + String(e.stderr || '');
93
- ok = false;
94
- }
189
+ });
95
190
  return { sessionId: extractSessionId(raw), ok, ms: Date.now() - started, raw };
96
191
  },
97
- codex() {
98
- throw new Error('runner "codex" not implemented (slot: codex exec --json). Cross-CLI compare is a separate mode.');
192
+ // Cross-CLI runner: codex exec --json (JSONL events). Prompt goes via stdin
193
+ // ("-") so no shell-quoting issues on Windows (.cmd shim needs shell:true).
194
+ // Sandbox is read-only: review tasks must not mutate the repo. Usage comes
195
+ // from turn.completed events; the final agent_message is kept for judging.
196
+ async codex(task, opts) {
197
+ const args = ['exec', '--json', '--skip-git-repo-check', '-s', 'read-only',
198
+ '-C', task.cwd ? resolve(task.cwd) : process.cwd()];
199
+ if (opts.model) args.push('-m', opts.model);
200
+ if (opts.effort) args.push('-c', `model_reasoning_effort="${opts.effort}"`);
201
+ args.push('-');
202
+ const started = Date.now();
203
+ const { raw, ok } = await new Promise((resolveRun) => {
204
+ const child = spawn('codex', args, { shell: true, env: { ...process.env, ...(opts.env || {}) } });
205
+ let out = '';
206
+ let errOut = '';
207
+ child.stdout.on('data', (d) => { out += d; });
208
+ child.stderr.on('data', (d) => { errOut += d; });
209
+ child.on('error', (e) => resolveRun({ raw: `${out}\n${errOut}\n${e.message}`, ok: false }));
210
+ child.on('close', (code) => resolveRun({ raw: code === 0 ? out : `${out}\n${errOut}`, ok: code === 0 }));
211
+ child.stdin.write(String(task.prompt || ''));
212
+ child.stdin.end();
213
+ });
214
+ const ms = Date.now() - started;
215
+ // Parse JSONL: thread id, summed usage, final agent message.
216
+ let threadId = null;
217
+ const usage = { uncached_in: 0, cached_in: 0, out_tokens: 0 };
218
+ let finalMessage = '';
219
+ for (const line of raw.split(/\r?\n/)) {
220
+ if (!line.trim().startsWith('{')) continue;
221
+ let ev = null;
222
+ try { ev = JSON.parse(line); } catch { continue; }
223
+ if (ev?.thread_id && !threadId) threadId = String(ev.thread_id);
224
+ const u = ev?.usage || ev?.turn?.usage || null;
225
+ if (u) {
226
+ usage.uncached_in += num(u.input_tokens) - num(u.cached_input_tokens);
227
+ usage.cached_in += num(u.cached_input_tokens);
228
+ usage.out_tokens += num(u.output_tokens);
229
+ }
230
+ const item = ev?.item;
231
+ if (item && (item.type === 'agent_message' || item.item_type === 'agent_message') && item.text) {
232
+ finalMessage = String(item.text);
233
+ }
234
+ }
235
+ const totalIn = usage.uncached_in + usage.cached_in;
236
+ const card = {
237
+ session: threadId || `codex-${started}`,
238
+ wall_ms: ms,
239
+ uncached_in: usage.uncached_in,
240
+ cached_in: usage.cached_in,
241
+ out_tokens: usage.out_tokens,
242
+ cache_ratio: totalIn > 0 ? Math.round((usage.cached_in / totalIn) * 1000) / 1000 : 0,
243
+ completed: ok ? 1 : 0,
244
+ };
245
+ return { sessionId: card.session, ok, ms, raw, card, finalMessage };
99
246
  },
100
247
  claude() {
101
248
  throw new Error('runner "claude" not implemented (slot: claude -p --output-format json). Cross-CLI compare is a separate mode.');
@@ -106,6 +253,7 @@ function scoreSessions(ids) {
106
253
  if (!ids.length) return null;
107
254
  const raw = execFileSync('node', [TASK_BENCH, '--session', ids.join(','), '--group', '--json'], {
108
255
  encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
256
+ env: { ...process.env, ...(opts?.env || {}) },
109
257
  });
110
258
  const s = raw.replace(/^\uFEFF/, '');
111
259
  const i = s.indexOf('{');
@@ -116,12 +264,13 @@ function sleepMs(ms) {
116
264
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(0, ms));
117
265
  }
118
266
 
119
- function scoreSession(sessionId, { attempts = 5 } = {}) {
267
+ function scoreSession(sessionId, { attempts = 5, env = null } = {}) {
120
268
  let lastError = null;
121
269
  for (let i = 0; i < attempts; i += 1) {
122
270
  try {
123
271
  const raw = execFileSync('node', [TASK_BENCH, '--session', sessionId, '--json'], {
124
272
  encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
273
+ env: { ...process.env, ...(env || {}) },
125
274
  });
126
275
  const s = raw.replace(/^\uFEFF/, '');
127
276
  const j = JSON.parse(s.slice(Math.max(0, s.indexOf('{'))));
@@ -189,25 +338,62 @@ const opts = {
189
338
  };
190
339
  process.stderr.write(`[bench-run] model=${opts.model || '(agent default)'} provider=${opts.provider || '(agent default)'} effort=${opts.effort || '-'} fast=${opts.fast} env=${JSON.stringify(_env)}\n`);
191
340
 
192
- const results = [];
193
- for (const task of tasks) {
341
+ // --parallel N: run up to N tasks concurrently (default: all). Scoring stays
342
+ // sequential after runs so task-bench reads a settled trace.
343
+ const parallel = Math.max(1, Number.parseInt(argValue('--parallel', String(tasks.length)), 10) || tasks.length);
344
+ // --repeat N: run the whole task set N times (repeat r appended to task id as
345
+ // "id#r") so group averages settle over more samples.
346
+ const repeat = Math.max(1, Number.parseInt(argValue('--repeat', '1'), 10) || 1);
347
+ process.stderr.write(`[bench-run] parallel=${parallel} repeat=${repeat}\n`);
348
+ const runList = [];
349
+ for (let r = 1; r <= repeat; r += 1) {
350
+ for (const task of tasks) {
351
+ runList.push(repeat > 1 ? { ...task, id: `${task.id || task.agent}#${r}` } : task);
352
+ }
353
+ }
354
+
355
+ async function runTask(task) {
194
356
  process.stderr.write(`[bench-run] round=${round} runner=${runnerName} task=${task.id || task.agent} ...\n`);
195
357
  let r;
196
- try { r = runner(task, opts); }
197
- catch (e) { console.error(`[bench-run] runner error: ${e.message}`); process.exit(1); }
198
- process.stderr.write(`[bench-run] -> ${r.ok ? 'ok' : 'FAIL'} ${Math.round(r.ms / 1000)}s session=${r.sessionId || '(none)'}\n`);
199
- const result = { id: task.id || null, agent: task.agent || null, ok: r.ok, ms: r.ms, sessionId: r.sessionId };
200
- if (r.sessionId) {
201
- const scored = scoreSession(r.sessionId);
202
- if (scored.ok) {
203
- result.card = scored.card;
204
- process.stderr.write(`[bench-run] -> scored ${r.sessionId.slice(0, 22)} turns=${scored.card.turns} tools=${scored.card.tool_calls}\n`);
205
- } else {
206
- result.scoreError = scored.error;
207
- process.stderr.write(`[bench-run] -> SCORE FAIL ${r.sessionId.slice(0, 22)} ${scored.error}\n`);
358
+ try { r = await runner(task, opts); }
359
+ catch (e) {
360
+ console.error(`[bench-run] runner error (${task.id || task.agent}): ${e.message}`);
361
+ return { id: task.id || null, agent: task.agent || null, ok: false, ms: 0, sessionId: null };
362
+ }
363
+ process.stderr.write(`[bench-run] -> ${task.id || task.agent}: ${r.ok ? 'ok' : 'FAIL'} ${Math.round(r.ms / 1000)}s session=${r.sessionId || '(none)'}\n`);
364
+ return {
365
+ id: task.id || null, agent: task.agent || null, ok: r.ok, ms: r.ms, sessionId: r.sessionId,
366
+ // External runners (codex/claude) pre-build their scorecard; mixdog scores
367
+ // from its own trace below. Output text is kept for quality judging.
368
+ ...(r.card ? { card: r.card } : {}),
369
+ output: String(r.finalMessage || r.raw || '').slice(-8000),
370
+ };
371
+ }
372
+
373
+ const results = new Array(runList.length);
374
+ {
375
+ let next = 0;
376
+ async function drain() {
377
+ for (;;) {
378
+ const i = next++;
379
+ if (i >= runList.length) return;
380
+ results[i] = await runTask(runList[i]);
208
381
  }
209
382
  }
210
- results.push(result);
383
+ await Promise.all(Array.from({ length: Math.min(parallel, runList.length) }, drain));
384
+ }
385
+
386
+ for (const result of results) {
387
+ if (!result.sessionId) continue;
388
+ if (result.card) continue; // external runner already carries its card
389
+ const scored = scoreSession(result.sessionId, { env: opts.env });
390
+ if (scored.ok) {
391
+ result.card = scored.card;
392
+ process.stderr.write(`[bench-run] -> scored ${result.sessionId.slice(0, 22)} turns=${scored.card.turns} tools=${scored.card.tool_calls}\n`);
393
+ } else {
394
+ result.scoreError = scored.error;
395
+ process.stderr.write(`[bench-run] -> SCORE FAIL ${result.sessionId.slice(0, 22)} ${scored.error}\n`);
396
+ }
211
397
  }
212
398
 
213
399
  const sessionIds = results.map((r) => r.sessionId).filter(Boolean);