@bitkyc08/opencodex 2.6.19 → 2.6.20

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.
@@ -16,7 +16,7 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-Barime1y.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-iWo2gxQ2.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-DDcEW0Cm.css">
21
21
  </head>
22
22
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.6.19",
3
+ "version": "2.6.20",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
package/src/server.ts CHANGED
@@ -444,15 +444,22 @@ async function handleResponses(
444
444
  linkAbortSignal(upstream, turnAc.signal);
445
445
  registerTurn(turnAc);
446
446
  if (recordTerminalOutcomes) {
447
+ // A real terminal was parsed from the (teed) inspection stream — record it as the outcome
448
+ // even if the client has already disconnected: the turn genuinely reached that terminal, so
449
+ // it must log as completed/failed, not be dropped or downgraded to a cancel (#44). A pure
450
+ // client-cancel (no terminal seen) is finalized separately via consumeForInspection's onCancel.
447
451
  const reportNativeTerminal = (status: ResponsesTerminalStatus) => {
448
- if (options.abortSignal?.aborted) {
449
- options.onNativePassthroughCancel?.();
450
- return;
451
- }
452
452
  terminalRecorder?.(status);
453
453
  options.onNativePassthroughTerminal?.(status);
454
454
  };
455
- consumeForInspection(inspectBody, reportNativeTerminal, turnAc.signal, () => unregisterTurn(turnAc), logCtx);
455
+ consumeForInspection(
456
+ inspectBody,
457
+ reportNativeTerminal,
458
+ turnAc.signal,
459
+ () => unregisterTurn(turnAc),
460
+ logCtx,
461
+ () => options.onNativePassthroughCancel?.(),
462
+ );
456
463
  } else {
457
464
  consumeForResponseLogMetadata(inspectBody, logCtx, turnAc.signal, () => unregisterTurn(turnAc));
458
465
  }
@@ -1302,12 +1309,13 @@ export function relaySseWithHeartbeat(
1302
1309
  * Background-consume an SSE stream purely for terminal-outcome inspection (quota tracking).
1303
1310
  * Does not produce output; safe to ignore errors (the client-facing stream is separate).
1304
1311
  */
1305
- function consumeForInspection(
1312
+ export function consumeForInspection(
1306
1313
  body: ReadableStream<Uint8Array>,
1307
1314
  onTerminal: (status: ResponsesTerminalStatus) => void,
1308
1315
  signal?: AbortSignal,
1309
1316
  onDone?: () => void,
1310
1317
  logCtx?: RequestLogContext,
1318
+ onCancel?: () => void,
1311
1319
  ): void {
1312
1320
  const reader = body.getReader();
1313
1321
  const decoder = new TextDecoder();
@@ -1316,13 +1324,21 @@ function consumeForInspection(
1316
1324
  let cancelled = false;
1317
1325
  if (signal) {
1318
1326
  if (signal.aborted) {
1327
+ // Aborted before we could read anything (Codex disconnects the instant it finishes reading).
1328
+ // Finalize as a client-cancel and release the turn — the early return skips pump()'s finally,
1329
+ // so onDone/onCancel must run here or the entry is silently dropped (#44).
1319
1330
  cancelled = true;
1320
1331
  reader.cancel(signal.reason).catch(() => {});
1332
+ onCancel?.();
1333
+ onDone?.();
1321
1334
  return;
1322
1335
  }
1323
1336
  signal.addEventListener("abort", () => {
1337
+ // Mid-drain disconnect: record a client-cancel entry (idempotent downstream) instead of the
1338
+ // suppressed onTerminal path. onDone still fires via pump()'s finally after the read rejects.
1324
1339
  cancelled = true;
1325
1340
  reader.cancel(signal.reason).catch(() => {});
1341
+ onCancel?.();
1326
1342
  }, { once: true });
1327
1343
  }
1328
1344
  const pump = async () => {
@@ -2102,9 +2118,15 @@ export function startServer(port?: number) {
2102
2118
  const listenPort = port ?? config.port ?? 10100;
2103
2119
  setCorsOrigin(listenPort);
2104
2120
 
2121
+ // Canonicalize an explicit "localhost" bind to IPv4 so it matches the injected base_url (which
2122
+ // resolves localhost→127.0.0.1): on Windows `localhost` resolves ::1-first, but the injected URL
2123
+ // is 127.0.0.1, so binding literal "localhost" would reintroduce the F4 refusal. Wildcards
2124
+ // (0.0.0.0/::) and specific hosts are left untouched so intentional exposure is preserved.
2125
+ const bindHost = /^localhost$/i.test(config.hostname ?? "") ? "127.0.0.1" : (config.hostname ?? "127.0.0.1");
2126
+
2105
2127
  const server: Server<WsData> = Bun.serve<WsData>({
2106
2128
  port: listenPort,
2107
- hostname: config.hostname ?? "127.0.0.1",
2129
+ hostname: bindHost,
2108
2130
  idleTimeout: 255,
2109
2131
  async fetch(req, requestServer): Promise<Response> {
2110
2132
  const url = new URL(req.url);
package/src/service.ts CHANGED
@@ -493,12 +493,41 @@ WantedBy=default.target
493
493
  `;
494
494
  }
495
495
 
496
+ /** The per-user runtime dir systemd creates (holds the user-bus socket), or null. */
497
+ function userRuntimeDir(): string | null {
498
+ const fromEnv = process.env.XDG_RUNTIME_DIR;
499
+ if (fromEnv && existsSync(fromEnv)) return fromEnv;
500
+ if (typeof process.getuid === "function") {
501
+ const candidate = `/run/user/${process.getuid()}`;
502
+ if (existsSync(candidate)) return candidate;
503
+ }
504
+ return null;
505
+ }
506
+
507
+ /**
508
+ * SSH sessions frequently start without `XDG_RUNTIME_DIR`/`DBUS_SESSION_BUS_ADDRESS`, so
509
+ * `systemctl --user` can't find the user bus even when systemd is running. Point `XDG_RUNTIME_DIR`
510
+ * at the per-user runtime dir when it exists so the `--user` probe and install commands reach the
511
+ * bus. No-op when already set or when no runtime dir exists (e.g. genuinely non-systemd hosts).
512
+ */
513
+ function ensureUserBusEnv(): void {
514
+ if (process.env.XDG_RUNTIME_DIR) return;
515
+ const dir = userRuntimeDir();
516
+ if (dir) process.env.XDG_RUNTIME_DIR = dir;
517
+ }
518
+
496
519
  function isSystemd(): boolean {
497
520
  try { execSync("systemctl --version", { stdio: "pipe" }); } catch { return false; }
498
- try { execSync("systemctl --user show-environment", { stdio: "pipe" }); return true; } catch { return false; }
521
+ ensureUserBusEnv();
522
+ // Prefer the user-bus probe; but an SSH session without a user D-Bus fails it even when systemd
523
+ // is present (F9). Fall back to the per-user runtime dir existing — a strong signal the user
524
+ // systemd instance is available — so a first-time `ocx service install` isn't wrongly refused.
525
+ try { execSync("systemctl --user show-environment", { stdio: "pipe" }); return true; } catch { /* no user bus in this session */ }
526
+ return userRuntimeDir() !== null;
499
527
  }
500
528
 
501
529
  function installSystemd(): void {
530
+ ensureUserBusEnv(); // reach the user bus over a bare SSH session (F9)
502
531
  const dir = unitDir();
503
532
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
504
533
  if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true });
package/src/update-job.ts CHANGED
@@ -78,10 +78,6 @@ function nodeBin(): string {
78
78
  return process.platform === "win32" ? "node.exe" : "node";
79
79
  }
80
80
 
81
- function ocxBin(): string {
82
- return process.platform === "win32" ? "ocx.cmd" : "ocx";
83
- }
84
-
85
81
  function packageLauncherPath(): string {
86
82
  return join(dirname(fileURLToPath(import.meta.url)), "..", "bin", "ocx.mjs");
87
83
  }
@@ -163,8 +159,11 @@ export function restartCommand(
163
159
  const args = serviceInstalled ? [launcher, "service", "install"] : [launcher, "start"];
164
160
  return { mode, bin, args, display: formatCommand(bin, args) };
165
161
  }
166
- const bin = ocxBin();
167
- const args = serviceInstalled ? ["service", "install"] : ["start"];
162
+ // bun/source installs: restart via the current runtime executable + package launcher (both real
163
+ // .exe files), NOT the `ocx.cmd` shim. Spawning a `.cmd` shell-less throws EINVAL on Windows
164
+ // Node/Bun ≥18.20/20.12 (CVE-2024-27980 hardening) — the same class the npm path (nodeBin) avoids.
165
+ const bin = process.execPath;
166
+ const args = serviceInstalled ? [launcher, "service", "install"] : [launcher, "start"];
168
167
  return { mode, bin, args, display: formatCommand(bin, args) };
169
168
  }
170
169