@bitkyc08/opencodex 2.81.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-BqHF5Trd.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-D3FBCNiP.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DTBC8n7-.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound.ts +77 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +2 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +5 -0
- package/src/cli/doctor.ts +16 -3
- package/src/cli/index.ts +14 -9
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integrations.ts +16 -3
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/build-entries.ts +18 -0
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/effort.ts +26 -5
- package/src/codex/catalog/gather-capture.ts +5 -0
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +43 -35
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -220
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +8 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/generated/compatibility-version.json +303 -171
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +29 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +18 -2
- package/src/integrations/writer.ts +9 -3
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/request-execution-budget.ts +8 -0
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/oauth/anthropic-account-refusal.ts +39 -1
- package/src/oauth/anthropic-routing.ts +1 -1
- package/src/oauth/anthropic-send-ownership.ts +3 -3
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/index.ts +6 -2
- package/src/oauth/store.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +15 -2
- package/src/responses/state/metrics.ts +8 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +12 -5
- package/src/responses/state.ts +18 -10
- package/src/responses/task-input.ts +17 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +1 -0
- package/src/server/chat-native.ts +28 -12
- package/src/server/claude-messages.ts +43 -6
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +1 -0
- package/src/server/management/agent-settings-routes.ts +93 -32
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +59 -8
- package/src/server/management/context.ts +2 -2
- package/src/server/management/shared.ts +3 -3
- package/src/server/messages-native-oauth.ts +9 -8
- package/src/server/messages-native.ts +49 -13
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +31 -2
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +34 -13
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +20 -3
- package/src/server/responses/core-combo.ts +22 -14
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +19 -2
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +25 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +3 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/config.ts +21 -0
- package/src/types/request.ts +5 -0
- package/src/types/wire.ts +1 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
package/src/cli/dispatch.ts
CHANGED
|
@@ -37,6 +37,8 @@ import { parseStopApproval, type StopApproval } from "./stop-approval";
|
|
|
37
37
|
import type { ResolveArgs } from "./resolve";
|
|
38
38
|
import type { ClientConnectionState } from "../client/state";
|
|
39
39
|
import { OCX_NATIVE_REPLAY_RECOVERY_NOTE } from "../responses/compaction";
|
|
40
|
+
import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
|
|
41
|
+
import { desktopStopNotice } from "./desktop-runtime-guidance";
|
|
40
42
|
|
|
41
43
|
export interface CliDispatchDeps {
|
|
42
44
|
args: string[];
|
|
@@ -51,6 +53,7 @@ export interface CliDispatchDeps {
|
|
|
51
53
|
spawnDetached: (argv: readonly string[]) => void;
|
|
52
54
|
handleStart: () => Promise<void>;
|
|
53
55
|
handleStop: (approval?: StopApproval) => Promise<StopOutcome>;
|
|
56
|
+
inspectDesktopSupervision?: typeof inspectDesktopSupervision;
|
|
54
57
|
handleEnsure: (options?: { existingIsSuccess?: boolean }) => Promise<boolean>;
|
|
55
58
|
handleResolve: (args: ResolveArgs) => Promise<number>;
|
|
56
59
|
handleTrayProxyStart: (existingIsSuccess?: boolean) => Promise<boolean>;
|
|
@@ -112,6 +115,8 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
112
115
|
// re-start the proxy immediately, so warning there would contradict the next line.
|
|
113
116
|
const warning = "⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').";
|
|
114
117
|
if (!parsed.json) {
|
|
118
|
+
const notice = desktopStopNotice((deps.inspectDesktopSupervision ?? inspectDesktopSupervision)());
|
|
119
|
+
if (notice) console.error(notice);
|
|
115
120
|
// handleStop returns the structured outcome now; an object is always truthy, so
|
|
116
121
|
// the warning must key on .ok — otherwise a failed stop would still claim downtime.
|
|
117
122
|
if ((await deps.handleStop()).ok) console.log(warning);
|
package/src/cli/doctor.ts
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* it never sets proxy env, relocates state dirs, mutates quota, or changes
|
|
8
8
|
* networking. See devlog/_plan/260630_wsl-account-autoswitch/30_*.
|
|
9
9
|
*/
|
|
10
|
+
import { cliCommandDoctorChecks, collectCliPathDiagnostics } from "./cli-path-diagnostics";
|
|
10
11
|
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
11
12
|
import { homedir } from "node:os";
|
|
12
13
|
import { dirname, join } from "node:path";
|
|
13
14
|
import { getConfigDir, getConfigPath, readConfigDiagnostics } from "../config";
|
|
14
15
|
import { readPid } from "../config/process-state";
|
|
16
|
+
import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
|
|
15
17
|
import { fetchLiveStartupHealth, probeUncleanExitState, selectStatusStartupHealth } from "./status";
|
|
16
18
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
17
19
|
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
@@ -1324,6 +1326,12 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1324
1326
|
console.log(` ${row.exists ? "ok " : "-- "} ${row.label}: ${row.path}${flags ? ` (${flags})` : ""}`);
|
|
1325
1327
|
}
|
|
1326
1328
|
|
|
1329
|
+
console.log("\nocx command selection");
|
|
1330
|
+
for (const check of cliCommandDoctorChecks(collectCliPathDiagnostics())) {
|
|
1331
|
+
console.log(` ${check.level === "OK" ? "ok " : "!! "} ${check.message}`);
|
|
1332
|
+
if (check.level === "FAIL") recordDoctorFailure();
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1327
1335
|
// Runs without the proxy on purpose: the worst accumulation happens when the proxy will
|
|
1328
1336
|
// not start, which is exactly when the in-process periodic reclaim never ticks.
|
|
1329
1337
|
const reclaimTemps = args.includes(RECLAIM_RESPONSE_TEMPS_FLAG);
|
|
@@ -1379,7 +1387,8 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1379
1387
|
configFn: () => ({ port: doctorConfig.port, hostname: doctorConfig.hostname }),
|
|
1380
1388
|
});
|
|
1381
1389
|
const liveStartup = live ? await fetchLiveStartupHealth(live) : null;
|
|
1382
|
-
const startup = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(doctorConfig)
|
|
1390
|
+
const { startup } = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(doctorConfig),
|
|
1391
|
+
live?.pid != null ? () => inspectDesktopSupervision({ targetPid: live.pid! }) : undefined, live?.pid);
|
|
1383
1392
|
console.log("\nCodex restart safety");
|
|
1384
1393
|
console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
|
|
1385
1394
|
console.log(` ${formatStartupRoutingDetail(startup)}`);
|
|
@@ -1685,8 +1694,12 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1685
1694
|
const anyDrvfs = paths.some(p => detectFsType(p.path, mounts).isDrvfs || detectFsType(p.path, mounts).isMntDrive);
|
|
1686
1695
|
const noProxy = currentProxyEnv.every(p => !p.present) && !configuredProxy.present;
|
|
1687
1696
|
if (!startup.rebootSafe) {
|
|
1688
|
-
|
|
1689
|
-
|
|
1697
|
+
if (startup.recommendedAction || startup.desktop?.supervisor) {
|
|
1698
|
+
hints.push(`Codex is pinned to the local proxy without persistent startup protection. ${startup.recommendedAction ?? "Reopen OpenCodex and check Start at Login."}`);
|
|
1699
|
+
} else {
|
|
1700
|
+
const command = startup.recommendedCommand ?? startup.commands.restoreNative;
|
|
1701
|
+
hints.push(`Codex is pinned to the local proxy without persistent startup protection. After restart, requests can reconnect indefinitely. Run '${command}'.`);
|
|
1702
|
+
}
|
|
1690
1703
|
}
|
|
1691
1704
|
if (anyDrvfs) {
|
|
1692
1705
|
hints.push("State dir is on a Windows-mounted (/mnt) drive. Prefer the Linux home (~) under WSL for token/lock reliability.");
|
package/src/cli/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { currentServingCommand, deferServiceChildToNewerRuntime, markDelegatedServiceReady, recordServingRuntime } from "../config/serving-runtimes";
|
|
10
10
|
import { packageVersion } from "../lib/package-version";
|
|
11
|
+
import { codexHomeIsAbsent } from "../codex/codex-home-owner";
|
|
12
|
+
import { getCodexHome } from "../codex/paths";
|
|
11
13
|
import { admitUpdateRestartChild } from "./update-restart-child";
|
|
12
14
|
import { UpdateRestartRequired } from "./update-restart-candidate";
|
|
13
15
|
import { describeUpdateRestartFailure, restartFromCurrentInstallation } from "./update-restart";
|
|
@@ -75,7 +77,7 @@ import {
|
|
|
75
77
|
pendingTeardownsAreExactly,
|
|
76
78
|
quarantinePendingTeardown,
|
|
77
79
|
} from "../config/pending-teardown";
|
|
78
|
-
import { collectStatus, deadProxyRoutingAdviceLines, detectMissingCodexCatalogPath, hubStatusLines, missingCodexCatalogLines, remoteHubBannerLine, remoteHubStatusLines, unusedProxyWarningLines } from "./status";
|
|
80
|
+
import { collectStatus, deadProxyRoutingAdviceLines, detectMissingCodexCatalogPath, hubStatusLines, missingCodexCatalogLines, remoteHubBannerLine, remoteHubStatusLines, runtimeSupervisorLine, unusedProxyWarningLines } from "./status";
|
|
79
81
|
import { endpointsToProve, everyEndpointProvenDownAsync, sharedTeardownAuthorized, type UninstallObservation } from "./uninstall-plan";
|
|
80
82
|
import { takeFlag } from "./runtime-api";
|
|
81
83
|
import { parseStartOptions, StartArgsError } from "./start-args";
|
|
@@ -85,11 +87,11 @@ import {
|
|
|
85
87
|
recheckRestartFailedStart, reobserveRestartReplacement,
|
|
86
88
|
restartStartOutcome,
|
|
87
89
|
waitForProxyReplacement,
|
|
88
|
-
runProxyRestart,
|
|
89
90
|
runTrayProxyStart,
|
|
90
91
|
type ProxyRestartLive,
|
|
91
92
|
type ProxyRestartStartOutcome,
|
|
92
93
|
} from "./tray-proxy";
|
|
94
|
+
import { duplicateRuntimeMessage, runDesktopAwareProxyRestart } from "./desktop-runtime-guidance";
|
|
93
95
|
import { reportRestartFailure } from "./restart-failure";
|
|
94
96
|
import { requestBoundSystemRestart } from "./system-restart-client";
|
|
95
97
|
import { installCrashGuards } from "../lib/crash-guard";
|
|
@@ -329,7 +331,7 @@ async function chooseListenPort(
|
|
|
329
331
|
throw new StartCommandExit(serviceStayOutExitCode());
|
|
330
332
|
}
|
|
331
333
|
if (decision === "refuse-live-proxy") {
|
|
332
|
-
console.error(
|
|
334
|
+
console.error(duplicateRuntimeMessage(holder?.pid, preferred));
|
|
333
335
|
throw new StartCommandExit(1);
|
|
334
336
|
}
|
|
335
337
|
if (decision === "refuse-unidentified-holder") {
|
|
@@ -444,7 +446,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
444
446
|
process.exit(serviceStayOutExitCode());
|
|
445
447
|
}
|
|
446
448
|
if (decision === "refuse" || decision === "await-parent") {
|
|
447
|
-
console.error(
|
|
449
|
+
console.error(duplicateRuntimeMessage(owner.live.pid ?? owner.pidSnapshot, owner.live.port));
|
|
448
450
|
process.exit(1);
|
|
449
451
|
}
|
|
450
452
|
// Sibling path. The new instance takes over this home's ocx.pid / runtime-port.json while
|
|
@@ -654,10 +656,12 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
654
656
|
removeRuntimePort(process.pid);
|
|
655
657
|
if (teardown.restoreNativeCodex && !currentExternalCodexModelProvider()) {
|
|
656
658
|
try {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
659
|
+
if (!codexHomeIsAbsent(getCodexHome())) {
|
|
660
|
+
const restored = restoreNativeCodex();
|
|
661
|
+
if (!restored.success) {
|
|
662
|
+
cleanupSucceeded = false;
|
|
663
|
+
console.error(`⚠️ Native Codex restore failed during shutdown: ${restored.message}`);
|
|
664
|
+
}
|
|
661
665
|
}
|
|
662
666
|
} catch (error) {
|
|
663
667
|
cleanupSucceeded = false;
|
|
@@ -928,7 +932,7 @@ async function handleProxyRestart(
|
|
|
928
932
|
startWhenStopped: (recoveringLiveRestart: boolean) => Promise<ProxyRestartStartOutcome>,
|
|
929
933
|
): Promise<boolean> {
|
|
930
934
|
const deadlineAt = Date.now() + PROXY_RESTART_OBSERVE_MS;
|
|
931
|
-
const result = await
|
|
935
|
+
const result = await runDesktopAwareProxyRestart({
|
|
932
936
|
findLive: () => discoverStableProxyForRestart({
|
|
933
937
|
findLive: () => findLiveProxy({ deadlineAt, attempts: 2, acceptPackageTreeFenced: true }),
|
|
934
938
|
expired: () => Date.now() >= deadlineAt,
|
|
@@ -1785,6 +1789,7 @@ async function handleStatus() {
|
|
|
1785
1789
|
console.log(` Config: ${status.json.paths.config}${local}`);
|
|
1786
1790
|
console.log(` PID file: ${status.json.paths.pid}${local}`);
|
|
1787
1791
|
console.log(` Runtime: ${status.json.paths.runtime}${local}`);
|
|
1792
|
+
if (runtimeSupervisorLine(status.json.startup)) console.log(` ${runtimeSupervisorLine(status.json.startup)}`);
|
|
1788
1793
|
console.log(` Runtime source: ${status.json.runtime.source}${status.json.runtime.overrideEnv ? ` (${status.json.runtime.overrideEnv})` : ""}${local}`);
|
|
1789
1794
|
// On a client this is the local default, which routing does not use — the hub applies its own.
|
|
1790
1795
|
console.log(` Default provider: ${status.json.defaultProvider}${local}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { redactUserPath } from "../lib/redact";
|
|
3
3
|
import type { RefusalReason } from "../integrations/mutation-plan";
|
|
4
|
+
import { ASIDE_SYNC_EMPTY_LINES } from "./aside-profile-recovery";
|
|
4
5
|
import { refreshAsideProfilesThroughServer } from "./aside-profiles";
|
|
5
6
|
import { runCatalogAction } from "./catalog-command-result";
|
|
6
7
|
import { CliUsageError, RuntimeApiError, printData, takeFlag, takeOption, type RuntimeApiDeps } from "./runtime-api";
|
|
@@ -56,7 +57,7 @@ export function handleIntegrationAsideSync(argv: string[], deps: AsideSyncCliDep
|
|
|
56
57
|
const parsed = resultsSchema.safeParse(outcomes);
|
|
57
58
|
if (!parsed.success) throw new Error("Invalid Aside synchronization outcome");
|
|
58
59
|
const results = parsed.data;
|
|
59
|
-
const lines = results.length === 0 ? [
|
|
60
|
+
const lines = results.length === 0 ? [...ASIDE_SYNC_EMPTY_LINES]
|
|
60
61
|
: results.flatMap(row => [
|
|
61
62
|
`Aside profile ${row.profileId}: ${row.ok ? row.changed ? "updated" : "unchanged" : "failed"}.`,
|
|
62
63
|
...(row.reason ? [row.reason] : []),
|
package/src/cli/integrations.ts
CHANGED
|
@@ -13,13 +13,14 @@ import {
|
|
|
13
13
|
type RuntimeApiDeps,
|
|
14
14
|
} from "./runtime-api";
|
|
15
15
|
import { clientIntegrationPath, validateAsideProfile } from "./integration-input";
|
|
16
|
+
import { asideProfileRecoveryLines } from "./aside-profile-recovery";
|
|
16
17
|
import type { IntegrationClientId } from "../integrations/registry";
|
|
17
18
|
|
|
18
19
|
const CLAUDE_USAGE = `Usage:
|
|
19
20
|
ocx claude config [status] [--json]
|
|
20
21
|
ocx claude config set [--enabled <on|off>] [--auth-mode <auto|proxy|subscription>]
|
|
21
22
|
[--system-env <on|off>] [--fast-mode <on|off>] [--auto-context <on|off>]
|
|
22
|
-
[--compact-window <tokens|default>] [--inject-agents <on|off>]
|
|
23
|
+
[--compact-window <tokens|default>] [--context-accounting <1m|200k>] [--inject-agents <on|off>]
|
|
23
24
|
[--small-fast-model <id|->] [--model-map <from=to,from=to|->]
|
|
24
25
|
[--blocked-skills <name,name|->] [--web-model <id|->] [--web-backend <openai|anthropic|xai|gemini|exa|->]
|
|
25
26
|
[--vision-model <id|->] [--vision-backend <openai|anthropic|->] [--json]
|
|
@@ -75,6 +76,7 @@ export async function handleClaudeConfigCommand(argv: string[], deps: RuntimeApi
|
|
|
75
76
|
const fastMode = takeBooleanOption(args, "--fast-mode");
|
|
76
77
|
const autoContext = takeBooleanOption(args, "--auto-context");
|
|
77
78
|
const compact = takeOption(args, "--compact-window");
|
|
79
|
+
const accounting = takeOption(args, "--context-accounting");
|
|
78
80
|
const injectAgents = takeBooleanOption(args, "--inject-agents");
|
|
79
81
|
const smallFastModel = takeOption(args, "--small-fast-model");
|
|
80
82
|
const modelMap = takeOption(args, "--model-map");
|
|
@@ -97,6 +99,11 @@ export async function handleClaudeConfigCommand(argv: string[], deps: RuntimeApi
|
|
|
97
99
|
body.autoCompactWindow = value;
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
if (accounting !== undefined) {
|
|
103
|
+
const value = accounting.toLowerCase();
|
|
104
|
+
if (value !== "1m" && value !== "200k") throw new CliUsageError("--context-accounting must be 1m or 200k", CLAUDE_USAGE);
|
|
105
|
+
body.contextAccounting = value;
|
|
106
|
+
}
|
|
100
107
|
if (injectAgents !== undefined) body.injectAgents = injectAgents;
|
|
101
108
|
if (smallFastModel !== undefined) body.smallFastModel = smallFastModel === "-" ? "" : smallFastModel;
|
|
102
109
|
if (modelMap !== undefined) body.modelMap = parseMap(modelMap);
|
|
@@ -194,7 +201,10 @@ function raycastBlock(result: unknown): RaycastStatusBlock | null {
|
|
|
194
201
|
*/
|
|
195
202
|
function singleClientStatusLines(result: unknown): string[] {
|
|
196
203
|
const raycast = raycastBlock(result);
|
|
197
|
-
if (!raycast)
|
|
204
|
+
if (!raycast) {
|
|
205
|
+
const aside = (result as { clientId?: unknown } | null)?.clientId === "aside";
|
|
206
|
+
return [...summaryLines(result), ...(aside ? asideProfileRecoveryLines([result]) : [])];
|
|
207
|
+
}
|
|
198
208
|
const rest = Object.fromEntries(Object.entries(result as Record<string, unknown>).filter(([key]) => key !== "raycast"));
|
|
199
209
|
const lines = [...summaryLines(rest), `plan: ${raycast.plan}`];
|
|
200
210
|
if (!raycast.aiDirPresent) {
|
|
@@ -252,7 +262,10 @@ export async function handleClientIntegrationCommand(
|
|
|
252
262
|
const profiles = (result as { profiles?: Array<Record<string, unknown>> }).profiles;
|
|
253
263
|
printData(result, wantsJson, profiles
|
|
254
264
|
? profiles.length > 0
|
|
255
|
-
?
|
|
265
|
+
? [
|
|
266
|
+
...profiles.map(row => `${String(row.profileId)} ${String(row.name ?? "Aside")}: ${row.enabled ? "on" : "off"} (${String(row.state)})${row.current ? " [current]" : ""}`),
|
|
267
|
+
...asideProfileRecoveryLines(profiles),
|
|
268
|
+
]
|
|
256
269
|
: [String((result as { error?: string }).error ?? "No Aside profiles found.")]
|
|
257
270
|
: rows
|
|
258
271
|
/*
|
package/src/cli/resolve.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
} from "../service/ownership-compatibility";
|
|
60
60
|
import { observeManagingClis } from "../service/managing-cli";
|
|
61
61
|
import { SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION } from "../service/install-state-contract.mjs";
|
|
62
|
+
import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
|
|
62
63
|
import { computeVersionSkew, type VersionSkew } from "./version-skew";
|
|
63
64
|
|
|
64
65
|
/** Wire version of the resolve document. Bump only on an incompatible shape change. */
|
|
@@ -123,6 +124,12 @@ export interface ResolveJson {
|
|
|
123
124
|
* takeover. Additive within ocx-resolve/1; older shells ignore it and read as unknown.
|
|
124
125
|
*/
|
|
125
126
|
versionSkew?: VersionSkew;
|
|
127
|
+
supervisor?: {
|
|
128
|
+
kind: "desktop" | "none" | "unknown" | "unsupported";
|
|
129
|
+
supervisorPid?: number;
|
|
130
|
+
runtimePid?: number;
|
|
131
|
+
app?: string;
|
|
132
|
+
};
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
export type ResolveTakeover =
|
|
@@ -160,6 +167,7 @@ export interface ResolveIo {
|
|
|
160
167
|
observeManagers?: (
|
|
161
168
|
state: ServiceInstallState | null,
|
|
162
169
|
) => Readonly<Record<ManagingCliRole, ManagingCliObservation>>;
|
|
170
|
+
inspectSupervision?: typeof inspectDesktopSupervision;
|
|
163
171
|
stdout?: { log: (s: string) => void };
|
|
164
172
|
stderr?: { error: (s: string) => void };
|
|
165
173
|
}
|
|
@@ -187,6 +195,7 @@ export function buildResolveJson(
|
|
|
187
195
|
cliVersion: string,
|
|
188
196
|
ownership: ServiceOwnershipResolution,
|
|
189
197
|
takeover: ResolveTakeover,
|
|
198
|
+
supervision?: ReturnType<typeof inspectDesktopSupervision>,
|
|
190
199
|
): ResolveJson {
|
|
191
200
|
const configured = config.port ?? RESOLVE_DEFAULT_PORT;
|
|
192
201
|
return {
|
|
@@ -199,6 +208,9 @@ export function buildResolveJson(
|
|
|
199
208
|
source: live ? live.source : "config",
|
|
200
209
|
},
|
|
201
210
|
liveness: livenessJson(live),
|
|
211
|
+
...(live && supervision ? { supervisor: supervision.kind === "desktop"
|
|
212
|
+
? { kind: supervision.kind, supervisorPid: supervision.supervisorPid, runtimePid: supervision.runtimePid, app: supervision.app }
|
|
213
|
+
: { kind: supervision.kind } } : {}),
|
|
202
214
|
ownership,
|
|
203
215
|
takeover,
|
|
204
216
|
// A proven absence has nothing to skew against; the field is omitted there rather
|
|
@@ -216,7 +228,8 @@ function reportHuman(json: ResolveJson, stdout: { log: (s: string) => void }): v
|
|
|
216
228
|
if (live.status === "live") {
|
|
217
229
|
const pidText = live.pid === null ? "unknown" : String(live.pid);
|
|
218
230
|
const versionText = live.version ?? "unknown version";
|
|
219
|
-
|
|
231
|
+
const supervisor = json.supervisor?.kind === "desktop" ? `; supervisor: desktop (pid ${json.supervisor.supervisorPid})` : "";
|
|
232
|
+
stdout.log(`Proxy live on port ${json.port.effective} (PID ${pidText}, ${versionText}); effective port ${json.port.effective}${supervisor}.`);
|
|
220
233
|
} else {
|
|
221
234
|
stdout.log(`No live proxy (absence proven); effective port ${json.port.effective} (configured).`);
|
|
222
235
|
}
|
|
@@ -353,7 +366,17 @@ export async function runResolve(args: ResolveArgs, io: ResolveIo = {}): Promise
|
|
|
353
366
|
};
|
|
354
367
|
}
|
|
355
368
|
}
|
|
356
|
-
|
|
369
|
+
let supervision: ReturnType<typeof inspectDesktopSupervision> | undefined;
|
|
370
|
+
if (live) {
|
|
371
|
+
try {
|
|
372
|
+
supervision = live.pid === null ? { kind: "unknown", reason: "pid-unavailable", desktopSeen: false }
|
|
373
|
+
: (io.inspectSupervision ?? inspectDesktopSupervision)({ targetPid: live.pid });
|
|
374
|
+
} catch {
|
|
375
|
+
// Supervision is independent evidence; its failure cannot invalidate established liveness.
|
|
376
|
+
supervision = { kind: "unknown", reason: "probe-failed", desktopSeen: false };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const json = buildResolveJson(diagnostics.config, live, configHome, cliVersion(), ownership, takeover, supervision);
|
|
357
380
|
if (args.json) stdout.log(JSON.stringify(json));
|
|
358
381
|
else reportHuman(json, stdout);
|
|
359
382
|
return 0;
|
package/src/cli/root.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI head: version/help early exits,
|
|
3
|
-
* Codex-shim auto-restore preflight
|
|
2
|
+
* CLI head: version/help early exits, ready/resolve pre-parse, lifecycle version
|
|
3
|
+
* notice, then bounded Codex-shim auto-restore preflight (Phase 1 of the CLI
|
|
4
4
|
* deepening — moved out of src/cli/index.ts).
|
|
5
5
|
*
|
|
6
6
|
* `parseCliHead` is pure (no I/O, no process access) so the ordering and the
|
|
@@ -16,6 +16,7 @@ import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
|
|
|
16
16
|
import { findCommand } from "./registry";
|
|
17
17
|
import { printUnknownCommand } from "./help-recovery";
|
|
18
18
|
import { noteCredentialArgv, redactSecretArgs } from "./secret-args";
|
|
19
|
+
import { maybeNoticeVersionSkew } from "./version-skew-notice";
|
|
19
20
|
|
|
20
21
|
export interface CliHead {
|
|
21
22
|
kind: "version" | "help" | "ready" | "resolve" | "command";
|
|
@@ -141,6 +142,7 @@ export async function runCli(argv: string[]): Promise<CliHead> {
|
|
|
141
142
|
console.error(uninstallError);
|
|
142
143
|
process.exit(2);
|
|
143
144
|
}
|
|
145
|
+
await maybeNoticeVersionSkew(head.command, head.args);
|
|
144
146
|
maybeAutoRestoreCodexShim(head.command, head.args);
|
|
145
147
|
return head;
|
|
146
148
|
}
|
package/src/cli/status.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectCliPathDiagnostics, formatCliStatusHealthLabel, type CliPathDiagnostics } from "./cli-path-diagnostics";
|
|
1
2
|
import type { CodexMainAccountPolicyHealth } from "../oauth/health";
|
|
2
3
|
import { durableBunRuntime } from "../lib/bun-runtime";
|
|
3
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -7,6 +8,7 @@ import { diagnoseCodexBundledPlugins, type CodexPluginsDiagnostic } from "../cod
|
|
|
7
8
|
import { findLiveProxy, probeHostname } from "../server/proxy-liveness";
|
|
8
9
|
import type { OcxConfig } from "../types";
|
|
9
10
|
import { diagnoseService, serviceLogPath } from "../service";
|
|
11
|
+
import { inspectDesktopSupervision } from "../service/desktop-supervision.mjs";
|
|
10
12
|
import { collectStartupHealth, startupHealthReadBudgetMs, type StartupHealth } from "../codex/autostart-health";
|
|
11
13
|
import { getCodexRoutingKind } from "../codex/inject";
|
|
12
14
|
import { missingOwnedCatalogPath } from "../codex/inject/config-toml";
|
|
@@ -215,6 +217,8 @@ export type CliStatusJson = {
|
|
|
215
217
|
* ignores the key is unaffected, and `proxyVersion` is null when nothing is live.
|
|
216
218
|
*/
|
|
217
219
|
versionSkew: VersionSkew;
|
|
220
|
+
cliCommand: CliPathDiagnostics;
|
|
221
|
+
startupSource: "live" | "local" | "local-supervision-override";
|
|
218
222
|
};
|
|
219
223
|
|
|
220
224
|
export type CliStatusView = {
|
|
@@ -259,12 +263,28 @@ export async function fetchLiveStartupHealth(
|
|
|
259
263
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
260
264
|
const row = payload as Record<string, unknown>;
|
|
261
265
|
if (row.status !== "native" && row.status !== "protected" && row.status !== "at-risk") return null;
|
|
262
|
-
if (row.protection !== "service" && row.protection !== "shim" && row.protection !== "none") return null;
|
|
266
|
+
if (row.protection !== "service" && row.protection !== "desktop" && row.protection !== "shim" && row.protection !== "none") return null;
|
|
263
267
|
if (row.routingKind !== "native" && row.routingKind !== "opencodex-local"
|
|
264
268
|
&& row.routingKind !== "custom-local" && row.routingKind !== "custom-remote" && row.routingKind !== "unknown") return null;
|
|
265
269
|
if (row.shimCoverage !== "full" && row.shimCoverage !== "cli-only" && row.shimCoverage !== "none") return null;
|
|
266
270
|
if (typeof row.platform !== "string") return null;
|
|
267
271
|
if (row.recommendedCommand !== null && typeof row.recommendedCommand !== "string") return null;
|
|
272
|
+
if (row.recommendedAction !== undefined && row.recommendedAction !== null
|
|
273
|
+
&& (typeof row.recommendedAction !== "string" || row.recommendedAction.length > 512)) return null;
|
|
274
|
+
if (row.desktop !== undefined) {
|
|
275
|
+
if (!row.desktop || typeof row.desktop !== "object" || Array.isArray(row.desktop)) return null;
|
|
276
|
+
const desktop = row.desktop as Record<string, unknown>;
|
|
277
|
+
for (const key of ["owned", "loginEnabled", "running", "viable"] as const) {
|
|
278
|
+
if (typeof desktop[key] !== "boolean") return null;
|
|
279
|
+
}
|
|
280
|
+
if (desktop.supervisor !== undefined) {
|
|
281
|
+
if (!desktop.supervisor || typeof desktop.supervisor !== "object" || Array.isArray(desktop.supervisor)) return null;
|
|
282
|
+
const supervisor = desktop.supervisor as Record<string, unknown>;
|
|
283
|
+
if (typeof supervisor.supervisorPid !== "number" || !Number.isFinite(supervisor.supervisorPid)
|
|
284
|
+
|| typeof supervisor.runtimePid !== "number" || !Number.isFinite(supervisor.runtimePid)
|
|
285
|
+
|| typeof supervisor.app !== "string") return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
268
288
|
if (!row.commands || typeof row.commands !== "object" || Array.isArray(row.commands)) return null;
|
|
269
289
|
for (const key of ["installService", "repairService", "installShim", "restoreNative"] as const) {
|
|
270
290
|
if (typeof (row.commands as Record<string, unknown>)[key] !== "string") return null;
|
|
@@ -286,12 +306,36 @@ export async function fetchLiveStartupHealth(
|
|
|
286
306
|
return payload as StartupHealth;
|
|
287
307
|
}
|
|
288
308
|
|
|
289
|
-
/**
|
|
309
|
+
/** Older runtimes cannot report supervision; only that missing capability permits a local override. */
|
|
290
310
|
export function selectStatusStartupHealth(
|
|
291
311
|
liveStartup: StartupHealth | null,
|
|
292
312
|
fallback: () => StartupHealth,
|
|
293
|
-
)
|
|
294
|
-
|
|
313
|
+
supervision?: () => ReturnType<typeof inspectDesktopSupervision>,
|
|
314
|
+
livePid?: number | null,
|
|
315
|
+
): { startup: StartupHealth; startupSource: CliStatusJson["startupSource"] } {
|
|
316
|
+
if (!liveStartup) return { startup: fallback(), startupSource: "local" };
|
|
317
|
+
if (liveStartup.desktop === undefined && supervision) {
|
|
318
|
+
let evidence: ReturnType<typeof inspectDesktopSupervision> | undefined;
|
|
319
|
+
try { evidence = supervision(); }
|
|
320
|
+
catch { /* Failed optional evidence cannot replace an attested live verdict. */ }
|
|
321
|
+
if (evidence?.kind === "desktop" && evidence.runtimePid === livePid) {
|
|
322
|
+
const local = fallback();
|
|
323
|
+
const supervisor = local.desktop?.supervisor;
|
|
324
|
+
// The local probe runs later; it must still describe the same unowned process pair.
|
|
325
|
+
if (local.desktop?.owned === false && supervisor
|
|
326
|
+
&& supervisor.runtimePid === evidence.runtimePid && supervisor.supervisorPid === evidence.supervisorPid) {
|
|
327
|
+
return { startup: local, startupSource: "local-supervision-override" };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { startup: liveStartup, startupSource: "live" };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function runtimeSupervisorLine(startup: StartupHealth): string | null {
|
|
335
|
+
const supervisor = startup.desktop?.supervisor;
|
|
336
|
+
return supervisor
|
|
337
|
+
? `Runtime supervisor: OpenCodex Desktop (pid ${supervisor.supervisorPid}, ${supervisor.app}); durable owner: ${startup.desktop?.owned ? "desktop" : "none"}`
|
|
338
|
+
: null;
|
|
295
339
|
}
|
|
296
340
|
|
|
297
341
|
/** Build the service summary from the same startup source that `ocx status` selected. */
|
|
@@ -299,18 +343,23 @@ export function statusServiceSummary(
|
|
|
299
343
|
liveStartup: StartupHealth | null,
|
|
300
344
|
service: Pick<ReturnType<typeof diagnoseService>, "installed" | "summary">,
|
|
301
345
|
live: boolean,
|
|
346
|
+
// Which verdict was selected. A live proxy can still be described by the local reading when
|
|
347
|
+
// the runtime predates supervision reporting, so the label follows the verdict, not liveness.
|
|
348
|
+
startupSource?: CliStatusJson["startupSource"],
|
|
302
349
|
): string {
|
|
303
|
-
if (liveStartup) {
|
|
350
|
+
if (live && liveStartup) {
|
|
351
|
+
const supervisor = liveStartup.desktop?.supervisor ? "OpenCodex Desktop supervises the running proxy; " : "";
|
|
304
352
|
if (liveStartup.protection === "service" && liveStartup.serviceViable) {
|
|
305
|
-
return
|
|
353
|
+
return `${supervisor}running under the live managed service (logs: ${serviceLogPath()})`;
|
|
306
354
|
}
|
|
307
355
|
const state = [
|
|
308
356
|
liveStartup.serviceInstalled ? "installed" : "absent",
|
|
309
357
|
liveStartup.serviceRunning ? "running" : "not running",
|
|
310
358
|
liveStartup.serviceViable ? "viable" : "not viable",
|
|
311
359
|
].join(", ");
|
|
312
|
-
const action = liveStartup.recommendedCommand ? `; run '${liveStartup.recommendedCommand}'` : "";
|
|
313
|
-
|
|
360
|
+
const action = !liveStartup.desktop?.supervisor && liveStartup.recommendedCommand ? `; run '${liveStartup.recommendedCommand}'` : "";
|
|
361
|
+
const label = (startupSource ?? (live ? "live" : "local")) === "live" ? "live" : "local";
|
|
362
|
+
return `${supervisor}${label} startup reports service ${state}${action} (logs: ${serviceLogPath()})`;
|
|
314
363
|
}
|
|
315
364
|
return service.installed && !live
|
|
316
365
|
? `${service.summary} — registered but NOT serving; see ${serviceLogPath()} and re-run 'ocx service repair'`
|
|
@@ -680,6 +729,7 @@ export function missingCodexCatalogLines(missingCatalogPath: string | null): str
|
|
|
680
729
|
|
|
681
730
|
/** `mainAccountPolicy`: only `--json` asks; the human report reads the same accounts once via OAuth health. */
|
|
682
731
|
export async function collectStatus(options: { mainAccountPolicy?: boolean } = {}): Promise<CliStatusView> {
|
|
732
|
+
const cliCommand = collectCliPathDiagnostics();
|
|
683
733
|
const configDiagnostics = readConfigDiagnostics();
|
|
684
734
|
const config = configDiagnostics.config;
|
|
685
735
|
const claudeDesktop = {
|
|
@@ -757,14 +807,14 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
|
|
|
757
807
|
// A service can be registered and still not serve: the manager reports the job
|
|
758
808
|
// either way. When the identity-probed live proxy provides an attested startup verdict,
|
|
759
809
|
// prefer it over a shell-local service-manager probe that lacks the service environment.
|
|
760
|
-
const serviceSummary = statusServiceSummary(liveStartup, service, Boolean(live));
|
|
761
810
|
const codexShim = diagnoseCodexShim();
|
|
762
811
|
const codexShimSummary = codexShim.summary;
|
|
763
|
-
const startup = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(config, {
|
|
812
|
+
const { startup, startupSource } = selectStatusStartupHealth(liveStartup, () => collectStartupHealth(config, {
|
|
764
813
|
service,
|
|
765
814
|
shim: codexShim,
|
|
766
815
|
routingKind: getCodexRoutingKind(),
|
|
767
|
-
}));
|
|
816
|
+
}), live?.pid != null ? () => inspectDesktopSupervision({ targetPid: live.pid! }) : undefined, live?.pid);
|
|
817
|
+
const serviceSummary = statusServiceSummary(startup, service, Boolean(live), startupSource);
|
|
768
818
|
const codexPlugins = diagnoseCodexBundledPlugins();
|
|
769
819
|
const lastClamp = loadLastEffortClamp();
|
|
770
820
|
const clampActive = effortClampAppliesToRuntime(lastClamp, resolvedRuntime.runtime);
|
|
@@ -869,7 +919,7 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
|
|
|
869
919
|
|
|
870
920
|
return {
|
|
871
921
|
proxyLabel,
|
|
872
|
-
healthLabel: health.label,
|
|
922
|
+
healthLabel: formatCliStatusHealthLabel(health.label, cliCommand, remoteHub.connected),
|
|
873
923
|
json: {
|
|
874
924
|
...(mainAccountHardLock ? { mainAccountHardLock } : {}),
|
|
875
925
|
schemaVersion: 1,
|
|
@@ -932,6 +982,8 @@ export async function collectStatus(options: { mainAccountPolicy?: boolean } = {
|
|
|
932
982
|
// fact about this install, not about the Codex runtime, and filing it there would
|
|
933
983
|
// print it under the wrong heading (#2701).
|
|
934
984
|
versionSkew,
|
|
985
|
+
cliCommand,
|
|
986
|
+
startupSource,
|
|
935
987
|
},
|
|
936
988
|
};
|
|
937
989
|
}
|
|
@@ -16,7 +16,8 @@ function parseMarker(raw: string): UpdateRestartChildMarker {
|
|
|
16
16
|
if (!value || !Number.isSafeInteger(value.deadlineAt) || !Number.isInteger(value.port)
|
|
17
17
|
|| typeof value.hostname !== "string" || value.port < 1 || value.port > 65535 || typeof value.version !== "string"
|
|
18
18
|
|| value.version === "0.0.0" || !parseStrictSemver(value.version) || !value.home
|
|
19
|
-
|| !Number.isSafeInteger(value.home.revision)
|
|
19
|
+
|| !Number.isSafeInteger(value.home.revision) || value.home.serviceRecord?.schema !== 1
|
|
20
|
+
|| typeof value.home.serviceRecord.digest !== "string" || !/^[a-f0-9]{64}$/.test(value.home.serviceRecord.digest)) throw new Error("update_restart_child_marker_invalid");
|
|
20
21
|
for (const directory of [value.home.config, value.home.codex]) {
|
|
21
22
|
// Windows reports 64-bit NTFS file ids (MFT sequence in the high 16 bits) as doubles above
|
|
22
23
|
// 2^53. Parent and child read them through the same stat API, so any finite integer is a
|
|
@@ -56,7 +57,7 @@ export function admitUpdateRestartChild(argv: string[], io: UpdateRestartChildIo
|
|
|
56
57
|
if (now() >= marker.deadlineAt) throw new Error("update_restart_deadline_expired");
|
|
57
58
|
const bindHost = (host: string) => !host.trim() || /^localhost\.?$/i.test(host.trim()) ? "127.0.0.1" : host.trim();
|
|
58
59
|
if (port !== marker.port || bindHost(hostname) !== bindHost(marker.hostname) || (io.version ?? packageVersion)() !== marker.version) throw new Error("update_restart_child_identity_changed");
|
|
59
|
-
(io.checkHome ?? assertUpdateRestartHome)(marker.home);
|
|
60
|
+
(io.checkHome ?? (home => assertUpdateRestartHome(home, marker.deadlineAt)))(marker.home);
|
|
60
61
|
(io.checkState ?? (expected => {
|
|
61
62
|
assertUpdateRestartConfiguration(expected.hostname);
|
|
62
63
|
const current = readRuntimePort();
|
|
@@ -65,6 +66,7 @@ export function admitUpdateRestartChild(argv: string[], io: UpdateRestartChildIo
|
|
|
65
66
|
throw new Error("update_restart_competing_runtime");
|
|
66
67
|
}
|
|
67
68
|
}))(marker);
|
|
69
|
+
if (now() >= marker.deadlineAt) throw new Error("update_restart_deadline_expired");
|
|
68
70
|
};
|
|
69
71
|
check();
|
|
70
72
|
const lease = (io.acquire ?? (waitMs => acquireOwnershipMutationLease(serviceStatePaths(), { waitMs })))(Math.min(2000, marker.deadlineAt - now()));
|
|
@@ -2,12 +2,15 @@ import { realpathSync, statSync } from "node:fs";
|
|
|
2
2
|
import { readClientConnectionState } from "../client/state";
|
|
3
3
|
import { readConfigDiagnostics } from "../config/diagnostics";
|
|
4
4
|
import { getConfigDir } from "../config/paths";
|
|
5
|
-
import { currentCodexHome
|
|
5
|
+
import { currentCodexHome } from "../service/state";
|
|
6
|
+
import { captureUpdateRestartServiceRecord, type UpdateRestartServiceRecord, type UpdateRestartServiceRecordDeps } from "./update-restart-service-record";
|
|
7
|
+
import { probeUpdateRestartSupervision, type UpdateRestartSupervisionDeps } from "./update-restart-supervision";
|
|
6
8
|
|
|
7
9
|
export interface UpdateRestartHome {
|
|
8
10
|
config: { path: string; dev: number; ino: number };
|
|
9
11
|
codex: { path: string; dev: number; ino: number };
|
|
10
12
|
revision: number;
|
|
13
|
+
serviceRecord: UpdateRestartServiceRecord;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
function physicalDirectory(path: string): UpdateRestartHome["config"] {
|
|
@@ -17,17 +20,32 @@ function physicalDirectory(path: string): UpdateRestartHome["config"] {
|
|
|
17
20
|
return { path: canonical, dev: stat.dev, ino: stat.ino };
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const owner = resolveServiceOwnership();
|
|
24
|
-
if (state.kind !== "none" || owner.kind !== "none") throw new Error("update_restart_owner_unverified");
|
|
25
|
-
return { config: physicalDirectory(getConfigDir()), codex: physicalDirectory(currentCodexHome()), revision: owner.revision };
|
|
23
|
+
export interface UpdateRestartHomeDeps {
|
|
24
|
+
record?: UpdateRestartServiceRecordDeps;
|
|
25
|
+
supervision?: UpdateRestartSupervisionDeps;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
/** Capture eligibility from the same bytes that supply the frozen fingerprint. */
|
|
29
|
+
export function readUpdateRestartHome(deps: UpdateRestartHomeDeps = {}): UpdateRestartHome {
|
|
30
|
+
const { serviceRecord, state, owner } = captureUpdateRestartServiceRecord(deps.record);
|
|
31
|
+
const platform = deps.record?.platform ?? process.platform;
|
|
32
|
+
if (owner.kind !== "none" || (state.kind === "state" && platform !== "darwin" && platform !== "linux")) {
|
|
33
|
+
throw new Error("update_restart_owner_unverified");
|
|
34
|
+
}
|
|
35
|
+
return { config: physicalDirectory(getConfigDir()), codex: physicalDirectory(currentCodexHome()), revision: owner.revision, serviceRecord };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function assertUpdateRestartHome(expected: UpdateRestartHome, deadlineAt = Date.now() + 2000, deps: UpdateRestartHomeDeps = {}): void {
|
|
39
|
+
try {
|
|
40
|
+
const current = readUpdateRestartHome(deps);
|
|
41
|
+
if (JSON.stringify(current) !== JSON.stringify(expected)) throw new Error("changed");
|
|
42
|
+
} catch { throw new Error("update_restart_home_changed"); }
|
|
43
|
+
if (probeUpdateRestartSupervision(deadlineAt, deps.supervision) !== "inactive") throw new Error("update_restart_supervision_unverified");
|
|
44
|
+
// Manager commands may yield to an external writer even though this guard is synchronous.
|
|
45
|
+
try {
|
|
46
|
+
if (JSON.stringify(readUpdateRestartHome(deps)) !== JSON.stringify(expected)) throw new Error("changed");
|
|
47
|
+
} catch { throw new Error("update_restart_home_changed"); }
|
|
48
|
+
if ((deps.supervision?.now ?? Date.now)() >= deadlineAt) throw new Error("update_restart_deadline_expired");
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
/** Read-only eligibility shared by parent pre-stop and child admission. */
|