@linzumi/cli 0.0.84-beta → 0.0.85-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 +332 -196
  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.84-beta --version
61
+ npx -y @linzumi/cli@0.0.85-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -9822,7 +9822,11 @@ async function handleKandanChatEvent(args, state, runnerIdentity, payloadContext
9822
9822
  seq: event.seq,
9823
9823
  actor_slug: event.actorSlug ?? null,
9824
9824
  actor_user_id: event.actorUserId ?? null,
9825
- reason: "different_thread"
9825
+ reason: "different_thread",
9826
+ thread_id: event.threadId ?? null,
9827
+ bound_thread_id: state.kandanThreadId ?? null,
9828
+ codex_thread_id: state.codexThreadId ?? null,
9829
+ body_preview: runnerConsoleBodyPreview(event.body)
9826
9830
  });
9827
9831
  return;
9828
9832
  }
@@ -12440,87 +12444,83 @@ async function startClaudeCodeSession(options) {
12440
12444
  assistantTextByKey: /* @__PURE__ */ new Map(),
12441
12445
  usage: void 0,
12442
12446
  startedSessionIds: /* @__PURE__ */ new Set(),
12443
- completedTurnCount: 0
12447
+ completedTurnCount: 0,
12448
+ lastCompletedTurnBody: void 0
12444
12449
  };
12445
12450
  const streamUsageTracker = createClaudeCodeStreamUsageTracker();
