@staff0rd/assist 0.431.0 → 0.433.0

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.431.0",
9
+ version: "0.433.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -432,6 +432,16 @@ var assistConfigSchema = z2.strictObject({
432
432
  promptJira: z2.boolean().default(false)
433
433
  }).optional(),
434
434
  repos: z2.record(z2.string(), z2.record(z2.string(), z2.unknown())).optional(),
435
+ worktree: z2.strictObject({
436
+ enabled: z2.boolean().default(false),
437
+ root: z2.string().optional(),
438
+ install: z2.union([z2.boolean(), z2.string()]).default(true),
439
+ copy: z2.array(z2.string()).default([
440
+ ".env",
441
+ "settings.local.json",
442
+ ".claude/settings.local.json"
443
+ ])
444
+ }).optional(),
435
445
  run: z2.array(z2.union([runConfigSchema, runLinkSchema])).optional(),
436
446
  subtasks: z2.array(
437
447
  z2.strictObject({
@@ -1542,6 +1552,7 @@ function warnIfUnexpectedBranch(config) {
1542
1552
  if (!expected) return;
1543
1553
  const current = currentBranch();
1544
1554
  if (current === null || current === expected) return;
1555
+ if (upstreamBranch() === expected) return;
1545
1556
  const line = "\u2500".repeat(64);
1546
1557
  console.warn(
1547
1558
  chalk3.yellow.bold(
@@ -1565,6 +1576,18 @@ function currentBranch() {
1565
1576
  return null;
1566
1577
  }
1567
1578
  }
1579
+ function upstreamBranch() {
1580
+ try {
1581
+ const upstream = execSync(
1582
+ "git rev-parse --abbrev-ref --symbolic-full-name @{upstream}",
1583
+ { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
1584
+ ).trim();
1585
+ const slash = upstream.indexOf("/");
1586
+ return slash === -1 ? upstream || null : upstream.slice(slash + 1) || null;
1587
+ } catch {
1588
+ return null;
1589
+ }
1590
+ }
1568
1591
 
1569
1592
  // src/commands/commit/findConflictMarkers.ts
1570
1593
  import { execSync as execSync2 } from "child_process";
@@ -1726,7 +1749,7 @@ function gitRefUrl(kind, ref, cwd) {
1726
1749
  }
1727
1750
 
1728
1751
  // src/commands/commit/collectCommitRefs.ts
1729
- function collectCommitRefs(message) {
1752
+ function collectCommitRefs(message2) {
1730
1753
  const refs = [];
1731
1754
  const branch2 = git("rev-parse --abbrev-ref HEAD");
1732
1755
  if (branch2 && branch2 !== "HEAD") {
@@ -1743,7 +1766,7 @@ function collectCommitRefs(message) {
1743
1766
  ref: sha,
1744
1767
  url: gitRefUrl("commit", sha)
1745
1768
  };
1746
- const subject = message.split("\n", 1)[0];
1769
+ const subject = message2.split("\n", 1)[0];
1747
1770
  if (subject) ref.title = subject;
1748
1771
  refs.push(ref);
1749
1772
  }
@@ -1762,16 +1785,16 @@ function git(args) {
1762
1785
 
1763
1786
  // src/commands/commit/stageAndCommit.ts
1764
1787
  import { execSync as execSync5 } from "child_process";
1765
- function commitStaged(message) {
1766
- execSync5(`git commit -m ${shellQuote(message)}`, { stdio: "inherit" });
1788
+ function commitStaged(message2) {
1789
+ execSync5(`git commit -m ${shellQuote(message2)}`, { stdio: "inherit" });
1767
1790
  return execSync5("git rev-parse --short=7 HEAD", {
1768
1791
  encoding: "utf8"
1769
1792
  }).trim();
1770
1793
  }
1771
- function stageAndCommit(files, message) {
1794
+ function stageAndCommit(files, message2) {
1772
1795
  const escaped = files.map(shellQuote).join(" ");
1773
1796
  execSync5(`git add ${escaped}`, { stdio: "inherit" });
1774
- return commitStaged(message);
1797
+ return commitStaged(message2);
1775
1798
  }
1776
1799
 
1777
1800
  // src/commands/sessions/summarise/iterateUserMessages.ts
@@ -1866,34 +1889,34 @@ function backlogRefError(subject, context, ids) {
1866
1889
  // src/commands/commit/validateMessage.ts
1867
1890
  var MAX_MESSAGE_LENGTH = 50;
1868
1891
  var CONVENTIONAL_COMMIT_REGEX = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!)?(\(.+\))?!?: .+$/;
1869
- function validateMessage(message, config) {
1870
- if (message.toLowerCase().includes("claude")) {
1892
+ function validateMessage(message2, config) {
1893
+ if (message2.toLowerCase().includes("claude")) {
1871
1894
  console.error("Error: Commit message must not reference Claude");
1872
1895
  process.exit(1);
1873
1896
  }
1874
- const backlogIds = findBacklogRefs(message);
1897
+ const backlogIds = findBacklogRefs(message2);
1875
1898
  if (backlogIds.length > 0) {
1876
1899
  console.error(
1877
1900
  backlogRefError("Commit message", "commit messages", backlogIds)
1878
1901
  );
1879
1902
  process.exit(1);
1880
1903
  }
1881
- if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message)) {
1904
+ if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message2)) {
1882
1905
  console.error(
1883
1906
  "Error: Commit message must follow conventional commit format (e.g., 'feat: add feature', 'fix(scope): fix bug')"
1884
1907
  );
1885
1908
  process.exit(1);
1886
1909
  }
1887
- if (message.length > MAX_MESSAGE_LENGTH) {
1910
+ if (message2.length > MAX_MESSAGE_LENGTH) {
1888
1911
  console.error(
1889
- `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message.length})`
1912
+ `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message2.length})`
1890
1913
  );
1891
1914
  process.exit(1);
1892
1915
  }
1893
1916
  }
1894
1917
 
1895
1918
  // src/commands/commit.ts
1896
- async function execCommit(files, message, config) {
1919
+ async function execCommit(files, message2, config) {
1897
1920
  try {
1898
1921
  warnIfUnexpectedBranch(config);
1899
1922
  const pulled = Boolean(config.commit?.pull);
@@ -1901,21 +1924,21 @@ async function execCommit(files, message, config) {
1901
1924
  execSync6("git pull --autostash", { stdio: "inherit" });
1902
1925
  }
1903
1926
  abortOnConflicts(files, pulled);
1904
- const sha = files.length > 0 ? stageAndCommit(files, message) : commitStaged(message);
1927
+ const sha = files.length > 0 ? stageAndCommit(files, message2) : commitStaged(message2);
1905
1928
  console.log(`Committed: ${sha}`);
1906
1929
  if (config.commit?.push) {
1907
1930
  execSync6("git push", { stdio: "inherit" });
1908
1931
  console.log("Pushed to remote");
1909
1932
  }
1910
- await recordCommitActivity(message);
1933
+ await recordCommitActivity(message2);
1911
1934
  process.exit(0);
1912
1935
  } catch {
1913
1936
  process.exit(1);
1914
1937
  }
1915
1938
  }
1916
- async function recordCommitActivity(message) {
1939
+ async function recordCommitActivity(message2) {
1917
1940
  if (resolveSessionItemId() === null) return;
1918
- await recordSessionRefs(collectCommitRefs(message));
1941
+ await recordSessionRefs(collectCommitRefs(message2));
1919
1942
  }
1920
1943
  async function commit(args) {
1921
1944
  if (args[0] === "status") {
@@ -1928,11 +1951,11 @@ async function commit(args) {
1928
1951
  console.error("Usage: assist commit <message> [files...]");
1929
1952
  process.exit(1);
1930
1953
  }
1931
- const message = args[0];
1954
+ const message2 = args[0];
1932
1955
  const files = args.slice(1);
1933
1956
  const config = loadConfig();
1934
- validateMessage(message, config);
1935
- await execCommit(files, message, config);
1957
+ validateMessage(message2, config);
1958
+ await execCommit(files, message2, config);
1936
1959
  }
1937
1960
 
1938
1961
  // src/commands/coverage.ts
@@ -1970,12 +1993,12 @@ async function exitOnCancel(promise) {
1970
1993
  }
1971
1994
 
1972
1995
  // src/shared/promptMultiselect.ts
1973
- async function promptMultiselect(message, options2) {
1996
+ async function promptMultiselect(message2, options2) {
1974
1997
  const { selected } = await exitOnCancel(
1975
1998
  enquirer.prompt({
1976
1999
  type: "multiselect",
1977
2000
  name: "selected",
1978
- message,
2001
+ message: message2,
1979
2002
  choices: options2.map((opt) => ({
1980
2003
  name: opt.value,
1981
2004
  message: `${opt.name} - ${chalk4.dim(opt.description)}`
@@ -2217,12 +2240,12 @@ import chalk13 from "chalk";
2217
2240
 
2218
2241
  // src/shared/promptConfirm.ts
2219
2242
  import enquirer2 from "enquirer";
2220
- async function promptConfirm(message, initial = true) {
2243
+ async function promptConfirm(message2, initial = true) {
2221
2244
  const { confirmed } = await exitOnCancel(
2222
2245
  enquirer2.prompt({
2223
2246
  type: "confirm",
2224
2247
  name: "confirmed",
2225
- message,
2248
+ message: message2,
2226
2249
  initial,
2227
2250
  // @ts-expect-error - enquirer types don't include symbols but it's supported
2228
2251
  symbols: {
@@ -4532,13 +4555,13 @@ function getSnoreToastPath() {
4532
4555
  return path21.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
4533
4556
  }
4534
4557
  function showWindowsNotificationFromWsl(options2) {
4535
- const { title, message, sound } = options2;
4558
+ const { title, message: message2, sound } = options2;
4536
4559
  const snoreToastPath = getSnoreToastPath();
4537
4560
  try {
4538
4561
  fs16.chmodSync(snoreToastPath, 493);
4539
4562
  } catch {
4540
4563
  }
4541
- const args = ["-t", title, "-m", message];
4564
+ const args = ["-t", title, "-m", message2];
4542
4565
  if (sound) {
4543
4566
  args.push("-s", "ms-winsoundevent:Notification.Default");
4544
4567
  }
@@ -4552,14 +4575,14 @@ function showWindowsNotificationFromWsl(options2) {
4552
4575
 
4553
4576
  // src/commands/notify/showNotification/index.ts
4554
4577
  function showNotification(options2) {
4555
- const { title, message, sound } = options2;
4578
+ const { title, message: message2, sound } = options2;
4556
4579
  const platform = detectPlatform();
4557
4580
  if (platform === "wsl") {
4558
- return showWindowsNotificationFromWsl({ title, message, sound });
4581
+ return showWindowsNotificationFromWsl({ title, message: message2, sound });
4559
4582
  }
4560
4583
  const notificationOptions = {
4561
4584
  title,
4562
- message,
4585
+ message: message2,
4563
4586
  wait: false
4564
4587
  };
4565
4588
  if (platform === "windows") {
@@ -4581,7 +4604,7 @@ async function notify() {
4581
4604
  }
4582
4605
  const inputData = await readStdin();
4583
4606
  const data = JSON.parse(inputData);
4584
- const { notification_type, cwd, message } = data;
4607
+ const { notification_type, cwd, message: message2 } = data;
4585
4608
  const projectName = cwd?.split(/[/\\]/).pop() ?? "Unknown Project";
4586
4609
  let title;
4587
4610
  let body;
@@ -4589,7 +4612,7 @@ async function notify() {
4589
4612
  switch (notification_type) {
4590
4613
  case "permission_prompt":
4591
4614
  title = "Claude needs permission";
4592
- body = `${projectName} - ${message || "Permission required"}`;
4615
+ body = `${projectName} - ${message2 || "Permission required"}`;
4593
4616
  sound = "Alarm";
4594
4617
  break;
4595
4618
  case "idle_prompt":
@@ -4599,7 +4622,7 @@ async function notify() {
4599
4622
  break;
4600
4623
  default:
4601
4624
  title = "Claude Code";
4602
- body = message ? `${projectName} - ${message}` : projectName;
4625
+ body = message2 ? `${projectName} - ${message2}` : projectName;
4603
4626
  sound = "Default";
4604
4627
  }
4605
4628
  showNotification({ title, message: body, sound });
@@ -4853,8 +4876,8 @@ function readScheduleBlock(crontab) {
4853
4876
  }
4854
4877
 
4855
4878
  // src/commands/backup/scheduleBackup.ts
4856
- function fail(message) {
4857
- console.error(chalk29.red(message));
4879
+ function fail(message2) {
4880
+ console.error(chalk29.red(message2));
4858
4881
  process.exit(1);
4859
4882
  }
4860
4883
  async function scheduleBackup({
@@ -5371,8 +5394,8 @@ function buildArgs(prompt, options2) {
5371
5394
 
5372
5395
  // src/commands/sessions/daemon/appendDaemonLog.ts
5373
5396
  import { appendFileSync } from "fs";
5374
- function appendDaemonLog(message) {
5375
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`;
5397
+ function appendDaemonLog(message2) {
5398
+ const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`;
5376
5399
  try {
5377
5400
  appendFileSync(daemonPaths.log, `${line}
5378
5401
  `);
@@ -6330,10 +6353,10 @@ async function reconcileResumePhase(orm, item, startPhase, resumeSessionId) {
6330
6353
  if (resumedPhaseIdx === void 0 || resumedPhaseIdx === startPhase) {
6331
6354
  return startPhase;
6332
6355
  }
6333
- const message = `resume reconciliation: currentPhase implied phase ${startPhase + 1} but the resumed conversation ${resumeSessionId} ran phase ${resumedPhaseIdx + 1}; resuming phase ${resumedPhaseIdx + 1} to match the conversation`;
6334
- appendDaemonLog(`backlog run ${item.id}: ${message}`);
6356
+ const message2 = `resume reconciliation: currentPhase implied phase ${startPhase + 1} but the resumed conversation ${resumeSessionId} ran phase ${resumedPhaseIdx + 1}; resuming phase ${resumedPhaseIdx + 1} to match the conversation`;
6357
+ appendDaemonLog(`backlog run ${item.id}: ${message2}`);
6335
6358
  try {
6336
- await appendComment(orm, item.id, `Resume reconciled \u2014 ${message}`, {
6359
+ await appendComment(orm, item.id, `Resume reconciled \u2014 ${message2}`, {
6337
6360
  phase: resumedPhaseIdx + 1
6338
6361
  });
6339
6362
  } catch {
@@ -6396,10 +6419,10 @@ async function awaitClaude(done2, context) {
6396
6419
  try {
6397
6420
  return await done2;
6398
6421
  } catch (error) {
6399
- const message = error instanceof Error ? error.message : String(error);
6422
+ const message2 = error instanceof Error ? error.message : String(error);
6400
6423
  console.error(
6401
6424
  chalk39.red(`
6402
- Failed to launch Claude for ${context}: ${message}`)
6425
+ Failed to launch Claude for ${context}: ${message2}`)
6403
6426
  );
6404
6427
  return CLAUDE_SPAWN_FAILED;
6405
6428
  }
@@ -6425,7 +6448,7 @@ async function isDaemonRunning() {
6425
6448
 
6426
6449
  // src/commands/sessions/daemon/sendToDaemon.ts
6427
6450
  var WRITE_TIMEOUT_MS = 500;
6428
- function sendToDaemon(message) {
6451
+ function sendToDaemon(message2) {
6429
6452
  return new Promise((resolve19, reject) => {
6430
6453
  connectToDaemon().then((socket) => {
6431
6454
  const timer = setTimeout(() => {
@@ -6436,7 +6459,7 @@ function sendToDaemon(message) {
6436
6459
  clearTimeout(timer);
6437
6460
  reject(error);
6438
6461
  });
6439
- socket.write(`${JSON.stringify(message)}
6462
+ socket.write(`${JSON.stringify(message2)}
6440
6463
  `, () => {
6441
6464
  clearTimeout(timer);
6442
6465
  socket.end();
@@ -6462,7 +6485,7 @@ function readSocketLines(socket, onLine) {
6462
6485
 
6463
6486
  // src/commands/sessions/daemon/sendToDaemonAwaitAck.ts
6464
6487
  var ACK_TIMEOUT_MS = 1e3;
6465
- function sendToDaemonAwaitAck(message) {
6488
+ function sendToDaemonAwaitAck(message2) {
6466
6489
  return new Promise((resolve19, reject) => {
6467
6490
  connectToDaemon().then((socket) => {
6468
6491
  let settled = false;
@@ -6486,7 +6509,7 @@ function sendToDaemonAwaitAck(message) {
6486
6509
  "close",
6487
6510
  () => finish(new Error("daemon closed before acknowledging"))
6488
6511
  );
6489
- socket.write(`${JSON.stringify(message)}
6512
+ socket.write(`${JSON.stringify(message2)}
6490
6513
  `);
6491
6514
  }, reject);
6492
6515
  });
@@ -7223,13 +7246,13 @@ async function findSessionJsonlPath(sessionId) {
7223
7246
  // src/commands/backlog/verifyResumeConversation.ts
7224
7247
  async function verifyResumeConversation(itemId2, resumeSessionId, phaseLabel2) {
7225
7248
  if (await findSessionJsonlPath(resumeSessionId)) return true;
7226
- const message = `${phaseLabel2}: resume found no conversation for session ${resumeSessionId}; phase not advanced`;
7249
+ const message2 = `${phaseLabel2}: resume found no conversation for session ${resumeSessionId}; phase not advanced`;
7227
7250
  console.error(chalk42.red(`
7228
- ${message}`));
7229
- appendDaemonLog(`backlog run ${itemId2}: ${message}`);
7251
+ ${message2}`));
7252
+ appendDaemonLog(`backlog run ${itemId2}: ${message2}`);
7230
7253
  try {
7231
7254
  const { orm } = await getReady();
7232
- await appendComment(orm, itemId2, `Resume failed \u2014 ${message}`);
7255
+ await appendComment(orm, itemId2, `Resume failed \u2014 ${message2}`);
7233
7256
  } catch {
7234
7257
  }
7235
7258
  return false;
@@ -9569,8 +9592,8 @@ async function runRestartItem(item, { runRestartDaemon, reExec }) {
9569
9592
  reExec();
9570
9593
  }
9571
9594
  } catch (error) {
9572
- const message = error instanceof Error ? error.message : String(error);
9573
- console.error(chalk57.red(`Restart failed: ${message}`));
9595
+ const message2 = error instanceof Error ? error.message : String(error);
9596
+ console.error(chalk57.red(`Restart failed: ${message2}`));
9574
9597
  }
9575
9598
  }
9576
9599
 
@@ -10120,8 +10143,8 @@ function originToSshUrl(origin) {
10120
10143
  }
10121
10144
 
10122
10145
  // src/commands/backlog/cloneRepo.ts
10123
- function fail2(message) {
10124
- console.log(chalk66.red(message));
10146
+ function fail2(message2) {
10147
+ console.log(chalk66.red(message2));
10125
10148
  process.exitCode = 1;
10126
10149
  }
10127
10150
  async function cloneRepo(originRaw) {
@@ -10777,8 +10800,8 @@ function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
10777
10800
  }
10778
10801
 
10779
10802
  // src/commands/backlog/link.ts
10780
- function fail3(message) {
10781
- console.log(chalk78.red(message));
10803
+ function fail3(message2) {
10804
+ console.log(chalk78.red(message2));
10782
10805
  return void 0;
10783
10806
  }
10784
10807
  function parseLinkType(type) {
@@ -10900,8 +10923,8 @@ Pass the full origin.`
10900
10923
  }
10901
10924
 
10902
10925
  // src/commands/backlog/move-repo/index.ts
10903
- function fail4(message) {
10904
- console.log(chalk81.red(message));
10926
+ function fail4(message2) {
10927
+ console.log(chalk81.red(message2));
10905
10928
  process.exitCode = 1;
10906
10929
  }
10907
10930
  async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
@@ -11056,11 +11079,11 @@ async function handleLaunchSignal(slashCommand, once) {
11056
11079
  await next({ allowEdits: true, once });
11057
11080
  }
11058
11081
  } catch (error) {
11059
- const message = error instanceof Error ? error.message : String(error);
11082
+ const message2 = error instanceof Error ? error.message : String(error);
11060
11083
  console.error(
11061
11084
  chalk83.yellow(
11062
11085
  `
11063
- Could not complete post-run step (${message}).
11086
+ Could not complete post-run step (${message2}).
11064
11087
  This is usually a transient database/network blip \u2014 the work is saved and the session is safe to resume.`
11065
11088
  )
11066
11089
  );
@@ -13168,15 +13191,15 @@ function loadDenyConfig(global) {
13168
13191
  }
13169
13192
 
13170
13193
  // src/commands/deny/denyAdd.ts
13171
- function denyAdd(pattern2, message, options2) {
13194
+ function denyAdd(pattern2, message2, options2) {
13172
13195
  const { deny, saveDeny } = loadDenyConfig(options2.global);
13173
13196
  if (deny.some((r) => r.pattern === pattern2)) {
13174
13197
  console.log(chalk110.yellow(`Deny rule already exists for: ${pattern2}`));
13175
13198
  return;
13176
13199
  }
13177
- deny.push({ pattern: pattern2, message });
13200
+ deny.push({ pattern: pattern2, message: message2 });
13178
13201
  saveDeny(deny);
13179
- console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${message}`));
13202
+ console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${message2}`));
13180
13203
  }
13181
13204
 
13182
13205
  // src/commands/deny/denyList.ts
@@ -14350,8 +14373,10 @@ function resolveRepoTarget(key, value, repo) {
14350
14373
 
14351
14374
  // src/commands/config/configSet.ts
14352
14375
  function configSet(key, value, options2 = {}) {
14353
- if (options2.repo !== void 0 && options2.global) {
14354
- console.error(chalk127.red("Use either --repo or --global, not both"));
14376
+ if (options2.repo !== void 0 && !options2.global) {
14377
+ console.error(
14378
+ chalk127.red("--repo writes to the global config; add -g (e.g. -g --repo)")
14379
+ );
14355
14380
  process.exit(1);
14356
14381
  }
14357
14382
  const resolved = resolveRepoTarget(key, value, options2.repo);
@@ -14376,7 +14401,7 @@ function registerConfig(program2) {
14376
14401
  const configCommand = program2.command("config").description("View and modify assist.yml configuration");
14377
14402
  configCommand.command("set <key> [value]").description("Set a config value (e.g. commit.push true)").option("-g, --global", "Write to global ~/.assist.yml").option(
14378
14403
  "-r, --repo [name]",
14379
- "Write to ~/.assist.yml under a repo's identity (defaults to the current repo)"
14404
+ "Requires -g: scope the global write to a repo's identity (defaults to the current repo)"
14380
14405
  ).action((key, value, options2) => configSet(key, value, options2));
14381
14406
  configCommand.command("get <key>").description("Get a config value").action(configGet);
14382
14407
  configCommand.command("list").description("List all config values").action(configList);
@@ -14733,14 +14758,14 @@ function parseGitLogCommits(output, ignore3, afterDate) {
14733
14758
  const commitsByDate = /* @__PURE__ */ new Map();
14734
14759
  for (const line of lines) {
14735
14760
  const [date, hash, ...messageParts] = line.split("|");
14736
- const message = messageParts.join("|");
14761
+ const message2 = messageParts.join("|");
14737
14762
  if (afterDate && date <= afterDate) {
14738
14763
  continue;
14739
14764
  }
14740
14765
  const files = getCommitFiles(hash);
14741
14766
  if (!shouldIgnoreCommit(files, ignore3)) {
14742
14767
  const existing = commitsByDate.get(date) || [];
14743
- existing.push({ date, hash, message, files });
14768
+ existing.push({ date, hash, message: message2, files });
14744
14769
  commitsByDate.set(date, existing);
14745
14770
  }
14746
14771
  }
@@ -16353,10 +16378,10 @@ function advisory(count7) {
16353
16378
  const noun = count7 === 1 ? "handover" : "handovers";
16354
16379
  return `${count7} unrecalled ${noun} for this repo. Run /recall to load.`;
16355
16380
  }
16356
- function emit2(message) {
16381
+ function emit2(message2) {
16357
16382
  const json = JSON.stringify({
16358
16383
  hookSpecificOutput: { hookEventName: "SessionStart" },
16359
- systemMessage: message
16384
+ systemMessage: message2
16360
16385
  });
16361
16386
  console.log(json);
16362
16387
  return json;
@@ -16540,11 +16565,11 @@ import { execSync as execSync39 } from "child_process";
16540
16565
  // src/shared/promptInput.ts
16541
16566
  import Enquirer from "enquirer";
16542
16567
  var prompts = Enquirer;
16543
- async function promptInput(name, message, initial) {
16544
- return exitOnCancel(new prompts.Input({ name, message, initial }).run());
16568
+ async function promptInput(name, message2, initial) {
16569
+ return exitOnCancel(new prompts.Input({ name, message: message2, initial }).run());
16545
16570
  }
16546
- async function promptPassword(name, message) {
16547
- return exitOnCancel(new prompts.Password({ name, message }).run());
16571
+ async function promptPassword(name, message2) {
16572
+ return exitOnCancel(new prompts.Password({ name, message: message2 }).run());
16548
16573
  }
16549
16574
 
16550
16575
  // src/commands/jira/jiraAuth.ts
@@ -18021,9 +18046,9 @@ function deleteCommentsCache(prNumber) {
18021
18046
 
18022
18047
  // src/commands/prs/replyToComment.ts
18023
18048
  import { execSync as execSync41 } from "child_process";
18024
- function replyToComment(org, repo, prNumber, commentId, message) {
18049
+ function replyToComment(org, repo, prNumber, commentId, message2) {
18025
18050
  execSync41(
18026
- `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, String.raw`\"`)}" -F in_reply_to=${commentId}`,
18051
+ `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message2.replace(/"/g, String.raw`\"`)}" -F in_reply_to=${commentId}`,
18027
18052
  { stdio: ["inherit", "pipe", "inherit"] }
18028
18053
  );
18029
18054
  }
@@ -18071,12 +18096,12 @@ function cleanupCacheIfDone(cache2, prNumber, commentId) {
18071
18096
  );
18072
18097
  if (!hasRemaining) deleteCommentsCache(prNumber);
18073
18098
  }
18074
- function resolveCommentWithReply(commentId, message) {
18099
+ function resolveCommentWithReply(commentId, message2) {
18075
18100
  const prNumber = getCurrentPrNumber();
18076
18101
  const { org, repo } = getRepoInfo();
18077
18102
  const cache2 = requireCache(prNumber);
18078
18103
  const comment3 = requireLineComment(cache2, commentId);
18079
- replyToComment(org, repo, prNumber, commentId, message);
18104
+ replyToComment(org, repo, prNumber, commentId, message2);
18080
18105
  console.log("Reply posted successfully.");
18081
18106
  resolveThread(comment3.threadId);
18082
18107
  console.log("Thread resolved successfully.");
@@ -18099,9 +18124,9 @@ function fixed(commentId, sha) {
18099
18124
  const fullSha = verifySha(sha);
18100
18125
  const { org, repo } = getRepoInfo();
18101
18126
  const repoUrl = `https://github.com/${org}/${repo}`;
18102
- const message = `Fixed in [${fullSha}](${repoUrl}/commit/${fullSha})`;
18127
+ const message2 = `Fixed in [${fullSha}](${repoUrl}/commit/${fullSha})`;
18103
18128
  execSync43("git push", { stdio: "inherit" });
18104
- resolveCommentWithReply(commentId, message);
18129
+ resolveCommentWithReply(commentId, message2);
18105
18130
  } catch (error) {
18106
18131
  if (isGhNotInstalled(error)) {
18107
18132
  console.error("Error: GitHub CLI (gh) is not installed.");
@@ -19018,9 +19043,9 @@ function getItemFields(itemId2) {
19018
19043
 
19019
19044
  // src/commands/ravendb/selectOpSecret.ts
19020
19045
  var { Input, Select } = Enquirer2;
19021
- async function selectOne(message, choices) {
19046
+ async function selectOne(message2, choices) {
19022
19047
  if (choices.length === 1) return choices[0].value;
19023
- const selected = await new Select({ name: "choice", message, choices }).run();
19048
+ const selected = await new Select({ name: "choice", message: message2, choices }).run();
19024
19049
  return choices.find((c) => c.name === selected)?.value ?? selected;
19025
19050
  }
19026
19051
  async function selectOpSecret(searchTerm) {
@@ -20735,8 +20760,8 @@ function findRootParent(file, importedBy, visited) {
20735
20760
  function clusterFiles(graph) {
20736
20761
  const clusters = /* @__PURE__ */ new Map();
20737
20762
  for (const file of graph.files) {
20738
- const basename14 = path52.basename(file, path52.extname(file));
20739
- if (basename14 === "index") continue;
20763
+ const basename17 = path52.basename(file, path52.extname(file));
20764
+ if (basename17 === "index") continue;
20740
20765
  const importers = graph.importedBy.get(file);
20741
20766
  if (!importers || importers.size !== 1) continue;
20742
20767
  const parent = [...importers][0];
@@ -21470,9 +21495,9 @@ function postFindings(findings) {
21470
21495
  posted++;
21471
21496
  } catch (error) {
21472
21497
  failed2++;
21473
- const message = error instanceof Error ? error.message : String(error);
21498
+ const message2 = error instanceof Error ? error.message : String(error);
21474
21499
  console.error(
21475
- `Failed to post comment on ${finding.file}:${finding.line}: ${message}`
21500
+ `Failed to post comment on ${finding.file}:${finding.line}: ${message2}`
21476
21501
  );
21477
21502
  }
21478
21503
  }
@@ -21491,8 +21516,8 @@ function submitPendingReview(body) {
21491
21516
  console.error("Error: GitHub CLI (gh) is not installed.");
21492
21517
  return;
21493
21518
  }
21494
- const message = error instanceof Error ? error.message : String(error);
21495
- console.error(`Failed to submit review: ${message}`);
21519
+ const message2 = error instanceof Error ? error.message : String(error);
21520
+ console.error(`Failed to submit review: ${message2}`);
21496
21521
  }
21497
21522
  }
21498
21523
 
@@ -22283,10 +22308,10 @@ function messageFor(err, command) {
22283
22308
  return err.message || String(err);
22284
22309
  }
22285
22310
  function handleSpawnError(ctx, err) {
22286
- const message = messageFor(err, ctx.command);
22311
+ const message2 = messageFor(err, ctx.command);
22287
22312
  const stderr = ctx.stderr ? `${ctx.stderr}
22288
- ${message}` : message;
22289
- if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${message}`);
22313
+ ${message2}` : message2;
22314
+ if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${message2}`);
22290
22315
  return {
22291
22316
  exitCode: 127,
22292
22317
  stderr,
@@ -25150,8 +25175,8 @@ import { createInterface as createInterface6 } from "readline";
25150
25175
  var RING_CAPACITY = 1e3;
25151
25176
  var ring = [];
25152
25177
  var sink;
25153
- function daemonLog(message) {
25154
- emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
25178
+ function daemonLog(message2) {
25179
+ emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`);
25155
25180
  }
25156
25181
  function relayDaemonLog(line) {
25157
25182
  emit3(`[windows] ${line}`);
@@ -25177,7 +25202,7 @@ var persistedSessionSchema = z5.object({
25177
25202
  subtitle: z5.string().optional(),
25178
25203
  commandType: z5.enum(["claude", "run", "assist"]),
25179
25204
  harness: z5.enum(["claude", "codex", "pi"]).optional(),
25180
- status: z5.enum(["running", "waiting", "done", "error"]).optional(),
25205
+ status: z5.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
25181
25206
  cwd: z5.string(),
25182
25207
  startedAt: z5.number(),
25183
25208
  runningMs: z5.number().optional(),
@@ -25187,7 +25212,8 @@ var persistedSessionSchema = z5.object({
25187
25212
  runArgs: z5.array(z5.string()).optional(),
25188
25213
  assistArgs: z5.array(z5.string()).optional(),
25189
25214
  activity: activitySchema.optional(),
25190
- starred: z5.boolean().optional()
25215
+ starred: z5.boolean().optional(),
25216
+ undurable: z5.object({ reason: z5.string() }).optional()
25191
25217
  });
25192
25218
 
25193
25219
  // src/commands/sessions/daemon/toPersistedSession.ts
@@ -25208,7 +25234,8 @@ function toPersistedSession(session) {
25208
25234
  runArgs: session.runArgs,
25209
25235
  assistArgs: session.assistArgs,
25210
25236
  activity: session.activity,
25211
- starred: session.starred
25237
+ starred: session.starred,
25238
+ undurable: session.undurable
25212
25239
  };
25213
25240
  }
25214
25241
  function accumulatedRunningMs(session) {
@@ -25230,7 +25257,7 @@ function savePersistedSessions(sessions) {
25230
25257
  }
25231
25258
  function persistLiveSessions(sessions) {
25232
25259
  const live = [...sessions.values()].filter(
25233
- (s) => s.pty && s.status !== "done"
25260
+ (s) => (s.pty || s.status === "stopped") && s.status !== "done"
25234
25261
  );
25235
25262
  savePersistedSessions(live.map(toPersistedSession));
25236
25263
  logPersist(live);
@@ -25282,7 +25309,7 @@ function requestDrain(socket) {
25282
25309
  }
25283
25310
 
25284
25311
  // src/commands/sessions/daemon/runDaemon.ts
25285
- import { mkdirSync as mkdirSync26 } from "fs";
25312
+ import { mkdirSync as mkdirSync27 } from "fs";
25286
25313
 
25287
25314
  // src/commands/sessions/daemon/createAutoExit.ts
25288
25315
  var DEFAULT_GRACE_MS = 6e4;
@@ -25385,7 +25412,8 @@ function toSessionInfo({
25385
25412
  starred,
25386
25413
  usedPct,
25387
25414
  design,
25388
- pendingPrPreview
25415
+ pendingPrPreview,
25416
+ undurable
25389
25417
  }) {
25390
25418
  return {
25391
25419
  id,
@@ -25413,7 +25441,8 @@ function toSessionInfo({
25413
25441
  starred,
25414
25442
  usedPct,
25415
25443
  design,
25416
- pendingPrPreview
25444
+ pendingPrPreview,
25445
+ undurable
25417
25446
  };
25418
25447
  }
25419
25448
 
@@ -25503,20 +25532,56 @@ var ClientHub = class extends Set {
25503
25532
  };
25504
25533
  };
25505
25534
 
25535
+ // src/commands/sessions/daemon/createSession.ts
25536
+ import { randomUUID as randomUUID5 } from "crypto";
25537
+
25538
+ // src/commands/sessions/daemon/resolveRunConfig.ts
25539
+ function resolveRunConfig(name, cwd) {
25540
+ try {
25541
+ const { run: run4 } = loadConfigFrom(cwd);
25542
+ const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
25543
+ const exact = configs.find((r) => r.name === name);
25544
+ if (exact) return exact;
25545
+ const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
25546
+ return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
25547
+ } catch {
25548
+ return void 0;
25549
+ }
25550
+ }
25551
+
25552
+ // src/commands/sessions/daemon/serverRunMeta.ts
25553
+ function serverRunMeta(runName, cwd) {
25554
+ const dir = cwd ?? process.cwd();
25555
+ const config = resolveRunConfig(runName, dir);
25556
+ if (!config?.server) return { server: false };
25557
+ return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
25558
+ }
25559
+
25560
+ // src/commands/sessions/daemon/readDesignSystemPrompt.ts
25561
+ import * as fs35 from "fs";
25562
+ import * as path58 from "path";
25563
+ import { fileURLToPath as fileURLToPath8 } from "url";
25564
+ var __filename4 = fileURLToPath8(import.meta.url);
25565
+ var __dirname6 = path58.dirname(__filename4);
25566
+ function readDesignSystemPrompt() {
25567
+ const promptPath = path58.join(__dirname6, "..", "claude", "system-prompt.md");
25568
+ return fs35.readFileSync(promptPath, "utf8");
25569
+ }
25570
+
25506
25571
  // src/commands/sessions/daemon/spawnPty.ts
25507
25572
  import * as pty from "node-pty";
25508
25573
 
25509
25574
  // src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
25510
25575
  import { chmodSync, existsSync as existsSync57, statSync as statSync9 } from "fs";
25511
25576
  import { createRequire as createRequire3 } from "module";
25512
- import path58 from "path";
25577
+ import path59 from "path";
25513
25578
  var require4 = createRequire3(import.meta.url);
25514
25579
  var ensured = false;
25515
25580
  function ensureSpawnHelperExecutable() {
25516
25581
  if (ensured || process.platform !== "darwin") return;
25517
25582
  ensured = true;
25518
- const ptyRoot = path58.join(path58.dirname(require4.resolve("node-pty")), "..");
25519
- const helper = path58.join(
25583
+ const ptyRoot = path59.join(path59.dirname(require4.resolve("node-pty")), "..");
25584
+ const helper = path59.join(
25520
25585
  ptyRoot,
25521
25586
  "prebuilds",
25522
25587
  `${process.platform}-${process.arch}`,
@@ -25553,62 +25618,6 @@ function shellEscape(s) {
25553
25618
  return `'${s.replace(/'/g, String.raw`'\''`)}'`;
25554
25619
  }
25555
25620
 
25556
- // src/commands/sessions/daemon/createAssistSession.ts
25557
- function createAssistSession(id, assistArgs, cwd, meta) {
25558
- const startedAt = Date.now();
25559
- return {
25560
- id,
25561
- name: `assist ${assistArgs.join(" ")}`,
25562
- title: meta?.title,
25563
- subtitle: meta?.subtitle,
25564
- commandType: "assist",
25565
- status: "running",
25566
- startedAt,
25567
- runningMs: 0,
25568
- runningSince: startedAt,
25569
- pty: spawnPty(["assist", ...assistArgs], cwd, id),
25570
- scrollback: "",
25571
- assistArgs,
25572
- cwd
25573
- };
25574
- }
25575
-
25576
- // src/commands/sessions/daemon/createSession.ts
25577
- import { randomUUID as randomUUID5 } from "crypto";
25578
-
25579
- // src/commands/sessions/daemon/resolveRunConfig.ts
25580
- function resolveRunConfig(name, cwd) {
25581
- try {
25582
- const { run: run4 } = loadConfigFrom(cwd);
25583
- const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
25584
- const exact = configs.find((r) => r.name === name);
25585
- if (exact) return exact;
25586
- const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
25587
- return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
25588
- } catch {
25589
- return void 0;
25590
- }
25591
- }
25592
-
25593
- // src/commands/sessions/daemon/serverRunMeta.ts
25594
- function serverRunMeta(runName, cwd) {
25595
- const dir = cwd ?? process.cwd();
25596
- const config = resolveRunConfig(runName, dir);
25597
- if (!config?.server) return { server: false };
25598
- return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
25599
- }
25600
-
25601
- // src/commands/sessions/daemon/readDesignSystemPrompt.ts
25602
- import * as fs35 from "fs";
25603
- import * as path59 from "path";
25604
- import { fileURLToPath as fileURLToPath8 } from "url";
25605
- var __filename4 = fileURLToPath8(import.meta.url);
25606
- var __dirname6 = path59.dirname(__filename4);
25607
- function readDesignSystemPrompt() {
25608
- const promptPath = path59.join(__dirname6, "..", "claude", "system-prompt.md");
25609
- return fs35.readFileSync(promptPath, "utf8");
25610
- }
25611
-
25612
25621
  // src/commands/sessions/daemon/spawnClaude.ts
25613
25622
  function spawnClaude2(opts = {}) {
25614
25623
  return spawnPty(buildArgs3(opts), opts.cwd, opts.sessionId);
@@ -25693,6 +25702,170 @@ function createRunSession(id, runName, runArgs, cwd, meta = serverRunMeta(runNam
25693
25702
  };
25694
25703
  }
25695
25704
 
25705
+ // src/commands/sessions/daemon/worktree/reapWorktree.ts
25706
+ import { existsSync as existsSync58 } from "fs";
25707
+
25708
+ // src/commands/sessions/daemon/worktree/git.ts
25709
+ import { execFile as execFile7, execFileSync as execFileSync12 } from "child_process";
25710
+ function isWindowsPath(cwd) {
25711
+ return /^[A-Za-z]:[\\/]/.test(cwd);
25712
+ }
25713
+ function gitInvocation(cwd, args) {
25714
+ const windows = isWindowsPath(cwd);
25715
+ return {
25716
+ file: windows ? "git.exe" : "git",
25717
+ argv: windows ? ["-C", cwd, ...args] : args,
25718
+ options: { encoding: "utf8", ...windows ? {} : { cwd } }
25719
+ };
25720
+ }
25721
+ function git3(cwd, args) {
25722
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
25723
+ return new Promise((resolve19, reject) => {
25724
+ execFile7(file, argv, options2, (error, stdout) => {
25725
+ if (error) reject(error);
25726
+ else resolve19(stdout.toString());
25727
+ });
25728
+ });
25729
+ }
25730
+ async function gitOrNull(cwd, args) {
25731
+ try {
25732
+ return (await git3(cwd, args)).trim() || null;
25733
+ } catch {
25734
+ return null;
25735
+ }
25736
+ }
25737
+ function gitSync(cwd, args) {
25738
+ const { file, argv, options: options2 } = gitInvocation(cwd, args);
25739
+ return execFileSync12(file, argv, {
25740
+ ...options2,
25741
+ stdio: ["pipe", "pipe", "pipe"]
25742
+ }).toString();
25743
+ }
25744
+ function gitSyncOrNull(cwd, args) {
25745
+ try {
25746
+ return gitSync(cwd, args).trim() || null;
25747
+ } catch {
25748
+ return null;
25749
+ }
25750
+ }
25751
+ function listWorktreePaths(cwd) {
25752
+ const out = gitSyncOrNull(cwd, ["worktree", "list", "--porcelain"]);
25753
+ if (!out) return [];
25754
+ return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
25755
+ }
25756
+ function mainWorktree(cwd) {
25757
+ return listWorktreePaths(cwd)[0] ?? null;
25758
+ }
25759
+ function listLocalBranches(cwd) {
25760
+ const out = gitSyncOrNull(cwd, [
25761
+ "for-each-ref",
25762
+ "--format=%(refname:short)",
25763
+ "refs/heads"
25764
+ ]);
25765
+ return out ? out.split("\n").map((line) => line.trim()) : [];
25766
+ }
25767
+ function gitCommonDir(cwd) {
25768
+ return gitSyncOrNull(cwd, [
25769
+ "rev-parse",
25770
+ "--path-format=absolute",
25771
+ "--git-common-dir"
25772
+ ]);
25773
+ }
25774
+
25775
+ // src/commands/sessions/daemon/worktree/treeDurability.ts
25776
+ function treeDurability(state) {
25777
+ if (state.dirty) return { durable: false, reason: "uncommitted changes" };
25778
+ if (!state.push && state.localOnlyCommits)
25779
+ return { durable: false, reason: "unpushed commits" };
25780
+ return { durable: true };
25781
+ }
25782
+ async function hasLocalOnlyCommits(cwd) {
25783
+ const upstream = await gitOrNull(cwd, [
25784
+ "rev-parse",
25785
+ "--abbrev-ref",
25786
+ "--symbolic-full-name",
25787
+ "@{upstream}"
25788
+ ]);
25789
+ if (upstream) {
25790
+ const ahead = await gitOrNull(cwd, [
25791
+ "rev-list",
25792
+ "--count",
25793
+ "@{upstream}..HEAD"
25794
+ ]);
25795
+ return Number(ahead ?? "0") > 0;
25796
+ }
25797
+ const remoteBranches = await gitOrNull(cwd, [
25798
+ "branch",
25799
+ "-r",
25800
+ "--contains",
25801
+ "HEAD"
25802
+ ]);
25803
+ return !remoteBranches;
25804
+ }
25805
+ async function collectTreeState(cwd, push) {
25806
+ const status3 = await gitOrNull(cwd, ["status", "--porcelain"]);
25807
+ return {
25808
+ dirty: status3 !== null,
25809
+ push,
25810
+ localOnlyCommits: await hasLocalOnlyCommits(cwd)
25811
+ };
25812
+ }
25813
+ function pushOnCommit(cwd) {
25814
+ try {
25815
+ return loadConfigFrom(cwd).commit.push;
25816
+ } catch {
25817
+ return false;
25818
+ }
25819
+ }
25820
+ async function checkDurability(cwd) {
25821
+ return treeDurability(await collectTreeState(cwd, pushOnCommit(cwd)));
25822
+ }
25823
+
25824
+ // src/commands/sessions/daemon/worktree/reapWorktree.ts
25825
+ async function reapWorktree(worktreePath, force = false) {
25826
+ if (!existsSync58(worktreePath)) {
25827
+ daemonLog(`worktree ${worktreePath} already gone; skipping reap`);
25828
+ return false;
25829
+ }
25830
+ if (!force) {
25831
+ const durability = await checkDurability(worktreePath);
25832
+ if (!durability.durable) {
25833
+ daemonLog(`worktree ${worktreePath} not reaped: ${durability.reason}`);
25834
+ return false;
25835
+ }
25836
+ }
25837
+ const clone = mainWorktree(worktreePath) ?? worktreePath;
25838
+ const branch2 = await gitOrNull(worktreePath, [
25839
+ "symbolic-ref",
25840
+ "--short",
25841
+ "HEAD"
25842
+ ]);
25843
+ try {
25844
+ await git3(clone, [
25845
+ "worktree",
25846
+ "remove",
25847
+ ...force ? ["--force"] : [],
25848
+ worktreePath
25849
+ ]);
25850
+ if (branch2) await deleteBranch(clone, branch2);
25851
+ daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
25852
+ return true;
25853
+ } catch (error) {
25854
+ daemonLog(
25855
+ `worktree ${worktreePath} reap failed: ${error instanceof Error ? error.message : String(error)}`
25856
+ );
25857
+ return false;
25858
+ }
25859
+ }
25860
+ async function deleteBranch(clone, branch2) {
25861
+ const current = await gitOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
25862
+ if (branch2 === current) return;
25863
+ try {
25864
+ await git3(clone, ["branch", "-D", branch2]);
25865
+ } catch {
25866
+ }
25867
+ }
25868
+
25696
25869
  // src/commands/sessions/daemon/dismissSession.ts
25697
25870
  function dismissSession(sessions, id) {
25698
25871
  const s = sessions.get(id);
@@ -25700,10 +25873,12 @@ function dismissSession(sessions, id) {
25700
25873
  if (s.status !== "done") s.pty?.kill();
25701
25874
  s.activityWatcher?.close();
25702
25875
  s.transcriptWatcher?.close();
25876
+ s.gitWatcher?.close();
25703
25877
  removeActivity(s.id);
25704
25878
  if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
25705
25879
  sessions.delete(id);
25706
25880
  daemonLog(`session ${id} dismissed (${s.name})`);
25881
+ if (s.worktree) void reapWorktree(s.worktree.path);
25707
25882
  return true;
25708
25883
  }
25709
25884
  function drainSessions(sessions, onDrained) {
@@ -25717,6 +25892,113 @@ function drainSessions(sessions, onDrained) {
25717
25892
  return ids.length;
25718
25893
  }
25719
25894
 
25895
+ // src/commands/sessions/daemon/killPtyTree.ts
25896
+ function killPtyTree(pty2) {
25897
+ if (process.platform === "win32") {
25898
+ pty2.kill();
25899
+ return;
25900
+ }
25901
+ try {
25902
+ process.kill(-pty2.pid, "SIGHUP");
25903
+ } catch {
25904
+ try {
25905
+ pty2.kill();
25906
+ } catch {
25907
+ }
25908
+ }
25909
+ }
25910
+
25911
+ // src/commands/sessions/daemon/setStatus.ts
25912
+ function setStatus2(session, newStatus) {
25913
+ const now = Date.now();
25914
+ if (session.status === "running" && session.runningSince != null) {
25915
+ session.runningMs += now - session.runningSince;
25916
+ }
25917
+ session.runningSince = newStatus === "running" ? now : null;
25918
+ session.status = newStatus;
25919
+ }
25920
+
25921
+ // src/commands/sessions/daemon/worktree/watchGitState.ts
25922
+ import { existsSync as existsSync59, watch } from "fs";
25923
+ var DEBOUNCE_MS = 500;
25924
+ function watchGitState(cwd, onChange) {
25925
+ const common = gitCommonDir(cwd);
25926
+ if (!common || !existsSync59(common)) return void 0;
25927
+ let timer = null;
25928
+ try {
25929
+ return watch(common, { recursive: true }, () => {
25930
+ if (timer) clearTimeout(timer);
25931
+ timer = setTimeout(onChange, DEBOUNCE_MS);
25932
+ });
25933
+ } catch (error) {
25934
+ daemonLog(
25935
+ `git-state watch failed for ${cwd}: ${error instanceof Error ? error.message : String(error)}`
25936
+ );
25937
+ return void 0;
25938
+ }
25939
+ }
25940
+
25941
+ // src/commands/sessions/daemon/worktree/resolveDoneDurability.ts
25942
+ async function resolveDoneDurability(session, finalize, notify2) {
25943
+ const worktree = session.worktree;
25944
+ if (!worktree) {
25945
+ finalize();
25946
+ return;
25947
+ }
25948
+ const durability = await checkDurability(worktree.path);
25949
+ if (!durability.durable) {
25950
+ holdStopped(session, durability.reason, finalize, notify2);
25951
+ return;
25952
+ }
25953
+ await reapWorktree(worktree.path);
25954
+ session.worktree = void 0;
25955
+ session.undurable = void 0;
25956
+ session.gitWatcher?.close();
25957
+ session.gitWatcher = void 0;
25958
+ finalize();
25959
+ }
25960
+ function holdStopped(session, reason, finalize, notify2) {
25961
+ if (session.undurable?.reason !== reason)
25962
+ daemonLog(`session ${session.id} stopped; reap blocked: ${reason}`);
25963
+ session.undurable = { reason };
25964
+ setStatus2(session, "stopped");
25965
+ if (!session.gitWatcher && session.worktree)
25966
+ session.gitWatcher = watchGitState(session.worktree.path, () => {
25967
+ if (session.status !== "stopped") return;
25968
+ void resolveDoneDurability(session, finalize, notify2);
25969
+ });
25970
+ notify2();
25971
+ }
25972
+
25973
+ // src/commands/sessions/daemon/dismissSessionGated.ts
25974
+ function dismissSessionGated(sessions, id, notify2, discard = false) {
25975
+ const s = sessions.get(id);
25976
+ if (!s?.worktree) {
25977
+ if (dismissSession(sessions, id)) notify2();
25978
+ return;
25979
+ }
25980
+ const removeCard = () => {
25981
+ if (dismissSession(sessions, id)) notify2();
25982
+ };
25983
+ const discardTree = async () => {
25984
+ if (s.worktree) {
25985
+ await reapWorktree(s.worktree.path, true);
25986
+ s.worktree = void 0;
25987
+ }
25988
+ removeCard();
25989
+ };
25990
+ if (s.pty) {
25991
+ s.pendingDismiss = discard ? () => void discardTree() : () => void resolveDoneDurability(s, removeCard, notify2);
25992
+ daemonLog(
25993
+ `session ${id} ${discard ? "discard" : "dismiss"} requested: killing process tree first`
25994
+ );
25995
+ killPtyTree(s.pty);
25996
+ return;
25997
+ }
25998
+ if (discard) void discardTree();
25999
+ else void resolveDoneDurability(s, removeCard, notify2);
26000
+ }
26001
+
25720
26002
  // src/shared/db/recordPhaseActiveMs.ts
25721
26003
  import { sql as sql20 } from "drizzle-orm";
25722
26004
  async function recordPhaseActiveMs(db, itemId2, phaseIdx, activeMs) {
@@ -25897,6 +26179,12 @@ function applySetStatus(sessions, id, status3, source, onStatusChange) {
25897
26179
  );
25898
26180
  return;
25899
26181
  }
26182
+ if (session.status === "stopped") {
26183
+ daemonLog(
26184
+ `set-status for stopped session id=${id} status=${status3} (held pending durable close; ignoring)`
26185
+ );
26186
+ return;
26187
+ }
25900
26188
  if (source === "permission") session.permissionActive = true;
25901
26189
  if (status3 === "running") session.permissionActive = false;
25902
26190
  onStatusChange(session, status3);
@@ -26070,16 +26358,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
26070
26358
  notify2();
26071
26359
  }
26072
26360
 
26073
- // src/commands/sessions/daemon/setStatus.ts
26074
- function setStatus2(session, newStatus) {
26075
- const now = Date.now();
26076
- if (session.status === "running" && session.runningSince != null) {
26077
- session.runningMs += now - session.runningSince;
26078
- }
26079
- session.runningSince = newStatus === "running" ? now : null;
26080
- session.status = newStatus;
26081
- }
26082
-
26083
26361
  // src/commands/sessions/daemon/shouldAutoDismiss.ts
26084
26362
  function shouldAutoDismiss(session, exitCode) {
26085
26363
  if (session.status !== "done" || exitCode !== 0) {
@@ -26125,13 +26403,26 @@ function shouldAutoRun(session) {
26125
26403
  // src/commands/sessions/daemon/applyStatusChange.ts
26126
26404
  function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
26127
26405
  if (session.status === status3) return;
26406
+ const deps2 = { dismiss, notify: notify2, reuseForRun };
26407
+ if (status3 === "done" && session.worktree) {
26408
+ void resolveDoneDurability(
26409
+ session,
26410
+ () => finishStatusChange(session, "done", exitCode, deps2),
26411
+ notify2
26412
+ );
26413
+ return;
26414
+ }
26415
+ finishStatusChange(session, status3, exitCode, deps2);
26416
+ }
26417
+ function finishStatusChange(session, status3, exitCode, deps2) {
26128
26418
  daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
26129
26419
  void flushPhaseActiveMs(session);
26130
26420
  setStatus2(session, status3);
26421
+ if (status3 !== "waiting") session.undurable = void 0;
26131
26422
  const autoRun = shouldAutoRun(session);
26132
26423
  if (autoRun.run) {
26133
- reuseForRun(session, autoRun.itemId);
26134
- notify2();
26424
+ deps2.reuseForRun(session, autoRun.itemId);
26425
+ deps2.notify();
26135
26426
  return;
26136
26427
  }
26137
26428
  if (status3 === "done" && autoRun.reason != null) {
@@ -26139,8 +26430,8 @@ function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseFo
26139
26430
  `session ${session.id} auto-run enabled but skipped (exit ${exitCode ?? "none"}): ${autoRun.reason}`
26140
26431
  );
26141
26432
  }
26142
- if (shouldAutoDismiss(session, exitCode)) dismiss(session.id);
26143
- else notify2();
26433
+ if (shouldAutoDismiss(session, exitCode)) deps2.dismiss(session.id);
26434
+ else deps2.notify();
26144
26435
  }
26145
26436
 
26146
26437
  // src/commands/sessions/daemon/makeStatusChangeHandler.ts
@@ -26161,7 +26452,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
26161
26452
  }
26162
26453
 
26163
26454
  // src/commands/sessions/daemon/watchActivity.ts
26164
- import { existsSync as existsSync58, mkdirSync as mkdirSync25, watch } from "fs";
26455
+ import { existsSync as existsSync60, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
26165
26456
  import { dirname as dirname29 } from "path";
26166
26457
 
26167
26458
  // src/commands/sessions/daemon/applyReviewPause.ts
@@ -26212,7 +26503,7 @@ async function seedRunningMsFromUsage(session, notify2) {
26212
26503
  }
26213
26504
 
26214
26505
  // src/commands/sessions/daemon/watchActivity.ts
26215
- var DEBOUNCE_MS = 50;
26506
+ var DEBOUNCE_MS2 = 50;
26216
26507
  function watchActivity(session, notify2, onClaudeSessionId) {
26217
26508
  if (session.commandType !== "assist" || !session.cwd) return;
26218
26509
  const path71 = activityPath(session.id);
@@ -26237,12 +26528,12 @@ function watchActivity(session, notify2, onClaudeSessionId) {
26237
26528
  void seedRunningMsFromUsage(session, notify2);
26238
26529
  notify2();
26239
26530
  };
26240
- session.activityWatcher = watch(dir, (_event, filename) => {
26531
+ session.activityWatcher = watch2(dir, (_event, filename) => {
26241
26532
  if (filename && !path71.endsWith(filename)) return;
26242
26533
  if (timer) clearTimeout(timer);
26243
- timer = setTimeout(read, DEBOUNCE_MS);
26534
+ timer = setTimeout(read, DEBOUNCE_MS2);
26244
26535
  });
26245
- if (existsSync58(path71)) read();
26536
+ if (existsSync60(path71)) read();
26246
26537
  }
26247
26538
  function refreshActivity(session) {
26248
26539
  if (session.commandType !== "assist" || !session.cwd) return;
@@ -26277,6 +26568,15 @@ function handlePtyExit(session, exitCode, onStatusChange) {
26277
26568
  return;
26278
26569
  }
26279
26570
  session.pty = null;
26571
+ if (session.pendingDismiss) {
26572
+ const dismiss = session.pendingDismiss;
26573
+ session.pendingDismiss = void 0;
26574
+ daemonLog(
26575
+ `session ${session.id} ("${session.name}") pty exited (code ${exitCode}) for dismiss; resolving durability now the process is gone`
26576
+ );
26577
+ dismiss();
26578
+ return;
26579
+ }
26280
26580
  if (handleStoppedExit(session, exitCode, onStatusChange)) return;
26281
26581
  refreshActivity(session);
26282
26582
  if (handleFailedResume(session, exitCode, onStatusChange)) return;
@@ -26317,6 +26617,10 @@ function wirePtyEvents(session, clients, onStatusChange) {
26317
26617
 
26318
26618
  // src/commands/sessions/daemon/respawnSession.ts
26319
26619
  function respawnSession(session, respawn, status3, clients, onStatusChange) {
26620
+ session.gitWatcher?.close();
26621
+ session.gitWatcher = void 0;
26622
+ session.undurable = void 0;
26623
+ session.pendingDismiss = void 0;
26320
26624
  session.scrollback = "";
26321
26625
  session.startedAt = Date.now();
26322
26626
  session.runningMs = 0;
@@ -26324,6 +26628,11 @@ function respawnSession(session, respawn, status3, clients, onStatusChange) {
26324
26628
  setStatus2(session, status3);
26325
26629
  session.restored = void 0;
26326
26630
  session.pty = respawn();
26631
+ if (session.cols && session.rows)
26632
+ try {
26633
+ session.pty?.resize(session.cols, session.rows);
26634
+ } catch {
26635
+ }
26327
26636
  broadcast(clients, { type: "clear", sessionId: session.id });
26328
26637
  wirePtyEvents(session, clients, onStatusChange);
26329
26638
  }
@@ -26405,20 +26714,6 @@ function restartSession(session, clients, onStatusChange) {
26405
26714
  }
26406
26715
  return true;
26407
26716
  }
26408
- function killPtyTree(pty2) {
26409
- if (process.platform === "win32") {
26410
- pty2.kill();
26411
- return;
26412
- }
26413
- try {
26414
- process.kill(-pty2.pid, "SIGHUP");
26415
- } catch {
26416
- try {
26417
- pty2.kill();
26418
- } catch {
26419
- }
26420
- }
26421
- }
26422
26717
 
26423
26718
  // src/commands/sessions/daemon/restartManagedSession.ts
26424
26719
  function restartManagedSession(sessions, id, clients, onStatusChange) {
@@ -26519,9 +26814,9 @@ function normalizeEntry(entry) {
26519
26814
  return null;
26520
26815
  }
26521
26816
  function normalizeAssistant(entry) {
26522
- const message = asRecord2(entry.message);
26523
- const stopReason = typeof message?.stop_reason === "string" ? message.stop_reason : null;
26524
- const content = message?.content;
26817
+ const message2 = asRecord2(entry.message);
26818
+ const stopReason = typeof message2?.stop_reason === "string" ? message2.stop_reason : null;
26819
+ const content = message2?.content;
26525
26820
  const toolUses = [];
26526
26821
  if (Array.isArray(content))
26527
26822
  for (const block of content) {
@@ -26699,6 +26994,12 @@ function runningSession(base, persisted, pty2) {
26699
26994
  function waitingSession(base, persisted, pty2) {
26700
26995
  return restoredSession(base, persisted, pty2, "waiting");
26701
26996
  }
26997
+ function stoppedSession(base, persisted) {
26998
+ return {
26999
+ ...restoredSession(base, persisted, null, "stopped"),
27000
+ undurable: persisted.undurable
27001
+ };
27002
+ }
26702
27003
  function restoredSession(base, persisted, pty2, status3) {
26703
27004
  const startedAt = Date.now();
26704
27005
  return {
@@ -26781,6 +27082,7 @@ function isUpdate(persisted) {
26781
27082
  // src/commands/sessions/daemon/restoreSession.ts
26782
27083
  function restoreSession(id, persisted) {
26783
27084
  const base = restoreBase(id, persisted);
27085
+ if (persisted.status === "stopped") return stoppedSession(base, persisted);
26784
27086
  if (isUpdate(persisted)) return updatedSession(id, persisted);
26785
27087
  const idle = deriveRestoreStatus(persisted) !== "running";
26786
27088
  if (needsWrapperRelaunch(persisted)) {
@@ -26862,27 +27164,6 @@ function restoreAll(spawn12, sessions) {
26862
27164
  });
26863
27165
  }
26864
27166
 
26865
- // src/commands/sessions/daemon/resumeSession.ts
26866
- function resumeSession(id, sessionId, cwd, name) {
26867
- const startedAt = Date.now();
26868
- return {
26869
- id,
26870
- name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
26871
- commandType: "claude",
26872
- status: "waiting",
26873
- startedAt,
26874
- runningMs: 0,
26875
- runningSince: null,
26876
- pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
26877
- scrollback: "",
26878
- cwd,
26879
- /* why: bind the card to the conversation it is resuming so a daemon restart
26880
- * re-resumes this same transcript, not whichever .jsonl a cwd poller would
26881
- * have re-discovered (#413). */
26882
- claudeSessionId: sessionId
26883
- };
26884
- }
26885
-
26886
27167
  // src/commands/sessions/daemon/retrySession.ts
26887
27168
  function retrySession(session, clients, onStatusChange) {
26888
27169
  const respawn = respawnThunk(session);
@@ -26958,6 +27239,9 @@ function runRetry(sessions, id, replace, deps2) {
26958
27239
  function reuseSessionForRun(session, itemId2, clients, onStatusChange) {
26959
27240
  const assistArgs = ["backlog", "run", String(itemId2)];
26960
27241
  session.pty?.kill();
27242
+ session.gitWatcher?.close();
27243
+ session.gitWatcher = void 0;
27244
+ session.undurable = void 0;
26961
27245
  session.assistArgs = assistArgs;
26962
27246
  session.name = `assist ${assistArgs.join(" ")}`;
26963
27247
  session.commandType = "assist";
@@ -26987,7 +27271,7 @@ function shutdownSessions(sessions) {
26987
27271
  }
26988
27272
 
26989
27273
  // src/commands/sessions/daemon/watchTranscript.ts
26990
- import { existsSync as existsSync60, watch as watch2 } from "fs";
27274
+ import { existsSync as existsSync62, watch as watch3 } from "fs";
26991
27275
 
26992
27276
  // src/commands/sessions/daemon/reconcileTranscriptStatus.ts
26993
27277
  async function resolveTranscriptPath(session) {
@@ -26999,7 +27283,8 @@ async function resolveTranscriptPath(session) {
26999
27283
  return filePath;
27000
27284
  }
27001
27285
  async function reconcileTranscriptStatus(session, onStatusChange) {
27002
- if (session.status === "done" || session.status === "error") return;
27286
+ if (session.status === "done" || session.status === "error" || session.status === "stopped")
27287
+ return;
27003
27288
  const filePath = await resolveTranscriptPath(session);
27004
27289
  if (!filePath) return;
27005
27290
  const entries = await readTranscriptTail(filePath);
@@ -27016,12 +27301,12 @@ async function reconcileTranscriptStatus(session, onStatusChange) {
27016
27301
  }
27017
27302
 
27018
27303
  // src/commands/sessions/daemon/watchTranscript.ts
27019
- var DEBOUNCE_MS2 = 100;
27304
+ var DEBOUNCE_MS3 = 100;
27020
27305
  function watchTranscript(session, onStatusChange) {
27021
27306
  if (!session.cwd || !session.claudeSessionId) return;
27022
27307
  if (session.watchedTranscriptId === session.claudeSessionId) return;
27023
27308
  const dir = projectDirForCwd(session.cwd);
27024
- if (!existsSync60(dir)) return;
27309
+ if (!existsSync62(dir)) return;
27025
27310
  if (session.transcriptWatcher) {
27026
27311
  session.transcriptWatcher.close();
27027
27312
  session.transcriptWatcher = void 0;
@@ -27034,10 +27319,10 @@ function watchTranscript(session, onStatusChange) {
27034
27319
  void reconcileTranscriptStatus(session, onStatusChange);
27035
27320
  };
27036
27321
  try {
27037
- session.transcriptWatcher = watch2(dir, (_event, changed) => {
27322
+ session.transcriptWatcher = watch3(dir, (_event, changed) => {
27038
27323
  if (changed && changed !== fileName) return;
27039
27324
  if (timer) clearTimeout(timer);
27040
- timer = setTimeout(run4, DEBOUNCE_MS2);
27325
+ timer = setTimeout(run4, DEBOUNCE_MS3);
27041
27326
  });
27042
27327
  } catch (error) {
27043
27328
  daemonLog(
@@ -27172,19 +27457,19 @@ async function defaultConnect() {
27172
27457
  }
27173
27458
 
27174
27459
  // src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
27175
- import { existsSync as existsSync61, readFileSync as readFileSync49 } from "fs";
27460
+ import { existsSync as existsSync63, readFileSync as readFileSync49 } from "fs";
27176
27461
  import { posix } from "path";
27177
27462
  function hasPersistedWindowsSessions() {
27178
27463
  const sessionsFile = windowsSessionsFileFromWsl();
27179
27464
  if (!sessionsFile) return false;
27180
27465
  try {
27181
- if (!existsSync61(sessionsFile)) return false;
27466
+ if (!existsSync63(sessionsFile)) return false;
27182
27467
  const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
27183
27468
  return Array.isArray(data) && data.length > 0;
27184
27469
  } catch (error) {
27185
- const message = error instanceof Error ? error.message : String(error);
27470
+ const message2 = error instanceof Error ? error.message : String(error);
27186
27471
  daemonLog(
27187
- `windows proxy: could not read windows sessions.json: ${message}`
27472
+ `windows proxy: could not read windows sessions.json: ${message2}`
27188
27473
  );
27189
27474
  return false;
27190
27475
  }
@@ -27211,8 +27496,8 @@ async function discoverWindowsSessions(conn) {
27211
27496
  try {
27212
27497
  await conn.ensure();
27213
27498
  } catch (error) {
27214
- const message = error instanceof Error ? error.message : String(error);
27215
- daemonLog(`windows proxy: discovery failed: ${message}`);
27499
+ const message2 = error instanceof Error ? error.message : String(error);
27500
+ daemonLog(`windows proxy: discovery failed: ${message2}`);
27216
27501
  }
27217
27502
  }
27218
27503
 
@@ -27250,11 +27535,11 @@ async function forwardWindowsCreate(conn, state, client, data) {
27250
27535
  state.pendingCreators.push({ client, timer });
27251
27536
  conn.write(stripOutboundSessionId(data));
27252
27537
  } catch (error) {
27253
- const message = error instanceof Error ? error.message : String(error);
27254
- daemonLog(`windows proxy: forwardCreate failed: ${message}`);
27538
+ const message2 = error instanceof Error ? error.message : String(error);
27539
+ daemonLog(`windows proxy: forwardCreate failed: ${message2}`);
27255
27540
  sendTo(client, {
27256
27541
  type: "error",
27257
- message: `Windows session unavailable: ${message}`
27542
+ message: `Windows session unavailable: ${message2}`
27258
27543
  });
27259
27544
  }
27260
27545
  }
@@ -27289,10 +27574,10 @@ function takePendingCreator(state) {
27289
27574
  clearTimeout(pending.timer);
27290
27575
  return pending.client;
27291
27576
  }
27292
- function failPendingCreators(state, message) {
27577
+ function failPendingCreators(state, message2) {
27293
27578
  for (const { client, timer } of state.pendingCreators) {
27294
27579
  clearTimeout(timer);
27295
- sendTo(client, { type: "error", message });
27580
+ sendTo(client, { type: "error", message: message2 });
27296
27581
  }
27297
27582
  state.pendingCreators = [];
27298
27583
  }
@@ -27432,9 +27717,9 @@ async function healWindowsDaemon() {
27432
27717
  try {
27433
27718
  await runOnWindowsHost("assist update", UPDATE_TIMEOUT_MS);
27434
27719
  } catch (error) {
27435
- const message = error instanceof Error ? error.message : String(error);
27720
+ const message2 = error instanceof Error ? error.message : String(error);
27436
27721
  daemonLog(
27437
- `windows daemon: auto-heal: \`assist update\` failed: ${message}`
27722
+ `windows daemon: auto-heal: \`assist update\` failed: ${message2}`
27438
27723
  );
27439
27724
  throw error;
27440
27725
  }
@@ -27609,11 +27894,11 @@ async function autoHealWindowsDaemon(conn, state, heal, version2) {
27609
27894
  daemonLog("windows proxy: heal complete, reconnecting to windows daemon");
27610
27895
  await conn.ensure();
27611
27896
  } catch (error) {
27612
- const message = error instanceof Error ? error.message : String(error);
27613
- daemonLog(`windows proxy: auto-heal failed: ${message}`);
27897
+ const message2 = error instanceof Error ? error.message : String(error);
27898
+ daemonLog(`windows proxy: auto-heal failed: ${message2}`);
27614
27899
  state.broadcast({
27615
27900
  type: "error",
27616
- message: `Windows host auto-update failed: ${message}`
27901
+ message: `Windows host auto-update failed: ${message2}`
27617
27902
  });
27618
27903
  }
27619
27904
  }
@@ -27721,6 +28006,335 @@ var WindowsProxy = class {
27721
28006
  }
27722
28007
  };
27723
28008
 
28009
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28010
+ import { existsSync as existsSync66 } from "fs";
28011
+ import { basename as basename14 } from "path";
28012
+
28013
+ // src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
28014
+ var DISABLED = {
28015
+ enabled: false,
28016
+ install: true,
28017
+ copy: [".env", "settings.local.json", ".claude/settings.local.json"]
28018
+ };
28019
+ function worktreeConfigFor(cwd) {
28020
+ try {
28021
+ return loadConfigFrom(cwd).worktree ?? DISABLED;
28022
+ } catch {
28023
+ return DISABLED;
28024
+ }
28025
+ }
28026
+
28027
+ // src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
28028
+ function detectExistingWorktree(cwd) {
28029
+ const repoRoot = findRepoRoot(cwd) ?? cwd;
28030
+ if (!worktreeConfigFor(repoRoot).enabled) return void 0;
28031
+ const clone = mainWorktree(repoRoot);
28032
+ if (clone && clone !== repoRoot) return { path: repoRoot, clone };
28033
+ return void 0;
28034
+ }
28035
+
28036
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
28037
+ import { execFile as execFile8 } from "child_process";
28038
+ import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
28039
+ import { dirname as dirname30, join as join71 } from "path";
28040
+
28041
+ // src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
28042
+ import { existsSync as existsSync64 } from "fs";
28043
+ import { join as join70 } from "path";
28044
+ function detectInstallCommand(repoRoot) {
28045
+ if (!existsSync64(join70(repoRoot, "package.json"))) return null;
28046
+ if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
28047
+ if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
28048
+ if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
28049
+ return "npm install";
28050
+ }
28051
+ function resolveInstallCommand(repoRoot, install) {
28052
+ if (install === false) return null;
28053
+ if (typeof install === "string") return install;
28054
+ return detectInstallCommand(repoRoot);
28055
+ }
28056
+
28057
+ // src/commands/sessions/daemon/worktree/seedWorktree.ts
28058
+ function seedWorktree(worktreePath, clone) {
28059
+ const cfg = worktreeConfigFor(clone);
28060
+ copyConfigFiles(worktreePath, clone, cfg.copy);
28061
+ runInstall(worktreePath, clone, cfg.install);
28062
+ }
28063
+ function copyConfigFiles(worktreePath, clone, copy) {
28064
+ for (const rel of copy) {
28065
+ const src = join71(clone, rel);
28066
+ if (!existsSync65(src)) continue;
28067
+ const dest = join71(worktreePath, rel);
28068
+ try {
28069
+ mkdirSync26(dirname30(dest), { recursive: true });
28070
+ copyFileSync(src, dest);
28071
+ daemonLog(`worktree ${worktreePath} seeded ${rel}`);
28072
+ } catch (error) {
28073
+ daemonLog(
28074
+ `worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
28075
+ );
28076
+ }
28077
+ }
28078
+ }
28079
+ function runInstall(worktreePath, clone, install) {
28080
+ const command = resolveInstallCommand(clone, install);
28081
+ if (!command) return;
28082
+ const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
28083
+ const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
28084
+ const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
28085
+ daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
28086
+ execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
28087
+ daemonLog(
28088
+ error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
28089
+ );
28090
+ });
28091
+ }
28092
+ function message(error) {
28093
+ return error instanceof Error ? error.message : String(error);
28094
+ }
28095
+
28096
+ // src/commands/sessions/daemon/worktree/bindNewWorktree.ts
28097
+ function boundTreeRoots(sessions) {
28098
+ const roots = /* @__PURE__ */ new Set();
28099
+ for (const session of sessions.values())
28100
+ if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
28101
+ return roots;
28102
+ }
28103
+ function bindNewWorktree(session, alloc, notify2) {
28104
+ if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
28105
+ return;
28106
+ session.worktree = { path: alloc.cwd, clone: alloc.clone };
28107
+ if (alloc.created) seedWorktree(alloc.cwd, alloc.clone);
28108
+ notify2();
28109
+ }
28110
+ function bindRestoredWorktrees(sessions) {
28111
+ for (const session of sessions.values()) {
28112
+ if (session.worktree || !session.cwd) continue;
28113
+ const worktree = detectExistingWorktree(session.cwd);
28114
+ if (worktree) session.worktree = worktree;
28115
+ }
28116
+ }
28117
+ function bindResumedWorktree(session, cwd, notify2) {
28118
+ if (!session) return;
28119
+ const worktree = detectExistingWorktree(cwd);
28120
+ if (!worktree) return;
28121
+ session.worktree = worktree;
28122
+ notify2();
28123
+ }
28124
+
28125
+ // src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
28126
+ var REGISTRY_FILE = "worktrees.json";
28127
+ function readWorktreeRegistry() {
28128
+ const data = loadJson(REGISTRY_FILE);
28129
+ return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
28130
+ }
28131
+ function recordWorktree(path71, clone, origin) {
28132
+ const data = loadJson(REGISTRY_FILE);
28133
+ data[path71] = { clone, origin };
28134
+ saveJson(REGISTRY_FILE, data);
28135
+ }
28136
+
28137
+ // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
28138
+ function reconcileWorktreesOnRestore(sessions) {
28139
+ bindRestoredWorktrees(sessions);
28140
+ void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
28141
+ }
28142
+ async function pruneWorktrees(liveCwds) {
28143
+ const accounted = /* @__PURE__ */ new Set();
28144
+ for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
28145
+ for (const s of loadPersistedSessions()) accounted.add(s.cwd);
28146
+ const clonesToPrune = /* @__PURE__ */ new Set();
28147
+ const orphanBranches = [];
28148
+ for (const { path: path71, clone } of readWorktreeRegistry()) {
28149
+ if (accounted.has(path71)) continue;
28150
+ if (!existsSync66(path71)) {
28151
+ clonesToPrune.add(clone);
28152
+ orphanBranches.push({ clone, branch: basename14(path71) });
28153
+ continue;
28154
+ }
28155
+ daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
28156
+ await reapWorktree(path71);
28157
+ }
28158
+ for (const clone of clonesToPrune) {
28159
+ if (!existsSync66(clone)) continue;
28160
+ try {
28161
+ await git3(clone, ["worktree", "prune"]);
28162
+ daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
28163
+ } catch {
28164
+ }
28165
+ }
28166
+ for (const { clone, branch: branch2 } of orphanBranches)
28167
+ if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
28168
+ }
28169
+ async function pruneStaleWorktreeBranch(clone, branch2) {
28170
+ if (!listLocalBranches(clone).includes(branch2)) return;
28171
+ try {
28172
+ await git3(clone, ["branch", "-d", branch2]);
28173
+ daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
28174
+ } catch {
28175
+ daemonLog(
28176
+ `stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
28177
+ );
28178
+ }
28179
+ }
28180
+
28181
+ // src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
28182
+ function rearmStoppedSessions(sessions, notify2) {
28183
+ for (const s of sessions.values()) {
28184
+ if (s.status !== "stopped" || !s.worktree) continue;
28185
+ void resolveDoneDurability(
28186
+ s,
28187
+ () => {
28188
+ if (dismissSession(sessions, s.id)) notify2();
28189
+ },
28190
+ notify2
28191
+ );
28192
+ }
28193
+ }
28194
+
28195
+ // src/commands/sessions/daemon/createAssistSession.ts
28196
+ function createAssistSession(id, assistArgs, cwd, meta) {
28197
+ const startedAt = Date.now();
28198
+ return {
28199
+ id,
28200
+ name: `assist ${assistArgs.join(" ")}`,
28201
+ title: meta?.title,
28202
+ subtitle: meta?.subtitle,
28203
+ commandType: "assist",
28204
+ status: "running",
28205
+ startedAt,
28206
+ runningMs: 0,
28207
+ runningSince: startedAt,
28208
+ pty: spawnPty(["assist", ...assistArgs], cwd, id),
28209
+ scrollback: "",
28210
+ assistArgs,
28211
+ cwd
28212
+ };
28213
+ }
28214
+
28215
+ // src/commands/sessions/daemon/resumeSession.ts
28216
+ function resumeSession(id, sessionId, cwd, name) {
28217
+ const startedAt = Date.now();
28218
+ return {
28219
+ id,
28220
+ name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
28221
+ commandType: "claude",
28222
+ status: "waiting",
28223
+ startedAt,
28224
+ runningMs: 0,
28225
+ runningSince: null,
28226
+ pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
28227
+ scrollback: "",
28228
+ cwd,
28229
+ /* why: bind the card to the conversation it is resuming so a daemon restart
28230
+ * re-resumes this same transcript, not whichever .jsonl a cwd poller would
28231
+ * have re-discovered (#413). */
28232
+ claudeSessionId: sessionId
28233
+ };
28234
+ }
28235
+
28236
+ // src/commands/sessions/daemon/worktree/createWorktree.ts
28237
+ import { existsSync as existsSync67 } from "fs";
28238
+ import { basename as basename16, dirname as dirname31 } from "path";
28239
+
28240
+ // src/commands/sessions/daemon/worktree/planAllocation.ts
28241
+ import { basename as basename15, join as join72 } from "path";
28242
+ function planAllocation(clone, boundTreeRoots2) {
28243
+ return boundTreeRoots2.has(clone) ? "spill" : "primary";
28244
+ }
28245
+ function nextWorktreePath(clone, base, isTaken) {
28246
+ const name = basename15(clone);
28247
+ for (let n = 2; n < 1e3; n++) {
28248
+ const candidate = join72(base, `${name}-${n}`);
28249
+ if (!isTaken(candidate)) return candidate;
28250
+ }
28251
+ throw new Error(`no free worktree suffix for ${clone}`);
28252
+ }
28253
+
28254
+ // src/commands/sessions/daemon/worktree/createWorktree.ts
28255
+ function createWorktree(clone, root, boundTreeRoots2) {
28256
+ const base = root ? expandTilde2(root) : dirname31(clone);
28257
+ const registered = new Set(listWorktreePaths(clone));
28258
+ const branches = new Set(listLocalBranches(clone));
28259
+ const path71 = nextWorktreePath(
28260
+ clone,
28261
+ base,
28262
+ (candidate) => registered.has(candidate) || existsSync67(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
28263
+ );
28264
+ const trunk = cloneTrunkBranch(clone);
28265
+ gitSync(clone, ["config", "push.default", "upstream"]);
28266
+ gitSync(clone, [
28267
+ "worktree",
28268
+ "add",
28269
+ "--track",
28270
+ "-b",
28271
+ basename16(path71),
28272
+ path71,
28273
+ `origin/${trunk}`
28274
+ ]);
28275
+ recordWorktree(path71, clone, getCurrentOrigin(clone));
28276
+ daemonLog(
28277
+ `worktree allocated ${path71} (branch ${basename16(path71)} tracking origin/${trunk}) for clone ${clone}`
28278
+ );
28279
+ return path71;
28280
+ }
28281
+ function cloneTrunkBranch(clone) {
28282
+ const branch2 = gitSyncOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
28283
+ if (branch2) return branch2;
28284
+ const head = gitSyncOrNull(clone, [
28285
+ "symbolic-ref",
28286
+ "--short",
28287
+ "refs/remotes/origin/HEAD"
28288
+ ]);
28289
+ if (head) {
28290
+ const slash = head.indexOf("/");
28291
+ return slash === -1 ? head : head.slice(slash + 1);
28292
+ }
28293
+ return "main";
28294
+ }
28295
+
28296
+ // src/commands/sessions/daemon/worktree/allocateTree.ts
28297
+ function allocateTree(requestedCwd, boundTreeRoots2) {
28298
+ if (!requestedCwd)
28299
+ return { cwd: requestedCwd, kind: "primary", created: false };
28300
+ const repoRoot = findRepoRoot(requestedCwd) ?? requestedCwd;
28301
+ const cfg = worktreeConfigFor(repoRoot);
28302
+ if (!cfg.enabled)
28303
+ return { cwd: requestedCwd, kind: "primary", created: false };
28304
+ const clone = mainWorktree(repoRoot) ?? repoRoot;
28305
+ if (planAllocation(clone, boundTreeRoots2) === "primary")
28306
+ return { cwd: clone, kind: "primary", created: false, clone };
28307
+ const path71 = createWorktree(clone, cfg.root, boundTreeRoots2);
28308
+ return { cwd: path71, kind: "worktree", created: true, clone };
28309
+ }
28310
+
28311
+ // src/commands/sessions/daemon/worktree/spawnInTree.ts
28312
+ function allocateAndBind(ctx, cwd, create) {
28313
+ const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
28314
+ const id = ctx.spawnWith((sid) => create(sid, alloc.cwd));
28315
+ bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify);
28316
+ return id;
28317
+ }
28318
+ function spawnInTree(ctx, prompt, cwd, design, harness) {
28319
+ return allocateAndBind(
28320
+ ctx,
28321
+ cwd,
28322
+ (sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
28323
+ );
28324
+ }
28325
+ function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
28326
+ return allocateAndBind(
28327
+ ctx,
28328
+ cwd,
28329
+ (sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
28330
+ );
28331
+ }
28332
+ function resumeInTree(ctx, sessionId, cwd, name) {
28333
+ const id = ctx.spawnWith((sid) => resumeSession(sid, sessionId, cwd, name));
28334
+ bindResumedWorktree(ctx.sessions.get(id), cwd, ctx.notify);
28335
+ return id;
28336
+ }
28337
+
27724
28338
  // src/commands/sessions/daemon/watchPromptSubmit.ts
27725
28339
  var CARRIAGE_RETURN = "\r";
27726
28340
  function isPromptSubmit(data) {
@@ -27744,6 +28358,8 @@ function writeToSession(sessions, id, data, onStatusChange) {
27744
28358
  function resizeSession(sessions, id, cols, rows) {
27745
28359
  const s = sessions.get(id);
27746
28360
  if (!s || s.status === "done" || !s.pty) return;
28361
+ s.cols = cols;
28362
+ s.rows = rows;
27747
28363
  try {
27748
28364
  s.pty.resize(cols, rows);
27749
28365
  } catch (error) {
@@ -27818,7 +28434,10 @@ var SessionManager = class {
27818
28434
  );
27819
28435
  }
27820
28436
  restore() {
27821
- return restoreAll(this.spawnWith, this.sessions);
28437
+ const names = restoreAll(this.spawnWith, this.sessions);
28438
+ reconcileWorktreesOnRestore(this.sessions);
28439
+ rearmStoppedSessions(this.sessions, this.notify);
28440
+ return names;
27822
28441
  }
27823
28442
  drain = () => drainSessions(this.sessions, this.notify);
27824
28443
  add(session) {
@@ -27828,10 +28447,15 @@ var SessionManager = class {
27828
28447
  return session.id;
27829
28448
  }
27830
28449
  spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
28450
+ treeCtx() {
28451
+ return {
28452
+ sessions: this.sessions,
28453
+ spawnWith: this.spawnWith,
28454
+ notify: this.notify
28455
+ };
28456
+ }
27831
28457
  spawn(prompt, cwd, design, harness) {
27832
- return this.spawnWith(
27833
- (id) => createSession(id, prompt, cwd, design, harness)
27834
- );
28458
+ return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
27835
28459
  }
27836
28460
  spawnRun(runName, runArgs, cwd, meta) {
27837
28461
  return this.spawnWith(
@@ -27842,12 +28466,10 @@ var SessionManager = class {
27842
28466
  return liveServerRun(this.sessions, origin, excludeId);
27843
28467
  }
27844
28468
  spawnAssist(assistArgs, cwd, meta) {
27845
- return this.spawnWith(
27846
- (id) => createAssistSession(id, assistArgs, cwd, meta)
27847
- );
28469
+ return spawnAssistInTree(this.treeCtx(), assistArgs, cwd, meta);
27848
28470
  }
27849
28471
  resume(sessionId, cwd, name) {
27850
- return this.spawnWith((id) => resumeSession(id, sessionId, cwd, name));
28472
+ return resumeInTree(this.treeCtx(), sessionId, cwd, name);
27851
28473
  }
27852
28474
  onStatusChange = makeStatusChangeHandler(
27853
28475
  (id) => this.dismissSession(id),
@@ -27884,7 +28506,10 @@ var SessionManager = class {
27884
28506
  return result;
27885
28507
  }
27886
28508
  dismissSession = (id) => {
27887
- if (dismissSession(this.sessions, id)) this.notify();
28509
+ dismissSessionGated(this.sessions, id, this.notify);
28510
+ };
28511
+ discardSession = (id) => {
28512
+ dismissSessionGated(this.sessions, id, this.notify, true);
27888
28513
  };
27889
28514
  stopSession(id) {
27890
28515
  stopServerSession(this.sessions, id);
@@ -28165,6 +28790,7 @@ var messageHandlers = {
28165
28790
  sendTo(client, { type: "error", message: result.reason });
28166
28791
  }),
28167
28792
  dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
28793
+ discard: routed((_client, m, d) => m.discardSession(d.sessionId)),
28168
28794
  stop: routed((_client, m, d) => m.stopSession(d.sessionId)),
28169
28795
  "set-autorun": routed(
28170
28796
  (_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
@@ -28326,7 +28952,7 @@ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
28326
28952
 
28327
28953
  // src/commands/sessions/daemon/runDaemon.ts
28328
28954
  async function runDaemon() {
28329
- mkdirSync26(daemonPaths.dir, { recursive: true });
28955
+ mkdirSync27(daemonPaths.dir, { recursive: true });
28330
28956
  daemonLog(
28331
28957
  `starting (reason: ${process.env.ASSIST_DAEMON_SPAWN_REASON ?? "manual"})`
28332
28958
  );
@@ -28379,6 +29005,50 @@ function registerSetStatusCommand(cmd) {
28379
29005
  );
28380
29006
  }
28381
29007
 
29008
+ // src/commands/sessions/sessionsConfigHelp.ts
29009
+ var sessionsConfigHelp = [
29010
+ {
29011
+ key: "sessions.windowsProjectsRoot",
29012
+ setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
29013
+ note: "WSL path to the Windows .claude/projects root to discover"
29014
+ },
29015
+ {
29016
+ key: "sessions.windowsDaemonHost",
29017
+ setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
29018
+ note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
29019
+ },
29020
+ {
29021
+ key: "sessions.windowsDaemonPort",
29022
+ setter: "assist config set sessions.windowsDaemonPort 51764",
29023
+ note: "TCP port the Windows daemon listens on (default: 51764)"
29024
+ },
29025
+ {
29026
+ key: "sessions.windowsVersionCheck",
29027
+ setter: "assist config set sessions.windowsVersionCheck block",
29028
+ note: "Windows daemon version mismatch handling: block | warn | off"
29029
+ },
29030
+ {
29031
+ key: "worktree.enabled",
29032
+ setter: "assist config set worktree.enabled true -g --repo",
29033
+ note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
29034
+ },
29035
+ {
29036
+ key: "worktree.root",
29037
+ setter: "assist config set worktree.root ~/git -g --repo",
29038
+ note: "optional; parent dir for <clone>-N worktrees (default: the clone's parent)"
29039
+ },
29040
+ {
29041
+ key: "worktree.install",
29042
+ setter: "assist config set worktree.install true -g --repo",
29043
+ note: "per-worktree dep install: true auto-detects the package manager, or give an explicit command; false to skip"
29044
+ },
29045
+ {
29046
+ key: "worktree.copy",
29047
+ setter: "assist config set worktree.copy .env,.claude/settings.local.json -g --repo",
29048
+ note: "gitignored files copied into a new worktree so it can build/run"
29049
+ }
29050
+ ];
29051
+
28382
29052
  // src/commands/sessions/summarise/index.ts
28383
29053
  import * as fs41 from "fs";
28384
29054
  import chalk204 from "chalk";
@@ -28397,7 +29067,7 @@ function summaryPathFor(jsonlPath2) {
28397
29067
  }
28398
29068
 
28399
29069
  // src/commands/sessions/summarise/summariseSession.ts
28400
- import { execFileSync as execFileSync12 } from "child_process";
29070
+ import { execFileSync as execFileSync13 } from "child_process";
28401
29071
 
28402
29072
  // src/commands/sessions/summarise/extractFirstUserMessage.ts
28403
29073
  function extractFirstUserMessage(filePath) {
@@ -28421,7 +29091,7 @@ function summariseSession(jsonlPath2) {
28421
29091
  }
28422
29092
  const prompt = buildPrompt3(firstMessage, backlogIds);
28423
29093
  try {
28424
- const output = execFileSync12("claude", ["-p", "--model", "haiku", prompt], {
29094
+ const output = execFileSync13("claude", ["-p", "--model", "haiku", prompt], {
28425
29095
  encoding: "utf8",
28426
29096
  timeout: 3e4,
28427
29097
  stdio: ["ignore", "pipe", "ignore"]
@@ -28516,28 +29186,7 @@ function registerSessions(program2) {
28516
29186
  );
28517
29187
  cmd.command("summarise").description("Generate one-line summaries for Claude sessions").option("-f, --force", "Re-generate all summaries, even existing ones").option("-n, --limit <count>", "Maximum number of sessions to summarise").action(summarise2);
28518
29188
  registerSetStatusCommand(cmd);
28519
- configHelp(cmd, [
28520
- {
28521
- key: "sessions.windowsProjectsRoot",
28522
- setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
28523
- note: "WSL path to the Windows .claude/projects root to discover"
28524
- },
28525
- {
28526
- key: "sessions.windowsDaemonHost",
28527
- setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
28528
- note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
28529
- },
28530
- {
28531
- key: "sessions.windowsDaemonPort",
28532
- setter: "assist config set sessions.windowsDaemonPort 51764",
28533
- note: "TCP port the Windows daemon listens on (default: 51764)"
28534
- },
28535
- {
28536
- key: "sessions.windowsVersionCheck",
28537
- setter: "assist config set sessions.windowsVersionCheck block",
28538
- note: "Windows daemon version mismatch handling: block | warn | off"
28539
- }
28540
- ]);
29189
+ configHelp(cmd, sessionsConfigHelp);
28541
29190
  }
28542
29191
 
28543
29192
  // src/commands/statusLine.ts
@@ -28616,9 +29265,9 @@ function buildLimitsSegment(rateLimits) {
28616
29265
 
28617
29266
  // src/commands/readGitBranch.ts
28618
29267
  import { readFileSync as readFileSync52, statSync as statSync11 } from "fs";
28619
- import { isAbsolute as isAbsolute2, join as join70, resolve as resolve17 } from "path";
29268
+ import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
28620
29269
  function resolveGitDir(cwd) {
28621
- const dotGit = join70(cwd, ".git");
29270
+ const dotGit = join73(cwd, ".git");
28622
29271
  let stat2;
28623
29272
  try {
28624
29273
  stat2 = statSync11(dotGit);
@@ -28648,7 +29297,7 @@ function readGitBranch(cwd) {
28648
29297
  }
28649
29298
  let head;
28650
29299
  try {
28651
- head = readFileSync52(join70(gitDir, "HEAD"), "utf8");
29300
+ head = readFileSync52(join73(gitDir, "HEAD"), "utf8");
28652
29301
  } catch {
28653
29302
  return null;
28654
29303
  }