@jpcasabiancai/cayenne 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +477 -222
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2261,7 +2261,7 @@ var init_registry2 = __esm({
2261
2261
  UNCLAIMED_PROFILE = "(unclaimed)";
2262
2262
  IDENTITY_ATTEMPTS = 4;
2263
2263
  IDENTITY_RETRY_MS = 500;
2264
- wait = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
2264
+ wait = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
2265
2265
  CAPABILITIES_REFRESH_MS = 5 * 6e4;
2266
2266
  }
2267
2267
  });
@@ -2626,10 +2626,10 @@ var init_version = __esm({
2626
2626
 
2627
2627
  // src/exec-path.ts
2628
2628
  import { accessSync, constants, statSync as statSync3 } from "fs";
2629
- import { delimiter, isAbsolute as isAbsolute4, join as join8 } from "path";
2629
+ import { delimiter, isAbsolute as isAbsolute5, join as join8 } from "path";
2630
2630
  function findOnPath(name, path) {
2631
2631
  for (const dir of (path ?? "").split(delimiter)) {
2632
- if (!isAbsolute4(dir)) continue;
2632
+ if (!isAbsolute5(dir)) continue;
2633
2633
  const candidate = join8(dir, name);
2634
2634
  try {
2635
2635
  if (!statSync3(candidate).isFile()) continue;
@@ -2698,8 +2698,8 @@ var init_rpc = __esm({
2698
2698
  request(method, params) {
2699
2699
  if (this.closed) return Promise.reject(new Error("PROVIDER_ERROR: codex app-server closed"));
2700
2700
  const id = this.nextId++;
2701
- return new Promise((resolve5, reject) => {
2702
- this.pending.set(id, { resolve: resolve5, reject });
2701
+ return new Promise((resolve6, reject) => {
2702
+ this.pending.set(id, { resolve: resolve6, reject });
2703
2703
  this.write({ id, method, params });
2704
2704
  });
2705
2705
  }
@@ -3498,8 +3498,8 @@ var init_source = __esm({
3498
3498
  });
3499
3499
 
3500
3500
  // src/paths.ts
3501
- import { lstatSync, realpathSync as realpathSync4 } from "fs";
3502
- import { dirname as dirname3, isAbsolute as isAbsolute5, join as join9, relative, sep } from "path";
3501
+ import { lstatSync, realpathSync as realpathSync5 } from "fs";
3502
+ import { dirname as dirname3, isAbsolute as isAbsolute6, join as join9, relative as relative2, sep as sep2 } from "path";
3503
3503
  function assertInsideRoots(target, roots) {
3504
3504
  return resolveInsideRoots(target, roots).real;
3505
3505
  }
@@ -3507,7 +3507,7 @@ function resolveInsideRoots(target, roots) {
3507
3507
  assertRoots(roots);
3508
3508
  let real;
3509
3509
  try {
3510
- real = realpathSync4.native(target);
3510
+ real = realpathSync5.native(target);
3511
3511
  } catch {
3512
3512
  throw new Error(`ROOT_NOT_ALLOWED: ${target} does not exist`);
3513
3513
  }
@@ -3517,7 +3517,7 @@ function resolveInsideRoots(target, roots) {
3517
3517
  }
3518
3518
  function assertInsideRootsOrAncestor(target, roots) {
3519
3519
  assertRoots(roots);
3520
- const absolute = isAbsolute5(target) ? target : `${process.cwd()}${sep}${target}`;
3520
+ const absolute = isAbsolute6(target) ? target : `${process.cwd()}${sep2}${target}`;
3521
3521
  let walked = walk(target, absolute);
3522
3522
  if (walked.tail.includes("..")) walked = walk(target, join9(walked.real, ...walked.tail));
3523
3523
  const checked = walked.tail.length === 0 ? walked.real : join9(walked.real, ...walked.tail);
@@ -3529,9 +3529,9 @@ function isPlainToolPath(value) {
3529
3529
  return !value.split(/[/\\]/).includes("..");
3530
3530
  }
3531
3531
  function walk(target, absolute) {
3532
- let real = sep;
3532
+ let real = sep2;
3533
3533
  const tail = [];
3534
- for (const segment of absolute.split(sep)) {
3534
+ for (const segment of absolute.split(sep2)) {
3535
3535
  if (segment === "" || segment === ".") continue;
3536
3536
  if (tail.length > 0) {
3537
3537
  tail.push(segment);
@@ -3543,7 +3543,7 @@ function walk(target, absolute) {
3543
3543
  continue;
3544
3544
  }
3545
3545
  try {
3546
- real = realpathSync4.native(next);
3546
+ real = realpathSync5.native(next);
3547
3547
  } catch {
3548
3548
  throw new Error(`ROOT_NOT_ALLOWED: ${target} does not exist`);
3549
3549
  }
@@ -3565,12 +3565,12 @@ function matchRoot(candidate, roots) {
3565
3565
  for (const root of roots) {
3566
3566
  let rootReal;
3567
3567
  try {
3568
- rootReal = realpathSync4.native(root);
3568
+ rootReal = realpathSync5.native(root);
3569
3569
  } catch {
3570
3570
  continue;
3571
3571
  }
3572
- const rel = relative(rootReal, candidate);
3573
- const inside4 = rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
3572
+ const rel = relative2(rootReal, candidate);
3573
+ const inside4 = rel === "" || !rel.startsWith("..") && !isAbsolute6(rel);
3574
3574
  if (inside4 && (match === null || rootReal.length < match.length)) match = rootReal;
3575
3575
  }
3576
3576
  return match;
@@ -3585,8 +3585,8 @@ var init_paths = __esm({
3585
3585
 
3586
3586
  // src/git/introspect.ts
3587
3587
  import { execFile as execFile3 } from "child_process";
3588
- import { readFileSync as readFileSync4, realpathSync as realpathSync5 } from "fs";
3589
- import { delimiter as delimiter2, dirname as dirname4, isAbsolute as isAbsolute6, join as join10, resolve } from "path";
3588
+ import { readFileSync as readFileSync4, realpathSync as realpathSync6 } from "fs";
3589
+ import { delimiter as delimiter2, dirname as dirname4, isAbsolute as isAbsolute7, join as join10, resolve as resolve2 } from "path";
3590
3590
  import { promisify as promisify2 } from "util";
3591
3591
  function parseGitHubRemote(url) {
3592
3592
  const trimmed = url.trim();
@@ -3658,7 +3658,7 @@ function makeGitRun(opts) {
3658
3658
  }
3659
3659
  function canonical(path, cwd) {
3660
3660
  try {
3661
- return realpathSync5.native(resolve(cwd, path));
3661
+ return realpathSync6.native(resolve2(cwd, path));
3662
3662
  } catch {
3663
3663
  return null;
3664
3664
  }
@@ -3702,7 +3702,7 @@ async function introspectRepo(cwd, run, root, { names = true } = {}) {
3702
3702
  const head = names ? await line(["rev-parse", "--abbrev-ref", "HEAD"]) : null;
3703
3703
  const branch = head === "HEAD" ? null : head;
3704
3704
  const dirs = (await line(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]))?.split("\n").map((d) => d.trim());
3705
- const [gitDir, commonDir] = dirs?.length === 2 && dirs.every((d) => isAbsolute6(d)) ? dirs.map((d) => canonical(d, cwd)) : [null, null];
3705
+ const [gitDir, commonDir] = dirs?.length === 2 && dirs.every((d) => isAbsolute7(d)) ? dirs.map((d) => canonical(d, cwd)) : [null, null];
3706
3706
  if (commonDir == null || gitDir == null) return { ...NONE };
3707
3707
  if (!insideRoot(commonDir, root) && !isLinkedWorktree(gitDir, commonDir, repoRoot)) {
3708
3708
  return { ...NONE };
@@ -3782,9 +3782,9 @@ var init_introspect = __esm({
3782
3782
  // src/repos/credentials.ts
3783
3783
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3784
3784
  import { devNull, homedir as homedir4 } from "os";
3785
- import { delimiter as delimiter3, isAbsolute as isAbsolute7, join as join11 } from "path";
3785
+ import { delimiter as delimiter3, isAbsolute as isAbsolute8, join as join11 } from "path";
3786
3786
  function gitWrapper(git, gitHome) {
3787
- if (git === null || !isAbsolute7(git)) {
3787
+ if (git === null || !isAbsolute8(git)) {
3788
3788
  return `#!/bin/sh
3789
3789
  printf '%s\\n' ${shellQuote(GIT_NOT_FOUND_TEXT)} >&2
3790
3790
  exit 127
@@ -3967,8 +3967,8 @@ exit 1
3967
3967
  });
3968
3968
 
3969
3969
  // src/git/commands.ts
3970
- import { lstatSync as lstatSync2, realpathSync as realpathSync6, statSync as statSync4 } from "fs";
3971
- import { dirname as dirname5, isAbsolute as isAbsolute8, join as join12, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
3970
+ import { lstatSync as lstatSync2, realpathSync as realpathSync7, statSync as statSync4 } from "fs";
3971
+ import { dirname as dirname5, isAbsolute as isAbsolute9, join as join12, relative as relative3, resolve as resolve3, sep as sep3 } from "path";
3972
3972
  async function scopeRepo(cwd, run, root) {
3973
3973
  const identity = await introspectRepo(cwd, run, root, { names: false });
3974
3974
  if (identity.repo_root === null) {
@@ -4126,9 +4126,9 @@ async function gitDirs(scope, run) {
4126
4126
  );
4127
4127
  if (r.code !== 0) return null;
4128
4128
  const lines = r.stdout.split("\n").filter((l) => l !== "");
4129
- if (lines.length === 0 || !lines.every((l) => isAbsolute8(l))) return null;
4129
+ if (lines.length === 0 || !lines.every((l) => isAbsolute9(l))) return null;
4130
4130
  try {
4131
- return lines.map((l) => realpathSync6.native(l));
4131
+ return lines.map((l) => realpathSync7.native(l));
4132
4132
  } catch {
4133
4133
  return null;
4134
4134
  }
@@ -4150,9 +4150,9 @@ function isGitDirShape(dir) {
4150
4150
  function enclosesHome(repoRoot, home) {
4151
4151
  let real;
4152
4152
  try {
4153
- real = realpathSync6.native(home);
4153
+ real = realpathSync7.native(home);
4154
4154
  } catch {
4155
- real = resolve2(home);
4155
+ real = resolve3(home);
4156
4156
  }
4157
4157
  if (within(real, repoRoot)) return true;
4158
4158
  let root;
@@ -4206,7 +4206,7 @@ function identities(paths) {
4206
4206
  const out = [];
4207
4207
  for (const path of paths) {
4208
4208
  try {
4209
- const real = realpathSync6.native(path);
4209
+ const real = realpathSync7.native(path);
4210
4210
  const st = statSync4(real, { bigint: true });
4211
4211
  out.push({ real, dev: st.dev, ino: st.ino });
4212
4212
  } catch {
@@ -4228,7 +4228,7 @@ async function gitDiff(scope, run, opts, guard = {}) {
4228
4228
  const named = join12(scope.repoRoot, opts.path);
4229
4229
  let resolved = null;
4230
4230
  try {
4231
- resolved = realpathSync6.native(named);
4231
+ resolved = realpathSync7.native(named);
4232
4232
  } catch {
4233
4233
  }
4234
4234
  if (isProtected(named) || resolved !== null && isProtected(resolved)) {
@@ -4271,7 +4271,7 @@ async function gitDiff(scope, run, opts, guard = {}) {
4271
4271
  throw new Error(`ROOT_NOT_ALLOWED: ${opts.path}`);
4272
4272
  }
4273
4273
  const dirs = await gitDirs(scope, run);
4274
- const relPath = relative2(scope.repoRoot, real);
4274
+ const relPath = relative3(scope.repoRoot, real);
4275
4275
  if (dirs === null || dirs.some((d) => within(real, d)) || namesGitDir(relPath) || insideOtherGitDir(real, scope.repoRoot) || isProtected(real)) {
4276
4276
  throw new Error(`ROOT_NOT_ALLOWED: ${opts.path}`);
4277
4277
  }
@@ -4355,7 +4355,7 @@ var init_commands = __esm({
4355
4355
  failed = (what, stderr) => new Error(`GIT_ERROR: ${what}: ${(stderr ?? "").trim() || "git exited non-zero"}`);
4356
4356
  UNQUOTED_PATHS = ["-c", "core.quotePath=false"];
4357
4357
  namesGitDir = (path) => path.split(/[\\/]/).some((segment) => segment.toLowerCase() === ".git");
4358
- within = (candidate, dir) => candidate === dir || candidate.startsWith(dir.endsWith(sep2) ? dir : `${dir}${sep2}`);
4358
+ within = (candidate, dir) => candidate === dir || candidate.startsWith(dir.endsWith(sep3) ? dir : `${dir}${sep3}`);
4359
4359
  }
4360
4360
  });
4361
4361
 
@@ -4368,12 +4368,12 @@ import {
4368
4368
  mkdirSync as mkdirSync5,
4369
4369
  openSync,
4370
4370
  readFileSync as readFileSync6,
4371
- realpathSync as realpathSync7,
4371
+ realpathSync as realpathSync8,
4372
4372
  statSync as statSync5,
4373
4373
  writeSync
4374
4374
  } from "fs";
4375
4375
  import { readdir } from "fs/promises";
4376
- import { dirname as dirname6, isAbsolute as isAbsolute9, join as join13, relative as relative3, sep as sep3 } from "path";
4376
+ import { dirname as dirname6, isAbsolute as isAbsolute10, join as join13, relative as relative4, sep as sep4 } from "path";
4377
4377
  import { setImmediate } from "timers/promises";
4378
4378
  function worktreePathFor(repoRoot, name) {
4379
4379
  return join13(repoRoot, WORKTREES_DIR, name);
@@ -4385,7 +4385,7 @@ async function listedWorktrees(scope, run) {
4385
4385
  for (const line of r.stdout.split("\n")) {
4386
4386
  if (!line.startsWith("worktree ")) continue;
4387
4387
  try {
4388
- out.add(realpathSync7.native(line.slice("worktree ".length)));
4388
+ out.add(realpathSync8.native(line.slice("worktree ".length)));
4389
4389
  } catch {
4390
4390
  }
4391
4391
  }
@@ -4398,7 +4398,7 @@ async function excludeCayenneDir(scope, run) {
4398
4398
  scope.ceiling
4399
4399
  );
4400
4400
  const common = r.stdout.trim();
4401
- if (r.code !== 0 || !isAbsolute9(common)) return;
4401
+ if (r.code !== 0 || !isAbsolute10(common)) return;
4402
4402
  const dotGit = join13(scope.repoRoot, ".git");
4403
4403
  let dotGitIsRealDir;
4404
4404
  try {
@@ -4410,8 +4410,8 @@ async function excludeCayenneDir(scope, run) {
4410
4410
  let commonReal;
4411
4411
  let dotGitReal;
4412
4412
  try {
4413
- commonReal = realpathSync7.native(common);
4414
- dotGitReal = realpathSync7.native(dotGit);
4413
+ commonReal = realpathSync8.native(common);
4414
+ dotGitReal = realpathSync8.native(dotGit);
4415
4415
  } catch {
4416
4416
  return;
4417
4417
  }
@@ -4482,7 +4482,7 @@ async function createWorktree(scope, run, name, base) {
4482
4482
  throw new Error(`ROOT_NOT_ALLOWED: ${path}`);
4483
4483
  }
4484
4484
  const listed = await listedWorktrees(scope, run);
4485
- if (listed.has(realpathSync7.native(path))) {
4485
+ if (listed.has(realpathSync8.native(path))) {
4486
4486
  return { worktreePath: path, branch: name, reused: true };
4487
4487
  }
4488
4488
  throw new Error(`VALIDATION: ${path} exists and is not a worktree of this repository`);
@@ -4546,9 +4546,9 @@ async function worktreeIdentity(scope, run) {
4546
4546
  scope.ceiling
4547
4547
  );
4548
4548
  const gitDir = r.stdout.trim();
4549
- if (r.code !== 0 || !isAbsolute9(gitDir)) throw failed2("git rev-parse --git-dir", r.stderr);
4549
+ if (r.code !== 0 || !isAbsolute10(gitDir)) throw failed2("git rev-parse --git-dir", r.stderr);
4550
4550
  try {
4551
- const st = statSync5(realpathSync7.native(gitDir), { bigint: true });
4551
+ const st = statSync5(realpathSync8.native(gitDir), { bigint: true });
4552
4552
  return {
4553
4553
  gitDirDev: st.dev.toString(),
4554
4554
  gitDirIno: st.ino.toString(),
@@ -4562,7 +4562,7 @@ async function removeWorktree(scope, run, worktreePath, force) {
4562
4562
  const worktreesDir = join13(scope.repoRoot, WORKTREES_DIR);
4563
4563
  let base;
4564
4564
  try {
4565
- base = realpathSync7.native(worktreesDir);
4565
+ base = realpathSync8.native(worktreesDir);
4566
4566
  } catch {
4567
4567
  throw new Error(`VALIDATION: ${worktreesDir} does not exist`);
4568
4568
  }
@@ -4573,7 +4573,7 @@ async function removeWorktree(scope, run, worktreePath, force) {
4573
4573
  }
4574
4574
  let real;
4575
4575
  try {
4576
- real = realpathSync7.native(worktreePath);
4576
+ real = realpathSync8.native(worktreePath);
4577
4577
  } catch {
4578
4578
  throw new Error(`VALIDATION: ${worktreePath} does not exist`);
4579
4579
  }
@@ -4582,11 +4582,11 @@ async function removeWorktree(scope, run, worktreePath, force) {
4582
4582
  } catch {
4583
4583
  throw new Error(`ROOT_NOT_ALLOWED: ${real}`);
4584
4584
  }
4585
- const rel = relative3(base, real);
4586
- if (rel === "" || rel.startsWith("..") || isAbsolute9(rel) || rel.includes("/")) {
4585
+ const rel = relative4(base, real);
4586
+ if (rel === "" || rel.startsWith("..") || isAbsolute10(rel) || rel.includes("/")) {
4587
4587
  throw new Error(`VALIDATION: ${worktreePath} is not under ${base}`);
4588
4588
  }
4589
- const inside4 = `${real}${sep3}`;
4589
+ const inside4 = `${real}${sep4}`;
4590
4590
  for (const listed of await listedWorktrees(scope, run)) {
4591
4591
  if (listed.startsWith(inside4)) {
4592
4592
  throw new Error(`VALIDATION: ${real} contains another worktree`);
@@ -4619,7 +4619,7 @@ var init_worktree = __esm({
4619
4619
  });
4620
4620
 
4621
4621
  // src/git/ops.ts
4622
- import { sep as sep4 } from "path";
4622
+ import { sep as sep5 } from "path";
4623
4623
  function managedGitRuns(gitHome) {
4624
4624
  const env = gitCredentialEnv(null, "", { home: gitHome });
4625
4625
  return {
@@ -4658,7 +4658,7 @@ var init_ops = __esm({
4658
4658
  init_commands();
4659
4659
  init_introspect();
4660
4660
  init_worktree();
4661
- isAtOrBelow = (path, dir) => path === dir || path.startsWith(dir.endsWith(sep4) ? dir : `${dir}${sep4}`);
4661
+ isAtOrBelow = (path, dir) => path === dir || path.startsWith(dir.endsWith(sep5) ? dir : `${dir}${sep5}`);
4662
4662
  }
4663
4663
  });
4664
4664
 
@@ -4773,11 +4773,11 @@ var init_session_worktrees = __esm({
4773
4773
 
4774
4774
  // src/git/watch.ts
4775
4775
  import { watch } from "fs";
4776
- import { isAbsolute as isAbsolute10, normalize, sep as sep5 } from "path";
4776
+ import { isAbsolute as isAbsolute11, normalize, sep as sep6 } from "path";
4777
4777
  function ignored(name) {
4778
4778
  if (name === null || name === "") return false;
4779
4779
  const rel = normalize(name);
4780
- if (isAbsolute10(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return true;
4780
+ if (isAbsolute11(rel) || rel === ".." || rel.startsWith(`..${sep6}`)) return true;
4781
4781
  return rel.split(/[\\/]/).some((segment) => IGNORED_NAMES.has(segment));
4782
4782
  }
4783
4783
  function watchChanges(cwd, onChange, debounceMs = CHANGES_DEBOUNCE_MS, onError = () => void 0, fsWatch = recursiveWatch) {
@@ -5480,14 +5480,14 @@ Content-Length: 0\r
5480
5480
  return bound;
5481
5481
  },
5482
5482
  listen() {
5483
- return new Promise((resolve5, reject) => {
5483
+ return new Promise((resolve6, reject) => {
5484
5484
  server.once("error", reject);
5485
5485
  server.listen(port, HOST, () => {
5486
5486
  server.off("error", reject);
5487
5487
  const address = server.address();
5488
5488
  bound = typeof address === "object" && address ? address.port : port;
5489
5489
  log.info({ host: HOST, port: bound }, "local server listening");
5490
- resolve5(bound);
5490
+ resolve6(bound);
5491
5491
  });
5492
5492
  });
5493
5493
  },
@@ -5517,9 +5517,9 @@ Content-Length: 0\r
5517
5517
  untapTerminals?.();
5518
5518
  untapTerminals = null;
5519
5519
  for (const client of wss.clients) client.close(1001, "runner stopping");
5520
- return new Promise((resolve5) => {
5520
+ return new Promise((resolve6) => {
5521
5521
  wss.close();
5522
- server.close(() => resolve5());
5522
+ server.close(() => resolve6());
5523
5523
  server.closeAllConnections();
5524
5524
  });
5525
5525
  }
@@ -5547,7 +5547,7 @@ import {
5547
5547
  openSync as openSync2,
5548
5548
  readdirSync,
5549
5549
  readFileSync as readFileSync8,
5550
- realpathSync as realpathSync8,
5550
+ realpathSync as realpathSync9,
5551
5551
  renameSync as renameSync3,
5552
5552
  rmSync as rmSync6,
5553
5553
  statSync as statSync6,
@@ -5562,7 +5562,7 @@ function githubRemoteUrl(repo) {
5562
5562
  }
5563
5563
  function createManagedRepos(deps) {
5564
5564
  mkdirSync7(deps.root, { recursive: true, mode: 448 });
5565
- const root = realpathSync8.native(deps.root);
5565
+ const root = realpathSync9.native(deps.root);
5566
5566
  const remoteUrl = deps.remoteUrl ?? githubRemoteUrl;
5567
5567
  const protocols = deps.allowFileProtocol ? [...HTTPS_ONLY, ...FILE_TOO] : HTTPS_ONLY;
5568
5568
  const cloneTimeoutMs = deps.cloneTimeoutMs ?? CLONE_TIMEOUT_MS;
@@ -5939,8 +5939,8 @@ function keyedMutex() {
5939
5939
  const prev = tails.get(key) ?? Promise.resolve();
5940
5940
  let done = () => {
5941
5941
  };
5942
- const mine = new Promise((resolve5) => {
5943
- done = resolve5;
5942
+ const mine = new Promise((resolve6) => {
5943
+ done = resolve6;
5944
5944
  });
5945
5945
  const tail = prev.then(() => mine);
5946
5946
  tails.set(key, tail);
@@ -6022,8 +6022,8 @@ var init_managed = __esm({
6022
6022
  });
6023
6023
 
6024
6024
  // src/repos/sweep.ts
6025
- import { lstatSync as lstatSync5, realpathSync as realpathSync9 } from "fs";
6026
- import { isAbsolute as isAbsolute11, join as join17, relative as relative4, sep as sep6 } from "path";
6025
+ import { lstatSync as lstatSync5, realpathSync as realpathSync10 } from "fs";
6026
+ import { isAbsolute as isAbsolute12, join as join17, relative as relative5, sep as sep7 } from "path";
6027
6027
  function createRepoWorktreeSweep(deps) {
6028
6028
  const { log } = deps;
6029
6029
  const said3 = /* @__PURE__ */ new Set();
@@ -6031,8 +6031,8 @@ function createRepoWorktreeSweep(deps) {
6031
6031
  let stopped = false;
6032
6032
  function candidateOf(sessionId) {
6033
6033
  const rec = deps.sessionWorktrees.read(sessionId);
6034
- if (!rec || rec.runnerId !== deps.runnerId || !isAbsolute11(rec.path)) return null;
6035
- const parts = relative4(deps.managedRoot, rec.path).split(sep6);
6034
+ if (!rec || rec.runnerId !== deps.runnerId || !isAbsolute12(rec.path)) return null;
6035
+ const parts = relative5(deps.managedRoot, rec.path).split(sep7);
6036
6036
  const [owner, name] = parts;
6037
6037
  if (parts.length !== 5 || owner === void 0 || name === void 0 || !RepositoryRef.shape.owner.safeParse(owner).success || !RepositoryRef.shape.name.safeParse(name).success) {
6038
6038
  return null;
@@ -6077,7 +6077,7 @@ function createRepoWorktreeSweep(deps) {
6077
6077
  const notRecorded = new Error(`VALIDATION: ${c.path} is not the worktree this runner recorded`);
6078
6078
  let real;
6079
6079
  try {
6080
- real = realpathSync9.native(c.path);
6080
+ real = realpathSync10.native(c.path);
6081
6081
  } catch {
6082
6082
  throw notRecorded;
6083
6083
  }
@@ -6556,8 +6556,8 @@ var init_attachments2 = __esm({
6556
6556
  });
6557
6557
 
6558
6558
  // src/fs/list.ts
6559
- import { readdirSync as readdirSync3, realpathSync as realpathSync10, statSync as statSync7 } from "fs";
6560
- import { join as join19, relative as relative5, sep as sep7 } from "path";
6559
+ import { readdirSync as readdirSync3, realpathSync as realpathSync11, statSync as statSync7 } from "fs";
6560
+ import { join as join19, relative as relative6, sep as sep8 } from "path";
6561
6561
  function order(a, b) {
6562
6562
  const aDir = a.kind === "dir" ? 0 : 1;
6563
6563
  const bDir = b.kind === "dir" ? 0 : 1;
@@ -6565,12 +6565,12 @@ function order(a, b) {
6565
6565
  }
6566
6566
  function inside(child, root) {
6567
6567
  if (child === root) return true;
6568
- const rel = relative5(root, child);
6569
- return rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep7) && !rel.includes(`..${sep7}`);
6568
+ const rel = relative6(root, child);
6569
+ return rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep8) && !rel.includes(`..${sep8}`);
6570
6570
  }
6571
6571
  function symlinkKind(path, root) {
6572
6572
  try {
6573
- const real = realpathSync10.native(path);
6573
+ const real = realpathSync11.native(path);
6574
6574
  if (!inside(real, root)) return { kind: "file", size: 0 };
6575
6575
  const st = statSync7(real);
6576
6576
  return st.isDirectory() ? { kind: "dir", size: 0 } : { kind: "file", size: st.size };
@@ -6788,7 +6788,7 @@ var init_refusals = __esm({
6788
6788
  });
6789
6789
 
6790
6790
  // src/sessions/handlers.ts
6791
- import { sep as sep8 } from "path";
6791
+ import { sep as sep9 } from "path";
6792
6792
  import { ZodError } from "zod";
6793
6793
  function parsePayload(row, kind) {
6794
6794
  try {
@@ -6809,7 +6809,7 @@ function buildHandlers(manager, info) {
6809
6809
  const { git } = info;
6810
6810
  const refuseManaged = (path) => {
6811
6811
  const managed = info.managedRoot;
6812
- if (path === managed || path.startsWith(managed.endsWith(sep8) ? managed : `${managed}${sep8}`)) {
6812
+ if (path === managed || path.startsWith(managed.endsWith(sep9) ? managed : `${managed}${sep9}`)) {
6813
6813
  throw new Error(
6814
6814
  `ROOT_NOT_ALLOWED: ${path} is inside the managed repositories directory; only repository chats make worktrees there`
6815
6815
  );
@@ -7042,17 +7042,17 @@ var init_broadcast = __esm({
7042
7042
  });
7043
7043
 
7044
7044
  // src/documents/paths.ts
7045
- import { realpathSync as realpathSync11 } from "fs";
7046
- import { isAbsolute as isAbsolute12, join as join20, relative as relative6, sep as sep9 } from "path";
7045
+ import { realpathSync as realpathSync12 } from "fs";
7046
+ import { isAbsolute as isAbsolute13, join as join20, relative as relative7, sep as sep10 } from "path";
7047
7047
  function contained(dir, candidate) {
7048
- const rel = relative6(dir, candidate);
7049
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute12(rel);
7048
+ const rel = relative7(dir, candidate);
7049
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep10}`) && !isAbsolute13(rel);
7050
7050
  }
7051
7051
  function resolveDocsDir(cwd, log) {
7052
7052
  const dir = docsDir(cwd);
7053
7053
  let real;
7054
7054
  try {
7055
- real = realpathSync11.native(dir);
7055
+ real = realpathSync12.native(dir);
7056
7056
  } catch {
7057
7057
  return null;
7058
7058
  }
@@ -7068,7 +7068,7 @@ function resolveDocsDir(cwd, log) {
7068
7068
  function assertInsideDocsDir(dir, candidate) {
7069
7069
  let real;
7070
7070
  try {
7071
- real = realpathSync11.native(candidate);
7071
+ real = realpathSync12.native(candidate);
7072
7072
  } catch {
7073
7073
  throw new Error(`ROOT_NOT_ALLOWED: ${candidate} cannot be resolved`);
7074
7074
  }
@@ -7733,7 +7733,7 @@ var init_pushable = __esm({
7733
7733
  const value = this.queue.shift();
7734
7734
  if (value !== void 0) return Promise.resolve({ value, done: false });
7735
7735
  if (this.ended) return Promise.resolve({ value: void 0, done: true });
7736
- return new Promise((resolve5) => this.waiters.push(resolve5));
7736
+ return new Promise((resolve6) => this.waiters.push(resolve6));
7737
7737
  }
7738
7738
  };
7739
7739
  }
@@ -7753,7 +7753,15 @@ async function askGuard(guard, toolName2, input) {
7753
7753
  return GUARD_FAILED;
7754
7754
  }
7755
7755
  }
7756
- var SESSION_FLAG_SETTINGS, GUARD_FAILED, ASK_TOOL_REASON, ClaudeSession;
7756
+ function asInterrupted(row) {
7757
+ return {
7758
+ ...row,
7759
+ content: row.content.map(
7760
+ (block) => block.type === "result" ? { ...block, isError: false, text: INTERRUPTED } : block
7761
+ )
7762
+ };
7763
+ }
7764
+ var SESSION_FLAG_SETTINGS, GUARD_FAILED, ASK_TOOL_REASON, INTERRUPTED, STOPPED_TURN, ClaudeSession;
7757
7765
  var init_session2 = __esm({
7758
7766
  "src/claude/session.ts"() {
7759
7767
  "use strict";
@@ -7768,6 +7776,8 @@ var init_session2 = __esm({
7768
7776
  };
7769
7777
  GUARD_FAILED = "Refused: the runner could not check this tool call.";
7770
7778
  ASK_TOOL_REASON = "This session was started from a task, so every shell command needs the member's approval.";
7779
+ INTERRUPTED = "interrupted";
7780
+ STOPPED_TURN = "error_during_execution after a requested stop: recorded as interrupted";
7771
7781
  ClaudeSession = class extends EventEmitter {
7772
7782
  constructor(opts) {
7773
7783
  super();
@@ -7786,6 +7796,13 @@ var init_session2 = __esm({
7786
7796
  /** `null` until `accountInfo()` answers, and for good when it cannot. */
7787
7797
  authMode = null;
7788
7798
  authAsked = false;
7799
+ /** A prompt is waiting for its turn, or a turn began (`init`), and no result has closed it. */
7800
+ turnOpen = false;
7801
+ /**
7802
+ * `interrupt()` or `stop()` was asked of the open turn. The CLI reports a turn cut short as
7803
+ * `error_during_execution`; with this set, that result is the stop landing, not a failure.
7804
+ */
7805
+ stopAsked = false;
7789
7806
  start() {
7790
7807
  if (this.q) throw new Error("VALIDATION: session already started");
7791
7808
  const queryFn = this.opts.queryFn ?? sdkQuery;
@@ -7869,6 +7886,7 @@ var init_session2 = __esm({
7869
7886
  })),
7870
7887
  { type: "text", text }
7871
7888
  ] : text;
7889
+ this.turnOpen = true;
7872
7890
  this.inbox.push({
7873
7891
  type: "user",
7874
7892
  message: { role: "user", content },
@@ -7885,6 +7903,7 @@ var init_session2 = __esm({
7885
7903
  return true;
7886
7904
  }
7887
7905
  async interrupt() {
7906
+ if (this.turnOpen) this.stopAsked = true;
7888
7907
  await this.q?.interrupt();
7889
7908
  }
7890
7909
  async setPermissionMode(mode) {
@@ -7901,6 +7920,7 @@ var init_session2 = __esm({
7901
7920
  for (const id of [...this.pending.keys()]) {
7902
7921
  this.expire(id, "session stopped");
7903
7922
  }
7923
+ if (this.turnOpen) this.stopAsked = true;
7904
7924
  void this.q?.interrupt().catch(() => void 0);
7905
7925
  const killer = setTimeout(() => this.abort.abort(), 5e3);
7906
7926
  await this.pumping;
@@ -7917,14 +7937,14 @@ var init_session2 = __esm({
7917
7937
  this.respondPermission(id, { behavior: "deny", message, interrupt: false });
7918
7938
  }
7919
7939
  requestPermission(toolName2, input, suggestions, toolUseId, signal) {
7920
- return new Promise((resolve5) => {
7940
+ return new Promise((resolve6) => {
7921
7941
  const id = randomUUID3();
7922
7942
  const settle2 = (d) => {
7923
7943
  const entry = this.pending.get(id);
7924
7944
  if (!entry) return;
7925
7945
  clearTimeout(entry.timer);
7926
7946
  this.pending.delete(id);
7927
- resolve5(
7947
+ resolve6(
7928
7948
  d.behavior === "allow" ? {
7929
7949
  behavior: "allow",
7930
7950
  updatedInput: d.updatedInput ?? input,
@@ -7955,6 +7975,7 @@ ${this.stderrTail.join("\n")}` : "";
7955
7975
  }
7956
7976
  route(msg) {
7957
7977
  if (msg.type === "system" && msg.subtype === "init") {
7978
+ this.turnOpen = true;
7958
7979
  this.providerSessionId = msg.session_id;
7959
7980
  this.lookUpAuthMode();
7960
7981
  this.emit("init", {
@@ -7974,15 +7995,19 @@ ${this.stderrTail.join("\n")}` : "";
7974
7995
  });
7975
7996
  return;
7976
7997
  }
7998
+ const stopped = msg.type === "result" && this.stopAsked && msg.subtype === "error_during_execution";
7999
+ if (stopped) logger.info({ providerSessionId: this.providerSessionId }, STOPPED_TURN);
7977
8000
  const normalized = normalizeSdkMessage(msg);
7978
- if (normalized) this.emit("message", normalized);
8001
+ if (normalized) this.emit("message", stopped ? asInterrupted(normalized) : normalized);
7979
8002
  if (msg.type === "result") {
8003
+ this.turnOpen = false;
8004
+ this.stopAsked = false;
7980
8005
  this.emit("result", {
7981
- isError: msg.is_error,
8006
+ isError: stopped ? false : msg.is_error,
7982
8007
  durationMs: msg.duration_ms,
7983
8008
  numTurns: msg.num_turns,
7984
8009
  totalCostUsd: msg.total_cost_usd,
7985
- stopReason: msg.stop_reason,
8010
+ stopReason: stopped ? INTERRUPTED : msg.stop_reason,
7986
8011
  authMode: this.authMode
7987
8012
  });
7988
8013
  }
@@ -8136,13 +8161,23 @@ import { randomUUID as randomUUID4 } from "crypto";
8136
8161
  import { EventEmitter as EventEmitter2 } from "events";
8137
8162
  import { chmodSync as chmodSync5, lstatSync as lstatSync7, mkdirSync as mkdirSync8, rmdirSync, rmSync as rmSync8, writeFileSync as writeFileSync6 } from "fs";
8138
8163
  import { dirname as dirname8, join as join22 } from "path";
8164
+ function newStop() {
8165
+ let resolve6;
8166
+ let reject;
8167
+ const settled = new Promise((res, rej) => {
8168
+ resolve6 = res;
8169
+ reject = rej;
8170
+ });
8171
+ settled.catch(() => void 0);
8172
+ return { sentAt: null, covered: [], settled, resolve: resolve6, reject };
8173
+ }
8139
8174
  function removeFiles(files) {
8140
8175
  for (const file of files) rmSync8(file, { force: true });
8141
8176
  }
8142
8177
  function scrubSecrets(text) {
8143
8178
  return SECRET_PATTERNS.reduce((acc, [pattern, to]) => acc.replace(pattern, to), text);
8144
8179
  }
8145
- var STDERR_TAIL_MAX, SIGKILL_AFTER_MS, SECRET_PATTERNS, APPROVAL_METHODS, CodexSession;
8180
+ var STDERR_TAIL_MAX, STOP_WAIT_MS, SIGKILL_AFTER_MS, SECRET_PATTERNS, APPROVAL_METHODS, CodexSession;
8146
8181
  var init_session3 = __esm({
8147
8182
  "src/codex/session.ts"() {
8148
8183
  "use strict";
@@ -8153,6 +8188,7 @@ var init_session3 = __esm({
8153
8188
  init_policy();
8154
8189
  init_rpc();
8155
8190
  STDERR_TAIL_MAX = 2 * 1024;
8191
+ STOP_WAIT_MS = 2e3;
8156
8192
  SIGKILL_AFTER_MS = 5e3;
8157
8193
  SECRET_PATTERNS = [
8158
8194
  [
@@ -8189,11 +8225,8 @@ var init_session3 = __esm({
8189
8225
  sandbox;
8190
8226
  pendingModel;
8191
8227
  pendingEffort;
8192
- turnId = null;
8193
- running = false;
8228
+ turn = null;
8194
8229
  queue = [];
8195
- /** The image files of the turn in flight, removed when it ends. */
8196
- turnFiles = [];
8197
8230
  approvals = /* @__PURE__ */ new Map();
8198
8231
  streaming = /* @__PURE__ */ new Set();
8199
8232
  stderrTail = [];
@@ -8269,7 +8302,7 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8269
8302
  if (staged.files.length !== staged.images.length) {
8270
8303
  throw new Error("PROVIDER_ERROR: codex images must be staged before the prompt");
8271
8304
  }
8272
- this.queue.push({ text, files: [...staged.files] });
8305
+ this.queue.push({ text, files: [...staged.files], stopped: false });
8273
8306
  this.flush();
8274
8307
  }
8275
8308
  /**
@@ -8311,13 +8344,19 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8311
8344
  }
8312
8345
  return written;
8313
8346
  }
8314
- /** The turn in flight is over: its images have been read, or never will be. */
8315
- endTurnFiles() {
8316
- removeFiles(this.turnFiles);
8317
- this.turnFiles = [];
8347
+ /**
8348
+ * The turn in flight is over: its images have been read, or never will be, and a Stop asked of
8349
+ * it has nothing left to stop.
8350
+ */
8351
+ endTurn() {
8352
+ const turn = this.turn;
8353
+ if (!turn) return;
8354
+ this.turn = null;
8355
+ removeFiles(turn.files);
8356
+ turn.stop?.resolve();
8318
8357
  }
8319
8358
  removeAllFiles() {
8320
- this.endTurnFiles();
8359
+ this.endTurn();
8321
8360
  for (const turn of this.queue) {
8322
8361
  removeFiles(turn.files);
8323
8362
  turn.files = [];
@@ -8331,11 +8370,18 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8331
8370
  }
8332
8371
  /** One turn at a time: Codex has no notion of a second concurrent turn on a thread. */
8333
8372
  flush() {
8334
- if (this.running || !this.rpc || !this.providerSessionId) return;
8373
+ if (this.turn || !this.rpc || !this.providerSessionId) return;
8335
8374
  const next = this.queue.shift();
8336
8375
  if (next === void 0) return;
8337
- this.running = true;
8338
- this.turnFiles = next.files;
8376
+ const turn = {
8377
+ id: null,
8378
+ answered: false,
8379
+ heard: null,
8380
+ started: false,
8381
+ files: next.files,
8382
+ stop: next.stopped ? newStop() : null
8383
+ };
8384
+ this.turn = turn;
8339
8385
  void this.rpc.request("turn/start", {
8340
8386
  threadId: this.providerSessionId,
8341
8387
  // app-server v2 `UserInput`: images as `localImage` paths, then the text.
@@ -8346,11 +8392,9 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8346
8392
  sandboxPolicy: this.sandbox,
8347
8393
  ...this.pendingModel ? { model: this.pendingModel } : {},
8348
8394
  ...this.pendingEffort ? { effort: this.pendingEffort } : {}
8349
- }).then((res) => {
8350
- this.turnId = res.turnId ?? null;
8351
- }).catch((err) => {
8352
- this.running = false;
8353
- this.endTurnFiles();
8395
+ }).then((res) => this.named(turn, res.turn?.id ?? res.turnId)).catch((err) => {
8396
+ if (this.turn !== turn) return;
8397
+ this.endTurn();
8354
8398
  this.emit("result", {
8355
8399
  isError: true,
8356
8400
  durationMs: 0,
@@ -8359,10 +8403,60 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8359
8403
  });
8360
8404
  });
8361
8405
  }
8406
+ /**
8407
+ * `turn/start` answered, with the turn's id. Should the answer name none, the id is the one
8408
+ * `turn/started` gave or gives. An answer for a turn that has ended names nothing.
8409
+ */
8410
+ named(turn, id) {
8411
+ if (this.turn !== turn) return;
8412
+ turn.answered = true;
8413
+ turn.id = id ?? turn.heard;
8414
+ if (turn.id && turn.heard === turn.id) turn.started = true;
8415
+ this.sendStop(turn);
8416
+ }
8417
+ /**
8418
+ * The app-server runs the turn `id`: `turn/started`, or something that turn produced. If it is
8419
+ * the one in flight, a Stop that was waiting for that goes out now. Another turn's is not: what
8420
+ * the turn before still had to say must not pass for the start of this one, since a stop sent
8421
+ * on it would come too early to stop anything. Before `turn/start` has answered there is no
8422
+ * telling, so the id is kept for the answer to settle.
8423
+ */
8424
+ began(id) {
8425
+ const turn = this.turn;
8426
+ if (!turn) return;
8427
+ if (id && !turn.id) {
8428
+ turn.heard = id;
8429
+ if (!turn.answered) return;
8430
+ turn.id = id;
8431
+ }
8432
+ if (id && id !== turn.id) return;
8433
+ turn.started = true;
8434
+ this.sendStop(turn);
8435
+ }
8436
+ /** Send the turn's Stop, if one was asked, it has not gone out, and the turn can be stopped. */
8437
+ sendStop(turn) {
8438
+ const { stop, id } = turn;
8439
+ if (!stop || stop.sentAt !== null || !id || !turn.started) return;
8440
+ if (!this.rpc || !this.providerSessionId) return;
8441
+ stop.sentAt = Date.now();
8442
+ this.rpc.request("turn/interrupt", { threadId: this.providerSessionId, turnId: id }).then(
8443
+ () => stop.resolve(),
8444
+ (err) => {
8445
+ if (this.turn !== turn) return;
8446
+ for (const queued of stop.covered) queued.stopped = false;
8447
+ if (turn.stop === stop) turn.stop = null;
8448
+ stop.reject(err);
8449
+ }
8450
+ );
8451
+ }
8362
8452
  onNotification(method, params) {
8453
+ const turn = params.turn;
8454
+ if (method.startsWith("item/") && this.turn?.id) {
8455
+ this.began(params.turnId);
8456
+ }
8363
8457
  switch (method) {
8364
8458
  case "turn/started":
8365
- this.turnId = params.turnId ?? this.turnId;
8459
+ this.began(turn?.id ?? params.turnId);
8366
8460
  return;
8367
8461
  case "item/agentMessage/delta":
8368
8462
  this.delta(params, "text", "delta");
@@ -8385,13 +8479,11 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8385
8479
  }
8386
8480
  case "turn/completed":
8387
8481
  case "turn/failed": {
8388
- this.running = false;
8389
- this.turnId = null;
8390
- this.endTurnFiles();
8391
- const status = params.status ?? (method === "turn/failed" ? "failed" : "completed");
8482
+ this.endTurn();
8483
+ const status = turn?.status ?? params.status ?? (method === "turn/failed" ? "failed" : "completed");
8392
8484
  this.emit("result", {
8393
8485
  isError: status === "failed",
8394
- durationMs: params.durationMs ?? 0,
8486
+ durationMs: turn?.durationMs ?? params.durationMs ?? 0,
8395
8487
  numTurns: 1,
8396
8488
  stopReason: status
8397
8489
  });
@@ -8477,23 +8569,23 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8477
8569
  if (this.approvals.has(id) || this.resolvers.has(id)) {
8478
8570
  throw new Error(`VALIDATION: approval id ${id} is already held`);
8479
8571
  }
8480
- return new Promise((resolve5) => {
8572
+ return new Promise((resolve6) => {
8481
8573
  const timer = setTimeout(() => {
8482
8574
  this.approvals.delete(id);
8483
8575
  this.resolvers.delete(id);
8484
8576
  this.questionCounts.delete(id);
8485
8577
  this.emit("permissionTimeout", { id });
8486
- resolve5(onTimeout);
8578
+ resolve6(onTimeout);
8487
8579
  }, this.opts.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS);
8488
8580
  this.approvals.set(id, { rpcId: 0, kind, refusal: onTimeout, timer });
8489
- this.resolvers.set(id, resolve5);
8581
+ this.resolvers.set(id, resolve6);
8490
8582
  this.emit("permission", { id, ...req, suggestions: null });
8491
8583
  });
8492
8584
  }
8493
8585
  respondPermission(id, decision) {
8494
8586
  const entry = this.approvals.get(id);
8495
- const resolve5 = this.resolvers.get(id);
8496
- if (!entry || !resolve5) return false;
8587
+ const resolve6 = this.resolvers.get(id);
8588
+ if (!entry || !resolve6) return false;
8497
8589
  let reply;
8498
8590
  if (entry.kind === "question") {
8499
8591
  if (decision.behavior === "deny") reply = "cancel";
@@ -8510,12 +8602,43 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8510
8602
  this.approvals.delete(id);
8511
8603
  this.resolvers.delete(id);
8512
8604
  this.questionCounts.delete(id);
8513
- resolve5(reply);
8605
+ resolve6(reply);
8514
8606
  return true;
8515
8607
  }
8516
- async interrupt() {
8517
- if (!this.rpc || !this.providerSessionId || !this.turnId) return;
8518
- await this.rpc.request("turn/interrupt", { threadId: this.providerSessionId, turnId: this.turnId }).catch(() => void 0);
8608
+ /**
8609
+ * Stop every prompt handed over before this call, and none after: the turn in flight, and each
8610
+ * prompt still queued here, which keeps its turn and its result and is stopped as that turn
8611
+ * starts. With nothing in flight or queued there is nothing to stop, and nothing is remembered.
8612
+ *
8613
+ * The app-server can stop a turn only once it has named it and started it, so a Stop asked
8614
+ * before that is kept on the turn and goes out at `turn/started` (before, it was dropped, and
8615
+ * the prompt ran). The promise is the turn in flight's: it resolves when the app-server took the
8616
+ * stop, when the turn ended, or after `STOP_WAIT_MS` without word of either, the stop staying on
8617
+ * the turn; it rejects when the app-server refused it while the turn was still running, in
8618
+ * which case this Stop covers nothing, the queued prompts included.
8619
+ *
8620
+ * `'turn'` is a Stop the manager already asked, sent again for the turn in flight: the queue is
8621
+ * left as that Stop found it, or the prompts handed over since would be stopped with it.
8622
+ */
8623
+ async interrupt(scope) {
8624
+ const covered = scope === "turn" ? [] : this.queue.filter((queued) => !queued.stopped);
8625
+ for (const queued of covered) queued.stopped = true;
8626
+ const turn = this.turn;
8627
+ if (!turn) return;
8628
+ const sentAt = turn.stop?.sentAt ?? null;
8629
+ if (sentAt !== null && Date.now() - sentAt > STOP_WAIT_MS) turn.stop = null;
8630
+ turn.stop ??= newStop();
8631
+ turn.stop.covered.push(...covered);
8632
+ this.sendStop(turn);
8633
+ let timer;
8634
+ const waited = new Promise((resolve6) => {
8635
+ timer = setTimeout(resolve6, STOP_WAIT_MS);
8636
+ });
8637
+ try {
8638
+ await Promise.race([turn.stop.settled, waited]);
8639
+ } finally {
8640
+ clearTimeout(timer);
8641
+ }
8519
8642
  }
8520
8643
  async setPermissionMode(mode) {
8521
8644
  this.mode = mode;
@@ -8541,7 +8664,8 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8541
8664
  this.resolvers.delete(id);
8542
8665
  this.emit("permissionTimeout", { id });
8543
8666
  }
8544
- await this.interrupt();
8667
+ const stoppable = this.turn?.started && this.turn.id;
8668
+ await (stoppable ? this.interrupt().catch(() => void 0) : void 0);
8545
8669
  this.removeAllFiles();
8546
8670
  this.rpc?.close();
8547
8671
  this.child?.stdin?.end();
@@ -8590,15 +8714,15 @@ var init_types = __esm({
8590
8714
  });
8591
8715
 
8592
8716
  // src/skills/paths.ts
8593
- import { realpathSync as realpathSync12 } from "fs";
8594
- import { isAbsolute as isAbsolute13, join as join23, relative as relative7, sep as sep10 } from "path";
8717
+ import { realpathSync as realpathSync13 } from "fs";
8718
+ import { isAbsolute as isAbsolute14, join as join23, relative as relative8, sep as sep11 } from "path";
8595
8719
  function clearSessionSkills(home, runnerId, heldBy) {
8596
8720
  clearRunnerStaging(home, join23(home, "session-skills"), runnerId, heldBy);
8597
8721
  }
8598
8722
  function assertInsideScopeDir(path, scopeDir) {
8599
8723
  let dirNow;
8600
8724
  try {
8601
- dirNow = realpathSync12.native(scopeDir);
8725
+ dirNow = realpathSync13.native(scopeDir);
8602
8726
  } catch {
8603
8727
  throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} does not exist`);
8604
8728
  }
@@ -8606,8 +8730,8 @@ function assertInsideScopeDir(path, scopeDir) {
8606
8730
  throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} no longer resolves to itself (${dirNow})`);
8607
8731
  }
8608
8732
  const real = assertInsideRoots(path, [scopeDir]);
8609
- const rel = relative7(scopeDir, real);
8610
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep10}`) || isAbsolute13(rel)) {
8733
+ const rel = relative8(scopeDir, real);
8734
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute14(rel)) {
8611
8735
  throw new Error(`ROOT_NOT_ALLOWED: ${path}`);
8612
8736
  }
8613
8737
  return real;
@@ -8755,17 +8879,17 @@ import {
8755
8879
  existsSync as existsSync5,
8756
8880
  constants as fsConstants2,
8757
8881
  lstatSync as lstatSync8,
8758
- realpathSync as realpathSync13
8882
+ realpathSync as realpathSync14
8759
8883
  } from "fs";
8760
8884
  import { open as open2, opendir } from "fs/promises";
8761
- import { isAbsolute as isAbsolute14, join as join24, relative as relative8, sep as sep11 } from "path";
8885
+ import { isAbsolute as isAbsolute15, join as join24, relative as relative9, sep as sep12 } from "path";
8762
8886
  function inside2(root, candidate) {
8763
- const rel = relative8(root, candidate);
8764
- return rel === "" || !rel.startsWith(`..${sep11}`) && rel !== ".." && !isAbsolute14(rel);
8887
+ const rel = relative9(root, candidate);
8888
+ return rel === "" || !rel.startsWith(`..${sep12}`) && rel !== ".." && !isAbsolute15(rel);
8765
8889
  }
8766
8890
  function canonical2(dir) {
8767
8891
  try {
8768
- return realpathSync13.native(dir);
8892
+ return realpathSync14.native(dir);
8769
8893
  } catch {
8770
8894
  return dir;
8771
8895
  }
@@ -8791,7 +8915,7 @@ function scopeRoots(opts) {
8791
8915
  let skillsRepo;
8792
8916
  if (opts.skillsRepo !== void 0) {
8793
8917
  try {
8794
- if (!isAbsolute14(opts.skillsRepo)) throw new Error("skillsRepo must be an absolute path");
8918
+ if (!isAbsolute15(opts.skillsRepo)) throw new Error("skillsRepo must be an absolute path");
8795
8919
  skillsRepo = resolveInsideRoots(opts.skillsRepo, opts.roots).real;
8796
8920
  } catch (err) {
8797
8921
  log.warn(
@@ -8803,7 +8927,7 @@ function scopeRoots(opts) {
8803
8927
  for (const root of opts.roots) {
8804
8928
  let real;
8805
8929
  try {
8806
- real = realpathSync13.native(root);
8930
+ real = realpathSync14.native(root);
8807
8931
  } catch {
8808
8932
  continue;
8809
8933
  }
@@ -8826,7 +8950,7 @@ function resolveScopeRoots(opts) {
8826
8950
  for (const root of scopeRoots(opts)) {
8827
8951
  let dirReal;
8828
8952
  try {
8829
- dirReal = realpathSync13.native(root.dir);
8953
+ dirReal = realpathSync14.native(root.dir);
8830
8954
  } catch {
8831
8955
  continue;
8832
8956
  }
@@ -8931,7 +9055,7 @@ async function readEntry(root, entry, log, budget) {
8931
9055
  if (!SKILL_SLUG.test(slug)) return skip("the filename is not a legal slug");
8932
9056
  let real;
8933
9057
  try {
8934
- real = realpathSync13.native(path);
9058
+ real = realpathSync14.native(path);
8935
9059
  } catch {
8936
9060
  return skip("cannot be resolved");
8937
9061
  }
@@ -8984,7 +9108,7 @@ async function readEntry(root, entry, log, budget) {
8984
9108
  ...parsed.ok && parsed.data.description !== void 0 ? { description: parsed.data.description } : {},
8985
9109
  declaredTools: parsed.ok ? parsed.data.tools : [],
8986
9110
  permission: parsed.ok ? parsed.data.permission : "inherit",
8987
- sourcePath: relative8(root.base, path),
9111
+ sourcePath: relative9(root.base, path),
8988
9112
  ...root.projectPath !== void 0 ? { projectPath: root.projectPath } : {},
8989
9113
  contentHash: createHash4("sha256").update(bytes).digest("hex"),
8990
9114
  ...fileMtime !== void 0 ? { fileMtime } : {},
@@ -9100,7 +9224,7 @@ import {
9100
9224
  rmSync as rmSync9
9101
9225
  } from "fs";
9102
9226
  import { open as open3, writeFile } from "fs/promises";
9103
- import { dirname as dirname9, isAbsolute as isAbsolute15, join as join25, relative as relative9, sep as sep12 } from "path";
9227
+ import { dirname as dirname9, isAbsolute as isAbsolute16, join as join25, relative as relative10, sep as sep13 } from "path";
9104
9228
  function isSkillRefusal(err) {
9105
9229
  const msg = err instanceof Error ? err.message : String(err);
9106
9230
  return msg.startsWith("ROOT_NOT_ALLOWED") || msg.startsWith(BUDGET) || msg.includes("is not a plain directory") || msg.includes("is not part of the session skills plugin");
@@ -9311,24 +9435,24 @@ var init_materialise = __esm({
9311
9435
  BUDGET = "SKILL_BUDGET:";
9312
9436
  PRECEDENCE = { personal: 0, project: 1, workspace: 2 };
9313
9437
  isAtOrBelow2 = (p, dir) => {
9314
- const rel = relative9(dir, p);
9315
- return rel === "" || !rel.startsWith(`..${sep12}`) && rel !== ".." && !isAbsolute15(rel);
9438
+ const rel = relative10(dir, p);
9439
+ return rel === "" || !rel.startsWith(`..${sep13}`) && rel !== ".." && !isAbsolute16(rel);
9316
9440
  };
9317
9441
  }
9318
9442
  });
9319
9443
 
9320
9444
  // src/sessions/managed-git-guard.ts
9321
- import { realpathSync as realpathSync14 } from "fs";
9322
- import { isAbsolute as isAbsolute16, relative as relative10, resolve as resolve3, sep as sep13 } from "path";
9445
+ import { realpathSync as realpathSync15 } from "fs";
9446
+ import { isAbsolute as isAbsolute17, relative as relative11, resolve as resolve4, sep as sep14 } from "path";
9323
9447
  function namesGitFiles(path, managedRoot) {
9324
- const rel = relative10(managedRoot, path);
9325
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`) || isAbsolute16(rel)) return false;
9326
- return rel.split(sep13).some((segment) => segment.toLowerCase() === ".git");
9448
+ const rel = relative11(managedRoot, path);
9449
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep14}`) || isAbsolute17(rel)) return false;
9450
+ return rel.split(sep14).some((segment) => segment.toLowerCase() === ".git");
9327
9451
  }
9328
9452
  function writesManagedGit(target, managedRoot, cwd) {
9329
- const absolute = isAbsolute16(target) ? target : `${cwd}${sep13}${target}`;
9330
- const canonical3 = assertInsideRootsOrAncestor(absolute, [sep13]);
9331
- return namesGitFiles(resolve3(absolute), managedRoot) || namesGitFiles(canonical3, managedRoot);
9453
+ const absolute = isAbsolute17(target) ? target : `${cwd}${sep14}${target}`;
9454
+ const canonical3 = assertInsideRootsOrAncestor(absolute, [sep14]);
9455
+ return namesGitFiles(resolve4(absolute), managedRoot) || namesGitFiles(canonical3, managedRoot);
9332
9456
  }
9333
9457
  function targetOf(keys, toolName2, input) {
9334
9458
  const key = keys[toolName2];
@@ -9355,7 +9479,7 @@ function walksInside(path, roots) {
9355
9479
  }
9356
9480
  function realOrSelf(dir) {
9357
9481
  try {
9358
- return realpathSync14.native(dir);
9482
+ return realpathSync15.native(dir);
9359
9483
  } catch {
9360
9484
  return dir;
9361
9485
  }
@@ -9364,13 +9488,13 @@ function walksStrictlyInside(path, dirs) {
9364
9488
  if (dirs.length === 0) return false;
9365
9489
  let canonical3;
9366
9490
  try {
9367
- canonical3 = assertInsideRootsOrAncestor(path, [sep13]);
9491
+ canonical3 = assertInsideRootsOrAncestor(path, [sep14]);
9368
9492
  } catch {
9369
9493
  return false;
9370
9494
  }
9371
9495
  return dirs.some((dir) => {
9372
- const rel = relative10(realOrSelf(dir), canonical3);
9373
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep13}`) && !isAbsolute16(rel);
9496
+ const rel = relative11(realOrSelf(dir), canonical3);
9497
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep14}`) && !isAbsolute17(rel);
9374
9498
  });
9375
9499
  }
9376
9500
  function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
@@ -9383,14 +9507,14 @@ function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
9383
9507
  const written = targetOf(WRITE_TARGET_KEYS, toolName2, input);
9384
9508
  const target = written ?? targetOf(READ_TARGET_KEYS, toolName2, input);
9385
9509
  if (target === null) return null;
9386
- if (!isPlainToolPath(target) || !isAbsolute16(target)) return repositoryPathRefusal(target);
9510
+ if (!isPlainToolPath(target) || !isAbsolute17(target)) return repositoryPathRefusal(target);
9387
9511
  if (written !== null) return walksInside(target, roots) ? null : worktreeBoundRefusal(target);
9388
9512
  return walksInside(target, readable) || walksStrictlyInside(target, readInside()) ? null : worktreeBoundRefusal(target);
9389
9513
  };
9390
9514
  }
9391
9515
  function patternStaysBelow(pattern) {
9392
9516
  const p = pattern.trim();
9393
- if (isAbsolute16(p) || /^[\\/]/.test(p) || /^[A-Za-z]:/.test(p) || p.startsWith("~")) return false;
9517
+ if (isAbsolute17(p) || /^[\\/]/.test(p) || /^[A-Za-z]:/.test(p) || p.startsWith("~")) return false;
9394
9518
  return !p.split(/[\\/]/).includes("..");
9395
9519
  }
9396
9520
  function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
@@ -9398,7 +9522,7 @@ function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
9398
9522
  const bound = createWorktreeBoundGuard(roots, cwd, opts);
9399
9523
  return async (toolName2, input) => {
9400
9524
  const target = targetOf(WRITE_TARGET_KEYS, toolName2, input) ?? targetOf(READ_TARGET_KEYS, toolName2, input);
9401
- if (target !== null && (!isPlainToolPath(target) || !isAbsolute16(target))) {
9525
+ if (target !== null && (!isPlainToolPath(target) || !isAbsolute17(target))) {
9402
9526
  return repositoryPathRefusal(target);
9403
9527
  }
9404
9528
  return await git(toolName2, input) ?? bound(toolName2, input);
@@ -9597,7 +9721,7 @@ var init_relay = __esm({
9597
9721
  });
9598
9722
 
9599
9723
  // src/sessions/repo-settings.ts
9600
- import { isAbsolute as isAbsolute17, relative as relative11, sep as sep14 } from "path";
9724
+ import { isAbsolute as isAbsolute18, relative as relative12, sep as sep15 } from "path";
9601
9725
  function repoSettingsPolicy(input) {
9602
9726
  const bypassLock = input.permissionMode === "bypassPermissions" ? {} : { permissions: { disableBypassPermissionsMode: "disable" } };
9603
9727
  const claudeMd = () => ({
@@ -9619,8 +9743,8 @@ function repoSettingsPolicy(input) {
9619
9743
  return { settingSources: ["user"], ...claudeMd(), settings: { ...bypassLock } };
9620
9744
  }
9621
9745
  function insideRoot2(candidate, root) {
9622
- const rel = relative11(root, candidate);
9623
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep14}`) && !isAbsolute17(rel);
9746
+ const rel = relative12(root, candidate);
9747
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep15}`) && !isAbsolute18(rel);
9624
9748
  }
9625
9749
  var init_repo_settings = __esm({
9626
9750
  "src/sessions/repo-settings.ts"() {
@@ -9629,10 +9753,10 @@ var init_repo_settings = __esm({
9629
9753
  });
9630
9754
 
9631
9755
  // src/sessions/secret-files.ts
9632
- import { realpathSync as realpathSync15 } from "fs";
9756
+ import { realpathSync as realpathSync16 } from "fs";
9633
9757
  import { realpath } from "fs/promises";
9634
9758
  import { homedir as homedir5 } from "os";
9635
- import { basename, isAbsolute as isAbsolute18, relative as relative12, resolve as resolve4, sep as sep15 } from "path";
9759
+ import { basename, isAbsolute as isAbsolute19, relative as relative13, resolve as resolve5, sep as sep16 } from "path";
9636
9760
  function isSecretFileName(name) {
9637
9761
  const n = name.toLowerCase();
9638
9762
  if (n.startsWith(".env")) return true;
@@ -9712,23 +9836,23 @@ function createSecretGuard(cwd, protectedPaths2 = []) {
9712
9836
  const home = homedir5();
9713
9837
  const protectedReal = protectedPaths2.flatMap((p) => {
9714
9838
  try {
9715
- const real = realpathSync15.native(p);
9839
+ const real = realpathSync16.native(p);
9716
9840
  return real === p ? [p] : [p, real];
9717
9841
  } catch {
9718
9842
  return [p];
9719
9843
  }
9720
9844
  });
9721
- const expand = (arg) => arg === "~" ? home : arg.startsWith("~/") ? resolve4(home, arg.slice(2)) : arg;
9845
+ const expand = (arg) => arg === "~" ? home : arg.startsWith("~/") ? resolve5(home, arg.slice(2)) : arg;
9722
9846
  const pathShaped = (path) => isSecretFileName(basename(path)) || inSecretDir(path) || protectedReal.some((root) => within2(path, root));
9723
9847
  const argTargets = (raw) => {
9724
9848
  if (GLOB_CHARS.test(raw))
9725
- return globTargetsSecrets(raw) || pathShaped(resolve4(cwd, expand(raw.replace(/[*?[{].*$/, ""))));
9726
- return pathShaped(raw) || pathShaped(resolve4(cwd, expand(raw)));
9849
+ return globTargetsSecrets(raw) || pathShaped(resolve5(cwd, expand(raw.replace(/[*?[{].*$/, ""))));
9850
+ return pathShaped(raw) || pathShaped(resolve5(cwd, expand(raw)));
9727
9851
  };
9728
9852
  const pathIsSecret = async (raw) => {
9729
9853
  if (argTargets(raw)) return true;
9730
9854
  try {
9731
- return pathShaped(await realpath(resolve4(cwd, expand(raw))));
9855
+ return pathShaped(await realpath(resolve5(cwd, expand(raw))));
9732
9856
  } catch {
9733
9857
  return false;
9734
9858
  }
@@ -9793,8 +9917,8 @@ var init_secret_files = __esm({
9793
9917
  CONTROLS = ["x.txt", "x.ts", "x.md", "readme", ".gitignore"];
9794
9918
  GLOB_CHARS = /[*?[{]/;
9795
9919
  within2 = (path, root) => {
9796
- const rel = relative12(root, path);
9797
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep15}`) && !isAbsolute18(rel);
9920
+ const rel = relative13(root, path);
9921
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep16}`) && !isAbsolute19(rel);
9798
9922
  };
9799
9923
  PATH_KEYS = {
9800
9924
  Read: ["file_path"],
@@ -9845,11 +9969,11 @@ var init_task_sessions = __esm({
9845
9969
 
9846
9970
  // src/sessions/user-guards.ts
9847
9971
  import { closeSync as closeSync4, constants as constants4, fstatSync, openSync as openSync4, readFileSync as readFileSync10 } from "fs";
9848
- import { isAbsolute as isAbsolute19, join as join27 } from "path";
9972
+ import { isAbsolute as isAbsolute20, join as join27 } from "path";
9849
9973
  function readUserGuards(configDir) {
9850
9974
  const settingsFile = join27(configDir, "settings.json");
9851
9975
  const none = { settingsFile, preToolUseHooks: 0, denyRules: 0 };
9852
- if (!isAbsolute19(configDir)) return none;
9976
+ if (!isAbsolute20(configDir)) return none;
9853
9977
  const text = readSmallFile(settingsFile);
9854
9978
  if (text === null) return none;
9855
9979
  let settings;
@@ -9915,8 +10039,8 @@ var init_user_guards = __esm({
9915
10039
 
9916
10040
  // src/sessions/manager.ts
9917
10041
  import { randomUUID as randomUUID5 } from "crypto";
9918
- import { realpathSync as realpathSync16 } from "fs";
9919
- import { isAbsolute as isAbsolute20, join as join28, relative as relative13, sep as sep16 } from "path";
10042
+ import { realpathSync as realpathSync17 } from "fs";
10043
+ import { isAbsolute as isAbsolute21, join as join28, relative as relative14, sep as sep17 } from "path";
9920
10044
  import { isDeepStrictEqual } from "util";
9921
10045
  function releaseCreds(creds) {
9922
10046
  if (creds?.state === "ok") creds.release();
@@ -9965,7 +10089,7 @@ function shrink(event) {
9965
10089
  return null;
9966
10090
  }
9967
10091
  }
9968
- var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
10092
+ var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, DRAINED_AFTER_MS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
9969
10093
  var init_manager = __esm({
9970
10094
  "src/sessions/manager.ts"() {
9971
10095
  "use strict";
@@ -10006,12 +10130,13 @@ var init_manager = __esm({
10006
10130
  state: "unavailable",
10007
10131
  readOnlyText: READ_ONLY_UNAVAILABLE
10008
10132
  };
10133
+ DRAINED_AFTER_MS = 2e3;
10009
10134
  roundUsd = (usd) => Math.round(usd * 1e6) / 1e6;
10010
10135
  UNIQUE_VIOLATION = "23505";
10011
10136
  ROW_WRITE_TIMEOUT_MS = 3e4;
10012
10137
  ORPHANED_STATUSES = ["running", "awaiting_permission"];
10013
10138
  RESTARTABLE_STATUSES = /* @__PURE__ */ new Set(["idle", ...ORPHANED_STATUSES]);
10014
- isAtOrBelow3 = (p, dir) => p === dir || p.startsWith(dir.endsWith(sep16) ? dir : `${dir}${sep16}`);
10139
+ isAtOrBelow3 = (p, dir) => p === dir || p.startsWith(dir.endsWith(sep17) ? dir : `${dir}${sep17}`);
10015
10140
  DENY_MESSAGE_MAX = 2e3;
10016
10141
  EXIT_ERROR_MAX = 8 * 1024;
10017
10142
  INITIAL_PROMPT_MAX = 2e5;
@@ -10379,7 +10504,7 @@ var init_manager = __esm({
10379
10504
  * chat-<hex>`. Null for any other path.
10380
10505
  */
10381
10506
  cloneOfWorktree(sessionId, worktree) {
10382
- const parts = relative13(this.deps.managedRoot, worktree).split(sep16);
10507
+ const parts = relative14(this.deps.managedRoot, worktree).split(sep17);
10383
10508
  const [owner, name, dot, worktrees, leaf] = parts;
10384
10509
  if (parts.length !== 5 || owner === void 0 || name === void 0 || dot !== ".cayenne" || worktrees !== "worktrees" || leaf !== repositoryWorktreeName(sessionId) || !RepositoryRef.shape.owner.safeParse(owner).success || !RepositoryRef.shape.name.safeParse(name).success) {
10385
10510
  return null;
@@ -10609,6 +10734,7 @@ var init_manager = __esm({
10609
10734
  );
10610
10735
  await this.setStatus(sessionId, "running");
10611
10736
  live.session.prompt(text, p.clientUuid, staged);
10737
+ this.turnHandedOver(live);
10612
10738
  this.deps.timings?.prompted(sessionId);
10613
10739
  } catch (err) {
10614
10740
  staged.discard();
@@ -10958,7 +11084,7 @@ var init_manager = __esm({
10958
11084
  if (stored === null) return false;
10959
11085
  if (isAtOrBelow3(stored, target)) return true;
10960
11086
  try {
10961
- return isAtOrBelow3(realpathSync16.native(stored), target);
11087
+ return isAtOrBelow3(realpathSync17.native(stored), target);
10962
11088
  } catch {
10963
11089
  return false;
10964
11090
  }
@@ -10971,12 +11097,83 @@ var init_manager = __esm({
10971
11097
  async interrupt(sessionId) {
10972
11098
  const live = this.live.get(sessionId);
10973
11099
  if (live) {
10974
- await this.provider(() => live.session.interrupt());
11100
+ const { turns } = live;
11101
+ turns.stops = turns.owed;
11102
+ turns.chased = false;
11103
+ turns.early = turns.owed > 0 && !turns.begun && !live.stopping && !live.dropping;
11104
+ if (turns.early) {
11105
+ this.sendStop(sessionId, live);
11106
+ if (live.status === "running") await this.setStatus(sessionId, "idle");
11107
+ return {};
11108
+ }
11109
+ try {
11110
+ await this.provider(() => live.session.interrupt());
11111
+ } catch (err) {
11112
+ turns.stops = 0;
11113
+ throw err;
11114
+ }
10975
11115
  return {};
10976
11116
  }
10977
11117
  await this.settleOrphans([sessionId]);
10978
11118
  return {};
10979
11119
  }
11120
+ /**
11121
+ * A stop nobody waits on: a provider that is starting, or between turns, may answer late.
11122
+ * `'turn'` when it is a Stop sent again for a turn that produced output: what it covers was
11123
+ * settled when it was asked, and a provider with a queue of its own (Codex) would take a plain
11124
+ * stop for a new one and stop the prompts handed over since.
11125
+ */
11126
+ sendStop(sessionId, live, scope) {
11127
+ void live.session.interrupt(scope).catch(
11128
+ (err) => this.deps.log.warn(
11129
+ { err: err instanceof Error ? err.message : String(err), sessionId },
11130
+ "a stop sent ahead of its turn failed"
11131
+ )
11132
+ );
11133
+ }
11134
+ /** A prompt went to the provider. */
11135
+ turnHandedOver(live) {
11136
+ const { turns } = live;
11137
+ const leftover = live.provider === "claude" && turns.stops === 0 && turns.drainedAt !== null && Date.now() - turns.drainedAt > DRAINED_AFTER_MS;
11138
+ if (leftover) {
11139
+ turns.owed = 0;
11140
+ turns.stops = 0;
11141
+ }
11142
+ turns.drainedAt = null;
11143
+ turns.owed += 1;
11144
+ }
11145
+ /** The provider announced a turn (`init`): if a Stop covers it, this is where it lands. */
11146
+ turnBegan(sessionId, live) {
11147
+ if (live.provider !== "claude") return;
11148
+ const { turns } = live;
11149
+ turns.drainedAt = null;
11150
+ if (turns.owed > 0) turns.begun = true;
11151
+ if (turns.stops > 0 && turns.early) this.sendStop(sessionId, live);
11152
+ }
11153
+ /**
11154
+ * The provider produced output. For a turn stopped before it began that means the stop did not
11155
+ * land (a backend with no `init` per turn, or a stop that was lost): it is sent once more, and
11156
+ * `true` tells the caller the row must say `running` again, since a turn is.
11157
+ */
11158
+ turnOutput(sessionId, live) {
11159
+ const { turns } = live;
11160
+ turns.drainedAt = null;
11161
+ if (turns.owed > 0) turns.begun = true;
11162
+ if (turns.stops === 0 || !turns.early || turns.chased) return false;
11163
+ turns.chased = true;
11164
+ this.sendStop(sessionId, live, "turn");
11165
+ return live.status === "idle";
11166
+ }
11167
+ /** A turn ended: whatever a Stop still covers is behind it, and has not begun. */
11168
+ turnEnded(live) {
11169
+ const { turns } = live;
11170
+ turns.owed = Math.max(0, turns.owed - 1);
11171
+ turns.stops = Math.max(0, turns.stops - 1);
11172
+ turns.begun = false;
11173
+ turns.drainedAt = Date.now();
11174
+ turns.early = true;
11175
+ turns.chased = false;
11176
+ }
10980
11177
  /**
10981
11178
  * Put rows on this runner that claim a turn in progress back to `idle`, for the chats that have
10982
11179
  * no live session here — at boot every one of them, since nothing is live yet, and again when
@@ -11274,10 +11471,10 @@ var init_manager = __esm({
11274
11471
  */
11275
11472
  claudeProjectDir(cwd) {
11276
11473
  const configDir = sessionClaudeConfigDir(this.deps.claudeEnv);
11277
- if (configDir === "" || !isAbsolute20(configDir)) return null;
11474
+ if (configDir === "" || !isAbsolute21(configDir)) return null;
11278
11475
  let canonical3 = configDir;
11279
11476
  try {
11280
- canonical3 = realpathSync16.native(configDir);
11477
+ canonical3 = realpathSync17.native(configDir);
11281
11478
  } catch {
11282
11479
  }
11283
11480
  return join28(canonical3, "projects", claudeProjectDirName(cwd));
@@ -11500,7 +11697,7 @@ var init_manager = __esm({
11500
11697
  */
11501
11698
  fromCwd(cwd, value) {
11502
11699
  if (!isPlainToolPath(value)) throw new Error(notPlainPathRefusal(value));
11503
- return isAbsolute20(value) ? value : `${cwd}${sep16}${value}`;
11700
+ return isAbsolute21(value) ? value : `${cwd}${sep17}${value}`;
11504
11701
  }
11505
11702
  /**
11506
11703
  * §2.9: a path in `updatedInput` passes the same boundary the session's `cwd` did, and the
@@ -11809,6 +12006,7 @@ var init_manager = __esm({
11809
12006
  permissions: /* @__PURE__ */ new Map(),
11810
12007
  cwd: opts.cwd,
11811
12008
  stopping: false,
12009
+ turns: { owed: 0, begun: false, drainedAt: null, stops: 0, early: false, chased: false },
11812
12010
  dropping: null,
11813
12011
  inflight: /* @__PURE__ */ new Set(),
11814
12012
  writes: Promise.resolve(),
@@ -11975,9 +12173,12 @@ var init_manager = __esm({
11975
12173
  live.inflight.add(task);
11976
12174
  void task.finally(() => live.inflight.delete(task));
11977
12175
  };
11978
- session.on(
11979
- "init",
11980
- (init) => guard(async () => {
12176
+ const output = () => {
12177
+ if (this.turnOutput(sessionId, live)) guard(() => this.setStatus(sessionId, "running"))();
12178
+ };
12179
+ session.on("init", (init) => {
12180
+ this.turnBegan(sessionId, live);
12181
+ return guard(async () => {
11981
12182
  await this.updateSession(sessionId, {
11982
12183
  provider_session_id: init.providerSessionId,
11983
12184
  model: init.model
@@ -11988,9 +12189,10 @@ var init_manager = __esm({
11988
12189
  providerSessionId: init.providerSessionId,
11989
12190
  model: init.model
11990
12191
  });
11991
- })()
11992
- );
12192
+ })();
12193
+ });
11993
12194
  session.on("stream", (s) => {
12195
+ output();
11994
12196
  this.deps.timings?.streamed(sessionId);
11995
12197
  guard(
11996
12198
  () => this.broadcast(sessionId, {
@@ -12001,9 +12203,9 @@ var init_manager = __esm({
12001
12203
  })
12002
12204
  )();
12003
12205
  });
12004
- session.on(
12005
- "message",
12006
- (m) => guard(async () => {
12206
+ session.on("message", (m) => {
12207
+ if (m.role === "assistant" || m.role === "tool") output();
12208
+ return guard(async () => {
12007
12209
  if (m.role === "user" && m.providerUuid && live.echoed.has(m.providerUuid)) return;
12008
12210
  await this.persist(
12009
12211
  sessionId,
@@ -12014,8 +12216,8 @@ var init_manager = __esm({
12014
12216
  m.parentToolUseId,
12015
12217
  m.raw
12016
12218
  );
12017
- })()
12018
- );
12219
+ })();
12220
+ });
12019
12221
  session.on(
12020
12222
  "permission",
12021
12223
  (rawReq) => guard(async () => {
@@ -12104,6 +12306,7 @@ var init_manager = __esm({
12104
12306
  })()
12105
12307
  );
12106
12308
  session.on("result", (r) => {
12309
+ this.turnEnded(live);
12107
12310
  this.deps.timings?.finished(sessionId);
12108
12311
  const lastSeq = live.writes.then(() => live.seq);
12109
12312
  guard(async () => {
@@ -12594,7 +12797,7 @@ var init_mirror2 = __esm({
12594
12797
 
12595
12798
  // src/skills/service.ts
12596
12799
  import { createHash as createHash6 } from "crypto";
12597
- import { constants as fsConstants4, realpathSync as realpathSync17 } from "fs";
12800
+ import { constants as fsConstants4, realpathSync as realpathSync18 } from "fs";
12598
12801
  import { open as open4 } from "fs/promises";
12599
12802
  function createSkillsService(deps) {
12600
12803
  const log = deps.log ?? logger;
@@ -12656,7 +12859,7 @@ function createSkillsService(deps) {
12656
12859
  let wanted;
12657
12860
  if (payload.scope === "project" && payload.projectPath !== void 0) {
12658
12861
  try {
12659
- wanted = realpathSync17.native(payload.projectPath);
12862
+ wanted = realpathSync18.native(payload.projectPath);
12660
12863
  } catch {
12661
12864
  throw missing();
12662
12865
  }
@@ -12814,12 +13017,12 @@ function createTerminalChannels({
12814
13017
  skipped: 0
12815
13018
  });
12816
13019
  try {
12817
- await new Promise((resolve5, reject) => {
13020
+ await new Promise((resolve6, reject) => {
12818
13021
  let settled = false;
12819
13022
  channel.subscribe((status, err) => {
12820
13023
  if (status === "SUBSCRIBED") {
12821
13024
  settled = true;
12822
- resolve5();
13025
+ resolve6();
12823
13026
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
12824
13027
  if (settled) {
12825
13028
  log.warn({ terminalId, status, err: err?.message }, "terminal channel dropped");
@@ -12851,9 +13054,9 @@ var init_channels = __esm({
12851
13054
  });
12852
13055
 
12853
13056
  // src/terminal/env.ts
12854
- import { readFileSync as readFileSync12, realpathSync as realpathSync18 } from "fs";
13057
+ import { readFileSync as readFileSync12, realpathSync as realpathSync19 } from "fs";
12855
13058
  import { lstat } from "fs/promises";
12856
- import { dirname as dirname10, isAbsolute as isAbsolute21, relative as relative14 } from "path";
13059
+ import { dirname as dirname10, isAbsolute as isAbsolute22, relative as relative15 } from "path";
12857
13060
  function terminalEnv(base, { runnerOwner, claudeConfigDir }) {
12858
13061
  const env = {};
12859
13062
  for (const [key, value] of Object.entries(base)) {
@@ -12925,8 +13128,8 @@ function tryRealpath(fs, p) {
12925
13128
  }
12926
13129
  }
12927
13130
  function inside3(candidate, root) {
12928
- const rel = relative14(root, candidate);
12929
- return rel === "" || !rel.startsWith("..") && !isAbsolute21(rel);
13131
+ const rel = relative15(root, candidate);
13132
+ return rel === "" || !rel.startsWith("..") && !isAbsolute22(rel);
12930
13133
  }
12931
13134
  var SECRET_ENV, MEMBER_ENV_ALLOW, MEMBER_ENV_KEYS, LOCALE_ENV, nodeShellFs;
12932
13135
  var init_env2 = __esm({
@@ -12956,7 +13159,7 @@ var init_env2 = __esm({
12956
13159
  return "";
12957
13160
  }
12958
13161
  },
12959
- realpath: (p) => realpathSync18.native(p),
13162
+ realpath: (p) => realpathSync19.native(p),
12960
13163
  lstat: (p) => lstat(p)
12961
13164
  };
12962
13165
  }
@@ -13034,12 +13237,12 @@ var init_process_group = __esm({
13034
13237
  defaultKill = (pid, signal) => {
13035
13238
  process.kill(pid, signal);
13036
13239
  };
13037
- defaultRun = (file, args) => new Promise((resolve5, reject) => {
13240
+ defaultRun = (file, args) => new Promise((resolve6, reject) => {
13038
13241
  execFile5(
13039
13242
  file,
13040
13243
  args,
13041
13244
  { encoding: "utf8", timeout: 5e3, env: { LC_ALL: "C", TZ: "UTC" } },
13042
- (err, stdout) => err ? reject(err) : resolve5(stdout)
13245
+ (err, stdout) => err ? reject(err) : resolve6(stdout)
13043
13246
  );
13044
13247
  });
13045
13248
  gone = (err) => err?.code === "ESRCH";
@@ -13099,7 +13302,7 @@ var init_ring = __esm({
13099
13302
 
13100
13303
  // src/terminal/manager.ts
13101
13304
  import { constants as osConstants } from "os";
13102
- import { sep as sep17 } from "path";
13305
+ import { sep as sep18 } from "path";
13103
13306
  function mayHoldTerminal(userId, deps) {
13104
13307
  return userId === deps.runnerUserId ? deps.serves(userId) : deps.memberTerminalIds.includes(userId) && deps.serves(userId) && deps.rosterHealthy();
13105
13308
  }
@@ -13122,10 +13325,10 @@ var init_manager2 = __esm({
13122
13325
  MINUTE_MS = 6e4;
13123
13326
  LIST_MAX_ATTEMPTS = 3;
13124
13327
  LIST_RETRY_DELAY_MS = 250;
13125
- delay = (ms) => new Promise((resolve5) => {
13126
- setTimeout(resolve5, ms).unref();
13328
+ delay = (ms) => new Promise((resolve6) => {
13329
+ setTimeout(resolve6, ms).unref();
13127
13330
  });
13128
- isAtOrBelow4 = (path, dir) => path === dir || path.startsWith(dir.endsWith(sep17) ? dir : `${dir}${sep17}`);
13331
+ isAtOrBelow4 = (path, dir) => path === dir || path.startsWith(dir.endsWith(sep18) ? dir : `${dir}${sep18}`);
13129
13332
  signalName = (n) => {
13130
13333
  if (!n) return null;
13131
13334
  for (const [name, value] of Object.entries(osConstants.signals)) if (value === n) return name;
@@ -13377,8 +13580,8 @@ var init_manager2 = __esm({
13377
13580
  }
13378
13581
  const term = this.terms.get(terminalId);
13379
13582
  if (!term || term.exited) return Promise.resolve({ closed: true });
13380
- return new Promise((resolve5) => {
13381
- term.waiters.push(() => resolve5({ closed: true }));
13583
+ return new Promise((resolve6) => {
13584
+ term.waiters.push(() => resolve6({ closed: true }));
13382
13585
  this.kill(terminalId, term);
13383
13586
  });
13384
13587
  }
@@ -14715,7 +14918,7 @@ var init_start = __esm({
14715
14918
  });
14716
14919
 
14717
14920
  // src/cli.ts
14718
- import { existsSync as existsSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync13, realpathSync as realpathSync19, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
14921
+ import { existsSync as existsSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync13, realpathSync as realpathSync20, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
14719
14922
  import { homedir as homedir7, hostname as hostname2, userInfo } from "os";
14720
14923
  import { dirname as dirname11, join as join31 } from "path";
14721
14924
  import { Command } from "commander";
@@ -14767,12 +14970,22 @@ async function doctor(deps) {
14767
14970
  detail: drifted ? `service uses ${deps.record?.nodePath}, current node is ${deps.execPath}` : deps.execPath,
14768
14971
  fix: drifted ? "Run `cayenne update` to re-pin the service to this node." : void 0
14769
14972
  });
14770
- checks.push({
14771
- name: "claude",
14772
- ok: deps.claudeOnPath !== null,
14773
- detail: deps.claudeOnPath ?? "not found on PATH",
14774
- fix: deps.claudeOnPath ? void 0 : "Install the claude CLI: https://claude.com/claude-code"
14775
- });
14973
+ const recorded = deps.record?.claudePath;
14974
+ const pinned = deps.claudeOnPath !== null && recorded !== void 0 ? recorded : null;
14975
+ const lost = pinned === null ? null : !deps.exists(pinned) ? "which no longer exists" : deps.temporary(pinned) ? "a temporary shim that is deleted when its terminal session ends" : null;
14976
+ checks.push(
14977
+ lost ? {
14978
+ name: "claude",
14979
+ ok: false,
14980
+ detail: `service uses ${pinned}, ${lost}`,
14981
+ fix: "Run `cayenne update` to re-pin the service to the claude on PATH."
14982
+ } : {
14983
+ name: "claude",
14984
+ ok: deps.claudeOnPath !== null,
14985
+ detail: deps.claudeOnPath ?? "not found on PATH",
14986
+ fix: deps.claudeOnPath ? void 0 : "Install the claude CLI: https://claude.com/claude-code"
14987
+ }
14988
+ );
14776
14989
  const loaded = await deps.launchd.isLoaded(deps.label);
14777
14990
  const running = loaded && await deps.launchd.isRunning(deps.label);
14778
14991
  checks.push({
@@ -14947,10 +15160,11 @@ async function up(deps) {
14947
15160
  const running = loaded && await deps.launchd.isRunning(label);
14948
15161
  const wantsChange = Boolean(deps.roots?.length || deps.workspaceSlug || claude.changed);
14949
15162
  if (!loaded && deps.cfg.runnerId) {
14950
- const { data } = await deps.db.from("runners").select("last_seen_at, profile").eq("id", deps.cfg.runnerId).maybeSingle();
15163
+ const { data } = await deps.db.from("runners").select("last_seen_at, profile, status").eq("id", deps.cfg.runnerId).maybeSingle();
14951
15164
  const seen = data?.last_seen_at;
14952
15165
  const ours = data?.profile === deps.profile || data?.profile === UNCLAIMED_PROFILE;
14953
- if (ours && seen && Date.now() - new Date(seen).getTime() < RUNNER_ONLINE_WINDOW_MS) {
15166
+ const fresh = seen && Date.now() - new Date(seen).getTime() < RUNNER_ONLINE_WINDOW_MS;
15167
+ if (ours && fresh && data?.status !== "offline") {
14954
15168
  throw new Error(
14955
15169
  "a runner for this machine is already online without the service \u2014 stop the foreground `cayenne start` (or `cayenne down` elsewhere) before running `up`"
14956
15170
  );
@@ -15019,14 +15233,58 @@ init_profile();
15019
15233
  init_registry2();
15020
15234
 
15021
15235
  // src/service/install.ts
15022
- import { chmodSync as chmodSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
15023
- import { dirname as dirname2, join as join6 } from "path";
15236
+ import {
15237
+ chmodSync as chmodSync3,
15238
+ existsSync as existsSync3,
15239
+ mkdirSync as mkdirSync3,
15240
+ readFileSync as readFileSync3,
15241
+ realpathSync as realpathSync4,
15242
+ writeFileSync as writeFileSync3
15243
+ } from "fs";
15244
+ import { tmpdir } from "os";
15245
+ import { dirname as dirname2, isAbsolute as isAbsolute4, join as join6, relative, resolve, sep } from "path";
15024
15246
  var recordPath = (cayenneHome2) => join6(cayenneHome2, "app", "install.json");
15025
15247
  async function which(exec4, bin) {
15026
15248
  const r = await exec4("which", [bin]);
15027
15249
  const path = r.stdout.trim();
15028
15250
  return r.code === 0 && path ? path : null;
15029
15251
  }
15252
+ async function whichAll(exec4, bin) {
15253
+ const r = await exec4("which", ["-a", bin]);
15254
+ if (r.code !== 0) return [];
15255
+ const paths = r.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
15256
+ return [...new Set(paths)];
15257
+ }
15258
+ function realOrSpelled(path) {
15259
+ try {
15260
+ return realpathSync4(path);
15261
+ } catch {
15262
+ return resolve(path);
15263
+ }
15264
+ }
15265
+ var isInside = (dir, path) => {
15266
+ const rel = relative(dir, path);
15267
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute4(rel);
15268
+ };
15269
+ function isTemporary(path, tmpDir = tmpdir()) {
15270
+ const roots = [resolve(tmpDir), realOrSpelled(tmpDir)];
15271
+ return [resolve(path), realOrSpelled(path)].some((p) => roots.some((root) => isInside(root, p)));
15272
+ }
15273
+ async function resolveClaude(exec4, tmpDir) {
15274
+ const candidates = await whichAll(exec4, "claude");
15275
+ const lasting = candidates.find((path) => !isTemporary(path, tmpDir));
15276
+ if (lasting) return lasting;
15277
+ if (candidates[0]) {
15278
+ throw new Error(
15279
+ `claude on PATH is a temporary shim: ${candidates[0]}
15280
+ It is deleted when the terminal session that made it ends, and the service would lose claude with it.
15281
+ Put the claude CLI itself on PATH, or run this from another terminal: https://claude.com/claude-code`
15282
+ );
15283
+ }
15284
+ throw new Error(
15285
+ "claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
15286
+ );
15287
+ }
15030
15288
  function buildPathEnv(nodePath, claudePath) {
15031
15289
  const dirs = ["/usr/bin", "/bin", "/usr/sbin", "/sbin", dirname2(nodePath), dirname2(claudePath)];
15032
15290
  return [...new Set(dirs)].join(":");
@@ -15049,12 +15307,7 @@ function readInstallRecord(cayenneHome2) {
15049
15307
  }
15050
15308
  }
15051
15309
  async function installApp(deps) {
15052
- const claudePath = await which(deps.exec, "claude");
15053
- if (!claudePath) {
15054
- throw new Error(
15055
- "claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
15056
- );
15057
- }
15310
+ const claudePath = await resolveClaude(deps.exec, deps.tmpDir ?? tmpdir());
15058
15311
  const appDir = join6(deps.cayenneHome, "app");
15059
15312
  mkdirSync3(appDir, { recursive: true, mode: 448 });
15060
15313
  const install = await deps.exec("npm", [
@@ -15101,10 +15354,10 @@ exec ${sh(nodePath)} ${sh(cliPath)} "$@"
15101
15354
  // src/service/launchd.ts
15102
15355
  import { execFile } from "child_process";
15103
15356
  function nodeExec() {
15104
- return (cmd, args) => new Promise((resolve5) => {
15357
+ return (cmd, args) => new Promise((resolve6) => {
15105
15358
  execFile(cmd, args, { encoding: "utf8" }, (err, stdout, stderr) => {
15106
15359
  const code = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
15107
- resolve5({ code, stdout, stderr });
15360
+ resolve6({ code, stdout, stderr });
15108
15361
  });
15109
15362
  });
15110
15363
  }
@@ -15303,6 +15556,8 @@ program.command("doctor").description("Check the install and say how to fix what
15303
15556
  record: readInstallRecord(cayenneDir()),
15304
15557
  execPath: process.execPath,
15305
15558
  claudeOnPath: await which(exec4, "claude"),
15559
+ exists: existsSync7,
15560
+ temporary: (path) => isTemporary(path),
15306
15561
  label,
15307
15562
  launchd,
15308
15563
  backendUrl: backend?.url ?? null,
@@ -15381,8 +15636,8 @@ program.command("uninstall").description("Stop the service and remove the launch
15381
15636
  console.log(`removed ${label}`);
15382
15637
  return;
15383
15638
  }
15384
- const dir = realpathSync19(cayenneDir());
15385
- const home = realpathSync19(homedir7());
15639
+ const dir = realpathSync20(cayenneDir());
15640
+ const home = realpathSync20(homedir7());
15386
15641
  if (dir === "/" || dir === home || home.startsWith(`${dir}/`)) {
15387
15642
  throw new Error(`refusing to purge ${dir}: it is the home directory or an ancestor of it`);
15388
15643
  }