@staff0rd/assist 0.244.2 → 0.245.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.244.2",
9
+ version: "0.245.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -3632,15 +3632,15 @@ Phase ${phaseNumber} completed.`));
3632
3632
 
3633
3633
  // src/commands/backlog/watchForMarker.ts
3634
3634
  import { existsSync as existsSync20, unwatchFile, watchFile } from "fs";
3635
- function watchForMarker(child) {
3635
+ function watchForMarker(child, options2) {
3636
3636
  const statusPath = getSignalPath();
3637
3637
  watchFile(statusPath, { interval: 1e3 }, () => {
3638
3638
  if (!existsSync20(statusPath)) return;
3639
3639
  const signal = readSignal();
3640
- if (signal) {
3641
- unwatchFile(statusPath);
3642
- child.kill("SIGTERM");
3643
- }
3640
+ if (!signal) return;
3641
+ if (signal.event === "done" && !options2?.actOnDone) return;
3642
+ unwatchFile(statusPath);
3643
+ child.kill("SIGTERM");
3644
3644
  });
3645
3645
  }
3646
3646
  function stopWatching() {
@@ -3809,7 +3809,7 @@ async function next(options2, startId) {
3809
3809
  firstPick = false;
3810
3810
  if (id === void 0) return;
3811
3811
  const completed = await run2(id, options2);
3812
- if (!completed) return;
3812
+ if (!completed || options2?.once) return;
3813
3813
  }
3814
3814
  }
3815
3815
 
@@ -4098,7 +4098,14 @@ function startWebServer(label2, port, handler, initialPath) {
4098
4098
 
4099
4099
  // src/commands/sessions/daemon/ensureDaemonRunning.ts
4100
4100
  import { spawn as spawn4 } from "child_process";
4101
- import { mkdirSync as mkdirSync4, openSync } from "fs";
4101
+ import {
4102
+ closeSync,
4103
+ mkdirSync as mkdirSync4,
4104
+ openSync,
4105
+ statSync,
4106
+ unlinkSync as unlinkSync4,
4107
+ writeSync
4108
+ } from "fs";
4102
4109
 
4103
4110
  // src/commands/sessions/daemon/connectToDaemon.ts
4104
4111
  import * as net from "net";
@@ -4111,7 +4118,8 @@ var daemonPaths = {
4111
4118
  dir: DAEMON_DIR,
4112
4119
  socket: process.platform === "win32" ? "\\\\.\\pipe\\assist-sessions-daemon" : join15(DAEMON_DIR, "daemon.sock"),
4113
4120
  log: join15(DAEMON_DIR, "daemon.log"),
4114
- pid: join15(DAEMON_DIR, "daemon.pid")
4121
+ pid: join15(DAEMON_DIR, "daemon.pid"),
4122
+ spawnLock: join15(DAEMON_DIR, "spawn.lock")
4115
4123
  };
4116
4124
 
4117
4125
  // src/commands/sessions/daemon/connectToDaemon.ts
@@ -4134,9 +4142,19 @@ async function isDaemonRunning() {
4134
4142
  // src/commands/sessions/daemon/ensureDaemonRunning.ts
4135
4143
  var SPAWN_TIMEOUT_MS = 1e4;
4136
4144
  var RETRY_DELAY_MS = 200;
4137
- async function ensureDaemonRunning() {
4145
+ var STALE_LOCK_MS = SPAWN_TIMEOUT_MS + 5e3;
4146
+ async function ensureDaemonRunning(reason = "unspecified") {
4138
4147
  if (await isDaemonRunning()) return;
4139
- spawnDaemon();
4148
+ mkdirSync4(daemonPaths.dir, { recursive: true });
4149
+ const holdsLock = acquireSpawnLock();
4150
+ if (holdsLock) spawnDaemon(reason);
4151
+ try {
4152
+ await waitForDaemon();
4153
+ } finally {
4154
+ if (holdsLock) releaseSpawnLock();
4155
+ }
4156
+ }
4157
+ async function waitForDaemon() {
4140
4158
  const deadline = Date.now() + SPAWN_TIMEOUT_MS;
4141
4159
  while (Date.now() < deadline) {
4142
4160
  await delay(RETRY_DELAY_MS);
@@ -4146,12 +4164,44 @@ async function ensureDaemonRunning() {
4146
4164
  `Sessions daemon did not start within ${SPAWN_TIMEOUT_MS / 1e3}s; see ${daemonPaths.log}`
4147
4165
  );
4148
4166
  }
4149
- function spawnDaemon() {
4150
- mkdirSync4(daemonPaths.dir, { recursive: true });
4167
+ function acquireSpawnLock() {
4168
+ if (tryCreateLock()) return true;
4169
+ if (!isLockStale()) return false;
4170
+ try {
4171
+ unlinkSync4(daemonPaths.spawnLock);
4172
+ } catch {
4173
+ }
4174
+ return tryCreateLock();
4175
+ }
4176
+ function tryCreateLock() {
4177
+ try {
4178
+ const fd = openSync(daemonPaths.spawnLock, "wx");
4179
+ writeSync(fd, String(process.pid));
4180
+ closeSync(fd);
4181
+ return true;
4182
+ } catch {
4183
+ return false;
4184
+ }
4185
+ }
4186
+ function isLockStale() {
4187
+ try {
4188
+ return Date.now() - statSync(daemonPaths.spawnLock).mtimeMs > STALE_LOCK_MS;
4189
+ } catch {
4190
+ return true;
4191
+ }
4192
+ }
4193
+ function releaseSpawnLock() {
4194
+ try {
4195
+ unlinkSync4(daemonPaths.spawnLock);
4196
+ } catch {
4197
+ }
4198
+ }
4199
+ function spawnDaemon(reason) {
4151
4200
  const log = openSync(daemonPaths.log, "a");
4152
4201
  const child = spawn4(process.execPath, [process.argv[1], "daemon", "run"], {
4153
4202
  detached: true,
4154
- stdio: ["ignore", log, log]
4203
+ stdio: ["ignore", log, log],
4204
+ env: { ...process.env, ASSIST_DAEMON_SPAWN_REASON: reason }
4155
4205
  });
4156
4206
  child.unref();
4157
4207
  }
@@ -4500,7 +4550,7 @@ function handleSocket(ws, ctx) {
4500
4550
  }
4501
4551
  async function openDaemonConnection(ws, ctx) {
4502
4552
  try {
4503
- await ensureDaemonRunning();
4553
+ await ensureDaemonRunning("web socket connection");
4504
4554
  const conn = await connectToDaemon();
4505
4555
  relayDaemonLines(conn, ws, ctx.repoCwd);
4506
4556
  return conn;
@@ -4554,7 +4604,7 @@ function withRepoCwd(line, repoCwd) {
4554
4604
 
4555
4605
  // src/commands/sessions/web/index.ts
4556
4606
  async function web(options2) {
4557
- await ensureDaemonRunning();
4607
+ await ensureDaemonRunning("web server start");
4558
4608
  const port = Number.parseInt(options2.port, 10);
4559
4609
  const server = startWebServer(
4560
4610
  "Assist",
@@ -4584,117 +4634,27 @@ async function web2(options2) {
4584
4634
  await web({ port: options2.port, initialPath: "/backlog" });
4585
4635
  }
4586
4636
 
4587
- // src/commands/backlog/refine.ts
4588
- import chalk47 from "chalk";
4589
- import enquirer6 from "enquirer";
4590
-
4591
- // src/commands/backlog/launchMode.ts
4592
- import chalk46 from "chalk";
4593
-
4594
- // src/commands/backlog/tryRunById.ts
4595
- import chalk45 from "chalk";
4596
- async function tryRunById(id, options2) {
4597
- const items2 = await loadBacklog();
4598
- const numericId = Number.parseInt(id, 10);
4599
- const item = Number.isNaN(numericId) ? void 0 : items2.find((i) => i.id === numericId);
4600
- if (!item) {
4601
- console.log(chalk45.red(`Item #${id} not found.`));
4602
- return false;
4603
- }
4604
- if (item.status === "done") {
4605
- console.log(chalk45.red(`Item #${id} is already done.`));
4606
- return false;
4607
- }
4608
- if (item.status === "wontdo") {
4609
- console.log(chalk45.red(`Item #${id} is marked won't do.`));
4610
- return false;
4611
- }
4612
- if (isBlocked(item, items2)) {
4613
- console.log(
4614
- chalk45.red(`Item #${id} is blocked by unresolved dependencies.`)
4615
- );
4616
- return false;
4617
- }
4618
- console.log(chalk45.bold(`
4619
- Running backlog item #${id}...
4620
- `));
4621
- await run2(id, options2);
4622
- return true;
4623
- }
4624
-
4625
- // src/commands/backlog/launchMode.ts
4626
- async function launchMode(slashCommand) {
4627
- pullIfConfigured();
4628
- process.env.ASSIST_SESSION_ID = String(process.pid);
4629
- const { child, done: done2 } = spawnClaude(`/${slashCommand}`, { allowEdits: true });
4630
- watchForMarker(child);
4631
- await done2;
4632
- stopWatching();
4633
- const signal = readSignal();
4634
- cleanupSignal();
4635
- if (signal?.event === "next") {
4636
- if (typeof signal.id === "string" && signal.id) {
4637
- if (await tryRunById(signal.id, { allowEdits: true })) return;
4638
- }
4639
- console.log(chalk46.bold("\nChaining into assist next...\n"));
4640
- await next({ allowEdits: true });
4641
- }
4642
- }
4643
-
4644
- // src/commands/backlog/refine.ts
4645
- async function pickItemForRefine() {
4646
- const items2 = await loadBacklog();
4647
- const active = items2.filter(
4648
- (i) => i.status === "todo" || i.status === "in-progress"
4649
- );
4650
- if (active.length === 0) {
4651
- console.log(chalk47.yellow("No active backlog items to refine."));
4652
- return void 0;
4653
- }
4654
- if (active.length === 1) {
4655
- const item = active[0];
4656
- console.log(chalk47.bold(`Auto-selecting item #${item.id}: ${item.name}`));
4657
- return String(item.id);
4658
- }
4659
- const { selected } = await exitOnCancel(
4660
- enquirer6.prompt({
4661
- type: "select",
4662
- name: "selected",
4663
- message: "Choose a backlog item to refine:",
4664
- choices: active.map((item) => ({
4665
- name: `${typeLabel(item.type)} #${item.id}: ${item.name}`
4666
- }))
4667
- })
4668
- );
4669
- return selected.match(/#(\d+)/)?.[1] ?? "";
4670
- }
4671
- async function refine(id) {
4672
- const itemId = id ?? await pickItemForRefine();
4673
- if (!itemId) return;
4674
- await launchMode(`refine ${itemId}`);
4675
- }
4676
-
4677
4637
  // src/commands/backlog/comment/index.ts
4678
- import chalk48 from "chalk";
4638
+ import chalk45 from "chalk";
4679
4639
  async function comment(id, text2) {
4680
4640
  const found = await findOneItem(id);
4681
4641
  if (!found) process.exit(1);
4682
4642
  await appendComment(found.orm, found.item.id, text2);
4683
- console.log(chalk48.green(`Comment added to item #${id}.`));
4643
+ console.log(chalk45.green(`Comment added to item #${id}.`));
4684
4644
  }
4685
4645
 
4686
4646
  // src/commands/backlog/comments/index.ts
4687
- import chalk49 from "chalk";
4647
+ import chalk46 from "chalk";
4688
4648
  async function comments2(id) {
4689
4649
  const found = await findOneItem(id);
4690
4650
  if (!found) process.exit(1);
4691
4651
  const { item } = found;
4692
4652
  const entries = item.comments ?? [];
4693
4653
  if (entries.length === 0) {
4694
- console.log(chalk49.dim(`No comments on item #${id}.`));
4654
+ console.log(chalk46.dim(`No comments on item #${id}.`));
4695
4655
  return;
4696
4656
  }
4697
- console.log(chalk49.bold(`Comments for #${id}: ${item.name}
4657
+ console.log(chalk46.bold(`Comments for #${id}: ${item.name}
4698
4658
  `));
4699
4659
  for (const entry of entries) {
4700
4660
  console.log(`${formatComment(entry)}
@@ -4703,7 +4663,7 @@ async function comments2(id) {
4703
4663
  }
4704
4664
 
4705
4665
  // src/commands/backlog/delete-comment/index.ts
4706
- import chalk50 from "chalk";
4666
+ import chalk47 from "chalk";
4707
4667
  async function deleteCommentCmd(id, commentId) {
4708
4668
  const found = await findOneItem(id);
4709
4669
  if (!found) process.exit(1);
@@ -4715,16 +4675,16 @@ async function deleteCommentCmd(id, commentId) {
4715
4675
  switch (outcome) {
4716
4676
  case "deleted":
4717
4677
  console.log(
4718
- chalk50.green(`Comment #${commentId} deleted from item #${id}.`)
4678
+ chalk47.green(`Comment #${commentId} deleted from item #${id}.`)
4719
4679
  );
4720
4680
  break;
4721
4681
  case "not-found":
4722
- console.log(chalk50.red(`Comment #${commentId} not found on item #${id}.`));
4682
+ console.log(chalk47.red(`Comment #${commentId} not found on item #${id}.`));
4723
4683
  process.exit(1);
4724
4684
  break;
4725
4685
  case "is-summary":
4726
4686
  console.log(
4727
- chalk50.red(
4687
+ chalk47.red(
4728
4688
  `Comment #${commentId} is a phase summary and cannot be deleted.`
4729
4689
  )
4730
4690
  );
@@ -4742,7 +4702,7 @@ function registerCommentCommands(cmd) {
4742
4702
 
4743
4703
  // src/commands/backlog/export/index.ts
4744
4704
  import { writeFile } from "fs/promises";
4745
- import chalk51 from "chalk";
4705
+ import chalk48 from "chalk";
4746
4706
 
4747
4707
  // src/commands/backlog/dump/DumpTable.ts
4748
4708
  var DUMP_FORMAT = "assist-backlog-dump";
@@ -4809,7 +4769,7 @@ async function exportBacklog(file) {
4809
4769
  if (file) {
4810
4770
  await writeFile(file, dump);
4811
4771
  console.error(
4812
- chalk51.green(`Exported backlog to ${file} (${dump.length} bytes).`)
4772
+ chalk48.green(`Exported backlog to ${file} (${dump.length} bytes).`)
4813
4773
  );
4814
4774
  return;
4815
4775
  }
@@ -4825,7 +4785,7 @@ function registerExportCommand(cmd) {
4825
4785
 
4826
4786
  // src/commands/backlog/import/index.ts
4827
4787
  import { readFile } from "fs/promises";
4828
- import chalk53 from "chalk";
4788
+ import chalk50 from "chalk";
4829
4789
 
4830
4790
  // src/commands/backlog/dump/countCopyRows.ts
4831
4791
  function countCopyRows(data) {
@@ -4902,7 +4862,7 @@ function validateDump({ header, sections }) {
4902
4862
  }
4903
4863
 
4904
4864
  // src/commands/backlog/import/confirmReplace.ts
4905
- import chalk52 from "chalk";
4865
+ import chalk49 from "chalk";
4906
4866
  async function countRows(client, table) {
4907
4867
  const { rows } = await client.query(
4908
4868
  `SELECT count(*)::int AS n FROM ${table}`
@@ -4913,7 +4873,7 @@ function printSummary(current, incoming) {
4913
4873
  const lines = DUMP_TABLES.map(
4914
4874
  (t, i) => ` ${t.name}: ${current[i]} \u2192 ${incoming[i]} rows`
4915
4875
  );
4916
- console.error(chalk52.bold("\nThis will REPLACE all backlog data:"));
4876
+ console.error(chalk49.bold("\nThis will REPLACE all backlog data:"));
4917
4877
  console.error(`${lines.join("\n")}
4918
4878
  `);
4919
4879
  }
@@ -4989,13 +4949,13 @@ async function importBacklog(file, options2 = {}) {
4989
4949
  );
4990
4950
  await withBacklogClient(async (client) => {
4991
4951
  if (!options2.yes && !await confirmReplace(client, incoming, !file)) {
4992
- console.error(chalk53.yellow("Import cancelled; no changes made."));
4952
+ console.error(chalk50.yellow("Import cancelled; no changes made."));
4993
4953
  return;
4994
4954
  }
4995
4955
  await restore(client, parsed);
4996
4956
  const total = incoming.reduce((sum, n) => sum + n, 0);
4997
4957
  console.error(
4998
- chalk53.green(
4958
+ chalk50.green(
4999
4959
  `Imported backlog: ${total} rows restored across ${DUMP_TABLES.length} tables.`
5000
4960
  )
5001
4961
  );
@@ -5012,16 +4972,16 @@ function registerImportCommand(cmd) {
5012
4972
  }
5013
4973
 
5014
4974
  // src/commands/backlog/add/index.ts
5015
- import chalk54 from "chalk";
4975
+ import chalk51 from "chalk";
5016
4976
 
5017
4977
  // src/commands/backlog/add/shared.ts
5018
4978
  import { spawnSync } from "child_process";
5019
- import { mkdtempSync, readFileSync as readFileSync14, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "fs";
4979
+ import { mkdtempSync, readFileSync as readFileSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync13 } from "fs";
5020
4980
  import { tmpdir } from "os";
5021
4981
  import { join as join17 } from "path";
5022
- import enquirer7 from "enquirer";
4982
+ import enquirer6 from "enquirer";
5023
4983
  async function promptType() {
5024
- const { type } = await enquirer7.prompt({
4984
+ const { type } = await enquirer6.prompt({
5025
4985
  type: "select",
5026
4986
  name: "type",
5027
4987
  message: "Type:",
@@ -5031,7 +4991,7 @@ async function promptType() {
5031
4991
  return type;
5032
4992
  }
5033
4993
  async function promptName() {
5034
- const { name } = await enquirer7.prompt({
4994
+ const { name } = await enquirer6.prompt({
5035
4995
  type: "input",
5036
4996
  name: "name",
5037
4997
  message: "Name:",
@@ -5040,14 +5000,14 @@ async function promptName() {
5040
5000
  return name.trim();
5041
5001
  }
5042
5002
  async function promptDescription() {
5043
- const { useEditor } = await enquirer7.prompt({
5003
+ const { useEditor } = await enquirer6.prompt({
5044
5004
  type: "confirm",
5045
5005
  name: "useEditor",
5046
5006
  message: "Open editor for description?",
5047
5007
  initial: false
5048
5008
  });
5049
5009
  if (!useEditor) {
5050
- const { description } = await enquirer7.prompt({
5010
+ const { description } = await enquirer6.prompt({
5051
5011
  type: "input",
5052
5012
  name: "description",
5053
5013
  message: "Description (optional):"
@@ -5063,17 +5023,17 @@ function openEditor() {
5063
5023
  writeFileSync13(filePath, "");
5064
5024
  const result = spawnSync(editor, [filePath], { stdio: "inherit" });
5065
5025
  if (result.status !== 0) {
5066
- unlinkSync4(filePath);
5026
+ unlinkSync5(filePath);
5067
5027
  return void 0;
5068
5028
  }
5069
5029
  const content = readFileSync14(filePath, "utf-8").trim();
5070
- unlinkSync4(filePath);
5030
+ unlinkSync5(filePath);
5071
5031
  return content || void 0;
5072
5032
  }
5073
5033
  async function promptAcceptanceCriteria() {
5074
5034
  const criteria = [];
5075
5035
  for (; ; ) {
5076
- const { criterion } = await enquirer7.prompt({
5036
+ const { criterion } = await enquirer6.prompt({
5077
5037
  type: "input",
5078
5038
  name: "criterion",
5079
5039
  message: "Acceptance criterion (empty to finish):"
@@ -5102,11 +5062,11 @@ async function add(options2) {
5102
5062
  },
5103
5063
  getOrigin()
5104
5064
  );
5105
- console.log(chalk54.green(`Added item #${id}: ${name}`));
5065
+ console.log(chalk51.green(`Added item #${id}: ${name}`));
5106
5066
  }
5107
5067
 
5108
5068
  // src/commands/backlog/addPhase.ts
5109
- import chalk56 from "chalk";
5069
+ import chalk53 from "chalk";
5110
5070
 
5111
5071
  // src/commands/backlog/insertPhaseAt.ts
5112
5072
  import { count, eq as eq14 } from "drizzle-orm";
@@ -5139,7 +5099,7 @@ async function insertPhaseAt(orm, itemId, phaseIdx, name, tasks, manualChecks, c
5139
5099
  }
5140
5100
 
5141
5101
  // src/commands/backlog/resolveInsertPosition.ts
5142
- import chalk55 from "chalk";
5102
+ import chalk52 from "chalk";
5143
5103
  import { count as count2, eq as eq15 } from "drizzle-orm";
5144
5104
  async function resolveInsertPosition(orm, itemId, position) {
5145
5105
  const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(eq15(planPhases.itemId, itemId));
@@ -5148,7 +5108,7 @@ async function resolveInsertPosition(orm, itemId, position) {
5148
5108
  const pos = Number.parseInt(position, 10);
5149
5109
  if (pos < 1 || pos > phaseCount + 1) {
5150
5110
  console.log(
5151
- chalk55.red(
5111
+ chalk52.red(
5152
5112
  `Position ${pos} is out of range. Must be between 1 and ${phaseCount + 1}.`
5153
5113
  )
5154
5114
  );
@@ -5169,7 +5129,7 @@ async function addPhase(id, name, options2) {
5169
5129
  if (!found) return;
5170
5130
  const tasks = options2.task ?? [];
5171
5131
  if (tasks.length === 0) {
5172
- console.log(chalk56.red("At least one --task is required."));
5132
+ console.log(chalk53.red("At least one --task is required."));
5173
5133
  process.exitCode = 1;
5174
5134
  return;
5175
5135
  }
@@ -5188,14 +5148,14 @@ async function addPhase(id, name, options2) {
5188
5148
  );
5189
5149
  const verb = options2.position !== void 0 ? "Inserted" : "Added";
5190
5150
  console.log(
5191
- chalk56.green(
5151
+ chalk53.green(
5192
5152
  `${verb} phase ${phaseIdx + 1} "${name}" to item #${itemId} with ${tasks.length} task(s).`
5193
5153
  )
5194
5154
  );
5195
5155
  }
5196
5156
 
5197
5157
  // src/commands/backlog/list/index.ts
5198
- import chalk57 from "chalk";
5158
+ import chalk54 from "chalk";
5199
5159
 
5200
5160
  // src/commands/backlog/originDisplayName.ts
5201
5161
  function originDisplayName(origin) {
@@ -5247,7 +5207,7 @@ async function list2(options2) {
5247
5207
  const allItems = await loadBacklog(options2.allRepos);
5248
5208
  const items2 = filterItems(allItems, options2);
5249
5209
  if (items2.length === 0) {
5250
- console.log(chalk57.dim("Backlog is empty."));
5210
+ console.log(chalk54.dim("Backlog is empty."));
5251
5211
  return;
5252
5212
  }
5253
5213
  const labels = originDisplayLabels(
@@ -5256,9 +5216,9 @@ async function list2(options2) {
5256
5216
  const repoNameOf = (item) => item.origin ? labels.get(item.origin) ?? "" : "";
5257
5217
  const prefixWidth = options2.allRepos ? Math.max(0, ...items2.map((i) => repoNameOf(i).length)) : 0;
5258
5218
  for (const item of items2) {
5259
- const repoPrefix2 = options2.allRepos ? `${chalk57.dim(repoNameOf(item).padEnd(prefixWidth))} ` : "";
5219
+ const repoPrefix2 = options2.allRepos ? `${chalk54.dim(repoNameOf(item).padEnd(prefixWidth))} ` : "";
5260
5220
  console.log(
5261
- `${repoPrefix2}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk57.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
5221
+ `${repoPrefix2}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk54.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
5262
5222
  );
5263
5223
  if (options2.verbose) {
5264
5224
  printVerboseDetails(item);
@@ -5284,7 +5244,7 @@ function registerItemCommands(cmd) {
5284
5244
  }
5285
5245
 
5286
5246
  // src/commands/backlog/link.ts
5287
- import chalk59 from "chalk";
5247
+ import chalk56 from "chalk";
5288
5248
 
5289
5249
  // src/commands/backlog/hasCycle.ts
5290
5250
  function hasCycle(adjacency, fromId, toId) {
@@ -5316,14 +5276,14 @@ async function loadDependencyGraph(orm) {
5316
5276
  }
5317
5277
 
5318
5278
  // src/commands/backlog/validateLinkTarget.ts
5319
- import chalk58 from "chalk";
5279
+ import chalk55 from "chalk";
5320
5280
  function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
5321
5281
  const duplicate = (fromItem.links ?? []).some(
5322
5282
  (l) => l.targetId === toNum && l.type === linkType
5323
5283
  );
5324
5284
  if (duplicate) {
5325
5285
  console.log(
5326
- chalk58.yellow(`Link already exists: #${fromNum} ${linkType} #${toNum}`)
5286
+ chalk55.yellow(`Link already exists: #${fromNum} ${linkType} #${toNum}`)
5327
5287
  );
5328
5288
  return false;
5329
5289
  }
@@ -5332,7 +5292,7 @@ function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
5332
5292
 
5333
5293
  // src/commands/backlog/link.ts
5334
5294
  function fail(message) {
5335
- console.log(chalk59.red(message));
5295
+ console.log(chalk56.red(message));
5336
5296
  return void 0;
5337
5297
  }
5338
5298
  function parseLinkType(type) {
@@ -5362,12 +5322,12 @@ async function link(fromId, toId, opts) {
5362
5322
  if (await createsCycle(orm, linkType, fromNum, toNum)) return;
5363
5323
  await orm.insert(links).values({ itemId: fromNum, type: linkType, targetId: toNum });
5364
5324
  console.log(
5365
- chalk59.green(`Linked #${fromNum} ${linkType} #${toNum} (${toItem.name})`)
5325
+ chalk56.green(`Linked #${fromNum} ${linkType} #${toNum} (${toItem.name})`)
5366
5326
  );
5367
5327
  }
5368
5328
 
5369
5329
  // src/commands/backlog/unlink.ts
5370
- import chalk60 from "chalk";
5330
+ import chalk57 from "chalk";
5371
5331
  import { and as and4, eq as eq17 } from "drizzle-orm";
5372
5332
  async function unlink(fromId, toId) {
5373
5333
  const fromNum = Number.parseInt(fromId, 10);
@@ -5375,19 +5335,19 @@ async function unlink(fromId, toId) {
5375
5335
  const { orm } = await getReady();
5376
5336
  const fromItem = await loadItem(orm, fromNum);
5377
5337
  if (!fromItem) {
5378
- console.log(chalk60.red(`Item #${fromId} not found.`));
5338
+ console.log(chalk57.red(`Item #${fromId} not found.`));
5379
5339
  return;
5380
5340
  }
5381
5341
  if (!fromItem.links || fromItem.links.length === 0) {
5382
- console.log(chalk60.yellow(`No links found on item #${fromId}.`));
5342
+ console.log(chalk57.yellow(`No links found on item #${fromId}.`));
5383
5343
  return;
5384
5344
  }
5385
5345
  if (!fromItem.links.some((l) => l.targetId === toNum)) {
5386
- console.log(chalk60.yellow(`No link from #${fromId} to #${toId} found.`));
5346
+ console.log(chalk57.yellow(`No link from #${fromId} to #${toId} found.`));
5387
5347
  return;
5388
5348
  }
5389
5349
  await orm.delete(links).where(and4(eq17(links.itemId, fromNum), eq17(links.targetId, toNum)));
5390
- console.log(chalk60.green(`Removed link from #${fromId} to #${toId}.`));
5350
+ console.log(chalk57.green(`Removed link from #${fromId} to #${toId}.`));
5391
5351
  }
5392
5352
 
5393
5353
  // src/commands/backlog/registerLinkCommands.ts
@@ -5401,17 +5361,17 @@ function registerLinkCommands(cmd) {
5401
5361
  }
5402
5362
 
5403
5363
  // src/commands/backlog/move-repo/index.ts
5404
- import chalk62 from "chalk";
5364
+ import chalk59 from "chalk";
5405
5365
  import { eq as eq19 } from "drizzle-orm";
5406
5366
 
5407
5367
  // src/commands/backlog/move-repo/confirmMove.ts
5408
- import chalk61 from "chalk";
5368
+ import chalk58 from "chalk";
5409
5369
  function pluralItems(n) {
5410
5370
  return `${n} item${n === 1 ? "" : "s"}`;
5411
5371
  }
5412
5372
  async function confirmMove(cnt, oldOrigin, newOrigin) {
5413
5373
  console.log(
5414
- `${pluralItems(cnt)}: ${chalk61.cyan(oldOrigin)} \u2192 ${chalk61.cyan(newOrigin)}`
5374
+ `${pluralItems(cnt)}: ${chalk58.cyan(oldOrigin)} \u2192 ${chalk58.cyan(newOrigin)}`
5415
5375
  );
5416
5376
  return promptConfirm(`Retag ${pluralItems(cnt)}?`);
5417
5377
  }
@@ -5443,7 +5403,7 @@ Pass the full origin.`
5443
5403
 
5444
5404
  // src/commands/backlog/move-repo/index.ts
5445
5405
  function fail2(message) {
5446
- console.log(chalk62.red(message));
5406
+ console.log(chalk59.red(message));
5447
5407
  process.exitCode = 1;
5448
5408
  }
5449
5409
  async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
@@ -5459,12 +5419,12 @@ async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
5459
5419
  }
5460
5420
  const cnt = await countByOrigin(orm, oldOrigin);
5461
5421
  if (!options2.yes && !await confirmMove(cnt, oldOrigin, newOrigin)) {
5462
- console.log(chalk62.yellow("Move cancelled; no changes made."));
5422
+ console.log(chalk59.yellow("Move cancelled; no changes made."));
5463
5423
  return;
5464
5424
  }
5465
5425
  await orm.update(items).set({ origin: newOrigin }).where(eq19(items.origin, oldOrigin));
5466
5426
  console.log(
5467
- chalk62.green(
5427
+ chalk59.green(
5468
5428
  `Moved ${pluralItems(cnt)} from "${oldOrigin}" to "${newOrigin}".`
5469
5429
  )
5470
5430
  );
@@ -5479,12 +5439,116 @@ function registerMoveRepoCommand(cmd) {
5479
5439
  );
5480
5440
  }
5481
5441
 
5442
+ // src/commands/backlog/registerNextCommand.ts
5443
+ function registerNextCommand(cmd) {
5444
+ cmd.command("next").argument("[id]", "Backlog item ID to run first").description("Pick and run the next backlog item, or open /draft if none").option("-w, --write", "Run Claude with auto permission mode (default)").option("--no-write", "Run Claude without auto permission mode").option("--once", "Exit after the first completed item run").action(
5445
+ (id, opts) => next({ allowEdits: opts.write !== false, once: opts.once }, id)
5446
+ );
5447
+ }
5448
+
5482
5449
  // src/commands/backlog/registerPlanCommands.ts
5483
5450
  function registerPlanCommands(cmd) {
5484
5451
  cmd.command("plan <id>").description("Display the plan for a backlog item").action(plan);
5485
5452
  cmd.command("phase-done <id> <phase> <summary>").description("Signal that a plan phase is complete").action(phaseDone);
5486
5453
  }
5487
5454
 
5455
+ // src/commands/backlog/refine.ts
5456
+ import chalk62 from "chalk";
5457
+ import enquirer7 from "enquirer";
5458
+
5459
+ // src/commands/backlog/launchMode.ts
5460
+ import chalk61 from "chalk";
5461
+
5462
+ // src/commands/backlog/tryRunById.ts
5463
+ import chalk60 from "chalk";
5464
+ async function tryRunById(id, options2) {
5465
+ const items2 = await loadBacklog();
5466
+ const numericId = Number.parseInt(id, 10);
5467
+ const item = Number.isNaN(numericId) ? void 0 : items2.find((i) => i.id === numericId);
5468
+ if (!item) {
5469
+ console.log(chalk60.red(`Item #${id} not found.`));
5470
+ return false;
5471
+ }
5472
+ if (item.status === "done") {
5473
+ console.log(chalk60.red(`Item #${id} is already done.`));
5474
+ return false;
5475
+ }
5476
+ if (item.status === "wontdo") {
5477
+ console.log(chalk60.red(`Item #${id} is marked won't do.`));
5478
+ return false;
5479
+ }
5480
+ if (isBlocked(item, items2)) {
5481
+ console.log(
5482
+ chalk60.red(`Item #${id} is blocked by unresolved dependencies.`)
5483
+ );
5484
+ return false;
5485
+ }
5486
+ console.log(chalk60.bold(`
5487
+ Running backlog item #${id}...
5488
+ `));
5489
+ await run2(id, options2);
5490
+ return true;
5491
+ }
5492
+
5493
+ // src/commands/backlog/launchMode.ts
5494
+ async function launchMode(slashCommand, options2) {
5495
+ pullIfConfigured();
5496
+ process.env.ASSIST_SESSION_ID = String(process.pid);
5497
+ const { child, done: done2 } = spawnClaude(`/${slashCommand}`, { allowEdits: true });
5498
+ watchForMarker(child, { actOnDone: options2?.once });
5499
+ await done2;
5500
+ stopWatching();
5501
+ const signal = readSignal();
5502
+ cleanupSignal();
5503
+ if (signal?.event === "next") {
5504
+ if (typeof signal.id === "string" && signal.id) {
5505
+ if (await tryRunById(signal.id, { allowEdits: true })) return;
5506
+ }
5507
+ console.log(chalk61.bold("\nChaining into assist next...\n"));
5508
+ await next({ allowEdits: true, once: options2?.once });
5509
+ }
5510
+ }
5511
+
5512
+ // src/commands/backlog/refine.ts
5513
+ async function pickItemForRefine() {
5514
+ const items2 = await loadBacklog();
5515
+ const active = items2.filter(
5516
+ (i) => i.status === "todo" || i.status === "in-progress"
5517
+ );
5518
+ if (active.length === 0) {
5519
+ console.log(chalk62.yellow("No active backlog items to refine."));
5520
+ return void 0;
5521
+ }
5522
+ if (active.length === 1) {
5523
+ const item = active[0];
5524
+ console.log(chalk62.bold(`Auto-selecting item #${item.id}: ${item.name}`));
5525
+ return String(item.id);
5526
+ }
5527
+ const { selected } = await exitOnCancel(
5528
+ enquirer7.prompt({
5529
+ type: "select",
5530
+ name: "selected",
5531
+ message: "Choose a backlog item to refine:",
5532
+ choices: active.map((item) => ({
5533
+ name: `${typeLabel(item.type)} #${item.id}: ${item.name}`
5534
+ }))
5535
+ })
5536
+ );
5537
+ return selected.match(/#(\d+)/)?.[1] ?? "";
5538
+ }
5539
+ async function refine(id, options2) {
5540
+ const itemId = id ?? await pickItemForRefine();
5541
+ if (!itemId) return;
5542
+ await launchMode(`refine ${itemId}`, options2);
5543
+ }
5544
+
5545
+ // src/commands/backlog/registerRefineCommand.ts
5546
+ function registerRefineCommand(cmd) {
5547
+ cmd.command("refine").argument("[id]", "Backlog item ID").description("Alias for refine").option("--once", "Exit when the initial task completes").action(
5548
+ (id, opts) => refine(id, { once: opts.once })
5549
+ );
5550
+ }
5551
+
5488
5552
  // src/commands/backlog/rewindPhase.ts
5489
5553
  import chalk63 from "chalk";
5490
5554
  function validateRewind2(item, phaseNumber) {
@@ -6021,14 +6085,6 @@ function registerShowCommands(cmd) {
6021
6085
  function registerWebCommand(cmd) {
6022
6086
  cmd.command("web").description("Open the backlog tab in the web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web2);
6023
6087
  }
6024
- function registerRefineCommand(cmd) {
6025
- cmd.command("refine").argument("[id]", "Backlog item ID").description("Alias for refine").action((id) => refine(id));
6026
- }
6027
- function registerNextCommand(cmd) {
6028
- cmd.command("next").argument("[id]", "Backlog item ID to run first").description("Pick and run the next backlog item, or open /draft if none").option("-w, --write", "Run Claude with auto permission mode (default)").option("--no-write", "Run Claude without auto permission mode").action(
6029
- (id, opts) => next({ allowEdits: opts.write !== false }, id)
6030
- );
6031
- }
6032
6088
  var registrars = [
6033
6089
  registerItemCommands,
6034
6090
  registerShowCommands,
@@ -8300,7 +8356,7 @@ function registerDevlog(program2) {
8300
8356
  }
8301
8357
 
8302
8358
  // src/commands/dotnet/checkBuildLocks.ts
8303
- import { closeSync, openSync as openSync2, readdirSync as readdirSync2 } from "fs";
8359
+ import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync2 } from "fs";
8304
8360
  import { join as join25 } from "path";
8305
8361
  import chalk95 from "chalk";
8306
8362
 
@@ -8332,7 +8388,7 @@ function isLockedDll(debugDir) {
8332
8388
  const dllPath = join25(debugDir, file);
8333
8389
  try {
8334
8390
  const fd = openSync2(dllPath, "r+");
8335
- closeSync(fd);
8391
+ closeSync2(fd);
8336
8392
  } catch {
8337
8393
  return dllPath;
8338
8394
  }
@@ -8422,7 +8478,7 @@ function collectAllDeps(node) {
8422
8478
  }
8423
8479
 
8424
8480
  // src/commands/dotnet/findContainingSolutions.ts
8425
- import { readdirSync as readdirSync3, readFileSync as readFileSync22, statSync } from "fs";
8481
+ import { readdirSync as readdirSync3, readFileSync as readFileSync22, statSync as statSync2 } from "fs";
8426
8482
  import path22 from "path";
8427
8483
  function findSlnFiles(dir, maxDepth, depth = 0) {
8428
8484
  if (depth > maxDepth) return [];
@@ -8438,7 +8494,7 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
8438
8494
  continue;
8439
8495
  const full = path22.join(dir, entry);
8440
8496
  try {
8441
- const stat = statSync(full);
8497
+ const stat = statSync2(full);
8442
8498
  if (stat.isFile() && entry.endsWith(".sln")) {
8443
8499
  results.push(full);
8444
8500
  } else if (stat.isDirectory()) {
@@ -8775,7 +8831,7 @@ function parseInspectReport(json) {
8775
8831
 
8776
8832
  // src/commands/dotnet/runInspectCode.ts
8777
8833
  import { execSync as execSync24 } from "child_process";
8778
- import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync5 } from "fs";
8834
+ import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync6 } from "fs";
8779
8835
  import { tmpdir as tmpdir3 } from "os";
8780
8836
  import path25 from "path";
8781
8837
  import chalk102 from "chalk";
@@ -8811,7 +8867,7 @@ function runInspectCode(slnPath, include, swea) {
8811
8867
  process.exit(1);
8812
8868
  }
8813
8869
  const xml = readFileSync23(reportPath, "utf-8");
8814
- unlinkSync5(reportPath);
8870
+ unlinkSync6(reportPath);
8815
8871
  return xml;
8816
8872
  }
8817
8873
 
@@ -8937,7 +8993,7 @@ function aggregateCommitters(authorLists) {
8937
8993
 
8938
8994
  // src/shared/runGhGraphql.ts
8939
8995
  import { spawnSync as spawnSync2 } from "child_process";
8940
- import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync18 } from "fs";
8996
+ import { unlinkSync as unlinkSync7, writeFileSync as writeFileSync18 } from "fs";
8941
8997
  import { tmpdir as tmpdir4 } from "os";
8942
8998
  import { join as join27 } from "path";
8943
8999
  function buildArgs(queryFile, vars) {
@@ -8958,7 +9014,7 @@ function runGhGraphql(mutation, vars) {
8958
9014
  if (result.status !== 0) throw new Error(result.stderr || result.stdout);
8959
9015
  return result.stdout;
8960
9016
  } finally {
8961
- unlinkSync6(queryFile);
9017
+ unlinkSync7(queryFile);
8962
9018
  }
8963
9019
  }
8964
9020
 
@@ -9598,13 +9654,21 @@ async function reviewComments(number) {
9598
9654
 
9599
9655
  // src/commands/registerLaunch.ts
9600
9656
  function registerLaunch(program2) {
9601
- program2.command("next").argument("[id]", "Backlog item ID to run first").description("Alias for backlog next").action((id) => next({ allowEdits: true }, id));
9657
+ program2.command("next").argument("[id]", "Backlog item ID to run first").description("Alias for backlog next").option("--once", "Exit after the first completed item run").action(
9658
+ (id, opts) => next({ allowEdits: true, once: opts.once }, id)
9659
+ );
9602
9660
  program2.command("draft").alias("feat").description(
9603
9661
  "Launch Claude in /draft mode, chain into next on /next signal"
9604
- ).action(() => launchMode("draft"));
9605
- program2.command("bug").description("Launch Claude in /bug mode, chain into next on /next signal").action(() => launchMode("bug"));
9662
+ ).option("--once", "Exit when the initial task completes").action(
9663
+ (opts) => launchMode("draft", { once: opts.once })
9664
+ );
9665
+ program2.command("bug").description("Launch Claude in /bug mode, chain into next on /next signal").option("--once", "Exit when the initial task completes").action(
9666
+ (opts) => launchMode("bug", { once: opts.once })
9667
+ );
9606
9668
  program2.command("review-comments").argument("[number]", "PR number to check out first").description("Launch Claude in /review-comments mode (single session)").action((number) => reviewComments(number));
9607
- program2.command("refine").argument("[id]", "Backlog item ID").description("Launch Claude in /refine mode to refine a backlog item").action((id) => refine(id));
9669
+ program2.command("refine").argument("[id]", "Backlog item ID").description("Launch Claude in /refine mode to refine a backlog item").option("--once", "Exit when the initial task completes").action(
9670
+ (id, opts) => refine(id, { once: opts.once })
9671
+ );
9608
9672
  }
9609
9673
 
9610
9674
  // src/commands/registerList.ts
@@ -10244,12 +10308,12 @@ import { execSync as execSync32 } from "child_process";
10244
10308
 
10245
10309
  // src/commands/prs/resolveCommentWithReply.ts
10246
10310
  import { execSync as execSync31 } from "child_process";
10247
- import { unlinkSync as unlinkSync8, writeFileSync as writeFileSync21 } from "fs";
10311
+ import { unlinkSync as unlinkSync9, writeFileSync as writeFileSync21 } from "fs";
10248
10312
  import { tmpdir as tmpdir5 } from "os";
10249
10313
  import { join as join32 } from "path";
10250
10314
 
10251
10315
  // src/commands/prs/loadCommentsCache.ts
10252
- import { existsSync as existsSync33, readFileSync as readFileSync28, unlinkSync as unlinkSync7 } from "fs";
10316
+ import { existsSync as existsSync33, readFileSync as readFileSync28, unlinkSync as unlinkSync8 } from "fs";
10253
10317
  import { join as join31 } from "path";
10254
10318
  import { parse as parse2 } from "yaml";
10255
10319
  function getCachePath(prNumber) {
@@ -10266,7 +10330,7 @@ function loadCommentsCache(prNumber) {
10266
10330
  function deleteCommentsCache(prNumber) {
10267
10331
  const cachePath = getCachePath(prNumber);
10268
10332
  if (existsSync33(cachePath)) {
10269
- unlinkSync7(cachePath);
10333
+ unlinkSync8(cachePath);
10270
10334
  console.log("No more unresolved line comments. Cache dropped.");
10271
10335
  }
10272
10336
  }
@@ -10288,7 +10352,7 @@ function resolveThread(threadId) {
10288
10352
  { stdio: ["inherit", "pipe", "inherit"] }
10289
10353
  );
10290
10354
  } finally {
10291
- unlinkSync8(queryFile);
10355
+ unlinkSync9(queryFile);
10292
10356
  }
10293
10357
  }
10294
10358
  function requireCache(prNumber) {
@@ -10368,7 +10432,7 @@ import { stringify } from "yaml";
10368
10432
 
10369
10433
  // src/commands/prs/fetchThreadIds.ts
10370
10434
  import { execSync as execSync33 } from "child_process";
10371
- import { unlinkSync as unlinkSync9, writeFileSync as writeFileSync22 } from "fs";
10435
+ import { unlinkSync as unlinkSync10, writeFileSync as writeFileSync22 } from "fs";
10372
10436
  import { tmpdir as tmpdir6 } from "os";
10373
10437
  import { join as join33 } from "path";
10374
10438
  var THREAD_QUERY = `query($owner: String!, $repo: String!, $prNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $prNumber) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 100) { nodes { databaseId } } } } } } }`;
@@ -10393,7 +10457,7 @@ function fetchThreadIds(org, repo, prNumber) {
10393
10457
  }
10394
10458
  return { threadMap, resolvedThreadIds };
10395
10459
  } finally {
10396
- unlinkSync9(queryFile);
10460
+ unlinkSync10(queryFile);
10397
10461
  }
10398
10462
  }
10399
10463
 
@@ -13301,10 +13365,10 @@ async function handlePostSynthesis(synthesisPath, options2) {
13301
13365
  }
13302
13366
 
13303
13367
  // src/commands/review/prepareReviewDir.ts
13304
- import { existsSync as existsSync36, mkdirSync as mkdirSync11, unlinkSync as unlinkSync10, writeFileSync as writeFileSync25 } from "fs";
13368
+ import { existsSync as existsSync36, mkdirSync as mkdirSync11, unlinkSync as unlinkSync11, writeFileSync as writeFileSync25 } from "fs";
13305
13369
  function clearReviewFiles(paths) {
13306
13370
  for (const path54 of [paths.claudePath, paths.codexPath, paths.synthesisPath]) {
13307
- if (existsSync36(path54)) unlinkSync10(path54);
13371
+ if (existsSync36(path54)) unlinkSync11(path54);
13308
13372
  }
13309
13373
  }
13310
13374
  function prepareReviewDir(paths, requestBody, force) {
@@ -13372,11 +13436,11 @@ async function runBacklogSession(synthesisPath) {
13372
13436
  }
13373
13437
 
13374
13438
  // src/commands/review/cachedReviewerResult.ts
13375
- import { statSync as statSync2 } from "fs";
13439
+ import { statSync as statSync3 } from "fs";
13376
13440
  function cachedReviewerResult(name, outputPath) {
13377
13441
  let size;
13378
13442
  try {
13379
- size = statSync2(outputPath).size;
13443
+ size = statSync3(outputPath).size;
13380
13444
  } catch {
13381
13445
  return null;
13382
13446
  }
@@ -13589,7 +13653,7 @@ function printReviewerFailures(results) {
13589
13653
  }
13590
13654
 
13591
13655
  // src/commands/review/runAndSynthesise.ts
13592
- import { existsSync as existsSync38, unlinkSync as unlinkSync12 } from "fs";
13656
+ import { existsSync as existsSync38, unlinkSync as unlinkSync13 } from "fs";
13593
13657
 
13594
13658
  // src/commands/review/buildReviewerStdin.ts
13595
13659
  var REVIEW_PROMPT = `You are acting as a reviewer for a proposed code change made by another engineer. The full review request \u2014 branch, base, changed files, and unified diff \u2014 is in request.md in the current working directory.
@@ -14007,7 +14071,7 @@ function resolveClaude(args) {
14007
14071
  }
14008
14072
 
14009
14073
  // src/commands/review/runCodexReviewer.ts
14010
- import { existsSync as existsSync37, unlinkSync as unlinkSync11 } from "fs";
14074
+ import { existsSync as existsSync37, unlinkSync as unlinkSync12 } from "fs";
14011
14075
 
14012
14076
  // src/commands/review/parseCodexEvent.ts
14013
14077
  function isItemStarted(value) {
@@ -14062,7 +14126,7 @@ async function runCodexReviewer(spec) {
14062
14126
  }
14063
14127
  });
14064
14128
  if (result.exitCode !== 0 && existsSync37(spec.outputPath)) {
14065
- unlinkSync11(spec.outputPath);
14129
+ unlinkSync12(spec.outputPath);
14066
14130
  }
14067
14131
  return finaliseReviewerRun({ ...spec, command }, spinner, result);
14068
14132
  }
@@ -14210,7 +14274,7 @@ async function runAndSynthesise(args) {
14210
14274
  return { ok: false, failures };
14211
14275
  }
14212
14276
  if (anyFresh && existsSync38(paths.synthesisPath)) {
14213
- unlinkSync12(paths.synthesisPath);
14277
+ unlinkSync13(paths.synthesisPath);
14214
14278
  }
14215
14279
  const synthesisResult = await synthesise(paths, { multi });
14216
14280
  if (synthesisResult.exitCode !== 0) failures.push(synthesisResult);
@@ -14709,6 +14773,10 @@ function registerSignal(program2) {
14709
14773
  writeSignal("next", id ? { id } : void 0);
14710
14774
  console.log("Signal written.");
14711
14775
  });
14776
+ signalCommand.command("done").description("Write a done signal to end a --once launch session").action(() => {
14777
+ writeSignal("done");
14778
+ console.log("Signal written.");
14779
+ });
14712
14780
  }
14713
14781
 
14714
14782
  // src/commands/sql/sqlAuth.ts
@@ -14981,7 +15049,7 @@ function registerSql(program2) {
14981
15049
  }
14982
15050
 
14983
15051
  // src/commands/transcript/shared.ts
14984
- import { existsSync as existsSync39, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
15052
+ import { existsSync as existsSync39, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
14985
15053
  import { basename as basename6, join as join37, relative as relative2 } from "path";
14986
15054
  import * as readline2 from "readline";
14987
15055
  var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
@@ -15001,7 +15069,7 @@ function collectFiles(dir, extension) {
15001
15069
  const results = [];
15002
15070
  for (const entry of readdirSync6(dir)) {
15003
15071
  const fullPath = join37(dir, entry);
15004
- if (statSync3(fullPath).isDirectory()) {
15072
+ if (statSync4(fullPath).isDirectory()) {
15005
15073
  results.push(...collectFiles(fullPath, extension));
15006
15074
  } else if (entry.endsWith(extension)) {
15007
15075
  results.push(fullPath);
@@ -15875,7 +15943,7 @@ function status() {
15875
15943
  }
15876
15944
 
15877
15945
  // src/commands/voice/stop.ts
15878
- import { existsSync as existsSync47, readFileSync as readFileSync37, unlinkSync as unlinkSync13 } from "fs";
15946
+ import { existsSync as existsSync47, readFileSync as readFileSync37, unlinkSync as unlinkSync14 } from "fs";
15879
15947
  function stop2() {
15880
15948
  if (!existsSync47(voicePaths.pid)) {
15881
15949
  console.log("Voice daemon is not running (no PID file)");
@@ -15889,12 +15957,12 @@ function stop2() {
15889
15957
  console.log(`Voice daemon (PID ${pid}) is not running`);
15890
15958
  }
15891
15959
  try {
15892
- unlinkSync13(voicePaths.pid);
15960
+ unlinkSync14(voicePaths.pid);
15893
15961
  } catch {
15894
15962
  }
15895
15963
  try {
15896
15964
  const lockFile = getLockFile();
15897
- if (existsSync47(lockFile)) unlinkSync13(lockFile);
15965
+ if (existsSync47(lockFile)) unlinkSync14(lockFile);
15898
15966
  } catch {
15899
15967
  }
15900
15968
  console.log("Voice daemon stopped");
@@ -16116,7 +16184,7 @@ async function auth() {
16116
16184
 
16117
16185
  // src/commands/roam/postRoamActivity.ts
16118
16186
  import { execFileSync as execFileSync7 } from "child_process";
16119
- import { readdirSync as readdirSync7, readFileSync as readFileSync38, statSync as statSync4 } from "fs";
16187
+ import { readdirSync as readdirSync7, readFileSync as readFileSync38, statSync as statSync5 } from "fs";
16120
16188
  import { join as join48 } from "path";
16121
16189
  function findPortFile(roamDir) {
16122
16190
  let entries;
@@ -16128,7 +16196,7 @@ function findPortFile(roamDir) {
16128
16196
  const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
16129
16197
  const path54 = join48(roamDir, name);
16130
16198
  try {
16131
- return { path: path54, mtimeMs: statSync4(path54).mtimeMs };
16199
+ return { path: path54, mtimeMs: statSync5(path54).mtimeMs };
16132
16200
  } catch {
16133
16201
  return void 0;
16134
16202
  }
@@ -16477,7 +16545,7 @@ function link2() {
16477
16545
  }
16478
16546
 
16479
16547
  // src/commands/run/remove.ts
16480
- import { existsSync as existsSync49, unlinkSync as unlinkSync14 } from "fs";
16548
+ import { existsSync as existsSync49, unlinkSync as unlinkSync15 } from "fs";
16481
16549
  import { join as join51 } from "path";
16482
16550
  function findRemoveIndex() {
16483
16551
  const idx = process.argv.indexOf("remove");
@@ -16495,7 +16563,7 @@ function parseRemoveName() {
16495
16563
  function deleteCommandFile(name) {
16496
16564
  const filePath = join51(".claude", "commands", `${name}.md`);
16497
16565
  if (existsSync49(filePath)) {
16498
- unlinkSync14(filePath);
16566
+ unlinkSync15(filePath);
16499
16567
  console.log(`Deleted command file: ${filePath}`);
16500
16568
  }
16501
16569
  }
@@ -16530,7 +16598,7 @@ function registerRun(program2) {
16530
16598
 
16531
16599
  // src/commands/screenshot/index.ts
16532
16600
  import { execSync as execSync47 } from "child_process";
16533
- import { existsSync as existsSync50, mkdirSync as mkdirSync18, unlinkSync as unlinkSync15, writeFileSync as writeFileSync31 } from "fs";
16601
+ import { existsSync as existsSync50, mkdirSync as mkdirSync18, unlinkSync as unlinkSync16, writeFileSync as writeFileSync31 } from "fs";
16534
16602
  import { tmpdir as tmpdir7 } from "os";
16535
16603
  import { join as join52, resolve as resolve13 } from "path";
16536
16604
  import chalk156 from "chalk";
@@ -16677,7 +16745,7 @@ function runPowerShellScript(processName, outputPath) {
16677
16745
  { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
16678
16746
  );
16679
16747
  } finally {
16680
- unlinkSync15(scriptPath);
16748
+ unlinkSync16(scriptPath);
16681
16749
  }
16682
16750
  }
16683
16751
  function screenshot(processName) {
@@ -16695,51 +16763,108 @@ function screenshot(processName) {
16695
16763
  }
16696
16764
  }
16697
16765
 
16698
- // src/commands/sessions/daemon/daemonStatus.ts
16699
- import { existsSync as existsSync51, readFileSync as readFileSync39 } from "fs";
16700
- import { createInterface as createInterface4 } from "readline";
16701
- var STATUS_TIMEOUT_MS = 5e3;
16702
- async function daemonStatus() {
16703
- let socket;
16766
+ // src/commands/sessions/daemon/listDaemonPids.ts
16767
+ import { execFileSync as execFileSync9 } from "child_process";
16768
+ function listDaemonPids() {
16769
+ if (process.platform === "win32") return [];
16704
16770
  try {
16705
- socket = await connectToDaemon();
16771
+ const out = execFileSync9("ps", ["-eo", "pid=,args="], {
16772
+ encoding: "utf-8"
16773
+ });
16774
+ return out.split("\n").filter((line) => line.includes("assist") && / daemon run\b/.test(line)).map((line) => Number.parseInt(line.trim(), 10)).filter((pid) => Number.isInteger(pid));
16706
16775
  } catch {
16707
- console.log("Sessions daemon is not running");
16708
- return;
16709
- }
16710
- const sessions = await readSessionList(socket);
16711
- socket.destroy();
16712
- console.log(`Sessions daemon is running${describePid()}`);
16713
- if (sessions.length === 0) {
16714
- console.log("No sessions");
16715
- return;
16776
+ return [];
16716
16777
  }
16717
- for (const session of sessions) {
16718
- const restored = session.restored === false ? " (not restored)" : "";
16719
- console.log(` [${session.status}] ${session.name}${restored}`);
16720
- }
16721
- }
16722
- function describePid() {
16723
- if (!existsSync51(daemonPaths.pid)) return "";
16724
- return ` (PID ${readFileSync39(daemonPaths.pid, "utf-8").trim()})`;
16725
16778
  }
16726
- function readSessionList(socket) {
16779
+
16780
+ // src/commands/sessions/daemon/queryDaemon.ts
16781
+ import { createInterface as createInterface4 } from "readline";
16782
+ var STATUS_TIMEOUT_MS = 5e3;
16783
+ function queryDaemon(socket) {
16784
+ socket.write(`${JSON.stringify({ type: "ping" })}
16785
+ `);
16727
16786
  return new Promise((resolve16) => {
16728
- const timer = setTimeout(() => resolve16([]), STATUS_TIMEOUT_MS);
16787
+ const result = { sessions: [] };
16788
+ const pending = /* @__PURE__ */ new Set(["sessions", "pong"]);
16789
+ const timer = setTimeout(() => resolve16(result), STATUS_TIMEOUT_MS);
16729
16790
  const lines = createInterface4({ input: socket });
16730
16791
  lines.on("error", () => {
16731
16792
  });
16732
16793
  lines.on("line", (line) => {
16733
- try {
16734
- const data = JSON.parse(line);
16735
- if (data.type !== "sessions") return;
16794
+ applyLine(result, pending, line);
16795
+ if (pending.size === 0) {
16736
16796
  clearTimeout(timer);
16737
- resolve16(data.sessions ?? []);
16738
- } catch {
16797
+ resolve16(result);
16739
16798
  }
16740
16799
  });
16741
16800
  });
16742
16801
  }
16802
+ function applyLine(result, pending, line) {
16803
+ try {
16804
+ const data = JSON.parse(line);
16805
+ if (data.type === "sessions") {
16806
+ result.sessions = data.sessions ?? [];
16807
+ pending.delete("sessions");
16808
+ } else if (data.type === "pong") {
16809
+ result.pid = data.pid;
16810
+ pending.delete("pong");
16811
+ }
16812
+ } catch {
16813
+ }
16814
+ }
16815
+
16816
+ // src/commands/sessions/daemon/reportStolenSocket.ts
16817
+ import { readFileSync as readFileSync39 } from "fs";
16818
+ function reportStolenSocket(socketPid) {
16819
+ if (!socketPid) return;
16820
+ const filePid = readPidFile();
16821
+ if (filePid === void 0 || filePid === socketPid) return;
16822
+ console.error(
16823
+ `Warning: daemon.pid records PID ${filePid} but the socket is owned by PID ${socketPid} (stolen socket)`
16824
+ );
16825
+ }
16826
+ function readPidFile() {
16827
+ try {
16828
+ const pid = Number.parseInt(
16829
+ readFileSync39(daemonPaths.pid, "utf-8").trim(),
16830
+ 10
16831
+ );
16832
+ return Number.isInteger(pid) ? pid : void 0;
16833
+ } catch {
16834
+ return void 0;
16835
+ }
16836
+ }
16837
+
16838
+ // src/commands/sessions/daemon/daemonStatus.ts
16839
+ async function daemonStatus() {
16840
+ let socket;
16841
+ try {
16842
+ socket = await connectToDaemon();
16843
+ } catch {
16844
+ console.log("Sessions daemon is not running");
16845
+ reportStrays(listDaemonPids());
16846
+ return;
16847
+ }
16848
+ const { pid, sessions } = await queryDaemon(socket);
16849
+ socket.destroy();
16850
+ console.log(`Sessions daemon is running${pid ? ` (PID ${pid})` : ""}`);
16851
+ reportStolenSocket(pid);
16852
+ reportStrays(listDaemonPids().filter((p) => p !== pid));
16853
+ if (sessions.length === 0) {
16854
+ console.log("No sessions");
16855
+ return;
16856
+ }
16857
+ for (const session of sessions) {
16858
+ const restored = session.restored === false ? " (not restored)" : "";
16859
+ console.log(` [${session.status}] ${session.name}${restored}`);
16860
+ }
16861
+ }
16862
+ function reportStrays(pids) {
16863
+ if (pids.length === 0) return;
16864
+ console.error(
16865
+ `Warning: stray daemon process(es) detected: ${pids.join(", ")}`
16866
+ );
16867
+ }
16743
16868
 
16744
16869
  // src/commands/sessions/daemon/stopDaemon.ts
16745
16870
  var STOP_TIMEOUT_MS = 5e3;
@@ -16781,15 +16906,14 @@ function closedBeforeTimeout(socket) {
16781
16906
  // src/commands/sessions/daemon/restartDaemon.ts
16782
16907
  async function restartDaemon() {
16783
16908
  await stopDaemon();
16784
- await ensureDaemonRunning();
16909
+ await ensureDaemonRunning("daemon restart");
16785
16910
  console.log(
16786
16911
  "Sessions daemon restarted; previously running claude sessions will resume"
16787
16912
  );
16788
16913
  }
16789
16914
 
16790
16915
  // src/commands/sessions/daemon/runDaemon.ts
16791
- import { existsSync as existsSync53, mkdirSync as mkdirSync19, unlinkSync as unlinkSync16, writeFileSync as writeFileSync32 } from "fs";
16792
- import * as net2 from "net";
16916
+ import { mkdirSync as mkdirSync19 } from "fs";
16793
16917
 
16794
16918
  // src/commands/sessions/daemon/createAutoExit.ts
16795
16919
  var DEFAULT_GRACE_MS = 6e4;
@@ -16805,137 +16929,9 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
16805
16929
  };
16806
16930
  }
16807
16931
 
16808
- // src/commands/sessions/daemon/handleConnection.ts
16809
- import { createInterface as createInterface5 } from "readline";
16810
-
16811
- // src/commands/sessions/daemon/broadcast.ts
16812
- function sendTo(client, msg) {
16813
- client.send(JSON.stringify(msg));
16814
- }
16815
- function broadcast(clients, msg) {
16816
- const json = JSON.stringify(msg);
16817
- for (const client of clients) {
16818
- client.send(json);
16819
- }
16820
- }
16821
-
16822
- // src/commands/sessions/daemon/handleRunConfigs.ts
16823
- function handleRunConfigs(client, cwd) {
16824
- try {
16825
- const { config, configDir } = loadConfigFrom(cwd);
16826
- const configs = resolveRunConfigs(config.run, configDir);
16827
- sendTo(client, {
16828
- type: "run-configs",
16829
- configs: configs.map(({ name, params }) => ({ name, params }))
16830
- });
16831
- } catch {
16832
- sendTo(client, { type: "run-configs", configs: [] });
16833
- }
16834
- }
16835
-
16836
- // src/commands/sessions/daemon/dispatchMessage.ts
16837
- function sendCreated(client, id) {
16838
- sendTo(client, { type: "created", sessionId: id });
16839
- }
16840
- function handleCreate(client, manager, data) {
16841
- sendCreated(
16842
- client,
16843
- manager.spawn(
16844
- data.prompt,
16845
- data.cwd
16846
- )
16847
- );
16848
- }
16849
- function handleCreateRun(client, manager, data) {
16850
- sendCreated(
16851
- client,
16852
- manager.spawnRun(
16853
- data.runName,
16854
- data.runArgs ?? [],
16855
- data.cwd
16856
- )
16857
- );
16858
- }
16859
- function handleCreateAssist(client, manager, data) {
16860
- sendCreated(
16861
- client,
16862
- manager.spawnAssist(
16863
- data.assistArgs ?? [],
16864
- data.cwd
16865
- )
16866
- );
16867
- }
16868
- function handleResume(client, manager, data) {
16869
- sendCreated(
16870
- client,
16871
- manager.resume(
16872
- data.sessionId,
16873
- data.cwd,
16874
- data.name
16875
- )
16876
- );
16877
- }
16878
- function runConfigs(client, _manager, data) {
16879
- handleRunConfigs(client, data.cwd);
16880
- }
16881
- function handleHistory(client, manager) {
16882
- manager.getHistory().then((history) => {
16883
- sendTo(client, { type: "history", sessions: history });
16884
- });
16885
- }
16886
- function handleShutdown(client, manager) {
16887
- manager.shutdown();
16888
- sendTo(client, { type: "shutting-down" });
16889
- setImmediate(() => process.exit(0));
16890
- }
16891
- var handlers = {
16892
- create: handleCreate,
16893
- "create-run": handleCreateRun,
16894
- "create-assist": handleCreateAssist,
16895
- resume: handleResume,
16896
- "run-configs": runConfigs,
16897
- history: handleHistory,
16898
- shutdown: handleShutdown,
16899
- input: (_client, m, d) => m.writeToSession(d.sessionId, d.data),
16900
- resize: (_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows),
16901
- retry: (_client, m, d) => m.retrySession(d.sessionId),
16902
- dismiss: (_client, m, d) => m.dismissSession(d.sessionId)
16903
- };
16904
- function dispatchMessage(client, manager, data) {
16905
- handlers[data.type]?.(client, manager, data);
16906
- }
16907
-
16908
- // src/commands/sessions/daemon/handleConnection.ts
16909
- function handleConnection(socket, manager) {
16910
- const client = {
16911
- send: (data) => {
16912
- if (socket.writable) socket.write(`${data}
16913
- `);
16914
- }
16915
- };
16916
- manager.addClient(client);
16917
- const lines = createInterface5({ input: socket });
16918
- lines.on("error", () => {
16919
- });
16920
- lines.on("line", (line) => {
16921
- let data;
16922
- try {
16923
- data = JSON.parse(line);
16924
- } catch {
16925
- return;
16926
- }
16927
- try {
16928
- dispatchMessage(client, manager, data);
16929
- } catch (e) {
16930
- sendTo(client, {
16931
- type: "error",
16932
- message: `${data.type} failed: ${e instanceof Error ? e.message : String(e)}`
16933
- });
16934
- }
16935
- });
16936
- socket.on("error", () => {
16937
- });
16938
- socket.on("close", () => manager.removeClient(client));
16932
+ // src/commands/sessions/daemon/daemonLog.ts
16933
+ function daemonLog(message) {
16934
+ console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
16939
16935
  }
16940
16936
 
16941
16937
  // src/commands/sessions/shared/discoverSessions.ts
@@ -17053,6 +17049,17 @@ async function discoverSessions() {
17053
17049
  return sessions;
17054
17050
  }
17055
17051
 
17052
+ // src/commands/sessions/daemon/broadcast.ts
17053
+ function sendTo(client, msg) {
17054
+ client.send(JSON.stringify(msg));
17055
+ }
17056
+ function broadcast(clients, msg) {
17057
+ const json = JSON.stringify(msg);
17058
+ for (const client of clients) {
17059
+ client.send(json);
17060
+ }
17061
+ }
17062
+
17056
17063
  // src/commands/sessions/daemon/repoPrefix.ts
17057
17064
  import * as path47 from "path";
17058
17065
  function repoPrefix(cwd) {
@@ -17064,7 +17071,7 @@ function repoPrefix(cwd) {
17064
17071
  import * as pty from "node-pty";
17065
17072
 
17066
17073
  // src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
17067
- import { chmodSync, existsSync as existsSync52, statSync as statSync5 } from "fs";
17074
+ import { chmodSync, existsSync as existsSync51, statSync as statSync6 } from "fs";
17068
17075
  import { createRequire as createRequire3 } from "module";
17069
17076
  import path48 from "path";
17070
17077
  var require4 = createRequire3(import.meta.url);
@@ -17079,8 +17086,8 @@ function ensureSpawnHelperExecutable() {
17079
17086
  `${process.platform}-${process.arch}`,
17080
17087
  "spawn-helper"
17081
17088
  );
17082
- if (!existsSync52(helper)) return;
17083
- const mode = statSync5(helper).mode;
17089
+ if (!existsSync51(helper)) return;
17090
+ const mode = statSync6(helper).mode;
17084
17091
  if ((mode & 73) === 0) chmodSync(helper, mode | 493);
17085
17092
  }
17086
17093
 
@@ -17474,9 +17481,10 @@ var SessionManager = class {
17474
17481
  shutdownSessions(this.sessions);
17475
17482
  }
17476
17483
  restore() {
17477
- for (const persisted of loadPersistedSessions()) {
17484
+ return loadPersistedSessions().map((persisted) => {
17478
17485
  this.add(restoreSession(String(this.nextId++), persisted));
17479
- }
17486
+ return persisted.name;
17487
+ });
17480
17488
  }
17481
17489
  add(session) {
17482
17490
  this.wire(session);
@@ -17538,31 +17546,231 @@ var SessionManager = class {
17538
17546
  };
17539
17547
  };
17540
17548
 
17549
+ // src/commands/sessions/daemon/startDaemonServer.ts
17550
+ import { unlinkSync as unlinkSync18 } from "fs";
17551
+ import * as net2 from "net";
17552
+
17553
+ // src/commands/sessions/daemon/handleConnection.ts
17554
+ import { createInterface as createInterface5 } from "readline";
17555
+
17556
+ // src/commands/sessions/daemon/handleRunConfigs.ts
17557
+ function handleRunConfigs(client, cwd) {
17558
+ try {
17559
+ const { config, configDir } = loadConfigFrom(cwd);
17560
+ const configs = resolveRunConfigs(config.run, configDir);
17561
+ sendTo(client, {
17562
+ type: "run-configs",
17563
+ configs: configs.map(({ name, params }) => ({ name, params }))
17564
+ });
17565
+ } catch {
17566
+ sendTo(client, { type: "run-configs", configs: [] });
17567
+ }
17568
+ }
17569
+
17570
+ // src/commands/sessions/daemon/dispatchMessage.ts
17571
+ function sendCreated(client, id) {
17572
+ sendTo(client, { type: "created", sessionId: id });
17573
+ }
17574
+ function handleCreate(client, manager, data) {
17575
+ sendCreated(
17576
+ client,
17577
+ manager.spawn(
17578
+ data.prompt,
17579
+ data.cwd
17580
+ )
17581
+ );
17582
+ }
17583
+ function handleCreateRun(client, manager, data) {
17584
+ sendCreated(
17585
+ client,
17586
+ manager.spawnRun(
17587
+ data.runName,
17588
+ data.runArgs ?? [],
17589
+ data.cwd
17590
+ )
17591
+ );
17592
+ }
17593
+ function handleCreateAssist(client, manager, data) {
17594
+ sendCreated(
17595
+ client,
17596
+ manager.spawnAssist(
17597
+ data.assistArgs ?? [],
17598
+ data.cwd
17599
+ )
17600
+ );
17601
+ }
17602
+ function handleResume(client, manager, data) {
17603
+ sendCreated(
17604
+ client,
17605
+ manager.resume(
17606
+ data.sessionId,
17607
+ data.cwd,
17608
+ data.name
17609
+ )
17610
+ );
17611
+ }
17612
+ function runConfigs(client, _manager, data) {
17613
+ handleRunConfigs(client, data.cwd);
17614
+ }
17615
+ function handleHistory(client, manager) {
17616
+ manager.getHistory().then((history) => {
17617
+ sendTo(client, { type: "history", sessions: history });
17618
+ });
17619
+ }
17620
+ function handleShutdown(client, manager) {
17621
+ manager.shutdown();
17622
+ sendTo(client, { type: "shutting-down" });
17623
+ setImmediate(() => process.exit(0));
17624
+ }
17625
+ var handlers = {
17626
+ ping: (client) => sendTo(client, { type: "pong", pid: process.pid }),
17627
+ create: handleCreate,
17628
+ "create-run": handleCreateRun,
17629
+ "create-assist": handleCreateAssist,
17630
+ resume: handleResume,
17631
+ "run-configs": runConfigs,
17632
+ history: handleHistory,
17633
+ shutdown: handleShutdown,
17634
+ input: (_client, m, d) => m.writeToSession(d.sessionId, d.data),
17635
+ resize: (_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows),
17636
+ retry: (_client, m, d) => m.retrySession(d.sessionId),
17637
+ dismiss: (_client, m, d) => m.dismissSession(d.sessionId)
17638
+ };
17639
+ function dispatchMessage(client, manager, data) {
17640
+ handlers[data.type]?.(client, manager, data);
17641
+ }
17642
+
17643
+ // src/commands/sessions/daemon/handleConnection.ts
17644
+ function handleConnection(socket, manager) {
17645
+ const client = {
17646
+ send: (data) => {
17647
+ if (socket.writable) socket.write(`${data}
17648
+ `);
17649
+ }
17650
+ };
17651
+ manager.addClient(client);
17652
+ const lines = createInterface5({ input: socket });
17653
+ lines.on("error", () => {
17654
+ });
17655
+ lines.on("line", (line) => {
17656
+ let data;
17657
+ try {
17658
+ data = JSON.parse(line);
17659
+ } catch {
17660
+ return;
17661
+ }
17662
+ try {
17663
+ dispatchMessage(client, manager, data);
17664
+ } catch (e) {
17665
+ sendTo(client, {
17666
+ type: "error",
17667
+ message: `${data.type} failed: ${e instanceof Error ? e.message : String(e)}`
17668
+ });
17669
+ }
17670
+ });
17671
+ socket.on("error", () => {
17672
+ });
17673
+ socket.on("close", () => manager.removeClient(client));
17674
+ }
17675
+
17676
+ // src/commands/sessions/daemon/onListening.ts
17677
+ import { unlinkSync as unlinkSync17, writeFileSync as writeFileSync32 } from "fs";
17678
+
17679
+ // src/commands/sessions/daemon/startPidFileWatchdog.ts
17680
+ import { readFileSync as readFileSync40 } from "fs";
17681
+ var WATCHDOG_INTERVAL_MS = 5e3;
17682
+ function startPidFileWatchdog(onLost, intervalMs = WATCHDOG_INTERVAL_MS) {
17683
+ const timer = setInterval(() => {
17684
+ if (!ownsPidFile()) onLost();
17685
+ }, intervalMs);
17686
+ timer.unref();
17687
+ return timer;
17688
+ }
17689
+ function ownsPidFile() {
17690
+ try {
17691
+ return readFileSync40(daemonPaths.pid, "utf-8").trim() === String(process.pid);
17692
+ } catch {
17693
+ return false;
17694
+ }
17695
+ }
17696
+
17697
+ // src/commands/sessions/daemon/onListening.ts
17698
+ function onListening(manager, checkAutoExit) {
17699
+ writeFileSync32(daemonPaths.pid, String(process.pid));
17700
+ startPidFileWatchdog(() => {
17701
+ daemonLog("lost daemon.pid ownership; shutting down sessions and exiting");
17702
+ manager.shutdown();
17703
+ process.exit(0);
17704
+ });
17705
+ process.on("exit", cleanupOwnedFiles);
17706
+ const restored = manager.restore();
17707
+ daemonLog(
17708
+ restored.length > 0 ? `restored ${restored.length} session(s): ${restored.join(", ")}` : "no persisted sessions to restore"
17709
+ );
17710
+ daemonLog(`listening on ${daemonPaths.socket}`);
17711
+ checkAutoExit(manager.isIdle());
17712
+ }
17713
+ function cleanupOwnedFiles() {
17714
+ if (!ownsPidFile()) return;
17715
+ try {
17716
+ unlinkSync17(daemonPaths.pid);
17717
+ } catch {
17718
+ }
17719
+ if (process.platform !== "win32") {
17720
+ try {
17721
+ unlinkSync17(daemonPaths.socket);
17722
+ } catch {
17723
+ }
17724
+ }
17725
+ }
17726
+
17727
+ // src/commands/sessions/daemon/startDaemonServer.ts
17728
+ function startDaemonServer(manager, checkAutoExit) {
17729
+ const server = net2.createServer(
17730
+ (socket) => handleConnection(socket, manager)
17731
+ );
17732
+ let retried = false;
17733
+ server.on("error", (e) => {
17734
+ if (e.code !== "EADDRINUSE" || retried) {
17735
+ daemonLog(`server error: ${e.message}; exiting`);
17736
+ process.exit(1);
17737
+ }
17738
+ retried = true;
17739
+ void recoverFromAddrInUse(server, manager, checkAutoExit);
17740
+ });
17741
+ server.listen(daemonPaths.socket, () => onListening(manager, checkAutoExit));
17742
+ }
17743
+ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
17744
+ if (await isDaemonRunning()) {
17745
+ daemonLog("another daemon owns the socket; exiting");
17746
+ process.exit(1);
17747
+ }
17748
+ daemonLog("removing stale socket left by a crashed daemon");
17749
+ if (process.platform !== "win32") {
17750
+ try {
17751
+ unlinkSync18(daemonPaths.socket);
17752
+ } catch {
17753
+ }
17754
+ }
17755
+ server.listen(daemonPaths.socket, () => onListening(manager, checkAutoExit));
17756
+ }
17757
+
17541
17758
  // src/commands/sessions/daemon/runDaemon.ts
17542
17759
  async function runDaemon() {
17543
17760
  mkdirSync19(daemonPaths.dir, { recursive: true });
17761
+ daemonLog(
17762
+ `starting (reason: ${process.env.ASSIST_DAEMON_SPAWN_REASON ?? "manual"})`
17763
+ );
17544
17764
  if (await isDaemonRunning()) {
17545
- console.error("Sessions daemon is already running");
17765
+ daemonLog("already running; exiting");
17546
17766
  process.exitCode = 1;
17547
17767
  return;
17548
17768
  }
17549
- if (process.platform !== "win32" && existsSync53(daemonPaths.socket)) {
17550
- unlinkSync16(daemonPaths.socket);
17551
- }
17552
17769
  const checkAutoExit = createAutoExit(() => {
17553
- console.log("No sessions and no connected server; exiting");
17770
+ daemonLog("no sessions and no connected server; exiting");
17554
17771
  process.exit(0);
17555
17772
  });
17556
- const manager = new SessionManager(checkAutoExit);
17557
- manager.restore();
17558
- const server = net2.createServer(
17559
- (socket) => handleConnection(socket, manager)
17560
- );
17561
- server.listen(daemonPaths.socket, () => {
17562
- writeFileSync32(daemonPaths.pid, String(process.pid));
17563
- console.log(`Sessions daemon listening on ${daemonPaths.socket}`);
17564
- checkAutoExit(manager.isIdle());
17565
- });
17773
+ startDaemonServer(new SessionManager(checkAutoExit), checkAutoExit);
17566
17774
  }
17567
17775
 
17568
17776
  // src/commands/sessions/daemon/registerDaemon.ts
@@ -17571,7 +17779,9 @@ function registerDaemon(program2) {
17571
17779
  cmd.command("run").description(
17572
17780
  "Run the sessions daemon in the foreground (auto-spawned by assist sessions)"
17573
17781
  ).action(runDaemon);
17574
- cmd.command("status").description("Show sessions daemon status and live sessions").action(daemonStatus);
17782
+ cmd.command("status").description(
17783
+ "Show sessions daemon status, live sessions, and any stray daemon processes"
17784
+ ).action(daemonStatus);
17575
17785
  cmd.command("stop").description(
17576
17786
  "Stop the sessions daemon; running claude sessions resume on next start"
17577
17787
  ).action(stopDaemon);
@@ -17598,7 +17808,7 @@ function summaryPathFor(jsonlPath2) {
17598
17808
  }
17599
17809
 
17600
17810
  // src/commands/sessions/summarise/summariseSession.ts
17601
- import { execFileSync as execFileSync9 } from "child_process";
17811
+ import { execFileSync as execFileSync10 } from "child_process";
17602
17812
 
17603
17813
  // src/commands/sessions/summarise/iterateUserMessages.ts
17604
17814
  import * as fs28 from "fs";
@@ -17675,7 +17885,7 @@ function summariseSession(jsonlPath2) {
17675
17885
  }
17676
17886
  const prompt = buildPrompt2(firstMessage, backlogIds);
17677
17887
  try {
17678
- const output = execFileSync9("claude", ["-p", "--model", "haiku", prompt], {
17888
+ const output = execFileSync10("claude", ["-p", "--model", "haiku", prompt], {
17679
17889
  encoding: "utf8",
17680
17890
  timeout: 3e4,
17681
17891
  stdio: ["ignore", "pipe", "ignore"]