@linzumi/cli 0.0.106-beta → 0.0.107-beta

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/README.md CHANGED
@@ -58,7 +58,7 @@ Install the CLI or run it with `npx`:
58
58
  ```bash
59
59
  npm install -g @linzumi/cli@latest
60
60
  npx -y @linzumi/cli@latest signup
61
- npx -y @linzumi/cli@0.0.106-beta --version
61
+ npx -y @linzumi/cli@0.0.107-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -15076,6 +15076,15 @@ import { createServer } from "node:net";
15076
15076
  import { homedir as homedir8 } from "node:os";
15077
15077
  import { join as join10 } from "node:path";
15078
15078
  import { WebSocket as NodeWebSocket } from "ws";
15079
+ function pingWebSocketIfSupported(websocket) {
15080
+ const candidate = websocket;
15081
+ if (typeof candidate.ping === "function" && candidate.readyState === WebSocket.OPEN) {
15082
+ try {
15083
+ candidate.ping();
15084
+ } catch {
15085
+ }
15086
+ }
15087
+ }
15079
15088
  async function chooseLoopbackPort() {
15080
15089
  return new Promise((resolve12, reject) => {
15081
15090
  const server = createServer();
@@ -15363,7 +15372,13 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
15363
15372
  pending.forEach((pendingRequest) => pendingRequest.reject(error));
15364
15373
  pending.clear();
15365
15374
  };
15375
+ const keepalive = setInterval(
15376
+ () => pingWebSocketIfSupported(websocket),
15377
+ codexAppServerKeepaliveIntervalMs
15378
+ );
15379
+ keepalive.unref?.();
15366
15380
  const handleConnectionClosed = (message) => {
15381
+ clearInterval(keepalive);
15367
15382
  rejectPending(message);
15368
15383
  if (state.closeError !== void 0) {
15369
15384
  return;
@@ -15443,7 +15458,10 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
15443
15458
  }
15444
15459
  closeCallbacks.add(callback);
15445
15460
  },
15446
- close: () => websocket.close()
15461
+ close: () => {
15462
+ clearInterval(keepalive);
15463
+ websocket.close();
15464
+ }
15447
15465
  };
15448
15466
  }
15449
15467
  function parseCodexAppServerFrame(data) {
@@ -15653,7 +15671,7 @@ function readyzUrlForWebsocket(websocketUrl) {
15653
15671
  parsed.hash = "";
15654
15672
  return parsed.toString();
15655
15673
  }
15656
- var codexAppServerWatchdogPollMs, blockedCodexAppServerEnvKeys, codexDisableAppsConfigArgs;
15674
+ var codexAppServerWatchdogPollMs, codexAppServerKeepaliveIntervalMs, blockedCodexAppServerEnvKeys, codexDisableAppsConfigArgs;
15657
15675
  var init_codexAppServer = __esm({
15658
15676
  "src/codexAppServer.ts"() {
15659
15677
  "use strict";
@@ -15663,6 +15681,14 @@ var init_codexAppServer = __esm({
15663
15681
  init_engineChildReaper();
15664
15682
  init_engineParentDeathWatchdog();
15665
15683
  codexAppServerWatchdogPollMs = 2e3;
15684
+ codexAppServerKeepaliveIntervalMs = (() => {
15685
+ const raw = process.env.LINZUMI_CODEX_WS_KEEPALIVE_MS;
15686
+ if (raw === void 0) {
15687
+ return 2e4;
15688
+ }
15689
+ const parsed = Number.parseInt(raw, 10);
15690
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 2e4;
15691
+ })();
15666
15692
  blockedCodexAppServerEnvKeys = [
15667
15693
  "LINZUMI_MCP_ACCESS_TOKEN",
15668
15694
  "LINZUMI_MCP_OWNER_USERNAME"
@@ -19972,7 +19998,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19972
19998
  var init_version = __esm({
19973
19999
  "src/version.ts"() {
19974
20000
  "use strict";
19975
- linzumiCliVersion = "0.0.106-beta";
20001
+ linzumiCliVersion = "0.0.107-beta";
19976
20002
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19977
20003
  }
19978
20004
  });
@@ -22702,6 +22728,9 @@ var init_authCache = __esm({
22702
22728
  });
22703
22729
 
22704
22730
  // src/threadCodexWorkerIpc.ts
22731
+ function isThreadCodexWorkerSessionClosedMessage(message) {
22732
+ return isJsonObject(message) && message.type === "linzumi_thread_codex_worker_session_closed";
22733
+ }
22705
22734
  function bindThreadCodexWorkerIpc(codex, ipc = process) {
22706
22735
  const pendingServerRequests = /* @__PURE__ */ new Map();
22707
22736
  const state = { nextServerRequestId: 1, sendDropLogged: false };
@@ -23654,15 +23683,33 @@ async function runThreadCodexWorker(options) {
23654
23683
  const log2 = makeRunnerLogger(options);
23655
23684
  const started = await startOwnedCodexAppServer(options);
23656
23685
  const codex = await connectCodexAppServer(started.url);
23686
+ const kandanThreadId = options.threadProcess.kandanThreadId;
23657
23687
  const stop = once(() => {
23658
23688
  codex.close();
23659
23689
  started.stop();
23660
23690
  log2.close();
23661
23691
  });
23662
23692
  bindThreadCodexWorkerIpc(codex);
23693
+ codex.onClose?.((error) => {
23694
+ log2("runner.thread_codex_worker_session_closed", {
23695
+ kandanThreadId,
23696
+ reason: error.message
23697
+ });
23698
+ if (process.send !== void 0 && process.connected !== false) {
23699
+ try {
23700
+ process.send({
23701
+ type: "linzumi_thread_codex_worker_session_closed",
23702
+ kandanThreadId,
23703
+ reason: error.message
23704
+ });
23705
+ } catch {
23706
+ }
23707
+ }
23708
+ stop();
23709
+ });
23663
23710
  process.send({
23664
23711
  type: "linzumi_thread_codex_worker_ready",
23665
- kandanThreadId: options.threadProcess.kandanThreadId,
23712
+ kandanThreadId,
23666
23713
  commanderManagedPorts: commanderManagedPortsForStartedCodex(started)
23667
23714
  });
23668
23715
  await waitForThreadCodexWorkerStop(started.process);
@@ -30038,6 +30085,9 @@ function threadRunnerEntryLiveness(entry) {
30038
30085
  if (ipcClosed) {
30039
30086
  return "dead";
30040
30087
  }
30088
+ if (handle.sessionClosed?.() === true) {
30089
+ return "dead";
30090
+ }
30041
30091
  return processIsAlive2(handle.processPid) ? "live" : "dead";
30042
30092
  }
30043
30093
  async function closeThreadRunnerEntry(entry) {
@@ -30095,6 +30145,18 @@ async function spawnLocalThreadRunnerProcess(options) {
30095
30145
  });
30096
30146
  const exitListeners = /* @__PURE__ */ new Set();
30097
30147
  const exitState = { exited: false };
30148
+ const sessionState = { closed: false };
30149
+ child.on("message", (message) => {
30150
+ if (isThreadCodexWorkerSessionClosedMessage(message)) {
30151
+ sessionState.closed = true;
30152
+ writeCliAuditEvent("runner.thread_runner_session_detached", {
30153
+ purpose: "linzumi.thread_runner",
30154
+ kandanThreadId: message.kandanThreadId,
30155
+ reason: message.reason,
30156
+ pid: child.pid
30157
+ });
30158
+ }
30159
+ });
30098
30160
  child.once("exit", (code, signal) => {
30099
30161
  writeCliAuditEvent("process.exit", {
30100
30162
  command: process.execPath,
@@ -30170,6 +30232,7 @@ async function spawnLocalThreadRunnerProcess(options) {
30170
30232
  codex: connectThreadCodexWorkerIpc(child),
30171
30233
  processPid: child.pid,
30172
30234
  commanderManagedPorts: ready2.commanderManagedPorts,
30235
+ sessionClosed: () => sessionState.closed,
30173
30236
  onExit: (listener) => {
30174
30237
  if (exitState.exited) {
30175
30238
  queueMicrotask(listener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.106-beta",
3
+ "version": "0.0.107-beta",
4
4
  "description": "Linzumi CLI \u2014 point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {