@linzumi/cli 0.0.104-beta → 0.0.106-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 +375 -30
  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.104-beta --version
61
+ npx -y @linzumi/cli@0.0.106-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -10637,6 +10637,28 @@ async function handleKandanChatEvent(args, state, runnerIdentity, payloadContext
10637
10637
  }
10638
10638
  startPortForwardWatchIfEnabled(args, state, payloadContext);
10639
10639
  if (event.threadId !== state.kandanThreadId) {
10640
+ if (args.options.onUnroutedThreadMessage !== void 0) {
10641
+ try {
10642
+ const outcome = await args.options.onUnroutedThreadMessage(event);
10643
+ if (outcome === "spawned") {
10644
+ args.log("kandan.message_spawn_on_miss", {
10645
+ seq: event.seq,
10646
+ actor_slug: event.actorSlug ?? null,
10647
+ actor_user_id: event.actorUserId ?? null,
10648
+ thread_id: event.threadId ?? null,
10649
+ bound_thread_id: state.kandanThreadId ?? null,
10650
+ body_preview: runnerConsoleBodyPreview(event.body)
10651
+ });
10652
+ return;
10653
+ }
10654
+ } catch (error) {
10655
+ args.log("kandan.message_spawn_on_miss_failed", {
10656
+ seq: event.seq,
10657
+ thread_id: event.threadId ?? null,
10658
+ message: error instanceof Error ? error.message : String(error)
10659
+ });
10660
+ }
10661
+ }
10640
10662
  args.log("kandan.message_ignored", {
10641
10663
  seq: event.seq,
10642
10664
  actor_slug: event.actorSlug ?? null,
@@ -10750,7 +10772,9 @@ async function startThreadMessageTurn(args, state, payloadContext, message) {
10750
10772
  actorSlug: message.actorSlug,
10751
10773
  actorUserId: message.actorUserId,
10752
10774
  body: message.body,
10753
- attachments: [],
10775
+ // Forward the user's attachments (files/images) just like the normal routed
10776
+ // path; a replayed follow-up with files must not lose them.
10777
+ attachments: message.attachments ?? [],
10754
10778
  receivedAtMs: Date.now()
10755
10779
  };
10756
10780
  enqueuePendingKandanMessage(state.queue, queued);
@@ -19948,7 +19972,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19948
19972
  var init_version = __esm({
19949
19973
  "src/version.ts"() {
19950
19974
  "use strict";
19951
- linzumiCliVersion = "0.0.104-beta";
19975
+ linzumiCliVersion = "0.0.106-beta";
19952
19976
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19953
19977
  }
19954
19978
  });
@@ -24749,6 +24773,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24749
24773
  clearForwardPortAttribution(port);
24750
24774
  return capabilitiesPayload();
24751
24775
  },
24776
+ // Catch unrouted thread.message events even when no dynamic worker
24777
+ // session is attached (the durable thread's worker fully died). Late-
24778
+ // bound to dodge the const's temporal dead zone (defined below).
24779
+ onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
24752
24780
  channelSession: options.channelSession
24753
24781
  },
24754
24782
  log: log2
@@ -24879,6 +24907,34 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24879
24907
  );
24880
24908
  threadRunnerProcesses.clear();
24881
24909
  });
24910
+ const reapStaleThreadRunnerProcesses = () => {
24911
+ for (const [kandanThreadId, entry] of Array.from(
24912
+ threadRunnerProcesses.entries()
24913
+ )) {
24914
+ if (threadRunnerEntryLiveness(entry) !== "dead") {
24915
+ continue;
24916
+ }
24917
+ if (threadRunnerProcesses.get(kandanThreadId) !== entry) {
24918
+ continue;
24919
+ }
24920
+ threadRunnerProcesses.delete(kandanThreadId);
24921
+ log2("runner.thread_process_stale_entry_reaped", {
24922
+ kandanThreadId,
24923
+ pid: entry.kind === "running" ? entry.handle.processPid ?? null : null,
24924
+ prior_kind: entry.kind
24925
+ });
24926
+ void closeThreadRunnerEntry(entry).catch(() => void 0);
24927
+ }
24928
+ };
24929
+ if (shouldUseThreadProcesses(options)) {
24930
+ const reaperIntervalMs = options.threadRunnerReaperIntervalMs ?? THREAD_RUNNER_REAPER_INTERVAL_MS;
24931
+ const reaperInterval = setInterval(
24932
+ reapStaleThreadRunnerProcesses,
24933
+ reaperIntervalMs
24934
+ );
24935
+ reaperInterval.unref?.();
24936
+ cleanup.actions.push(() => clearInterval(reaperInterval));
24937
+ }
24882
24938
  const threadModelProviderBindings = /* @__PURE__ */ new Map();
