@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Server } from "bun";
|
|
2
|
+
import { supportsDesktopPicker } from "../desktop-picker-platform";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
4
|
import { getConfigDir } from "../../config/paths";
|
|
4
5
|
import type { DesktopPickerController } from "../desktop-picker";
|
|
@@ -12,6 +13,7 @@ import { discardPickerCaKey } from "./picker-ca";
|
|
|
12
13
|
import { preparePersistentPickerAuthority } from "./picker-ca-startup";
|
|
13
14
|
import type { PickerCaStore } from "./picker-ca-store";
|
|
14
15
|
import type { PickerRouteInput } from "./picker-models";
|
|
16
|
+
import { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
|
|
15
17
|
import { createPickerRuntime, pickerDesired, type CreatePickerRuntimeOptions, type PickerRuntime } from "./picker-runtime";
|
|
16
18
|
import type { SecurityRunner } from "./picker-trust";
|
|
17
19
|
import { ensureClaudeInterceptProxyToken, readClaudeInterceptProxyToken } from "./proxy-auth";
|
|
@@ -28,7 +30,7 @@ import { buildClaudeInterceptEnv, migrateClaudeInterceptSettings } from "./setti
|
|
|
28
30
|
* proxy. Desktop also hands that proxy to the Claude Code processes it spawns, so the choice is per
|
|
29
31
|
* client: Claude Code (no User-Agent on CONNECT) trusts only the intercept CA and gets the
|
|
30
32
|
* api.anthropic.com intercept and nothing else; the app itself (a browser User-Agent) trusts only
|
|
31
|
-
* the
|
|
33
|
+
* the OS root store and never meets the api.anthropic.com intercept, and only its claude.ai
|
|
32
34
|
* tunnels may be terminated by the picker runtime (src/claude/intercept/picker-runtime.ts).
|
|
33
35
|
*/
|
|
34
36
|
|
|
@@ -41,25 +43,7 @@ const PICKER_AUTHORITY_ERROR_CODES = new Set([
|
|
|
41
43
|
"picker_ca_unsupported_platform",
|
|
42
44
|
]);
|
|
43
45
|
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
export function claudeInterceptEnabled(config: Pick<OcxConfig, "claudeCode" | "runtimeRole">): boolean {
|
|
47
|
-
if (config.runtimeRole === "client") return false;
|
|
48
|
-
if (config.claudeCode?.enabled === false) return false;
|
|
49
|
-
return config.claudeCode?.intercept?.enabled !== false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function claudeInterceptProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
53
|
-
const configured = config.claudeCode?.intercept?.port;
|
|
54
|
-
if (typeof configured === "number" && Number.isInteger(configured) && configured >= 1 && configured <= 65535) return configured;
|
|
55
|
-
return publicPort + CLAUDE_INTERCEPT_PORT_OFFSET;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Desktop's egress proxy for picker mode: the port after the intercept proxy (before it at 65535). */
|
|
59
|
-
export function claudePickerProxyPort(config: Pick<OcxConfig, "claudeCode">, publicPort: number): number {
|
|
60
|
-
const interceptPort = claudeInterceptProxyPort(config, publicPort);
|
|
61
|
-
return interceptPort < 65535 ? interceptPort + 1 : interceptPort - 1;
|
|
62
|
-
}
|
|
46
|
+
export { CLAUDE_INTERCEPT_PORT_OFFSET, claudeInterceptEnabled, claudeInterceptProxyPort, claudePickerProxyPort } from "./port-policy";
|
|
63
47
|
|
|
64
48
|
export type ClaudeInterceptOutcome = { ok: true; state: ClaudeInterceptState } | { ok: false; reason: "disabled" | "client_role" | "ephemeral_port" | "port_in_use" | "stopped" | "failed"; port?: number; message?: string };
|
|
65
49
|
|
|
@@ -144,7 +128,7 @@ export interface StartClaudeInterceptOptions<T> {
|
|
|
144
128
|
createPicker?: (options: CreatePickerRuntimeOptions) => PickerRuntime;
|
|
145
129
|
/** Test seam: bind real CONNECT handlers on kernel-assigned ports without probe-and-release races. */
|
|
146
130
|
startProxy?: typeof startConnectProxy;
|
|
147
|
-
/** Test seams: the
|
|
131
|
+
/** Test seams: the platform trust runner and platform for the picker runtime and controller. */
|
|
148
132
|
pickerCaStore?: PickerCaStore;
|
|
149
133
|
pickerSecurity?: SecurityRunner;
|
|
150
134
|
pickerPlatform?: NodeJS.Platform;
|
|
@@ -167,18 +151,6 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
167
151
|
const startProxy = options.startProxy ?? startConnectProxy;
|
|
168
152
|
const authToken = ensureClaudeInterceptProxyToken(configDir);
|
|
169
153
|
const leaf = issueLocalInterceptLeaf(ca, CLAUDE_INTERCEPT_HOSTS);
|
|
170
|
-
// Refresh an env we already own (e.g. a pre-auth proxy URL left by an upgrade) before the
|
|
171
|
-
// authenticated proxy takes over the port — a plain `ocx start` after an update would
|
|
172
|
-
// otherwise 407 every CONNECT until the next `ocx ensure` or apply. Only `stale` state is
|
|
173
|
-
// rewritten, so installs that never applied first-party are untouched.
|
|
174
|
-
try {
|
|
175
|
-
const proxyPort = claudeInterceptProxyPort(options.config, options.publicPort);
|
|
176
|
-
migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxyPort, claudeInterceptCaCertPath(configDir), authToken));
|
|
177
|
-
} catch (error) {
|
|
178
|
-
// A skipped rewrite degrades to the pre-migration behaviour and ensure/apply retries it,
|
|
179
|
-
// but silence here leaves upgraded clients hitting 407 with no recorded cause.
|
|
180
|
-
console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
-
}
|
|
182
154
|
const listener = startClaudeInterceptListener<T>({
|
|
183
155
|
leaf,
|
|
184
156
|
dispatch: options.dispatch,
|
|
@@ -203,15 +175,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
203
175
|
await listener.stop(true);
|
|
204
176
|
throw new ClaudeInterceptProxyBindError(error, claudeInterceptProxyPort(options.config, options.publicPort));
|
|
205
177
|
}
|
|
178
|
+
// Publish only a successfully bound endpoint, using the handle's actual port. A failed
|
|
179
|
+
// fixed-port bind leaves settings untouched; an ephemeral test binder never publishes :0
|
|
180
|
+
// or a probed port another process can steal. Only stale, owned env blocks are rewritten.
|
|
181
|
+
try {
|
|
182
|
+
migrateClaudeInterceptSettings(buildClaudeInterceptEnv(proxy.port, claudeInterceptCaCertPath(configDir), authToken));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// Ensure/apply retries a skipped migration; upgraded clients otherwise keep hitting 407.
|
|
185
|
+
console.warn(`[claude-intercept] settings migration skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
}
|
|
206
187
|
// Widened on purpose: assignments happen in nested awaits the catch below must still see.
|
|
207
188
|
let picker = null as PickerRuntime | null;
|
|
208
189
|
let pickerProxy = null as ConnectProxyHandle | null;
|
|
209
190
|
let controller = null as DesktopPickerController | null;
|
|
210
191
|
let pickerProxyLive = false;
|
|
192
|
+
let stopped = false;
|
|
211
193
|
let pickerReason: ClaudeInterceptState["pickerReason"] = null;
|
|
212
194
|
let pickerFailurePort: number | undefined;
|
|
213
195
|
try {
|
|
214
|
-
if (options.loadPickerRoutes && (options.pickerPlatform ?? process.platform)
|
|
196
|
+
if (options.loadPickerRoutes && supportsDesktopPicker(options.pickerPlatform ?? process.platform)) {
|
|
215
197
|
// Remove any legacy plaintext key before inspecting or activating picker state.
|
|
216
198
|
discardPickerCaKey(configDir);
|
|
217
199
|
const { inspectDesktopPickerProfile } = await import("../desktop-picker-profile");
|
|
@@ -225,11 +207,15 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
225
207
|
});
|
|
226
208
|
const pickerProfileApplied = pickerProfile.kind === "applied";
|
|
227
209
|
let pickerBlocked = false;
|
|
210
|
+
let pickerWired = false;
|
|
228
211
|
try {
|
|
229
212
|
const { observeClaudeDesktopMode, resolveClaudeDesktopMode } = await import("../desktop-first-party");
|
|
230
213
|
const fresh = options.config;
|
|
231
214
|
const platform = options.pickerPlatform ?? process.platform;
|
|
232
|
-
|
|
215
|
+
const pickerRequired = pickerProfileApplied || pickerDesired(fresh, resolveClaudeDesktopMode(fresh, observeClaudeDesktopMode(fresh)), platform);
|
|
216
|
+
// Preserve macOS's dormant relay; Windows defers binding until enable or profile recovery.
|
|
217
|
+
pickerWired = platform !== "win32" || pickerRequired;
|
|
218
|
+
if (pickerRequired) {
|
|
233
219
|
await preparePersistentPickerAuthority({ configDir, store: options.pickerCaStore, security: options.pickerSecurity, platform });
|
|
234
220
|
}
|
|
235
221
|
} catch (error) {
|
|
@@ -276,63 +262,76 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
276
262
|
const runtime = picker;
|
|
277
263
|
const interceptPort = listener.port!;
|
|
278
264
|
const pickerPort = claudePickerProxyPort(options.config, options.publicPort);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
265
|
+
const bindPickerProxy = async (): Promise<void> => {
|
|
266
|
+
if (stopped || pickerProxyLive) return;
|
|
267
|
+
try {
|
|
268
|
+
pickerProxy = await startProxy(pickerPort, {
|
|
269
|
+
interceptPort,
|
|
270
|
+
// No authToken: Desktop's egressProxyUrl cannot present proxy credentials, so this
|
|
271
|
+
// listener stays an unauthenticated loopback relay until the profile format can carry
|
|
272
|
+
// one. The intercept proxy above is the credential-bearing hop.
|
|
273
|
+
// No host list here: the choice below depends on which client opened the tunnel.
|
|
274
|
+
interceptHosts: [],
|
|
275
|
+
selectTunnel: (host, port, request) => {
|
|
276
|
+
// Desktop hands its pinned egress proxy to the Claude Code processes it spawns, so their
|
|
277
|
+
// api.anthropic.com traffic arrives here too and gets the same intercept as on the Claude
|
|
278
|
+
// Code proxy. Those processes trust only the intercept CA, so the picker never terminates
|
|
279
|
+
// their claude.ai tunnels; only the app's own (browser) CONNECTs reach the picker.
|
|
280
|
+
if (!isBrowserConnect(request)) {
|
|
281
|
+
return port === 443 && (CLAUDE_INTERCEPT_HOSTS as readonly string[]).includes(host.toLowerCase())
|
|
282
|
+
? { kind: "intercept", port: interceptPort }
|
|
283
|
+
: { kind: "blind" };
|
|
284
|
+
}
|
|
285
|
+
return runtime.selectTunnel(host, port);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
if (stopped) { await pickerProxy.close(); return; }
|
|
289
|
+
pickerProxyLive = true;
|
|
290
|
+
pickerReason = null;
|
|
291
|
+
pickerFailurePort = undefined;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// Picker mode is optional: a busy port leaves the intercept pair running without it.
|
|
294
|
+
// The selected profile row survives; a dormant enable can retry after the port is free.
|
|
295
|
+
pickerReason = error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE" ? "port_in_use" : "failed";
|
|
296
|
+
if (pickerReason === "port_in_use") pickerFailurePort = pickerPort;
|
|
297
|
+
console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
if (pickerWired) {
|
|
302
|
+
try { await bindPickerProxy(); }
|
|
303
|
+
catch { await runtime.stop(); picker = null; }
|
|
304
|
+
}
|
|
305
|
+
if (picker) {
|
|
306
|
+
// Dynamic: the controller reaches desktop-first-party, which imports this module.
|
|
307
|
+
const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
|
|
308
|
+
import("../desktop-picker"),
|
|
309
|
+
import("../../config"),
|
|
310
|
+
createPickerPreferenceWriter(options.config),
|
|
311
|
+
]);
|
|
312
|
+
controller = createDesktopPickerController({
|
|
313
|
+
runtime: picker,
|
|
314
|
+
readConfig: loadConfig,
|
|
315
|
+
persistPreference,
|
|
316
|
+
proxyPort: () => (pickerProxyLive ? pickerProxy?.port ?? null : null),
|
|
317
|
+
ensureProxy: async () => {
|
|
318
|
+
await bindPickerProxy();
|
|
319
|
+
if (!pickerProxyLive) return;
|
|
320
|
+
await runtime.start();
|
|
321
|
+
if (!stopped && activeController === controller) activePicker = runtime;
|
|
298
322
|
},
|
|
323
|
+
configDir,
|
|
324
|
+
persistentAuthority: { store: options.pickerCaStore },
|
|
325
|
+
...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
|
|
326
|
+
...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
|
|
299
327
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.warn(`⚠ Claude Desktop picker proxy could not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
308
|
-
await runtime.stop();
|
|
309
|
-
picker = null;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (picker) {
|
|
313
|
-
// Dynamic: the controller reaches desktop-first-party, which imports this module.
|
|
314
|
-
const [{ createDesktopPickerController }, { loadConfig }, persistPreference] = await Promise.all([
|
|
315
|
-
import("../desktop-picker"),
|
|
316
|
-
import("../../config"),
|
|
317
|
-
createPickerPreferenceWriter(options.config),
|
|
318
|
-
]);
|
|
319
|
-
const boundProxy = pickerProxy;
|
|
320
|
-
controller = createDesktopPickerController({
|
|
321
|
-
runtime: picker,
|
|
322
|
-
readConfig: loadConfig,
|
|
323
|
-
persistPreference,
|
|
324
|
-
proxyPort: () => (pickerProxyLive && boundProxy ? boundProxy.port : null),
|
|
325
|
-
configDir,
|
|
326
|
-
persistentAuthority: { store: options.pickerCaStore },
|
|
327
|
-
...(options.pickerSecurity ? { security: options.pickerSecurity } : {}),
|
|
328
|
-
...(options.pickerPlatform ? { platform: options.pickerPlatform } : {}),
|
|
329
|
-
});
|
|
330
|
-
await picker.start();
|
|
331
|
-
// Restore inspects the retained identity without installing trust. Explicit enable/trust
|
|
332
|
-
// owns consent when trust was revoked or legacy migration introduced a new authority.
|
|
333
|
-
if (pickerProfileApplied && controller) {
|
|
334
|
-
void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
|
|
335
|
-
.catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
328
|
+
if (pickerWired) await picker.start();
|
|
329
|
+
// Restore inspects the retained identity without installing trust. Explicit enable/trust
|
|
330
|
+
// owns consent when trust was revoked or legacy migration introduced a new authority.
|
|
331
|
+
if (pickerProfileApplied && controller) {
|
|
332
|
+
void controller.enable({ persist: false, context: "server", allowTrustPrompt: false })
|
|
333
|
+
.catch(error => console.warn(`⚠ Claude Desktop picker restore failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
334
|
+
}
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
337
|
}
|
|
@@ -348,21 +347,25 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
|
|
|
348
347
|
throw error;
|
|
349
348
|
}
|
|
350
349
|
const state: ClaudeInterceptState = {
|
|
351
|
-
pickerReason,
|
|
352
|
-
pickerFailurePort,
|
|
350
|
+
get pickerReason() { return pickerReason; },
|
|
351
|
+
get pickerFailurePort() { return pickerFailurePort; },
|
|
353
352
|
proxyPort: proxy.port,
|
|
354
353
|
caCertPath: claudeInterceptCaCertPath(configDir),
|
|
355
|
-
pickerProxyPort
|
|
354
|
+
get pickerProxyPort() { return pickerProxyLive ? pickerProxy?.port ?? null : null; },
|
|
356
355
|
};
|
|
357
356
|
activeState = state;
|
|
358
|
-
activePicker = picker;
|
|
357
|
+
activePicker = pickerProxyLive ? picker : null;
|
|
359
358
|
activeController = controller;
|
|
360
359
|
const ownPicker = picker;
|
|
361
360
|
const ownController = controller;
|
|
362
361
|
return {
|
|
363
362
|
...state,
|
|
363
|
+
get pickerProxyPort() { return state.pickerProxyPort; },
|
|
364
|
+
get pickerReason() { return state.pickerReason; },
|
|
365
|
+
get pickerFailurePort() { return state.pickerFailurePort; },
|
|
364
366
|
listener,
|
|
365
367
|
stop: async () => {
|
|
368
|
+
stopped = true;
|
|
366
369
|
if (activeState === state) activeState = null;
|
|
367
370
|
if (activePicker === ownPicker) activePicker = null;
|
|
368
371
|
if (activeController === ownController) activeController = null;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, renameSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { atomicWriteFileNoFollow } from "../config/atomic-write";
|
|
5
|
+
import { hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
|
|
6
|
+
import { assertNotRealClaudeConfigUnderTest } from "../lib/test-home-guard";
|
|
7
|
+
import { claudeConfigDir } from "./auth-detect";
|
|
8
|
+
import { claudeTierDisplayName, type ClaudeTierModels } from "./context-windows";
|
|
9
|
+
|
|
10
|
+
const tiers = ["opus", "sonnet", "haiku", "fable"] as const;
|
|
11
|
+
|
|
12
|
+
function readSettings(path: string): { raw: string; exists: boolean; doc: Record<string, unknown>; env: Record<string, unknown> } {
|
|
13
|
+
let raw = "";
|
|
14
|
+
let exists = false;
|
|
15
|
+
try {
|
|
16
|
+
const stat = lstatSync(path);
|
|
17
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024) throw new Error("settings_unreadable");
|
|
18
|
+
raw = readFileSync(path, "utf8");
|
|
19
|
+
exists = true;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw new Error("settings_unreadable");
|
|
22
|
+
}
|
|
23
|
+
let doc: Record<string, unknown>;
|
|
24
|
+
try {
|
|
25
|
+
const parsed: unknown = exists ? JSON.parse(raw) : {};
|
|
26
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
27
|
+
doc = parsed as Record<string, unknown>;
|
|
28
|
+
if (doc.env !== undefined && (!doc.env || typeof doc.env !== "object" || Array.isArray(doc.env))) throw new Error();
|
|
29
|
+
} catch { throw new Error("settings_unreadable"); }
|
|
30
|
+
return { raw, exists, doc, env: (doc.env ?? {}) as Record<string, unknown> };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function snapshotFingerprint(current: ReturnType<typeof readSettings>, models: ClaudeTierModels | undefined) {
|
|
34
|
+
const target = selectedEnv(models, current.env);
|
|
35
|
+
return createHash("sha256").update(JSON.stringify([current.exists, current.raw, target])).digest("hex");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Test seams for edits at publication boundaries; never configured through the API. */
|
|
39
|
+
export interface ModelDefaultWriteHooks {
|
|
40
|
+
beforeCapture?: () => void;
|
|
41
|
+
beforePublish?: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ModelDefaultsRecoveryError extends Error {
|
|
45
|
+
constructor(readonly backupPath: string, options?: ErrorOptions) {
|
|
46
|
+
super("settings_recovery_required", options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Unset tiers stay untouched; only a label matching its current alias is managed. */
|
|
51
|
+
function selectedEnv(models: ClaudeTierModels | undefined, current: Record<string, unknown>): Record<string, string | null> {
|
|
52
|
+
const env: Record<string, string | null> = {};
|
|
53
|
+
for (const tier of tiers) {
|
|
54
|
+
const slot = `ANTHROPIC_DEFAULT_${tier.toUpperCase()}_MODEL`;
|
|
55
|
+
const name = `${slot}_NAME`;
|
|
56
|
+
const value = models?.[tier]?.trim();
|
|
57
|
+
if (!value) continue;
|
|
58
|
+
env[slot] = value;
|
|
59
|
+
const previous = typeof current[slot] === "string" ? claudeTierDisplayName(current[slot]) : undefined;
|
|
60
|
+
const managed = previous !== undefined && current[name] === previous;
|
|
61
|
+
// Custom (including empty/non-string) labels are user-owned. Exact pairing
|
|
62
|
+
// follows the launcher label convention; the name alone proves no ownership.
|
|
63
|
+
if (!Object.hasOwn(current, name) || managed) {
|
|
64
|
+
const label = claudeTierDisplayName(value);
|
|
65
|
+
if (label !== undefined) env[name] = label;
|
|
66
|
+
else if (managed) env[name] = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return env;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function previewClaudeModelDefaults(models: ClaudeTierModels | undefined, configDir = claudeConfigDir()) {
|
|
73
|
+
const path = join(configDir, "settings.json");
|
|
74
|
+
const current = readSettings(path);
|
|
75
|
+
const target = selectedEnv(models, current.env);
|
|
76
|
+
const changes = Object.entries(target).filter(([key, value]) => current.env[key] !== value)
|
|
77
|
+
// The existing preview wire shape uses an empty replacement for label removal.
|
|
78
|
+
.map(([key, value]) => ({ key, before: typeof current.env[key] === "string" ? current.env[key] as string : null, after: value ?? "" }));
|
|
79
|
+
const fingerprint = snapshotFingerprint(current, models);
|
|
80
|
+
return { path, fingerprint, changes };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function applyClaudeModelDefaults(
|
|
84
|
+
models: ClaudeTierModels | undefined,
|
|
85
|
+
fingerprint: string,
|
|
86
|
+
configDir = claudeConfigDir(),
|
|
87
|
+
backupDir = join(configDir, "model-default-backups"),
|
|
88
|
+
hooks: ModelDefaultWriteHooks = {},
|
|
89
|
+
) {
|
|
90
|
+
const preview = previewClaudeModelDefaults(models, configDir);
|
|
91
|
+
if (preview.fingerprint !== fingerprint) throw new Error("settings_changed");
|
|
92
|
+
if (!preview.changes.length) return { changed: false, path: preview.path };
|
|
93
|
+
assertNotRealClaudeConfigUnderTest(configDir, preview.path);
|
|
94
|
+
const current = readSettings(preview.path);
|
|
95
|
+
const check = snapshotFingerprint(current, models);
|
|
96
|
+
if (check !== fingerprint) throw new Error("settings_changed");
|
|
97
|
+
mkdirSync(configDir, { recursive: true });
|
|
98
|
+
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
99
|
+
const recoveryDir = mkdtempSync(join(backupDir, "apply-"));
|
|
100
|
+
if (process.platform === "win32") hardenSecretDir(recoveryDir, { required: true });
|
|
101
|
+
const backupPath = join(recoveryDir, "settings.json");
|
|
102
|
+
const staged = join(configDir, `.model-defaults-${randomUUID()}.tmp`);
|
|
103
|
+
let captured = false;
|
|
104
|
+
let published = false;
|
|
105
|
+
try {
|
|
106
|
+
const env = { ...current.env };
|
|
107
|
+
for (const [key, value] of Object.entries(selectedEnv(models, current.env))) {
|
|
108
|
+
if (value === null) delete env[key];
|
|
109
|
+
else env[key] = value;
|
|
110
|
+
}
|
|
111
|
+
current.doc.env = env;
|
|
112
|
+
atomicWriteFileNoFollow(staged, `${JSON.stringify(current.doc, null, 2)}\n`);
|
|
113
|
+
// Verify same-volume exclusive publication before moving the shared file.
|
|
114
|
+
const probe = join(recoveryDir, "link-probe");
|
|
115
|
+
linkSync(staged, probe);
|
|
116
|
+
unlinkSync(probe);
|
|
117
|
+
hooks.beforeCapture?.();
|
|
118
|
+
if (current.exists) {
|
|
119
|
+
// Move the actual destination, not a stale in-memory copy. A same-filesystem
|
|
120
|
+
// move captures late edits; cross-device failures leave the original alone.
|
|
121
|
+
try { renameSync(preview.path, backupPath); } catch (error) {
|
|
122
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") throw new Error("settings_changed");
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
captured = true;
|
|
126
|
+
const capturedEntry = lstatSync(backupPath);
|
|
127
|
+
if (!capturedEntry.isFile() || capturedEntry.isSymbolicLink()) throw new Error("settings_changed");
|
|
128
|
+
if (process.platform === "win32") hardenSecretPath(backupPath, { required: true });
|
|
129
|
+
if (snapshotFingerprint(readSettings(backupPath), models) !== fingerprint) throw new Error("settings_changed");
|
|
130
|
+
}
|
|
131
|
+
hooks.beforePublish?.();
|
|
132
|
+
// A hard-link publish is exclusive on POSIX and Windows: never replace a
|
|
133
|
+
// document created by another writer during the brief move-aside interval.
|
|
134
|
+
try { linkSync(staged, preview.path); } catch (error) {
|
|
135
|
+
if ((error as NodeJS.ErrnoException).code === "EEXIST") throw new Error("settings_changed");
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
published = true;
|
|
139
|
+
return { changed: true, path: preview.path, ...(captured ? { backupPath } : {}) };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (captured && !published) {
|
|
142
|
+
try { linkSync(backupPath, preview.path); } catch (restoreError) {
|
|
143
|
+
// A newer destination wins. Retain the captured document privately;
|
|
144
|
+
// never delete the winner or overwrite it as an automatic rollback.
|
|
145
|
+
throw new ModelDefaultsRecoveryError(backupPath, { cause: restoreError });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
} finally {
|
|
150
|
+
// The private stage is also present at the destination after successful
|
|
151
|
+
// linking; cleanup failure must not turn a committed apply into failure.
|
|
152
|
+
try { unlinkSync(staged); } catch { /* private residue, no shared-file cleanup */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/claude/model-info.ts
CHANGED
|
@@ -103,19 +103,19 @@ function modelInfo(id: string, displayName: string, ladder: readonly string[], i
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Which id family the discovery list carries (devlog 050): Claude Code (CLI)
|
|
106
|
-
* gets readable `claude
|
|
107
|
-
* `claude-
|
|
106
|
+
* gets readable `ocx-claude-*` ids; Claude Desktop keeps its profile's
|
|
107
|
+
* `claude-fable-5-p<code>` ids (legacy no-profile callers use hash aliases). Both families
|
|
108
108
|
* decode in resolveInboundModel regardless of the style served here.
|
|
109
109
|
*/
|
|
110
110
|
export type AnthropicIdStyle = "desktop3p" | "readable";
|
|
111
111
|
|
|
112
|
-
/** Build
|
|
112
|
+
/** Build discovery rows; a Desktop resolver can return undefined to withhold an unbound ID. */
|
|
113
113
|
export function buildAnthropicModelInfos(
|
|
114
114
|
nativeSlugs: readonly string[],
|
|
115
115
|
routedModels: readonly CatalogModel[],
|
|
116
116
|
auto: AutoContextMode = AUTO_CONTEXT_OFF,
|
|
117
117
|
idStyle: AnthropicIdStyle = "desktop3p",
|
|
118
|
-
aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
|
|
118
|
+
aliasForRoute: (provider: string, modelId: string) => string | undefined = desktop3pAlias,
|
|
119
119
|
nativeContextCap?: NativeContextLimitsInput,
|
|
120
120
|
fastMode?: boolean,
|
|
121
121
|
// Presence is the feature gate: the caller passes undefined when `fastRows` is off, so a
|
|
@@ -144,7 +144,7 @@ export function buildAnthropicModelInfos(
|
|
|
144
144
|
? claudeCodeAlias(m.provider, listed)
|
|
145
145
|
: aliasForRoute(m.provider, m.id);
|
|
146
146
|
}),
|
|
147
|
-
]);
|
|
147
|
+
].filter((id): id is string => typeof id === "string" && id.length > 0));
|
|
148
148
|
// [1m] picker variant (devlog 260712 B1; widened in devlog/_plan/261009_claude_1m_default/020):
|
|
149
149
|
// Claude Code accounts exactly 1M for ids carrying the marker (2.1.207 binary: /\[1m\]/i -> 1e6,
|
|
150
150
|
// compaction preserved). A discovery client may lack CLAUDE_CODE_AUTO_COMPACT_WINDOW, so the
|
|
@@ -201,7 +201,7 @@ export function buildAnthropicModelInfos(
|
|
|
201
201
|
};
|
|
202
202
|
for (const slug of nativeSlugs) {
|
|
203
203
|
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);
|
|
204
|
-
if (seen.has(id)) continue;
|
|
204
|
+
if (!id || seen.has(id)) continue;
|
|
205
205
|
seen.add(id);
|
|
206
206
|
const nativeWindow = nativeOpenAiContextWindow(slug, nativeContextCap);
|
|
207
207
|
const nativeMaxInput = nativeOpenAiMaxInputTokens(slug, nativeContextCap);
|
|
@@ -228,7 +228,7 @@ export function buildAnthropicModelInfos(
|
|
|
228
228
|
const id = idStyle === "readable"
|
|
229
229
|
? claudeCodeAlias(m.provider, listedModelId)
|
|
230
230
|
: aliasForRoute(m.provider, m.id);
|
|
231
|
-
if (seen.has(id)) continue;
|
|
231
|
+
if (!id || seen.has(id)) continue;
|
|
232
232
|
seen.add(id);
|
|
233
233
|
const groupStart = out.length;
|
|
234
234
|
const ladder = Array.isArray(m.reasoningEfforts) ? m.reasoningEfforts : [];
|