12446
- for await (const message of runner(options)) {
12447
- state.sessionId = state.sessionId ?? extractClaudeSessionId(message);
12448
- const sessionId = extractClaudeSessionId(message) ?? state.sessionId;
12449
- if (sessionId !== void 0 && !state.startedSessionIds.has(sessionId)) {
12450
- state.startedSessionIds.add(sessionId);
12451
- await options.onTranscriptEvent?.({ type: "session_started", sessionId });
12452
- }
12453
- for (const event of transcriptEventsForClaudeMessage(
12454
- message,
12455
- sessionId,
12456
- streamUsageTracker
12457
- )) {
12458
- if (event.type === "assistant_message") {
12459
- recordClaudeAssistantAggregate(state, event);
12460
- }
12461
- if (event.type === "usage") {
12462
- state.usage = event.usage;
12463
- }
12464
- await options.onTranscriptEvent?.(event);
12465
- }
12466
- const resultOutcome = extractClaudeResultOutcome(message);
12467
- switch (resultOutcome.type) {
12468
- case "success": {
12469
- if (resultOutcome.text !== void 0) {
12470
- state.resultText = resultOutcome.text;
12471
- }
12472
- state.usage = extractClaudeUsage(message) ?? state.usage;
12473
- state.completedTurnCount += 1;
12474
- const queuedTurnCountBeforeTranscript = options.streamingInput === void 0 ? void 0 : options.streamingInput.queuedTurnCount();
12475
- if (options.streamingInput !== void 0) {
12476
- await emitClaudeCodeTurnCompleted(options, state);
12477
- }
12478
- if (options.streamingInput !== void 0 && queuedTurnCountBeforeTranscript !== void 0 && state.completedTurnCount >= Math.max(
12479
- queuedTurnCountBeforeTranscript,
12480
- options.streamingInput.queuedTurnCount()
12481
- )) {
12482
- options.streamingInput.close();
12483
- return completeClaudeCodeSession(options, state);
12484
- }
12485
- if (options.streamingInput !== void 0) {
12486
- await options.onTurnCompleted?.();
12487
- resetClaudeAssistantAggregate(state);
12488
- state.resultText = void 0;
12451
+ try {
12452
+ for await (const message of runner(options)) {
12453
+ state.sessionId = state.sessionId ?? extractClaudeSessionId(message);
12454
+ const sessionId = extractClaudeSessionId(message) ?? state.sessionId;
12455
+ if (sessionId !== void 0 && !state.startedSessionIds.has(sessionId)) {
12456
+ state.startedSessionIds.add(sessionId);
12457
+ await options.onTranscriptEvent?.({
12458
+ type: "session_started",
12459
+ sessionId
12460
+ });
12461
+ }
12462
+ for (const event of transcriptEventsForClaudeMessage(
12463
+ message,
12464
+ sessionId,
12465
+ streamUsageTracker
12466
+ )) {
12467
+ if (event.type === "assistant_message") {
12468
+ recordClaudeAssistantAggregate(state, event);
12489
12469
  }
12490
- break;
12470
+ if (event.type === "usage") {
12471
+ state.usage = event.usage;
12472
+ }
12473
+ await options.onTranscriptEvent?.(event);
12491
12474
  }
12492
- case "interrupted": {
12493
- state.completedTurnCount += 1;
12494
- if (state.sessionId !== void 0) {
12495
- await options.onTranscriptEvent?.({
12496
- type: "turn_interrupted",
12497
- sessionId: state.sessionId
12498
- });
12475
+ const resultOutcome = extractClaudeResultOutcome(message);
12476
+ switch (resultOutcome.type) {
12477
+ case "success": {
12478
+ if (resultOutcome.text !== void 0) {
12479
+ state.resultText = resultOutcome.text;
12480
+ }
12481
+ state.usage = extractClaudeUsage(message) ?? state.usage;
12482
+ state.completedTurnCount += 1;
12483
+ if (options.streamingInput !== void 0) {
12484
+ await emitClaudeCodeTurnCompleted(options, state);
12485
+ state.lastCompletedTurnBody = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state)) ?? "";
12486
+ await options.onTurnCompleted?.();
12487
+ resetClaudeAssistantAggregate(state);
12488
+ state.resultText = void 0;
12489
+ }
12490
+ break;
12499
12491
  }
12500
- if (options.streamingInput !== void 0) {
12501
- await options.onTurnCompleted?.();
12502
- resetClaudeAssistantAggregate(state);
12503
- state.resultText = void 0;
12492
+ case "interrupted": {
12493
+ state.completedTurnCount += 1;
12494
+ if (state.sessionId !== void 0) {
12495
+ await options.onTranscriptEvent?.({
12496
+ type: "turn_interrupted",
12497
+ sessionId: state.sessionId
12498
+ });
12499
+ }
12500
+ if (options.streamingInput !== void 0) {
12501
+ await options.onTurnCompleted?.();
12502
+ resetClaudeAssistantAggregate(state);
12503
+ state.resultText = void 0;
12504
+ }
12505
+ break;
12504
12506
  }
12505
- break;
12507
+ case "error":
12508
+ return await failClaudeCodeSession(
12509
+ options,
12510
+ state.sessionId,
12511
+ `Claude Code failed: ${resultOutcome.reason}`
12512
+ );
12513
+ case "none":
12514
+ break;
12506
12515
  }
12507
- case "error":
12508
- return await failClaudeCodeSession(
12509
- options,
12510
- state.sessionId,
12511
- `Claude Code failed: ${resultOutcome.reason}`
12512
- );
12513
- case "none":
12514
- break;
12515
12516
  }
12517
+ } finally {
12518
+ options.streamingInput?.close();
12516
12519
  }
12517
12520
  return completeClaudeCodeSession(options, state);
12518
12521
  }
12519
12522
  async function emitClaudeCodeTurnCompleted(options, state) {
12520
- const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state));
12521
- if (body === void 0) {
12522
- return;
12523
- }
12523
+ const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state)) ?? "";
12524
12524
  if (state.sessionId === void 0) {
12525
12525
  return;
12526
12526
  }
@@ -12532,7 +12532,7 @@ async function emitClaudeCodeTurnCompleted(options, state) {
12532
12532
  });
12533
12533
  }
