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,465 @@
1
+ /**
2
+ * openai-ws-pool.mjs — WebSocket connection pool for the OpenAI OAuth provider.
3
+ *
4
+ * Extracted from openai-oauth-ws.mjs. Owns the socket pool singleton
5
+ * (_wsPool), handshake/open/acquire/release lifecycle, idle-close timers and
6
+ * the process-exit drain fence. openai-oauth-ws.mjs imports acquire/release/
7
+ * _sendFrame and re-exports the drain hooks for legacy import paths.
8
+ */
9
+ import WebSocket from 'ws';
10
+ import { errText } from '../../../shared/err-text.mjs';
11
+ import { createHash, randomBytes } from 'crypto';
12
+ import { appendFileSync } from 'node:fs';
13
+ import { codexUserAgent, codexVersionHeader } from './codex-client-meta.mjs';
14
+ import {
15
+ PROVIDER_WS_ACQUIRE_TIMEOUT_MS,
16
+ PROVIDER_WS_HANDSHAKE_TIMEOUT_MS,
17
+ resolveTimeoutMs,
18
+ } from '../stall-policy.mjs';
19
+
20
+ // Human-readable transport label for handshake/acquire error messages. Shared
21
+ // with openai-oauth-ws.mjs (stream-side errors use the same labels).
22
+ export function _wsErrLabel(p) {
23
+ if (p === 'xai') return 'xAI WS';
24
+ if (p === 'openai-direct' || p === 'openai') return 'OpenAI WS';
25
+ return 'OpenAI OAuth WS';
26
+ }
27
+
28
+ const CODEX_WS_URL = 'wss://chatgpt.com/backend-api/codex/responses';
29
+ const CODEX_OAUTH_ORIGINATOR = 'codex_cli_rs';
30
+ const OPENAI_WS_URL = 'wss://api.openai.com/v1/responses';
31
+ const XAI_WS_URL = 'wss://api.x.ai/v1/responses';
32
+ export const WS_IDLE_MS = resolveTimeoutMs(
33
+ 'MIXDOG_PROVIDER_WS_IDLE_MS',
34
+ 20 * 60_000,
35
+ { minMs: 60_000, maxMs: 60 * 60_000 },
36
+ );
37
+ const WS_HANDSHAKE_TIMEOUT_MS = PROVIDER_WS_HANDSHAKE_TIMEOUT_MS;
38
+ const WS_ACQUIRE_TIMEOUT_MS = PROVIDER_WS_ACQUIRE_TIMEOUT_MS;
39
+
40
+ // WS socket pool buckets are keyed by `poolKey` (the per-call sessionId)
41
+ // to isolate parallel agent invocations — each gets its own socket so
42
+ // a second caller cannot grab a sibling's mid-turn entry (openai-oauth would
43
+ // otherwise reject the new response.create with "No tool output found
44
+ // for function call ..."). The handshake `session_id` header/URL
45
+ // uses `cacheKey` — for OpenAI OAuth this mirrors Codex's thread-scoped
46
+ // prompt_cache_key by default. The backend dedupes cache by handshake
47
+ // session_id, not by body.prompt_cache_key alone (measured 2026-04-19 after the
48
+ // v0.6.151 regression).
49
+ const MAX_POOLED_SOCKETS_PER_KEY = 8;
50
+
51
+ // poolKey -> Entry[]
52
+ // Entry: { socket, busy, idleTimer, lastResponseId, lastRequestSansInput,
53
+ // lastRequestInput, lastResponseItems, lastInputLen, turnState,
54
+ // closing, ephemeral }
55
+ const _wsPool = new Map();
56
+
57
+ function _getPoolArr(poolKey) {
58
+ if (!poolKey) return null;
59
+ let arr = _wsPool.get(poolKey);
60
+ if (!arr) {
61
+ arr = [];
62
+ _wsPool.set(poolKey, arr);
63
+ }
64
+ return arr;
65
+ }
66
+
67
+ function _removeFromPool(poolKey, entry) {
68
+ if (!poolKey) return;
69
+ const arr = _wsPool.get(poolKey);
70
+ if (!arr) return;
71
+ const idx = arr.indexOf(entry);
72
+ if (idx >= 0) arr.splice(idx, 1);
73
+ if (arr.length === 0) _wsPool.delete(poolKey);
74
+ }
75
+
76
+ function _scheduleIdleClose(poolKey, entry) {
77
+ if (!entry) return;
78
+ if (entry.idleTimer) clearTimeout(entry.idleTimer);
79
+ entry.idleTimer = setTimeout(() => {
80
+ if (entry.busy) return;
81
+ try { entry.socket.close(1000, 'idle_timeout'); } catch {}
82
+ _removeFromPool(poolKey, entry);
83
+ }, WS_IDLE_MS);
84
+ try { entry.idleTimer.unref?.(); } catch {}
85
+ }
86
+
87
+ function _clearIdle(entry) {
88
+ if (entry?.idleTimer) {
89
+ clearTimeout(entry.idleTimer);
90
+ entry.idleTimer = null;
91
+ }
92
+ }
93
+
94
+ function _isOpen(entry) {
95
+ return entry?.socket?.readyState === WebSocket.OPEN;
96
+ }
97
+
98
+ // Awaited frame send. Asserts the socket is OPEN and resolves only after
99
+ // the underlying transport reports the buffered write succeeded (or fails)
100
+ // via the WebSocket send callback. Raw `socket.send(JSON.stringify(...))`
101
+ // is fire-and-forget — a wedged or half-closed socket silently queues the
102
+ // payload and the caller assumes it landed, then later times out waiting
103
+ // for a server event that will never arrive. Tag any failure with
104
+ // `wsSendFailed=true` so _classifyMidstreamError routes the next attempt
105
+ // through a fresh socket.
106
+ export function _sendFrame(entry, frame) {
107
+ return new Promise((resolve, reject) => {
108
+ const socket = entry?.socket;
109
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
110
+ const err = new Error(`WS send: socket not OPEN (readyState=${socket?.readyState ?? 'n/a'})`);
111
+ err.wsSendFailed = true;
112
+ reject(err);
113
+ return;
114
+ }
115
+ let payload;
116
+ try { payload = JSON.stringify(frame); }
117
+ catch (e) {
118
+ const err = e instanceof Error ? e : new Error(String(e));
119
+ err.wsSendFailed = true;
120
+ reject(err);
121
+ return;
122
+ }
123
+ try {
124
+ // Do NOT await the send callback: on a wedged-but-OPEN socket the
125
+ // ws write callback may never fire, which would hang this Promise
126
+ // before _streamResponse arms its first-byte watchdog. Fire and
127
+ // resolve immediately; transport failures surface via the socket
128
+ // 'error'/'close' handlers and the first-byte watchdog.
129
+ socket.send(payload, () => {});
130
+ resolve();
131
+ } catch (e) {
132
+ const err = e instanceof Error ? e : new Error(String(e));
133
+ err.wsSendFailed = true;
134
+ reject(err);
135
+ }
136
+ });
137
+ }
138
+
139
+ function _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey: _cacheKey, codexHeaders }) {
140
+ // xAI WS: do NOT pin x-grok-conv-id. Measured parallel runs show that
141
+ // forcing a routing shard via that header alternates cold caches across
142
+ // parallel workers; the automatic prompt-prefix cache holds up better
143
+ // when each handshake is unpinned. Reference: vercel/ai xai provider.
144
+ const headers = auth.type === 'xai'
145
+ ? {
146
+ 'Authorization': `Bearer ${auth.apiKey}`,
147
+ }
148
+ : auth.type === 'openai-direct'
149
+ ? {
150
+ 'Authorization': `Bearer ${auth.apiKey}`,
151
+ 'OpenAI-Beta': 'responses_websockets=2026-02-06',
152
+ }
153
+ : {
154
+ 'Authorization': `Bearer ${auth.access_token}`,
155
+ 'chatgpt-account-id': auth.account_id || '',
156
+ 'originator': CODEX_OAUTH_ORIGINATOR,
157
+ 'OpenAI-Beta': 'responses_websockets=2026-02-06',
158
+ // codex-rs merges provider http_headers ("version") plus
159
+ // default_headers (User-Agent) into the WS handshake
160
+ // (client.rs:970-975, model-provider-info/src/lib.rs:339-343,
161
+ // login default_client.rs:289-305). The backend fingerprints
162
+ // clients on these; missing them can route us onto a different
163
+ // (colder) cache-node class than codex.
164
+ 'User-Agent': codexUserAgent(),
165
+ 'version': codexVersionHeader(),
166
+ // codex advertises enabled beta features on every request incl.
167
+ // the WS handshake (client.rs:1038-1041 via build_responses_headers,
168
+ // session/mod.rs:1006-1027). With a default config the list is
169
+ // exactly "remote_compaction_v2" (features/src/lib.rs: the only
170
+ // always-advertised Stable default-on feature). Servers gate
171
+ // behavior (plausibly incl. x-codex-turn-state issuance) on it.
172
+ 'x-codex-beta-features': process.env.MIXDOG_CODEX_BETA_FEATURES || 'remote_compaction_v2',
173
+ };
174
+ const isOpenAiOauth = auth.type !== 'xai' && auth.type !== 'openai-direct';
175
+ // codex-rs sends only the dashed session-id/thread-id pair
176
+ // (client.rs:1033-1057), but OUR backend measurements disagree with pure
177
+ // codex parity here: 2026-04-19 probes showed the OAuth backend dedupes
178
+ // its in-memory prefix state by the underscore session_id handshake
179
+ // header, and the only 0.0%-miss full-frame rounds (R7/R8, R15 regressed
180
+ // to 13% after this header was dropped) all had it present. Send both.
181
+ if (sessionToken) {
182
+ headers['session_id'] = String(sessionToken);
183
+ }
184
+ if (isOpenAiOauth && (sessionToken || _cacheKey)) {
185
+ const threadId = String(_cacheKey || sessionToken);
186
+ const sessionId = String(sessionToken || _cacheKey);
187
+ headers['session-id'] = sessionId;
188
+ headers['thread-id'] = threadId;
189
+ headers['x-client-request-id'] = threadId;
190
+ if (codexHeaders && typeof codexHeaders === 'object') {
191
+ for (const [key, value] of Object.entries(codexHeaders)) {
192
+ if (typeof key === 'string' && typeof value === 'string' && value) {
193
+ headers[key] = value;
194
+ }
195
+ }
196
+ }
197
+ } else {
198
+ // xAI/direct keep a per-request value so their server-side traces stay
199
+ // distinguishable across reconnects.
200
+ headers['x-client-request-id'] = randomBytes(16).toString('hex');
201
+ }
202
+ if (turnState) headers['x-codex-turn-state'] = turnState;
203
+ return headers;
204
+ }
205
+
206
+ // handshake session_id is the conversation slot openai-oauth uses for in-memory
207
+ // prefix state. OpenAI OAuth uses the Codex-style thread cache key by default;
208
+ // xAI leaves routing unpinned.
209
+ function _mintSessionToken(cacheKey, auth) {
210
+ // xAI's public WebSocket endpoint uses the open connection plus
211
+ // response ids for continuation; unlike openai-oauth, it does not need the
212
+ // OAuth-specific session_id handshake shard.
213
+ if (auth?.type === 'xai') return null;
214
+ return cacheKey || 'mixdog-default';
215
+ }
216
+
217
+ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey, codexHeaders }) {
218
+ const headers = _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey, codexHeaders });
219
+ const baseUrl = auth.type === 'xai'
220
+ ? XAI_WS_URL
221
+ : auth.type === 'openai-direct'
222
+ ? OPENAI_WS_URL
223
+ : CODEX_WS_URL;
224
+ const _wsOpenStart = Date.now();
225
+ if (process.env.MIXDOG_DEBUG_AGENT) {
226
+ process.stderr.write(`[agent-trace] ws-open-start url=${baseUrl} tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} ts=${_wsOpenStart}\n`);
227
+ }
228
+ const url = baseUrl + (sessionToken ? `?session_id=${encodeURIComponent(String(sessionToken))}` : '');
229
+ return new Promise((resolve, reject) => {
230
+ let settled = false;
231
+ let abortListener = null;
232
+ let acquireTimer = null;
233
+ const settle = (ok, val) => {
234
+ if (settled) return;
235
+ settled = true;
236
+ if (acquireTimer) {
237
+ clearTimeout(acquireTimer);
238
+ acquireTimer = null;
239
+ }
240
+ if (abortListener && externalSignal) {
241
+ try { externalSignal.removeEventListener('abort', abortListener); } catch {}
242
+ }
243
+ (ok ? resolve : reject)(val);
244
+ };
245
+ const socket = new WebSocket(url, { headers, handshakeTimeout: WS_HANDSHAKE_TIMEOUT_MS });
246
+ acquireTimer = setTimeout(() => {
247
+ if (settled) return;
248
+ if (process.env.MIXDOG_DEBUG_AGENT) {
249
+ process.stderr.write(`[agent-trace] ws-open-fail kind=acquire_timeout timeoutMs=${WS_ACQUIRE_TIMEOUT_MS} elapsed=${Date.now() - _wsOpenStart}ms\n`);
250
+ }
251
+ try { socket.terminate(); } catch {}
252
+ settle(false, Object.assign(
253
+ new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} acquire timed out before open (${WS_ACQUIRE_TIMEOUT_MS}ms)`),
254
+ { code: 'EWSACQUIRETIMEOUT', acquireTimeoutMs: WS_ACQUIRE_TIMEOUT_MS },
255
+ ));
256
+ }, WS_ACQUIRE_TIMEOUT_MS);
257
+ try { acquireTimer.unref?.(); } catch {}
258
+ const capturedHeaders = { turnState: null };
259
+ socket.once('upgrade', (res) => {
260
+ try {
261
+ const ts = res?.headers?.['x-codex-turn-state'];
262
+ if (typeof ts === 'string' && ts.length) capturedHeaders.turnState = ts;
263
+ // Probe: dump the full 101-upgrade response header set so we can
264
+ // see what the server actually issues (turn-state investigation).
265
+ if (process.env.MIXDOG_WS_UPGRADE_HEADER_PROBE) {
266
+ const all = res?.headers && typeof res.headers === 'object'
267
+ ? Object.entries(res.headers).map(([k, v]) => `${k}: ${String(v).slice(0, 120)}`).join(' | ')
268
+ : '(none)';
269
+ const line = `[ws-upgrade-probe] ts=${new Date().toISOString()} status=${res?.statusCode} headers={ ${all} }\n`;
270
+ process.stderr.write(line);
271
+ // Bench runners swallow child stderr on success; persist to a
272
+ // file so the probe survives (value of the env var = path, or
273
+ // default under tmp).
274
+ try {
275
+ const probePath = process.env.MIXDOG_WS_UPGRADE_HEADER_PROBE !== '1'
276
+ ? process.env.MIXDOG_WS_UPGRADE_HEADER_PROBE
277
+ : `${process.env.TEMP || process.env.TMPDIR || '.'}/mixdog-ws-upgrade-probe.log`;
278
+ appendFileSync(probePath, line);
279
+ } catch {}
280
+ }
281
+ } catch {}
282
+ });
283
+ socket.once('open', () => {
284
+ if (process.env.MIXDOG_DEBUG_AGENT) {
285
+ process.stderr.write(`[agent-trace] ws-open-ok elapsed=${Date.now() - _wsOpenStart}ms\n`);
286
+ }
287
+ settle(true, { socket, turnState: capturedHeaders.turnState });
288
+ });
289
+ socket.once('error', (err) => {
290
+ if (process.env.MIXDOG_DEBUG_AGENT) {
291
+ process.stderr.write(`[agent-trace] ws-open-fail kind=error msg=${String(err?.message || err).slice(0, 120)} elapsed=${Date.now() - _wsOpenStart}ms\n`);
292
+ }
293
+ try { socket.terminate(); } catch {}
294
+ settle(false, err instanceof Error ? err : Object.assign(new Error(errText(err) || 'openai-oauth WS error'), { wsErrorEvent: true, original: err }));
295
+ });
296
+ socket.once('close', (code, reason) => {
297
+ // Half-open handshake: the peer closed before 'open'/'error' fired
298
+ // (TCP RST / TLS edge). Without this the connect Promise never
299
+ // settles and only the 600s outer watchdog can break the stall
300
+ // (observed stage=requesting 601s hang). Open-path closes are
301
+ // no-ops here because settle() has already flipped `settled`.
302
+ if (settled) return;
303
+ try { socket.terminate(); } catch {}
304
+ settle(false, Object.assign(
305
+ new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} handshake closed before open (code=${code})`),
306
+ { wsCloseCode: code, wsCloseReason: (reason && reason.toString) ? reason.toString('utf-8') : '' }));
307
+ });
308
+ socket.once('unexpected-response', (_req, res) => {
309
+ if (settled) return;
310
+ const status = res?.statusCode || 0;
311
+ let body = '';
312
+ res.on('data', c => { if (body.length < 2048) body += c.toString('utf-8'); });
313
+ res.on('end', () => {
314
+ if (process.env.MIXDOG_DEBUG_AGENT) {
315
+ process.stderr.write(`[agent-trace] ws-open-fail kind=http status=${status} body=${body.slice(0, 120)} elapsed=${Date.now() - _wsOpenStart}ms\n`);
316
+ }
317
+ try { socket.terminate(); } catch {}
318
+ settle(false, Object.assign(new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} handshake ${status}: ${body.slice(0, 200)}`), { httpStatus: status, httpBody: body }));
319
+ });
320
+ });
321
+ if (externalSignal) {
322
+ const onAbort = () => {
323
+ try { socket.terminate(); } catch {}
324
+ const reason = externalSignal.reason;
325
+ settle(false, reason instanceof Error ? reason : new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} handshake aborted`));
326
+ };
327
+ if (externalSignal.aborted) { onAbort(); return; }
328
+ abortListener = onAbort;
329
+ externalSignal.addEventListener('abort', onAbort, { once: true });
330
+ }
331
+ });
332
+ }
333
+
334
+ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders, forceFresh, externalSignal }) {
335
+ const _acqStart = Date.now();
336
+ if (process.env.MIXDOG_DEBUG_AGENT) {
337
+ process.stderr.write(`[agent-trace] acquire-start poolKey=${poolKey} cacheKey=${cacheKey} forceFresh=${forceFresh} externalAborted=${!!externalSignal?.aborted} ts=${_acqStart}\n`);
338
+ }
339
+ if (externalSignal?.aborted) {
340
+ const reason = externalSignal.reason;
341
+ throw reason instanceof Error ? reason : new Error('OpenAI OAuth WS acquire aborted');
342
+ }
343
+ if (poolKey && !forceFresh) {
344
+ const arr = _wsPool.get(poolKey) || [];
345
+ // Prune dead entries first.
346
+ for (let i = arr.length - 1; i >= 0; i--) {
347
+ if (!_isOpen(arr[i]) || arr[i].closing) {
348
+ _clearIdle(arr[i]);
349
+ arr.splice(i, 1);
350
+ }
351
+ }
352
+ if (arr.length === 0) _wsPool.delete(poolKey);
353
+ // Reuse any idle open entry (cache-warm path).
354
+ const idle = arr.find(e => !e.busy);
355
+ if (idle) {
356
+ _clearIdle(idle);
357
+ idle.busy = true;
358
+ // Defensive: pre-existing pooled entries created before the
359
+ // prefix-hash field was introduced may not have it set. Normalize
360
+ // to null so the first delta check reads a deterministic value
361
+ // (and falls back to full-create instead of silently passing).
362
+ if (idle.lastInputPrefixHash === undefined) idle.lastInputPrefixHash = null;
363
+ if (idle.lastRequestInput === undefined) idle.lastRequestInput = null;
364
+ if (idle.lastResponseItems === undefined) idle.lastResponseItems = null;
365
+ if (process.env.MIXDOG_DEBUG_AGENT) {
366
+ process.stderr.write(`[agent-trace] acquire-reuse poolKey=${poolKey} openSockets=${arr.length} elapsed=${Date.now() - _acqStart}ms\n`);
367
+ }
368
+ return { entry: idle, reused: true };
369
+ }
370
+ // All entries busy and bucket at cap: fall through to ephemeral socket.
371
+ if (arr.length >= MAX_POOLED_SOCKETS_PER_KEY) {
372
+ if (process.env.MIXDOG_DEBUG_AGENT) {
373
+ process.stderr.write(`[agent-trace] acquire-ephemeral cacheKey=${cacheKey} reason=cap elapsed=${Date.now() - _acqStart}ms\n`);
374
+ }
375
+ const ephSessionToken = _mintSessionToken(cacheKey, auth);
376
+ const { socket, turnState } = await _openSocket({ auth, sessionToken: ephSessionToken, turnState: null, externalSignal, cacheKey, codexHeaders });
377
+ // Drain-complete fence: same invariant as the normal acquire path —
378
+ // if drain fired during the await, do NOT push an ephemeral entry
379
+ // back into the pool.
380
+ if (_drainComplete) {
381
+ try { socket.close(1000, 'drain-complete'); } catch {}
382
+ throw new Error('WS pool drained — process exiting');
383
+ }
384
+ const entry = {
385
+ socket,
386
+ busy: true,
387
+ idleTimer: null,
388
+ lastResponseId: null,
389
+ lastRequestSansInput: null,
390
+ lastRequestInput: null,
391
+ lastResponseItems: null,
392
+ lastInputLen: 0,
393
+ lastInputPrefixHash: null,
394
+ turnState: turnState || null,
395
+ closing: false,
396
+ ephemeral: true,
397
+ sessionToken: ephSessionToken,
398
+ };
399
+ socket.on('close', () => { entry.closing = true; });
400
+ return { entry, reused: false };
401
+ }
402
+ }
403
+ // Parallel sockets must not inherit sibling turnState or the openai-oauth server
404
+ // treats the new request as a continuation of another in-flight turn and
405
+ // returns "No tool output found for function call …". turnState only
406
+ // propagates within a single entry across its own iterations.
407
+ const sessionToken = _mintSessionToken(cacheKey, auth);
408
+ if (process.env.MIXDOG_DEBUG_AGENT) {
409
+ process.stderr.write(`[agent-trace] acquire-new tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} elapsed=${Date.now() - _acqStart}ms\n`);
410
+ }
411
+ const { socket, turnState } = await _openSocket({ auth, sessionToken, turnState: null, externalSignal, cacheKey, codexHeaders });
412
+ const entry = {
413
+ socket,
414
+ busy: true,
415
+ idleTimer: null,
416
+ lastResponseId: null,
417
+ lastRequestSansInput: null,
418
+ lastRequestInput: null,
419
+ lastResponseItems: null,
420
+ lastInputLen: 0,
421
+ lastInputPrefixHash: null,
422
+ turnState: turnState || null,
423
+ closing: false,
424
+ ephemeral: false,
425
+ sessionToken,
426
+ };
427
+ if (poolKey && !forceFresh) _getPoolArr(poolKey).push(entry);
428
+ socket.on('close', () => {
429
+ entry.closing = true;
430
+ _removeFromPool(poolKey, entry);
431
+ });
432
+ return { entry, reused: false };
433
+ }
434
+
435
+ export function releaseWebSocket({ entry, poolKey, keep }) {
436
+ if (!entry) return;
437
+ entry.busy = false;
438
+ if (!keep || !_isOpen(entry) || !poolKey || entry.ephemeral) {
439
+ try { entry.socket.close(1000, keep ? 'no_session' : 'release_no_keep'); } catch {}
440
+ _removeFromPool(poolKey, entry);
441
+ return;
442
+ }
443
+ _scheduleIdleClose(poolKey, entry);
444
+ }
445
+
446
+ // Drain-complete fence — set true once _closeAllPooledSockets runs so any
447
+ // in-flight acquire that resumes after drain throws instead of pushing a
448
+ // fresh socket into the cleared pool. Single-set, process-lifetime invariant.
449
+ let _drainComplete = false;
450
+
451
+ // Drain hook — self-registered exit drain.
452
+ // Force-closes pooled sockets and fences subsequent acquires.
453
+ // `drainOpenaiWsPool` alias matches the registry's `drain*` naming convention;
454
+ // `_closeAllPooledSockets` kept for backward compat with existing call sites.
455
+ export function _closeAllPooledSockets(reason = 'shutdown') {
456
+ _drainComplete = true;
457
+ for (const arr of _wsPool.values()) {
458
+ for (const entry of arr) {
459
+ try { entry.socket.close(1000, reason); } catch {}
460
+ }
461
+ }
462
+ _wsPool.clear();
463
+ }
464
+ export const drainOpenaiWsPool = _closeAllPooledSockets;
465
+ process.on('exit', drainOpenaiWsPool);