@linzumi/cli 0.0.101-beta → 0.0.103-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 +292 -146
  3. package/package.json +2 -2
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.101-beta --version
61
+ npx -y @linzumi/cli@0.0.103-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -3634,7 +3634,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3634
3634
  }
3635
3635
  dispatchPushedFrame(topic, name, ref, payload);
3636
3636
  };
3637
- const pushOnOpenSocket = (topic, event, payload, timeoutMs = pushTimeoutMs) => {
3637
+ const pushOnOpenSocket = (topic, event, payload, timeoutMs = pushTimeoutMs, onSent) => {
3638
3638
  const websocket = state.websocket;
3639
3639
  if (websocket === void 0 || websocket.readyState !== WebSocket.OPEN) {
3640
3640
  return Promise.reject(phoenixPushErrors.socketNotOpen());
@@ -3655,6 +3655,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3655
3655
  }, replyBudgetMs);
3656
3656
  pending.set(ref, { event, timer, resolve: resolve12, reject });
3657
3657
  websocket.send(JSON.stringify(frame));
3658
+ onSent?.();
3658
3659
  });
3659
3660
  };
3660
3661
  const mergedJoinPayload = (topic, payloads) => {
@@ -3867,10 +3868,16 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
3867
3868
  }
3868
3869
  );
3869
3870
  });
