@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/hub.ts
CHANGED
|
@@ -228,8 +228,9 @@ export function hubInviteCommand(
|
|
|
228
228
|
managementUrl: string,
|
|
229
229
|
clients: OcxConnectedClientId[],
|
|
230
230
|
): string {
|
|
231
|
-
const
|
|
232
|
-
|
|
231
|
+
const shellQuote = (value: string): string => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
232
|
+
const clientsFlag = clients.length > 0 ? ` --clients ${shellQuote(clients.join(","))}` : "";
|
|
233
|
+
return `echo ${shellQuote(code)} | ocx connect ${shellQuote(dataUrl)} --management-url ${shellQuote(managementUrl)}${clientsFlag} --pairing-code-stdin`;
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
async function runInvite(args: string[], deps: HubCommandDeps): Promise<number> {
|
package/src/cli/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { findGuiDist } from "../server/gui-static";
|
|
6
|
+
import { inspectGuiBundleFreshness, staleGuiBundleLines } from "../server/gui-freshness";
|
|
4
7
|
|
|
5
8
|
// Best-effort recovery for runtime execution and spawned children if launched
|
|
6
9
|
// from an unlinked/deleted working directory (runs after hoisted ESM module imports).
|
|
@@ -63,8 +66,8 @@ import {
|
|
|
63
66
|
pendingTeardownsAreExactly,
|
|
64
67
|
quarantinePendingTeardown,
|
|
65
68
|
} from "../config/pending-teardown";
|
|
66
|
-
import { collectStatus, hubStatusLines, remoteHubBannerLine, remoteHubStatusLines, unusedProxyWarningLines } from "./status";
|
|
67
|
-
import { endpointsToProve,
|
|
69
|
+
import { collectStatus, deadProxyRoutingAdviceLines, detectMissingCodexCatalogPath, hubStatusLines, missingCodexCatalogLines, remoteHubBannerLine, remoteHubStatusLines, unusedProxyWarningLines } from "./status";
|
|
70
|
+
import { endpointsToProve, everyEndpointProvenDownAsync, sharedTeardownAuthorized, type UninstallObservation } from "./uninstall-plan";
|
|
68
71
|
import { takeFlag } from "./runtime-api";
|
|
69
72
|
import { parseStartOptions, StartArgsError } from "./start-args";
|
|
70
73
|
|
|
@@ -78,17 +81,28 @@ import {
|
|
|
78
81
|
} from "./tray-proxy";
|
|
79
82
|
import { requestBoundSystemRestart } from "./system-restart-client";
|
|
80
83
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
84
|
+
import { SpendLedgerOwnerError } from "../lib/spend-ledger-owner";
|
|
81
85
|
import { redactUrlForLog } from "../lib/redact";
|
|
82
86
|
import { dispatchCommand, decideBusyPreferredPort, decideStartWithLiveOwner } from "./dispatch";
|
|
83
87
|
import { AuxiliaryListenerBindError, findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
84
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
findLiveProxy,
|
|
90
|
+
probeEndpointLiveness,
|
|
91
|
+
probeHostname,
|
|
92
|
+
probePortOwner,
|
|
93
|
+
START_OWNERSHIP_LIVENESS,
|
|
94
|
+
type LiveProxy,
|
|
95
|
+
} from "../server/proxy-liveness";
|
|
85
96
|
import { createReadinessGate } from "../server/readiness";
|
|
86
97
|
import { isApiAuthRequired } from "../server/auth-cors";
|
|
87
98
|
import { runReady, type ReadyArgs } from "./ready";
|
|
99
|
+
import { runResolve, type ResolveArgs } from "./resolve";
|
|
100
|
+
import { summarizeStopRun, type StopOutcome, type StopRunRecord } from "./stop-report";
|
|
88
101
|
import { runCli } from "./root";
|
|
89
102
|
import { isProcessAlive, ProxyOwnershipRefusedError, refusalNextStep, stopProxy } from "../lib/process-control";
|
|
90
103
|
import { startupDataPlaneToken } from "../lib/service-secrets";
|
|
91
|
-
import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, proxyStillLiveAfterStop, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalledDetailed, uninstallServiceIfInstalled, uninstallServiceDetailed } from "../service";
|
|
104
|
+
import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, proxyStillLiveAfterStop, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatePaths, serviceStatusSummary, stopServiceIfInstalledDetailed, uninstallServiceIfInstalled, uninstallServiceDetailed } from "../service";
|
|
105
|
+
import { acquireOwnershipMutationLease } from "../service/ownership-mutation-lease.mjs";
|
|
92
106
|
import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
|
|
93
107
|
import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
|
|
94
108
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
@@ -97,6 +111,10 @@ import { startHistoryMigrationGuardian } from "../codex/history-migration-guardi
|
|
|
97
111
|
import { maybeShowStarPrompt } from "./star-prompt";
|
|
98
112
|
import { scheduleCatalogPrewarm } from "./catalog-prewarm";
|
|
99
113
|
import { maybeShowUpdatePrompt } from "../update/notify";
|
|
114
|
+
import {
|
|
115
|
+
bindAndPublishStartOwnership,
|
|
116
|
+
StartOwnershipRollbackUncertainError,
|
|
117
|
+
} from "./start-ownership-publication";
|
|
100
118
|
import { syncModelsToCodex } from "../codex/sync";
|
|
101
119
|
import {
|
|
102
120
|
HUB_GATED_SKIP_MESSAGE,
|
|
@@ -215,6 +233,13 @@ function startArgv(port?: number): string[] {
|
|
|
215
233
|
return selfLaunchArgv(args);
|
|
216
234
|
}
|
|
217
235
|
|
|
236
|
+
class StartCommandExit extends Error {
|
|
237
|
+
constructor(readonly exitCode: number) {
|
|
238
|
+
super(`start command exited with code ${exitCode}`);
|
|
239
|
+
this.name = "StartCommandExit";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
218
243
|
async function chooseListenPort(
|
|
219
244
|
requestedPort?: number,
|
|
220
245
|
options: { sibling?: boolean } = {},
|
|
@@ -287,17 +312,17 @@ async function chooseListenPort(
|
|
|
287
312
|
// Same contract as the pre-bind owner check: the wrapper's retry loop terminates
|
|
288
313
|
// on a zero exit, and the port it was asked to serve is already served.
|
|
289
314
|
console.log(`Proxy already running (PID ${holder?.pid ?? "unknown"}, port ${preferred}); service wrapper staying out of the way.`);
|
|
290
|
-
|
|
315
|
+
throw new StartCommandExit(0);
|
|
291
316
|
}
|
|
292
317
|
if (decision === "refuse-live-proxy") {
|
|
293
318
|
console.error(`ā ļø Proxy already running (PID ${holder?.pid ?? "unknown"}, port ${preferred}). Use 'ocx stop' first.`);
|
|
294
|
-
|
|
319
|
+
throw new StartCommandExit(1);
|
|
295
320
|
}
|
|
296
321
|
if (decision === "refuse-unidentified-holder") {
|
|
297
322
|
console.error(`ā Port ${preferred} is busy and its holder did not identify as opencodex.`);
|
|
298
323
|
console.error(" Starting on another port would leave Codex pointed at a proxy you did not ask for.");
|
|
299
324
|
console.error(" Stop whatever holds that port, or start on a free one with 'ocx start --port <port>'.");
|
|
300
|
-
|
|
325
|
+
throw new StartCommandExit(1);
|
|
301
326
|
}
|
|
302
327
|
if (preferred > 0) {
|
|
303
328
|
console.log(`ā ļø Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
@@ -312,7 +337,7 @@ async function chooseListenPort(
|
|
|
312
337
|
if (err instanceof PortUnavailableError) {
|
|
313
338
|
console.error(`ā ${err.message}`);
|
|
314
339
|
console.error(" Stop whatever holds that port, or change config.port, then retry.");
|
|
315
|
-
|
|
340
|
+
throw new StartCommandExit(1);
|
|
316
341
|
}
|
|
317
342
|
throw err;
|
|
318
343
|
}
|
|
@@ -418,8 +443,8 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
418
443
|
// to bake the service (observed: a probe on 10198 left the service pinned there).
|
|
419
444
|
siblingStart = true;
|
|
420
445
|
console.warn(
|
|
421
|
-
`Proxy already running on port ${owner.live.port};
|
|
422
|
-
+ `
|
|
446
|
+
`Proxy already running on port ${owner.live.port}; requested a second instance on port ${requestedPort}. `
|
|
447
|
+
+ `Startup continues only for an independent OPENCODEX_HOME; one state directory has one spend-ledger writer.`,
|
|
423
448
|
);
|
|
424
449
|
}
|
|
425
450
|
|
|
@@ -442,49 +467,99 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
442
467
|
// live daemon holding resources while it overwrites its own binary.
|
|
443
468
|
await maybeShowUpdatePrompt();
|
|
444
469
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
470
|
+
type StartServerModule = typeof import("../server");
|
|
471
|
+
type BoundStart = {
|
|
472
|
+
server: ReturnType<StartServerModule["startServer"]>;
|
|
473
|
+
serverModule: StartServerModule;
|
|
474
|
+
port: number;
|
|
475
|
+
readinessGate: ReturnType<typeof createReadinessGate>;
|
|
476
|
+
localAttestationSecret: string;
|
|
477
|
+
config: ReturnType<typeof loadConfig>;
|
|
478
|
+
};
|
|
479
|
+
let boundStart: BoundStart;
|
|
480
|
+
try {
|
|
481
|
+
boundStart = await bindAndPublishStartOwnership({
|
|
482
|
+
acquireLease: () => acquireOwnershipMutationLease(serviceStatePaths()),
|
|
483
|
+
bind: async () => {
|
|
484
|
+
// The earlier probe owned journal cleanup. This one owns the bind decision: an
|
|
485
|
+
// updater may have stopped the old runtime and acquired this lease for replacement.
|
|
486
|
+
const fencedLive = await findLiveProxy(START_OWNERSHIP_LIVENESS);
|
|
487
|
+
if (fencedLive) {
|
|
488
|
+
const decision = decideStartWithLiveOwner({
|
|
489
|
+
livePort: fencedLive.port,
|
|
490
|
+
requestedPort,
|
|
491
|
+
ocxService: process.env.OCX_SERVICE,
|
|
492
|
+
});
|
|
493
|
+
if (decision === "service-stay-out") {
|
|
494
|
+
console.log(`Proxy already running (PID ${fencedLive.pid ?? "unknown"}, port ${fencedLive.port}); service wrapper staying out of the way.`);
|
|
495
|
+
throw new StartCommandExit(0);
|
|
496
|
+
}
|
|
497
|
+
if (decision === "refuse") {
|
|
498
|
+
console.error(`ā ļø Proxy appeared before bind (PID ${fencedLive.pid ?? "unknown"}, port ${fencedLive.port}). Use 'ocx stop' first.`);
|
|
499
|
+
throw new StartCommandExit(1);
|
|
500
|
+
}
|
|
501
|
+
siblingStart = true;
|
|
474
502
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
503
|
+
|
|
504
|
+
// Port selection is check-then-bind. The lease prevents every cooperating start or
|
|
505
|
+
// updater from turning that check into a different ownership decision.
|
|
506
|
+
let port = await chooseListenPort(requestedPort, { sibling: siblingStart });
|
|
507
|
+
const serverModule = await import("../server");
|
|
508
|
+
const readinessGate = createReadinessGate();
|
|
509
|
+
const localAttestationSecret = createLocalAttestationSecret();
|
|
510
|
+
const config = loadConfig();
|
|
511
|
+
let server: ReturnType<typeof serverModule.startServer>;
|
|
512
|
+
for (let attempt = 0; ; attempt++) {
|
|
513
|
+
try {
|
|
514
|
+
server = serverModule.startServer(port, { localAttestationSecret, readinessGate });
|
|
515
|
+
break;
|
|
516
|
+
} catch (err) {
|
|
517
|
+
try { await serverModule.waitForFailedStartRollback(err); }
|
|
518
|
+
catch (rollbackError) {
|
|
519
|
+
throw new StartOwnershipRollbackUncertainError([err, rollbackError]);
|
|
520
|
+
}
|
|
521
|
+
if (err instanceof SpendLedgerOwnerError) {
|
|
522
|
+
console.error(`ā ${err.message}`);
|
|
523
|
+
throw new StartCommandExit(1);
|
|
524
|
+
}
|
|
525
|
+
if (err instanceof AuxiliaryListenerBindError || !isAddrInUse(err) || attempt >= 2) throw err;
|
|
526
|
+
if (requestedPort !== undefined) {
|
|
527
|
+
console.log(`ā ļø Port ${port} was taken while starting; waiting to retry the same port...`);
|
|
528
|
+
const hostname = config.hostname ?? "127.0.0.1";
|
|
529
|
+
const freed = await waitForPortAvailable(port, hostname, { timeoutMs: 3_000, intervalMs: 50 });
|
|
530
|
+
if (!freed) {
|
|
531
|
+
console.error(`ā Port ${port} stayed busy; refusing to hop to an ephemeral port.`);
|
|
532
|
+
throw new StartCommandExit(1);
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
console.log(`ā ļø Port ${port} was taken while starting; picking another...`);
|
|
537
|
+
port = await chooseListenPort(requestedPort, { sibling: siblingStart });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return { server, serverModule, port, readinessGate, localAttestationSecret, config };
|
|
541
|
+
},
|
|
542
|
+
writePid: () => writePid(process.pid),
|
|
543
|
+
writeRuntime: bound => writeRuntimePort({
|
|
544
|
+
pid: process.pid,
|
|
545
|
+
port: bound.port,
|
|
546
|
+
hostname: bound.config.hostname,
|
|
547
|
+
attestationSecret: bound.localAttestationSecret,
|
|
548
|
+
}),
|
|
549
|
+
stopBound: bound => bound.server.stop(true),
|
|
550
|
+
removeRuntime: () => removeRuntimePortIfPidIs(process.pid),
|
|
551
|
+
removePid: () => removePidIfValueIs(process.pid),
|
|
552
|
+
});
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error instanceof StartCommandExit) { process.exitCode = error.exitCode; return; }
|
|
555
|
+
throw error;
|
|
480
556
|
}
|
|
481
|
-
// A single request's streaming error must never crash the daemon serving every
|
|
482
|
-
// other Codex session ā capture the full stack to crash.log and stay up.
|
|
483
|
-
installCrashGuards();
|
|
484
|
-
writePid(process.pid);
|
|
485
557
|
|
|
486
|
-
const config =
|
|
487
|
-
|
|
558
|
+
const { server, serverModule, port, readinessGate, config } = boundStart;
|
|
559
|
+
const { drainAndShutdown, isRecyclingForExit } = serverModule;
|
|
560
|
+
// Records are visible now; background work may observe this runtime without a gap.
|
|
561
|
+
scheduleCatalogPrewarm();
|
|
562
|
+
installCrashGuards();
|
|
488
563
|
// No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
|
|
489
564
|
// is about to transform; snapshotting earlier only captured a baseline that could
|
|
490
565
|
// already be stale by the time injection ran (#477).
|
|
@@ -592,11 +667,11 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
592
667
|
try {
|
|
593
668
|
const { fetchAllModels } = await import("../server/management-api");
|
|
594
669
|
const { desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
595
|
-
const {
|
|
670
|
+
const { resolveAdmittedCodexModelEntitlements } = await import("../codex/model-entitlement-admission");
|
|
596
671
|
const { buildDesktopDiscoveryInputs } = await import("../claude/desktop-discovery-inputs");
|
|
597
672
|
const [models, modelEntitlements] = await Promise.all([
|
|
598
673
|
fetchAllModels(config),
|
|
599
|
-
|
|
674
|
+
resolveAdmittedCodexModelEntitlements(config, { clientVersion: null }),
|
|
600
675
|
]);
|
|
601
676
|
const inputs = buildDesktopDiscoveryInputs({
|
|
602
677
|
config, models, modelEntitlements,
|
|
@@ -916,6 +991,12 @@ async function restoreSharedClientStateAfterStop(): Promise<{ historyOnly: boole
|
|
|
916
991
|
}
|
|
917
992
|
|
|
918
993
|
async function handleStop() {
|
|
994
|
+
const lease = acquireOwnershipMutationLease(serviceStatePaths());
|
|
995
|
+
try { return await handleStopUnlocked(); }
|
|
996
|
+
finally { lease.release(); }
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function handleStopUnlocked() {
|
|
919
1000
|
// The receipt must name the endpoint the owner was stopping ā an obligation nobody can
|
|
920
1001
|
// locate cannot be proven discharged. Only the runtime record knows it; a proxy started
|
|
921
1002
|
// with an explicit --port is not on the configured one.
|
|
@@ -947,8 +1028,7 @@ async function handleStop() {
|
|
|
947
1028
|
// An obligation that cannot name its endpoint cannot be proven discharged.
|
|
948
1029
|
if (!endpoint) return false;
|
|
949
1030
|
try {
|
|
950
|
-
|
|
951
|
-
return probeProxyLiveness(endpoint.port, endpoint.hostname) === "dead";
|
|
1031
|
+
return await probeEndpointLiveness(endpoint) === "dead";
|
|
952
1032
|
} catch {
|
|
953
1033
|
// A probe that could not run is not evidence of absence.
|
|
954
1034
|
return false;
|
|
@@ -972,6 +1052,16 @@ async function handleStop() {
|
|
|
972
1052
|
// service ā the exact failure this flag prevents. A plain stop failure is different: we
|
|
973
1053
|
// tried, so local teardown still proceeds.
|
|
974
1054
|
let ownershipBlocked = false;
|
|
1055
|
+
// Structured twin of the human lines below, for `ocx stop --json`: one document the
|
|
1056
|
+
// desktop shell can read across the process boundary (D4). Every field is assigned
|
|
1057
|
+
// where the corresponding boolean already flips ā the summarizer never re-decides.
|
|
1058
|
+
const record: StopRunRecord = {
|
|
1059
|
+
service: "absent",
|
|
1060
|
+
proxy: "unknown",
|
|
1061
|
+
sharedTeardown: "skipped",
|
|
1062
|
+
inheritedTeardownBlocks: false,
|
|
1063
|
+
receiptClearFailed: false,
|
|
1064
|
+
};
|
|
975
1065
|
// Deferring shared teardown to this process is an obligation, so record it on disk
|
|
976
1066
|
// before asking for it (#3008). A parent that dies mid-stop would otherwise leave the
|
|
977
1067
|
// client config routed at a proxy that is already gone, with nothing to find later.
|
|
@@ -1049,6 +1139,7 @@ async function handleStop() {
|
|
|
1049
1139
|
};
|
|
1050
1140
|
try {
|
|
1051
1141
|
const serviceStop = stopServiceIfInstalledDetailed();
|
|
1142
|
+
record.service = serviceStop;
|
|
1052
1143
|
stoppedService = serviceStop === "stopped" || serviceStop === "stopped-respawnable";
|
|
1053
1144
|
schedulerCanRespawn = serviceStop === "stopped-respawnable";
|
|
1054
1145
|
// No "won't respawn" claim here: a stopped Task Scheduler can still respawn through
|
|
@@ -1069,6 +1160,7 @@ async function handleStop() {
|
|
|
1069
1160
|
console.error(" Run 'ocx service status' to see the query error, repair Task Scheduler access, then retry.");
|
|
1070
1161
|
}
|
|
1071
1162
|
} catch (err) {
|
|
1163
|
+
record.service = "error";
|
|
1072
1164
|
if (isServiceOwnershipError(err)) {
|
|
1073
1165
|
ownershipBlocked = true;
|
|
1074
1166
|
stopFailed = true;
|
|
@@ -1092,8 +1184,10 @@ async function handleStop() {
|
|
|
1092
1184
|
console.log(`ā
Proxy (PID ${pid}) stopped.`);
|
|
1093
1185
|
removePid(pid);
|
|
1094
1186
|
removeRuntimePort(pid);
|
|
1187
|
+
record.proxy = "stopped";
|
|
1095
1188
|
} catch (err) {
|
|
1096
1189
|
stopFailed = true;
|
|
1190
|
+
record.proxy = err instanceof ProxyOwnershipRefusedError ? "ownership-refused" : "stop-failed";
|
|
1097
1191
|
console.error(`ā Failed to stop proxy (PID ${pid}).`);
|
|
1098
1192
|
// stopProxy throws with the reason ā an ownership refusal (409) carries the
|
|
1099
1193
|
// remediation ("run the stop from that home"). Swallowing it leaves the operator
|
|
@@ -1128,8 +1222,10 @@ async function handleStop() {
|
|
|
1128
1222
|
{ hostname: live.hostname ?? "127.0.0.1", port: live.port },
|
|
1129
1223
|
);
|
|
1130
1224
|
console.log(`ā
Proxy (PID ${live.pid}) stopped.`);
|
|
1225
|
+
record.proxy = "stopped-orphan";
|
|
1131
1226
|
} catch (err) {
|
|
1132
1227
|
stopFailed = true;
|
|
1228
|
+
record.proxy = err instanceof ProxyOwnershipRefusedError ? "ownership-refused" : "stop-failed";
|
|
1133
1229
|
console.error(`ā Failed to stop proxy (PID ${live.pid}).`);
|
|
1134
1230
|
const detail = err instanceof Error ? err.message : String(err);
|
|
1135
1231
|
if (detail) console.error(` ${detail}`);
|
|
@@ -1147,12 +1243,14 @@ async function handleStop() {
|
|
|
1147
1243
|
// under a proxy that is still serving ā the exact failure the deferral exists to
|
|
1148
1244
|
// prevent, arrived at from the other direction.
|
|
1149
1245
|
stopFailed = true;
|
|
1246
|
+
record.proxy = "unresolvable-pid";
|
|
1150
1247
|
ownershipBlocked = true;
|
|
1151
1248
|
console.error(`ā A proxy is answering on port ${live.port}, but no process id could be resolved for it, so it cannot be stopped from here.`);
|
|
1152
1249
|
console.error(" Skipping shared teardown: restoring client config while it serves would leave both pointing at each other.");
|
|
1153
1250
|
console.error(" Stop it from the home that started it, or end the process manually, then rerun 'ocx stop'.");
|
|
1154
|
-
} else
|
|
1155
|
-
|
|
1251
|
+
} else {
|
|
1252
|
+
record.proxy = "not-running";
|
|
1253
|
+
if (!stoppedService) console.log("No running proxy found.");
|
|
1156
1254
|
}
|
|
1157
1255
|
if (!stopFailed) {
|
|
1158
1256
|
// `readPid() === null` means the snapshotted pid file was absent, invalid, dead, or
|
|
@@ -1175,6 +1273,7 @@ async function handleStop() {
|
|
|
1175
1273
|
const survivor = await proxyStillLiveAfterStop({ canRespawn: true });
|
|
1176
1274
|
if (survivor) {
|
|
1177
1275
|
stopFailed = true;
|
|
1276
|
+
record.proxy = "respawned";
|
|
1178
1277
|
console.error(`ā A proxy is still listening on port ${survivor.port} after the service stop; it is being respawned.`);
|
|
1179
1278
|
console.error(" Skipping shared teardown: restoring client config while the proxy runs leaves both pointing at each other.");
|
|
1180
1279
|
ownershipBlocked = true;
|
|
@@ -1210,6 +1309,7 @@ async function handleStop() {
|
|
|
1210
1309
|
// No file, no nonce: nothing to quarantine and nothing to remove. The home itself
|
|
1211
1310
|
// may be hiding an obligation, so block and ask for the directory to be fixed.
|
|
1212
1311
|
inheritedBlocks = true;
|
|
1312
|
+
record.inheritedTeardownBlocks = true;
|
|
1213
1313
|
stopFailed = true;
|
|
1214
1314
|
console.error(`ā ${read.detail}, so this stop cannot tell whether a shared teardown is still owed.`);
|
|
1215
1315
|
console.error(" Skipping shared teardown. Fix access to the opencodex home, then rerun 'ocx stop'.");
|
|
@@ -1218,6 +1318,7 @@ async function handleStop() {
|
|
|
1218
1318
|
if (read.state === "invalid") {
|
|
1219
1319
|
unreadable.push(read);
|
|
1220
1320
|
inheritedBlocks = true;
|
|
1321
|
+
record.inheritedTeardownBlocks = true;
|
|
1221
1322
|
stopFailed = true;
|
|
1222
1323
|
console.error(`ā A pending-teardown receipt could not be read (${read.detail}).`);
|
|
1223
1324
|
console.error(" It names no endpoint, so this stop cannot prove the proxy it belonged to is down.");
|
|
@@ -1229,6 +1330,7 @@ async function handleStop() {
|
|
|
1229
1330
|
// proxy on an explicit --port can be respawned there while this address refuses,
|
|
1230
1331
|
// so "dead" here proves nothing and must not authorize a restore.
|
|
1231
1332
|
inheritedBlocks = true;
|
|
1333
|
+
record.inheritedTeardownBlocks = true;
|
|
1232
1334
|
stopFailed = true;
|
|
1233
1335
|
console.error("ā A shared teardown from an earlier stop is outstanding, but that stop could not record the address it was stopping.");
|
|
1234
1336
|
console.error(` Only the configured address (${read.receipt.endpoint.hostname}:${read.receipt.endpoint.port}) was recorded, which cannot prove the right proxy is down.`);
|
|
@@ -1240,12 +1342,14 @@ async function handleStop() {
|
|
|
1240
1342
|
continue;
|
|
1241
1343
|
}
|
|
1242
1344
|
inheritedBlocks = true;
|
|
1345
|
+
record.inheritedTeardownBlocks = true;
|
|
1243
1346
|
stopFailed = true;
|
|
1244
1347
|
console.error(`ā A shared teardown from an earlier stop is still outstanding, and the proxy on ${read.receipt.endpoint.hostname}:${read.receipt.endpoint.port} could not be confirmed down.`);
|
|
1245
1348
|
console.error(" Skipping shared teardown: restoring client config under a proxy that may still be running is what the deferral exists to prevent.");
|
|
1246
1349
|
console.error(" The obligation is preserved; retry once the proxy is confirmed stopped.");
|
|
1247
1350
|
}
|
|
1248
1351
|
}
|
|
1352
|
+
if (nativeRestoreHandledByProxy) record.sharedTeardown = "performed-by-proxy";
|
|
1249
1353
|
const restoreBlocked = ownershipBlocked || inheritedBlocks || nativeRestoreHandledByProxy;
|
|
1250
1354
|
if (!restoreBlocked) {
|
|
1251
1355
|
if (recoveredNonces.length > 0) {
|
|
@@ -1254,6 +1358,7 @@ async function handleStop() {
|
|
|
1254
1358
|
console.log("ā©ļø Finishing a shared teardown left unfinished by an earlier stop.");
|
|
1255
1359
|
}
|
|
1256
1360
|
const restore = await restoreSharedClientStateAfterStop();
|
|
1361
|
+
record.sharedTeardown = restore.historyDeferred ? "refused" : restore.other ? "failed" : "restored";
|
|
1257
1362
|
if (restore.other) stopFailed = true;
|
|
1258
1363
|
else if (restore.historyDeferred) historyDeferredNonces = teardownNonce ? [teardownNonce, ...recoveredNonces] : recoveredNonces;
|
|
1259
1364
|
else if (restore.historyOnly) historyOnlyFailure = true;
|
|
@@ -1279,6 +1384,7 @@ async function handleStop() {
|
|
|
1279
1384
|
// removal is surfaced rather than swallowed.
|
|
1280
1385
|
if (!clearPendingTeardown(nonce)) {
|
|
1281
1386
|
stopFailed = true;
|
|
1387
|
+
record.receiptClearFailed = true;
|
|
1282
1388
|
console.error(`ā The shared teardown finished, but its receipt could not be removed: ${pendingTeardownPathFor(nonce)}`);
|
|
1283
1389
|
console.error(" Remove it manually; otherwise every later stop and update will try to recover it again.");
|
|
1284
1390
|
}
|
|
@@ -1323,18 +1429,23 @@ async function handleStop() {
|
|
|
1323
1429
|
? STOP_HISTORY_DEFERRED_EXIT_CODE
|
|
1324
1430
|
: 1;
|
|
1325
1431
|
}
|
|
1326
|
-
|
|
1432
|
+
const summary = summarizeStopRun(record, {
|
|
1433
|
+
failed: stopFailed,
|
|
1434
|
+
historyOnly: historyOnlyFailure,
|
|
1435
|
+
historyDeferred: historyDeferredNonces !== null,
|
|
1436
|
+
exitCode: Number(process.exitCode ?? 0),
|
|
1437
|
+
});
|
|
1438
|
+
return { ok: !stopFailed, summary };
|
|
1327
1439
|
}
|
|
1328
1440
|
|
|
1329
1441
|
async function handleUninstall() {
|
|
1330
1442
|
/** Definitive "nothing is answering" on the endpoint this home would serve. */
|
|
1331
1443
|
const proxyEndpointProvenDown = async (): Promise<boolean> => {
|
|
1332
1444
|
try {
|
|
1333
|
-
const { probeProxyLiveness } = await import("../update/proxy-liveness-probe.mjs");
|
|
1334
1445
|
// Every candidate, not just the preferred one: a stale runtime record pointing at a
|
|
1335
1446
|
// closed port would otherwise "prove" a live proxy on the configured port is gone.
|
|
1336
1447
|
const endpoints = endpointsToProve(readRuntimePort(), loadConfig());
|
|
1337
|
-
return
|
|
1448
|
+
return await everyEndpointProvenDownAsync(endpoints, probeEndpointLiveness);
|
|
1338
1449
|
} catch {
|
|
1339
1450
|
return false;
|
|
1340
1451
|
}
|
|
@@ -1581,8 +1692,26 @@ async function handleStatus() {
|
|
|
1581
1692
|
console.log(installed
|
|
1582
1693
|
? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
|
|
1583
1694
|
: " Restart with 'ocx start', or install the persistent service: 'ocx service install'.");
|
|
1695
|
+
// Restarting is only half the choice. A user who cannot sign in to Codex at all needs the
|
|
1696
|
+
// way out that does not require this proxy to come back (#5261).
|
|
1697
|
+
for (const line of deadProxyRoutingAdviceLines({
|
|
1698
|
+
proxyUp: false,
|
|
1699
|
+
routingKind: status.json.startup.routingKind,
|
|
1700
|
+
})) {
|
|
1701
|
+
console.log(` ${line}`);
|
|
1702
|
+
}
|
|
1584
1703
|
}
|
|
1585
1704
|
console.log(` Dashboard: ${status.json.dashboard.url}${local}`);
|
|
1705
|
+
// The dashboard is a build artifact, so a checkout that moved without `bun run build:gui` keeps
|
|
1706
|
+
// serving the previous bundle and every feature added since simply does not appear (#5196's
|
|
1707
|
+
// usage panel was invisible this way for five days). Reported next to the dashboard URL, which
|
|
1708
|
+
// is where someone looks when the page is wrong.
|
|
1709
|
+
for (const line of staleGuiBundleLines(inspectGuiBundleFreshness({
|
|
1710
|
+
bundlePath: findGuiDist(),
|
|
1711
|
+
sourcePath: join(import.meta.dir, "..", "..", "gui", "src"),
|
|
1712
|
+
}))) {
|
|
1713
|
+
console.log(` ${line}`);
|
|
1714
|
+
}
|
|
1586
1715
|
console.log(` Config: ${status.json.paths.config}${local}`);
|
|
1587
1716
|
console.log(` PID file: ${status.json.paths.pid}${local}`);
|
|
1588
1717
|
console.log(` Runtime: ${status.json.paths.runtime}${local}`);
|
|
@@ -1601,6 +1730,12 @@ async function handleStatus() {
|
|
|
1601
1730
|
console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}${local}`);
|
|
1602
1731
|
console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}${local}`);
|
|
1603
1732
|
console.log(` ${formatStartupRoutingDetail(status.json.startup)}${local}`);
|
|
1733
|
+
// Independent of whether the proxy is up: a catalog pointer whose file is gone stops Codex
|
|
1734
|
+
// loading its config at all, and presents as the same blank wall as dead routing (#5261).
|
|
1735
|
+
// Tagged `(local)` on its header like every other local-state line, so a connected client
|
|
1736
|
+
// cannot read a finding about its own Codex home as something the hub reported.
|
|
1737
|
+
missingCodexCatalogLines(detectMissingCodexCatalogPath())
|
|
1738
|
+
.forEach((line, index) => console.log(` ${line}${index === 0 ? local : ""}`));
|
|
1604
1739
|
if (status.json.startup.routingKind === "native") {
|
|
1605
1740
|
let retainedProviderTable = false;
|
|
1606
1741
|
try {
|
|
@@ -1729,6 +1864,14 @@ async function handleReady(args: ReadyArgs): Promise<number> {
|
|
|
1729
1864
|
return runReady(args);
|
|
1730
1865
|
}
|
|
1731
1866
|
|
|
1867
|
+
/**
|
|
1868
|
+
* `ocx resolve` ā argument parsing already happened in src/cli/root.ts (before any
|
|
1869
|
+
* preflight side effect), so this handler only runs the runner and returns its exit code.
|
|
1870
|
+
*/
|
|
1871
|
+
async function handleResolve(args: ResolveArgs): Promise<number> {
|
|
1872
|
+
return runResolve(args);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1732
1875
|
process.exit(await dispatchCommand(head, {
|
|
1733
1876
|
args,
|
|
1734
1877
|
command,
|
|
@@ -1749,6 +1892,7 @@ process.exit(await dispatchCommand(head, {
|
|
|
1749
1892
|
},
|
|
1750
1893
|
handleStart,
|
|
1751
1894
|
handleStop,
|
|
1895
|
+
handleResolve,
|
|
1752
1896
|
handleEnsure,
|
|
1753
1897
|
handleTrayProxyStart,
|
|
1754
1898
|
handleTrayProxyRestart,
|
package/src/cli/init.ts
CHANGED
|
@@ -253,6 +253,14 @@ export async function runInit(): Promise<void> {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
console.log(`\nš Setup complete! Run 'ocx start' to start the proxy.`);
|
|
256
|
+
// Said after the autostart choice, because the choice is what decides whether it applies.
|
|
257
|
+
// Setup otherwise ends on a success line while leaving a restart dependency unmentioned.
|
|
258
|
+
try {
|
|
259
|
+
const { collectStartupHealth, injectedRoutingRestartWarningLines } = await import("../codex/autostart-health");
|
|
260
|
+
for (const line of injectedRoutingRestartWarningLines(collectStartupHealth(config))) console.log(line);
|
|
261
|
+
} catch {
|
|
262
|
+
// A diagnostic that cannot be computed must not fail a completed setup.
|
|
263
|
+
}
|
|
256
264
|
for (const line of modelSelectionGuidance(providerName)) console.log(line);
|
|
257
265
|
} catch (error) {
|
|
258
266
|
if (error instanceof InitCancelledError) {
|
package/src/cli/integrations.ts
CHANGED
|
@@ -229,7 +229,13 @@ export async function handleClientIntegrationCommand(
|
|
|
229
229
|
? profiles.map(row => `${String(row.profileId)} ${String(row.name ?? "Aside")}: ${row.enabled ? "on" : "off"} (${String(row.state)})${row.current ? " [current]" : ""}`)
|
|
230
230
|
: [String((result as { error?: string }).error ?? "No Aside profiles found.")]
|
|
231
231
|
: rows
|
|
232
|
-
|
|
232
|
+
/*
|
|
233
|
+
* `supersededBy` is named here and not only in the single-client view
|
|
234
|
+
* because this list is where a user looks to see that everything is
|
|
235
|
+
* connected, and "current" alone is exactly the reassurance that hid a
|
|
236
|
+
* client reading a file opencodex does not write.
|
|
237
|
+
*/
|
|
238
|
+
? rows.map(row => `${String(row.clientId)}: ${String(row.state)}${row.installed ? "" : " (not installed)"}${row.supersededBy ? " (client reads another file)" : ""}`)
|
|
233
239
|
: singleClientStatusLines(result));
|
|
234
240
|
return;
|
|
235
241
|
}
|
package/src/cli/opencode.ts
CHANGED
|
@@ -41,7 +41,7 @@ import type {
|
|
|
41
41
|
} from "../clients/config-export";
|
|
42
42
|
import { filterCatalogVisibleModels, visibleNativeSlugs } from "../codex/catalog";
|
|
43
43
|
import { commandInvocation } from "../lib/win-exec";
|
|
44
|
-
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
44
|
+
import { configuredAdminToken, opencodeCatalogToken } from "../lib/admin-secrets";
|
|
45
45
|
import { localManagementOrigin } from "../lib/local-destinations";
|
|
46
46
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
47
47
|
import { loadServiceTokenFromFile, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
@@ -684,7 +684,7 @@ export async function cmdOpencode(args: string[]): Promise<number> {
|
|
|
684
684
|
try {
|
|
685
685
|
const managementToken = configuredAdminToken();
|
|
686
686
|
if (!managementToken) throw new Error("No local admin token is available; check the running proxy's home.");
|
|
687
|
-
proxyModels = await fetchOpencodeProxyModels(live, managementToken, {
|
|
687
|
+
proxyModels = await fetchOpencodeProxyModels(live, opencodeCatalogToken(managementToken), {
|
|
688
688
|
managementOrigin: localManagementOrigin({ ...startupConfig, hostname: live.hostname }, live.port),
|
|
689
689
|
});
|
|
690
690
|
} catch (error) {
|
package/src/cli/provider.ts
CHANGED
|
@@ -139,7 +139,7 @@ function handleList(args: string[]): void {
|
|
|
139
139
|
// provider add
|
|
140
140
|
// ---------------------------------------------------------------------------
|
|
141
141
|
|
|
142
|
-
const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--api-key-transport <x-api-key|bearer>] [--default-model <model>] [--model <id> --text-only] [--allow-private-network] [--set-default] [--force] [--json] [--sync]";
|
|
142
|
+
const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--api-key-transport <x-api-key|bearer>] [--default-model <model>] [--model <id> --text-only] [--google-tool-schema-policy <compatible|reject-lossy>] [--allow-private-network] [--set-default] [--force] [--json] [--sync]";
|
|
143
143
|
|
|
144
144
|
async function handleAdd(args: string[]): Promise<void> {
|
|
145
145
|
const name = args[0];
|
|
@@ -164,6 +164,7 @@ async function handleAdd(args: string[]): Promise<void> {
|
|
|
164
164
|
const adapter = consumeFlagValue(restArgs, "--adapter");
|
|
165
165
|
const baseUrl = consumeFlagValue(restArgs, "--base-url");
|
|
166
166
|
const defaultModel = consumeFlagValue(restArgs, "--default-model");
|
|
167
|
+
const googleToolSchemaPolicy = consumeFlagValue(restArgs, "--google-tool-schema-policy");
|
|
167
168
|
const textOnly = consumeFlag(restArgs, "--text-only");
|
|
168
169
|
const capabilityModel = consumeFlagValue(restArgs, "--model");
|
|
169
170
|
rejectUnknownArgs(restArgs, ADD_USAGE);
|
|
@@ -228,6 +229,17 @@ async function handleAdd(args: string[]): Promise<void> {
|
|
|
228
229
|
}
|
|
229
230
|
provConfig.apiKeyTransport = apiKeyTransport;
|
|
230
231
|
}
|
|
232
|
+
if (googleToolSchemaPolicy !== undefined) {
|
|
233
|
+
if (googleToolSchemaPolicy !== "compatible" && googleToolSchemaPolicy !== "reject-lossy") {
|
|
234
|
+
console.error('Error: --google-tool-schema-policy must be "compatible" or "reject-lossy".');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
if (provConfig.adapter !== "google") {
|
|
238
|
+
console.error("Error: --google-tool-schema-policy requires the google adapter.");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
provConfig.googleToolSchemaPolicy = googleToolSchemaPolicy;
|
|
242
|
+
}
|
|
231
243
|
|
|
232
244
|
const existingProvider = config.providers[name];
|
|
233
245
|
if (existingProvider?.modelCapabilities !== undefined && provConfig.modelCapabilities === undefined) {
|