@linzumi/cli 0.0.97-beta → 0.0.99-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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +115 -22
  3. package/package.json +1 -1
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.97-beta --version
61
+ npx -y @linzumi/cli@0.0.99-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -14705,7 +14705,16 @@ function linzumiMcpServerConfig(options) {
14705
14705
  ...options.toolScope === void 0 || options.toolScope === "all" ? [] : ["--tool-scope", options.toolScope]
14706
14706
  ],
14707
14707
  env: {
14708
- ...options.accessToken === void 0 ? {} : { LINZUMI_MCP_ACCESS_TOKEN: options.accessToken }
14708
+ ...options.accessToken === void 0 ? {} : { LINZUMI_MCP_ACCESS_TOKEN: options.accessToken },
14709
+ // The Linzumi metadata MCP is launched by codex as a STDIO child whose
14710
+ // `command` may be the Electron app binary. That binary only behaves as a
14711
+ // plain Node process (and thus boots the MCP server instead of a GUI
14712
+ // shell) when ELECTRON_RUN_AS_NODE=1 is present in its env. Newer codex
14713
+ // sanitizes MCP-child env to an allowlist that drops ELECTRON_RUN_AS_NODE,
14714
+ // so we both (a) pin it here and (b) explicitly forward it via
14715
+ // codexMcpConfigArgs / codexMcpConfigToml below. For a real Node `command`
14716
+ // this flag is a harmless no-op, so we always include it.
14717
+ ELECTRON_RUN_AS_NODE: "1"
14709
14718
  }
14710
14719
  };
14711
14720
  }
@@ -14714,7 +14723,16 @@ function codexMcpConfigArgs(config) {
14714
14723
  "-c",
14715
14724
  `mcp_servers.${config.name}.command=${JSON.stringify(config.command)}`,
14716
14725
  "-c",
14717
- `mcp_servers.${config.name}.args=${tomlStringArray(config.args)}`
14726
+ `mcp_servers.${config.name}.args=${tomlStringArray(config.args)}`,
14727
+ // Explicitly forward the server env (ELECTRON_RUN_AS_NODE,
14728
+ // LINZUMI_MCP_ACCESS_TOKEN, …) so codex passes it to the stdio child
14729
+ // regardless of its env-forwarding/sanitization allowlist. Without this,
14730
+ // newer codex strips ELECTRON_RUN_AS_NODE and the Electron-binary `command`
14731
+ // boots a GUI shell that pollutes the MCP stdout JSON-RPC framing.
14732
+ ...Object.entries(config.env).flatMap(([key, value]) => [
14733
+ "-c",
14734
+ `mcp_servers.${config.name}.env.${key}=${JSON.stringify(value)}`
14735
+ ])
14718
14736
  ];
14719
14737
  }
14720
14738
  function linzumiMcpCommandForProcess(processExecPath, scriptPath, execArgv = []) {
@@ -19785,7 +19803,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19785
19803
  var init_version = __esm({
19786
19804
  "src/version.ts"() {
19787
19805
  "use strict";
19788
- linzumiCliVersion = "0.0.97-beta";
19806
+ linzumiCliVersion = "0.0.99-beta";
19789
19807
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19790
19808
  }
19791
19809
  });
@@ -24561,6 +24579,17 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24561
24579
  }
24562
24580
  const dynamicChannelSessions = /* @__PURE__ */ new Map();
24563
24581
  const dynamicChannelSessionAttachInFlight = /* @__PURE__ */ new Map();
24582
+ const dynamicChannelSessionAttachWaiters = /* @__PURE__ */ new Map();
24583
+ const resolveDynamicChannelSessionAttachWaiters = (codexThreadId, session) => {
24584
+ const waiters = dynamicChannelSessionAttachWaiters.get(codexThreadId);
24585
+ if (waiters === void 0) {
24586
+ return;
24587
+ }
24588
+ dynamicChannelSessionAttachWaiters.delete(codexThreadId);
24589
+ for (const waiter of waiters) {
24590
+ waiter(session);
24591
+ }
24592
+ };
24564
24593
  const dynamicChannelSessionCodexClients = /* @__PURE__ */ new Map();
24565
24594
  const codexTurnFailureGuards = /* @__PURE__ */ new Map();
24566
24595
  const lossReportPushes = [];
@@ -24622,6 +24651,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24622
24651
  );
24623
24652
  dynamicChannelSessions.clear();
