@iislee/opencodex 2.11.0 → 2.35.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 +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -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 +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -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 +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- 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 +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- 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 +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- 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 +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- 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 +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- 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 +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- 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 +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -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 +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -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/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -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 +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +360 -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 +622 -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 +675 -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 +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -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 +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -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 +157 -33
- package/src/images/plan.ts +24 -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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- 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 +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- 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/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- 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/service-secrets.ts +15 -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 +253 -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 +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -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/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- 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/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -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 +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -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 +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -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 +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -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 +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- 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 +1021 -190
- package/src/server/lifecycle.ts +336 -14
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -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 +289 -33
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -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 +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -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 +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- 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 +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- 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 +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +27 -16
- 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 +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- 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,13 +1,20 @@
|
|
|
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,
|
|
9
13
|
getConfigDir,
|
|
10
14
|
loadConfig,
|
|
15
|
+
saveConfig,
|
|
16
|
+
} from "../config";
|
|
17
|
+
import {
|
|
11
18
|
readPid,
|
|
12
19
|
readPidFileValue,
|
|
13
20
|
readRuntimePort,
|
|
@@ -15,56 +22,84 @@ import {
|
|
|
15
22
|
removePidIfValueIs,
|
|
16
23
|
removeRuntimePort,
|
|
17
24
|
removeRuntimePortIfPidIs,
|
|
18
|
-
saveConfig,
|
|
19
25
|
writePid,
|
|
20
26
|
writeRuntimePort,
|
|
21
|
-
} from "../config";
|
|
22
|
-
import { collectStatus } from "./status";
|
|
23
|
-
import {
|
|
24
|
-
|
|
27
|
+
} from "../config/process-state";
|
|
28
|
+
import { collectStatus, unusedProxyWarningLines } from "./status";
|
|
29
|
+
import { takeFlag } from "./runtime-api";
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
discoverStableProxyForRestart,
|
|
33
|
+
isProxyReplacement,
|
|
34
|
+
runProxyRestart,
|
|
35
|
+
runTrayProxyStart,
|
|
36
|
+
type ProxyRestartLive,
|
|
37
|
+
type ProxyRestartResult,
|
|
38
|
+
} from "./tray-proxy";
|
|
39
|
+
import { requestBoundSystemRestart } from "./system-restart-client";
|
|
25
40
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
26
|
-
import {
|
|
41
|
+
import { dispatchCommand } from "./dispatch";
|
|
27
42
|
import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
28
43
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
29
|
-
import {
|
|
44
|
+
import { createReadinessGate } from "../server/readiness";
|
|
45
|
+
import { runReady, type ReadyArgs } from "./ready";
|
|
46
|
+
import { runCli } from "./root";
|
|
47
|
+
import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
|
|
30
48
|
import { loadServiceTokenFromFile } from "../lib/service-secrets";
|
|
31
|
-
import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
|
|
32
|
-
import { startupHealthSummary } from "../codex/autostart-health";
|
|
49
|
+
import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
|
|
50
|
+
import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
|
|
33
51
|
import { drainAndShutdown, isRecyclingForExit, startServer } from "../server";
|
|
34
|
-
import { injectSystemEnv, revertSystemEnv } from "../server/system-env";
|
|
52
|
+
import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
|
|
35
53
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
36
|
-
import { installShellHook, uninstallShellHook } from "../server/system-env";
|
|
37
54
|
import { startTokenGuardian } from "../oauth/token-guardian";
|
|
38
55
|
import { startHistoryMigrationGuardian } from "../codex/history-migration-guardian";
|
|
39
|
-
import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
|
|
40
56
|
import { maybeShowStarPrompt } from "./star-prompt";
|
|
41
57
|
import { scheduleCatalogPrewarm } from "./catalog-prewarm";
|
|
42
58
|
import { maybeShowUpdatePrompt } from "../update/notify";
|
|
43
59
|
import { syncModelsToCodex } from "../codex/sync";
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
import {
|
|
61
|
+
shouldSyncGrokOnStart,
|
|
62
|
+
syncCodexOnStartIfEnabled,
|
|
63
|
+
} from "../codex/desired-state";
|
|
64
|
+
import {
|
|
65
|
+
reconcileClientStartupBeforeReady,
|
|
66
|
+
syncClaudeAgentDefsAtProxyStartup,
|
|
67
|
+
} from "./claude-agent-startup-sync";
|
|
68
|
+
import {
|
|
69
|
+
grokSyncFailureMessage,
|
|
70
|
+
reconcileEnsureDesiredIntegrations,
|
|
71
|
+
} from "./ensure-desired-integrations";
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
/**
|
|
74
|
+
* A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
|
|
75
|
+
* `claude-env.sh` from every new interactive shell, pointing at a proxy or a CLI that may no
|
|
76
|
+
* longer exist. `reconcileShellHook` already reports `state: "failed"`, but both call sites
|
|
77
|
+
* discarded it, so the one outcome the user has to act on was the one they never saw.
|
|
78
|
+
*/
|
|
79
|
+
function reportShellHookFailure(result: { state: "installed" | "absent" | "failed"; reason?: string }): void {
|
|
80
|
+
if (result.state !== "failed") return;
|
|
81
|
+
console.warn(` Claude shell hook not reconciled${result.reason ? `: ${result.reason}` : ""}`);
|
|
82
|
+
console.warn(" Check ~/.zshrc for the '# opencodex claude-env hook' block.");
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
if (command !== undefined && command !== "help" && hasHelpFlag(args.slice(1))) {
|
|
63
|
-
printSubcommandUsage(command);
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
import { removeOwnedConfigState } from "../lib/config-ownership";
|
|
87
|
+
import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
|
|
88
|
+
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
89
|
+
import { initializeNodeLauncherContext } from "./launcher-context";
|
|
90
|
+
import { createLocalAttestationSecret } from "../lib/local-management-attestation";
|
|
91
|
+
import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
|
|
92
|
+
|
|
93
|
+
initializeNodeLauncherContext();
|
|
94
|
+
|
|
95
|
+
// Head: version/help early exits, `ready` pre-parse (exit 64 before any
|
|
96
|
+
// preflight), and the bounded Codex-shim auto-restore preflight live in
|
|
97
|
+
// src/cli/root.ts (Phase 1 of the CLI deepening). runCli exits for
|
|
98
|
+
// version/help and returns the dispatchable head otherwise; the switch below
|
|
99
|
+
// owns command dispatch.
|
|
100
|
+
const head = await runCli(process.argv.slice(2));
|
|
101
|
+
const args = head.args;
|
|
102
|
+
const command = head.command;
|
|
68
103
|
|
|
69
104
|
function parsePortOption(): number | undefined {
|
|
70
105
|
if (args.length === 1) return undefined;
|
|
@@ -95,34 +130,31 @@ async function waitForProxy(timeoutMs = 8_000): Promise<LiveProxy | null> {
|
|
|
95
130
|
return null;
|
|
96
131
|
}
|
|
97
132
|
|
|
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
133
|
/** Argv for detached `start`, optionally hard-pinning the listen port. */
|
|
114
134
|
function startArgv(port?: number): string[] {
|
|
115
|
-
const args = [
|
|
135
|
+
const args = ["start"];
|
|
116
136
|
if (typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535) {
|
|
117
137
|
args.push("--port", String(Math.trunc(port)));
|
|
118
138
|
}
|
|
119
|
-
return args;
|
|
139
|
+
return selfLaunchArgv(args);
|
|
120
140
|
}
|
|
121
141
|
|
|
122
142
|
async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
123
143
|
const config = loadConfig();
|
|
124
144
|
const preferred = requestedPort ?? config.port ?? 10100;
|
|
125
145
|
const hardPin = requestedPort !== undefined && requestedPort > 0;
|
|
146
|
+
const reservedLoopbackPort = config.unauthenticatedLoopbackListener?.enabled
|
|
147
|
+
? config.unauthenticatedLoopbackListener.port
|
|
148
|
+
: undefined;
|
|
149
|
+
// Before the reclaim path, not after (#1102). Asking for the port the loopback listener is
|
|
150
|
+
// configured to bind is a configuration mistake, and reclaim would spend up to 60 seconds
|
|
151
|
+
// waiting for a socket to free before reporting "port is busy" — the wrong diagnosis for a
|
|
152
|
+
// collision the config can state outright.
|
|
153
|
+
if (reservedLoopbackPort !== undefined && preferred === reservedLoopbackPort) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Port ${preferred} is reserved for unauthenticatedLoopbackListener; choose a different proxy port.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
126
158
|
// Soft start: brief prefer-retry then ephemeral hop.
|
|
127
159
|
// Explicit `--port` (service wrappers / update restart): wait for the pinned port
|
|
128
160
|
// to free without killing any listener (healthy ocx / foreign). Never hop.
|
|
@@ -146,6 +178,11 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
|
146
178
|
preferRetryMs: hardPin ? 5_000 : 750,
|
|
147
179
|
preferRetryIntervalMs: 50,
|
|
148
180
|
allowEphemeralFallback: !hardPin,
|
|
181
|
+
// Never hand the public listener the port the loopback listener is configured to
|
|
182
|
+
// bind (#1102). Without this, `--port <loopback port>` binds the public listener
|
|
183
|
+
// first and the loopback bind then fails, rolling back a startup that was only
|
|
184
|
+
// ever a config collision.
|
|
185
|
+
...(reservedLoopbackPort !== undefined ? { reservedPort: reservedLoopbackPort } : {}),
|
|
149
186
|
});
|
|
150
187
|
if (preferred > 0 && selected !== preferred) {
|
|
151
188
|
console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
@@ -165,22 +202,49 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
|
165
202
|
}
|
|
166
203
|
}
|
|
167
204
|
|
|
205
|
+
async function findProxyOwnerBeforeJournalRecovery(
|
|
206
|
+
options: { probeConfiguredPort?: boolean } = {},
|
|
207
|
+
): Promise<{ live: LiveProxy | null; pidSnapshot: number | null }> {
|
|
208
|
+
const pidSnapshot = readPidFileValue();
|
|
209
|
+
const hasRuntimeOwner = readRuntimePort() !== null;
|
|
210
|
+
const shouldProbe = pidSnapshot !== null || hasRuntimeOwner || options.probeConfiguredPort === true;
|
|
211
|
+
const live = shouldProbe ? await findLiveProxy() : null;
|
|
212
|
+
if (live) return { live, pidSnapshot };
|
|
213
|
+
|
|
214
|
+
// The probe established that the snapshotted owner is stale. Compare before
|
|
215
|
+
// deleting so a concurrent start that rewrote the PID file keeps its state.
|
|
216
|
+
removePidIfValueIs(pidSnapshot);
|
|
217
|
+
if (!currentExternalCodexModelProvider()) reconcileJournal();
|
|
218
|
+
return { live: null, pidSnapshot };
|
|
219
|
+
}
|
|
220
|
+
|
|
168
221
|
async function handleStart(options: { block?: boolean } = {}) {
|
|
169
222
|
// Native (WinSW) service mode has no batch wrapper to read the service token file
|
|
170
223
|
// into the environment, so the app loads it here before the server binds. The server
|
|
171
224
|
// auth path reads OPENCODEX_API_AUTH_TOKEN from the environment.
|
|
172
225
|
const serviceToken = loadServiceTokenFromFile(process.env);
|
|
173
226
|
if (serviceToken) process.env.OPENCODEX_API_AUTH_TOKEN = serviceToken;
|
|
227
|
+
// The service wrapper (and WinSW via OCX_API_TOKEN_FILE) can still export a colliding
|
|
228
|
+
// token that install now refuses to write. Refuse it here too, before bind, so an
|
|
229
|
+
// already-broken file cannot fence /api/* closed at boot (#2696).
|
|
230
|
+
const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
231
|
+
if (present) assertNotAdminToken(present);
|
|
174
232
|
const requestedPort = parsePortOption();
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
const owner = await findProxyOwnerBeforeJournalRecovery();
|
|
234
|
+
if (owner.live) {
|
|
235
|
+
// Service-wrapper context (opencodex-service.cmd `:loop`): a healthy proxy from
|
|
236
|
+
// ANY source means the requested port is already served. Exit 0 so the wrapper's
|
|
237
|
+
// `if %ERRORLEVEL% NEQ 0` retry loop terminates instead of respawning every 5s
|
|
238
|
+
// against a listener it can never claim (observed as an endless
|
|
239
|
+
// "Proxy already running" service.log loop).
|
|
240
|
+
// Only the exact "1" sentinel takes this path — the same check syncCleanup
|
|
241
|
+
// uses — so an env value like "0" or "false" cannot bypass the conflict error.
|
|
242
|
+
if (process.env.OCX_SERVICE === "1") {
|
|
243
|
+
console.log(`Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}); service wrapper staying out of the way.`);
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
console.error(`⚠️ Proxy already running (PID ${owner.live.pid ?? owner.pidSnapshot ?? "unknown"}, port ${owner.live.port}). Use 'ocx stop' first.`);
|
|
247
|
+
process.exit(1);
|
|
184
248
|
}
|
|
185
249
|
|
|
186
250
|
// Interactive-only update prompt. Must run BEFORE we bind a port / write a
|
|
@@ -192,10 +256,16 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
192
256
|
// between the probe and Bun.serve. Soft starts may re-pick; hard-pinned `--port` retries
|
|
193
257
|
// the same port only (never hop — that was the remaining PR #152 gap).
|
|
194
258
|
let port = await chooseListenPort(requestedPort);
|
|
259
|
+
// One private readiness gate for this startServer invocation, captured by the
|
|
260
|
+
// listener's closure. handleStart owns it and transitions it after the
|
|
261
|
+
// post-startup sync settles. A second startServer in the same process would
|
|
262
|
+
// get its own gate and could never reset/mutate this one.
|
|
263
|
+
const readinessGate = createReadinessGate();
|
|
195
264
|
let server: ReturnType<typeof startServer>;
|
|
265
|
+
const localAttestationSecret = createLocalAttestationSecret();
|
|
196
266
|
for (let attempt = 0; ; attempt++) {
|
|
197
267
|
try {
|
|
198
|
-
server = startServer(port);
|
|
268
|
+
server = startServer(port, { localAttestationSecret, readinessGate });
|
|
199
269
|
// Prewarm the live provider model cache as soon as the port is bound so the
|
|
200
270
|
// first GUI /v1/models (and syncModelsToCodex below) share one discovery flight
|
|
201
271
|
// instead of racing duplicate upstream /models fetches.
|
|
@@ -223,7 +293,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
223
293
|
writePid(process.pid);
|
|
224
294
|
|
|
225
295
|
const config = loadConfig();
|
|
226
|
-
writeRuntimePort({ pid: process.pid, port, hostname: config.hostname });
|
|
296
|
+
writeRuntimePort({ pid: process.pid, port, hostname: config.hostname, attestationSecret: localAttestationSecret });
|
|
227
297
|
// No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
|
|
228
298
|
// is about to transform; snapshotting earlier only captured a baseline that could
|
|
229
299
|
// already be stale by the time injection ran (#477).
|
|
@@ -251,7 +321,8 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
251
321
|
}
|
|
252
322
|
removePid(process.pid);
|
|
253
323
|
removeRuntimePort(process.pid);
|
|
254
|
-
|
|
324
|
+
const preserveRouting = process.env.OCX_SERVICE === "1";
|
|
325
|
+
if (!recycling && !preserveRouting && !currentExternalCodexModelProvider()) {
|
|
255
326
|
try {
|
|
256
327
|
const restored = restoreNativeCodex();
|
|
257
328
|
if (!restored.success) {
|
|
@@ -267,7 +338,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
267
338
|
// Grok fence is shared state we must not remove — that service keeps running and would be
|
|
268
339
|
// left pointing nowhere. This guard also covers signal-driven exits, which is the path that
|
|
269
340
|
// would otherwise bypass handleStop's gate entirely.
|
|
270
|
-
if (!recycling && !
|
|
341
|
+
if (!recycling && !preserveRouting && serviceEnvironmentOwnedHere()) {
|
|
271
342
|
try { stripGrokConfig(); } catch { /* best-effort restore */ }
|
|
272
343
|
}
|
|
273
344
|
return cleanupSucceeded;
|
|
@@ -310,12 +381,32 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
310
381
|
|
|
311
382
|
// System-wide env injection AFTER signal handlers are registered (crash safety:
|
|
312
383
|
// syncCleanup reverts even if injection itself or subsequent startup steps fail).
|
|
313
|
-
await injectSystemEnv(port, config).catch(() => {});
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
384
|
+
const systemEnv = await injectSystemEnv(port, config).catch(() => ({ injected: false }));
|
|
385
|
+
// The hook is useful only for an installed Claude Code CLI. Reconcile instead of
|
|
386
|
+
// appending unconditionally so stale OpenCodex-owned hooks are removed as well.
|
|
387
|
+
reportShellHookFailure(reconcileShellHook(systemEnv.injected));
|
|
317
388
|
await maybeShowStarPrompt(); // once-only Yes/No GitHub-star prompt on first interactive start
|
|
318
|
-
|
|
389
|
+
// Codex sync owns the ready/failed verdict, but its successful transition is
|
|
390
|
+
// deferred until the best-effort Claude roster reconciliation settles. This
|
|
391
|
+
// keeps /readyz closed across both startup writes without making an optional
|
|
392
|
+
// Claude integration failure prevent the proxy from starting.
|
|
393
|
+
const startupSync = await reconcileClientStartupBeforeReady(
|
|
394
|
+
readinessGate,
|
|
395
|
+
gate => syncCodexOnStartIfEnabled(port, config, undefined, gate),
|
|
396
|
+
() => systemEnv.injected
|
|
397
|
+
? Promise.resolve(null)
|
|
398
|
+
: syncClaudeAgentDefsAtProxyStartup(config, port),
|
|
399
|
+
);
|
|
400
|
+
if (!startupSync.ran) console.log(" Codex integration OFF; startup left Codex native.");
|
|
401
|
+
// #1046: one warning per startup, after BOTH writes. The server's cache
|
|
402
|
+
// invalidation happens first and the catalog sync second, so the mtime is only
|
|
403
|
+
// final here — and neither write site warns on its own, or a boot that hits
|
|
404
|
+
// both would warn twice.
|
|
405
|
+
const { consumeStartupCacheInvalidationWrite } = await import("../server");
|
|
406
|
+
if (consumeStartupCacheInvalidationWrite() || startupSync.catalogWritten || startupSync.cacheSynced) {
|
|
407
|
+
const { warnIfStaleCodexAppServersAfterStartupWrite } = await import("../codex/app-server-processes");
|
|
408
|
+
warnIfStaleCodexAppServersAfterStartupWrite({ log: console });
|
|
409
|
+
}
|
|
319
410
|
if (!currentExternalCodexModelProvider() && !shouldInjectApiAuthHeader(config) && config.syncResumeHistory !== false) {
|
|
320
411
|
historyGuardian = startHistoryMigrationGuardian();
|
|
321
412
|
}
|
|
@@ -335,7 +426,10 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
335
426
|
// absent or the bind is non-loopback; removed again by stop/eject/uninstall/shutdown.
|
|
336
427
|
// Deliberately a SIBLING of the Desktop-3P block above: nesting it there meant a catalog
|
|
337
428
|
// failure skipped the fence entirely, even though syncGrokConfig handles that case itself.
|
|
338
|
-
|
|
429
|
+
//
|
|
430
|
+
// Gated on the persisted switch: without this, turning Grok off lasted exactly
|
|
431
|
+
// one restart, because the toggle removed the fence and start wrote it back.
|
|
432
|
+
if (shouldSyncGrokOnStart(config)) try {
|
|
339
433
|
const { syncGrokConfig } = await import("../grok/sync");
|
|
340
434
|
const r = await syncGrokConfig(port, config, config.hostname ? { hostname: config.hostname } : {});
|
|
341
435
|
if (r.changed) console.log(" + Grok Build config updated (~/.grok/config.toml)");
|
|
@@ -354,30 +448,46 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
354
448
|
}
|
|
355
449
|
}
|
|
356
450
|
|
|
357
|
-
|
|
358
|
-
|
|
451
|
+
function detachedStartEnvironment(): NodeJS.ProcessEnv {
|
|
452
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
453
|
+
// Only a real service wrapper may claim supervision. A detached ensure/tray child
|
|
454
|
+
// is an ordinary owner: while live it maintains routing, and on exit it restores it.
|
|
455
|
+
delete env.OCX_SERVICE;
|
|
456
|
+
return withProcessRuntimeProvenance(env);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Promise<boolean> {
|
|
460
|
+
const owner = await findProxyOwnerBeforeJournalRecovery({ probeConfiguredPort: true });
|
|
359
461
|
const config = loadConfig();
|
|
360
462
|
if (!codexAutoStartEnabled(config)) {
|
|
361
463
|
console.log("Codex autostart is disabled.");
|
|
362
|
-
return;
|
|
464
|
+
return false;
|
|
363
465
|
}
|
|
364
|
-
const live =
|
|
365
|
-
|
|
366
|
-
|
|
466
|
+
const live = owner.live;
|
|
467
|
+
if (live) {
|
|
468
|
+
if (options.existingIsSuccess === false) {
|
|
469
|
+
console.error("Proxy appeared while restart was confirming absence; no start was attempted.");
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const synced = await syncModelsToCodex(live.port).catch(e => {
|
|
367
473
|
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
474
|
+
return null;
|
|
368
475
|
});
|
|
476
|
+
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
369
477
|
// Ensure env file exists for already-running proxy (may have been deleted or pre-dates this feature).
|
|
370
|
-
await injectSystemEnv(live.port, config).catch(() => {});
|
|
478
|
+
const systemEnv = await injectSystemEnv(live.port, config).catch(() => ({ injected: false }));
|
|
479
|
+
reportShellHookFailure(reconcileShellHook(systemEnv.injected));
|
|
480
|
+
if (!systemEnv.injected) await syncClaudeAgentDefsAtProxyStartup(config, live.port);
|
|
371
481
|
// Refresh the Grok Build fence too (same contract as start). live.hostname is the
|
|
372
482
|
// hostname the running proxy actually bound — config.hostname may have drifted.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
483
|
+
// The reconciler re-reads immediately before each client-file mutation; only
|
|
484
|
+
// the live proxy's observed bind host is safe to carry across this boundary.
|
|
485
|
+
await reconcileEnsureDesiredIntegrations(
|
|
486
|
+
live.port,
|
|
487
|
+
{ kind: "live", hostname: live.hostname },
|
|
488
|
+
);
|
|
379
489
|
console.log(`✅ Proxy running on port ${live.port}`);
|
|
380
|
-
return;
|
|
490
|
+
return true;
|
|
381
491
|
}
|
|
382
492
|
|
|
383
493
|
const pinPort = config.port ?? 10100;
|
|
@@ -385,36 +495,42 @@ async function handleEnsure() {
|
|
|
385
495
|
detached: true,
|
|
386
496
|
stdio: "ignore",
|
|
387
497
|
windowsHide: true,
|
|
388
|
-
env:
|
|
498
|
+
env: detachedStartEnvironment(),
|
|
389
499
|
});
|
|
390
500
|
child.unref();
|
|
391
501
|
|
|
392
502
|
const port = (await waitForProxy())?.port;
|
|
393
503
|
if (!port) {
|
|
394
504
|
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)}`); }
|
|
505
|
+
process.exitCode = 1;
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
// Deterministic fence guarantee when the durable switch is ON: the spawned child
|
|
509
|
+
// injects late in its own startup, but this parent returns as soon as /healthz
|
|
510
|
+
// responds — align here too so `ocx ensure` never returns with a stale ON/OFF mismatch.
|
|
511
|
+
// Persisted state is loaded inside each mutation after waitForProxy, so a
|
|
512
|
+
// toggle while the child starts wins over the pre-spawn snapshot.
|
|
513
|
+
await reconcileEnsureDesiredIntegrations(port, { kind: "spawned" });
|
|
406
514
|
// Always sync the LIVE port: after a fallback-port start, config.port still names the
|
|
407
515
|
// busy preferred port — syncing that would point Codex at a dead listener.
|
|
408
|
-
await syncModelsToCodex(port).catch(e => {
|
|
516
|
+
const synced = await syncModelsToCodex(port).catch(e => {
|
|
409
517
|
console.error(`⚠️ Model sync skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
518
|
+
return null;
|
|
410
519
|
});
|
|
520
|
+
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
521
|
+
// The child opens /healthz before its best-effort roster reconcile. Await the same idempotent
|
|
522
|
+
// operation in the parent so `ocx ensure` cannot report success while stale ocx-*.md files are
|
|
523
|
+
// still observable. Always use the live port, including fallback-port starts.
|
|
524
|
+
await syncClaudeAgentDefsAtProxyStartup(config, port);
|
|
411
525
|
console.log(`✅ Proxy running on port ${port}`);
|
|
526
|
+
return true;
|
|
412
527
|
}
|
|
413
528
|
|
|
414
529
|
/** Fixed tray action: start the proxy without depending on codexAutoStart. */
|
|
415
|
-
async function handleTrayProxyStart(): Promise<
|
|
530
|
+
async function handleTrayProxyStart(existingIsSuccess = true): Promise<boolean> {
|
|
416
531
|
const ok = await runTrayProxyStart({
|
|
417
532
|
findLive: findLiveProxy,
|
|
533
|
+
existingIsSuccess,
|
|
418
534
|
diagnoseService: () => {
|
|
419
535
|
const service = diagnoseService();
|
|
420
536
|
return { installed: service.installed, startable: serviceStartableFromTray(service), summary: service.summary };
|
|
@@ -427,29 +543,117 @@ async function handleTrayProxyStart(): Promise<void> {
|
|
|
427
543
|
detached: true,
|
|
428
544
|
stdio: "ignore",
|
|
429
545
|
windowsHide: true,
|
|
430
|
-
env:
|
|
546
|
+
env: detachedStartEnvironment(),
|
|
431
547
|
});
|
|
432
548
|
child.unref();
|
|
433
549
|
},
|
|
434
|
-
|
|
550
|
+
// serviceCommand("start") already spends up to 20s confirming the supervised
|
|
551
|
+
// child. Slow Windows hosts can still be publishing native-main state after that
|
|
552
|
+
// first window, so keep one shared follow-up budget instead of returning a false
|
|
553
|
+
// failure while Task Scheduler is still starting the approved child.
|
|
554
|
+
waitForProxy: () => waitForProxy(40_000),
|
|
435
555
|
info: message => console.log(message),
|
|
436
556
|
error: message => console.error(message),
|
|
437
557
|
});
|
|
438
|
-
|
|
558
|
+
// serviceCommand("start") can set exitCode=1 after its own 20s probe, while
|
|
559
|
+
// the coordinator's bounded follow-up observes the same service become live.
|
|
560
|
+
// The final observed state, not the earlier probe, owns this command result.
|
|
561
|
+
process.exitCode = ok ? 0 : 1;
|
|
562
|
+
return ok;
|
|
439
563
|
}
|
|
440
564
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
565
|
+
const PROXY_RESTART_OBSERVE_MS = MEMORY_DRAIN_RESTART_MS + REPLACEMENT_READY_TIMEOUT_MS + 15_000;
|
|
566
|
+
|
|
567
|
+
async function waitForProxyReplacement(
|
|
568
|
+
previous: ProxyRestartLive,
|
|
569
|
+
deadlineAt: number,
|
|
570
|
+
): Promise<ProxyRestartLive | null> {
|
|
571
|
+
while (Date.now() < deadlineAt) {
|
|
572
|
+
const live = await findLiveProxy({ deadlineAt });
|
|
573
|
+
if (Date.now() >= deadlineAt) return null;
|
|
574
|
+
// Modern /healthz publishes a PID. Require a different, identity-verified process;
|
|
575
|
+
// merely seeing the old port online again is not proof that restart completed.
|
|
576
|
+
if (isProxyReplacement(previous, live)) {
|
|
577
|
+
return live;
|
|
578
|
+
}
|
|
579
|
+
const remainingMs = deadlineAt - Date.now();
|
|
580
|
+
if (remainingMs > 0) await Bun.sleep(Math.min(250, remainingMs));
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function reportRestartFailure(result: Extract<ProxyRestartResult, { ok: false }>): void {
|
|
586
|
+
if (result.phase === "identity") {
|
|
587
|
+
console.error("❌ Refusing to restart because the running proxy identity could not be attested.");
|
|
588
|
+
} else if (result.phase === "request") {
|
|
589
|
+
const code = result.error instanceof Error ? result.error.message : "";
|
|
590
|
+
if (code === "restart_capability_unsupported") {
|
|
591
|
+
console.error("❌ The running proxy predates process-bound restart support; no unsafe fallback was attempted.");
|
|
592
|
+
console.error(" After confirming this home owns the proxy, run `ocx stop` and then `ocx start` once.");
|
|
593
|
+
} else {
|
|
594
|
+
console.error("❌ Proxy restart request could not be confirmed; no fallback stop/start was attempted.");
|
|
595
|
+
}
|
|
596
|
+
} else if (result.phase === "replacement") {
|
|
597
|
+
console.error("❌ Proxy restart was accepted, but no identity-verified replacement became healthy in time.");
|
|
598
|
+
} else {
|
|
599
|
+
console.error("❌ Proxy was not running and the fallback start did not become healthy.");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function handleProxyRestart(
|
|
604
|
+
startWhenStopped: () => Promise<boolean | "skipped">,
|
|
605
|
+
): Promise<boolean> {
|
|
606
|
+
const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
|
|
607
|
+
const result = await runProxyRestart({
|
|
608
|
+
findLive: () => discoverStableProxyForRestart({
|
|
609
|
+
findLive: () => findLiveProxy({ deadlineAt, attempts: 2 }),
|
|
610
|
+
expired: () => Date.now() >= deadlineAt,
|
|
611
|
+
}),
|
|
612
|
+
startWhenStopped,
|
|
613
|
+
requestInPlaceRestart: previous => requestBoundSystemRestart(previous, deadlineAt),
|
|
614
|
+
waitForReplacement: previous => waitForProxyReplacement(previous, deadlineAt),
|
|
451
615
|
});
|
|
452
|
-
if (!ok)
|
|
616
|
+
if (!result.ok) reportRestartFailure(result);
|
|
617
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
618
|
+
return result.ok;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function handleTrayProxyRestart(): Promise<void> {
|
|
622
|
+
await handleProxyRestart(() => handleTrayProxyStart(false));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> {
|
|
626
|
+
if (!codexAutoStartEnabled(loadConfig())) {
|
|
627
|
+
console.log("Codex autostart is disabled; no proxy was started.");
|
|
628
|
+
return "skipped";
|
|
629
|
+
}
|
|
630
|
+
return handleEnsure({ existingIsSuccess: false });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function restoreSharedClientStateAfterStop(): Promise<boolean> {
|
|
634
|
+
let restored = true;
|
|
635
|
+
try {
|
|
636
|
+
const result = await restoreNativeCodexAsync();
|
|
637
|
+
if (result.success) console.log(`↩️ ${result.message}`);
|
|
638
|
+
else {
|
|
639
|
+
restored = false;
|
|
640
|
+
console.error(`⚠️ ${result.message}`);
|
|
641
|
+
}
|
|
642
|
+
} catch (error) {
|
|
643
|
+
restored = false;
|
|
644
|
+
console.error(`⚠️ Native Codex restore failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// A refused or thrown Grok strip is actionable because it would point Grok at a dead proxy.
|
|
648
|
+
try {
|
|
649
|
+
const grok = stripGrokConfig();
|
|
650
|
+
if (grok.changed) console.log(`↩️ ${grok.message}`);
|
|
651
|
+
else if (!grok.ok) { restored = false; console.error(`⚠️ ${grok.message}`); }
|
|
652
|
+
} catch (error) {
|
|
653
|
+
restored = false;
|
|
654
|
+
console.error(`⚠️ Grok config restore failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
655
|
+
}
|
|
656
|
+
return restored;
|
|
453
657
|
}
|
|
454
658
|
|
|
455
659
|
async function handleStop() {
|
|
@@ -493,6 +697,10 @@ async function handleStop() {
|
|
|
493
697
|
// exact teardown the refusal exists to prevent.
|
|
494
698
|
const detail = err instanceof Error ? err.message : String(err);
|
|
495
699
|
if (detail) console.error(` ${detail}`);
|
|
700
|
+
if (err instanceof ProxyOwnershipRefusedError) {
|
|
701
|
+
ownershipBlocked = true;
|
|
702
|
+
console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
|
|
703
|
+
}
|
|
496
704
|
}
|
|
497
705
|
} else {
|
|
498
706
|
// Snapshot the stale on-disk state BEFORE the async probe: a concurrent `ocx start`
|
|
@@ -511,6 +719,10 @@ async function handleStop() {
|
|
|
511
719
|
console.error(`❌ Failed to stop proxy (PID ${live.pid}).`);
|
|
512
720
|
const detail = err instanceof Error ? err.message : String(err);
|
|
513
721
|
if (detail) console.error(` ${detail}`);
|
|
722
|
+
if (err instanceof ProxyOwnershipRefusedError) {
|
|
723
|
+
ownershipBlocked = true;
|
|
724
|
+
console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running.");
|
|
725
|
+
}
|
|
514
726
|
}
|
|
515
727
|
} else if (!stoppedService) {
|
|
516
728
|
console.log("No running proxy found.");
|
|
@@ -523,26 +735,11 @@ async function handleStop() {
|
|
|
523
735
|
removeRuntimePortIfPidIs(staleRuntimePid);
|
|
524
736
|
}
|
|
525
737
|
}
|
|
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).
|
|
738
|
+
// Environment ownership is independent from service ownership. Always roll back
|
|
739
|
+
// current-home variables; the helper refuses foreign markers on its own.
|
|
536
740
|
try { revertSystemEnv(); } catch { /* best-effort */ }
|
|
537
741
|
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 */ }
|
|
742
|
+
if (!await restoreSharedClientStateAfterStop()) stopFailed = true;
|
|
546
743
|
}
|
|
547
744
|
// Set the code rather than exiting inline: `restart` and the tray coordinator call this
|
|
548
745
|
// function and need it to RETURN so they can decide what to do next.
|
|
@@ -586,8 +783,8 @@ async function handleUninstall() {
|
|
|
586
783
|
});
|
|
587
784
|
}
|
|
588
785
|
|
|
589
|
-
await runStep("native Codex restored", () => {
|
|
590
|
-
const r =
|
|
786
|
+
await runStep("native Codex restored", async () => {
|
|
787
|
+
const r = await restoreNativeCodexAsync();
|
|
591
788
|
if (!r.success) throw new Error(r.message);
|
|
592
789
|
});
|
|
593
790
|
|
|
@@ -634,13 +831,17 @@ async function handleUninstall() {
|
|
|
634
831
|
console.error(`\nUninstall finished with ${failures.length} failed step(s): ${failures.join(", ")}`);
|
|
635
832
|
process.exit(1);
|
|
636
833
|
}
|
|
637
|
-
console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @
|
|
834
|
+
console.log("\n✅ opencodex local state removed. Remove the package with: npm uninstall -g @bitkyc08/opencodex");
|
|
638
835
|
}
|
|
639
836
|
|
|
640
837
|
async function handleStatus() {
|
|
641
838
|
const statusArgs = args.slice(1);
|
|
642
|
-
|
|
643
|
-
|
|
839
|
+
// Order-independent: the previous form only honoured `--json` as the LONE argument, so
|
|
840
|
+
// `ocx status --json --anything` silently printed human output to a caller that asked
|
|
841
|
+
// for JSON. Take the flag out of argv, then reject whatever is left over -- which keeps
|
|
842
|
+
// the strict unknown-argument behaviour rather than trading one defect for another.
|
|
843
|
+
const wantsJson = takeFlag(statusArgs, "--json");
|
|
844
|
+
if (statusArgs.length > 0) {
|
|
644
845
|
console.error("Usage: ocx status [--json]");
|
|
645
846
|
process.exit(1);
|
|
646
847
|
}
|
|
@@ -657,9 +858,28 @@ async function handleStatus() {
|
|
|
657
858
|
console.log(`❌ Proxy: ${status.proxyLabel}`);
|
|
658
859
|
}
|
|
659
860
|
console.log(` Health: ${status.healthLabel}`);
|
|
861
|
+
// Printed here, not only in --json: a stale ocx on PATH is exactly the situation where
|
|
862
|
+
// the operator is reading human output and wondering why the CLI disagrees with the
|
|
863
|
+
// dashboard. Adding the JSON field alone would satisfy a test and help nobody (#2701).
|
|
864
|
+
if (status.json.versionSkew.warning) {
|
|
865
|
+
console.log(` ⚠️ ${status.json.versionSkew.warning}`);
|
|
866
|
+
}
|
|
867
|
+
for (const line of unusedProxyWarningLines({
|
|
868
|
+
proxyUp: Boolean(status.json.proxy.pid || status.json.proxy.health.ok),
|
|
869
|
+
routingKind: status.json.startup.routingKind,
|
|
870
|
+
})) {
|
|
871
|
+
console.log(` ${line}`);
|
|
872
|
+
}
|
|
660
873
|
if (!(status.json.proxy.pid || status.json.proxy.health.ok)) {
|
|
661
874
|
console.log(" ↳ Not running — Codex/Claude requests will fail with connection errors.");
|
|
662
|
-
|
|
875
|
+
// The service summary a few lines below already tells a registered-but-not-serving
|
|
876
|
+
// user to repair. Printing "install the persistent service" unconditionally
|
|
877
|
+
// contradicted it in the same report, and install re-registers: UAC on Windows and a
|
|
878
|
+
// possible WinSW-to-scheduler switch for someone who already has a service.
|
|
879
|
+
const installed = status.json.startup.serviceInstalled && !status.json.startup.serviceConflict;
|
|
880
|
+
console.log(installed
|
|
881
|
+
? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
|
|
882
|
+
: " Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
|
|
663
883
|
}
|
|
664
884
|
console.log(` Dashboard: ${status.json.dashboard.url}`);
|
|
665
885
|
console.log(` Config: ${status.json.paths.config}`);
|
|
@@ -669,6 +889,7 @@ async function handleStatus() {
|
|
|
669
889
|
console.log(` Default provider: ${status.json.defaultProvider}`);
|
|
670
890
|
console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}`);
|
|
671
891
|
console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}`);
|
|
892
|
+
console.log(` ${formatStartupRoutingDetail(status.json.startup)}`);
|
|
672
893
|
console.log(` Service: ${status.json.service.summary}`);
|
|
673
894
|
console.log(` ${status.json.codexShim.summary}`);
|
|
674
895
|
console.log(` Codex runtime: ${status.json.codexRuntime.path}`);
|
|
@@ -707,407 +928,75 @@ async function handleStatus() {
|
|
|
707
928
|
}
|
|
708
929
|
}
|
|
709
930
|
|
|
710
|
-
function handleRecoverHistory() {
|
|
931
|
+
async function handleRecoverHistory() {
|
|
711
932
|
if (args[1] !== "--legacy-openai") {
|
|
712
|
-
console.error("Usage: ocx recover-history --legacy-openai");
|
|
713
|
-
console.error("
|
|
933
|
+
console.error("Usage: ocx recover-history --legacy-openai --yes");
|
|
934
|
+
console.error("This force-relabels every user-message opencodex row to OpenAI, including legitimate dedicated-provider history. Back up first and use it only for pre-backup legacy recovery.");
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
console.error("WARNING: this force-relabels every user-message opencodex row to OpenAI, normalizes exec to cli, and includes legitimate dedicated-provider history.");
|
|
938
|
+
if (args.length !== 3 || args[2] !== "--yes") {
|
|
939
|
+
console.error("Re-run with explicit confirmation: ocx recover-history --legacy-openai --yes");
|
|
714
940
|
process.exit(1);
|
|
715
941
|
}
|
|
716
|
-
|
|
942
|
+
// Manifest-independent legacy ejection, serialized like every other history
|
|
943
|
+
// mutation. It is a separate operation from generic restore precisely because
|
|
944
|
+
// it must not read, consume or replace the backup manifest.
|
|
945
|
+
const outcome = await runCodexHistoryJob({
|
|
946
|
+
...resolveCodexHistoryJobTarget(),
|
|
947
|
+
operation: "recover-legacy-openai",
|
|
948
|
+
});
|
|
949
|
+
const r = outcome.kind === "converged"
|
|
950
|
+
? { rows: outcome.rows, files: outcome.files, failed: undefined }
|
|
951
|
+
: { rows: 0, files: 0, failed: true as const };
|
|
717
952
|
if (r.failed) {
|
|
718
953
|
console.error(
|
|
719
|
-
|
|
954
|
+
`⚠️ Recovery SKIPPED: ${describeHistoryJobFailure(outcome, "recover-legacy")}`,
|
|
720
955
|
);
|
|
721
956
|
process.exit(1);
|
|
722
957
|
}
|
|
723
958
|
console.log(`Recovered ${r.rows} legacy thread(s) to openai (${r.files} rollout file(s) updated).`);
|
|
724
959
|
}
|
|
725
960
|
|
|
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);
|
|
961
|
+
/**
|
|
962
|
+
* `ocx ready` — arguments are pre-parsed above (before
|
|
963
|
+
* maybeAutoRestoreCodexShim) so invalid usage exits 64 before any global
|
|
964
|
+
* preflight. This handler only runs the dependency-injected runner in ./ready
|
|
965
|
+
* and exits with the returned code; it performs no parsing and no I/O of its
|
|
966
|
+
* own. The full behavior is unit-testable without spawning a subprocess.
|
|
967
|
+
*/
|
|
968
|
+
async function handleReady(args: ReadyArgs): Promise<number> {
|
|
969
|
+
return runReady(args);
|
|
1113
970
|
}
|
|
971
|
+
|
|
972
|
+
process.exit(await dispatchCommand(head, {
|
|
973
|
+
args,
|
|
974
|
+
command,
|
|
975
|
+
head,
|
|
976
|
+
loadConfig,
|
|
977
|
+
findLiveProxy,
|
|
978
|
+
probeHostname,
|
|
979
|
+
waitForProxy,
|
|
980
|
+
startArgv,
|
|
981
|
+
spawnDetached: argv => {
|
|
982
|
+
const child = spawn(process.execPath, argv, {
|
|
983
|
+
detached: true,
|
|
984
|
+
stdio: "ignore",
|
|
985
|
+
windowsHide: true,
|
|
986
|
+
env: withProcessRuntimeProvenance(process.env),
|
|
987
|
+
});
|
|
988
|
+
child.unref();
|
|
989
|
+
},
|
|
990
|
+
handleStart,
|
|
991
|
+
handleStop,
|
|
992
|
+
handleEnsure,
|
|
993
|
+
handleTrayProxyStart,
|
|
994
|
+
handleTrayProxyRestart,
|
|
995
|
+
handleRestartStartWhenStopped,
|
|
996
|
+
handleProxyRestart,
|
|
997
|
+
handleUninstall,
|
|
998
|
+
handleStatus,
|
|
999
|
+
handleRecoverHistory,
|
|
1000
|
+
handleReady,
|
|
1001
|
+
serviceCommand,
|
|
1002
|
+
}));
|