@bitkyc08/opencodex 2.50.0 → 2.52.0-preview.20260911
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 +222 -71
- package/gui/dist/assets/{index-C39tnjXO.js → index-Dx0xv2EA.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/qoder/adapter.ts +69 -1
- package/src/adapters/qoder/scaffold-guard.ts +233 -0
- package/src/claude/agents-inject.ts +29 -5
- package/src/claude/desktop-3p.ts +31 -3
- package/src/claude/gateway-cache.ts +12 -21
- package/src/cli/capabilities.ts +28 -0
- package/src/cli/claude-agent-startup-sync.ts +26 -1
- package/src/cli/claude.ts +138 -20
- package/src/cli/config-command.ts +67 -1
- package/src/cli/connect.ts +181 -14
- package/src/cli/dispatch.ts +53 -9
- package/src/cli/doctor.ts +9 -2
- package/src/cli/ensure-desired-integrations.ts +10 -0
- package/src/cli/gui-pair-client.ts +1 -12
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +367 -0
- package/src/cli/index.ts +94 -30
- package/src/cli/launcher-context.ts +1 -1
- package/src/cli/registry.ts +43 -3
- package/src/cli/status.ts +325 -5
- package/src/cli/version-skew.ts +4 -1
- package/src/cli.ts +2 -2
- package/src/client/catalog-compatibility.ts +192 -0
- package/src/client/connect.ts +31 -0
- package/src/client/hub-client.ts +52 -0
- package/src/client/hub-state.ts +214 -0
- package/src/codex/account-usability.ts +48 -12
- package/src/codex/auth-api.ts +49 -5
- package/src/codex/catalog/effort.ts +67 -8
- package/src/codex/catalog/sync.ts +85 -0
- package/src/codex/codex-write-lock.ts +11 -2
- package/src/codex/desired-state.ts +47 -1
- package/src/codex/inject-coordination.ts +10 -5
- package/src/codex/inject.ts +26 -10
- package/src/codex/loopback-target.ts +45 -0
- package/src/codex/routing.ts +48 -1
- package/src/codex/runtime.ts +37 -3
- package/src/codex/sync.ts +29 -9
- package/src/codex/warmup.ts +21 -4
- package/src/config/pending-teardown.ts +1 -1
- package/src/config.ts +126 -12
- package/src/generated/compatibility-version.json +136 -76
- package/src/grok/status.ts +9 -1
- package/src/integrations/config-io.ts +54 -1
- package/src/lib/bun-runtime.ts +1 -1
- package/src/lib/gui-pair-capability.ts +27 -0
- package/src/lib/local-destinations.ts +162 -0
- package/src/lib/package-tree-integrity.ts +1 -1
- package/src/lib/process-control.ts +130 -20
- package/src/lib/service-secrets.ts +28 -0
- package/src/lib/test-home-guard.ts +49 -0
- package/src/providers/opencode-go-transport.ts +9 -1
- package/src/providers/quota.ts +5 -1
- package/src/providers/registry.ts +34 -5
- package/src/remote/hub-state.ts +182 -0
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -3
- package/src/server/claude-messages.ts +7 -1
- package/src/server/hub-state.ts +98 -0
- package/src/server/index.ts +124 -6
- package/src/server/management/api-access.ts +14 -3
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/cursor-integration-routes.ts +13 -4
- package/src/server/proxy-liveness.ts +7 -1
- package/src/server/request-log-conversation.ts +41 -1
- package/src/server/responses/codex-auth-error.ts +18 -1
- package/src/server/responses/codex-ws-exchange.ts +36 -4
- package/src/server/responses/codex-ws-wire.ts +75 -4
- package/src/server/responses/compact.ts +20 -9
- package/src/server/responses/core.ts +57 -10
- package/src/server/responses/policy-fallback.ts +7 -1
- package/src/server/system-env-shell.ts +14 -2
- package/src/server/system-env.ts +106 -14
- package/src/service.ts +906 -94
- package/src/types/config.ts +57 -4
- package/src/update/badge.ts +3 -2
- package/src/update/index.ts +317 -64
- package/src/update/install-detection.d.mts +6 -0
- package/src/update/install-detection.mjs +73 -0
- package/src/update/job.ts +101 -49
- package/src/update/pnpm-global-install.d.mts +144 -0
- package/src/update/pnpm-global-install.mjs +591 -0
- package/src/update/pnpm-invocation.d.mts +43 -0
- package/src/update/pnpm-invocation.mjs +141 -0
- package/src/update/registry-integrity.d.mts +16 -0
- package/src/update/registry-integrity.mjs +37 -0
- package/src/update/transactional-install.d.mts +1 -1
- package/src/update/transactional-install.mjs +101 -7
- package/src/update/tray-update-plan.mjs +1 -1
- package/src/vision/plan.ts +13 -3
- package/src/vision/routed-describe.ts +51 -20
package/bin/ocx.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* opencodex
|
|
3
|
+
* opencodex published-package bin launcher.
|
|
4
4
|
*
|
|
5
5
|
* The package source is TypeScript that runs on the Bun runtime. To let
|
|
6
|
-
*
|
|
6
|
+
* global npm and pnpm installs of `@bitkyc08/opencodex` work without a separately-installed Bun,
|
|
7
7
|
* we bundle the runtime via the `bun` npm dependency and exec it from this
|
|
8
8
|
* Node shim. (Dev still runs `bun run src/cli/index.ts` directly via the shebang on
|
|
9
|
-
* src/cli/index.ts — only the published npm `bin` routes through here.)
|
|
9
|
+
* src/cli/index.ts — only the published npm/pnpm `bin` routes through here.)
|
|
10
10
|
*/
|
|
11
11
|
import { spawn, spawnSync } from "node:child_process";
|
|
12
12
|
import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs";
|
|
@@ -20,6 +20,14 @@ import { dirname, join, resolve } from "node:path";
|
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { isRealBunBinary } from "../src/lib/bun-binary-validator.mjs";
|
|
22
22
|
import { npmInvocation } from "../src/update/npm-invocation.mjs";
|
|
23
|
+
import { pnpmInvocationForPath, resolvePnpmCommands } from "../src/update/pnpm-invocation.mjs";
|
|
24
|
+
import { detectInstallFromPath } from "../src/update/install-detection.mjs";
|
|
25
|
+
import {
|
|
26
|
+
pnpmOwnerInvocation,
|
|
27
|
+
resolvePnpmGlobalOwner,
|
|
28
|
+
runPnpmGlobalUpdate,
|
|
29
|
+
} from "../src/update/pnpm-global-install.mjs";
|
|
30
|
+
import { checkRegistryPackageIntegrity } from "../src/update/registry-integrity.mjs";
|
|
23
31
|
import { hasPendingTeardownIn } from "../src/config/pending-teardown-names.mjs";
|
|
24
32
|
import {
|
|
25
33
|
npmCachePreflightFailureMessage,
|
|
@@ -44,6 +52,7 @@ try {
|
|
|
44
52
|
}
|
|
45
53
|
const require = createRequire(import.meta.url);
|
|
46
54
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
const installMethod = detectInstallFromPath(here, { exists: existsSync });
|
|
47
56
|
const cliPath = join(here, "..", "src", "cli", "index.ts");
|
|
48
57
|
const NODE_LAUNCH_CONTEXT_ENV = "OCX_NODE_LAUNCH_CONTEXT";
|
|
49
58
|
const NODE_LAUNCH_PROOF_PREFIX = "--ocx-internal-launch-proof=";
|
|
@@ -53,7 +62,7 @@ function isNodeModulesInstall() {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
function isBunGlobalInstall() {
|
|
56
|
-
return
|
|
65
|
+
return installMethod === "bun";
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
function currentPackageVersion() {
|
|
@@ -102,10 +111,9 @@ function historyRestoreIncomplete() {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
function repairCodexShimIfNeeded() {
|
|
114
|
+
function repairCodexShimIfNeeded(launcherPath = fileURLToPath(import.meta.url)) {
|
|
106
115
|
if (!shouldRepairCodexShim()) return;
|
|
107
|
-
const
|
|
108
|
-
const res = spawnSync(process.execPath, [launcher, "codex-shim", "install"], {
|
|
116
|
+
const res = spawnSync(process.execPath, [launcherPath, "codex-shim", "install"], {
|
|
109
117
|
stdio: "inherit",
|
|
110
118
|
windowsHide: true,
|
|
111
119
|
});
|
|
@@ -135,34 +143,116 @@ function runTrayLifecycle(launcher, action) {
|
|
|
135
143
|
});
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
function shellQuote(value) {
|
|
147
|
+
if (process.platform === "win32") return `"${value.replaceAll("\"", "\\\"")}"`;
|
|
148
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function launcherStartHint(launcher, port) {
|
|
152
|
+
return `${shellQuote(process.execPath)} ${shellQuote(launcher)} start --port ${Math.trunc(port)}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
138
155
|
function runNpmSelfUpdate() {
|
|
156
|
+
return runPackageManagerSelfUpdate("npm");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function runPnpmSelfUpdate() {
|
|
160
|
+
return runPackageManagerSelfUpdate("pnpm");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function runningPnpmShimPath() {
|
|
164
|
+
const invoked = process.argv[1];
|
|
165
|
+
if (!invoked) return undefined;
|
|
166
|
+
const name = invoked.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase();
|
|
167
|
+
if (!new Set(["ocx", "opencodex", "ocx.cmd", "opencodex.cmd", "ocx.ps1", "opencodex.ps1"]).has(name ?? "")) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
return resolve(invoked);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function runPackageManagerSelfUpdate(manager) {
|
|
139
174
|
const current = currentPackageVersion();
|
|
140
175
|
const tag = updateTag(current);
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
let owner;
|
|
177
|
+
if (manager === "pnpm") {
|
|
178
|
+
const ownerResult = resolvePnpmGlobalOwner({
|
|
179
|
+
packageName: PKG,
|
|
180
|
+
packagePath: resolve(here, ".."),
|
|
181
|
+
commandPaths: resolvePnpmCommands(),
|
|
182
|
+
runningShimPath: runningPnpmShimPath(),
|
|
183
|
+
runPnpm: (commandPath, args, capture = false) => {
|
|
184
|
+
const invocation = pnpmInvocationForPath(commandPath, args);
|
|
185
|
+
if (!invocation) return { status: 1 };
|
|
186
|
+
return spawnSync(invocation.file, invocation.args, {
|
|
187
|
+
stdio: capture ? "pipe" : "ignore",
|
|
188
|
+
encoding: "utf8",
|
|
189
|
+
timeout: 20_000,
|
|
190
|
+
windowsHide: true,
|
|
191
|
+
...invocation.options,
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
if (!ownerResult.ok) {
|
|
196
|
+
console.error(`opencodex: ${ownerResult.reason}; aborting before stopping the proxy.`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
owner = ownerResult.owner;
|
|
200
|
+
}
|
|
201
|
+
const managerInvocation = args => manager === "pnpm"
|
|
202
|
+
? pnpmOwnerInvocation(owner, args)
|
|
203
|
+
: npmInvocation(args);
|
|
204
|
+
const latestInvocation = managerInvocation(["view", `${PKG}@${tag}`, "version"]);
|
|
205
|
+
const installArgs = manager === "pnpm"
|
|
206
|
+
? ["add", "-g", "--allow-build=bun", `${PKG}@${tag}`]
|
|
207
|
+
: ["install", "-g", `${PKG}@${tag}`];
|
|
208
|
+
const installInvocation = managerInvocation(installArgs);
|
|
143
209
|
if (!latestInvocation || !installInvocation) {
|
|
144
|
-
console.error(
|
|
210
|
+
console.error(`opencodex: could not resolve ${manager} from a trusted absolute PATH entry; aborting before stopping the proxy.`);
|
|
145
211
|
process.exit(1);
|
|
146
212
|
}
|
|
147
213
|
const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, {
|
|
148
214
|
encoding: "utf8",
|
|
149
215
|
timeout: 12000,
|
|
150
216
|
windowsHide: true,
|
|
217
|
+
...(latestInvocation.env ? { env: latestInvocation.env } : {}),
|
|
151
218
|
...latestInvocation.options,
|
|
152
219
|
});
|
|
153
|
-
const latest = latestResult.status === 0 ? latestResult.stdout.trim() : "";
|
|
220
|
+
const latest = latestResult.status === 0 && typeof latestResult.stdout === "string" ? latestResult.stdout.trim() : "";
|
|
154
221
|
|
|
155
|
-
console.log(`opencodex v${current} (installed via
|
|
222
|
+
console.log(`opencodex v${current} (installed via ${manager}, tag ${tag})`);
|
|
156
223
|
if (latest && latest === current) {
|
|
157
224
|
console.log(`Already on the latest ${tag} version (v${latest}).`);
|
|
158
225
|
process.exit(0);
|
|
159
226
|
}
|
|
160
227
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
228
|
+
const integrity = checkRegistryPackageIntegrity(PKG, latest || null, args => {
|
|
229
|
+
const invocation = managerInvocation(args);
|
|
230
|
+
if (!invocation) return { status: 1 };
|
|
231
|
+
return spawnSync(invocation.file, invocation.args, {
|
|
232
|
+
encoding: "utf8",
|
|
233
|
+
timeout: 12000,
|
|
234
|
+
windowsHide: true,
|
|
235
|
+
...(invocation.env ? { env: invocation.env } : {}),
|
|
236
|
+
...invocation.options,
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
if (integrity.ok === false) {
|
|
240
|
+
console.error(`opencodex: ${integrity.reason}; aborting before stopping the proxy.`);
|
|
164
241
|
process.exit(1);
|
|
165
242
|
}
|
|
243
|
+
if (integrity.ok === "skipped") {
|
|
244
|
+
console.warn(`opencodex: integrity pre-flight skipped: ${integrity.reason}. Proceeding best-effort.`);
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`Verified ${PKG}@${latest} integrity metadata ${integrity.integrity.slice(0, 24)}…`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (manager === "npm") {
|
|
250
|
+
const cachePreflight = runNpmCachePreflight();
|
|
251
|
+
if (!cachePreflight.ok) {
|
|
252
|
+
console.error(`opencodex: ${npmCachePreflightFailureMessage(cachePreflight.reason)}. Aborting before stopping the proxy.`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
166
256
|
|
|
167
257
|
// Remember whether a background service manages the proxy BEFORE stopping — `ocx stop`
|
|
168
258
|
// unloads it, so a successful update must refresh and restart it afterwards.
|
|
@@ -177,15 +267,15 @@ function runNpmSelfUpdate() {
|
|
|
177
267
|
* may be re-registered and require elevation.
|
|
178
268
|
*/
|
|
179
269
|
function serviceRefreshArgs() {
|
|
180
|
-
return [
|
|
270
|
+
return [postUpdateLauncher, "service", "repair"];
|
|
181
271
|
}
|
|
182
272
|
/** Register from scratch, preserving the recorded backend. Only for a genuinely absent service. */
|
|
183
273
|
function serviceInstallArgs() {
|
|
184
274
|
try {
|
|
185
275
|
const state = JSON.parse(readFileSync(serviceStatePath, "utf8"));
|
|
186
|
-
if (state.backend === "native") return [
|
|
276
|
+
if (state.backend === "native") return [postUpdateLauncher, "service", "install", "--native"];
|
|
187
277
|
} catch { /* missing or corrupt — fall through to default */ }
|
|
188
|
-
return [
|
|
278
|
+
return [postUpdateLauncher, "service", "install"];
|
|
189
279
|
}
|
|
190
280
|
/**
|
|
191
281
|
* Structured "is a service actually registered?" answer.
|
|
@@ -262,16 +352,23 @@ function runNpmSelfUpdate() {
|
|
|
262
352
|
// get it from one place.
|
|
263
353
|
|
|
264
354
|
const launcher = fileURLToPath(import.meta.url);
|
|
355
|
+
// The pnpm owner preflight has verified this package tree and global group. Keep that exact
|
|
356
|
+
// package path as the recovery starting point; a path returned by the pnpm transaction
|
|
357
|
+
// replaces it only after the new tree and shims have been verified.
|
|
358
|
+
let postUpdateLauncher = manager === "pnpm" && owner
|
|
359
|
+
? join(owner.packagePath, "bin", "ocx.mjs")
|
|
360
|
+
: launcher;
|
|
361
|
+
let postUpdateLauncherUsable = true;
|
|
265
362
|
|
|
266
363
|
function startProxyDirectly() {
|
|
267
|
-
if (!existsSync(
|
|
364
|
+
if (!postUpdateLauncherUsable || !existsSync(postUpdateLauncher)) {
|
|
268
365
|
console.error("opencodex: cannot restart the proxy because the launcher is missing; reinstall opencodex manually.");
|
|
269
366
|
return;
|
|
270
367
|
}
|
|
271
368
|
const env = { ...process.env };
|
|
272
369
|
delete env.OCX_SERVICE;
|
|
273
370
|
console.log(`Attempting to restart the proxy on port ${bakePort}.`);
|
|
274
|
-
const child = spawn(process.execPath, [
|
|
371
|
+
const child = spawn(process.execPath, [postUpdateLauncher, "start", "--port", String(bakePort)], {
|
|
275
372
|
detached: true,
|
|
276
373
|
stdio: "ignore",
|
|
277
374
|
windowsHide: true,
|
|
@@ -296,7 +393,7 @@ function runNpmSelfUpdate() {
|
|
|
296
393
|
// diagnostic says the service is genuinely absent. Installing after ANY repair
|
|
297
394
|
// failure would resurrect the elevation prompt this change exists to avoid, and
|
|
298
395
|
// could re-register a service the user just uninstalled.
|
|
299
|
-
if (svc.status !== 0 && readServiceInstalledFromStatus(
|
|
396
|
+
if (svc.status !== 0 && readServiceInstalledFromStatus(postUpdateLauncher) === false) {
|
|
300
397
|
console.log("No registered service found — installing it instead.");
|
|
301
398
|
svc = spawnSync(process.execPath, serviceInstallArgs(), { stdio: "inherit", windowsHide: true });
|
|
302
399
|
}
|
|
@@ -305,7 +402,7 @@ function runNpmSelfUpdate() {
|
|
|
305
402
|
// Exit 0 can still leave stale/missing assets that never bring the proxy
|
|
306
403
|
// back — match the GUI/CLI fallthrough so /healthz is not left dead.
|
|
307
404
|
try {
|
|
308
|
-
const st = spawnSync(process.execPath, [
|
|
405
|
+
const st = spawnSync(process.execPath, [postUpdateLauncher, "status", "--json"], {
|
|
309
406
|
encoding: "utf8",
|
|
310
407
|
timeout: 20_000,
|
|
311
408
|
windowsHide: true,
|
|
@@ -365,6 +462,10 @@ function runNpmSelfUpdate() {
|
|
|
365
462
|
existsSync(join(configDir(), "ocx.pid")) || existsSync(join(configDir(), "runtime-port.json"));
|
|
366
463
|
|
|
367
464
|
function recoverStoppedRuntimeAfterFailure() {
|
|
465
|
+
if (!postUpdateLauncherUsable) {
|
|
466
|
+
console.error("opencodex: no verified active launcher remains for automatic recovery; reinstall opencodex manually.");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
368
469
|
if (serviceWasInstalled) {
|
|
369
470
|
console.warn("opencodex: update failed after stopping the proxy — restoring the previous background service.");
|
|
370
471
|
refreshBackgroundServiceOrStartDirect();
|
|
@@ -423,62 +524,105 @@ function runNpmSelfUpdate() {
|
|
|
423
524
|
}
|
|
424
525
|
}
|
|
425
526
|
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
const packageDir = resolve(here, "..");
|
|
431
|
-
console.log(`Updating${latest ? ` to v${latest}` : ""} (transactional)...`);
|
|
527
|
+
// npm keeps the existing stage -> verify -> swap -> rollback flow. pnpm owns a
|
|
528
|
+
// content-addressable store and generated global shims, so its path uses pnpm's own
|
|
529
|
+
// global update operation and verifies the active group instead of renaming files.
|
|
530
|
+
console.log(`Updating${latest ? ` to v${latest}` : ""} (${manager === "npm" ? "transactional" : "pnpm-managed"})...`);
|
|
432
531
|
let res;
|
|
433
532
|
try {
|
|
434
|
-
|
|
435
|
-
packageDir,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
533
|
+
if (manager === "npm") {
|
|
534
|
+
const packageDir = resolve(here, "..");
|
|
535
|
+
const tx = transactionalNpmUpdate({
|
|
536
|
+
packageDir,
|
|
537
|
+
pkgName: PKG,
|
|
538
|
+
targetVersion: latest || undefined,
|
|
539
|
+
tag,
|
|
540
|
+
runNpm: (args) => {
|
|
541
|
+
const invocation = npmInvocation(args);
|
|
542
|
+
if (!invocation) return { status: 1 };
|
|
543
|
+
return spawnSync(invocation.file, invocation.args, {
|
|
544
|
+
stdio: "inherit",
|
|
545
|
+
timeout: 180000,
|
|
546
|
+
windowsHide: true,
|
|
547
|
+
...invocation.options,
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
log: (line) => console.log(line),
|
|
551
|
+
});
|
|
552
|
+
postUpdateLauncherUsable = tx.ok
|
|
553
|
+
|| tx.rolledBack === true
|
|
554
|
+
|| ["stage", "verify", "swap-backup"].includes(tx.phase);
|
|
555
|
+
if (tx.ok) {
|
|
556
|
+
res = { status: 0 };
|
|
557
|
+
} else if (tx.phase === "stage" || tx.phase === "verify") {
|
|
558
|
+
// Live tree untouched: report and stop. Nothing to roll back.
|
|
559
|
+
console.error(`opencodex: update aborted before touching the live install (${tx.phase}): ${tx.error}`);
|
|
560
|
+
res = { status: 1 };
|
|
561
|
+
} else {
|
|
562
|
+
console.error(`opencodex: update failed (${tx.phase}): ${tx.error}${tx.rolledBack ? " — previous version restored." : ""}`);
|
|
563
|
+
res = { status: 1 };
|
|
564
|
+
}
|
|
457
565
|
} else {
|
|
458
|
-
|
|
459
|
-
|
|
566
|
+
const update = runPnpmGlobalUpdate({
|
|
567
|
+
packageName: PKG,
|
|
568
|
+
currentVersion: current,
|
|
569
|
+
targetVersion: latest || undefined,
|
|
570
|
+
tag,
|
|
571
|
+
owner,
|
|
572
|
+
runningPackagePath: resolve(here, ".."),
|
|
573
|
+
runPnpm: (args, capture = false) => {
|
|
574
|
+
const invocation = pnpmOwnerInvocation(owner, args);
|
|
575
|
+
if (!invocation) return { status: 1 };
|
|
576
|
+
return spawnSync(invocation.file, invocation.args, {
|
|
577
|
+
stdio: capture ? "pipe" : "inherit",
|
|
578
|
+
encoding: "utf8",
|
|
579
|
+
timeout: 180000,
|
|
580
|
+
windowsHide: true,
|
|
581
|
+
env: invocation.env,
|
|
582
|
+
...invocation.options,
|
|
583
|
+
});
|
|
584
|
+
},
|
|
585
|
+
log: line => console.log(line),
|
|
586
|
+
});
|
|
587
|
+
if (update.ok) {
|
|
588
|
+
// pnpm switches the active global group and updates its shim. Continue recovery
|
|
589
|
+
// through that fresh package tree, not the old group whose launcher is still
|
|
590
|
+
// executing this update.
|
|
591
|
+
postUpdateLauncher = join(update.path, "bin", "ocx.mjs");
|
|
592
|
+
res = { status: 0 };
|
|
593
|
+
} else {
|
|
594
|
+
console.error(`opencodex: ${update.error}${update.rolledBack ? "." : " Manual recovery may be required."}`);
|
|
595
|
+
postUpdateLauncherUsable = Boolean(update.activePath);
|
|
596
|
+
if (update.activePath) postUpdateLauncher = join(update.activePath, "bin", "ocx.mjs");
|
|
597
|
+
res = { status: 1 };
|
|
598
|
+
}
|
|
460
599
|
}
|
|
461
600
|
} catch (error) {
|
|
462
601
|
// An unexpected throw means we cannot prove the live tree is untouched, so the
|
|
463
602
|
// legacy in-place install (which deletes live first) is exactly the wrong rescue —
|
|
464
603
|
// it recreates the #1849 destruction path. Report and stop; the boot probe and the
|
|
465
604
|
// recovery marker cover the swap-window states.
|
|
466
|
-
|
|
467
|
-
`
|
|
468
|
-
`npm install -g --allow-scripts=bun ${PKG}@${tag}
|
|
605
|
+
const manual = manager === "pnpm"
|
|
606
|
+
? `pnpm add -g --allow-build=bun ${PKG}@${tag}`
|
|
607
|
+
: `npm install -g --allow-scripts=bun ${PKG}@${tag}`;
|
|
608
|
+
// An unexpected exception leaves the active package path unproven for either manager.
|
|
609
|
+
// Do not run service/tray/proxy recovery through a possibly half-swapped tree.
|
|
610
|
+
postUpdateLauncherUsable = false;
|
|
611
|
+
console.error(`opencodex: ${manager} update failed unexpectedly (${error?.message ?? error}). ` +
|
|
612
|
+
`The live install was not knowingly modified; run 'ocx update' again or reinstall with ${manual}.`);
|
|
469
613
|
res = { status: 1 };
|
|
470
614
|
}
|
|
471
615
|
if (res.status === 0) {
|
|
472
616
|
console.log(`\nUpdated${latest ? ` to v${latest}` : ""}.`);
|
|
473
|
-
repairCodexShimIfNeeded();
|
|
617
|
+
repairCodexShimIfNeeded(postUpdateLauncher);
|
|
474
618
|
if (trayBeforeUpdate.refreshAfterReplacement) {
|
|
475
|
-
const tray = spawnSync(process.execPath, [
|
|
619
|
+
const tray = spawnSync(process.execPath, [postUpdateLauncher, ...trayBeforeUpdate.installArgs], {
|
|
476
620
|
stdio: "inherit",
|
|
477
621
|
windowsHide: true,
|
|
478
622
|
});
|
|
479
623
|
if (tray.status !== 0) {
|
|
480
624
|
console.warn("opencodex: Windows tray refresh failed. Run: ocx tray install");
|
|
481
|
-
if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(
|
|
625
|
+
if (trayBeforeUpdate.restoreOnFailure && postUpdateLauncherUsable) runTrayLifecycle(postUpdateLauncher, "start");
|
|
482
626
|
}
|
|
483
627
|
}
|
|
484
628
|
// The stop above unloaded any managed service; refresh via the freshly-installed
|
|
@@ -487,19 +631,22 @@ function runNpmSelfUpdate() {
|
|
|
487
631
|
console.log("Refreshing the background service with the updated files...");
|
|
488
632
|
refreshBackgroundServiceOrStartDirect();
|
|
489
633
|
} else {
|
|
490
|
-
console.log(
|
|
634
|
+
console.log(`Restart the proxy: ${launcherStartHint(postUpdateLauncher, bakePort)}`);
|
|
491
635
|
}
|
|
492
636
|
process.exit(0);
|
|
493
637
|
}
|
|
494
|
-
if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(
|
|
638
|
+
if (trayBeforeUpdate.restoreOnFailure && postUpdateLauncherUsable) runTrayLifecycle(postUpdateLauncher, "start");
|
|
495
639
|
recoverStoppedRuntimeAfterFailure();
|
|
496
|
-
|
|
640
|
+
const manual = manager === "pnpm"
|
|
641
|
+
? `pnpm add -g --allow-build=bun ${PKG}@${tag}`
|
|
642
|
+
: `npm install -g --allow-scripts=bun ${PKG}@${tag}`;
|
|
643
|
+
console.error(`\nUpdate failed (${manager} exit ${res.status ?? "?"}). Try manually: ${manual}`);
|
|
497
644
|
process.exit(1);
|
|
498
645
|
}
|
|
499
646
|
|
|
500
647
|
function bunBinDir() {
|
|
501
648
|
// Resolve the `bun` dependency's directory without hardcoding the platform
|
|
502
|
-
// package —
|
|
649
|
+
// package — the package manager's os/cpu/libc resolution already picked the right @oven/bun-*.
|
|
503
650
|
return dirname(require.resolve("bun/package.json"));
|
|
504
651
|
}
|
|
505
652
|
|
|
@@ -511,7 +658,7 @@ const BUN_RUNTIME_SOURCE_ENV = "OCX_BUN_RUNTIME_SOURCE";
|
|
|
511
658
|
const BUN_RUNTIME_PATH_ENV = "OCX_BUN_RUNTIME_PATH";
|
|
512
659
|
|
|
513
660
|
function findBunBinary(bunDir) {
|
|
514
|
-
// The
|
|
661
|
+
// The bundled `bun` package ships the binary as bin/bun.exe on every platform;
|
|
515
662
|
// probe bin/bun too for forward compatibility.
|
|
516
663
|
for (const name of ["bun.exe", "bun"]) {
|
|
517
664
|
const p = join(bunDir, "bin", name);
|
|
@@ -521,12 +668,15 @@ function findBunBinary(bunDir) {
|
|
|
521
668
|
}
|
|
522
669
|
|
|
523
670
|
function fail(msg) {
|
|
671
|
+
const reinstall = installMethod === "pnpm"
|
|
672
|
+
? "pnpm add -g --allow-build=bun @bitkyc08/opencodex"
|
|
673
|
+
: "npm install -g --allow-scripts=bun @bitkyc08/opencodex";
|
|
524
674
|
console.error(
|
|
525
675
|
`opencodex: ${msg}\n` +
|
|
526
676
|
"The bundled Bun runtime could not be prepared. This usually means the\n" +
|
|
527
|
-
"install skipped lifecycle scripts (
|
|
528
|
-
"
|
|
529
|
-
|
|
677
|
+
"install skipped lifecycle scripts (for example npm blocked bun's postinstall\n" +
|
|
678
|
+
"or pnpm did not approve bun's build) or optional dependencies. Reinstall with:\n" +
|
|
679
|
+
` ${reinstall}\n` +
|
|
530
680
|
"(use sudo if the original install used sudo; without --ignore-scripts\n" +
|
|
531
681
|
"and without --omit=optional / optional=false)"
|
|
532
682
|
);
|
|
@@ -534,7 +684,7 @@ function fail(msg) {
|
|
|
534
684
|
}
|
|
535
685
|
|
|
536
686
|
function resolveBun({ allowInstall = true } = {}) {
|
|
537
|
-
// Keep direct
|
|
687
|
+
// Keep direct package-launcher starts aligned with durable service/shim installs:
|
|
538
688
|
// a valid explicit runtime must win even when the bundled dependency exists.
|
|
539
689
|
const override = process.env[BUN_OVERRIDE_ENV]?.trim();
|
|
540
690
|
if (override) {
|
|
@@ -566,7 +716,7 @@ function resolveBun({ allowInstall = true } = {}) {
|
|
|
566
716
|
return { path: bin, source: "bundled" };
|
|
567
717
|
}
|
|
568
718
|
|
|
569
|
-
// `ocx update --help` prints usage and exits WITHOUT side effects. The
|
|
719
|
+
// `ocx update --help` prints usage and exits WITHOUT side effects. The Node launcher
|
|
570
720
|
// intercepts `update` before the Bun CLI starts, so the help short-circuit must live
|
|
571
721
|
// here too — otherwise --help runs the real self-update, stops the proxy, and drops
|
|
572
722
|
// in-flight routed streams (issue #168).
|
|
@@ -584,13 +734,14 @@ if (codexCliUpdateInspection && typeof process.versions.bun === "string") {
|
|
|
584
734
|
}
|
|
585
735
|
|
|
586
736
|
if (process.argv[2] === "update" && isNodeModulesInstall() && !isBunGlobalInstall()) {
|
|
587
|
-
runNpmSelfUpdate();
|
|
737
|
+
if (installMethod === "npm") runNpmSelfUpdate();
|
|
738
|
+
if (installMethod === "pnpm") runPnpmSelfUpdate();
|
|
588
739
|
}
|
|
589
740
|
|
|
590
741
|
// #1849 boot probe: a prior update that lost power (or double-faulted) mid-swap leaves a
|
|
591
742
|
// backup sibling and a broken live tree. Restore before anything tries to run from the
|
|
592
743
|
// broken tree; reap stale backups once the live tree verifies healthy.
|
|
593
|
-
if (!codexCliUpdateInspection && isNodeModulesInstall() && !isBunGlobalInstall()) {
|
|
744
|
+
if (!codexCliUpdateInspection && installMethod === "npm" && isNodeModulesInstall() && !isBunGlobalInstall()) {
|
|
594
745
|
try {
|
|
595
746
|
const probe = bootRestoreProbe(resolve(here, ".."));
|
|
596
747
|
if (probe.action === "restored") {
|