@linzumi/cli 0.0.98-beta → 0.0.100-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 +157 -47
  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.98-beta --version
61
+ npx -y @linzumi/cli@0.0.100-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -19803,7 +19803,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19803
19803
  var init_version = __esm({
19804
19804
  "src/version.ts"() {
19805
19805
  "use strict";
19806
- linzumiCliVersion = "0.0.98-beta";
19806
+ linzumiCliVersion = "0.0.100-beta";
19807
19807
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19808
19808
  }
19809
19809
  });
@@ -22098,6 +22098,37 @@ var init_runnerConsoleReporter = __esm({
22098
22098
  }
22099
22099
  });
22100
22100
 
22101
+ // src/threadRebuildSerializer.ts
22102
+ function createThreadRebuildSerializer() {
22103
+ const chains = /* @__PURE__ */ new Map();
22104
+ return {
22105
+ async run(key, fn) {
22106
+ const prior = chains.get(key) ?? Promise.resolve();
22107
+ const run = prior.then(fn, fn);
22108
+ const tail = run.then(
22109
+ () => void 0,
22110
+ () => void 0
22111
+ );
22112
+ chains.set(key, tail);
22113
+ try {
22114
+ return await run;
22115
+ } finally {
22116
+ if (chains.get(key) === tail) {
22117
+ chains.delete(key);
22118
+ }
22119
+ }
22120
+ },
22121
+ activeKeyCount() {
22122
+ return chains.size;
22123
+ }
22124
+ };
22125
+ }
22126
+ var init_threadRebuildSerializer = __esm({
22127
+ "src/threadRebuildSerializer.ts"() {
22128
+ "use strict";
22129
+ }
22130
+ });
22131
+
22101
22132
  // src/telemetry.ts
22102
22133
  import { mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
22103
22134
  import { dirname as dirname11, basename as basename7, join as join18 } from "node:path";
@@ -24579,6 +24610,17 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24579
24610
  }
24580
24611
  const dynamicChannelSessions = /* @__PURE__ */ new Map();
24581
24612
  const dynamicChannelSessionAttachInFlight = /* @__PURE__ */ new Map();
24613
+ const dynamicChannelSessionAttachWaiters = /* @__PURE__ */ new Map();
24614
+ const resolveDynamicChannelSessionAttachWaiters = (codexThreadId, session) => {
24615
+ const waiters = dynamicChannelSessionAttachWaiters.get(codexThreadId);
24616
+ if (waiters === void 0) {
24617
+ return;
24618
+ }
24619
+ dynamicChannelSessionAttachWaiters.delete(codexThreadId);
24620
+ for (const waiter of waiters) {
24621
+ waiter(session);
24622
+ }
24623
+ };
24582
24624
  const dynamicChannelSessionCodexClients = /* @__PURE__ */ new Map();
24583
24625
  const codexTurnFailureGuards = /* @__PURE__ */ new Map();
24584
24626
  const lossReportPushes = [];
@@ -24640,6 +24682,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24640
24682
  );
24641
24683
  dynamicChannelSessions.clear();
24642
24684
  dynamicChannelSessionCodexClients.clear();
24685
+ dynamicChannelSessionAttachWaiters.clear();
24643
24686
  const claudeSessions = [...activeClaudeCodeSessions.values()];
24644
24687
  for (const session of claudeSessions) {
24645
24688
  try {
@@ -24687,6 +24730,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24687
24730
  );
24688
24731
  threadRunnerProcesses.clear();
24689
24732
  });
24733
+ const threadRebuildSerializer = createThreadRebuildSerializer();
24690
24734
  const evictStaleDynamicChannelSession = async (codexThreadId, expectClient) => {
24691
24735
  const session = dynamicChannelSessions.get(codexThreadId);
24692
24736
  const boundClient = dynamicChannelSessionCodexClients.get(codexThreadId);
@@ -24716,6 +24760,28 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24716
24760
  }
24717
24761
  return true;
24718
24762
  };
