@staff0rd/assist 0.432.0 → 0.433.1

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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.432.0",
9
+ version: "0.433.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -8070,17 +8070,18 @@ function delay(ms) {
8070
8070
 
8071
8071
  // src/shared/createBundleHandler.ts
8072
8072
  import { createHash } from "crypto";
8073
- import { readFileSync as readFileSync18 } from "fs";
8073
+ import { readFileSync as readFileSync18, statSync as statSync2 } from "fs";
8074
8074
  import { dirname as dirname19, join as join22 } from "path";
8075
8075
  import { fileURLToPath as fileURLToPath4 } from "url";
8076
8076
  function createBundleHandler(importMetaUrl, bundlePath, contentType = "application/javascript") {
8077
- const dir = dirname19(fileURLToPath4(importMetaUrl));
8077
+ const file = join22(dirname19(fileURLToPath4(importMetaUrl)), bundlePath);
8078
8078
  let cache2;
8079
8079
  return (req, res) => {
8080
- if (!cache2) {
8081
- const body = readFileSync18(join22(dir, bundlePath), "utf8");
8080
+ const mtimeMs = statSync2(file).mtimeMs;
8081
+ if (cache2?.mtimeMs !== mtimeMs) {
8082
+ const body = readFileSync18(file, "utf8");
8082
8083
  const etag = `"${createHash("sha256").update(body).digest("hex").slice(0, 16)}"`;
8083
- cache2 = { body, etag };
8084
+ cache2 = { body, etag, mtimeMs };
8084
8085
  }
8085
8086
  const headers = {
8086
8087
  ETag: cache2.etag,
@@ -8700,14 +8701,14 @@ import { basename as basename6, join as join25 } from "path";
8700
8701
  import { promisify as promisify3 } from "util";
8701
8702
 
8702
8703
  // src/commands/sessions/web/findSynthesisForBranch.ts
8703
- import { existsSync as existsSync26, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
8704
+ import { existsSync as existsSync26, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
8704
8705
  import { basename as basename5, dirname as dirname20, join as join24 } from "path";
8705
8706
  function findSynthesisForBranch(repoReviewsDir, branch2) {
8706
8707
  const branchKeyPath = join24(repoReviewsDir, `${branch2}-`);
8707
8708
  const parent = dirname20(branchKeyPath);
8708
8709
  const branchPrefix = basename5(branchKeyPath);
8709
8710
  if (!existsSync26(parent)) return null;
8710
- const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join24(parent, name, "synthesis.md")).filter((path71) => existsSync26(path71)).map((path71) => ({ path: path71, mtime: statSync2(path71).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
8711
+ const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join24(parent, name, "synthesis.md")).filter((path71) => existsSync26(path71)).map((path71) => ({ path: path71, mtime: statSync3(path71).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
8711
8712
  return synthesisFiles[0]?.path ?? null;
8712
8713
  }
8713
8714
 
@@ -13295,7 +13296,7 @@ function getPinStatePath(pin) {
13295
13296
  }
13296
13297
 
13297
13298
  // src/commands/codeComment/sweepRestrictedDir.ts
13298
- import { readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync7 } from "fs";
13299
+ import { readdirSync as readdirSync3, statSync as statSync4, unlinkSync as unlinkSync7 } from "fs";
13299
13300
  import { join as join34 } from "path";
13300
13301
  var STALE_AFTER_MS = 30 * 60 * 1e3;
13301
13302
  function sweepRestrictedDir(dir = getRestrictedDir()) {
@@ -13309,7 +13310,7 @@ function sweepRestrictedDir(dir = getRestrictedDir()) {
13309
13310
  for (const entry of entries) {
13310
13311
  const path71 = join34(dir, entry);
13311
13312
  try {
13312
- if (statSync3(path71).mtimeMs < cutoff) unlinkSync7(path71);
13313
+ if (statSync4(path71).mtimeMs < cutoff) unlinkSync7(path71);
13313
13314
  } catch {
13314
13315
  continue;
13315
13316
  }
@@ -14373,8 +14374,10 @@ function resolveRepoTarget(key, value, repo) {
14373
14374
 
14374
14375
  // src/commands/config/configSet.ts
14375
14376
  function configSet(key, value, options2 = {}) {
14376
- if (options2.repo !== void 0 && options2.global) {
14377
- console.error(chalk127.red("Use either --repo or --global, not both"));
14377
+ if (options2.repo !== void 0 && !options2.global) {
14378
+ console.error(
14379
+ chalk127.red("--repo writes to the global config; add -g (e.g. -g --repo)")
14380
+ );
14378
14381
  process.exit(1);
14379
14382
  }
14380
14383
  const resolved = resolveRepoTarget(key, value, options2.repo);
@@ -14399,7 +14402,7 @@ function registerConfig(program2) {
14399
14402
  const configCommand = program2.command("config").description("View and modify assist.yml configuration");
14400
14403
  configCommand.command("set <key> [value]").description("Set a config value (e.g. commit.push true)").option("-g, --global", "Write to global ~/.assist.yml").option(
14401
14404
  "-r, --repo [name]",
14402
- "Write to ~/.assist.yml under a repo's identity (defaults to the current repo)"
14405
+ "Requires -g: scope the global write to a repo's identity (defaults to the current repo)"
14403
14406
  ).action((key, value, options2) => configSet(key, value, options2));
14404
14407
  configCommand.command("get <key>").description("Get a config value").action(configGet);
14405
14408
  configCommand.command("list").description("List all config values").action(configList);
@@ -15280,7 +15283,7 @@ function collectAllDeps(node) {
15280
15283
  }
15281
15284
 
15282
15285
  // src/commands/dotnet/findContainingSolutions.ts
15283
- import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as statSync4 } from "fs";
15286
+ import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as statSync5 } from "fs";
15284
15287
  import path32 from "path";
15285
15288
  function findSlnFiles(dir, maxDepth, depth = 0) {
15286
15289
  if (depth > maxDepth) return [];
@@ -15296,7 +15299,7 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
15296
15299
  continue;
15297
15300
  const full = path32.join(dir, entry);
15298
15301
  try {
15299
- const stat2 = statSync4(full);
15302
+ const stat2 = statSync5(full);
15300
15303
  if (stat2.isFile() && entry.endsWith(".sln")) {
15301
15304
  results.push(full);
15302
15305
  } else if (stat2.isDirectory()) {
@@ -16267,7 +16270,7 @@ import {
16267
16270
  readdirSync as readdirSync8,
16268
16271
  readFileSync as readFileSync35,
16269
16272
  rmSync as rmSync3,
16270
- statSync as statSync5
16273
+ statSync as statSync6
16271
16274
  } from "fs";
16272
16275
  import { basename as basename10, join as join45 } from "path";
16273
16276
 
@@ -16340,13 +16343,13 @@ async function migrateFile(orm, origin, file, createdAt) {
16340
16343
  async function migrateDiskHandovers(orm, origin, cwd = process.cwd()) {
16341
16344
  let migrated = 0;
16342
16345
  for (const file of collectMarkdown(getHandoversDir(cwd))) {
16343
- const createdAt = parseArchiveTimestamp(basename10(file)) ?? statSync5(file).mtime;
16346
+ const createdAt = parseArchiveTimestamp(basename10(file)) ?? statSync6(file).mtime;
16344
16347
  await migrateFile(orm, origin, file, createdAt);
16345
16348
  migrated++;
16346
16349
  }
16347
16350
  const handoverPath = getHandoverPath(cwd);
16348
16351
  if (existsSync42(handoverPath)) {
16349
- await migrateFile(orm, origin, handoverPath, statSync5(handoverPath).mtime);
16352
+ await migrateFile(orm, origin, handoverPath, statSync6(handoverPath).mtime);
16350
16353
  migrated++;
16351
16354
  }
16352
16355
  return migrated;
@@ -21806,11 +21809,11 @@ async function runBacklogSession(synthesisPath) {
21806
21809
  }
21807
21810
 
21808
21811
  // src/commands/review/cachedReviewerResult.ts
21809
- import { statSync as statSync6 } from "fs";
21812
+ import { statSync as statSync7 } from "fs";
21810
21813
  function cachedReviewerResult(name, outputPath) {
21811
21814
  let size;
21812
21815
  try {
21813
- size = statSync6(outputPath).size;
21816
+ size = statSync7(outputPath).size;
21814
21817
  } catch {
21815
21818
  return null;
21816
21819
  }
@@ -23521,14 +23524,14 @@ async function configure() {
23521
23524
  }
23522
23525
 
23523
23526
  // src/commands/transcript/list.ts
23524
- import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
23527
+ import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
23525
23528
  import { join as join56 } from "path";
23526
23529
  function list4() {
23527
23530
  const { vttDir } = getTranscriptConfig();
23528
23531
  if (!existsSync48(vttDir)) return;
23529
23532
  for (const entry of readdirSync10(vttDir)) {
23530
23533
  if (!entry.endsWith(".vtt")) continue;
23531
- if (statSync7(join56(vttDir, entry)).isDirectory()) continue;
23534
+ if (statSync8(join56(vttDir, entry)).isDirectory()) continue;
23532
23535
  console.log(entry);
23533
23536
  }
23534
23537
  }
@@ -24364,7 +24367,7 @@ async function auth() {
24364
24367
 
24365
24368
  // src/commands/roam/postRoamActivity.ts
24366
24369
  import { execFileSync as execFileSync9 } from "child_process";
24367
- import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as statSync8 } from "fs";
24370
+ import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as statSync9 } from "fs";
24368
24371
  import { join as join63 } from "path";
24369
24372
  function findPortFile(roamDir) {
24370
24373
  let entries;
@@ -24376,7 +24379,7 @@ function findPortFile(roamDir) {
24376
24379
  const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
24377
24380
  const path71 = join63(roamDir, name);
24378
24381
  try {
24379
- return { path: path71, mtimeMs: statSync8(path71).mtimeMs };
24382
+ return { path: path71, mtimeMs: statSync9(path71).mtimeMs };
24380
24383
  } catch {
24381
24384
  return void 0;
24382
24385
  }
@@ -25570,7 +25573,7 @@ function readDesignSystemPrompt() {
25570
25573
  import * as pty from "node-pty";
25571
25574
 
25572
25575
  // src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
25573
- import { chmodSync, existsSync as existsSync57, statSync as statSync9 } from "fs";
25576
+ import { chmodSync, existsSync as existsSync57, statSync as statSync10 } from "fs";
25574
25577
  import { createRequire as createRequire3 } from "module";
25575
25578
  import path59 from "path";
25576
25579
  var require4 = createRequire3(import.meta.url);
@@ -25586,7 +25589,7 @@ function ensureSpawnHelperExecutable() {
25586
25589
  "spawn-helper"
25587
25590
  );
25588
25591
  if (!existsSync57(helper)) return;
25589
- const mode = statSync9(helper).mode;
25592
+ const mode = statSync10(helper).mode;
25590
25593
  if ((mode & 73) === 0) chmodSync(helper, mode | 493);
25591
25594
  }
25592
25595
 
@@ -25646,7 +25649,13 @@ function spawnRun(opts) {
25646
25649
  return spawnPty(["assist", "run", opts.name, ...opts.args ?? []], opts.cwd);
25647
25650
  }
25648
25651
 
25649
- // src/commands/sessions/daemon/createSession.ts
25652
+ // src/commands/sessions/daemon/startOrHoldPty.ts
25653
+ function startOrHoldPty(start3, hold) {
25654
+ if (hold) return { pty: null, pendingStart: start3 };
25655
+ return { pty: start3() };
25656
+ }
25657
+
25658
+ // src/commands/sessions/daemon/sessionBase.ts
25650
25659
  function sessionBase(id, status3) {
25651
25660
  const startedAt = Date.now();
25652
25661
  return {
@@ -25660,27 +25669,32 @@ function sessionBase(id, status3) {
25660
25669
  scrollback: ""
25661
25670
  };
25662
25671
  }
25663
- function createSession(id, prompt, cwd, design, harness) {
25664
- if (harness === "pi") return createPiSession(id, prompt, cwd);
25672
+
25673
+ // src/commands/sessions/daemon/createSession.ts
25674
+ function createSession(id, prompt, cwd, design, harness, holdPty) {
25675
+ if (harness === "pi") return createPiSession(id, prompt, cwd, holdPty);
25665
25676
  const claudeSessionId = randomUUID5();
25666
25677
  return {
25667
25678
  ...sessionBase(id, prompt ? "running" : "waiting"),
25668
25679
  name: prompt?.slice(0, 40) || `Session ${id}`,
25669
25680
  commandType: "claude",
25670
- pty: spawnClaude2({ prompt, cwd, sessionId: id, claudeSessionId, design }),
25681
+ ...startOrHoldPty(
25682
+ () => spawnClaude2({ prompt, cwd, sessionId: id, claudeSessionId, design }),
25683
+ holdPty
25684
+ ),
25671
25685
  cwd,
25672
25686
  claudeSessionId,
25673
25687
  initialPrompt: prompt,
25674
25688
  design
25675
25689
  };
25676
25690
  }
25677
- function createPiSession(id, prompt, cwd) {
25691
+ function createPiSession(id, prompt, cwd, holdPty) {
25678
25692
  return {
25679
25693
  ...sessionBase(id, prompt ? "running" : "waiting"),
25680
25694
  name: prompt?.slice(0, 40) || `Session ${id}`,
25681
25695
  commandType: "claude",
25682
25696
  harness: "pi",
25683
- pty: spawnPi2({ prompt, cwd, sessionId: id }),
25697
+ ...startOrHoldPty(() => spawnPi2({ prompt, cwd, sessionId: id }), holdPty),
25684
25698
  cwd,
25685
25699
  initialPrompt: prompt
25686
25700
  };
@@ -26371,12 +26385,12 @@ function shouldAutoDismiss(session, exitCode) {
26371
26385
  }
26372
26386
 
26373
26387
  // src/commands/sessions/daemon/shouldAutoRun.ts
26374
- function shouldAutoRun(session) {
26388
+ function shouldAutoRun(session, status3 = session.status) {
26375
26389
  if (!session.autoRun) return { run: false, reason: null };
26376
- if (session.status !== "done")
26390
+ if (status3 !== "done")
26377
26391
  return {
26378
26392
  run: false,
26379
- reason: `session status is "${session.status}", not "done"`
26393
+ reason: `session status is "${status3}", not "done"`
26380
26394
  };
26381
26395
  if (session.commandType !== "assist")
26382
26396
  return {
@@ -26402,7 +26416,8 @@ function shouldAutoRun(session) {
26402
26416
  function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
26403
26417
  if (session.status === status3) return;
26404
26418
  const deps2 = { dismiss, notify: notify2, reuseForRun };
26405
- if (status3 === "done" && session.worktree) {
26419
+ const chainsToNextRun = shouldAutoRun(session, status3).run;
26420
+ if (status3 === "done" && session.worktree && !chainsToNextRun) {
26406
26421
  void resolveDoneDurability(
26407
26422
  session,
26408
26423
  () => finishStatusChange(session, "done", exitCode, deps2),
@@ -26437,6 +26452,25 @@ function makeStatusChangeHandler(dismiss, notify2, reuseForRun) {
26437
26452
  return (s, status3, exitCode) => applyStatusChange(s, status3, exitCode, dismiss, notify2, reuseForRun);
26438
26453
  }
26439
26454
 
26455
+ // src/commands/sessions/daemon/emitSessionOutput.ts
26456
+ var MAX_SCROLLBACK = 256 * 1024;
26457
+ function emitSessionOutput(session, clients, data) {
26458
+ session.scrollback += data;
26459
+ if (session.scrollback.length > MAX_SCROLLBACK) {
26460
+ session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
26461
+ }
26462
+ broadcast(clients, { type: "output", sessionId: session.id, data });
26463
+ }
26464
+
26465
+ // src/commands/sessions/daemon/exitReason.ts
26466
+ import { existsSync as existsSync60 } from "fs";
26467
+ function exitReason(session, exitCode) {
26468
+ const base = `process exited with code ${exitCode}`;
26469
+ if (session.cwd && !existsSync60(session.cwd))
26470
+ return `${base}: working directory ${session.cwd} no longer exists`;
26471
+ return base;
26472
+ }
26473
+
26440
26474
  // src/commands/sessions/daemon/handleFailedResume.ts
26441
26475
  function handleFailedResume(session, exitCode, onStatusChange) {
26442
26476
  const failed2 = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
@@ -26450,7 +26484,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
26450
26484
  }
26451
26485
 
26452
26486
  // src/commands/sessions/daemon/watchActivity.ts
26453
- import { existsSync as existsSync60, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26487
+ import { existsSync as existsSync61, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26454
26488
  import { dirname as dirname29 } from "path";
26455
26489
 
26456
26490
  // src/commands/sessions/daemon/applyReviewPause.ts
@@ -26531,7 +26565,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
26531
26565
  if (timer) clearTimeout(timer);
26532
26566
  timer = setTimeout(read, DEBOUNCE_MS2);
26533
26567
  });
26534
- if (existsSync60(path71)) read();
26568
+ if (existsSync61(path71)) read();
26535
26569
  }
26536
26570
  function refreshActivity(session) {
26537
26571
  if (session.commandType !== "assist" || !session.cwd) return;
@@ -26580,7 +26614,7 @@ function handlePtyExit(session, exitCode, onStatusChange) {
26580
26614
  if (handleFailedResume(session, exitCode, onStatusChange)) return;
26581
26615
  const priorStatus = session.status;
26582
26616
  if (exitCode !== 0) {
26583
- session.error = `process exited with code ${exitCode}`;
26617
+ session.error = exitReason(session, exitCode);
26584
26618
  daemonLog(
26585
26619
  `session ${session.id} ("${session.name}") pty exited with code ${exitCode} from status "${priorStatus}" \u2014 unexpected exit, marking error`
26586
26620
  );
@@ -26595,21 +26629,23 @@ function handlePtyExit(session, exitCode, onStatusChange) {
26595
26629
  }
26596
26630
 
26597
26631
  // src/commands/sessions/daemon/wirePtyEvents.ts
26598
- var MAX_SCROLLBACK = 256 * 1024;
26599
- function appendScrollback(session, data) {
26600
- session.scrollback += data;
26601
- if (session.scrollback.length > MAX_SCROLLBACK) {
26602
- session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
26603
- }
26604
- }
26605
26632
  function wirePtyEvents(session, clients, onStatusChange) {
26606
26633
  if (!session.pty) return;
26607
- session.pty.onData((data) => {
26608
- appendScrollback(session, data);
26609
- broadcast(clients, { type: "output", sessionId: session.id, data });
26634
+ session.pty.onData((data) => emitSessionOutput(session, clients, data));
26635
+ session.pty.onExit(({ exitCode }) => {
26636
+ handlePtyExit(session, exitCode, onStatusChange);
26637
+ reportSilentFailure(session, clients);
26610
26638
  });
26611
- session.pty.onExit(
26612
- ({ exitCode }) => handlePtyExit(session, exitCode, onStatusChange)
26639
+ }
26640
+ function reportSilentFailure(session, clients) {
26641
+ if (session.status !== "error" || !session.error) return;
26642
+ if (session.scrollback.length > 0) return;
26643
+ emitSessionOutput(
26644
+ session,
26645
+ clients,
26646
+ `\r
26647
+ \x1B[31m${session.error}\x1B[0m\r
26648
+ `
26613
26649
  );
26614
26650
  }
26615
26651
 
@@ -27162,6 +27198,207 @@ function restoreAll(spawn12, sessions) {
27162
27198
  });
27163
27199
  }
27164
27200
 
27201
+ // src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
27202
+ function rearmStoppedSessions(sessions, notify2) {
27203
+ for (const s of sessions.values()) {
27204
+ if (s.status !== "stopped" || !s.worktree) continue;
27205
+ void resolveDoneDurability(
27206
+ s,
27207
+ () => {
27208
+ if (dismissSession(sessions, s.id)) notify2();
27209
+ },
27210
+ notify2
27211
+ );
27212
+ }
27213
+ }
27214
+
27215
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
27216
+ import { existsSync as existsSync65 } from "fs";
27217
+ import { basename as basename14 } from "path";
27218
+
27219
+ // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
27220
+ var DISABLED = {
27221
+ enabled: false,
27222
+ install: true,
27223
+ copy: [".env", "settings.local.json", ".claude/settings.local.json"]
27224
+ };
27225
+ function worktreeConfigFor(cwd) {
27226
+ try {
27227
+ return loadConfigFrom(cwd).worktree ?? DISABLED;
27228
+ } catch {
27229
+ return DISABLED;
27230
+ }
27231
+ }
27232
+
27233
+ // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
27234
+ function detectExistingWorktree(cwd) {
27235
+ const repoRoot = findRepoRoot(cwd) ?? cwd;
27236
+ if (!worktreeConfigFor(repoRoot).enabled) return void 0;
27237
+ const clone = mainWorktree(repoRoot);
27238
+ if (clone && clone !== repoRoot) return { path: repoRoot, clone };
27239
+ return void 0;
27240
+ }
27241
+
27242
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
27243
+ import { execFile as execFile8 } from "child_process";
27244
+ import { copyFileSync, existsSync as existsSync64, mkdirSync as mkdirSync26 } from "fs";
27245
+ import { dirname as dirname30, join as join71 } from "path";
27246
+
27247
+ // src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
27248
+ import { existsSync as existsSync63 } from "fs";
27249
+ import { join as join70 } from "path";
27250
+ function detectInstallCommand(repoRoot) {
27251
+ if (!existsSync63(join70(repoRoot, "package.json"))) return null;
27252
+ if (existsSync63(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
27253
+ if (existsSync63(join70(repoRoot, "yarn.lock"))) return "yarn install";
27254
+ if (existsSync63(join70(repoRoot, "bun.lockb"))) return "bun install";
27255
+ return "npm install";
27256
+ }
27257
+ function resolveInstallCommand(repoRoot, install) {
27258
+ if (install === false) return null;
27259
+ if (typeof install === "string") return install;
27260
+ return detectInstallCommand(repoRoot);
27261
+ }
27262
+
27263
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
27264
+ function seedWorktree(worktreePath, clone, onSeeded = () => {
27265
+ }) {
27266
+ const cfg = worktreeConfigFor(clone);
27267
+ copyConfigFiles(worktreePath, clone, cfg.copy);
27268
+ runInstall(worktreePath, clone, cfg.install, onSeeded);
27269
+ }
27270
+ function copyConfigFiles(worktreePath, clone, copy) {
27271
+ for (const rel of copy) {
27272
+ const src = join71(clone, rel);
27273
+ if (!existsSync64(src)) continue;
27274
+ const dest = join71(worktreePath, rel);
27275
+ try {
27276
+ mkdirSync26(dirname30(dest), { recursive: true });
27277
+ copyFileSync(src, dest);
27278
+ daemonLog(`worktree ${worktreePath} seeded ${rel}`);
27279
+ } catch (error) {
27280
+ daemonLog(
27281
+ `worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
27282
+ );
27283
+ }
27284
+ }
27285
+ }
27286
+ function runInstall(worktreePath, clone, install, onSeeded) {
27287
+ const command = resolveInstallCommand(clone, install);
27288
+ if (!command) {
27289
+ onSeeded();
27290
+ return;
27291
+ }
27292
+ const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
27293
+ const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
27294
+ const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
27295
+ daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
27296
+ execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
27297
+ daemonLog(
27298
+ error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
27299
+ );
27300
+ onSeeded();
27301
+ });
27302
+ }
27303
+ function message(error) {
27304
+ return error instanceof Error ? error.message : String(error);
27305
+ }
27306
+
27307
+ // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
27308
+ function boundTreeRoots(sessions) {
27309
+ const roots = /* @__PURE__ */ new Set();
27310
+ for (const session of sessions.values())
27311
+ if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
27312
+ return roots;
27313
+ }
27314
+ function bindNewWorktree(session, alloc, notify2, startHeld = () => {
27315
+ }) {
27316
+ if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
27317
+ return;
27318
+ session.worktree = { path: alloc.cwd, clone: alloc.clone };
27319
+ if (alloc.created)
27320
+ seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
27321
+ notify2();
27322
+ }
27323
+ function bindRestoredWorktrees(sessions) {
27324
+ for (const session of sessions.values()) {
27325
+ if (session.worktree || !session.cwd) continue;
27326
+ const worktree = detectExistingWorktree(session.cwd);
27327
+ if (worktree) session.worktree = worktree;
27328
+ }
27329
+ }
27330
+ function bindResumedWorktree(session, cwd, notify2) {
27331
+ if (!session) return;
27332
+ const worktree = detectExistingWorktree(cwd);
27333
+ if (!worktree) return;
27334
+ session.worktree = worktree;
27335
+ notify2();
27336
+ }
27337
+
27338
+ // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
27339
+ var REGISTRY_FILE = "worktrees.json";
27340
+ function readWorktreeRegistry() {
27341
+ const data = loadJson(REGISTRY_FILE);
27342
+ return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
27343
+ }
27344
+ function recordWorktree(path71, clone, origin) {
27345
+ const data = loadJson(REGISTRY_FILE);
27346
+ data[path71] = { clone, origin };
27347
+ saveJson(REGISTRY_FILE, data);
27348
+ }
27349
+
27350
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
27351
+ function reconcileWorktreesOnRestore(sessions) {
27352
+ bindRestoredWorktrees(sessions);
27353
+ void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
27354
+ }
27355
+ async function pruneWorktrees(liveCwds) {
27356
+ const accounted = /* @__PURE__ */ new Set();
27357
+ for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
27358
+ for (const s of loadPersistedSessions()) accounted.add(s.cwd);
27359
+ const clonesToPrune = /* @__PURE__ */ new Set();
27360
+ const orphanBranches = [];
27361
+ for (const { path: path71, clone } of readWorktreeRegistry()) {
27362
+ if (accounted.has(path71)) continue;
27363
+ if (!existsSync65(path71)) {
27364
+ clonesToPrune.add(clone);
27365
+ orphanBranches.push({ clone, branch: basename14(path71) });
27366
+ continue;
27367
+ }
27368
+ daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
27369
+ await reapWorktree(path71);
27370
+ }
27371
+ for (const clone of clonesToPrune) {
27372
+ if (!existsSync65(clone)) continue;
27373
+ try {
27374
+ await git3(clone, ["worktree", "prune"]);
27375
+ daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
27376
+ } catch {
27377
+ }
27378
+ }
27379
+ for (const { clone, branch: branch2 } of orphanBranches)
27380
+ if (existsSync65(clone)) await pruneStaleWorktreeBranch(clone, branch2);
27381
+ }
27382
+ async function pruneStaleWorktreeBranch(clone, branch2) {
27383
+ if (!listLocalBranches(clone).includes(branch2)) return;
27384
+ try {
27385
+ await git3(clone, ["branch", "-d", branch2]);
27386
+ daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
27387
+ } catch {
27388
+ daemonLog(
27389
+ `stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
27390
+ );
27391
+ }
27392
+ }
27393
+
27394
+ // src/commands/sessions/daemon/restoreAllSessions.ts
27395
+ function restoreAllSessions(spawnWith, sessions, notify2) {
27396
+ const names = restoreAll(spawnWith, sessions);
27397
+ reconcileWorktreesOnRestore(sessions);
27398
+ rearmStoppedSessions(sessions, notify2);
27399
+ return names;
27400
+ }
27401
+
27165
27402
  // src/commands/sessions/daemon/retrySession.ts
27166
27403
  function retrySession(session, clients, onStatusChange) {
27167
27404
  const respawn = respawnThunk(session);
@@ -27268,8 +27505,44 @@ function shutdownSessions(sessions) {
27268
27505
  }
27269
27506
  }
27270
27507
 
27508
+ // src/commands/sessions/daemon/startHeldSession.ts
27509
+ function startHeldSession(session, sessions, clients, onStatusChange, notify2) {
27510
+ const start3 = session.pendingStart;
27511
+ if (!start3) return;
27512
+ session.pendingStart = void 0;
27513
+ if (sessions.get(session.id) !== session) {
27514
+ daemonLog(
27515
+ `session ${session.id} not started after seeding: card is gone (${session.worktree?.path ?? "no worktree"})`
27516
+ );
27517
+ return;
27518
+ }
27519
+ session.startedAt = Date.now();
27520
+ if (session.status === "running") session.runningSince = session.startedAt;
27521
+ session.pty = start3();
27522
+ if (session.cols && session.rows)
27523
+ try {
27524
+ session.pty?.resize(session.cols, session.rows);
27525
+ } catch {
27526
+ }
27527
+ wirePtyEvents(session, clients, onStatusChange);
27528
+ daemonLog(
27529
+ `session ${session.id} started once worktree ${session.worktree?.path ?? session.cwd} finished seeding`
27530
+ );
27531
+ notify2();
27532
+ }
27533
+
27534
+ // src/commands/sessions/daemon/treeSpawnContext.ts
27535
+ function treeSpawnContext(sessions, spawnWith, notify2, clients, onStatusChange) {
27536
+ return {
27537
+ sessions,
27538
+ spawnWith,
27539
+ notify: notify2,
27540
+ startHeld: (session) => startHeldSession(session, sessions, clients, onStatusChange, notify2)
27541
+ };
27542
+ }
27543
+
27271
27544
  // src/commands/sessions/daemon/watchTranscript.ts
27272
- import { existsSync as existsSync62, watch as watch3 } from "fs";
27545
+ import { existsSync as existsSync66, watch as watch3 } from "fs";
27273
27546
 
27274
27547
  // src/commands/sessions/daemon/reconcileTranscriptStatus.ts
27275
27548
  async function resolveTranscriptPath(session) {
@@ -27304,7 +27577,7 @@ function watchTranscript(session, onStatusChange) {
27304
27577
  if (!session.cwd || !session.claudeSessionId) return;
27305
27578
  if (session.watchedTranscriptId === session.claudeSessionId) return;
27306
27579
  const dir = projectDirForCwd(session.cwd);
27307
- if (!existsSync62(dir)) return;
27580
+ if (!existsSync66(dir)) return;
27308
27581
  if (session.transcriptWatcher) {
27309
27582
  session.transcriptWatcher.close();
27310
27583
  session.transcriptWatcher = void 0;
@@ -27455,13 +27728,13 @@ async function defaultConnect() {
27455
27728
  }
27456
27729
 
27457
27730
  // src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
27458
- import { existsSync as existsSync63, readFileSync as readFileSync49 } from "fs";
27731
+ import { existsSync as existsSync67, readFileSync as readFileSync49 } from "fs";
27459
27732
  import { posix } from "path";
27460
27733
  function hasPersistedWindowsSessions() {
27461
27734
  const sessionsFile = windowsSessionsFileFromWsl();
27462
27735
  if (!sessionsFile) return false;
27463
27736
  try {
27464
- if (!existsSync63(sessionsFile)) return false;
27737
+ if (!existsSync67(sessionsFile)) return false;
27465
27738
  const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
27466
27739
  return Array.isArray(data) && data.length > 0;
27467
27740
  } catch (error) {
@@ -27588,7 +27861,7 @@ function replayScrollback2(state, client) {
27588
27861
  data: stripReplayQueries(data)
27589
27862
  });
27590
27863
  }
27591
- function appendScrollback2(state, sessionId, data) {
27864
+ function appendScrollback(state, sessionId, data) {
27592
27865
  const next3 = (state.scrollback.get(sessionId) ?? "") + data;
27593
27866
  state.scrollback.set(
27594
27867
  sessionId,
@@ -27688,7 +27961,7 @@ function handleCreated(state, msg) {
27688
27961
  function handleOutput(state, msg) {
27689
27962
  const sessionId = nsId(msg);
27690
27963
  const data = msg.data;
27691
- appendScrollback2(state, sessionId, data);
27964
+ appendScrollback(state, sessionId, data);
27692
27965
  state.broadcast({ type: "output", sessionId, data });
27693
27966
  }
27694
27967
  function relayWithSessionId(state, msg) {
@@ -28004,194 +28277,8 @@ var WindowsProxy = class {
28004
28277
  }
28005
28278
  };
28006
28279
 
28007
- // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28008
- import { existsSync as existsSync66 } from "fs";
28009
- import { basename as basename14 } from "path";
28010
-
28011
- // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
28012
- var DISABLED = {
28013
- enabled: false,
28014
- install: true,
28015
- copy: [".env", "settings.local.json", ".claude/settings.local.json"]
28016
- };
28017
- function worktreeConfigFor(cwd) {
28018
- try {
28019
- return loadConfigFrom(cwd).worktree ?? DISABLED;
28020
- } catch {
28021
- return DISABLED;
28022
- }
28023
- }
28024
-
28025
- // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
28026
- function detectExistingWorktree(cwd) {
28027
- const repoRoot = findRepoRoot(cwd) ?? cwd;
28028
- if (!worktreeConfigFor(repoRoot).enabled) return void 0;
28029
- const clone = mainWorktree(repoRoot);
28030
- if (clone && clone !== repoRoot) return { path: repoRoot, clone };
28031
- return void 0;
28032
- }
28033
-
28034
- // src/commands/sessions/daemon/worktree/seedWorktree.ts
28035
- import { execFile as execFile8 } from "child_process";
28036
- import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
28037
- import { dirname as dirname30, join as join71 } from "path";
28038
-
28039
- // src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
28040
- import { existsSync as existsSync64 } from "fs";
28041
- import { join as join70 } from "path";
28042
- function detectInstallCommand(repoRoot) {
28043
- if (!existsSync64(join70(repoRoot, "package.json"))) return null;
28044
- if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
28045
- if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
28046
- if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
28047
- return "npm install";
28048
- }
28049
- function resolveInstallCommand(repoRoot, install) {
28050
- if (install === false) return null;
28051
- if (typeof install === "string") return install;
28052
- return detectInstallCommand(repoRoot);
28053
- }
28054
-
28055
- // src/commands/sessions/daemon/worktree/seedWorktree.ts
28056
- function seedWorktree(worktreePath, clone) {
28057
- const cfg = worktreeConfigFor(clone);
28058
- copyConfigFiles(worktreePath, clone, cfg.copy);
28059
- runInstall(worktreePath, clone, cfg.install);
28060
- }
28061
- function copyConfigFiles(worktreePath, clone, copy) {
28062
- for (const rel of copy) {
28063
- const src = join71(clone, rel);
28064
- if (!existsSync65(src)) continue;
28065
- const dest = join71(worktreePath, rel);
28066
- try {
28067
- mkdirSync26(dirname30(dest), { recursive: true });
28068
- copyFileSync(src, dest);
28069
- daemonLog(`worktree ${worktreePath} seeded ${rel}`);
28070
- } catch (error) {
28071
- daemonLog(
28072
- `worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
28073
- );
28074
- }
28075
- }
28076
- }
28077
- function runInstall(worktreePath, clone, install) {
28078
- const command = resolveInstallCommand(clone, install);
28079
- if (!command) return;
28080
- const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
28081
- const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
28082
- const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
28083
- daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
28084
- execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
28085
- daemonLog(
28086
- error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
28087
- );
28088
- });
28089
- }
28090
- function message(error) {
28091
- return error instanceof Error ? error.message : String(error);
28092
- }
28093
-
28094
- // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
28095
- function boundTreeRoots(sessions) {
28096
- const roots = /* @__PURE__ */ new Set();
28097
- for (const session of sessions.values())
28098
- if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
28099
- return roots;
28100
- }
28101
- function bindNewWorktree(session, alloc, notify2) {
28102
- if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
28103
- return;
28104
- session.worktree = { path: alloc.cwd, clone: alloc.clone };
28105
- if (alloc.created) seedWorktree(alloc.cwd, alloc.clone);
28106
- notify2();
28107
- }
28108
- function bindRestoredWorktrees(sessions) {
28109
- for (const session of sessions.values()) {
28110
- if (session.worktree || !session.cwd) continue;
28111
- const worktree = detectExistingWorktree(session.cwd);
28112
- if (worktree) session.worktree = worktree;
28113
- }
28114
- }
28115
- function bindResumedWorktree(session, cwd, notify2) {
28116
- if (!session) return;
28117
- const worktree = detectExistingWorktree(cwd);
28118
- if (!worktree) return;
28119
- session.worktree = worktree;
28120
- notify2();
28121
- }
28122
-
28123
- // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
28124
- var REGISTRY_FILE = "worktrees.json";
28125
- function readWorktreeRegistry() {
28126
- const data = loadJson(REGISTRY_FILE);
28127
- return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
28128
- }
28129
- function recordWorktree(path71, clone, origin) {
28130
- const data = loadJson(REGISTRY_FILE);
28131
- data[path71] = { clone, origin };
28132
- saveJson(REGISTRY_FILE, data);
28133
- }
28134
-
28135
- // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28136
- function reconcileWorktreesOnRestore(sessions) {
28137
- bindRestoredWorktrees(sessions);
28138
- void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
28139
- }
28140
- async function pruneWorktrees(liveCwds) {
28141
- const accounted = /* @__PURE__ */ new Set();
28142
- for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
28143
- for (const s of loadPersistedSessions()) accounted.add(s.cwd);
28144
- const clonesToPrune = /* @__PURE__ */ new Set();
28145
- const orphanBranches = [];
28146
- for (const { path: path71, clone } of readWorktreeRegistry()) {
28147
- if (accounted.has(path71)) continue;
28148
- if (!existsSync66(path71)) {
28149
- clonesToPrune.add(clone);
28150
- orphanBranches.push({ clone, branch: basename14(path71) });
28151
- continue;
28152
- }
28153
- daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
28154
- await reapWorktree(path71);
28155
- }
28156
- for (const clone of clonesToPrune) {
28157
- if (!existsSync66(clone)) continue;
28158
- try {
28159
- await git3(clone, ["worktree", "prune"]);
28160
- daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
28161
- } catch {
28162
- }
28163
- }
28164
- for (const { clone, branch: branch2 } of orphanBranches)
28165
- if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
28166
- }
28167
- async function pruneStaleWorktreeBranch(clone, branch2) {
28168
- if (!listLocalBranches(clone).includes(branch2)) return;
28169
- try {
28170
- await git3(clone, ["branch", "-d", branch2]);
28171
- daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
28172
- } catch {
28173
- daemonLog(
28174
- `stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
28175
- );
28176
- }
28177
- }
28178
-
28179
- // src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
28180
- function rearmStoppedSessions(sessions, notify2) {
28181
- for (const s of sessions.values()) {
28182
- if (s.status !== "stopped" || !s.worktree) continue;
28183
- void resolveDoneDurability(
28184
- s,
28185
- () => {
28186
- if (dismissSession(sessions, s.id)) notify2();
28187
- },
28188
- notify2
28189
- );
28190
- }
28191
- }
28192
-
28193
28280
  // src/commands/sessions/daemon/createAssistSession.ts
28194
- function createAssistSession(id, assistArgs, cwd, meta) {
28281
+ function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
28195
28282
  const startedAt = Date.now();
28196
28283
  return {
28197
28284
  id,
@@ -28203,7 +28290,10 @@ function createAssistSession(id, assistArgs, cwd, meta) {
28203
28290
  startedAt,
28204
28291
  runningMs: 0,
28205
28292
  runningSince: startedAt,
28206
- pty: spawnPty(["assist", ...assistArgs], cwd, id),
28293
+ ...startOrHoldPty(
28294
+ () => spawnPty(["assist", ...assistArgs], cwd, id),
28295
+ holdPty
28296
+ ),
28207
28297
  scrollback: "",
28208
28298
  assistArgs,
28209
28299
  cwd
@@ -28232,7 +28322,7 @@ function resumeSession(id, sessionId, cwd, name) {
28232
28322
  }
28233
28323
 
28234
28324
  // src/commands/sessions/daemon/worktree/createWorktree.ts
28235
- import { existsSync as existsSync67 } from "fs";
28325
+ import { existsSync as existsSync68 } from "fs";
28236
28326
  import { basename as basename16, dirname as dirname31 } from "path";
28237
28327
 
28238
28328
  // src/commands/sessions/daemon/worktree/planAllocation.ts
@@ -28257,7 +28347,7 @@ function createWorktree(clone, root, boundTreeRoots2) {
28257
28347
  const path71 = nextWorktreePath(
28258
28348
  clone,
28259
28349
  base,
28260
- (candidate) => registered.has(candidate) || existsSync67(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28350
+ (candidate) => registered.has(candidate) || existsSync68(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28261
28351
  );
28262
28352
  const trunk = cloneTrunkBranch(clone);
28263
28353
  gitSync(clone, ["config", "push.default", "upstream"]);
@@ -28309,22 +28399,23 @@ function allocateTree(requestedCwd, boundTreeRoots2) {
28309
28399
  // src/commands/sessions/daemon/worktree/spawnInTree.ts
28310
28400
  function allocateAndBind(ctx, cwd, create) {
28311
28401
  const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
28312
- const id = ctx.spawnWith((sid) => create(sid, alloc.cwd));
28313
- bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify);
28402
+ const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
28403
+ const id = ctx.spawnWith((sid) => create(sid, alloc.cwd, needsSeeding));
28404
+ bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
28314
28405
  return id;
28315
28406
  }
28316
28407
  function spawnInTree(ctx, prompt, cwd, design, harness) {
28317
28408
  return allocateAndBind(
28318
28409
  ctx,
28319
28410
  cwd,
28320
- (sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
28411
+ (sid, resolvedCwd, holdUntilSeeded) => createSession(sid, prompt, resolvedCwd, design, harness, holdUntilSeeded)
28321
28412
  );
28322
28413
  }
28323
28414
  function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
28324
28415
  return allocateAndBind(
28325
28416
  ctx,
28326
28417
  cwd,
28327
- (sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
28418
+ (sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded)
28328
28419
  );
28329
28420
  }
28330
28421
  function resumeInTree(ctx, sessionId, cwd, name) {
@@ -28432,10 +28523,7 @@ var SessionManager = class {
28432
28523
  );
28433
28524
  }
28434
28525
  restore() {
28435
- const names = restoreAll(this.spawnWith, this.sessions);
28436
- reconcileWorktreesOnRestore(this.sessions);
28437
- rearmStoppedSessions(this.sessions, this.notify);
28438
- return names;
28526
+ return restoreAllSessions(this.spawnWith, this.sessions, this.notify);
28439
28527
  }
28440
28528
  drain = () => drainSessions(this.sessions, this.notify);
28441
28529
  add(session) {
@@ -28446,11 +28534,13 @@ var SessionManager = class {
28446
28534
  }
28447
28535
  spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
28448
28536
  treeCtx() {
28449
- return {
28450
- sessions: this.sessions,
28451
- spawnWith: this.spawnWith,
28452
- notify: this.notify
28453
- };
28537
+ return treeSpawnContext(
28538
+ this.sessions,
28539
+ this.spawnWith,
28540
+ this.notify,
28541
+ this.clients,
28542
+ this.onStatusChange
28543
+ );
28454
28544
  }
28455
28545
  spawn(prompt, cwd, design, harness) {
28456
28546
  return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
@@ -29027,22 +29117,22 @@ var sessionsConfigHelp = [
29027
29117
  },
29028
29118
  {
29029
29119
  key: "worktree.enabled",
29030
- setter: "assist config set worktree.enabled true --repo",
29120
+ setter: "assist config set worktree.enabled true -g --repo",
29031
29121
  note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
29032
29122
  },
29033
29123
  {
29034
29124
  key: "worktree.root",
29035
- setter: "assist config set worktree.root ~/git --repo",
29125
+ setter: "assist config set worktree.root ~/git -g --repo",
29036
29126
  note: "optional; parent dir for <clone>-N worktrees (default: the clone's parent)"
29037
29127
  },
29038
29128
  {
29039
29129
  key: "worktree.install",
29040
- setter: "assist config set worktree.install true --repo",
29130
+ setter: "assist config set worktree.install true -g --repo",
29041
29131
  note: "per-worktree dep install: true auto-detects the package manager, or give an explicit command; false to skip"
29042
29132
  },
29043
29133
  {
29044
29134
  key: "worktree.copy",
29045
- setter: "assist config set worktree.copy .env,.claude/settings.local.json --repo",
29135
+ setter: "assist config set worktree.copy .env,.claude/settings.local.json -g --repo",
29046
29136
  note: "gitignored files copied into a new worktree so it can build/run"
29047
29137
  }
29048
29138
  ];
@@ -29262,13 +29352,13 @@ function buildLimitsSegment(rateLimits) {
29262
29352
  }
29263
29353
 
29264
29354
  // src/commands/readGitBranch.ts
29265
- import { readFileSync as readFileSync52, statSync as statSync11 } from "fs";
29355
+ import { readFileSync as readFileSync52, statSync as statSync12 } from "fs";
29266
29356
  import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
29267
29357
  function resolveGitDir(cwd) {
29268
29358
  const dotGit = join73(cwd, ".git");
29269
29359
  let stat2;
29270
29360
  try {
29271
- stat2 = statSync11(dotGit);
29361
+ stat2 = statSync12(dotGit);
29272
29362
  } catch {
29273
29363
  return null;
29274
29364
  }