@iislee/opencodex 2.11.0 → 2.31.0
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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http2 from "node:http2";
|
|
2
2
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
3
3
|
import { namespacedToolName, type OcxProviderConfig, type OcxUsage } from "../../types";
|
|
4
|
-
import { CONNECT_FLAG_END_STREAM,
|
|
4
|
+
import { CONNECT_FLAG_END_STREAM, ConnectFrameError, consumeConnectFrames, encodeConnectFrame } from "./framing";
|
|
5
5
|
import {
|
|
6
6
|
CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES,
|
|
7
7
|
CURSOR_MAX_CONNECT_FRAME_BYTES,
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
type TranslatorBudget,
|
|
14
14
|
} from "../../lib/translator-budget";
|
|
15
15
|
import { activePromptText, prepareCursorRunRequest } from "./protobuf-request";
|
|
16
|
+
import { prepareCursorRawMessages, resolveActiveCursorImages } from "./images";
|
|
17
|
+
import { cursorRequestMessagesFromRaw } from "./request-builder";
|
|
16
18
|
import {
|
|
17
19
|
createCursorContextUsageTracker,
|
|
18
20
|
createCursorProtobufEventState,
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
CreatePlanRequestResponseSchema,
|
|
34
36
|
CreatePlanResultSchema,
|
|
35
37
|
CreatePlanSuccessSchema,
|
|
38
|
+
ConversationStateStructureSchema,
|
|
36
39
|
ExaFetchRequestResponseSchema,
|
|
37
40
|
ExaFetchRequestResponse_ApprovedSchema,
|
|
38
41
|
ExaSearchRequestResponseSchema,
|
|
@@ -48,7 +51,7 @@ import {
|
|
|
48
51
|
type InteractionResponse,
|
|
49
52
|
} from "./gen/agent_pb";
|
|
50
53
|
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
51
|
-
import { classifyCursorError, isCursorBenignCancelError, safeCursorErrorMessage } from "./cursor-errors";
|
|
54
|
+
import { classifyCursorError, CursorUnexpectedCancelError, isCursorAbortError, isCursorBenignCancelError, safeCursorErrorMessage } from "./cursor-errors";
|
|
52
55
|
import { mcpArgsFromToolCall } from "./protobuf-events";
|
|
53
56
|
import { OCX_RESPONSES_TOOL_PROVIDER } from "./tool-definitions";
|
|
54
57
|
import {
|
|
@@ -70,6 +73,7 @@ import {
|
|
|
70
73
|
cursorToolArgNormalizeSchema,
|
|
71
74
|
cursorToolWireName,
|
|
72
75
|
cursorToolsForActivePrompt,
|
|
76
|
+
isCursorSyntheticStructuredEditTool,
|
|
73
77
|
isGenericToolUseCountDemoPrompt,
|
|
74
78
|
requestedCursorToolUseCount,
|
|
75
79
|
} from "./tool-definitions";
|
|
@@ -80,11 +84,31 @@ import {
|
|
|
80
84
|
} from "./native-exec-shell";
|
|
81
85
|
import type { CursorClientMessage, CursorRunRequest, CursorServerMessage } from "./types";
|
|
82
86
|
import type { CursorTransport, CursorTransportFactoryInput } from "./transport";
|
|
87
|
+
import { CursorHttp1BidiConnection } from "./http1-bidi";
|
|
88
|
+
import { isPinnedHttp1 } from "../../lib/upstream-http-version";
|
|
83
89
|
|
|
84
90
|
const CURSOR_RUN_PATH = "/agent.v1.AgentService/Run";
|
|
85
91
|
const CURSOR_CLIENT_VERSION = "cli-2026.07.08-0c04a8a";
|
|
86
92
|
const HEARTBEAT_MS = 5_000;
|
|
87
93
|
const CURSOR_FIRST_FRAME_TIMEOUT_MS = 30_000;
|
|
94
|
+
/**
|
|
95
|
+
* T04 (senpi #1062 second half): after the first frame, a turn with NO inbound decoded
|
|
96
|
+
* frames for this long is failed instead of waiting for the 300s bridge stall watchdog
|
|
97
|
+
* (issue #2210). Reset on every decoded AgentServerMessage.
|
|
98
|
+
*/
|
|
99
|
+
const CURSOR_STREAM_SILENCE_FAIL_MS = 30_000;
|
|
100
|
+
/**
|
|
101
|
+
* A stream that produces ONLY liveness frames (server heartbeat / conversationCheckpointUpdate)
|
|
102
|
+
* for this long is equally stuck — the server is alive but the turn is not progressing.
|
|
103
|
+
* Reset on every decoded frame that is not liveness-only.
|
|
104
|
+
*/
|
|
105
|
+
const CURSOR_STREAM_HEARTBEAT_ONLY_FAIL_MS = 90_000;
|
|
106
|
+
/**
|
|
107
|
+
* After `turnEnded` is decoded, the application turn is complete. A server that keeps
|
|
108
|
+
* HTTP/2 open past this point cannot hold the turn hostage (senpi #1062): we close our side
|
|
109
|
+
* after a short grace so any trailing frames (late usage, checkpoint) still land.
|
|
110
|
+
*/
|
|
111
|
+
const TURN_ENDED_CLOSE_GRACE_MS = 500;
|
|
88
112
|
const CURSOR_TIMEOUT_DESTROY_GRACE_MS = 1_000;
|
|
89
113
|
const CLIENT_TOOL_FINALIZE_GRACE_MS = 50;
|
|
90
114
|
const GENERIC_TOOL_COUNT_MIN_FINALIZE_GRACE_MS = 750;
|
|
@@ -405,10 +429,29 @@ export function clientToolFinalizeGraceMsForRequest(request: CursorRunRequest, b
|
|
|
405
429
|
class LiveCursorTransport implements CursorTransport {
|
|
406
430
|
private session?: http2.ClientHttp2Session;
|
|
407
431
|
private stream?: http2.ClientHttp2Stream;
|
|
432
|
+
private http1Connection?: CursorHttp1BidiConnection;
|
|
408
433
|
private heartbeat?: ReturnType<typeof setInterval>;
|
|
409
434
|
private firstFrameTimer?: ReturnType<typeof setTimeout>;
|
|
435
|
+
private turnEndedCloseTimer?: ReturnType<typeof setTimeout>;
|
|
436
|
+
/**
|
|
437
|
+
* T04 inbound stream-health watchdog. Armed after the request is on the wire, reset by
|
|
438
|
+
* every DECODED frame (raw chunks deliberately do not count — TLS keepalive noise must not
|
|
439
|
+
* defeat it), disarmed by any settle/expected-close path. One timer covers both thresholds:
|
|
440
|
+
* it always fires at min(lastInbound + silence, lastMeaningful + heartbeatOnly) and re-arms
|
|
441
|
+
* when neither deadline has actually elapsed.
|
|
442
|
+
*/
|
|
443
|
+
private streamHealthTimer?: ReturnType<typeof setTimeout>;
|
|
444
|
+
private lastInboundFrameAt = 0;
|
|
445
|
+
private lastMeaningfulFrameAt = 0;
|
|
446
|
+
private streamHealthFail?: (error: Error) => void;
|
|
410
447
|
private committed = false;
|
|
411
448
|
private expectedClose = false;
|
|
449
|
+
/**
|
|
450
|
+
* True once a terminal (`done` or `error`) has been admitted to the outbound queue. Read only
|
|
451
|
+
* by the EOF branch below: after a mapper error the bridge has already failed the turn, so
|
|
452
|
+
* failing again on EOF would add a duplicate adapter error for no benefit.
|
|
453
|
+
*/
|
|
454
|
+
private emittedTerminal = false;
|
|
412
455
|
private pendingFinalize?: ReturnType<typeof setTimeout>;
|
|
413
456
|
private readonly clientToolFinalizeGraceMs: number;
|
|
414
457
|
private activeClientToolFinalizeGraceMs: number;
|
|
@@ -427,12 +470,17 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
427
470
|
// close; safe to read after a stream failure because open() owns the only writer before run().
|
|
428
471
|
private turnStartedAt = 0;
|
|
429
472
|
private framesReceived = 0;
|
|
473
|
+
private sawAssistantText = false;
|
|
430
474
|
private firstFrameAt?: number;
|
|
431
|
-
|
|
475
|
+
private firstFrameLogged = false;
|
|
432
476
|
/** Stable session identifier sent as x-session-id; mirrors IDE session semantics. */
|
|
433
|
-
private readonly sessionId
|
|
477
|
+
private readonly sessionId: string;
|
|
478
|
+
/** Per-transport owner for native-exec / background shells. Must not share conversationId. */
|
|
479
|
+
private readonly shellOwnerId = crypto.randomUUID();
|
|
480
|
+
private capturedCheckpointBytes?: Uint8Array;
|
|
434
481
|
|
|
435
482
|
constructor(private readonly input: CursorTransportFactoryInput) {
|
|
483
|
+
this.sessionId = input.sessionId?.trim() || crypto.randomUUID();
|
|
436
484
|
this.translatorBudget = input.translatorBudget;
|
|
437
485
|
this.token = resolveCursorToken(input.provider, input.headers);
|
|
438
486
|
// Grace window before a drained client-tool turn is finalized. Small enough not to look like a
|
|
@@ -444,7 +492,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
444
492
|
this.desktopDeps = desktopDepsFromConfig(input.provider.desktopExecutor);
|
|
445
493
|
this.execContext = {
|
|
446
494
|
...this.desktopDeps,
|
|
447
|
-
sessionId: this.
|
|
495
|
+
sessionId: this.shellOwnerId,
|
|
448
496
|
unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(input.provider, input.requestDeclaresFullAccess === true),
|
|
449
497
|
};
|
|
450
498
|
const servers = resolveMcpServers(input.provider);
|
|
@@ -479,7 +527,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
479
527
|
...this.desktopDeps,
|
|
480
528
|
...mcpDepsFromManager(this.mcpManager!),
|
|
481
529
|
mcpToolDefs,
|
|
482
|
-
sessionId: this.
|
|
530
|
+
sessionId: this.shellOwnerId,
|
|
483
531
|
unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(this.input.provider, this.input.requestDeclaresFullAccess === true),
|
|
484
532
|
};
|
|
485
533
|
} catch (err) {
|
|
@@ -521,6 +569,22 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
521
569
|
}
|
|
522
570
|
return err;
|
|
523
571
|
};
|
|
572
|
+
/**
|
|
573
|
+
* A cancel we did not request is a real transport failure, but as a raw `NGHTTP2_CANCEL` it
|
|
574
|
+
* gets swallowed twice over: the adapter re-decides "benign" from the error code alone
|
|
575
|
+
* (`cursor.ts:181`) and drops the turn, and any message that survives is re-matched
|
|
576
|
+
* downstream and labelled an intentional "Cursor stream suspended". Raising a typed error
|
|
577
|
+
* carries the provenance this class already holds.
|
|
578
|
+
*
|
|
579
|
+
* Suppressed once a terminal was emitted: the turn already ended, and a second terminal flips
|
|
580
|
+
* a completed buffered response to failed.
|
|
581
|
+
*/
|
|
582
|
+
const classifyTurnFailure = (err: Error): Error => {
|
|
583
|
+
if (!this.expectedClose && !this.emittedTerminal && isCursorBenignCancelError(err)) {
|
|
584
|
+
return summarizeFailure(new CursorUnexpectedCancelError(err));
|
|
585
|
+
}
|
|
586
|
+
return summarizeFailure(err);
|
|
587
|
+
};
|
|
524
588
|
const wake = () => {
|
|
525
589
|
const fn = notify;
|
|
526
590
|
notify = undefined;
|
|
@@ -530,20 +594,54 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
530
594
|
const push = (message: CursorServerMessage) => {
|
|
531
595
|
const bytes = new TextEncoder().encode(JSON.stringify(message)).byteLength;
|
|
532
596
|
this.reserveTransportBytes(bytes);
|
|
597
|
+
if (message.type === "done" || message.type === "error") this.emittedTerminal = true;
|
|
533
598
|
queue.push({ message, bytes });
|
|
534
599
|
wake();
|
|
535
600
|
};
|
|
536
601
|
|
|
537
602
|
// Advertise MCP tools before the stream opens — the server only calls tools it was told about.
|
|
538
603
|
await this.prepareMcp();
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
604
|
+
// JPEG soft-cap rewrite for active-turn data: images before encode. Rebuild text
|
|
605
|
+
// messages from the prepared raw channel so omission markers replace stale
|
|
606
|
+
// pre-rewrite content that activePromptText and the tool filter would otherwise see.
|
|
607
|
+
const preparedRaw = await prepareCursorRawMessages(request.rawMessages, signal);
|
|
608
|
+
const preparedRawMessages = preparedRaw.messages;
|
|
609
|
+
const selectedImages = await resolveActiveCursorImages(
|
|
610
|
+
preparedRawMessages,
|
|
611
|
+
signal,
|
|
612
|
+
preparedRaw.images,
|
|
613
|
+
);
|
|
614
|
+
const preparedMessages = preparedRawMessages === request.rawMessages
|
|
615
|
+
? request.messages
|
|
616
|
+
: cursorRequestMessagesFromRaw(preparedRawMessages);
|
|
617
|
+
const activeRequest: CursorRunRequest = {
|
|
618
|
+
...request,
|
|
619
|
+
messages: preparedMessages,
|
|
620
|
+
rawMessages: preparedRawMessages,
|
|
621
|
+
selectedImages,
|
|
622
|
+
};
|
|
623
|
+
const activeText = activePromptText(activeRequest);
|
|
624
|
+
this.activeClientToolFinalizeGraceMs = clientToolFinalizeGraceMsForRequest(activeRequest, this.clientToolFinalizeGraceMs);
|
|
625
|
+
const cursorVisibleTools = cursorToolsForActivePrompt(activeRequest.tools, activeText, activeRequest.toolChoice);
|
|
626
|
+
const clientToolDefs = buildCursorToolDefinitions(cursorVisibleTools, activeRequest.toolChoice);
|
|
627
|
+
// `request.tools` is the catalog already filtered and budgeted by request-builder. Derive
|
|
628
|
+
// conversion provenance only from tagged synthetic tools that also survive this final prompt
|
|
629
|
+
// filter; a client tool with the same wire name can never opt into conversion by collision.
|
|
630
|
+
const syntheticStructuredEditToolNames = new Set(
|
|
631
|
+
(cursorVisibleTools ?? [])
|
|
632
|
+
.filter(isCursorSyntheticStructuredEditTool)
|
|
633
|
+
.map(cursorToolWireName),
|
|
634
|
+
);
|
|
635
|
+
const freeformToolNames = new Set(
|
|
636
|
+
(cursorVisibleTools ?? [])
|
|
637
|
+
.filter(tool => tool.freeform)
|
|
638
|
+
.map(tool => namespacedToolName(tool.namespace, tool.name)),
|
|
639
|
+
);
|
|
543
640
|
this.execContext = {
|
|
544
641
|
...this.execContext,
|
|
545
642
|
clientToolDefs,
|
|
546
643
|
rejectNativeFileMutations: cursorRequestAdvertisesApplyPatch(request.tools, request.toolChoice),
|
|
644
|
+
structuredEditAvailable: syntheticStructuredEditToolNames.size > 0,
|
|
547
645
|
};
|
|
548
646
|
const toolSchemas = new Map<string, unknown>();
|
|
549
647
|
const cursorToolNameMap = new Map<string, string>();
|
|
@@ -561,16 +659,18 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
561
659
|
});
|
|
562
660
|
// Build the payload once. The estimate is only worth deriving when there is no
|
|
563
661
|
// carry-forward to fall back on — with a carry present it would never be used (#373).
|
|
564
|
-
const prepared = prepareCursorRunRequest(
|
|
662
|
+
const prepared = prepareCursorRunRequest(activeRequest, {
|
|
565
663
|
estimateInputTokens: contextUsage.carryForwardTokens === undefined,
|
|
566
664
|
});
|
|
567
665
|
this.blobRequestScope = prepared.blobRequestScope;
|
|
568
666
|
try {
|
|
569
667
|
state = createCursorProtobufEventState({
|
|
570
668
|
clientToolNames: clientToolDefs.map(tool => tool.toolName || tool.name),
|
|
669
|
+
freeformToolNames,
|
|
571
670
|
parallelToolCalls: request.parallelToolCalls,
|
|
572
671
|
toolSchemas,
|
|
573
672
|
cursorToolNameMap,
|
|
673
|
+
syntheticStructuredEditToolNames,
|
|
574
674
|
translatorBudget: this.translatorBudget,
|
|
575
675
|
contextUsage,
|
|
576
676
|
...(prepared.estimatedInputTokens !== undefined
|
|
@@ -603,7 +703,19 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
603
703
|
// A CANCEL is benign only on the client-tool suspend path (expectedClose); an
|
|
604
704
|
// unexpected server-side NGHTTP2_CANCEL must surface as a real transport error.
|
|
605
705
|
if (this.expectedClose && isCursorBenignCancelError(failure)) return;
|
|
606
|
-
|
|
706
|
+
// A teardown error arriving AFTER the turn's terminal frame describes the connection,
|
|
707
|
+
// not the turn: the answer is committed and every queued message has been yielded.
|
|
708
|
+
//
|
|
709
|
+
// Narrow on purpose. A benign cancel after a terminal is already swallowed one layer
|
|
710
|
+
// up (`cursor.ts:183`), so widening this to every post-terminal error would change
|
|
711
|
+
// what the adapter sees for genuine faults. What it does cover is the abort case
|
|
712
|
+
// from #1527: `signal.abort` fires `failAndClear(new Error("Cursor request was
|
|
713
|
+
// aborted"))`, which is NOT benign (`cursor-errors.ts:74`), so an ordinary completed
|
|
714
|
+
// turn that is then torn down still surfaced as `turn-failed` with
|
|
715
|
+
// `expectedClose:false`. Only `cancelCursorRun()` sets `expectedClose`, so a normal
|
|
716
|
+
// completion never qualified for the branch above.
|
|
717
|
+
if (this.emittedTerminal && isCursorAbortError(failure)) return;
|
|
718
|
+
throw attachPartialUsage(classifyTurnFailure(failure), state);
|
|
607
719
|
}
|
|
608
720
|
if (done) break;
|
|
609
721
|
await new Promise<void>(resolve => {
|
|
@@ -612,7 +724,8 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
612
724
|
}
|
|
613
725
|
if (failure) {
|
|
614
726
|
if (this.expectedClose && isCursorBenignCancelError(failure)) return;
|
|
615
|
-
|
|
727
|
+
if (this.emittedTerminal && isCursorAbortError(failure)) return;
|
|
728
|
+
throw attachPartialUsage(classifyTurnFailure(failure), state);
|
|
616
729
|
}
|
|
617
730
|
}
|
|
618
731
|
|
|
@@ -639,12 +752,24 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
639
752
|
|| this.pendingTransportFrames >= CURSOR_MAX_PENDING_FRAMES
|
|
640
753
|
) {
|
|
641
754
|
this.stream?.pause();
|
|
755
|
+
this.http1Connection?.pause();
|
|
642
756
|
return;
|
|
643
757
|
}
|
|
644
758
|
if (
|
|
645
759
|
this.transportBufferedBytes <= CURSOR_TRANSPORT_RESUME_BYTES
|
|
646
760
|
&& this.pendingTransportFrames <= CURSOR_PENDING_FRAMES_RESUME
|
|
647
|
-
)
|
|
761
|
+
) {
|
|
762
|
+
this.stream?.resume();
|
|
763
|
+
this.http1Connection?.resume();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private writeConnectFrame(frame: Uint8Array): void {
|
|
768
|
+
if (this.http1Connection) {
|
|
769
|
+
this.http1Connection.write(frame);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this.stream?.write(frame);
|
|
648
773
|
}
|
|
649
774
|
|
|
650
775
|
requestCommitted(): boolean {
|
|
@@ -658,16 +783,103 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
658
783
|
}
|
|
659
784
|
}
|
|
660
785
|
|
|
786
|
+
private clearStreamHealthTimer(): void {
|
|
787
|
+
if (this.streamHealthTimer) {
|
|
788
|
+
clearTimeout(this.streamHealthTimer);
|
|
789
|
+
this.streamHealthTimer = undefined;
|
|
790
|
+
}
|
|
791
|
+
this.streamHealthFail = undefined;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* T04: arm (or re-arm) the inbound stream-health watchdog. `fail` is the turn's
|
|
796
|
+
* failAndClear; the timer owns nothing else. Never armed before the first decoded
|
|
797
|
+
* frame (the first-frame timer covers dial + first response), and disarmed by
|
|
798
|
+
* every settle / expected-close path alongside the other timers.
|
|
799
|
+
*/
|
|
800
|
+
private armStreamHealthTimer(fail: (error: Error) => void): void {
|
|
801
|
+
if (this.streamHealthTimer) clearTimeout(this.streamHealthTimer);
|
|
802
|
+
if (this.expectedClose) return;
|
|
803
|
+
this.streamHealthFail = fail;
|
|
804
|
+
const silenceMs = this.input.streamSilenceFailMs ?? CURSOR_STREAM_SILENCE_FAIL_MS;
|
|
805
|
+
const heartbeatOnlyMs = this.input.streamHeartbeatOnlyFailMs ?? CURSOR_STREAM_HEARTBEAT_ONLY_FAIL_MS;
|
|
806
|
+
const now = Date.now();
|
|
807
|
+
const deadline = Math.min(
|
|
808
|
+
this.lastInboundFrameAt + silenceMs,
|
|
809
|
+
this.lastMeaningfulFrameAt + heartbeatOnlyMs,
|
|
810
|
+
);
|
|
811
|
+
this.streamHealthTimer = setTimeout(() => {
|
|
812
|
+
this.streamHealthTimer = undefined;
|
|
813
|
+
const failFn = this.streamHealthFail;
|
|
814
|
+
if (!failFn || this.expectedClose) return;
|
|
815
|
+
const stalledFor = Date.now() - this.lastInboundFrameAt;
|
|
816
|
+
const meaningfulStalledFor = Date.now() - this.lastMeaningfulFrameAt;
|
|
817
|
+
if (stalledFor < silenceMs && meaningfulStalledFor < heartbeatOnlyMs) {
|
|
818
|
+
// A frame landed between arming and firing — re-arm for the fresh deadline.
|
|
819
|
+
this.armStreamHealthTimer(failFn);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const heartbeatOnly = stalledFor < silenceMs;
|
|
823
|
+
debugProviderDiagnostic("cursor", "stream-health-timeout", {
|
|
824
|
+
stalledMs: stalledFor,
|
|
825
|
+
meaningfulStalledMs: meaningfulStalledFor,
|
|
826
|
+
heartbeatOnly,
|
|
827
|
+
framesReceived: this.framesReceived,
|
|
828
|
+
elapsedMs: Date.now() - this.turnStartedAt,
|
|
829
|
+
});
|
|
830
|
+
const reason = heartbeatOnly
|
|
831
|
+
? `Cursor stream stalled: heartbeat-only traffic for ${Math.round(meaningfulStalledFor / 1000)}s without turn progress`
|
|
832
|
+
: `Cursor stream stalled: no inbound frames for ${Math.round(stalledFor / 1000)}s before turnEnded`;
|
|
833
|
+
failFn(new Error(reason));
|
|
834
|
+
try { this.stream?.close(); } catch { this.stream?.destroy(); }
|
|
835
|
+
this.session?.close();
|
|
836
|
+
this.http1Connection?.close();
|
|
837
|
+
}, Math.max(0, deadline - now));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* T04: record a decoded inbound frame. Liveness-only frames (server heartbeat,
|
|
842
|
+
* conversationCheckpointUpdate) keep the silence clock fresh but not the progress
|
|
843
|
+
* clock — matching senpi's split so a server that only pings still fails at the
|
|
844
|
+
* heartbeat-only threshold.
|
|
845
|
+
*/
|
|
846
|
+
private noteInboundFrame(livenessOnly: boolean): void {
|
|
847
|
+
const now = Date.now();
|
|
848
|
+
this.lastInboundFrameAt = now;
|
|
849
|
+
if (!livenessOnly) this.lastMeaningfulFrameAt = now;
|
|
850
|
+
if (this.streamHealthFail) this.armStreamHealthTimer(this.streamHealthFail);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* A clean Connect END_STREAM owns the turn terminal even when Cursor keeps the
|
|
855
|
+
* HTTP body open or tears it down with an abort/reset immediately afterward.
|
|
856
|
+
* Stop client-side liveness work and classify that later transport close as
|
|
857
|
+
* expected without actively sending an RST_STREAM back to Cursor.
|
|
858
|
+
*/
|
|
859
|
+
private markProtocolComplete(): void {
|
|
860
|
+
this.expectedClose = true;
|
|
861
|
+
this.clearPendingFinalize();
|
|
862
|
+
if (this.heartbeat) {
|
|
863
|
+
clearInterval(this.heartbeat);
|
|
864
|
+
this.heartbeat = undefined;
|
|
865
|
+
}
|
|
866
|
+
this.clearFirstFrameTimer();
|
|
867
|
+
this.clearStreamHealthTimer();
|
|
868
|
+
}
|
|
869
|
+
|
|
661
870
|
private startShellCleanup(): Promise<BackgroundShellTerminationReport> {
|
|
662
|
-
return this.shellCleanup ??= terminateBackgroundShellsForSession(this.
|
|
871
|
+
return this.shellCleanup ??= terminateBackgroundShellsForSession(this.shellOwnerId);
|
|
663
872
|
}
|
|
664
873
|
|
|
665
874
|
async close(): Promise<void> {
|
|
666
875
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
876
|
+
if (this.turnEndedCloseTimer) clearTimeout(this.turnEndedCloseTimer);
|
|
667
877
|
this.clearPendingFinalize();
|
|
668
878
|
this.clearFirstFrameTimer();
|
|
879
|
+
this.clearStreamHealthTimer();
|
|
669
880
|
this.stream?.close();
|
|
670
881
|
this.session?.close();
|
|
882
|
+
this.http1Connection?.close();
|
|
671
883
|
this.releaseBlobRequestScope();
|
|
672
884
|
this.releaseMcpObservation?.();
|
|
673
885
|
this.releaseMcpObservation = undefined;
|
|
@@ -680,12 +892,17 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
680
892
|
this.clearPendingFinalize();
|
|
681
893
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
682
894
|
this.clearFirstFrameTimer();
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
895
|
+
this.clearStreamHealthTimer();
|
|
896
|
+
if (this.http1Connection) {
|
|
897
|
+
this.http1Connection.close();
|
|
898
|
+
} else {
|
|
899
|
+
try {
|
|
900
|
+
this.stream?.close(http2.constants.NGHTTP2_CANCEL);
|
|
901
|
+
} catch {
|
|
902
|
+
this.stream?.destroy();
|
|
903
|
+
}
|
|
904
|
+
this.session?.close();
|
|
687
905
|
}
|
|
688
|
-
this.session?.close();
|
|
689
906
|
this.releaseBlobRequestScope();
|
|
690
907
|
this.releaseMcpObservation?.();
|
|
691
908
|
this.releaseMcpObservation = undefined;
|
|
@@ -693,6 +910,46 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
693
910
|
void this.startShellCleanup().catch(() => { /* close() observes the same cleanup promise */ });
|
|
694
911
|
}
|
|
695
912
|
|
|
913
|
+
/**
|
|
914
|
+
* T03 (#1062): after the server sends `turnEnded`, the application turn is complete.
|
|
915
|
+
* A server that keeps the HTTP/2 stream open past this point cannot hold the turn
|
|
916
|
+
* hostage until a 300s bridge idle timeout. Close our side after a short grace so any
|
|
917
|
+
* trailing frames (late usage, checkpoint) still land before we release the socket.
|
|
918
|
+
*/
|
|
919
|
+
private closeAfterTurnEnded(): void {
|
|
920
|
+
if (this.turnEndedCloseTimer) return;
|
|
921
|
+
// The application turn is over: the T03 grace timer owns the socket from here.
|
|
922
|
+
// The T04 watchdog must disarm NOW, not at the grace close — a watchdog shorter
|
|
923
|
+
// than the grace would otherwise fail a completed turn.
|
|
924
|
+
this.clearStreamHealthTimer();
|
|
925
|
+
this.turnEndedCloseTimer = setTimeout(() => {
|
|
926
|
+
this.turnEndedCloseTimer = undefined;
|
|
927
|
+
// Only expectedClose (client-tool suspend cancel) blocks the close.
|
|
928
|
+
// emittedTerminal is intentionally NOT checked here: finalizeTurnEvents sets it
|
|
929
|
+
// synchronously during turnEnded mapping, ~500ms before this timer fires, so
|
|
930
|
+
// checking it would make the close unreachable on every real path (the exact
|
|
931
|
+
// scenario this PR exists to fix — senpi #1062).
|
|
932
|
+
if (this.expectedClose) return;
|
|
933
|
+
debugProviderDiagnostic("cursor", "turn-ended-close", {
|
|
934
|
+
committed: this.committed,
|
|
935
|
+
framesReceived: this.framesReceived,
|
|
936
|
+
});
|
|
937
|
+
this.expectedClose = true;
|
|
938
|
+
this.clearFirstFrameTimer();
|
|
939
|
+
this.clearStreamHealthTimer();
|
|
940
|
+
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
941
|
+
if (this.http1Connection) {
|
|
942
|
+
this.http1Connection.close();
|
|
943
|
+
} else {
|
|
944
|
+
try {
|
|
945
|
+
this.stream?.close();
|
|
946
|
+
} catch {
|
|
947
|
+
this.stream?.destroy();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}, TURN_ENDED_CLOSE_GRACE_MS);
|
|
951
|
+
}
|
|
952
|
+
|
|
696
953
|
private releaseBlobRequestScope(): void {
|
|
697
954
|
const scope = this.blobRequestScope;
|
|
698
955
|
if (!scope) return;
|
|
@@ -750,33 +1007,52 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
750
1007
|
fail: (error: Error) => void,
|
|
751
1008
|
finish: () => void,
|
|
752
1009
|
): void {
|
|
1010
|
+
if (signal?.aborted) {
|
|
1011
|
+
fail(signal.reason instanceof Error ? signal.reason : new Error("Cursor request was aborted"));
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
753
1014
|
this.turnStartedAt = Date.now();
|
|
754
1015
|
this.framesReceived = 0;
|
|
1016
|
+
this.sawAssistantText = false;
|
|
1017
|
+
this.emittedTerminal = false;
|
|
755
1018
|
this.firstFrameAt = undefined;
|
|
756
1019
|
this.firstFrameLogged = false;
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
"connect
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1020
|
+
const baseUrl = this.input.provider.baseUrl || "https://api2.cursor.sh";
|
|
1021
|
+
const useHttp1 = isPinnedHttp1(this.input.provider.upstreamHttpVersion);
|
|
1022
|
+
const requestId = crypto.randomUUID();
|
|
1023
|
+
const dialHost = cursorHostLabel(baseUrl);
|
|
1024
|
+
debugProviderDiagnostic("cursor", "dial", { host: dialHost, transport: useHttp1 ? "http1.1" : "http2" });
|
|
1025
|
+
|
|
1026
|
+
let session: http2.ClientHttp2Session | undefined;
|
|
1027
|
+
let stream: http2.ClientHttp2Stream | undefined;
|
|
1028
|
+
if (!useHttp1) {
|
|
1029
|
+
session = http2.connect(baseUrl);
|
|
1030
|
+
this.session = session;
|
|
1031
|
+
// The run request is buffered until the HTTP/2 session connects. Failures before `connect`
|
|
1032
|
+
// (DNS, ECONNREFUSED, TLS, connect timeout) mean the server never received the request, so they
|
|
1033
|
+
// are safe to retry. Once connected, bytes flush to the server and the turn must not be replayed.
|
|
1034
|
+
session.on("connect", () => {
|
|
1035
|
+
this.committed = true;
|
|
1036
|
+
debugProviderDiagnostic("cursor", "connected", {
|
|
1037
|
+
transport: "http2",
|
|
1038
|
+
connectMs: Date.now() - this.turnStartedAt,
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
stream = session.request({
|
|
1042
|
+
":method": "POST",
|
|
1043
|
+
":path": CURSOR_RUN_PATH,
|
|
1044
|
+
"content-type": "application/connect+proto",
|
|
1045
|
+
"connect-protocol-version": "1",
|
|
1046
|
+
te: "trailers",
|
|
1047
|
+
authorization: `Bearer ${this.token}`,
|
|
1048
|
+
"x-ghost-mode": "true",
|
|
1049
|
+
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
1050
|
+
"x-cursor-client-type": "cli",
|
|
1051
|
+
"x-request-id": requestId,
|
|
1052
|
+
"x-session-id": this.sessionId,
|
|
1053
|
+
});
|
|
1054
|
+
this.stream = stream;
|
|
1055
|
+
}
|
|
780
1056
|
|
|
781
1057
|
// Single-shot terminal owner for this turn (createTerminalSettler): stream error, session
|
|
782
1058
|
// error, trailers, end, abort, and the first-frame timeout all race into it, and only the
|
|
@@ -784,9 +1060,13 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
784
1060
|
const settler = createTerminalSettler({
|
|
785
1061
|
fail,
|
|
786
1062
|
finish,
|
|
787
|
-
clearTimer: () =>
|
|
1063
|
+
clearTimer: () => {
|
|
1064
|
+
this.clearFirstFrameTimer();
|
|
1065
|
+
this.clearStreamHealthTimer();
|
|
1066
|
+
},
|
|
788
1067
|
});
|
|
789
1068
|
const failAndClear = (error: Error) => {
|
|
1069
|
+
releaseBacklogLease();
|
|
790
1070
|
if (this.expectedClose) {
|
|
791
1071
|
// We already emitted a terminal `done` and cancelled the run (client-tool suspension). The
|
|
792
1072
|
// RST_STREAM CANCEL surfaces here as a stream error/abort; it is expected, not a failure.
|
|
@@ -801,11 +1081,9 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
801
1081
|
}
|
|
802
1082
|
settler.settleFail(error);
|
|
803
1083
|
};
|
|
804
|
-
const session = this.session;
|
|
805
|
-
const stream = this.stream;
|
|
806
1084
|
// Session-level errors (TLS/socket/GOAWAY) do not always propagate to the stream listener;
|
|
807
1085
|
// without this handler they could bypass orderly failure reporting entirely.
|
|
808
|
-
|
|
1086
|
+
const onSessionError = (err: unknown) => {
|
|
809
1087
|
const realErr = err instanceof Error ? err : new Error(String(err));
|
|
810
1088
|
debugProviderDiagnostic("cursor", "session-error", {
|
|
811
1089
|
code: String((realErr as { code?: unknown }).code ?? ""),
|
|
@@ -813,37 +1091,65 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
813
1091
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
814
1092
|
});
|
|
815
1093
|
failAndClear(realErr);
|
|
816
|
-
}
|
|
1094
|
+
};
|
|
817
1095
|
this.firstFrameTimer = setTimeout(() => {
|
|
818
1096
|
this.firstFrameTimer = undefined;
|
|
819
1097
|
debugProviderDiagnostic("cursor", "first-frame-timeout", { timeoutMs: this.input.firstFrameTimeoutMs ?? CURSOR_FIRST_FRAME_TIMEOUT_MS });
|
|
820
|
-
try { stream
|
|
821
|
-
try { session
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1098
|
+
try { stream?.close(); } catch { /* already closing */ }
|
|
1099
|
+
try { session?.close(); } catch { /* already closing */ }
|
|
1100
|
+
this.http1Connection?.close();
|
|
1101
|
+
if (stream && session) {
|
|
1102
|
+
// close() waits for in-flight frames; a dead socket can ignore it — force-destroy shortly
|
|
1103
|
+
// after so a stalled TLS session cannot linger past the timeout.
|
|
1104
|
+
armTimeoutDestroyFallback(stream, session, this.input.timeoutDestroyGraceMs ?? CURSOR_TIMEOUT_DESTROY_GRACE_MS);
|
|
1105
|
+
}
|
|
1106
|
+
releaseBacklogLease();
|
|
825
1107
|
settler.settleFail(new Error("Cursor transport timed out before first response"));
|
|
826
1108
|
}, this.input.firstFrameTimeoutMs ?? CURSOR_FIRST_FRAME_TIMEOUT_MS);
|
|
827
1109
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1110
|
+
// Raw Connect backlog with a parse cursor: appends copy only the incoming
|
|
1111
|
+
// chunk (amortized capacity growth), the consumed prefix is reclaimed
|
|
1112
|
+
// lazily, and the RAW used length (headers included) is what the 32 MiB
|
|
1113
|
+
// transport cap bounds — payload-only accounting let tiny-frame/header
|
|
1114
|
+
// floods slip through.
|
|
1115
|
+
let backlog = new Uint8Array();
|
|
1116
|
+
let backlogStart = 0;
|
|
1117
|
+
let backlogEnd = 0;
|
|
1118
|
+
const BACKLOG_COMPACT_MIN_SAVINGS = 64 * 1024;
|
|
1119
|
+
const appendBacklog = (chunk: Uint8Array): void => {
|
|
1120
|
+
const used = backlogEnd - backlogStart;
|
|
1121
|
+
let start = backlogStart;
|
|
1122
|
+
let end = backlogEnd;
|
|
1123
|
+
// Reclaim the consumed prefix when it is large or needed for capacity.
|
|
1124
|
+
if (start > 0 && (start >= BACKLOG_COMPACT_MIN_SAVINGS || end + chunk.byteLength > backlog.byteLength)) {
|
|
1125
|
+
backlog = backlog.slice(start, end);
|
|
1126
|
+
start = 0;
|
|
1127
|
+
end = used;
|
|
833
1128
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
},
|
|
844
|
-
};
|
|
1129
|
+
if (end + chunk.byteLength > backlog.byteLength) {
|
|
1130
|
+
const capacity = Math.max(8192, backlog.byteLength * 2, end + chunk.byteLength);
|
|
1131
|
+
const next = new Uint8Array(Math.min(CURSOR_TRANSPORT_MAX_BUFFERED_BYTES, capacity));
|
|
1132
|
+
next.set(backlog.subarray(start, end), 0);
|
|
1133
|
+
backlog = next;
|
|
1134
|
+
}
|
|
1135
|
+
backlog.set(chunk, end);
|
|
1136
|
+
backlogStart = start;
|
|
1137
|
+
backlogEnd = end + chunk.byteLength;
|
|
845
1138
|
};
|
|
846
|
-
|
|
1139
|
+
let frameWork: Promise<void> = Promise.resolve();
|
|
1140
|
+
// Idempotent terminal owner for the backlog lease: every settle/close path
|
|
1141
|
+
// must leave the raw charge at zero instead of relying on budget disposal.
|
|
1142
|
+
let backlogLeaseReleased = false;
|
|
1143
|
+
const releaseBacklogLease = () => {
|
|
1144
|
+
if (backlogLeaseReleased) return;
|
|
1145
|
+
backlogLeaseReleased = true;
|
|
1146
|
+
const leftover = backlogEnd - backlogStart;
|
|
1147
|
+
if (leftover > 0) this.releaseTransportBytes(leftover);
|
|
1148
|
+
backlog = new Uint8Array();
|
|
1149
|
+
backlogStart = 0;
|
|
1150
|
+
backlogEnd = 0;
|
|
1151
|
+
};
|
|
1152
|
+
const handleFrame = async (frame: ReturnType<typeof consumeConnectFrames>["frames"][number]) => {
|
|
847
1153
|
this.framesReceived++;
|
|
848
1154
|
if ((frame.flags & CONNECT_FLAG_END_STREAM) === CONNECT_FLAG_END_STREAM) {
|
|
849
1155
|
const endError = parseConnectEndStreamError(frame.payload);
|
|
@@ -854,29 +1160,76 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
854
1160
|
framesReceived: this.framesReceived,
|
|
855
1161
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
856
1162
|
} : { framesReceived: this.framesReceived, elapsedMs: Date.now() - this.turnStartedAt });
|
|
857
|
-
if (endError)
|
|
1163
|
+
if (endError) {
|
|
1164
|
+
failAndClear(endError);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
// Connect's clean END_STREAM envelope is the protocol terminal. Cursor's RunSSE body can
|
|
1168
|
+
// remain open after this frame (or close through an AbortError), so waiting for HTTP EOF
|
|
1169
|
+
// strands an otherwise completed turn until the outer bridge stall watchdog fires.
|
|
1170
|
+
//
|
|
1171
|
+
// Earlier frames in this serialized frameWork chain have already run. Preserve their real
|
|
1172
|
+
// turnEnded terminal when present; otherwise finalize the clean protocol end once so open
|
|
1173
|
+
// tool calls still fail closed, a text-only turn receives its normal done event, and a
|
|
1174
|
+
// drained client-tool turn does not lose the pending terminal when protocol cleanup clears
|
|
1175
|
+
// its grace timer.
|
|
1176
|
+
const hasPendingClientToolFinalization = this.pendingFinalize !== undefined;
|
|
1177
|
+
if (
|
|
1178
|
+
!this.expectedClose
|
|
1179
|
+
&& !state.terminated
|
|
1180
|
+
&& !this.emittedTerminal
|
|
1181
|
+
&& (
|
|
1182
|
+
state.openToolCalls.size > 0
|
|
1183
|
+
|| this.sawAssistantText
|
|
1184
|
+
|| hasPendingClientToolFinalization
|
|
1185
|
+
)
|
|
1186
|
+
) {
|
|
1187
|
+
const terminal = hasPendingClientToolFinalization && state.openToolCalls.size === 0
|
|
1188
|
+
? finalizeAfterDrain(state)
|
|
1189
|
+
: finalizeTurnEvents(state);
|
|
1190
|
+
for (const event of terminal) push(event);
|
|
1191
|
+
}
|
|
1192
|
+
this.markProtocolComplete();
|
|
1193
|
+
releaseBacklogLease();
|
|
1194
|
+
settler.settleFinish();
|
|
858
1195
|
return;
|
|
859
1196
|
}
|
|
860
|
-
|
|
1197
|
+
const decoded = fromBinary(AgentServerMessageSchema, frame.payload);
|
|
1198
|
+
// T04: every decoded frame refreshes the silence clock; only non-liveness frames
|
|
1199
|
+
// refresh the progress clock. First decoded frame arms the watchdog (the first-frame
|
|
1200
|
+
// timer owned everything before this point).
|
|
1201
|
+
const decodedUpdate = decoded.message.case === "interactionUpdate" ? decoded.message.value.message?.case : undefined;
|
|
1202
|
+
const livenessOnly = decodedUpdate === "heartbeat" || decoded.message.case === "conversationCheckpointUpdate";
|
|
1203
|
+
if (!this.streamHealthFail) {
|
|
1204
|
+
const now = Date.now();
|
|
1205
|
+
this.lastInboundFrameAt = now;
|
|
1206
|
+
this.lastMeaningfulFrameAt = now;
|
|
1207
|
+
this.streamHealthFail = failAndClear;
|
|
1208
|
+
}
|
|
1209
|
+
this.noteInboundFrame(livenessOnly);
|
|
1210
|
+
await this.handleServerMessage(decoded, state, push);
|
|
861
1211
|
};
|
|
862
1212
|
const drainPendingFrames = () => {
|
|
863
1213
|
const availableSlots = CURSOR_MAX_PENDING_FRAMES - this.pendingTransportFrames;
|
|
864
|
-
|
|
1214
|
+
const used = backlogEnd - backlogStart;
|
|
1215
|
+
if (availableSlots <= 0 || used === 0) {
|
|
865
1216
|
this.updateTransportFlowControl();
|
|
866
1217
|
return;
|
|
867
1218
|
}
|
|
868
|
-
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
1219
|
+
// Cursor decode, zero-copy: frame payloads are views into the backlog, so
|
|
1220
|
+
// the charge TRANSFERS — only consumed header bytes leave the counter
|
|
1221
|
+
// (payload bytes stay charged and are released when each frame's work
|
|
1222
|
+
// finishes). An exact 16 MiB payload therefore peaks at 16 MiB + 5, not
|
|
1223
|
+
// at double its size.
|
|
1224
|
+
const decoded = consumeConnectFrames(
|
|
1225
|
+
backlog.subarray(backlogStart, backlogEnd),
|
|
874
1226
|
CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES,
|
|
875
1227
|
availableSlots,
|
|
876
|
-
reservePayloadCopy,
|
|
877
1228
|
);
|
|
878
|
-
|
|
879
|
-
|
|
1229
|
+
if (decoded.frames.length > 0) {
|
|
1230
|
+
backlogStart += decoded.consumedBytes;
|
|
1231
|
+
this.releaseTransportBytes(decoded.consumedBytes - decoded.frames.reduce((n, frame) => n + frame.payload.byteLength, 0));
|
|
1232
|
+
}
|
|
880
1233
|
for (const frame of decoded.frames) {
|
|
881
1234
|
this.pendingTransportFrames += 1;
|
|
882
1235
|
this.updateTransportFlowControl();
|
|
@@ -891,43 +1244,42 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
891
1244
|
});
|
|
892
1245
|
}
|
|
893
1246
|
};
|
|
894
|
-
|
|
1247
|
+
const onData = (chunk: string | Uint8Array) => {
|
|
895
1248
|
this.clearFirstFrameTimer();
|
|
1249
|
+
// Once the turn has settled, late network bytes must never be charged —
|
|
1250
|
+
// the backlog lease is already released and nobody would own these.
|
|
1251
|
+
if (settler.settled()) return;
|
|
896
1252
|
if (!this.firstFrameLogged) {
|
|
897
1253
|
this.firstFrameLogged = true;
|
|
898
1254
|
this.firstFrameAt = Date.now();
|
|
899
1255
|
debugProviderDiagnostic("cursor", "first-frame", { latencyMs: this.firstFrameAt - this.turnStartedAt });
|
|
900
1256
|
}
|
|
901
1257
|
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
902
|
-
let
|
|
903
|
-
let
|
|
904
|
-
let replacement: ReturnType<typeof reservePayloadCopy> | undefined;
|
|
1258
|
+
let charged = false;
|
|
1259
|
+
let appended = false;
|
|
905
1260
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
const nextPending = concatBytes(pending, bytes);
|
|
913
|
-
pending = nextPending;
|
|
914
|
-
replacement.commitRetained();
|
|
915
|
-
replacement = undefined;
|
|
916
|
-
this.releaseTransportBytes(previousPendingPayloadBytes + incomingPayloadBytes);
|
|
917
|
-
incomingCharged = false;
|
|
1261
|
+
// RAW chunk bytes (headers included) join the backlog charge; consumed
|
|
1262
|
+
// bytes leave it at drain. No whole-backlog replacement copy anymore.
|
|
1263
|
+
this.reserveTransportBytes(bytes.byteLength);
|
|
1264
|
+
charged = true;
|
|
1265
|
+
appendBacklog(bytes);
|
|
1266
|
+
appended = true;
|
|
918
1267
|
drainPendingFrames();
|
|
919
1268
|
} catch (err) {
|
|
920
|
-
|
|
921
|
-
|
|
1269
|
+
// Release ONLY when the reservation succeeded but the append never
|
|
1270
|
+
// happened. A failed reservation charged nothing — releasing here
|
|
1271
|
+
// would debit unrelated existing ownership; an appended chunk is owned
|
|
1272
|
+
// by the terminal backlog cleanup.
|
|
1273
|
+
if (charged && !appended) this.releaseTransportBytes(bytes.byteLength);
|
|
922
1274
|
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
923
1275
|
}
|
|
924
|
-
}
|
|
925
|
-
|
|
1276
|
+
};
|
|
1277
|
+
const onTrailers = (trailers: http2.IncomingHttpHeaders) => {
|
|
926
1278
|
const status = trailers["grpc-status"];
|
|
927
1279
|
if (status !== undefined) debugProviderDiagnostic("cursor", "trailers", { grpcStatus: String(status) });
|
|
928
1280
|
if (status && status !== "0") failAndClear(new Error(`Cursor gRPC error ${status}`));
|
|
929
|
-
}
|
|
930
|
-
|
|
1281
|
+
};
|
|
1282
|
+
const onStreamError = (err: unknown) => {
|
|
931
1283
|
const realErr = err instanceof Error ? err : new Error(String(err));
|
|
932
1284
|
if (this.expectedClose) {
|
|
933
1285
|
failAndClear(realErr);
|
|
@@ -945,8 +1297,8 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
945
1297
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
946
1298
|
});
|
|
947
1299
|
failAndClear(realErr);
|
|
948
|
-
}
|
|
949
|
-
|
|
1300
|
+
};
|
|
1301
|
+
const onStreamEnd = () => {
|
|
950
1302
|
this.clearFirstFrameTimer();
|
|
951
1303
|
debugProviderDiagnostic("cursor", "stream-end", {
|
|
952
1304
|
committed: this.committed,
|
|
@@ -954,39 +1306,134 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
954
1306
|
expectedClose: this.expectedClose,
|
|
955
1307
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
956
1308
|
});
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1309
|
+
// Settle only after queued frame work drains to quiescence (the chain can
|
|
1310
|
+
// extend itself while draining), then classify the terminal state:
|
|
1311
|
+
// a trailing incomplete frame is a typed failure, and a zero-frame,
|
|
1312
|
+
// zero-byte end without an expected close stays the existing unexpected
|
|
1313
|
+
// EOF — both beat the old silent success.
|
|
1314
|
+
void (async () => {
|
|
1315
|
+
let previous: Promise<void>;
|
|
1316
|
+
do {
|
|
1317
|
+
previous = frameWork;
|
|
1318
|
+
await previous;
|
|
1319
|
+
} while (previous !== frameWork);
|
|
1320
|
+
})().then(() => {
|
|
1321
|
+
if (settler.settled()) return;
|
|
1322
|
+
const leftover = backlogEnd - backlogStart;
|
|
1323
|
+
if (leftover > 0 && !this.expectedClose) {
|
|
1324
|
+
releaseBacklogLease();
|
|
1325
|
+
settler.settleFail(new ConnectFrameError(
|
|
1326
|
+
"frame_incomplete",
|
|
1327
|
+
`Cursor Connect stream ended with ${leftover} unconsumed bytes (incomplete frame)`,
|
|
1328
|
+
));
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (this.framesReceived === 0 && !this.expectedClose) {
|
|
1332
|
+
releaseBacklogLease();
|
|
1333
|
+
settler.settleFail(new Error("Cursor stream ended before any response frame (unexpected EOF)"));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
// `emittedTerminal` joins dev's two conditions so EOF finalization cannot append a
|
|
1337
|
+
// second terminal after a mapper error already failed the turn (integration 010).
|
|
1338
|
+
if (state.terminated || this.expectedClose || this.emittedTerminal) {
|
|
1339
|
+
releaseBacklogLease();
|
|
1340
|
+
settler.settleFinish();
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
// Open tools fail-closed as a truncation *event* (finalizeTurnEvents), not a thrown
|
|
1344
|
+
// transport error. settleFail here would hide that typed message as adapter_eof.
|
|
1345
|
+
if (state.openToolCalls.size > 0) {
|
|
1346
|
+
for (const event of finalizeTurnEvents(state)) push(event);
|
|
1347
|
+
releaseBacklogLease();
|
|
1348
|
+
settler.settleFinish();
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (this.framesReceived > 0 && this.sawAssistantText) {
|
|
1352
|
+
for (const event of finalizeTurnEvents(state)) push(event);
|
|
1353
|
+
releaseBacklogLease();
|
|
1354
|
+
settler.settleFinish();
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
releaseBacklogLease();
|
|
1358
|
+
settler.settleFinish();
|
|
1359
|
+
}).catch((err) => {
|
|
1360
|
+
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
if (useHttp1) {
|
|
1365
|
+
const providerFetch = this.input.fetch
|
|
1366
|
+
?? (this.input.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch;
|
|
1367
|
+
this.http1Connection = new CursorHttp1BidiConnection({
|
|
1368
|
+
baseUrl,
|
|
1369
|
+
token: this.token,
|
|
1370
|
+
clientVersion: CURSOR_CLIENT_VERSION,
|
|
1371
|
+
sessionId: this.sessionId,
|
|
1372
|
+
requestId,
|
|
1373
|
+
translatorBudget: this.translatorBudget,
|
|
1374
|
+
callbacks: {
|
|
1375
|
+
onCommitted: () => {
|
|
1376
|
+
this.committed = true;
|
|
1377
|
+
debugProviderDiagnostic("cursor", "connected", {
|
|
1378
|
+
transport: "http1.1",
|
|
1379
|
+
connectMs: Date.now() - this.turnStartedAt,
|
|
1380
|
+
});
|
|
1381
|
+
},
|
|
1382
|
+
onData,
|
|
1383
|
+
onEnd: onStreamEnd,
|
|
1384
|
+
onError: onStreamError,
|
|
1385
|
+
},
|
|
1386
|
+
...(providerFetch ? { fetch: providerFetch } : {}),
|
|
1387
|
+
});
|
|
1388
|
+
this.http1Connection.start();
|
|
1389
|
+
} else {
|
|
1390
|
+
session!.on("error", onSessionError);
|
|
1391
|
+
stream!.on("data", onData);
|
|
1392
|
+
stream!.on("trailers", onTrailers);
|
|
1393
|
+
stream!.on("error", onStreamError);
|
|
1394
|
+
stream!.on("end", onStreamEnd);
|
|
1395
|
+
}
|
|
967
1396
|
|
|
968
1397
|
signal?.addEventListener("abort", () => {
|
|
969
1398
|
this.close();
|
|
970
1399
|
failAndClear(new Error("Cursor request was aborted"));
|
|
971
1400
|
}, { once: true });
|
|
1401
|
+
// Close the race between the preflight above and listener installation. No request payload is
|
|
1402
|
+
// written until after this check.
|
|
1403
|
+
if (signal?.aborted) {
|
|
1404
|
+
this.close();
|
|
1405
|
+
failAndClear(signal.reason instanceof Error ? signal.reason : new Error("Cursor request was aborted"));
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
972
1408
|
|
|
973
|
-
this.
|
|
1409
|
+
this.writeConnectFrame(encodeConnectFrame(encodedRequest));
|
|
974
1410
|
this.heartbeat = setInterval(() => {
|
|
975
|
-
this.
|
|
1411
|
+
this.writeConnectFrame(encodeClientMessage({
|
|
976
1412
|
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
977
1413
|
}));
|
|
978
1414
|
}, HEARTBEAT_MS);
|
|
979
1415
|
}
|
|
980
1416
|
|
|
1417
|
+
capturedConversationCheckpoint(): Uint8Array | undefined {
|
|
1418
|
+
return this.capturedCheckpointBytes;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
981
1421
|
private async handleServerMessage(
|
|
982
1422
|
message: AgentServerMessage,
|
|
983
1423
|
state: ReturnType<typeof createCursorProtobufEventState>,
|
|
984
1424
|
push: (message: CursorServerMessage) => void,
|
|
985
1425
|
): Promise<void> {
|
|
986
|
-
if (!this.stream) return;
|
|
1426
|
+
if (!this.stream && !this.http1Connection) return;
|
|
987
1427
|
debugProviderDiagnostic("cursor", "frame", describeCursorServerFrame(message));
|
|
1428
|
+
if (message.message.case === "conversationCheckpointUpdate") {
|
|
1429
|
+
try {
|
|
1430
|
+
this.capturedCheckpointBytes = toBinary(ConversationStateStructureSchema, message.message.value);
|
|
1431
|
+
} catch {
|
|
1432
|
+
this.capturedCheckpointBytes = undefined;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
988
1435
|
if (message.message.case === "kvServerMessage") {
|
|
989
|
-
this.
|
|
1436
|
+
this.writeConnectFrame(encodeConnectFrame(handleCursorNativeKv(message.message.value, this.blobRequestScope)));
|
|
990
1437
|
return;
|
|
991
1438
|
}
|
|
992
1439
|
if (message.message.case === "execServerMessage") {
|
|
@@ -1007,7 +1454,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1007
1454
|
// run the same local action twice.
|
|
1008
1455
|
push({ type: "local_side_effect" });
|
|
1009
1456
|
const replies = await handleCursorNativeExec(message.message.value, this.execContext);
|
|
1010
|
-
for (const reply of replies) this.
|
|
1457
|
+
for (const reply of replies) this.writeConnectFrame(encodeConnectFrame(reply));
|
|
1011
1458
|
return;
|
|
1012
1459
|
}
|
|
1013
1460
|
if (message.message.case === "interactionQuery") {
|
|
@@ -1017,29 +1464,58 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1017
1464
|
const query = message.message.value;
|
|
1018
1465
|
const plan = planInteractionQueryReply(query);
|
|
1019
1466
|
debugProviderDiagnostic("cursor", "interaction-query", { id: query.id, queryCase: query.query.case ?? "unknown", reply: plan.replyCase });
|
|
1020
|
-
this.
|
|
1467
|
+
this.writeConnectFrame(encodeClientMessage({ message: { case: "interactionResponse", value: plan.response } }));
|
|
1021
1468
|
if (!state.terminated) {
|
|
1022
|
-
if (plan.planText)
|
|
1469
|
+
if (plan.planText) {
|
|
1470
|
+
this.sawAssistantText = true;
|
|
1471
|
+
push({ type: "text", text: plan.planText });
|
|
1472
|
+
}
|
|
1023
1473
|
push({ type: "heartbeat" });
|
|
1024
1474
|
}
|
|
1025
1475
|
return;
|
|
1026
1476
|
}
|
|
1477
|
+
// A completion may carry only callId. Capture its ownership before mapping removes the open
|
|
1478
|
+
// call, because the embedded-tool classifier cannot identify that valid compact frame.
|
|
1479
|
+
const update = message.message.case === "interactionUpdate" ? message.message.value.message : undefined;
|
|
1480
|
+
if (update?.case === "turnEnded") {
|
|
1481
|
+
// T03: the application turn is complete. Close our side of HTTP/2 after a short
|
|
1482
|
+
// grace so a held-open server response cannot pin the turn to the bridge's idle
|
|
1483
|
+
// timeout (senpi #1062). finalizeTurnEvents already emitted done via the mapper.
|
|
1484
|
+
this.closeAfterTurnEnded();
|
|
1485
|
+
}
|
|
1486
|
+
const completesOpenClientTool = update?.case === "toolCallCompleted"
|
|
1487
|
+
&& state.openToolCalls.has(update.value.callId);
|
|
1488
|
+
const awaitedNativeArgsBeforeMapping = update?.case === "toolCallCompleted"
|
|
1489
|
+
&& state.openToolCalls.get(update.value.callId)?.awaitingNativeArgs === true;
|
|
1027
1490
|
const mapped = mapCursorProtobufServerMessage(message, state);
|
|
1491
|
+
if (mapped.some(event => event.type === "text")) this.sawAssistantText = true;
|
|
1492
|
+
const beganAwaitingNativeClientToolArgs = update?.case === "toolCallCompleted"
|
|
1493
|
+
&& !awaitedNativeArgsBeforeMapping
|
|
1494
|
+
&& state.openToolCalls.get(update.value.callId)?.awaitingNativeArgs === true;
|
|
1028
1495
|
if (mapped.length > 0) {
|
|
1029
1496
|
// A client tool call announced/committed via interactionUpdate (toolCallStarted/partialToolCall/
|
|
1030
1497
|
// toolCallCompleted) changes the call set, so revoke any finalize armed by an earlier drain.
|
|
1031
|
-
|
|
1498
|
+
// A completion can also commit and drain a late call without a following mcpArgs frame; in
|
|
1499
|
+
// that case re-arm finalization here so the Responses bridge does not wait forever.
|
|
1500
|
+
const clientToolFrame = completesOpenClientTool || isClientToolFrame(message);
|
|
1501
|
+
if (clientToolFrame) this.noteClientToolActivity();
|
|
1032
1502
|
for (const event of mapped) push(event);
|
|
1503
|
+
if (
|
|
1504
|
+
clientToolFrame
|
|
1505
|
+
&& state.openToolCalls.size === 0
|
|
1506
|
+
&& mapped.some(event => event.type === "tool_call_end")
|
|
1507
|
+
) this.scheduleClientToolFinalize(state, push);
|
|
1033
1508
|
return;
|
|
1034
1509
|
}
|
|
1035
1510
|
// The frame produced no outward Responses event (e.g. toolCallStarted / partialToolCall args
|
|
1036
|
-
// buffering, toolCallDelta, tokenDelta, or a checkpoint
|
|
1037
|
-
// deferred to completion for atomic, parallel-safe
|
|
1038
|
-
// several tool calls can otherwise exceed the
|
|
1511
|
+
// buffering, a completion waiting for native args, toolCallDelta, tokenDelta, or a checkpoint
|
|
1512
|
+
// update). Tool-call protocol events are deferred to completion for atomic, parallel-safe
|
|
1513
|
+
// emission, so a turn that silently assembles several tool calls can otherwise exceed the
|
|
1514
|
+
// bridge's stall watchdog (upstream_stall_timeout).
|
|
1039
1515
|
// Emit a liveness heartbeat for these progress frames so the watchdog sees the upstream is alive.
|
|
1040
1516
|
// Never after a terminal (done/truncation): a stray post-terminal frame must stay fully inert.
|
|
1041
|
-
if (!state.terminated && isCursorProgressFrame(message)) {
|
|
1042
|
-
if (isClientToolFrame(message)) this.noteClientToolActivity();
|
|
1517
|
+
if (!state.terminated && (isCursorProgressFrame(message) || beganAwaitingNativeClientToolArgs)) {
|
|
1518
|
+
if (isClientToolFrame(message) || beganAwaitingNativeClientToolArgs) this.noteClientToolActivity();
|
|
1043
1519
|
push({ type: "heartbeat" });
|
|
1044
1520
|
}
|
|
1045
1521
|
}
|
|
@@ -1140,47 +1616,6 @@ export function isClientToolFrame(message: AgentServerMessage): boolean {
|
|
|
1140
1616
|
}
|
|
1141
1617
|
}
|
|
1142
1618
|
|
|
1143
|
-
function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
1144
|
-
const out = new Uint8Array(a.length + b.length);
|
|
1145
|
-
out.set(a);
|
|
1146
|
-
out.set(b, a.length);
|
|
1147
|
-
return out;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function connectBufferedPayloadBytes(input: Uint8Array): number {
|
|
1151
|
-
let offset = 0;
|
|
1152
|
-
let payloadBytes = 0;
|
|
1153
|
-
while (input.byteLength - offset >= 5) {
|
|
1154
|
-
const length = new DataView(input.buffer, input.byteOffset + offset, input.byteLength - offset).getUint32(1, false);
|
|
1155
|
-
const available = Math.min(length, input.byteLength - offset - 5);
|
|
1156
|
-
payloadBytes += available;
|
|
1157
|
-
if (available < length) break;
|
|
1158
|
-
offset += 5 + length;
|
|
1159
|
-
}
|
|
1160
|
-
return payloadBytes;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
/** Payload-byte count for a virtual concatenation, without allocating the concatenated buffer. */
|
|
1164
|
-
function connectBufferedPayloadBytesAcross(first: Uint8Array, second: Uint8Array): number {
|
|
1165
|
-
const totalBytes = first.byteLength + second.byteLength;
|
|
1166
|
-
const byteAt = (index: number): number => index < first.byteLength
|
|
1167
|
-
? first[index]!
|
|
1168
|
-
: second[index - first.byteLength]!;
|
|
1169
|
-
let offset = 0;
|
|
1170
|
-
let payloadBytes = 0;
|
|
1171
|
-
while (totalBytes - offset >= 5) {
|
|
1172
|
-
const length = (byteAt(offset + 1) * 0x1000000)
|
|
1173
|
-
+ (byteAt(offset + 2) << 16)
|
|
1174
|
-
+ (byteAt(offset + 3) << 8)
|
|
1175
|
-
+ byteAt(offset + 4);
|
|
1176
|
-
const available = Math.min(length, totalBytes - offset - 5);
|
|
1177
|
-
payloadBytes += available;
|
|
1178
|
-
if (available < length) break;
|
|
1179
|
-
offset += 5 + length;
|
|
1180
|
-
}
|
|
1181
|
-
return payloadBytes;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
1619
|
/** Host-only label for Cursor transport diagnostics — never leaks path/query/credentials. */
|
|
1185
1620
|
function cursorHostLabel(baseUrl: string): string {
|
|
1186
1621
|
try {
|
|
@@ -1212,3 +1647,7 @@ function cursorConnectErrorCode(payload: Uint8Array): string | undefined {
|
|
|
1212
1647
|
export function createLiveCursorTransport(input: CursorTransportFactoryInput): CursorTransport {
|
|
1213
1648
|
return new LiveCursorTransport(input);
|
|
1214
1649
|
}
|
|
1650
|
+
|
|
1651
|
+
export function capturedCursorCheckpointBytes(transport: CursorTransport): Uint8Array | undefined {
|
|
1652
|
+
return transport.capturedConversationCheckpoint?.();
|
|
1653
|
+
}
|