@bitkyc08/opencodex 2.84.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +44 -3
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +41 -9
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cold start for `ocx ensure` when OpenCodex Desktop owns or runs the proxy (#6971).
|
|
3
|
+
*
|
|
4
|
+
* The Codex launcher shim runs `ocx ensure` before every Codex launch. Without this policy,
|
|
5
|
+
* ensure spawned a detached CLI proxy whenever nothing answered on the port. That is wrong in two
|
|
6
|
+
* Desktop situations: during the Desktop supervisor's restart backoff, and after Desktop quit
|
|
7
|
+
* while its durable ownership claim remains. In both, the CLI proxy took the port and Desktop came
|
|
8
|
+
* back as a guest without supervision. That is why the dashboard used to disable the shim under
|
|
9
|
+
* Desktop.
|
|
10
|
+
*
|
|
11
|
+
* Rules, in order:
|
|
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, 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
|
|
16
|
+
* never authorizes a CLI proxy by itself; handing ownership to the CLI stays an explicit
|
|
17
|
+
* `ocx service install` consent step.
|
|
18
|
+
* - Ownership or the process list that cannot be read is not evidence of absence: refuse.
|
|
19
|
+
* - No claim and no running app: the ordinary CLI cold start.
|
|
20
|
+
*
|
|
21
|
+
* Concurrent shim launches share one Desktop launch through a lease file held by a live process;
|
|
22
|
+
* every caller then waits on the same readiness probe.
|
|
23
|
+
*/
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
import { randomUUID } from "node:crypto";
|
|
26
|
+
import { accessSync, constants, existsSync, linkSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
import { basename, dirname, join, posix, win32 } from "node:path";
|
|
29
|
+
import { getConfigDir } from "../config/paths";
|
|
30
|
+
import { resolveServiceOwnership } from "../service/state";
|
|
31
|
+
|
|
32
|
+
export type DesktopProcessState = "running" | "absent" | "unknown";
|
|
33
|
+
export type DesktopOwnershipState = "desktop" | "other" | "unknown";
|
|
34
|
+
|
|
35
|
+
export type EnsureColdStartPlan =
|
|
36
|
+
| { kind: "spawn-cli" }
|
|
37
|
+
| { kind: "wait-desktop" }
|
|
38
|
+
| { kind: "launch-desktop" }
|
|
39
|
+
| { kind: "refuse"; reason: "desktop-unlaunchable" | "unverified" };
|
|
40
|
+
|
|
41
|
+
export function planEnsureColdStart(facts: {
|
|
42
|
+
ownership: DesktopOwnershipState;
|
|
43
|
+
desktopProcess: DesktopProcessState;
|
|
44
|
+
platform: NodeJS.Platform;
|
|
45
|
+
}): EnsureColdStartPlan {
|
|
46
|
+
if (facts.desktopProcess === "running") return { kind: "wait-desktop" };
|
|
47
|
+
if (facts.ownership === "unknown" || facts.desktopProcess === "unknown") return { kind: "refuse", reason: "unverified" };
|
|
48
|
+
if (facts.ownership === "other") return { kind: "spawn-cli" };
|
|
49
|
+
return facts.platform === "darwin" ? { kind: "launch-desktop" } : { kind: "refuse", reason: "desktop-unlaunchable" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Desktop restart backoff tops out at 30 s; the budget covers it plus a cold sidecar start. */
|
|
53
|
+
export const DESKTOP_COLD_START_TIMEOUT_MS = 40_000;
|
|
54
|
+
|
|
55
|
+
export type DesktopColdStartResult<T> =
|
|
56
|
+
| { kind: "cli" }
|
|
57
|
+
| { kind: "live"; live: T }
|
|
58
|
+
| { kind: "failed"; message: string };
|
|
59
|
+
|
|
60
|
+
export interface DesktopColdStartDeps<T> {
|
|
61
|
+
platform: NodeJS.Platform;
|
|
62
|
+
ownership: () => DesktopOwnershipState;
|
|
63
|
+
desktopProcess: () => DesktopProcessState;
|
|
64
|
+
/** Start the Desktop app hidden. Returns false when the request could not be made. */
|
|
65
|
+
launchDesktop: () => boolean;
|
|
66
|
+
/** Returns a release function when this caller should launch, or null when another caller already did. */
|
|
67
|
+
acquireLaunchLease: () => (() => void) | null;
|
|
68
|
+
waitForProxy: (timeoutMs: number) => Promise<T | null>;
|
|
69
|
+
info?: (line: string) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const QUIT_HINT = "To run the proxy from this CLI instead, quit OpenCodex and run `ocx service install` or `ocx start`.";
|
|
73
|
+
|
|
74
|
+
export async function runDesktopColdStart<T>(deps: DesktopColdStartDeps<T>): Promise<DesktopColdStartResult<T>> {
|
|
75
|
+
const plan = planEnsureColdStart({
|
|
76
|
+
ownership: deps.ownership(),
|
|
77
|
+
desktopProcess: deps.desktopProcess(),
|
|
78
|
+
platform: deps.platform,
|
|
79
|
+
});
|
|
80
|
+
if (plan.kind === "spawn-cli") return { kind: "cli" };
|
|
81
|
+
if (plan.kind === "refuse") {
|
|
82
|
+
return { kind: "failed", message: plan.reason === "unverified"
|
|
83
|
+
? "Could not verify whether OpenCodex Desktop manages this proxy, so no CLI proxy was started. Run `ocx doctor`, or start the proxy explicitly with `ocx start`."
|
|
84
|
+
: `OpenCodex Desktop manages this proxy but is not running. Open OpenCodex to start it. ${QUIT_HINT}` };
|
|
85
|
+
}
|
|
86
|
+
if (plan.kind === "launch-desktop") {
|
|
87
|
+
const release = deps.acquireLaunchLease();
|
|
88
|
+
try {
|
|
89
|
+
if (release) {
|
|
90
|
+
deps.info?.("Starting OpenCodex Desktop, which manages this proxy...");
|
|
91
|
+
if (!deps.launchDesktop()) {
|
|
92
|
+
return { kind: "failed", message: `OpenCodex Desktop manages this proxy, but it could not be started. Open OpenCodex manually. ${QUIT_HINT}` };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const live = await deps.waitForProxy(DESKTOP_COLD_START_TIMEOUT_MS);
|
|
96
|
+
if (live) return { kind: "live", live };
|
|
97
|
+
return { kind: "failed", message: `OpenCodex Desktop was started, but its proxy did not answer in time. Check the OpenCodex menu. ${QUIT_HINT}` };
|
|
98
|
+
} finally { release?.(); }
|
|
99
|
+
}
|
|
100
|
+
deps.info?.("Waiting for OpenCodex Desktop to restart its proxy...");
|
|
101
|
+
const live = await deps.waitForProxy(DESKTOP_COLD_START_TIMEOUT_MS);
|
|
102
|
+
if (live) return { kind: "live", live };
|
|
103
|
+
return {
|
|
104
|
+
kind: "failed",
|
|
105
|
+
message: `OpenCodex Desktop is running but its proxy is not answering. Use Start Proxy in the OpenCodex menu. ${QUIT_HINT}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Execute = (command: string, args: string[], timeoutMs: number) => string;
|
|
110
|
+
|
|
111
|
+
function execute(command: string, args: string[], timeoutMs: number): string {
|
|
112
|
+
return execFileSync(command, args, {
|
|
113
|
+
encoding: "utf8", timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A Desktop app executable: `opencodex-desktop`, inside an app bundle's Contents/MacOS on macOS. */
|
|
118
|
+
export function isDesktopExecutable(path: string, platform: NodeJS.Platform): boolean {
|
|
119
|
+
if (basename(path) !== "opencodex-desktop") return false;
|
|
120
|
+
return platform !== "darwin" || (basename(dirname(path)) === "MacOS" && basename(dirname(dirname(path))) === "Contents");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DesktopProcessOptions {
|
|
124
|
+
platform?: NodeJS.Platform;
|
|
125
|
+
uid?: number;
|
|
126
|
+
env?: NodeJS.ProcessEnv;
|
|
127
|
+
run?: Execute;
|
|
128
|
+
/** Linux: executable paths of this user's processes, or \`unverified\`. */
|
|
129
|
+
linuxExecutables?: (uid: number) => string[] | "unverified";
|
|
130
|
+
/** Linux: whether procfs hides processes (hidepid=2/4). */
|
|
131
|
+
procfsHidesProcesses?: () => boolean;
|
|
132
|
+
/** Linux: whether this machine shows evidence of an OpenCodex Desktop install. */
|
|
133
|
+
desktopInstalled?: () => boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Linux: hidepid=2 (invisible) or 4 (ptraceable) can hide a same-uid process whose credentials
|
|
138
|
+
* differ from the caller's (for example after newgrp), so an empty scan proves nothing there.
|
|
139
|
+
* An unreadable mount table is treated as filtering.
|
|
140
|
+
*/
|
|
141
|
+
export function procfsHidesProcesses(mountinfo = "/proc/self/mountinfo"): boolean {
|
|
142
|
+
let text: string;
|
|
143
|
+
try { text = readFileSync(mountinfo, "utf8"); } catch { return true; }
|
|
144
|
+
return text.split("\n").some(line => {
|
|
145
|
+
const [mount, fs] = line.split(" - ");
|
|
146
|
+
if (mount?.split(" ")[4] !== "/proc" || !fs) return false;
|
|
147
|
+
const [type, , options = ""] = fs.split(" ");
|
|
148
|
+
return type === "proc" && options.split(",").some(option => /^hidepid=(2|4|invisible|ptraceable)$/.test(option));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function linuxDesktopInstalled(): boolean {
|
|
153
|
+
return existsSync(join(homedir(), ".opencodex-desktop")) || existsSync("/usr/bin/opencodex-desktop");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Linux truncates comm to 15 bytes. */
|
|
157
|
+
const LINUX_DESKTOP_COMM = "opencodex-desktop".slice(0, 15);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Linux: executables of this user's processes. A process that exited mid-scan or belongs to someone
|
|
161
|
+
* else is skipped. A non-dumpable process (ssh-agent, gpg-agent) hides its executable but not its
|
|
162
|
+
* comm, so it is ruled out by name; one whose comm could be Desktop makes the answer unverifiable.
|
|
163
|
+
* The scan must also see this process, or procfs is filtered and absence proves nothing.
|
|
164
|
+
*/
|
|
165
|
+
export function scanLinuxExecutables(uid: number, selfPid: number = process.pid, proc = "/proc"): string[] | "unverified" {
|
|
166
|
+
const paths: string[] = [];
|
|
167
|
+
let sawSelf = false;
|
|
168
|
+
const code = (error: unknown) => (error as { code?: unknown } | null)?.code;
|
|
169
|
+
for (const entry of readdirSync(proc)) {
|
|
170
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
171
|
+
if (Number(entry) === selfPid) sawSelf = true;
|
|
172
|
+
try {
|
|
173
|
+
if (statSync(`${proc}/${entry}`).uid !== uid) continue;
|
|
174
|
+
paths.push(readlinkSync(`${proc}/${entry}/exe`).replace(/ \(deleted\)$/, ""));
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (code(error) === "ENOENT" || code(error) === "ESRCH") continue;
|
|
177
|
+
try {
|
|
178
|
+
if (readFileSync(`${proc}/${entry}/comm`, "utf8").trim() !== LINUX_DESKTOP_COMM) continue;
|
|
179
|
+
} catch (commError) {
|
|
180
|
+
if (code(commError) === "ENOENT" || code(commError) === "ESRCH") continue;
|
|
181
|
+
}
|
|
182
|
+
return "unverified";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return sawSelf ? paths : "unverified";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Whether this user runs an OpenCodex Desktop app, judged by executable identity, never arguments. */
|
|
189
|
+
export function inspectDesktopProcess(options: DesktopProcessOptions = {}): DesktopProcessState {
|
|
190
|
+
const platform = options.platform ?? process.platform;
|
|
191
|
+
const run = options.run ?? execute;
|
|
192
|
+
try {
|
|
193
|
+
const uid = options.uid ?? process.getuid?.();
|
|
194
|
+
if (platform === "linux") {
|
|
195
|
+
if (uid === undefined) return "unknown";
|
|
196
|
+
const executables = (options.linuxExecutables ?? scanLinuxExecutables)(uid);
|
|
197
|
+
if (executables === "unverified") return "unknown";
|
|
198
|
+
if (executables.some(path => isDesktopExecutable(path, platform))) return "running";
|
|
199
|
+
// A filtered procfs cannot prove absence where Desktop may be installed. Without an install
|
|
200
|
+
// there is nothing to hide, so hardened hosts keep the plain CLI cold start.
|
|
201
|
+
return (options.procfsHidesProcesses ?? procfsHidesProcesses)() && (options.desktopInstalled ?? linuxDesktopInstalled)()
|
|
202
|
+
? "unknown" : "absent";
|
|
203
|
+
}
|
|
204
|
+
if (platform === "darwin") {
|
|
205
|
+
if (uid === undefined) return "unknown";
|
|
206
|
+
// macOS reports the full executable path as comm.
|
|
207
|
+
const output = run("/bin/ps", ["-U", String(uid), "-o", "comm="], 3_000);
|
|
208
|
+
return output.split("\n").some(line => isDesktopExecutable(line.trim(), platform)) ? "running" : "absent";
|
|
209
|
+
}
|
|
210
|
+
if (platform === "win32") {
|
|
211
|
+
const env = options.env ?? process.env;
|
|
212
|
+
const root = env.SystemRoot ?? env.windir ?? "C:\\Windows";
|
|
213
|
+
const user = env.USERNAME?.trim();
|
|
214
|
+
const args = ["/FI", "IMAGENAME eq opencodex-desktop.exe", ...(user ? ["/FI", `USERNAME eq ${user}`] : []), "/FO", "CSV", "/NH"];
|
|
215
|
+
const output = run(win32.join(root, "System32", "tasklist.exe"), args, 3_000);
|
|
216
|
+
return /^"opencodex-desktop\.exe",/im.test(output) ? "running" : "absent";
|
|
217
|
+
}
|
|
218
|
+
return "absent";
|
|
219
|
+
} catch {
|
|
220
|
+
return "unknown";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
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 {
|
|
274
|
+
if ((options.platform ?? process.platform) !== "darwin") return false;
|
|
275
|
+
try {
|
|
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);
|
|
281
|
+
return true;
|
|
282
|
+
} catch { return false; }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function processAlive(pid: number): boolean {
|
|
286
|
+
try { process.kill(pid, 0); return true; }
|
|
287
|
+
catch (error) { return (error as NodeJS.ErrnoException | null)?.code === "EPERM"; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function readHolder(path: string): number | null {
|
|
291
|
+
const pid = Number(readFileSync(path, "utf8").trim());
|
|
292
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const LEASE_FILE = /^desktop-launch\.(\d+)\.lease$/;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* One Desktop launch per cold start. Leases are numbered generations, each published whole with
|
|
299
|
+
* link(2), which fails if the name exists, so exactly one contender can create generation N+1. A
|
|
300
|
+
* caller launches only after creating the next generation while the newest one belongs to a dead
|
|
301
|
+
* process (or none exists). A lease stays valid while its holder lives, so a slow launch never
|
|
302
|
+
* expires under its owner. Nothing ever moves or overwrites another holder's lease.
|
|
303
|
+
*/
|
|
304
|
+
export function acquireDesktopLaunchLease(
|
|
305
|
+
configDir: string,
|
|
306
|
+
io: { pid?: number; alive?: (pid: number) => boolean } = {},
|
|
307
|
+
): (() => void) | null {
|
|
308
|
+
const pid = io.pid ?? process.pid;
|
|
309
|
+
const alive = io.alive ?? processAlive;
|
|
310
|
+
const leasePath = (generation: number) => join(configDir, `desktop-launch.${generation}.lease`);
|
|
311
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
312
|
+
let generations: number[];
|
|
313
|
+
try {
|
|
314
|
+
generations = readdirSync(configDir).flatMap(name => {
|
|
315
|
+
const match = LEASE_FILE.exec(name);
|
|
316
|
+
return match ? [Number(match[1])] : [];
|
|
317
|
+
}).sort((a, b) => b - a);
|
|
318
|
+
} catch {
|
|
319
|
+
// An unreadable lease directory must not suppress the launch; it only loses deduplication.
|
|
320
|
+
return () => {};
|
|
321
|
+
}
|
|
322
|
+
const newest = generations[0];
|
|
323
|
+
if (newest !== undefined) {
|
|
324
|
+
try {
|
|
325
|
+
const holder = readHolder(leasePath(newest));
|
|
326
|
+
if (holder !== null && alive(holder)) return null;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if ((error as { code?: unknown })?.code === "ENOENT") continue;
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const next = (newest ?? 0) + 1;
|
|
333
|
+
const staged = join(configDir, `desktop-launch.${randomUUID()}.tmp`);
|
|
334
|
+
try {
|
|
335
|
+
writeFileSync(staged, String(pid), { flag: "wx", mode: 0o600 });
|
|
336
|
+
linkSync(staged, leasePath(next));
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if ((error as { code?: unknown })?.code === "EEXIST") continue;
|
|
339
|
+
return () => {};
|
|
340
|
+
} finally {
|
|
341
|
+
try { unlinkSync(staged); } catch { /* not created */ }
|
|
342
|
+
}
|
|
343
|
+
const withdraw = () => {
|
|
344
|
+
try { if (readHolder(leasePath(next)) === pid) unlinkSync(leasePath(next)); } catch { /* already gone */ }
|
|
345
|
+
};
|
|
346
|
+
// A contender with a stale directory read can publish a generation a winner already cleaned
|
|
347
|
+
// up. The highest generation is the one launcher; anyone below it stands down.
|
|
348
|
+
try {
|
|
349
|
+
if (readdirSync(configDir).some(name => Number(LEASE_FILE.exec(name)?.[1] ?? 0) > next)) {
|
|
350
|
+
withdraw();
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
} catch { /* keep the lease: the directory was readable a moment ago */ }
|
|
354
|
+
// Older generations belong to dead holders: the newest was checked before publishing above it.
|
|
355
|
+
for (const generation of generations) {
|
|
356
|
+
try { unlinkSync(leasePath(generation)); } catch { /* already gone */ }
|
|
357
|
+
}
|
|
358
|
+
// The newest generation is never deleted on release: removing it would let numbering restart
|
|
359
|
+
// below a stale contender. It stops counting once this process exits.
|
|
360
|
+
return () => {};
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Production wiring; `waitForProxy` is ensure's identity-checked readiness probe. */
|
|
366
|
+
export function productionDesktopColdStartDeps<T>(waitForProxy: (timeoutMs: number) => Promise<T | null>): DesktopColdStartDeps<T> {
|
|
367
|
+
return {
|
|
368
|
+
platform: process.platform,
|
|
369
|
+
ownership: () => {
|
|
370
|
+
try {
|
|
371
|
+
const owner = resolveServiceOwnership();
|
|
372
|
+
if (owner.kind === "unknown") return "unknown";
|
|
373
|
+
return owner.kind === "owned" && owner.ownership.owner === "desktop" ? "desktop" : "other";
|
|
374
|
+
} catch { return "unknown"; }
|
|
375
|
+
},
|
|
376
|
+
desktopProcess: () => inspectDesktopProcess(),
|
|
377
|
+
launchDesktop: () => launchDesktopApp(),
|
|
378
|
+
acquireLaunchLease: () => acquireDesktopLaunchLease(getConfigDir()),
|
|
379
|
+
waitForProxy,
|
|
380
|
+
info: line => console.log(line),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
@@ -169,7 +169,7 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
|
|
|
169
169
|
const spec = EXPORT_CLIENTS[client];
|
|
170
170
|
const root = await runtimeBaseUrl(deps);
|
|
171
171
|
let built: { document: unknown; text: string };
|
|
172
|
-
if (client === "raycast" || client === "droid") {
|
|
172
|
+
if (client === "raycast" || client === "droid" || client === "qoder") {
|
|
173
173
|
// The dial address alone cannot distinguish a wildcard authenticated bind
|
|
174
174
|
// from loopback. Let the live server resolve its admission/listener policy;
|
|
175
175
|
// saved config can differ from the process serving this request.
|
package/src/cli/help.ts
CHANGED
|
@@ -85,20 +85,22 @@ Usage:
|
|
|
85
85
|
ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
|
|
86
86
|
ocx route <sub> Routing features (combo, policy)
|
|
87
87
|
ocx logs [filters] Alias of ocx observe logs
|
|
88
|
-
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
|
|
88
|
+
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>] [--top <1-1000>]
|
|
89
89
|
Token and estimated-cost report (alias of ocx observe usage)
|
|
90
90
|
ocx storage <sub> Storage report, cleanup, trash, and the cleanup policy
|
|
91
91
|
ocx memory [--json] Alias of ocx observe memory
|
|
92
92
|
ocx api-key <sub> Alias of ocx access key
|
|
93
93
|
ocx access <sub> External API keys and endpoint information
|
|
94
94
|
ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
|
|
95
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
95
|
+
ocx export --client <id> Print a client config wired to the running proxy (20 clients)
|
|
96
96
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
97
97
|
ocx grok <sub> Grok Build model selection and apply
|
|
98
98
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
99
99
|
ocx config [sub] Validated configuration show/get/set/import/export
|
|
100
|
+
ocx advisor <status|on|off|set|consent> Advisor sidecar settings and context-sharing consent
|
|
100
101
|
ocx companion <show|set|reset> Menu-bar and widget companion usage settings
|
|
101
102
|
ocx lab <sub> Inspect Lab evidence and control local automation
|
|
103
|
+
ocx codex-provider <sub> Confirm custom Codex provider routing: status|allow|revoke
|
|
102
104
|
ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
|
|
103
105
|
ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
|
|
104
106
|
ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
|
package/src/cli/index.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { UpdateRestartRequired } from "./update-restart-candidate";
|
|
|
16
16
|
import { describeUpdateRestartFailure, restartFromCurrentInstallation } from "./update-restart";
|
|
17
17
|
import { findGuiDist } from "../server/gui-static";
|
|
18
18
|
import { inspectGuiBundleFreshness, staleGuiBundleLines } from "../server/gui-freshness";
|
|
19
|
-
|
|
20
19
|
// Best-effort recovery for runtime execution and spawned children if launched
|
|
21
20
|
// from an unlinked/deleted working directory (runs after hoisted ESM module imports).
|
|
22
21
|
try {
|
|
@@ -156,9 +155,9 @@ import {
|
|
|
156
155
|
reconcileEnsureDesiredIntegrations,
|
|
157
156
|
} from "./ensure-desired-integrations";
|
|
158
157
|
import { ENSURE_READY_TIMEOUT_MS, ensureKeepWaiting, waitForLiveProxy } from "./ensure-readiness";
|
|
158
|
+
import { productionDesktopColdStartDeps, runDesktopColdStart } from "./ensure-desktop-cold-start";
|
|
159
159
|
import { refreshOwnedCatalogIntegrations } from "../integrations/catalog-refresh";
|
|
160
160
|
import { loadExportModels } from "../server/management/model-rows";
|
|
161
|
-
|
|
162
161
|
import { removeOwnedConfigAfterDesktopCleanup } from "./uninstall-client-state";
|
|
163
162
|
import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
|
|
164
163
|
import { startArgv } from "../lib/self-launch-argv";
|
|
@@ -169,6 +168,7 @@ import { recoverStartStateUnderOwnershipLease } from "./start-owner-fence";
|
|
|
169
168
|
import { handledSignalExitCode } from "../lib/handled-signal-exit";
|
|
170
169
|
import { createLocalAttestationSecret } from "../lib/local-management-attestation";
|
|
171
170
|
import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
|
|
171
|
+
import { startServiceLogging } from "../service/logging";
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
|
|
@@ -208,6 +208,7 @@ async function refreshOwnedRaycastCatalog(
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
if (process.argv[2] === "start" && process.env.OCX_SERVICE) startServiceLogging(join(getConfigDir(), "service.log"));
|
|
211
212
|
const updateRestartChild = admitUpdateRestartChild(process.argv.slice(2));
|
|
212
213
|
initializeNodeLauncherContext();
|
|
213
214
|
// The compiled executable is also the capture-only MCP server's launcher.
|
|
@@ -282,6 +283,7 @@ async function chooseListenPort(
|
|
|
282
283
|
`Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
|
|
283
284
|
);
|
|
284
285
|
}
|
|
286
|
+
await (await import("./auxiliary-listener-preflight")).assertAuxiliaryListenerPortsAvailable(config);
|
|
285
287
|
// Soft start: brief prefer-retry then ephemeral hop.
|
|
286
288
|
// Explicit `--port` (service wrappers / update restart): wait for the pinned port
|
|
287
289
|
// to free without killing any listener (healthy ocx / foreign). Never hop.
|
|
@@ -658,7 +660,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
658
660
|
}
|
|
659
661
|
removePid(process.pid);
|
|
660
662
|
removeRuntimePort(process.pid);
|
|
661
|
-
if (teardown.restoreNativeCodex
|
|
663
|
+
if (teardown.restoreNativeCodex) {
|
|
662
664
|
try {
|
|
663
665
|
if (!codexHomeIsAbsent(getCodexHome())) {
|
|
664
666
|
const restored = restoreNativeCodex();
|
|
@@ -816,9 +818,16 @@ async function handleEnsure(options: { existingIsSuccess?: boolean; forceStart?:
|
|
|
816
818
|
console.log("Codex autostart is disabled.");
|
|
817
819
|
return false;
|
|
818
820
|
}
|
|
819
|
-
|
|
821
|
+
let live = owner.live;
|
|
822
|
+
if (!live) {
|
|
823
|
+
// OpenCodex Desktop owns or runs this proxy: wait for it or start it, never a competing CLI proxy (#6971).
|
|
824
|
+
const desktopStart = await runDesktopColdStart(productionDesktopColdStartDeps(timeoutMs => waitForProxy(timeoutMs)));
|
|
825
|
+
if (desktopStart.kind === "failed") { console.error(`❌ ${desktopStart.message}`); process.exitCode = 1; return false; }
|
|
826
|
+
// Also for restart: the proxy was absent and Desktop started it, so the live tail below applies.
|
|
827
|
+
if (desktopStart.kind === "live") live = desktopStart.live;
|
|
828
|
+
}
|
|
820
829
|
if (live) {
|
|
821
|
-
if (options.existingIsSuccess === false) {
|
|
830
|
+
if (options.existingIsSuccess === false && live === owner.live) {
|
|
822
831
|
console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
|
|
823
832
|
return false;
|
|
824
833
|
}
|
|
@@ -26,6 +26,8 @@ export const FILE_INTEGRATION_CLIENTS = [
|
|
|
26
26
|
"commandcode",
|
|
27
27
|
"kilo",
|
|
28
28
|
"droid",
|
|
29
|
+
"qoder",
|
|
30
|
+
"gentle-shell",
|
|
29
31
|
] as const;
|
|
30
32
|
|
|
31
33
|
export const INTEGRATION_REFUSAL_REASONS: ReadonlySet<string> = new Set<IntegrationRefusalReason>([
|
|
@@ -65,6 +67,7 @@ const PLAN_SCHEMA_PATHS = new Set([
|
|
|
65
67
|
"settings.providers.opencodex",
|
|
66
68
|
"catalog.providers.opencodex",
|
|
67
69
|
"customModels.*",
|
|
70
|
+
"modelConfigs.customModels.*",
|
|
68
71
|
// ZCode reads its providers from a second file; a plan for it publishes that
|
|
69
72
|
// file's templates, and a path missing here is rejected as an invalid preview.
|
|
70
73
|
"config.providerConfigRules.providerRules.[providerId=opencodex]",
|