@bitkyc08/opencodex 2.83.0 → 2.86.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-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- 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/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- 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 +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -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 +4 -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 +55 -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 +173 -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 +59 -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/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -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 +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- 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 +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -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/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- 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/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- 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/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/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -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/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- 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/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- 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.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -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/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/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
package/src/oauth/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutat
|
|
|
5
5
|
import { resolveProviderApiKey } from "../providers/key-store";
|
|
6
6
|
import { projectEmail } from "../lib/privacy";
|
|
7
7
|
import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
|
|
8
|
+
import { KiroUserIdentityUnavailableError, kiroReauthIdentityMatches } from "./kiro-account-identity";
|
|
8
9
|
import {
|
|
9
10
|
OAuthMutationBusyError,
|
|
10
11
|
OAuthRefreshIntentIOError,
|
|
@@ -48,7 +49,7 @@ import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUr
|
|
|
48
49
|
import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
|
|
49
50
|
import { loginMetaMuse, refreshMetaMuseToken } from "./meta-muse";
|
|
50
51
|
import { loginOrcaRouter, orcaRouterInferenceBaseUrl, refreshOrcaRouterKey } from "./orcarouter";
|
|
51
|
-
import {
|
|
52
|
+
import { ANTIGRAVITY_DISCOVERY_UA } from "../adapters/google-antigravity-wire";
|
|
52
53
|
import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
|
|
53
54
|
import { apiKeyPoolEntryId, sanitizeApiKeyValue } from "../providers/api-keys";
|
|
54
55
|
import { effectiveGoogleMode, getProviderRegistryEntry, mergeRegistryStaticHeaders, providerMatchesRegistryTransport } from "../providers/registry";
|
|
@@ -86,6 +87,7 @@ import { codexAccountNamespaceProviderCollisionError } from "../codex/account-na
|
|
|
86
87
|
|
|
87
88
|
import { REFRESH_SKEW_MS } from "./refresh-policy";
|
|
88
89
|
import { newerClaudeCredentialForInstance, captureAnthropicCredentialOwnerForInstance } from "./anthropic-continuity";
|
|
90
|
+
import { normalizeOAuthPlan, mergeOAuthPlan } from "./plan";
|
|
89
91
|
import { mergeAnthropicIdentity, type AnthropicIdentityResolver } from "./anthropic-identity";
|
|
90
92
|
import { AnthropicCrossInstanceDuplicateError, AnthropicLocalCliImportError, assertAnthropicCredentialSource } from "./store-anthropic-instance";
|
|
91
93
|
export interface OAuthAccessSnapshot {
|
|
@@ -491,6 +493,9 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
|
491
493
|
|| error instanceof AnthropicInstanceCollisionError
|
|
492
494
|
|| error instanceof AnthropicCrossInstanceDuplicateError
|
|
493
495
|
|| error instanceof AnthropicLocalCliImportError
|
|
496
|
+
// Fixed, account-free text: a Kiro login that could not name its user left the existing
|
|
497
|
+
// organization account in place, and the user must confirm the signed-in CLI user (#6641).
|
|
498
|
+
|| error instanceof KiroUserIdentityUnavailableError
|
|
494
499
|
) return error.message;
|
|
495
500
|
return "OAuth authentication failed. Check the OpenCodex account status and retry.";
|
|
496
501
|
}
|
|
@@ -749,6 +754,7 @@ function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCrede
|
|
|
749
754
|
return {
|
|
750
755
|
...fresh,
|
|
751
756
|
...mergeAnthropicIdentity(fresh, previous),
|
|
757
|
+
plan: mergeOAuthPlan(fresh, previous),
|
|
752
758
|
// Shared: a refresh function returns "local-cli" only when the credential it hands back
|
|
753
759
|
// still is the local CLI's (Devin re-reading the CLI file, Meta Muse echoing its durable
|
|
754
760
|
// CLI key). Relabelling that "oauth" would stop the next forced refresh from re-reading it.
|
|
@@ -1155,7 +1161,9 @@ export function buildModelsRequest(
|
|
|
1155
1161
|
if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
|
|
1156
1162
|
headers.Accept = "application/json";
|
|
1157
1163
|
headers["Content-Type"] = "application/json";
|
|
1158
|
-
|
|
1164
|
+
// CLI UA: CCA lists Gemini 4 Argon's wire ids only to the CLI client family. Inference keeps
|
|
1165
|
+
// choosing per wire model (antigravityUserAgentForWireModel).
|
|
1166
|
+
headers["User-Agent"] = ANTIGRAVITY_DISCOVERY_UA;
|
|
1159
1167
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1160
1168
|
return guarded({
|
|
1161
1169
|
method: "POST" as const,
|
|
@@ -1617,15 +1625,22 @@ export async function runLogin(
|
|
|
1617
1625
|
if (!existing.accountId && !existing.email) {
|
|
1618
1626
|
throw new OAuthReauthIdentityUnverifiedError();
|
|
1619
1627
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1628
|
+
// A Kiro organization profile ARN is shared by its users, so it alone is not the account.
|
|
1629
|
+
const identityMatches = provider === "kiro"
|
|
1630
|
+
? kiroReauthIdentityMatches(existing, cred, getAccountSet("kiro")?.accounts
|
|
1631
|
+
.filter(account => account.id !== opts.reauthAccountId).map(account => account.credential))
|
|
1632
|
+
: existing.accountId && cred.accountId
|
|
1633
|
+
? existing.accountId === cred.accountId
|
|
1634
|
+
: existing.email && cred.email
|
|
1635
|
+
? existing.email.toLowerCase() === cred.email.toLowerCase()
|
|
1636
|
+
: false;
|
|
1625
1637
|
if (!identityMatches) {
|
|
1626
1638
|
throw new OAuthReauthIdentityMismatchError();
|
|
1627
1639
|
}
|
|
1628
|
-
|
|
1640
|
+
// Proven same account: a reauth whose tier read failed keeps the stored label.
|
|
1641
|
+
const reauthPlan = mergeOAuthPlan(cred, existing);
|
|
1642
|
+
const reauthCred = reauthPlan === undefined ? cred : { ...cred, plan: reauthPlan };
|
|
1643
|
+
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
|
|
1629
1644
|
assertBeforePersist: deps.assertCurrentOwner,
|
|
1630
1645
|
rotateLoginId: true,
|
|
1631
1646
|
});
|
|
@@ -1736,10 +1751,9 @@ export interface OAuthAccountSummary {
|
|
|
1736
1751
|
* version looked and upstream did not say. Omitting it would make those indistinguishable and
|
|
1737
1752
|
* invite a consumer to assume a tier.
|
|
1738
1753
|
*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1742
|
-
* `fetchAnthropicUsageQuota` in `src/providers/quota.ts`.
|
|
1754
|
+
* Reports the validated upstream tier stored with the credential. Anthropic profile reads
|
|
1755
|
+
* supply the organization tier; Antigravity loadCodeAssist reads prefer paidTier to currentTier.
|
|
1756
|
+
* Missing or invalid labels remain null; quota percentages never establish a subscription.
|
|
1743
1757
|
*/
|
|
1744
1758
|
plan: string | null;
|
|
1745
1759
|
}
|
|
@@ -1764,11 +1778,8 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
|
|
|
1764
1778
|
...(accountNeedsReauthForStatus(provider, a) ? { needsReauth: true } : {}),
|
|
1765
1779
|
...(a.needsReauth && a.needsReauthReason === "verify_account" ? { needsReauthReason: a.needsReauthReason } : {}),
|
|
1766
1780
|
expiresAt: a.credential.expires,
|
|
1767
|
-
// Explicitly null rather than omitted
|
|
1768
|
-
|
|
1769
|
-
// from quota percentages is not possible, because they are normalized per account and a
|
|
1770
|
-
// half-consumed small seat is indistinguishable from a half-consumed large one.
|
|
1771
|
-
plan: null,
|
|
1781
|
+
// Explicitly null rather than omitted when upstream tier metadata is unknown.
|
|
1782
|
+
plan: normalizeOAuthPlan(a.credential.plan) ?? null,
|
|
1772
1783
|
}));
|
|
1773
1784
|
|
|
1774
1785
|
// A stored credential counts as "logged in" when it exists and is not marked for
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiro account identity (#6641).
|
|
3
|
+
*
|
|
4
|
+
* A Kiro profile ARN names an organization (IAM Identity Center) profile, and every user of
|
|
5
|
+
* that profile shares it, so it cannot tell two users apart. The user-level identifier OCX can
|
|
6
|
+
* observe is the email that same-session `kiro-cli whoami --format json` reports. Account
|
|
7
|
+
* identity is therefore the ARN together with that email; `accountId` keeps the ARN because
|
|
8
|
+
* request routing, rotated-session recovery and persisted evidence read it as the profile.
|
|
9
|
+
*
|
|
10
|
+
* Stored rows are never rewritten to a new key. A legacy row keeps matching the shape it was
|
|
11
|
+
* saved with, and a row that predates the email is only claimed by a login proven to be the
|
|
12
|
+
* same CLI session (identical refresh token). A login that cannot name its user keeps the legacy
|
|
13
|
+
* profile identity, but is refused rather than overwrite an identified user of that profile.
|
|
14
|
+
*/
|
|
15
|
+
import type { OAuthCredentials, ProviderAccount } from "./types";
|
|
16
|
+
|
|
17
|
+
type KiroIdentityFields = Pick<OAuthCredentials, "accountId" | "email">;
|
|
18
|
+
|
|
19
|
+
export class KiroUserIdentityUnavailableError extends Error {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(
|
|
22
|
+
"Kiro CLI did not report which user is signed in, so OpenCodex cannot tell this login apart "
|
|
23
|
+
+ "from the identified users of the same Kiro profile. No existing account was replaced. "
|
|
24
|
+
+ "Confirm that `kiro-cli whoami` reports the signed-in user, then retry; to renew one "
|
|
25
|
+
+ "account, use its Reauthenticate action.",
|
|
26
|
+
);
|
|
27
|
+
this.name = "KiroUserIdentityUnavailableError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function kiroUserEmail(credential: Pick<OAuthCredentials, "email">): string | undefined {
|
|
32
|
+
const email = credential.email?.trim().toLowerCase();
|
|
33
|
+
return email ? email : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** ARN + user email when both are known; otherwise whichever one the credential carries. */
|
|
37
|
+
export function kiroIdentityKey(credential: KiroIdentityFields): string | undefined {
|
|
38
|
+
const email = kiroUserEmail(credential);
|
|
39
|
+
if (credential.accountId && email) return `${credential.accountId}\n${email}`;
|
|
40
|
+
return credential.accountId ?? email;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function sameCliSession(account: ProviderAccount, incoming: OAuthCredentials): boolean {
|
|
44
|
+
return Boolean(incoming.refresh) && account.credential.refresh === incoming.refresh;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Pick the stored row an identity-bearing Kiro login updates, or undefined to append a new row.
|
|
49
|
+
* Throws KiroUserIdentityUnavailableError instead of guessing between identified users.
|
|
50
|
+
*/
|
|
51
|
+
export function findKiroAccountForLogin(
|
|
52
|
+
accounts: readonly ProviderAccount[],
|
|
53
|
+
incoming: OAuthCredentials,
|
|
54
|
+
): ProviderAccount | undefined {
|
|
55
|
+
const key = kiroIdentityKey(incoming);
|
|
56
|
+
if (!key) return undefined;
|
|
57
|
+
const profileArn = incoming.accountId;
|
|
58
|
+
const sameProfile = profileArn
|
|
59
|
+
? accounts.filter(account => account.credential.accountId === profileArn)
|
|
60
|
+
: [];
|
|
61
|
+
if (profileArn && !kiroUserEmail(incoming)) {
|
|
62
|
+
// whoami named no user. Without any identified user the profile ARN stays the identity, as it
|
|
63
|
+
// always was. Once the profile has an identified user, a profile-only match could be anyone
|
|
64
|
+
// in the organization: only the same CLI session may update a row; otherwise refuse.
|
|
65
|
+
if (sameProfile.some(account => kiroUserEmail(account.credential))) {
|
|
66
|
+
const proven = sameProfile.find(account => sameCliSession(account, incoming));
|
|
67
|
+
if (proven) return proven;
|
|
68
|
+
throw new KiroUserIdentityUnavailableError();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const exact = accounts.find(account => kiroIdentityKey(account.credential) === key);
|
|
72
|
+
if (exact) return exact;
|
|
73
|
+
if (profileArn && kiroUserEmail(incoming)) {
|
|
74
|
+
// A row saved before whoami named its user: only the same CLI session proves it is this user.
|
|
75
|
+
return sameProfile.find(account => !kiroUserEmail(account.credential) && sameCliSession(account, incoming));
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A proven same-session update without whoami keeps the user email the row already has. */
|
|
81
|
+
export function withRetainedKiroUser(incoming: OAuthCredentials, existing: OAuthCredentials): OAuthCredentials {
|
|
82
|
+
if (incoming.email || !existing.email || incoming.accountId !== existing.accountId) return incoming;
|
|
83
|
+
return { ...incoming, email: existing.email };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reauth may refresh a slot only with the same profile AND, once known, the same user. A slot
|
|
88
|
+
* saved before its user was known is renewable only by this explicit reauth, unless the incoming
|
|
89
|
+
* user already owns another slot of the profile (then it is provably someone else).
|
|
90
|
+
*/
|
|
91
|
+
export function kiroReauthIdentityMatches(
|
|
92
|
+
existing: KiroIdentityFields,
|
|
93
|
+
incoming: KiroIdentityFields,
|
|
94
|
+
otherAccounts: readonly KiroIdentityFields[] = [],
|
|
95
|
+
): boolean {
|
|
96
|
+
const existingEmail = kiroUserEmail(existing);
|
|
97
|
+
const incomingEmail = kiroUserEmail(incoming);
|
|
98
|
+
if (existing.accountId && incoming.accountId) {
|
|
99
|
+
if (existing.accountId !== incoming.accountId) return false;
|
|
100
|
+
if (existingEmail !== undefined) return existingEmail === incomingEmail;
|
|
101
|
+
return incomingEmail === undefined || !otherAccounts.some(other =>
|
|
102
|
+
other.accountId === incoming.accountId && kiroUserEmail(other) === incomingEmail);
|
|
103
|
+
}
|
|
104
|
+
return Boolean(existingEmail && incomingEmail && existingEmail === incomingEmail);
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -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
|
@@ -27,8 +27,9 @@ import {
|
|
|
27
27
|
type KiroImportDiagnostic,
|
|
28
28
|
} from "./kiro-credentials";
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
|
-
import { getAccountSet, saveAccountCredential } from "./store";
|
|
30
|
+
import { bindKiroAccountUserEmail, getAccountSet, saveAccountCredential } from "./store";
|
|
31
31
|
import { KIRO_BUILDER_ID_SERVICE_PROFILE_ARN } from "../adapters/kiro-constants";
|
|
32
|
+
import { kiroUserEmail } from "./kiro-account-identity";
|
|
32
33
|
|
|
33
34
|
const DEFAULT_REGION = "us-east-1";
|
|
34
35
|
const REFRESH_URL = "https://prod.{region}.auth.desktop.kiro.dev/refreshToken";
|
|
@@ -44,6 +45,10 @@ const KIRO_TERMINAL_REFRESH_ERRORS = new Set([
|
|
|
44
45
|
"access_denied",
|
|
45
46
|
"expired_token",
|
|
46
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";
|
|
47
52
|
|
|
48
53
|
interface ImportedKiroToken {
|
|
49
54
|
access: string;
|
|
@@ -246,8 +251,10 @@ export function environmentKiroRoutingMetadata(): Pick<KiroOAuthMetadata, "profi
|
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
/**
|
|
249
|
-
* Before Add-account switches the external CLI identity, bind any matching legacy
|
|
250
|
-
*
|
|
254
|
+
* Before Add-account switches the external CLI identity, bind any matching legacy OCX row to the
|
|
255
|
+
* current CLI session: identity-less rows gain their identity, and rows keyed only by a profile
|
|
256
|
+
* ARN gain the same-session whoami user, so the user being added next is told apart from them
|
|
257
|
+
* when both share an organization profile (#6641). Unmatched rows are left alone here so a
|
|
251
258
|
* cancelled browser login cannot destroy the only stored credential; after a successful add they
|
|
252
259
|
* remain selectable but cannot borrow the new CLI identity (and reauth refuses identity-less slots).
|
|
253
260
|
*/
|
|
@@ -258,7 +265,7 @@ async function bindMatchingLegacyIdentitylessKiroAccounts(
|
|
|
258
265
|
const set = getAccountSet("kiro");
|
|
259
266
|
if (!set) return;
|
|
260
267
|
const legacyAccounts = set.accounts.filter(
|
|
261
|
-
account => account.credential.
|
|
268
|
+
account => account.credential.email === undefined,
|
|
262
269
|
);
|
|
263
270
|
if (legacyAccounts.length === 0) return;
|
|
264
271
|
|
|
@@ -275,7 +282,14 @@ async function bindMatchingLegacyIdentitylessKiroAccounts(
|
|
|
275
282
|
if (!refresh || imported.refresh !== refresh) continue;
|
|
276
283
|
const bound = await oauthCredentialFromImported(imported, runner, signal);
|
|
277
284
|
if (!bound.accountId && !bound.email) continue;
|
|
278
|
-
|
|
285
|
+
const storedArn = account.credential.accountId;
|
|
286
|
+
if (storedArn === undefined) {
|
|
287
|
+
await saveAccountCredential("kiro", account.id, bound);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
// An ARN-keyed row only gains its user; tokens, routing, plan and health stay as stored.
|
|
291
|
+
if (bound.accountId !== storedArn || !bound.email) continue;
|
|
292
|
+
await bindKiroAccountUserEmail(account.id, { profileArn: storedArn, refresh, email: bound.email });
|
|
279
293
|
}
|
|
280
294
|
}
|
|
281
295
|
|
|
@@ -535,12 +549,14 @@ async function kiroTokenRefreshError(response: Response, awsSsoRefresh = false):
|
|
|
535
549
|
let oauthError: string | undefined;
|
|
536
550
|
let refreshAttention = false;
|
|
537
551
|
try {
|
|
538
|
-
const payload = await response.json() as { error?: unknown };
|
|
552
|
+
const payload = await response.json() as { error?: unknown; message?: unknown };
|
|
539
553
|
// This input-error code is attention evidence only for our AWS SSO refresh request,
|
|
540
554
|
// never a terminal grant verdict. Do not surface arbitrary upstream descriptions.
|
|
541
555
|
refreshAttention = awsSsoRefresh && response.status === 400 && payload.error === "invalid_request";
|
|
542
556
|
if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
|
|
543
557
|
oauthError = payload.error;
|
|
558
|
+
} else if (!awsSsoRefresh && response.status === 401 && payload.message === "Bad credentials") {
|
|
559
|
+
oauthError = KIRO_DESKTOP_BAD_CREDENTIALS;
|
|
544
560
|
}
|
|
545
561
|
} catch {
|
|
546
562
|
// Error bodies are untrusted and intentionally excluded from the surfaced message.
|
|
@@ -659,10 +675,40 @@ function metadataForRotatedKiroCliCredential(
|
|
|
659
675
|
};
|
|
660
676
|
}
|
|
661
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Users of one organization share its profile ARN (#6641). A row that names its user adopts the
|
|
680
|
+
* local CLI session only when same-session whoami reports that user and the CLI still holds the
|
|
681
|
+
* matched session. A row without a user keeps the profile match only while no other stored Kiro
|
|
682
|
+
* row shares the profile. Cancellation propagates as cancellation, never as the refresh refusal.
|
|
683
|
+
*/
|
|
684
|
+
async function localKiroCliSessionBelongsToUser(
|
|
685
|
+
credential: OAuthCredentials,
|
|
686
|
+
local: ImportedKiroCredential,
|
|
687
|
+
runner: KiroCliRunner,
|
|
688
|
+
signal?: AbortSignal,
|
|
689
|
+
): Promise<boolean> {
|
|
690
|
+
const email = kiroUserEmail(credential);
|
|
691
|
+
if (!email) {
|
|
692
|
+
return !(getAccountSet("kiro")?.accounts ?? []).some(account =>
|
|
693
|
+
account.credential.refresh !== credential.refresh && account.credential.accountId === local.profileArn);
|
|
694
|
+
}
|
|
695
|
+
const identity = await readKiroCliIdentity(runner, signal);
|
|
696
|
+
signal?.throwIfAborted();
|
|
697
|
+
if (identity.email !== email) return false;
|
|
698
|
+
let current: ImportedKiroCredential | null;
|
|
699
|
+
try {
|
|
700
|
+
current = readKiroCliSqliteCredential();
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
return current !== null && (current.refresh || current.access) === (local.refresh || local.access);
|
|
705
|
+
}
|
|
706
|
+
|
|
662
707
|
export async function refreshKiroToken(
|
|
663
708
|
refresh: string,
|
|
664
709
|
signal?: AbortSignal,
|
|
665
710
|
credential?: OAuthCredentials,
|
|
711
|
+
options: { cliRunner?: KiroCliRunner } = {},
|
|
666
712
|
): Promise<OAuthCredentials> {
|
|
667
713
|
if (!refresh) throw new Error("Kiro: no refresh token available (re-run `kiro-cli login`).");
|
|
668
714
|
try {
|
|
@@ -672,6 +718,9 @@ export async function refreshKiroToken(
|
|
|
672
718
|
if (!credential) throw error;
|
|
673
719
|
const local = matchingRotatedKiroCliCredential(refresh, credential);
|
|
674
720
|
if (!local) throw error;
|
|
721
|
+
if (!await localKiroCliSessionBelongsToUser(credential, local, options.cliRunner ?? defaultKiroCliRunner, signal)) {
|
|
722
|
+
throw error;
|
|
723
|
+
}
|
|
675
724
|
const retryMetadata = metadataForRotatedKiroCliCredential(credential, local);
|
|
676
725
|
try {
|
|
677
726
|
const fresh = await refreshAwsSsoOidcToken(local.refresh, signal, {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OAuthCredentials } from "./types";
|
|
2
|
+
|
|
3
|
+
/** Display-only upstream tier; never infer a plan from normalized quota percentages. */
|
|
4
|
+
export function normalizeOAuthPlan(value: unknown): string | undefined {
|
|
5
|
+
if (typeof value !== "string") return undefined;
|
|
6
|
+
const plan = value.trim();
|
|
7
|
+
return plan.length <= 64 && /^[a-z0-9_.-]+$/.test(plan) ? plan : undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Same account by the strongest identifier both credentials carry, compared kind for kind
|
|
12
|
+
* (Anthropic UUID, then accountId, then case-insensitive email); undefined when none overlap.
|
|
13
|
+
*/
|
|
14
|
+
function sameAccount(a: OAuthCredentials, b: OAuthCredentials): boolean | undefined {
|
|
15
|
+
const pairs: Array<[string | undefined, string | undefined]> = [
|
|
16
|
+
[a.anthropicIdentity?.accountUuid, b.anthropicIdentity?.accountUuid],
|
|
17
|
+
[a.accountId, b.accountId],
|
|
18
|
+
[a.email?.toLowerCase(), b.email?.toLowerCase()],
|
|
19
|
+
];
|
|
20
|
+
for (const [left, right] of pairs) if (left !== undefined && right !== undefined) return left === right;
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** An observed unknown clears old metadata; an account replacement cannot inherit a plan. */
|
|
25
|
+
export function mergeOAuthPlan(fresh: OAuthCredentials, previous: OAuthCredentials): string | null | undefined {
|
|
26
|
+
if (fresh.plan !== undefined) return normalizeOAuthPlan(fresh.plan) ?? null;
|
|
27
|
+
if (sameAccount(fresh, previous) === false) return undefined;
|
|
28
|
+
return previous.plan === null ? null : normalizeOAuthPlan(previous.plan);
|
|
29
|
+
}
|
package/src/oauth/store.ts
CHANGED
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* distinct one. Kimi extracts JWT `user_id`/`sub` as accountId; Cursor extracts JWT `sub` —
|
|
20
20
|
* both append distinct identified accounts under multiauth.
|
|
21
21
|
*/
|
|
22
|
+
import { normalizeOAuthPlan, mergeOAuthPlan } from "./plan";
|
|
22
23
|
import { normalizeAnthropicIdentity, matchesAnthropicImport, mergeAnthropicIdentity } from "./anthropic-identity";
|
|
24
|
+
import { findKiroAccountForLogin, withRetainedKiroUser } from "./kiro-account-identity";
|
|
23
25
|
import { isAnthropicInstanceId, type AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
24
26
|
import { assertNoCrossAnthropicRegistration, assertAnthropicCredentialSource, assertAnthropicInstanceLoginConfig } from "./store-anthropic-instance";
|
|
25
27
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -537,6 +539,7 @@ function normalizeCredential(cred: unknown): OAuthCredentials | null {
|
|
|
537
539
|
if (typeof candidate.accountId === "string" && candidate.accountId.length > 0) normalized.accountId = candidate.accountId;
|
|
538
540
|
const identity = normalizeAnthropicIdentity(candidate.anthropicIdentity, candidate.access);
|
|
539
541
|
if (identity) normalized.anthropicIdentity = identity;
|
|
542
|
+
if (candidate.plan !== undefined) normalized.plan = normalizeOAuthPlan(candidate.plan) ?? null;
|
|
540
543
|
if (isCredentialSource(candidate.source)) normalized.source = candidate.source;
|
|
541
544
|
if (typeof candidate.projectId === "string" && candidate.projectId.length > 0) normalized.projectId = candidate.projectId;
|
|
542
545
|
if (typeof candidate.apiBaseUrl === "string" && candidate.apiBaseUrl.length > 0) {
|
|
@@ -905,6 +908,7 @@ export async function saveCredentialWithReceipt(
|
|
|
905
908
|
const existing = set.accounts.find(a => matchesAnthropicImport(a.credential, safe));
|
|
906
909
|
if (existing) {
|
|
907
910
|
existing.credential = { ...safe, ...mergeAnthropicIdentity(safe, existing.credential),
|
|
911
|
+
plan: mergeOAuthPlan(safe, existing.credential),
|
|
908
912
|
accountId: safe.accountId ?? existing.credential.accountId, email: safe.email ?? existing.credential.email };
|
|
909
913
|
set.selectionRevision = previousSelectionRevision;
|
|
910
914
|
delete existing.needsReauth;
|
|
@@ -918,9 +922,17 @@ export async function saveCredentialWithReceipt(
|
|
|
918
922
|
accountId = id;
|
|
919
923
|
}
|
|
920
924
|
} else if (identity) {
|
|
921
|
-
|
|
925
|
+
// Kiro: a shared organization profile ARN is not a user, so its identity also carries the
|
|
926
|
+
// same-session whoami email (#6641); the helper never guesses between identified users.
|
|
927
|
+
const existing = provider === "kiro"
|
|
928
|
+
? findKiroAccountForLogin(set.accounts, safe)
|
|
929
|
+
: set.accounts.find(a => (a.credential.accountId ?? a.credential.email) === identity);
|
|
922
930
|
if (existing) {
|
|
923
|
-
existing.credential
|
|
931
|
+
const incoming = provider === "kiro" ? withRetainedKiroUser(safe, existing.credential) : safe;
|
|
932
|
+
// Same identity: a login whose tier read failed keeps the stored label (plan absent),
|
|
933
|
+
// while an observed unknown (plan null) still clears it.
|
|
934
|
+
const plan = mergeOAuthPlan(incoming, existing.credential);
|
|
935
|
+
existing.credential = plan === undefined ? incoming : { ...incoming, plan };
|
|
924
936
|
delete existing.needsReauth;
|
|
925
937
|
delete existing.needsReauthReason;
|
|
926
938
|
delete existing.refreshAttentionGeneration;
|
|
@@ -932,7 +944,13 @@ export async function saveCredentialWithReceipt(
|
|
|
932
944
|
// active identity-less row in place prevents a stale duplicate that stays selectable
|
|
933
945
|
// and would re-refresh into a second row with the same identity.
|
|
934
946
|
const active = set.accounts.find(a => a.id === set.activeAccountId);
|
|
935
|
-
|
|
947
|
+
// Kiro rows of one organization differ only by user: while another row shares the profile,
|
|
948
|
+
// an unmatched Kiro login claims an identity-less slot only as the same CLI session (#6641).
|
|
949
|
+
const legacyUpgrade = active !== undefined && active.credential.accountId === undefined
|
|
950
|
+
&& active.credential.email === undefined
|
|
951
|
+
&& (provider !== "kiro" || (Boolean(safe.refresh) && active.credential.refresh === safe.refresh)
|
|
952
|
+
|| !set.accounts.some(a => safe.accountId !== undefined && a.credential.accountId === safe.accountId));
|
|
953
|
+
if (!opts.preserveIdentityless && active && legacyUpgrade) {
|
|
936
954
|
active.credential = safe;
|
|
937
955
|
delete active.needsReauth;
|
|
938
956
|
delete active.needsReauthReason;
|
|
@@ -1014,6 +1032,25 @@ export async function saveCredential(
|
|
|
1014
1032
|
await saveCredentialWithReceipt(provider, cred, opts);
|
|
1015
1033
|
}
|
|
1016
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* Record the same-session whoami user on a Kiro row saved before whoami named it (#6641).
|
|
1037
|
+
* Applies only while the row still holds the proven session's refresh token and profile ARN
|
|
1038
|
+
* and has no user yet; tokens, routing, plan and health are left exactly as stored.
|
|
1039
|
+
*/
|
|
1040
|
+
export async function bindKiroAccountUserEmail(
|
|
1041
|
+
accountId: string,
|
|
1042
|
+
proof: { profileArn: string; refresh: string; email: string },
|
|
1043
|
+
): Promise<boolean> {
|
|
1044
|
+
return await mutateStore(store => {
|
|
1045
|
+
const account = store.kiro?.accounts.find(candidate => candidate.id === accountId);
|
|
1046
|
+
const credential = account?.credential;
|
|
1047
|
+
if (!credential || credential.email !== undefined) return false;
|
|
1048
|
+
if (credential.accountId !== proof.profileArn || credential.refresh !== proof.refresh) return false;
|
|
1049
|
+
credential.email = proof.email;
|
|
1050
|
+
return true;
|
|
1051
|
+
}, [accountId, proof]);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1017
1054
|
/** Native device approval always gets a new slot; no unverified identity can replace one. */
|
|
1018
1055
|
export async function appendKiroAccountFromDeviceLogin(
|
|
1019
1056
|
cred: OAuthCredentials,
|
package/src/oauth/types.ts
CHANGED
|
@@ -62,6 +62,8 @@ export type OAuthCredentials = {
|
|
|
62
62
|
email?: string;
|
|
63
63
|
accountId?: string;
|
|
64
64
|
source?: OAuthCredentialSource;
|
|
65
|
+
/** Validated upstream subscription label; null is an observed unknown, absent is not read. */
|
|
66
|
+
plan?: string | null;
|
|
65
67
|
/** Private authenticated account proof; never projected into account summaries. */
|
|
66
68
|
anthropicIdentity?: { v: 1; accountUuid: string; bearerSha256: string };
|
|
67
69
|
/** Google Antigravity (Cloud Code Assist) discovered project id; injected into the CCA envelope. */
|
package/src/plugins/loader.ts
CHANGED
|
@@ -121,8 +121,19 @@ export function macAclProbeTrustError(probe: () => MacAclProbeResult): string |
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
let pluginAclInspectorForTests: ((path: string) => string | null) | undefined;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Shipped mutable seam, test-only by intent: no config or env path reaches it.
|
|
128
|
+
* Passing undefined restores the real ACL inspector.
|
|
129
|
+
*/
|
|
130
|
+
export function setPluginAclInspectorForTests(inspector?: (path: string) => string | null): void {
|
|
131
|
+
pluginAclInspectorForTests = inspector;
|
|
132
|
+
}
|
|
133
|
+
|
|
124
134
|
/** Refuse extended ACLs: mode bits alone cannot prove who can rewrite a plugin path. */
|
|
125
135
|
function aclTrustError(path: string): string | null {
|
|
136
|
+
if (pluginAclInspectorForTests) return pluginAclInspectorForTests(path);
|
|
126
137
|
if (process.platform !== "darwin" && process.platform !== "linux") return null;
|
|
127
138
|
const mac = process.platform === "darwin";
|
|
128
139
|
// CI and operator PATHs may put GNU coreutils ahead of the macOS tool.
|