@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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
16
16
|
import { modelInList } from "../types";
|
|
17
17
|
import { codexEffortRank, configuredReasoningEfforts, isCodexReasoningEffort, isDeclaredReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
18
|
-
import { catalogModelEfforts } from "../codex/catalog";
|
|
18
|
+
import { catalogModelEfforts } from "../codex/catalog/effort";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* True when the request carries codex-rs's spawned-child markers, matched EXACTLY.
|
package/src/server/images.ts
CHANGED
|
@@ -27,9 +27,11 @@ import { formatCodexProviderForLog } from "../codex/routing";
|
|
|
27
27
|
import { signalWithTimeout } from "../lib/abort";
|
|
28
28
|
import { readBoundedResponseBytes, type BoundedBytesResult } from "../lib/bounded-body";
|
|
29
29
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
30
|
+
import { isNonReplayableResponse } from "../lib/upstream-retry";
|
|
30
31
|
import type { OcxConfig } from "../types";
|
|
31
32
|
import { resolveFirstUsableOpenAiSidecar, selectImagesProvider } from "../providers/openai-sidecar";
|
|
32
|
-
import { selectProactiveApiKeyTransport } from "../providers/key-failover";
|
|
33
|
+
import { recordDistributedKeyFailure, selectProactiveApiKeyTransport } from "../providers/key-failover";
|
|
34
|
+
import { requestPacingOverloadResponse } from "./responses/pacing-overload";
|
|
33
35
|
import { getProviderRegistryEntry } from "../providers/registry";
|
|
34
36
|
import { readJsonRequestBody, resolveInboundBodyLimitBytes } from "./request-decompress";
|
|
35
37
|
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "./auth-cors";
|
|
@@ -756,6 +758,7 @@ export async function handleImages(
|
|
|
756
758
|
catch { console.error("[images] Failed to release probe lease"); }
|
|
757
759
|
};
|
|
758
760
|
let url: string;
|
|
761
|
+
let recordKeyFailure = (_status: 401 | 429, _retryAfter: string | null) => {};
|
|
759
762
|
// Each branch is re-checked as it is entered: the forward check is defence in
|
|
760
763
|
// depth for the pre-filter above, and the keyed check runs before a key
|
|
761
764
|
// rotation is committed.
|
|
@@ -805,8 +808,15 @@ export async function handleImages(
|
|
|
805
808
|
//
|
|
806
809
|
// Transport variant: this branch reads `provider.baseUrl` and `provider.headers` to build
|
|
807
810
|
// the URL and the request, and it comes back with the credential already resolved.
|
|
808
|
-
|
|
811
|
+
let warmKeyProvider;
|
|
812
|
+
try { warmKeyProvider = selectProactiveApiKeyTransport(config, providerName, candidates.keyed.provider); }
|
|
813
|
+
catch (error) {
|
|
814
|
+
const refusal = requestPacingOverloadResponse(error);
|
|
815
|
+
if (refusal) return refusal;
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
809
818
|
const provider = warmKeyProvider ?? candidates.keyed.provider;
|
|
819
|
+
recordKeyFailure = (status, retryAfter) => { recordDistributedKeyFailure(config, providerName, provider._apiKeyAttempt, status, Date.now(), retryAfter); };
|
|
810
820
|
// No fall back to the earlier snapshot once a pick has happened. The picker COMMITS its
|
|
811
821
|
// choice before returning, so if the chosen reference will not resolve -- revoked keychain
|
|
812
822
|
// entry, unset env var -- sending the previous key would authenticate a non-idempotent
|
|
@@ -865,6 +875,9 @@ export async function handleImages(
|
|
|
865
875
|
// compact already set this; the credential-bearing sidecars did not.
|
|
866
876
|
redirect: "manual",
|
|
867
877
|
});
|
|
878
|
+
if ((upstreamResponse.status === 401 || upstreamResponse.status === 429) && !isNonReplayableResponse(upstreamResponse)) {
|
|
879
|
+
recordKeyFailure(upstreamResponse.status, upstreamResponse.headers.get("retry-after"));
|
|
880
|
+
}
|
|
868
881
|
const observed = await readImageResponseBytes(upstreamResponse, {
|
|
869
882
|
maxBytes: IMAGES_RESPONSE_MAX_BYTES,
|
|
870
883
|
signal: linkedSignal.signal,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { siblingOfLivePort } from "../../codex/sibling-start";
|
|
3
|
+
import { isTestHomeGuardArmed } from "../../lib/test-home-guard";
|
|
4
|
+
import type { DesktopCompatibilityRuntime } from "../../codex/desktop-compatibility/runtime";
|
|
5
|
+
import { bindNativeCompatibilityOwner } from "../../codex/desktop-compatibility/routing-binding";
|
|
6
|
+
import type { ReadinessGate } from "../readiness";
|
|
7
|
+
|
|
8
|
+
type RuntimeModule = { getDesktopCompatibilityRuntime(): DesktopCompatibilityRuntime; shutdownDesktopCompatibility(): Promise<void> };
|
|
9
|
+
interface StartupIo {
|
|
10
|
+
platform?: string;
|
|
11
|
+
testGuard?: boolean;
|
|
12
|
+
sibling?: boolean;
|
|
13
|
+
load?: () => Promise<RuntimeModule>;
|
|
14
|
+
warn?: (message: string) => void;
|
|
15
|
+
boundPort?: number;
|
|
16
|
+
boundHostname?: string;
|
|
17
|
+
loopbackPort?: number;
|
|
18
|
+
readiness?: Pick<ReadinessGate, "getStatus">;
|
|
19
|
+
readinessTimeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
/** Core-safe gate: off installs do not load the optional runtime, read credentials or start timers. */
|
|
22
|
+
export function scheduleDesktopCompatibilityStartup(config: OcxConfig, io: StartupIo = {}): { shutdown(): Promise<void> } {
|
|
23
|
+
let stopped = false, module: RuntimeModule | undefined;
|
|
24
|
+
let cancelReadiness: (() => void) | undefined;
|
|
25
|
+
const waitForReadiness = () => !io.readiness ? Promise.resolve(true) : new Promise<boolean>(resolve => {
|
|
26
|
+
const deadline = Date.now() + (io.readinessTimeoutMs ?? 120000);
|
|
27
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
28
|
+
const finish = (ready: boolean) => { if (timer) clearTimeout(timer); cancelReadiness = undefined; resolve(ready); };
|
|
29
|
+
cancelReadiness = () => finish(false);
|
|
30
|
+
const check = () => {
|
|
31
|
+
const status = io.readiness!.getStatus();
|
|
32
|
+
if (stopped || status !== "pending" || Date.now() >= deadline) { finish(!stopped && status === "ready"); return; }
|
|
33
|
+
timer = setTimeout(check, 100); timer.unref();
|
|
34
|
+
};
|
|
35
|
+
check();
|
|
36
|
+
});
|
|
37
|
+
const unbind = io.boundPort === undefined || io.boundHostname === undefined ? () => {} : bindNativeCompatibilityOwner({ config, hostname: io.boundHostname, port: io.boundPort, loopbackPort: io.loopbackPort });
|
|
38
|
+
const enabled = config.desktopCompatibility?.startOnProxyStart === true && (io.platform ?? process.platform) === "win32"
|
|
39
|
+
&& !(io.testGuard ?? isTestHomeGuardArmed()) && !(io.sibling ?? siblingOfLivePort() !== null)
|
|
40
|
+
&& config.runtimeRole !== "client";
|
|
41
|
+
const pending = enabled ? Promise.resolve().then(async () => {
|
|
42
|
+
if (stopped) return;
|
|
43
|
+
if (io.readiness) {
|
|
44
|
+
const ready = await waitForReadiness();
|
|
45
|
+
if (stopped) return;
|
|
46
|
+
if (!ready) throw new Error("desktop_compatibility_startup_not_ready");
|
|
47
|
+
}
|
|
48
|
+
module = await (io.load?.() ?? import("../../codex/desktop-compatibility/service"));
|
|
49
|
+
if (stopped) return;
|
|
50
|
+
await module.getDesktopCompatibilityRuntime().start();
|
|
51
|
+
}).catch(() => { (io.warn ?? console.warn)("Desktop compatibility observation did not start. Inspect Desktop compatibility status; no automatic trust or correction was applied."); }) : Promise.resolve();
|
|
52
|
+
return { async shutdown() {
|
|
53
|
+
stopped = true; cancelReadiness?.(); unbind(); await pending;
|
|
54
|
+
if (module) await module.shutdownDesktopCompatibility();
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fastRowBases } from "../fast-row";
|
|
2
|
+
import { previewRouteModel } from "../../router";
|
|
3
|
+
import type { OcxConfig } from "../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build the alias promoter for one model listing. The Fast-base inventory walks
|
|
7
|
+
* the configured catalog, so it is computed at most once per listing instead of
|
|
8
|
+
* once per aliased row.
|
|
9
|
+
*/
|
|
10
|
+
export function modelAliasIdPromoter(config: OcxConfig) {
|
|
11
|
+
let isFastBase: ((id: string) => boolean) | undefined;
|
|
12
|
+
const fastBases = () => (isFastBase ??= fastRowBases(config));
|
|
13
|
+
return (provider: string, model: string, alias: string, fastEligible: boolean) =>
|
|
14
|
+
preferredModelAliasId(config, provider, model, alias, fastEligible, fastBases);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Promote only a short alias whose ordinary route is this exact upstream model. */
|
|
18
|
+
export function preferredModelAliasId(
|
|
19
|
+
config: OcxConfig, provider: string, model: string, alias: string, fastEligible: boolean,
|
|
20
|
+
fastBases: () => (id: string) => boolean = () => fastRowBases(config),
|
|
21
|
+
): string | undefined {
|
|
22
|
+
if (config.preferModelAliasIds !== true) return undefined;
|
|
23
|
+
// Built-in bare aliases are not stable Fast bases. Preserve the qualified ID
|
|
24
|
+
// when promotion would publish a synthetic sibling that ingress cannot parse.
|
|
25
|
+
if (config.fastRows !== false && fastEligible && !fastBases()(alias)) return undefined;
|
|
26
|
+
try {
|
|
27
|
+
// Preview cannot consume a combo's round-robin selection state. A policy/combo,
|
|
28
|
+
// reserved native name or another provider's native id must keep its own meaning.
|
|
29
|
+
const route = previewRouteModel(config, alias);
|
|
30
|
+
return route.routeReason === "model-alias" && route.providerName === provider && route.modelId === model
|
|
31
|
+
? alias : undefined;
|
|
32
|
+
} catch {
|
|
33
|
+
// An ambiguous alias stays available through the existing qualified public id.
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -21,6 +21,7 @@ export { LINK_INGRESS_HOSTNAME } from "./link-listener";
|
|
|
21
21
|
import type { ServerIngress } from "./serve-options";
|
|
22
22
|
|
|
23
23
|
export interface OptionalListenerStartContext<T> {
|
|
24
|
+
startProxy?: typeof import("../../claude/intercept/connect-proxy").startConnectProxy;
|
|
24
25
|
config: OcxConfig;
|
|
25
26
|
publicPort: number;
|
|
26
27
|
requestedPort?: number;
|
|
@@ -106,6 +107,7 @@ export function createOptionalListenerSet<T>(linkDeps: LinkListenerDeps = {}): O
|
|
|
106
107
|
}
|
|
107
108
|
supervisorStop = () => supervisor.stop();
|
|
108
109
|
claudeIntercept.start({
|
|
110
|
+
startProxy: ctx.startProxy,
|
|
109
111
|
config: ctx.config,
|
|
110
112
|
publicPort: ctx.publicPort,
|
|
111
113
|
requestedPort: ctx.requestedPort,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Server, ServerWebSocket } from "bun";
|
|
2
|
+
import { modelAliasIdPromoter } from "./model-alias-id";
|
|
2
3
|
import type { StartServerDeps } from "./startup-warnings";
|
|
3
4
|
import { isLinkManagementPath } from "../../link/management-routes";
|
|
4
5
|
import {
|
|
@@ -1055,7 +1056,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1055
1056
|
);
|
|
1056
1057
|
const { buildAnthropicModelInfos } = await import("../../claude/model-info");
|
|
1057
1058
|
const { resolveAutoContext } = await import("../../claude/context-windows");
|
|
1058
|
-
const {
|
|
1059
|
+
const { resolvableDesktop3pAlias } = await import("../../claude/desktop-3p");
|
|
1059
1060
|
// Per-surface id family (devlog 050): explicit ?ids= wins; otherwise the
|
|
1060
1061
|
// Claude Code CLI discovery UA (`claude-code/<version>`, binary n_()) gets
|
|
1061
1062
|
// readable claude-ocx ids and every other client (Desktop 3P) keeps the
|
|
@@ -1071,7 +1072,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1071
1072
|
goOrdered,
|
|
1072
1073
|
resolveAutoContext(config.claudeCode),
|
|
1073
1074
|
idStyle,
|
|
1074
|
-
|
|
1075
|
+
resolvableDesktop3pAlias,
|
|
1075
1076
|
desktopInputs.nativeContextCap,
|
|
1076
1077
|
config.fastMode,
|
|
1077
1078
|
// Explicit opt-out omits the Fast predicate.
|
|
@@ -1252,6 +1253,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1252
1253
|
effortRowKnownIds,
|
|
1253
1254
|
));
|
|
1254
1255
|
};
|
|
1256
|
+
const promoteAliasId = modelAliasIdPromoter(config);
|
|
1255
1257
|
const routedRows = await Promise.all(uniqueCatalogModelsForRawPublicList(goOrdered)
|
|
1256
1258
|
.filter(m => listAllows(m.provider, m.id))
|
|
1257
1259
|
.map(async m => {
|
|
@@ -1269,8 +1271,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1269
1271
|
knownModelIdsForProvider(m.provider, provider, config),
|
|
1270
1272
|
).get(m.id)
|
|
1271
1273
|
: undefined;
|
|
1274
|
+
const effectiveId = !isCombo && !fastModelId && effective
|
|
1275
|
+
? promoteAliasId(m.provider, m.id, effective.alias, catalogRowFastEligible(m)) ?? publicId
|
|
1276
|
+
: publicId;
|
|
1272
1277
|
const row = {
|
|
1273
|
-
id:
|
|
1278
|
+
id: effectiveId,
|
|
1274
1279
|
object: "model",
|
|
1275
1280
|
created: 0,
|
|
1276
1281
|
// This endpoint is an OpenAI-compatible inbound contract. Some clients use
|
|
@@ -1279,6 +1284,9 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1279
1284
|
owned_by: isCombo ? "openai" : (m.owned_by ?? m.provider),
|
|
1280
1285
|
...(isCombo ? { is_combo: true } : {}),
|
|
1281
1286
|
...(effective ? { alias_of: `${provider?.alias || m.provider}/${effective.alias}` } : {}),
|
|
1287
|
+
// When preferModelAliasIds promotes the alias to `id`, retain the original
|
|
1288
|
+
// provider/nativeId so clients that need the upstream identity can read it.
|
|
1289
|
+
...(effectiveId !== publicId ? { native_id: publicId } : {}),
|
|
1282
1290
|
...grokEffortFields(m.reasoningEfforts ?? [], m.defaultReasoningEffort),
|
|
1283
1291
|
...modelCapabilityFields({
|
|
1284
1292
|
reasoningEfforts: m.reasoningEfforts,
|
|
@@ -149,6 +149,8 @@ export function withRequestLogId(response: Response, requestId: string): Respons
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export interface StartServerDeps {
|
|
152
|
+
/** Test-only CONNECT binder; permits kernel-assigned ports without probe-and-release races. */
|
|
153
|
+
startClaudeInterceptProxy?: typeof import("../../claude/intercept/connect-proxy").startConnectProxy;
|
|
152
154
|
/** Test-only seam; production always initializes its own management credential state. */
|
|
153
155
|
managementAuthState?: ManagementAuthState;
|
|
154
156
|
/** Test-only route dependencies, forwarded only after management admission succeeds. */
|
|
@@ -277,3 +279,12 @@ export function warnPlaintextV2AgentMessagesStartup(config: { plaintextV2AgentMe
|
|
|
277
279
|
console.warn(" Eligible ChatGPT collaboration calls may carry plaintext message arguments. HTTPS remains encrypted, but task text may be retained in Codex history, selected providers, and local response/debug state.");
|
|
278
280
|
console.warn(" This depends on undocumented ChatGPT and Codex behavior; it does not decrypt existing tasks.");
|
|
279
281
|
}
|
|
282
|
+
|
|
283
|
+
export function logProxyEndpoints(actualPort: number): void {
|
|
284
|
+
console.log(`🚀 opencodex proxy running on http://localhost:${actualPort}`);
|
|
285
|
+
console.log(` POST /v1/responses → provider translation`);
|
|
286
|
+
console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
|
|
287
|
+
console.log(` GET /healthz → health check`);
|
|
288
|
+
console.log(` GET /api/* → management API`);
|
|
289
|
+
console.log(` GET / → GUI dashboard`);
|
|
290
|
+
}
|
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
} from "../ws-bridge";
|
|
31
31
|
import {
|
|
32
32
|
CodexAccountCooldownError,
|
|
33
|
+
CodexMainAccountHardLockError,
|
|
33
34
|
cooldownErrorMessage,
|
|
35
|
+
cooldownErrorResponse,
|
|
34
36
|
} from "../../codex/auth-context";
|
|
35
37
|
import { codexAccountNamespaceForModel } from "../../codex/account-namespace-match";
|
|
36
38
|
import {
|
|
@@ -368,6 +370,13 @@ export function createWebsocketHandler(
|
|
|
368
370
|
const accountSelector = typeof payload.model === "string"
|
|
369
371
|
? codexAccountNamespaceForModel(config.codexAccountNamespaces, payload.model)
|
|
370
372
|
: undefined;
|
|
373
|
+
if (err instanceof CodexMainAccountHardLockError) {
|
|
374
|
+
// Same terminal usage-limit body and Retry-After as HTTP (#7046).
|
|
375
|
+
const refusal = cooldownErrorResponse(err, Date.now(), accountSelector);
|
|
376
|
+
const body = await refusal.json() as { error: Record<string, unknown> };
|
|
377
|
+
sendJsonFrame(ws, buildWsErrorFrame(429, body.error, refusal.headers));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
371
380
|
sendJsonFrame(ws, buildWsErrorFrame(429, {
|
|
372
381
|
type: "rate_limit_error",
|
|
373
382
|
message: cooldownErrorMessage(err, accountSelector),
|
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { remoteWorkspaceEnabled } from "../remote-control/workspace-activation";
|
|
2
|
+
import { scheduleDesktopCompatibilityStartup } from "./index/desktop-compatibility-startup";
|
|
2
3
|
import { AuxiliaryListenerBindError } from "./ports";
|
|
3
4
|
import { runAdmittedBodyWork } from "./inbound-body-admission";
|
|
4
5
|
import {
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
websocketsEnabled,
|
|
25
26
|
} from "../config";
|
|
26
27
|
import { flushConfigDirHardeningAndReaps } from "../config/paths";
|
|
28
|
+
import { trackBackgroundWork } from "../lib/background-work";
|
|
27
29
|
import { migrateStartupSubagentModels } from "./subagent-models-startup";
|
|
28
30
|
import { migrateStartupXaiResponses } from "./xai-responses-startup";
|
|
29
31
|
import { migrateStartupZaiResponses } from "./zai-responses-startup";
|
|
@@ -62,6 +64,7 @@ import {
|
|
|
62
64
|
} from "../lib/app-owned-memory-stores";
|
|
63
65
|
import { acquireServerBackgroundLifecycle } from "./background-lifecycle";
|
|
64
66
|
import { startPackageRefresh, stopPackageRefresh } from "../update/refresh-scheduler";
|
|
67
|
+
import { activateAdvisor } from "../lib/advisor-activation";
|
|
65
68
|
import { activateLab, labActivationRequired } from "../lib/lab-activation";
|
|
66
69
|
import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
|
|
67
70
|
import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
|
|
@@ -198,7 +201,7 @@ import { createReadinessGate, type ReadinessGate } from "./readiness";
|
|
|
198
201
|
import { createServeOptions, type ServerIngress } from "./index/serve-options";
|
|
199
202
|
import { createOptionalListenerSet, LINK_INGRESS_HOSTNAME } from "./index/optional-listeners";
|
|
200
203
|
import { createPackageTreeIntegrityGuardForServer } from "./index/package-tree-guard";
|
|
201
|
-
import { inspectStartupOwnership, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
|
|
204
|
+
import { inspectStartupOwnership, logProxyEndpoints, resolveInboundBodyLimitWithWarning, setStartupCacheInvalidationWrite, warnAgentTaskRecoveryStartup, warnPlaintextV2AgentMessagesStartup, type StartServerDeps } from "./index/startup-warnings";
|
|
202
205
|
import { acquireSpendLedgerServerLifecycle, recordFailedStartRollback, type SpendLedgerServerLifecycle } from "./index/spend-ledger-lifecycle";
|
|
203
206
|
export { waitForFailedStartRollback } from "./index/spend-ledger-lifecycle";
|
|
204
207
|
|
|
@@ -638,11 +641,14 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
638
641
|
let unregisterQuotaAutoRefresh: (() => void) | null = null;
|
|
639
642
|
let remoteWorkspaceStopping = false;
|
|
640
643
|
let remoteWorkspaceShutdown: (() => Promise<void>) | undefined;
|
|
644
|
+
let desktopCompatibilityStartup: ReturnType<typeof scheduleDesktopCompatibilityStartup> | undefined;
|
|
645
|
+
let desktopCompatibilityShutdown: (() => Promise<void>) | undefined;
|
|
641
646
|
const managementApiDeps: ManagementApiDeps = {
|
|
642
647
|
...deps.managementApi,
|
|
643
648
|
listLowQuotaEvents: limit => backgroundLifecycle?.listLowQuotaEvents(limit) ?? [],
|
|
644
649
|
remoteWorkspaceStopping: () => remoteWorkspaceStopping,
|
|
645
650
|
onRemoteWorkspaceShutdown: shutdown => { remoteWorkspaceShutdown = shutdown; }, linkSupervisor: () => optionalListeners.linkSupervisor(), linkListener: () => optionalListeners,
|
|
651
|
+
onDesktopCompatibilityShutdown: shutdown => { desktopCompatibilityShutdown = shutdown; },
|
|
646
652
|
};
|
|
647
653
|
let workspaceRuntimeFlight: Promise<typeof import("../remote-control/workspace-runtime")> | undefined;
|
|
648
654
|
const loadRemoteWorkspaceRuntime = () => {
|
|
@@ -732,7 +738,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
732
738
|
throw new AuxiliaryListenerBindError("hub.managementIngress", managementIngressPort, "127.0.0.1", error);
|
|
733
739
|
}
|
|
734
740
|
}
|
|
735
|
-
optionalListeners.start({ config, publicPort: server.port ?? listenPort, requestedPort: listenPort,
|
|
741
|
+
optionalListeners.start({ config, publicPort: server.port ?? listenPort, requestedPort: listenPort, startProxy: deps.startClaudeInterceptProxy,
|
|
736
742
|
maxRequestBodySize: inboundBodyLimitBytes, dispatch: (req, requestServer) => serveOptions.fetch(req, requestServer) });
|
|
737
743
|
} catch (error) {
|
|
738
744
|
unregisterQuotaAutoRefresh?.();
|
|
@@ -772,6 +778,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
772
778
|
: []),
|
|
773
779
|
() => optionalListeners.stop(),
|
|
774
780
|
async () => { await remoteWorkspaceShutdown?.(); },
|
|
781
|
+
async () => { await desktopCompatibilityStartup?.shutdown(); await desktopCompatibilityShutdown?.(); },
|
|
775
782
|
async () => {
|
|
776
783
|
try {
|
|
777
784
|
userCostOverlayReconciler?.stop();
|
|
@@ -801,13 +808,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
801
808
|
boundPort = actualPort;
|
|
802
809
|
setCorsOrigin(actualPort);
|
|
803
810
|
|
|
804
|
-
|
|
805
|
-
console.log(` POST /v1/responses → provider translation`);
|
|
806
|
-
console.log(` POST /v1/chat/completions → OpenAI-compatible clients`);
|
|
807
|
-
console.log(` GET /healthz → health check`);
|
|
808
|
-
console.log(` GET /api/* → management API`);
|
|
809
|
-
console.log(` GET / → GUI dashboard`);
|
|
810
|
-
|
|
811
|
+
logProxyEndpoints(actualPort);
|
|
811
812
|
if (loopbackServer) {
|
|
812
813
|
// Loud on every start, not once at enable time. An operator who inherits a config, or
|
|
813
814
|
// who forgot, has to be able to see that an unauthenticated surface is live without
|
|
@@ -843,7 +844,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
843
844
|
&& isCanonicalOpenAiForwardProvider(openAiProvider)
|
|
844
845
|
&& providerCodexAccountMode("openai", openAiProvider) === "pool"
|
|
845
846
|
) {
|
|
846
|
-
import("../codex/plan-from-token")
|
|
847
|
+
void trackBackgroundWork("codex-pool-quota-prime-startup", import("../codex/plan-from-token")
|
|
847
848
|
.then(({ reconcileCodexPlansFromTokens }) => {
|
|
848
849
|
try {
|
|
849
850
|
reconcileCodexPlansFromTokens(config);
|
|
@@ -853,7 +854,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
853
854
|
return import("../codex/auth-api");
|
|
854
855
|
})
|
|
855
856
|
.then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "startup"))
|
|
856
|
-
.catch(() => {});
|
|
857
|
+
.catch(() => {}));
|
|
857
858
|
}
|
|
858
859
|
|
|
859
860
|
// Opt-in storage policy (default OFF). Never blocks listen; cancellable on shutdown.
|
|
@@ -864,6 +865,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
864
865
|
// startServer returns, in the same turn as Bun.serve, so a policy route can never be
|
|
865
866
|
// evaluated before its evidence provider is registered. That ordering is load-bearing:
|
|
866
867
|
// the subagent-fallback chain routes synchronously and has nowhere to await.
|
|
868
|
+
activateAdvisor(config);
|
|
867
869
|
const labConfigDir = getConfigDir();
|
|
868
870
|
if (labActivationRequired(config, labConfigDir)) {
|
|
869
871
|
activateLab(config, labConfigDir);
|
|
@@ -879,6 +881,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
879
881
|
}
|
|
880
882
|
|
|
881
883
|
startPackageRefresh();
|
|
884
|
+
desktopCompatibilityStartup = scheduleDesktopCompatibilityStartup(config, { boundHostname: bindHost, boundPort: actualPort, loopbackPort: loopbackServer?.port ?? undefined, readiness: deps.readinessGate });
|
|
882
885
|
return server;
|
|
883
886
|
}
|
|
884
887
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RequestLogContext } from "../request-log";
|
|
2
|
+
import { ADVISOR_INTERNAL_CAPABILITY_HEADER, isInternalCallCapability } from "../../lib/local-internal-call-capability";
|
|
3
|
+
|
|
4
|
+
export const ADVISOR_REASONING_HEADER = "x-opencodex-advisor-reasoning";
|
|
5
|
+
const EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
6
|
+
|
|
7
|
+
/** Native Chat sends the finalized body directly instead of the translating adapter builder. */
|
|
8
|
+
export function observeAdvisorNativeReasoning(headers: Headers, log: RequestLogContext, body: string): void {
|
|
9
|
+
if (!isInternalCallCapability(headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER))) return;
|
|
10
|
+
delete log.effectiveEffort;
|
|
11
|
+
try {
|
|
12
|
+
const effort = JSON.parse(body)?.reasoning_effort;
|
|
13
|
+
if (typeof effort === "string" && EFFORTS.has(effort)) log.effectiveEffort = effort;
|
|
14
|
+
} catch { /* absence of evidence stays unreported */ }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Only the authenticated internal caller receives adapter-owned serialization evidence. */
|
|
18
|
+
export function attachAdvisorReasoning(response: Response, log: RequestLogContext): Response {
|
|
19
|
+
const headers = new Headers(response.headers);
|
|
20
|
+
headers.set(ADVISOR_REASONING_HEADER, JSON.stringify({
|
|
21
|
+
effectiveEffort: log.effectiveEffort && EFFORTS.has(log.effectiveEffort) ? log.effectiveEffort : null,
|
|
22
|
+
}));
|
|
23
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function readAdvisorReasoning(headers: Headers): string | null {
|
|
27
|
+
try {
|
|
28
|
+
const value = JSON.parse(headers.get(ADVISOR_REASONING_HEADER) ?? "null")?.effectiveEffort;
|
|
29
|
+
return typeof value === "string" && EFFORTS.has(value) ? value : null;
|
|
30
|
+
} catch { return null; }
|
|
31
|
+
}
|
|
@@ -111,17 +111,18 @@ export interface ClientEncodedDelivery {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
* Record the attempt's observed path
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* Record the attempt's observed path. The response reaches the client from the IR without the
|
|
115
|
+
* internal Responses hop. The request side is the bridge's unless the ingress also decoded the
|
|
116
|
+
* conversation straight into the IR (the Antigravity direct lane, `requestLane: "direct"`).
|
|
117
117
|
*/
|
|
118
118
|
function markDirectEncoderPath(logCtx: RequestLogContext, encoder: ClientEncoderOption, adapter: string): void {
|
|
119
119
|
const attempt = logCtx.activeAttempt;
|
|
120
120
|
if (!attempt) return;
|
|
121
121
|
const upstream = upstreamWireForAdapter(attempt.adapter || adapter);
|
|
122
|
+
const lane = encoder.requestLane ?? "bridge";
|
|
122
123
|
markAttemptProtocolPath(attempt, {
|
|
123
|
-
mode: deliveryModeForLane(encoder.protocol,
|
|
124
|
-
requestPath: requestPathForLane(encoder.protocol,
|
|
124
|
+
mode: deliveryModeForLane(encoder.protocol, lane, upstream),
|
|
125
|
+
requestPath: requestPathForLane(encoder.protocol, lane, upstream),
|
|
125
126
|
responsePath: [upstream, "ir", encoder.protocol],
|
|
126
127
|
});
|
|
127
128
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user's login-shell PATH, for installers the dashboard runs under OpenCodex Desktop (#6971).
|
|
3
|
+
*
|
|
4
|
+
* A Finder or login launch gives the Desktop app, and so its proxy sidecar, only the system PATH
|
|
5
|
+
* (`/usr/bin:/bin:/usr/sbin:/sbin`). `ocx codex-shim install` scans PATH for the native Codex
|
|
6
|
+
* launcher, so from the dashboard it reported "Native Codex launcher not found" for Homebrew, npm,
|
|
7
|
+
* bun, nvm, fnm, volta and mise installs, and an npm launcher had no `node` for its probe.
|
|
8
|
+
*
|
|
9
|
+
* This asks the user's shell once, as an interactive login shell would start, and keeps only PATH:
|
|
10
|
+
* the value is framed by markers so startup-file output cannot leak into it. The read has a
|
|
11
|
+
* deadline, runs in its own process group so a hung startup file is killed with all of its
|
|
12
|
+
* children, and its result, including failure, is cached so repeated dashboard actions do not
|
|
13
|
+
* spawn shells. It is scoped to the shim installer's child environment; the service definition's
|
|
14
|
+
* PATH has its own policy.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
|
|
17
|
+
import { userInfo } from "node:os";
|
|
18
|
+
import { basename, isAbsolute } from "node:path";
|
|
19
|
+
|
|
20
|
+
const BEGIN = "__OCX_LOGIN_PATH_BEGIN__";
|
|
21
|
+
const END = "__OCX_LOGIN_PATH_END__";
|
|
22
|
+
export const LOGIN_SHELL_PATH_TIMEOUT_MS = 5_000;
|
|
23
|
+
export const LOGIN_SHELL_PATH_CACHE_MS = 5 * 60_000;
|
|
24
|
+
const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
25
|
+
/** After the shell exits, how long its stdout may stay open before a background job is assumed to hold it. */
|
|
26
|
+
const STDOUT_DRAIN_MS = 500;
|
|
27
|
+
|
|
28
|
+
export function loginShellPathArgs(shell: string): string[] {
|
|
29
|
+
const print = basename(shell) === "fish"
|
|
30
|
+
? `printf '%s%s%s\\n' ${BEGIN} (string join : $PATH) ${END}`
|
|
31
|
+
: `printf '%s%s%s\\n' ${BEGIN} "$PATH" ${END}`;
|
|
32
|
+
return ["-l", "-i", "-c", print];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The framed PATH, only when every entry is absolute. */
|
|
36
|
+
export function parseLoginShellPath(output: string): string | null {
|
|
37
|
+
const start = output.lastIndexOf(BEGIN);
|
|
38
|
+
if (start < 0) return null;
|
|
39
|
+
const end = output.indexOf(END, start + BEGIN.length);
|
|
40
|
+
if (end < 0) return null;
|
|
41
|
+
const value = output.slice(start + BEGIN.length, end);
|
|
42
|
+
if (!value || /[\r\n\0]/.test(value)) return null;
|
|
43
|
+
const entries = value.split(":").filter(Boolean);
|
|
44
|
+
return entries.length > 0 && entries.every(entry => isAbsolute(entry)) ? entries.join(":") : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Ordered union: the shell's entries first, then any the process had that the shell did not. */
|
|
48
|
+
export function mergePathLists(preferred: string, fallback: string | undefined): string {
|
|
49
|
+
// POSIX only: the login shell runs on macOS and Linux, where PATH is colon-separated.
|
|
50
|
+
return [...new Set([...preferred.split(":"), ...(fallback ?? "").split(":")].filter(Boolean))].join(":");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LoginShellPathDeps {
|
|
54
|
+
platform?: NodeJS.Platform;
|
|
55
|
+
shell?: string | null;
|
|
56
|
+
env?: NodeJS.ProcessEnv;
|
|
57
|
+
spawn?: (command: string, args: string[], options: Parameters<typeof nodeSpawn>[2]) => ChildProcess;
|
|
58
|
+
killGroup?: (pid: number) => void;
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
now?: () => number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let cached: { at: number; value: string | null } | null = null;
|
|
64
|
+
|
|
65
|
+
export function resetLoginShellPathCacheForTests(): void {
|
|
66
|
+
cached = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function defaultShell(env: NodeJS.ProcessEnv): string | null {
|
|
70
|
+
const fromEnv = env.SHELL?.trim();
|
|
71
|
+
if (fromEnv && isAbsolute(fromEnv)) return fromEnv;
|
|
72
|
+
try {
|
|
73
|
+
const shell = userInfo().shell;
|
|
74
|
+
return shell && isAbsolute(shell) ? shell : null;
|
|
75
|
+
} catch { return null; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function killProcessGroup(pid: number): void {
|
|
79
|
+
try { process.kill(-pid, "SIGKILL"); } catch { /* already gone */ }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function readLoginShellPath(deps: LoginShellPathDeps = {}): Promise<string | null> {
|
|
83
|
+
const platform = deps.platform ?? process.platform;
|
|
84
|
+
if (platform === "win32") return null;
|
|
85
|
+
const now = deps.now ?? Date.now;
|
|
86
|
+
if (cached && now() - cached.at < LOGIN_SHELL_PATH_CACHE_MS) return cached.value;
|
|
87
|
+
const env = deps.env ?? process.env;
|
|
88
|
+
const shell = deps.shell === undefined ? defaultShell(env) : deps.shell;
|
|
89
|
+
const value = shell ? await probe(shell, env, deps) : null;
|
|
90
|
+
cached = { at: now(), value };
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function probe(shell: string, env: NodeJS.ProcessEnv, deps: LoginShellPathDeps): Promise<string | null> {
|
|
95
|
+
const spawn = deps.spawn ?? nodeSpawn;
|
|
96
|
+
const killGroup = deps.killGroup ?? killProcessGroup;
|
|
97
|
+
return new Promise(resolve => {
|
|
98
|
+
let child: ChildProcess;
|
|
99
|
+
try {
|
|
100
|
+
child = spawn(shell, loginShellPathArgs(shell), {
|
|
101
|
+
detached: true,
|
|
102
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
103
|
+
// Only what a login shell needs to find its startup files; no proxy or credential state.
|
|
104
|
+
env: Object.fromEntries(Object.entries({
|
|
105
|
+
HOME: env.HOME, USER: env.USER, LOGNAME: env.LOGNAME, SHELL: shell, LANG: env.LANG,
|
|
106
|
+
PATH: env.PATH, TERM: "dumb",
|
|
107
|
+
}).filter(([, value]) => value !== undefined)) as NodeJS.ProcessEnv,
|
|
108
|
+
});
|
|
109
|
+
} catch { resolve(null); return; }
|
|
110
|
+
let output = "";
|
|
111
|
+
let settled = false;
|
|
112
|
+
let exitCode: number | null = null;
|
|
113
|
+
let drain: ReturnType<typeof setTimeout> | undefined;
|
|
114
|
+
const finish = (value: string | null, killGroupMembers: boolean) => {
|
|
115
|
+
if (settled) return;
|
|
116
|
+
settled = true;
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
clearTimeout(drain);
|
|
119
|
+
// A hung startup file, or a background job still holding the pipe, is killed with its group.
|
|
120
|
+
// A shell that exited and closed its output is left alone: its group id may already be free.
|
|
121
|
+
if (killGroupMembers && child.pid) killGroup(child.pid);
|
|
122
|
+
resolve(value);
|
|
123
|
+
};
|
|
124
|
+
const timer = setTimeout(() => finish(null, true), deps.timeoutMs ?? LOGIN_SHELL_PATH_TIMEOUT_MS);
|
|
125
|
+
child.stdout?.setEncoding("utf8");
|
|
126
|
+
child.stdout?.on("data", (chunk: string) => {
|
|
127
|
+
output += chunk;
|
|
128
|
+
if (output.length > MAX_OUTPUT_BYTES) finish(null, true);
|
|
129
|
+
});
|
|
130
|
+
const parsed = () => exitCode === 0 ? parseLoginShellPath(output) : null;
|
|
131
|
+
child.once("error", () => finish(null, true));
|
|
132
|
+
// Parse only after stdout closes: `exit` can arrive before the last chunk.
|
|
133
|
+
child.once("close", code => { exitCode ??= code; finish(parsed(), false); });
|
|
134
|
+
child.once("exit", code => {
|
|
135
|
+
exitCode = code;
|
|
136
|
+
drain = setTimeout(() => finish(parsed(), true), STDOUT_DRAIN_MS);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** `env` with the login-shell PATH in front, or unchanged when it cannot be read. */
|
|
142
|
+
export async function withLoginShellPath(env: NodeJS.ProcessEnv, deps: LoginShellPathDeps = {}): Promise<NodeJS.ProcessEnv> {
|
|
143
|
+
const loginPath = await readLoginShellPath({ ...deps, env });
|
|
144
|
+
return loginPath ? { ...env, PATH: mergePathLists(loginPath, env.PATH) } : env;
|
|
145
|
+
}
|
|
146
|
+
|