@staff0rd/assist 0.245.0 → 0.245.2

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.245.0",
9
+ version: "0.245.2",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -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",
@@ -4926,7 +4976,7 @@ import chalk51 from "chalk";
4926
4976
 
4927
4977
  // src/commands/backlog/add/shared.ts
4928
4978
  import { spawnSync } from "child_process";
4929
- 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";
4930
4980
  import { tmpdir } from "os";
4931
4981
  import { join as join17 } from "path";
4932
4982
  import enquirer6 from "enquirer";
@@ -4973,11 +5023,11 @@ function openEditor() {
4973
5023
  writeFileSync13(filePath, "");
4974
5024
  const result = spawnSync(editor, [filePath], { stdio: "inherit" });
4975
5025
  if (result.status !== 0) {
4976
- unlinkSync4(filePath);
5026
+ unlinkSync5(filePath);
4977
5027
  return void 0;
4978
5028
  }
4979
5029
  const content = readFileSync14(filePath, "utf-8").trim();
4980
- unlinkSync4(filePath);
5030
+ unlinkSync5(filePath);
4981
5031
  return content || void 0;
4982
5032
  }
4983
5033
  async function promptAcceptanceCriteria() {
@@ -8306,7 +8356,7 @@ function registerDevlog(program2) {
8306
8356
  }
8307
8357
 
8308
8358
  // src/commands/dotnet/checkBuildLocks.ts
8309
- import { closeSync, openSync as openSync2, readdirSync as readdirSync2 } from "fs";
8359
+ import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync2 } from "fs";
8310
8360
  import { join as join25 } from "path";
8311
8361
  import chalk95 from "chalk";
8312
8362
 
