@bitkyc08/opencodex 2.84.0 → 2.87.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +44 -3
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +41 -9
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isOwnedDesktopGatewayEntry,
|
|
17
17
|
parseMetadata,
|
|
18
18
|
profilePath,
|
|
19
|
+
readDesktopLibraryText,
|
|
19
20
|
resolveDesktop3pConfigLibraryPath,
|
|
20
21
|
SAFE_DESKTOP_PROFILE_ID,
|
|
21
22
|
type Desktop3pConfigLibraryOptions,
|
|
@@ -73,6 +74,12 @@ function readFileSnapshot(path: string): { exists: boolean; content?: string } {
|
|
|
73
74
|
return existsSync(path) ? { exists: true, content: readFileSync(path, "utf8") } : { exists: false };
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// Desktop library paths go through the verified regular-file reader: a swapped FIFO or symlink
|
|
78
|
+
// must surface as a refusal, never as a blocking open. Our own picker state file keeps the plain read.
|
|
79
|
+
function readLibrarySnapshot(path: string): { exists: boolean; content?: string } {
|
|
80
|
+
return existsSync(path) ? { exists: true, content: readDesktopLibraryText(path) } : { exists: false };
|
|
81
|
+
}
|
|
82
|
+
|
|
76
83
|
function restoreFile(path: string, snapshot: { exists: boolean; content?: string }): void {
|
|
77
84
|
if (snapshot.exists) {
|
|
78
85
|
rollbackAtomicWriteFile(path, snapshot.content ?? "");
|
|
@@ -98,7 +105,7 @@ function pickerEntry(metadata: Desktop3pMetadata): Desktop3pMetadataEntry | unde
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
function profileObject(path: string): Record<string, unknown> {
|
|
101
|
-
const parsed = JSON.parse(
|
|
108
|
+
const parsed = JSON.parse(readDesktopLibraryText(path)) as unknown;
|
|
102
109
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
103
110
|
throw new Error("picker_profile_unreadable");
|
|
104
111
|
}
|
|
@@ -175,10 +182,10 @@ function applyDesktopPickerProfileLocked(options: { proxyPort: number } & Deskto
|
|
|
175
182
|
const alreadySelected = selectedId === entryId;
|
|
176
183
|
const priorState = readPickerState(previousStatePath);
|
|
177
184
|
const previousAppliedId = alreadySelected ? priorState?.previousAppliedId ?? null : selectedId;
|
|
178
|
-
const oldMeta =
|
|
179
|
-
const oldProfile =
|
|
185
|
+
const oldMeta = readLibrarySnapshot(metaPath);
|
|
186
|
+
const oldProfile = readLibrarySnapshot(profile);
|
|
180
187
|
const backupPath = `${profile}.bak`;
|
|
181
|
-
const oldBackup =
|
|
188
|
+
const oldBackup = readLibrarySnapshot(backupPath);
|
|
182
189
|
const oldState = readFileSnapshot(previousStatePath);
|
|
183
190
|
const changed = !alreadySelected || !oldProfile.exists || oldProfile.content !== target;
|
|
184
191
|
if (!changed) return { ok: true, changed: false, path: profile };
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type DesktopPickerProfileOptions,
|
|
24
24
|
} from "./desktop-picker-profile";
|
|
25
25
|
import { resolveClaudeDesktopMode, observeClaudeDesktopMode } from "./desktop-first-party";
|
|
26
|
+
import { supportsDesktopPicker } from "./desktop-picker-platform";
|
|
26
27
|
|
|
27
28
|
/** Desktop no longer selects the picker profile, so removing the CA's trust cannot strand it. */
|
|
28
29
|
function profileReleased(profile: DesktopPickerProfileInspection): boolean {
|
|
@@ -77,6 +78,8 @@ export interface DesktopPickerControllerDeps {
|
|
|
77
78
|
persistPreference: (value: boolean) => boolean;
|
|
78
79
|
/** Bound picker CONNECT proxy port (Desktop's egress), or null when it is not running. */
|
|
79
80
|
proxyPort: () => number | null;
|
|
81
|
+
/** Bind a dormant relay on explicit enable, under the controller's mutation lock. */
|
|
82
|
+
ensureProxy?: () => Promise<void>;
|
|
80
83
|
configDir: string;
|
|
81
84
|
persistentAuthority?: { store?: PickerCaStore };
|
|
82
85
|
security?: SecurityRunner;
|
|
@@ -91,7 +94,7 @@ function profileOptions(deps: DesktopPickerControllerDeps): DesktopPickerProfile
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
function statusReasonForConfig(config: OcxConfig, platform: NodeJS.Platform, proxyBound: boolean): DesktopPickerReason {
|
|
94
|
-
if (platform
|
|
97
|
+
if (!supportsDesktopPicker(platform)) return "unsupported_platform";
|
|
95
98
|
if (resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) !== "first-party") return "not_first_party";
|
|
96
99
|
if (!claudeDesktopIntegrationEnabled(config)) return "integration_off";
|
|
97
100
|
if (config.claudeCode?.intercept?.picker === false) return "disabled";
|
|
@@ -101,11 +104,11 @@ function statusReasonForConfig(config: OcxConfig, platform: NodeJS.Platform, pro
|
|
|
101
104
|
|
|
102
105
|
function emptyStatus(config: OcxConfig, platform: NodeJS.Platform, reason: DesktopPickerReason): DesktopPickerStatus {
|
|
103
106
|
return {
|
|
104
|
-
desired: platform
|
|
107
|
+
desired: supportsDesktopPicker(platform)
|
|
105
108
|
&& claudeDesktopIntegrationEnabled(config)
|
|
106
109
|
&& resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) === "first-party"
|
|
107
110
|
&& config.claudeCode?.intercept?.picker !== false,
|
|
108
|
-
supported: platform
|
|
111
|
+
supported: supportsDesktopPicker(platform),
|
|
109
112
|
trust: "unknown",
|
|
110
113
|
profile: "absent",
|
|
111
114
|
listenerReady: false,
|
|
@@ -142,7 +145,7 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
142
145
|
const trust = trustOverride ?? runtime.trust;
|
|
143
146
|
const effective = runtime.effective && profileCurrent && trust === "trusted";
|
|
144
147
|
let reason: DesktopPickerReason;
|
|
145
|
-
if (platform
|
|
148
|
+
if (!supportsDesktopPicker(platform)) reason = "unsupported_platform";
|
|
146
149
|
else if (!runtime.desired) reason = statusReasonForConfig(deps.readConfig(), platform, deps.proxyPort() !== null);
|
|
147
150
|
else if (deps.proxyPort() === null) reason = "proxy_unavailable";
|
|
148
151
|
else if (trust !== "trusted") reason = "trust_pending";
|
|
@@ -178,7 +181,7 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
178
181
|
|
|
179
182
|
async function untrustCurrentCa(): Promise<boolean> {
|
|
180
183
|
const caPath = pickerCaCertPath(deps.configDir);
|
|
181
|
-
if (platform
|
|
184
|
+
if (!supportsDesktopPicker(platform) || !existsSync(caPath)) return true;
|
|
182
185
|
try {
|
|
183
186
|
const sha1 = pickerCaFingerprints(readFileSync(caPath, "utf8")).sha1;
|
|
184
187
|
return (await untrustPickerCa(caPath, sha1, deps.security, platform)).ok;
|
|
@@ -207,11 +210,11 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
207
210
|
return withTrustFailure({ ...runtimeStatus(observedTrust), reason }, trustFailed);
|
|
208
211
|
};
|
|
209
212
|
const check = (config: OcxConfig, includePreference: boolean): DesktopPickerReason | null => {
|
|
210
|
-
if (platform
|
|
213
|
+
if (!supportsDesktopPicker(platform)) return "unsupported_platform";
|
|
211
214
|
if (resolveClaudeDesktopMode(config, observeClaudeDesktopMode(config)) !== "first-party") return "mode_not_committed";
|
|
212
215
|
if (!claudeDesktopIntegrationEnabled(config)) return "integration_off";
|
|
213
216
|
if (includePreference && config.claudeCode?.intercept?.picker === false) return "disabled";
|
|
214
|
-
if (deps.proxyPort() === null) return "proxy_unavailable";
|
|
217
|
+
if (deps.proxyPort() === null && !deps.ensureProxy) return "proxy_unavailable";
|
|
215
218
|
return null;
|
|
216
219
|
};
|
|
217
220
|
|
|
@@ -224,6 +227,10 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
224
227
|
}
|
|
225
228
|
const second = check(deps.readConfig(), true);
|
|
226
229
|
if (second) return refuse(second);
|
|
230
|
+
if (deps.proxyPort() === null) {
|
|
231
|
+
try { await deps.ensureProxy?.(); } catch { return refuse("proxy_unavailable"); }
|
|
232
|
+
}
|
|
233
|
+
if (deps.proxyPort() === null) return refuse("proxy_unavailable");
|
|
227
234
|
|
|
228
235
|
let caPath: string;
|
|
229
236
|
let caSha1: string;
|
|
@@ -336,7 +343,7 @@ export async function removeDesktopPickerArtifacts(options: { configDir?: string
|
|
|
336
343
|
return { ok: false, residual };
|
|
337
344
|
}
|
|
338
345
|
const caPath = pickerCaCertPath(configDir);
|
|
339
|
-
if (platform
|
|
346
|
+
if (supportsDesktopPicker(platform) && existsSync(caPath)) {
|
|
340
347
|
try {
|
|
341
348
|
const sha1 = pickerCaFingerprints(readFileSync(caPath, "utf8")).sha1;
|
|
342
349
|
if (!(await untrustPickerCa(caPath, sha1, options.security, platform)).ok) residual.push("trust");
|
|
@@ -33,7 +33,9 @@ export interface RenderedDesktopModel extends DesktopProfileModel {
|
|
|
33
33
|
// renderDesktopProfile() converts it to a letter-first wire id because current
|
|
34
34
|
// Claude Desktop strips terminal dates before comparing model identities.
|
|
35
35
|
const DATE_ALIAS = /^claude-opus-4-8-(202[6-9]\d{4}|203[0-5]\d{4})$/;
|
|
36
|
-
|
|
36
|
+
// Desktop 2.31226's effort fallback recognizes fable prefixes, while Opus
|
|
37
|
+
// requires an exact canonical id. Keep the suffix so set_model stays distinct.
|
|
38
|
+
const PROFILE_WIRE_PREFIX = "claude-fable-5-";
|
|
37
39
|
const LEGACY_YEAR = 2026;
|
|
38
40
|
const LEGACY_DAY_COUNT = 365;
|
|
39
41
|
const ALIAS_FIRST_YEAR = 2026;
|
|
@@ -144,6 +146,13 @@ export function desktopProfileWireAlias(alias: string): string {
|
|
|
144
146
|
return `${PROFILE_WIRE_PREFIX}p${slot.toString(36).padStart(3, "0")}`;
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
/** Decode-only profile namespace emitted before the Desktop effort compatibility fix. */
|
|
150
|
+
export function legacyDesktopProfileWireAlias(alias: string): string {
|
|
151
|
+
return validDateAlias(alias)
|
|
152
|
+
? desktopProfileWireAlias(alias).replace(PROFILE_WIRE_PREFIX, "claude-opus-4-8-")
|
|
153
|
+
: alias;
|
|
154
|
+
}
|
|
155
|
+
|
|
147
156
|
export function parseDesktopProfile(value: unknown): DesktopProfile {
|
|
148
157
|
if (!isPlainObject(value)) throw new DesktopProfileError("must be an object");
|
|
149
158
|
// `appliedFingerprint`/`appliedAt` are written back by the apply route once a profile
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { chmodSync, lstatSync, mkdirSync,
|
|
1
|
+
import { chmodSync, lstatSync, mkdirSync, realpathSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { atomicWriteFile, getConfigDir } from "../config";
|
|
4
4
|
import { hardenSecretDir } from "../lib/windows-secret-acl";
|
|
5
|
-
import { isRecord, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
|
|
5
|
+
import { isRecord, readDesktopLibraryBytes, resolveDesktop3pConfigLibraryPath, SAFE_DESKTOP_PROFILE_ID, type Desktop3pMetadata } from "./desktop-3p-library";
|
|
6
6
|
import { DesktopStoreError, digest } from "./desktop-remote-store-state";
|
|
7
7
|
|
|
8
8
|
export const MAX_DESKTOP_METADATA_ENTRIES = 256;
|
|
@@ -43,7 +43,9 @@ export class StoreIO {
|
|
|
43
43
|
this.seenSizes.set(path, Math.max(counted, stat.size));
|
|
44
44
|
if (this.bytes > 8 * 1024 * 1024) throw new DesktopStoreError("unsafe");
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
// The shared verified regular-file read keeps a swapped FIFO from parking this
|
|
47
|
+
// open before the post-read identity recheck below can run.
|
|
48
|
+
const bytes = readDesktopLibraryBytes(path);
|
|
47
49
|
if (bytes.byteLength > maxBytes || identity(lstatSync(path)) !== identity(stat)) throw new DesktopStoreError("conflict");
|
|
48
50
|
const value: unknown = JSON.parse(bytes.toString("utf8"));
|
|
49
51
|
if (!isRecord(value)) throw new DesktopStoreError("unsafe");
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { OcxConfig } from "../types";
|
|
2
|
+
import { connect } from "node:net";
|
|
3
|
+
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
2
4
|
import {
|
|
3
5
|
readWindowsProxyBypassRegistry,
|
|
4
6
|
readWindowsSystemProxy,
|
|
@@ -8,6 +10,7 @@ import {
|
|
|
8
10
|
} from "../lib/windows-system-proxy";
|
|
9
11
|
import { inspectDesktopFirstParty, observeClaudeDesktopMode } from "./desktop-first-party";
|
|
10
12
|
import { desktopFirstPartyDesired } from "./first-party-settings";
|
|
13
|
+
import { inspectDesktopPickerProfile, type DesktopPickerProfileInspection } from "./desktop-picker-profile";
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Whether the Windows system proxy silently bypasses Desktop first-party mode.
|
|
@@ -17,7 +20,8 @@ import { desktopFirstPartyDesired } from "./first-party-settings";
|
|
|
17
20
|
* as `HTTPS_PROXY`/`HTTP_PROXY`. That inherited value takes precedence over the `env` block
|
|
18
21
|
* OpenCodex writes into the user's `~/.claude/settings.json` (only Claude Code managed settings
|
|
19
22
|
* override it), so Code-tab traffic goes to the system proxy and never reaches the intercept.
|
|
20
|
-
*
|
|
23
|
+
* A selected picker profile with an available fixed egress relay takes precedence instead.
|
|
24
|
+
* Otherwise routed subagent models fail. A system proxy such as Clash or
|
|
21
25
|
* v2rayN, with no bypass for the API host, is the common case.
|
|
22
26
|
*
|
|
23
27
|
* Observe-only: this reads the registry and never changes it. A PAC script and WPAD automatic
|
|
@@ -123,6 +127,39 @@ export interface DesktopSystemProxyDeps {
|
|
|
123
127
|
firstPartyState?: () => DesktopFirstPartySettingsState;
|
|
124
128
|
readSystemProxy?: () => WindowsSystemProxyResult;
|
|
125
129
|
readBypass?: () => WindowsProxyBypassValues | null;
|
|
130
|
+
inspectPickerProfile?: () => DesktopPickerProfileInspection;
|
|
131
|
+
pickerRelayAvailable?: (proxyUrl: string) => boolean | Promise<boolean>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Observe the selected endpoint even in a separate doctor process or a blind-only recovery relay. */
|
|
135
|
+
async function livePickerRelayAvailable(proxyUrl: string): Promise<boolean> {
|
|
136
|
+
const port = /^http:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(proxyUrl)?.[1];
|
|
137
|
+
if (!port || Number(port) > 65535) return false;
|
|
138
|
+
try {
|
|
139
|
+
// A listening TCP socket alone is not evidence of an OpenCodex CONNECT relay.
|
|
140
|
+
const response = await directLocalHttpFetch(proxyUrl, { signal: AbortSignal.timeout(1_000) });
|
|
141
|
+
if (response.status !== 405 || response.headers.get("allow") !== "CONNECT"
|
|
142
|
+
|| !(await response.text()).startsWith("OpenCodex Claude intercept proxy accepts only CONNECT tunnels.")) return false;
|
|
143
|
+
// The main intercept proxy has the same HTTP response but requires credentials the Desktop
|
|
144
|
+
// profile cannot send. A loopback CONNECT is denied before dialing or choosing any tunnel;
|
|
145
|
+
// 403 proves unauthenticated admission, whereas the credential-bearing proxy returns 407.
|
|
146
|
+
return await new Promise<boolean>(resolve => {
|
|
147
|
+
const socket = connect({ host: "127.0.0.1", port: Number(port) });
|
|
148
|
+
const finish = (available: boolean) => { clearTimeout(timer); socket.destroy(); resolve(available); };
|
|
149
|
+
const timer = setTimeout(() => finish(false), 1_000);
|
|
150
|
+
let head = "";
|
|
151
|
+
socket.once("error", () => finish(false));
|
|
152
|
+
socket.once("close", () => finish(false));
|
|
153
|
+
socket.once("connect", () => socket.write("CONNECT 127.0.0.1:1 HTTP/1.1\r\nHost: 127.0.0.1:1\r\n\r\n"));
|
|
154
|
+
socket.on("data", bytes => {
|
|
155
|
+
head += bytes.toString("latin1");
|
|
156
|
+
if (head.includes("\r\n\r\n")) finish(/^HTTP\/1\.[01] 403 Forbidden\r\n/.test(head));
|
|
157
|
+
else if (head.length > 8 * 1024) finish(false);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
} catch { // no-excuse-ok: catch -- failed relay probes retain the registry diagnostic.
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
126
163
|
}
|
|
127
164
|
|
|
128
165
|
function liveFirstPartyState(
|
|
@@ -137,15 +174,24 @@ function liveFirstPartyState(
|
|
|
137
174
|
}
|
|
138
175
|
}
|
|
139
176
|
|
|
140
|
-
/**
|
|
141
|
-
export function collectDesktopSystemProxy(
|
|
177
|
+
/** Registry reads run only on Windows with a Desktop first-party env and no verified picker relay. */
|
|
178
|
+
export async function collectDesktopSystemProxy(
|
|
142
179
|
config: Pick<OcxConfig, "claudeCode" | "clientIntegrations" | "port" | "runtimeRole">,
|
|
143
180
|
deps: DesktopSystemProxyDeps = {},
|
|
144
|
-
): DesktopSystemProxyAssessment {
|
|
181
|
+
): Promise<DesktopSystemProxyAssessment> {
|
|
145
182
|
const platform = deps.platform ?? process.platform;
|
|
146
183
|
if (platform !== "win32") return { kind: "not-applicable" };
|
|
147
184
|
const firstParty = (deps.firstPartyState ?? (() => liveFirstPartyState(config)))();
|
|
148
185
|
if (firstParty === "off") return { kind: "not-applicable" };
|
|
186
|
+
if (firstParty === "applied" && config.claudeCode?.intercept?.picker !== false) {
|
|
187
|
+
try {
|
|
188
|
+
const profile = (deps.inspectPickerProfile ?? (() => inspectDesktopPickerProfile({ platform })))();
|
|
189
|
+
if (profile.kind === "applied" && await (deps.pickerRelayAvailable ?? livePickerRelayAvailable)(profile.proxyUrl)) {
|
|
190
|
+
return { kind: "not-applicable" };
|
|
191
|
+
}
|
|
192
|
+
} catch { // no-excuse-ok: catch -- missing profile/relay evidence must not hide a registry conflict.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
149
195
|
return assessDesktopSystemProxy({
|
|
150
196
|
platform,
|
|
151
197
|
firstParty,
|
|
@@ -76,6 +76,17 @@ export function toolsToResponses(tools: unknown): Rec[] | undefined {
|
|
|
76
76
|
return out.length > 0 ? out : undefined;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The caller's typed Anthropic web tools (`web_search_*`, `web_fetch_*`), as declared. The
|
|
81
|
+
* Responses body has no slot for them — web_search becomes the hosted sidecar tool above and
|
|
82
|
+
* web_fetch is dropped — so an Anthropic destination receives them out of body, unchanged.
|
|
83
|
+
*/
|
|
84
|
+
export function declaredAnthropicServerTools(raw: unknown): Rec[] | undefined {
|
|
85
|
+
if (!isRec(raw) || !Array.isArray(raw.tools)) return undefined;
|
|
86
|
+
const tools = raw.tools.filter((t): t is Rec => isRec(t) && typeof t.type === "string" && /^web_(search|fetch)_\d+$/.test(t.type));
|
|
87
|
+
return tools.length > 0 ? tools : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
export function toolChoiceToResponses(choice: unknown, body: Rec): void {
|
|
80
91
|
if (!isRec(choice)) return;
|
|
81
92
|
if (choice.disable_parallel_tool_use === true) body.parallel_tool_calls = false;
|
package/src/claude/inbound.ts
CHANGED
|
@@ -24,7 +24,12 @@ import { createTranslatorBudget, type TranslatorBudget } from "../lib/translator
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The reference an Anthropic image block carries, as the Responses `image_url` string the bridge
|
|
29
|
+
* builds from it, or `null` for a source this route does not read. A file-backed source refuses.
|
|
30
|
+
* Shared with the direct Messages decoder (`src/protocols/codecs/messages-ir.ts`).
|
|
31
|
+
*/
|
|
32
|
+
export function anthropicImageBlockUrl(block: Rec): string | null {
|
|
28
33
|
const source = block.source;
|
|
29
34
|
if (!isRec(source)) return null;
|
|
30
35
|
if (source.type === "file") {
|
|
@@ -34,20 +39,25 @@ function imageBlockToInputImage(block: Rec): Rec | null {
|
|
|
34
39
|
}
|
|
35
40
|
if (source.type === "base64" && typeof source.data === "string") {
|
|
36
41
|
const media = typeof source.media_type === "string" ? source.media_type : "image/png";
|
|
37
|
-
return
|
|
42
|
+
return `data:${media};base64,${source.data}`;
|
|
38
43
|
}
|
|
39
44
|
if (source.type === "url" && typeof source.url === "string") {
|
|
40
|
-
return
|
|
45
|
+
return source.url;
|
|
41
46
|
}
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
function
|
|
50
|
+
function imageBlockToInputImage(block: Rec): Rec | null {
|
|
51
|
+
const url = anthropicImageBlockUrl(block);
|
|
52
|
+
return url === null ? null : { type: "input_image", image_url: url };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function documentTitle(block: Rec): string | undefined {
|
|
46
56
|
return typeof block.title === "string" && block.title.length > 0 ? block.title : undefined;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
/**
|
|
50
|
-
function
|
|
59
|
+
/** A base64 document's bytes as a `data:` URL plus its title, or `null` for any other source. */
|
|
60
|
+
export function anthropicDocumentData(block: Rec): { fileData: string; title?: string } | null {
|
|
51
61
|
const source = block.source;
|
|
52
62
|
if (!isRec(source) || source.type !== "base64") return null;
|
|
53
63
|
const mediaType = typeof source.media_type === "string" && source.media_type.length > 0
|
|
@@ -55,10 +65,17 @@ function documentBlockToInputFile(block: Rec): Rec | null {
|
|
|
55
65
|
: "application/octet-stream";
|
|
56
66
|
if (typeof source.data !== "string" || source.data.length === 0) return null;
|
|
57
67
|
const title = documentTitle(block);
|
|
68
|
+
return { fileData: `data:${mediaType};base64,${source.data}`, ...(title !== undefined ? { title } : {}) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** An Anthropic base64 document as the Responses `input_file` block that carries its bytes. */
|
|
72
|
+
function documentBlockToInputFile(block: Rec): Rec | null {
|
|
73
|
+
const document = anthropicDocumentData(block);
|
|
74
|
+
if (!document) return null;
|
|
58
75
|
return {
|
|
59
76
|
type: "input_file",
|
|
60
|
-
file_data:
|
|
61
|
-
...(title !== undefined ? { filename: title } : {}),
|
|
77
|
+
file_data: document.fileData,
|
|
78
|
+
...(document.title !== undefined ? { filename: document.title } : {}),
|
|
62
79
|
};
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -145,7 +162,7 @@ const NO_ELISION: SkillElisionContext = { callIds: new Set(), names: [] };
|
|
|
145
162
|
* `Base directory for this skill: <dir>/<skill-name>`. Stub that block when the
|
|
146
163
|
* directory basename matches a blocked skill.
|
|
147
164
|
*/
|
|
148
|
-
function maybeElideSkillText(text: string, names: readonly string[]): string {
|
|
165
|
+
export function maybeElideSkillText(text: string, names: readonly string[]): string {
|
|
149
166
|
if (names.length === 0 || text.length < SKILL_ELISION_MIN_CHARS) return text;
|
|
150
167
|
if (!text.startsWith(SKILL_TEXT_MARKER)) return text;
|
|
151
168
|
const pathStart = SKILL_TEXT_MARKER.length;
|
|
@@ -162,14 +179,14 @@ function maybeElideSkillText(text: string, names: readonly string[]): string {
|
|
|
162
179
|
+ "(claudeCode.blockedSkills). The skill is loaded; answer from general knowledge instead of citing the bundle.";
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
function skillElisionStub(callId: string): string {
|
|
182
|
+
export function skillElisionStub(callId: string): string {
|
|
166
183
|
return "[opencodex] Skill document bundle elided for routed models (claudeCode.blockedSkills). "
|
|
167
184
|
+ `The skill loaded, but its reference documents were removed to save context (call ${callId}). `
|
|
168
185
|
+ "Answer from general knowledge instead of citing the bundle.";
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
/** Collect Skill-tool call ids whose input names a blocked skill. */
|
|
172
|
-
function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly string[]): Set<string> {
|
|
189
|
+
export function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly string[]): Set<string> {
|
|
173
190
|
const ids = new Set<string>();
|
|
174
191
|
if (blocked.length === 0) return ids;
|
|
175
192
|
const needles = blocked.map(name => name.toLowerCase()).filter(name => name.length > 0);
|
|
@@ -222,7 +239,7 @@ export function anthropicBodyElidesBlockedSkill(body: unknown, cc?: Pick<OcxClau
|
|
|
222
239
|
* prefix and rotating the Desktop `prompt_cache_key` fallback below on every turn
|
|
223
240
|
* (#4148).
|
|
224
241
|
*/
|
|
225
|
-
function systemMessageText(content: unknown): string {
|
|
242
|
+
export function systemMessageText(content: unknown): string {
|
|
226
243
|
if (typeof content === "string") return content;
|
|
227
244
|
if (!Array.isArray(content)) return "";
|
|
228
245
|
const parts: string[] = [];
|
|
@@ -370,6 +387,39 @@ function canonicalJson(value: unknown): string {
|
|
|
370
387
|
/** Provenance of the generated prompt_cache_key (never serialized into the wire body). */
|
|
371
388
|
export type ClaudeCacheKeySource = "metadata" | "system" | null;
|
|
372
389
|
|
|
390
|
+
/**
|
|
391
|
+
* The translated lane places its own prompt-cache breakpoints, so the caller's are dropped;
|
|
392
|
+
* their TTL is the one thing worth keeping. When every caller breakpoint asks for `ttl: "1h"`
|
|
393
|
+
* (Claude Code on a subscription marks them so) this maps to `cacheRetention: "long"`. A mixed
|
|
394
|
+
* caller (1h prefix, 5m tail) stays on the default: "long" also makes the moving per-turn
|
|
395
|
+
* breakpoint 1h, which bills writes the caller asked to keep at the 5m rate at the 1h rate.
|
|
396
|
+
* Read out of body: the Responses body has no field for it.
|
|
397
|
+
*/
|
|
398
|
+
export function callerCacheRetention(raw: unknown): "long" | undefined {
|
|
399
|
+
if (!isRec(raw)) return undefined;
|
|
400
|
+
// Top-level automatic caching is a breakpoint too, and content blocks nest (tool_result,
|
|
401
|
+
// search_result, document sources), so every block container is walked. Tool inputs and
|
|
402
|
+
// schemas are caller data, not blocks: a `cache_control` key there is not a breakpoint.
|
|
403
|
+
const ttls: unknown[] = [];
|
|
404
|
+
const visit = (node: unknown, depth: number): void => {
|
|
405
|
+
if (depth > 16) return;
|
|
406
|
+
if (Array.isArray(node)) { for (const item of node) visit(item, depth + 1); return; }
|
|
407
|
+
if (!isRec(node)) return;
|
|
408
|
+
if (isRec(node.cache_control)) ttls.push(node.cache_control.ttl);
|
|
409
|
+
for (const [key, value] of Object.entries(node)) {
|
|
410
|
+
if (key !== "cache_control" && key !== "input" && key !== "input_schema"
|
|
411
|
+
&& (Array.isArray(value) || isRec(value))) visit(value, depth + 1);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
if (isRec(raw.cache_control)) ttls.push(raw.cache_control.ttl);
|
|
415
|
+
visit(raw.system, 0);
|
|
416
|
+
visit(raw.tools, 0);
|
|
417
|
+
visit(raw.messages, 0);
|
|
418
|
+
return ttls.length > 0 && ttls.every(ttl => ttl === "1h")
|
|
419
|
+
? "long"
|
|
420
|
+
: undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
373
423
|
export interface ClaudeInboundTranslation {
|
|
374
424
|
body: Rec;
|
|
375
425
|
cacheKeySource: ClaudeCacheKeySource;
|
|
@@ -410,19 +460,10 @@ function translateAnthropicRequest(
|
|
|
410
460
|
cc: OcxClaudeCodeConfig | undefined,
|
|
411
461
|
budget: TranslatorBudget,
|
|
412
462
|
): ClaudeInboundTranslation {
|
|
413
|
-
|
|
414
|
-
if (typeof raw.model !== "string" || raw.model.length === 0) {
|
|
415
|
-
throw new AnthropicRequestError("model is required");
|
|
416
|
-
}
|
|
417
|
-
if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
|
|
418
|
-
throw new AnthropicRequestError("messages must be a non-empty array");
|
|
419
|
-
}
|
|
463
|
+
assertAnthropicRequestShape(raw);
|
|
420
464
|
|
|
421
465
|
const input: Rec[] = [];
|
|
422
466
|
const nativeReasoningReplay = new Map<string, string>();
|
|
423
|
-
const systemParts: string[] = [];
|
|
424
|
-
const topLevelSystem = systemToInstructions(raw.system);
|
|
425
|
-
if (topLevelSystem !== undefined) systemParts.push(topLevelSystem);
|
|
426
467
|
const blockedNames = effectiveBlockedSkillNames(cc);
|
|
427
468
|
const elide: SkillElisionContext = {
|
|
428
469
|
callIds: blockedSkillCallIds(raw.messages, blockedNames),
|
|
@@ -451,36 +492,99 @@ function translateAnthropicRequest(
|
|
|
451
492
|
stream: raw.stream === true,
|
|
452
493
|
};
|
|
453
494
|
|
|
495
|
+
const system = anthropicSystemLayout(raw.system, cc);
|
|
496
|
+
if (system.instructions !== undefined) body.instructions = system.instructions;
|
|
497
|
+
if (system.dynamicNotice !== undefined) {
|
|
498
|
+
input.push({
|
|
499
|
+
type: "message",
|
|
500
|
+
role: "user",
|
|
501
|
+
content: [{ type: "input_text", text: system.dynamicNotice }],
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const tools = toolsToResponses(raw.tools);
|
|
506
|
+
if (tools) body.tools = tools;
|
|
507
|
+
const cacheKeySource = applyAnthropicSettings(raw, body, system.cacheSystem, tools);
|
|
508
|
+
return { body, cacheKeySource, nativeReasoningReplay };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function assertAnthropicRequestShape(raw: unknown): asserts raw is Rec & { model: string; messages: unknown[] } {
|
|
512
|
+
if (!isRec(raw)) throw new AnthropicRequestError("request body must be a JSON object");
|
|
513
|
+
if (typeof raw.model !== "string" || raw.model.length === 0) {
|
|
514
|
+
throw new AnthropicRequestError("model is required");
|
|
515
|
+
}
|
|
516
|
+
if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
|
|
517
|
+
throw new AnthropicRequestError("messages must be a non-empty array");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Where the top-level `system` lands on the bridge body, and what the cache-cohort key hashes. */
|
|
522
|
+
export interface AnthropicSystemLayout {
|
|
523
|
+
/** The Responses `instructions`, when there are any. */
|
|
524
|
+
instructions?: string;
|
|
525
|
+
/** A dynamic notice `stabilizePromptCache` moved out of the system text into a trailing user turn. */
|
|
526
|
+
dynamicNotice?: string;
|
|
527
|
+
cacheSystem: string | string[];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** The system text placement `claudeCode.stabilizePromptCache` decides; shared with the direct decoder. */
|
|
531
|
+
export function anthropicSystemLayout(system: unknown, cc: OcxClaudeCodeConfig | undefined): AnthropicSystemLayout {
|
|
532
|
+
const systemParts: string[] = [];
|
|
533
|
+
const topLevelSystem = systemToInstructions(system);
|
|
534
|
+
if (topLevelSystem !== undefined) systemParts.push(topLevelSystem);
|
|
454
535
|
const joinedSystem = systemParts.length > 0 ? systemParts.join("\n\n") : "";
|
|
455
536
|
const stabilizePromptCache = cc?.stabilizePromptCache === true;
|
|
456
537
|
// Desktop fallback hashes raw systemParts unless the caller opted into
|
|
457
538
|
// harness cleanup. Opt-in then hashes the same string as body.instructions.
|
|
458
|
-
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
content: [{ type: "input_text", text: stabilized.dynamicNotice }],
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
cacheSystem = stabilized.instructions;
|
|
477
|
-
} else {
|
|
478
|
-
body.instructions = joinedSystem;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
539
|
+
if (!joinedSystem) return { cacheSystem: systemParts };
|
|
540
|
+
if (!stabilizePromptCache) return { instructions: joinedSystem, cacheSystem: systemParts };
|
|
541
|
+
// Claude Code appends growing <total_tokens>N tokens left</total_tokens>
|
|
542
|
+
// footers (and occasional TaskCreate nudges) into system text. That churn
|
|
543
|
+
// breaks Muse/Go prefix cache on the Responses instructions prefix even
|
|
544
|
+
// when tools stay stable. Relocation is caller-opted, not inferred from
|
|
545
|
+
// a matching suffix or metadata.user_id.
|
|
546
|
+
const stabilized = stabilizeClaudeInstructionsForPromptCache(joinedSystem);
|
|
547
|
+
return {
|
|
548
|
+
...(stabilized.instructions ? { instructions: stabilized.instructions } : {}),
|
|
549
|
+
// Token footers are dropped; append nothing if no other notice remains.
|
|
550
|
+
...(stabilized.dynamicNotice ? { dynamicNotice: stabilized.dynamicNotice } : {}),
|
|
551
|
+
cacheSystem: stabilized.instructions,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
481
554
|
|
|
482
|
-
|
|
483
|
-
|
|
555
|
+
/**
|
|
556
|
+
* The request settings of a Messages body as a Responses body with no conversation: `input` is
|
|
557
|
+
* empty and there are no `instructions` or `tools`, but the prompt-cache key is still derived
|
|
558
|
+
* from the system text and translated tool catalog exactly as the bridge derives it. The
|
|
559
|
+
* Antigravity direct lane hands this to the Responses core while the conversation is decoded
|
|
560
|
+
* straight into the IR (`src/protocols/codecs/messages-ir.ts`); both bodies are completed by
|
|
561
|
+
* `applyAnthropicSettings`. It validates the same request shape, but a conversation error the
|
|
562
|
+
* bridge would report first is not looked for here: a caller that gets a throw must translate
|
|
563
|
+
* the full body to learn the bridge's answer.
|
|
564
|
+
*/
|
|
565
|
+
export function anthropicSettingsTranslation(
|
|
566
|
+
raw: unknown,
|
|
567
|
+
cc?: OcxClaudeCodeConfig,
|
|
568
|
+
): { body: Rec; cacheKeySource: ClaudeCacheKeySource } {
|
|
569
|
+
assertAnthropicRequestShape(raw);
|
|
570
|
+
const body: Rec = {
|
|
571
|
+
model: resolveInboundModel(raw.model, cc),
|
|
572
|
+
input: [],
|
|
573
|
+
store: false,
|
|
574
|
+
stream: raw.stream === true,
|
|
575
|
+
};
|
|
576
|
+
const system = anthropicSystemLayout(raw.system, cc);
|
|
577
|
+
const cacheKeySource = applyAnthropicSettings(raw, body, system.cacheSystem, toolsToResponses(raw.tools));
|
|
578
|
+
return { body, cacheKeySource };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Everything after the conversation and tool catalog: choice, limits, format, cache key, reasoning. */
|
|
582
|
+
function applyAnthropicSettings(
|
|
583
|
+
raw: Rec,
|
|
584
|
+
body: Rec,
|
|
585
|
+
cacheSystem: string | string[],
|
|
586
|
+
tools: Rec[] | undefined,
|
|
587
|
+
): ClaudeCacheKeySource {
|
|
484
588
|
toolChoiceToResponses(raw.tool_choice, body);
|
|
485
589
|
|
|
486
590
|
if (typeof raw.max_tokens === "number") body.max_output_tokens = raw.max_tokens;
|
|
@@ -505,7 +609,7 @@ function translateAnthropicRequest(
|
|
|
505
609
|
// per-session key with a bounded length/charset.
|
|
506
610
|
body.prompt_cache_key = userIdHash.slice(0, 32);
|
|
507
611
|
cacheKeySource = "metadata";
|
|
508
|
-
} else if (
|
|
612
|
+
} else if (typeof cacheSystem === "string" || cacheSystem.length > 0) {
|
|
509
613
|
// Claude Desktop sends no metadata.user_id (H1, devlog 130): without any key the
|
|
510
614
|
// ChatGPT/OpenAI backends reported cached_tokens:0 on every turn. Fall back to a
|
|
511
615
|
// cache-cohort hash (devlog 260712 B4 + Pro review 012): fingerprint what the
|
|
@@ -526,7 +630,7 @@ function translateAnthropicRequest(
|
|
|
526
630
|
version: 2,
|
|
527
631
|
model: body.model,
|
|
528
632
|
system: cacheSystem,
|
|
529
|
-
tools:
|
|
633
|
+
tools: tools ?? [],
|
|
530
634
|
}))
|
|
531
635
|
.digest("hex").slice(0, 32);
|
|
532
636
|
cacheKeySource = "system";
|
|
@@ -556,7 +660,7 @@ function translateAnthropicRequest(
|
|
|
556
660
|
body.include = ["reasoning.encrypted_content"];
|
|
557
661
|
}
|
|
558
662
|
|
|
559
|
-
return
|
|
663
|
+
return cacheKeySource;
|
|
560
664
|
}
|
|
561
665
|
|
|
562
666
|
/**
|
|
@@ -197,7 +197,9 @@ export interface AuthorityOptions {
|
|
|
197
197
|
permittedDnsNames?: readonly string[];
|
|
198
198
|
/** With permittedDnsNames: also exclude every IP address (default true). */
|
|
199
199
|
excludeAllIpAddresses?: boolean;
|
|
200
|
-
|
|
200
|
+
/** Restrict this authority to TLS server authentication; legacy callers keep their existing scope. */
|
|
201
|
+
serverAuthOnly?: boolean;
|
|
202
|
+
}
|
|
201
203
|
|
|
202
204
|
export function createCertificateAuthority(options: AuthorityOptions): LocalInterceptCa {
|
|
203
205
|
const validityDays = options.validityDays ?? CA_VALIDITY_DAYS;
|
|
@@ -217,6 +219,7 @@ export function createCertificateAuthority(options: AuthorityOptions): LocalInte
|
|
|
217
219
|
// keyCertSign | cRLSign
|
|
218
220
|
extension(OID.keyUsage, true, bitString(Uint8Array.of(0x06), 1)),
|
|
219
221
|
extension(OID.subjectKeyIdentifier, false, octetString(keyIdentifier(publicKey))),
|
|
222
|
+
...(options.serverAuthOnly ? [extension(OID.extendedKeyUsage, true, sequence(objectIdentifier(OID.serverAuth)))] : []),
|
|
220
223
|
...(options.permittedDnsNames?.length
|
|
221
224
|
? [extension(OID.nameConstraints, true, nameConstraints(options.permittedDnsNames, options.excludeAllIpAddresses !== false))]
|
|
222
225
|
: []),
|
|
@@ -234,6 +237,10 @@ export function createLocalInterceptCa(): LocalInterceptCa {
|
|
|
234
237
|
return createCertificateAuthority({ commonName: CLAUDE_INTERCEPT_CA_COMMON_NAME });
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
export function isServerAuthOnlyCertificate(certificate: X509Certificate): boolean {
|
|
241
|
+
return certificate.keyUsage?.length === 1 && certificate.keyUsage[0] === OID.serverAuth;
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
/**
|
|
238
245
|
* Test hook for trust-boundary suites: mint a self-signed authority carrying an arbitrary list of
|
|
239
246
|
* DER-encoded Extension items so adversarial profiles still bear a valid signature. Production
|
|
@@ -2,12 +2,13 @@ import { ensurePickerCa, type PickerCa } from "./picker-ca";
|
|
|
2
2
|
import { drainPendingPickerCaUntrust } from "./picker-ca-cleanup";
|
|
3
3
|
import type { PickerCaStore } from "./picker-ca-store";
|
|
4
4
|
import type { SecurityRunner } from "./picker-trust";
|
|
5
|
+
import { supportsDesktopPicker } from "../desktop-picker-platform";
|
|
5
6
|
|
|
6
7
|
/** Internal activation seam: finish legacy cleanup before a persistent issuer is usable. */
|
|
7
8
|
export async function preparePersistentPickerAuthority(options: {
|
|
8
9
|
configDir: string; store?: PickerCaStore; security?: SecurityRunner; platform: NodeJS.Platform;
|
|
9
10
|
}): Promise<PickerCa> {
|
|
10
|
-
if (options.platform
|
|
11
|
+
if (!supportsDesktopPicker(options.platform)) throw new Error("picker_ca_unsupported_platform");
|
|
11
12
|
const drain = () => drainPendingPickerCaUntrust(options.configDir, options.security, options.platform);
|
|
12
13
|
if (!await drain()) throw new Error("picker_ca_pending_untrust");
|
|
13
14
|
const ca = ensurePickerCa(options.configDir, { rotation: "startup", persistent: true, store: options.store });
|