@linzumi/cli 0.0.96-beta → 0.0.98-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 +611 -82
  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.96-beta --version
61
+ npx -y @linzumi/cli@0.0.98-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
134
+ });
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
144
150
  });
145
- const sourceRoots = args.sourceRoots ?? defaultLocalProjectSourceRoots(args.cwd);
146
- let placesSearched = 1;
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
+ }
174
204
  continue;
175
205
  }
176
- const foundInRoot = discoverGitWorktreesInSourceRoot(sourceRoot);
177
- for (const worktreePath of foundInRoot) {
178
- mergeCandidate(
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) {
226
+ continue;
227
+ }
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
+ });
329
+ }
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;
264
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);
265
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
 
@@ -14371,7 +14705,16 @@ function linzumiMcpServerConfig(options) {
14371
14705
  ...options.toolScope === void 0 || options.toolScope === "all" ? [] : ["--tool-scope", options.toolScope]
14372
14706
  ],
14373
14707
  env: {
14374
- ...options.accessToken === void 0 ? {} : { LINZUMI_MCP_ACCESS_TOKEN: options.accessToken }
14708
+ ...options.accessToken === void 0 ? {} : { LINZUMI_MCP_ACCESS_TOKEN: options.accessToken },
14709
+ // The Linzumi metadata MCP is launched by codex as a STDIO child whose
14710
+ // `command` may be the Electron app binary. That binary only behaves as a
14711
+ // plain Node process (and thus boots the MCP server instead of a GUI
14712
+ // shell) when ELECTRON_RUN_AS_NODE=1 is present in its env. Newer codex
14713
+ // sanitizes MCP-child env to an allowlist that drops ELECTRON_RUN_AS_NODE,
14714
+ // so we both (a) pin it here and (b) explicitly forward it via
14715
+ // codexMcpConfigArgs / codexMcpConfigToml below. For a real Node `command`
14716
+ // this flag is a harmless no-op, so we always include it.
14717
+ ELECTRON_RUN_AS_NODE: "1"
14375
14718
  }
14376
14719
  };
14377
14720
  }
@@ -14380,7 +14723,16 @@ function codexMcpConfigArgs(config) {
14380
14723
  "-c",
14381
14724
  `mcp_servers.${config.name}.command=${JSON.stringify(config.command)}`,
14382
14725
  "-c",
14383
- `mcp_servers.${config.name}.args=${tomlStringArray(config.args)}`
14726
+ `mcp_servers.${config.name}.args=${tomlStringArray(config.args)}`,
14727
+ // Explicitly forward the server env (ELECTRON_RUN_AS_NODE,
14728
+ // LINZUMI_MCP_ACCESS_TOKEN, …) so codex passes it to the stdio child
14729
+ // regardless of its env-forwarding/sanitization allowlist. Without this,
14730
+ // newer codex strips ELECTRON_RUN_AS_NODE and the Electron-binary `command`
14731
+ // boots a GUI shell that pollutes the MCP stdout JSON-RPC framing.
14732
+ ...Object.entries(config.env).flatMap(([key, value]) => [
14733
+ "-c",
14734
+ `mcp_servers.${config.name}.env.${key}=${JSON.stringify(value)}`
14735
+ ])
14384
14736
  ];
14385
14737
  }