@@ -8338,7 +8388,7 @@ function isLockedDll(debugDir) {
8338
8388
  const dllPath = join25(debugDir, file);
8339
8389
  try {
8340
8390
  const fd = openSync2(dllPath, "r+");
8341
- closeSync(fd);
8391
+ closeSync2(fd);
8342
8392
  } catch {
8343
8393
  return dllPath;
8344
8394
  }
@@ -8428,7 +8478,7 @@ function collectAllDeps(node) {
8428
8478
  }
8429
8479
 
8430
8480
  // src/commands/dotnet/findContainingSolutions.ts
8431
- import { readdirSync as readdirSync3, readFileSync as readFileSync22, statSync } from "fs";
8481
+ import { readdirSync as readdirSync3, readFileSync as readFileSync22, statSync as statSync2 } from "fs";
8432
8482
  import path22 from "path";
8433
8483
  function findSlnFiles(dir, maxDepth, depth = 0) {
8434
8484
  if (depth > maxDepth) return [];
@@ -8444,7 +8494,7 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
8444
8494
  continue;
8445
8495
  const full = path22.join(dir, entry);
8446
8496
  try {
8447
- const stat = statSync(full);
8497
+ const stat = statSync2(full);
8448
8498
  if (stat.isFile() && entry.endsWith(".sln")) {
8449
8499
  results.push(full);
8450
8500
  } else if (stat.isDirectory()) {
@@ -8781,7 +8831,7 @@ function parseInspectReport(json) {
8781
8831
 
8782
8832
  // src/commands/dotnet/runInspectCode.ts
8783
8833
  import { execSync as execSync24 } from "child_process";
8784
- 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";
8785
8835
  import { tmpdir as tmpdir3 } from "os";
8786
8836
  import path25 from "path";
8787
8837
  import chalk102 from "chalk";
@@ -8817,7 +8867,7 @@ function runInspectCode(slnPath, include, swea) {
8817
8867
  process.exit(1);
8818
8868
  }
8819
8869
  const xml = readFileSync23(reportPath, "utf-8");
8820
- unlinkSync5(reportPath);
8870
+ unlinkSync6(reportPath);
8821
8871
  return xml;
8822
8872
  }
8823
8873
 
@@ -8943,7 +8993,7 @@ function aggregateCommitters(authorLists) {
8943
8993
 
8944
8994
  // src/shared/runGhGraphql.ts
8945
8995
  import { spawnSync as spawnSync2 } from "child_process";
8946
- import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync18 } from "fs";
8996
+ import { unlinkSync as unlinkSync7, writeFileSync as writeFileSync18 } from "fs";
8947
8997
  import { tmpdir as tmpdir4 } from "os";
8948
8998
  import { join as join27 } from "path";
8949
8999
  function buildArgs(queryFile, vars) {
@@ -8964,7 +9014,7 @@ function runGhGraphql(mutation, vars) {
8964
9014
  if (result.status !== 0) throw new Error(result.stderr || result.stdout);
8965
9015
  return result.stdout;
8966
9016
  } finally {
8967
- unlinkSync6(queryFile);
9017
+ unlinkSync7(queryFile);
8968
9018
  }
8969
9019
  }
8970
9020
 
@@ -10258,12 +10308,12 @@ import { execSync as execSync32 } from "child_process";
10258
10308
 
10259
10309
  // src/commands/prs/resolveCommentWithReply.ts
10260
10310
  import { execSync as execSync31 } from "child_process";
10261
- import { unlinkSync as unlinkSync8, writeFileSync as writeFileSync21 } from "fs";
10311
+ import { unlinkSync as unlinkSync9, writeFileSync as writeFileSync21 } from "fs";
10262
10312
  import { tmpdir as tmpdir5 } from "os";
10263
10313
  import { join as join32 } from "path";
10264
10314
 
10265
10315
  // src/commands/prs/loadCommentsCache.ts
10266
- 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";
10267
10317
  import { join as join31 } from "path";
10268
10318
  import { parse as parse2 } from "yaml";
10269
10319
  function getCachePath(prNumber) {
@@ -10280,7 +10330,7 @@ function loadCommentsCache(prNumber) {
10280
10330
  function deleteCommentsCache(prNumber) {
10281
10331
  const cachePath = getCachePath(prNumber);
10282
10332
  if (existsSync33(cachePath)) {
10283
- unlinkSync7(cachePath);
10333
+ unlinkSync8(cachePath);
10284
10334
  console.log("No more unresolved line comments. Cache dropped.");
10285
10335
  }
10286
10336
  }
@@ -10302,7 +10352,7 @@ function resolveThread(threadId) {
10302
10352
  { stdio: ["inherit", "pipe", "inherit"] }
10303
10353
  );
10304
10354
  } finally {
10305
- unlinkSync8(queryFile);
10355
+ unlinkSync9(queryFile);
10306
10356
  }
10307
10357
  }
10308
10358
  function requireCache(prNumber) {
@@ -10382,7 +10432,7 @@ import { stringify } from "yaml";
10382
10432
 
10383
10433
  // src/commands/prs/fetchThreadIds.ts
10384
10434
  import { execSync as execSync33 } from "child_process";
10385
- import { unlinkSync as unlinkSync9, writeFileSync as writeFileSync22 } from "fs";
10435
+ import { unlinkSync as unlinkSync10, writeFileSync as writeFileSync22 } from "fs";
10386
10436
  import { tmpdir as tmpdir6 } from "os";
10387
10437
  import { join as join33 } from "path";
10388
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 } } } } } } }`;
@@ -10407,7 +10457,7 @@ function fetchThreadIds(org, repo, prNumber) {
10407
10457
  }
10408
10458
  return { threadMap, resolvedThreadIds };
10409
10459
  } finally {
10410
- unlinkSync9(queryFile);
10460
+ unlinkSync10(queryFile);
10411
10461
  }
10412
10462
  }
10413
10463
 
@@ -13315,10 +13365,10 @@ async function handlePostSynthesis(synthesisPath, options2) {
13315
13365
  }
13316
13366
 
13317
13367
  // src/commands/review/prepareReviewDir.ts
13318
- 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";
13319
13369
  function clearReviewFiles(paths) {
13320
13370
  for (const path54 of [paths.claudePath, paths.codexPath, paths.synthesisPath]) {
13321
- if (existsSync36(path54)) unlinkSync10(path54);
13371
+ if (existsSync36(path54)) unlinkSync11(path54);
13322
13372
  }
13323
13373
  }
13324
13374
  function prepareReviewDir(paths, requestBody, force) {
@@ -13386,11 +13436,11 @@ async function runBacklogSession(synthesisPath) {
13386
13436
  }
13387
13437
 
13388
13438
  // src/commands/review/cachedReviewerResult.ts
13389
- import { statSync as statSync2 } from "fs";
13439
+ import { statSync as statSync3 } from "fs";
13390
13440
  function cachedReviewerResult(name, outputPath) {
13391
13441
  let size;
13392
13442
  try {
13393
- size = statSync2(outputPath).size;
13443
+ size = statSync3(outputPath).size;
13394
13444
  } catch {
13395
13445
  return null;
13396
13446
  }
@@ -13603,7 +13653,7 @@ function printReviewerFailures(results) {
13603
13653
  }
13604
13654
 
13605
13655
  // src/commands/review/runAndSynthesise.ts
13606
- import { existsSync as existsSync38, unlinkSync as unlinkSync12 } from "fs";
13656
+ import { existsSync as existsSync38, unlinkSync as unlinkSync13 } from "fs";
13607
13657
 
13608
13658
  // src/commands/review/buildReviewerStdin.ts
13609
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.
@@ -14021,7 +14071,7 @@ function resolveClaude(args) {
14021
14071
  }
14022
14072
 
14023
14073
  // src/commands/review/runCodexReviewer.ts
14024
- import { existsSync as existsSync37, unlinkSync as unlinkSync11 } from "fs";
14074
+ import { existsSync as existsSync37, unlinkSync as unlinkSync12 } from "fs";
14025
14075
 
14026
14076
  // src/commands/review/parseCodexEvent.ts
14027
14077
  function isItemStarted(value) {
@@ -14076,7 +14126,7 @@ async function runCodexReviewer(spec) {
14076
14126
  }
14077
14127
  });
14078
14128
  if (result.exitCode !== 0 && existsSync37(spec.outputPath)) {
14079
- unlinkSync11(spec.outputPath);
14129
+ unlinkSync12(spec.outputPath);
14080
14130
  }
14081
14131
  return finaliseReviewerRun({ ...spec, command }, spinner, result);
14082
14132
  }
@@ -14224,7 +14274,7 @@ async function runAndSynthesise(args) {
14224
14274
  return { ok: false, failures };
14225
14275
  }
14226
14276
  if (anyFresh && existsSync38(paths.synthesisPath)) {
14227
- unlinkSync12(paths.synthesisPath);
14277
+ unlinkSync13(paths.synthesisPath);
14228
14278
  }
14229
14279
  const synthesisResult = await synthesise(paths, { multi });
14230
14280
  if (synthesisResult.exitCode !== 0) failures.push(synthesisResult);
@@ -14999,7 +15049,7 @@ function registerSql(program2) {
14999
15049
  }
15000
15050
 
15001
15051
  // src/commands/transcript/shared.ts
15002
- 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";
15003
15053
  import { basename as basename6, join as join37, relative as relative2 } from "path";
15004
15054
  import * as readline2 from "readline";
15005
15055
  var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
@@ -15019,7 +15069,7 @@ function collectFiles(dir, extension) {
15019
15069
  const results = [];
15020
15070
  for (const entry of readdirSync6(dir)) {
15021
15071
  const fullPath = join37(dir, entry);
15022
- if (statSync3(fullPath).isDirectory()) {
15072
+ if (statSync4(fullPath).isDirectory()) {
15023
15073
  results.push(...collectFiles(fullPath, extension));
15024
15074
  } else if (entry.endsWith(extension)) {
15025
15075
  results.push(fullPath);
@@ -15893,7 +15943,7 @@ function status() {
15893
15943
  }
15894
15944
 
15895
15945
  // src/commands/voice/stop.ts
15896
- 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";
15897
15947
  function stop2() {
15898
15948
  if (!existsSync47(voicePaths.pid)) {
15899
15949
  console.log("Voice daemon is not running (no PID file)");
@@ -15907,12 +15957,12 @@ function stop2() {
15907
15957
  console.log(`Voice daemon (PID ${pid}) is not running`);
15908
15958
  }
15909
15959
  try {
15910
- unlinkSync13(voicePaths.pid);
15960
+ unlinkSync14(voicePaths.pid);
15911
15961
  } catch {
15912
15962
  }
15913
15963
  try {
15914
15964
  const lockFile = getLockFile();
15915
- if (existsSync47(lockFile)) unlinkSync13(lockFile);
15965
+ if (existsSync47(lockFile)) unlinkSync14(lockFile);
15916
15966
  } catch {
15917
15967
  }
15918
15968
  console.log("Voice daemon stopped");
@@ -16134,7 +16184,7 @@ async function auth() {
16134
16184
 
16135
16185
  // src/commands/roam/postRoamActivity.ts
16136
16186
  import { execFileSync as execFileSync7 } from "child_process";
16137
- 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";
16138
16188
  import { join as join48 } from "path";
16139
16189
  function findPortFile(roamDir) {
16140
16190
  let entries;
@@ -16146,7 +16196,7 @@ function findPortFile(roamDir) {
16146
16196
  const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
16147
16197
  const path54 = join48(roamDir, name);
16148
16198
  try {
16149
- return { path: path54, mtimeMs: statSync4(path54).mtimeMs };
16199
+ return { path: path54, mtimeMs: statSync5(path54).mtimeMs };
16150
16200
  } catch {
16151
16201
  return void 0;
16152
16202
  }
@@ -16495,7 +16545,7 @@ function link2() {
16495
16545
  }
16496
16546
 
16497
16547
  // src/commands/run/remove.ts
16498
- import { existsSync as existsSync49, unlinkSync as unlinkSync14 } from "fs";
16548
+ import { existsSync as existsSync49, unlinkSync as unlinkSync15 } from "fs";
16499
16549
  import { join as join51 } from "path";
16500
16550
  function findRemoveIndex() {
16501
16551
  const idx = process.argv.indexOf("remove");
@@ -16513,7 +16563,7 @@ function parseRemoveName() {
16513
16563
  function deleteCommandFile(name) {
16514
16564
  const filePath = join51(".claude", "commands", `${name}.md`);
16515
16565
  if (existsSync49(filePath)) {
16516
- unlinkSync14(filePath);
16566
+ unlinkSync15(filePath);
16517
16567
  console.log(`Deleted command file: ${filePath}`);
16518
16568
  }
16519
16569
  }
@@ -16548,7 +16598,7 @@ function registerRun(program2) {
16548
16598
 
16549
16599
  // src/commands/screenshot/index.ts
16550
16600
  import { execSync as execSync47 } from "child_process";
16551
- 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";
16552
16602
  import { tmpdir as tmpdir7 } from "os";
16553
16603
  import { join as join52, resolve as resolve13 } from "path";
16554
16604
  import chalk156 from "chalk";
@@ -16695,7 +16745,7 @@ function runPowerShellScript(processName, outputPath) {
16695
16745
  { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }
16696
16746
  );
16697
16747
  } finally {
16698
- unlinkSync15(scriptPath);
16748
+ unlinkSync16(scriptPath);
16699
16749
  }
16700
16750
  }
16701
16751
  function screenshot(processName) {
@@ -16713,21 +16763,93 @@ function screenshot(processName) {
16713
16763
  }
16714
16764
  }
16715
16765
 
16716
- // src/commands/sessions/daemon/daemonStatus.ts
16717
- import { existsSync as existsSync51, readFileSync as readFileSync39 } from "fs";
16766
+ // src/commands/sessions/daemon/listDaemonPids.ts
16767
+ import { execFileSync as execFileSync9 } from "child_process";
16768
+ function listDaemonPids() {
16769
+ if (process.platform === "win32") return [];
16770
+ try {
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));
16775
+ } catch {
16776
+ return [];
16777
+ }
16778
+ }
16779
+
16780
+ // src/commands/sessions/daemon/queryDaemon.ts
16718
16781
  import { createInterface as createInterface4 } from "readline";
16719
16782
  var STATUS_TIMEOUT_MS = 5e3;
16783
+ function queryDaemon(socket) {
16784
+ socket.write(`${JSON.stringify({ type: "ping" })}
16785
+ `);
16786
+ return new Promise((resolve16) => {
16787
+ const result = { sessions: [] };
16788
+ const pending = /* @__PURE__ */ new Set(["sessions", "pong"]);
16789
+ const timer = setTimeout(() => resolve16(result), STATUS_TIMEOUT_MS);
16790
+ const lines = createInterface4({ input: socket });
16791
+ lines.on("error", () => {
16792
+ });
16793
+ lines.on("line", (line) => {
16794
+ applyLine(result, pending, line);
16795
+ if (pending.size === 0) {
16796
+ clearTimeout(timer);
16797
+ resolve16(result);
16798
+ }
16799
+ });
16800
+ });
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
16720
16839
  async function daemonStatus() {
16721
16840
  let socket;
16722
16841
  try {
16723
16842
  socket = await connectToDaemon();
16724
16843
  } catch {
16725
16844
  console.log("Sessions daemon is not running");
16845
+ reportStrays(listDaemonPids());
16726
16846
  return;
16727
16847
  }
16728
- const sessions = await readSessionList(socket);
16848
+ const { pid, sessions } = await queryDaemon(socket);
16729
16849
  socket.destroy();
16730
- console.log(`Sessions daemon is running${describePid()}`);
16850
+ console.log(`Sessions daemon is running${pid ? ` (PID ${pid})` : ""}`);
16851
+ reportStolenSocket(pid);
16852
+ reportStrays(listDaemonPids().filter((p) => p !== pid));
16731
16853
  if (sessions.length === 0) {
16732
16854
  console.log("No sessions");
16733
16855
  return;
@@ -16737,26 +16859,11 @@ async function daemonStatus() {
16737
16859
  console.log(` [${session.status}] ${session.name}${restored}`);
16738
16860
  }
16739
16861
  }
16740
- function describePid() {
16741
- if (!existsSync51(daemonPaths.pid)) return "";
16742
- return ` (PID ${readFileSync39(daemonPaths.pid, "utf-8").trim()})`;
16743
- }
16744
- function readSessionList(socket) {
16745
- return new Promise((resolve16) => {
16746
- const timer = setTimeout(() => resolve16([]), STATUS_TIMEOUT_MS);
16747
- const lines = createInterface4({ input: socket });
16748
- lines.on("error", () => {
16749
- });
16750
- lines.on("line", (line) => {
16751
- try {
16752
- const data = JSON.parse(line);
16753
- if (data.type !== "sessions") return;
16754
- clearTimeout(timer);
16755
- resolve16(data.sessions ?? []);
16756
- } catch {
16757
- }
16758
- });
16759
- });
16862
+ function reportStrays(pids) {
16863
+ if (pids.length === 0) return;
16864
+ console.error(
16865
+ `Warning: stray daemon process(es) detected: ${pids.join(", ")}`
16866
+ );
16760
16867
  }
16761
16868
 
16762
16869
  // src/commands/sessions/daemon/stopDaemon.ts
@@ -16799,15 +16906,14 @@ function closedBeforeTimeout(socket) {
16799
16906
  // src/commands/sessions/daemon/restartDaemon.ts
16800
16907
  async function restartDaemon() {
16801
16908
  await stopDaemon();
16802
- await ensureDaemonRunning();
16909
+ await ensureDaemonRunning("daemon restart");
16803
16910
  console.log(
16804
16911
  "Sessions daemon restarted; previously running claude sessions will resume"
16805
16912
  );
16806
16913
  }
16807
16914
 
16808
16915
  // src/commands/sessions/daemon/runDaemon.ts
16809
- import { existsSync as existsSync53, mkdirSync as mkdirSync19, unlinkSync as unlinkSync16, writeFileSync as writeFileSync32 } from "fs";
16810
- import * as net2 from "net";
16916
+ import { mkdirSync as mkdirSync19 } from "fs";
16811
16917
 
16812
16918
  // src/commands/sessions/daemon/createAutoExit.ts
16813
16919
  var DEFAULT_GRACE_MS = 6e4;
@@ -16823,137 +16929,9 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
16823
16929
  };
16824
16930
  }
16825
16931
 
16826
- // src/commands/sessions/daemon/handleConnection.ts
16827
- import { createInterface as createInterface5 } from "readline";
16828
-
16829
- // src/commands/sessions/daemon/broadcast.ts
16830
- function sendTo(client, msg) {
16831
- client.send(JSON.stringify(msg));
16832
- }
16833
- function broadcast(clients, msg) {
16834
- const json = JSON.stringify(msg);
16835
- for (const client of clients) {
16836
- client.send(json);
16837
- }
16838
- }
16839
-
16840
- // src/commands/sessions/daemon/handleRunConfigs.ts
16841
- function handleRunConfigs(client, cwd) {
16842
- try {
16843
- const { config, configDir } = loadConfigFrom(cwd);
16844
- const configs = resolveRunConfigs(config.run, configDir);
16845
- sendTo(client, {
16846
- type: "run-configs",
16847
- configs: configs.map(({ name, params }) => ({ name, params }))
16848
- });
16849
- } catch {
16850
- sendTo(client, { type: "run-configs", configs: [] });
16851
- }
16852
- }
16853
-
16854
- // src/commands/sessions/daemon/dispatchMessage.ts
16855
- function sendCreated(client, id) {
16856
- sendTo(client, { type: "created", sessionId: id });
16857
- }
16858
- function handleCreate(client, manager, data) {
16859
- sendCreated(
16860
- client,
16861
- manager.spawn(
16862
- data.prompt,
16863
- data.cwd
16864
- )
16865
- );
16866
- }
16867
- function handleCreateRun(client, manager, data) {
16868
- sendCreated(
16869
- client,
16870
- manager.spawnRun(
16871
- data.runName,
16872
- data.runArgs ?? [],
16873
- data.cwd
16874
- )
16875
- );
16876
- }
16877
- function handleCreateAssist(client, manager, data) {
16878
- sendCreated(
16879
- client,
16880
- manager.spawnAssist(
16881
- data.assistArgs ?? [],
16882
- data.cwd
16883
- )
16884
- );
16885
- }
16886
- function handleResume(client, manager, data) {
16887
- sendCreated(
16888
- client,
16889
- manager.resume(
16890
- data.sessionId,
16891
- data.cwd,
16892
- data.name
16893
- )
16894
- );
16895
- }
16896
- function runConfigs(client, _manager, data) {
16897
- handleRunConfigs(client, data.cwd);
16898
- }
16899
- function handleHistory(client, manager) {
16900
- manager.getHistory().then((history) => {
16901
- sendTo(client, { type: "history", sessions: history });
16902
- });
16903
- }
16904
- function handleShutdown(client, manager) {
16905
- manager.shutdown();
16906
- sendTo(client, { type: "shutting-down" });
16907
- setImmediate(() => process.exit(0));
16908
- }
16909
- var handlers = {
16910
- create: handleCreate,
16911
- "create-run": handleCreateRun,
16912
- "create-assist": handleCreateAssist,
16913
- resume: handleResume,
16914
- "run-configs": runConfigs,
16915
- history: handleHistory,
16916
- shutdown: handleShutdown,
16917
- input: (_client, m, d) => m.writeToSession(d.sessionId, d.data),
16918
- resize: (_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows),
16919
- retry: (_client, m, d) => m.retrySession(d.sessionId),
16920
- dismiss: (_client, m, d) => m.dismissSession(d.sessionId)
16921
- };
16922
- function dispatchMessage(client, manager, data) {
16923
- handlers[data.type]?.(client, manager, data);
16924
- }
16925
-
16926
- // src/commands/sessions/daemon/handleConnection.ts
16927
- function handleConnection(socket, manager) {
16928
- const client = {
16929
- send: (data) => {
16930
- if (socket.writable) socket.write(`${data}
16931
- `);
16932
- }
16933
- };
16934
- manager.addClient(client);
16935
- const lines = createInterface5({ input: socket });
16936
- lines.on("error", () => {
16937
- });
16938
- lines.on("line", (line) => {
16939
- let data;
16940
- try {
16941
- data = JSON.parse(line);
16942
- } catch {
16943
- return;
16944
- }
16945
- try {
16946
- dispatchMessage(client, manager, data);
16947
- } catch (e) {
16948
- sendTo(client, {
16949
- type: "error",
16950
- message: `${data.type} failed: ${e instanceof Error ? e.message : String(e)}`
16951
- });
16952
- }
16953
- });
16954
- socket.on("error", () => {
16955
- });
16956
- 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}`);
16957
16935
  }
