@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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 +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
package/src/cli/registry.ts
CHANGED
|
@@ -29,18 +29,32 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
29
29
|
"--socks5-off Clear a saved SOCKS5 outbound proxy from config.proxy.",
|
|
30
30
|
],
|
|
31
31
|
},
|
|
32
|
-
{
|
|
32
|
+
{
|
|
33
|
+
name: "stop",
|
|
34
|
+
usage: "ocx stop [--json]",
|
|
35
|
+
summary: "Stop the proxy and restore native Codex config.",
|
|
36
|
+
details: [
|
|
37
|
+
"--json keeps the stop path unchanged and prints one structured summary document on stdout; human output moves to stderr.",
|
|
38
|
+
"Exit codes are identical with and without --json: 0, 1, 79 (history cleanup incomplete), 80 (teardown deferred).",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
33
41
|
{
|
|
34
42
|
name: "restore",
|
|
35
43
|
aliases: ["eject"],
|
|
36
44
|
usage: "ocx restore [back]",
|
|
37
45
|
summary: "Restore native Codex config without stopping the proxy; `restore back` re-points codex at the running proxy.",
|
|
46
|
+
details: [
|
|
47
|
+
"--remove-codex-provider-table Also remove [model_providers.opencodex] when a paginated home made restore keep it. Conversations tagged opencodex stop opening.",
|
|
48
|
+
],
|
|
38
49
|
},
|
|
39
50
|
{
|
|
40
51
|
name: "eject",
|
|
41
52
|
aliases: [],
|
|
42
53
|
usage: "ocx eject [back]",
|
|
43
54
|
summary: "Restore native Codex config without stopping the proxy; `eject back` re-points codex at the running proxy.",
|
|
55
|
+
details: [
|
|
56
|
+
"--remove-codex-provider-table Also remove [model_providers.opencodex] when a paginated home made restore keep it. Conversations tagged opencodex stop opening.",
|
|
57
|
+
],
|
|
44
58
|
},
|
|
45
59
|
{
|
|
46
60
|
name: "recover-history",
|
|
@@ -94,9 +108,10 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
94
108
|
{
|
|
95
109
|
name: "tray",
|
|
96
110
|
usage: "ocx tray <install|start|stop|status|uninstall|remove> [--json] [--no-start]",
|
|
97
|
-
summary: "Install and control the Windows status tray icon.",
|
|
111
|
+
summary: "Install and control the Windows status tray icon (deprecated in favor of the desktop app).",
|
|
98
112
|
details: [
|
|
99
113
|
"The tray starts at Windows login and provides one-click proxy controls.",
|
|
114
|
+
"Deprecated: the OpenCodex desktop app provides the tray on Windows, macOS, and Linux; `ocx tray` remains for installs without the desktop app.",
|
|
100
115
|
"Tray start/stop controls the icon only; use its menu to start or stop the proxy.",
|
|
101
116
|
"--no-start (install only) installs the tray without launching it immediately.",
|
|
102
117
|
],
|
|
@@ -298,6 +313,16 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
298
313
|
usage: "ocx model <subcommand>",
|
|
299
314
|
summary: "Alias of ocx models.",
|
|
300
315
|
},
|
|
316
|
+
{
|
|
317
|
+
name: "companion",
|
|
318
|
+
usage: "ocx companion <show|set|reset> ...",
|
|
319
|
+
summary: "Inspect and configure menu-bar and widget companion usage settings.",
|
|
320
|
+
details: [
|
|
321
|
+
"ocx companion and ocx companion show read settings; use --json for machine-readable output.",
|
|
322
|
+
"ocx companion set accepts one or more key=value assignments; values are parsed as JSON when possible.",
|
|
323
|
+
"ocx companion reset restores the default settings.",
|
|
324
|
+
],
|
|
325
|
+
},
|
|
301
326
|
{
|
|
302
327
|
name: "combo",
|
|
303
328
|
usage: "ocx combo <list|show|set|remove> ...",
|
|
@@ -480,6 +505,7 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
480
505
|
details: [
|
|
481
506
|
"Alias of ocx integration client <sub> --client zcode.",
|
|
482
507
|
"enable writes the managed provider.opencodex block into ~/.zcode/v2/config.json; disable removes only that block.",
|
|
508
|
+
"ZCode 3.14 moved its providers to ~/.zcode/v2/provider_config.json; where that file exists, enable is refused because the write cannot reach the client.",
|
|
483
509
|
"ZCode reads its config at startup — restart ZCode after enable/disable.",
|
|
484
510
|
"Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
|
|
485
511
|
],
|
|
@@ -531,6 +557,19 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
531
557
|
"Invalid or unknown arguments exit 64. Not-ready, pending, failed, timeout, and unreachable exit 1.",
|
|
532
558
|
],
|
|
533
559
|
},
|
|
560
|
+
{
|
|
561
|
+
name: "resolve",
|
|
562
|
+
usage: "ocx resolve [--json]",
|
|
563
|
+
summary: "Emit the resolved config home, effective port, and identity-checked proxy liveness as one JSON document.",
|
|
564
|
+
details: [
|
|
565
|
+
"Machine surface for embedding shells: it replaces a second home/port/liveness implementation beside the CLI.",
|
|
566
|
+
"The port is the live listener's port when an opencodex proxy answers, otherwise the configured port (default 10100).",
|
|
567
|
+
"Liveness is three-valued: live, absent-proven (every recorded and configured endpoint definitively dead), or unknown — unknown exits 1 and never reads as absent.",
|
|
568
|
+
"--json emits one versioned document (schema ocx-resolve/1); the default prints two human lines.",
|
|
569
|
+
"Exit 0 carries a trustworthy verdict; exit 1 means the CLI could not resolve (invalid config or undecidable liveness) and callers must refuse to guess.",
|
|
570
|
+
"Any unknown argument exits 64 before preflight side effects.",
|
|
571
|
+
],
|
|
572
|
+
},
|
|
534
573
|
{
|
|
535
574
|
name: "lab",
|
|
536
575
|
usage: "ocx lab <status|verdicts|subjects|subject|observations|events|event|artifacts|artifact|catalog> [options] [--json]",
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ocx resolve` — the machine-readable runtime resolution surface for an embedding shell.
|
|
3
|
+
*
|
|
4
|
+
* D5 of devlog/_plan/260921_app_runtime_ownership/: the desktop shell must stop resolving
|
|
5
|
+
* the config home, the port and liveness itself. The tuned probe budgets in
|
|
6
|
+
* src/server/proxy-liveness.ts exist because a shell-side reimplementation answered
|
|
7
|
+
* "nobody listening" twice and started duplicate proxies; this verb exposes that module's
|
|
8
|
+
* verdict instead of copying it, alongside src/config/paths.ts (the home) and the CLI's
|
|
9
|
+
* own preferred-port selection (`config.port ?? 10100` — resolve takes no --port).
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - `--json` puts exactly ONE JSON document on stdout, versioned by `schema`; the
|
|
13
|
+
* default prints two human lines, the same opt-in split as `ocx ready --json`.
|
|
14
|
+
* - liveness has three answers, not two: "live", "absent-proven" (every recorded and
|
|
15
|
+
* configured endpoint definitively refused or answered non-opencodex), and unknown.
|
|
16
|
+
* Unknown NEVER reaches the wire as absent — a probe that timed out, a listener that
|
|
17
|
+
* withheld /healthz, or an identity mismatch exits 1 instead. Only "absent-proven"
|
|
18
|
+
* may authorise starting a new runtime.
|
|
19
|
+
* - exit 0 whenever a trustworthy verdict exists — live, or proven absent. A MISSING
|
|
20
|
+
* config.json is defaults, not an error.
|
|
21
|
+
* - exit 1 when the CLI cannot resolve: an invalid config.json must NOT be answered
|
|
22
|
+
* with `loadConfig`'s repair-to-defaults behaviour, because that hands the caller
|
|
23
|
+
* a guessed port; and unknown liveness must not be answered as absence.
|
|
24
|
+
* - exit 64 for any argument, pre-parsed in src/cli/root.ts before preflight side
|
|
25
|
+
* effects, mirroring `ocx ready`. The verb is read-only and listed in
|
|
26
|
+
* skipsCodexShimAutoRestore, so a lookup made to populate a consent surface never
|
|
27
|
+
* triggers a shim repair side effect.
|
|
28
|
+
*
|
|
29
|
+
* Discovery uses the START_OWNERSHIP_LIVENESS budget, not the 750ms single-shot default:
|
|
30
|
+
* the shell's launch decision keys on this verdict, and answering "nobody" for a slow
|
|
31
|
+
* live proxy is the duplicate-proxy decision the start path tunes against (#5004).
|
|
32
|
+
*
|
|
33
|
+
* Lives outside cli/index.ts (which dispatches argv at module top level) so tests can
|
|
34
|
+
* import it, the same split as ready.ts.
|
|
35
|
+
*/
|
|
36
|
+
import { readConfigDiagnostics, type ConfigDiagnostics } from "../config";
|
|
37
|
+
import { getConfigDir } from "../config/paths";
|
|
38
|
+
import { readRuntimePort } from "../config/process-state";
|
|
39
|
+
import { packageVersion } from "../lib/package-version";
|
|
40
|
+
import {
|
|
41
|
+
findLiveProxy,
|
|
42
|
+
probeEndpointLiveness,
|
|
43
|
+
START_OWNERSHIP_LIVENESS,
|
|
44
|
+
type EndpointLiveness,
|
|
45
|
+
type LiveProxy,
|
|
46
|
+
} from "../server/proxy-liveness";
|
|
47
|
+
import { endpointsToProve, everyEndpointProvenDownAsync, type ProbeEndpoint } from "./uninstall-plan";
|
|
48
|
+
|
|
49
|
+
/** Wire version of the resolve document. Bump only on an incompatible shape change. */
|
|
50
|
+
export const RESOLVE_SCHEMA = "ocx-resolve/1";
|
|
51
|
+
|
|
52
|
+
/** The port every preferred-port selection in the CLI falls back to. */
|
|
53
|
+
export const RESOLVE_DEFAULT_PORT = 10100;
|
|
54
|
+
|
|
55
|
+
export interface ResolveLivenessJson {
|
|
56
|
+
/**
|
|
57
|
+
* "live" when the identity-checked probe found our proxy; "absent-proven" when every
|
|
58
|
+
* recorded and configured endpoint is definitively dead. The third state — unknown —
|
|
59
|
+
* exits 1 before this document is printed, so it never appears on the wire as absence.
|
|
60
|
+
*/
|
|
61
|
+
status: "live" | "absent-proven";
|
|
62
|
+
pid: number | null;
|
|
63
|
+
port: number | null;
|
|
64
|
+
/** Raw bind hostname that answered; compose probe URLs via probeHostname semantics. */
|
|
65
|
+
hostname?: string;
|
|
66
|
+
/** Where the verdict came from: the runtime record, or the configured listen port. */
|
|
67
|
+
source: LiveProxy["source"] | null;
|
|
68
|
+
/** Version the live proxy reported on /healthz, when it reported one. */
|
|
69
|
+
version?: string;
|
|
70
|
+
/** Listener role the live proxy reported, when it reported one ("client" = connected client). */
|
|
71
|
+
role?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ResolveJson {
|
|
75
|
+
schema: typeof RESOLVE_SCHEMA;
|
|
76
|
+
/** Version of this CLI binary, so a shell can compare its engine against the live proxy. */
|
|
77
|
+
cliVersion: string;
|
|
78
|
+
/** Resolved opencodex home (OPENCODEX_HOME or ~/.opencodex), from src/config/paths.ts. */
|
|
79
|
+
configHome: string;
|
|
80
|
+
port: {
|
|
81
|
+
/** The port a client should use: the live listener's port when one answers, else the configured one. */
|
|
82
|
+
effective: number;
|
|
83
|
+
/** The configured listen port (config.port ?? 10100); what a start would prefer. */
|
|
84
|
+
configured: number;
|
|
85
|
+
/** Whether `effective` came from a live proxy or from configuration. */
|
|
86
|
+
source: LiveProxy["source"];
|
|
87
|
+
};
|
|
88
|
+
liveness: ResolveLivenessJson;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ResolveArgs {
|
|
92
|
+
json: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type ResolveParseResult = { ok: true; args: ResolveArgs } | { ok: false; code: 64 };
|
|
96
|
+
|
|
97
|
+
/** Pure argument parser: the only flag is `--json`. */
|
|
98
|
+
export function parseResolveArgs(argv: string[]): ResolveParseResult {
|
|
99
|
+
for (const flag of argv) {
|
|
100
|
+
if (flag !== "--json") return { ok: false, code: 64 };
|
|
101
|
+
}
|
|
102
|
+
return { ok: true, args: { json: argv.includes("--json") } };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ResolveIo {
|
|
106
|
+
configDir?: () => string;
|
|
107
|
+
readDiagnostics?: () => ConfigDiagnostics;
|
|
108
|
+
findLive?: () => Promise<LiveProxy | null>;
|
|
109
|
+
/** Runtime-port record reader; production default is readRuntimePort. */
|
|
110
|
+
readRuntime?: () => { port?: number; hostname?: string } | null;
|
|
111
|
+
/** Tri-state endpoint probe; production default runs in-process for compiled standalone binaries. */
|
|
112
|
+
probeEndpoint?: (endpoint: ProbeEndpoint) => EndpointLiveness | Promise<EndpointLiveness>;
|
|
113
|
+
cliVersion?: () => string;
|
|
114
|
+
stdout?: { log: (s: string) => void };
|
|
115
|
+
stderr?: { error: (s: string) => void };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function livenessJson(live: LiveProxy | null): ResolveLivenessJson {
|
|
119
|
+
// Reaching here with null means absence was PROVEN by the caller (unknown exits 1
|
|
120
|
+
// before this document is built).
|
|
121
|
+
if (!live) return { status: "absent-proven", pid: null, port: null, source: null };
|
|
122
|
+
return {
|
|
123
|
+
status: "live",
|
|
124
|
+
pid: live.pid,
|
|
125
|
+
port: live.port,
|
|
126
|
+
source: live.source,
|
|
127
|
+
...(live.hostname === undefined ? {} : { hostname: live.hostname }),
|
|
128
|
+
...(live.version === undefined ? {} : { version: live.version }),
|
|
129
|
+
...(live.role === undefined ? {} : { role: live.role }),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Pure shaper: one live verdict plus configuration becomes the wire document. */
|
|
134
|
+
export function buildResolveJson(
|
|
135
|
+
config: { port?: number },
|
|
136
|
+
live: LiveProxy | null,
|
|
137
|
+
configHome: string,
|
|
138
|
+
cliVersion: string,
|
|
139
|
+
): ResolveJson {
|
|
140
|
+
const configured = config.port ?? RESOLVE_DEFAULT_PORT;
|
|
141
|
+
return {
|
|
142
|
+
schema: RESOLVE_SCHEMA,
|
|
143
|
+
cliVersion,
|
|
144
|
+
configHome,
|
|
145
|
+
port: {
|
|
146
|
+
effective: live ? live.port : configured,
|
|
147
|
+
configured,
|
|
148
|
+
source: live ? live.source : "config",
|
|
149
|
+
},
|
|
150
|
+
liveness: livenessJson(live),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Human form: two lines, no prose flourish — an operator skims it, a shell uses --json.
|
|
156
|
+
*/
|
|
157
|
+
function reportHuman(json: ResolveJson, stdout: { log: (s: string) => void }): void {
|
|
158
|
+
stdout.log(`Config home: ${json.configHome}`);
|
|
159
|
+
const live = json.liveness;
|
|
160
|
+
if (live.status === "live") {
|
|
161
|
+
const pidText = live.pid === null ? "unknown" : String(live.pid);
|
|
162
|
+
const versionText = live.version ?? "unknown version";
|
|
163
|
+
stdout.log(`Proxy live on port ${json.port.effective} (PID ${pidText}, ${versionText}); effective port ${json.port.effective}.`);
|
|
164
|
+
} else {
|
|
165
|
+
stdout.log(`No live proxy (absence proven); effective port ${json.port.effective} (configured).`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run `ocx resolve` over injected I/O. Returns the exit code. The production defaults
|
|
171
|
+
* read config through the diagnostics surface (which distinguishes missing, valid and
|
|
172
|
+
* invalid instead of repairing to defaults) and perform one identity-checked discovery
|
|
173
|
+
* at the ownership-safe budget — resolve adds no probing policy of its own.
|
|
174
|
+
*/
|
|
175
|
+
export async function runResolve(args: ResolveArgs, io: ResolveIo = {}): Promise<number> {
|
|
176
|
+
const stdout = io.stdout ?? console;
|
|
177
|
+
const stderr = io.stderr ?? console;
|
|
178
|
+
const configDir = io.configDir ?? getConfigDir;
|
|
179
|
+
const readDiagnostics = io.readDiagnostics ?? readConfigDiagnostics;
|
|
180
|
+
const findLive = io.findLive ?? (() => findLiveProxy(START_OWNERSHIP_LIVENESS));
|
|
181
|
+
const readRuntime = io.readRuntime ?? readRuntimePort;
|
|
182
|
+
const probeEndpoint = io.probeEndpoint ?? probeEndpointLiveness;
|
|
183
|
+
const cliVersion = io.cliVersion ?? packageVersion;
|
|
184
|
+
const configHome = configDir();
|
|
185
|
+
let diagnostics: ConfigDiagnostics;
|
|
186
|
+
try {
|
|
187
|
+
diagnostics = readDiagnostics();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
// A resolution that could not run must not read as "no proxy": the caller has to
|
|
190
|
+
// refuse to guess (D5) rather than treat this as a proven-absent verdict.
|
|
191
|
+
stderr.error(`resolve failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
if (diagnostics.source === "fallback") {
|
|
195
|
+
// An invalid config must not resolve to defaults: the effective port would be a
|
|
196
|
+
// guess at 10100 while the operator's config.port is unread. The repair-to-defaults
|
|
197
|
+
// policy in loadConfig is for interactive recovery, not for a shell contract.
|
|
198
|
+
stderr.error(`resolve failed: the config in ${configHome} is invalid (${diagnostics.error ?? "unknown error"}); refusing to guess.`);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
let live: LiveProxy | null;
|
|
202
|
+
try {
|
|
203
|
+
live = await findLive();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
stderr.error(`resolve failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
if (!live) {
|
|
209
|
+
// findLiveProxy collapses "definitely nothing" and "could not tell" into the same
|
|
210
|
+
// null. The launch decision keys on this verdict, so resolve owes the caller the
|
|
211
|
+
// tri-state answer the updater already enforces: only EVERY candidate definitively
|
|
212
|
+
// dead is absence. Anything else is unknown, and unknown exits 1 — it must never
|
|
213
|
+
// authorise starting a second runtime.
|
|
214
|
+
let provenDown = false;
|
|
215
|
+
try {
|
|
216
|
+
provenDown = await everyEndpointProvenDownAsync(endpointsToProve(readRuntime(), diagnostics.config), probeEndpoint);
|
|
217
|
+
} catch {
|
|
218
|
+
// A probe that cannot run is not evidence of absence.
|
|
219
|
+
provenDown = false;
|
|
220
|
+
}
|
|
221
|
+
if (!provenDown) {
|
|
222
|
+
stderr.error("resolve: liveness is unknown (a probe timed out or a listener withheld /healthz); refusing to treat unknown as absent.");
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const json = buildResolveJson(diagnostics.config, live, configHome, cliVersion());
|
|
227
|
+
if (args.json) stdout.log(JSON.stringify(json));
|
|
228
|
+
else reportHuman(json, stdout);
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
package/src/cli/root.ts
CHANGED
|
@@ -10,16 +10,19 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { hasHelpFlag, printSubcommandUsage, printUsage, printVersion } from "./help";
|
|
12
12
|
import { parseReadyArgs, type ReadyArgs } from "./ready";
|
|
13
|
+
import { parseResolveArgs, type ResolveArgs } from "./resolve";
|
|
13
14
|
import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
|
|
14
15
|
|
|
15
16
|
export interface CliHead {
|
|
16
|
-
kind: "version" | "help" | "ready" | "command";
|
|
17
|
+
kind: "version" | "help" | "ready" | "resolve" | "command";
|
|
17
18
|
command: string | undefined;
|
|
18
19
|
args: string[];
|
|
19
20
|
/** For kind "help": the subcommand whose usage should print, if any. */
|
|
20
21
|
helpTarget?: string;
|
|
21
22
|
/** Present only for `ready`; undefined when the ready args failed to parse. */
|
|
22
23
|
readyArgs?: ReadyArgs;
|
|
24
|
+
/** Present only for `resolve`; undefined when the resolve args failed to parse. */
|
|
25
|
+
resolveArgs?: ResolveArgs;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export function parseCliHead(argv: string[]): CliHead {
|
|
@@ -51,6 +54,13 @@ export function parseCliHead(argv: string[]): CliHead {
|
|
|
51
54
|
if (!parsed.ok) return { kind: "ready", command, args, readyArgs: undefined };
|
|
52
55
|
return { kind: "ready", command, args, readyArgs: parsed.args };
|
|
53
56
|
}
|
|
57
|
+
// Same ordering contract as `ready`: `ocx resolve` rejects any argument with exit
|
|
58
|
+
// 64 BEFORE maybeAutoRestoreCodexShim (or any other preflight with side effects) runs.
|
|
59
|
+
if (command === "resolve") {
|
|
60
|
+
const parsed = parseResolveArgs(args.slice(1));
|
|
61
|
+
if (!parsed.ok) return { kind: "resolve", command, args, resolveArgs: undefined };
|
|
62
|
+
return { kind: "resolve", command, args, resolveArgs: parsed.args };
|
|
63
|
+
}
|
|
54
64
|
return { kind: "command", command, args };
|
|
55
65
|
}
|
|
56
66
|
|
|
@@ -79,6 +89,19 @@ export async function runCli(argv: string[]): Promise<CliHead> {
|
|
|
79
89
|
maybeAutoRestoreCodexShim(head.command, head.args);
|
|
80
90
|
return head;
|
|
81
91
|
}
|
|
92
|
+
case "resolve": {
|
|
93
|
+
// Fail-closed impossible-state guard, mirroring ready: the pre-parse above already
|
|
94
|
+
// rejected invalid arguments before any preflight, so a missing resolveArgs means
|
|
95
|
+
// dispatch diverged. Refuse with code 64 and perform NO I/O.
|
|
96
|
+
if (!head.resolveArgs) {
|
|
97
|
+
console.error("Usage: ocx resolve [--json]");
|
|
98
|
+
console.error(" --json prints one JSON document: the config home, the effective port,");
|
|
99
|
+
console.error(" and the identity-checked liveness verdict.");
|
|
100
|
+
process.exit(64);
|
|
101
|
+
}
|
|
102
|
+
maybeAutoRestoreCodexShim(head.command, head.args);
|
|
103
|
+
return head;
|
|
104
|
+
}
|
|
82
105
|
case "command":
|
|
83
106
|
maybeAutoRestoreCodexShim(head.command, head.args);
|
|
84
107
|
return head;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface StartOwnershipLease {
|
|
2
|
+
release(): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class StartOwnershipRollbackUncertainError extends AggregateError {
|
|
6
|
+
constructor(errors: Iterable<unknown>) {
|
|
7
|
+
super(errors, "start listener rollback could not be proven complete");
|
|
8
|
+
this.name = "StartOwnershipRollbackUncertainError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StartOwnershipPublicationDeps<TBound> {
|
|
13
|
+
acquireLease(): StartOwnershipLease;
|
|
14
|
+
bind(): Promise<TBound>;
|
|
15
|
+
writePid(bound: TBound): void;
|
|
16
|
+
writeRuntime(bound: TBound): void;
|
|
17
|
+
stopBound(bound: TBound): void | Promise<void>;
|
|
18
|
+
removeRuntime(): void;
|
|
19
|
+
removePid(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Bind and publish PID/runtime ownership as one lease-protected transaction. */
|
|
23
|
+
export async function bindAndPublishStartOwnership<TBound>(
|
|
24
|
+
deps: StartOwnershipPublicationDeps<TBound>,
|
|
25
|
+
): Promise<TBound> {
|
|
26
|
+
const lease = deps.acquireLease();
|
|
27
|
+
let bound: TBound;
|
|
28
|
+
let releaseLease = true;
|
|
29
|
+
try {
|
|
30
|
+
try { bound = await deps.bind(); }
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof StartOwnershipRollbackUncertainError) releaseLease = false;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
deps.writePid(bound);
|
|
37
|
+
deps.writeRuntime(bound);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const failures: unknown[] = [error];
|
|
40
|
+
let stopFailed = false;
|
|
41
|
+
try { await deps.stopBound(bound); }
|
|
42
|
+
catch (failure) { stopFailed = true; failures.push(failure); }
|
|
43
|
+
try { deps.removeRuntime(); } catch (failure) { failures.push(failure); }
|
|
44
|
+
try { deps.removePid(); } catch (failure) { failures.push(failure); }
|
|
45
|
+
if (stopFailed) {
|
|
46
|
+
releaseLease = false;
|
|
47
|
+
throw new StartOwnershipRollbackUncertainError(failures);
|
|
48
|
+
}
|
|
49
|
+
if (failures.length > 1) throw new AggregateError(failures, "start ownership publication rollback failed");
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
return bound;
|
|
53
|
+
} finally {
|
|
54
|
+
if (releaseLease) lease.release();
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/cli/status-probes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readPidFileValue, readRuntimePort } from "../config/process-state";
|
|
2
|
-
import { isOpencodexHealthz, probeHostname } from "../server/proxy-liveness";
|
|
2
|
+
import { isConnectionRefused, isOpencodexHealthz, probeHostname } from "../server/proxy-liveness";
|
|
3
3
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
4
4
|
import { isProcessAlive } from "../lib/process-control";
|
|
5
5
|
|
|
@@ -26,23 +26,7 @@ export function proxyHealthFailureReason(error: unknown, signal: AbortSignal): "
|
|
|
26
26
|
: "unreachable";
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
* "Nothing is listening" is narrower than "the probe failed". `unreachable` covers every
|
|
31
|
-
* non-abort failure, including a socket that was ACCEPTED and then reset — which is what
|
|
32
|
-
* an in-flight start looks like mid-bind. Only a connect-phase refusal proves the port is
|
|
33
|
-
* free, so this reads the underlying errno instead of the display string.
|
|
34
|
-
*/
|
|
35
|
-
export function isConnectionRefused(error: unknown): boolean {
|
|
36
|
-
for (let current: unknown = error, depth = 0; current instanceof Error && depth < 4; depth++) {
|
|
37
|
-
const code = (current as { code?: unknown }).code;
|
|
38
|
-
if (code === "ECONNREFUSED" || code === "ConnectionRefused") return true;
|
|
39
|
-
// Bun surfaces the refusal as a plain message on some platforms; the errno name is
|
|
40
|
-
// still the discriminator, not a substring of arbitrary prose.
|
|
41
|
-
if (typeof code === "string" && code.endsWith("ECONNREFUSED")) return true;
|
|
42
|
-
current = (current as { cause?: unknown }).cause;
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
29
|
+
export { isConnectionRefused } from "../server/proxy-liveness";
|
|
46
30
|
|
|
47
31
|
/**
|
|
48
32
|
* A proxy killed by a native trap or SIGKILL never runs the exit cleanup that removes
|
package/src/cli/status.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { durableBunRuntime } from "../lib/bun-runtime";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { codexAutoStartEnabled, getConfigPath, readConfigDiagnostics } from "../config";
|
|
3
4
|
import { getPidPath, readPid, readRuntimePort, type RuntimePortState } from "../config/process-state";
|
|
4
5
|
import { diagnoseCodexBundledPlugins, type CodexPluginsDiagnostic } from "../codex/plugins-doctor";
|
|
@@ -7,6 +8,8 @@ import type { OcxConfig } from "../types";
|
|
|
7
8
|
import { diagnoseService, serviceLogPath } from "../service";
|
|
8
9
|
import { collectStartupHealth, type StartupHealth } from "../codex/autostart-health";
|
|
9
10
|
import { getCodexRoutingKind } from "../codex/inject";
|
|
11
|
+
import { missingOwnedCatalogPath } from "../codex/inject/config-toml";
|
|
12
|
+
import { CODEX_CONFIG_PATH } from "../codex/paths";
|
|
10
13
|
import { diagnoseCodexShim } from "../codex/shim";
|
|
11
14
|
import { displayCodexRuntimePath, effortClampAppliesToRuntime, liveRemovedEfforts, loadLastEffortClamp, resolveCodexRuntime } from "../codex/runtime";
|
|
12
15
|
import { packageVersion } from "./help";
|
|
@@ -502,6 +505,65 @@ export function unusedProxyWarningLines(input: {
|
|
|
502
505
|
];
|
|
503
506
|
}
|
|
504
507
|
|
|
508
|
+
/**
|
|
509
|
+
* The mirror case: routing is ours and nothing is answering it.
|
|
510
|
+
*
|
|
511
|
+
* #5261: this state does not merely fail model calls. The root `openai_base_url` we inject
|
|
512
|
+
* is the base URL of Codex's own built-in openai provider, so with the proxy down a user can
|
|
513
|
+
* be stopped at Codex sign-in with no mention of opencodex anywhere on the screen. The
|
|
514
|
+
* injection is on disk and survives reboot, so it does not clear itself.
|
|
515
|
+
*
|
|
516
|
+
* The rest of the not-running report offers only ways to bring the proxy BACK, which is the
|
|
517
|
+
* wrong half of the choice for someone who wants their editor working again now. `ocx restore`
|
|
518
|
+
* needs no proxy, no management API and no network, so name it here — this report is the
|
|
519
|
+
* surface such a user is most likely to reach before the config file itself.
|
|
520
|
+
*
|
|
521
|
+
* Restricted to routing opencodex owns. `custom-local` is somebody else's gateway, and
|
|
522
|
+
* `ocx restore` would not remove it.
|
|
523
|
+
*/
|
|
524
|
+
export function deadProxyRoutingAdviceLines(input: {
|
|
525
|
+
proxyUp: boolean;
|
|
526
|
+
routingKind: StartupHealth["routingKind"];
|
|
527
|
+
}): string[] {
|
|
528
|
+
if (input.proxyUp || input.routingKind !== "opencodex-local") return [];
|
|
529
|
+
return [
|
|
530
|
+
"Codex is still pointed at this proxy, so sign-in and model requests both fail while it is down.",
|
|
531
|
+
"To hand Codex back to its own account and endpoints without starting anything: ocx restore",
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Read the live Codex config and report an opencodex catalog pointer whose file is gone.
|
|
537
|
+
*
|
|
538
|
+
* Unreadable or absent config is reported as no finding rather than as a problem: this is a
|
|
539
|
+
* diagnostic line, and inventing one from missing evidence is worse than staying quiet.
|
|
540
|
+
*/
|
|
541
|
+
export function detectMissingCodexCatalogPath(): string | null {
|
|
542
|
+
try {
|
|
543
|
+
if (!existsSync(CODEX_CONFIG_PATH)) return null;
|
|
544
|
+
return missingOwnedCatalogPath(readFileSync(CODEX_CONFIG_PATH, "utf8"));
|
|
545
|
+
} catch {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The one state in this report where Codex is broken independently of the proxy (#5261).
|
|
552
|
+
*
|
|
553
|
+
* A `model_catalog_json` naming a file that is gone stops Codex loading its configuration at
|
|
554
|
+
* all, so it presents as the same blank wall as dead routing while having a different cause and
|
|
555
|
+
* a different fix. Both are named, because restarting the proxy rewrites the catalog and
|
|
556
|
+
* restoring removes the pointer, and which one the user wants is their choice, not ours.
|
|
557
|
+
*/
|
|
558
|
+
export function missingCodexCatalogLines(missingCatalogPath: string | null): string[] {
|
|
559
|
+
if (!missingCatalogPath) return [];
|
|
560
|
+
return [
|
|
561
|
+
"⚠️ Codex is pointed at a model catalog that is no longer on disk, so Codex cannot load its config:",
|
|
562
|
+
` ${missingCatalogPath}`,
|
|
563
|
+
" Regenerate it with 'ocx start', or remove opencodex from Codex with 'ocx restore'.",
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
|
|
505
567
|
export async function collectStatus(): Promise<CliStatusView> {
|
|
506
568
|
const configDiagnostics = readConfigDiagnostics();
|
|
507
569
|
const config = configDiagnostics.config;
|