14386
14738
  function linzumiMcpCommandForProcess(processExecPath, scriptPath, execArgv = []) {
@@ -19451,7 +19803,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19451
19803
  var init_version = __esm({
19452
19804
  "src/version.ts"() {
19453
19805
  "use strict";
19454
- linzumiCliVersion = "0.0.96-beta";
19806
+ linzumiCliVersion = "0.0.98-beta";
19455
19807
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19456
19808
  }
19457
19809
  });
@@ -23211,6 +23563,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23211
23563
  const missingAllowedCwds = {
23212
23564
  value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
23213
23565
  };
23566
+ const projectDiscoveryCwdIsSelectedRootFromSyncedConfig = { value: false };
23214
23567
  const applyRunnerConfigSync = (config, syncOptions) => {
23215
23568
  const persistedAllowedCwds = replaceAllowedCwdsForLinzumiUrl(
23216
23569
  config.allowedCwds,
@@ -23219,6 +23572,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23219
23572
  const updatedAllowedCwds = configuredAllowedCwds(persistedAllowedCwds, {
23220
23573
  createMissing: true
23221
23574
  });
23575
+ projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value = projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value || allowedCwdsContainAny(updatedAllowedCwds.allowedCwds, [
23576
+ options.cwd,
23577
+ ...startupAllowedCwds
23578
+ ]);
23222
23579
  allowedCwds.value = normalizeAllowedCwds([
23223
23580
  ...syncOptions.includeStartupAllowedCwds ? startupAllowedCwds : [],
23224
23581
  ...updatedAllowedCwds.allowedCwds
@@ -23305,6 +23662,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23305
23662
  allowedCwds.value
23306
23663
  ),
23307
23664
  allowedCwdProjects: allowedCwdProjects(allowedCwds.value),
23665
+ onboardingSelectedFolderScan: true,
23308
23666
  portForwarding: liveForwardPorts.size > 0,
23309
23667
  tcpForwarding: true,
23310
23668
  linzumiMcp: true,
@@ -24386,6 +24744,59 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24386
24744
  );
24387
24745
  }
24388
24746
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
24747
+ const rebuildOnDeadCodexWorker = shouldUseThreadProcesses(options) ? async (rebuildArgs) => {
24748
+ await evictStaleDynamicChannelSession(rebuildArgs.codexThreadId);
24749
+ let rebuilt = false;
24750
+ for (const message of rebuildArgs.pendingMessages) {
24751
+ const reconnectControl = {
24752
+ type: "reconnect_thread",
24753
+ workspace: workspaceSlug,
24754
+ channel: channelSlug,
24755
+ threadId: rebuildArgs.kandanThreadId,
24756
+ codexThreadId: rebuildArgs.codexThreadId,
24757
+ cwd,
24758
+ // Carry the conversation context through the rebuild-replay so
24759
+ // the respawned worker's developer instructions match what the
24760
+ // server would have replayed (parity with the live reconnect
24761
+ // control built above at channelSession.linzumiContext).
24762
+ ...control.linzumiContext === void 0 ? {} : { linzumiContext: control.linzumiContext },
24763
+ ...integerValue(control.rootSeq) === void 0 ? {} : { rootSeq: integerValue(control.rootSeq) },
24764
+ sourceSeq: message.seq,
24765
+ workDescription: message.body,
24766
+ ...runtimeSettings.model === void 0 ? {} : { model: runtimeSettings.model },
24767
+ ...runtimeSettings.reasoningEffort === void 0 ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
24768
+ ...runtimeSettings.sandbox === void 0 ? {} : { sandbox: runtimeSettings.sandbox },
24769
+ ...runtimeSettings.approvalPolicy === void 0 ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
24770
+ ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
24771
+ };
24772
+ const liveSession = dynamicChannelSessions.get(
24773
+ rebuildArgs.codexThreadId
24774
+ );
24775
+ try {
24776
+ if (liveSession !== void 0 && rebuilt) {
24777
+ await liveSession.startThreadMessageTurn({
24778
+ seq: message.seq,
24779
+ body: message.body,
24780
+ actorSlug: message.actorSlug,
24781
+ actorUserId: message.actorUserId,
24782
+ boundStartTimeout: true
24783
+ });
24784
+ } else {
24785
+ await startThreadRunnerProcess(reconnectControl, cwd);
24786
+ }
24787
+ rebuilt = true;
24788
+ } catch (error) {
24789
+ log2("runner.thread_dead_worker_rebuild_failed", {
24790
+ codex_thread_id: rebuildArgs.codexThreadId,
24791
+ kandan_thread_id: rebuildArgs.kandanThreadId,
24792
+ seq: message.seq,
24793
+ message: error instanceof Error ? error.message : String(error)
24794
+ });
24795
+ return false;
24796
+ }
24797
+ }
24798
+ return rebuilt;
24799
+ } : void 0;
24389
24800
  const session = await attachChannelSession({
24390
24801
  kandan,
24391
24802
  codex: sessionCodex,
@@ -24400,6 +24811,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24400
24811
  fetch: options.fetch,
24401
24812
  fast: control.fast ?? options.fast,
24402
24813
  launchTui: false,
24814
+ rebuildOnDeadCodexWorker,
24403
24815
  pipelinePersistDir: commanderOutboxPersistDir(),
24404
24816
  enablePortForwardWatch: true,
24405
24817
  initialForwardPorts: allowedForwardPorts,
@@ -25115,6 +25527,29 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25115
25527
  }
25116
25528
  );
25117
25529
  }
25530
+ if (control.type === "scan_onboarding_projects") {
25531
+ const selectedScanRoots = onboardingProjectScanRoots(control.paths);
25532
+ return runLocalOnboardingProjectDiscovery(options, log2, {
25533
+ workspaceSlug: stringValue(control.workspace),
25534
+ selectedScanRoots,
25535
+ sourceRoots: selectedScanRoots,
25536
+ workerId: "projects-selected-folders",
25537
+ workerLabel: "Selected folders",
25538
+ startingMessage: "Checking selected folders",
25539
+ emptyMessage: "No Git projects found in selected folders",
25540
+ foundMessage: "Selected folder projects found",
25541
+ failedMessage: "Selected folder project scan failed",
25542
+ currentPlace: selectedScanRoots[0] ?? options.cwd,
25543
+ requireTerminalStatusWrite: true,
25544
+ includeRuntimeCwdHint: false
25545
+ }).catch((error) => {
25546
+ log2("onboarding.project_scan_failed", {
25547
+ runnerId: options.runnerId,
25548
+ message: error instanceof Error ? error.message : String(error)
25549
+ });
25550
+ throw error;
25551
+ });
25552
+ }
25118
25553
  if (control.type === "start_instance") {
25119
25554
  const staleAgeMs = staleStartInstanceAgeMs(control, Date.now());
25120
25555
  if (staleAgeMs !== void 0) {
@@ -25219,7 +25654,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25219
25654
  settleControlDispatch(control.type, () => handleControl(control), ack, log2);
25220
25655
  };
25221
25656
  finishRunnerStartup(
25222
- options,
25657
+ onboardingDiscoveryRunnerOptions(
25658
+ options,
25659
+ allowedCwds.value,
25660
+ missingAllowedCwds.value,
25661
+ projectDiscoveryCwdIsSelectedRootFromSyncedConfig.value
25662
+ ),
25223
25663
  log2,
25224
25664
  pendingControls,
25225
25665
  controlDispatcher,
@@ -26090,7 +26530,10 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
26090
26530
  threadId: codexThreadId,
26091
26531
  ...resumeOverrides
26092
26532
  };
26093
- const resumeResponse = await codex.request("thread/resume", resumeParams);
26533
+ const resumeResponse = await withCodexStartRequestTimeout(
26534
+ "thread/resume",
26535
+ codex.request("thread/resume", resumeParams)
26536
+ );
26094
26537
  if (!("error" in resumeResponse)) {
26095
26538
  return;
26096
26539
  }
@@ -26108,7 +26551,10 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
26108
26551
  `failed to prepare Codex thread before reconnect: ${injected.error.message}`
26109
26552
  );
26110
26553
  }
