@jpcasabiancai/cayenne 0.5.4 → 0.5.6

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 +366 -210
  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
  }
@@ -7937,14 +7937,14 @@ var init_session2 = __esm({
7937
7937
  this.respondPermission(id, { behavior: "deny", message, interrupt: false });
7938
7938
  }
7939
7939
  requestPermission(toolName2, input, suggestions, toolUseId, signal) {
7940
- return new Promise((resolve5) => {
7940
+ return new Promise((resolve6) => {
7941
7941
  const id = randomUUID3();
7942
7942
  const settle2 = (d) => {
7943
7943
  const entry = this.pending.get(id);
7944
7944
  if (!entry) return;
7945
7945
  clearTimeout(entry.timer);
7946
7946
  this.pending.delete(id);
7947
- resolve5(
7947
+ resolve6(
7948
7948
  d.behavior === "allow" ? {
7949
7949
  behavior: "allow",
7950
7950
  updatedInput: d.updatedInput ?? input,
@@ -8161,13 +8161,23 @@ import { randomUUID as randomUUID4 } from "crypto";
8161
8161
  import { EventEmitter as EventEmitter2 } from "events";
8162
8162
  import { chmodSync as chmodSync5, lstatSync as lstatSync7, mkdirSync as mkdirSync8, rmdirSync, rmSync as rmSync8, writeFileSync as writeFileSync6 } from "fs";
8163
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
+ }
8164
8174
  function removeFiles(files) {
8165
8175
  for (const file of files) rmSync8(file, { force: true });
8166
8176
  }
8167
8177
  function scrubSecrets(text) {
8168
8178
  return SECRET_PATTERNS.reduce((acc, [pattern, to]) => acc.replace(pattern, to), text);
8169
8179
  }
8170
- 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;
8171
8181
  var init_session3 = __esm({
8172
8182
  "src/codex/session.ts"() {
8173
8183
  "use strict";
@@ -8178,6 +8188,7 @@ var init_session3 = __esm({
8178
8188
  init_policy();
8179
8189
  init_rpc();
8180
8190
  STDERR_TAIL_MAX = 2 * 1024;
8191
+ STOP_WAIT_MS = 2e3;
8181
8192
  SIGKILL_AFTER_MS = 5e3;
8182
8193
  SECRET_PATTERNS = [
8183
8194
  [
@@ -8214,11 +8225,8 @@ var init_session3 = __esm({
8214
8225
  sandbox;
8215
8226
  pendingModel;
8216
8227
  pendingEffort;
8217
- turnId = null;
8218
- running = false;
8228
+ turn = null;
8219
8229
  queue = [];
8220
- /** The image files of the turn in flight, removed when it ends. */
8221
- turnFiles = [];
8222
8230
  approvals = /* @__PURE__ */ new Map();
8223
8231
  streaming = /* @__PURE__ */ new Set();
8224
8232
  stderrTail = [];
@@ -8294,7 +8302,7 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8294
8302
  if (staged.files.length !== staged.images.length) {
8295
8303
  throw new Error("PROVIDER_ERROR: codex images must be staged before the prompt");
8296
8304
  }
8297
- this.queue.push({ text, files: [...staged.files] });
8305
+ this.queue.push({ text, files: [...staged.files], stopped: false });
8298
8306
  this.flush();
8299
8307
  }
8300
8308
  /**
@@ -8336,13 +8344,19 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8336
8344
  }
8337
8345
  return written;
8338
8346
  }
8339
- /** The turn in flight is over: its images have been read, or never will be. */
8340
- endTurnFiles() {
8341
- removeFiles(this.turnFiles);
8342
- 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();
8343
8357
  }
8344
8358
  removeAllFiles() {
8345
- this.endTurnFiles();
8359
+ this.endTurn();
8346
8360
  for (const turn of this.queue) {
8347
8361
  removeFiles(turn.files);
8348
8362
  turn.files = [];
@@ -8356,11 +8370,18 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8356
8370
  }
8357
8371
  /** One turn at a time: Codex has no notion of a second concurrent turn on a thread. */
8358
8372
  flush() {
8359
- if (this.running || !this.rpc || !this.providerSessionId) return;
8373
+ if (this.turn || !this.rpc || !this.providerSessionId) return;
8360
8374
  const next = this.queue.shift();
8361
8375
  if (next === void 0) return;
8362
- this.running = true;
8363
- 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;
8364
8385
  void this.rpc.request("turn/start", {
8365
8386
  threadId: this.providerSessionId,
8366
8387
  // app-server v2 `UserInput`: images as `localImage` paths, then the text.
@@ -8371,11 +8392,9 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8371
8392
  sandboxPolicy: this.sandbox,
8372
8393
  ...this.pendingModel ? { model: this.pendingModel } : {},
8373
8394
  ...this.pendingEffort ? { effort: this.pendingEffort } : {}
8374
- }).then((res) => {
8375
- this.turnId = res.turnId ?? null;
8376
- }).catch((err) => {
8377
- this.running = false;
8378
- this.endTurnFiles();
8395
+ }).then((res) => this.named(turn, res.turn?.id ?? res.turnId)).catch((err) => {
8396
+ if (this.turn !== turn) return;
8397
+ this.endTurn();
8379
8398
  this.emit("result", {
8380
8399
  isError: true,
8381
8400
  durationMs: 0,
@@ -8384,10 +8403,60 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8384
8403
  });
8385
8404
  });
8386
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
+ }
8387
8452
  onNotification(method, params) {
8453
+ const turn = params.turn;
8454
+ if (method.startsWith("item/") && this.turn?.id) {
8455
+ this.began(params.turnId);
8456
+ }
8388
8457
  switch (method) {
8389
8458
  case "turn/started":
8390
- this.turnId = params.turnId ?? this.turnId;
8459
+ this.began(turn?.id ?? params.turnId);
8391
8460
  return;
8392
8461
  case "item/agentMessage/delta":
8393
8462
  this.delta(params, "text", "delta");
@@ -8410,13 +8479,11 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8410
8479
  }
8411
8480
  case "turn/completed":
8412
8481
  case "turn/failed": {
8413
- this.running = false;
8414
- this.turnId = null;
8415
- this.endTurnFiles();
8416
- const status = params.status ?? (method === "turn/failed" ? "failed" : "completed");
8482
+ this.endTurn();
8483
+ const status = turn?.status ?? params.status ?? (method === "turn/failed" ? "failed" : "completed");
8417
8484
  this.emit("result", {
8418
8485
  isError: status === "failed",
8419
- durationMs: params.durationMs ?? 0,
8486
+ durationMs: turn?.durationMs ?? params.durationMs ?? 0,
8420
8487
  numTurns: 1,
8421
8488
  stopReason: status
8422
8489
  });
@@ -8502,23 +8569,23 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8502
8569
  if (this.approvals.has(id) || this.resolvers.has(id)) {
8503
8570
  throw new Error(`VALIDATION: approval id ${id} is already held`);
8504
8571
  }
8505
- return new Promise((resolve5) => {
8572
+ return new Promise((resolve6) => {
8506
8573
  const timer = setTimeout(() => {
8507
8574
  this.approvals.delete(id);
8508
8575
  this.resolvers.delete(id);
8509
8576
  this.questionCounts.delete(id);
8510
8577
  this.emit("permissionTimeout", { id });
8511
- resolve5(onTimeout);
8578
+ resolve6(onTimeout);
8512
8579
  }, this.opts.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS);
8513
8580
  this.approvals.set(id, { rpcId: 0, kind, refusal: onTimeout, timer });
8514
- this.resolvers.set(id, resolve5);
8581
+ this.resolvers.set(id, resolve6);
8515
8582
  this.emit("permission", { id, ...req, suggestions: null });
8516
8583
  });
8517
8584
  }
8518
8585
  respondPermission(id, decision) {
8519
8586
  const entry = this.approvals.get(id);
8520
- const resolve5 = this.resolvers.get(id);
8521
- if (!entry || !resolve5) return false;
8587
+ const resolve6 = this.resolvers.get(id);
8588
+ if (!entry || !resolve6) return false;
8522
8589
  let reply;
8523
8590
  if (entry.kind === "question") {
8524
8591
  if (decision.behavior === "deny") reply = "cancel";
@@ -8535,12 +8602,43 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8535
8602
  this.approvals.delete(id);
8536
8603
  this.resolvers.delete(id);
8537
8604
  this.questionCounts.delete(id);
8538
- resolve5(reply);
8605
+ resolve6(reply);
8539
8606
  return true;
8540
8607
  }
8541
- async interrupt() {
8542
- if (!this.rpc || !this.providerSessionId || !this.turnId) return;
8543
- 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
+ }
8544
8642
  }
8545
8643
  async setPermissionMode(mode) {
8546
8644
  this.mode = mode;
@@ -8566,7 +8664,8 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
8566
8664
  this.resolvers.delete(id);
8567
8665
  this.emit("permissionTimeout", { id });
8568
8666
  }
8569
- await this.interrupt();
8667
+ const stoppable = this.turn?.started && this.turn.id;
8668
+ await (stoppable ? this.interrupt().catch(() => void 0) : void 0);
8570
8669
  this.removeAllFiles();
8571
8670
  this.rpc?.close();
8572
8671
  this.child?.stdin?.end();
@@ -8615,15 +8714,15 @@ var init_types = __esm({
8615
8714
  });
8616
8715
 
8617
8716
  // src/skills/paths.ts
8618
- import { realpathSync as realpathSync12 } from "fs";
8619
- 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";
8620
8719
  function clearSessionSkills(home, runnerId, heldBy) {
8621
8720
  clearRunnerStaging(home, join23(home, "session-skills"), runnerId, heldBy);
8622
8721
  }
8623
8722
  function assertInsideScopeDir(path, scopeDir) {
8624
8723
  let dirNow;
8625
8724
  try {
8626
- dirNow = realpathSync12.native(scopeDir);
8725
+ dirNow = realpathSync13.native(scopeDir);
8627
8726
  } catch {
8628
8727
  throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} does not exist`);
8629
8728
  }
@@ -8631,8 +8730,8 @@ function assertInsideScopeDir(path, scopeDir) {
8631
8730
  throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} no longer resolves to itself (${dirNow})`);
8632
8731
  }
8633
8732
  const real = assertInsideRoots(path, [scopeDir]);
8634
- const rel = relative7(scopeDir, real);
8635
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep10}`) || isAbsolute13(rel)) {
8733
+ const rel = relative8(scopeDir, real);
8734
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute14(rel)) {
8636
8735
  throw new Error(`ROOT_NOT_ALLOWED: ${path}`);
8637
8736
  }
8638
8737
  return real;
@@ -8780,17 +8879,17 @@ import {
8780
8879
  existsSync as existsSync5,
8781
8880
  constants as fsConstants2,
8782
8881
  lstatSync as lstatSync8,
8783
- realpathSync as realpathSync13
8882
+ realpathSync as realpathSync14
8784
8883
  } from "fs";
8785
8884
  import { open as open2, opendir } from "fs/promises";
8786
- 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";
8787
8886
  function inside2(root, candidate) {
8788
- const rel = relative8(root, candidate);
8789
- return rel === "" || !rel.startsWith(`..${sep11}`) && rel !== ".." && !isAbsolute14(rel);
8887
+ const rel = relative9(root, candidate);
8888
+ return rel === "" || !rel.startsWith(`..${sep12}`) && rel !== ".." && !isAbsolute15(rel);
8790
8889
  }
8791
8890
  function canonical2(dir) {
8792
8891
  try {
8793
- return realpathSync13.native(dir);
8892
+ return realpathSync14.native(dir);
8794
8893
  } catch {
8795
8894
  return dir;
8796
8895
  }
@@ -8816,7 +8915,7 @@ function scopeRoots(opts) {
8816
8915
  let skillsRepo;
8817
8916
  if (opts.skillsRepo !== void 0) {
8818
8917
  try {
8819
- 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");
8820
8919
  skillsRepo = resolveInsideRoots(opts.skillsRepo, opts.roots).real;
8821
8920
  } catch (err) {
8822
8921
  log.warn(
@@ -8828,7 +8927,7 @@ function scopeRoots(opts) {
8828
8927
  for (const root of opts.roots) {
8829
8928
  let real;
8830
8929
  try {
8831
- real = realpathSync13.native(root);
8930
+ real = realpathSync14.native(root);
8832
8931
  } catch {
8833
8932
  continue;
8834
8933
  }
@@ -8851,7 +8950,7 @@ function resolveScopeRoots(opts) {
8851
8950
  for (const root of scopeRoots(opts)) {
8852
8951
  let dirReal;
8853
8952
  try {
8854
- dirReal = realpathSync13.native(root.dir);
8953
+ dirReal = realpathSync14.native(root.dir);
8855
8954
  } catch {
8856
8955
  continue;
8857
8956
  }
@@ -8956,7 +9055,7 @@ async function readEntry(root, entry, log, budget) {
8956
9055
  if (!SKILL_SLUG.test(slug)) return skip("the filename is not a legal slug");
8957
9056
  let real;
8958
9057
  try {
8959
- real = realpathSync13.native(path);
9058
+ real = realpathSync14.native(path);
8960
9059
  } catch {
8961
9060
  return skip("cannot be resolved");
8962
9061
  }
@@ -9009,7 +9108,7 @@ async function readEntry(root, entry, log, budget) {
9009
9108
  ...parsed.ok && parsed.data.description !== void 0 ? { description: parsed.data.description } : {},
9010
9109
  declaredTools: parsed.ok ? parsed.data.tools : [],
9011
9110
  permission: parsed.ok ? parsed.data.permission : "inherit",
9012
- sourcePath: relative8(root.base, path),
9111
+ sourcePath: relative9(root.base, path),
9013
9112
  ...root.projectPath !== void 0 ? { projectPath: root.projectPath } : {},
9014
9113
  contentHash: createHash4("sha256").update(bytes).digest("hex"),
9015
9114
  ...fileMtime !== void 0 ? { fileMtime } : {},
@@ -9125,7 +9224,7 @@ import {
9125
9224
  rmSync as rmSync9
9126
9225
  } from "fs";
9127
9226
  import { open as open3, writeFile } from "fs/promises";
9128
- 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";
9129
9228
  function isSkillRefusal(err) {
9130
9229
  const msg = err instanceof Error ? err.message : String(err);
9131
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");
@@ -9336,24 +9435,24 @@ var init_materialise = __esm({
9336
9435
  BUDGET = "SKILL_BUDGET:";
9337
9436
  PRECEDENCE = { personal: 0, project: 1, workspace: 2 };
9338
9437
  isAtOrBelow2 = (p, dir) => {
9339
- const rel = relative9(dir, p);
9340
- return rel === "" || !rel.startsWith(`..${sep12}`) && rel !== ".." && !isAbsolute15(rel);
9438
+ const rel = relative10(dir, p);
9439
+ return rel === "" || !rel.startsWith(`..${sep13}`) && rel !== ".." && !isAbsolute16(rel);
9341
9440
  };
9342
9441
  }
9343
9442
  });
9344
9443
 
9345
9444
  // src/sessions/managed-git-guard.ts
9346
- import { realpathSync as realpathSync14 } from "fs";
9347
- 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";
9348
9447
  function namesGitFiles(path, managedRoot) {
9349
- const rel = relative10(managedRoot, path);
9350
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`) || isAbsolute16(rel)) return false;
9351
- 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");
9352
9451
  }
9353
9452
  function writesManagedGit(target, managedRoot, cwd) {
9354
- const absolute = isAbsolute16(target) ? target : `${cwd}${sep13}${target}`;
9355
- const canonical3 = assertInsideRootsOrAncestor(absolute, [sep13]);
9356
- 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);
9357
9456
  }
9358
9457
  function targetOf(keys, toolName2, input) {
9359
9458
  const key = keys[toolName2];
@@ -9380,7 +9479,7 @@ function walksInside(path, roots) {
9380
9479
  }
9381
9480
  function realOrSelf(dir) {
9382
9481
  try {
9383
- return realpathSync14.native(dir);
9482
+ return realpathSync15.native(dir);
9384
9483
  } catch {
9385
9484
  return dir;
9386
9485
  }
@@ -9389,13 +9488,13 @@ function walksStrictlyInside(path, dirs) {
9389
9488
  if (dirs.length === 0) return false;
9390
9489
  let canonical3;
9391
9490
  try {
9392
- canonical3 = assertInsideRootsOrAncestor(path, [sep13]);
9491
+ canonical3 = assertInsideRootsOrAncestor(path, [sep14]);
9393
9492
  } catch {
9394
9493
  return false;
9395
9494
  }
9396
9495
  return dirs.some((dir) => {
9397
- const rel = relative10(realOrSelf(dir), canonical3);
9398
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep13}`) && !isAbsolute16(rel);
9496
+ const rel = relative11(realOrSelf(dir), canonical3);
9497
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep14}`) && !isAbsolute17(rel);
9399
9498
  });
9400
9499
  }
9401
9500
  function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
@@ -9408,14 +9507,14 @@ function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
9408
9507
  const written = targetOf(WRITE_TARGET_KEYS, toolName2, input);
9409
9508
  const target = written ?? targetOf(READ_TARGET_KEYS, toolName2, input);
9410
9509
  if (target === null) return null;
9411
- if (!isPlainToolPath(target) || !isAbsolute16(target)) return repositoryPathRefusal(target);
9510
+ if (!isPlainToolPath(target) || !isAbsolute17(target)) return repositoryPathRefusal(target);
9412
9511
  if (written !== null) return walksInside(target, roots) ? null : worktreeBoundRefusal(target);
9413
9512
  return walksInside(target, readable) || walksStrictlyInside(target, readInside()) ? null : worktreeBoundRefusal(target);
9414
9513
  };
9415
9514
  }
9416
9515
  function patternStaysBelow(pattern) {
9417
9516
  const p = pattern.trim();
9418
- 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;
9419
9518
  return !p.split(/[\\/]/).includes("..");
9420
9519
  }
9421
9520
  function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
@@ -9423,7 +9522,7 @@ function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
9423
9522
  const bound = createWorktreeBoundGuard(roots, cwd, opts);
9424
9523
  return async (toolName2, input) => {
9425
9524
  const target = targetOf(WRITE_TARGET_KEYS, toolName2, input) ?? targetOf(READ_TARGET_KEYS, toolName2, input);
9426
- if (target !== null && (!isPlainToolPath(target) || !isAbsolute16(target))) {
9525
+ if (target !== null && (!isPlainToolPath(target) || !isAbsolute17(target))) {
9427
9526
  return repositoryPathRefusal(target);
9428
9527
  }
9429
9528
  return await git(toolName2, input) ?? bound(toolName2, input);
@@ -9622,7 +9721,7 @@ var init_relay = __esm({
9622
9721
  });
9623
9722
 
9624
9723
  // src/sessions/repo-settings.ts
9625
- 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";
9626
9725
  function repoSettingsPolicy(input) {
9627
9726
  const bypassLock = input.permissionMode === "bypassPermissions" ? {} : { permissions: { disableBypassPermissionsMode: "disable" } };
9628
9727
  const claudeMd = () => ({
@@ -9644,8 +9743,8 @@ function repoSettingsPolicy(input) {
9644
9743
  return { settingSources: ["user"], ...claudeMd(), settings: { ...bypassLock } };
9645
9744
  }
9646
9745
  function insideRoot2(candidate, root) {
9647
- const rel = relative11(root, candidate);
9648
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep14}`) && !isAbsolute17(rel);
9746
+ const rel = relative12(root, candidate);
9747
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep15}`) && !isAbsolute18(rel);
9649
9748
  }
9650
9749
  var init_repo_settings = __esm({
9651
9750
  "src/sessions/repo-settings.ts"() {
@@ -9654,10 +9753,10 @@ var init_repo_settings = __esm({
9654
9753
  });
9655
9754
 
9656
9755
  // src/sessions/secret-files.ts
9657
- import { realpathSync as realpathSync15 } from "fs";
9756
+ import { realpathSync as realpathSync16 } from "fs";
9658
9757
  import { realpath } from "fs/promises";
9659
9758
  import { homedir as homedir5 } from "os";
9660
- 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";
9661
9760
  function isSecretFileName(name) {
9662
9761
  const n = name.toLowerCase();
9663
9762
  if (n.startsWith(".env")) return true;
@@ -9737,23 +9836,23 @@ function createSecretGuard(cwd, protectedPaths2 = []) {
9737
9836
  const home = homedir5();
9738
9837
  const protectedReal = protectedPaths2.flatMap((p) => {
9739
9838
  try {
9740
- const real = realpathSync15.native(p);
9839
+ const real = realpathSync16.native(p);
9741
9840
  return real === p ? [p] : [p, real];
9742
9841
  } catch {
9743
9842
  return [p];
9744
9843
  }
9745
9844
  });
9746
- 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;
9747
9846
  const pathShaped = (path) => isSecretFileName(basename(path)) || inSecretDir(path) || protectedReal.some((root) => within2(path, root));
9748
9847
  const argTargets = (raw) => {
9749
9848
  if (GLOB_CHARS.test(raw))
9750
- return globTargetsSecrets(raw) || pathShaped(resolve4(cwd, expand(raw.replace(/[*?[{].*$/, ""))));
9751
- 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)));
9752
9851
  };
9753
9852
  const pathIsSecret = async (raw) => {
9754
9853
  if (argTargets(raw)) return true;
9755
9854
  try {
9756
- return pathShaped(await realpath(resolve4(cwd, expand(raw))));
9855
+ return pathShaped(await realpath(resolve5(cwd, expand(raw))));
9757
9856
  } catch {
9758
9857
  return false;
9759
9858
  }
@@ -9818,8 +9917,8 @@ var init_secret_files = __esm({
9818
9917
  CONTROLS = ["x.txt", "x.ts", "x.md", "readme", ".gitignore"];
9819
9918
  GLOB_CHARS = /[*?[{]/;
9820
9919
  within2 = (path, root) => {
9821
- const rel = relative12(root, path);
9822
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep15}`) && !isAbsolute18(rel);
9920
+ const rel = relative13(root, path);
9921
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep16}`) && !isAbsolute19(rel);
9823
9922
  };
9824
9923
  PATH_KEYS = {
9825
9924
  Read: ["file_path"],
@@ -9870,11 +9969,11 @@ var init_task_sessions = __esm({
9870
9969
 
9871
9970
  // src/sessions/user-guards.ts
9872
9971
  import { closeSync as closeSync4, constants as constants4, fstatSync, openSync as openSync4, readFileSync as readFileSync10 } from "fs";
9873
- import { isAbsolute as isAbsolute19, join as join27 } from "path";
9972
+ import { isAbsolute as isAbsolute20, join as join27 } from "path";
9874
9973
  function readUserGuards(configDir) {
9875
9974
  const settingsFile = join27(configDir, "settings.json");
9876
9975
  const none = { settingsFile, preToolUseHooks: 0, denyRules: 0 };
9877
- if (!isAbsolute19(configDir)) return none;
9976
+ if (!isAbsolute20(configDir)) return none;
9878
9977
  const text = readSmallFile(settingsFile);
9879
9978
  if (text === null) return none;
9880
9979
  let settings;
@@ -9940,8 +10039,8 @@ var init_user_guards = __esm({
9940
10039
 
9941
10040
  // src/sessions/manager.ts
9942
10041
  import { randomUUID as randomUUID5 } from "crypto";
9943
- import { realpathSync as realpathSync16 } from "fs";
9944
- 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";
9945
10044
  import { isDeepStrictEqual } from "util";
9946
10045
  function releaseCreds(creds) {
9947
10046
  if (creds?.state === "ok") creds.release();
@@ -10037,7 +10136,7 @@ var init_manager = __esm({
10037
10136
  ROW_WRITE_TIMEOUT_MS = 3e4;
10038
10137
  ORPHANED_STATUSES = ["running", "awaiting_permission"];
10039
10138
  RESTARTABLE_STATUSES = /* @__PURE__ */ new Set(["idle", ...ORPHANED_STATUSES]);
10040
- 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}`);
10041
10140
  DENY_MESSAGE_MAX = 2e3;
10042
10141
  EXIT_ERROR_MAX = 8 * 1024;
10043
10142
  INITIAL_PROMPT_MAX = 2e5;
@@ -10405,7 +10504,7 @@ var init_manager = __esm({
10405
10504
  * chat-<hex>`. Null for any other path.
10406
10505
  */
10407
10506
  cloneOfWorktree(sessionId, worktree) {
10408
- const parts = relative13(this.deps.managedRoot, worktree).split(sep16);
10507
+ const parts = relative14(this.deps.managedRoot, worktree).split(sep17);
10409
10508
  const [owner, name, dot, worktrees, leaf] = parts;
10410
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) {
10411
10510
  return null;
@@ -10985,7 +11084,7 @@ var init_manager = __esm({
10985
11084
  if (stored === null) return false;
10986
11085
  if (isAtOrBelow3(stored, target)) return true;
10987
11086
  try {
10988
- return isAtOrBelow3(realpathSync16.native(stored), target);
11087
+ return isAtOrBelow3(realpathSync17.native(stored), target);
10989
11088
  } catch {
10990
11089
  return false;
10991
11090
  }
@@ -11018,9 +11117,14 @@ var init_manager = __esm({
11018
11117
  await this.settleOrphans([sessionId]);
11019
11118
  return {};
11020
11119
  }
11021
- /** A stop nobody waits on: a provider that is starting, or between turns, may answer late. */
11022
- sendStop(sessionId, live) {
11023
- void live.session.interrupt().catch(
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(
11024
11128
  (err) => this.deps.log.warn(
11025
11129
  { err: err instanceof Error ? err.message : String(err), sessionId },
11026
11130
  "a stop sent ahead of its turn failed"
@@ -11057,7 +11161,7 @@ var init_manager = __esm({
11057
11161
  if (turns.owed > 0) turns.begun = true;
11058
11162
  if (turns.stops === 0 || !turns.early || turns.chased) return false;
11059
11163
  turns.chased = true;
11060
- this.sendStop(sessionId, live);
11164
+ this.sendStop(sessionId, live, "turn");
11061
11165
  return live.status === "idle";
11062
11166
  }
11063
11167
  /** A turn ended: whatever a Stop still covers is behind it, and has not begun. */
@@ -11367,10 +11471,10 @@ var init_manager = __esm({
11367
11471
  */
11368
11472
  claudeProjectDir(cwd) {
11369
11473
  const configDir = sessionClaudeConfigDir(this.deps.claudeEnv);
11370
- if (configDir === "" || !isAbsolute20(configDir)) return null;
11474
+ if (configDir === "" || !isAbsolute21(configDir)) return null;
11371
11475
  let canonical3 = configDir;
11372
11476
  try {
11373
- canonical3 = realpathSync16.native(configDir);
11477
+ canonical3 = realpathSync17.native(configDir);
11374
11478
  } catch {
11375
11479
  }
11376
11480
  return join28(canonical3, "projects", claudeProjectDirName(cwd));
@@ -11593,7 +11697,7 @@ var init_manager = __esm({
11593
11697
  */
11594
11698
  fromCwd(cwd, value) {
11595
11699
  if (!isPlainToolPath(value)) throw new Error(notPlainPathRefusal(value));
11596
- return isAbsolute20(value) ? value : `${cwd}${sep16}${value}`;
11700
+ return isAbsolute21(value) ? value : `${cwd}${sep17}${value}`;
11597
11701
  }
11598
11702
  /**
11599
11703
  * §2.9: a path in `updatedInput` passes the same boundary the session's `cwd` did, and the
@@ -12693,7 +12797,7 @@ var init_mirror2 = __esm({
12693
12797
 
12694
12798
  // src/skills/service.ts
12695
12799
  import { createHash as createHash6 } from "crypto";
12696
- import { constants as fsConstants4, realpathSync as realpathSync17 } from "fs";
12800
+ import { constants as fsConstants4, realpathSync as realpathSync18 } from "fs";
12697
12801
  import { open as open4 } from "fs/promises";
12698
12802
  function createSkillsService(deps) {
12699
12803
  const log = deps.log ?? logger;
@@ -12755,7 +12859,7 @@ function createSkillsService(deps) {
12755
12859
  let wanted;
12756
12860
  if (payload.scope === "project" && payload.projectPath !== void 0) {
12757
12861
  try {
12758
- wanted = realpathSync17.native(payload.projectPath);
12862
+ wanted = realpathSync18.native(payload.projectPath);
12759
12863
  } catch {
12760
12864
  throw missing();
12761
12865
  }
@@ -12913,12 +13017,12 @@ function createTerminalChannels({
12913
13017
  skipped: 0
12914
13018
  });
12915
13019
  try {
12916
- await new Promise((resolve5, reject) => {
13020
+ await new Promise((resolve6, reject) => {
12917
13021
  let settled = false;
12918
13022
  channel.subscribe((status, err) => {
12919
13023
  if (status === "SUBSCRIBED") {
12920
13024
  settled = true;
12921
- resolve5();
13025
+ resolve6();
12922
13026
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
12923
13027
  if (settled) {
12924
13028
  log.warn({ terminalId, status, err: err?.message }, "terminal channel dropped");
@@ -12950,9 +13054,9 @@ var init_channels = __esm({
12950
13054
  });
12951
13055
 
12952
13056
  // src/terminal/env.ts
12953
- import { readFileSync as readFileSync12, realpathSync as realpathSync18 } from "fs";
13057
+ import { readFileSync as readFileSync12, realpathSync as realpathSync19 } from "fs";
12954
13058
  import { lstat } from "fs/promises";
12955
- 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";
12956
13060
  function terminalEnv(base, { runnerOwner, claudeConfigDir }) {
12957
13061
  const env = {};
12958
13062
  for (const [key, value] of Object.entries(base)) {
@@ -13024,8 +13128,8 @@ function tryRealpath(fs, p) {
13024
13128
  }
13025
13129
  }
13026
13130
  function inside3(candidate, root) {
13027
- const rel = relative14(root, candidate);
13028
- return rel === "" || !rel.startsWith("..") && !isAbsolute21(rel);
13131
+ const rel = relative15(root, candidate);
13132
+ return rel === "" || !rel.startsWith("..") && !isAbsolute22(rel);
13029
13133
  }
13030
13134
  var SECRET_ENV, MEMBER_ENV_ALLOW, MEMBER_ENV_KEYS, LOCALE_ENV, nodeShellFs;
13031
13135
  var init_env2 = __esm({
@@ -13055,7 +13159,7 @@ var init_env2 = __esm({
13055
13159
  return "";
13056
13160
  }
13057
13161
  },
13058
- realpath: (p) => realpathSync18.native(p),
13162
+ realpath: (p) => realpathSync19.native(p),
13059
13163
  lstat: (p) => lstat(p)
13060
13164
  };
13061
13165
  }
@@ -13133,12 +13237,12 @@ var init_process_group = __esm({
13133
13237
  defaultKill = (pid, signal) => {
13134
13238
  process.kill(pid, signal);
13135
13239
  };
13136
- defaultRun = (file, args) => new Promise((resolve5, reject) => {
13240
+ defaultRun = (file, args) => new Promise((resolve6, reject) => {
13137
13241
  execFile5(
13138
13242
  file,
13139
13243
  args,
13140
13244
  { encoding: "utf8", timeout: 5e3, env: { LC_ALL: "C", TZ: "UTC" } },
13141
- (err, stdout) => err ? reject(err) : resolve5(stdout)
13245
+ (err, stdout) => err ? reject(err) : resolve6(stdout)
13142
13246
  );
13143
13247
  });
13144
13248
  gone = (err) => err?.code === "ESRCH";
@@ -13198,7 +13302,7 @@ var init_ring = __esm({
13198
13302
 
13199
13303
  // src/terminal/manager.ts
13200
13304
  import { constants as osConstants } from "os";
13201
- import { sep as sep17 } from "path";
13305
+ import { sep as sep18 } from "path";
13202
13306
  function mayHoldTerminal(userId, deps) {
13203
13307
  return userId === deps.runnerUserId ? deps.serves(userId) : deps.memberTerminalIds.includes(userId) && deps.serves(userId) && deps.rosterHealthy();
13204
13308
  }
@@ -13221,10 +13325,10 @@ var init_manager2 = __esm({
13221
13325
  MINUTE_MS = 6e4;
13222
13326
  LIST_MAX_ATTEMPTS = 3;
13223
13327
  LIST_RETRY_DELAY_MS = 250;
13224
- delay = (ms) => new Promise((resolve5) => {
13225
- setTimeout(resolve5, ms).unref();
13328
+ delay = (ms) => new Promise((resolve6) => {
13329
+ setTimeout(resolve6, ms).unref();
13226
13330
  });
13227
- 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}`);
13228
13332
  signalName = (n) => {
13229
13333
  if (!n) return null;
13230
13334
  for (const [name, value] of Object.entries(osConstants.signals)) if (value === n) return name;
@@ -13476,8 +13580,8 @@ var init_manager2 = __esm({
13476
13580
  }
13477
13581
  const term = this.terms.get(terminalId);
13478
13582
  if (!term || term.exited) return Promise.resolve({ closed: true });
13479
- return new Promise((resolve5) => {
13480
- term.waiters.push(() => resolve5({ closed: true }));
13583
+ return new Promise((resolve6) => {
13584
+ term.waiters.push(() => resolve6({ closed: true }));
13481
13585
  this.kill(terminalId, term);
13482
13586
  });
13483
13587
  }
@@ -14814,7 +14918,7 @@ var init_start = __esm({
14814
14918
  });
14815
14919
 
14816
14920
  // src/cli.ts
14817
- 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";
14818
14922
  import { homedir as homedir7, hostname as hostname2, userInfo } from "os";
14819
14923
  import { dirname as dirname11, join as join31 } from "path";
14820
14924
  import { Command } from "commander";
@@ -14866,12 +14970,22 @@ async function doctor(deps) {
14866
14970
  detail: drifted ? `service uses ${deps.record?.nodePath}, current node is ${deps.execPath}` : deps.execPath,
14867
14971
  fix: drifted ? "Run `cayenne update` to re-pin the service to this node." : void 0
14868
14972
  });
14869
- checks.push({
14870
- name: "claude",
14871
- ok: deps.claudeOnPath !== null,
14872
- detail: deps.claudeOnPath ?? "not found on PATH",
14873
- fix: deps.claudeOnPath ? void 0 : "Install the claude CLI: https://claude.com/claude-code"
14874
- });
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
+ );
14875
14989
  const loaded = await deps.launchd.isLoaded(deps.label);
14876
14990
  const running = loaded && await deps.launchd.isRunning(deps.label);
14877
14991
  checks.push({
@@ -15046,10 +15160,11 @@ async function up(deps) {
15046
15160
  const running = loaded && await deps.launchd.isRunning(label);
15047
15161
  const wantsChange = Boolean(deps.roots?.length || deps.workspaceSlug || claude.changed);
15048
15162
  if (!loaded && deps.cfg.runnerId) {
15049
- 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();
15050
15164
  const seen = data?.last_seen_at;
15051
15165
  const ours = data?.profile === deps.profile || data?.profile === UNCLAIMED_PROFILE;
15052
- 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") {
15053
15168
  throw new Error(
15054
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`"
15055
15170
  );
@@ -15118,14 +15233,58 @@ init_profile();
15118
15233
  init_registry2();
15119
15234
 
15120
15235
  // src/service/install.ts
15121
- import { chmodSync as chmodSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
15122
- 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";
15123
15246
  var recordPath = (cayenneHome2) => join6(cayenneHome2, "app", "install.json");
15124
15247
  async function which(exec4, bin) {
15125
15248
  const r = await exec4("which", [bin]);
15126
15249
  const path = r.stdout.trim();
15127
15250
  return r.code === 0 && path ? path : null;
15128
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
+ }
15129
15288
  function buildPathEnv(nodePath, claudePath) {
15130
15289
  const dirs = ["/usr/bin", "/bin", "/usr/sbin", "/sbin", dirname2(nodePath), dirname2(claudePath)];
15131
15290
  return [...new Set(dirs)].join(":");
@@ -15148,12 +15307,7 @@ function readInstallRecord(cayenneHome2) {
15148
15307
  }
15149
15308
  }
15150
15309
  async function installApp(deps) {
15151
- const claudePath = await which(deps.exec, "claude");
15152
- if (!claudePath) {
15153
- throw new Error(
15154
- "claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
15155
- );
15156
- }
15310
+ const claudePath = await resolveClaude(deps.exec, deps.tmpDir ?? tmpdir());
15157
15311
  const appDir = join6(deps.cayenneHome, "app");
15158
15312
  mkdirSync3(appDir, { recursive: true, mode: 448 });
15159
15313
  const install = await deps.exec("npm", [
@@ -15200,10 +15354,10 @@ exec ${sh(nodePath)} ${sh(cliPath)} "$@"
15200
15354
  // src/service/launchd.ts
15201
15355
  import { execFile } from "child_process";
15202
15356
  function nodeExec() {
15203
- return (cmd, args) => new Promise((resolve5) => {
15357
+ return (cmd, args) => new Promise((resolve6) => {
15204
15358
  execFile(cmd, args, { encoding: "utf8" }, (err, stdout, stderr) => {
15205
15359
  const code = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
15206
- resolve5({ code, stdout, stderr });
15360
+ resolve6({ code, stdout, stderr });
15207
15361
  });
15208
15362
  });
15209
15363
  }
@@ -15402,6 +15556,8 @@ program.command("doctor").description("Check the install and say how to fix what
15402
15556
  record: readInstallRecord(cayenneDir()),
15403
15557
  execPath: process.execPath,
15404
15558
  claudeOnPath: await which(exec4, "claude"),
15559
+ exists: existsSync7,
15560
+ temporary: (path) => isTemporary(path),
15405
15561
  label,
15406
15562
  launchd,
15407
15563
  backendUrl: backend?.url ?? null,
@@ -15480,8 +15636,8 @@ program.command("uninstall").description("Stop the service and remove the launch
15480
15636
  console.log(`removed ${label}`);
15481
15637
  return;
15482
15638
  }
15483
- const dir = realpathSync19(cayenneDir());
15484
- const home = realpathSync19(homedir7());
15639
+ const dir = realpathSync20(cayenneDir());
15640
+ const home = realpathSync20(homedir7());
15485
15641
  if (dir === "/" || dir === home || home.startsWith(`${dir}/`)) {
15486
15642
  throw new Error(`refusing to purge ${dir}: it is the home directory or an ancestor of it`);
15487
15643
  }