24763
+ const resolveLiveDynamicChannelSession = async (codexThreadId, options2 = {}) => {
24764
+ const existing = dynamicChannelSessions.get(codexThreadId);
24765
+ if (existing !== void 0) {
24766
+ return existing;
24767
+ }
24768
+ const inFlight = dynamicChannelSessionAttachInFlight.get(codexThreadId);
24769
+ if (inFlight !== void 0) {
24770
+ const attached = await inFlight;
24771
+ if (attached !== void 0) {
24772
+ return attached;
24773
+ }
24774
+ }
24775
+ const settled = dynamicChannelSessions.get(codexThreadId);
24776
+ if (settled !== void 0 || options2.waitForSiblingSpawn !== true) {
24777
+ return settled;
24778
+ }
24779
+ return await new Promise((resolve12) => {
24780
+ const waiters = dynamicChannelSessionAttachWaiters.get(codexThreadId) ?? [];
24781
+ waiters.push(resolve12);
24782
+ dynamicChannelSessionAttachWaiters.set(codexThreadId, waiters);
24783
+ });
24784
+ };
24719
24785
  const attachThreadSessionWithCodex = async (args) => {
24720
24786
  const { control, cwd, codexThreadId, sessionCodex } = args;
24721
24787
  const portForwardWatcherRootPid = args.portForwardWatcherRootPid;
@@ -24744,36 +24810,50 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24744
24810
  );
24745
24811
  }