24883
24939
  const threadRebuildSerializer = createThreadRebuildSerializer();
24884
24940
  const evictStaleDynamicChannelSession = async (codexThreadId, expectClient) => {
@@ -25081,6 +25137,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25081
25137
  fast: control.fast ?? options.fast,
25082
25138
  launchTui: false,
25083
25139
  rebuildOnDeadCodexWorker,
25140
+ // Late-bound (defined after startThreadRunnerProcess); the arrow keeps
25141
+ // it out of the const's temporal dead zone while still resolving the
25142
+ // live handler when an unrouted chat event actually arrives.
25143
+ onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
25084
25144
  pipelinePersistDir: commanderOutboxPersistDir(),
25085
25145
  enablePortForwardWatch: true,
25086
25146
  initialForwardPorts: allowedForwardPorts,
@@ -25277,15 +25337,29 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25277
25337
  }
25278
25338
  const existing = threadRunnerProcesses.get(kandanThreadId);
25279
25339
  if (existing !== void 0) {
25280
- return {
25281
- instanceId,
25282
- controlType: control.type,
25283
- ok: true,
25284
- delegated: true,
25285
- threadProcess: "already_started",
25340
+ const liveness = threadRunnerEntryLiveness(existing);
25341
+ if (liveness !== "dead") {
25342
+ return {
25343
+ instanceId,
25344
+ controlType: control.type,
25345
+ ok: true,
25346
+ delegated: true,
25347
+ threadProcess: "already_started",
25348
+ kandanThreadId,
25349
+ cwd
25350
+ };
25351
+ }
25352
+ log2("runner.thread_process_stale_entry_evicted", {
25286
25353
  kandanThreadId,
25287
- cwd
25288
- };
25354
+ codex_thread_id: control.type === "reconnect_thread" ? stringValue(control.codexThreadId) ?? null : null,
25355
+ prior_kind: existing.kind,
25356
+ pid: existing.kind === "running" ? existing.handle.processPid ?? null : null,
25357
+ control_type: control.type
25358
+ });
25359
+ if (threadRunnerProcesses.get(kandanThreadId) === existing) {
25360
+ threadRunnerProcesses.delete(kandanThreadId);
25361
+ }
25362
+ void closeThreadRunnerEntry(existing).catch(() => void 0);
25289
25363
  }
25290
25364
  const spawnThreadRunner = options.spawnThreadRunner ?? spawnLocalThreadRunnerProcess;
25291
25365
  const readyTimeoutMs = threadRunnerReadyTimeoutMs(options);
@@ -25398,6 +25472,17 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25398
25472
  (port) => port.port
25399
25473
  )
25400
25474
  });
