@linzumi/cli 0.0.95-beta → 0.0.97-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 +625 -78
  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.95-beta --version
61
+ npx -y @linzumi/cli@0.0.97-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -125,25 +125,31 @@ function discoverCurrentGitProject(args) {
125
125
  const responsiveness = args.placeResponsiveness;
126
126
  const candidates = /* @__PURE__ */ new Map();
127
127
  const searchStats = [];
128
- const cwdResponsive = isPlaceResponsive(args.cwd, responsiveness);
129
- const gitStartedAtMs = Date.now();
130
- const topLevel = cwdResponsive ? gitOutput(args.cwd, ["rev-parse", "--show-toplevel"]) : void 0;
131
- const gitDurationMs = Date.now() - gitStartedAtMs;
132
- if (topLevel !== void 0) {
133
- mergeCandidate(
134
- candidates,
135
- projectCandidateFromWorktree(topLevel, "local_current_git", topLevel)
136
- );
137
- }
138
- searchStats.push({
139
- place: args.cwd,
140
- source: "git",
141
- duration_ms: gitDurationMs,
142
- result_count: topLevel === void 0 ? 0 : 1,
143
- skipped_unresponsive: cwdResponsive ? void 0 : true
128
+ const selectedScanRoots = uniqueStrings(args.selectedScanRoots ?? []);
129
+ const selectedScanRootSet = new Set(selectedScanRoots);
130
+ const sourceRoots = args.sourceRoots ?? defaultLocalProjectSourceRoots({
131
+ cwd: args.cwd,
132
+ homeDir: args.homeDir,
133
+ selectedScanRoots
144
134
  });
145
- const sourceRoots = args.sourceRoots ?? defaultLocalProjectSourceRoots(args.cwd);
146
- let placesSearched = 1;
135
+ const sourceRootMaxDepth = normalizeSourceRootMaxDepth(
136
+ args.sourceRootMaxDepth
137
+ );
138
+ const sourceRootScanDeadlineMs = Date.now() + normalizeSourceRootScanBudgetMs(args.sourceRootScanBudgetMs);
139
+ const createSourceRootScanBudget = () => ({
140
+ deadlineMs: sourceRootScanDeadlineMs,
141
+ maxDirectoryVisits: normalizeSourceRootMaxDirectoryVisits(
142
+ args.sourceRootMaxDirectoryVisits
143
+ ),
144
+ maxProjectEnrichments: normalizeSourceRootMaxProjectEnrichments(
145
+ args.sourceRootMaxProjectEnrichments
146
+ ),
147
+ directoryVisits: 0,
148
+ projectEnrichments: 0,
149
+ exhausted: false
150
+ });
151
+ const defaultSourceRootScanBudget = createSourceRootScanBudget();
152
+ let placesSearched = 0;
147
153
  const scanBudgetMs = projectScanBudgetMs();
148
154
  for (const sourceRoot of sourceRoots) {
149
155
  if (Date.now() - startedAtMs >= scanBudgetMs) {
@@ -158,6 +164,19 @@ function discoverCurrentGitProject(args) {
158
164
  continue;
159
165
  }
160
166
  const sourceRootStartedAtMs = Date.now();
167
+ const isSelectedScanRoot = selectedScanRootSet.has(sourceRoot);
168
+ const sourceRootScanBudget = isSelectedScanRoot ? createSourceRootScanBudget() : defaultSourceRootScanBudget;
169
+ if (isSourceRootScanBudgetExhausted(sourceRootScanBudget)) {
170
+ searchStats.push({
171
+ place: sourceRoot,
172
+ source: "git",
173
+ duration_ms: Date.now() - sourceRootStartedAtMs,
174
+ result_count: 0,
175
+ scan_budget_exhausted: true
176
+ });
177
+ placesSearched += 1;
178
+ continue;
179
+ }
161
180
  const rootResponsive = isPlaceResponsive(sourceRoot, responsiveness);
162
181
  if (!rootResponsive) {
163
182
  searchStats.push({
@@ -170,25 +189,60 @@ function discoverCurrentGitProject(args) {
170
189
  placesSearched += 1;
171
190
  continue;
172
191
  }
173
- if (!isDirectory(sourceRoot)) {
192
+ const directoryExists2 = isDirectory(sourceRoot);
193
+ if (!directoryExists2) {
194
+ if (isSelectedScanRoot) {
195
+ searchStats.push({
196
+ place: sourceRoot,
197
+ source: "git",
198
+ duration_ms: Date.now() - sourceRootStartedAtMs,
199
+ result_count: 0,
200
+ missing_directory: true
201
+ });
202
+ placesSearched += 1;
203
+ }
204
+ continue;
205
+ }
206
+ const foundInRoot = discoverGitWorktreesInSourceRoot(
207
+ sourceRoot,
208
+ sourceRootMaxDepth,
209
+ responsiveness,
210
+ sourceRootScanBudget
211
+ );
212
+ const enrichedProjectCount = enrichSourceRootProjects(
213
+ candidates,
214
+ foundInRoot,
215
+ sourceRootScanBudget
216
+ );
217
+ searchStats.push({
218
+ place: sourceRoot,
219
+ source: "git",
220
+ duration_ms: Date.now() - sourceRootStartedAtMs,
221
+ result_count: enrichedProjectCount,
222
+ scan_budget_exhausted: sourceRootScanBudget.exhausted ? true : void 0
223
+ });
224
+ placesSearched += 1;
225
+ if (sourceRootScanBudget.exhausted) {
174
226
  continue;
175
227
  }
176
- const foundInRoot = discoverGitWorktreesInSourceRoot(sourceRoot);
177
- for (const worktreePath of foundInRoot) {
178
- mergeCandidate(
228
+ }
229
+ if (args.includeRuntimeCwdHint !== false) {
230
+ const cwdResponsive = isPlaceResponsive(args.cwd, responsiveness);
231
+ const gitStartedAtMs = Date.now();
232
+ const topLevel = cwdResponsive ? gitOutput(args.cwd, ["rev-parse", "--show-toplevel"]) : void 0;
233
+ const gitDurationMs = Date.now() - gitStartedAtMs;
234
+ if (topLevel !== void 0) {
235
+ mergeRuntimeCwdCandidate(
179
236
  candidates,
180
- projectCandidateFromWorktree(
181
- worktreePath,
182
- "local_source_root",
183
- topLevel
184
- )
237
+ projectCandidateFromWorktree(topLevel, "runtime_cwd_hint", topLevel)
185
238
  );
186
239
  }
187
240
  searchStats.push({
188
- place: sourceRoot,
241
+ place: args.cwd,
189
242
  source: "git",
190
- duration_ms: Date.now() - sourceRootStartedAtMs,
191
- result_count: foundInRoot.length
243
+ duration_ms: gitDurationMs,
244
+ result_count: topLevel === void 0 ? 0 : 1,
245
+ skipped_unresponsive: cwdResponsive ? void 0 : true
192
246
  });
193
247
  placesSearched += 1;
194
248
  }
@@ -261,16 +315,60 @@ function mergeCandidate(candidates, candidate) {
261
315
  const existing = candidates.get(candidate.importKey);
262
316
  if (existing === void 0 || shouldReplaceCandidate(existing, candidate)) {
263
317
  candidates.set(candidate.importKey, candidate);
318
+ return;
319
+ }
320
+ const currentWorktreePath = candidate.metadata.current_worktree_path;
321
+ if (existing.metadata.current_worktree_path === void 0 && typeof currentWorktreePath === "string") {
322
+ candidates.set(candidate.importKey, {
323
+ ...existing,
324
+ metadata: {
325
+ ...existing.metadata,
326
+ current_worktree_path: currentWorktreePath
327
+ }
328
+ });
264
329
  }
265
330
  }
331
+ function mergeRuntimeCwdCandidate(candidates, candidate) {
332
+ if (candidate === void 0) {
333
+ return;
334
+ }
335
+ const existing = candidates.get(candidate.importKey);
336
+ if (existing !== void 0 && existing.metadata.discovery_method === "local_source_root" && existing.path !== candidate.path) {
337
+ candidates.set(candidate.importKey, {
338
+ ...candidate,
339
+ metadata: {
340
+ ...candidate.metadata,
341
+ discovery_method: "local_source_root"
342
+ }
343
+ });
344
+ return;
345
+ }
346
+ mergeCandidate(candidates, candidate);
347
+ }
348
+ function enrichSourceRootProjects(candidates, worktreePaths, scanBudget) {
349
+ return worktreePaths.reduce((enrichedCount, worktreePath) => {
350
+ if (!consumeSourceRootProjectEnrichmentBudget(scanBudget)) {
351
+ return enrichedCount;
352
+ }
353
+ const candidate = projectCandidateFromWorktree(
354
+ worktreePath,
355
+ "local_source_root"
356
+ );
357
+ if (candidate === void 0) {
358
+ return enrichedCount;
359
+ }
360
+ mergeCandidate(candidates, candidate);
361
+ return enrichedCount + 1;
362
+ }, 0);
363
+ }
266
364
  function shouldReplaceCandidate(existing, candidate) {
267
365
  const existingMethod = existing.metadata.discovery_method;
268
366
  const candidateMethod = candidate.metadata.discovery_method;
269
- if (existingMethod === "local_current_git") {
270
- return false;
367
+ if (existingMethod === "runtime_cwd_hint") {
368
+ return candidateMethod !== "runtime_cwd_hint";
271
369
  }
272
- if (candidateMethod === "local_current_git") {
273
- return true;
370
+ if (candidateMethod === "runtime_cwd_hint") {
371
+ return false;
274
372
  }
275
373
  const existingActivity = Date.parse(existing.activityAt ?? "");
276
374
  const candidateActivity = Date.parse(candidate.activityAt ?? "");
@@ -296,32 +394,91 @@ function compareProjectCandidates(left, right) {
296
394
  }
297
395
  return left.displayName.localeCompare(right.displayName);
298
396
  }
299
- function discoverGitWorktreesInSourceRoot(sourceRoot) {
397
+ function discoverGitWorktreesInSourceRoot(sourceRoot, maxDepth, responsiveness, scanBudget) {
300
398
  if (!isDirectory(sourceRoot)) {
301
399
  return [];
302
400
  }
401
+ return uniqueStrings(
402
+ discoverGitWorktreesBelow(sourceRoot, maxDepth, responsiveness, scanBudget)
403
+ ).sort();
404
+ }
405
+ function discoverGitWorktreesBelow(directory, depthRemaining, responsiveness, scanBudget) {
406
+ if (!consumeSourceRootScanBudget(scanBudget)) {
407
+ return [];
408
+ }
409
+ if (depthRemaining < 0) {
410
+ return [];
411
+ }
412
+ if (isGitWorktreeRoot(directory)) {
413
+ return [directory];
414
+ }
415
+ if (depthRemaining === 0) {
416
+ return [];
417
+ }
418
+ let entries;
303
419
  try {
304
- return readdirSync(sourceRoot, { withFileTypes: true }).flatMap((entry) => {
305
- if (!entry.isDirectory()) {
306
- return [];
307
- }
308
- const candidatePath = join(sourceRoot, entry.name);
309
- return isGitWorktreeRoot(candidatePath) ? [candidatePath] : [];
310
- }).sort();
420
+ entries = readdirSync(directory, { withFileTypes: true });
311
421
  } catch (_error) {
312
422
  return [];
313
423
  }
424
+ return entries.flatMap((entry) => {
425
+ if (!entry.isDirectory()) {
426
+ return [];
427
+ }
428
+ if (ignoredSourceRootDirectoryNames.has(entry.name)) {
429
+ return [];
430
+ }
431
+ const childPath = join(directory, entry.name);
432
+ if (isSourceRootScanBudgetExhausted(scanBudget)) {
433
+ return [];
434
+ }
435
+ if (!isPlaceResponsive(childPath, responsiveness)) {
436
+ return [];
437
+ }
438
+ return discoverGitWorktreesBelow(
439
+ childPath,
440
+ depthRemaining - 1,
441
+ responsiveness,
442
+ scanBudget
443
+ );
444
+ });
445
+ }
446
+ function consumeSourceRootScanBudget(scanBudget) {
447
+ if (isSourceRootScanBudgetExhausted(scanBudget)) {
448
+ return false;
449
+ }
450
+ scanBudget.directoryVisits += 1;
451
+ return true;
452
+ }
453
+ function consumeSourceRootProjectEnrichmentBudget(scanBudget) {
454
+ if (Date.now() >= scanBudget.deadlineMs || scanBudget.projectEnrichments >= scanBudget.maxProjectEnrichments) {
455
+ scanBudget.exhausted = true;
456
+ return false;
457
+ }
458
+ scanBudget.projectEnrichments += 1;
459
+ return true;
460
+ }
461
+ function isSourceRootScanBudgetExhausted(scanBudget) {
462
+ if (scanBudget.exhausted) {
463
+ return true;
464
+ }
465
+ if (Date.now() >= scanBudget.deadlineMs || scanBudget.directoryVisits >= scanBudget.maxDirectoryVisits || scanBudget.projectEnrichments >= scanBudget.maxProjectEnrichments) {
466
+ scanBudget.exhausted = true;
467
+ return true;
468
+ }
469
+ return false;
314
470
  }
315
471
  function isGitWorktreeRoot(path2) {
316
472
  return existsSync(join(path2, ".git"));
317
473
  }
318
- function defaultLocalProjectSourceRoots(cwd) {
319
- if (cwd === dirname(cwd)) {
474
+ function defaultLocalProjectSourceRoots(args) {
475
+ if (args.cwd === dirname(args.cwd)) {
320
476
  return [];
321
477
  }
322
478
  return uniqueStrings([
323
- ...ancestorSourceRoots(cwd),
324
- ...homeSourceRoots(),
479
+ ...homeSourceRoots(args.homeDir),
480
+ ...args.selectedScanRoots ?? [],
481
+ ...ancestorSourceRoots(args.cwd),
325
482
  ...externalVolumeSourceRoots()
326
483
  ]);
327
484
  }
@@ -344,8 +501,8 @@ function ancestorSourceRoots(cwd) {
344
501
  }
345
502
  return roots;
346
503
  }
347
- function homeSourceRoots() {
348
- const home = homedir();
504
+ function homeSourceRoots(homeDir) {
505
+ const home = homeDir ?? homedir();
349
506
  return [
350
507
  "code",
351
508
  "src",
@@ -385,6 +542,42 @@ function isDirectory(path2) {
385
542
  return false;
386
543
  }
387
544
  }
545
+ function normalizeSourceRootMaxDepth(value) {
546
+ if (value === void 0) {
547
+ return defaultSourceRootMaxDepth;
548
+ }
549
+ if (!Number.isFinite(value) || value < 0) {
550
+ return defaultSourceRootMaxDepth;
551
+ }
552
+ return Math.floor(value);
553
+ }
554
+ function normalizeSourceRootScanBudgetMs(value) {
555
+ if (value === void 0) {
556
+ return defaultSourceRootScanBudgetMs;
557
+ }
558
+ if (!Number.isFinite(value) || value <= 0) {
559
+ return defaultSourceRootScanBudgetMs;
560
+ }
561
+ return Math.floor(value);
562
+ }
563
+ function normalizeSourceRootMaxDirectoryVisits(value) {
564
+ if (value === void 0) {
565
+ return defaultSourceRootMaxDirectoryVisits;
566
+ }
567
+ if (!Number.isFinite(value) || value <= 0) {
568
+ return defaultSourceRootMaxDirectoryVisits;
569
+ }
570
+ return Math.floor(value);
571
+ }
572
+ function normalizeSourceRootMaxProjectEnrichments(value) {
573
+ if (value === void 0) {
574
+ return defaultSourceRootMaxProjectEnrichments;
575
+ }
576
+ if (!Number.isFinite(value) || value <= 0) {
577
+ return defaultSourceRootMaxProjectEnrichments;
578
+ }
579
+ return Math.floor(value);
580
+ }
388
581
  function uniqueStrings(values) {
389
582
  return Array.from(new Set(values));
390
583
  }
@@ -432,13 +625,29 @@ function compactJsonObject(value) {
432
625
  })
433
626
  );
434
627
  }
435
- var worktreePathSampleLimit, gitSpawnTimeoutMs, defaultProjectScanBudgetMs;
628
+ var worktreePathSampleLimit, gitSpawnTimeoutMs, defaultSourceRootMaxDepth, defaultSourceRootScanBudgetMs, defaultSourceRootMaxDirectoryVisits, defaultSourceRootMaxProjectEnrichments, ignoredSourceRootDirectoryNames, defaultProjectScanBudgetMs;
436
629
  var init_onboardingProjectDiscovery = __esm({
437
630
  "src/onboardingProjectDiscovery.ts"() {
438
631
  "use strict";
439
632
  init_onboardingPlaceResponsiveness();
440
633
  worktreePathSampleLimit = 25;
441
634
  gitSpawnTimeoutMs = 4e3;
635
+ defaultSourceRootMaxDepth = 4;
636
+ defaultSourceRootScanBudgetMs = 2e4;
637
+ defaultSourceRootMaxDirectoryVisits = 5e3;
638
+ defaultSourceRootMaxProjectEnrichments = 100;
639
+ ignoredSourceRootDirectoryNames = /* @__PURE__ */ new Set([
640
+ ".cache",
641
+ ".git",
642
+ ".next",
643
+ ".pnpm-store",
644
+ ".turbo",
645
+ "build",
646
+ "dist",
647
+ "Library",
648
+ "node_modules",
649
+ "target"
650
+ ]);
442
651
  defaultProjectScanBudgetMs = 2e4;
443
652
  }
444
653
  });
@@ -1115,7 +1324,27 @@ async function runOnboardingDiscoveryChildRole(kind, stdin, stdout) {
1115
1324
  if (kind === "projects") {
1116
1325
  const { discoverCurrentGitProject: discoverCurrentGitProject2 } = await Promise.resolve().then(() => (init_onboardingProjectDiscovery(), onboardingProjectDiscovery_exports));
1117
1326
  const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
1118
- stdout.write(JSON.stringify(discoverCurrentGitProject2({ cwd })));
1327
+ const homeDir = typeof args.homeDir === "string" ? args.homeDir : void 0;
1328
+ const sourceRoots = Array.isArray(args.sourceRoots) ? args.sourceRoots.filter(
1329
+ (path2) => typeof path2 === "string"
1330
+ ) : void 0;
1331
+ const selectedScanRoots = Array.isArray(args.selectedScanRoots) ? args.selectedScanRoots.filter(
1332
+ (path2) => typeof path2 === "string"
1333
+ ) : void 0;
1334
+ const sourceRootMaxDepth = typeof args.sourceRootMaxDepth === "number" ? args.sourceRootMaxDepth : void 0;
1335
+ const includeRuntimeCwdHint = typeof args.includeRuntimeCwdHint === "boolean" ? args.includeRuntimeCwdHint : void 0;
1336
+ stdout.write(
1337
+ JSON.stringify(
1338
+ discoverCurrentGitProject2({
1339
+ cwd,
1340
+ homeDir,
1341
+ sourceRoots,
1342
+ selectedScanRoots,
1343
+ includeRuntimeCwdHint,
1344
+ sourceRootMaxDepth
1345
+ })
1346
+ )
1347
+ );
1119
1348
  return 0;
1120
1349
  }
1121
1350
  if (kind === "conversations") {
@@ -7269,6 +7498,18 @@ function dequeuePendingKandanMessage(queue) {
7269
7498
  compactPendingKandanMessageQueue(queue);
7270
7499
  return message;
7271
7500
  }
7501
+ function drainAllPendingKandanMessages(queue) {
7502
+ const drained = [];
7503
+ for (let index = queue.head; index < queue.items.length; index += 1) {
7504
+ const message = queue.items[index];
7505
+ if (message !== void 0) {
7506
+ drained.push(message);
7507
+ }
7508
+ }
7509
+ queue.items = [];
7510
+ queue.head = 0;
7511
+ return drained;
7512
+ }
7272
7513
  function requeuePendingKandanMessageFront(queue, message) {
7273
7514
  if (queue.head > 0) {
7274
7515
  queue.head = queue.head - 1;
@@ -7278,7 +7519,10 @@ function requeuePendingKandanMessageFront(queue, message) {
7278
7519
  queue.items.unshift(message);
7279
7520
  }
7280
7521
  function interruptPendingKandanMessages(queue, throughSeq) {
7281
- const result = interruptQueuedMessages(pendingKandanMessages(queue), throughSeq);
7522
+ const result = interruptQueuedMessages(
7523
+ pendingKandanMessages(queue),
7524
+ throughSeq
7525
+ );
7282
7526
  if (result.ok) {
7283
7527
  replacePendingKandanMessages(queue, result.queue);
7284
7528
  }
@@ -8997,6 +9241,7 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
8997
9241
  failedSeqs: /* @__PURE__ */ new Map(),
8998
9242
  drainInFlight: false,
8999
9243
  drainRequested: false,
9244
+ deadWorkerRebuildAttempts: 0,
9000
9245
  boundStartTimeout: false,
9001
9246
  fusedQueuedSeqs: /* @__PURE__ */ new Map(),
9002
9247
  reconnectRetryTimer: void 0,
@@ -10568,6 +10813,9 @@ async function drainKandanMessageQueue(args, state, payloadContext) {
10568
10813
  }
10569
10814
  }
10570
10815
  async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10816
+ if (await rebuildIfCodexWorkerDead(args, state)) {
10817
+ return;
10818
+ }
10571
10819
  if (drainKandanMessageQueueBlocked(args, state)) {
10572
10820
  return;
10573
10821
  }
@@ -10649,6 +10897,14 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10649
10897
  await drainKandanMessageQueue(args, state, payloadContext);
10650
10898
  }
10651
10899
  } catch (error) {
10900
+ if (codexWorkerIsDead(args)) {
10901
+ await stopCodexTyping(args, state);
10902
+ requeuePendingKandanMessageFront(state.queue, next);
10903
+ state.turn = { status: "idle" };
10904
+ clearActiveProcessingState(state, next.seq);
10905
+ await rebuildIfCodexWorkerDead(args, state);
10906
+ return;
10907
+ }
10652
10908
  if (isRecoverableCodexThreadError(error) && state.kandanThreadId !== void 0) {
10653
10909
  const oldCodexThreadId = state.codexThreadId;
10654
10910
  try {
@@ -10709,6 +10965,83 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
10709
10965
  }
10710
10966
  }
10711
10967
  }
10968
+ function codexWorkerIsDead(args) {
10969
+ return args.codex.isClosed?.() === true;
10970
+ }
10971
+ async function rebuildIfCodexWorkerDead(args, state) {
10972
+ if (!codexWorkerIsDead(args)) {
10973
+ return false;
10974
+ }
10975
+ const pending = drainAllPendingKandanMessages(state.queue);
10976
+ if (pending.length === 0) {
10977
+ return false;
10978
+ }
10979
+ const { codexThreadId, kandanThreadId } = state;
10980
+ const rebuild = args.options.rebuildOnDeadCodexWorker;
10981
+ args.log("kandan.queue_drain_dead_worker_rebuild", {
10982
+ codex_thread_id: codexThreadId ?? null,
10983
+ kandan_thread_id: kandanThreadId ?? null,
10984
+ pending_count: pending.length,
10985
+ attempt: state.deadWorkerRebuildAttempts + 1,
10986
+ rebuild_available: rebuild !== void 0
10987
+ });
10988
+ const failPending = async (reason) => {
10989
+ for (const message of pending) {
10990
+ try {
10991
+ await publishQueuedMessageState(args, state, message, {
10992
+ status: "failed",
10993
+ reason
10994
+ });
10995
+ } catch (error) {
10996
+ args.log("kandan.queue_drain_dead_worker_fail_publish_failed", {
10997
+ codex_thread_id: codexThreadId ?? null,
10998
+ seq: message.seq,
10999
+ message: error instanceof Error ? error.message : String(error)
11000
+ });
11001
+ }
11002
+ }
11003
+ };
11004
+ if (rebuild === void 0 || codexThreadId === void 0 || kandanThreadId === void 0) {
11005
+ await failPending(
11006
+ "Codex worker for this thread exited and could not be respawned to resume the conversation."
11007
+ );
11008
+ return true;
11009
+ }
11010
+ if (state.deadWorkerRebuildAttempts >= maxDeadWorkerRebuildAttempts) {
11011
+ args.log("kandan.queue_drain_dead_worker_rebuild_exhausted", {
11012
+ codex_thread_id: codexThreadId,
11013
+ kandan_thread_id: kandanThreadId,
11014
+ attempts: state.deadWorkerRebuildAttempts
11015
+ });
11016
+ await failPending(
11017
+ "Codex worker for this thread kept exiting on resume; giving up after several respawn attempts."
11018
+ );
11019
+ return true;
11020
+ }
11021
+ state.deadWorkerRebuildAttempts += 1;
11022
+ state.closed = true;
11023
+ let rebuilt = false;
11024
+ try {
11025
+ rebuilt = await rebuild({
11026
+ codexThreadId,
11027
+ kandanThreadId,
11028
+ pendingMessages: pending
11029
+ });
11030
+ } catch (error) {
11031
+ args.log("kandan.queue_drain_dead_worker_rebuild_failed", {
11032
+ codex_thread_id: codexThreadId,
11033
+ kandan_thread_id: kandanThreadId,
11034
+ message: error instanceof Error ? error.message : String(error)
11035
+ });
11036
+ rebuilt = false;
11037
+ }
11038
+ if (!rebuilt) {
11039
+ await failPending(
11040
+ "Codex worker for this thread exited and could not be respawned to resume the conversation."
11041
+ );
11042
+ }
11043
+ return true;
11044
+ }
10712
11045
  function drainKandanMessageQueueBlocked(args, state) {
10713
11046
  if (state.closed) {
10714
11047
  logQueuedDrainBlocked(args, state, "session_closed");
@@ -11720,7 +12053,7 @@ async function pushOptional(kandan, topic, event, payload, log2) {
11720
12053
  });
11721
12054
  }
11722
12055
  }
11723
- var codexTypingHeartbeatMs, CODEX_START_REQUEST_TIMEOUT_MS, CodexStartRequestTimeoutError, maxTrackedFailedChatEventSeqs, maxChatEventFailureAttempts, maxForwardedTurnIds, maxClaimedKandanMessageKeys, claimedKandanMessageKeys, nextChatJoinRegistrationSerial, defaultReconnectHandlingRetryDelaysMs, defaultTuiInputMirrorRetryDelaysMs, pipelineCloseTimeoutMs;
12056
+ var codexTypingHeartbeatMs, CODEX_START_REQUEST_TIMEOUT_MS, CodexStartRequestTimeoutError, maxTrackedFailedChatEventSeqs, maxChatEventFailureAttempts, maxForwardedTurnIds, maxClaimedKandanMessageKeys, claimedKandanMessageKeys, nextChatJoinRegistrationSerial, defaultReconnectHandlingRetryDelaysMs, defaultTuiInputMirrorRetryDelaysMs, pipelineCloseTimeoutMs, maxDeadWorkerRebuildAttempts;
11724
12057
  var init_channelSession = __esm({
11725
12058
  "src/channelSession.ts"() {
11726
12059
  "use strict";
@@ -11771,6 +12104,7 @@ var init_channelSession = __esm({
11771
12104
  15e3
11772
12105
  ];
11773
12106
  pipelineCloseTimeoutMs = 1e4;
12107
+ maxDeadWorkerRebuildAttempts = 3;
11774
12108
  }
11775
12109
  });
11776
12110
 
@@ -19451,7 +19785,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19451
19785
  var init_version = __esm({
19452
19786
  "src/version.ts"() {
19453
19787
  "use strict";
19454
- linzumiCliVersion = "0.0.95-beta";
19788
+ linzumiCliVersion = "0.0.97-beta";
19455
19789
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19456
19790
  }
19457
19791
  });
@@ -22340,6 +22674,11 @@ function connectThreadCodexWorkerIpc(child) {
22340
22674
  onRequest: (callback) => {
22341
22675
  requestCallbacks.add(callback);
22342
22676
  },
22677
+ // The worker child exited/disconnected (onClosed) or close() was called.
22678
+ // A reconnect uses this to detect a DEAD cached client and respawn a fresh
22679
+ // worker instead of send()-ing on a closed channel (which throws "thread
22680
+ // Codex worker IPC client is closed" and strands the resume).
22681
+ isClosed: () => state.closed || child.connected === false,
22343
22682
  close: () => {
22344
22683
  state.closed = true;
22345
22684
  child.off("message", onMessage);
@@ -23206,6 +23545,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23206
23545
  const missingAllowedCwds = {
23207
23546
  value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
23208
23547
  };
23548
+ const projectDiscoveryCwdIsSelectedRootFromSyncedConfig = { value: false };
23209
23549
  const applyRunnerConfigSync = (config, syncOptions) => {
23210
23550
  const persistedAllowedCwds = replaceAllowedCwdsForLinzumiUrl(
23211
23551
  config.allowedCwds,
@@ -23214,6 +23554,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23214
23554
  const updatedAllowedCwds = configuredAllowedCwds(persistedAllowedCwds, {
23215
23555
  createMissing: true
23216
23556
  });
23557
+ projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value = projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value || allowedCwdsContainAny(updatedAllowedCwds.allowedCwds, [
23558
+ options.cwd,
23559
+ ...startupAllowedCwds
23560
+ ]);
23217
23561
  allowedCwds.value = normalizeAllowedCwds([
23218
23562
  ...syncOptions.includeStartupAllowedCwds ? startupAllowedCwds : [],
23219
23563
  ...updatedAllowedCwds.allowedCwds
@@ -23300,6 +23644,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23300
23644
  allowedCwds.value
23301
23645
  ),
23302
23646
  allowedCwdProjects: allowedCwdProjects(allowedCwds.value),
23647
+ onboardingSelectedFolderScan: true,
23303
23648
  portForwarding: liveForwardPorts.size > 0,
23304
23649
  tcpForwarding: true,
23305
23650
  linzumiMcp: true,
@@ -24324,6 +24669,35 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24324
24669
  );
24325
24670
  threadRunnerProcesses.clear();
24326
24671
  });
24672
+ const evictStaleDynamicChannelSession = async (codexThreadId, expectClient) => {
24673
+ const session = dynamicChannelSessions.get(codexThreadId);
24674
+ const boundClient = dynamicChannelSessionCodexClients.get(codexThreadId);
24675
+ if (session === void 0) {
24676
+ return false;
24677
+ }
24678
+ if (expectClient !== void 0 && boundClient !== expectClient) {
24679
+ return false;
24680
+ }
24681
+ if (boundClient?.isClosed?.() !== true) {
24682
+ return false;
24683
+ }
24684
+ dynamicChannelSessions.delete(codexThreadId);
24685
+ dynamicChannelSessionCodexClients.delete(codexThreadId);
24686
+ dynamicChannelSessionAttachInFlight.delete(codexThreadId);
24687
+ log2("runner.thread_session_evicted_dead_worker", {
24688
+ codex_thread_id: codexThreadId,
24689
+ kandan_thread_id: session.currentKandanThreadId() ?? null
24690
+ });
24691
+ try {
24692
+ await session.close();
24693
+ } catch (error) {
24694
+ log2("runner.thread_session_evict_close_failed", {
24695
+ codex_thread_id: codexThreadId,
24696
+ message: error instanceof Error ? error.message : String(error)
24697
+ });
24698
+ }
24699
+ return true;
24700
+ };
24327
24701
  const attachThreadSessionWithCodex = async (args) => {
24328
24702
  const { control, cwd, codexThreadId, sessionCodex } = args;
24329
24703
  const portForwardWatcherRootPid = args.portForwardWatcherRootPid;
@@ -24335,7 +24709,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24335
24709
  }
24336
24710
  const existingSession = dynamicChannelSessions.get(codexThreadId);
24337
24711
  if (existingSession !== void 0) {
24338
- return existingSession;
24712
+ const evicted = await evictStaleDynamicChannelSession(codexThreadId);
24713
+ if (!evicted) {
24714
+ return existingSession;
24715
+ }
24339
24716
  }
24340
24717
  const alreadyAttaching = dynamicChannelSessionAttachInFlight.get(codexThreadId);
24341
24718
  if (alreadyAttaching !== void 0) {
@@ -24349,6 +24726,53 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24349
24726
  );
24350
24727
  }
24351
24728
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
24729
+ const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => {
24730
+ await evictStaleDynamicChannelSession(rebuildArgs.codexThreadId);
24731
+ let rebuilt = false;
24732
+ for (const message of rebuildArgs.pendingMessages) {
24733
+ const reconnectControl = {
24734
+ type: "reconnect_thread",
24735
+ workspace: workspaceSlug,
24736
+ channel: channelSlug,
24737
+ threadId: rebuildArgs.kandanThreadId,
24738
+ codexThreadId: rebuildArgs.codexThreadId,
24739
+ cwd,
24740
+ ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24741
+ sourceSeq: message.seq,
24742
+ workDescription: message.body,
24743
+ ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24744
+ ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24745
+ ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24746
+ ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24747
+ ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24748
+ };
24749
+ const liveSession = dynamicChannelSessions.get(
24750
+ rebuildArgs.codexThreadId
24751
+ );
24752
+ try {
24753
+ if (liveSession !== void 0 && rebuilt) {
24754
+ await liveSession.startThreadMessageTurn({
24755
+ seq: message.seq,
24756
+ body: message.body,
24757
+ actorSlug: message.actorSlug,
24758
+ actorUserId: message.actorUserId
24759
+ });
24760
+ } else {
24761
+ await startThreadRunnerProcess(reconnectControl, cwd);
24762
+ }
24763
+ rebuilt = true;
24764
+ } catch (error) {
24765
+ log2("runner.thread_dead_worker_rebuild_failed", {
24766
+ codex_thread_id: rebuildArgs.codexThreadId,
24767
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24768
+ seq: message.seq,
24769
+ message: error instanceof Error ? error.message : String(error)
24770
+ });
24771
+ return false;
24772
+ }
24773
+ }
24774
+ return rebuilt;
24775
+ } : void 0;
24352
24776
  const session = await attachChannelSession({
24353
24777
  kandan,
24354
24778
  codex: sessionCodex,
@@ -24363,6 +24787,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24363
24787
  fetch: options.fetch,
24364
24788
  fast: control.fast ?? options.fast,
24365
24789
  launchTui: false,
24790
+ rebuildOnDeadCodexWorker,
24366
24791
  pipelinePersistDir: commanderOutboxPersistDir(),
24367
24792
  enablePortForwardWatch: true,
24368
24793
  initialForwardPorts: allowedForwardPorts,
@@ -24641,6 +25066,18 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24641
25066
  threadRunnerProcesses.delete(kandanThreadId);
24642
25067
  }
24643
25068
  });
25069
+ handle.onExit?.(() => {
25070
+ if (cleanup.closePromise !== void 0) {
25071
+ return;
25072
+ }
25073
+ for (const [codexThreadId, client] of Array.from(
25074
+ dynamicChannelSessionCodexClients.entries()
25075
+ )) {
25076
+ if (client === threadCodex) {
25077
+ void evictStaleDynamicChannelSession(codexThreadId, threadCodex);
25078
+ }
25079
+ }
25080
+ });
24644
25081
  log2("runner.thread_process_started", {
24645
25082
  kandanThreadId,
24646
25083
  cwd
@@ -25066,6 +25503,29 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25066
25503
  }
25067
25504
  );
25068
25505
  }
25506
+ if (control.type === "scan_onboarding_projects") {
25507
+ const selectedScanRoots = onboardingProjectScanRoots(control.paths);
25508
+ return runLocalOnboardingProjectDiscovery(options, log2, {
25509
+ workspaceSlug: stringValue(control.workspace),
25510
+ selectedScanRoots,
25511
+ sourceRoots: selectedScanRoots,
25512
+ workerId: "projects-selected-folders",
25513
+ workerLabel: "Selected folders",
25514
+ startingMessage: "Checking selected folders",
25515
+ emptyMessage: "No Git projects found in selected folders",
25516
+ foundMessage: "Selected folder projects found",
25517
+ failedMessage: "Selected folder project scan failed",
25518
+ currentPlace: selectedScanRoots[0] ?? options.cwd,
25519
+ requireTerminalStatusWrite: true,
25520
+ includeRuntimeCwdHint: false
25521
+ }).catch((error) => {
25522
+ log2("onboarding.project_scan_failed", {
25523
+ runnerId: options.runnerId,
25524
+ message: error instanceof Error ? error.message : String(error)
25525
+ });
25526
+ throw error;
25527
+ });
25528
+ }
25069
25529
  if (control.type === "start_instance") {
25070
25530
  const staleAgeMs = staleStartInstanceAgeMs(control, Date.now());
25071
25531
  if (staleAgeMs !== void 0) {
@@ -25170,7 +25630,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25170
25630
  settleControlDispatch(control.type, () => handleControl(control), ack, log2);
25171
25631
  };
25172
25632
  finishRunnerStartup(
25173
- options,
25633
+ onboardingDiscoveryRunnerOptions(
25634
+ options,
25635
+ allowedCwds.value,
25636
+ missingAllowedCwds.value,
25637
+ projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value
25638
+ ),
25174
25639
  log2,
25175
25640
  pendingControls,
25176
25641
  controlDispatcher,
@@ -29536,6 +30001,14 @@ function onboardingConversationImportSources(value) {
29536
30001
  )
29537
30002
  ).sort();
29538
30003
  }
30004
+ function onboardingProjectScanRoots(value) {
30005
+ return normalizeAllowedCwds(
30006
+ (arrayValue(value) ?? []).flatMap((path2) => {
30007
+ const normalizedPath = stringValue(path2)?.trim();
30008
+ return normalizedPath === void 0 || normalizedPath === "" ? [] : [normalizedPath];
30009
+ })
30010
+ );
30011
+ }
29539
30012
  async function replayLocalOnboardingConversation(args) {
29540
30013
  let replayedChunkCount = 0;
29541
30014
  for (const message of args.messages) {
@@ -29962,27 +30435,42 @@ async function runLocalOnboardingConversationDiscovery(options, log2) {
29962
30435
  );
29963
30436
  }
29964
30437
  }
29965
- async function runLocalOnboardingProjectDiscovery(options, log2) {
29966
- const workspaceSlug = runnerWorkspaceSlug(options);
30438
+ async function runLocalOnboardingProjectDiscovery(options, log2, args = {}) {
30439
+ const workspaceSlug = args.workspaceSlug ?? runnerWorkspaceSlug(options);
30440
+ const selectedScanRoots = args.selectedScanRoots ?? selectedProjectDiscoveryScanRoots(options);
29967
30441
  if (workspaceSlug === void 0) {
29968
30442
  return;
29969
30443
  }
30444
+ const workerId = args.workerId ?? "projects-local-filesystem";
30445
+ const workerLabel = args.workerLabel ?? "Local Git projects";
30446
+ const currentPlace = args.currentPlace ?? selectedScanRoots[0] ?? options.cwd;
29970
30447
  await reportOnboardingDiscoveryStatus(options, log2, {
29971
30448
  phase: "projects",
29972
30449
  status: "starting",
29973
30450
  placesSearched: 0,
29974
- message: "Checking current Git project",
29975
- currentPlace: options.cwd,
30451
+ message: args.startingMessage ?? "Searching local Git projects",
30452
+ currentPlace,
29976
30453
  currentSource: "git",
30454
+ workspaceSlug,
29977
30455
  metadata: {
29978
- worker_id: "projects-local-current",
29979
- worker_label: "Current Git project"
30456
+ worker_id: workerId,
30457
+ worker_label: workerLabel,
30458
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots }
29980
30459
  }
29981
30460
  });
29982
30461
  let successfulPostStartWriteCount = 0;
29983
30462
  try {
29984
- const summary = await options.localProjectDiscovery?.() ?? await discoverCurrentGitProjectInChildProcess(
29985
- { cwd: options.cwd },
30463
+ const summary = await options.localProjectDiscovery?.({
30464
+ selectedScanRoots,
30465
+ sourceRoots: args.sourceRoots,
30466
+ includeRuntimeCwdHint: args.includeRuntimeCwdHint
30467
+ }) ?? await discoverCurrentGitProjectInChildProcess(
30468
+ {
30469
+ cwd: options.cwd,
30470
+ selectedScanRoots,
30471
+ sourceRoots: args.sourceRoots,
30472
+ includeRuntimeCwdHint: args.includeRuntimeCwdHint
30473
+ },
29986
30474
  { log: (event, payload) => log2(event, payload) }
29987
30475
  );
29988
30476
  const client = createLinzumiMcpApiClient({
@@ -30012,22 +30500,31 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30012
30500
  phase: "projects",
30013
30501
  status: "completed",
30014
30502
  placesSearched: summary.placesSearched,
30015
- message: summary.candidates.length === 0 ? "Current folder is not a Git project" : "Current Git project found",
30016
- currentPlace: options.cwd,
30503
+ message: summary.candidates.length === 0 ? args.emptyMessage ?? "No local Git projects found" : args.foundMessage ?? "Local Git projects found",
30504
+ currentPlace,
30017
30505
  currentSource: "git",
30506
+ workspaceSlug,
30018
30507
  metadata: {
30019
- worker_id: "projects-local-current",
30020
- worker_label: "Current Git project",
30508
+ worker_id: workerId,
30509
+ worker_label: workerLabel,
30021
30510
  duration_ms: summary.durationMs,
30022
30511
  reported_count: summary.candidates.length,
30023
30512
  failed_report_count: failedReportCount,
30513
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots },
30024
30514
  search_stats: summary.searchStats
30025
30515
  }
30026
30516
  };
30027
- successfulPostStartWriteCount = await addStatusWriteResult(
30028
- successfulPostStartWriteCount,
30029
- reportOnboardingDiscoveryStatus(options, log2, completedReport)
30517
+ const completedStatusWritten = await reportOnboardingDiscoveryStatus(
30518
+ options,
30519
+ log2,
30520
+ completedReport
30030
30521
  );
30522
+ if (!completedStatusWritten && args.requireTerminalStatusWrite === true) {
30523
+ throw new Error(
30524
+ "onboarding project discovery terminal status write failed"
30525
+ );
30526
+ }
30527
+ successfulPostStartWriteCount = completedStatusWritten ? successfulPostStartWriteCount + 1 : successfulPostStartWriteCount;
30031
30528
  log2("onboarding.local_project_discovery_completed", {
30032
30529
  runnerId: options.runnerId,
30033
30530
  workspace: workspaceSlug,
@@ -30045,19 +30542,26 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30045
30542
  phase: "projects",
30046
30543
  status: "failed",
30047
30544
  placesSearched: 0,
30048
- message: "Current Git project scan failed",
30049
- currentPlace: options.cwd,
30545
+ message: args.failedMessage ?? "Local Git project scan failed",
30546
+ currentPlace,
30050
30547
  currentSource: "git",
30051
30548
  lastError: message,
30549
+ workspaceSlug,
30052
30550
  metadata: {
30053
- worker_id: "projects-local-current",
30054
- worker_label: "Current Git project"
30551
+ worker_id: workerId,
30552
+ worker_label: workerLabel,
30553
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots }
30055
30554
  }
30056
30555
  };
30057
- successfulPostStartWriteCount = await addStatusWriteResult(
30058
- successfulPostStartWriteCount,
30059
- reportOnboardingDiscoveryStatus(options, log2, failedReport)
30556
+ const failedStatusWritten = await reportOnboardingDiscoveryStatus(
30557
+ options,
30558
+ log2,
30559
+ failedReport
30060
30560
  );
30561
+ if (!failedStatusWritten && args.requireTerminalStatusWrite === true) {
30562
+ throw error;
30563
+ }
30564
+ successfulPostStartWriteCount = failedStatusWritten ? successfulPostStartWriteCount + 1 : successfulPostStartWriteCount;
30061
30565
  log2("onboarding.local_project_discovery_failed", {
30062
30566
  runnerId: options.runnerId,
30063
30567
  workspace: workspaceSlug,
@@ -30069,11 +30573,45 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30069
30573
  );
30070
30574
  }
30071
30575
  }
30576
+ function selectedProjectDiscoveryScanRoots(options) {
30577
+ const cwd = options.cwd.trim();
30578
+ const implicitCwdAliases = new Set(implicitCwdStringAliases(cwd));
30579
+ for (const alias of options.projectDiscoveryImplicitCwdAliases ?? []) {
30580
+ implicitCwdAliases.add(alias);
30581
+ }
30582
+ const allowedCwds = options.projectDiscoveryCwdIsSelectedRoot === true ? options.allowedCwds : options.allowedCwds.filter(
30583
+ (allowedCwd) => !implicitCwdAliases.has(allowedCwd)
30584
+ );
30585
+ return normalizeAllowedCwds([
30586
+ ...allowedCwds,
30587
+ ...options.missingAllowedCwds ?? []
30588
+ ]);
30589
+ }
30590
+ function implicitCwdStringAliases(cwd) {
30591
+ if (cwd === "") {
30592
+ return [];
30593
+ }
30594
+ return normalizeAllowedCwds([cwd, resolve8(expandUserPath(cwd))]);
30595
+ }
30596
+ function onboardingDiscoveryRunnerOptions(options, allowedCwds, missingAllowedCwds, projectDiscoveryCwdIsSelectedRootFromSyncedConfig) {
30597
+ return {
30598
+ ...options,
30599
+ allowedCwds,
30600
+ missingAllowedCwds,
30601
+ projectDiscoveryCwdIsSelectedRoot: options.projectDiscoveryCwdIsSelectedRoot === true || projectDiscoveryCwdIsSelectedRootFromSyncedConfig
30602
+ };
30603
+ }
30604
+ function allowedCwdsContainAny(allowedCwds, candidates) {
30605
+ const allowed = new Set(normalizeAllowedCwds(allowedCwds));
30606
+ return normalizeAllowedCwds(candidates).some(
30607
+ (candidate) => allowed.has(candidate)
30608
+ );
30609
+ }
30072
30610
  function shouldStartOnboardingDiscoveryAgents(options, workspaceSlug = runnerWorkspaceSlug(options)) {
30073
30611
  return workspaceSlug !== void 0 && options.threadProcess === void 0 && options.onboardingDiscovery === "start";
30074
30612
  }
30075
30613
  async function reportOnboardingDiscoveryStatus(options, log2, args) {
30076
- const workspaceSlug = runnerWorkspaceSlug(options);
30614
+ const workspaceSlug = args.workspaceSlug ?? runnerWorkspaceSlug(options);
30077
30615
  if (workspaceSlug === void 0) {
30078
30616
  return false;
30079
30617
  }
@@ -69158,6 +69696,7 @@ async function parseStartRunnerArgs(args, deps = {
69158
69696
  fast: values.get("fast") === true,
69159
69697
  logFile: stringValue8(values, "log-file"),
69160
69698
  allowedCwds,
69699
+ projectDiscoveryCwdIsSelectedRoot: true,
69161
69700
  allowedForwardPorts: parseAllowedPortList(
69162
69701
  stringValue8(values, "forward-port")
69163
69702
  ),
@@ -69225,6 +69764,7 @@ async function parseAgentRunnerArgs(args, deps = {
69225
69764
  parseAllowedCwdList(stringValue8(values, "allowed-cwd"))
69226
69765
  ) : requestedCwdValue === void 0 ? configuredAllowedCwds2.allowedCwds.length > 0 ? [...configuredAllowedCwds2.allowedCwds] : assertConfiguredAllowedCwds([requestedCwd]) : assertConfiguredAllowedCwds([requestedCwd]);
69227
69766
  const cwd = allowedCwds[0] ?? requestedCwd;
69767
+ const projectDiscoveryCwdIsSelectedRoot = values.has("allowed-cwd") || requestedCwdValue === void 0 && configuredAllowedCwds2.allowedCwds.length > 0;
69228
69768
  const requestedCodexBin = stringValue8(values, "codex-bin") ?? "codex";
69229
69769
  const customCodeServerBin = stringValue8(values, "code-server-bin");
69230
69770
  const initialDependencyStatus = await deps.buildDependencyStatus({
@@ -69264,6 +69804,8 @@ async function parseAgentRunnerArgs(args, deps = {
69264
69804
  logFile: stringValue8(values, "log-file"),
69265
69805
  allowedCwds,
69266
69806
  missingAllowedCwds: configuredAllowedCwds2.missingAllowedCwds,
69807
+ projectDiscoveryCwdIsSelectedRoot,
69808
+ projectDiscoveryImplicitCwdAliases: projectDiscoveryCwdIsSelectedRoot ? void 0 : allowedCwds,
69267
69809
  allowedForwardPorts: parseAllowedPortList(
69268
69810
  stringValue8(values, "forward-port")
69269
69811
  ),
@@ -69387,6 +69929,9 @@ async function parseRunnerArgs(args, deps = {
69387
69929
  const configuredAllowedCwds2 = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(
69388
69930
  parseAllowedCwdList(stringValue8(values, "allowed-cwd"))
69389
69931
  ) : [...localConfiguredAllowedCwds.allowedCwds];
69932
+ const projectDiscoveryCwdIsSelectedRoot = cwdAllowedCwds.some(
69933
+ (cwdAllowedCwd) => configuredAllowedCwds2.includes(cwdAllowedCwd)
69934
+ );
69390
69935
  const requestedCodexBin = stringValue8(values, "codex-bin") ?? "codex";
69391
69936
  const customCodeServerBin = stringValue8(values, "code-server-bin");
69392
69937
  const onboardingDiscovery = values.get("no-onboarding-discovery") === true ? void 0 : "start";
@@ -69441,6 +69986,8 @@ async function parseRunnerArgs(args, deps = {
69441
69986
  /* @__PURE__ */ new Set([...cwdAllowedCwds, ...configuredAllowedCwds2])
69442
69987
  ),
69443
69988
  missingAllowedCwds: localConfiguredAllowedCwds.missingAllowedCwds,
69989
+ projectDiscoveryCwdIsSelectedRoot,
69990
+ projectDiscoveryImplicitCwdAliases: projectDiscoveryCwdIsSelectedRoot ? void 0 : cwdAllowedCwds,
69444
69991
  allowedForwardPorts: parseAllowedPortList(
69445
69992
  stringValue8(values, "forward-port")
69446
69993
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.95-beta",
3
+ "version": "0.0.97-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": {