24746
24812
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
24747
- const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => {
24748
- await evictStaleDynamicChannelSession(rebuildArgs.codexThreadId);
24749
- let rebuilt = false;
24750
- for (const message of rebuildArgs.pendingMessages) {
24751
- const reconnectControl = {
24752
- type: "reconnect_thread",
24753
- workspace: workspaceSlug,
24754
- channel: channelSlug,
24755
- threadId: rebuildArgs.kandanThreadId,
24756
- codexThreadId: rebuildArgs.codexThreadId,
24757
- cwd,
24758
- // Carry the conversation context through the rebuild-replay so
24759
- // the respawned worker's developer instructions match what the
24760
- // server would have replayed (parity with the live reconnect
24761
- // control built above at channelSession.linzumiContext).
24762
- ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24763
- ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24764
- sourceSeq: message.seq,
24765
- workDescription: message.body,
24766
- ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24767
- ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24768
- ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24769
- ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24770
- ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24771
- };
24772
- const liveSession = dynamicChannelSessions.get(
24813
+ const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => threadRebuildSerializer.run(
24814
+ rebuildArgs.kandanThreadId,
24815
+ async () => {
24816
+ await evictStaleDynamicChannelSession(
24773
24817
  rebuildArgs.codexThreadId
24774
24818
  );
24775
- try {
24776
- if (liveSession !== void 0 && rebuilt) {
24819
+ let rebuilt = false;
24820
+ for (const message of rebuildArgs.pendingMessages) {
24821
+ const reconnectControl = {
24822
+ type: "reconnect_thread",
24823
+ workspace: workspaceSlug,
24824
+ channel: channelSlug,
24825
+ threadId: rebuildArgs.kandanThreadId,
24826
+ codexThreadId: rebuildArgs.codexThreadId,
24827
+ cwd,
24828
+ // Carry the conversation context through the rebuild-replay so
24829
+ // the respawned worker's developer instructions match what the
24830
+ // server would have replayed (parity with the live reconnect
24831
+ // control built above at channelSession.linzumiContext).
24832
+ ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24833
+ ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24834
+ sourceSeq: message.seq,
24835
+ workDescription: message.body,
24836
+ ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24837
+ ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24838
+ ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24839
+ ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24840
+ ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24841
+ };
24842
+ const replayOntoLiveSession = async (options2) => {
24843
+ const liveSession = await withCodexStartRequestTimeout(
24844
+ "thread/attach",
24845
+ resolveLiveDynamicChannelSession(
24846
+ rebuildArgs.codexThreadId,
24847
+ {
24848
+ waitForSiblingSpawn: options2.waitForSiblingSpawn
24849
+ }
24850
+ )
24851
+ );
24852
+ if (liveSession === void 0) {
24853
+ throw new Error(
24854
+ `dead-worker rebuild could not resolve a live session for codex thread ${rebuildArgs.codexThreadId}`
24855
+ );
24856
+ }
24777
24857
  await liveSession.startThreadMessageTurn({
24778
24858
  seq: message.seq,
24779
24859
  body: message.body,
@@ -24781,22 +24861,45 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24781
24861
  actorUserId: message.actorUserId,
24782
24862
  boundStartTimeout: true
24783
24863
  });
24784
- } else {
24785
- await startThreadRunnerProcess(reconnectControl, cwd);
24864
+ };
24865
+ try {
24866
+ if (rebuilt) {
24867
+ await replayOntoLiveSession({
24868
+ waitForSiblingSpawn: false
24869
+ });
24870
+ } else {
24871
+ const startResult = await startThreadRunnerProcess(
24872
+ reconnectControl,
24873
+ cwd
24874
+ );
24875
+ if (threadStartDelegatedToSibling(startResult)) {
24876
+ log2(
24877
+ "runner.thread_dead_worker_rebuild_replay_on_sibling",
24878
+ {
24879
+ codex_thread_id: rebuildArgs.codexThreadId,
24880
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24881
+ seq: message.seq
24882
+ }
24883
+ );
24884
+ await replayOntoLiveSession({
24885
+ waitForSiblingSpawn: true
24886
+ });
24887
+ }
24888
+ }
24889
+ rebuilt = true;
24890
+ } catch (error) {
24891
+ log2("runner.thread_dead_worker_rebuild_failed", {
24892
+ codex_thread_id: rebuildArgs.codexThreadId,
24893
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24894
+ seq: message.seq,
24895
+ message: error instanceof Error ? error.message : String(error)
24896
+ });
24897
+ return false;
24786
24898
  }
24787
- rebuilt = true;
24788
- } catch (error) {
24789
- log2("runner.thread_dead_worker_rebuild_failed", {
24790
- codex_thread_id: rebuildArgs.codexThreadId,
24791
- kandan_thread_id: rebuildArgs.kandanThreadId,
24792
- seq: message.seq,
24793
- message: error instanceof Error ? error.message : String(error)
24794
- });
24795
- return false;
24796
24899
  }
24900
+ return rebuilt;
24797
24901
  }
24798
- return rebuilt;
24799
- } : void 0;
24902
+ ) : void 0;
24800
24903
  const session = await attachChannelSession({
24801
24904
  kandan,
24802
24905
  codex: sessionCodex,
@@ -24854,6 +24957,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24854
24957
  });
24855
24958
  dynamicChannelSessions.set(codexThreadId, session);
24856
24959
  dynamicChannelSessionCodexClients.set(codexThreadId, sessionCodex);
24960
+ resolveDynamicChannelSessionAttachWaiters(codexThreadId, session);
24857
24961
  threadSessionRegistry.upsert({
24858
24962
  workspace: workspaceSlug,
24859
24963
  channel: channelSlug,
@@ -25973,6 +26077,9 @@ function controlTargetsInstance(control, instanceId) {
25973
26077
  function controlInstanceId(control) {
25974
26078
  return "instanceId" in control ? control.instanceId : void 0;
25975
26079
  }
26080
+ function threadStartDelegatedToSibling(startResult) {
26081
+ return startResult !== void 0 && startResult.delegated === true && startResult.threadProcess === "already_started";
26082
+ }
25976
26083
  function shouldUseThreadProcesses(options) {
25977
26084
  if (options.threadProcess?.role === "thread") {
25978
26085
  return false;
@@ -27119,7 +27226,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27119
27226
  ensureCodexProjectTrusted(cwd.cwd);
27120
27227
  }
27121
27228
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
27122
- const startedThreadSession = onStartedThread === void 0 ? void 0 : await onStartedThread(control, cwd.cwd, codexThreadId);
27229
+ const startedThreadSession = onStartedThread === void 0 ? void 0 : await withCodexStartRequestTimeout(
27230
+ "thread/attach",
27231
+ onStartedThread(control, cwd.cwd, codexThreadId)
27232
+ );
27123
27233
  if (workDescription !== void 0) {
27124
27234
  const rootSeq = integerValue(control.rootSeq);
27125
27235
  const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
@@ -27811,10 +27921,9 @@ async function startCodexProviderInstance(args) {
27811
27921
  let startedThreadSession;
27812
27922
  if (codexThreadId !== void 0 && args.onStartedThread !== void 0) {
27813
27923
  args.setStartupStage("binding_kandan_thread");
27814
- startedThreadSession = await args.onStartedThread(
27815
- args.control,
27816
- args.cwd,
27817
- codexThreadId
27924
+ startedThreadSession = await withCodexStartRequestTimeout(
27925
+ "thread/attach",
27926
+ args.onStartedThread(args.control, args.cwd, codexThreadId)
27818
27927
  );
27819
27928
  }
27820
27929
  if (codexThreadId !== void 0 && workDescription !== void 0) {
@@ -31130,6 +31239,7 @@ var init_runner = __esm({
31130
31239
  init_runnerLockTakeover();
31131
31240
  init_runnerConsoleReporter();
31132
31241
  init_version();
31242
+ init_threadRebuildSerializer();
31133
31243
  init_telemetry();
31134
31244
  init_mcpConfig();
31135
31245
  init_linzumiApiClient();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.98-beta",
3
+ "version": "0.0.100-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": {