24624
24653
  dynamicChannelSessionCodexClients.clear();
24654
+ dynamicChannelSessionAttachWaiters.clear();
24625
24655
  const claudeSessions = [...activeClaudeCodeSessions.values()];
24626
24656
  for (const session of claudeSessions) {
24627
24657
  try {
@@ -24698,6 +24728,28 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24698
24728
  }
24699
24729
  return true;
24700
24730
  };
24731
+ const resolveLiveDynamicChannelSession = async (codexThreadId, options2 = {}) => {
24732
+ const existing = dynamicChannelSessions.get(codexThreadId);
24733
+ if (existing !== void 0) {
24734
+ return existing;
24735
+ }
24736
+ const inFlight = dynamicChannelSessionAttachInFlight.get(codexThreadId);
24737
+ if (inFlight !== void 0) {
24738
+ const attached = await inFlight;
24739
+ if (attached !== void 0) {
24740
+ return attached;
24741
+ }
24742
+ }
24743
+ const settled = dynamicChannelSessions.get(codexThreadId);
24744
+ if (settled !== void 0 || options2.waitForSiblingSpawn !== true) {
24745
+ return settled;
24746
+ }
24747
+ return await new Promise((resolve12) => {
24748
+ const waiters = dynamicChannelSessionAttachWaiters.get(codexThreadId) ?? [];
24749
+ waiters.push(resolve12);
24750
+ dynamicChannelSessionAttachWaiters.set(codexThreadId, waiters);
24751
+ });
24752
+ };
24701
24753
  const attachThreadSessionWithCodex = async (args) => {
24702
24754
  const { control, cwd, codexThreadId, sessionCodex } = args;
24703
24755
  const portForwardWatcherRootPid = args.portForwardWatcherRootPid;
@@ -24737,6 +24789,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24737
24789
  threadId: rebuildArgs.kandanThreadId,
24738
24790
  codexThreadId: rebuildArgs.codexThreadId,
24739
24791
  cwd,
24792
+ // Carry the conversation context through the rebuild-replay so
24793
+ // the respawned worker's developer instructions match what the
24794
+ // server would have replayed (parity with the live reconnect
24795
+ // control built above at channelSession.linzumiContext).
24796
+ ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24740
24797
  ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24741
24798
  sourceSeq: message.seq,
24742
24799
  workDescription: message.body,
@@ -24746,19 +24803,42 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24746
24803
  ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24747
24804
  ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24748
24805
  };
24749
- const liveSession = dynamicChannelSessions.get(
24750
- rebuildArgs.codexThreadId
24751
- );
24806
+ const replayOntoLiveSession = async (options2) => {
24807
+ const liveSession = await withCodexStartRequestTimeout(
24808
+ "thread/attach",
24809
+ resolveLiveDynamicChannelSession(rebuildArgs.codexThreadId, {
24810
+ waitForSiblingSpawn: options2.waitForSiblingSpawn
24811
+ })
24812
+ );
24813
+ if (liveSession === void 0) {
24814
+ throw new Error(
24815
+ `dead-worker rebuild could not resolve a live session for codex thread ${rebuildArgs.codexThreadId}`
24816
+ );
24817
+ }
24818
+ await liveSession.startThreadMessageTurn({
24819
+ seq: message.seq,
24820
+ body: message.body,
24821
+ actorSlug: message.actorSlug,
24822
+ actorUserId: message.actorUserId,
24823
+ boundStartTimeout: true
24824
+ });
24825
+ };
24752
24826
  try {
24753
- if (liveSession !== void 0 && rebuilt) {
24754
- await liveSession.startThreadMessageTurn({
24755
- seq: message.seq,
24756
- body: message.body,
24757
- actorSlug: message.actorSlug,
24758
- actorUserId: message.actorUserId
24759
- });
24827
+ if (rebuilt) {
24828
+ await replayOntoLiveSession({ waitForSiblingSpawn: false });
24760
24829
  } else {
24761
- await startThreadRunnerProcess(reconnectControl, cwd);
24830
+ const startResult = await startThreadRunnerProcess(
24831
+ reconnectControl,
24832
+ cwd
24833
+ );
24834
+ if (threadStartDelegatedToSibling(startResult)) {
24835
+ log2("runner.thread_dead_worker_rebuild_replay_on_sibling", {
24836
+ codex_thread_id: rebuildArgs.codexThreadId,
24837
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24838
+ seq: message.seq
24839
+ });
24840
+ await replayOntoLiveSession({ waitForSiblingSpawn: true });
24841
+ }
24762
24842
  }
24763
24843
  rebuilt = true;
24764
24844
  } catch (error) {
@@ -24830,6 +24910,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24830
24910
  });
