@linzumi/cli 0.0.99-beta → 0.0.101-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 +145 -71
  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.99-beta --version
61
+ npx -y @linzumi/cli@0.0.101-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -3361,7 +3361,8 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3361
3361
  resolveReady: void 0,
3362
3362
  rejectReady: void 0,
3363
3363
  reconnectTimer: void 0,
3364
- reconnectAttempts: 0
3364
+ reconnectAttempts: 0,
3365
+ heartbeatTimer: void 0
3365
3366
  };
3366
3367
  const pushTimeoutMs = options.pushTimeoutMs ?? defaultPushTimeoutMs;
3367
3368
  const controlCursorStore = options.controlCursorStore;
@@ -3751,6 +3752,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3751
3752
  state.connected = false;
3752
3753
  const websocket = state.websocket;
3753
3754
  state.websocket = void 0;
3755
+ stopHeartbeat();
3754
3756
  if (websocket !== void 0 && (websocket.readyState === WebSocket.CONNECTING || websocket.readyState === WebSocket.OPEN)) {
3755
3757
  try {
3756
3758
  websocket.close();
@@ -3768,6 +3770,29 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3768
3770
  });
3769
3771
  }
3770
3772
  };
3773
+ const HEARTBEAT_INTERVAL_MS = 3e4;
3774
+ const stopHeartbeat = () => {
3775
+ if (state.heartbeatTimer !== void 0) {
3776
+ clearInterval(state.heartbeatTimer);
3777
+ state.heartbeatTimer = void 0;
3778
+ }
3779
+ };
3780
+ const startHeartbeat = () => {
3781
+ stopHeartbeat();
3782
+ state.heartbeatTimer = setInterval(() => {
3783
+ const websocket = state.websocket;
3784
+ if (websocket === void 0 || websocket.readyState !== WebSocket.OPEN) {
3785
+ return;
3786
+ }
3787
+ const ref = String(state.nextRef);
3788
+ state.nextRef += 1;
3789
+ try {
3790
+ websocket.send(JSON.stringify([null, ref, "phoenix", "heartbeat", {}]));
3791
+ } catch {
3792
+ }
3793
+ }, HEARTBEAT_INTERVAL_MS);
3794
+ state.heartbeatTimer.unref?.();
3795
+ };
3771
3796
  const openSocket = async () => {
3772
3797
  if (state.closed) {
3773
3798
  return;
@@ -3803,6 +3828,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3803
3828
  state.connected = true;
3804
3829
  state.hasEverConnected = true;
3805
3830
  state.reconnectAttempts = 0;
3831
+ startHeartbeat();
3806
3832
  markReady();
3807
3833
  if (state.connectionGeneration > 1) {
3808
3834
  reconnectCallbacks.forEach((callback) => {
@@ -19803,7 +19829,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19803
19829
  var init_version = __esm({
19804
19830
  "src/version.ts"() {
19805
19831
  "use strict";
19806
- linzumiCliVersion = "0.0.99-beta";
19832
+ linzumiCliVersion = "0.0.101-beta";
19807
19833
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19808
19834
  }
19809
19835
  });
@@ -22098,6 +22124,37 @@ var init_runnerConsoleReporter = __esm({
22098
22124
  }
22099
22125
  });
22100
22126
 
22127
+ // src/threadRebuildSerializer.ts
22128
+ function createThreadRebuildSerializer() {
22129
+ const chains = /* @__PURE__ */ new Map();
22130
+ return {
22131
+ async run(key, fn) {
22132
+ const prior = chains.get(key) ?? Promise.resolve();
22133
+ const run = prior.then(fn, fn);
22134
+ const tail = run.then(
22135
+ () => void 0,
22136
+ () => void 0
22137
+ );
22138
+ chains.set(key, tail);
22139
+ try {
22140
+ return await run;
22141
+ } finally {
22142
+ if (chains.get(key) === tail) {
22143
+ chains.delete(key);
22144
+ }
22145
+ }
22146
+ },
22147
+ activeKeyCount() {
22148
+ return chains.size;
22149
+ }
22150
+ };
22151
+ }
22152
+ var init_threadRebuildSerializer = __esm({
22153
+ "src/threadRebuildSerializer.ts"() {
22154
+ "use strict";
22155
+ }
22156
+ });
22157
+
22101
22158
  // src/telemetry.ts
22102
22159
  import { mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
22103
22160
  import { dirname as dirname11, basename as basename7, join as join18 } from "node:path";
@@ -24699,6 +24756,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24699
24756
  );
24700
24757
  threadRunnerProcesses.clear();
24701
24758
  });
24759
+ const threadRebuildSerializer = createThreadRebuildSerializer();
24702
24760
  const evictStaleDynamicChannelSession = async (codexThreadId, expectClient) => {
24703
24761
  const session = dynamicChannelSessions.get(codexThreadId);
24704
24762
  const boundClient = dynamicChannelSessionCodexClients.get(codexThreadId);
@@ -24778,81 +24836,96 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24778
24836
  );
24779
24837
  }
24780
24838
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
24781
- const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => {
24782
- await evictStaleDynamicChannelSession(rebuildArgs.codexThreadId);
24783
- let rebuilt = false;
24784
- for (const message of rebuildArgs.pendingMessages) {
24785
- const reconnectControl = {
24786
- type: "reconnect_thread",
24787
- workspace: workspaceSlug,
24788
- channel: channelSlug,
24789
- threadId: rebuildArgs.kandanThreadId,
24790
- codexThreadId: rebuildArgs.codexThreadId,
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 },
24797
- ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24798
- sourceSeq: message.seq,
24799
- workDescription: message.body,
24800
- ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24801
- ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24802
- ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24803
- ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24804
- ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24805
- };
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
- };
24826
- try {
24827
- if (rebuilt) {
24828
- await replayOntoLiveSession({ waitForSiblingSpawn: false });
24829
- } else {
24830
- const startResult = await startThreadRunnerProcess(
24831
- reconnectControl,
24832
- cwd
24839
+ const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => threadRebuildSerializer.run(
24840
+ rebuildArgs.kandanThreadId,
24841
+ async () => {
24842
+ await evictStaleDynamicChannelSession(
24843
+ rebuildArgs.codexThreadId
24844
+ );
24845
+ let rebuilt = false;
24846
+ for (const message of rebuildArgs.pendingMessages) {
24847
+ const reconnectControl = {
24848
+ type: "reconnect_thread",
24849
+ workspace: workspaceSlug,
24850
+ channel: channelSlug,
24851
+ threadId: rebuildArgs.kandanThreadId,
24852
+ codexThreadId: rebuildArgs.codexThreadId,
24853
+ cwd,
24854
+ // Carry the conversation context through the rebuild-replay so
24855
+ // the respawned worker's developer instructions match what the
24856
+ // server would have replayed (parity with the live reconnect
24857
+ // control built above at channelSession.linzumiContext).
24858
+ ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24859
+ ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24860
+ sourceSeq: message.seq,
24861
+ workDescription: message.body,
24862
+ ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24863
+ ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24864
+ ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24865
+ ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24866
+ ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24867
+ };
24868
+ const replayOntoLiveSession = async (options2) => {
24869
+ const liveSession = await withCodexStartRequestTimeout(
24870
+ "thread/attach",
24871
+ resolveLiveDynamicChannelSession(
24872
+ rebuildArgs.codexThreadId,
24873
+ {
24874
+ waitForSiblingSpawn: options2.waitForSiblingSpawn
24875
+ }
24876
+ )
24833
24877
  );
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
24878
+ if (liveSession === void 0) {
24879
+ throw new Error(
24880
+ `dead-worker rebuild could not resolve a live session for codex thread ${rebuildArgs.codexThreadId}`
24881
+ );
24882
+ }
24883
+ await liveSession.startThreadMessageTurn({
24884
+ seq: message.seq,
24885
+ body: message.body,
24886
+ actorSlug: message.actorSlug,
24887
+ actorUserId: message.actorUserId,
24888
+ boundStartTimeout: true
24889
+ });
24890
+ };
24891
+ try {
24892
+ if (rebuilt) {
24893
+ await replayOntoLiveSession({
24894
+ waitForSiblingSpawn: false
24839
24895
  });
24840
- await replayOntoLiveSession({ waitForSiblingSpawn: true });
24896
+ } else {
24897
+ const startResult = await startThreadRunnerProcess(
24898
+ reconnectControl,
24899
+ cwd
24900
+ );
24901
+ if (threadStartDelegatedToSibling(startResult)) {
24902
+ log2(
24903
+ "runner.thread_dead_worker_rebuild_replay_on_sibling",
24904
+ {
24905
+ codex_thread_id: rebuildArgs.codexThreadId,
24906
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24907
+ seq: message.seq
24908
+ }
24909
+ );
24910
+ await replayOntoLiveSession({
24911
+ waitForSiblingSpawn: true
24912
+ });
24913
+ }
24841
24914
  }
24915
+ rebuilt = true;
24916
+ } catch (error) {
24917
+ log2("runner.thread_dead_worker_rebuild_failed", {
24918
+ codex_thread_id: rebuildArgs.codexThreadId,
24919
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24920
+ seq: message.seq,
24921
+ message: error instanceof Error ? error.message : String(error)
24922
+ });
24923
+ return false;
24842
24924
  }
24843
- rebuilt = true;
24844
- } catch (error) {
24845
- log2("runner.thread_dead_worker_rebuild_failed", {
24846
- codex_thread_id: rebuildArgs.codexThreadId,
24847
- kandan_thread_id: rebuildArgs.kandanThreadId,
24848
- seq: message.seq,
24849
- message: error instanceof Error ? error.message : String(error)
24850
- });
24851
- return false;
24852
24925
  }
24926
+ return rebuilt;
24853
24927
  }
24854
- return rebuilt;
24855
- } : void 0;
24928
+ ) : void 0;
24856
24929
  const session = await attachChannelSession({
24857
24930
  kandan,
24858
24931
  codex: sessionCodex,
@@ -31192,6 +31265,7 @@ var init_runner = __esm({
31192
31265
  init_runnerLockTakeover();
31193
31266
  init_runnerConsoleReporter();
31194
31267
  init_version();
31268
+ init_threadRebuildSerializer();
31195
31269
  init_telemetry();
31196
31270
  init_mcpConfig();
31197
31271
  init_linzumiApiClient();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.99-beta",
3
+ "version": "0.0.101-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": {