@bitkyc08/opencodex 2.86.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/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.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 -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.ts +108 -21
- 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.ts +35 -8
- 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 +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/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 +11 -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 +38 -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 +17 -14
- 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/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 +12 -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/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 +52 -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 +608 -284
- 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/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/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 +33 -7
- 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 +5 -4
- 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/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 +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/router.ts +8 -321
- 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 +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -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 +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- 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 +101 -21
- 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 +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- 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 +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- 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/request-prepare.ts +76 -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 +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- 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 +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -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 +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/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- 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,
|
|
@@ -160,7 +161,7 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
160
161
|
};
|
|
161
162
|
|
|
162
163
|
const defaultHome = join(options.homeDir ?? homedir(), ".opencodex");
|
|
163
|
-
recordHome(defaultHome);
|
|
164
|
+
if (options.homeDir !== undefined || !isProtectedHomeUnderTest(defaultHome)) recordHome(defaultHome);
|
|
164
165
|
// The shared registry is what lets one custom home find another: every runtime
|
|
165
166
|
// that published a runtime record registered its home beside the shared clients.
|
|
166
167
|
const registry = readOwnerRegistry();
|
|
@@ -291,33 +292,35 @@ export async function findCrossHomeOwnerDetailed(options: { homeDir?: string; io
|
|
|
291
292
|
}
|
|
292
293
|
|
|
293
294
|
/** Mark this process before any shared-client write when another home owns the clients. */
|
|
294
|
-
export async function markCrossHomeSibling(
|
|
295
|
-
|
|
295
|
+
export async function markCrossHomeSibling(
|
|
296
|
+
options: Parameters<typeof findCrossHomeOwnerDetailed>[0] = {},
|
|
297
|
+
): Promise<boolean> {
|
|
298
|
+
const verdict = await findCrossHomeOwnerDetailed(options);
|
|
296
299
|
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
300
|
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.",
|
|
301
|
+
// A missed probe is not proof of a sibling topology. Refuse before publication
|
|
302
|
+
// so the service can retry discovery without freezing uncertainty for its lifetime.
|
|
303
|
+
throw new Error(
|
|
304
|
+
`Shared-client owner could not be verified (${verdict.reason}); `
|
|
305
|
+
+ "refusing startup before any shared-client write. Retry startup after discovery recovers.",
|
|
305
306
|
);
|
|
306
307
|
}
|
|
307
|
-
|
|
308
|
-
markSiblingStart(port);
|
|
308
|
+
markSiblingStart(verdict.port);
|
|
309
309
|
return true;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
/** A live proxy in this home can publish its sibling owner even while that owner is down. */
|
|
313
|
-
export async function markLiveHomeSibling(
|
|
313
|
+
export async function markLiveHomeSibling(
|
|
314
|
+
live: { pid: number | null; port: number },
|
|
315
|
+
options: Parameters<typeof findCrossHomeOwnerDetailed>[0] = {},
|
|
316
|
+
): Promise<boolean> {
|
|
314
317
|
const runtime = readRuntimePort();
|
|
315
318
|
if (live.pid !== null && runtime?.pid === live.pid && runtime.port === live.port
|
|
316
319
|
&& runtime.siblingOfPort !== undefined && runtime.siblingOfPort !== live.port) {
|
|
317
320
|
markSiblingStart(runtime.siblingOfPort);
|
|
318
321
|
return true;
|
|
319
322
|
}
|
|
320
|
-
const verdict = await findCrossHomeOwnerDetailed();
|
|
323
|
+
const verdict = await findCrossHomeOwnerDetailed(options);
|
|
321
324
|
if (verdict.kind === "none" || verdict.port === live.port) return false;
|
|
322
325
|
if (verdict.port === null) throw new Error("Shared-client owner could not be located; refusing startup before any shared-client write.");
|
|
323
326
|
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);
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Rules, in order:
|
|
12
12
|
* - A running Desktop app is the supervisor. Wait for its proxy; never start a competing one.
|
|
13
|
-
* - A durable Desktop claim with no running app: start Desktop (hidden,
|
|
14
|
-
*
|
|
13
|
+
* - A durable Desktop claim with no running app: start Desktop (hidden, by the path its own
|
|
14
|
+
* login registration recorded — LaunchServices never picks the bundle) where that is
|
|
15
|
+
* verifiable, then wait. Elsewhere, fail with guidance. A missing Desktop process
|
|
15
16
|
* never authorizes a CLI proxy by itself; handing ownership to the CLI stays an explicit
|
|
16
17
|
* `ocx service install` consent step.
|
|
17
18
|
* - Ownership or the process list that cannot be read is not evidence of absence: refuse.
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
*/
|
|
23
24
|
import { execFileSync } from "node:child_process";
|
|
24
25
|
import { randomUUID } from "node:crypto";
|
|
25
|
-
import { existsSync, linkSync, readdirSync, readFileSync, readlinkSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { accessSync, constants, existsSync, linkSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
26
27
|
import { homedir } from "node:os";
|
|
27
|
-
import { basename, dirname, join, win32 } from "node:path";
|
|
28
|
+
import { basename, dirname, join, posix, win32 } from "node:path";
|
|
28
29
|
import { getConfigDir } from "../config/paths";
|
|
29
30
|
import { resolveServiceOwnership } from "../service/state";
|
|
30
31
|
|
|
@@ -220,11 +221,63 @@ export function inspectDesktopProcess(options: DesktopProcessOptions = {}): Desk
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
const DESKTOP_BUNDLE_ID = "com.opencodex.desktop";
|
|
225
|
+
const DESKTOP_PLIST_LABEL = "OpenCodex";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The app bundle the consented installation itself registered as its Start-at-Login program —
|
|
229
|
+
* not whichever bundle LaunchServices would resolve for our identifier. `open -b` answers a
|
|
230
|
+
* mutable per-user registry: any planted bundle claiming {@link DESKTOP_BUNDLE_ID} could be
|
|
231
|
+
* selected and executed in the real app's place. The recorded claim, this app's own install-id
|
|
232
|
+
* store, and the login registration it wrote are the durable evidence of which installation
|
|
233
|
+
* that is; a claim whose registration is absent, malformed, stale or points off a Desktop
|
|
234
|
+
* executable refuses the launch instead of guessing.
|
|
235
|
+
*/
|
|
236
|
+
function consentedDesktopBundle(home: string, installId: string, run: Execute): string | null {
|
|
237
|
+
const localId = readFileSync(
|
|
238
|
+
join(home, "Library", "Application Support", DESKTOP_BUNDLE_ID, "install-id"), "utf8",
|
|
239
|
+
).trim();
|
|
240
|
+
if (!localId || localId !== installId) return null;
|
|
241
|
+
const plistPath = realpathSync(join(home, "Library", "LaunchAgents", `${DESKTOP_PLIST_LABEL}.plist`));
|
|
242
|
+
const registration = JSON.parse(
|
|
243
|
+
run("/usr/bin/plutil", ["-convert", "json", "-o", "-", plistPath], 3_000),
|
|
244
|
+
) as { Label?: unknown; ProgramArguments?: unknown; Program?: unknown };
|
|
245
|
+
const args = registration.ProgramArguments;
|
|
246
|
+
// The shapes auto-launch writes: [program] or [program, "--autostart"]; see the migration
|
|
247
|
+
// rules in desktop/src-tauri/src/login_registration.rs, which classify anything else Leave.
|
|
248
|
+
// This is a macOS plist path, regardless of the host running an injected launch probe.
|
|
249
|
+
if (registration.Label !== DESKTOP_PLIST_LABEL || !Array.isArray(args)
|
|
250
|
+
|| (args.length !== 1 && args.length !== 2) || typeof args[0] !== "string" || !posix.isAbsolute(args[0])
|
|
251
|
+
|| (args.length === 2 && args[1] !== "--autostart")
|
|
252
|
+
|| (registration.Program !== undefined && registration.Program !== args[0])) return null;
|
|
253
|
+
const executable = realpathSync(args[0]);
|
|
254
|
+
if (!isDesktopExecutable(executable, "darwin") || !statSync(executable).isFile()) return null;
|
|
255
|
+
accessSync(executable, constants.X_OK);
|
|
256
|
+
const bundle = dirname(dirname(dirname(executable)));
|
|
257
|
+
if (!bundle.endsWith(".app")) return null;
|
|
258
|
+
const info = JSON.parse(
|
|
259
|
+
run("/usr/bin/plutil", ["-convert", "json", "-o", "-", join(bundle, "Contents", "Info.plist")], 3_000),
|
|
260
|
+
) as { CFBundleIdentifier?: unknown; CFBundleExecutable?: unknown };
|
|
261
|
+
// `open` uses this selector, so checking only the registration and identifier would
|
|
262
|
+
// validate one binary while launching another. The registered path is already canonical.
|
|
263
|
+
if (info.CFBundleIdentifier !== DESKTOP_BUNDLE_ID || info.CFBundleExecutable !== "opencodex-desktop") return null;
|
|
264
|
+
return realpathSync(join(bundle, "Contents", "MacOS", info.CFBundleExecutable)) === executable ? bundle : null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Start the consented macOS app hidden, by the path its own login registration recorded. */
|
|
268
|
+
export function launchDesktopApp(options: {
|
|
269
|
+
platform?: NodeJS.Platform;
|
|
270
|
+
run?: Execute;
|
|
271
|
+
home?: string;
|
|
272
|
+
ownership?: () => ReturnType<typeof resolveServiceOwnership>;
|
|
273
|
+
} = {}): boolean {
|
|
225
274
|
if ((options.platform ?? process.platform) !== "darwin") return false;
|
|
226
275
|
try {
|
|
227
|
-
(options.
|
|
276
|
+
const owner = (options.ownership ?? resolveServiceOwnership)();
|
|
277
|
+
if (owner.kind !== "owned" || owner.ownership.owner !== "desktop") return false;
|
|
278
|
+
const bundle = consentedDesktopBundle(options.home ?? homedir(), owner.ownership.installId, options.run ?? execute);
|
|
279
|
+
if (bundle === null) return false;
|
|
280
|
+
(options.run ?? execute)("/usr/bin/open", ["-g", bundle, "--args", "--autostart"], 10_000);
|
|
228
281
|
return true;
|
|
229
282
|
} catch { return false; }
|
|
230
283
|
}
|