@linzumi/cli 0.0.93-beta → 0.0.95-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 +326 -36
  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.93-beta --version
61
+ npx -y @linzumi/cli@0.0.95-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -112,6 +112,14 @@ import { spawnSync } from "node:child_process";
112
112
  import { homedir } from "node:os";
113
113
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
114
114
  import { basename, dirname, join } from "node:path";
115
+ function projectScanBudgetMs() {
116
+ const raw = process.env.LINZUMI_ONBOARDING_PROJECT_SCAN_BUDGET_MS?.trim();
117
+ if (raw === void 0 || raw === "") {
118
+ return defaultProjectScanBudgetMs;
119
+ }
120
+ const parsed = Number.parseInt(raw, 10);
121
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultProjectScanBudgetMs;
122
+ }
115
123
  function discoverCurrentGitProject(args) {
116
124
  const startedAtMs = args.nowMs ?? Date.now();
117
125
  const responsiveness = args.placeResponsiveness;
@@ -136,7 +144,19 @@ function discoverCurrentGitProject(args) {
136
144
  });
137
145
  const sourceRoots = args.sourceRoots ?? defaultLocalProjectSourceRoots(args.cwd);
138
146
  let placesSearched = 1;
147
+ const scanBudgetMs = projectScanBudgetMs();
139
148
  for (const sourceRoot of sourceRoots) {
149
+ if (Date.now() - startedAtMs >= scanBudgetMs) {
150
+ searchStats.push({
151
+ place: sourceRoot,
152
+ source: "git",
153
+ duration_ms: 0,
154
+ result_count: 0,
155
+ skipped_scan_budget_exceeded: true
156
+ });
157
+ placesSearched += 1;
158
+ continue;
159
+ }
140
160
  const sourceRootStartedAtMs = Date.now();
141
161
  const rootResponsive = isPlaceResponsive(sourceRoot, responsiveness);
142
162
  if (!rootResponsive) {
@@ -296,6 +316,9 @@ function isGitWorktreeRoot(path2) {
296
316
  return existsSync(join(path2, ".git"));
297
317
  }
298
318
  function defaultLocalProjectSourceRoots(cwd) {
319
+ if (cwd === dirname(cwd)) {
320
+ return [];
321
+ }
299
322
  return uniqueStrings([
300
323
  ...ancestorSourceRoots(cwd),
301
324
  ...homeSourceRoots(),
@@ -409,13 +432,14 @@ function compactJsonObject(value) {
409
432
  })
410
433
  );
411
434
  }
412
- var worktreePathSampleLimit, gitSpawnTimeoutMs;
435
+ var worktreePathSampleLimit, gitSpawnTimeoutMs, defaultProjectScanBudgetMs;
413
436
  var init_onboardingProjectDiscovery = __esm({
414
437
  "src/onboardingProjectDiscovery.ts"() {
415
438
  "use strict";
416
439
  init_onboardingPlaceResponsiveness();
417
440
  worktreePathSampleLimit = 25;
418
441
  gitSpawnTimeoutMs = 4e3;
442
+ defaultProjectScanBudgetMs = 2e4;
419
443
  }
420
444
  });
421
445
 
@@ -8437,6 +8461,38 @@ var init_userFacingErrors = __esm({
8437
8461
 
8438
8462
  // src/channelSession.ts
8439
8463
  import { createHash as createHash3, randomUUID } from "node:crypto";
8464
+ function codexStartRequestTimeoutMs() {
8465
+ const raw = process.env.LINZUMI_CODEX_START_REQUEST_TIMEOUT_MS?.trim();
8466
+ if (raw === void 0 || raw === "") {
8467
+ return CODEX_START_REQUEST_TIMEOUT_MS;
8468
+ }
8469
+ const parsed = Number.parseInt(raw, 10);
8470
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : CODEX_START_REQUEST_TIMEOUT_MS;
8471
+ }
8472
+ function isCodexStartRequestTimeoutError(error) {
8473
+ return error instanceof CodexStartRequestTimeoutError;
8474
+ }
8475
+ function withCodexStartRequestTimeout(method, request, timeoutMs = codexStartRequestTimeoutMs()) {
8476
+ if (!(timeoutMs > 0)) {
8477
+ return request;
8478
+ }
8479
+ return new Promise((resolve12, reject) => {
8480
+ const timer = setTimeout(() => {
8481
+ reject(new CodexStartRequestTimeoutError(method, timeoutMs));
8482
+ }, timeoutMs);
8483
+ timer.unref?.();
8484
+ request.then(
8485
+ (value) => {
8486
+ clearTimeout(timer);
8487
+ resolve12(value);
8488
+ },
8489
+ (error) => {
8490
+ clearTimeout(timer);
8491
+ reject(error);
8492
+ }
8493
+ );
8494
+ });
8495
+ }
8440
8496
  async function attachChannelSession(args) {
8441
8497
  const session = args.options.channelSession;
8442
8498
  const chatTopic = `chat:${session.workspaceSlug}:${session.channelSlug}`;
@@ -8941,6 +8997,7 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
8941
8997
  failedSeqs: /* @__PURE__ */ new Map(),
8942
8998
  drainInFlight: false,
8943
8999
  drainRequested: false,
9000
+ boundStartTimeout: false,
8944
9001
  fusedQueuedSeqs: /* @__PURE__ */ new Map(),
8945
9002
  reconnectRetryTimer: void 0,
8946
9003
  reconnectRetryAttempts: 0,
@@ -10295,6 +10352,9 @@ async function startThreadMessageTurn(args, state, payloadContext, message) {
10295
10352
  if (state.kandanThreadId === void 0 || state.codexThreadId === void 0) {
10296
10353
  throw new Error("cannot start a local Codex turn before thread binding");
10297
10354
  }
10355
+ if (message.boundStartTimeout === true) {
10356
+ state.boundStartTimeout = true;
10357
+ }
10298
10358
  const queued = {
10299
10359
  seq: message.seq,
10300
10360
  actorSlug: message.actorSlug,
@@ -10558,7 +10618,9 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10558
10618
  if (state.kandanThreadId !== void 0) {
10559
10619
  startCodexTypingHeartbeat(args, state, state.kandanThreadId);
10560
10620
  }
10561
- const started = await args.codex.request("turn/start", {
10621
+ const boundStartTimeout = state.boundStartTimeout;
10622
+ state.boundStartTimeout = false;
10623
+ const turnStartRequest = args.codex.request("turn/start", {
10562
10624
  threadId: codexThreadId,
10563
10625
  input: await codexInputItemsForQueuedKandanMessage(
10564
10626
  args,
@@ -10569,6 +10631,7 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10569
10631
  runtimeOptionsForSettings(args.options, state.runtimeSettings)
10570
10632
  )
10571
10633
  });
10634
+ const started = boundStartTimeout ? await withCodexStartRequestTimeout("turn/start", turnStartRequest) : await turnStartRequest;
10572
10635
  const turnId = extractTurnIdFromResponse(started);
10573
10636
  state.pendingReconnectContextInjection = void 0;
10574
10637
  const interruptAfterStart = state.turn.status === "starting" && state.turn.interruptAfterStart;
@@ -10641,6 +10704,9 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10641
10704
  queued_seq: next.seq,
10642
10705
  message: error instanceof Error ? error.message : String(error)
10643
10706
  });
10707
+ if (isCodexStartRequestTimeoutError(error)) {
10708
+ throw error;
10709
+ }
10644
10710
  }
10645
10711
  }
10646
10712
  function drainKandanMessageQueueBlocked(args, state) {
@@ -11654,7 +11720,7 @@ async function pushOptional(kandan, topic, event, payload, log2) {
11654
11720
  });
11655
11721
  }
11656
11722
  }
11657
- var codexTypingHeartbeatMs, maxTrackedFailedChatEventSeqs, maxChatEventFailureAttempts, maxForwardedTurnIds, maxClaimedKandanMessageKeys, claimedKandanMessageKeys, nextChatJoinRegistrationSerial, defaultReconnectHandlingRetryDelaysMs, defaultTuiInputMirrorRetryDelaysMs, pipelineCloseTimeoutMs;
11723
+ var codexTypingHeartbeatMs, CODEX_START_REQUEST_TIMEOUT_MS, CodexStartRequestTimeoutError, maxTrackedFailedChatEventSeqs, maxChatEventFailureAttempts, maxForwardedTurnIds, maxClaimedKandanMessageKeys, claimedKandanMessageKeys, nextChatJoinRegistrationSerial, defaultReconnectHandlingRetryDelaysMs, defaultTuiInputMirrorRetryDelaysMs, pipelineCloseTimeoutMs;
11658
11724
  var init_channelSession = __esm({
11659
11725
  "src/channelSession.ts"() {
11660
11726
  "use strict";
@@ -11679,6 +11745,15 @@ var init_channelSession = __esm({
11679
11745
  init_processNameCatalog();
11680
11746
  init_userFacingErrors();
11681
11747
  codexTypingHeartbeatMs = 5e3;
11748
+ CODEX_START_REQUEST_TIMEOUT_MS = 12e4;
11749
+ CodexStartRequestTimeoutError = class extends Error {
11750
+ constructor(method, timeoutMs) {
11751
+ super(
11752
+ `Codex ${method} did not reply within ${timeoutMs}ms (start request timed out)`
11753
+ );
11754
+ this.name = "CodexStartRequestTimeoutError";
11755
+ }
11756
+ };
11682
11757
  maxTrackedFailedChatEventSeqs = 256;
11683
11758
  maxChatEventFailureAttempts = 5;
11684
11759
  maxForwardedTurnIds = 64;
@@ -19376,7 +19451,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19376
19451
  var init_version = __esm({
19377
19452
  "src/version.ts"() {
19378
19453
  "use strict";
19379
- linzumiCliVersion = "0.0.93-beta";
19454
+ linzumiCliVersion = "0.0.95-beta";
19380
19455
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19381
19456
  }
19382
19457
  });
@@ -19487,6 +19562,82 @@ function assessRunnerLockHolder(record, isPidAlive, now) {
19487
19562
  }
19488
19563
  return { state: "live" };
19489
19564
  }
19565
+ function shouldAutoTakeOverForUpdate(existing, myVersion, myLaunchSource, holderState) {
19566
+ if (myLaunchSource !== electronAutoConnectLaunchSource) {
19567
+ return false;
19568
+ }
19569
+ if (existing.launchSource !== electronAutoConnectLaunchSource) {
19570
+ return false;
19571
+ }
19572
+ if (holderState.state !== "live") {
19573
+ return true;
19574
+ }
19575
+ return compareCliVersions(myVersion, existing.cliVersion) > 0;
19576
+ }
19577
+ function compareCliVersions(a, b) {
19578
+ const pa = parseCliVersion(a);
19579
+ const pb = parseCliVersion(b);
19580
+ for (let i = 0; i < 3; i++) {
19581
+ if (pa.core[i] !== pb.core[i]) {
19582
+ return pa.core[i] > pb.core[i] ? 1 : -1;
19583
+ }
19584
+ }
19585
+ if (pa.prerelease.length === 0 && pb.prerelease.length === 0) {
19586
+ return 0;
19587
+ }
19588
+ if (pa.prerelease.length === 0) {
19589
+ return 1;
19590
+ }
19591
+ if (pb.prerelease.length === 0) {
19592
+ return -1;
19593
+ }
19594
+ return comparePrerelease(pa.prerelease, pb.prerelease);
19595
+ }
19596
+ function parseCliVersion(value) {
19597
+ const trimmed = value.trim().replace(/^v/i, "").split("+", 1)[0] ?? "";
19598
+ const hyphen = trimmed.indexOf("-");
19599
+ const coreText = hyphen === -1 ? trimmed : trimmed.slice(0, hyphen);
19600
+ const prereleaseText = hyphen === -1 ? "" : trimmed.slice(hyphen + 1);
19601
+ const parts = coreText.split(".");
19602
+ const core = [
19603
+ parseCoreNumber(parts[0]),
19604
+ parseCoreNumber(parts[1]),
19605
+ parseCoreNumber(parts[2])
19606
+ ];
19607
+ const prerelease = prereleaseText === "" ? [] : prereleaseText.split(".").filter((id) => id !== "");
19608
+ return { core, prerelease };
19609
+ }
19610
+ function parseCoreNumber(value) {
19611
+ const parsed = Number.parseInt(value ?? "", 10);
19612
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
19613
+ }
19614
+ function comparePrerelease(a, b) {
19615
+ const length = Math.max(a.length, b.length);
19616
+ for (let i = 0; i < length; i++) {
19617
+ if (i >= a.length) {
19618
+ return -1;
19619
+ }
19620
+ if (i >= b.length) {
19621
+ return 1;
19622
+ }
19623
+ const idA = a[i];
19624
+ const idB = b[i];
19625
+ const numA = Number.parseInt(idA, 10);
19626
+ const numB = Number.parseInt(idB, 10);
19627
+ const aIsNum = numA.toString() === idA;
19628
+ const bIsNum = numB.toString() === idB;
19629
+ if (aIsNum && bIsNum) {
19630
+ if (numA !== numB) {
19631
+ return numA > numB ? 1 : -1;
19632
+ }
19633
+ } else if (aIsNum !== bIsNum) {
19634
+ return aIsNum ? -1 : 1;
19635
+ } else if (idA !== idB) {
19636
+ return idA > idB ? 1 : -1;
19637
+ }
19638
+ }
19639
+ return 0;
19640
+ }
19490
19641
  function takeOverWedgedHolder(lockPath, holder, reason, killPid, onTakeover) {
19491
19642
  try {
19492
19643
  killPid(holder.pid);
@@ -19754,7 +19905,7 @@ function displayLinzumiUrl(linzumiUrl) {
19754
19905
  function isNodeErrorCode2(error, code) {
19755
19906
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
19756
19907
  }
19757
- var runnerLockHeartbeatIntervalMs, runnerLockTakeoverAfterMs, runnerLockHeldErrorName, RunnerLockHeldError;
19908
+ var runnerLockHeartbeatIntervalMs, runnerLockTakeoverAfterMs, runnerLockHeldErrorName, RunnerLockHeldError, electronAutoConnectLaunchSource;
19758
19909
  var init_runnerLock = __esm({
19759
19910
  "src/runnerLock.ts"() {
19760
19911
  "use strict";
@@ -19775,6 +19926,7 @@ var init_runnerLock = __esm({
19775
19926
  this.heldBy = heldBy;
19776
19927
  }
19777
19928
  };
19929
+ electronAutoConnectLaunchSource = "electron_auto_connect";
19778
19930
  }
19779
19931
  });
19780
19932
 
@@ -22958,6 +23110,34 @@ async function runThreadCodexWorker(options) {
22958
23110
  async function openLocalCodexRunner(options, log2, cleanup, close) {
22959
23111
  const agentProviders = availableRunnerAgentProviders(options);
22960
23112
  const localCodexAppServerAvailable = hasLocalCodexAppServerCapability(options);
23113
+ const dependencyStatusRef = {
23114
+ value: options.dependencyStatus
23115
+ };
23116
+ const refreshDependencyStatus = async () => {
23117
+ if (dependencyStatusRef.value === void 0) {
23118
+ return;
23119
+ }
23120
+ try {
23121
+ const refreshed = await buildRunnerDependencyStatus({
23122
+ cwd: options.cwd,
23123
+ codexBin: options.codexBin,
23124
+ codeServerBin: options.codeServerBin,
23125
+ editorRuntime: dependencyStatusRef.value?.editorRuntime
23126
+ });
23127
+ const previousCodexAvailable = dependencyStatusRef.value?.codex.available;
23128
+ dependencyStatusRef.value = refreshed;
23129
+ log2("runner.dependency_status_reprobed", {
23130
+ runnerId: options.runnerId,
23131
+ codex_available: refreshed.codex.available,
23132
+ codex_available_changed: previousCodexAvailable !== refreshed.codex.available
23133
+ });
23134
+ } catch (error) {
23135
+ log2("runner.dependency_status_reprobe_failed", {
23136
+ runnerId: options.runnerId,
23137
+ message: error instanceof Error ? error.message : String(error)
23138
+ });
23139
+ }
23140
+ };
22961
23141
  const allowedForwardPorts = options.allowedForwardPorts ?? [];
22962
23142
  const liveForwardPorts = new Set(allowedForwardPorts);
22963
23143
  const managedForwardPorts = /* @__PURE__ */ new Set();
@@ -23130,8 +23310,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23130
23310
  ),
23131
23311
  forwardedPortAttributions: buildForwardPortAttributionPayload(),
23132
23312
  pendingEditorForwardPortRequests: pendingEditorForwardPortRequests(),
23133
- toolStatus: options.dependencyStatus === void 0 ? null : dependencyStatusPayload(options.dependencyStatus),
23134
- editorRuntime: options.dependencyStatus?.editorRuntime === void 0 ? null : dependencyStatusPayload(options.dependencyStatus).editorRuntime,
23313
+ toolStatus: dependencyStatusRef.value === void 0 ? null : dependencyStatusPayload(dependencyStatusRef.value),
23314
+ editorRuntime: dependencyStatusRef.value?.editorRuntime === void 0 ? null : dependencyStatusPayload(dependencyStatusRef.value).editorRuntime,
23135
23315
  ...localEditorCapabilities(
23136
23316
  options.editorRuntime,
23137
23317
  allowedCwds.value,
@@ -24994,14 +25174,15 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24994
25174
  log2,
24995
25175
  pendingControls,
24996
25176
  controlDispatcher,
24997
- dispatchControl
25177
+ dispatchControl,
25178
+ refreshDependencyStatus
24998
25179
  );
24999
25180
  return { instanceId, codexUrl, close };
25000
25181
  }
25001
- function finishRunnerStartup(options, log2, pendingControls, controlDispatcher, dispatchControl) {
25182
+ function finishRunnerStartup(options, log2, pendingControls, controlDispatcher, dispatchControl, onOnboardingDiscoveryComplete) {
25002
25183
  controlDispatcher.value = dispatchControl;
25003
25184
  pendingControls.splice(0).forEach(({ control, ack }) => dispatchControl(control, ack));
25004
- startOnboardingDiscoveryAgents(options, log2);
25185
+ startOnboardingDiscoveryAgents(options, log2, onOnboardingDiscoveryComplete);
25005
25186
  }
25006
25187
  function commanderOutboxPersistDir() {
25007
25188
  const override = process.env.LINZUMI_COMMANDER_OUTBOX_DIR?.trim();
@@ -25632,6 +25813,43 @@ async function acquireRunnerLockWithConflictResolution(acquire, options, log2) {
25632
25813
  lockPath: error.lockPath
25633
25814
  });
25634
25815
  const lockTakeover = options.lockTakeover;
25816
+ const autoTakeoverDeps = resolveRunnerLockTakeoverDeps(
25817
+ error.lockPath,
25818
+ lockTakeover?.takeover,
25819
+ log2
25820
+ );
25821
+ const holderState = assessRunnerLockHolder(
25822
+ error.heldBy,
25823
+ autoTakeoverDeps.isPidAlive,
25824
+ () => new Date(autoTakeoverDeps.now())
25825
+ );
25826
+ if (shouldAutoTakeOverForUpdate(
25827
+ error.heldBy,
25828
+ linzumiCliVersion,
25829
+ options.launchSource,
25830
+ holderState
25831
+ )) {
25832
+ log2("runner.lock_auto_takeover_for_update", {
25833
+ runnerId: options.runnerId,
25834
+ heldByRunnerId: error.heldBy.runnerId,
25835
+ heldByPid: error.heldBy.pid,
25836
+ lockPath: error.lockPath,
25837
+ fromVersion: error.heldBy.cliVersion,
25838
+ toVersion: linzumiCliVersion,
25839
+ launchSource: electronAutoConnectLaunchSource,
25840
+ holderState: holderState.state
25841
+ });
25842
+ const autoPromptDeps = lockTakeover?.prompt ?? defaultRunnerLockTakeoverPromptDeps(lockTakeover);
25843
+ await resolveRunnerLockConflict({
25844
+ holder: error.heldBy,
25845
+ lockPath: error.lockPath,
25846
+ baseMessage: error.message,
25847
+ takeOverWithoutPrompt: true,
25848
+ prompt: autoPromptDeps,
25849
+ takeover: autoTakeoverDeps
25850
+ });
25851
+ return acquire();
25852
+ }
25635
25853
  if (!shouldResolveRunnerLockConflict(lockTakeover)) {
25636
25854
  throw error;
25637
25855
  }
@@ -25848,6 +26066,32 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
25848
26066
  );
25849
26067
  }
25850
26068
  }
26069
+ async function applyCodexUnavailableControl(kandan, topic, instanceId, control, log2) {
26070
+ const error = "agent_provider_unavailable:codex";
26071
+ log2("kandan.start_instance_codex_app_server_unavailable", {
26072
+ controlType: control.type,
26073
+ thread_id: controlThreadId(control) ?? null,
26074
+ instanceId
26075
+ });
26076
+ if (control.type === "start_instance" || control.type === "reconnect_thread") {
26077
+ try {
26078
+ await publishStartInstanceMessageState(
26079
+ kandan,
26080
+ topic,
26081
+ control,
26082
+ "failed",
26083
+ error,
26084
+ { instanceId }
26085
+ );
26086
+ } catch (publishError) {
26087
+ log2("kandan.start_instance_unavailable_state_push_failed", {
26088
+ agent_provider: "codex",
26089
+ message: publishError instanceof Error ? publishError.message : String(publishError)
26090
+ });
26091
+ }
26092
+ }
26093
+ throw new Error(error);
26094
+ }
25851
26095
  async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart) {
25852
26096
  if (codex === void 0) {
25853
26097
  switch (control.type) {
@@ -25861,6 +26105,43 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
25861
26105
  activeRemoteCodexExecRequests,
25862
26106
  log2
25863
26107
  );
26108
+ // PROD WEDGE (coding jobs down on the shipped CLI beta): a runner whose
26109
+ // cached dependencyStatus.codex.available was `false` at connect (the
26110
+ // `codex --version` probe timed out during the blocking onboarding scan
26111
+ // and was never re-probed) never started its SHARED codex app-server, so
26112
+ // this `codex` is undefined. But a start_instance / reconnect_thread runs
26113
+ // in its OWN thread worker that spawns its OWN codex app-server - it does
26114
+ // NOT need the shared client. The old `default` arm returned
26115
+ // `agent_provider_unavailable:codex` HERE, before delegating, and only as
26116
+ // a bare codex_response: no thread-worker spawn, no
26117
+ // `runner.thread_process_starting` log, no message_state - a SILENT no-op
26118
+ // that left the user's job stuck in `coding` (prod thread 99d5808e,
26119
+ // control_seq 13). When a thread worker IS available (onThreadProcessStart
26120
+ // wired), fall through to the normal switch so the control delegates to
26121
+ // the worker; the worker's own start surfaces any genuine engine failure.
26122
+ // When NO thread worker exists, codex genuinely cannot run the job, so
26123
+ // fail LOUD (log + failed message_state + throw -> codex_error) instead of
26124
+ // leaving it silently stuck. This loud path is SCOPED to start_instance /
26125
+ // reconnect_thread only.
26126
+ case "start_instance":
26127
+ case "reconnect_thread":
26128
+ if (onThreadProcessStart !== void 0) {
26129
+ break;
26130
+ }
26131
+ return await applyCodexUnavailableControl(
26132
+ kandan,
26133
+ topic,
26134
+ instanceId,
26135
+ control,
26136
+ log2
26137
+ );
26138
+ // Every OTHER control type that reaches here is a request/response control
26139
+ // that does NOT need the shared codex app-server to fail gracefully
26140
+ // (browse_directory, create_project, suggest_tasks,
26141
+ // list_claude_code_sessions, interrupt, cancel_turn, ...). Preserve the
26142
+ // original behavior: return a bare `agent_provider_unavailable:codex`
26143
+ // codex_response - NO throw, NO failed message_state, NO start_instance
26144
+ // log - so onboarding/UI features keep working while codex is unavailable.
25864
26145
  default:
25865
26146
  return {
25866
26147
  instanceId,
@@ -26998,22 +27279,25 @@ async function startCodexProviderInstance(args) {
26998
27279
  args.options,
26999
27280
  args.control
27000
27281
  );
27001
- const response = await args.codex.request("thread/start", {
27002
- cwd: args.cwd,
27003
- serviceName: "kandan-local-runner",
27004
- personality: "pragmatic",
27005
- developerInstructions: commanderDeveloperInstructions({
27282
+ const response = await withCodexStartRequestTimeout(
27283
+ "thread/start",
27284
+ args.codex.request("thread/start", {
27006
27285
  cwd: args.cwd,
27007
- planTool: "linzumi-mcp",
27008
- developerPrompt,
27009
- linzumiContext
27010
- }),
27011
- ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
27012
- ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
27013
- ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
27014
- ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
27015
- ...runtimeSettings.fast === true ? { serviceTier: "fast" } : {}
27016
- });
27286
+ serviceName: "kandan-local-runner",
27287
+ personality: "pragmatic",
27288
+ developerInstructions: commanderDeveloperInstructions({
27289
+ cwd: args.cwd,
27290
+ planTool: "linzumi-mcp",
27291
+ developerPrompt,
27292
+ linzumiContext
27293
+ }),
27294
+ ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
27295
+ ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
27296
+ ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
27297
+ ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
27298
+ ...runtimeSettings.fast === true ? { serviceTier: "fast" } : {}
27299
+ })
27300
+ );
27017
27301
  const codexThreadId = extractStartedThreadId(response);
27018
27302
  args.setStartedCodexThreadId(codexThreadId);
27019
27303
  const workDescription = normalizedWorkDescription(
@@ -27047,13 +27331,17 @@ async function startCodexProviderInstance(args) {
27047
27331
  seq: sourceSeq,
27048
27332
  body: workDescription,
27049
27333
  actorSlug: identity.actorUsername,
27050
- actorUserId: identity.actorUserId
27334
+ actorUserId: identity.actorUserId,
27335
+ boundStartTimeout: true
27051
27336
  });
27052
27337
  } else {
27053
- await args.codex.request("turn/start", {
27054
- threadId: codexThreadId,
27055
- input: [{ type: "text", text: workDescription }]
27056
- });
27338
+ await withCodexStartRequestTimeout(
27339
+ "turn/start",
27340
+ args.codex.request("turn/start", {
27341
+ threadId: codexThreadId,
27342
+ input: [{ type: "text", text: workDescription }]
27343
+ })
27344
+ );
27057
27345
  }
27058
27346
  }
27059
27347
  return {
@@ -28953,9 +29241,10 @@ function redactedThreadRunnerCliArgs(args) {
28953
29241
  function optionalCliValue(flag, value) {
28954
29242
  return value === void 0 || value === "" ? [] : [flag, value];
28955
29243
  }
28956
- function startOnboardingDiscoveryAgents(options, log2) {
29244
+ function startOnboardingDiscoveryAgents(options, log2, onOnboardingDiscoveryComplete) {
28957
29245
  const workspaceSlug = runnerWorkspaceSlug(options);
28958
29246
  if (!shouldStartOnboardingDiscoveryAgents(options, workspaceSlug)) {
29247
+ void onOnboardingDiscoveryComplete?.();
28959
29248
  return;
28960
29249
  }
28961
29250
  const startKey = onboardingDiscoveryAgentStartKey(options, workspaceSlug);
@@ -29013,6 +29302,7 @@ function startOnboardingDiscoveryAgents(options, log2) {
29013
29302
  workers_completed: 2,
29014
29303
  successful_write_count: successfulWriteCount
29015
29304
  });
29305
+ await onOnboardingDiscoveryComplete?.();
29016
29306
  });
29017
29307
  }
29018
29308
  function withSuccessfulWriteCount(report, successfulPostStartWriteCount) {
@@ -68863,7 +69153,7 @@ async function parseStartRunnerArgs(args, deps = {
68863
69153
  cwd,
68864
69154
  codexBin,
68865
69155
  codexUrl: stringValue8(values, "codex-url"),
68866
- launchSource: electronAutoConnectLaunchSource(),
69156
+ launchSource: electronAutoConnectLaunchSource2(),
68867
69157
  launchTui: values.get("launch-tui") === true,
68868
69158
  fast: values.get("fast") === true,
68869
69159
  logFile: stringValue8(values, "log-file"),
@@ -68968,7 +69258,7 @@ async function parseAgentRunnerArgs(args, deps = {
68968
69258
  cwd,
68969
69259
  codexBin,
68970
69260
  codexUrl: stringValue8(values, "codex-url"),
68971
- launchSource: electronAutoConnectLaunchSource(),
69261
+ launchSource: electronAutoConnectLaunchSource2(),
68972
69262
  launchTui: values.get("launch-tui") === true,
68973
69263
  fast: values.get("fast") === true,
68974
69264
  logFile: stringValue8(values, "log-file"),
@@ -69143,7 +69433,7 @@ async function parseRunnerArgs(args, deps = {
69143
69433
  cwd,
69144
69434
  codexBin,
69145
69435
  codexUrl: stringValue8(values, "codex-url"),
69146
- launchSource: electronAutoConnectLaunchSource(),
69436
+ launchSource: electronAutoConnectLaunchSource2(),
69147
69437
  launchTui: values.get("launch-tui") === true,
69148
69438
  fast: values.get("fast") === true,
69149
69439
  logFile: stringValue8(values, "log-file"),
@@ -69178,11 +69468,11 @@ function runnerRuntimeDefaultsFromValues(values) {
69178
69468
  allowPortForwardingByDefault: true
69179
69469
  };
69180
69470
  }
69181
- function electronAutoConnectLaunchSource() {
69471
+ function electronAutoConnectLaunchSource2() {
69182
69472
  return process.env.LINZUMI_ELECTRON_AUTO_CONNECT_RUNNER === "1" ? "electron_auto_connect" : void 0;
69183
69473
  }
69184
69474
  function connectLaunchSourceTelemetryValue() {
69185
- return electronAutoConnectLaunchSource() ?? "cli";
69475
+ return electronAutoConnectLaunchSource2() ?? "cli";
69186
69476
  }
69187
69477
  function workspaceTelemetryAttribute(workspace) {
69188
69478
  return workspace === void 0 ? {} : { workspace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.93-beta",
3
+ "version": "0.0.95-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": {