mixdog 0.9.152 → 0.9.154
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/LICENSES/browser-import-NOTICE.txt +11 -0
- package/NOTICE.md +11 -0
- package/README.md +8 -10
- package/package.json +19 -3
- package/scripts/computer-task-cost-report.mjs +144 -0
- package/scripts/run-office-live-tests.mjs +32 -0
- package/src/agents/front-worker/AGENT.md +13 -0
- package/src/agents/front-worker/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +10 -11
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +0 -2
- package/src/agents/reviewer/AGENT.md +9 -13
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/security/AGENT.md +14 -0
- package/src/agents/security/agent.json +6 -0
- package/src/agents/worker/AGENT.md +8 -8
- package/src/agents/worker/agent.json +1 -1
- package/src/agents/writer/AGENT.md +14 -0
- package/src/agents/writer/agent.json +6 -0
- package/src/rules/agent/00-common.md +4 -7
- package/src/rules/lead/01-general.md +0 -3
- package/src/rules/shared/10-tool-workflow.md +10 -2
- package/src/rules/shared/60-verification.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
- package/src/runtime/agent/orchestrator/config.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -10
- package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +140 -86
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -13
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +72 -4
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +0 -22
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +11 -23
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +28 -269
- package/src/runtime/agent/orchestrator/session/compact.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +86 -11
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +80 -18
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +80 -21
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +33 -72
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +21 -2
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -51
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +39 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +4 -0
- package/src/runtime/attachments/pdf-extract.mjs +3 -0
- package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
- package/src/runtime/browser-bridge/action-schema.mjs +251 -0
- package/src/runtime/browser-bridge/client.mjs +75 -26
- package/src/runtime/browser-bridge/tool-defs.mjs +132 -15
- package/src/runtime/computer-bridge/action-schema.mjs +653 -0
- package/src/runtime/computer-bridge/client.mjs +211 -28
- package/src/runtime/computer-bridge/tool-defs.mjs +25 -45
- package/src/runtime/office/assurance-benchmark.mjs +358 -0
- package/src/runtime/office/assurance.mjs +833 -0
- package/src/runtime/office/benchmark.mjs +496 -0
- package/src/runtime/office/capabilities.mjs +551 -0
- package/src/runtime/office/com-adapter.mjs +283 -0
- package/src/runtime/office/content-model.mjs +183 -0
- package/src/runtime/office/contract-benchmark.mjs +105 -0
- package/src/runtime/office/design-library.mjs +1562 -0
- package/src/runtime/office/design-system.mjs +1966 -0
- package/src/runtime/office/index.mjs +2413 -0
- package/src/runtime/office/journal.mjs +82 -0
- package/src/runtime/office/office-com-host.ps1 +3781 -0
- package/src/runtime/office/office-com-session-host.ps1 +855 -0
- package/src/runtime/office/ooxml-validator.mjs +271 -0
- package/src/runtime/office/pagination.mjs +132 -0
- package/src/runtime/office/pdf-adapter.mjs +591 -0
- package/src/runtime/office/pdf-analysis.mjs +372 -0
- package/src/runtime/office/pdf-render.mjs +167 -0
- package/src/runtime/office/pdf-security.mjs +63 -0
- package/src/runtime/office/polish-benchmark.mjs +190 -0
- package/src/runtime/office/portable-ooxml.mjs +1867 -0
- package/src/runtime/office/quality-live-benchmark.mjs +486 -0
- package/src/runtime/office/quality-pipeline.mjs +136 -0
- package/src/runtime/office/tabular.mjs +302 -0
- package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
- package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
- package/src/runtime/office/tool-defs.mjs +113 -0
- package/src/runtime/office/visual-diff.mjs +89 -0
- package/src/runtime/office/xlsx-assertions.mjs +172 -0
- package/src/runtime/office/xlsx-contract.mjs +147 -0
- package/src/runtime/shared/agent-route-config.mjs +9 -0
- package/src/runtime/shared/tool-execution-contract.mjs +16 -0
- package/src/runtime/shared/tool-surface.mjs +9 -0
- package/src/session-runtime/context-status.mjs +9 -3
- package/src/session-runtime/deferred-tool-delta.mjs +104 -0
- package/src/session-runtime/goal-runtime.mjs +617 -197
- package/src/session-runtime/lifecycle-api.mjs +50 -8
- package/src/session-runtime/provider-request-snapshot.mjs +33 -9
- package/src/session-runtime/resource-api.mjs +31 -94
- package/src/session-runtime/runtime-core.mjs +78 -26
- package/src/session-runtime/session-lifecycle.mjs +34 -12
- package/src/session-runtime/session-title.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +15 -14
- package/src/session-runtime/tool-catalog-data.mjs +1 -1
- package/src/session-runtime/tool-catalog.mjs +38 -99
- package/src/session-runtime/tool-defs.mjs +6 -1
- package/src/session-runtime/tool-surface.mjs +18 -8
- package/src/session-runtime/workflow-agents-api.mjs +8 -25
- package/src/session-runtime/workflow.mjs +8 -6
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/job-views.mjs +3 -1
- package/src/standalone/agent-tool/notify.mjs +5 -1
- package/src/standalone/agent-tool/render.mjs +3 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool/worker-index.mjs +123 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +57 -10
- package/src/standalone/daemon.mjs +105 -6
- package/src/standalone/session-runtime-host.mjs +61 -48
- package/src/standalone/session-runtime-inline-host.mjs +34 -127
- package/src/standalone/session-runtime-worker.mjs +20 -198
- package/src/standalone/session-service.mjs +632 -7
- package/src/tui/dist/index.mjs +24 -1
- package/src/tui/lib/voice-setup.mjs +4 -1
- package/src/tui/session/context-state.mjs +2 -13
- package/src/tui/session/goal-continuation.mjs +38 -7
- package/src/tui/session/session-api-ext.mjs +9 -0
- package/src/tui/session/session-api.mjs +15 -1
- package/src/tui/session/session-flow.mjs +25 -1
- package/src/tui/session/turn.mjs +88 -16
- package/src/tui/session-local.mjs +25 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
- package/scripts/generate-voice-runtime-manifest.test.mjs +0 -135
- package/scripts/native-binary-arch.test.mjs +0 -111
- package/scripts/patch-replay.test.mjs +0 -94
- package/src/headless-exec.test.mjs +0 -539
- package/src/runtime/agent/orchestrator/agent-trace-timing.test.mjs +0 -57
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +0 -153
- package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +0 -139
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +0 -66
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +0 -80
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +0 -90
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +0 -215
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.test.mjs +0 -165
- package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +0 -142
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -153
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.test.mjs +0 -24
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +0 -190
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +0 -235
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +0 -719
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.test.mjs +0 -40
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +0 -176
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +0 -161
- package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +0 -115
- package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +0 -228
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +0 -245
- package/src/runtime/agent/orchestrator/session/lossless-tool-output.test.mjs +0 -125
- package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +0 -325
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +0 -70
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +0 -457
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.test.mjs +0 -117
- package/src/runtime/agent/orchestrator/session/reduction-metrics.test.mjs +0 -87
- package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +0 -60
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +0 -583
- package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +0 -78
- package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +0 -43
- package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +0 -63
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +0 -14
- package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +0 -224
- package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +0 -224
- package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +0 -56
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +0 -103
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.test.mjs +0 -30
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +0 -390
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.test.mjs +0 -83
- package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +0 -31
- package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +0 -106
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.test.mjs +0 -102
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +0 -152
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +0 -24
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +0 -24
- package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +0 -69
- package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +0 -71
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +0 -56
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +0 -16
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-top-k.test.mjs +0 -39
- package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +0 -153
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +0 -55
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +0 -88
- package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +0 -72
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +0 -54
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.test.mjs +0 -20
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +0 -55
- package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +0 -268
- package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +0 -57
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.test.mjs +0 -19
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +0 -104
- package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +0 -661
- package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +0 -552
- package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +0 -141
- package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +0 -57
- package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +0 -81
- package/src/runtime/agent/orchestrator/tools/spawn-binary-fetcher.test.mjs +0 -52
- package/src/runtime/attachments/store.test.mjs +0 -209
- package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +0 -17
- package/src/runtime/channels/lib/webhook/deliveries.test.mjs +0 -18
- package/src/runtime/channels/lib/webhook/relay-tunnel.test.mjs +0 -68
- package/src/runtime/media/adapters/codex-image.test.mjs +0 -22
- package/src/runtime/media/download.test.mjs +0 -52
- package/src/runtime/media/renditions.test.mjs +0 -99
- package/src/runtime/media/store.test.mjs +0 -159
- package/src/runtime/memory/lib/archive-security.test.mjs +0 -128
- package/src/runtime/memory/lib/embedding-model-config.test.mjs +0 -33
- package/src/runtime/memory/lib/http-wire.test.mjs +0 -15
- package/src/runtime/memory/lib/memory-cycle-packets.test.mjs +0 -49
- package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +0 -78
- package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +0 -50
- package/src/runtime/memory/lib/pg/process.test.mjs +0 -36
- package/src/runtime/memory/lib/trace-store.test.mjs +0 -57
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +0 -52
- package/src/runtime/shared/background-tasks.test.mjs +0 -78
- package/src/runtime/shared/bounded-download.test.mjs +0 -41
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -25
- package/src/runtime/shared/child-spawn-remote.test.mjs +0 -124
- package/src/runtime/shared/remote-intent.test.mjs +0 -40
- package/src/runtime/shared/resource-admission.test.mjs +0 -43
- package/src/runtime/shared/runtime-root.test.mjs +0 -34
- package/src/runtime/shared/schedule-time.test.mjs +0 -29
- package/src/runtime/shared/session-runtime-health.test.mjs +0 -83
- package/src/runtime/shared/skill-document.test.mjs +0 -81
- package/src/runtime/shared/tool-surface.test.mjs +0 -87
- package/src/runtime/shared/update-checker.test.mjs +0 -33
- package/src/runtime/shared/user-cwd.test.mjs +0 -43
- package/src/runtime/shared/webhook-session-run.test.mjs +0 -14
- package/src/session-runtime/agent-disable.test.mjs +0 -123
- package/src/session-runtime/cwd-plugins.test.mjs +0 -75
- package/src/session-runtime/cwd-tool-routing.test.mjs +0 -61
- package/src/session-runtime/global-extensions.test.mjs +0 -28
- package/src/session-runtime/goal-runtime.test.mjs +0 -108
- package/src/session-runtime/lifecycle-api.test.mjs +0 -21
- package/src/session-runtime/model-settings-persist.test.mjs +0 -105
- package/src/session-runtime/notification-bus.test.mjs +0 -148
- package/src/session-runtime/plugin-mcp-standard.test.mjs +0 -177
- package/src/session-runtime/prewarm.test.mjs +0 -52
- package/src/session-runtime/provider-request-snapshot.test.mjs +0 -37
- package/src/session-runtime/resource-api-global.test.mjs +0 -69
- package/src/session-runtime/session-lifecycle.test.mjs +0 -125
- package/src/session-runtime/session-title.test.mjs +0 -36
- package/src/session-runtime/skills-api.test.mjs +0 -99
- package/src/session-runtime/tool-policy-surface.test.mjs +0 -331
- package/src/standalone/agent-tool/helpers.test.mjs +0 -62
- package/src/standalone/agent-tool/notify.test.mjs +0 -37
- package/src/standalone/agent-tool/spawn-flow.test.mjs +0 -63
- package/src/standalone/channel-restart.test.mjs +0 -145
- package/src/standalone/channel-session-router.test.mjs +0 -37
- package/src/standalone/channel-worker-heartbeat.test.mjs +0 -30
- package/src/standalone/hook-bus/handlers.test.mjs +0 -88
- package/src/standalone/plugin-admin.test.mjs +0 -173
- package/src/standalone/session-runtime-agent-control-client.test.mjs +0 -71
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +0 -139
- package/src/standalone/session-runtime-host-factory.test.mjs +0 -31
- package/src/standalone/session-runtime-host-health.test.mjs +0 -160
- package/src/standalone/session-runtime-inline-host.test.mjs +0 -83
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +0 -80
- package/src/standalone/session-runtime-shard-host.test.mjs +0 -569
- package/src/standalone/session-runtime-shard-router.test.mjs +0 -101
- package/src/tui/app/live-spinner-visibility.test.mjs +0 -44
- package/src/tui/app/panel-epoch.test.mjs +0 -1009
- package/src/tui/app/panel-handoff.test.mjs +0 -311
- package/src/tui/app/prompt-submit.test.mjs +0 -206
- package/src/tui/session/completion-card-restore.test.mjs +0 -111
- package/src/tui/session/goal-continuation.test.mjs +0 -84
- package/src/tui/session/oauth-flows.test.mjs +0 -134
- package/src/tui/session/queue-helpers.test.mjs +0 -46
- package/src/tui/session/session-action-surface.test.mjs +0 -108
- package/src/tui/session/task-wait-submit.test.mjs +0 -80
- package/src/tui/session/tool-result-status.test.mjs +0 -131
- package/src/tui/session/tui-steering-persist.test.mjs +0 -104
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
// Human-readable transport label for handshake/acquire error messages. Shared
|
|
27
27
|
// with openai-oauth-ws.mjs (stream-side errors use the same labels).
|
|
28
28
|
import { _envOn, _codexBetaFeatures, _dumpHandshakeHeaders, _dumpFrame, _formatRedactedHeaders, _cfCookieHeader, _cfCookieCapture } from './openai-ws-headers.mjs';
|
|
29
|
+
import {
|
|
30
|
+
clearAllCodexTurnStates,
|
|
31
|
+
clearCodexTurnStateScope,
|
|
32
|
+
retireCodexTurnStateOwner,
|
|
33
|
+
} from './openai-turn-state.mjs';
|
|
29
34
|
export function _wsErrLabel(p) {
|
|
30
35
|
if (p === 'xai') return 'xAI WS';
|
|
31
36
|
if (p === 'openai-direct' || p === 'openai') return 'OpenAI WS';
|
|
@@ -81,44 +86,94 @@ const MAX_POOLED_SOCKETS_PER_KEY = 8;
|
|
|
81
86
|
export const _wsPool = new Map();
|
|
82
87
|
let _releaseSequence = 0;
|
|
83
88
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
89
|
+
// Codex gives one turn-scoped session exclusive use of one WebSocket until its
|
|
90
|
+
// response stream ends. Mirror that ownership per poolKey: a concurrent acquire
|
|
91
|
+
// waits instead of opening a sibling connection with a mismatched response
|
|
92
|
+
// chain. The claim is retained through close when keep=false so only an actual
|
|
93
|
+
// close can hand turn state to a replacement socket.
|
|
94
|
+
const _poolOwnerByKey = new Map();
|
|
95
|
+
|
|
96
|
+
function _acquireAbortError(externalSignal) {
|
|
97
|
+
const reason = externalSignal?.reason;
|
|
98
|
+
return reason instanceof Error ? reason : new Error('OpenAI OAuth WS acquire aborted');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _waitForPoolOwnerRelease(claim, externalSignal) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
let settled = false;
|
|
104
|
+
const finish = (fn, value) => {
|
|
105
|
+
if (settled) return;
|
|
106
|
+
settled = true;
|
|
107
|
+
claim.waiters.delete(onRelease);
|
|
108
|
+
try { externalSignal?.removeEventListener('abort', onAbort); } catch {}
|
|
109
|
+
fn(value);
|
|
110
|
+
};
|
|
111
|
+
const onRelease = () => finish(resolve);
|
|
112
|
+
const onAbort = () => finish(reject, _acquireAbortError(externalSignal));
|
|
113
|
+
claim.waiters.add(onRelease);
|
|
114
|
+
if (externalSignal) {
|
|
115
|
+
if (externalSignal.aborted) {
|
|
116
|
+
onAbort();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
externalSignal.addEventListener('abort', onAbort, { once: true });
|
|
120
|
+
}
|
|
113
121
|
});
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
function
|
|
124
|
+
async function _claimPoolOwner(poolKey, externalSignal) {
|
|
117
125
|
if (!poolKey) return null;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
const waitStartedAt = performance.now();
|
|
127
|
+
for (;;) {
|
|
128
|
+
if (externalSignal?.aborted) throw _acquireAbortError(externalSignal);
|
|
129
|
+
const current = _poolOwnerByKey.get(poolKey);
|
|
130
|
+
if (!current) {
|
|
131
|
+
const claim = {
|
|
132
|
+
entry: null,
|
|
133
|
+
waiters: new Set(),
|
|
134
|
+
waitMs: Math.max(0, performance.now() - waitStartedAt),
|
|
135
|
+
};
|
|
136
|
+
_poolOwnerByKey.set(poolKey, claim);
|
|
137
|
+
return claim;
|
|
138
|
+
}
|
|
139
|
+
await _waitForPoolOwnerRelease(current, externalSignal);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function _bindPoolOwner(poolKey, claim, entry) {
|
|
144
|
+
if (!poolKey || !claim) return;
|
|
145
|
+
if (_poolOwnerByKey.get(poolKey) !== claim) {
|
|
146
|
+
try { entry?.socket?.close?.(1000, 'pool_owner_changed'); } catch {}
|
|
147
|
+
_scheduleForcedTerminate(entry?.socket);
|
|
148
|
+
throw new Error('OpenAI OAuth WS ownership changed during acquire');
|
|
149
|
+
}
|
|
150
|
+
claim.entry = entry;
|
|
151
|
+
entry.poolOwnerClaim = claim;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function _releasePoolOwner(poolKey, owner) {
|
|
155
|
+
if (!poolKey || !owner) return;
|
|
156
|
+
const claim = owner.poolOwnerClaim || owner;
|
|
157
|
+
if (_poolOwnerByKey.get(poolKey) !== claim) return;
|
|
158
|
+
_poolOwnerByKey.delete(poolKey);
|
|
159
|
+
if (claim.entry?.poolOwnerClaim === claim) {
|
|
160
|
+
try { delete claim.entry.poolOwnerClaim; } catch {}
|
|
161
|
+
}
|
|
162
|
+
for (const waiter of [...claim.waiters]) {
|
|
163
|
+
try { waiter(); } catch {}
|
|
164
|
+
}
|
|
165
|
+
claim.waiters.clear();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function _clearPoolOwnerScope(poolKey) {
|
|
169
|
+
const claim = poolKey ? _poolOwnerByKey.get(poolKey) : null;
|
|
170
|
+
if (claim) _releasePoolOwner(poolKey, claim);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function _clearAllPoolOwners() {
|
|
174
|
+
for (const [poolKey, claim] of [..._poolOwnerByKey.entries()]) {
|
|
175
|
+
_releasePoolOwner(poolKey, claim);
|
|
176
|
+
}
|
|
122
177
|
}
|
|
123
178
|
|
|
124
179
|
function _poolCompatibility(auth, cacheKey) {
|
|
@@ -141,12 +196,9 @@ function _entryCompatible(entry, compatibility) {
|
|
|
141
196
|
&& entry?.cacheKeyIdentity === compatibility.cacheKeyIdentity;
|
|
142
197
|
}
|
|
143
198
|
|
|
144
|
-
// A previous_response_id is valid on the connection that produced it.
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// to be inserted first. This is the closest representation of Codex's
|
|
148
|
-
// turn-local single-connection ownership without weakening the existing
|
|
149
|
-
// full-frame fallback checks.
|
|
199
|
+
// A previous_response_id is valid on the connection that produced it. If a
|
|
200
|
+
// bucket contains multiple legacy idle entries, continue on the most recently
|
|
201
|
+
// completed one instead of whichever socket happened to be inserted first.
|
|
150
202
|
function _selectIdleEntry(entries, compatibility) {
|
|
151
203
|
let selected;
|
|
152
204
|
for (const entry of entries || []) {
|
|
@@ -203,9 +255,6 @@ function _removeFromPool(poolKey, entry) {
|
|
|
203
255
|
// idle-close or liveness-ping interval.
|
|
204
256
|
_clearIdle(entry);
|
|
205
257
|
_clearLiveness(entry);
|
|
206
|
-
// The entry is going away; keep its shard pin so the session's next socket
|
|
207
|
-
// can reconnect onto the same backend node.
|
|
208
|
-
_retainTurnState(poolKey, entry);
|
|
209
258
|
if (!poolKey) return;
|
|
210
259
|
const arr = _wsPool.get(poolKey);
|
|
211
260
|
if (!arr) return;
|
|
@@ -375,7 +424,7 @@ export function _sendFrame(entry, frame, sendSpan = null, timeoutMs = WS_SEND_TI
|
|
|
375
424
|
});
|
|
376
425
|
}
|
|
377
426
|
|
|
378
|
-
function _buildHandshakeHeaders({ auth, sessionToken,
|
|
427
|
+
function _buildHandshakeHeaders({ auth, sessionToken, cacheKey: _cacheKey, codexHeaders }) {
|
|
379
428
|
// xAI WS: do NOT pin x-grok-conv-id. Measured parallel runs show that
|
|
380
429
|
// forcing a routing shard via that header alternates cold caches across
|
|
381
430
|
// parallel workers; the automatic prompt-prefix cache holds up better
|
|
@@ -431,7 +480,6 @@ function _buildHandshakeHeaders({ auth, sessionToken, turnState, cacheKey: _cach
|
|
|
431
480
|
// distinguishable across reconnects.
|
|
432
481
|
headers['x-client-request-id'] = randomBytes(16).toString('hex');
|
|
433
482
|
}
|
|
434
|
-
if (turnState) headers['x-codex-turn-state'] = turnState;
|
|
435
483
|
// Probe knobs (cache-route hunt 2026-07-04): see jar block at top of file.
|
|
436
484
|
if (isOpenAiOauth) {
|
|
437
485
|
const jar = _cfCookieHeader(auth);
|
|
@@ -454,8 +502,8 @@ function _mintSessionToken(cacheKey, auth) {
|
|
|
454
502
|
return cacheKey || 'mixdog-default';
|
|
455
503
|
}
|
|
456
504
|
|
|
457
|
-
function _openSocket({ auth, sessionToken,
|
|
458
|
-
const headers = _buildHandshakeHeaders({ auth, sessionToken,
|
|
505
|
+
function _openSocket({ auth, sessionToken, externalSignal, cacheKey, codexHeaders }) {
|
|
506
|
+
const headers = _buildHandshakeHeaders({ auth, sessionToken, cacheKey, codexHeaders });
|
|
459
507
|
const baseUrl = auth.type === 'xai'
|
|
460
508
|
? XAI_WS_URL
|
|
461
509
|
: auth.type === 'openai-direct'
|
|
@@ -510,11 +558,8 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
|
|
|
510
558
|
));
|
|
511
559
|
}, WS_ACQUIRE_TIMEOUT_MS);
|
|
512
560
|
try { acquireTimer.unref?.(); } catch {}
|
|
513
|
-
const capturedHeaders = { turnState: null };
|
|
514
561
|
socket.once('upgrade', (res) => {
|
|
515
562
|
try {
|
|
516
|
-
const ts = res?.headers?.['x-codex-turn-state'];
|
|
517
|
-
if (typeof ts === 'string' && ts.length) capturedHeaders.turnState = ts;
|
|
518
563
|
_cfCookieCapture(auth, res?.headers?.['set-cookie']);
|
|
519
564
|
// Probe: dump the full 101-upgrade response header set so we can
|
|
520
565
|
// see what the server actually issues (turn-state investigation).
|
|
@@ -541,7 +586,7 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
|
|
|
541
586
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
542
587
|
process.stderr.write(`[agent-trace] ws-open-ok elapsed=${Date.now() - _wsOpenStart}ms\n`);
|
|
543
588
|
}
|
|
544
|
-
settle(true, { socket
|
|
589
|
+
settle(true, { socket });
|
|
545
590
|
});
|
|
546
591
|
socket.once('error', (err) => {
|
|
547
592
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
@@ -590,15 +635,23 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
|
|
|
590
635
|
|
|
591
636
|
let _openSocketImpl = _openSocket;
|
|
592
637
|
|
|
593
|
-
export async function acquireWebSocket({
|
|
638
|
+
export async function acquireWebSocket({
|
|
639
|
+
auth,
|
|
640
|
+
poolKey,
|
|
641
|
+
cacheKey,
|
|
642
|
+
codexHeaders,
|
|
643
|
+
forceFresh,
|
|
644
|
+
externalSignal,
|
|
645
|
+
}) {
|
|
594
646
|
const _acqStart = Date.now();
|
|
595
647
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
596
648
|
process.stderr.write(`[agent-trace] acquire-start poolKey=${poolKey} cacheKey=${cacheKey} forceFresh=${forceFresh} externalAborted=${!!externalSignal?.aborted} ts=${_acqStart}\n`);
|
|
597
649
|
}
|
|
598
650
|
if (externalSignal?.aborted) {
|
|
599
|
-
|
|
600
|
-
throw reason instanceof Error ? reason : new Error('OpenAI OAuth WS acquire aborted');
|
|
651
|
+
throw _acquireAbortError(externalSignal);
|
|
601
652
|
}
|
|
653
|
+
const ownerClaim = await _claimPoolOwner(poolKey, externalSignal);
|
|
654
|
+
try {
|
|
602
655
|
if (poolKey && !forceFresh) {
|
|
603
656
|
const arr = _wsPool.get(poolKey) || [];
|
|
604
657
|
const compatibility = _poolCompatibility(auth, cacheKey);
|
|
@@ -610,13 +663,6 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
610
663
|
if (!_isOpen(arr[i]) || arr[i].closing || incompatibleIdle) {
|
|
611
664
|
if (incompatibleIdle) {
|
|
612
665
|
try { arr[i].socket.close(1000, 'pool_boundary_changed'); } catch {}
|
|
613
|
-
} else {
|
|
614
|
-
// A dead socket pruned here never reaches _removeFromPool,
|
|
615
|
-
// so retain its shard pin explicitly — this is the common
|
|
616
|
-
// reconnect path and the one the replacement handshake
|
|
617
|
-
// below reads. A boundary change deliberately drops the
|
|
618
|
-
// pin: it belongs to the old auth/cache identity.
|
|
619
|
-
_retainTurnState(poolKey, arr[i]);
|
|
620
666
|
}
|
|
621
667
|
_clearIdle(arr[i]);
|
|
622
668
|
_clearLiveness(arr[i]);
|
|
@@ -660,7 +706,8 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
660
706
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
661
707
|
process.stderr.write(`[agent-trace] acquire-reuse poolKey=${poolKey} openSockets=${arr.length} elapsed=${Date.now() - _acqStart}ms\n`);
|
|
662
708
|
}
|
|
663
|
-
|
|
709
|
+
_bindPoolOwner(poolKey, ownerClaim, idle);
|
|
710
|
+
return { entry: idle, reused: true, ownerWaitMs: ownerClaim?.waitMs || 0 };
|
|
664
711
|
}
|
|
665
712
|
// All entries busy and bucket at cap: fall through to ephemeral socket.
|
|
666
713
|
if (arr.length >= MAX_POOLED_SOCKETS_PER_KEY) {
|
|
@@ -668,7 +715,7 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
668
715
|
process.stderr.write(`[agent-trace] acquire-ephemeral cacheKey=${cacheKey} reason=cap elapsed=${Date.now() - _acqStart}ms\n`);
|
|
669
716
|
}
|
|
670
717
|
const ephSessionToken = _mintSessionToken(cacheKey, auth);
|
|
671
|
-
const { socket
|
|
718
|
+
const { socket } = await _openSocketImpl({ auth, sessionToken: ephSessionToken, externalSignal, cacheKey, codexHeaders });
|
|
672
719
|
// Drain-complete fence: same invariant as the normal acquire path —
|
|
673
720
|
// if drain fired during the await, do NOT push an ephemeral entry
|
|
674
721
|
// back into the pool.
|
|
@@ -688,7 +735,7 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
688
735
|
lastInputPrefixHash: null,
|
|
689
736
|
releaseSequence: 0,
|
|
690
737
|
...compatibility,
|
|
691
|
-
turnState:
|
|
738
|
+
turnState: null,
|
|
692
739
|
closing: false,
|
|
693
740
|
ephemeral: true,
|
|
694
741
|
sessionToken: ephSessionToken,
|
|
@@ -698,26 +745,27 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
698
745
|
entry.probing = false;
|
|
699
746
|
socket.on('pong', () => { entry.lastAliveAt = Date.now(); });
|
|
700
747
|
socket.on('message', () => { entry.lastAliveAt = Date.now(); });
|
|
701
|
-
socket.on('close', () => {
|
|
748
|
+
socket.on('close', () => {
|
|
749
|
+
entry.closing = true;
|
|
750
|
+
retireCodexTurnStateOwner(entry.turnStateScope || poolKey, entry);
|
|
751
|
+
_releasePoolOwner(poolKey, entry);
|
|
752
|
+
_untrackUnpooled(entry);
|
|
753
|
+
});
|
|
702
754
|
// Cap-overflow ephemeral: never pooled, still owned by this session.
|
|
703
755
|
_trackUnpooled(entry, poolKey);
|
|
704
|
-
|
|
756
|
+
_bindPoolOwner(poolKey, ownerClaim, entry);
|
|
757
|
+
return { entry, reused: false, ownerWaitMs: ownerClaim?.waitMs || 0 };
|
|
705
758
|
}
|
|
706
759
|
}
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
// propagates within a single entry across its own iterations — plus the
|
|
711
|
-
// codex reconnect case below, where the bucket holds no live entry and the
|
|
712
|
-
// retained token re-pins the shard the dead socket was using. forceFresh
|
|
713
|
-
// asks for a deliberately unpinned socket, so it always opens clean.
|
|
760
|
+
// A handshake is scoped to a physical connection, while turn state belongs
|
|
761
|
+
// to one logical turn. Every new socket therefore opens without turn state;
|
|
762
|
+
// request metadata restores it later only when the turn id matches.
|
|
714
763
|
const sessionToken = _mintSessionToken(cacheKey, auth);
|
|
715
764
|
const compatibility = _poolCompatibility(auth, cacheKey);
|
|
716
|
-
const retainedTurnState = forceFresh ? null : _replacementTurnState(poolKey);
|
|
717
765
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
718
766
|
process.stderr.write(`[agent-trace] acquire-new tokenHash=${createHash('sha256').update(String(sessionToken)).digest('hex').slice(0, 8)} elapsed=${Date.now() - _acqStart}ms\n`);
|
|
719
767
|
}
|
|
720
|
-
const { socket
|
|
768
|
+
const { socket } = await _openSocketImpl({ auth, sessionToken, externalSignal, cacheKey, codexHeaders });
|
|
721
769
|
// Drain may complete while the normal handshake is awaiting 'open'. Never
|
|
722
770
|
// return or insert that late socket into the already-drained process pool.
|
|
723
771
|
if (_drainComplete) {
|
|
@@ -736,11 +784,8 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
736
784
|
lastInputPrefixHash: null,
|
|
737
785
|
releaseSequence: 0,
|
|
738
786
|
...compatibility,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// original turn owner so the per-turn drop guard still retires it.
|
|
742
|
-
turnState: turnState || retainedTurnState?.turnState || null,
|
|
743
|
-
turnStateTurnId: turnState ? null : (retainedTurnState?.turnStateTurnId ?? null),
|
|
787
|
+
turnState: null,
|
|
788
|
+
turnStateTurnId: null,
|
|
744
789
|
closing: false,
|
|
745
790
|
ephemeral: false,
|
|
746
791
|
sessionToken,
|
|
@@ -757,10 +802,17 @@ export async function acquireWebSocket({ auth, poolKey, cacheKey, codexHeaders,
|
|
|
757
802
|
else _trackUnpooled(entry, poolKey);
|
|
758
803
|
socket.on('close', () => {
|
|
759
804
|
entry.closing = true;
|
|
805
|
+
retireCodexTurnStateOwner(entry.turnStateScope || poolKey, entry);
|
|
806
|
+
_releasePoolOwner(poolKey, entry);
|
|
760
807
|
_untrackUnpooled(entry);
|
|
761
808
|
_removeFromPool(poolKey, entry);
|
|
762
809
|
});
|
|
763
|
-
|
|
810
|
+
_bindPoolOwner(poolKey, ownerClaim, entry);
|
|
811
|
+
return { entry, reused: false, ownerWaitMs: ownerClaim?.waitMs || 0 };
|
|
812
|
+
} catch (err) {
|
|
813
|
+
_releasePoolOwner(poolKey, ownerClaim);
|
|
814
|
+
throw err;
|
|
815
|
+
}
|
|
764
816
|
}
|
|
765
817
|
|
|
766
818
|
export function releaseWebSocket({ entry, poolKey, keep }) {
|
|
@@ -790,14 +842,13 @@ export function releaseWebSocket({ entry, poolKey, keep }) {
|
|
|
790
842
|
// Idle pooled sockets retain reuse state without retaining the process.
|
|
791
843
|
// acquireWebSocket re-refs the transport before probing or handing it out.
|
|
792
844
|
_setTransportReferenced(entry, false);
|
|
845
|
+
_releasePoolOwner(poolKey, entry);
|
|
793
846
|
}
|
|
794
847
|
|
|
795
848
|
export function closeOpenaiWsPoolForSession(poolKey, reason = 'session_closed') {
|
|
796
849
|
if (!poolKey) return;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
// close() calls also keeps the async 'close' handlers from re-retaining it.
|
|
800
|
-
_turnStateByPoolKey.delete(poolKey);
|
|
850
|
+
_clearPoolOwnerScope(poolKey);
|
|
851
|
+
clearCodexTurnStateScope(poolKey);
|
|
801
852
|
const closeReason = String(reason || 'session_closed');
|
|
802
853
|
const entries = _wsPool.get(poolKey);
|
|
803
854
|
if (entries) {
|
|
@@ -859,6 +910,7 @@ export function hasPooledWebSocket(poolKey) {
|
|
|
859
910
|
}
|
|
860
911
|
|
|
861
912
|
export function _clearWebSocketPoolForTest() {
|
|
913
|
+
_clearAllPoolOwners();
|
|
862
914
|
for (const arr of _wsPool.values()) {
|
|
863
915
|
for (const entry of arr) {
|
|
864
916
|
entry.turnState = null;
|
|
@@ -875,7 +927,7 @@ export function _clearWebSocketPoolForTest() {
|
|
|
875
927
|
}
|
|
876
928
|
_unpooledSockets.clear();
|
|
877
929
|
_wsPool.clear();
|
|
878
|
-
|
|
930
|
+
clearAllCodexTurnStates();
|
|
879
931
|
_releaseSequence = 0;
|
|
880
932
|
}
|
|
881
933
|
|
|
@@ -892,6 +944,8 @@ let _drainComplete = false;
|
|
|
892
944
|
// Force-closes pooled sockets and fences subsequent acquires.
|
|
893
945
|
export function drainOpenaiWsPool(reason = 'shutdown', { immediate = false } = {}) {
|
|
894
946
|
_drainComplete = true;
|
|
947
|
+
_clearAllPoolOwners();
|
|
948
|
+
clearAllCodexTurnStates();
|
|
895
949
|
for (const arr of _wsPool.values()) {
|
|
896
950
|
// _shutdownEntry tears down per-entry timers before the map is dropped
|
|
897
951
|
// (otherwise the idle-close and liveness-ping intervals outlive the
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { customToolCallFromResponseItem } from './custom-tool-wire.mjs';
|
|
36
36
|
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
37
37
|
import { _wsErrLabel, WS_MAX_INCOMING_FRAME_BYTES } from './openai-ws-pool.mjs';
|
|
38
|
+
import { captureCodexTurnState } from './openai-turn-state.mjs';
|
|
38
39
|
import {
|
|
39
40
|
_sansInput,
|
|
40
41
|
_stableStringify,
|
|
@@ -181,24 +182,19 @@ function _hasHeaderKey(headers, name) {
|
|
|
181
182
|
|
|
182
183
|
export function _captureTurnStateFromEvent(entry, event) {
|
|
183
184
|
if (!entry || entry.turnState || !event || typeof event !== 'object') return;
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// previous_response_id calls. This event path is the fallback for
|
|
187
|
-
// deployments that surface the header on a response event instead; the
|
|
188
|
-
// write-once guard above means it can never swap a live chain's token.
|
|
185
|
+
// WebSocket turn state is returned by response metadata and replayed only
|
|
186
|
+
// in later response.create metadata for the same logical turn.
|
|
189
187
|
const turnState = _headerString(event.headers, X_CODEX_TURN_STATE_HEADER)
|
|
190
188
|
|| _headerString(event.response?.headers, X_CODEX_TURN_STATE_HEADER)
|
|
191
189
|
|| _headerString(event.response?.metadata?.headers, X_CODEX_TURN_STATE_HEADER)
|
|
192
190
|
|| _headerString(event.metadata?.headers, X_CODEX_TURN_STATE_HEADER);
|
|
193
191
|
if (turnState) {
|
|
194
|
-
entry.turnState =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// the next real turn instead of leaking onto it.
|
|
201
|
-
entry.turnStateTurnId = entry.currentTurnId != null ? entry.currentTurnId : null;
|
|
192
|
+
entry.turnState = captureCodexTurnState(
|
|
193
|
+
entry.turnStateScope,
|
|
194
|
+
entry.turnStateTurnId,
|
|
195
|
+
turnState,
|
|
196
|
+
entry,
|
|
197
|
+
) || ((!entry.turnStateScope || !entry.turnStateTurnId) ? turnState : null);
|
|
202
198
|
}
|
|
203
199
|
}
|
|
204
200
|
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
_intraTurnSig,
|
|
28
28
|
} from './loop/tool-classify.mjs';
|
|
29
29
|
import { preDispatchDenyForSession } from './loop/pre-dispatch-deny.mjs';
|
|
30
|
-
import { runRecallFastTrackCompact } from './loop/recall-fasttrack.mjs';
|
|
31
30
|
import { executeTool, _scopedCacheOutcomeForCall, resolveLiveToolCwd } from './loop/tool-exec.mjs';
|
|
32
31
|
|
|
33
32
|
// classifyResultKind is imported from result-classification.mjs at the top of
|
|
@@ -35,6 +34,7 @@ import { executeTool, _scopedCacheOutcomeForCall, resolveLiveToolCwd } from './l
|
|
|
35
34
|
import { recordToolBatch } from '../tools/tool-batch-trace.mjs';
|
|
36
35
|
import { projectProviderEvidence } from './evidence-union.mjs';
|
|
37
36
|
import { prepareProviderPrefixGuard } from './provider-prefix-guard.mjs';
|
|
37
|
+
import { traceCacheBreak } from '../cache-break-trace.mjs';
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
import { resolve as resolvePath, isAbsolute } from 'path';
|
|
@@ -147,6 +147,7 @@ const MAX_OUTPUT_EXHAUSTED_NOTICE = '[mixdog-runtime] Output remained truncated
|
|
|
147
147
|
* tool. When aborted, throws SessionClosedError so the ask
|
|
148
148
|
* wrapper can propagate a clean cancellation.
|
|
149
149
|
* - `onStageChange(stage)` / `onStreamDelta()` — forwarded to provider.send for heartbeats
|
|
150
|
+
* - `liveProjection` — when true, Agent sessions keep provider onTextDelta / mid-turn text
|
|
150
151
|
*/
|
|
151
152
|
// Stop reasons that signal the turn was cut short mid-synthesis (token cap,
|
|
152
153
|
// provider pause). Empty content + one of these reasons means the worker
|
|
@@ -162,6 +163,19 @@ export function attachAssistantTranscriptMetadata(message, opts = {}) {
|
|
|
162
163
|
return { ...message, meta: { ...meta, transcript } };
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
// Agent mid-turn text is suppressed unless THIS send explicitly requested a
|
|
167
|
+
// live projection. The request is a send-opt (`liveProjection`) — never a
|
|
168
|
+
// durable session field — so a transient Agent pane cannot leak into later
|
|
169
|
+
// silent dispatches or stored session JSON. The standalone surface still
|
|
170
|
+
// flips `interactiveSessionSurface` on the in-memory session; honor that
|
|
171
|
+
// in-process hint without treating it as something dispatch should persist.
|
|
172
|
+
export function shouldSuppressAgentMidTurnText(sessionRef, opts = {}) {
|
|
173
|
+
if (!isAgentOwner(sessionRef)) return false;
|
|
174
|
+
if (opts?.liveProjection === true) return false;
|
|
175
|
+
if (sessionRef?.interactiveSessionSurface === true) return false;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
165
179
|
export async function agentLoop(provider, messages, model, tools, onToolCall, cwd, sendOpts) {
|
|
166
180
|
let iterations = 0;
|
|
167
181
|
let toolCallsTotal = 0;
|
|
@@ -203,6 +217,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
203
217
|
};
|
|
204
218
|
const sessionRef = opts.session || null;
|
|
205
219
|
let _providerPrefixGuardState = sessionRef?._providerPrefixGuardState || null;
|
|
220
|
+
const _cacheBreakTraceKeys = new Set();
|
|
206
221
|
// Provider tool snapshots are request-loop state, never durable session
|
|
207
222
|
// state. Older builds persisted this field, which let a resumed session
|
|
208
223
|
// keep advertising a retired schema even after session.tools was rebuilt.
|
|
@@ -222,7 +237,11 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
222
237
|
// - buffered : opts.onAssistantText skipped (response.content below)
|
|
223
238
|
// - history : tool-call turn content blanked before messages.push
|
|
224
239
|
// Reasoning/thinking deltas, tool calls, and the final answer are kept.
|
|
225
|
-
|
|
240
|
+
// Provider onTextDelta is restored only when live projection was
|
|
241
|
+
// explicitly requested for THIS send (`opts.liveProjection`). Absence
|
|
242
|
+
// of that flag preserves the silent Agent path even if a tracker
|
|
243
|
+
// wrapper was installed upstream.
|
|
244
|
+
const suppressMidTurnText = shouldSuppressAgentMidTurnText(sessionRef, opts);
|
|
226
245
|
if (suppressMidTurnText) opts.onTextDelta = undefined;
|
|
227
246
|
// Deferred mutation-body compaction: bodies left verbatim by a previous
|
|
228
247
|
// turn's push (deferBodies below) collapse to markers now — the model has
|
|
@@ -264,6 +283,13 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
264
283
|
const submissionIds = Array.isArray(merged.ids) ? merged.ids : [];
|
|
265
284
|
const submittedAt = Number(merged.submittedAt);
|
|
266
285
|
const injectedAt = Date.now();
|
|
286
|
+
const steeringTranscriptMeta = merged.transcriptMeta
|
|
287
|
+
&& typeof merged.transcriptMeta === 'object'
|
|
288
|
+
? {
|
|
289
|
+
at: Number.isFinite(submittedAt) && submittedAt > 0 ? submittedAt : injectedAt,
|
|
290
|
+
...merged.transcriptMeta,
|
|
291
|
+
}
|
|
292
|
+
: null;
|
|
267
293
|
if (Number.isFinite(submittedAt) && submittedAt > 0) {
|
|
268
294
|
maxQueueWaitMs = Math.max(maxQueueWaitMs, injectedAt - submittedAt);
|
|
269
295
|
}
|
|
@@ -277,6 +303,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
277
303
|
meta: {
|
|
278
304
|
source: 'steering',
|
|
279
305
|
...(submissionIds.length ? { submissionIds } : {}),
|
|
306
|
+
...(steeringTranscriptMeta ? { transcript: steeringTranscriptMeta } : {}),
|
|
280
307
|
},
|
|
281
308
|
});
|
|
282
309
|
const text = merged.text || steeringContentText(merged.content);
|
|
@@ -289,6 +316,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
289
316
|
injectedAt,
|
|
290
317
|
stage,
|
|
291
318
|
...(Array.isArray(merged.images) && merged.images.length ? { images: merged.images } : {}),
|
|
319
|
+
...(steeringTranscriptMeta ? { transcriptMeta: steeringTranscriptMeta } : {}),
|
|
292
320
|
});
|
|
293
321
|
} catch {}
|
|
294
322
|
}
|
|
@@ -621,6 +649,9 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
621
649
|
pathAliases: false,
|
|
622
650
|
});
|
|
623
651
|
const _providerMessages = _evidenceProjection.messages;
|
|
652
|
+
const _prefixMutationSource = opts.cacheBreakIntent === 'transcript_rebuild'
|
|
653
|
+
? 'transcript_rebuild'
|
|
654
|
+
: (_evidenceProjection.stats.changedToolResults > 0 ? 'evidence_union' : null);
|
|
624
655
|
const _providerPrefixGuardCandidate = prepareProviderPrefixGuard(
|
|
625
656
|
_providerPrefixGuardState,
|
|
626
657
|
_providerMessages,
|
|
@@ -630,7 +661,28 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
630
661
|
},
|
|
631
662
|
{
|
|
632
663
|
provider: sessionRef?.provider || provider?.name || null,
|
|
664
|
+
model: model || null,
|
|
633
665
|
cacheBreakIntent: opts.cacheBreakIntent,
|
|
666
|
+
mutationSource: _prefixMutationSource,
|
|
667
|
+
onCacheBreak: (details) => {
|
|
668
|
+
const key = [
|
|
669
|
+
details.classification,
|
|
670
|
+
details.reason,
|
|
671
|
+
details.index,
|
|
672
|
+
details.previousHash,
|
|
673
|
+
details.nextHash,
|
|
674
|
+
details.previousRequestPrefixHash,
|
|
675
|
+
details.nextRequestPrefixHash,
|
|
676
|
+
].join('|');
|
|
677
|
+
if (_cacheBreakTraceKeys.has(key)) return;
|
|
678
|
+
_cacheBreakTraceKeys.add(key);
|
|
679
|
+
traceCacheBreak({
|
|
680
|
+
sessionId,
|
|
681
|
+
iteration: nextIteration,
|
|
682
|
+
intentionalTransition: opts.cacheBreakIntent,
|
|
683
|
+
...details,
|
|
684
|
+
});
|
|
685
|
+
},
|
|
634
686
|
},
|
|
635
687
|
);
|
|
636
688
|
if (_evidenceProjection.stats.reusedRows > 0
|
|
@@ -701,8 +753,24 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
701
753
|
response = _sendResult.response;
|
|
702
754
|
_providerPrefixGuardState = _providerPrefixGuardCandidate;
|
|
703
755
|
if (sessionRef) sessionRef._providerPrefixGuardState = _providerPrefixGuardState;
|
|
704
|
-
if (_imageStripActive
|
|
705
|
-
|
|
756
|
+
if (_imageStripActive) {
|
|
757
|
+
if (Array.isArray(_pendingImageStripPersistMessages)) {
|
|
758
|
+
messages.splice(0, messages.length, ..._pendingImageStripPersistMessages);
|
|
759
|
+
} else {
|
|
760
|
+
_providerPrefixGuardState = null;
|
|
761
|
+
if (sessionRef) delete sessionRef._providerPrefixGuardState;
|
|
762
|
+
traceCacheBreak({
|
|
763
|
+
sessionId,
|
|
764
|
+
iteration: nextIteration,
|
|
765
|
+
classification: 'intentional',
|
|
766
|
+
reason: 'image_strip_nonpersistent_rebaseline',
|
|
767
|
+
source: 'image_strip_retry',
|
|
768
|
+
provider: sessionRef?.provider || provider?.name || null,
|
|
769
|
+
model: model || null,
|
|
770
|
+
previousCount: _providerMessages.length,
|
|
771
|
+
nextCount: messages.length,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
706
774
|
_imageStripActive = false;
|
|
707
775
|
_pendingImageStripPersistMessages = null;
|
|
708
776
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const SNAPSHOT_RE = /(?:^|\n)Snapshot:\s+(p\d+)-s\d+\b/i;
|
|
2
|
+
|
|
3
|
+
export const BROWSER_SNAPSHOT_SUPERSEDED =
|
|
4
|
+
'[Browser snapshot superseded — a newer snapshot for this page exists later in the conversation. '
|
|
5
|
+
+ 'Use refs from the latest snapshot or call browser snapshot/observe again.]';
|
|
6
|
+
|
|
7
|
+
function contentText(content) {
|
|
8
|
+
if (typeof content === 'string') return content;
|
|
9
|
+
if (content && typeof content === 'object' && Array.isArray(content.content)) {
|
|
10
|
+
return contentText(content.content);
|
|
11
|
+
}
|
|
12
|
+
if (!Array.isArray(content)) return '';
|
|
13
|
+
return content
|
|
14
|
+
.filter((part) => part && typeof part === 'object' && part.type === 'text')
|
|
15
|
+
.map((part) => String(part.text || ''))
|
|
16
|
+
.join('\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function supersedeBrowserSnapshots(messages) {
|
|
20
|
+
if (!Array.isArray(messages) || messages.length < 2) {
|
|
21
|
+
return { messages, replaced: 0, savedBytes: 0 };
|
|
22
|
+
}
|
|
23
|
+
const browserCallIds = new Set();
|
|
24
|
+
for (const message of messages) {
|
|
25
|
+
if (message?.role !== 'assistant' || !Array.isArray(message.toolCalls)) continue;
|
|
26
|
+
for (const call of message.toolCalls) {
|
|
27
|
+
const name = String(call?.name || call?.function?.name || '').toLowerCase();
|
|
28
|
+
if (name === 'browser' && call?.id) browserCallIds.add(call.id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const locations = [];
|
|
32
|
+
const latestByPage = new Map();
|
|
33
|
+
messages.forEach((message, index) => {
|
|
34
|
+
if (message?.role !== 'tool') return;
|
|
35
|
+
if (browserCallIds.size > 0 && !browserCallIds.has(message.toolCallId)) return;
|
|
36
|
+
const text = contentText(message.content);
|
|
37
|
+
const match = SNAPSHOT_RE.exec(text);
|
|
38
|
+
if (!match) return;
|
|
39
|
+
const pageId = match[1].toLowerCase();
|
|
40
|
+
locations.push({ index, pageId, text });
|
|
41
|
+
latestByPage.set(pageId, index);
|
|
42
|
+
});
|
|
43
|
+
const stale = locations.filter((entry) => latestByPage.get(entry.pageId) !== entry.index);
|
|
44
|
+
if (!stale.length) return { messages, replaced: 0, savedBytes: 0 };
|
|
45
|
+
const projected = messages.slice();
|
|
46
|
+
let savedBytes = 0;
|
|
47
|
+
for (const entry of stale) {
|
|
48
|
+
const original = messages[entry.index];
|
|
49
|
+
const replacement = `${BROWSER_SNAPSHOT_SUPERSEDED} (${entry.pageId})`;
|
|
50
|
+
savedBytes += Math.max(0, Buffer.byteLength(entry.text) - Buffer.byteLength(replacement));
|
|
51
|
+
projected[entry.index] = {
|
|
52
|
+
...original,
|
|
53
|
+
content: replacement,
|
|
54
|
+
meta: {
|
|
55
|
+
...(original?.meta && typeof original.meta === 'object' ? original.meta : {}),
|
|
56
|
+
browserSnapshotSuperseded: true,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { messages: projected, replaced: stale.length, savedBytes };
|
|
61
|
+
}
|