16958
16936
 
16959
16937
  // src/commands/sessions/shared/discoverSessions.ts
@@ -17071,6 +17049,17 @@ async function discoverSessions() {
17071
17049
  return sessions;
17072
17050
  }
17073
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
+
17074
17063
  // src/commands/sessions/daemon/repoPrefix.ts
17075
17064
  import * as path47 from "path";
17076
17065
  function repoPrefix(cwd) {
@@ -17082,7 +17071,7 @@ function repoPrefix(cwd) {
17082
17071
  import * as pty from "node-pty";
17083
17072
 
17084
17073
  // src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
17085
- import { chmodSync, existsSync as existsSync52, statSync as statSync5 } from "fs";
17074
+ import { chmodSync, existsSync as existsSync51, statSync as statSync6 } from "fs";
17086
17075
  import { createRequire as createRequire3 } from "module";
17087
17076
  import path48 from "path";
17088
17077
  var require4 = createRequire3(import.meta.url);
@@ -17097,8 +17086,8 @@ function ensureSpawnHelperExecutable() {
17097
17086
  `${process.platform}-${process.arch}`,
17098
17087
  "spawn-helper"
17099
17088
  );
17100
- if (!existsSync52(helper)) return;
17101
- const mode = statSync5(helper).mode;
17089
+ if (!existsSync51(helper)) return;
17090
+ const mode = statSync6(helper).mode;
17102
17091
  if ((mode & 73) === 0) chmodSync(helper, mode | 493);
17103
17092
  }
17104
17093
 
@@ -17492,9 +17481,10 @@ var SessionManager = class {
17492
17481
  shutdownSessions(this.sessions);
17493
17482
  }
17494
17483
  restore() {
17495
- for (const persisted of loadPersistedSessions()) {
17484
+ return loadPersistedSessions().map((persisted) => {
17496
17485
  this.add(restoreSession(String(this.nextId++), persisted));
17497
- }
17486
+ return persisted.name;
17487
+ });
17498
17488
  }
17499
17489
  add(session) {
17500
17490
  this.wire(session);
@@ -17556,31 +17546,231 @@ var SessionManager = class {
17556
17546
  };
17557
17547
  };
17558
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
+
17559
17758
  // src/commands/sessions/daemon/runDaemon.ts
17560
17759
  async function runDaemon() {
17561
17760
  mkdirSync19(daemonPaths.dir, { recursive: true });
17761
+ daemonLog(
17762
+ `starting (reason: ${process.env.ASSIST_DAEMON_SPAWN_REASON ?? "manual"})`
17763
+ );
17562
17764
  if (await isDaemonRunning()) {
17563
- console.error("Sessions daemon is already running");
17765
+ daemonLog("already running; exiting");
17564
17766
  process.exitCode = 1;
17565
17767
  return;
17566
17768
  }
17567
- if (process.platform !== "win32" && existsSync53(daemonPaths.socket)) {
17568
- unlinkSync16(daemonPaths.socket);
17569
- }
17570
17769
  const checkAutoExit = createAutoExit(() => {
17571
- console.log("No sessions and no connected server; exiting");
17770
+ daemonLog("no sessions and no connected server; exiting");
17572
17771
  process.exit(0);
17573
17772
  });
17574
- const manager = new SessionManager(checkAutoExit);
17575
- manager.restore();
17576
- const server = net2.createServer(
17577
- (socket) => handleConnection(socket, manager)
17578
- );
17579
- server.listen(daemonPaths.socket, () => {
17580
- writeFileSync32(daemonPaths.pid, String(process.pid));
17581
- console.log(`Sessions daemon listening on ${daemonPaths.socket}`);
17582
- checkAutoExit(manager.isIdle());
17583
- });
17773
+ startDaemonServer(new SessionManager(checkAutoExit), checkAutoExit);
17584
17774
  }
17585
17775
 
17586
17776
  // src/commands/sessions/daemon/registerDaemon.ts
@@ -17589,7 +17779,9 @@ function registerDaemon(program2) {
17589
17779
  cmd.command("run").description(
17590
17780
  "Run the sessions daemon in the foreground (auto-spawned by assist sessions)"
17591
17781
  ).action(runDaemon);
17592
- 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);
17593
17785
  cmd.command("stop").description(
17594
17786
  "Stop the sessions daemon; running claude sessions resume on next start"
17595
17787
  ).action(stopDaemon);
@@ -17616,7 +17808,7 @@ function summaryPathFor(jsonlPath2) {
17616
17808
  }
17617
17809
 
17618
17810
  // src/commands/sessions/summarise/summariseSession.ts
17619
- import { execFileSync as execFileSync9 } from "child_process";
17811
+ import { execFileSync as execFileSync10 } from "child_process";
17620
17812
 
17621
17813
  // src/commands/sessions/summarise/iterateUserMessages.ts
17622
17814
  import * as fs28 from "fs";
@@ -17693,7 +17885,7 @@ function summariseSession(jsonlPath2) {
17693
17885
  }
17694
17886
  const prompt = buildPrompt2(firstMessage, backlogIds);
17695
17887
  try {
17696
- const output = execFileSync9("claude", ["-p", "--model", "haiku", prompt], {
17888
+ const output = execFileSync10("claude", ["-p", "--model", "haiku", prompt], {
17697
17889
  encoding: "utf8",
17698
17890
  timeout: 3e4,
17699
17891
  stdio: ["ignore", "pipe", "ignore"]