@iislee/opencodex 2.11.0 → 2.35.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 +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -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 +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -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 +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- 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 +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- 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 +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- 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 +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- 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 +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- 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 +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- 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 +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -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 +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -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/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -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 +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +360 -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 +622 -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 +675 -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 +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -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 +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -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 +157 -33
- package/src/images/plan.ts +24 -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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- 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 +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- 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/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- 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/service-secrets.ts +15 -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 +253 -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 +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -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/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- 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/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -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 +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -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 +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -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 +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -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 +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- 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 +1021 -190
- package/src/server/lifecycle.ts +336 -14
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -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 +289 -33
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -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 +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -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 +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- 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 +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- 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 +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +27 -16
- 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 +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- 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;
|
|
@@ -391,6 +415,20 @@ export function finalizeAfterDrain(state: ReturnType<typeof createCursorProtobuf
|
|
|
391
415
|
export function clientToolFinalizeGraceMsForRequest(request: CursorRunRequest, baseGraceMs = CLIENT_TOOL_FINALIZE_GRACE_MS): number {
|
|
392
416
|
if (request.rawMessages?.at(-1)?.role === "toolResult") return baseGraceMs;
|
|
393
417
|
const text = activePromptText(request);
|
|
418
|
+
// Parallel-tool requests with several advertised tools get the expanded window regardless of
|
|
419
|
+
// prompt shape: external models (grok) assemble sibling calls serially over multiple frames,
|
|
420
|
+
// and the 50ms drain grace ended the turn after 1-2 of them (devlog 260826_cursor_responses_gap,
|
|
421
|
+
// live 10-parallel probe: calls=2 then calls=1).
|
|
422
|
+
if (request.parallelToolCalls === true && (request.tools?.length ?? 0) > 1) {
|
|
423
|
+
const advertised = request.tools?.length ?? 0;
|
|
424
|
+
return Math.max(
|
|
425
|
+
baseGraceMs,
|
|
426
|
+
Math.min(
|
|
427
|
+
GENERIC_TOOL_COUNT_MAX_FINALIZE_GRACE_MS,
|
|
428
|
+
Math.max(GENERIC_TOOL_COUNT_MIN_FINALIZE_GRACE_MS, advertised * GENERIC_TOOL_COUNT_PER_TOOL_GRACE_MS),
|
|
429
|
+
),
|
|
430
|
+
);
|
|
431
|
+
}
|
|
394
432
|
if (!cursorRequestHasShellAlias(request.tools) || !isGenericToolUseCountDemoPrompt(text)) return baseGraceMs;
|
|
395
433
|
const requestedCount = requestedCursorToolUseCount(text);
|
|
396
434
|
const expandedGraceMs = requestedCount
|
|
@@ -405,10 +443,29 @@ export function clientToolFinalizeGraceMsForRequest(request: CursorRunRequest, b
|
|
|
405
443
|
class LiveCursorTransport implements CursorTransport {
|
|
406
444
|
private session?: http2.ClientHttp2Session;
|
|
407
445
|
private stream?: http2.ClientHttp2Stream;
|
|
446
|
+
private http1Connection?: CursorHttp1BidiConnection;
|
|
408
447
|
private heartbeat?: ReturnType<typeof setInterval>;
|
|
409
448
|
private firstFrameTimer?: ReturnType<typeof setTimeout>;
|
|
449
|
+
private turnEndedCloseTimer?: ReturnType<typeof setTimeout>;
|
|
450
|
+
/**
|
|
451
|
+
* T04 inbound stream-health watchdog. Armed after the request is on the wire, reset by
|
|
452
|
+
* every DECODED frame (raw chunks deliberately do not count — TLS keepalive noise must not
|
|
453
|
+
* defeat it), disarmed by any settle/expected-close path. One timer covers both thresholds:
|
|
454
|
+
* it always fires at min(lastInbound + silence, lastMeaningful + heartbeatOnly) and re-arms
|
|
455
|
+
* when neither deadline has actually elapsed.
|
|
456
|
+
*/
|
|
457
|
+
private streamHealthTimer?: ReturnType<typeof setTimeout>;
|
|
458
|
+
private lastInboundFrameAt = 0;
|
|
459
|
+
private lastMeaningfulFrameAt = 0;
|
|
460
|
+
private streamHealthFail?: (error: Error) => void;
|
|
410
461
|
private committed = false;
|
|
411
462
|
private expectedClose = false;
|
|
463
|
+
/**
|
|
464
|
+
* True once a terminal (`done` or `error`) has been admitted to the outbound queue. Read only
|
|
465
|
+
* by the EOF branch below: after a mapper error the bridge has already failed the turn, so
|
|
466
|
+
* failing again on EOF would add a duplicate adapter error for no benefit.
|
|
467
|
+
*/
|
|
468
|
+
private emittedTerminal = false;
|
|
412
469
|
private pendingFinalize?: ReturnType<typeof setTimeout>;
|
|
413
470
|
private readonly clientToolFinalizeGraceMs: number;
|
|
414
471
|
private activeClientToolFinalizeGraceMs: number;
|
|
@@ -427,12 +484,17 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
427
484
|
// close; safe to read after a stream failure because open() owns the only writer before run().
|
|
428
485
|
private turnStartedAt = 0;
|
|
429
486
|
private framesReceived = 0;
|
|
487
|
+
private sawAssistantText = false;
|
|
430
488
|
private firstFrameAt?: number;
|
|
431
|
-
|
|
489
|
+
private firstFrameLogged = false;
|
|
432
490
|
/** Stable session identifier sent as x-session-id; mirrors IDE session semantics. */
|
|
433
|
-
private readonly sessionId
|
|
491
|
+
private readonly sessionId: string;
|
|
492
|
+
/** Per-transport owner for native-exec / background shells. Must not share conversationId. */
|
|
493
|
+
private readonly shellOwnerId = crypto.randomUUID();
|
|
494
|
+
private capturedCheckpointBytes?: Uint8Array;
|
|
434
495
|
|
|
435
496
|
constructor(private readonly input: CursorTransportFactoryInput) {
|
|
497
|
+
this.sessionId = input.sessionId?.trim() || crypto.randomUUID();
|
|
436
498
|
this.translatorBudget = input.translatorBudget;
|
|
437
499
|
this.token = resolveCursorToken(input.provider, input.headers);
|
|
438
500
|
// Grace window before a drained client-tool turn is finalized. Small enough not to look like a
|
|
@@ -444,7 +506,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
444
506
|
this.desktopDeps = desktopDepsFromConfig(input.provider.desktopExecutor);
|
|
445
507
|
this.execContext = {
|
|
446
508
|
...this.desktopDeps,
|
|
447
|
-
sessionId: this.
|
|
509
|
+
sessionId: this.shellOwnerId,
|
|
448
510
|
unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(input.provider, input.requestDeclaresFullAccess === true),
|
|
449
511
|
};
|
|
450
512
|
const servers = resolveMcpServers(input.provider);
|
|
@@ -479,7 +541,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
479
541
|
...this.desktopDeps,
|
|
480
542
|
...mcpDepsFromManager(this.mcpManager!),
|
|
481
543
|
mcpToolDefs,
|
|
482
|
-
sessionId: this.
|
|
544
|
+
sessionId: this.shellOwnerId,
|
|
483
545
|
unsafeAllowNativeLocalExec: effectiveCursorNativeExecAllow(this.input.provider, this.input.requestDeclaresFullAccess === true),
|
|
484
546
|
};
|
|
485
547
|
} catch (err) {
|
|
@@ -521,6 +583,22 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
521
583
|
}
|
|
522
584
|
return err;
|
|
523
585
|
};
|
|
586
|
+
/**
|
|
587
|
+
* A cancel we did not request is a real transport failure, but as a raw `NGHTTP2_CANCEL` it
|
|
588
|
+
* gets swallowed twice over: the adapter re-decides "benign" from the error code alone
|
|
589
|
+
* (`cursor.ts:181`) and drops the turn, and any message that survives is re-matched
|
|
590
|
+
* downstream and labelled an intentional "Cursor stream suspended". Raising a typed error
|
|
591
|
+
* carries the provenance this class already holds.
|
|
592
|
+
*
|
|
593
|
+
* Suppressed once a terminal was emitted: the turn already ended, and a second terminal flips
|
|
594
|
+
* a completed buffered response to failed.
|
|
595
|
+
*/
|
|
596
|
+
const classifyTurnFailure = (err: Error): Error => {
|
|
597
|
+
if (!this.expectedClose && !this.emittedTerminal && isCursorBenignCancelError(err)) {
|
|
598
|
+
return summarizeFailure(new CursorUnexpectedCancelError(err));
|
|
599
|
+
}
|
|
600
|
+
return summarizeFailure(err);
|
|
601
|
+
};
|
|
524
602
|
const wake = () => {
|
|
525
603
|
const fn = notify;
|
|
526
604
|
notify = undefined;
|
|
@@ -530,20 +608,54 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
530
608
|
const push = (message: CursorServerMessage) => {
|
|
531
609
|
const bytes = new TextEncoder().encode(JSON.stringify(message)).byteLength;
|
|
532
610
|
this.reserveTransportBytes(bytes);
|
|
611
|
+
if (message.type === "done" || message.type === "error") this.emittedTerminal = true;
|
|
533
612
|
queue.push({ message, bytes });
|
|
534
613
|
wake();
|
|
535
614
|
};
|
|
536
615
|
|
|
537
616
|
// Advertise MCP tools before the stream opens — the server only calls tools it was told about.
|
|
538
617
|
await this.prepareMcp();
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
618
|
+
// JPEG soft-cap rewrite for active-turn data: images before encode. Rebuild text
|
|
619
|
+
// messages from the prepared raw channel so omission markers replace stale
|
|
620
|
+
// pre-rewrite content that activePromptText and the tool filter would otherwise see.
|
|
621
|
+
const preparedRaw = await prepareCursorRawMessages(request.rawMessages, signal);
|
|
622
|
+
const preparedRawMessages = preparedRaw.messages;
|
|
623
|
+
const selectedImages = await resolveActiveCursorImages(
|
|
624
|
+
preparedRawMessages,
|
|
625
|
+
signal,
|
|
626
|
+
preparedRaw.images,
|
|
627
|
+
);
|
|
628
|
+
const preparedMessages = preparedRawMessages === request.rawMessages
|
|
629
|
+
? request.messages
|
|
630
|
+
: cursorRequestMessagesFromRaw(preparedRawMessages);
|
|
631
|
+
const activeRequest: CursorRunRequest = {
|
|
632
|
+
...request,
|
|
633
|
+
messages: preparedMessages,
|
|
634
|
+
rawMessages: preparedRawMessages,
|
|
635
|
+
selectedImages,
|
|
636
|
+
};
|
|
637
|
+
const activeText = activePromptText(activeRequest);
|
|
638
|
+
this.activeClientToolFinalizeGraceMs = clientToolFinalizeGraceMsForRequest(activeRequest, this.clientToolFinalizeGraceMs);
|
|
639
|
+
const cursorVisibleTools = cursorToolsForActivePrompt(activeRequest.tools, activeText, activeRequest.toolChoice);
|
|
640
|
+
const clientToolDefs = buildCursorToolDefinitions(cursorVisibleTools, activeRequest.toolChoice);
|
|
641
|
+
// `request.tools` is the catalog already filtered and budgeted by request-builder. Derive
|
|
642
|
+
// conversion provenance only from tagged synthetic tools that also survive this final prompt
|
|
643
|
+
// filter; a client tool with the same wire name can never opt into conversion by collision.
|
|
644
|
+
const syntheticStructuredEditToolNames = new Set(
|
|
645
|
+
(cursorVisibleTools ?? [])
|
|
646
|
+
.filter(isCursorSyntheticStructuredEditTool)
|
|
647
|
+
.map(cursorToolWireName),
|
|
648
|
+
);
|
|
649
|
+
const freeformToolNames = new Set(
|
|
650
|
+
(cursorVisibleTools ?? [])
|
|
651
|
+
.filter(tool => tool.freeform)
|
|
652
|
+
.map(tool => namespacedToolName(tool.namespace, tool.name)),
|
|
653
|
+
);
|
|
543
654
|
this.execContext = {
|
|
544
655
|
...this.execContext,
|
|
545
656
|
clientToolDefs,
|
|
546
657
|
rejectNativeFileMutations: cursorRequestAdvertisesApplyPatch(request.tools, request.toolChoice),
|
|
658
|
+
structuredEditAvailable: syntheticStructuredEditToolNames.size > 0,
|
|
547
659
|
};
|
|
548
660
|
const toolSchemas = new Map<string, unknown>();
|
|
549
661
|
const cursorToolNameMap = new Map<string, string>();
|
|
@@ -561,16 +673,18 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
561
673
|
});
|
|
562
674
|
// Build the payload once. The estimate is only worth deriving when there is no
|
|
563
675
|
// carry-forward to fall back on — with a carry present it would never be used (#373).
|
|
564
|
-
const prepared = prepareCursorRunRequest(
|
|
676
|
+
const prepared = prepareCursorRunRequest(activeRequest, {
|
|
565
677
|
estimateInputTokens: contextUsage.carryForwardTokens === undefined,
|
|
566
678
|
});
|
|
567
679
|
this.blobRequestScope = prepared.blobRequestScope;
|
|
568
680
|
try {
|
|
569
681
|
state = createCursorProtobufEventState({
|
|
570
682
|
clientToolNames: clientToolDefs.map(tool => tool.toolName || tool.name),
|
|
683
|
+
freeformToolNames,
|
|
571
684
|
parallelToolCalls: request.parallelToolCalls,
|
|
572
685
|
toolSchemas,
|
|
573
686
|
cursorToolNameMap,
|
|
687
|
+
syntheticStructuredEditToolNames,
|
|
574
688
|
translatorBudget: this.translatorBudget,
|
|
575
689
|
contextUsage,
|
|
576
690
|
...(prepared.estimatedInputTokens !== undefined
|
|
@@ -603,7 +717,19 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
603
717
|
// A CANCEL is benign only on the client-tool suspend path (expectedClose); an
|
|
604
718
|
// unexpected server-side NGHTTP2_CANCEL must surface as a real transport error.
|
|
605
719
|
if (this.expectedClose && isCursorBenignCancelError(failure)) return;
|
|
606
|
-
|
|
720
|
+
// A teardown error arriving AFTER the turn's terminal frame describes the connection,
|
|
721
|
+
// not the turn: the answer is committed and every queued message has been yielded.
|
|
722
|
+
//
|
|
723
|
+
// Narrow on purpose. A benign cancel after a terminal is already swallowed one layer
|
|
724
|
+
// up (`cursor.ts:183`), so widening this to every post-terminal error would change
|
|
725
|
+
// what the adapter sees for genuine faults. What it does cover is the abort case
|
|
726
|
+
// from #1527: `signal.abort` fires `failAndClear(new Error("Cursor request was
|
|
727
|
+
// aborted"))`, which is NOT benign (`cursor-errors.ts:74`), so an ordinary completed
|
|
728
|
+
// turn that is then torn down still surfaced as `turn-failed` with
|
|
729
|
+
// `expectedClose:false`. Only `cancelCursorRun()` sets `expectedClose`, so a normal
|
|
730
|
+
// completion never qualified for the branch above.
|
|
731
|
+
if (this.emittedTerminal && isCursorAbortError(failure)) return;
|
|
732
|
+
throw attachPartialUsage(classifyTurnFailure(failure), state);
|
|
607
733
|
}
|
|
608
734
|
if (done) break;
|
|
609
735
|
await new Promise<void>(resolve => {
|
|
@@ -612,7 +738,8 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
612
738
|
}
|
|
613
739
|
if (failure) {
|
|
614
740
|
if (this.expectedClose && isCursorBenignCancelError(failure)) return;
|
|
615
|
-
|
|
741
|
+
if (this.emittedTerminal && isCursorAbortError(failure)) return;
|
|
742
|
+
throw attachPartialUsage(classifyTurnFailure(failure), state);
|
|
616
743
|
}
|
|
617
744
|
}
|
|
618
745
|
|
|
@@ -639,12 +766,24 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
639
766
|
|| this.pendingTransportFrames >= CURSOR_MAX_PENDING_FRAMES
|
|
640
767
|
) {
|
|
641
768
|
this.stream?.pause();
|
|
769
|
+
this.http1Connection?.pause();
|
|
642
770
|
return;
|
|
643
771
|
}
|
|
644
772
|
if (
|
|
645
773
|
this.transportBufferedBytes <= CURSOR_TRANSPORT_RESUME_BYTES
|
|
646
774
|
&& this.pendingTransportFrames <= CURSOR_PENDING_FRAMES_RESUME
|
|
647
|
-
)
|
|
775
|
+
) {
|
|
776
|
+
this.stream?.resume();
|
|
777
|
+
this.http1Connection?.resume();
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
private writeConnectFrame(frame: Uint8Array): void {
|
|
782
|
+
if (this.http1Connection) {
|
|
783
|
+
this.http1Connection.write(frame);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
this.stream?.write(frame);
|
|
648
787
|
}
|
|
649
788
|
|
|
650
789
|
requestCommitted(): boolean {
|
|
@@ -658,16 +797,103 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
658
797
|
}
|
|
659
798
|
}
|
|
660
799
|
|
|
800
|
+
private clearStreamHealthTimer(): void {
|
|
801
|
+
if (this.streamHealthTimer) {
|
|
802
|
+
clearTimeout(this.streamHealthTimer);
|
|
803
|
+
this.streamHealthTimer = undefined;
|
|
804
|
+
}
|
|
805
|
+
this.streamHealthFail = undefined;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* T04: arm (or re-arm) the inbound stream-health watchdog. `fail` is the turn's
|
|
810
|
+
* failAndClear; the timer owns nothing else. Never armed before the first decoded
|
|
811
|
+
* frame (the first-frame timer covers dial + first response), and disarmed by
|
|
812
|
+
* every settle / expected-close path alongside the other timers.
|
|
813
|
+
*/
|
|
814
|
+
private armStreamHealthTimer(fail: (error: Error) => void): void {
|
|
815
|
+
if (this.streamHealthTimer) clearTimeout(this.streamHealthTimer);
|
|
816
|
+
if (this.expectedClose) return;
|
|
817
|
+
this.streamHealthFail = fail;
|
|
818
|
+
const silenceMs = this.input.streamSilenceFailMs ?? CURSOR_STREAM_SILENCE_FAIL_MS;
|
|
819
|
+
const heartbeatOnlyMs = this.input.streamHeartbeatOnlyFailMs ?? CURSOR_STREAM_HEARTBEAT_ONLY_FAIL_MS;
|
|
820
|
+
const now = Date.now();
|
|
821
|
+
const deadline = Math.min(
|
|
822
|
+
this.lastInboundFrameAt + silenceMs,
|
|
823
|
+
this.lastMeaningfulFrameAt + heartbeatOnlyMs,
|
|
824
|
+
);
|
|
825
|
+
this.streamHealthTimer = setTimeout(() => {
|
|
826
|
+
this.streamHealthTimer = undefined;
|
|
827
|
+
const failFn = this.streamHealthFail;
|
|
828
|
+
if (!failFn || this.expectedClose) return;
|
|
829
|
+
const stalledFor = Date.now() - this.lastInboundFrameAt;
|
|
830
|
+
const meaningfulStalledFor = Date.now() - this.lastMeaningfulFrameAt;
|
|
831
|
+
if (stalledFor < silenceMs && meaningfulStalledFor < heartbeatOnlyMs) {
|
|
832
|
+
// A frame landed between arming and firing — re-arm for the fresh deadline.
|
|
833
|
+
this.armStreamHealthTimer(failFn);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const heartbeatOnly = stalledFor < silenceMs;
|
|
837
|
+
debugProviderDiagnostic("cursor", "stream-health-timeout", {
|
|
838
|
+
stalledMs: stalledFor,
|
|
839
|
+
meaningfulStalledMs: meaningfulStalledFor,
|
|
840
|
+
heartbeatOnly,
|
|
841
|
+
framesReceived: this.framesReceived,
|
|
842
|
+
elapsedMs: Date.now() - this.turnStartedAt,
|
|
843
|
+
});
|
|
844
|
+
const reason = heartbeatOnly
|
|
845
|
+
? `Cursor stream stalled: heartbeat-only traffic for ${Math.round(meaningfulStalledFor / 1000)}s without turn progress`
|
|
846
|
+
: `Cursor stream stalled: no inbound frames for ${Math.round(stalledFor / 1000)}s before turnEnded`;
|
|
847
|
+
failFn(new Error(reason));
|
|
848
|
+
try { this.stream?.close(); } catch { this.stream?.destroy(); }
|
|
849
|
+
this.session?.close();
|
|
850
|
+
this.http1Connection?.close();
|
|
851
|
+
}, Math.max(0, deadline - now));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* T04: record a decoded inbound frame. Liveness-only frames (server heartbeat,
|
|
856
|
+
* conversationCheckpointUpdate) keep the silence clock fresh but not the progress
|
|
857
|
+
* clock — matching senpi's split so a server that only pings still fails at the
|
|
858
|
+
* heartbeat-only threshold.
|
|
859
|
+
*/
|
|
860
|
+
private noteInboundFrame(livenessOnly: boolean): void {
|
|
861
|
+
const now = Date.now();
|
|
862
|
+
this.lastInboundFrameAt = now;
|
|
863
|
+
if (!livenessOnly) this.lastMeaningfulFrameAt = now;
|
|
864
|
+
if (this.streamHealthFail) this.armStreamHealthTimer(this.streamHealthFail);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* A clean Connect END_STREAM owns the turn terminal even when Cursor keeps the
|
|
869
|
+
* HTTP body open or tears it down with an abort/reset immediately afterward.
|
|
870
|
+
* Stop client-side liveness work and classify that later transport close as
|
|
871
|
+
* expected without actively sending an RST_STREAM back to Cursor.
|
|
872
|
+
*/
|
|
873
|
+
private markProtocolComplete(): void {
|
|
874
|
+
this.expectedClose = true;
|
|
875
|
+
this.clearPendingFinalize();
|
|
876
|
+
if (this.heartbeat) {
|
|
877
|
+
clearInterval(this.heartbeat);
|
|
878
|
+
this.heartbeat = undefined;
|
|
879
|
+
}
|
|
880
|
+
this.clearFirstFrameTimer();
|
|
881
|
+
this.clearStreamHealthTimer();
|
|
882
|
+
}
|
|
883
|
+
|
|
661
884
|
private startShellCleanup(): Promise<BackgroundShellTerminationReport> {
|
|
662
|
-
return this.shellCleanup ??= terminateBackgroundShellsForSession(this.
|
|
885
|
+
return this.shellCleanup ??= terminateBackgroundShellsForSession(this.shellOwnerId);
|
|
663
886
|
}
|
|
664
887
|
|
|
665
888
|
async close(): Promise<void> {
|
|
666
889
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
890
|
+
if (this.turnEndedCloseTimer) clearTimeout(this.turnEndedCloseTimer);
|
|
667
891
|
this.clearPendingFinalize();
|
|
668
892
|
this.clearFirstFrameTimer();
|
|
893
|
+
this.clearStreamHealthTimer();
|
|
669
894
|
this.stream?.close();
|
|
670
895
|
this.session?.close();
|
|
896
|
+
this.http1Connection?.close();
|
|
671
897
|
this.releaseBlobRequestScope();
|
|
672
898
|
this.releaseMcpObservation?.();
|
|
673
899
|
this.releaseMcpObservation = undefined;
|
|
@@ -680,12 +906,17 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
680
906
|
this.clearPendingFinalize();
|
|
681
907
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
682
908
|
this.clearFirstFrameTimer();
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
909
|
+
this.clearStreamHealthTimer();
|
|
910
|
+
if (this.http1Connection) {
|
|
911
|
+
this.http1Connection.close();
|
|
912
|
+
} else {
|
|
913
|
+
try {
|
|
914
|
+
this.stream?.close(http2.constants.NGHTTP2_CANCEL);
|
|
915
|
+
} catch {
|
|
916
|
+
this.stream?.destroy();
|
|
917
|
+
}
|
|
918
|
+
this.session?.close();
|
|
687
919
|
}
|
|
688
|
-
this.session?.close();
|
|
689
920
|
this.releaseBlobRequestScope();
|
|
690
921
|
this.releaseMcpObservation?.();
|
|
691
922
|
this.releaseMcpObservation = undefined;
|
|
@@ -693,6 +924,46 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
693
924
|
void this.startShellCleanup().catch(() => { /* close() observes the same cleanup promise */ });
|
|
694
925
|
}
|
|
695
926
|
|
|
927
|
+
/**
|
|
928
|
+
* T03 (#1062): after the server sends `turnEnded`, the application turn is complete.
|
|
929
|
+
* A server that keeps the HTTP/2 stream open past this point cannot hold the turn
|
|
930
|
+
* hostage until a 300s bridge idle timeout. Close our side after a short grace so any
|
|
931
|
+
* trailing frames (late usage, checkpoint) still land before we release the socket.
|
|
932
|
+
*/
|
|
933
|
+
private closeAfterTurnEnded(): void {
|
|
934
|
+
if (this.turnEndedCloseTimer) return;
|
|
935
|
+
// The application turn is over: the T03 grace timer owns the socket from here.
|
|
936
|
+
// The T04 watchdog must disarm NOW, not at the grace close — a watchdog shorter
|
|
937
|
+
// than the grace would otherwise fail a completed turn.
|
|
938
|
+
this.clearStreamHealthTimer();
|
|
939
|
+
this.turnEndedCloseTimer = setTimeout(() => {
|
|
940
|
+
this.turnEndedCloseTimer = undefined;
|
|
941
|
+
// Only expectedClose (client-tool suspend cancel) blocks the close.
|
|
942
|
+
// emittedTerminal is intentionally NOT checked here: finalizeTurnEvents sets it
|
|
943
|
+
// synchronously during turnEnded mapping, ~500ms before this timer fires, so
|
|
944
|
+
// checking it would make the close unreachable on every real path (the exact
|
|
945
|
+
// scenario this PR exists to fix — senpi #1062).
|
|
946
|
+
if (this.expectedClose) return;
|
|
947
|
+
debugProviderDiagnostic("cursor", "turn-ended-close", {
|
|
948
|
+
committed: this.committed,
|
|
949
|
+
framesReceived: this.framesReceived,
|
|
950
|
+
});
|
|
951
|
+
this.expectedClose = true;
|
|
952
|
+
this.clearFirstFrameTimer();
|
|
953
|
+
this.clearStreamHealthTimer();
|
|
954
|
+
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
955
|
+
if (this.http1Connection) {
|
|
956
|
+
this.http1Connection.close();
|
|
957
|
+
} else {
|
|
958
|
+
try {
|
|
959
|
+
this.stream?.close();
|
|
960
|
+
} catch {
|
|
961
|
+
this.stream?.destroy();
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}, TURN_ENDED_CLOSE_GRACE_MS);
|
|
965
|
+
}
|
|
966
|
+
|
|
696
967
|
private releaseBlobRequestScope(): void {
|
|
697
968
|
const scope = this.blobRequestScope;
|
|
698
969
|
if (!scope) return;
|
|
@@ -750,33 +1021,52 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
750
1021
|
fail: (error: Error) => void,
|
|
751
1022
|
finish: () => void,
|
|
752
1023
|
): void {
|
|
1024
|
+
if (signal?.aborted) {
|
|
1025
|
+
fail(signal.reason instanceof Error ? signal.reason : new Error("Cursor request was aborted"));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
753
1028
|
this.turnStartedAt = Date.now();
|
|
754
1029
|
this.framesReceived = 0;
|
|
1030
|
+
this.sawAssistantText = false;
|
|
1031
|
+
this.emittedTerminal = false;
|
|
755
1032
|
this.firstFrameAt = undefined;
|
|
756
1033
|
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
|
-
|
|
1034
|
+
const baseUrl = this.input.provider.baseUrl || "https://api2.cursor.sh";
|
|
1035
|
+
const useHttp1 = isPinnedHttp1(this.input.provider.upstreamHttpVersion);
|
|
1036
|
+
const requestId = crypto.randomUUID();
|
|
1037
|
+
const dialHost = cursorHostLabel(baseUrl);
|
|
1038
|
+
debugProviderDiagnostic("cursor", "dial", { host: dialHost, transport: useHttp1 ? "http1.1" : "http2" });
|
|
1039
|
+
|
|
1040
|
+
let session: http2.ClientHttp2Session | undefined;
|
|
1041
|
+
let stream: http2.ClientHttp2Stream | undefined;
|
|
1042
|
+
if (!useHttp1) {
|
|
1043
|
+
session = http2.connect(baseUrl);
|
|
1044
|
+
this.session = session;
|
|
1045
|
+
// The run request is buffered until the HTTP/2 session connects. Failures before `connect`
|
|
1046
|
+
// (DNS, ECONNREFUSED, TLS, connect timeout) mean the server never received the request, so they
|
|
1047
|
+
// are safe to retry. Once connected, bytes flush to the server and the turn must not be replayed.
|
|
1048
|
+
session.on("connect", () => {
|
|
1049
|
+
this.committed = true;
|
|
1050
|
+
debugProviderDiagnostic("cursor", "connected", {
|
|
1051
|
+
transport: "http2",
|
|
1052
|
+
connectMs: Date.now() - this.turnStartedAt,
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
stream = session.request({
|
|
1056
|
+
":method": "POST",
|
|
1057
|
+
":path": CURSOR_RUN_PATH,
|
|
1058
|
+
"content-type": "application/connect+proto",
|
|
1059
|
+
"connect-protocol-version": "1",
|
|
1060
|
+
te: "trailers",
|
|
1061
|
+
authorization: `Bearer ${this.token}`,
|
|
1062
|
+
"x-ghost-mode": "true",
|
|
1063
|
+
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
1064
|
+
"x-cursor-client-type": "cli",
|
|
1065
|
+
"x-request-id": requestId,
|
|
1066
|
+
"x-session-id": this.sessionId,
|
|
1067
|
+
});
|
|
1068
|
+
this.stream = stream;
|
|
1069
|
+
}
|
|
780
1070
|
|
|
781
1071
|
// Single-shot terminal owner for this turn (createTerminalSettler): stream error, session
|
|
782
1072
|
// error, trailers, end, abort, and the first-frame timeout all race into it, and only the
|
|
@@ -784,9 +1074,13 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
784
1074
|
const settler = createTerminalSettler({
|
|
785
1075
|
fail,
|
|
786
1076
|
finish,
|
|
787
|
-
clearTimer: () =>
|
|
1077
|
+
clearTimer: () => {
|
|
1078
|
+
this.clearFirstFrameTimer();
|
|
1079
|
+
this.clearStreamHealthTimer();
|
|
1080
|
+
},
|
|
788
1081
|
});
|
|
789
1082
|
const failAndClear = (error: Error) => {
|
|
1083
|
+
releaseBacklogLease();
|
|
790
1084
|
if (this.expectedClose) {
|
|
791
1085
|
// We already emitted a terminal `done` and cancelled the run (client-tool suspension). The
|
|
792
1086
|
// RST_STREAM CANCEL surfaces here as a stream error/abort; it is expected, not a failure.
|
|
@@ -801,11 +1095,9 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
801
1095
|
}
|
|
802
1096
|
settler.settleFail(error);
|
|
803
1097
|
};
|
|
804
|
-
const session = this.session;
|
|
805
|
-
const stream = this.stream;
|
|
806
1098
|
// Session-level errors (TLS/socket/GOAWAY) do not always propagate to the stream listener;
|
|
807
1099
|
// without this handler they could bypass orderly failure reporting entirely.
|
|
808
|
-
|
|
1100
|
+
const onSessionError = (err: unknown) => {
|
|
809
1101
|
const realErr = err instanceof Error ? err : new Error(String(err));
|
|
810
1102
|
debugProviderDiagnostic("cursor", "session-error", {
|
|
811
1103
|
code: String((realErr as { code?: unknown }).code ?? ""),
|
|
@@ -813,37 +1105,65 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
813
1105
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
814
1106
|
});
|
|
815
1107
|
failAndClear(realErr);
|
|
816
|
-
}
|
|
1108
|
+
};
|
|
817
1109
|
this.firstFrameTimer = setTimeout(() => {
|
|
818
1110
|
this.firstFrameTimer = undefined;
|
|
819
1111
|
debugProviderDiagnostic("cursor", "first-frame-timeout", { timeoutMs: this.input.firstFrameTimeoutMs ?? CURSOR_FIRST_FRAME_TIMEOUT_MS });
|
|
820
|
-
try { stream
|
|
821
|
-
try { session
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1112
|
+
try { stream?.close(); } catch { /* already closing */ }
|
|
1113
|
+
try { session?.close(); } catch { /* already closing */ }
|
|
1114
|
+
this.http1Connection?.close();
|
|
1115
|
+
if (stream && session) {
|
|
1116
|
+
// close() waits for in-flight frames; a dead socket can ignore it — force-destroy shortly
|
|
1117
|
+
// after so a stalled TLS session cannot linger past the timeout.
|
|
1118
|
+
armTimeoutDestroyFallback(stream, session, this.input.timeoutDestroyGraceMs ?? CURSOR_TIMEOUT_DESTROY_GRACE_MS);
|
|
1119
|
+
}
|
|
1120
|
+
releaseBacklogLease();
|
|
825
1121
|
settler.settleFail(new Error("Cursor transport timed out before first response"));
|
|
826
1122
|
}, this.input.firstFrameTimeoutMs ?? CURSOR_FIRST_FRAME_TIMEOUT_MS);
|
|
827
1123
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1124
|
+
// Raw Connect backlog with a parse cursor: appends copy only the incoming
|
|
1125
|
+
// chunk (amortized capacity growth), the consumed prefix is reclaimed
|
|
1126
|
+
// lazily, and the RAW used length (headers included) is what the 32 MiB
|
|
1127
|
+
// transport cap bounds — payload-only accounting let tiny-frame/header
|
|
1128
|
+
// floods slip through.
|
|
1129
|
+
let backlog = new Uint8Array();
|
|
1130
|
+
let backlogStart = 0;
|
|
1131
|
+
let backlogEnd = 0;
|
|
1132
|
+
const BACKLOG_COMPACT_MIN_SAVINGS = 64 * 1024;
|
|
1133
|
+
const appendBacklog = (chunk: Uint8Array): void => {
|
|
1134
|
+
const used = backlogEnd - backlogStart;
|
|
1135
|
+
let start = backlogStart;
|
|
1136
|
+
let end = backlogEnd;
|
|
1137
|
+
// Reclaim the consumed prefix when it is large or needed for capacity.
|
|
1138
|
+
if (start > 0 && (start >= BACKLOG_COMPACT_MIN_SAVINGS || end + chunk.byteLength > backlog.byteLength)) {
|
|
1139
|
+
backlog = backlog.slice(start, end);
|
|
1140
|
+
start = 0;
|
|
1141
|
+
end = used;
|
|
833
1142
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
},
|
|
844
|
-
};
|
|
1143
|
+
if (end + chunk.byteLength > backlog.byteLength) {
|
|
1144
|
+
const capacity = Math.max(8192, backlog.byteLength * 2, end + chunk.byteLength);
|
|
1145
|
+
const next = new Uint8Array(Math.min(CURSOR_TRANSPORT_MAX_BUFFERED_BYTES, capacity));
|
|
1146
|
+
next.set(backlog.subarray(start, end), 0);
|
|
1147
|
+
backlog = next;
|
|
1148
|
+
}
|
|
1149
|
+
backlog.set(chunk, end);
|
|
1150
|
+
backlogStart = start;
|
|
1151
|
+
backlogEnd = end + chunk.byteLength;
|
|
845
1152
|
};
|
|
846
|
-
|
|
1153
|
+
let frameWork: Promise<void> = Promise.resolve();
|
|
1154
|
+
// Idempotent terminal owner for the backlog lease: every settle/close path
|
|
1155
|
+
// must leave the raw charge at zero instead of relying on budget disposal.
|
|
1156
|
+
let backlogLeaseReleased = false;
|
|
1157
|
+
const releaseBacklogLease = () => {
|
|
1158
|
+
if (backlogLeaseReleased) return;
|
|
1159
|
+
backlogLeaseReleased = true;
|
|
1160
|
+
const leftover = backlogEnd - backlogStart;
|
|
1161
|
+
if (leftover > 0) this.releaseTransportBytes(leftover);
|
|
1162
|
+
backlog = new Uint8Array();
|
|
1163
|
+
backlogStart = 0;
|
|
1164
|
+
backlogEnd = 0;
|
|
1165
|
+
};
|
|
1166
|
+
const handleFrame = async (frame: ReturnType<typeof consumeConnectFrames>["frames"][number]) => {
|
|
847
1167
|
this.framesReceived++;
|
|
848
1168
|
if ((frame.flags & CONNECT_FLAG_END_STREAM) === CONNECT_FLAG_END_STREAM) {
|
|
849
1169
|
const endError = parseConnectEndStreamError(frame.payload);
|
|
@@ -854,29 +1174,76 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
854
1174
|
framesReceived: this.framesReceived,
|
|
855
1175
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
856
1176
|
} : { framesReceived: this.framesReceived, elapsedMs: Date.now() - this.turnStartedAt });
|
|
857
|
-
if (endError)
|
|
1177
|
+
if (endError) {
|
|
1178
|
+
failAndClear(endError);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
// Connect's clean END_STREAM envelope is the protocol terminal. Cursor's RunSSE body can
|
|
1182
|
+
// remain open after this frame (or close through an AbortError), so waiting for HTTP EOF
|
|
1183
|
+
// strands an otherwise completed turn until the outer bridge stall watchdog fires.
|
|
1184
|
+
//
|
|
1185
|
+
// Earlier frames in this serialized frameWork chain have already run. Preserve their real
|
|
1186
|
+
// turnEnded terminal when present; otherwise finalize the clean protocol end once so open
|
|
1187
|
+
// tool calls still fail closed, a text-only turn receives its normal done event, and a
|
|
1188
|
+
// drained client-tool turn does not lose the pending terminal when protocol cleanup clears
|
|
1189
|
+
// its grace timer.
|
|
1190
|
+
const hasPendingClientToolFinalization = this.pendingFinalize !== undefined;
|
|
1191
|
+
if (
|
|
1192
|
+
!this.expectedClose
|
|
1193
|
+
&& !state.terminated
|
|
1194
|
+
&& !this.emittedTerminal
|
|
1195
|
+
&& (
|
|
1196
|
+
state.openToolCalls.size > 0
|
|
1197
|
+
|| this.sawAssistantText
|
|
1198
|
+
|| hasPendingClientToolFinalization
|
|
1199
|
+
)
|
|
1200
|
+
) {
|
|
1201
|
+
const terminal = hasPendingClientToolFinalization && state.openToolCalls.size === 0
|
|
1202
|
+
? finalizeAfterDrain(state)
|
|
1203
|
+
: finalizeTurnEvents(state);
|
|
1204
|
+
for (const event of terminal) push(event);
|
|
1205
|
+
}
|
|
1206
|
+
this.markProtocolComplete();
|
|
1207
|
+
releaseBacklogLease();
|
|
1208
|
+
settler.settleFinish();
|
|
858
1209
|
return;
|
|
859
1210
|
}
|
|
860
|
-
|
|
1211
|
+
const decoded = fromBinary(AgentServerMessageSchema, frame.payload);
|
|
1212
|
+
// T04: every decoded frame refreshes the silence clock; only non-liveness frames
|
|
1213
|
+
// refresh the progress clock. First decoded frame arms the watchdog (the first-frame
|
|
1214
|
+
// timer owned everything before this point).
|
|
1215
|
+
const decodedUpdate = decoded.message.case === "interactionUpdate" ? decoded.message.value.message?.case : undefined;
|
|
1216
|
+
const livenessOnly = decodedUpdate === "heartbeat" || decoded.message.case === "conversationCheckpointUpdate";
|
|
1217
|
+
if (!this.streamHealthFail) {
|
|
1218
|
+
const now = Date.now();
|
|
1219
|
+
this.lastInboundFrameAt = now;
|
|
1220
|
+
this.lastMeaningfulFrameAt = now;
|
|
1221
|
+
this.streamHealthFail = failAndClear;
|
|
1222
|
+
}
|
|
1223
|
+
this.noteInboundFrame(livenessOnly);
|
|
1224
|
+
await this.handleServerMessage(decoded, state, push);
|
|
861
1225
|
};
|
|
862
1226
|
const drainPendingFrames = () => {
|
|
863
1227
|
const availableSlots = CURSOR_MAX_PENDING_FRAMES - this.pendingTransportFrames;
|
|
864
|
-
|
|
1228
|
+
const used = backlogEnd - backlogStart;
|
|
1229
|
+
if (availableSlots <= 0 || used === 0) {
|
|
865
1230
|
this.updateTransportFlowControl();
|
|
866
1231
|
return;
|
|
867
1232
|
}
|
|
868
|
-
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
1233
|
+
// Cursor decode, zero-copy: frame payloads are views into the backlog, so
|
|
1234
|
+
// the charge TRANSFERS — only consumed header bytes leave the counter
|
|
1235
|
+
// (payload bytes stay charged and are released when each frame's work
|
|
1236
|
+
// finishes). An exact 16 MiB payload therefore peaks at 16 MiB + 5, not
|
|
1237
|
+
// at double its size.
|
|
1238
|
+
const decoded = consumeConnectFrames(
|
|
1239
|
+
backlog.subarray(backlogStart, backlogEnd),
|
|
874
1240
|
CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES,
|
|
875
1241
|
availableSlots,
|
|
876
|
-
reservePayloadCopy,
|
|
877
1242
|
);
|
|
878
|
-
|
|
879
|
-
|
|
1243
|
+
if (decoded.frames.length > 0) {
|
|
1244
|
+
backlogStart += decoded.consumedBytes;
|
|
1245
|
+
this.releaseTransportBytes(decoded.consumedBytes - decoded.frames.reduce((n, frame) => n + frame.payload.byteLength, 0));
|
|
1246
|
+
}
|
|
880
1247
|
for (const frame of decoded.frames) {
|
|
881
1248
|
this.pendingTransportFrames += 1;
|
|
882
1249
|
this.updateTransportFlowControl();
|
|
@@ -891,43 +1258,42 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
891
1258
|
});
|
|
892
1259
|
}
|
|
893
1260
|
};
|
|
894
|
-
|
|
1261
|
+
const onData = (chunk: string | Uint8Array) => {
|
|
895
1262
|
this.clearFirstFrameTimer();
|
|
1263
|
+
// Once the turn has settled, late network bytes must never be charged —
|
|
1264
|
+
// the backlog lease is already released and nobody would own these.
|
|
1265
|
+
if (settler.settled()) return;
|
|
896
1266
|
if (!this.firstFrameLogged) {
|
|
897
1267
|
this.firstFrameLogged = true;
|
|
898
1268
|
this.firstFrameAt = Date.now();
|
|
899
1269
|
debugProviderDiagnostic("cursor", "first-frame", { latencyMs: this.firstFrameAt - this.turnStartedAt });
|
|
900
1270
|
}
|
|
901
1271
|
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;
|
|
1272
|
+
let charged = false;
|
|
1273
|
+
let appended = false;
|
|
905
1274
|
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;
|
|
1275
|
+
// RAW chunk bytes (headers included) join the backlog charge; consumed
|
|
1276
|
+
// bytes leave it at drain. No whole-backlog replacement copy anymore.
|
|
1277
|
+
this.reserveTransportBytes(bytes.byteLength);
|
|
1278
|
+
charged = true;
|
|
1279
|
+
appendBacklog(bytes);
|
|
1280
|
+
appended = true;
|
|
918
1281
|
drainPendingFrames();
|
|
919
1282
|
} catch (err) {
|
|
920
|
-
|
|
921
|
-
|
|
1283
|
+
// Release ONLY when the reservation succeeded but the append never
|
|
1284
|
+
// happened. A failed reservation charged nothing — releasing here
|
|
1285
|
+
// would debit unrelated existing ownership; an appended chunk is owned
|
|
1286
|
+
// by the terminal backlog cleanup.
|
|
1287
|
+
if (charged && !appended) this.releaseTransportBytes(bytes.byteLength);
|
|
922
1288
|
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
923
1289
|
}
|
|
924
|
-
}
|
|
925
|
-
|
|
1290
|
+
};
|
|
1291
|
+
const onTrailers = (trailers: http2.IncomingHttpHeaders) => {
|
|
926
1292
|
const status = trailers["grpc-status"];
|
|
927
1293
|
if (status !== undefined) debugProviderDiagnostic("cursor", "trailers", { grpcStatus: String(status) });
|
|
928
1294
|
if (status && status !== "0") failAndClear(new Error(`Cursor gRPC error ${status}`));
|
|
929
|
-
}
|
|
930
|
-
|
|
1295
|
+
};
|
|
1296
|
+
const onStreamError = (err: unknown) => {
|
|
931
1297
|
const realErr = err instanceof Error ? err : new Error(String(err));
|
|
932
1298
|
if (this.expectedClose) {
|
|
933
1299
|
failAndClear(realErr);
|
|
@@ -945,8 +1311,8 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
945
1311
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
946
1312
|
});
|
|
947
1313
|
failAndClear(realErr);
|
|
948
|
-
}
|
|
949
|
-
|
|
1314
|
+
};
|
|
1315
|
+
const onStreamEnd = () => {
|
|
950
1316
|
this.clearFirstFrameTimer();
|
|
951
1317
|
debugProviderDiagnostic("cursor", "stream-end", {
|
|
952
1318
|
committed: this.committed,
|
|
@@ -954,39 +1320,134 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
954
1320
|
expectedClose: this.expectedClose,
|
|
955
1321
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
956
1322
|
});
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1323
|
+
// Settle only after queued frame work drains to quiescence (the chain can
|
|
1324
|
+
// extend itself while draining), then classify the terminal state:
|
|
1325
|
+
// a trailing incomplete frame is a typed failure, and a zero-frame,
|
|
1326
|
+
// zero-byte end without an expected close stays the existing unexpected
|
|
1327
|
+
// EOF — both beat the old silent success.
|
|
1328
|
+
void (async () => {
|
|
1329
|
+
let previous: Promise<void>;
|
|
1330
|
+
do {
|
|
1331
|
+
previous = frameWork;
|
|
1332
|
+
await previous;
|
|
1333
|
+
} while (previous !== frameWork);
|
|
1334
|
+
})().then(() => {
|
|
1335
|
+
if (settler.settled()) return;
|
|
1336
|
+
const leftover = backlogEnd - backlogStart;
|
|
1337
|
+
if (leftover > 0 && !this.expectedClose) {
|
|
1338
|
+
releaseBacklogLease();
|
|
1339
|
+
settler.settleFail(new ConnectFrameError(
|
|
1340
|
+
"frame_incomplete",
|
|
1341
|
+
`Cursor Connect stream ended with ${leftover} unconsumed bytes (incomplete frame)`,
|
|
1342
|
+
));
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (this.framesReceived === 0 && !this.expectedClose) {
|
|
1346
|
+
releaseBacklogLease();
|
|
1347
|
+
settler.settleFail(new Error("Cursor stream ended before any response frame (unexpected EOF)"));
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
// `emittedTerminal` joins dev's two conditions so EOF finalization cannot append a
|
|
1351
|
+
// second terminal after a mapper error already failed the turn (integration 010).
|
|
1352
|
+
if (state.terminated || this.expectedClose || this.emittedTerminal) {
|
|
1353
|
+
releaseBacklogLease();
|
|
1354
|
+
settler.settleFinish();
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
// Open tools fail-closed as a truncation *event* (finalizeTurnEvents), not a thrown
|
|
1358
|
+
// transport error. settleFail here would hide that typed message as adapter_eof.
|
|
1359
|
+
if (state.openToolCalls.size > 0) {
|
|
1360
|
+
for (const event of finalizeTurnEvents(state)) push(event);
|
|
1361
|
+
releaseBacklogLease();
|
|
1362
|
+
settler.settleFinish();
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (this.framesReceived > 0 && this.sawAssistantText) {
|
|
1366
|
+
for (const event of finalizeTurnEvents(state)) push(event);
|
|
1367
|
+
releaseBacklogLease();
|
|
1368
|
+
settler.settleFinish();
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
releaseBacklogLease();
|
|
1372
|
+
settler.settleFinish();
|
|
1373
|
+
}).catch((err) => {
|
|
1374
|
+
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
1375
|
+
});
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
if (useHttp1) {
|
|
1379
|
+
const providerFetch = this.input.fetch
|
|
1380
|
+
?? (this.input.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch;
|
|
1381
|
+
this.http1Connection = new CursorHttp1BidiConnection({
|
|
1382
|
+
baseUrl,
|
|
1383
|
+
token: this.token,
|
|
1384
|
+
clientVersion: CURSOR_CLIENT_VERSION,
|
|
1385
|
+
sessionId: this.sessionId,
|
|
1386
|
+
requestId,
|
|
1387
|
+
translatorBudget: this.translatorBudget,
|
|
1388
|
+
callbacks: {
|
|
1389
|
+
onCommitted: () => {
|
|
1390
|
+
this.committed = true;
|
|
1391
|
+
debugProviderDiagnostic("cursor", "connected", {
|
|
1392
|
+
transport: "http1.1",
|
|
1393
|
+
connectMs: Date.now() - this.turnStartedAt,
|
|
1394
|
+
});
|
|
1395
|
+
},
|
|
1396
|
+
onData,
|
|
1397
|
+
onEnd: onStreamEnd,
|
|
1398
|
+
onError: onStreamError,
|
|
1399
|
+
},
|
|
1400
|
+
...(providerFetch ? { fetch: providerFetch } : {}),
|
|
1401
|
+
});
|
|
1402
|
+
this.http1Connection.start();
|
|
1403
|
+
} else {
|
|
1404
|
+
session!.on("error", onSessionError);
|
|
1405
|
+
stream!.on("data", onData);
|
|
1406
|
+
stream!.on("trailers", onTrailers);
|
|
1407
|
+
stream!.on("error", onStreamError);
|
|
1408
|
+
stream!.on("end", onStreamEnd);
|
|
1409
|
+
}
|
|
967
1410
|
|
|
968
1411
|
signal?.addEventListener("abort", () => {
|
|
969
1412
|
this.close();
|
|
970
1413
|
failAndClear(new Error("Cursor request was aborted"));
|
|
971
1414
|
}, { once: true });
|
|
1415
|
+
// Close the race between the preflight above and listener installation. No request payload is
|
|
1416
|
+
// written until after this check.
|
|
1417
|
+
if (signal?.aborted) {
|
|
1418
|
+
this.close();
|
|
1419
|
+
failAndClear(signal.reason instanceof Error ? signal.reason : new Error("Cursor request was aborted"));
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
972
1422
|
|
|
973
|
-
this.
|
|
1423
|
+
this.writeConnectFrame(encodeConnectFrame(encodedRequest));
|
|
974
1424
|
this.heartbeat = setInterval(() => {
|
|
975
|
-
this.
|
|
1425
|
+
this.writeConnectFrame(encodeClientMessage({
|
|
976
1426
|
message: { case: "clientHeartbeat", value: create(ClientHeartbeatSchema, {}) },
|
|
977
1427
|
}));
|
|
978
1428
|
}, HEARTBEAT_MS);
|
|
979
1429
|
}
|
|
980
1430
|
|
|
1431
|
+
capturedConversationCheckpoint(): Uint8Array | undefined {
|
|
1432
|
+
return this.capturedCheckpointBytes;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
981
1435
|
private async handleServerMessage(
|
|
982
1436
|
message: AgentServerMessage,
|
|
983
1437
|
state: ReturnType<typeof createCursorProtobufEventState>,
|
|
984
1438
|
push: (message: CursorServerMessage) => void,
|
|
985
1439
|
): Promise<void> {
|
|
986
|
-
if (!this.stream) return;
|
|
1440
|
+
if (!this.stream && !this.http1Connection) return;
|
|
987
1441
|
debugProviderDiagnostic("cursor", "frame", describeCursorServerFrame(message));
|
|
1442
|
+
if (message.message.case === "conversationCheckpointUpdate") {
|
|
1443
|
+
try {
|
|
1444
|
+
this.capturedCheckpointBytes = toBinary(ConversationStateStructureSchema, message.message.value);
|
|
1445
|
+
} catch {
|
|
1446
|
+
this.capturedCheckpointBytes = undefined;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
988
1449
|
if (message.message.case === "kvServerMessage") {
|
|
989
|
-
this.
|
|
1450
|
+
this.writeConnectFrame(encodeConnectFrame(handleCursorNativeKv(message.message.value, this.blobRequestScope)));
|
|
990
1451
|
return;
|
|
991
1452
|
}
|
|
992
1453
|
if (message.message.case === "execServerMessage") {
|
|
@@ -1007,7 +1468,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1007
1468
|
// run the same local action twice.
|
|
1008
1469
|
push({ type: "local_side_effect" });
|
|
1009
1470
|
const replies = await handleCursorNativeExec(message.message.value, this.execContext);
|
|
1010
|
-
for (const reply of replies) this.
|
|
1471
|
+
for (const reply of replies) this.writeConnectFrame(encodeConnectFrame(reply));
|
|
1011
1472
|
return;
|
|
1012
1473
|
}
|
|
1013
1474
|
if (message.message.case === "interactionQuery") {
|
|
@@ -1017,29 +1478,58 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1017
1478
|
const query = message.message.value;
|
|
1018
1479
|
const plan = planInteractionQueryReply(query);
|
|
1019
1480
|
debugProviderDiagnostic("cursor", "interaction-query", { id: query.id, queryCase: query.query.case ?? "unknown", reply: plan.replyCase });
|
|
1020
|
-
this.
|
|
1481
|
+
this.writeConnectFrame(encodeClientMessage({ message: { case: "interactionResponse", value: plan.response } }));
|
|
1021
1482
|
if (!state.terminated) {
|
|
1022
|
-
if (plan.planText)
|
|
1483
|
+
if (plan.planText) {
|
|
1484
|
+
this.sawAssistantText = true;
|
|
1485
|
+
push({ type: "text", text: plan.planText });
|
|
1486
|
+
}
|
|
1023
1487
|
push({ type: "heartbeat" });
|
|
1024
1488
|
}
|
|
1025
1489
|
return;
|
|
1026
1490
|
}
|
|
1491
|
+
// A completion may carry only callId. Capture its ownership before mapping removes the open
|
|
1492
|
+
// call, because the embedded-tool classifier cannot identify that valid compact frame.
|
|
1493
|
+
const update = message.message.case === "interactionUpdate" ? message.message.value.message : undefined;
|
|
1494
|
+
if (update?.case === "turnEnded") {
|
|
1495
|
+
// T03: the application turn is complete. Close our side of HTTP/2 after a short
|
|
1496
|
+
// grace so a held-open server response cannot pin the turn to the bridge's idle
|
|
1497
|
+
// timeout (senpi #1062). finalizeTurnEvents already emitted done via the mapper.
|
|
1498
|
+
this.closeAfterTurnEnded();
|
|
1499
|
+
}
|
|
1500
|
+
const completesOpenClientTool = update?.case === "toolCallCompleted"
|
|
1501
|
+
&& state.openToolCalls.has(update.value.callId);
|
|
1502
|
+
const awaitedNativeArgsBeforeMapping = update?.case === "toolCallCompleted"
|
|
1503
|
+
&& state.openToolCalls.get(update.value.callId)?.awaitingNativeArgs === true;
|
|
1027
1504
|
const mapped = mapCursorProtobufServerMessage(message, state);
|
|
1505
|
+
if (mapped.some(event => event.type === "text")) this.sawAssistantText = true;
|
|
1506
|
+
const beganAwaitingNativeClientToolArgs = update?.case === "toolCallCompleted"
|
|
1507
|
+
&& !awaitedNativeArgsBeforeMapping
|
|
1508
|
+
&& state.openToolCalls.get(update.value.callId)?.awaitingNativeArgs === true;
|
|
1028
1509
|
if (mapped.length > 0) {
|
|
1029
1510
|
// A client tool call announced/committed via interactionUpdate (toolCallStarted/partialToolCall/
|
|
1030
1511
|
// toolCallCompleted) changes the call set, so revoke any finalize armed by an earlier drain.
|
|
1031
|
-
|
|
1512
|
+
// A completion can also commit and drain a late call without a following mcpArgs frame; in
|
|
1513
|
+
// that case re-arm finalization here so the Responses bridge does not wait forever.
|
|
1514
|
+
const clientToolFrame = completesOpenClientTool || isClientToolFrame(message);
|
|
1515
|
+
if (clientToolFrame) this.noteClientToolActivity();
|
|
1032
1516
|
for (const event of mapped) push(event);
|
|
1517
|
+
if (
|
|
1518
|
+
clientToolFrame
|
|
1519
|
+
&& state.openToolCalls.size === 0
|
|
1520
|
+
&& mapped.some(event => event.type === "tool_call_end")
|
|
1521
|
+
) this.scheduleClientToolFinalize(state, push);
|
|
1033
1522
|
return;
|
|
1034
1523
|
}
|
|
1035
1524
|
// 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
|
|
1525
|
+
// buffering, a completion waiting for native args, toolCallDelta, tokenDelta, or a checkpoint
|
|
1526
|
+
// update). Tool-call protocol events are deferred to completion for atomic, parallel-safe
|
|
1527
|
+
// emission, so a turn that silently assembles several tool calls can otherwise exceed the
|
|
1528
|
+
// bridge's stall watchdog (upstream_stall_timeout).
|
|
1039
1529
|
// Emit a liveness heartbeat for these progress frames so the watchdog sees the upstream is alive.
|
|
1040
1530
|
// 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();
|
|
1531
|
+
if (!state.terminated && (isCursorProgressFrame(message) || beganAwaitingNativeClientToolArgs)) {
|
|
1532
|
+
if (isClientToolFrame(message) || beganAwaitingNativeClientToolArgs) this.noteClientToolActivity();
|
|
1043
1533
|
push({ type: "heartbeat" });
|
|
1044
1534
|
}
|
|
1045
1535
|
}
|
|
@@ -1140,47 +1630,6 @@ export function isClientToolFrame(message: AgentServerMessage): boolean {
|
|
|
1140
1630
|
}
|
|
1141
1631
|
}
|
|
1142
1632
|
|
|
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
1633
|
/** Host-only label for Cursor transport diagnostics — never leaks path/query/credentials. */
|
|
1185
1634
|
function cursorHostLabel(baseUrl: string): string {
|
|
1186
1635
|
try {
|
|
@@ -1212,3 +1661,7 @@ function cursorConnectErrorCode(payload: Uint8Array): string | undefined {
|
|
|
1212
1661
|
export function createLiveCursorTransport(input: CursorTransportFactoryInput): CursorTransport {
|
|
1213
1662
|
return new LiveCursorTransport(input);
|
|
1214
1663
|
}
|
|
1664
|
+
|
|
1665
|
+
export function capturedCursorCheckpointBytes(transport: CursorTransport): Uint8Array | undefined {
|
|
1666
|
+
return transport.capturedConversationCheckpoint?.();
|
|
1667
|
+
}
|