12534
12534
  async function completeClaudeCodeSession(options, state) {
12535
- const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state));
12535
+ const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state)) ?? state.lastCompletedTurnBody;
12536
12536
  if (body === void 0) {
12537
12537
  return await failClaudeCodeSession(
12538
12538
  options,
@@ -18127,7 +18127,7 @@ var linzumiCliVersion, linzumiCliVersionText;
18127
18127
  var init_version = __esm({
18128
18128
  "src/version.ts"() {
18129
18129
  "use strict";
18130
- linzumiCliVersion = "0.0.84-beta";
18130
+ linzumiCliVersion = "0.0.85-beta";
18131
18131
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
18132
18132
  }
18133
18133
  });
@@ -19250,7 +19250,9 @@ function ignoredMessage(payload) {
19250
19250
  `reason=${text(payload.reason)}`,
19251
19251
  optionalField("type", payload.type),
19252
19252
  optionalField("thread", payload.thread_id),
19253
+ optionalField("bound_thread", payload.bound_thread_id),
19253
19254
  optionalField("body_chars", payload.body_length),
19255
+ optionalField("body", payload.body_preview),
19254
19256
  optionalField("attachments", payload.attachment_count),
19255
19257
  optionalField("local_event", payload.local_runner_event_type)
19256
19258
  ].filter((part) => part !== void 0).join(" ");
@@ -22592,7 +22594,29 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
22592
22594
  );
22593
22595
  dynamicChannelSessions.clear();
22594
22596
  dynamicChannelSessionCodexClients.clear();
22595
- for (const session of activeClaudeCodeSessions.values()) {
22597
+ const claudeSessions = [...activeClaudeCodeSessions.values()];
22598
+ for (const session of claudeSessions) {
22599
+ try {
22600
+ session.closeInput();
22601
+ } catch (error) {
22602
+ log2("claude_code.session_input_close_failed", {
22603
+ linzumi_thread_id: session.threadId,
22604
+ message: error instanceof Error ? error.message : String(error)
22605
+ });
22606
+ }
22607
+ }
22608
+ await Promise.race([
22609
+ Promise.allSettled(
22610
+ claudeSessions.flatMap((session) => {
22611
+ const work = session.sessionWork();
22612
+ return work === void 0 ? [] : [work];
22613
+ })
22614
+ ),
22615
+ new Promise((resolve12) => {
22616
+ setTimeout(resolve12, 1e4).unref?.();
22617
+ })
22618
+ ]);
22619
+ for (const session of claudeSessions) {
22596
22620
  session.abortController.abort(new Error("local runner stopped"));
22597
22621
  }
22598
22622
  activeClaudeCodeSessions.clear();
@@ -24449,10 +24473,28 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
24449
24473
  };
24450
24474
  }
