mixdog 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -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,13 +17,14 @@
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';
24
24
 
25
25
  const __dir = dirname(fileURLToPath(import.meta.url));
26
26
  const HEADLESS = pathToFileURL(resolve(__dir, '../src/headless-role.mjs')).href;
27
+ const WORKFLOW_MOD = pathToFileURL(resolve(__dir, '../src/session-runtime/workflow.mjs')).href;
27
28
  const TASK_BENCH = resolve(__dir, 'task-bench.mjs');
28
29
 
29
30
  function argValue(name, fallback = null) {
@@ -57,7 +58,108 @@ function extractSessionId(text) {
57
58
  }
58
59
 
59
60
  const RUNNERS = {
60
- mixdog(task, opts) {
61
+ // Full Lead session (workflow routing + agent tool fan-out). This is the
62
+ // same path the REPL drives: createMixdogSessionRuntime().ask(). Use it to
63
+ // measure the delegation/cost-routing design (explorer/worker on cheap
64
+ // models, lead/reviewer on frontier) instead of a single-role headless run.
65
+ async lead(task, opts) {
66
+ const RUNTIME_URL = pathToFileURL(resolve(__dir, '../src/mixdog-session-runtime.mjs')).href;
67
+ const driver = [
68
+ `import { createMixdogSessionRuntime } from ${JSON.stringify(RUNTIME_URL)};`,
69
+ `import { normalizeWorkflowId } from ${JSON.stringify(WORKFLOW_MOD)};`,
70
+ `process.chdir(${JSON.stringify(task.cwd ? resolve(task.cwd) : process.cwd())});`,
71
+ `const rt = await createMixdogSessionRuntime({`,
72
+ ` provider: ${JSON.stringify(opts.provider || null)} || undefined,`,
73
+ ` model: ${JSON.stringify(opts.model || null)} || undefined,`,
74
+ `});`,
75
+ `if (${JSON.stringify(opts.workflow || null)}) {`,
76
+ ` await rt.setWorkflow(normalizeWorkflowId(${JSON.stringify(opts.workflow || null)}));`,
77
+ `}`,
78
+ `let text = '';`,
79
+ `// Mirror the TUI auto-resume EXACTLY (engine/agent-job-feed.mjs):`,
80
+ `// runtime notifications enqueue the model-visible completion into the`,
81
+ `// session's pending queue; the TUI then kicks an EMPTY pending-resume`,
82
+ `// ask (no injected user text) via queueMicrotask — the pre-send drain`,
83
+ `// pulls the pending messages. No polling, no synthetic 'continue', no`,
84
+ `// verdict heuristic: resume immediately per kick, finish when no kick`,
85
+ `// arrived during the last turn.`,
86
+ `let busy = false;`,
87
+ `let kickDeferred = false;`,
88
+ `let wake = null;`,
89
+ `rt.onNotification(() => {`,
90
+ ` // Defer like scheduleExecutionPendingResumeKick: the model-visible`,
91
+ ` // body is enqueued after onNotification returns.`,
92
+ ` queueMicrotask(() => {`,
93
+ ` if (busy) { kickDeferred = true; return; }`,
94
+ ` kickDeferred = true;`,
95
+ ` if (wake) wake();`,
96
+ ` });`,
97
+ ` return false;`,
98
+ `});`,
99
+ `const askOnce = async (msg) => {`,
100
+ ` busy = true;`,
101
+ ` try {`,
102
+ ` let t = '';`,
103
+ ` const { result } = await rt.ask(msg, { onTextDelta: (c) => { t += c; } });`,
104
+ ` return String(result?.text ?? t ?? '');`,
105
+ ` } finally { busy = false; }`,
106
+ `};`,
107
+ `text = await askOnce(${JSON.stringify(task.prompt || '')});`,
108
+ `const deadline = Date.now() + 30 * 60_000;`,
109
+ `// Session is "settled" only when no delegated agent task is still`,
110
+ `// running/pending — the live TUI stays open while agents run, so the`,
111
+ `// bench must too. Idle grace only applies once the agent board is clear.`,
112
+ `const agentsBusy = () => {`,
113
+ ` try {`,
114
+ ` const st = rt.agentStatus?.() || {};`,
115
+ ` const jobs = Array.isArray(st.agentJobs) ? st.agentJobs : [];`,
116
+ ` return jobs.some((j) => /running|pending|spawn|queued/i.test(String(j?.status || j?.state || '')));`,
117
+ ` } catch { return false; }`,
118
+ `};`,
119
+ `while (Date.now() < deadline) {`,
120
+ ` if (!kickDeferred) {`,
121
+ ` // Wait for the next kick. While agent tasks are still running the`,
122
+ ` // wait is unbounded (like a live TUI left open); once the board is`,
123
+ ` // clear a short grace catches stragglers, then the session settles.`,
124
+ ` for (;;) {`,
125
+ ` const kicked = await new Promise((r) => {`,
126
+ ` wake = () => r(true);`,
127
+ ` setTimeout(() => r(false), 10_000);`,
128
+ ` });`,
129
+ ` wake = null;`,
130
+ ` if (kicked || kickDeferred) break;`,
131
+ ` if (!agentsBusy()) break;`,
132
+ ` if (Date.now() >= deadline) break;`,
133
+ ` }`,
134
+ ` if (!kickDeferred && !agentsBusy()) break;`,
135
+ ` }`,
136
+ ` kickDeferred = false;`,
137
+ ` const t = await askOnce('');`,
138
+ ` if (String(t || '').trim()) text = t;`,
139
+ `}`,
140
+ `const sid = rt.sessionId || rt.session?.id || '';`,
141
+ `process.stdout.write('sessionId: ' + sid + '\\n');`,
142
+ `process.stdout.write(text);`,
143
+ `await rt.close('bench-exit');`,
144
+ `process.exit(0);`,
145
+ ].join('\n');
146
+ const started = Date.now();
147
+ const { raw, ok } = await new Promise((resolveRun) => {
148
+ execFile('node', ['--input-type=module', '-e', driver], {
149
+ encoding: 'utf8',
150
+ maxBuffer: 64 * 1024 * 1024,
151
+ env: {
152
+ ...process.env,
153
+ ...(opts.env || {}),
154
+ },
155
+ }, (err, stdout, stderrOut) => {
156
+ if (err) resolveRun({ raw: String(stdout || '') + String(stderrOut || ''), ok: false });
157
+ else resolveRun({ raw: String(stdout || ''), ok: true });
158
+ });
159
+ });
160
+ return { sessionId: extractSessionId(raw), ok, ms: Date.now() - started, raw };
161
+ },
162
+ async mixdog(task, opts) {
61
163
  const driver = [
62
164
  `import { runHeadlessRole } from ${JSON.stringify(HEADLESS)};`,
63
165
  `const out = [];`,
@@ -66,6 +168,8 @@ const RUNNERS = {
66
168
  ` message: ${JSON.stringify(task.prompt || '')},`,
67
169
  ` provider: ${JSON.stringify(opts.provider || null)},`,
68
170
  ` model: ${JSON.stringify(opts.model || null)},`,
171
+ ` effort: ${JSON.stringify(opts.effort || null)},`,
172
+ ` fast: ${JSON.stringify(opts.fast === true)},`,
69
173
  ` cwd: ${JSON.stringify(task.cwd ? resolve(task.cwd) : process.cwd())},`,
70
174
  ` write: (t) => out.push(t),`,
71
175
  ` writeErr: (t) => process.stderr.write(t),`,
@@ -74,28 +178,77 @@ const RUNNERS = {
74
178
  `process.exit(code);`,
75
179
  ].join('\n');
76
180
  const started = Date.now();
77
- let raw = '';
78
- let ok = false;
79
- try {
80
- raw = execFileSync('node', ['--input-type=module', '-e', driver], {
181
+ const { raw, ok } = await new Promise((resolveRun) => {
182
+ execFile('node', ['--input-type=module', '-e', driver], {
81
183
  encoding: 'utf8',
82
184
  maxBuffer: 64 * 1024 * 1024,
83
185
  env: {
84
186
  ...process.env,
85
- ...(opts.effort ? { MIXDOG_AGENT_EFFORT: opts.effort } : {}),
86
- ...(opts.fast ? { MIXDOG_AGENT_FAST: '1' } : {}),
87
187
  ...(opts.env || {}),
88
188
  },
189
+ }, (err, stdout, stderrOut) => {
190
+ if (err) resolveRun({ raw: String(stdout || '') + String(stderrOut || ''), ok: false });
191
+ else resolveRun({ raw: String(stdout || ''), ok: true });
89
192
  });
90
- ok = true;
91
- } catch (e) {
92
- raw = String(e.stdout || '') + String(e.stderr || '');
93
- ok = false;
94
- }
193
+ });
95
194
  return { sessionId: extractSessionId(raw), ok, ms: Date.now() - started, raw };
96
195
  },
97
- codex() {
98
- throw new Error('runner "codex" not implemented (slot: codex exec --json). Cross-CLI compare is a separate mode.');
196
+ // Cross-CLI runner: codex exec --json (JSONL events). Prompt goes via stdin
197
+ // ("-") so no shell-quoting issues on Windows (.cmd shim needs shell:true).
198
+ // Sandbox is read-only: review tasks must not mutate the repo. Usage comes
199
+ // from turn.completed events; the final agent_message is kept for judging.
200
+ async codex(task, opts) {
201
+ const args = ['exec', '--json', '--skip-git-repo-check', '-s', 'read-only',
202
+ '-C', task.cwd ? resolve(task.cwd) : process.cwd()];
203
+ if (opts.model) args.push('-m', opts.model);
204
+ if (opts.effort) args.push('-c', `model_reasoning_effort="${opts.effort}"`);
205
+ if (opts.fast) args.push('-c', 'service_tier="fast"');
206
+ args.push('-');
207
+ const started = Date.now();
208
+ const { raw, ok } = await new Promise((resolveRun) => {
209
+ const child = spawn('codex', args, { shell: true, env: { ...process.env, ...(opts.env || {}) } });
210
+ let out = '';
211
+ let errOut = '';
212
+ child.stdout.on('data', (d) => { out += d; });
213
+ child.stderr.on('data', (d) => { errOut += d; });
214
+ child.on('error', (e) => resolveRun({ raw: `${out}\n${errOut}\n${e.message}`, ok: false }));
215
+ child.on('close', (code) => resolveRun({ raw: code === 0 ? out : `${out}\n${errOut}`, ok: code === 0 }));
216
+ child.stdin.write(String(task.prompt || ''));
217
+ child.stdin.end();
218
+ });
219
+ const ms = Date.now() - started;
220
+ // Parse JSONL: thread id, summed usage, final agent message.
221
+ let threadId = null;
222
+ const usage = { uncached_in: 0, cached_in: 0, out_tokens: 0 };
223
+ let finalMessage = '';
224
+ for (const line of raw.split(/\r?\n/)) {
225
+ if (!line.trim().startsWith('{')) continue;
226
+ let ev = null;
227
+ try { ev = JSON.parse(line); } catch { continue; }
228
+ if (ev?.thread_id && !threadId) threadId = String(ev.thread_id);
229
+ const u = ev?.usage || ev?.turn?.usage || null;
230
+ if (u) {
231
+ usage.uncached_in += num(u.input_tokens) - num(u.cached_input_tokens);
232
+ usage.cached_in += num(u.cached_input_tokens);
233
+ usage.out_tokens += num(u.output_tokens);
234
+ }
235
+ const item = ev?.item;
236
+ if (item && (item.type === 'agent_message' || item.item_type === 'agent_message') && item.text) {
237
+ finalMessage = String(item.text);
238
+ }
239
+ }
240
+ const totalIn = usage.uncached_in + usage.cached_in;
241
+ const card = {
242
+ session: threadId || `codex-${started}`,
243
+ wall_ms: ms,
244
+ uncached_in: usage.uncached_in,
245
+ cached_in: usage.cached_in,
246
+ out_tokens: usage.out_tokens,
247
+ cache_ratio: totalIn > 0 ? Math.round((usage.cached_in / totalIn) * 1000) / 1000 : 0,
248
+ completed: ok ? 1 : 0,
249
+ usd_cost: usdCost(opts.model, usage.uncached_in, usage.cached_in, usage.out_tokens),
250
+ };
251
+ return { sessionId: card.session, ok, ms, raw, card, finalMessage };
99
252
  },
100
253
  claude() {
101
254
  throw new Error('runner "claude" not implemented (slot: claude -p --output-format json). Cross-CLI compare is a separate mode.');
@@ -106,6 +259,7 @@ function scoreSessions(ids) {
106
259
  if (!ids.length) return null;
107
260
  const raw = execFileSync('node', [TASK_BENCH, '--session', ids.join(','), '--group', '--json'], {
108
261
  encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
262
+ env: { ...process.env, ...(opts?.env || {}) },
109
263
  });
110
264
  const s = raw.replace(/^\uFEFF/, '');
111
265
  const i = s.indexOf('{');
@@ -116,12 +270,13 @@ function sleepMs(ms) {
116
270
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(0, ms));
117
271
  }
118
272
 
119
- function scoreSession(sessionId, { attempts = 5 } = {}) {
273
+ function scoreSession(sessionId, { attempts = 5, env = null } = {}) {
120
274
  let lastError = null;
121
275
  for (let i = 0; i < attempts; i += 1) {
122
276
  try {
123
277
  const raw = execFileSync('node', [TASK_BENCH, '--session', sessionId, '--json'], {
124
278
  encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
279
+ env: { ...process.env, ...(env || {}) },
125
280
  });
126
281
  const s = raw.replace(/^\uFEFF/, '');
127
282
  const j = JSON.parse(s.slice(Math.max(0, s.indexOf('{'))));
@@ -141,6 +296,23 @@ function num(v) {
141
296
  return Number.isFinite(n) ? n : 0;
142
297
  }
143
298
 
299
+ // USD list pricing per 1M tokens (input / cached input / output), 2026-07.
300
+ // Cross-harness cost comparison needs a real currency: OAuth lanes bill
301
+ // quota, not dollars, but token counts priced at API list rates are the
302
+ // only harness-neutral metric. Unknown models fall back to gpt-5.5 rates.
303
+ const USD_PER_1M = {
304
+ 'gpt-5.5': { in: 5.00, cachedIn: 0.50, out: 30.00 },
305
+ 'claude-sonnet-5': { in: 2.00, cachedIn: 0.20, out: 10.00 },
306
+ 'claude-fable-5': { in: 5.00, cachedIn: 0.50, out: 25.00 },
307
+ };
308
+ function usdCost(model, uncachedIn, cachedIn, outTokens) {
309
+ const key = String(model || '').toLowerCase();
310
+ const p = USD_PER_1M[key]
311
+ || USD_PER_1M[Object.keys(USD_PER_1M).find((k) => key.includes(k))]
312
+ || USD_PER_1M['gpt-5.5'];
313
+ return Math.round(((num(uncachedIn) * p.in + num(cachedIn) * p.cachedIn + num(outTokens) * p.out) / 1e6) * 10000) / 10000;
314
+ }
315
+
144
316
  function averageCards(cards) {
145
317
  if (!cards.length) return null;
146
318
  const keys = Object.keys(cards[0]).filter((k) => cards.some((c) => typeof c[k] === 'number' && Number.isFinite(c[k])));
@@ -185,29 +357,76 @@ const opts = {
185
357
  model: _mo.model,
186
358
  effort: argValue('--effort', null),
187
359
  fast: hasFlag('--fast'),
360
+ workflow: argValue('--workflow', null),
188
361
  env: _env,
189
362
  };
190
- 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`);
363
+ process.stderr.write(`[bench-run] model=${opts.model || '(agent default)'} provider=${opts.provider || '(agent default)'} effort=${opts.effort || '-'} fast=${opts.fast} workflow=${opts.workflow || '-'} env=${JSON.stringify(_env)}\n`);
364
+
365
+ // --parallel N: run up to N tasks concurrently (default: all). Scoring stays
366
+ // sequential after runs so task-bench reads a settled trace.
367
+ const parallel = Math.max(1, Number.parseInt(argValue('--parallel', String(tasks.length)), 10) || tasks.length);
368
+ // --repeat N: run the whole task set N times (repeat r appended to task id as
369
+ // "id#r") so group averages settle over more samples.
370
+ const repeat = Math.max(1, Number.parseInt(argValue('--repeat', '1'), 10) || 1);
371
+ // --stagger MS: delay each parallel lane's start by laneIndex*MS so session
372
+ // first-calls don't race the server prompt-cache write window (A/B for
373
+ // parallel fan-out cache misses).
374
+ const staggerMs = Math.max(0, Number.parseInt(argValue('--stagger', '0'), 10) || 0);
375
+ process.stderr.write(`[bench-run] parallel=${parallel} repeat=${repeat} stagger=${staggerMs}ms\n`);
376
+ const runList = [];
377
+ for (let r = 1; r <= repeat; r += 1) {
378
+ for (const task of tasks) {
379
+ runList.push(repeat > 1 ? { ...task, id: `${task.id || task.agent}#${r}` } : task);
380
+ }
381
+ }
191
382
 
192
- const results = [];
193
- for (const task of tasks) {
383
+ async function runTask(task) {
194
384
  process.stderr.write(`[bench-run] round=${round} runner=${runnerName} task=${task.id || task.agent} ...\n`);
195
385
  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`);
386
+ try { r = await runner(task, opts); }
387
+ catch (e) {
388
+ console.error(`[bench-run] runner error (${task.id || task.agent}): ${e.message}`);
389
+ return { id: task.id || null, agent: task.agent || null, ok: false, ms: 0, sessionId: null };
390
+ }
391
+ process.stderr.write(`[bench-run] -> ${task.id || task.agent}: ${r.ok ? 'ok' : 'FAIL'} ${Math.round(r.ms / 1000)}s session=${r.sessionId || '(none)'}\n`);
392
+ return {
393
+ id: task.id || null, agent: task.agent || null, ok: r.ok, ms: r.ms, sessionId: r.sessionId,
394
+ // External runners (codex/claude) pre-build their scorecard; mixdog scores
395
+ // from its own trace below. Output text is kept for quality judging.
396
+ ...(r.card ? { card: r.card } : {}),
397
+ output: String(r.finalMessage || r.raw || '').slice(-8000),
398
+ };
399
+ }
400
+
401
+ const results = new Array(runList.length);
402
+ {
403
+ let next = 0;
404
+ async function drain(lane) {
405
+ if (staggerMs > 0 && lane > 0) await new Promise((res) => setTimeout(res, lane * staggerMs));
406
+ for (;;) {
407
+ const i = next++;
408
+ if (i >= runList.length) return;
409
+ results[i] = await runTask(runList[i]);
208
410
  }
209
411
  }
210
- results.push(result);
412
+ await Promise.all(Array.from({ length: Math.min(parallel, runList.length) }, (_, lane) => drain(lane)));
413
+ }
414
+
415
+ for (const result of results) {
416
+ if (!result.sessionId) continue;
417
+ if (result.card) continue; // external runner already carries its card
418
+ const scored = scoreSession(result.sessionId, { env: opts.env });
419
+ if (scored.ok) {
420
+ result.card = scored.card;
421
+ // task-bench cards expose uncached/cached/output token fields; price at
422
+ // the session's own model when present, else the round's --model.
423
+ const c = scored.card;
424
+ c.usd_cost = usdCost(c.model || opts.model, c.uncached_tokens, c.cached_tokens, c.output_tokens);
425
+ process.stderr.write(`[bench-run] -> scored ${result.sessionId.slice(0, 22)} turns=${scored.card.turns} tools=${scored.card.tool_calls}\n`);
426
+ } else {
427
+ result.scoreError = scored.error;
428
+ process.stderr.write(`[bench-run] -> SCORE FAIL ${result.sessionId.slice(0, 22)} ${scored.error}\n`);
429
+ }
211
430
  }
212
431
 
213
432
  const sessionIds = results.map((r) => r.sessionId).filter(Boolean);