@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
package/src/oauth/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { MuseLoginError, museLoginErrorMessage } from "./meta-muse-errors";
|
|
1
2
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
3
|
+
import { mapFill } from "../providers/resolved-model-policy-merge";
|
|
2
4
|
import { initializeProviderModelSelection } from "../providers/initial-model-selection";
|
|
3
5
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
4
6
|
import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, observeInitialConfigState, saveConfig } from "../config";
|
|
@@ -48,8 +50,9 @@ import { validateDevinApiBaseUrl } from "./devin/api-base";
|
|
|
48
50
|
import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
|
|
49
51
|
import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
|
|
50
52
|
import { loginMetaMuse, refreshMetaMuseToken } from "./meta-muse";
|
|
53
|
+
import { DSH_ACCOUNT_AUTH } from "./dsh";
|
|
51
54
|
import { loginOrcaRouter, orcaRouterInferenceBaseUrl, refreshOrcaRouterKey } from "./orcarouter";
|
|
52
|
-
import {
|
|
55
|
+
import { ANTIGRAVITY_DISCOVERY_UA } from "../adapters/google-antigravity-wire";
|
|
53
56
|
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
54
57
|
import { apiKeyPoolEntryId, sanitizeApiKeyValue } from "../providers/api-keys";
|
|
55
58
|
import { effectiveGoogleMode, getProviderRegistryEntry, mergeRegistryStaticHeaders, providerMatchesRegistryTransport } from "../providers/registry";
|
|
@@ -57,9 +60,12 @@ import { providerModelsUrl, resolveProviderModelDiscoveryUrl } from "../provider
|
|
|
57
60
|
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
58
61
|
import { detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
|
|
59
62
|
import { logOAuthEvent } from "./log";
|
|
63
|
+
import { MAX_OAUTH_TOKEN_REFRESH_WAITERS, retireSettledOAuthRefreshFlight, tokenRefreshWaiters, type OAuthRefreshFlightEvidence } from "./refresh-flight-retirement";
|
|
64
|
+
export { MAX_OAUTH_TOKEN_REFRESH_WAITERS };
|
|
60
65
|
import { captureConfigGeneration, sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
61
|
-
import { clearManualCodeSlot, ensureManualCodeSlot, kiroLoginSettling, loginAbort, loginState, waitForManualLoginCode, type OAuthLoginHint } from "./login-flow-state";
|
|
62
|
-
export { reconcileOAuthFlowState, submitManualLoginCode } from "./login-flow-state";
|
|
66
|
+
import { admitLoginFlow, classifyLoginCodeError, clearManualCodeSlot, CodeLoginUnsupportedError, ensureManualCodeSlot, kiroLoginSettling, latestLoginFlowKey, loginAbort, loginFlowProvider, loginState, recordStartedLoginFlow, settleLoginFlow, waitForManualLoginCode, type LoginCodeError, type OAuthLoginHint } from "./login-flow-state";
|
|
67
|
+
export { cancelLoginFlow, clearLoginState, CodeLoginUnsupportedError, isCodeLoginFlow, loginFlowAccountId, reconcileOAuthFlowState, submitLoginCode, submitManualLoginCode, waitForLoginSettled } from "./login-flow-state";
|
|
68
|
+
export type { LoginCodeError, LoginFlowState } from "./login-flow-state";
|
|
63
69
|
import { randomUUID } from "node:crypto";
|
|
64
70
|
export {
|
|
65
71
|
CODEX_HEALTH_AUTH_FAILED_NOTE,
|
|
@@ -132,8 +138,7 @@ export type OAuthActiveTokenObservation =
|
|
|
132
138
|
|
|
133
139
|
const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
|
|
134
140
|
const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
|
|
135
|
-
interface OAuthRefreshFlightEvidence {
|
|
136
|
-
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController }
|
|
141
|
+
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController; settled?: boolean; retirementDeferredLogged?: boolean }
|
|
137
142
|
const tokenRefreshes = new Map<string, OAuthTokenRefreshFlight>();
|
|
138
143
|
export class OAuthTokenRefreshBusyError extends Error {
|
|
139
144
|
readonly code = "OAUTH_TOKEN_REFRESH_BUSY";
|
|
@@ -157,7 +162,8 @@ export function seedOAuthTokenRefreshFlightsForTests(rows: Array<{ key: string;
|
|
|
157
162
|
const promise = new Promise<OAuthAccessSnapshot>((_resolve, reject) => {
|
|
158
163
|
abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
|
|
159
164
|
});
|
|
160
|
-
const flight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
|
|
165
|
+
const flight: OAuthTokenRefreshFlight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
|
|
166
|
+
abort.signal.addEventListener("abort", () => { flight.settled = true; }, { once: true });
|
|
161
167
|
tokenRefreshes.set(key, flight);
|
|
162
168
|
inserted.push(flight);
|
|
163
169
|
return promise;
|
|
@@ -192,6 +198,8 @@ export interface LoginOpts {
|
|
|
192
198
|
* listener (#3366). Ignored by every other provider.
|
|
193
199
|
*/
|
|
194
200
|
flow?: ChatGPTLoginFlow;
|
|
201
|
+
/** Code-display login: no callback server; completes from a pasted code (needs `codeRedirectUri`). */
|
|
202
|
+
codeMode?: boolean;
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
export interface LoginFlowLifecycle {
|
|
@@ -220,6 +228,8 @@ export interface OAuthProviderDef {
|
|
|
220
228
|
* overrides this. Default when unset here: "lazy-only".
|
|
221
229
|
*/
|
|
222
230
|
defaultRefreshPolicy?: RefreshPolicy;
|
|
231
|
+
/** Provider-hosted page that DISPLAYS the code; its presence enables `codeMode`. */
|
|
232
|
+
codeRedirectUri?: string;
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
function oauthConfig(id: string): OcxProviderConfig {
|
|
@@ -235,6 +245,11 @@ function oauthDefaultModel(id: string): string {
|
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
248
|
+
"dsh-account": {
|
|
249
|
+
...DSH_ACCOUNT_AUTH,
|
|
250
|
+
providerConfig: oauthConfig("dsh-account"),
|
|
251
|
+
defaultModel: oauthDefaultModel("dsh-account"),
|
|
252
|
+
},
|
|
238
253
|
"command-code": {
|
|
239
254
|
// Add-account/reauth must not reimport the current local CLI credential.
|
|
240
255
|
login: (ctrl, opts) => loginCommandCode(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
|
|
@@ -383,6 +398,11 @@ export function isPublicOAuthProvider(name: string): boolean {
|
|
|
383
398
|
return name !== "chatgpt" && isOAuthProvider(name);
|
|
384
399
|
}
|
|
385
400
|
|
|
401
|
+
/** Providers that can log in from a displayed code, with no inbound port and no browser here. */
|
|
402
|
+
export function supportsCodeLoginMode(name: string): boolean {
|
|
403
|
+
return OAUTH_PROVIDERS[name]?.codeRedirectUri !== undefined;
|
|
404
|
+
}
|
|
405
|
+
|
|
386
406
|
function isRefreshPolicy(value: unknown): value is RefreshPolicy {
|
|
387
407
|
return value === "proactive" || value === "lazy-only" || value === "disabled";
|
|
388
408
|
}
|
|
@@ -452,6 +472,14 @@ export class OAuthProviderPublicationError extends Error {
|
|
|
452
472
|
}
|
|
453
473
|
}
|
|
454
474
|
|
|
475
|
+
/** A local configuration conflict found before any credential is written; its text is fixed. */
|
|
476
|
+
export class OAuthProviderNamespaceCollisionError extends Error {
|
|
477
|
+
constructor(message: string) {
|
|
478
|
+
super(message);
|
|
479
|
+
this.name = "OAuthProviderNamespaceCollisionError";
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
455
483
|
export class OAuthReauthIdentityMismatchError extends Error {
|
|
456
484
|
constructor() {
|
|
457
485
|
super("Signed-in account does not match the selected account. Sign in with the same account.");
|
|
@@ -475,6 +503,7 @@ class OAuthLoginSupersededError extends Error {
|
|
|
475
503
|
|
|
476
504
|
/** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
|
|
477
505
|
export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
506
|
+
if (error instanceof MuseLoginError) return museLoginErrorMessage(error.kind, error.status);
|
|
478
507
|
if (error instanceof OAuthMutationBusyError) {
|
|
479
508
|
return error.message === "OAuth mutation queue wait timed out"
|
|
480
509
|
? "OAuth mutation queue wait timed out"
|
|
@@ -484,6 +513,7 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
|
484
513
|
(error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
|
|
485
514
|
|| error instanceof OAuthAccountPausedError
|
|
486
515
|
|| error instanceof OAuthProviderPublicationError
|
|
516
|
+
|| error instanceof OAuthProviderNamespaceCollisionError
|
|
487
517
|
// Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
|
|
488
518
|
// generic message hides WHICH failure the user must fix (sign in with the selected account).
|
|
489
519
|
|| error instanceof OAuthReauthIdentityMismatchError
|
|
@@ -608,45 +638,71 @@ async function resolveAccessSnapshotForAccount(
|
|
|
608
638
|
const key = `${provider}\u0000${accountId}`;
|
|
609
639
|
let existing = tokenRefreshes.get(key);
|
|
610
640
|
let replacedStaleFlight: OAuthRefreshFlightEvidence | undefined;
|
|
611
|
-
if (existing &&
|
|
612
|
-
|
|
613
|
-
return existing.promise;
|
|
614
|
-
}
|
|
615
|
-
if (existing) {
|
|
641
|
+
if (existing && (existing.abort.signal.reason instanceof OAuthTokenRefreshStaleError
|
|
642
|
+
|| Date.now() - existing.startedAt > OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS)) {
|
|
616
643
|
replacedStaleFlight = { flightId: existing.flightId, dispatched: existing.dispatched };
|
|
617
644
|
existing.abort.abort(new OAuthTokenRefreshStaleError());
|
|
618
|
-
if (tokenRefreshes.get(key) === existing) tokenRefreshes.delete(key);
|
|
619
|
-
existing = undefined;
|
|
620
645
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (persisted.access !== accessToken) {
|
|
638
|
-
throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
|
|
646
|
+
// Count every consumer, including same-flight joins, until its promise settles. Cancelling
|
|
647
|
+
// a client cannot recycle this permit. Stale-flight abort above still runs under pressure;
|
|
648
|
+
// no replacement starts until the old consumers have actually released enough capacity.
|
|
649
|
+
const waiter = tokenRefreshWaiters.tryAcquire();
|
|
650
|
+
if (!waiter) throw new OAuthTokenRefreshBusyError();
|
|
651
|
+
try {
|
|
652
|
+
if (existing && replacedStaleFlight) {
|
|
653
|
+
// A replacement would wait for this owner's lock anyway. Keep its registry slot
|
|
654
|
+
// through definitive-result cleanup and durable retirement, even with spare capacity.
|
|
655
|
+
// In particular, an ignored abort must not mark a late explicit rejection as uncertain.
|
|
656
|
+
await existing.promise.catch(() => {});
|
|
657
|
+
if (tokenRefreshes.get(key) === existing && !retireSettledOAuthRefreshFlight(tokenRefreshes, key, existing)) {
|
|
658
|
+
throw new OAuthTokenRefreshStaleError();
|
|
659
|
+
}
|
|
660
|
+
// Other admitted replacements may have resumed first and installed the next flight.
|
|
661
|
+
existing = tokenRefreshes.get(key);
|
|
639
662
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
663
|
+
if (existing) {
|
|
664
|
+
logOAuthEvent("OAuth refresh joined existing operation", { provider, accountId });
|
|
665
|
+
return await existing.promise;
|
|
666
|
+
}
|
|
667
|
+
if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) {
|
|
668
|
+
for (const [staleKey, stale] of tokenRefreshes) {
|
|
669
|
+
if (stale.abort.signal.reason instanceof OAuthTokenRefreshStaleError) retireSettledOAuthRefreshFlight(tokenRefreshes, staleKey, stale);
|
|
670
|
+
}
|
|
671
|
+
if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) throw new OAuthTokenRefreshBusyError();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const abort = new AbortController();
|
|
675
|
+
const flight: OAuthTokenRefreshFlight = {
|
|
676
|
+
promise: undefined as unknown as Promise<OAuthAccessSnapshot>,
|
|
677
|
+
startedAt: Date.now(),
|
|
678
|
+
abort,
|
|
679
|
+
flightId: randomUUID(),
|
|
680
|
+
dispatched: false,
|
|
681
|
+
};
|
|
682
|
+
const refresh = (async (): Promise<OAuthAccessSnapshot> => {
|
|
683
|
+
const accessToken = await refreshAndPersistAccessToken(provider, accountId, def, cred, abort.signal, flight, replacedStaleFlight);
|
|
684
|
+
const persistedRow = getAccountCredentialWithStatus(provider, accountId);
|
|
685
|
+
if (!persistedRow) throw new OAuthLoginRequiredError(provider);
|
|
686
|
+
if (persistedRow.paused === true) throw new OAuthAccountPausedError();
|
|
687
|
+
const persisted = persistedRow.credential;
|
|
688
|
+
if (persisted.access !== accessToken) {
|
|
689
|
+
throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
|
|
690
|
+
}
|
|
691
|
+
return accessSnapshot(provider, accountId, persisted, oauthProvider);
|
|
692
|
+
})().catch(error => {
|
|
693
|
+
if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) throw abort.signal.reason;
|
|
694
|
+
throw error;
|
|
695
|
+
}).finally(() => {
|
|
696
|
+
flight.settled = true;
|
|
697
|
+
if (tokenRefreshes.get(key) !== flight) return;
|
|
698
|
+
if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) {
|
|
699
|
+
retireSettledOAuthRefreshFlight(tokenRefreshes, key, flight);
|
|
700
|
+
} else tokenRefreshes.delete(key);
|
|
701
|
+
});
|
|
702
|
+
flight.promise = refresh;
|
|
703
|
+
tokenRefreshes.set(key, flight);
|
|
704
|
+
return await refresh;
|
|
705
|
+
} finally { waiter.release(); }
|
|
650
706
|
}
|
|
651
707
|
|
|
652
708
|
export async function getValidAccessTokenSnapshot(
|
|
@@ -666,6 +722,7 @@ const FORCE_REFRESH_PROVIDERS = new Set([
|
|
|
666
722
|
"google-antigravity",
|
|
667
723
|
"orcarouter-oauth",
|
|
668
724
|
"devin",
|
|
725
|
+
"dsh-account",
|
|
669
726
|
]);
|
|
670
727
|
|
|
671
728
|
export async function forceRefreshOAuthAccessSnapshot(
|
|
@@ -1161,7 +1218,9 @@ export function buildModelsRequest(
|
|
|
1161
1218
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
|
|
1162
1219
|
headers.Accept = "application/json";
|
|
1163
1220
|
headers["Content-Type"] = "application/json";
|
|
1164
|
-
|
|
1221
|
+
// CLI UA: CCA lists Gemini 4 Argon's wire ids only to the CLI client family. Inference keeps
|
|
1222
|
+
// choosing per wire model (antigravityUserAgentForWireModel).
|
|
1223
|
+
headers["User-Agent"] = ANTIGRAVITY_DISCOVERY_UA;
|
|
1165
1224
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1166
1225
|
return guarded({
|
|
1167
1226
|
method: "POST" as const,
|
|
@@ -1237,26 +1296,21 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
|
|
|
1237
1296
|
// preserves explicit saved values.
|
|
1238
1297
|
|
|
1239
1298
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
* Without that, the fields behaved as if they could not be configured at all.
|
|
1247
|
-
* No OAuth preset seeds either one, so the delete branch was the only branch
|
|
1248
|
-
* these two ever took, and a hand-edited `defaultMaxOutputTokens` was gone
|
|
1249
|
-
* before the first turn of the next startup — leaving the adapter's own
|
|
1250
|
-
* fallback as the only reachable output cap (#5190).
|
|
1251
|
-
*
|
|
1252
|
-
* Scoped to the output budget on purpose. The input side (`contextWindow`,
|
|
1253
|
-
* `modelContextWindows`) describes what the account's models are, which the
|
|
1254
|
-
* preset and live discovery do own; an output budget is a spend decision the
|
|
1255
|
-
* operator makes.
|
|
1299
|
+
* Saved limits and per-model declarations are operator input, including Copilot's
|
|
1300
|
+
* long-context evidence. Presets supply missing defaults, not replacements for
|
|
1301
|
+
* configured entries. Catalog rosters, capability lists and seeded reasoning
|
|
1302
|
+
* ladders still refresh below; unseeded tiers remain operator input.
|
|
1303
|
+
* Without provenance, a saved seed cannot be distinguished from an operator edit.
|
|
1256
1304
|
*/
|
|
1305
|
+
const OAUTH_OPERATOR_MAP_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1306
|
+
"modelContextWindows", "modelMaxOutputTokens", "modelInputModalities",
|
|
1307
|
+
"modelReasoningEffortMap", "reasoningEffortMap",
|
|
1308
|
+
]);
|
|
1309
|
+
const OAUTH_OPERATOR_LIMIT_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1310
|
+
"contextWindow", "defaultMaxOutputTokens",
|
|
1311
|
+
]);
|
|
1257
1312
|
const OAUTH_PRESERVE_WHEN_PRESET_UNSET: ReadonlySet<keyof OcxProviderConfig> = new Set([
|
|
1258
|
-
"
|
|
1259
|
-
"modelMaxOutputTokens",
|
|
1313
|
+
"reasoningEfforts",
|
|
1260
1314
|
]);
|
|
1261
1315
|
|
|
1262
1316
|
const GOOGLE_ANTIGRAVITY_PROVIDER = "google-antigravity";
|
|
@@ -1296,7 +1350,23 @@ function applyOAuthPresetCatalog(
|
|
|
1296
1350
|
): void {
|
|
1297
1351
|
for (const field of OAUTH_RECONCILE_FIELDS) {
|
|
1298
1352
|
if (JSON.stringify(provider[field]) === JSON.stringify(preset[field])) continue;
|
|
1299
|
-
if (
|
|
1353
|
+
if (field === "modelReasoningEfforts") {
|
|
1354
|
+
// Known unmarked ladders are saved catalog seeds, not evidence of an
|
|
1355
|
+
// operator override. Preserve unseeded rows and explicit authoritative
|
|
1356
|
+
// overrides; refresh the preset-owned rows (e.g. xAI's effort ladder).
|
|
1357
|
+
const authoritative = provider.modelReasoningEffortsAuthoritative === true;
|
|
1358
|
+
provider.modelReasoningEfforts = mapFill(
|
|
1359
|
+
authoritative ? preset.modelReasoningEfforts : provider.modelReasoningEfforts,
|
|
1360
|
+
authoritative ? provider.modelReasoningEfforts : preset.modelReasoningEfforts,
|
|
1361
|
+
)[0];
|
|
1362
|
+
} else if (OAUTH_OPERATOR_MAP_FIELDS.has(field)) {
|
|
1363
|
+
provider[field] = mapFill<unknown>(
|
|
1364
|
+
preset[field] as Record<string, unknown> | undefined,
|
|
1365
|
+
provider[field] as Record<string, unknown> | undefined,
|
|
1366
|
+
)[0] as never;
|
|
1367
|
+
} else if (OAUTH_OPERATOR_LIMIT_FIELDS.has(field) && provider[field] !== undefined) {
|
|
1368
|
+
continue;
|
|
1369
|
+
} else if (preset[field] !== undefined) {
|
|
1300
1370
|
provider[field] = cloneProviderField(preset[field]) as never;
|
|
1301
1371
|
} else if (!OAUTH_PRESERVE_WHEN_PRESET_UNSET.has(field)) {
|
|
1302
1372
|
delete provider[field];
|
|
@@ -1492,7 +1562,7 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1492
1562
|
if (!def) return;
|
|
1493
1563
|
if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) migrateLegacyAntigravityStaticCatalog(config);
|
|
1494
1564
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
|
|
1495
|
-
if (namespaceCollision) throw new
|
|
1565
|
+
if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
|
|
1496
1566
|
const existing = config.providers[provider];
|
|
1497
1567
|
const providerConfig = def.resolveProviderConfig?.(config) ?? def.providerConfig;
|
|
1498
1568
|
// Clone operator state, including xAI wire choices and their migration version.
|
|
@@ -1559,6 +1629,8 @@ interface RunLoginDeps {
|
|
|
1559
1629
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
1560
1630
|
rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
|
|
1561
1631
|
assertCurrentOwner?: () => void;
|
|
1632
|
+
/** Told which account row the credential write landed in (an id, never the credential). */
|
|
1633
|
+
onAccountPersisted?: (accountId: string) => void;
|
|
1562
1634
|
}
|
|
1563
1635
|
|
|
1564
1636
|
/** Run the login flow, persist the credential + upsert the provider entry to disk, return cred. */
|
|
@@ -1589,7 +1661,7 @@ export async function runLogin(
|
|
|
1589
1661
|
preflightConfig.codexAccountNamespaces,
|
|
1590
1662
|
provider,
|
|
1591
1663
|
);
|
|
1592
|
-
if (namespaceCollision) throw new
|
|
1664
|
+
if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
|
|
1593
1665
|
}
|
|
1594
1666
|
if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
|
|
1595
1667
|
throw new OAuthProviderPublicationError(provider, false);
|
|
@@ -1638,10 +1710,12 @@ export async function runLogin(
|
|
|
1638
1710
|
// Proven same account: a reauth whose tier read failed keeps the stored label.
|
|
1639
1711
|
const reauthPlan = mergeOAuthPlan(cred, existing);
|
|
1640
1712
|
const reauthCred = reauthPlan === undefined ? cred : { ...cred, plan: reauthPlan };
|
|
1641
|
-
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
|
|
1713
|
+
const written = await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
|
|
1642
1714
|
assertBeforePersist: deps.assertCurrentOwner,
|
|
1643
1715
|
rotateLoginId: true,
|
|
1644
1716
|
});
|
|
1717
|
+
// A reauth whose account was deleted meanwhile wrote nothing and owns no row to report.
|
|
1718
|
+
if (written) deps.onAccountPersisted?.(opts.reauthAccountId);
|
|
1645
1719
|
} else {
|
|
1646
1720
|
const saveOptions = {
|
|
1647
1721
|
preserveIdentityless: opts?.forceLogin === true || (provider === "anthropic" && cred.source === "local-cli"),
|
|
@@ -1653,8 +1727,11 @@ export async function runLogin(
|
|
|
1653
1727
|
cred,
|
|
1654
1728
|
saveOptions,
|
|
1655
1729
|
);
|
|
1730
|
+
if (kiroCredentialWrite) deps.onAccountPersisted?.(kiroCredentialWrite.accountId);
|
|
1656
1731
|
} else {
|
|
1657
|
-
|
|
1732
|
+
// The receipt names the row written; a stubbed save may return none.
|
|
1733
|
+
const receipt = await (deps.saveCredential ?? saveCredential)(provider, cred, saveOptions);
|
|
1734
|
+
if (receipt) deps.onAccountPersisted?.(receipt.accountId);
|
|
1658
1735
|
}
|
|
1659
1736
|
}
|
|
1660
1737
|
if (provider !== "chatgpt") {
|
|
@@ -1709,7 +1786,7 @@ export async function runLogin(
|
|
|
1709
1786
|
if (provider !== "chatgpt") {
|
|
1710
1787
|
try {
|
|
1711
1788
|
const { clearModelCache } = await import("../codex/model-cache");
|
|
1712
|
-
const { clearGatherRoutedModelsInflight } = await import("../codex/catalog");
|
|
1789
|
+
const { clearGatherRoutedModelsInflight } = await import("../codex/catalog/routed-gather");
|
|
1713
1790
|
clearModelCache(provider);
|
|
1714
1791
|
clearGatherRoutedModelsInflight();
|
|
1715
1792
|
const { clearAccountQuotaCache, clearProviderQuotaCache } = await import("../providers/quota");
|
|
@@ -1764,9 +1841,12 @@ export interface OAuthAccountSummary {
|
|
|
1764
1841
|
* the config at its request boundary and resolves the policy there with `emailMaskingEnabled`.
|
|
1765
1842
|
* The default masks, so every existing caller keeps today's behaviour.
|
|
1766
1843
|
*/
|
|
1767
|
-
export function getLoginStatus(provider: string, maskEmails = true): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; done: boolean; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
|
|
1844
|
+
export function getLoginStatus(provider: string, maskEmails = true, flowId?: string): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; errorCode?: LoginCodeError; done: boolean; status: "idle" | "pending" | "complete" | "error"; mode?: "callback" | "code"; flowId?: string; expiresAt?: number; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
|
|
1768
1845
|
const cred = getCredential(provider);
|
|
1769
|
-
|
|
1846
|
+
// `flowId` reads one of several concurrent flows; without it, the provider's newest flow.
|
|
1847
|
+
const flowKey = flowId ?? latestLoginFlowKey(provider);
|
|
1848
|
+
// An explicit flow id from another provider must not leak that flow's lifecycle or hint.
|
|
1849
|
+
const st = flowKey === undefined || loginFlowProvider(flowKey) !== provider ? undefined : loginState.get(flowKey);
|
|
1770
1850
|
const set = getAccountSet(provider);
|
|
1771
1851
|
const accounts: OAuthAccountSummary[] | undefined = set?.accounts.map(a => ({
|
|
1772
1852
|
id: a.id,
|
|
@@ -1792,7 +1872,14 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1792
1872
|
email: projectEmail(cred?.email, maskEmails) ?? undefined,
|
|
1793
1873
|
source: cred?.source,
|
|
1794
1874
|
error: st?.error,
|
|
1875
|
+
...(st?.errorCode ? { errorCode: st.errorCode } : {}),
|
|
1795
1876
|
done: st?.done ?? false,
|
|
1877
|
+
// The flow's own lifecycle, which `done` alone cannot express: a finished flow that failed
|
|
1878
|
+
// and one that succeeded both report done:true. Callers poll this.
|
|
1879
|
+
status: !st ? "idle" : !st.done ? "pending" : st.error ? "error" : "complete",
|
|
1880
|
+
...(st?.mode ? { mode: st.mode } : {}),
|
|
1881
|
+
...(st?.flowId ? { flowId: st.flowId } : {}),
|
|
1882
|
+
...(st?.expiresAt !== undefined ? { expiresAt: st.expiresAt } : {}),
|
|
1796
1883
|
...(st?.hint && !st.done ? { hint: { url: st.hint.url, instructions: st.hint.instructions, deviceCode: st.hint.deviceCode } } : {}),
|
|
1797
1884
|
...(set ? { activeAccountId: set.activeAccountId, accounts } : {}),
|
|
1798
1885
|
};
|
|
@@ -1809,65 +1896,53 @@ export function oauthLoginSummary(maskEmails = true): Array<{ provider: string;
|
|
|
1809
1896
|
});
|
|
1810
1897
|
}
|
|
1811
1898
|
|
|
1812
|
-
export function clearLoginState(provider: string): void {
|
|
1813
|
-
loginAbort.get(provider)?.controller.abort("cleared");
|
|
1814
|
-
loginAbort.delete(provider);
|
|
1815
|
-
clearManualCodeSlot(provider);
|
|
1816
|
-
loginState.delete(provider);
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
export function cancelLoginFlow(provider: string, flowId?: string): boolean {
|
|
1820
|
-
const active = loginAbort.get(provider);
|
|
1821
|
-
const existing = loginState.get(provider);
|
|
1822
|
-
if (flowId !== undefined && active?.flowId !== flowId) return false;
|
|
1823
|
-
if (!active && (!existing || existing.done)) return false;
|
|
1824
|
-
active?.controller.abort("cancelled");
|
|
1825
|
-
loginAbort.delete(provider);
|
|
1826
|
-
clearManualCodeSlot(provider);
|
|
1827
|
-
loginState.set(provider, { done: true, error: "Login cancelled" });
|
|
1828
|
-
return true;
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
1899
|
export async function startLoginFlow(
|
|
1832
1900
|
provider: string,
|
|
1833
1901
|
opts?: LoginOpts,
|
|
1834
1902
|
lifecycle?: LoginFlowLifecycle,
|
|
1835
|
-
): Promise<{ url: string; instructions?: string; deviceCode?: string }> {
|
|
1903
|
+
): Promise<{ url: string; instructions?: string; deviceCode?: string; expiresAt?: number }> {
|
|
1836
1904
|
const def = OAUTH_PROVIDERS[provider];
|
|
1837
1905
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1906
|
+
// Never silently downgrade to the localhost redirect: the caller asked for code mode
|
|
1907
|
+
// precisely because it cannot reach a localhost page.
|
|
1908
|
+
if (opts?.codeMode && !supportsCodeLoginMode(provider)) throw new CodeLoginUnsupportedError(provider);
|
|
1909
|
+
admitLoginFlow(provider, opts?.codeMode === true);
|
|
1910
|
+
// The flow id is the key of every map below. Callers that own one (the management route, the
|
|
1911
|
+
// codex auth API) supply it so a later paste can be pinned to this exact attempt.
|
|
1912
|
+
const flowId = lifecycle?.flowId ?? randomUUID();
|
|
1913
|
+
const expiresAt = recordStartedLoginFlow(provider, flowId, opts?.codeMode === true);
|
|
1844
1914
|
const abort = new AbortController();
|
|
1845
|
-
loginAbort.set(
|
|
1915
|
+
loginAbort.set(flowId, { controller: abort, provider });
|
|
1846
1916
|
if (provider === "kiro") kiroLoginSettling.add(provider);
|
|
1847
1917
|
return new Promise((resolve, reject) => {
|
|
1848
1918
|
let urlResolved = false;
|
|
1849
1919
|
const ctrl: OAuthController = {
|
|
1850
1920
|
onAuth: ({ url, instructions, deviceCode }) => {
|
|
1851
|
-
if (abort.signal.aborted || loginAbort.get(
|
|
1921
|
+
if (abort.signal.aborted || loginAbort.get(flowId)?.controller !== abort) return;
|
|
1852
1922
|
// Device approval can fall back to manual input. Replace, never merge: the
|
|
1853
1923
|
// previous device code must disappear when the provider changes the next step.
|
|
1854
1924
|
const hint = { url, instructions, deviceCode };
|
|
1855
|
-
|
|
1925
|
+
// Merged, not replaced: the flow's own identity (mode/flowId/expiresAt) was recorded
|
|
1926
|
+
// before the provider produced a URL and must survive every hint update.
|
|
1927
|
+
loginState.set(flowId, { ...loginState.get(flowId), done: false, hint });
|
|
1856
1928
|
if (!urlResolved) {
|
|
1857
1929
|
urlResolved = true;
|
|
1858
|
-
resolve({ ...hint });
|
|
1930
|
+
resolve({ ...hint, ...(expiresAt !== undefined ? { expiresAt } : {}) });
|
|
1859
1931
|
}
|
|
1860
1932
|
},
|
|
1861
1933
|
onProgress: () => {},
|
|
1862
1934
|
// GUI fallback when the browser cannot hit the loopback callback server.
|
|
1863
|
-
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(
|
|
1935
|
+
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(flowId, abort.signal, expectedState),
|
|
1864
1936
|
signal: abort.signal,
|
|
1865
1937
|
};
|
|
1866
1938
|
const abandonIfNotOwner = (error?: unknown): boolean => {
|
|
1867
|
-
if (loginAbort.get(
|
|
1939
|
+
if (loginAbort.get(flowId)?.controller === abort) return false;
|
|
1868
1940
|
if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
|
|
1869
1941
|
return true;
|
|
1870
1942
|
};
|
|
1943
|
+
// The row this flow's own credential write landed in, from the store's receipt. A token match
|
|
1944
|
+
// after the fact would miss it once a concurrent login for the same account rotates the token.
|
|
1945
|
+
let persistedAccountId: string | undefined;
|
|
1871
1946
|
const settle = async (error?: unknown): Promise<void> => {
|
|
1872
1947
|
// Cancellation deletes this controller and records its own terminal result. A late provider
|
|
1873
1948
|
// rejection (or an older flow settling after a replacement starts) must not overwrite that
|
|
@@ -1883,9 +1958,10 @@ export async function startLoginFlow(
|
|
|
1883
1958
|
}
|
|
1884
1959
|
if (abandonIfNotOwner(finalError)) return;
|
|
1885
1960
|
if (finalError === undefined) {
|
|
1886
|
-
loginAbort.delete(
|
|
1887
|
-
clearManualCodeSlot(
|
|
1888
|
-
|
|
1961
|
+
loginAbort.delete(flowId);
|
|
1962
|
+
clearManualCodeSlot(flowId);
|
|
1963
|
+
// Answer this flow's caller with the account it wrote, not whichever one is active now.
|
|
1964
|
+
settleLoginFlow(flowId, persistedAccountId ? { accountId: persistedAccountId } : {});
|
|
1889
1965
|
// Local-token import (grok-cli / Claude Code keychain) completes WITHOUT firing onAuth —
|
|
1890
1966
|
// resolve so the GUI call returns instead of hanging.
|
|
1891
1967
|
if (!urlResolved) resolve({ url: "", instructions: "Logged in via an existing local CLI/keychain token — no browser needed." });
|
|
@@ -1893,27 +1969,28 @@ export async function startLoginFlow(
|
|
|
1893
1969
|
}
|
|
1894
1970
|
|
|
1895
1971
|
const e = finalError;
|
|
1896
|
-
loginAbort.delete(
|
|
1897
|
-
clearManualCodeSlot(
|
|
1972
|
+
loginAbort.delete(flowId);
|
|
1973
|
+
clearManualCodeSlot(flowId);
|
|
1898
1974
|
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1899
|
-
|
|
1975
|
+
settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
|
|
1900
1976
|
if (!urlResolved) reject(e);
|
|
1901
1977
|
};
|
|
1902
1978
|
// Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
|
|
1903
1979
|
// lets a long-lived server config adopt that settled state before clients observe done=true.
|
|
1904
1980
|
const assertCurrentOwner = (): void => {
|
|
1905
|
-
if (loginAbort.get(
|
|
1981
|
+
if (loginAbort.get(flowId)?.controller !== abort) throw new OAuthLoginSupersededError();
|
|
1906
1982
|
};
|
|
1907
|
-
|
|
1983
|
+
const onAccountPersisted = (accountId: string): void => { persistedAccountId = accountId; };
|
|
1984
|
+
void runLogin(provider, ctrl, opts, { assertCurrentOwner, onAccountPersisted }).then(
|
|
1908
1985
|
() => settle(),
|
|
1909
1986
|
(e: unknown) => settle(e),
|
|
1910
1987
|
).catch((e: unknown) => {
|
|
1911
1988
|
// settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
|
|
1912
1989
|
if (abandonIfNotOwner(e)) return;
|
|
1913
|
-
loginAbort.delete(
|
|
1914
|
-
clearManualCodeSlot(
|
|
1990
|
+
loginAbort.delete(flowId);
|
|
1991
|
+
clearManualCodeSlot(flowId);
|
|
1915
1992
|
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1916
|
-
|
|
1993
|
+
settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
|
|
1917
1994
|
if (!urlResolved) reject(e);
|
|
1918
1995
|
}).finally(() => {
|
|
1919
1996
|
if (provider === "kiro") kiroLoginSettling.delete(provider);
|
|
@@ -250,9 +250,10 @@ async function poll(flow: Flow, principal: KiroDevicePrincipal): Promise<KiroDev
|
|
|
250
250
|
if (data.status === "authorization_pending") return publicView(flow);
|
|
251
251
|
if (data.status === "expired_token" || data.status === "expired") return terminal(flow, "expired");
|
|
252
252
|
}
|
|
253
|
+
// The social poll reports a completed grant as status "authorized"; "approved" is kept as an alias.
|
|
253
254
|
if (result.status !== 200 || Object.hasOwn(data, "error") || error || (builder && (data.status !== undefined || !positive(data.expiresIn) || !isStorableKiroClientPart(flow.clientId)
|
|
254
255
|
|| !isStorableKiroClientPart(flow.clientSecret)))
|
|
255
|
-
|| (!builder && (data.status !== undefined && data.status !== "approved"))) return terminal(flow, "failed");
|
|
256
|
+
|| (!builder && (data.status !== undefined && data.status !== "authorized" && data.status !== "approved"))) return terminal(flow, "failed");
|
|
256
257
|
if (!code(data.accessToken) || !code(data.refreshToken)
|
|
257
258
|
|| (!builder && (typeof data.profileArn !== "string" || !PROFILE_ARN.test(data.profileArn)))) return terminal(flow, "failed");
|
|
258
259
|
const credential: OAuthCredentials = {
|
package/src/oauth/kiro.ts
CHANGED
|
@@ -45,6 +45,10 @@ const KIRO_TERMINAL_REFRESH_ERRORS = new Set([
|
|
|
45
45
|
"access_denied",
|
|
46
46
|
"expired_token",
|
|
47
47
|
]);
|
|
48
|
+
// The desktop refresh endpoint rejects a dead refresh token with HTTP 401 and a bare
|
|
49
|
+
// `{"message":"Bad credentials"}` body, without an OAuth `error` code. Only that exact
|
|
50
|
+
// pair is a terminal verdict; it is surfaced under this fixed code, never the raw text.
|
|
51
|
+
const KIRO_DESKTOP_BAD_CREDENTIALS = "bad_credentials";
|
|
48
52
|
|
|
49
53
|
interface ImportedKiroToken {
|
|
50
54
|
access: string;
|
|
@@ -545,12 +549,14 @@ async function kiroTokenRefreshError(response: Response, awsSsoRefresh = false):
|
|
|
545
549
|
let oauthError: string | undefined;
|
|
546
550
|
let refreshAttention = false;
|
|
547
551
|
try {
|
|
548
|
-
const payload = await response.json() as { error?: unknown };
|
|
552
|
+
const payload = await response.json() as { error?: unknown; message?: unknown };
|
|
549
553
|
// This input-error code is attention evidence only for our AWS SSO refresh request,
|
|
550
554
|
// never a terminal grant verdict. Do not surface arbitrary upstream descriptions.
|
|
551
555
|
refreshAttention = awsSsoRefresh && response.status === 400 && payload.error === "invalid_request";
|
|
552
556
|
if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
|
|
553
557
|
oauthError = payload.error;
|
|
558
|
+
} else if (!awsSsoRefresh && response.status === 401 && payload.message === "Bad credentials") {
|
|
559
|
+
oauthError = KIRO_DESKTOP_BAD_CREDENTIALS;
|
|
554
560
|
}
|
|
555
561
|
} catch {
|
|
556
562
|
// Error bodies are untrusted and intentionally excluded from the surfaced message.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Read-only: never writes to external credential stores.
|
|
4
4
|
* Ported from jawcode packages/ai/src/utils/oauth/local-token-detect.ts (xAI portion).
|
|
5
5
|
*/
|
|
6
|
-
import { execSync } from "node:child_process";
|
|
6
|
+
import { execFile, execSync } from "node:child_process";
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
@@ -109,6 +109,26 @@ function readClaudeSecureStorage(): string | null {
|
|
|
109
109
|
return readClaudeKeychain() ?? readClaudeCredentialsFile();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/** Non-blocking Keychain read for request paths; same item and timeout as the sync reader. */
|
|
113
|
+
function readClaudeKeychainAsync(): Promise<string | null> {
|
|
114
|
+
if (process.platform !== "darwin") return Promise.resolve(null);
|
|
115
|
+
return new Promise(resolve => {
|
|
116
|
+
execFile("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], { encoding: "utf8", timeout: 5000 },
|
|
117
|
+
(error, stdout) => resolve(error ? null : stdout.trim() || null));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Async variant of {@link detectClaudeCodeToken} with the same source precedence. A request-path
|
|
123
|
+
* caller must not hold the event loop for a Keychain lookup (up to five seconds).
|
|
124
|
+
*/
|
|
125
|
+
export async function detectClaudeCodeTokenAsync(): Promise<OAuthCredentials | null> {
|
|
126
|
+
const raw = process.env.CLAUDE_CONFIG_DIR?.trim()
|
|
127
|
+
? readClaudeCredentialsFile() ?? await readClaudeKeychainAsync()
|
|
128
|
+
: await readClaudeKeychainAsync() ?? readClaudeCredentialsFile();
|
|
129
|
+
return raw ? parseClaudeOauthPayload(raw) : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
export function parseClaudeOauthPayload(raw: string): OAuthCredentials | null {
|
|
113
133
|
try {
|
|
114
134
|
const data = JSON.parse(raw) as { claudeAiOauth?: { accessToken?: string; refreshToken?: string; expiresAt?: number } };
|