24831
24911
  dynamicChannelSessions.set(codexThreadId, session);
24832
24912
  dynamicChannelSessionCodexClients.set(codexThreadId, sessionCodex);
24913
+ resolveDynamicChannelSessionAttachWaiters(codexThreadId, session);
24833
24914
  threadSessionRegistry.upsert({
24834
24915
  workspace: workspaceSlug,
24835
24916
  channel: channelSlug,
@@ -25949,6 +26030,9 @@ function controlTargetsInstance(control, instanceId) {
25949
26030
  function controlInstanceId(control) {
25950
26031
  return "instanceId" in control ? control.instanceId : void 0;
25951
26032
  }
26033
+ function threadStartDelegatedToSibling(startResult) {
26034
+ return startResult !== void 0 && startResult.delegated === true && startResult.threadProcess === "already_started";
26035
+ }
25952
26036
  function shouldUseThreadProcesses(options) {
25953
26037
  if (options.threadProcess?.role === "thread") {
25954
26038
  return false;
@@ -26506,7 +26590,10 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
26506
26590
  threadId: codexThreadId,
26507
26591
  ...resumeOverrides
26508
26592
  };
26509
- const resumeResponse = await codex.request("thread/resume", resumeParams);
26593
+ const resumeResponse = await withCodexStartRequestTimeout(
26594
+ "thread/resume",
26595
+ codex.request("thread/resume", resumeParams)
26596
+ );
26510
26597
  if (!("error" in resumeResponse)) {
26511
26598
  return;
26512
26599
  }
@@ -26524,7 +26611,10 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
26524
26611
  `failed to prepare Codex thread before reconnect: ${injected.error.message}`
26525
26612
  );
26526
26613
  }
26527
- const verified = await codex.request("thread/resume", resumeParams);
26614
+ const verified = await withCodexStartRequestTimeout(
26615
+ "thread/resume",
26616
+ codex.request("thread/resume", resumeParams)
26617
+ );
26528
26618
  if ("error" in verified) {
26529
26619
  throw new Error(
26530
26620
  `failed to verify Codex thread before reconnect: ${verified.error.message}`
@@ -27089,7 +27179,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27089
27179
  ensureCodexProjectTrusted(cwd.cwd);
27090
27180
  }
27091
27181
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
27092
- const startedThreadSession = onStartedThread === void 0 ? void 0 : await onStartedThread(control, cwd.cwd, codexThreadId);
27182
+ const startedThreadSession = onStartedThread === void 0 ? void 0 : await withCodexStartRequestTimeout(
27183
+ "thread/attach",
27184
+ onStartedThread(control, cwd.cwd, codexThreadId)
27185
+ );
27093
27186
  if (workDescription !== void 0) {
27094
27187
  const rootSeq = integerValue(control.rootSeq);
27095
27188
  const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
@@ -27115,7 +27208,8 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27115
27208
  seq: sourceSeq,
27116
27209
  body: workDescription,
27117
27210
  actorSlug: identity.actorUsername,
27118
- actorUserId: identity.actorUserId
27211
+ actorUserId: identity.actorUserId,
27212
+ boundStartTimeout: true
27119
27213
  });
27120
27214
  }
27121
27215
  return {
@@ -27780,10 +27874,9 @@ async function startCodexProviderInstance(args) {
27780
27874
  let startedThreadSession;
27781
27875
  if (codexThreadId !== void 0 && args.onStartedThread !== void 0) {
27782
27876
  args.setStartupStage("binding_kandan_thread");
27783
- startedThreadSession = await args.onStartedThread(
27784
- args.control,
27785
- args.cwd,
27786
- codexThreadId
27877
+ startedThreadSession = await withCodexStartRequestTimeout(
27878
+ "thread/attach",
27879
+ args.onStartedThread(args.control, args.cwd, codexThreadId)
27787
27880
  );
27788
27881
  }
27789
27882
  if (codexThreadId !== void 0 && workDescription !== void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.97-beta",
3
+ "version": "0.0.99-beta",
4
4
  "description": "Linzumi CLI — 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": {