@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.
- package/dist/cli.js +366 -210
- 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((
|
|
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
|
|
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 (!
|
|
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((
|
|
2702
|
-
this.pending.set(id, { resolve:
|
|
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
|
|
3502
|
-
import { dirname as dirname3, isAbsolute as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3532
|
+
let real = sep2;
|
|
3533
3533
|
const tail = [];
|
|
3534
|
-
for (const segment of absolute.split(
|
|
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 =
|
|
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 =
|
|
3568
|
+
rootReal = realpathSync5.native(root);
|
|
3569
3569
|
} catch {
|
|
3570
3570
|
continue;
|
|
3571
3571
|
}
|
|
3572
|
-
const rel =
|
|
3573
|
-
const inside4 = rel === "" || !rel.startsWith("..") && !
|
|
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
|
|
3589
|
-
import { delimiter as delimiter2, dirname as dirname4, isAbsolute as
|
|
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
|
|
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) =>
|
|
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
|
|
3785
|
+
import { delimiter as delimiter3, isAbsolute as isAbsolute8, join as join11 } from "path";
|
|
3786
3786
|
function gitWrapper(git, gitHome) {
|
|
3787
|
-
if (git === null || !
|
|
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
|
|
3971
|
-
import { dirname as dirname5, isAbsolute as
|
|
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) =>
|
|
4129
|
+
if (lines.length === 0 || !lines.every((l) => isAbsolute9(l))) return null;
|
|
4130
4130
|
try {
|
|
4131
|
-
return lines.map((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 =
|
|
4153
|
+
real = realpathSync7.native(home);
|
|
4154
4154
|
} catch {
|
|
4155
|
-
real =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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 || !
|
|
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 =
|
|
4414
|
-
dotGitReal =
|
|
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(
|
|
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 || !
|
|
4549
|
+
if (r.code !== 0 || !isAbsolute10(gitDir)) throw failed2("git rev-parse --git-dir", r.stderr);
|
|
4550
4550
|
try {
|
|
4551
|
-
const st = statSync5(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4586
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
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}${
|
|
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
|
|
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(
|
|
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
|
|
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 (
|
|
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((
|
|
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
|
-
|
|
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((
|
|
5520
|
+
return new Promise((resolve6) => {
|
|
5521
5521
|
wss.close();
|
|
5522
|
-
server.close(() =>
|
|
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
|
|
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 =
|
|
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((
|
|
5943
|
-
done =
|
|
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
|
|
6026
|
-
import { isAbsolute as
|
|
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 || !
|
|
6035
|
-
const parts =
|
|
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 =
|
|
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
|
|
6560
|
-
import { join as join19, relative as
|
|
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 =
|
|
6569
|
-
return rel !== "" && !rel.startsWith("..") && !rel.startsWith(
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
7046
|
-
import { isAbsolute as
|
|
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 =
|
|
7049
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
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 =
|
|
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 =
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
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.
|
|
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.
|
|
8373
|
+
if (this.turn || !this.rpc || !this.providerSessionId) return;
|
|
8360
8374
|
const next = this.queue.shift();
|
|
8361
8375
|
if (next === void 0) return;
|
|
8362
|
-
|
|
8363
|
-
|
|
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.
|
|
8376
|
-
|
|
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.
|
|
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.
|
|
8414
|
-
|
|
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((
|
|
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
|
-
|
|
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,
|
|
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
|
|
8521
|
-
if (!entry || !
|
|
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
|
-
|
|
8605
|
+
resolve6(reply);
|
|
8539
8606
|
return true;
|
|
8540
8607
|
}
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
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
|
-
|
|
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
|
|
8619
|
-
import { isAbsolute as
|
|
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 =
|
|
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 =
|
|
8635
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
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
|
|
8882
|
+
realpathSync as realpathSync14
|
|
8784
8883
|
} from "fs";
|
|
8785
8884
|
import { open as open2, opendir } from "fs/promises";
|
|
8786
|
-
import { isAbsolute as
|
|
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 =
|
|
8789
|
-
return rel === "" || !rel.startsWith(`..${
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
9340
|
-
return rel === "" || !rel.startsWith(`..${
|
|
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
|
|
9347
|
-
import { isAbsolute as
|
|
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 =
|
|
9350
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
9351
|
-
return rel.split(
|
|
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 =
|
|
9355
|
-
const canonical3 = assertInsideRootsOrAncestor(absolute, [
|
|
9356
|
-
return namesGitFiles(
|
|
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
|
|
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, [
|
|
9491
|
+
canonical3 = assertInsideRootsOrAncestor(path, [sep14]);
|
|
9393
9492
|
} catch {
|
|
9394
9493
|
return false;
|
|
9395
9494
|
}
|
|
9396
9495
|
return dirs.some((dir) => {
|
|
9397
|
-
const rel =
|
|
9398
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
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) || !
|
|
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 (
|
|
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) || !
|
|
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
|
|
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 =
|
|
9648
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
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
|
|
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
|
|
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 =
|
|
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("~/") ?
|
|
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(
|
|
9751
|
-
return pathShaped(raw) || pathShaped(
|
|
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(
|
|
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 =
|
|
9822
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
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
|
|
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 (!
|
|
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
|
|
9944
|
-
import { isAbsolute as
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
/**
|
|
11022
|
-
|
|
11023
|
-
|
|
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 === "" || !
|
|
11474
|
+
if (configDir === "" || !isAbsolute21(configDir)) return null;
|
|
11371
11475
|
let canonical3 = configDir;
|
|
11372
11476
|
try {
|
|
11373
|
-
canonical3 =
|
|
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
|
|
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
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
13057
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync19 } from "fs";
|
|
12954
13058
|
import { lstat } from "fs/promises";
|
|
12955
|
-
import { dirname as dirname10, isAbsolute as
|
|
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 =
|
|
13028
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
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) =>
|
|
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((
|
|
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) :
|
|
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
|
|
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((
|
|
13225
|
-
setTimeout(
|
|
13328
|
+
delay = (ms) => new Promise((resolve6) => {
|
|
13329
|
+
setTimeout(resolve6, ms).unref();
|
|
13226
13330
|
});
|
|
13227
|
-
isAtOrBelow4 = (path, dir) => path === dir || path.startsWith(dir.endsWith(
|
|
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((
|
|
13480
|
-
term.waiters.push(() =>
|
|
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
|
|
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
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
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
|
-
|
|
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 {
|
|
15122
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
15484
|
-
const home =
|
|
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
|
}
|