25475
+ const evictOnFailedResult = async (result) => {
25476
+ if (result !== void 0 && result.ok !== false) {
25477
+ return result;
25478
+ }
25479
+ const entry = threadRunnerProcesses.get(kandanThreadId);
25480
+ if (entry === startingEntry || entry?.kind === "running") {
25481
+ threadRunnerProcesses.delete(kandanThreadId);
25482
+ }
25483
+ await (entry?.kind === "running" ? entry.handle.close() : handle.close()).catch(() => void 0);
25484
+ return result;
25485
+ };
25401
25486
  switch (control.type) {
25402
25487
  case "start_instance": {
25403
25488
  const result = await startCodexProviderInstance({
@@ -25413,27 +25498,29 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25413
25498
  setStartupStage: () => void 0,
25414
25499
  setStartedCodexThreadId: () => void 0
25415
25500
  });
25416
- return result.controlResponse;
25501
+ return await evictOnFailedResult(result.controlResponse);
25417
25502
  }
25418
25503
  case "reconnect_thread":
25419
- return await applyControl(
25420
- threadCodex,
25421
- kandan,
25422
- topic,
25423
- instanceId,
25424
- options,
25425
- agentProviders,
25426
- allowedCwds,
25427
- remoteCodexSandboxRunner,
25428
- activeRemoteCodexExecRequests,
25429
- activeClaudeCodeSessions,
25430
- pendingClaudeCodeApprovals,
25431
- ensureClaudeCodeForwardPortSession,
25432
- disposeClaudeCodeForwardPortSession,
25433
- control,
25434
- log2,
25435
- onStartedThread,
25436
- void 0
25504
+ return await evictOnFailedResult(
25505
+ await applyControl(
25506
+ threadCodex,
25507
+ kandan,
25508
+ topic,
25509
+ instanceId,
25510
+ options,
25511
+ agentProviders,
25512
+ allowedCwds,
25513
+ remoteCodexSandboxRunner,
25514
+ activeRemoteCodexExecRequests,
25515
+ activeClaudeCodeSessions,
25516
+ pendingClaudeCodeApprovals,
25517
+ ensureClaudeCodeForwardPortSession,
25518
+ disposeClaudeCodeForwardPortSession,
25519
+ control,
25520
+ log2,
25521
+ onStartedThread,
25522
+ void 0
25523
+ )
25437
25524
  );
25438
25525
  }
25439
25526
  } catch (error) {
@@ -25445,6 +25532,222 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25445
25532
  throw error;
25446
25533
  }
25447
25534
  };
