mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,436 @@
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 { codexUserAgent, codexVersionHeader } from './codex-client-meta.mjs';
13
+ import {
14
+ PROVIDER_WS_ACQUIRE_TIMEOUT_MS,
15
+ PROVIDER_WS_HANDSHAKE_TIMEOUT_MS,
16
+ resolveTimeoutMs,
17
+ } from '../stall-policy.mjs';
18
+
19
+ // Human-readable transport label for handshake/acquire error messages. Shared
20
+ // with openai-oauth-ws.mjs (stream-side errors use the same labels).
21
+ export function _wsErrLabel(p) {
22
+ if (p === 'xai') return 'xAI WS';
23
+ if (p === 'openai-direct' || p === 'openai') return 'OpenAI WS';
24
+ return 'OpenAI OAuth WS';
25
+ }
26
+
27
+ const CODEX_WS_URL = 'wss://chatgpt.com/backend-api/codex/responses';
28
+ const CODEX_OAUTH_ORIGINATOR = 'codex_cli_rs';
29
+ const OPENAI_WS_URL = 'wss://api.openai.com/v1/responses';
30
+ const XAI_WS_URL = 'wss://api.x.ai/v1/responses';
31
+ export const WS_IDLE_MS = resolveTimeoutMs(
32
+ 'MIXDOG_PROVIDER_WS_IDLE_MS',
33
+ 20 * 60_000,
34
+ { minMs: 60_000, maxMs: 60 * 60_000 },
35
+ );
36
+ const WS_HANDSHAKE_TIMEOUT_MS = PROVIDER_WS_HANDSHAKE_TIMEOUT_MS;
37
+ const WS_ACQUIRE_TIMEOUT_MS = PROVIDER_WS_ACQUIRE_TIMEOUT_MS;
38
+
39
+ // WS socket pool buckets are keyed by `poolKey` (the per-call sessionId)
40
+ // to isolate parallel agent invocations — each gets its own socket so
41
+ // a second caller cannot grab a sibling's mid-turn entry (openai-oauth would
42
+ // otherwise reject the new response.create with "No tool output found
43
+ // for function call ..."). The handshake `session_id` header/URL
44
+ // uses `cacheKey` — for OpenAI OAuth this mirrors Codex's thread-scoped
45
+ // prompt_cache_key by default. The backend dedupes cache by handshake
46
+ // session_id, not by body.prompt_cache_key alone (measured 2026-04-19 after the
47
+ // v0.6.151 regression).
48
+ const MAX_POOLED_SOCKETS_PER_KEY = 8;
49
+
50
+ // poolKey -> Entry[]
51
+ // Entry: { socket, busy, idleTimer, lastResponseId, lastRequestSansInput,
52
+ // lastRequestInput, lastResponseItems, lastInputLen, turnState,
53
+ // closing, ephemeral }
54
+ const _wsPool = new Map();
55
+
56
+ function _getPoolArr(poolKey) {
57
+ if (!poolKey) return null;
58
+ let arr = _wsPool.get(poolKey);
59
+ if (!arr) {
60
+ arr = [];
61
+ _wsPool.set(poolKey, arr);
62
+ }
63
+ return arr;
64
+ }
65
+
66
+ function _removeFromPool(poolKey, entry) {
67
+ if (!poolKey) return;
68
+ const arr = _wsPool.get(poolKey);
69
+ if (!arr) return;
70
+ const idx = arr.indexOf(entry);
71
+ if (idx >= 0) arr.splice(idx, 1);
72
+ if (arr.length === 0) _wsPool.delete(poolKey);
73
+ }
74
+
75
+ function _scheduleIdleClose(poolKey, entry) {
76
+ if (!entry) return;
77
+ if (entry.idleTimer) clearTimeout(entry.idleTimer);
78
+ entry.idleTimer = setTimeout(() => {
79
+ if (entry.busy) return;
80
+ try { entry.socket.close(1000, 'idle_timeout'); } catch {}
81
+ _removeFromPool(poolKey, entry);
82
+ }, WS_IDLE_MS);
83
+ try { entry.idleTimer.unref?.(); } catch {}
84
+ }
85
+
86
+ function _clearIdle(entry) {
87
+ if (entry?.idleTimer) {
88
+ clearTimeout(entry.idleTimer);
89
+ entry.idleTimer = null;
90
+ }
91
+ }
92
+
93
+ function _isOpen(entry) {
94
+ return entry?.socket?.readyState === WebSocket.OPEN;
95
+ }
96
+
97
+ // Awaited frame send. Asserts the socket is OPEN and resolves only after
98
+ // the underlying transport reports the buffered write succeeded (or fails)
99
+ // via the WebSocket send callback. Raw `socket.send(JSON.stringify(...))`
100
+ // is fire-and-forget — a wedged or half-closed socket silently queues the
101
+ // payload and the caller assumes it landed, then later times out waiting
102
+ // for a server event that will never arrive. Tag any failure with
103
+ // `wsSendFailed=true` so _classifyMidstreamError routes the next attempt
104
+ // through a fresh socket.
105
+ export function _sendFrame(entry, frame) {
106
+ return new Promise((resolve, reject) => {
107
+ const socket = entry?.socket;
108
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
109
+ const err = new Error(`WS send: socket not OPEN (readyState=${socket?.readyState ?? 'n/a'})`);
110
+ err.wsSendFailed = true;
111
+ reject(err);
112
+ return;
113
+ }
114
+ let payload;
115
+ try { payload = JSON.stringify(frame); }
116
+ catch (e) {
117
+ const err = e instanceof Error ? e : new Error(String(e));
118
+ err.wsSendFailed = true;
119
+ reject(err);
120
+ return;
121
+ }
122
+ try {
123
+ // Do NOT await the send callback: on a wedged-but-OPEN socket the
124
+ // ws write callback may never fire, which would hang this Promise
125
+ // before _streamResponse arms its first-byte watchdog. Fire and
126
+ // resolve immediately; transport failures surface via the socket
127
+ // 'error'/'close' handlers and the first-byte watchdog.
128
+ socket.send(payload, () => {});
129
+ resolve();
130
+ } catch (e) {
131
+ const err = e instanceof Error ? e : new Error(String(e));
132
+ err.wsSendFailed = true;
133
+ reject(err);
134
+ }
135
+ });
136
+ }
137
+
138
+ function _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey: _cacheKey, codexHeaders }) {
139
+ // xAI WS: do NOT pin x-grok-conv-id. Measured parallel runs show that
140
+ // forcing a routing shard via that header alternates cold caches across
141
+ // parallel workers; the automatic prompt-prefix cache holds up better
142
+ // when each handshake is unpinned. Reference: vercel/ai xai provider.
143
+ const headers = auth.type === 'xai'
144
+ ? {
145
+ 'Authorization': `Bearer ${auth.apiKey}`,
146
+ }
147
+ : auth.type === 'openai-direct'
148
+ ? {
149
+ 'Authorization': `Bearer ${auth.apiKey}`,
150
+ 'OpenAI-Beta': 'responses_websockets=2026-02-06',
151
+ }
152
+ : {
153
+ 'Authorization': `Bearer ${auth.access_token}`,
154
+ 'chatgpt-account-id': auth.account_id || '',
155
+ 'originator': CODEX_OAUTH_ORIGINATOR,
156
+ 'OpenAI-Beta': 'responses_websockets=2026-02-06',
157
+ // codex-rs merges provider http_headers ("version") plus
158
+ // default_headers (User-Agent) into the WS handshake
159
+ // (client.rs:970-975, model-provider-info/src/lib.rs:339-343,
160
+ // login default_client.rs:289-305). The backend fingerprints
161
+ // clients on these; missing them can route us onto a different
162
+ // (colder) cache-node class than codex.
163
+ 'User-Agent': codexUserAgent(),
164
+ 'version': codexVersionHeader(),
165
+ };
166
+ const isOpenAiOauth = auth.type !== 'xai' && auth.type !== 'openai-direct';
167
+ if (sessionToken && !isOpenAiOauth) {
168
+ // openai-direct keeps the legacy underscore header; codex-rs sends no
169
+ // underscore session_id on the OAuth WS handshake (client.rs:1033-1057),
170
+ // only the dashed session-id/thread-id pair below.
171
+ headers['session_id'] = String(sessionToken);
172
+ }
173
+ if (isOpenAiOauth && (sessionToken || _cacheKey)) {
174
+ const threadId = String(_cacheKey || sessionToken);
175
+ const sessionId = String(sessionToken || _cacheKey);
176
+ headers['session-id'] = sessionId;
177
+ headers['thread-id'] = threadId;
178
+ headers['x-client-request-id'] = threadId;
179
+ if (codexHeaders && typeof codexHeaders === 'object') {
180
+ for (const [key, value] of Object.entries(codexHeaders)) {
181
+ if (typeof key === 'string' && typeof value === 'string' && value) {
182
+ headers[key] = value;
183
+ }
184
+ }
185
+ }
186
+ } else {
187
+ // xAI/direct keep a per-request value so their server-side traces stay
188
+ // distinguishable across reconnects.
189
+ headers['x-client-request-id'] = randomBytes(16).toString('hex');
190
+ }
191
+ if (turnState) headers['x-codex-turn-state'] = turnState;
192
+ return headers;
193
+ }
194
+
195
+ // handshake session_id is the conversation slot openai-oauth uses for in-memory
196
+ // prefix state. OpenAI OAuth uses the Codex-style thread cache key by default;
197
+ // xAI leaves routing unpinned.
198
+ function _mintSessionToken(cacheKey, auth) {
199
+ // xAI's public WebSocket endpoint uses the open connection plus
200
+ // response ids for continuation; unlike openai-oauth, it does not need the
201
+ // OAuth-specific session_id handshake shard.
202
+ if (auth?.type === 'xai') return null;
203
+ return cacheKey || 'mixdog-default';
204
+ }
205
+
206
+ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey, codexHeaders }) {
207
+ const headers = _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey, codexHeaders });
208
+ const baseUrl = auth.type === 'xai'
209
+ ? XAI_WS_URL
210
+ : auth.type === 'openai-direct'
211
+ ? OPENAI_WS_URL
212
+ : CODEX_WS_URL;
213
+ const _wsOpenStart = Date.now();
214
+ if (process.env.MIXDOG_DEBUG_AGENT) {
215
+ process.stderr.write(`[agent-trace] ws-open-start url=${baseUrl} tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} ts=${_wsOpenStart}\n`);
216
+ }
217
+ const url = baseUrl + (sessionToken ? `?session_id=${encodeURIComponent(String(sessionToken))}` : '');
218
+ return new Promise((resolve, reject) => {
219
+ let settled = false;
220
+ let abortListener = null;
221
+ let acquireTimer = null;
222
+ const settle = (ok, val) => {
223
+ if (settled) return;
224
+ settled = true;
225
+ if (acquireTimer) {
226
+ clearTimeout(acquireTimer);
227
+ acquireTimer = null;
228
+ }
229
+ if (abortListener && externalSignal) {
230
+ try { externalSignal.removeEventListener('abort', abortListener); } catch {}
231
+ }
232
+ (ok ? resolve : reject)(val);
233
+ };
234
+ const socket = new WebSocket(url, { headers, handshakeTimeout: WS_HANDSHAKE_TIMEOUT_MS });
235
+ acquireTimer = setTimeout(() => {
236
+ if (settled) return;
237
+ if (process.env.MIXDOG_DEBUG_AGENT) {
238
+ process.stderr.write(`[agent-trace] ws-open-fail kind=acquire_timeout timeoutMs=${WS_ACQUIRE_TIMEOUT_MS} elapsed=${Date.now() - _wsOpenStart}ms\n`);
239
+ }
240
+ try { socket.terminate(); } catch {}
241
+ settle(false, Object.assign(
242
+ 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)`),
243
+ { code: 'EWSACQUIRETIMEOUT', acquireTimeoutMs: WS_ACQUIRE_TIMEOUT_MS },
244
+ ));
245
+ }, WS_ACQUIRE_TIMEOUT_MS);
246
+ try { acquireTimer.unref?.(); } catch {}
247
+ const capturedHeaders = { turnState: null };
248
+ socket.once('upgrade', (res) => {
249
+ try {
250
+ const ts = res?.headers?.['x-codex-turn-state'];
251
+ if (typeof ts === 'string' && ts.length) capturedHeaders.turnState = ts;
252
+ } catch {}
253
+ });
254
+ socket.once('open', () => {
255
+ if (process.env.MIXDOG_DEBUG_AGENT) {
256
+ process.stderr.write(`[agent-trace] ws-open-ok elapsed=${Date.now() - _wsOpenStart}ms\n`);
257
+ }
258
+ settle(true, { socket, turnState: capturedHeaders.turnState });
259
+ });
260
+ socket.once('error', (err) => {
261
+ if (process.env.MIXDOG_DEBUG_AGENT) {
262
+ process.stderr.write(`[agent-trace] ws-open-fail kind=error msg=${String(err?.message || err).slice(0, 120)} elapsed=${Date.now() - _wsOpenStart}ms\n`);
263
+ }
264
+ try { socket.terminate(); } catch {}
265
+ settle(false, err instanceof Error ? err : Object.assign(new Error(errText(err) || 'openai-oauth WS error'), { wsErrorEvent: true, original: err }));
266
+ });
267
+ socket.once('close', (code, reason) => {
268
+ // Half-open handshake: the peer closed before 'open'/'error' fired
269
+ // (TCP RST / TLS edge). Without this the connect Promise never
270
+ // settles and only the 600s outer watchdog can break the stall
271
+ // (observed stage=requesting 601s hang). Open-path closes are
272
+ // no-ops here because settle() has already flipped `settled`.
273
+ if (settled) return;
274
+ try { socket.terminate(); } catch {}
275
+ settle(false, Object.assign(
276
+ new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} handshake closed before open (code=${code})`),
277
+ { wsCloseCode: code, wsCloseReason: (reason && reason.toString) ? reason.toString('utf-8') : '' }));
278
+ });
279
+ socket.once('unexpected-response', (_req, res) => {
280
+ if (settled) return;
281
+ const status = res?.statusCode || 0;
282
+ let body = '';
283
+ res.on('data', c => { if (body.length < 2048) body += c.toString('utf-8'); });
284
+ res.on('end', () => {
285
+ if (process.env.MIXDOG_DEBUG_AGENT) {
286
+ process.stderr.write(`[agent-trace] ws-open-fail kind=http status=${status} body=${body.slice(0, 120)} elapsed=${Date.now() - _wsOpenStart}ms\n`);
287
+ }
288
+ try { socket.terminate(); } catch {}
289
+ 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 }));
290
+ });
291
+ });
292
+ if (externalSignal) {
293
+ const onAbort = () => {
294
+ try { socket.terminate(); } catch {}
295
+ const reason = externalSignal.reason;
296
+ settle(false, reason instanceof Error ? reason : new Error(`${_wsErrLabel(auth?.type === 'xai' ? 'xai' : auth?.type === 'openai-direct' ? 'openai-direct' : 'openai-oauth')} handshake aborted`));
297
+ };
298
+ if (externalSignal.aborted) { onAbort(); return; }
299
+ abortListener = onAbort;
300
+ externalSignal.addEventListener('abort', onAbort, { once: true });
301
+ }
302
+ });
303
+ }
304
+
305
+ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders, forceFresh, externalSignal }) {
306
+ const _acqStart = Date.now();
307
+ if (process.env.MIXDOG_DEBUG_AGENT) {
308
+ process.stderr.write(`[agent-trace] acquire-start poolKey=${poolKey} cacheKey=${cacheKey} forceFresh=${forceFresh} externalAborted=${!!externalSignal?.aborted} ts=${_acqStart}\n`);
309
+ }
310
+ if (externalSignal?.aborted) {
311
+ const reason = externalSignal.reason;
312
+ throw reason instanceof Error ? reason : new Error('OpenAI OAuth WS acquire aborted');
313
+ }
314
+ if (poolKey && !forceFresh) {
315
+ const arr = _wsPool.get(poolKey) || [];
316
+ // Prune dead entries first.
317
+ for (let i = arr.length - 1; i >= 0; i--) {
318
+ if (!_isOpen(arr[i]) || arr[i].closing) {
319
+ _clearIdle(arr[i]);
320
+ arr.splice(i, 1);
321
+ }
322
+ }
323
+ if (arr.length === 0) _wsPool.delete(poolKey);
324
+ // Reuse any idle open entry (cache-warm path).
325
+ const idle = arr.find(e => !e.busy);
326
+ if (idle) {
327
+ _clearIdle(idle);
328
+ idle.busy = true;
329
+ // Defensive: pre-existing pooled entries created before the
330
+ // prefix-hash field was introduced may not have it set. Normalize
331
+ // to null so the first delta check reads a deterministic value
332
+ // (and falls back to full-create instead of silently passing).
333
+ if (idle.lastInputPrefixHash === undefined) idle.lastInputPrefixHash = null;
334
+ if (idle.lastRequestInput === undefined) idle.lastRequestInput = null;
335
+ if (idle.lastResponseItems === undefined) idle.lastResponseItems = null;
336
+ if (process.env.MIXDOG_DEBUG_AGENT) {
337
+ process.stderr.write(`[agent-trace] acquire-reuse poolKey=${poolKey} openSockets=${arr.length} elapsed=${Date.now() - _acqStart}ms\n`);
338
+ }
339
+ return { entry: idle, reused: true };
340
+ }
341
+ // All entries busy and bucket at cap: fall through to ephemeral socket.
342
+ if (arr.length >= MAX_POOLED_SOCKETS_PER_KEY) {
343
+ if (process.env.MIXDOG_DEBUG_AGENT) {
344
+ process.stderr.write(`[agent-trace] acquire-ephemeral cacheKey=${cacheKey} reason=cap elapsed=${Date.now() - _acqStart}ms\n`);
345
+ }
346
+ const ephSessionToken = _mintSessionToken(cacheKey, auth);
347
+ const { socket, turnState } = await _openSocket({ auth, sessionToken: ephSessionToken, turnState: null, externalSignal, cacheKey, codexHeaders });
348
+ // Drain-complete fence: same invariant as the normal acquire path —
349
+ // if drain fired during the await, do NOT push an ephemeral entry
350
+ // back into the pool.
351
+ if (_drainComplete) {
352
+ try { socket.close(1000, 'drain-complete'); } catch {}
353
+ throw new Error('WS pool drained — process exiting');
354
+ }
355
+ const entry = {
356
+ socket,
357
+ busy: true,
358
+ idleTimer: null,
359
+ lastResponseId: null,
360
+ lastRequestSansInput: null,
361
+ lastRequestInput: null,
362
+ lastResponseItems: null,
363
+ lastInputLen: 0,
364
+ lastInputPrefixHash: null,
365
+ turnState: turnState || null,
366
+ closing: false,
367
+ ephemeral: true,
368
+ sessionToken: ephSessionToken,
369
+ };
370
+ socket.on('close', () => { entry.closing = true; });
371
+ return { entry, reused: false };
372
+ }
373
+ }
374
+ // Parallel sockets must not inherit sibling turnState or the openai-oauth server
375
+ // treats the new request as a continuation of another in-flight turn and
376
+ // returns "No tool output found for function call …". turnState only
377
+ // propagates within a single entry across its own iterations.
378
+ const sessionToken = _mintSessionToken(cacheKey, auth);
379
+ if (process.env.MIXDOG_DEBUG_AGENT) {
380
+ process.stderr.write(`[agent-trace] acquire-new tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} elapsed=${Date.now() - _acqStart}ms\n`);
381
+ }
382
+ const { socket, turnState } = await _openSocket({ auth, sessionToken, turnState: null, externalSignal, cacheKey, codexHeaders });
383
+ const entry = {
384
+ socket,
385
+ busy: true,
386
+ idleTimer: null,
387
+ lastResponseId: null,
388
+ lastRequestSansInput: null,
389
+ lastRequestInput: null,
390
+ lastResponseItems: null,
391
+ lastInputLen: 0,
392
+ lastInputPrefixHash: null,
393
+ turnState: turnState || null,
394
+ closing: false,
395
+ ephemeral: false,
396
+ sessionToken,
397
+ };
398
+ if (poolKey && !forceFresh) _getPoolArr(poolKey).push(entry);
399
+ socket.on('close', () => {
400
+ entry.closing = true;
401
+ _removeFromPool(poolKey, entry);
402
+ });
403
+ return { entry, reused: false };
404
+ }
405
+
406
+ export function releaseWebSocket({ entry, poolKey, keep }) {
407
+ if (!entry) return;
408
+ entry.busy = false;
409
+ if (!keep || !_isOpen(entry) || !poolKey || entry.ephemeral) {
410
+ try { entry.socket.close(1000, keep ? 'no_session' : 'release_no_keep'); } catch {}
411
+ _removeFromPool(poolKey, entry);
412
+ return;
413
+ }
414
+ _scheduleIdleClose(poolKey, entry);
415
+ }
416
+
417
+ // Drain-complete fence — set true once _closeAllPooledSockets runs so any
418
+ // in-flight acquire that resumes after drain throws instead of pushing a
419
+ // fresh socket into the cleared pool. Single-set, process-lifetime invariant.
420
+ let _drainComplete = false;
421
+
422
+ // Drain hook — self-registered exit drain.
423
+ // Force-closes pooled sockets and fences subsequent acquires.
424
+ // `drainOpenaiWsPool` alias matches the registry's `drain*` naming convention;
425
+ // `_closeAllPooledSockets` kept for backward compat with existing call sites.
426
+ export function _closeAllPooledSockets(reason = 'shutdown') {
427
+ _drainComplete = true;
428
+ for (const arr of _wsPool.values()) {
429
+ for (const entry of arr) {
430
+ try { entry.socket.close(1000, reason); } catch {}
431
+ }
432
+ }
433
+ _wsPool.clear();
434
+ }
435
+ export const drainOpenaiWsPool = _closeAllPooledSockets;
436
+ process.on('exit', drainOpenaiWsPool);