3870
- const push = async (topic, event, payload) => {
3871
+ const push = async (topic, event, payload, onSent) => {
3871
3872
  const deadlineMs = Date.now() + pushTimeoutMs;
3872
3873
  await awaitReady(event, deadlineMs);
3873
- return pushOnOpenSocket(topic, event, payload, deadlineMs - Date.now());
3874
+ return pushOnOpenSocket(
3875
+ topic,
3876
+ event,
3877
+ payload,
3878
+ deadlineMs - Date.now(),
3879
+ onSent
3880
+ );
3874
3881
  };
3875
3882
  return {
3876
3883
  join: async (topic, payload, options2) => {
@@ -4548,10 +4555,15 @@ function createThreadOutbox(options) {
4548
4555
  let paused = false;
4549
4556
  let acceptingEnqueues = true;
4550
4557
  let stopped = false;
4551
- let drainPromise;
4552
- let wakeSleep;
4553
4558
  let closePromise;
4554
4559
  let journal;
4560
+ const maxInFlightPushes = Math.max(
4561
+ 1,
4562
+ Math.floor(options.maxInFlightPushes ?? 1)
4563
+ );
4564
+ let inFlightCount = 0;
4565
+ let drainIdleWaiters = [];
4566
+ let sendChainTail = Promise.resolve();
4555
4567
  const headEntry = () => queue[headIndex];
4556
4568
  const depth = () => queue.length - headIndex;
4557
4569
  const currentCounters = () => ({
@@ -4661,44 +4673,90 @@ function createThreadOutbox(options) {
4661
4673
  coalescible.delete(key);
4662
4674
  }
4663
4675
  };
4664
- const settleHead = (pending) => {
4676
+ const applyHeadOutcome = (pending, outcome) => {
4677
+ const entry = pending.entry;
4678
+ switch (outcome.kind) {
4679
+ case "ack": {
4680
+ const builder = builders[entry.kind];
4681
+ try {
4682
+ builder?.onReply?.(entry.data, outcome.reply, context);
4683
+ } catch (error) {
4684
+ log2("outbox_on_reply_failed", {
4685
+ threadKey,
4686
+ entrySeq: entry.entrySeq,
4687
+ kind: entry.kind,
4688
+ error: errorMessage2(error)
4689
+ });
4690
+ }
4691
+ journal?.appendAck(entry.entrySeq);
4692
+ break;
4693
+ }
4694
+ case "skip": {
4695
+ log2("outbox_entry_skipped", {
4696
+ threadKey,
4697
+ entrySeq: entry.entrySeq,
4698
+ kind: entry.kind,
4699
+ reason: outcome.reason
4700
+ });
4701
+ journal?.appendSkip(entry.entrySeq, outcome.reason);
4702
+ break;
4703
+ }
4704
+ case "fail": {
4705
+ if (entry.resolution === "must_ack") {
4706
+ journal?.appendFail(entry.entrySeq);
4707
+ try {
4708
+ onPermanentFailure(entry, outcome.failure);
4709
+ } catch (error) {
4710
+ log2("outbox_permanent_failure_callback_failed", {
4711
+ threadKey,
4712
+ entrySeq: entry.entrySeq,
4713
+ error: errorMessage2(error)
4714
+ });
4715
+ }
4716
+ } else {
4717
+ journal?.appendDrop(entry.entrySeq);
4718
+ }
4719
+ break;
4720
+ }
4721
+ }
4665
4722
  removeFromCoalescible(pending);
4666
4723
  advanceHead();
4667
4724
  notifyFlushWaiters();
4668
4725
  };
4669
- const settleFailure = (pending, failure) => {
4726
+ const settleContiguousPrefix = () => {
4727
+ while (true) {
4728
+ const head = headEntry();
4729
+ if (head === void 0 || head.outcome === void 0) {
4730
+ return;
4731
+ }
4732
+ applyHeadOutcome(head, head.outcome);
4733
+ }
4734
+ };
4735
+ const recordOutcome = (pending, outcome) => {
4670
4736
  const entry = pending.entry;
4671
- if (entry.resolution === "must_ack") {
4672
- totalPermanentFailures += 1;
4673
- log2("outbox_permanent_failure", {
4674
- threadKey,
4675
- entrySeq: entry.entrySeq,
4676
- kind: entry.kind,
4677
- attempts: entry.attempts,
4678
- error: failure.message
4679
- });
4680
- journal?.appendFail(entry.entrySeq);
4681
- try {
4682
- onPermanentFailure(entry, failure);
4683
- } catch (error) {
4684
- log2("outbox_permanent_failure_callback_failed", {
4737
+ if (outcome.kind === "fail") {
4738
+ if (entry.resolution === "must_ack") {
4739
+ totalPermanentFailures += 1;
4740
+ log2("outbox_permanent_failure", {
4685
4741
  threadKey,
4686
4742
  entrySeq: entry.entrySeq,
4687
- error: errorMessage2(error)
4743
+ kind: entry.kind,
4744
+ attempts: entry.attempts,
4745
+ error: outcome.failure.message
4746
+ });
4747
+ } else {
4748
+ totalDroppedBestEffort += 1;
4749
+ log2("outbox_dropped_best_effort", {
4750
+ threadKey,
4751
+ entrySeq: entry.entrySeq,
4752
+ kind: entry.kind,
4753
+ attempts: entry.attempts,
4754
+ error: outcome.failure.message
4688
4755
  });
4689
4756
  }
4690
- } else {
4691
- totalDroppedBestEffort += 1;
4692
- log2("outbox_dropped_best_effort", {
4693
- threadKey,
4694
- entrySeq: entry.entrySeq,
4695
- kind: entry.kind,
4696
- attempts: entry.attempts,
4697
- error: failure.message
4698
- });
4699
- journal?.appendDrop(entry.entrySeq);
4700
4757
  }
4701
- settleHead(pending);
4758
+ pending.outcome = outcome;
4759
+ pending.state = "queued";
4702
4760
  };
4703
4761
  const recordPushLatency = (latencyMs) => {
4704
4762
  if (pushLatencyWindow.length < outboxPushLatencyWindowSize) {
@@ -4708,136 +4766,180 @@ function createThreadOutbox(options) {
4708
4766
  }
4709
4767
  pushLatencyNextIndex = (pushLatencyNextIndex + 1) % outboxPushLatencyWindowSize;
4710
4768
  };
4711
- const sleep2 = (ms) => new Promise((resolve12) => {
4712
- const timer = setTimeout(() => {
4713
- wakeSleep = void 0;
4714
- resolve12();
4715
- }, ms);
4716
- wakeSleep = () => {
4717
- clearTimeout(timer);
4718
- wakeSleep = void 0;
4769
+ const clearRetryTimer = (pending) => {
4770
+ if (pending.retryTimer !== void 0) {
4771
+ clearTimeout(pending.retryTimer);
4772
+ pending.retryTimer = void 0;
4773
+ }
4774
+ };
4775
+ const notifyDrainIdle = () => {
4776
+ if (inFlightCount > 0) {
4777
+ return;
4778
+ }
4779
+ const waiters = drainIdleWaiters;
4780
+ drainIdleWaiters = [];
4781
+ for (const resolve12 of waiters) {
4719
4782
  resolve12();
4720
- };
4721
- });
4722
- const drainHeadEntry = async (pending) => {
4783
+ }
4784
+ };
4785
+ const headInBackoff = () => {
4786
+ const head = headEntry();
4787
+ return head !== void 0 && head.outcome === void 0 && head.state === "queued" && head.retryTimer !== void 0;
4788
+ };
4789
+ const scheduleRetry = (pending, delayMs) => {
4790
+ clearRetryTimer(pending);
4791
+ pending.retryTimer = setTimeout(() => {
4792
+ pending.retryTimer = void 0;
4793
+ if (stopped || paused || pending.outcome !== void 0) {
4794
+ return;
4795
+ }
4796
+ pumpWindow();
4797
+ }, delayMs);
4798
+ pending.retryTimer?.unref?.();
4799
+ };
4800
+ const pushEntry = async (pending) => {
4723
4801
  const entry = pending.entry;
4724
- while (!stopped) {
4802
+ const sendTurn = sendChainTail;
4803
+ let releaseSendChain = () => void 0;
4804
+ sendChainTail = new Promise((resolve12) => {
4805
+ releaseSendChain = resolve12;
4806
+ });
4807
+ let released = false;
4808
+ const releaseSend = () => {
4809
+ if (!released) {
4810
+ released = true;
4811
+ releaseSendChain();
4812
+ }
4813
+ };
4814
+ pending.state = "pushing";
4815
+ inFlightCount += 1;
4816
+ try {
4817
+ await sendTurn;
4818
+ if (stopped || paused || pending.outcome !== void 0) {
4819
+ pending.state = "queued";
4820
+ releaseSend();
4821
+ return;
4822
+ }
4725
4823
  const builder = builders[entry.kind];
4726
4824
  if (builder === void 0) {
4727
- settleFailure(
4728
- pending,
4729
- new Error(`no outbox builder registered for kind ${entry.kind}`)
4730
- );
4825
+ releaseSend();
4826
+ recordOutcome(pending, {
4827
+ kind: "fail",
4828
+ failure: new Error(
4829
+ `no outbox builder registered for kind ${entry.kind}`
4830
+ )
4831
+ });
4731
4832
  return;
4732
4833
  }
4733
4834
  let built;
4734
4835
  try {
4735
4836
  built = builder.build(entry.data, context);
4736
4837
  } catch (error) {
4737
- settleFailure(pending, toError(error));
4838
+ releaseSend();
4839
+ recordOutcome(pending, { kind: "fail", failure: toError(error) });
4738
4840
  return;
4739
4841
  }
4740
4842
  if ("skip" in built) {
4741
- log2("outbox_entry_skipped", {
4843
+ releaseSend();
4844
+ recordOutcome(pending, { kind: "skip", reason: built.reason });
4845
+ return;
4846
+ }
4847
+ entry.attempts += 1;
4848
+ await pushBuilt(pending, built, releaseSend);
4849
+ } finally {
4850
+ inFlightCount -= 1;
4851
+ }
4852
+ };
4853
+ const pushBuilt = async (pending, built, releaseSend) => {
4854
+ const entry = pending.entry;
4855
+ const pushStartedAtMs = now();
4856
+ try {
4857
+ const reply = await options.push(
4858
+ built.event,
4859
+ built.payload,
4860
+ built.timeoutMs,
4861
+ releaseSend
4862
+ );
4863
+ releaseSend();
4864
+ recordPushLatency(Math.max(0, now() - pushStartedAtMs));
4865
+ totalAcked += 1;
4866
+ recordOutcome(pending, { kind: "ack", reply });
4867
+ } catch (error) {
4868
+ releaseSend();
4869
+ pending.state = "queued";
4870
+ const failure = toError(error);
4871
+ if (options.isRetryBudgetExempt?.(failure) === true) {
4872
+ entry.attempts -= 1;
4873
+ totalRetried += 1;
4874
+ log2("outbox_push_connection_wait", {
4742
4875
  threadKey,
4743
4876
  entrySeq: entry.entrySeq,
4744
4877
  kind: entry.kind,
4745
- reason: built.reason
4878
+ error: failure.message
4746
4879
  });
4747
- journal?.appendSkip(entry.entrySeq, built.reason);
4748
- settleHead(pending);
4749
- return;
4750
- }
4751
- pending.state = "pushing";
4752
- entry.attempts += 1;
4753
- const pushStartedAtMs = now();
4754
- let reply;
4755
- try {
4756
- reply = await options.push(built.event, built.payload, built.timeoutMs);
4757
- } catch (error) {
4758
- pending.state = "queued";
4759
- const failure = toError(error);
4760
- if (options.isRetryBudgetExempt?.(failure) === true) {
4761
- entry.attempts -= 1;
4880
+ if (!stopped && !paused) {
4881
+ scheduleRetry(pending, budgetExemptRetryFallbackDelayMs);
4882
+ }
4883
+ } else {
4884
+ const delayMs = retryDelaysMs[entry.attempts - 1];
4885
+ if (delayMs === void 0) {
4886
+ recordOutcome(pending, { kind: "fail", failure });
4887
+ } else {
4762
4888
  totalRetried += 1;
4763
- log2("outbox_push_connection_wait", {
4889
+ log2("outbox_push_retry", {
4764
4890
  threadKey,
4765
4891
  entrySeq: entry.entrySeq,
4766
4892
  kind: entry.kind,
4893
+ attempt: entry.attempts,
4894
+ delayMs,
4767
4895
  error: failure.message
4768
4896
  });
4769
- if (stopped || paused) {
4770
- return;
4897
+ if (!stopped && !paused) {
4898
+ scheduleRetry(pending, delayMs);
4771
4899
  }
4772
- await sleep2(budgetExemptRetryFallbackDelayMs);
4773
- if (stopped || paused) {
4774
- return;
4775
- }
4776
- continue;
4777
4900
  }
4778
- const delayMs = retryDelaysMs[entry.attempts - 1];
4779
- if (delayMs === void 0) {
4780
- settleFailure(pending, failure);
4781
- return;
4782
- }
4783
- totalRetried += 1;
4784
- log2("outbox_push_retry", {
4785
- threadKey,
4786
- entrySeq: entry.entrySeq,
4787
- kind: entry.kind,
4788
- attempt: entry.attempts,
4789
- delayMs,
4790
- error: failure.message
4791
- });
4792
- await sleep2(delayMs);
4793
- if (stopped || paused) {
4794
- return;
4795
- }
4796
- continue;
4797
4901
  }
4798
- pending.state = "queued";
4799
- try {
4800
- builder.onReply?.(entry.data, reply, context);
4801
- } catch (error) {
4802
- log2("outbox_on_reply_failed", {
4803
- threadKey,
4804
- entrySeq: entry.entrySeq,
4805
- kind: entry.kind,
4806
- error: errorMessage2(error)
4807
- });
4808
- }
4809
- totalAcked += 1;
4810
- recordPushLatency(Math.max(0, now() - pushStartedAtMs));
4811
- journal?.appendAck(entry.entrySeq);
4812
- settleHead(pending);
4813
- return;
4814
4902
  }
4815
4903
  };
4816
- const drainLoop = async () => {
4817
- await Promise.resolve();
4818
- while (!stopped && !paused) {
4819
- const pending = headEntry();
4820
- if (pending === void 0) {
4821
- return;
4904
+ const pumpWindow = () => {
4905
+ settleContiguousPrefix();
4906
+ if (stopped || paused) {
4907
+ notifyDrainIdle();
4908
+ return;
4909
+ }
4910
+ if (headInBackoff()) {
4911
+ notifyDrainIdle();
4912
+ return;
4913
+ }
4914
+ for (let index = headIndex; index < queue.length && inFlightCount < maxInFlightPushes; index += 1) {
4915
+ const pending = queue[index];
4916
+ if (pending === void 0 || pending.outcome !== void 0 || pending.state === "pushing" || pending.retryTimer !== void 0) {
4917
+ continue;
4822
4918
  }
4823
- await drainHeadEntry(pending);
4919
+ void pushEntry(pending).then(
4920
+ () => pumpWindow(),
4921
+ (error) => {
4922
+ log2("outbox_drain_loop_error", {
4923
+ threadKey,
4924
+ error: errorMessage2(error)
4925
+ });
4926
+ pumpWindow();
4927
+ }
4928
+ );
4824
4929
  }
4930
+ notifyDrainIdle();
4825
4931
  };
4826
4932
  const ensureDraining = () => {
4827
- if (drainPromise !== void 0 || paused || stopped) {
4933
+ if (paused || stopped) {
4828
4934
  return;
4829
4935
  }
4830
4936
  if (headEntry() === void 0) {
4831
4937
  return;
4832
4938
  }
4833
- drainPromise = drainLoop().catch((error) => {
4834
- log2("outbox_drain_loop_error", {
4835
- threadKey,
4836
- error: errorMessage2(error)
4837
- });
4838
- }).finally(() => {
4839
- drainPromise = void 0;
4840
- ensureDraining();
4939
+ void Promise.resolve().then(() => {
4940
+ if (!stopped && !paused) {
4941
+ pumpWindow();
4942
+ }
4841
4943
  });
4842
4944
  };
4843
4945
  const flush = (timeoutMs) => {
@@ -4881,10 +4983,16 @@ function createThreadOutbox(options) {
4881
4983
  });
4882
4984
  }
4883
4985
  stopped = true;
4884
- wakeSleep?.();
4885
- const active = drainPromise;
4886
- if (active !== void 0) {
4887
- await Promise.race([active, sleepUncancellable(closeDrainGraceMs)]);
4986
+ for (let index = headIndex; index < queue.length; index += 1) {
4987
+ clearRetryTimer(queue[index]);
4988
+ }
4989
+ if (inFlightCount > 0) {
4990
+ await Promise.race([
4991
+ new Promise((resolve12) => {
4992
+ drainIdleWaiters.push(resolve12);
4993
+ }),
4994
+ sleepUncancellable(closeDrainGraceMs)
4995
+ ]);
4888
4996
  }
4889
4997
  for (const waiter of flushWaiters.splice(0)) {
4890
4998
  if (waiter.timer !== void 0) {
@@ -4903,7 +5011,7 @@ function createThreadOutbox(options) {
4903
5011
  if (input.coalesceKey !== void 0) {
4904
5012
  const key = coalesceMapKey(input.kind, input.coalesceKey);
4905
5013
  const existing = coalescible.get(key);
4906
- if (existing !== void 0 && existing.state === "queued") {
5014
+ if (existing !== void 0 && existing.state === "queued" && existing.outcome === void 0) {
4907
5015
  existing.entry.data = coalesceEntryData(
4908
5016
  builders[input.kind],
4909
5017
  existing.entry,
@@ -4948,9 +5056,9 @@ function createThreadOutbox(options) {
4948
5056
  const pending = queue[index];
4949
5057
  if (pending !== void 0) {
4950
5058
  pending.entry.attempts = 0;
5059
+ clearRetryTimer(pending);
4951
5060
  }
4952
5061
  }
4953
- wakeSleep?.();
4954
5062
  ensureDraining();
4955
5063
  },
4956
5064
  metrics: () => {
@@ -6619,6 +6727,7 @@ function createSessionPipeline(host) {
6619
6727
  builders,
6620
6728
  ...host.persistDir === void 0 ? {} : { persistDir: host.persistDir },
6621
6729
  ...host.outboxRetryDelaysMs === void 0 ? {} : { retryDelaysMs: [...host.outboxRetryDelaysMs] },
6730
+ maxInFlightPushes: host.outboxMaxInFlightPushes ?? defaultOutboxMaxInFlightPushes,
6622
6731
  ...host.journalRecoveryMaxAgeMs === void 0 ? {} : { journalRecoveryMaxAgeMs: host.journalRecoveryMaxAgeMs },
6623
6732
  // Budget semantics (pre-merge review finding 6): wait_for_connection
6624
6733
  // failures never consume retry budget - the push never reached the
@@ -7067,7 +7176,7 @@ function sleep(ms) {
7067
7176
  setTimeout(resolve12, ms);
7068
7177
  });
7069
7178
  }
7070
- var defaultSnapshotRetryDelaysMs, toolCallOutputTypes, toolCallItemTypes;
7179
+ var defaultSnapshotRetryDelaysMs, defaultOutboxMaxInFlightPushes, toolCallOutputTypes, toolCallItemTypes;
7071
7180
  var init_integration = __esm({
7072
7181
  "src/pipeline/integration.ts"() {
7073
7182
  "use strict";
@@ -7081,6 +7190,7 @@ var init_integration = __esm({
7081
7190
  init_threadEventLoop();
7082
7191
  init_transition();
7083
7192
  defaultSnapshotRetryDelaysMs = [1e3, 5e3];
7193
+ defaultOutboxMaxInFlightPushes = 16;
7084
7194
  toolCallOutputTypes = /* @__PURE__ */ new Map([
7085
7195
  ["function_call", "function_call_output"],
7086
7196
  ["custom_tool_call", "custom_tool_call_output"]
@@ -8739,6 +8849,14 @@ function codexStartRequestTimeoutMs() {
8739
8849
  const parsed = Number.parseInt(raw, 10);
8740
8850
  return Number.isFinite(parsed) && parsed > 0 ? parsed : CODEX_START_REQUEST_TIMEOUT_MS;
8741
8851
  }
8852
+ function outboxMaxInFlightPushesOverride() {
8853
+ const raw = process.env.LINZUMI_OUTBOX_MAX_INFLIGHT_PUSHES?.trim();
8854
+ if (raw === void 0 || raw === "") {
8855
+ return void 0;
8856
+ }
8857
+ const parsed = Number.parseInt(raw, 10);
8858
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
8859
+ }
8742
8860
  function isCodexStartRequestTimeoutError(error) {
8743
8861
  return error instanceof CodexStartRequestTimeoutError;
8744
8862
  }
@@ -8983,7 +9101,7 @@ function createChannelSessionPipeline(args, state, payloadContext) {
8983
9101
  instanceId: args.instanceId,
8984
9102
  threadKey,
8985
9103
  persistDir: args.options.pipelinePersistDir,
8986
- push: (event, payload) => pipelinePush(args, state, event, payload),
9104
+ push: (event, payload, _timeoutMs, onSent) => pipelinePush(args, state, event, payload, onSent),
8987
9105
  wire: () => ({
8988
9106
  workspaceSlug: session.workspaceSlug,
8989
9107
  channelSlug: session.channelSlug,
@@ -9014,15 +9132,16 @@ function createChannelSessionPipeline(args, state, payloadContext) {
9014
9132
  args.log(event, fields);
9015
9133
  },
9016
9134
  outboxRetryDelaysMs: args.options.pipelineOutboxRetryDelaysMs,
9135
+ outboxMaxInFlightPushes: outboxMaxInFlightPushesOverride(),
9017
9136
  snapshotRetryDelaysMs: args.options.pipelineSnapshotRetryDelaysMs,
9018
9137
  statsIntervalMs: args.options.pipelineStatsIntervalMs,
9019
9138
  turnStalledThresholdMs: args.options.pipelineTurnStalledThresholdMs
9020
9139
  });
9021
9140
  }
9022
- async function pipelinePush(args, state, event, payload) {
9141
+ async function pipelinePush(args, state, event, payload, onSent) {
9023
9142
  const prepared = await preparePipelinePushPayload(args, event, payload);
9024
9143
  try {
9025
- return await pushOk2(args.kandan, args.topic, event, prepared);
9144
+ return await pushOk2(args.kandan, args.topic, event, prepared, onSent);
9026
9145
  } catch (error) {
9027
9146
  if (isConnectionLostPushError(error)) {
9028
9147
  state.pipeline?.pause();
@@ -12060,8 +12179,8 @@ function developerInstructionsForThreadStart(options) {
12060
12179
  })
12061
12180
  };
12062
12181
  }
12063
- async function pushOk2(kandan, topic, event, payload) {
12064
- const reply = await kandan.push(topic, event, payload);
12182
+ async function pushOk2(kandan, topic, event, payload, onSent) {
12183
+ const reply = await kandan.push(topic, event, payload, onSent);
12065
12184
  if (isJsonObject(reply) && reply.status === "ok" && isJsonObject(reply.response)) {
12066
12185
  return reply.response;
12067
12186
  }
@@ -12393,10 +12512,10 @@ function createClaudeCodeSessionPipeline(host) {
12393
12512
  let lastSourceSeq;
12394
12513
  let lastUsage;
12395
12514
  let pipeline2;
12396
- const push = async (event, payload, timeoutMs) => {
12515
+ const push = async (event, payload, timeoutMs, onSent) => {
12397
12516
  const prepared = host.preparePush === void 0 ? payload : await host.preparePush(event, payload);
12398
12517
  try {
12399
- return await host.push(event, prepared, timeoutMs);
12518
+ return await host.push(event, prepared, timeoutMs, onSent);
12400
12519
  } catch (error) {
12401
12520
  if (isConnectionLostPushError(error)) {
12402
12521
  pipeline2.pause();
@@ -19829,7 +19948,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19829
19948
  var init_version = __esm({
19830
19949
  "src/version.ts"() {
19831
19950
  "use strict";
19832
- linzumiCliVersion = "0.0.101-beta";
19951
+ linzumiCliVersion = "0.0.103-beta";
19833
19952
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19834
19953
  }
19835
19954
  });
@@ -21102,6 +21221,8 @@ function rateLimitSummaryKey(summary) {
21102
21221
  }
21103
21222
  function formatRunnerConsoleEvent(event, payload) {
21104
21223
  switch (event) {
21224
+ case "runner.starting":
21225
+ return runnerWelcomeLine;
21105
21226
  case "runner.instance_started":
21106
21227
  return connectedRunnerMessage(payload);
21107
21228
  case "runner.replaced":
@@ -22043,11 +22164,12 @@ function stringValue5(value) {
22043
22164
  function numberValue(value) {
22044
22165
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
22045
22166
  }
22046
- var dashboardState, maxRawLines, escapeKey, ctrlCKey, enterKey, upKey, downKey, dashboardTableColumns, runnerWelcomeHeaderLines, runnerWelcomeCompactHeaderLines, minimumVisibleTuiTableRows, redrawScreen, keyboardState, tickerState, tuiState;
22167
+ var dashboardState, maxRawLines, escapeKey, ctrlCKey, enterKey, upKey, downKey, dashboardTableColumns, runnerWelcomeLine, runnerWelcomeHeaderLines, runnerWelcomeCompactHeaderLines, minimumVisibleTuiTableRows, redrawScreen, keyboardState, tickerState, tuiState;
22047
22168
  var init_runnerConsoleReporter = __esm({
22048
22169
  "src/runnerConsoleReporter.ts"() {
22049
22170
  "use strict";
22050
22171
  init_blessedTputSetulcShim();
22172
+ init_version();
22051
22173
  dashboardState = {
22052
22174
  jobs: /* @__PURE__ */ new Map(),
22053
22175
  discovery: /* @__PURE__ */ new Map(),
@@ -22081,6 +22203,7 @@ var init_runnerConsoleReporter = __esm({
22081
22203
  { width: 12 },
22082
22204
  { width: 24 }
22083
22205
  ];
22206
+ runnerWelcomeLine = `Welcome to the Linzumi runner (version ${linzumiCliVersion}).`;
22084
22207
  runnerWelcomeHeaderLines = [
22085
22208
  "\u2593\u2593\u2557 \u2593\u2593\u2557\u2593\u2593\u2593\u2557 \u2593\u2593\u2557\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2557\u2593\u2593\u2557 \u2593\u2593\u2557\u2593\u2593\u2593\u2557 \u2593\u2593\u2593\u2557\u2593\u2593\u2557",
22086
22209
  "\u2593\u2593\u2551 \u2593\u2593\u2551\u2593\u2593\u2593\u2593\u2557 \u2593\u2593\u2551\u255A\u2550\u2550\u2593\u2593\u2593\u2554\u255D\u2593\u2593\u2551 \u2593\u2593\u2551\u2593\u2593\u2593\u2593\u2557 \u2593\u2593\u2593\u2593\u2551\u2593\u2593\u2551",
@@ -22097,7 +22220,7 @@ var init_runnerConsoleReporter = __esm({
22097
22220
  " \u2551\u2551 \"/` ___ ;_________\u2551\u2551_.'",
22098
22221
  " \u2551\u2551 ` ^^ ^^ \u2551\u2551",
22099
22222
  "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2568\u2568\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\xB7\xB7\u2500\u2500\u2500\u2500\xB7\xB7\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2568\u2568\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
22100
- "Welcome to the Linzumi runner.",
22223
+ runnerWelcomeLine,
22101
22224
  "Codex is running on this computer and connected to Linzumi.",
22102
22225
  "Active Codex and Claude Code jobs appear below as they run.",
22103
22226
  "You can return to the Linzumi app now."
@@ -22106,7 +22229,7 @@ var init_runnerConsoleReporter = __esm({
22106
22229
  "\u2593\u2593\u2557 LINZUMI RUNNER \u2593\u2593\u2557",
22107
22230
  "\u2551\u2551 ()-().----. \u2551\u2551",
22108
22231
  "\u2568\u2568\u2500\u2500 connected to Codex and Linzumi \u2500\u2500\u2568\u2568",
22109
- "Welcome to the Linzumi runner.",
22232
+ runnerWelcomeLine,
22110
22233
  "Codex is running on this computer and connected to Linzumi.",
22111
22234
  "Active Codex and Claude Code jobs appear below as they run.",
22112
22235
  "You can return to the Linzumi app now."
@@ -24756,6 +24879,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24756
24879
  );
24757
24880
  threadRunnerProcesses.clear();
24758
24881
  });
24882
+ const threadModelProviderBindings = /* @__PURE__ */ new Map();
24759
24883
  const threadRebuildSerializer = createThreadRebuildSerializer();
24760
24884
  const evictStaleDynamicChannelSession = async (codexThreadId, expectClient) => {
24761
24885
  const session = dynamicChannelSessions.get(codexThreadId);
@@ -24843,6 +24967,14 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24843
24967
  rebuildArgs.codexThreadId
24844
24968
  );
24845
24969
  let rebuilt = false;
24970
+ const rebuildProviderBinding = threadModelProviderBindings.get(rebuildArgs.kandanThreadId) ?? // Belt-and-suspenders: if the persisted map missed this thread
24971
+ // but the in-scope establishing `control` still carries the
24972
+ // provider (the server now re-injects it on reconnect_thread),
24973
+ // forward that directly so the rebuild never strands a wafer turn.
24974
+ (control.modelProvider !== void 0 || control.llmProxy !== void 0 ? {
24975
+ ...control.modelProvider === void 0 ? {} : { modelProvider: control.modelProvider },
24976
+ ...control.llmProxy === void 0 ? {} : { llmProxy: control.llmProxy }
24977
+ } : void 0);
24846
24978
  for (const message of rebuildArgs.pendingMessages) {
24847
24979
  const reconnectControl = {
24848
24980
  type: "reconnect_thread",
@@ -24857,6 +24989,14 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24857
24989
  // control built above at channelSession.linzumiContext).
24858
24990
  ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24859
24991
  ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24992
+ // RESUME-CRITICAL: replay the persisted model-provider binding
24993
+ // (the in-scope `control` is a reconnect_thread that no longer
24994
+ // carries it) so the respawned wafer/GLM worker can load the
24995
+ // `linzumi` provider and resume instead of stranding the turn.
24996
+ ...rebuildProviderBinding?.modelProvider === void 0 ? {} : {
24997
+ modelProvider: rebuildProviderBinding.modelProvider
24998
+ },
24999
+ ...rebuildProviderBinding?.llmProxy === void 0 ? {} : { llmProxy: rebuildProviderBinding.llmProxy },
24860
25000
  sourceSeq: message.seq,
24861
25001
  workDescription: message.body,
24862
25002
  ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
@@ -25118,6 +25258,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25118
25258
  return void 0;
25119
25259
  }
25120
25260
  const providerResolution = resolveControlCodexModelProvider(control);
25261
+ if (control.modelProvider !== void 0 || control.llmProxy !== void 0) {
25262
+ threadModelProviderBindings.set(kandanThreadId, {
25263
+ ...control.modelProvider === void 0 ? {} : { modelProvider: control.modelProvider },
25264
+ ...control.llmProxy === void 0 ? {} : { llmProxy: control.llmProxy }
25265
+ });
25266
+ }
25121
25267
  const providerResolutionError = codexModelProviderResolutionError(providerResolution);
25122
25268
  if (providerResolutionError !== void 0) {
25123
25269
  return {
@@ -28425,8 +28571,8 @@ async function startClaudeCodeProviderInstance(args) {
28425
28571
  codexThreadId: activeSessionId ?? args.resumeSessionId,
28426
28572
  rootSeq
28427
28573
  }),
28428
- push: async (event, payload) => {
28429
- const reply = await args.kandan.push(args.topic, event, payload);
28574
+ push: async (event, payload, _timeoutMs, onSent) => {
28575
+ const reply = await args.kandan.push(args.topic, event, payload, onSent);
28430
28576
  if (isJsonObject(reply) && reply.status === "ok") {
28431
28577
  return reply;
28432
28578
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.101-beta",
4
- "description": "Linzumi CLI point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
3
+ "version": "0.0.103-beta",
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": {
7
7
  "linzumi": "bin/linzumi.js"