24451
24475
  try {
24452
- activeSession.enqueueInput({
24476
+ const enqueueResult = activeSession.enqueueInput({
24453
24477
  content: contentResult.content,
24454
24478
  sourceSeq
24455
24479
  });
24480
+ if (enqueueResult === "duplicate") {
24481
+ log2("claude_code.message_duplicate_ignored", {
24482
+ linzumi_thread_id: optionalThreadControlField(control, "threadId") ?? null,
24483
+ claude_session_id: codexThreadId,
24484
+ body_preview: claudeConsoleBodyPreview(workDescription),
24485
+ source_seq: sourceSeq ?? null
24486
+ });
24487
+ return {
24488
+ instanceId,
24489
+ controlType: control.type,
24490
+ agentProvider: "claude-code",
24491
+ cwd: cwd.cwd,
24492
+ matchedRoot: cwd.matchedRoot,
24493
+ codexThreadId,
24494
+ queuedInput: true,
24495
+ duplicate: true
24496
+ };
24497
+ }
24456
24498
  log2("claude_code.message_queued", {
24457
24499
  linzumi_thread_id: optionalThreadControlField(control, "threadId") ?? null,
24458
24500
  claude_session_id: codexThreadId,
@@ -24956,7 +24998,24 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
24956
24998
  };
24957
24999
  }
24958
25000
  case "stop_instance":
24959
- case "kill_instance":
25001
+ case "kill_instance": {
25002
+ const controlThreadId2 = "threadId" in control && typeof control.threadId === "string" ? control.threadId : void 0;
25003
+ const activeSession = controlThreadId2 === void 0 ? void 0 : activeClaudeCodeSessions.get(controlThreadId2);
25004
+ if (activeSession !== void 0) {
25005
+ await activeSession.interruptTurn("Claude Code stopped by Linzumi");
25006
+ activeSession.abortController.abort(
25007
+ new Error("Claude Code stopped by Linzumi")
25008
+ );
25009
+ return {
25010
+ instanceId,
25011
+ controlType: control.type,
25012
+ agentProvider: "claude-code",
25013
+ threadId: controlThreadId2,
25014
+ stopped: true
25015
+ };
25016
+ }
25017
+ return { instanceId, controlType: control.type, skipped: true };
25018
+ }
24960
25019
  case "resolve_port_forward_request":
24961
25020
  case "update_thread_interaction_access":
24962
25021
  case "set_port_forward_enabled":
@@ -25456,10 +25515,10 @@ function createClaudeCodeInputQueue(input) {
25456
25515
  claudeCodeUserInputMessage(input.content)
25457
25516
  ];
25458
25517
  const pendingSourceSeqs = [input.sourceSeq];
25518
+ const deferredFollowUps = [];
25459
25519
  const waiters = [];
25460
25520
  const state = {
25461
- closed: false,
25462
- queuedTurnCount: 1
25521
+ closed: false
25463
25522
  };
25464
25523
  const drainClosedWaiters = () => {
25465
25524
  while (state.closed) {
@@ -25470,19 +25529,25 @@ function createClaudeCodeInputQueue(input) {
25470
25529
  waiter({ done: true, value: void 0 });
25471
25530
  }
25472
25531
  };
25532
+ const deliverNow = (message, sourceSeq) => {
25533
+ pendingSourceSeqs.push(sourceSeq);
25534
+ const waiter = waiters.shift();
25535
+ if (waiter === void 0) {
25536
+ pendingMessages.push(message);
25537
+ return;
25538
+ }
25539
+ waiter({ done: false, value: message });
25540
+ };
25473
25541
  const enqueue = (next) => {
25474
25542
  if (state.closed) {
25475
25543
  throw new Error("Claude Code streaming input is closed");
25476
25544
  }
25477
25545
  const message = claudeCodeUserInputMessage(next.content);
25478
- pendingSourceSeqs.push(next.sourceSeq);
25479
- state.queuedTurnCount += 1;
25480
- const waiter = waiters.shift();
25481
- if (waiter === void 0) {
25482
- pendingMessages.push(message);
25546
+ if (pendingSourceSeqs.length > 0) {
25547
+ deferredFollowUps.push({ message, sourceSeq: next.sourceSeq });
25483
25548
  return;
25484
25549
  }
25485
- waiter({ done: false, value: message });
25550
+ deliverNow(message, next.sourceSeq);
25486
25551
  };
25487
25552
  const enqueueSteer = (content) => {
25488
25553
  if (state.closed) {
@@ -25520,14 +25585,30 @@ function createClaudeCodeInputQueue(input) {
25520
25585
  };
25521
25586
  }
25522
25587
  },
25523
- queuedTurnCount: () => state.queuedTurnCount,
25524
25588
  close: () => {
25589
+ if (pendingSourceSeqs.length === 0) {
25590
+ while (deferredFollowUps.length > 0) {
25591
+ const followUp = deferredFollowUps.shift();
25592
+ if (followUp !== void 0) {
25593
+ deliverNow(followUp.message, followUp.sourceSeq);
25594
+ }
25595
+ }
25596
+ }
25525
25597
  state.closed = true;
25526
25598
  drainClosedWaiters();
25527
25599
  },
25528
25600
  enqueue,
25529
25601
  enqueueSteer,
25530
- completeTurn: () => pendingSourceSeqs.shift(),
25602
+ completeTurn: () => {
25603
+ const completed = pendingSourceSeqs.shift();
25604
+ if (!state.closed && pendingSourceSeqs.length === 0) {
25605
+ const nextFollowUp = deferredFollowUps.shift();
25606
+ if (nextFollowUp !== void 0) {
25607
+ deliverNow(nextFollowUp.message, nextFollowUp.sourceSeq);
25608
+ }
25609
+ }
25610
+ return completed;
25611
+ },
25531
25612
  currentSourceSeq: () => pendingSourceSeqs[0]
25532
25613
  };
25533
25614
  }
@@ -25775,6 +25856,11 @@ async function startClaudeCodeProviderInstance(args) {
25775
25856
  codexVersion: void 0
25776
25857
  };
25777
25858
  const abortController = new AbortController();
25859
+ const acceptedSourceSeqs = new Set(
25860
+ sourceSeq === void 0 ? [] : [sourceSeq]
25861
+ );
25862
+ const sessionWorkHandle = { value: void 0 };
25863
+ let startControlResponded = false;
25778
25864
  let activeSessionId;
25779
25865
  let sessionControls;
25780
25866
  const planMirrorClient = createLinzumiMcpApiClient({
@@ -25909,7 +25995,7 @@ async function startClaudeCodeProviderInstance(args) {
25909
25995
  rootSeq,
25910
25996
  log: args.log
25911
25997
  };
25912
- let lastRateLimitSignature;
25998
+ const lastRateLimitSignatureByWindow = /* @__PURE__ */ new Map();
25913
25999
  const reportClaudeCodeRateLimit = async (event) => {
25914
26000
  const nowMs = Date.now();
25915
26001
  const summary = claudeCodeRateLimitSummaryText(event.rateLimit, nowMs);
@@ -25925,13 +26011,13 @@ async function startClaudeCodeProviderInstance(args) {
25925
26011
  utilization_percent: event.rateLimit.utilizationPercent ?? null,
25926
26012
  resets_at_ms: event.rateLimit.resetsAtMs ?? null
25927
26013
  });
26014
+ const windowKey = event.rateLimit.rateLimitType ?? "account";
25928
26015
  const signature = claudeRateLimitSignature(event.rateLimit);
25929
- if (signature === lastRateLimitSignature) {
26016
+ if (signature === lastRateLimitSignatureByWindow.get(windowKey)) {
25930
26017
  return;
25931
26018
  }
25932
- lastRateLimitSignature = signature;
25933
26019
  try {
25934
- await postClaudeCodeRateLimitMessage({
26020
+ await publishClaudeCodeRateLimitState({
25935
26021
  kandan: args.kandan,
25936
26022
  topic: args.topic,
25937
26023
  workspace,
@@ -25940,6 +26026,7 @@ async function startClaudeCodeProviderInstance(args) {
25940
26026
  claudeSessionId: event.sessionId ?? activeSessionId,
25941
26027
  rateLimit: event.rateLimit
25942
26028
  });
26029
+ lastRateLimitSignatureByWindow.set(windowKey, signature);
25943
26030
  } catch (error) {
25944
26031
  args.log("claude_code.rate_limit_post_failed", {
25945
26032
  thread_id: threadId,
@@ -25947,6 +26034,10 @@ async function startClaudeCodeProviderInstance(args) {
25947
26034
  });
25948
26035
  }
25949
26036
  };
26037
+ let settleFirstTurn = () => void 0;
26038
+ const firstTurnSettled = new Promise((resolve12) => {
26039
+ settleFirstTurn = resolve12;
26040
+ });
25950
26041
  const onTranscriptEvent = async (event) => {
25951
26042
  try {
25952
26043
  await mirrorClaudeSessionStoreAppend(mirrorArgs, event);
@@ -25969,7 +26060,19 @@ async function startClaudeCodeProviderInstance(args) {
25969
26060
  channel,
25970
26061
  threadId,
25971
26062
  currentSourceSeq: inputQueue.currentSourceSeq,
25972
- enqueueInput: inputQueue.enqueue,
26063
+ enqueueInput: (input) => {
26064
+ if (input.sourceSeq !== void 0 && acceptedSourceSeqs.has(input.sourceSeq)) {
26065
+ return "duplicate";
26066
+ }
26067
+ inputQueue.enqueue(input);
26068
+ if (input.sourceSeq !== void 0) {
26069
+ acceptedSourceSeqs.add(input.sourceSeq);
26070
+ }
26071
+ return "queued";
26072
+ },
26073
+ closeInput: inputQueue.close,
26074
+ hasActiveTurn: () => adapter.activeTurnId() !== void 0,
26075
+ sessionWork: () => sessionWorkHandle.value,
25973
26076
  steerTurn: inputQueue.enqueueSteer,
25974
26077
  interruptTurn: async (reason) => {
25975
26078
  const aborted = adapter.interruptActiveTurn(reason);
@@ -26025,6 +26128,10 @@ async function startClaudeCodeProviderInstance(args) {
26025
26128
  claude_session_id: event.sessionId,
26026
26129
  body_preview: claudeConsoleBodyPreview(event.body)
26027
26130
  });
26131
+ settleFirstTurn();
26132
+ }
26133
+ if (event.type === "turn_interrupted") {
26134
+ settleFirstTurn();
26028
26135
  }
26029
26136
  if (event.type === "rate_limit") {
26030
26137
  await reportClaudeCodeRateLimit(event);
@@ -26071,80 +26178,97 @@ async function startClaudeCodeProviderInstance(args) {
26071
26178
  request,
26072
26179
  signal
26073
26180
  });
26074
- let result;
26075
- try {
26181
+ const runSession = async () => {
26182
+ let result2;
26076
26183
  try {
26077
- result = await startClaudeCodeSession({
26078
- cwd: args.cwd,
26079
- prompt: workDescription,
26080
- developerInstructions: commanderDeveloperInstructions({
26184
+ try {
26185
+ result2 = await startClaudeCodeSession({
26081
26186
  cwd: args.cwd,
26082
- agentLabel: "Claude Code",
26083
- planTool: "todo-write",
26084
- developerPrompt,
26085
- linzumiContext
26086
- }),
26087
- model: claudeCodeModelForRuntimeModel(runtimeSettings.model),
26088
- resumeSessionId: args.resumeSessionId,
26089
- abortController,
26090
- streamingInput: inputQueue,
26091
- runner: args.options.claudeCodeRunner,
26092
- canUseTool,
26093
- ...mcpServers === void 0 ? {} : { mcpServers },
26094
- ...liveBashCapture === void 0 ? {} : {
26095
- preToolUseHookMatchers: liveBashCapture.hookMatchers,
26096
- wrapApprovedToolInput: (toolUseId, toolName2, input) => toolName2 === "Bash" ? liveBashCapture.wrapApprovedTool(toolUseId, input) : void 0
26097
- },
26098
- env: sessionEnv,
26099
- // The built-in Linzumi MCP is trusted like the codex side: its tools
26100
- // run without an approval round-trip. SDK allowedTools matches MCP
26101
- // tools as mcp__<server>__<tool>; the __* suffix covers the server.
26102
- allowedTools: ["mcp__linzumi__*"],
26103
- onSessionControls: (controls) => {
26104
- sessionControls = controls;
26105
- },
26106
- onTurnCompleted: () => {
26107
- inputQueue.completeTurn();
26108
- },
26109
- onTranscriptEvent
26110
- });
26111
- } finally {
26112
- inputQueue.close();
26113
- mcpAuthCleanup?.();
26114
- liveBashCapture?.close();
26115
- if (activeSessionId !== void 0) {
26116
- args.activeClaudeCodeSessions.delete(activeSessionId);
26117
- await args.disposeClaudeCodeForwardPortSession?.(activeSessionId);
26187
+ prompt: workDescription,
26188
+ developerInstructions: commanderDeveloperInstructions({
26189
+ cwd: args.cwd,
26190
+ agentLabel: "Claude Code",
26191
+ planTool: "todo-write",
26192
+ developerPrompt,
26193
+ linzumiContext
26194
+ }),
26195
+ model: claudeCodeModelForRuntimeModel(runtimeSettings.model),
26196
+ resumeSessionId: args.resumeSessionId,
26197
+ abortController,
26198
+ streamingInput: inputQueue,
26199
+ runner: args.options.claudeCodeRunner,
26200
+ canUseTool,
26201
+ ...mcpServers === void 0 ? {} : { mcpServers },
26202
+ ...liveBashCapture === void 0 ? {} : {
26203
+ preToolUseHookMatchers: liveBashCapture.hookMatchers,
26204
+ wrapApprovedToolInput: (toolUseId, toolName2, input) => toolName2 === "Bash" ? liveBashCapture.wrapApprovedTool(toolUseId, input) : void 0
26205
+ },
26206
+ env: sessionEnv,
26207
+ // The built-in Linzumi MCP is trusted like the codex side: its tools
26208
+ // run without an approval round-trip. SDK allowedTools matches MCP
26209
+ // tools as mcp__<server>__<tool>; the __* suffix covers the server.
26210
+ allowedTools: ["mcp__linzumi__*"],
26211
+ onSessionControls: (controls) => {
26212
+ sessionControls = controls;
26213
+ },
26214
+ onTurnCompleted: () => {
26215
+ inputQueue.completeTurn();
26216
+ settleFirstTurn();
26217
+ },
26218
+ onTranscriptEvent
26219
+ });
26220
+ } finally {
26221
+ inputQueue.close();
26222
+ mcpAuthCleanup?.();
26223
+ liveBashCapture?.close();
26224
+ if (activeSessionId !== void 0) {
26225
+ args.activeClaudeCodeSessions.delete(activeSessionId);
26226
+ await args.disposeClaudeCodeForwardPortSession?.(activeSessionId);
26227
+ }
26118
26228
  }
26229
+ } catch (error) {
26230
+ adapter.submitLifecycle(
26231
+ "codexDied",
26232
+ error instanceof Error ? error.message : String(error)
26233
+ );
26234
+ await adapter.close(5e3).catch(() => void 0);
26235
+ await Promise.race([
26236
+ planMirror.settle().catch(() => void 0),
26237
+ new Promise((resolve12) => {
26238
+ setTimeout(resolve12, 5e3).unref?.();
26239
+ })
26240
+ ]);
26241
+ logStartFailureTerminal(
26242
+ error instanceof Error ? error.message : String(error)
26243
+ );
26244
+ if (!startControlResponded) {
26245
+ args.setStartupStage("claude_code_session_failed");
26246
+ }
26247
+ throw error;
26119
26248
  }
26120
- } catch (error) {
26121
- adapter.submitLifecycle(
26122
- "codexDied",
26123
- error instanceof Error ? error.message : String(error)
26124
- );
26125
- await adapter.close(5e3).catch(() => void 0);
26249
+ if (!startControlResponded) {
26250
+ args.setStartupStage("posting_claude_code_output");
26251
+ }
26252
+ await adapter.awaitTurnsSettled(3e4);
26253
+ await adapter.close(3e4);
26126
26254
  await Promise.race([
26127
- planMirror.settle().catch(() => void 0),
26255
+ planMirror.settle(),
26128
26256
  new Promise((resolve12) => {
26129
- setTimeout(resolve12, 5e3).unref?.();
26257
+ setTimeout(resolve12, 1e4).unref?.();
26130
26258
  })
26131
26259
  ]);
26132
- logStartFailureTerminal(
26133
- error instanceof Error ? error.message : String(error)
26134
- );
26135
- args.setStartupStage("claude_code_session_failed");
26136
- throw error;
26137
- }
26138
- args.setStartupStage("posting_claude_code_output");
26139
- await adapter.awaitTurnsSettled(3e4);
26140
- await adapter.close(3e4);
26141
- await Promise.race([
26142
- planMirror.settle(),
26143
- new Promise((resolve12) => {
26144
- setTimeout(resolve12, 1e4).unref?.();
26145
- })
26260
+ return result2;
26261
+ };
26262
+ const sessionWork = runSession();
26263
+ sessionWorkHandle.value = sessionWork;
26264
+ const settled = await Promise.race([
26265
+ sessionWork.then((result2) => ({
26266
+ type: "session_ended",
26267
+ result: result2
26268
+ })),
26269
+ firstTurnSettled.then(() => ({ type: "first_turn_settled" }))
26146
26270
  ]);
26147
- return {
26271
+ const respondWithSessionId = (sessionId) => ({
26148
26272
  controlResponse: {
26149
26273
  instanceId: args.instanceId,
26150
26274
  controlType: args.control.type,
@@ -26152,16 +26276,52 @@ async function startClaudeCodeProviderInstance(args) {
26152
26276
  cwd: args.cwd,
26153
26277
  matchedRoot: args.matchedRoot,
26154
26278
  response: {
26155
- session: { id: result.sessionId }
26279
+ session: { id: sessionId }
26156
26280
  }
26157
26281
  },
26158
26282
  providerSession: {
26159
26283
  provider: "claude-code",
26160
26284
  cwd: args.cwd,
26161
26285
  matchedRoot: args.matchedRoot,
26162
- sessionId: result.sessionId
26286
+ sessionId
26163
26287
  }
26164
- };
26288
+ });
26289
+ if (settled.type === "session_ended") {
26290
+ return respondWithSessionId(settled.result.sessionId);
26291
+ }
26292
+ args.setStartupStage("posting_claude_code_output");
26293
+ startControlResponded = true;
26294
+ await adapter.awaitTurnsSettled(3e4);
26295
+ try {
26296
+ await adapter.flush(3e4);
26297
+ } catch (error) {
26298
+ args.log("claude_code.first_turn_flush_timeout", {
26299
+ linzumi_thread_id: threadId,
26300
+ claude_session_id: activeSessionId ?? null,
26301
+ message: error instanceof Error ? error.message : String(error)
26302
+ });
26303
+ }
26304
+ void sessionWork.then(
26305
+ () => {
26306
+ args.log("claude_code.session_loop_ended", {
26307
+ linzumi_thread_id: threadId,
26308
+ claude_session_id: activeSessionId ?? null
26309
+ });
26310
+ },
26311
+ (error) => {
26312
+ args.log("claude_code.session_loop_failed", {
26313
+ linzumi_thread_id: threadId,
26314
+ claude_session_id: activeSessionId ?? null,
26315
+ message: error instanceof Error ? error.message : String(error)
26316
+ });
26317
+ }
26318
+ );
26319
+ const liveSessionId = activeSessionId ?? args.resumeSessionId;
26320
+ if (liveSessionId !== void 0) {
26321
+ return respondWithSessionId(liveSessionId);
26322
+ }
26323
+ const result = await sessionWork;
26324
+ return respondWithSessionId(result.sessionId);
26165
26325
  }
26166
26326
  function claudePermissionModeForApprovalPolicy(approvalPolicy) {
26167
26327
  switch (approvalPolicy) {
@@ -26403,50 +26563,26 @@ function claudeRateLimitSignature(rateLimit) {
26403
26563
  rateLimit.resetsAtMs ?? "na"
26404
26564
  ].join(":");
26405
26565
  }
26406
- async function postClaudeCodeRateLimitMessage(args) {
26407
- const nowMs = Date.now();
26408
- const summary = claudeCodeRateLimitSummaryText(args.rateLimit, nowMs);
26409
- const headline = `Claude Code rate limit: ${summary}`;
26410
- const resetLabel = claudeCodeRateLimitResetLabel(
26411
- args.rateLimit.resetsAtMs,
26412
- nowMs
26413
- );
26414
- const richUi = args.rateLimit.utilizationPercent === void 0 ? [{ primitive: "label", label: headline }] : [
26415
- {
26416
- primitive: "progress",
26417
- value: Math.min(
26418
- 100,
26419
- Math.max(0, Math.round(args.rateLimit.utilizationPercent))
26420
- ),
26421
- max: 100,
26422
- label: `Claude Code rate limit${resetLabel === void 0 ? "" : ` - ${resetLabel}`}`
26423
- }
26424
- ];
26425
- await args.kandan.push(args.topic, "session:post_thread_message", {
26566
+ async function publishClaudeCodeRateLimitState(args) {
26567
+ await args.kandan.push(args.topic, "claude_rate_limit_state", {
26426
26568
  workspace: args.workspace,
26427
26569
  channel: args.channel,
26428
26570
  thread_id: args.threadId,
26429
- body: headline,
26430
- payload: {
26431
- rich_ui: richUi,
26432
- metadata: {
26433
- local_codex_runner: {
26434
- event_type: "claude_code_rate_limit",
26435
- agent_provider: "claude-code",
26436
- ...args.claudeSessionId === void 0 ? {} : { claude_session_id: args.claudeSessionId }
26437
- }
26438
- }
26571
+ agent_provider: "claude-code",
26572
+ ...args.claudeSessionId === void 0 ? {} : { claude_session_id: args.claudeSessionId },
26573
+ // "account" mirrors the server-side default for SDK frames that omit a
26574
+ // window type; sending it explicitly keeps the wire contract obvious.
26575
+ rate_limit_type: args.rateLimit.rateLimitType ?? "account",
26576
+ status: args.rateLimit.status ?? "allowed",
26577
+ ...args.rateLimit.utilizationPercent === void 0 ? {} : {
26578
+ utilization_percent: Math.min(
26579
+ 100,
26580
+ Math.max(0, Math.round(args.rateLimit.utilizationPercent))
26581
+ )
26439
26582
  },
26440
- client_message_id: claudeRateLimitClientMessageId(
26441
- args.threadId,
26442
- args.rateLimit
26443
- )
26583
+ ...args.rateLimit.resetsAtMs === void 0 ? {} : { resets_at_ms: args.rateLimit.resetsAtMs }
26444
26584
  });
26445
26585
  }
26446
- function claudeRateLimitClientMessageId(threadId, rateLimit) {
26447
- const digest = createHash5("sha256").update(`${threadId}:${claudeRateLimitSignature(rateLimit)}`).digest("hex").slice(0, 32);
26448
- return `claude-rate-limit-${digest}`;
26449
- }
26450
26586
  async function publishStartInstanceMessageState(kandan, topic, control, status, reason, diagnostics = {}) {
26451
26587
  const payload = startInstanceMessageStatePayload(
26452
26588
  control,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.84-beta",
3
+ "version": "0.0.85-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": {