@iislee/opencodex 2.11.0 → 2.31.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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/index.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { currentExternalCodexModelProvider, restoreNativeCodex, shouldInjectApiAuthHeader } from "../codex/inject";
|
|
3
|
+
import { currentExternalCodexModelProvider, restoreNativeCodex, restoreNativeCodexAsync, shouldInjectApiAuthHeader } from "../codex/inject";
|
|
4
4
|
import { stripGrokConfig } from "../grok/inject";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
describeHistoryJobFailure,
|
|
7
|
+
resolveCodexHistoryJobTarget,
|
|
8
|
+
runCodexHistoryJob,
|
|
9
|
+
} from "../codex/history-job";
|
|
6
10
|
import { reconcileJournal } from "../codex/journal";
|
|
7
11
|
import {
|
|
8
12
|
codexAutoStartEnabled,
|
|
@@ -20,51 +24,79 @@ import {
|
|
|
20
24
|
writeRuntimePort,
|
|
21
25
|
} from "../config";
|
|
22
26
|
import { collectStatus } from "./status";
|
|
23
|
-
|
|
24
|
-
import {
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
discoverStableProxyForRestart,
|
|
30
|
+
isProxyReplacement,
|
|
31
|
+
runProxyRestart,
|
|
32
|
+
runTrayProxyStart,
|
|
33
|
+
type ProxyRestartLive,
|
|
34
|
+
type ProxyRestartResult,
|
|
35
|
+
} from "./tray-proxy";
|
|
36
|
+
import { requestBoundSystemRestart } from "./system-restart-client";
|
|
25
37
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
26
|
-
import {
|
|
38
|
+
import { dispatchCommand } from "./dispatch";
|
|
27
39
|
import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
28
40
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
29
|
-
import {
|
|
41
|
+
import { createReadinessGate } from "../server/readiness";
|
|
42
|
+
import { runReady, type ReadyArgs } from "./ready";
|
|
43
|
+
import { runCli } from "./root";
|
|
44
|
+
import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
|
|
30
45
|
import { loadServiceTokenFromFile } from "../lib/service-secrets";
|
|
31
46
|
import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
|
|
32
47
|
import { startupHealthSummary } from "../codex/autostart-health";
|
|
33
48
|
import { drainAndShutdown, isRecyclingForExit, startServer } from "../server";
|
|
34
|
-
import { injectSystemEnv, revertSystemEnv } from "../server/system-env";
|
|
49
|
+
import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
|
|
35
50
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
36
|
-
import { installShellHook, uninstallShellHook } from "../server/system-env";
|
|
37
51
|
import { startTokenGuardian } from "../oauth/token-guardian";
|
|
38
52
|
import { startHistoryMigrationGuardian } from "../codex/history-migration-guardian";
|
|
39
|
-
import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
|
|
40
53
|
import { maybeShowStarPrompt } from "./star-prompt";
|
|
41
54
|
import { scheduleCatalogPrewarm } from "./catalog-prewarm";
|
|
42
55
|
import { maybeShowUpdatePrompt } from "../update/notify";
|
|
43
56
|
import { syncModelsToCodex } from "../codex/sync";
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
import {
|
|
58
|
+
shouldSyncGrokOnStart,
|
|
59
|
+
syncCodexOnStartIfEnabled,
|
|
60
|
+
} from "../codex/desired-state";
|
|
61
|
+
import {
|
|
62
|
+
reconcileClientStartupBeforeReady,
|
|
63
|
+
syncClaudeAgentDefsAtProxyStartup,
|
|
64
|
+
} from "./claude-agent-startup-sync";
|
|
65
|
+
import {
|
|
66
|
+
grokSyncFailureMessage,
|
|
67
|
+
reconcileEnsureDesiredIntegrations,
|
|
68
|
+
} from "./ensure-desired-integrations";
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
/**
|
|
71
|
+
* A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
|
|
72
|
+
* `claude-env.sh` from every new interactive shell, pointing at a proxy or a CLI that may no
|
|
73
|
+
* longer exist. `reconcileShellHook` already reports `state: "failed"`, but both call sites
|
|
74
|
+
* discarded it, so the one outcome the user has to act on was the one they never saw.
|
|
75
|
+
*/
|
|
76
|
+
function reportShellHookFailure(result: { state: "installed" | "absent" | "failed"; reason?: string }): void {
|
|
77
|
+
if (result.state !== "failed") return;
|
|
78
|
+
console.warn(` Claude shell hook not reconciled${result.reason ? `: ${result.reason}` : ""}`);
|
|
79
|
+
console.warn(" Check ~/.zshrc for the '# opencodex claude-env hook' block.");
|
|
60
80
|
}
|
|
61
81
|
|
|
62
|
-
if (command !== undefined && command !== "help" && hasHelpFlag(args.slice(1))) {
|
|
63
|
-
printSubcommandUsage(command);
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
82
|
|
|
67
|
-
|
|
83
|
+
import { removeOwnedConfigState } from "../lib/config-ownership";
|
|
84
|
+
import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
|
|
85
|
+
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
86
|
+
import { initializeNodeLauncherContext } from "./launcher-context";
|
|
87
|
+
import { createLocalAttestationSecret } from "../lib/local-management-attestation";
|
|
88
|
+
import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
|
|
89
|
+
|
|
90
|
+
initializeNodeLauncherContext();
|
|
91
|
+
|
|
92
|
+
// Head: version/help early exits, `ready` pre-parse (exit 64 before any
|
|
93
|
+
// preflight), and the bounded Codex-shim auto-restore preflight live in
|
|
94
|
+
// src/cli/root.ts (Phase 1 of the CLI deepening). runCli exits for
|
|
95
|
+
// version/help and returns the dispatchable head otherwise; the switch below
|
|
96
|
+
// owns command dispatch.
|
|
97
|
+
const head = await runCli(process.argv.slice(2));
|
|
98
|
+
const args = head.args;
|
|
99
|
+
const command = head.command;
|
|
68
100
|
|
|
69
101
|
function parsePortOption(): number | undefined {
|
|
70
102
|
if (args.length === 1) return undefined;
|
|
@@ -95,34 +127,31 @@ async function waitForProxy(timeoutMs = 8_000): Promise<LiveProxy | null> {
|
|
|
95
127
|
return null;
|
|
96
128
|
}
|
|
97
129
|
|
|
98
|
-
/**
|
|
99
|
-
* A Grok fence sync that throws is best-effort by design — it must never block startup.
|
|
100
|
-
* Reporting nothing, however, is what lets a STALE fence survive: `~/.grok/config.toml`
|
|
101
|
-
* keeps naming whatever port the last successful sync wrote, and once that listener is
|
|
102
|
-
* gone every grok turn retries against a refused connection while our own log stays
|
|
103
|
-
* silent (2026-07-27 field report: 8 entries pinned to a dead 127.0.0.1:4179).
|
|
104
|
-
* So say what failed and name the single command that repairs it.
|
|
105
|
-
*/
|
|
106
|
-
function grokSyncFailureMessage(err: unknown): string {
|
|
107
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
108
|
-
return `Grok Build config sync failed: ${detail}. `
|
|
109
|
-
+ "~/.grok/config.toml may still point at a previous proxy port — "
|
|
110
|
-
+ "run 'ocx ensure' (or apply from the dashboard's Grok page) to repoint it.";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
130
|
/** Argv for detached `start`, optionally hard-pinning the listen port. */
|
|
114
131
|
function startArgv(port?: number): string[] {
|
|
115
|
-
const args = [
|
|
132
|
+
const args = ["start"];
|
|
116
133
|
if (typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535) {
|
|
117
134
|
args.push("--port", String(Math.trunc(port)));
|
|
118
135
|
}
|
|
119
|
-
return args;
|
|
136
|
+
return selfLaunchArgv(args);
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
123
140
|
const config = loadConfig();
|
|
124
141
|
const preferred = requestedPort ?? config.port ?? 10100;
|
|
125
142
|
const hardPin = requestedPort !== undefined && requestedPort > 0;
|
|
143
|
+
const reservedLoopbackPort = config.unauthenticatedLoopbackListener?.enabled
|
|
144
|
+
? config.unauthenticatedLoopbackListener.port
|
|
145
|
+
: undefined;
|
|
146
|
+
// Before the reclaim path, not after (#1102). Asking for the port the loopback listener is
|
|
147
|
+
// configured to bind is a configuration mistake, and reclaim would spend up to 60 seconds
|
|
148
|
+
// waiting for a socket to free before reporting "port is busy" — the wrong diagnosis for a
|
|
149
|
+
// collision the config can state outright.
|
|
150
|
+
if (reservedLoopbackPort !== undefined && preferred === reservedLoopbackPort) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
126
155
|
// Soft start: brief prefer-retry then ephemeral hop.
|
|
127
156
|
// Explicit `--port` (service wrappers / update restart): wait for the pinned port
|
|
128
157
|
// to free without killing any listener (healthy ocx / foreign). Never hop.
|
|
@@ -146,6 +175,11 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
|
146
175
|
preferRetryMs: hardPin ? 5_000 : 750,
|
|
147
176
|
preferRetryIntervalMs: 50,
|
|
148
177
|
allowEphemeralFallback: !hardPin,
|
|
178
|
+
// Never hand the public listener the port the loopback listener is configured to
|
|
179
|
+
// bind (#1102). Without this, `--port <loopback port>` binds the public listener
|
|
180
|
+
// first and the loopback bind then fails, rolling back a startup that was only
|
|
181
|
+
// ever a config collision.
|
|
182
|
+
...(reservedLoopbackPort !== undefined ? { reservedPort: reservedLoopbackPort } : {}),
|
|
149
183
|
});
|
|
150
184
|
if (preferred > 0 && selected !== preferred) {
|
|
151
185
|
console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
@@ -165,6 +199,22 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
|
165
199
|
}
|
|
166
200
|
}
|
|
167
201
|
|
|
202
|
+
async function findProxyOwnerBeforeJournalRecovery(
|
|
203
|
+
options: { probeConfiguredPort?: boolean } = {},
|
|
204
|
+
): Promise<{ live: LiveProxy | null; pidSnapshot: number | null }> {
|
|
205
|
+
const pidSnapshot = readPidFileValue();
|
|
206
|
+
const hasRuntimeOwner = readRuntimePort() !== null;
|
|
207
|
+
const shouldProbe = pidSnapshot !== null || hasRuntimeOwner || options.probeConfiguredPort === true;
|
|
208
|
+
const live = shouldProbe ? await findLiveProxy() : null;
|
|
209
|
+
if (live) return { live, pidSnapshot };
|
|
210
|
+
|
|
211
|
+
// The probe established that the snapshotted owner is stale. Compare before
|
|
212
|
+
// deleting so a concurrent start that rewrote the PID file keeps its state.
|
|
213
|
+
removePidIfValueIs(pidSnapshot);
|
|
214
|
+
if (!currentExternalCodexModelProvider()) reconcileJournal();
|
|
215
|
+
return { live: null, pidSnapshot };
|
|
216
|
+
}
|
|
217
|
+
|
|
168
218
|
async function handleStart(options: { block?: boolean } = {}) {
|
|
169
219
|
// Native (WinSW) service mode has no batch wrapper to read the service token file
|
|
170
220
|
// into the environment, so the app loads it here before the server binds. The server
|
|
@@ -172,15 +222,21 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
172
222
|
const serviceToken = loadServiceTokenFromFile(process.env);
|
|
173
223
|
if (serviceToken) process.env.OPENCODEX_API_AUTH_TOKEN = serviceToken;
|
|
174
224
|
const requestedPort = parsePortOption();
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
225
|
+
const owner = await findProxyOwnerBeforeJournalRecovery();
|
|
226
|
+
if (owner.live) {
|
|
227
|
+
// Service-wrapper context (opencodex-service.cmd `:loop`): a healthy proxy from
|
|
228
|
+
// ANY source means the requested port is already served. Exit 0 so the wrapper's
|
|
229
|
+
// `if %ERRORLEVEL% NEQ 0` retry loop terminates instead of respawning every 5s
|
|
230
|
+
// against a listener it can never claim (observed as an endless
|
|
231
|
+
// "Proxy already running" service.log loop).
|
|
232
|
+
// Only the exact "1" sentinel takes this path — the same check syncCleanup
|
|
233
|
+
// uses — so an env value like "0" or "false" cannot bypass the conflict error.
|
|
234
|
+
if (process.env.OCX_SERVICE === "1") {
|
|
235
|
+
console.log(`Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}); service wrapper staying out of the way.`);
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
console.error(`⚠️ Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}). Use 'ocx stop' first.`);
|
|
239
|
+
process.exit(1);
|
|
184
240
|
}
|
|
185
241
|
|
|
186
242
|
// Interactive-only update prompt. Must run BEFORE we bind a port / write a
|
|
@@ -192,10 +248,16 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
192
248
|
// between the probe and Bun.serve. Soft starts may re-pick; hard-pinned `--port` retries
|
|
193
249
|
// the same port only (never hop — that was the remaining PR #152 gap).
|
|
194
250
|
let port = await chooseListenPort(requestedPort);
|
|
251
|
+
// One private readiness gate for this startServer invocation, captured by the
|
|
252
|
+
// listener's closure. handleStart owns it and transitions it after the
|
|
253
|
+
// post-startup sync settles. A second startServer in the same process would
|
|
254
|
+
// get its own gate and could never reset/mutate this one.
|
|
255
|
+
const readinessGate = createReadinessGate();
|
|
195
256
|
let server: ReturnType<typeof startServer>;
|
|
257
|
+
const localAttestationSecret = createLocalAttestationSecret();
|
|
196
258
|
for (let attempt = 0; ; attempt++) {
|
|
197
259
|
try {
|
|
198
|
-
server = startServer(port);
|
|
260
|
+
server = startServer(port, { localAttestationSecret, readinessGate });
|
|
199
261
|
// Prewarm the live provider model cache as soon as the port is bound so the
|
|
200
262
|
// first GUI /v1/models (and syncModelsToCodex below) share one discovery flight
|
|
201
263
|
// instead of racing duplicate upstream /models fetches.
|
|
@@ -223,7 +285,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
223
285
|
writePid(process.pid);
|
|
224
286
|
|
|
225
287
|
const config = loadConfig();
|
|
226
|
-
writeRuntimePort({ pid: process.pid, port, hostname: config.hostname });
|
|
288
|
+
writeRuntimePort({ pid: process.pid, port, hostname: config.hostname, attestationSecret: localAttestationSecret });
|
|
227
289
|
// No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
|
|
228
290
|
// is about to transform; snapshotting earlier only captured a baseline that could
|
|
229
291
|
// already be stale by the time injection ran (#477).
|
|
@@ -251,7 +313,8 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
251
313
|
}
|
|
252
314
|
removePid(process.pid);
|
|
253
315
|
removeRuntimePort(process.pid);
|
|
254
|
-
|
|
316
|
+
const preserveRouting = process.env.OCX_SERVICE === "1";
|
|
317
|
+
if (!recycling && !preserveRouting && !currentExternalCodexModelProvider()) {
|
|
255
318
|
try {
|
|
256
319
|
const restored = restoreNativeCodex();
|
|
257
320
|
if (!restored.success) {
|
|
@@ -267,7 +330,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
267
330
|
// Grok fence is shared state we must not remove — that service keeps running and would be
|
|
268
331
|
// left pointing nowhere. This guard also covers signal-driven exits, which is the path that
|
|
269
332
|
// would otherwise bypass handleStop's gate entirely.
|
|
270
|
-
if (!recycling && !
|
|
333
|
+
if (!recycling && !preserveRouting && serviceEnvironmentOwnedHere()) {
|
|
271
334
|
try { stripGrokConfig(); } catch { /* best-effort restore */ }
|
|
272
335
|
}
|
|
273
336
|
return cleanupSucceeded;
|
|
@@ -310,12 +373,32 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
310
373
|
|
|
311
374
|
// System-wide env injection AFTER signal handlers are registered (crash safety:
|
|
312
375
|
// syncCleanup reverts even if injection itself or subsequent startup steps fail).
|
|
313
|
-
await injectSystemEnv(port, config).catch(() => {});
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
376
|
+
const systemEnv = await injectSystemEnv(port, config).catch(() => ({ injected: false }));
|
|
377
|
+
// The hook is useful only for an installed Claude Code CLI. Reconcile instead of
|
|
378
|
+
// appending unconditionally so stale OpenCodex-owned hooks are removed as well.
|
|
379
|
+
reportShellHookFailure(reconcileShellHook(systemEnv.injected));
|
|
317
380
|
await maybeShowStarPrompt(); // once-only Yes/No GitHub-star prompt on first interactive start
|
|
318
|
-
|
|
381
|
+
// Codex sync owns the ready/failed verdict, but its successful transition is
|
|
382
|
+
// deferred until the best-effort Claude roster reconciliation settles. This
|
|
383
|
+
// keeps /readyz closed across both startup writes without making an optional
|
|
384
|
+
// Claude integration failure prevent the proxy from starting.
|
|
385
|
+
const startupSync = await reconcileClientStartupBeforeReady(
|
|
386
|
+
readinessGate,
|
|
387
|
+
gate => syncCodexOnStartIfEnabled(port, config, undefined, gate),
|
|
388
|
+
() => systemEnv.injected
|
|
389
|
+
? Promise.resolve(null)
|
|
390
|
+
: syncClaudeAgentDefsAtProxyStartup(config, port),
|
|
391
|
+
);
|
|
392
|
+
if (!startupSync.ran) console.log(" Codex integration OFF; startup left Codex native.");
|
|
393
|
+
// #1046: one warning per startup, after BOTH writes. The server's cache
|
|
394
|
+
// invalidation happens first and the catalog sync second, so the mtime is only
|
|
395
|
+
// final here — and neither write site warns on its own, or a boot that hits
|
|
396
|
+
// both would warn twice.
|
|
397
|
+
const { consumeStartupCacheInvalidationWrite } = await import("../server");
|
|
398
|
+
if (consumeStartupCacheInvalidationWrite() || startupSync.catalogWritten || startupSync.cacheSynced) {
|
|
399
|
+
const { warnIfStaleCodexAppServersAfterStartupWrite } = await import("../codex/app-server-processes");
|
|
400
|
+
warnIfStaleCodexAppServersAfterStartupWrite({ log: console });
|
|
401
|
+
}
|
|
319
402
|
if (!currentExternalCodexModelProvider() && !shouldInjectApiAuthHeader(config) && config.syncResumeHistory !== false) {
|
|
320
403
|
historyGuardian = startHistoryMigrationGuardian();
|
|
321
404
|
}
|
|
@@ -335,7 +418,10 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
335
418
|
// absent or the bind is non-loopback; removed again by stop/eject/uninstall/shutdown.
|
|
336
419
|
// Deliberately a SIBLING of the Desktop-3P block above: nesting it there meant a catalog
|
|
337
420
|
// failure skipped the fence entirely, even though syncGrokConfig handles that case itself.
|
|
338
|
-
|
|
421
|
+
//
|
|
422
|
+
// Gated on the persisted switch: without this, turning Grok off lasted exactly
|
|
423
|
+
// one restart, because the toggle removed the fence and start wrote it back.
|
|
424
|
+
if (shouldSyncGrokOnStart(config)) try {
|
|
339
425
|
const { syncGrokConfig } = await import("../grok/sync");
|
|
340
426
|
const r = await syncGrokConfig(port, config, config.hostname ? { hostname: config.hostname } : {});
|
|
341
427
|
if (r.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
|
|
@@ -354,30 +440,46 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
354
440
|
}
|
|
355
441
|
}
|
|
356
442
|
|
|
357
|
-
|
|
358
|
-
|
|
443
|
+
function detachedStartEnvironment(): NodeJS.ProcessEnv {
|
|
444
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
445
|
+
// Only a real service wrapper may claim supervision. A detached ensure/tray child
|
|
446
|
+
// is an ordinary owner: while live it maintains routing, and on exit it restores it.
|
|
447
|
+
delete env.OCX_SERVICE;
|
|
448
|
+
return withProcessRuntimeProvenance(env);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Promise<boolean> {
|
|
452
|
+
const owner = await findProxyOwnerBeforeJournalRecovery({ probeConfiguredPort: true });
|
|
359
453
|
const config = loadConfig();
|
|
360
454
|
if (!codexAutoStartEnabled(config)) {
|
|
361
455
|
console.log("Codex autostart is disabled.");
|
|
362
|
-
return;
|
|
456
|
+
return false;
|
|
363
457
|
}
|
|
364
|
-
const live =
|
|
365
|
-
|
|
366
|
-
|
|
458
|
+
const live = owner.live;
|
|
459
|
+
if (live) {
|
|
460
|
+
if (options.existingIsSuccess === false) {
|
|
461
|
+
console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const synced = await syncModelsToCodex(live.port).catch(e => {
|
|
367
465
|
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
466
|
+
return null;
|
|
368
467
|
});
|
|
468
|
+
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
369
469
|
// Ensure env file exists for already-running proxy (may have been deleted or pre-dates this feature).
|
|
370
|
-
await injectSystemEnv(live.port, config).catch(() => {});
|
|
470
|
+
const systemEnv = await injectSystemEnv(live.port, config).catch(() => ({ injected: false }));
|
|
471
|
+
reportShellHookFailure(reconcileShellHook(systemEnv.injected));
|
|
472
|
+
if (!systemEnv.injected) await syncClaudeAgentDefsAtProxyStartup(config, live.port);
|
|
371
473
|
// Refresh the Grok Build fence too (same contract as start). live.hostname is the
|
|
372
474
|
// hostname the running proxy actually bound — config.hostname may have drifted.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
475
|
+
// The reconciler re-reads immediately before each client-file mutation; only
|
|
476
|
+
// the live proxy's observed bind host is safe to carry across this boundary.
|
|
477
|
+
await reconcileEnsureDesiredIntegrations(
|
|
478
|
+
live.port,
|
|
479
|
+
{ kind: "live", hostname: live.hostname },
|
|
480
|
+
);
|
|
379
481
|
console.log(`✅ Proxy running on port ${live.port}`);
|
|
380
|
-
return;
|
|
482
|
+
return true;
|
|
381
483
|
}
|
|
382
484
|
|
|
383
485
|
const pinPort = config.port ?? 10100;
|
|
@@ -385,36 +487,42 @@ async function handleEnsure() {
|
|
|
385
487
|
detached: true,
|
|
386
488
|
stdio: "ignore",
|
|
387
489
|
windowsHide: true,
|
|
388
|
-
env:
|
|
490
|
+
env: detachedStartEnvironment(),
|
|
389
491
|
});
|
|
390
492
|
child.unref();
|
|
391
493
|
|
|
392
494
|
const port = (await waitForProxy())?.port;
|
|
393
495
|
if (!port) {
|
|
394
496
|
console.error("❌ Proxy did not become healthy after starting.");
|
|
395
|
-
process.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
else if (!g.ok) console.error(`⚠️ ${g.message}`);
|
|
405
|
-
} catch (err) { console.error(`⚠️ ${grokSyncFailureMessage(err)}`); }
|
|
497
|
+
process.exitCode = 1;
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
// Deterministic fence guarantee when the durable switch is ON: the spawned child
|
|
501
|
+
// injects late in its own startup, but this parent returns as soon as /healthz
|
|
502
|
+
// responds — align here too so `ocx ensure` never returns with a stale ON/OFF mismatch.
|
|
503
|
+
// Persisted state is loaded inside each mutation after waitForProxy, so a
|
|
504
|
+
// toggle while the child starts wins over the pre-spawn snapshot.
|
|
505
|
+
await reconcileEnsureDesiredIntegrations(port, { kind: "spawned" });
|
|
406
506
|
// Always sync the LIVE port: after a fallback-port start, config.port still names the
|
|
407
507
|
// busy preferred port — syncing that would point Codex at a dead listener.
|
|
408
|
-
await syncModelsToCodex(port).catch(e => {
|
|
508
|
+
const synced = await syncModelsToCodex(port).catch(e => {
|
|
409
509
|
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
510
|
+
return null;
|
|
410
511
|
});
|
|
512
|
+
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
513
|
+
// The child opens /healthz before its best-effort roster reconcile. Await the same idempotent
|
|
514
|
+
// operation in the parent so `ocx ensure` cannot report success while stale ocx-*.md files are
|
|
515
|
+
// still observable. Always use the live port, including fallback-port starts.
|
|
516
|
+
await syncClaudeAgentDefsAtProxyStartup(config, port);
|
|
411
517
|
console.log(`✅ Proxy running on port ${port}`);
|
|
518
|
+
return true;
|
|
412
519
|
}
|
|
413
520
|
|
|
414
521
|
/** Fixed tray action: start the proxy without depending on codexAutoStart. */
|
|
415
|
-
async function handleTrayProxyStart(): Promise<
|
|
522
|
+
async function handleTrayProxyStart(existingIsSuccess = true): Promise<boolean> {
|
|
416
523
|
const ok = await runTrayProxyStart({
|
|
417
524
|
findLive: findLiveProxy,
|
|
525
|
+
existingIsSuccess,
|
|
418
526
|
diagnoseService: () => {
|
|
419
527
|
const service = diagnoseService();
|
|
420
528
|
return { installed: service.installed, startable: serviceStartableFromTray(service), summary: service.summary };
|
|
@@ -427,29 +535,117 @@ async function handleTrayProxyStart(): Promise<void> {
|
|
|
427
535
|
detached: true,
|
|
428
536
|
stdio: "ignore",
|
|
429
537
|
windowsHide: true,
|
|
430
|
-
env:
|
|
538
|
+
env: detachedStartEnvironment(),
|
|
431
539
|
});
|
|
432
540
|
child.unref();
|
|
433
541
|
},
|
|
434
|
-
|
|
542
|
+
// serviceCommand("start") already spends up to 20s confirming the supervised
|
|
543
|
+
// child. Slow Windows hosts can still be publishing native-main state after that
|
|
544
|
+
// first window, so keep one shared follow-up budget instead of returning a false
|
|
545
|
+
// failure while Task Scheduler is still starting the approved child.
|
|
546
|
+
waitForProxy: () => waitForProxy(40_000),
|
|
435
547
|
info: message => console.log(message),
|
|
436
548
|
error: message => console.error(message),
|
|
437
549
|
});
|
|
438
|
-
|
|
550
|
+
// serviceCommand("start") can set exitCode=1 after its own 20s probe, while
|
|
551
|
+
// the coordinator's bounded follow-up observes the same service become live.
|
|
552
|
+
// The final observed state, not the earlier probe, owns this command result.
|
|
553
|
+
process.exitCode = ok ? 0 : 1;
|
|
554
|
+
return ok;
|
|
439
555
|
}
|
|
440
556
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
557
|
+
const PROXY_RESTART_OBSERVE_MS = MEMORY_DRAIN_RESTART_MS + REPLACEMENT_READY_TIMEOUT_MS + 15_000;
|
|
558
|
+
|
|
559
|
+
async function waitForProxyReplacement(
|
|
560
|
+
previous: ProxyRestartLive,
|
|
561
|
+
deadlineAt: number,
|
|
562
|
+
): Promise<ProxyRestartLive | null> {
|
|
563
|
+
while (Date.now() < deadlineAt) {
|
|
564
|
+
const live = await findLiveProxy({ deadlineAt });
|
|
565
|
+
if (Date.now() >= deadlineAt) return null;
|
|
566
|
+
// Modern /healthz publishes a PID. Require a different, identity-verified process;
|
|
567
|
+
// merely seeing the old port online again is not proof that restart completed.
|
|
568
|
+
if (isProxyReplacement(previous, live)) {
|
|
569
|
+
return live;
|
|
570
|
+
}
|
|
571
|
+
const remainingMs = deadlineAt - Date.now();
|
|
572
|
+
if (remainingMs > 0) await Bun.sleep(Math.min(250, remainingMs));
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function reportRestartFailure(result: Extract<ProxyRestartResult, { ok: false }>): void {
|
|
578
|
+
if (result.phase === "identity") {
|
|
579
|
+
console.error("❌ Refusing to restart because the running proxy identity could not be attested.");
|
|
580
|
+
} else if (result.phase === "request") {
|
|
581
|
+
const code = result.error instanceof Error ? result.error.message : "";
|
|
582
|
+
if (code === "restart_capability_unsupported") {
|
|
583
|
+
console.error("❌ The running proxy predates process-bound restart support; no unsafe fallback was attempted.");
|
|
584
|
+
console.error(" After confirming this home owns the proxy, run `ocx stop` and then `ocx start` once.");
|
|
585
|
+
} else {
|
|
586
|
+
console.error("❌ Proxy restart request could not be confirmed; no fallback stop/start was attempted.");
|
|
587
|
+
}
|
|
588
|
+
} else if (result.phase === "replacement") {
|
|
589
|
+
console.error("❌ Proxy restart was accepted, but no identity-verified replacement became healthy in time.");
|
|
590
|
+
} else {
|
|
591
|
+
console.error("❌ Proxy was not running and the fallback start did not become healthy.");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async function handleProxyRestart(
|
|
596
|
+
startWhenStopped: () => Promise<boolean | "skipped">,
|
|
597
|
+
): Promise<boolean> {
|
|
598
|
+
const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
|
|
599
|
+
const result = await runProxyRestart({
|
|
600
|
+
findLive: () => discoverStableProxyForRestart({
|
|
601
|
+
findLive: () => findLiveProxy({ deadlineAt, attempts: 2 }),
|
|
602
|
+
expired: () => Date.now() >= deadlineAt,
|
|
603
|
+
}),
|
|
604
|
+
startWhenStopped,
|
|
605
|
+
requestInPlaceRestart: previous => requestBoundSystemRestart(previous, deadlineAt),
|
|
606
|
+
waitForReplacement: previous => waitForProxyReplacement(previous, deadlineAt),
|
|
451
607
|
});
|
|
452
|
-
if (!ok)
|
|
608
|
+
if (!result.ok) reportRestartFailure(result);
|
|
609
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
610
|
+
return result.ok;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async function handleTrayProxyRestart(): Promise<void> {
|
|
614
|
+
await handleProxyRestart(() => handleTrayProxyStart(false));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> {
|
|
618
|
+
if (!codexAutoStartEnabled(loadConfig())) {
|
|
619
|
+
console.log("Codex autostart is disabled; no proxy was started.");
|
|
620
|
+
return "skipped";
|
|
621
|
+
}
|
|
622
|
+
return handleEnsure({ existingIsSuccess: false });
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function restoreSharedClientStateAfterStop(): Promise<boolean> {
|
|
626
|
+
let restored = true;
|
|
627
|
+
try {
|
|
628
|
+
const result = await restoreNativeCodexAsync();
|
|
629
|
+
if (result.success) console.log(`↩️ ${result.message}`);
|
|
630
|
+
else {
|
|
631
|
+
restored = false;
|
|
632
|
+
console.error(`⚠️ ${result.message}`);
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
restored = false;
|
|
636
|
+
console.error(`⚠️ Native Codex restore failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// A refused or thrown Grok strip is actionable because it would point Grok at a dead proxy.
|
|
640
|
+
try {
|
|
641
|
+
const grok = stripGrokConfig();
|
|
642
|
+
if (grok.changed) console.log(`↩️ ${grok.message}`);
|
|
643
|
+
else if (!grok.ok) { restored = false; console.error(`⚠️ ${grok.message}`); }
|
|
644
|
+
} catch (error) {
|
|
645
|
+
restored = false;
|
|
646
|
+
console.error(`⚠️ Grok config restore failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
647
|
+
}
|
|
648
|
+
return restored;
|
|
453
649
|
}
|
|
454
650
|
|
|
455
651
|
async function handleStop() {
|
|
@@ -493,6 +689,10 @@ async function handleStop() {
|
|
|
493
689
|
// exact teardown the refusal exists to prevent.
|
|
494
690
|
const detail = err instanceof Error ? err.message : String(err);
|
|
495
691
|
if (detail) console.error(` ${detail}`);
|
|
692
|
+
if (err instanceof ProxyOwnershipRefusedError) {
|
|
693
|
+
ownershipBlocked = true;
|
|
694
|
+
console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
|
|
695
|
+
}
|
|
496
696
|
}
|
|
497
697
|
} else {
|
|
498
698
|
// Snapshot the stale on-disk state BEFORE the async probe: a concurrent `ocx start`
|
|
@@ -511,6 +711,10 @@ async function handleStop() {
|
|
|
511
711
|
console.error(`❌ Failed to stop proxy (PID ${live.pid}).`);
|
|
512
712
|
const detail = err instanceof Error ? err.message : String(err);
|
|
513
713
|
if (detail) console.error(` ${detail}`);
|
|
714
|
+
if (err instanceof ProxyOwnershipRefusedError) {
|
|
715
|
+
ownershipBlocked = true;
|
|
716
|
+
console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
|
|
717
|
+
}
|
|
514
718
|
}
|
|
515
719
|
} else if (!stoppedService) {
|
|
516
720
|
console.log("No running proxy found.");
|
|
@@ -523,26 +727,11 @@ async function handleStop() {
|
|
|
523
727
|
removeRuntimePortIfPidIs(staleRuntimePid);
|
|
524
728
|
}
|
|
525
729
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (r.success) console.log(`↩️ ${r.message}`);
|
|
529
|
-
else {
|
|
530
|
-
stopFailed = true;
|
|
531
|
-
console.error(`⚠️ ${r.message}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
// revertSystemEnv is NOT gated: it carries its own ownership check and concerns launchctl
|
|
535
|
-
// user env, not CODEX_HOME. Safety net for when the daemon's syncCleanup didn't run (SIGKILL).
|
|
730
|
+
// Environment ownership is independent from service ownership. Always roll back
|
|
731
|
+
// current-home variables; the helper refuses foreign markers on its own.
|
|
536
732
|
try { revertSystemEnv(); } catch { /* best-effort */ }
|
|
537
733
|
if (!ownershipBlocked) {
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
const g = stripGrokConfig();
|
|
541
|
-
if (g.changed) console.log(`↩️ ${g.message}`);
|
|
542
|
-
// A refused strip (e.g. orphaned marker) leaves the fence pointing at a dead proxy —
|
|
543
|
-
// reporting success there hides a broken end state.
|
|
544
|
-
else if (!g.ok) { stopFailed = true; console.error(`⚠️ ${g.message}`); }
|
|
545
|
-
} catch { /* best-effort */ }
|
|
734
|
+
if (!await restoreSharedClientStateAfterStop()) stopFailed = true;
|
|
546
735
|
}
|
|
547
736
|
// Set the code rather than exiting inline: `restart` and the tray coordinator call this
|
|
548
737
|
// function and need it to RETURN so they can decide what to do next.
|
|
@@ -586,8 +775,8 @@ async function handleUninstall() {
|
|
|
586
775
|
});
|
|
587
776
|
}
|
|
588
777
|
|
|
589
|
-
await runStep("native Codex restored", () => {
|
|
590
|
-
const r =
|
|
778
|
+
await runStep("native Codex restored", async () => {
|
|
779
|
+
const r = await restoreNativeCodexAsync();
|
|
591
780
|
if (!r.success) throw new Error(r.message);
|
|
592
781
|
});
|
|
593
782
|
|
|
@@ -634,7 +823,7 @@ async function handleUninstall() {
|
|
|
634
823
|
console.error(`\nUninstall finished with ${failures.length} failed step(s): ${failures.join(", ")}`);
|
|
635
824
|
process.exit(1);
|
|
636
825
|
}
|
|
637
|
-
console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @
|
|
826
|
+
console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @bitkyc08/opencodex");
|
|
638
827
|
}
|
|
639
828
|
|
|
640
829
|
async function handleStatus() {
|
|
@@ -659,7 +848,14 @@ async function handleStatus() {
|
|
|
659
848
|
console.log(` Health: ${status.healthLabel}`);
|
|
660
849
|
if (!(status.json.proxy.pid || status.json.proxy.health.ok)) {
|
|
661
850
|
console.log(" ↳ Not running — Codex/Claude requests will fail with connection errors.");
|
|
662
|
-
|
|
851
|
+
// The service summary a few lines below already tells a registered-but-not-serving
|
|
852
|
+
// user to repair. Printing "install the persistent service" unconditionally
|
|
853
|
+
// contradicted it in the same report, and install re-registers: UAC on Windows and a
|
|
854
|
+
// possible WinSW-to-scheduler switch for someone who already has a service.
|
|
855
|
+
const installed = status.json.startup.serviceInstalled && !status.json.startup.serviceConflict;
|
|
856
|
+
console.log(installed
|
|
857
|
+
? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
|
|
858
|
+
: " Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
|
|
663
859
|
}
|
|
664
860
|
console.log(` Dashboard: ${status.json.dashboard.url}`);
|
|
665
861
|
console.log(` Config: ${status.json.paths.config}`);
|
|
@@ -707,407 +903,70 @@ async function handleStatus() {
|
|
|
707
903
|
}
|
|
708
904
|
}
|
|
709
905
|
|
|
710
|
-
function handleRecoverHistory() {
|
|
906
|
+
async function handleRecoverHistory() {
|
|
711
907
|
if (args[1] !== "--legacy-openai") {
|
|
712
908
|
console.error("Usage: ocx recover-history --legacy-openai");
|
|
713
909
|
console.error("Only use this if an older syncResumeHistory build already remapped OpenAI Codex App history to opencodex before backup support existed.");
|
|
714
910
|
process.exit(1);
|
|
715
911
|
}
|
|
716
|
-
|
|
912
|
+
// Manifest-independent legacy ejection, serialized like every other history
|
|
913
|
+
// mutation. It is a separate operation from generic restore precisely because
|
|
914
|
+
// it must not read, consume or replace the backup manifest.
|
|
915
|
+
const outcome = await runCodexHistoryJob({
|
|
916
|
+
...resolveCodexHistoryJobTarget(),
|
|
917
|
+
operation: "recover-legacy-openai",
|
|
918
|
+
});
|
|
919
|
+
const r = outcome.kind === "converged"
|
|
920
|
+
? { rows: outcome.rows, files: outcome.files, failed: undefined }
|
|
921
|
+
: { rows: 0, files: 0, failed: true as const };
|
|
717
922
|
if (r.failed) {
|
|
718
923
|
console.error(
|
|
719
|
-
|
|
924
|
+
`⚠️ Recovery SKIPPED: ${describeHistoryJobFailure(outcome, "recover-legacy")}`,
|
|
720
925
|
);
|
|
721
926
|
process.exit(1);
|
|
722
927
|
}
|
|
723
928
|
console.log(`Recovered ${r.rows} legacy thread(s) to openai (${r.files} rollout file(s) updated).`);
|
|
724
929
|
}
|
|
725
930
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
break;
|
|
736
|
-
case "stop": {
|
|
737
|
-
// Downtime warning lives HERE, not in handleStop: `restart`/tray-restart callers
|
|
738
|
-
// re-start the proxy immediately, so warning there would contradict the next line.
|
|
739
|
-
if (await handleStop()) {
|
|
740
|
-
console.log("⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').");
|
|
741
|
-
}
|
|
742
|
-
break;
|
|
743
|
-
}
|
|
744
|
-
case "restore":
|
|
745
|
-
case "eject": {
|
|
746
|
-
if (args[1] === "back") {
|
|
747
|
-
// Reverse switch: re-point plain `codex` at the RUNNING proxy without touching its
|
|
748
|
-
// lifecycle — the counterpart of `ocx restore`. Start/stop triggers are unchanged;
|
|
749
|
-
// this only re-runs the same inject (config + catalog + history) `ocx start` does.
|
|
750
|
-
const live = await findLiveProxy();
|
|
751
|
-
if (!live) {
|
|
752
|
-
console.error("No running proxy found. Run 'ocx start' — it injects opencodex automatically.");
|
|
753
|
-
process.exit(1);
|
|
754
|
-
}
|
|
755
|
-
const synced = await syncModelsToCodex(live.port);
|
|
756
|
-
if (!synced.ok) {
|
|
757
|
-
process.exitCode = 1;
|
|
758
|
-
console.error("Plain `codex` was not switched back to opencodex. Fix the reported Codex config issue and retry.");
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
const target = collectOrcaCodexHomeDiagnostic();
|
|
762
|
-
console.log(`Plain \`codex\` now routes through opencodex in ${target.effectiveCodexHome} (undo with: ocx restore).`);
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
let r: { success: boolean; message: string };
|
|
766
|
-
try {
|
|
767
|
-
r = restoreNativeCodex();
|
|
768
|
-
} catch (err) {
|
|
769
|
-
r = { success: false, message: err instanceof Error ? err.message : String(err) };
|
|
770
|
-
}
|
|
771
|
-
if (r.success) console.log(`✅ ${r.message}`);
|
|
772
|
-
else {
|
|
773
|
-
console.error(`⚠️ ${r.message}`);
|
|
774
|
-
process.exitCode = 1;
|
|
775
|
-
}
|
|
776
|
-
try {
|
|
777
|
-
const g = stripGrokConfig();
|
|
778
|
-
if (g.changed) console.log(`✅ ${g.message}`);
|
|
779
|
-
else if (!g.ok) {
|
|
780
|
-
console.error(`⚠️ ${g.message}`);
|
|
781
|
-
process.exitCode = 1;
|
|
782
|
-
}
|
|
783
|
-
} catch { /* best-effort */ }
|
|
784
|
-
if (r.success) {
|
|
785
|
-
console.log("Plain `codex` now runs natively (no proxy). Switch back with: ocx restore back");
|
|
786
|
-
} else {
|
|
787
|
-
console.error("Plain `codex` was not fully restored. Inspect $CODEX_HOME/config.toml before using native Codex.");
|
|
788
|
-
}
|
|
789
|
-
break;
|
|
790
|
-
}
|
|
791
|
-
case "recover-history":
|
|
792
|
-
handleRecoverHistory();
|
|
793
|
-
break;
|
|
794
|
-
case "uninstall":
|
|
795
|
-
case "remove":
|
|
796
|
-
await handleUninstall();
|
|
797
|
-
break;
|
|
798
|
-
case "status":
|
|
799
|
-
await handleStatus();
|
|
800
|
-
break;
|
|
801
|
-
case "doctor": {
|
|
802
|
-
const { runDoctor } = await import("./doctor");
|
|
803
|
-
await runDoctor(args.slice(1));
|
|
804
|
-
break;
|
|
805
|
-
}
|
|
806
|
-
case "debug": {
|
|
807
|
-
const { handleDebugCommand } = await import("./debug");
|
|
808
|
-
await handleDebugCommand(args.slice(1));
|
|
809
|
-
break;
|
|
810
|
-
}
|
|
811
|
-
case "ensure":
|
|
812
|
-
await handleEnsure();
|
|
813
|
-
break;
|
|
814
|
-
case "login": {
|
|
815
|
-
const { handleLogin } = await import("../oauth/login-cli");
|
|
816
|
-
await handleLogin(args[1]);
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
case "logout": {
|
|
820
|
-
const { removeCredential } = await import("../oauth/store");
|
|
821
|
-
const name = (args[1] ?? "").trim().toLowerCase();
|
|
822
|
-
await removeCredential(name);
|
|
823
|
-
console.log(`Logged out of ${name || "(none)"}.`);
|
|
824
|
-
break;
|
|
825
|
-
}
|
|
826
|
-
case "sync": {
|
|
827
|
-
const restartCodex = args.slice(1).includes("--restart-codex");
|
|
828
|
-
const synced = await syncModelsToCodex((await findLiveProxy())?.port);
|
|
829
|
-
if (!synced.ok) {
|
|
830
|
-
process.exitCode = 1;
|
|
831
|
-
console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
|
|
832
|
-
}
|
|
833
|
-
// Only warn/restart when a catalog or models_cache write actually happened. This is
|
|
834
|
-
// deliberately not an `else`: refreshCodexModelCatalog runs before injectCodexConfig,
|
|
835
|
-
// so a sync can fail (`ok: false`) after the catalog was already rewritten — which is
|
|
836
|
-
// exactly when a long-lived app-server is holding the stale list.
|
|
837
|
-
if (synced.catalogWritten || synced.cacheSynced) {
|
|
838
|
-
const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
|
|
839
|
-
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
840
|
-
}
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
case "v2": {
|
|
844
|
-
const { cmdV2 } = await import("./v2");
|
|
845
|
-
process.exitCode = await cmdV2(args.slice(1), {}, async () => (await findLiveProxy())?.port);
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
848
|
-
case "sync-cache": {
|
|
849
|
-
const restartCodex = args.slice(1).includes("--restart-codex");
|
|
850
|
-
const { invalidateCodexModelsCache } = await import("../codex/catalog");
|
|
851
|
-
// Only warn/restart when models_cache was actually rewritten from a readable catalog.
|
|
852
|
-
if (invalidateCodexModelsCache()) {
|
|
853
|
-
const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
|
|
854
|
-
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
855
|
-
}
|
|
856
|
-
break;
|
|
857
|
-
}
|
|
858
|
-
case "gui": {
|
|
859
|
-
const cfg = await import("../config");
|
|
860
|
-
const config = cfg.loadConfig();
|
|
861
|
-
// Identity-checked liveness (not the pid file + a fixed sleep): finds a fallback-port
|
|
862
|
-
// proxy and waits until the spawned one actually answers before opening the browser.
|
|
863
|
-
let live = await findLiveProxy();
|
|
864
|
-
if (!live) {
|
|
865
|
-
console.log("Proxy not running. Starting...");
|
|
866
|
-
const child = spawn(process.execPath, startArgv((config.port ?? 10100) > 0 ? (config.port ?? 10100) : undefined), {
|
|
867
|
-
detached: true,
|
|
868
|
-
stdio: "ignore",
|
|
869
|
-
windowsHide: true,
|
|
870
|
-
env: process.env,
|
|
871
|
-
});
|
|
872
|
-
child.unref();
|
|
873
|
-
live = await waitForProxy();
|
|
874
|
-
if (!live) {
|
|
875
|
-
console.error("❌ Proxy did not become healthy after starting. Not opening the GUI.");
|
|
876
|
-
process.exit(1);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
// Open the host the proxy actually binds — `localhost` only answers for
|
|
880
|
-
// loopback/wildcard binds, not a concrete LAN/IPv6 hostname.
|
|
881
|
-
const guiHost = probeHostname(live?.hostname ?? config.hostname);
|
|
882
|
-
const guiUrl = `http://${guiHost === "127.0.0.1" ? "localhost" : guiHost}:${live?.port ?? config.port}`;
|
|
883
|
-
console.log(`Opening ${guiUrl}`);
|
|
884
|
-
const { openUrl } = await import("../lib/open-url");
|
|
885
|
-
openUrl(guiUrl);
|
|
886
|
-
break;
|
|
887
|
-
}
|
|
888
|
-
case "service":
|
|
889
|
-
await serviceCommand(...args.slice(1));
|
|
890
|
-
break;
|
|
891
|
-
case "tray": {
|
|
892
|
-
const { windowsTrayCommand } = await import("../tray/windows");
|
|
893
|
-
await windowsTrayCommand(args.slice(1));
|
|
894
|
-
break;
|
|
895
|
-
}
|
|
896
|
-
case "codex-shim": {
|
|
897
|
-
const { codexShimStatus, installCodexShim, uninstallCodexShim } = await import("../codex/shim");
|
|
898
|
-
switch (args[1]) {
|
|
899
|
-
case "install": {
|
|
900
|
-
const r = installCodexShim();
|
|
901
|
-
console.log(r.installed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
case "status":
|
|
905
|
-
console.log(codexShimStatus());
|
|
906
|
-
break;
|
|
907
|
-
case "uninstall":
|
|
908
|
-
case "remove": {
|
|
909
|
-
const r = uninstallCodexShim();
|
|
910
|
-
console.log(r.removed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
|
|
911
|
-
break;
|
|
912
|
-
}
|
|
913
|
-
default:
|
|
914
|
-
console.error("Usage: ocx codex-shim <install|status|uninstall|remove>");
|
|
915
|
-
process.exit(1);
|
|
916
|
-
}
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
case "update": {
|
|
920
|
-
// `ocx update --help` must print usage and exit WITHOUT side effects — running the
|
|
921
|
-
// real self-update stops the proxy and drops in-flight routed streams (issue #168).
|
|
922
|
-
if (hasHelpFlag(args.slice(1))) {
|
|
923
|
-
printSubcommandUsage("update");
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
const { runUpdate } = await import("../update");
|
|
927
|
-
await runUpdate();
|
|
928
|
-
break;
|
|
929
|
-
}
|
|
930
|
-
case "__refresh-version": {
|
|
931
|
-
// Hidden, detached helper spawned by the update prompt to refresh the
|
|
932
|
-
// cached latest version without blocking the foreground start. Not in help.
|
|
933
|
-
const { refreshVersionCache } = await import("../update/notify");
|
|
934
|
-
const channel = args[1] === "preview" ? "preview" : "latest";
|
|
935
|
-
await refreshVersionCache(channel);
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
case "__tray-start":
|
|
939
|
-
case "__tray-restart":
|
|
940
|
-
case "__startup-health":
|
|
941
|
-
await dispatchInternalCliCommand(command as InternalCliCommand, {
|
|
942
|
-
trayStart: handleTrayProxyStart,
|
|
943
|
-
trayRestart: handleTrayProxyRestart,
|
|
944
|
-
startupHealth: async () => {
|
|
945
|
-
const { collectStartupHealth } = await import("../codex/autostart-health");
|
|
946
|
-
console.log(JSON.stringify(collectStartupHealth(loadConfig())));
|
|
947
|
-
},
|
|
948
|
-
});
|
|
949
|
-
break;
|
|
950
|
-
case "__tray-host": {
|
|
951
|
-
const { runWindowsTrayHost } = await import("../tray/windows");
|
|
952
|
-
await runWindowsTrayHost();
|
|
953
|
-
break;
|
|
954
|
-
}
|
|
955
|
-
case "__gui-update-worker": {
|
|
956
|
-
const jobId = args[1];
|
|
957
|
-
if (!jobId) process.exit(1);
|
|
958
|
-
const channel = normalizeUpdateChannel(args[2]);
|
|
959
|
-
await runGuiUpdateWorker(jobId, channel, args[3] === "restart");
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
962
|
-
case "restart": {
|
|
963
|
-
// A failed stop must not be followed by a re-inject: with a foreign service still running
|
|
964
|
-
// (ownership mismatch) we would rewrite shared config we just declined to touch.
|
|
965
|
-
if (await handleStop()) await handleEnsure();
|
|
966
|
-
else console.error("↩️ Restart aborted: the proxy was not stopped cleanly.");
|
|
967
|
-
break;
|
|
968
|
-
}
|
|
969
|
-
case "health": {
|
|
970
|
-
const healthArgs = args.slice(1);
|
|
971
|
-
const wantsHealthJson = healthArgs.includes("--json");
|
|
972
|
-
const live = await findLiveProxy();
|
|
973
|
-
if (wantsHealthJson) {
|
|
974
|
-
console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null }));
|
|
975
|
-
} else {
|
|
976
|
-
console.log(live ? `Proxy healthy (PID ${live.pid}, port ${live.port})` : "Proxy not healthy");
|
|
977
|
-
}
|
|
978
|
-
process.exit(live ? 0 : 1);
|
|
979
|
-
}
|
|
980
|
-
case "provider": {
|
|
981
|
-
const { handleProviderCommand } = await import("./provider");
|
|
982
|
-
await handleProviderCommand(args.slice(1));
|
|
983
|
-
break;
|
|
984
|
-
}
|
|
985
|
-
case "account": {
|
|
986
|
-
const { cmdAccount } = await import("./account");
|
|
987
|
-
process.exitCode = await cmdAccount(args.slice(1));
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
case "models":
|
|
991
|
-
case "model": {
|
|
992
|
-
const { handleModels } = await import("./models");
|
|
993
|
-
await handleModels(args.slice(1));
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
case "combo": {
|
|
997
|
-
const { handleComboCommand } = await import("./combo");
|
|
998
|
-
process.exitCode = await handleComboCommand(args.slice(1));
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
case "route": {
|
|
1002
|
-
if (args[1] !== "combo") {
|
|
1003
|
-
console.error("Usage: ocx route combo <subcommand>");
|
|
1004
|
-
process.exitCode = 2;
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
const { handleComboCommand } = await import("./combo");
|
|
1008
|
-
process.exitCode = await handleComboCommand(args.slice(2));
|
|
1009
|
-
break;
|
|
1010
|
-
}
|
|
1011
|
-
case "agent": {
|
|
1012
|
-
const { handleAgentCommand } = await import("./agent");
|
|
1013
|
-
process.exitCode = await handleAgentCommand(args.slice(1));
|
|
1014
|
-
break;
|
|
1015
|
-
}
|
|
1016
|
-
case "observe": {
|
|
1017
|
-
const { handleObserveCommand } = await import("./observe");
|
|
1018
|
-
process.exitCode = await handleObserveCommand(args.slice(1));
|
|
1019
|
-
break;
|
|
1020
|
-
}
|
|
1021
|
-
case "logs":
|
|
1022
|
-
case "usage":
|
|
1023
|
-
case "storage":
|
|
1024
|
-
case "memory": {
|
|
1025
|
-
const { handleObserveCommand } = await import("./observe");
|
|
1026
|
-
process.exitCode = await handleObserveCommand([command, ...args.slice(1)]);
|
|
1027
|
-
break;
|
|
1028
|
-
}
|
|
1029
|
-
case "access": {
|
|
1030
|
-
const { handleAccessCommand } = await import("./access");
|
|
1031
|
-
process.exitCode = await handleAccessCommand(args.slice(1));
|
|
1032
|
-
break;
|
|
1033
|
-
}
|
|
1034
|
-
case "api-key": {
|
|
1035
|
-
const { handleAccessCommand } = await import("./access");
|
|
1036
|
-
process.exitCode = await handleAccessCommand(["key", ...args.slice(1)]);
|
|
1037
|
-
break;
|
|
1038
|
-
}
|
|
1039
|
-
case "export": {
|
|
1040
|
-
const { handleExportCommand } = await import("./export-command");
|
|
1041
|
-
process.exitCode = await handleExportCommand(args.slice(1));
|
|
1042
|
-
break;
|
|
1043
|
-
}
|
|
1044
|
-
case "grok": {
|
|
1045
|
-
const { handleGrokCommand } = await import("./integrations");
|
|
1046
|
-
process.exitCode = await handleGrokCommand(args.slice(1));
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
case "integration": {
|
|
1050
|
-
const integration = args[1];
|
|
1051
|
-
if (integration === "grok") {
|
|
1052
|
-
const { handleGrokCommand } = await import("./integrations");
|
|
1053
|
-
process.exitCode = await handleGrokCommand(args.slice(2));
|
|
1054
|
-
} else if (integration === "claude") {
|
|
1055
|
-
const { handleClaudeConfigCommand } = await import("./integrations");
|
|
1056
|
-
process.exitCode = await handleClaudeConfigCommand(args.slice(2));
|
|
1057
|
-
} else {
|
|
1058
|
-
console.error("Usage: ocx integration <claude|grok> <subcommand>");
|
|
1059
|
-
process.exitCode = 2;
|
|
1060
|
-
}
|
|
1061
|
-
break;
|
|
1062
|
-
}
|
|
1063
|
-
case "system": {
|
|
1064
|
-
const { handleSystemCommand } = await import("./system-command");
|
|
1065
|
-
process.exitCode = await handleSystemCommand(args.slice(1));
|
|
1066
|
-
break;
|
|
1067
|
-
}
|
|
1068
|
-
case "config": {
|
|
1069
|
-
const { handleConfigCommand } = await import("./config-command");
|
|
1070
|
-
process.exitCode = await handleConfigCommand(args.slice(1));
|
|
1071
|
-
break;
|
|
1072
|
-
}
|
|
1073
|
-
case "sync-cloud": {
|
|
1074
|
-
const { handleSyncCloudCommand } = await import("./sync-cloud");
|
|
1075
|
-
process.exitCode = await handleSyncCloudCommand(args.slice(1));
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
case "claude": {
|
|
1079
|
-
const { cmdClaude } = await import("./claude");
|
|
1080
|
-
// "ocx claude desktop" → write Desktop 3P config
|
|
1081
|
-
if (args[1] === "desktop") {
|
|
1082
|
-
const { handleClaudeDesktopCommand } = await import("./claude-desktop");
|
|
1083
|
-
const exitCode = await handleClaudeDesktopCommand(args.slice(2));
|
|
1084
|
-
if (exitCode !== 0) process.exit(exitCode);
|
|
1085
|
-
break;
|
|
1086
|
-
}
|
|
1087
|
-
if (args[1] === "config") {
|
|
1088
|
-
const { handleClaudeConfigCommand } = await import("./integrations");
|
|
1089
|
-
process.exitCode = await handleClaudeConfigCommand(args.slice(2));
|
|
1090
|
-
break;
|
|
1091
|
-
}
|
|
1092
|
-
process.exit(await cmdClaude(args.slice(1)));
|
|
1093
|
-
}
|
|
1094
|
-
case "opencode": {
|
|
1095
|
-
const { cmdOpencode } = await import("./opencode");
|
|
1096
|
-
process.exit(await cmdOpencode(args.slice(1)));
|
|
1097
|
-
}
|
|
1098
|
-
case "pi": {
|
|
1099
|
-
const { handlePiCommand } = await import("./pi");
|
|
1100
|
-
process.exitCode = await handlePiCommand(args.slice(1));
|
|
1101
|
-
break;
|
|
1102
|
-
}
|
|
1103
|
-
case "help":
|
|
1104
|
-
case "--help":
|
|
1105
|
-
case "-h":
|
|
1106
|
-
case undefined:
|
|
1107
|
-
printUsage();
|
|
1108
|
-
break;
|
|
1109
|
-
default:
|
|
1110
|
-
console.error(`Unknown command: ${command}`);
|
|
1111
|
-
printUsage();
|
|
1112
|
-
process.exit(1);
|
|
931
|
+
/**
|
|
932
|
+
* `ocx ready` — arguments are pre-parsed above (before
|
|
933
|
+
* maybeAutoRestoreCodexShim) so invalid usage exits 64 before any global
|
|
934
|
+
* preflight. This handler only runs the dependency-injected runner in ./ready
|
|
935
|
+
* and exits with the returned code; it performs no parsing and no I/O of its
|
|
936
|
+
* own. The full behavior is unit-testable without spawning a subprocess.
|
|
937
|
+
*/
|
|
938
|
+
async function handleReady(args: ReadyArgs): Promise<number> {
|
|
939
|
+
return runReady(args);
|
|
1113
940
|
}
|
|
941
|
+
|
|
942
|
+
process.exit(await dispatchCommand(head, {
|
|
943
|
+
args,
|
|
944
|
+
command,
|
|
945
|
+
head,
|
|
946
|
+
loadConfig,
|
|
947
|
+
findLiveProxy,
|
|
948
|
+
probeHostname,
|
|
949
|
+
waitForProxy,
|
|
950
|
+
startArgv,
|
|
951
|
+
spawnDetached: argv => {
|
|
952
|
+
const child = spawn(process.execPath, argv, {
|
|
953
|
+
detached: true,
|
|
954
|
+
stdio: "ignore",
|
|
955
|
+
windowsHide: true,
|
|
956
|
+
env: withProcessRuntimeProvenance(process.env),
|
|
957
|
+
});
|
|
958
|
+
child.unref();
|
|
959
|
+
},
|
|
960
|
+
handleStart,
|
|
961
|
+
handleStop,
|
|
962
|
+
handleEnsure,
|
|
963
|
+
handleTrayProxyStart,
|
|
964
|
+
handleTrayProxyRestart,
|
|
965
|
+
handleRestartStartWhenStopped,
|
|
966
|
+
handleProxyRestart,
|
|
967
|
+
handleUninstall,
|
|
968
|
+
handleStatus,
|
|
969
|
+
handleRecoverHistory,
|
|
970
|
+
handleReady,
|
|
971
|
+
serviceCommand,
|
|
972
|
+
}));
|