25535
+ const spawnOnMissInFlight = /* @__PURE__ */ new Map();
25536
+ const spawnOnMissHandledMessages = /* @__PURE__ */ new Map();
25537
+ const replaySpawnOnMissMessageOnLiveSession = async (event, record, reconnectControl) => {
25538
+ try {
25539
+ log2("runner.spawn_on_miss_replay_on_sibling", {
25540
+ kandanThreadId: event.threadId ?? null,
25541
+ codex_thread_id: record.codexThreadId,
25542
+ seq: event.seq
25543
+ });
25544
+ const liveSession = await withCodexStartRequestTimeout(
25545
+ "thread/attach",
25546
+ resolveLiveDynamicChannelSession(record.codexThreadId, {
25547
+ waitForSiblingSpawn: true
25548
+ })
25549
+ );
25550
+ if (liveSession === void 0) {
25551
+ throw new Error(
25552
+ `spawn-on-miss could not resolve a live session for codex thread ${record.codexThreadId}`
25553
+ );
25554
+ }
25555
+ await liveSession.startThreadMessageTurn({
25556
+ seq: event.seq,
25557
+ body: event.body,
25558
+ actorSlug: event.actorSlug,
25559
+ actorUserId: event.actorUserId,
25560
+ // Forward the user's attachments; a replayed follow-up with files/images
25561
+ // must not be processed as if they were never sent.
25562
+ attachments: event.attachments,
25563
+ boundStartTimeout: true
25564
+ });
25565
+ } catch (error) {
25566
+ log2("runner.spawn_on_miss_failed", {
25567
+ kandanThreadId: event.threadId ?? null,
25568
+ codex_thread_id: record.codexThreadId,
25569
+ seq: event.seq,
25570
+ message: error instanceof Error ? error.message : String(error)
25571
+ });
25572
+ await publishSpawnOnMissMessageState(
25573
+ reconnectControl,
25574
+ "failed",
25575
+ `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25576
+ record.codexThreadId
25577
+ );
25578
+ }
25579
+ };
25580
+ const handleUnroutedThreadMessage = async (event) => {
25581
+ const kandanThreadId = event.threadId;
25582
+ if (!shouldUseThreadProcesses(options) || kandanThreadId === void 0) {
25583
+ return "ignored";
25584
+ }
25585
+ const messageKey = `${kandanThreadId}:${event.seq}`;
25586
+ const inFlightMessage = spawnOnMissHandledMessages.get(messageKey);
25587
+ if (inFlightMessage !== void 0) {
25588
+ return await inFlightMessage;
25589
+ }
25590
+ const run = handleUnroutedThreadMessageOnce(event, kandanThreadId);
25591
+ spawnOnMissHandledMessages.set(messageKey, run);
25592
+ try {
25593
+ return await run;
25594
+ } finally {
25595
+ spawnOnMissHandledMessages.delete(messageKey);
25596
+ }
25597
+ };
25598
+ const handleUnroutedThreadMessageOnce = async (event, kandanThreadId) => {
25599
+ const existing = threadRunnerProcesses.get(kandanThreadId);
25600
+ const existingLiveness = existing === void 0 ? void 0 : threadRunnerEntryLiveness(existing);
25601
+ if (existingLiveness === "live") {
25602
+ return "ignored";
25603
+ }
25604
+ const record = threadSessionRegistry.list().find((entry) => entry.kandanThreadId === kandanThreadId);
25605
+ if (record === void 0) {
25606
+ return "ignored";
25607
+ }
25608
+ const baseControl = rehydrationReconnectControl(record);
25609
+ const providerBinding = threadModelProviderBindings.get(kandanThreadId);
25610
+ const reconnectControl = {
25611
+ ...baseControl,
25612
+ sourceSeq: event.seq,
25613
+ workDescription: event.body,
25614
+ // Carry the user's attachments on the reconstructed control (Codex P2) so
25615
+ // the FIRST spawn-on-miss message - the one that owns the respawn and runs
25616
+ // its turn inside startThreadRunnerProcess, not via the replay helper -
25617
+ // still resumes with its files/images. Re-serialized to the wire shape the
25618
+ // resume turn parses (parseKandanChatAttachments).
25619
+ ...event.attachments.length === 0 ? {} : { attachments: kandanChatAttachmentsToWire(event.attachments) },
25620
+ ...providerBinding?.modelProvider === void 0 ? {} : { modelProvider: providerBinding.modelProvider },
25621
+ ...providerBinding?.llmProxy === void 0 ? {} : { llmProxy: providerBinding.llmProxy }
25622
+ };
25623
+ const cwd = resolveAllowedCwd(record.cwd, allowedCwds.value);
25624
+ if (!cwd.ok) {
25625
+ log2("runner.spawn_on_miss_cwd_rejected", {
25626
+ kandanThreadId,
25627
+ cwd: record.cwd,
25628
+ reason: cwd.reason,
25629
+ seq: event.seq
25630
+ });
25631
+ await publishSpawnOnMissMessageState(
25632
+ reconnectControl,
25633
+ "failed",
25634
+ `could not resume the thread's coding session: working directory ${record.cwd} is not an allowed root (${cwd.reason})`,
25635
+ record.codexThreadId
25636
+ );
25637
+ return "spawned";
25638
+ }
25639
+ const resolvedCwd = cwd.cwd;
25640
+ const inFlightSpawn = spawnOnMissInFlight.get(kandanThreadId);
25641
+ if (inFlightSpawn !== void 0) {
25642
+ log2("runner.spawn_on_miss_resuming", {
25643
+ kandanThreadId,
25644
+ codex_thread_id: record.codexThreadId,
25645
+ provider_session_key: record.codexThreadId,
25646
+ seq: event.seq,
25647
+ decision: "await_in_flight_spawn"
25648
+ });
25649
+ const outcome = await inFlightSpawn;
25650
+ if (outcome === "no_session") {
25651
+ await publishSpawnOnMissMessageState(
25652
+ reconnectControl,
25653
+ "failed",
25654
+ `could not resume the thread's coding session: no live session after spawn`,
25655
+ record.codexThreadId
25656
+ );
25657
+ return "spawned";
25658
+ }
25659
+ await replaySpawnOnMissMessageOnLiveSession(
25660
+ event,
25661
+ record,
25662
+ reconnectControl
25663
+ );
25664
+ return "spawned";
25665
+ }
25666
+ log2("runner.spawn_on_miss_resuming", {
25667
+ kandanThreadId,
25668
+ codex_thread_id: record.codexThreadId,
25669
+ provider_session_key: record.codexThreadId,
25670
+ seq: event.seq,
25671
+ decision: "spawned_resume"
25672
+ });
25673
+ let spawnSettle = () => void 0;
25674
+ const spawnPromise = new Promise((resolve12) => {
25675
+ spawnSettle = resolve12;
25676
+ });
25677
+ spawnOnMissInFlight.set(kandanThreadId, spawnPromise);
25678
+ try {
25679
+ const startResult = await startThreadRunnerProcess(
25680
+ reconnectControl,
25681
+ resolvedCwd
25682
+ );
25683
+ if (threadStartDelegatedToSibling(startResult)) {
25684
+ spawnSettle("spawned");
25685
+ await replaySpawnOnMissMessageOnLiveSession(
25686
+ event,
25687
+ record,
25688
+ reconnectControl
25689
+ );
25690
+ return "spawned";
25691
+ }
25692
+ if (startResult === void 0 || startResult.ok === false) {
25693
+ spawnSettle("no_session");
25694
+ const startError = startResult !== void 0 && typeof startResult.error === "string" ? startResult.error : "the thread could not be resumed (no resumable codex session)";
25695
+ log2("runner.spawn_on_miss_failed", {
25696
+ kandanThreadId,
25697
+ codex_thread_id: record.codexThreadId,
25698
+ seq: event.seq,
25699
+ message: startError
25700
+ });
25701
+ await publishSpawnOnMissMessageState(
25702
+ reconnectControl,
25703
+ "failed",
25704
+ `could not resume the thread's coding session: ${startError}`,
25705
+ record.codexThreadId
25706
+ );
25707
+ return "spawned";
25708
+ }
25709
+ spawnSettle("spawned");
25710
+ return "spawned";
25711
+ } catch (error) {
25712
+ spawnSettle("no_session");
25713
+ log2("runner.spawn_on_miss_failed", {
25714
+ kandanThreadId,
25715
+ codex_thread_id: record.codexThreadId,
25716
+ seq: event.seq,
25717
+ message: error instanceof Error ? error.message : String(error)
25718
+ });
25719
+ await publishSpawnOnMissMessageState(
25720
+ reconnectControl,
25721
+ "failed",
25722
+ `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25723
+ record.codexThreadId
25724
+ );
25725
+ return "spawned";
25726
+ } finally {
25727
+ spawnSettle("spawned");
25728
+ if (spawnOnMissInFlight.get(kandanThreadId) === spawnPromise) {
25729
+ spawnOnMissInFlight.delete(kandanThreadId);
25730
+ }
25731
+ }
25732
+ };
25733
+ const publishSpawnOnMissMessageState = async (control, status, reason, codexThreadId) => {
25734
+ const payload = startInstanceMessageStatePayload(control, status, reason, {
25735
+ instanceId,
25736
+ codexThreadId
25737
+ });
25738
+ if (payload === void 0) {
25739
+ return;
25740
+ }
25741
+ try {
25742
+ await kandan.push(topic, "message_state", payload);
25743
+ } catch (error) {
25744
+ log2("runner.spawn_on_miss_state_push_failed", {
25745
+ thread_id: optionalThreadControlField(control, "threadId") ?? null,
25746
+ status,
25747
+ message: error instanceof Error ? error.message : String(error)
25748
+ });
25749
+ }
25750
+ };
25448
25751
  const heartbeatPayload = () => ({
25449
25752
  instanceId,
25450
25753
  clientId,
@@ -27462,11 +27765,15 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27462
27765
  })
27463
27766
  );
27464
27767
  const identity = identityFromAccessToken(options.token);
27768
+ const reconnectAttachments = parseKandanChatAttachments(
27769
+ arrayValue(control.attachments) ?? []
27770
+ );
27465
27771
  await startedThreadSession.startThreadMessageTurn({
27466
27772
  seq: sourceSeq,
27467
27773
  body: workDescription,
27468
27774
  actorSlug: identity.actorUsername,
27469
27775
  actorUserId: identity.actorUserId,
27776
+ attachments: reconnectAttachments,
27470
27777
  boundStartTimeout: true
27471
27778
  });
27472
27779
  }
@@ -28141,6 +28448,9 @@ async function startCodexProviderInstance(args) {
28141
28448
  args.setStartupStage("starting_first_turn");
28142
28449
  const rootSeq = integerValue(args.control.rootSeq);
28143
28450
  const sourceSeq = integerValue(args.control.sourceSeq) ?? rootSeq;
28451
+ const firstTurnAttachments = parseKandanChatAttachments(
28452
+ arrayValue(args.control.attachments) ?? []
28453
+ );
28144
28454
  if (startedThreadSession !== void 0 && sourceSeq !== void 0) {
28145
28455
  const identity = identityFromAccessToken(args.options.token);
28146
28456
  await startedThreadSession.startThreadMessageTurn({
@@ -28148,6 +28458,7 @@ async function startCodexProviderInstance(args) {
28148
28458
  body: workDescription,
28149
28459
  actorSlug: identity.actorUsername,
28150
28460
  actorUserId: identity.actorUserId,
28461
+ attachments: firstTurnAttachments,
28151
28462
  boundStartTimeout: true
28152
28463
  });
28153
28464
  } else {
@@ -29506,6 +29817,16 @@ function rehydrationControlSnapshot(args) {
29506
29817
  ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
29507
29818
  };
29508
29819
  }
29820
+ function kandanChatAttachmentsToWire(attachments) {
29821
+ return attachments.map((attachment) => ({
29822
+ ...attachment.id === void 0 ? {} : { id: attachment.id },
29823
+ ...attachment.kind === void 0 ? {} : { kind: attachment.kind },
29824
+ ...attachment.fileName === void 0 ? {} : { file_name: attachment.fileName },
29825
+ ...attachment.contentType === void 0 ? {} : { content_type: attachment.contentType },
29826
+ ...attachment.sizeBytes === void 0 ? {} : { size_bytes: attachment.sizeBytes },
29827
+ ...attachment.url === void 0 ? {} : { url: attachment.url }
29828
+ }));
29829
+ }
29509
29830
  function rehydrationReconnectControl(record) {
29510
29831
  const snapshot = record.control;
29511
29832
  const rootSeq = integerValue(snapshot.rootSeq);
@@ -29696,6 +30017,29 @@ function threadRunnerOptions(args) {
29696
30017
  channelSession: void 0
29697
30018
  };
29698
30019
  }
30020
+ function processIsAlive2(pid) {
30021
+ if (pid === void 0 || !Number.isInteger(pid) || pid <= 0) {
30022
+ return true;
30023
+ }
30024
+ try {
30025
+ process.kill(pid, 0);
30026
+ return true;
30027
+ } catch (error) {
30028
+ const code = error?.code;
30029
+ return code === "EPERM";
30030
+ }
30031
+ }
30032
+ function threadRunnerEntryLiveness(entry) {
30033
+ if (entry.kind === "starting") {
30034
+ return "starting";
30035
+ }
30036
+ const handle = entry.handle;
30037
+ const ipcClosed = handle.codex.isClosed?.() === true;
30038
+ if (ipcClosed) {
30039
+ return "dead";
30040
+ }
30041
+ return processIsAlive2(handle.processPid) ? "live" : "dead";
30042
+ }
29699
30043
  async function closeThreadRunnerEntry(entry) {
29700
30044
  switch (entry.kind) {
29701
30045
  case "running":
@@ -31411,7 +31755,7 @@ async function suggestRunnerTasks(control, options, allowedCwds) {
31411
31755
  };
31412
31756
  }
31413
31757
  }
31414
- var THREAD_RUNNER_READY_TIMEOUT_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
31758
+ var THREAD_RUNNER_READY_TIMEOUT_MS, THREAD_RUNNER_REAPER_INTERVAL_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
31415
31759
  var init_runner = __esm({
31416
31760
  "src/runner.ts"() {
31417
31761
  "use strict";
@@ -31462,6 +31806,7 @@ var init_runner = __esm({
31462
31806
  init_userFacingErrors();
31463
31807
  init_remoteCodexSandboxRunner();
31464
31808
  THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
31809
+ THREAD_RUNNER_REAPER_INTERVAL_MS = 3e4;
31465
31810
  onboardingDiscoveryAgentStartKeys = /* @__PURE__ */ new Set();
31466
31811
  onboardingConversationImportKeys = /* @__PURE__ */ new Set();
31467
31812
  onboardingConversationDiscoveryReportLimit = 100;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.104-beta",
3
+ "version": "0.0.106-beta",
4
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": {