@bitkyc08/opencodex 2.86.0 → 2.88.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/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- 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/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- 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-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 +4 -2
- 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 +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- 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/outbound.ts +46 -13
- 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 +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- 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 +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -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 +28 -8
- 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 +2 -1
- package/src/clients/config-export.ts +18 -44
- 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/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 +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -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 +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- 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 +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- 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/main-account-hard-lock.ts +30 -1
- 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 +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- 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/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- 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 +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -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 +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -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/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- 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 +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- 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/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- 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 +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- 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/opencode-zen-rate-limit.ts +7 -2
- 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 +37 -8
- 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 +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- 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/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- 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 +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- 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 +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -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 +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- 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/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 +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- 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 +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -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 +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- 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 +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -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/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- 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/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -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 +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- 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/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
inspectCodexCliInstall,
|
|
3
|
+
observeCodexCliSelectedCommand,
|
|
3
4
|
type CodexCliInstallProvenanceDeps,
|
|
4
5
|
type CodexCliInstallReport,
|
|
5
6
|
} from "../codex/cli-install-provenance";
|
|
@@ -11,20 +12,29 @@ import type {
|
|
|
11
12
|
CodexCliInstallationSnapshot,
|
|
12
13
|
CodexCliInstallationTargetDerivation,
|
|
13
14
|
} from "../codex/cli-installation-targets";
|
|
15
|
+
import {
|
|
16
|
+
createCodexCliUpdatePlan,
|
|
17
|
+
type CodexCliUpdateChannel,
|
|
18
|
+
type CodexCliUpdatePlan,
|
|
19
|
+
type CodexCliUpdatePlanDeps,
|
|
20
|
+
} from "../codex/cli-update-plan";
|
|
21
|
+
import {
|
|
22
|
+
type ResolveCodexRuntimeDeps,
|
|
23
|
+
type ResolveCodexRuntimeResult,
|
|
24
|
+
} from "../codex/runtime";
|
|
14
25
|
import { CliUsageError, isJsonOption, printData, runCliAction } from "./runtime-api";
|
|
15
26
|
import { trustedNodeLauncherContext } from "./launcher-context";
|
|
16
27
|
|
|
17
28
|
export const CODEX_CLI_UPDATE_USAGE = `Usage:
|
|
18
29
|
ocx system codex-cli-update check [--json]
|
|
19
30
|
ocx system codex-cli-update attest [--json]
|
|
20
|
-
ocx system codex-cli-update attest --candidate <absolute-path> --npm-prefix <absolute-path> --npm-cli <absolute-path> --node <absolute-path> [--json]
|
|
31
|
+
ocx system codex-cli-update attest --candidate <absolute-path> --npm-prefix <absolute-path> --npm-cli <absolute-path> --node <absolute-path> [--json]
|
|
32
|
+
ocx system codex-cli-update plan [--channel latest] [--json]`;
|
|
21
33
|
|
|
22
|
-
export type ParsedCodexCliUpdateArgs =
|
|
23
|
-
json: boolean
|
|
24
|
-
|
|
25
|
-
json: boolean;
|
|
26
|
-
attest: CodexCliInstallationIdentityInput | "selected";
|
|
27
|
-
}>;
|
|
34
|
+
export type ParsedCodexCliUpdateArgs =
|
|
35
|
+
| Readonly<{ action: "check"; json: boolean }>
|
|
36
|
+
| Readonly<{ action: "plan"; json: boolean; channel: CodexCliUpdateChannel }>
|
|
37
|
+
| Readonly<{ json: boolean; attest: CodexCliInstallationIdentityInput | "selected" }>;
|
|
28
38
|
|
|
29
39
|
export interface CodexCliUpdateCommandDeps {
|
|
30
40
|
readonly inspectInstall?: (deps: CodexCliInstallProvenanceDeps) => Promise<CodexCliInstallReport>;
|
|
@@ -32,6 +42,8 @@ export interface CodexCliUpdateCommandDeps {
|
|
|
32
42
|
readonly deriveInstallationInput?: (
|
|
33
43
|
snapshot: CodexCliInstallationSnapshot,
|
|
34
44
|
) => Promise<CodexCliInstallationTargetDerivation>;
|
|
45
|
+
readonly createPlan?: (deps: CodexCliUpdatePlanDeps) => Promise<CodexCliUpdatePlan>;
|
|
46
|
+
readonly resolveSelectedRuntime?: (deps: ResolveCodexRuntimeDeps) => ResolveCodexRuntimeResult;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
function identitySummary(report: CodexCliInstallationIdentityReport): string[] {
|
|
@@ -68,24 +80,60 @@ function installSummary(report: CodexCliInstallReport): string[] {
|
|
|
68
80
|
];
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
function planSummary(plan: CodexCliUpdatePlan): string[] {
|
|
84
|
+
const lines = [
|
|
85
|
+
`applicable: ${plan.applicable ? "yes" : "no"}`,
|
|
86
|
+
`reason: ${plan.refusal ?? "none"}`,
|
|
87
|
+
`provenance: ${plan.provenance}`,
|
|
88
|
+
`installed-version: ${plan.installedVersion ?? "unavailable"}`,
|
|
89
|
+
`version-evidence: ${plan.versionEvidence}`,
|
|
90
|
+
`channel: ${plan.channel}`,
|
|
91
|
+
`target-version: ${plan.targetVersion ?? "unresolved"}`,
|
|
92
|
+
`target-integrity: ${plan.targetIntegrity ?? "unresolved"}`,
|
|
93
|
+
`session: ${plan.session.state}${plan.session.matches === null ? "" : ` (${plan.session.matches})`}`,
|
|
94
|
+
];
|
|
95
|
+
if (plan.planId) lines.push(`plan: ${plan.planId}`);
|
|
96
|
+
if (plan.command) lines.push(`command: ${plan.command.join(" ")} (indicative — a future apply phase verifies the packed tarball and installs the verified file)`);
|
|
97
|
+
return lines;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Read ``--name value`` and ``--name=value`` alike; both spellings reach this CLI. */
|
|
102
|
+
function optionValue(tokens: readonly string[], index: number, name: string): { value: string; next: number } {
|
|
103
|
+
const token = tokens[index]!;
|
|
104
|
+
const inline = `--${name}=`;
|
|
105
|
+
if (token.startsWith(inline)) {
|
|
106
|
+
const value = token.slice(inline.length);
|
|
107
|
+
if (!value) throw new CliUsageError(`--${name} requires a value`, CODEX_CLI_UPDATE_USAGE);
|
|
108
|
+
return { value, next: index + 1 };
|
|
109
|
+
}
|
|
110
|
+
const value = tokens[index + 1];
|
|
111
|
+
if (value === undefined) throw new CliUsageError(`--${name} requires a value`, CODEX_CLI_UPDATE_USAGE);
|
|
112
|
+
return { value, next: index + 2 };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isOption(token: string, name: string): boolean {
|
|
116
|
+
return token === `--${name}` || token.startsWith(`--${name}=`);
|
|
117
|
+
}
|
|
118
|
+
|
|
71
119
|
export function parseCodexCliUpdateArgs(argv: readonly string[]): ParsedCodexCliUpdateArgs {
|
|
72
120
|
// `--json` is accepted in any argv position CLI-wide, so remove it before positional
|
|
73
|
-
// validation. Requiring
|
|
121
|
+
// validation. Requiring the action at index 0 first would reject `--json check`, which
|
|
74
122
|
// automation that puts output flags ahead of the subcommand legitimately produces.
|
|
75
123
|
let json = false;
|
|
76
|
-
const
|
|
124
|
+
const rest: string[] = [];
|
|
77
125
|
for (const token of argv) {
|
|
78
126
|
if (isJsonOption(token)) {
|
|
79
127
|
if (json) throw new CliUsageError("--json may be specified only once", CODEX_CLI_UPDATE_USAGE);
|
|
80
128
|
json = true;
|
|
81
129
|
continue;
|
|
82
130
|
}
|
|
83
|
-
|
|
131
|
+
rest.push(token);
|
|
84
132
|
}
|
|
85
|
-
if (
|
|
133
|
+
if (rest[0] === "attest") {
|
|
86
134
|
// No options: attest the selected candidate identified from the proof-bound
|
|
87
135
|
// launcher snapshot. The four explicit paths remain all-or-none.
|
|
88
|
-
if (
|
|
136
|
+
if (rest.length === 1) {
|
|
89
137
|
return Object.freeze({ json, attest: "selected" as const });
|
|
90
138
|
}
|
|
91
139
|
const options = new Map<string, keyof CodexCliInstallationIdentityInput>([
|
|
@@ -93,12 +141,12 @@ export function parseCodexCliUpdateArgs(argv: readonly string[]): ParsedCodexCli
|
|
|
93
141
|
["--npm-cli", "npmCli"], ["--node", "node"],
|
|
94
142
|
]);
|
|
95
143
|
const input: Partial<Record<keyof CodexCliInstallationIdentityInput, string>> = {};
|
|
96
|
-
for (let index = 1; index <
|
|
97
|
-
const key = options.get(
|
|
144
|
+
for (let index = 1; index < rest.length; index += 2) {
|
|
145
|
+
const key = options.get(rest[index]!);
|
|
98
146
|
if (!key || input[key] !== undefined) {
|
|
99
147
|
throw new CliUsageError("unsupported or duplicate attest option", CODEX_CLI_UPDATE_USAGE);
|
|
100
148
|
}
|
|
101
|
-
const value =
|
|
149
|
+
const value = rest[index + 1];
|
|
102
150
|
if (!value || !value.trim() || /[\0\r\n]/.test(value)
|
|
103
151
|
|| !(value.startsWith("/") || /^[a-z]:[\\/]/i.test(value))) {
|
|
104
152
|
throw new CliUsageError("attest options require explicit absolute paths", CODEX_CLI_UPDATE_USAGE);
|
|
@@ -112,13 +160,78 @@ export function parseCodexCliUpdateArgs(argv: readonly string[]): ParsedCodexCli
|
|
|
112
160
|
candidate: input.candidate, npmPrefix: input.npmPrefix, npmCli: input.npmCli, node: input.node,
|
|
113
161
|
}) });
|
|
114
162
|
}
|
|
115
|
-
|
|
116
|
-
|
|
163
|
+
const action = rest[0];
|
|
164
|
+
if (action !== "check" && action !== "plan") {
|
|
165
|
+
throw new CliUsageError("codex-cli-update action must be check, attest or plan", CODEX_CLI_UPDATE_USAGE);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (action === "check") {
|
|
169
|
+
if (rest.length > 1) throw new CliUsageError("unsupported codex-cli-update argument", CODEX_CLI_UPDATE_USAGE);
|
|
170
|
+
return Object.freeze({ action, json });
|
|
117
171
|
}
|
|
118
|
-
|
|
119
|
-
|
|
172
|
+
|
|
173
|
+
if (action === "plan") {
|
|
174
|
+
let channel: CodexCliUpdateChannel = "latest";
|
|
175
|
+
let seen = false;
|
|
176
|
+
let index = 1;
|
|
177
|
+
while (index < rest.length) {
|
|
178
|
+
const token = rest[index]!;
|
|
179
|
+
if (!isOption(token, "channel")) {
|
|
180
|
+
throw new CliUsageError("unsupported codex-cli-update argument", CODEX_CLI_UPDATE_USAGE);
|
|
181
|
+
}
|
|
182
|
+
if (seen) throw new CliUsageError("--channel may be specified only once", CODEX_CLI_UPDATE_USAGE);
|
|
183
|
+
const read = optionValue(rest, index, "channel");
|
|
184
|
+
// Only the stable channel is offered. A preview channel would need its own
|
|
185
|
+
// provenance story before it may install anything on the operator's behalf.
|
|
186
|
+
if (read.value !== "latest") throw new CliUsageError("--channel must be latest", CODEX_CLI_UPDATE_USAGE);
|
|
187
|
+
channel = read.value;
|
|
188
|
+
seen = true;
|
|
189
|
+
index = read.next;
|
|
190
|
+
}
|
|
191
|
+
return Object.freeze({ action, json, channel });
|
|
120
192
|
}
|
|
121
|
-
|
|
193
|
+
|
|
194
|
+
// Unreachable: the action check above already rejected every other verb, but the
|
|
195
|
+
// fallthrough keeps the parser total for the type checker.
|
|
196
|
+
throw new CliUsageError("unsupported codex-cli-update action", CODEX_CLI_UPDATE_USAGE);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Inspection inputs for this one-shot CLI process.
|
|
201
|
+
*
|
|
202
|
+
* The published Node launcher supplies a proof-bound snapshot of configured candidate
|
|
203
|
+
* evidence, not selected-runtime admission. A direct Bun or source launch has no such
|
|
204
|
+
* proof, so nothing ambient or persisted is inspected at all.
|
|
205
|
+
*/
|
|
206
|
+
function inspectionDeps(commandDeps: CodexCliUpdateCommandDeps): CodexCliInstallProvenanceDeps {
|
|
207
|
+
const trusted = trustedNodeLauncherContext()?.codexCliInspectionEnv;
|
|
208
|
+
if (!trusted || trusted.managerRoots === null) return { env: { PATH: "" }, configDir: "." };
|
|
209
|
+
// Attestation also requires the runtime resolver to pick the snapshot's
|
|
210
|
+
// candidate. probeVersion: false keeps this read-only — no codex --version
|
|
211
|
+
// executes on a check/plan path — while still refusing attestation when the
|
|
212
|
+
// resolver would settle on a different executable.
|
|
213
|
+
const selectedCommand = observeCodexCliSelectedCommand({
|
|
214
|
+
env: {
|
|
215
|
+
CODEX_CLI_PATH: trusted.codexCliPath ?? undefined,
|
|
216
|
+
PATH: trusted.path ?? undefined,
|
|
217
|
+
PATHEXT: trusted.pathExt ?? undefined,
|
|
218
|
+
},
|
|
219
|
+
configDir: trusted.configDir,
|
|
220
|
+
}, commandDeps.resolveSelectedRuntime);
|
|
221
|
+
return {
|
|
222
|
+
env: {
|
|
223
|
+
...trusted.managerRoots,
|
|
224
|
+
CODEX_CLI_PATH: trusted.codexCliPath ?? undefined,
|
|
225
|
+
PATH: trusted.path ?? undefined,
|
|
226
|
+
PATHEXT: trusted.pathExt ?? undefined,
|
|
227
|
+
},
|
|
228
|
+
configDir: trusted.configDir,
|
|
229
|
+
// The snapshot is proof-bound to the launcher, but the report may only attest
|
|
230
|
+
// the environment candidate when the resolver picks the same canonical path —
|
|
231
|
+
// managed requires both.
|
|
232
|
+
selectionAttested: true,
|
|
233
|
+
selectedCommand,
|
|
234
|
+
};
|
|
122
235
|
}
|
|
123
236
|
|
|
124
237
|
export async function handleCodexCliUpdateCommand(
|
|
@@ -136,7 +249,9 @@ export async function handleCodexCliUpdateCommand(
|
|
|
136
249
|
}
|
|
137
250
|
throw error;
|
|
138
251
|
}
|
|
139
|
-
|
|
252
|
+
// A refusal or an unapplied update is a legitimate answer rather than a crash, so the
|
|
253
|
+
// outcome exit code is decided here and only a thrown error is left to runCliAction.
|
|
254
|
+
const code = await runCliAction(async () => {
|
|
140
255
|
if ("attest" in parsed) {
|
|
141
256
|
let report: CodexCliInstallationIdentityReport;
|
|
142
257
|
try {
|
|
@@ -167,25 +282,20 @@ export async function handleCodexCliUpdateCommand(
|
|
|
167
282
|
printData(report, parsed.json, identitySummary(report));
|
|
168
283
|
return;
|
|
169
284
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// ambient or persisted candidate state at all.
|
|
185
|
-
env: { PATH: "" },
|
|
186
|
-
configDir: ".",
|
|
187
|
-
};
|
|
188
|
-
const report = await (deps.inspectInstall ?? inspectCodexCliInstall)(inspectionDeps);
|
|
189
|
-
printData(report, parsed.json, installSummary(report));
|
|
285
|
+
if (parsed.action === "check") {
|
|
286
|
+
const report = await (deps.inspectInstall ?? inspectCodexCliInstall)(inspectionDeps(deps));
|
|
287
|
+
printData(report, parsed.json, installSummary(report));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (parsed.action === "plan") {
|
|
291
|
+
const plan = await (deps.createPlan ?? createCodexCliUpdatePlan)({
|
|
292
|
+
channel: parsed.channel,
|
|
293
|
+
inspectionDeps: inspectionDeps(deps),
|
|
294
|
+
inspect: deps.inspectInstall,
|
|
295
|
+
});
|
|
296
|
+
printData(plan, parsed.json, planSummary(plan));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
190
299
|
});
|
|
300
|
+
return code;
|
|
191
301
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { allowCodexProviderRouting, approvedCodexProviders, previewCodexProviderConsent, restoreApprovedCodexProviders } from "../codex/provider-routing";
|
|
2
|
+
import { interactiveConfirm } from "./interactive-confirm";
|
|
3
|
+
import { terminalSafeError, terminalSafeText } from "./runtime-api";
|
|
4
|
+
|
|
5
|
+
export const CODEX_PROVIDER_USAGE = "Usage: ocx codex-provider <status|allow <id> [--yes]|revoke <id>>";
|
|
6
|
+
|
|
7
|
+
export async function handleCodexProviderCommand(args: string[]): Promise<number> {
|
|
8
|
+
const [verb, id, flag] = args;
|
|
9
|
+
if (verb === "status" && args.length === 1) {
|
|
10
|
+
console.log(JSON.stringify({ approvedProviders: approvedCodexProviders() }));
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
if ((verb !== "allow" && verb !== "revoke") || !id || args.length > 3
|
|
14
|
+
|| (flag !== undefined && (verb !== "allow" || flag !== "--yes"))) {
|
|
15
|
+
console.error(CODEX_PROVIDER_USAGE);
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
if (verb === "revoke") {
|
|
20
|
+
restoreApprovedCodexProviders(id);
|
|
21
|
+
console.log(`Routing consent revoked for ${terminalSafeText(id)}; any unchanged managed URL was restored.`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
const expected = previewCodexProviderConsent(id);
|
|
25
|
+
console.log(`OpenCodex will manage only base_url for Codex provider ${terminalSafeText(id)} in the selected CODEX_HOME.`);
|
|
26
|
+
console.log("Start/sync will route it to the local proxy; stop/restore will restore its original URL. Provider identity, transport settings and history stay unchanged.");
|
|
27
|
+
if (flag !== "--yes") {
|
|
28
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
29
|
+
console.error("Explicit consent required: use an interactive terminal or pass --yes with the exact provider ID.");
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (!await interactiveConfirm({ question: "Allow this provider's routing to be managed?", defaultYes: false })) {
|
|
33
|
+
console.log("Cancelled; no consent or routing changes were saved.");
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
allowCodexProviderRouting(id, expected);
|
|
38
|
+
console.log("Consent saved. Run ocx start or ocx sync to apply routing; use ocx codex-provider revoke <id> to undo consent.");
|
|
39
|
+
return 0;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(terminalSafeError(error));
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/cli/combo-input.ts
CHANGED
|
@@ -8,8 +8,9 @@ type ComboInput = {
|
|
|
8
8
|
imageInput?: "auto" | "disabled";
|
|
9
9
|
reasoningEffortMode?: "strict" | "adaptive";
|
|
10
10
|
nativeAlias?: boolean;
|
|
11
|
+
allowMeteredTargets?: boolean;
|
|
11
12
|
};
|
|
12
|
-
const TARGET_KEYS = new Set(["provider", "model", "weight", "reasoningEfforts", "modelProfile", "lastResort"]);
|
|
13
|
+
const TARGET_KEYS = new Set(["provider", "model", "weight", "reasoningEfforts", "modelProfile", "lastResort", "metered"]);
|
|
13
14
|
|
|
14
15
|
export function parseComboTargets(value: unknown): Array<Record<string, unknown>> {
|
|
15
16
|
if (!Array.isArray(value) || value.length === 0) throw new CliUsageError("Targets input must be a nonempty array");
|
|
@@ -41,6 +42,9 @@ export function parseComboTargets(value: unknown): Array<Record<string, unknown>
|
|
|
41
42
|
if (Object.hasOwn(target, "lastResort") && typeof target.lastResort !== "boolean") {
|
|
42
43
|
throw new CliUsageError("Target lastResort must be a boolean");
|
|
43
44
|
}
|
|
45
|
+
if (Object.hasOwn(target, "metered") && typeof target.metered !== "boolean") {
|
|
46
|
+
throw new CliUsageError("Target metered must be a boolean");
|
|
47
|
+
}
|
|
44
48
|
// Preserve raw slashes, target order, explicit false and omitted metadata.
|
|
45
49
|
return { ...target };
|
|
46
50
|
});
|
|
@@ -70,6 +74,10 @@ export async function prepareComboInput(args: string[], deps: RuntimeApiDeps = {
|
|
|
70
74
|
const image = takeOptionWithSyntax(args, "--image-input")?.value;
|
|
71
75
|
const reasoning = takeOptionWithSyntax(args, "--reasoning-effort-mode")?.value;
|
|
72
76
|
const nativeAlias = takeNativeAlias(args);
|
|
77
|
+
const metered = takeOptionWithSyntax(args, "--allow-metered-targets")?.value;
|
|
78
|
+
if (metered !== undefined && metered !== "on" && metered !== "off") {
|
|
79
|
+
throw new CliUsageError("--allow-metered-targets must be on or off");
|
|
80
|
+
}
|
|
73
81
|
if (file !== undefined && args.some(arg => arg === "--targets" || arg.startsWith("--targets="))) {
|
|
74
82
|
throw new CliUsageError("--targets-file cannot be combined with --targets");
|
|
75
83
|
}
|
|
@@ -84,5 +92,6 @@ export async function prepareComboInput(args: string[], deps: RuntimeApiDeps = {
|
|
|
84
92
|
...(image === undefined ? {} : { imageInput: image }),
|
|
85
93
|
...(reasoning === undefined ? {} : { reasoningEffortMode: reasoning }),
|
|
86
94
|
...(nativeAlias === undefined ? {} : { nativeAlias }),
|
|
95
|
+
...(metered === undefined ? {} : { allowMeteredTargets: metered === "on" }),
|
|
87
96
|
};
|
|
88
97
|
}
|
package/src/cli/combo-result.ts
CHANGED
|
@@ -33,7 +33,7 @@ function publicCombo(value: unknown): Record<string, unknown> {
|
|
|
33
33
|
} else if (Object.hasOwn(RANGES, key)) {
|
|
34
34
|
const [min, max] = RANGES[key]!;
|
|
35
35
|
if (typeof child !== "number" || !Number.isSafeInteger(child) || child < min || child > max) invalid();
|
|
36
|
-
} else if (key === "nativeAlias") {
|
|
36
|
+
} else if (key === "nativeAlias" || key === "allowMeteredTargets") {
|
|
37
37
|
if (typeof child !== "boolean") invalid();
|
|
38
38
|
} else invalid();
|
|
39
39
|
result[key] = child;
|
package/src/cli/combo.ts
CHANGED
|
@@ -23,7 +23,7 @@ const USAGE = `Usage:
|
|
|
23
23
|
[--strategy <failover|round-robin|random|least-used|reset-window|jev>] [--sticky <1-100|->]
|
|
24
24
|
[--effort <low|medium|high|xhigh|max|ultra|->] [--effort-mode <fallback|force|->]
|
|
25
25
|
(force overrides valid client effort and can increase cost/latency) [--alias <name|->]
|
|
26
|
-
[--native-alias [on|off]] [--display-name <label|->]
|
|
26
|
+
[--native-alias [on|off]] [--allow-metered-targets <on|off>] [--display-name <label|->]
|
|
27
27
|
[--image-input <auto|disabled>] [--reasoning-effort-mode <strict|adaptive>]
|
|
28
28
|
[--decision-provider <provider|-> | --decision-model <route|->] [--decision-timeout <ms|->]
|
|
29
29
|
(jev only; the provider must be a configured jev-decision row)
|
|
@@ -160,6 +160,7 @@ async function set(argv: string[], deps: RuntimeApiDeps): Promise<number> {
|
|
|
160
160
|
if (effortMode !== undefined) combo.defaultEffortMode = effortMode === "-" ? "fallback" : effortMode;
|
|
161
161
|
if (alias !== undefined) combo.alias = alias === "-" ? "" : alias;
|
|
162
162
|
if (additional.nativeAlias !== undefined) combo.nativeAlias = additional.nativeAlias;
|
|
163
|
+
if (additional.allowMeteredTargets !== undefined) combo.allowMeteredTargets = additional.allowMeteredTargets;
|
|
163
164
|
if (additional.imageInput !== undefined) combo.imageInput = additional.imageInput;
|
|
164
165
|
if (additional.reasoningEffortMode !== undefined) combo.reasoningEffortMode = additional.reasoningEffortMode;
|
|
165
166
|
if (displayName !== undefined) combo.displayName = displayName === "-" ? "" : displayName;
|
|
@@ -14,6 +14,7 @@ import { readClientConnectionState } from "../client/state";
|
|
|
14
14
|
import { findManagedRegion, resolveGrokHome } from "../grok/inject";
|
|
15
15
|
import { providerTableString } from "../codex/injected-marker";
|
|
16
16
|
import { isLocalAttestationSecret } from "../lib/local-management-attestation";
|
|
17
|
+
import { isProtectedHomeUnderTest } from "../lib/test-home-guard";
|
|
17
18
|
import { readOwnerRegistry } from "../config/owner-registry";
|
|
18
19
|
import {
|
|
19
20
|
classifyHealthz,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
} from "../server/proxy-liveness";
|
|
26
27
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
27
28
|
import type { RuntimePortState } from "../config/process-state";
|
|
29
|
+
import { loopbackFamilyAvailability } from "./loopback-family";
|
|
28
30
|
|
|
29
31
|
const MAX_HINT_BYTES = 256 * 1024;
|
|
30
32
|
// Far above any real Grok config; discovery must not stall startup when Grok sync is off.
|
|
@@ -127,13 +129,19 @@ function loopbackPort(raw: string | null): number | null {
|
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
type CrossHomeOwnerOptions = {
|
|
133
|
+
homeDir?: string;
|
|
134
|
+
io?: LivenessIo;
|
|
135
|
+
loopbackAvailabilityFn?: typeof loopbackFamilyAvailability;
|
|
136
|
+
};
|
|
137
|
+
|
|
130
138
|
/** Returns only a different process with an identity-checked /healthz response. */
|
|
131
|
-
export async function findCrossHomeOwner(options:
|
|
139
|
+
export async function findCrossHomeOwner(options: CrossHomeOwnerOptions = {}): Promise<number | null> {
|
|
132
140
|
const verdict = await findCrossHomeOwnerDetailed(options);
|
|
133
141
|
return verdict.kind === "owner" ? verdict.port : null;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
|
-
export async function findCrossHomeOwnerDetailed(options:
|
|
144
|
+
export async function findCrossHomeOwnerDetailed(options: CrossHomeOwnerOptions = {}): Promise<CrossHomeOwnerVerdict> {
|
|
137
145
|
const io = options.io ?? {};
|
|
138
146
|
const nowFn = io.nowFn ?? Date.now;
|
|
139
147
|
const timeoutMs = io.timeoutMs ?? START_OWNERSHIP_LIVENESS.timeoutMs ?? 1_500;
|
|
@@ -160,7 +168,7 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
160
168
|
};
|
|
161
169
|
|
|
162
170
|
const defaultHome = join(options.homeDir ?? homedir(), ".opencodex");
|
|
163
|
-
recordHome(defaultHome);
|
|
171
|
+
if (options.homeDir !== undefined || !isProtectedHomeUnderTest(defaultHome)) recordHome(defaultHome);
|
|
164
172
|
// The shared registry is what lets one custom home find another: every runtime
|
|
165
173
|
// that published a runtime record registered its home beside the shared clients.
|
|
166
174
|
const registry = readOwnerRegistry();
|
|
@@ -204,6 +212,8 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
const queue = [...candidates];
|
|
215
|
+
const availability = new Map<string, ReturnType<typeof loopbackFamilyAvailability>>();
|
|
216
|
+
const checkAvailability = options.loopbackAvailabilityFn ?? loopbackFamilyAvailability;
|
|
207
217
|
const probed = new Set<number>();
|
|
208
218
|
for (const port of queue) {
|
|
209
219
|
if (probed.has(port)) continue;
|
|
@@ -224,6 +234,11 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
224
234
|
noteIndeterminate(port, "ownership discovery ran out of its shared time budget");
|
|
225
235
|
break;
|
|
226
236
|
}
|
|
237
|
+
// Disabled loopback IPv6 can route ::1 off-host and time out. An ephemeral
|
|
238
|
+
// local bind proves address absence without treating an available family's
|
|
239
|
+
// timeout as empty. Unknown bind failures retain the existing probes.
|
|
240
|
+
if (!availability.has(hostname)) availability.set(hostname, checkAvailability(hostname));
|
|
241
|
+
if (availability.get(hostname) === "unavailable") continue;
|
|
227
242
|
const identity = await proxyIdentityAt(port, { hostname }, probeIo);
|
|
228
243
|
if (identity === null) {
|
|
229
244
|
// A null identity is not "free": distinguish a unanimous connect refusal
|
|
@@ -291,33 +306,35 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
291
306
|
}
|
|
292
307
|
|
|
293
308
|
/** Mark this process before any shared-client write when another home owns the clients. */
|
|
294
|
-
export async function markCrossHomeSibling(
|
|
295
|
-
|
|
309
|
+
export async function markCrossHomeSibling(
|
|
310
|
+
options: Parameters<typeof findCrossHomeOwnerDetailed>[0] = {},
|
|
311
|
+
): Promise<boolean> {
|
|
312
|
+
const verdict = await findCrossHomeOwnerDetailed(options);
|
|
296
313
|
if (verdict.kind === "none") return false;
|
|
297
|
-
if (verdict.port === null) throw new Error("Shared-client owner could not be located; refusing startup before any shared-client write.");
|
|
298
314
|
if (verdict.kind === "indeterminate") {
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"
|
|
304
|
-
+ "treating this instance as a sibling so Codex, Grok and Claude configs are left alone.",
|
|
315
|
+
// A missed probe is not proof of a sibling topology. Refuse before publication
|
|
316
|
+
// so the service can retry discovery without freezing uncertainty for its lifetime.
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Shared-client owner could not be verified (${verdict.reason}); `
|
|
319
|
+
+ "refusing startup before any shared-client write. Retry startup after discovery recovers.",
|
|
305
320
|
);
|
|
306
321
|
}
|
|
307
|
-
|
|
308
|
-
markSiblingStart(port);
|
|
322
|
+
markSiblingStart(verdict.port);
|
|
309
323
|
return true;
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
/** A live proxy in this home can publish its sibling owner even while that owner is down. */
|
|
313
|
-
export async function markLiveHomeSibling(
|
|
327
|
+
export async function markLiveHomeSibling(
|
|
328
|
+
live: { pid: number | null; port: number },
|
|
329
|
+
options: Parameters<typeof findCrossHomeOwnerDetailed>[0] = {},
|
|
330
|
+
): Promise<boolean> {
|
|
314
331
|
const runtime = readRuntimePort();
|
|
315
332
|
if (live.pid !== null && runtime?.pid === live.pid && runtime.port === live.port
|
|
316
333
|
&& runtime.siblingOfPort !== undefined && runtime.siblingOfPort !== live.port) {
|
|
317
334
|
markSiblingStart(runtime.siblingOfPort);
|
|
318
335
|
return true;
|
|
319
336
|
}
|
|
320
|
-
const verdict = await findCrossHomeOwnerDetailed();
|
|
337
|
+
const verdict = await findCrossHomeOwnerDetailed(options);
|
|
321
338
|
if (verdict.kind === "none" || verdict.port === live.port) return false;
|
|
322
339
|
if (verdict.port === null) throw new Error("Shared-client owner could not be located; refusing startup before any shared-client write.");
|
|
323
340
|
markSiblingStart(verdict.port);
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -370,19 +370,32 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
370
370
|
},
|
|
371
371
|
login: async deps => {
|
|
372
372
|
const loginArgs = deps.args.slice(1);
|
|
373
|
+
// `--headless` is the only flag this command takes; the provider is the first bare word, so
|
|
374
|
+
// `ocx login --headless anthropic` and `ocx login anthropic --headless` mean the same thing.
|
|
375
|
+
// It is deliberately not `--code`: that name carries a secret value everywhere else in the
|
|
376
|
+
// CLI (`ocx account login <provider> --code <value>`) and its operand is scrubbed from stderr.
|
|
377
|
+
const codeMode = loginArgs.includes("--headless");
|
|
378
|
+
const positional = loginArgs.filter(arg => !arg.startsWith("--"));
|
|
373
379
|
// 'ocx login codex' is the command people type first, and until now it answered with
|
|
374
380
|
// the full provider wall because the Codex pool lives behind 'ocx account login'.
|
|
375
381
|
// Route the three Codex spellings to that flow instead of making the user discover
|
|
376
382
|
// a second noun. Everything else stays on the local OAuth/API-key path.
|
|
377
383
|
const { isCodexAccountLoginName, handleAccountAuthCommand } = await import("./account-auth");
|
|
378
|
-
if (isCodexAccountLoginName(
|
|
384
|
+
if (isCodexAccountLoginName(positional[0] ?? "")) {
|
|
385
|
+
// `--headless` is the OAuth code-display mode, which Codex does not have; account-auth
|
|
386
|
+
// would reject the flag as an unexpected argument. Codex's headless path is the device-code
|
|
387
|
+
// login, so point there instead.
|
|
388
|
+
if (codeMode) {
|
|
389
|
+
console.error("Codex has no code-display login. For a headless host use: ocx account login codex --device");
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
379
392
|
// null means "unknown subcommand", which "login" never is; the coalesce exists because
|
|
380
393
|
// the shared signature serves callers that do pass an unknown one.
|
|
381
394
|
const code = await handleAccountAuthCommand("login", loginArgs, { findLiveProxy: deps.findLiveProxy });
|
|
382
395
|
return code ?? 1;
|
|
383
396
|
}
|
|
384
397
|
const { handleLogin } = await import("../oauth/login-cli");
|
|
385
|
-
await handleLogin(
|
|
398
|
+
await handleLogin(positional[0], {}, { codeMode });
|
|
386
399
|
return 0;
|
|
387
400
|
},
|
|
388
401
|
logout: async deps => {
|
|
@@ -461,11 +474,15 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
461
474
|
code = 1;
|
|
462
475
|
console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
|
|
463
476
|
}
|
|
464
|
-
//
|
|
477
|
+
// Catalog/model-cache writes retain their warning/restart handling. This is
|
|
465
478
|
// deliberately not an `else`: refreshCodexModelCatalog runs before injectCodexConfig,
|
|
466
479
|
// so a sync can fail (`ok: false`) after the catalog was already rewritten — which is
|
|
467
480
|
// exactly when a long-lived app-server is holding the stale list.
|
|
468
|
-
|
|
481
|
+
// A configuration-only change also needs a reload, but only when explicitly
|
|
482
|
+
// requested. A successful no-op or refused write is never restart authority.
|
|
483
|
+
const requestedConfigRestart = (restartScope.appServers || restartScope.desktopApp)
|
|
484
|
+
&& synced.ok && synced.configChanged === true;
|
|
485
|
+
if (synced.catalogWritten || synced.cacheSynced || requestedConfigRestart) {
|
|
469
486
|
await handleRestartScopeAfterWrite(restartScope, console);
|
|
470
487
|
}
|
|
471
488
|
// `ocx sync` is a direct CLI path; it does not call the management
|
|
@@ -945,6 +962,10 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
945
962
|
const { handleLabCommand } = await import("./lab");
|
|
946
963
|
return await handleLabCommand(deps.args.slice(1));
|
|
947
964
|
},
|
|
965
|
+
"codex-provider": async deps => {
|
|
966
|
+
const { handleCodexProviderCommand } = await import("./codex-provider");
|
|
967
|
+
return await handleCodexProviderCommand(deps.args.slice(1));
|
|
968
|
+
},
|
|
948
969
|
chatgpt: async deps => {
|
|
949
970
|
const { handleChatgptCommand } = await import("./chatgpt-command");
|
|
950
971
|
return await handleChatgptCommand(deps.args.slice(1));
|
package/src/cli/doctor.ts
CHANGED
|
@@ -1160,6 +1160,11 @@ async function fetchExposedModelIds(live: LiveProxy, fetchFn: ExposedModelsFetch
|
|
|
1160
1160
|
if (id.length === 0) continue;
|
|
1161
1161
|
if (id.length > EXPOSED_MODEL_ID_MAX_LENGTH) return null;
|
|
1162
1162
|
ids.add(id);
|
|
1163
|
+
const nativeId = (row as { native_id?: unknown }).native_id;
|
|
1164
|
+
if (nativeId !== undefined) {
|
|
1165
|
+
if (typeof nativeId !== "string" || nativeId.length > EXPOSED_MODEL_ID_MAX_LENGTH) return null;
|
|
1166
|
+
if (nativeId.length > 0) ids.add(nativeId);
|
|
1167
|
+
}
|
|
1163
1168
|
}
|
|
1164
1169
|
return ids;
|
|
1165
1170
|
} catch {
|
|
@@ -1467,7 +1472,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1467
1472
|
|
|
1468
1473
|
// Observe-only and warning-level: a bypassed Desktop first-party install still serves the
|
|
1469
1474
|
// CLI and every non-Desktop client, so this never records a doctor failure.
|
|
1470
|
-
const desktopSystemProxyLines = formatDesktopSystemProxyLines(collectDesktopSystemProxy(doctorConfig));
|
|
1475
|
+
const desktopSystemProxyLines = formatDesktopSystemProxyLines(await collectDesktopSystemProxy(doctorConfig));
|
|
1471
1476
|
if (desktopSystemProxyLines.length > 0) {
|
|
1472
1477
|
console.log("\nClaude Desktop first-party vs Windows system proxy");
|
|
1473
1478
|
for (const line of desktopSystemProxyLines) console.log(line);
|