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.
- package/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- 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);
|