26111
- const verified = await codex.request("thread/resume", resumeParams);
26554
+ const verified = await withCodexStartRequestTimeout(
26555
+ "thread/resume",
26556
+ codex.request("thread/resume", resumeParams)
26557
+ );
26112
26558
  if ("error" in verified) {
26113
26559
  throw new Error(
26114
26560
  `failed to verify Codex thread before reconnect: ${verified.error.message}`
@@ -26699,7 +27145,8 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
26699
27145
  seq: sourceSeq,
26700
27146
  body: workDescription,
26701
27147
  actorSlug: identity.actorUsername,
26702
- actorUserId: identity.actorUserId
27148
+ actorUserId: identity.actorUserId,
27149
+ boundStartTimeout: true
26703
27150
  });
26704
27151
  }
26705
27152
  return {
@@ -29585,6 +30032,14 @@ function onboardingConversationImportSources(value) {
29585
30032
  )
29586
30033
  ).sort();
29587
30034
  }
30035
+ function onboardingProjectScanRoots(value) {
30036
+ return normalizeAllowedCwds(
30037
+ (arrayValue(value) ?? []).flatMap((path2) => {
30038
+ const normalizedPath = stringValue(path2)?.trim();
30039
+ return normalizedPath === void 0 || normalizedPath === "" ? [] : [normalizedPath];
30040
+ })
30041
+ );
30042
+ }
29588
30043
  async function replayLocalOnboardingConversation(args) {
29589
30044
  let replayedChunkCount = 0;
29590
30045
  for (const message of args.messages) {
@@ -30011,27 +30466,42 @@ async function runLocalOnboardingConversationDiscovery(options, log2) {
30011
30466
  );
30012
30467
  }
30013
30468
  }
