@bitkyc08/opencodex 2.60.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 +5 -1
- package/src/adapters/anthropic.ts +16 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +10 -2
- package/src/adapters/command-code.ts +2 -1
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +13 -3
- package/src/adapters/cursor.ts +11 -2
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin.ts +75 -23
- package/src/adapters/google-antigravity-wire.ts +5 -2
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google.ts +29 -5
- 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.ts +18 -57
- package/src/adapters/openai-responses/passthrough.ts +2 -0
- 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 +10 -0
- 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/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 +43 -4
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/index.ts +200 -61
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -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-listener.ts +2 -5
- 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/app-server-processes.ts +72 -40
- package/src/codex/auth-api/login-flow.ts +6 -1
- 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/provider-models.ts +24 -15
- package/src/codex/catalog/retained-sync.ts +2 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/history-provider.ts +12 -1
- 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/main-account.ts +6 -0
- package/src/codex/model-cache.ts +52 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +87 -44
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +5 -41
- package/src/codex/shim-templates.ts +29 -3
- package/src/companion/settings.ts +132 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/load-degrade.ts +34 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/schema/config-schema.ts +27 -1
- package/src/config/schema/leaf-validators.ts +47 -0
- package/src/config.ts +1 -1
- package/src/generated/compatibility-version.json +418 -174
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +124 -18
- 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 +49 -11
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug.ts +40 -0
- 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/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +59 -14
- 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/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/index.ts +24 -1
- package/src/oauth/login-cli.ts +80 -29
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/key-failover.ts +31 -1
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +124 -37
- package/src/providers/quota/vendor-probes-key.ts +37 -22
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +9 -4
- package/src/providers/registry/entries-extended.ts +29 -4
- package/src/providers/registry/model-seeds.ts +47 -10
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -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/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/schema.ts +3 -0
- package/src/router.ts +17 -2
- 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 +26 -0
- package/src/server/chat-completions.ts +55 -2
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +55 -17
- 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 +56 -10
- package/src/server/index/spend-ledger-lifecycle.ts +34 -8
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index.ts +21 -28
- 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/companion-routes.ts +77 -0
- package/src/server/management/logs-usage-routes.ts +19 -0
- 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 +6 -0
- package/src/server/management/shared.ts +18 -1
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +8 -9
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/relay.ts +19 -2
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +114 -0
- package/src/server/request-metrics.ts +92 -30
- 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 +11 -0
- package/src/server/responses/core-opaque-recovery.ts +90 -0
- 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 +14 -1
- package/src/server/responses/passthrough-dispatch.ts +179 -35
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/request-prepare.ts +42 -1
- package/src/server/responses/request-send-budget.ts +12 -0
- package/src/server/responses/request-transport.ts +24 -4
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +4 -1
- package/src/server/search.ts +25 -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 +1 -1
- package/src/types/config.ts +37 -0
- package/src/types/provider.ts +73 -0
- package/src/types/request.ts +28 -2
- package/src/types/tools.ts +19 -0
- package/src/types.ts +3 -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/web-search/alpha-search.ts +21 -1
- package/gui/dist/assets/index-BTuCbqQd.css +0 -1
- package/gui/dist/assets/index-DoBVdPHP.js +0 -134
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
|
|
|
@@ -82,14 +85,24 @@ import { SpendLedgerOwnerError } from "../lib/spend-ledger-owner";
|
|
|
82
85
|
import { redactUrlForLog } from "../lib/redact";
|
|
83
86
|
import { dispatchCommand, decideBusyPreferredPort, decideStartWithLiveOwner } from "./dispatch";
|
|
84
87
|
import { AuxiliaryListenerBindError, findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
|
|
85
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
findLiveProxy,
|
|
90
|
+
probeEndpointLiveness,
|
|
91
|
+
probeHostname,
|
|
92
|
+
probePortOwner,
|
|
93
|
+
START_OWNERSHIP_LIVENESS,
|
|
94
|
+
type LiveProxy,
|
|
95
|
+
} from "../server/proxy-liveness";
|
|
86
96
|
import { createReadinessGate } from "../server/readiness";
|
|
87
97
|
import { isApiAuthRequired } from "../server/auth-cors";
|
|
88
98
|
import { runReady, type ReadyArgs } from "./ready";
|
|
99
|
+
import { runResolve, type ResolveArgs } from "./resolve";
|
|
100
|
+
import { summarizeStopRun, type StopOutcome, type StopRunRecord } from "./stop-report";
|
|
89
101
|
import { runCli } from "./root";
|
|
90
102
|
import { isProcessAlive, ProxyOwnershipRefusedError, refusalNextStep, stopProxy } from "../lib/process-control";
|
|
91
103
|
import { startupDataPlaneToken } from "../lib/service-secrets";
|
|
92
|
-
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";
|
|
93
106
|
import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
|
|
94
107
|
import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env";
|
|
95
108
|
import { buildDesktop3pRegistry } from "../claude/desktop-3p";
|
|
@@ -98,6 +111,10 @@ import { startHistoryMigrationGuardian } from "../codex/history-migration-guardi
|
|
|
98
111
|
import { maybeShowStarPrompt } from "./star-prompt";
|
|
99
112
|
import { scheduleCatalogPrewarm } from "./catalog-prewarm";
|
|
100
113
|
import { maybeShowUpdatePrompt } from "../update/notify";
|
|
114
|
+
import {
|
|
115
|
+
bindAndPublishStartOwnership,
|
|
116
|
+
StartOwnershipRollbackUncertainError,
|
|
117
|
+
} from "./start-ownership-publication";
|
|
101
118
|
import { syncModelsToCodex } from "../codex/sync";
|
|
102
119
|
import {
|
|
103
120
|
HUB_GATED_SKIP_MESSAGE,
|
|
@@ -216,6 +233,13 @@ function startArgv(port?: number): string[] {
|
|
|
216
233
|
return selfLaunchArgv(args);
|
|
217
234
|
}
|
|
218
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
|
+
|
|
219
243
|
async function chooseListenPort(
|
|
220
244
|
requestedPort?: number,
|
|
221
245
|
options: { sibling?: boolean } = {},
|
|
@@ -288,17 +312,17 @@ async function chooseListenPort(
|
|
|
288
312
|
// Same contract as the pre-bind owner check: the wrapper's retry loop terminates
|
|
289
313
|
// on a zero exit, and the port it was asked to serve is already served.
|
|
290
314
|
console.log(`Proxy already running (PID ${holder?.pid ?? "unknown"}, port ${preferred}); service wrapper staying out of the way.`);
|
|
291
|
-
|
|
315
|
+
throw new StartCommandExit(0);
|
|
292
316
|
}
|
|
293
317
|
if (decision === "refuse-live-proxy") {
|
|
294
318
|
console.error(`ā ļø Proxy already running (PID ${holder?.pid ?? "unknown"}, port ${preferred}). Use 'ocx stop' first.`);
|
|
295
|
-
|
|
319
|
+
throw new StartCommandExit(1);
|
|
296
320
|
}
|
|
297
321
|
if (decision === "refuse-unidentified-holder") {
|
|
298
322
|
console.error(`ā Port ${preferred} is busy and its holder did not identify as opencodex.`);
|
|
299
323
|
console.error(" Starting on another port would leave Codex pointed at a proxy you did not ask for.");
|
|
300
324
|
console.error(" Stop whatever holds that port, or start on a free one with 'ocx start --port <port>'.");
|
|
301
|
-
|
|
325
|
+
throw new StartCommandExit(1);
|
|
302
326
|
}
|
|
303
327
|
if (preferred > 0) {
|
|
304
328
|
console.log(`ā ļø Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
@@ -313,7 +337,7 @@ async function chooseListenPort(
|
|
|
313
337
|
if (err instanceof PortUnavailableError) {
|
|
314
338
|
console.error(`ā ${err.message}`);
|
|
315
339
|
console.error(" Stop whatever holds that port, or change config.port, then retry.");
|
|
316
|
-
|
|
340
|
+
throw new StartCommandExit(1);
|
|
317
341
|
}
|
|
318
342
|
throw err;
|
|
319
343
|
}
|
|
@@ -443,53 +467,99 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
443
467
|
// live daemon holding resources while it overwrites its own binary.
|
|
444
468
|
await maybeShowUpdatePrompt();
|
|
445
469
|
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
process.exit(1);
|
|
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;
|
|
479
502
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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;
|
|
485
556
|
}
|
|
486
|
-
// A single request's streaming error must never crash the daemon serving every
|
|
487
|
-
// other Codex session ā capture the full stack to crash.log and stay up.
|
|
488
|
-
installCrashGuards();
|
|
489
|
-
writePid(process.pid);
|
|
490
557
|
|
|
491
|
-
const config =
|
|
492
|
-
|
|
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();
|
|
493
563
|
// No pre-emptive snapshot here. `injectCodexConfig` journals the exact bytes it
|
|
494
564
|
// is about to transform; snapshotting earlier only captured a baseline that could
|
|
495
565
|
// already be stale by the time injection ran (#477).
|
|
@@ -597,11 +667,11 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
597
667
|
try {
|
|
598
668
|
const { fetchAllModels } = await import("../server/management-api");
|
|
599
669
|
const { desktopVisibleNativeSlugs } = await import("../codex/catalog");
|
|
600
|
-
const {
|
|
670
|
+
const { resolveAdmittedCodexModelEntitlements } = await import("../codex/model-entitlement-admission");
|
|
601
671
|
const { buildDesktopDiscoveryInputs } = await import("../claude/desktop-discovery-inputs");
|
|
602
672
|
const [models, modelEntitlements] = await Promise.all([
|
|
603
673
|
fetchAllModels(config),
|
|
604
|
-
|
|
674
|
+
resolveAdmittedCodexModelEntitlements(config, { clientVersion: null }),
|
|
605
675
|
]);
|
|
606
676
|
const inputs = buildDesktopDiscoveryInputs({
|
|
607
677
|
config, models, modelEntitlements,
|
|
@@ -921,6 +991,12 @@ async function restoreSharedClientStateAfterStop(): Promise<{ historyOnly: boole
|
|
|
921
991
|
}
|
|
922
992
|
|
|
923
993
|
async function handleStop() {
|
|
994
|
+
const lease = acquireOwnershipMutationLease(serviceStatePaths());
|
|
995
|
+
try { return await handleStopUnlocked(); }
|
|
996
|
+
finally { lease.release(); }
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function handleStopUnlocked() {
|
|
924
1000
|
// The receipt must name the endpoint the owner was stopping ā an obligation nobody can
|
|
925
1001
|
// locate cannot be proven discharged. Only the runtime record knows it; a proxy started
|
|
926
1002
|
// with an explicit --port is not on the configured one.
|
|
@@ -952,8 +1028,7 @@ async function handleStop() {
|
|
|
952
1028
|
// An obligation that cannot name its endpoint cannot be proven discharged.
|
|
953
1029
|
if (!endpoint) return false;
|
|
954
1030
|
try {
|
|
955
|
-
|
|
956
|
-
return probeProxyLiveness(endpoint.port, endpoint.hostname) === "dead";
|
|
1031
|
+
return await probeEndpointLiveness(endpoint) === "dead";
|
|
957
1032
|
} catch {
|
|
958
1033
|
// A probe that could not run is not evidence of absence.
|
|
959
1034
|
return false;
|
|
@@ -977,6 +1052,16 @@ async function handleStop() {
|
|
|
977
1052
|
// service ā the exact failure this flag prevents. A plain stop failure is different: we
|
|
978
1053
|
// tried, so local teardown still proceeds.
|
|
979
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
|
+
};
|
|
980
1065
|
// Deferring shared teardown to this process is an obligation, so record it on disk
|
|
981
1066
|
// before asking for it (#3008). A parent that dies mid-stop would otherwise leave the
|
|
982
1067
|
// client config routed at a proxy that is already gone, with nothing to find later.
|
|
@@ -1054,6 +1139,7 @@ async function handleStop() {
|
|
|
1054
1139
|
};
|
|
1055
1140
|
try {
|
|
1056
1141
|
const serviceStop = stopServiceIfInstalledDetailed();
|
|
1142
|
+
record.service = serviceStop;
|
|
1057
1143
|
stoppedService = serviceStop === "stopped" || serviceStop === "stopped-respawnable";
|
|
1058
1144
|
schedulerCanRespawn = serviceStop === "stopped-respawnable";
|
|
1059
1145
|
// No "won't respawn" claim here: a stopped Task Scheduler can still respawn through
|
|
@@ -1074,6 +1160,7 @@ async function handleStop() {
|
|
|
1074
1160
|
console.error(" Run 'ocx service status' to see the query error, repair Task Scheduler access, then retry.");
|
|
1075
1161
|
}
|
|
1076
1162
|
} catch (err) {
|
|
1163
|
+
record.service = "error";
|
|
1077
1164
|
if (isServiceOwnershipError(err)) {
|
|
1078
1165
|
ownershipBlocked = true;
|
|
1079
1166
|
stopFailed = true;
|
|
@@ -1097,8 +1184,10 @@ async function handleStop() {
|
|
|
1097
1184
|
console.log(`ā
Proxy (PID ${pid}) stopped.`);
|
|
1098
1185
|
removePid(pid);
|
|
1099
1186
|
removeRuntimePort(pid);
|
|
1187
|
+
record.proxy = "stopped";
|
|
1100
1188
|
} catch (err) {
|
|
1101
1189
|
stopFailed = true;
|
|
1190
|
+
record.proxy = err instanceof ProxyOwnershipRefusedError ? "ownership-refused" : "stop-failed";
|
|
1102
1191
|
console.error(`ā Failed to stop proxy (PID ${pid}).`);
|
|
1103
1192
|
// stopProxy throws with the reason ā an ownership refusal (409) carries the
|
|
1104
1193
|
// remediation ("run the stop from that home"). Swallowing it leaves the operator
|
|
@@ -1133,8 +1222,10 @@ async function handleStop() {
|
|
|
1133
1222
|
{ hostname: live.hostname ?? "127.0.0.1", port: live.port },
|
|
1134
1223
|
);
|
|
1135
1224
|
console.log(`ā
Proxy (PID ${live.pid}) stopped.`);
|
|
1225
|
+
record.proxy = "stopped-orphan";
|
|
1136
1226
|
} catch (err) {
|
|
1137
1227
|
stopFailed = true;
|
|
1228
|
+
record.proxy = err instanceof ProxyOwnershipRefusedError ? "ownership-refused" : "stop-failed";
|
|
1138
1229
|
console.error(`ā Failed to stop proxy (PID ${live.pid}).`);
|
|
1139
1230
|
const detail = err instanceof Error ? err.message : String(err);
|
|
1140
1231
|
if (detail) console.error(` ${detail}`);
|
|
@@ -1152,12 +1243,14 @@ async function handleStop() {
|
|
|
1152
1243
|
// under a proxy that is still serving ā the exact failure the deferral exists to
|
|
1153
1244
|
// prevent, arrived at from the other direction.
|
|
1154
1245
|
stopFailed = true;
|
|
1246
|
+
record.proxy = "unresolvable-pid";
|
|
1155
1247
|
ownershipBlocked = true;
|
|
1156
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.`);
|
|
1157
1249
|
console.error(" Skipping shared teardown: restoring client config while it serves would leave both pointing at each other.");
|
|
1158
1250
|
console.error(" Stop it from the home that started it, or end the process manually, then rerun 'ocx stop'.");
|
|
1159
|
-
} else
|
|
1160
|
-
|
|
1251
|
+
} else {
|
|
1252
|
+
record.proxy = "not-running";
|
|
1253
|
+
if (!stoppedService) console.log("No running proxy found.");
|
|
1161
1254
|
}
|
|
1162
1255
|
if (!stopFailed) {
|
|
1163
1256
|
// `readPid() === null` means the snapshotted pid file was absent, invalid, dead, or
|
|
@@ -1180,6 +1273,7 @@ async function handleStop() {
|
|
|
1180
1273
|
const survivor = await proxyStillLiveAfterStop({ canRespawn: true });
|
|
1181
1274
|
if (survivor) {
|
|
1182
1275
|
stopFailed = true;
|
|
1276
|
+
record.proxy = "respawned";
|
|
1183
1277
|
console.error(`ā A proxy is still listening on port ${survivor.port} after the service stop; it is being respawned.`);
|
|
1184
1278
|
console.error(" Skipping shared teardown: restoring client config while the proxy runs leaves both pointing at each other.");
|
|
1185
1279
|
ownershipBlocked = true;
|
|
@@ -1215,6 +1309,7 @@ async function handleStop() {
|
|
|
1215
1309
|
// No file, no nonce: nothing to quarantine and nothing to remove. The home itself
|
|
1216
1310
|
// may be hiding an obligation, so block and ask for the directory to be fixed.
|
|
1217
1311
|
inheritedBlocks = true;
|
|
1312
|
+
record.inheritedTeardownBlocks = true;
|
|
1218
1313
|
stopFailed = true;
|
|
1219
1314
|
console.error(`ā ${read.detail}, so this stop cannot tell whether a shared teardown is still owed.`);
|
|
1220
1315
|
console.error(" Skipping shared teardown. Fix access to the opencodex home, then rerun 'ocx stop'.");
|
|
@@ -1223,6 +1318,7 @@ async function handleStop() {
|
|
|
1223
1318
|
if (read.state === "invalid") {
|
|
1224
1319
|
unreadable.push(read);
|
|
1225
1320
|
inheritedBlocks = true;
|
|
1321
|
+
record.inheritedTeardownBlocks = true;
|
|
1226
1322
|
stopFailed = true;
|
|
1227
1323
|
console.error(`ā A pending-teardown receipt could not be read (${read.detail}).`);
|
|
1228
1324
|
console.error(" It names no endpoint, so this stop cannot prove the proxy it belonged to is down.");
|
|
@@ -1234,6 +1330,7 @@ async function handleStop() {
|
|
|
1234
1330
|
// proxy on an explicit --port can be respawned there while this address refuses,
|
|
1235
1331
|
// so "dead" here proves nothing and must not authorize a restore.
|
|
1236
1332
|
inheritedBlocks = true;
|
|
1333
|
+
record.inheritedTeardownBlocks = true;
|
|
1237
1334
|
stopFailed = true;
|
|
1238
1335
|
console.error("ā A shared teardown from an earlier stop is outstanding, but that stop could not record the address it was stopping.");
|
|
1239
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.`);
|
|
@@ -1245,12 +1342,14 @@ async function handleStop() {
|
|
|
1245
1342
|
continue;
|
|
1246
1343
|
}
|
|
1247
1344
|
inheritedBlocks = true;
|
|
1345
|
+
record.inheritedTeardownBlocks = true;
|
|
1248
1346
|
stopFailed = true;
|
|
1249
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.`);
|
|
1250
1348
|
console.error(" Skipping shared teardown: restoring client config under a proxy that may still be running is what the deferral exists to prevent.");
|
|
1251
1349
|
console.error(" The obligation is preserved; retry once the proxy is confirmed stopped.");
|
|
1252
1350
|
}
|
|
1253
1351
|
}
|
|
1352
|
+
if (nativeRestoreHandledByProxy) record.sharedTeardown = "performed-by-proxy";
|
|
1254
1353
|
const restoreBlocked = ownershipBlocked || inheritedBlocks || nativeRestoreHandledByProxy;
|
|
1255
1354
|
if (!restoreBlocked) {
|
|
1256
1355
|
if (recoveredNonces.length > 0) {
|
|
@@ -1259,6 +1358,7 @@ async function handleStop() {
|
|
|
1259
1358
|
console.log("ā©ļø Finishing a shared teardown left unfinished by an earlier stop.");
|
|
1260
1359
|
}
|
|
1261
1360
|
const restore = await restoreSharedClientStateAfterStop();
|
|
1361
|
+
record.sharedTeardown = restore.historyDeferred ? "refused" : restore.other ? "failed" : "restored";
|
|
1262
1362
|
if (restore.other) stopFailed = true;
|
|
1263
1363
|
else if (restore.historyDeferred) historyDeferredNonces = teardownNonce ? [teardownNonce, ...recoveredNonces] : recoveredNonces;
|
|
1264
1364
|
else if (restore.historyOnly) historyOnlyFailure = true;
|
|
@@ -1284,6 +1384,7 @@ async function handleStop() {
|
|
|
1284
1384
|
// removal is surfaced rather than swallowed.
|
|
1285
1385
|
if (!clearPendingTeardown(nonce)) {
|
|
1286
1386
|
stopFailed = true;
|
|
1387
|
+
record.receiptClearFailed = true;
|
|
1287
1388
|
console.error(`ā The shared teardown finished, but its receipt could not be removed: ${pendingTeardownPathFor(nonce)}`);
|
|
1288
1389
|
console.error(" Remove it manually; otherwise every later stop and update will try to recover it again.");
|
|
1289
1390
|
}
|
|
@@ -1328,18 +1429,23 @@ async function handleStop() {
|
|
|
1328
1429
|
? STOP_HISTORY_DEFERRED_EXIT_CODE
|
|
1329
1430
|
: 1;
|
|
1330
1431
|
}
|
|
1331
|
-
|
|
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 };
|
|
1332
1439
|
}
|
|
1333
1440
|
|
|
1334
1441
|
async function handleUninstall() {
|
|
1335
1442
|
/** Definitive "nothing is answering" on the endpoint this home would serve. */
|
|
1336
1443
|
const proxyEndpointProvenDown = async (): Promise<boolean> => {
|
|
1337
1444
|
try {
|
|
1338
|
-
const { probeProxyLiveness } = await import("../update/proxy-liveness-probe.mjs");
|
|
1339
1445
|
// Every candidate, not just the preferred one: a stale runtime record pointing at a
|
|
1340
1446
|
// closed port would otherwise "prove" a live proxy on the configured port is gone.
|
|
1341
1447
|
const endpoints = endpointsToProve(readRuntimePort(), loadConfig());
|
|
1342
|
-
return
|
|
1448
|
+
return await everyEndpointProvenDownAsync(endpoints, probeEndpointLiveness);
|
|
1343
1449
|
} catch {
|
|
1344
1450
|
return false;
|
|
1345
1451
|
}
|
|
@@ -1586,8 +1692,26 @@ async function handleStatus() {
|
|
|
1586
1692
|
console.log(installed
|
|
1587
1693
|
? " Restart with 'ocx start', or refresh the installed service: 'ocx service repair'."
|
|
1588
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
|
+
}
|
|
1589
1703
|
}
|
|
1590
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
|
+
}
|
|
1591
1715
|
console.log(` Config: ${status.json.paths.config}${local}`);
|
|
1592
1716
|
console.log(` PID file: ${status.json.paths.pid}${local}`);
|
|
1593
1717
|
console.log(` Runtime: ${status.json.paths.runtime}${local}`);
|
|
@@ -1606,6 +1730,12 @@ async function handleStatus() {
|
|
|
1606
1730
|
console.log(` Codex autostart: ${status.json.codexAutostart ? "enabled" : "disabled"}${local}`);
|
|
1607
1731
|
console.log(` Restart safety: ${startupHealthSummary(status.json.startup)}${local}`);
|
|
1608
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 : ""}`));
|
|
1609
1739
|
if (status.json.startup.routingKind === "native") {
|
|
1610
1740
|
let retainedProviderTable = false;
|
|
1611
1741
|
try {
|
|
@@ -1734,6 +1864,14 @@ async function handleReady(args: ReadyArgs): Promise<number> {
|
|
|
1734
1864
|
return runReady(args);
|
|
1735
1865
|
}
|
|
1736
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
|
+
|
|
1737
1875
|
process.exit(await dispatchCommand(head, {
|
|
1738
1876
|
args,
|
|
1739
1877
|
command,
|
|
@@ -1754,6 +1892,7 @@ process.exit(await dispatchCommand(head, {
|
|
|
1754
1892
|
},
|
|
1755
1893
|
handleStart,
|
|
1756
1894
|
handleStop,
|
|
1895
|
+
handleResolve,
|
|
1757
1896
|
handleEnsure,
|
|
1758
1897
|
handleTrayProxyStart,
|
|
1759
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/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]",
|