30014
- async function runLocalOnboardingProjectDiscovery(options, log2) {
30015
- const workspaceSlug = runnerWorkspaceSlug(options);
30469
+ async function runLocalOnboardingProjectDiscovery(options, log2, args = {}) {
30470
+ const workspaceSlug = args.workspaceSlug ?? runnerWorkspaceSlug(options);
30471
+ const selectedScanRoots = args.selectedScanRoots ?? selectedProjectDiscoveryScanRoots(options);
30016
30472
  if (workspaceSlug === void 0) {
30017
30473
  return;
30018
30474
  }
30475
+ const workerId = args.workerId ?? "projects-local-filesystem";
30476
+ const workerLabel = args.workerLabel ?? "Local Git projects";
30477
+ const currentPlace = args.currentPlace ?? selectedScanRoots[0] ?? options.cwd;
30019
30478
  await reportOnboardingDiscoveryStatus(options, log2, {
30020
30479
  phase: "projects",
30021
30480
  status: "starting",
30022
30481
  placesSearched: 0,
30023
- message: "Checking current Git project",
30024
- currentPlace: options.cwd,
30482
+ message: args.startingMessage ?? "Searching local Git projects",
30483
+ currentPlace,
30025
30484
  currentSource: "git",
30485
+ workspaceSlug,
30026
30486
  metadata: {
30027
- worker_id: "projects-local-current",
30028
- worker_label: "Current Git project"
30487
+ worker_id: workerId,
30488
+ worker_label: workerLabel,
30489
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots }
30029
30490
  }
30030
30491
  });
30031
30492
  let successfulPostStartWriteCount = 0;
30032
30493
  try {
30033
- const summary = await options.localProjectDiscovery?.() ?? await discoverCurrentGitProjectInChildProcess(
30034
- { cwd: options.cwd },
30494
+ const summary = await options.localProjectDiscovery?.({
30495
+ selectedScanRoots,
30496
+ sourceRoots: args.sourceRoots,
30497
+ includeRuntimeCwdHint: args.includeRuntimeCwdHint
30498
+ }) ?? await discoverCurrentGitProjectInChildProcess(
30499
+ {
30500
+ cwd: options.cwd,
30501
+ selectedScanRoots,
30502
+ sourceRoots: args.sourceRoots,
30503
+ includeRuntimeCwdHint: args.includeRuntimeCwdHint
30504
+ },
30035
30505
  { log: (event, payload) => log2(event, payload) }
30036
30506
  );
30037
30507
  const client = createLinzumiMcpApiClient({
@@ -30061,22 +30531,31 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30061
30531
  phase: "projects",
30062
30532
  status: "completed",
30063
30533
  placesSearched: summary.placesSearched,
30064
- message: summary.candidates.length === 0 ? "Current folder is not a Git project" : "Current Git project found",
30065
- currentPlace: options.cwd,
30534
+ message: summary.candidates.length === 0 ? args.emptyMessage ?? "No local Git projects found" : args.foundMessage ?? "Local Git projects found",
30535
+ currentPlace,
30066
30536
  currentSource: "git",
30537
+ workspaceSlug,
30067
30538
  metadata: {
30068
- worker_id: "projects-local-current",
30069
- worker_label: "Current Git project",
30539
+ worker_id: workerId,
30540
+ worker_label: workerLabel,
30070
30541
  duration_ms: summary.durationMs,
30071
30542
  reported_count: summary.candidates.length,
30072
30543
  failed_report_count: failedReportCount,
30544
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots },
30073
30545
  search_stats: summary.searchStats
30074
30546
  }
30075
30547
  };
30076
- successfulPostStartWriteCount = await addStatusWriteResult(
30077
- successfulPostStartWriteCount,
30078
- reportOnboardingDiscoveryStatus(options, log2, completedReport)
30548
+ const completedStatusWritten = await reportOnboardingDiscoveryStatus(
30549
+ options,
30550
+ log2,
30551
+ completedReport
30079
30552
  );
30553
+ if (!completedStatusWritten && args.requireTerminalStatusWrite === true) {
30554
+ throw new Error(
30555
+ "onboarding project discovery terminal status write failed"
30556
+ );
30557
+ }
30558
+ successfulPostStartWriteCount = completedStatusWritten ? successfulPostStartWriteCount + 1 : successfulPostStartWriteCount;
30080
30559
  log2("onboarding.local_project_discovery_completed", {
30081
30560
  runnerId: options.runnerId,
30082
30561
  workspace: workspaceSlug,
@@ -30094,19 +30573,26 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30094
30573
  phase: "projects",
30095
30574
  status: "failed",
30096
30575
  placesSearched: 0,
30097
- message: "Current Git project scan failed",
30098
- currentPlace: options.cwd,
30576
+ message: args.failedMessage ?? "Local Git project scan failed",
30577
+ currentPlace,
30099
30578
  currentSource: "git",
30100
30579
  lastError: message,
30580
+ workspaceSlug,
30101
30581
  metadata: {
30102
- worker_id: "projects-local-current",
30103
- worker_label: "Current Git project"
30582
+ worker_id: workerId,
30583
+ worker_label: workerLabel,
30584
+ ...selectedScanRoots.length === 0 ? {} : { selected_scan_roots: selectedScanRoots }
30104
30585
  }
30105
30586
  };
30106
- successfulPostStartWriteCount = await addStatusWriteResult(
30107
- successfulPostStartWriteCount,
30108
- reportOnboardingDiscoveryStatus(options, log2, failedReport)
30587
+ const failedStatusWritten = await reportOnboardingDiscoveryStatus(
30588
+ options,
30589
+ log2,
30590
+ failedReport
30109
30591
  );
30592
+ if (!failedStatusWritten && args.requireTerminalStatusWrite === true) {
30593
+ throw error;
30594
+ }
30595
+ successfulPostStartWriteCount = failedStatusWritten ? successfulPostStartWriteCount + 1 : successfulPostStartWriteCount;
30110
30596
  log2("onboarding.local_project_discovery_failed", {
30111
30597
  runnerId: options.runnerId,
30112
30598
  workspace: workspaceSlug,
@@ -30118,11 +30604,45 @@ async function runLocalOnboardingProjectDiscovery(options, log2) {
30118
30604
  );
30119
30605
  }
30120
30606
  }
30607
+ function selectedProjectDiscoveryScanRoots(options) {
30608
+ const cwd = options.cwd.trim();
30609
+ const implicitCwdAliases = new Set(implicitCwdStringAliases(cwd));
30610
+ for (const alias of options.projectDiscoveryImplicitCwdAliases ?? []) {
30611
+ implicitCwdAliases.add(alias);
30612
+ }
30613
+ const allowedCwds = options.projectDiscoveryCwdIsSelectedRoot === true ? options.allowedCwds : options.allowedCwds.filter(
30614
+ (allowedCwd) => !implicitCwdAliases.has(allowedCwd)
30615
+ );
30616
+ return normalizeAllowedCwds([
30617
+ ...allowedCwds,
30618
+ ...options.missingAllowedCwds ?? []
30619
+ ]);
30620
+ }
30621
+ function implicitCwdStringAliases(cwd) {
30622
+ if (cwd === "") {
30623
+ return [];
30624
+ }
30625
+ return normalizeAllowedCwds([cwd, resolve8(expandUserPath(cwd))]);
30626
+ }
30627
+ function onboardingDiscoveryRunnerOptions(options, allowedCwds, missingAllowedCwds, projectDiscoveryCwdIsSelectedRootFromSyncedConfig) {
30628
+ return {
30629
+ ...options,
30630
+ allowedCwds,
30631
+ missingAllowedCwds,
30632
+ projectDiscoveryCwdIsSelectedRoot: options.projectDiscoveryCwdIsSelectedRoot === true || projectDiscoveryCwdIsSelectedRootFromSyncedConfig
30633
+ };
30634
+ }
30635
+ function allowedCwdsContainAny(allowedCwds, candidates) {
30636
+ const allowed = new Set(normalizeAllowedCwds(allowedCwds));
30637
+ return normalizeAllowedCwds(candidates).some(
30638
+ (candidate) => allowed.has(candidate)
30639
+ );
30640
+ }
30121
30641
  function shouldStartOnboardingDiscoveryAgents(options, workspaceSlug = runnerWorkspaceSlug(options)) {
30122
30642
  return workspaceSlug !== void 0 && options.threadProcess === void 0 && options.onboardingDiscovery === "start";
30123
30643
  }
30124
30644
  async function reportOnboardingDiscoveryStatus(options, log2, args) {
30125
- const workspaceSlug = runnerWorkspaceSlug(options);
30645
+ const workspaceSlug = args.workspaceSlug ?? runnerWorkspaceSlug(options);
30126
30646
  if (workspaceSlug === void 0) {
30127
30647
  return false;
30128
30648
  }
@@ -69207,6 +69727,7 @@ async function parseStartRunnerArgs(args, deps = {
69207
69727
  fast: values.get("fast") === true,
69208
69728
  logFile: stringValue8(values, "log-file"),
69209
69729
  allowedCwds,
69730
+ projectDiscoveryCwdIsSelectedRoot: true,
69210
69731
  allowedForwardPorts: parseAllowedPortList(
69211
69732
  stringValue8(values, "forward-port")
69212
69733
  ),
@@ -69274,6 +69795,7 @@ async function parseAgentRunnerArgs(args, deps = {
69274
69795
  parseAllowedCwdList(stringValue8(values, "allowed-cwd"))
69275
69796
  ) : requestedCwdValue === void 0 ? configuredAllowedCwds2.allowedCwds.length > 0 ? [...configuredAllowedCwds2.allowedCwds] : assertConfiguredAllowedCwds([requestedCwd]) : assertConfiguredAllowedCwds([requestedCwd]);
69276
69797
  const cwd = allowedCwds[0] ?? requestedCwd;
69798
+ const projectDiscoveryCwdIsSelectedRoot = values.has("allowed-cwd") || requestedCwdValue === void 0 && configuredAllowedCwds2.allowedCwds.length > 0;
69277
69799
  const requestedCodexBin = stringValue8(values, "codex-bin") ?? "codex";
69278
69800
  const customCodeServerBin = stringValue8(values, "code-server-bin");
69279
69801
  const initialDependencyStatus = await deps.buildDependencyStatus({
@@ -69313,6 +69835,8 @@ async function parseAgentRunnerArgs(args, deps = {
69313
69835
  logFile: stringValue8(values, "log-file"),
69314
69836
  allowedCwds,
69315
69837
  missingAllowedCwds: configuredAllowedCwds2.missingAllowedCwds,
69838
+ projectDiscoveryCwdIsSelectedRoot,
69839
+ projectDiscoveryImplicitCwdAliases: projectDiscoveryCwdIsSelectedRoot ? void 0 : allowedCwds,
69316
69840
  allowedForwardPorts: parseAllowedPortList(
69317
69841
  stringValue8(values, "forward-port")
69318
69842
  ),
@@ -69436,6 +69960,9 @@ async function parseRunnerArgs(args, deps = {
69436
69960
  const configuredAllowedCwds2 = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(
69437
69961
  parseAllowedCwdList(stringValue8(values, "allowed-cwd"))
69438
69962
  ) : [...localConfiguredAllowedCwds.allowedCwds];
69963
+ const projectDiscoveryCwdIsSelectedRoot = cwdAllowedCwds.some(
69964
+ (cwdAllowedCwd) => configuredAllowedCwds2.includes(cwdAllowedCwd)
69965
+ );
69439
69966
  const requestedCodexBin = stringValue8(values, "codex-bin") ?? "codex";
69440
69967
  const customCodeServerBin = stringValue8(values, "code-server-bin");
69441
69968
  const onboardingDiscovery = values.get("no-onboarding-discovery") === true ? void 0 : "start";
@@ -69490,6 +70017,8 @@ async function parseRunnerArgs(args, deps = {
69490
70017
  /* @__PURE__ */ new Set([...cwdAllowedCwds, ...configuredAllowedCwds2])
69491
70018
  ),
69492
70019
  missingAllowedCwds: localConfiguredAllowedCwds.missingAllowedCwds,
70020
+ projectDiscoveryCwdIsSelectedRoot,
70021
+ projectDiscoveryImplicitCwdAliases: projectDiscoveryCwdIsSelectedRoot ? void 0 : cwdAllowedCwds,
69493
70022
  allowedForwardPorts: parseAllowedPortList(
69494
70023
  stringValue8(values, "forward-port")
69495
70024
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.96-beta",
3
+ "version": "0.0.98-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": {