@jpcasabiancai/cayenne 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +477 -222
- 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
|
}
|
|
@@ -7753,7 +7753,15 @@ async function askGuard(guard, toolName2, input) {
|
|
|
7753
7753
|
return GUARD_FAILED;
|
|
7754
7754
|
}
|
|
7755
7755
|
}
|
|
7756
|
-
|
|
7756
|
+
function asInterrupted(row) {
|
|
7757
|
+
return {
|
|
7758
|
+
...row,
|
|
7759
|
+
content: row.content.map(
|
|
7760
|
+
(block) => block.type === "result" ? { ...block, isError: false, text: INTERRUPTED } : block
|
|
7761
|
+
)
|
|
7762
|
+
};
|
|
7763
|
+
}
|
|
7764
|
+
var SESSION_FLAG_SETTINGS, GUARD_FAILED, ASK_TOOL_REASON, INTERRUPTED, STOPPED_TURN, ClaudeSession;
|
|
7757
7765
|
var init_session2 = __esm({
|
|
7758
7766
|
"src/claude/session.ts"() {
|
|
7759
7767
|
"use strict";
|
|
@@ -7768,6 +7776,8 @@ var init_session2 = __esm({
|
|
|
7768
7776
|
};
|
|
7769
7777
|
GUARD_FAILED = "Refused: the runner could not check this tool call.";
|
|
7770
7778
|
ASK_TOOL_REASON = "This session was started from a task, so every shell command needs the member's approval.";
|
|
7779
|
+
INTERRUPTED = "interrupted";
|
|
7780
|
+
STOPPED_TURN = "error_during_execution after a requested stop: recorded as interrupted";
|
|
7771
7781
|
ClaudeSession = class extends EventEmitter {
|
|
7772
7782
|
constructor(opts) {
|
|
7773
7783
|
super();
|
|
@@ -7786,6 +7796,13 @@ var init_session2 = __esm({
|
|
|
7786
7796
|
/** `null` until `accountInfo()` answers, and for good when it cannot. */
|
|
7787
7797
|
authMode = null;
|
|
7788
7798
|
authAsked = false;
|
|
7799
|
+
/** A prompt is waiting for its turn, or a turn began (`init`), and no result has closed it. */
|
|
7800
|
+
turnOpen = false;
|
|
7801
|
+
/**
|
|
7802
|
+
* `interrupt()` or `stop()` was asked of the open turn. The CLI reports a turn cut short as
|
|
7803
|
+
* `error_during_execution`; with this set, that result is the stop landing, not a failure.
|
|
7804
|
+
*/
|
|
7805
|
+
stopAsked = false;
|
|
7789
7806
|
start() {
|
|
7790
7807
|
if (this.q) throw new Error("VALIDATION: session already started");
|
|
7791
7808
|
const queryFn = this.opts.queryFn ?? sdkQuery;
|
|
@@ -7869,6 +7886,7 @@ var init_session2 = __esm({
|
|
|
7869
7886
|
})),
|
|
7870
7887
|
{ type: "text", text }
|
|
7871
7888
|
] : text;
|
|
7889
|
+
this.turnOpen = true;
|
|
7872
7890
|
this.inbox.push({
|
|
7873
7891
|
type: "user",
|
|
7874
7892
|
message: { role: "user", content },
|
|
@@ -7885,6 +7903,7 @@ var init_session2 = __esm({
|
|
|
7885
7903
|
return true;
|
|
7886
7904
|
}
|
|
7887
7905
|
async interrupt() {
|
|
7906
|
+
if (this.turnOpen) this.stopAsked = true;
|
|
7888
7907
|
await this.q?.interrupt();
|
|
7889
7908
|
}
|
|
7890
7909
|
async setPermissionMode(mode) {
|
|
@@ -7901,6 +7920,7 @@ var init_session2 = __esm({
|
|
|
7901
7920
|
for (const id of [...this.pending.keys()]) {
|
|
7902
7921
|
this.expire(id, "session stopped");
|
|
7903
7922
|
}
|
|
7923
|
+
if (this.turnOpen) this.stopAsked = true;
|
|
7904
7924
|
void this.q?.interrupt().catch(() => void 0);
|
|
7905
7925
|
const killer = setTimeout(() => this.abort.abort(), 5e3);
|
|
7906
7926
|
await this.pumping;
|
|
@@ -7917,14 +7937,14 @@ var init_session2 = __esm({
|
|
|
7917
7937
|
this.respondPermission(id, { behavior: "deny", message, interrupt: false });
|
|
7918
7938
|
}
|
|
7919
7939
|
requestPermission(toolName2, input, suggestions, toolUseId, signal) {
|
|
7920
|
-
return new Promise((
|
|
7940
|
+
return new Promise((resolve6) => {
|
|
7921
7941
|
const id = randomUUID3();
|
|
7922
7942
|
const settle2 = (d) => {
|
|
7923
7943
|
const entry = this.pending.get(id);
|
|
7924
7944
|
if (!entry) return;
|
|
7925
7945
|
clearTimeout(entry.timer);
|
|
7926
7946
|
this.pending.delete(id);
|
|
7927
|
-
|
|
7947
|
+
resolve6(
|
|
7928
7948
|
d.behavior === "allow" ? {
|
|
7929
7949
|
behavior: "allow",
|
|
7930
7950
|
updatedInput: d.updatedInput ?? input,
|
|
@@ -7955,6 +7975,7 @@ ${this.stderrTail.join("\n")}` : "";
|
|
|
7955
7975
|
}
|
|
7956
7976
|
route(msg) {
|
|
7957
7977
|
if (msg.type === "system" && msg.subtype === "init") {
|
|
7978
|
+
this.turnOpen = true;
|
|
7958
7979
|
this.providerSessionId = msg.session_id;
|
|
7959
7980
|
this.lookUpAuthMode();
|
|
7960
7981
|
this.emit("init", {
|
|
@@ -7974,15 +7995,19 @@ ${this.stderrTail.join("\n")}` : "";
|
|
|
7974
7995
|
});
|
|
7975
7996
|
return;
|
|
7976
7997
|
}
|
|
7998
|
+
const stopped = msg.type === "result" && this.stopAsked && msg.subtype === "error_during_execution";
|
|
7999
|
+
if (stopped) logger.info({ providerSessionId: this.providerSessionId }, STOPPED_TURN);
|
|
7977
8000
|
const normalized = normalizeSdkMessage(msg);
|
|
7978
|
-
if (normalized) this.emit("message", normalized);
|
|
8001
|
+
if (normalized) this.emit("message", stopped ? asInterrupted(normalized) : normalized);
|
|
7979
8002
|
if (msg.type === "result") {
|
|
8003
|
+
this.turnOpen = false;
|
|
8004
|
+
this.stopAsked = false;
|
|
7980
8005
|
this.emit("result", {
|
|
7981
|
-
isError: msg.is_error,
|
|
8006
|
+
isError: stopped ? false : msg.is_error,
|
|
7982
8007
|
durationMs: msg.duration_ms,
|
|
7983
8008
|
numTurns: msg.num_turns,
|
|
7984
8009
|
totalCostUsd: msg.total_cost_usd,
|
|
7985
|
-
stopReason: msg.stop_reason,
|
|
8010
|
+
stopReason: stopped ? INTERRUPTED : msg.stop_reason,
|
|
7986
8011
|
authMode: this.authMode
|
|
7987
8012
|
});
|
|
7988
8013
|
}
|
|
@@ -8136,13 +8161,23 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
8136
8161
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
8137
8162
|
import { chmodSync as chmodSync5, lstatSync as lstatSync7, mkdirSync as mkdirSync8, rmdirSync, rmSync as rmSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
8138
8163
|
import { dirname as dirname8, join as join22 } from "path";
|
|
8164
|
+
function newStop() {
|
|
8165
|
+
let resolve6;
|
|
8166
|
+
let reject;
|
|
8167
|
+
const settled = new Promise((res, rej) => {
|
|
8168
|
+
resolve6 = res;
|
|
8169
|
+
reject = rej;
|
|
8170
|
+
});
|
|
8171
|
+
settled.catch(() => void 0);
|
|
8172
|
+
return { sentAt: null, covered: [], settled, resolve: resolve6, reject };
|
|
8173
|
+
}
|
|
8139
8174
|
function removeFiles(files) {
|
|
8140
8175
|
for (const file of files) rmSync8(file, { force: true });
|
|
8141
8176
|
}
|
|
8142
8177
|
function scrubSecrets(text) {
|
|
8143
8178
|
return SECRET_PATTERNS.reduce((acc, [pattern, to]) => acc.replace(pattern, to), text);
|
|
8144
8179
|
}
|
|
8145
|
-
var STDERR_TAIL_MAX, SIGKILL_AFTER_MS, SECRET_PATTERNS, APPROVAL_METHODS, CodexSession;
|
|
8180
|
+
var STDERR_TAIL_MAX, STOP_WAIT_MS, SIGKILL_AFTER_MS, SECRET_PATTERNS, APPROVAL_METHODS, CodexSession;
|
|
8146
8181
|
var init_session3 = __esm({
|
|
8147
8182
|
"src/codex/session.ts"() {
|
|
8148
8183
|
"use strict";
|
|
@@ -8153,6 +8188,7 @@ var init_session3 = __esm({
|
|
|
8153
8188
|
init_policy();
|
|
8154
8189
|
init_rpc();
|
|
8155
8190
|
STDERR_TAIL_MAX = 2 * 1024;
|
|
8191
|
+
STOP_WAIT_MS = 2e3;
|
|
8156
8192
|
SIGKILL_AFTER_MS = 5e3;
|
|
8157
8193
|
SECRET_PATTERNS = [
|
|
8158
8194
|
[
|
|
@@ -8189,11 +8225,8 @@ var init_session3 = __esm({
|
|
|
8189
8225
|
sandbox;
|
|
8190
8226
|
pendingModel;
|
|
8191
8227
|
pendingEffort;
|
|
8192
|
-
|
|
8193
|
-
running = false;
|
|
8228
|
+
turn = null;
|
|
8194
8229
|
queue = [];
|
|
8195
|
-
/** The image files of the turn in flight, removed when it ends. */
|
|
8196
|
-
turnFiles = [];
|
|
8197
8230
|
approvals = /* @__PURE__ */ new Map();
|
|
8198
8231
|
streaming = /* @__PURE__ */ new Set();
|
|
8199
8232
|
stderrTail = [];
|
|
@@ -8269,7 +8302,7 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8269
8302
|
if (staged.files.length !== staged.images.length) {
|
|
8270
8303
|
throw new Error("PROVIDER_ERROR: codex images must be staged before the prompt");
|
|
8271
8304
|
}
|
|
8272
|
-
this.queue.push({ text, files: [...staged.files] });
|
|
8305
|
+
this.queue.push({ text, files: [...staged.files], stopped: false });
|
|
8273
8306
|
this.flush();
|
|
8274
8307
|
}
|
|
8275
8308
|
/**
|
|
@@ -8311,13 +8344,19 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8311
8344
|
}
|
|
8312
8345
|
return written;
|
|
8313
8346
|
}
|
|
8314
|
-
/**
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8347
|
+
/**
|
|
8348
|
+
* The turn in flight is over: its images have been read, or never will be, and a Stop asked of
|
|
8349
|
+
* it has nothing left to stop.
|
|
8350
|
+
*/
|
|
8351
|
+
endTurn() {
|
|
8352
|
+
const turn = this.turn;
|
|
8353
|
+
if (!turn) return;
|
|
8354
|
+
this.turn = null;
|
|
8355
|
+
removeFiles(turn.files);
|
|
8356
|
+
turn.stop?.resolve();
|
|
8318
8357
|
}
|
|
8319
8358
|
removeAllFiles() {
|
|
8320
|
-
this.
|
|
8359
|
+
this.endTurn();
|
|
8321
8360
|
for (const turn of this.queue) {
|
|
8322
8361
|
removeFiles(turn.files);
|
|
8323
8362
|
turn.files = [];
|
|
@@ -8331,11 +8370,18 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8331
8370
|
}
|
|
8332
8371
|
/** One turn at a time: Codex has no notion of a second concurrent turn on a thread. */
|
|
8333
8372
|
flush() {
|
|
8334
|
-
if (this.
|
|
8373
|
+
if (this.turn || !this.rpc || !this.providerSessionId) return;
|
|
8335
8374
|
const next = this.queue.shift();
|
|
8336
8375
|
if (next === void 0) return;
|
|
8337
|
-
|
|
8338
|
-
|
|
8376
|
+
const turn = {
|
|
8377
|
+
id: null,
|
|
8378
|
+
answered: false,
|
|
8379
|
+
heard: null,
|
|
8380
|
+
started: false,
|
|
8381
|
+
files: next.files,
|
|
8382
|
+
stop: next.stopped ? newStop() : null
|
|
8383
|
+
};
|
|
8384
|
+
this.turn = turn;
|
|
8339
8385
|
void this.rpc.request("turn/start", {
|
|
8340
8386
|
threadId: this.providerSessionId,
|
|
8341
8387
|
// app-server v2 `UserInput`: images as `localImage` paths, then the text.
|
|
@@ -8346,11 +8392,9 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8346
8392
|
sandboxPolicy: this.sandbox,
|
|
8347
8393
|
...this.pendingModel ? { model: this.pendingModel } : {},
|
|
8348
8394
|
...this.pendingEffort ? { effort: this.pendingEffort } : {}
|
|
8349
|
-
}).then((res) => {
|
|
8350
|
-
this.
|
|
8351
|
-
|
|
8352
|
-
this.running = false;
|
|
8353
|
-
this.endTurnFiles();
|
|
8395
|
+
}).then((res) => this.named(turn, res.turn?.id ?? res.turnId)).catch((err) => {
|
|
8396
|
+
if (this.turn !== turn) return;
|
|
8397
|
+
this.endTurn();
|
|
8354
8398
|
this.emit("result", {
|
|
8355
8399
|
isError: true,
|
|
8356
8400
|
durationMs: 0,
|
|
@@ -8359,10 +8403,60 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8359
8403
|
});
|
|
8360
8404
|
});
|
|
8361
8405
|
}
|
|
8406
|
+
/**
|
|
8407
|
+
* `turn/start` answered, with the turn's id. Should the answer name none, the id is the one
|
|
8408
|
+
* `turn/started` gave or gives. An answer for a turn that has ended names nothing.
|
|
8409
|
+
*/
|
|
8410
|
+
named(turn, id) {
|
|
8411
|
+
if (this.turn !== turn) return;
|
|
8412
|
+
turn.answered = true;
|
|
8413
|
+
turn.id = id ?? turn.heard;
|
|
8414
|
+
if (turn.id && turn.heard === turn.id) turn.started = true;
|
|
8415
|
+
this.sendStop(turn);
|
|
8416
|
+
}
|
|
8417
|
+
/**
|
|
8418
|
+
* The app-server runs the turn `id`: `turn/started`, or something that turn produced. If it is
|
|
8419
|
+
* the one in flight, a Stop that was waiting for that goes out now. Another turn's is not: what
|
|
8420
|
+
* the turn before still had to say must not pass for the start of this one, since a stop sent
|
|
8421
|
+
* on it would come too early to stop anything. Before `turn/start` has answered there is no
|
|
8422
|
+
* telling, so the id is kept for the answer to settle.
|
|
8423
|
+
*/
|
|
8424
|
+
began(id) {
|
|
8425
|
+
const turn = this.turn;
|
|
8426
|
+
if (!turn) return;
|
|
8427
|
+
if (id && !turn.id) {
|
|
8428
|
+
turn.heard = id;
|
|
8429
|
+
if (!turn.answered) return;
|
|
8430
|
+
turn.id = id;
|
|
8431
|
+
}
|
|
8432
|
+
if (id && id !== turn.id) return;
|
|
8433
|
+
turn.started = true;
|
|
8434
|
+
this.sendStop(turn);
|
|
8435
|
+
}
|
|
8436
|
+
/** Send the turn's Stop, if one was asked, it has not gone out, and the turn can be stopped. */
|
|
8437
|
+
sendStop(turn) {
|
|
8438
|
+
const { stop, id } = turn;
|
|
8439
|
+
if (!stop || stop.sentAt !== null || !id || !turn.started) return;
|
|
8440
|
+
if (!this.rpc || !this.providerSessionId) return;
|
|
8441
|
+
stop.sentAt = Date.now();
|
|
8442
|
+
this.rpc.request("turn/interrupt", { threadId: this.providerSessionId, turnId: id }).then(
|
|
8443
|
+
() => stop.resolve(),
|
|
8444
|
+
(err) => {
|
|
8445
|
+
if (this.turn !== turn) return;
|
|
8446
|
+
for (const queued of stop.covered) queued.stopped = false;
|
|
8447
|
+
if (turn.stop === stop) turn.stop = null;
|
|
8448
|
+
stop.reject(err);
|
|
8449
|
+
}
|
|
8450
|
+
);
|
|
8451
|
+
}
|
|
8362
8452
|
onNotification(method, params) {
|
|
8453
|
+
const turn = params.turn;
|
|
8454
|
+
if (method.startsWith("item/") && this.turn?.id) {
|
|
8455
|
+
this.began(params.turnId);
|
|
8456
|
+
}
|
|
8363
8457
|
switch (method) {
|
|
8364
8458
|
case "turn/started":
|
|
8365
|
-
this.
|
|
8459
|
+
this.began(turn?.id ?? params.turnId);
|
|
8366
8460
|
return;
|
|
8367
8461
|
case "item/agentMessage/delta":
|
|
8368
8462
|
this.delta(params, "text", "delta");
|
|
@@ -8385,13 +8479,11 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8385
8479
|
}
|
|
8386
8480
|
case "turn/completed":
|
|
8387
8481
|
case "turn/failed": {
|
|
8388
|
-
this.
|
|
8389
|
-
|
|
8390
|
-
this.endTurnFiles();
|
|
8391
|
-
const status = params.status ?? (method === "turn/failed" ? "failed" : "completed");
|
|
8482
|
+
this.endTurn();
|
|
8483
|
+
const status = turn?.status ?? params.status ?? (method === "turn/failed" ? "failed" : "completed");
|
|
8392
8484
|
this.emit("result", {
|
|
8393
8485
|
isError: status === "failed",
|
|
8394
|
-
durationMs: params.durationMs ?? 0,
|
|
8486
|
+
durationMs: turn?.durationMs ?? params.durationMs ?? 0,
|
|
8395
8487
|
numTurns: 1,
|
|
8396
8488
|
stopReason: status
|
|
8397
8489
|
});
|
|
@@ -8477,23 +8569,23 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8477
8569
|
if (this.approvals.has(id) || this.resolvers.has(id)) {
|
|
8478
8570
|
throw new Error(`VALIDATION: approval id ${id} is already held`);
|
|
8479
8571
|
}
|
|
8480
|
-
return new Promise((
|
|
8572
|
+
return new Promise((resolve6) => {
|
|
8481
8573
|
const timer = setTimeout(() => {
|
|
8482
8574
|
this.approvals.delete(id);
|
|
8483
8575
|
this.resolvers.delete(id);
|
|
8484
8576
|
this.questionCounts.delete(id);
|
|
8485
8577
|
this.emit("permissionTimeout", { id });
|
|
8486
|
-
|
|
8578
|
+
resolve6(onTimeout);
|
|
8487
8579
|
}, this.opts.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS);
|
|
8488
8580
|
this.approvals.set(id, { rpcId: 0, kind, refusal: onTimeout, timer });
|
|
8489
|
-
this.resolvers.set(id,
|
|
8581
|
+
this.resolvers.set(id, resolve6);
|
|
8490
8582
|
this.emit("permission", { id, ...req, suggestions: null });
|
|
8491
8583
|
});
|
|
8492
8584
|
}
|
|
8493
8585
|
respondPermission(id, decision) {
|
|
8494
8586
|
const entry = this.approvals.get(id);
|
|
8495
|
-
const
|
|
8496
|
-
if (!entry || !
|
|
8587
|
+
const resolve6 = this.resolvers.get(id);
|
|
8588
|
+
if (!entry || !resolve6) return false;
|
|
8497
8589
|
let reply;
|
|
8498
8590
|
if (entry.kind === "question") {
|
|
8499
8591
|
if (decision.behavior === "deny") reply = "cancel";
|
|
@@ -8510,12 +8602,43 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8510
8602
|
this.approvals.delete(id);
|
|
8511
8603
|
this.resolvers.delete(id);
|
|
8512
8604
|
this.questionCounts.delete(id);
|
|
8513
|
-
|
|
8605
|
+
resolve6(reply);
|
|
8514
8606
|
return true;
|
|
8515
8607
|
}
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8608
|
+
/**
|
|
8609
|
+
* Stop every prompt handed over before this call, and none after: the turn in flight, and each
|
|
8610
|
+
* prompt still queued here, which keeps its turn and its result and is stopped as that turn
|
|
8611
|
+
* starts. With nothing in flight or queued there is nothing to stop, and nothing is remembered.
|
|
8612
|
+
*
|
|
8613
|
+
* The app-server can stop a turn only once it has named it and started it, so a Stop asked
|
|
8614
|
+
* before that is kept on the turn and goes out at `turn/started` (before, it was dropped, and
|
|
8615
|
+
* the prompt ran). The promise is the turn in flight's: it resolves when the app-server took the
|
|
8616
|
+
* stop, when the turn ended, or after `STOP_WAIT_MS` without word of either, the stop staying on
|
|
8617
|
+
* the turn; it rejects when the app-server refused it while the turn was still running, in
|
|
8618
|
+
* which case this Stop covers nothing, the queued prompts included.
|
|
8619
|
+
*
|
|
8620
|
+
* `'turn'` is a Stop the manager already asked, sent again for the turn in flight: the queue is
|
|
8621
|
+
* left as that Stop found it, or the prompts handed over since would be stopped with it.
|
|
8622
|
+
*/
|
|
8623
|
+
async interrupt(scope) {
|
|
8624
|
+
const covered = scope === "turn" ? [] : this.queue.filter((queued) => !queued.stopped);
|
|
8625
|
+
for (const queued of covered) queued.stopped = true;
|
|
8626
|
+
const turn = this.turn;
|
|
8627
|
+
if (!turn) return;
|
|
8628
|
+
const sentAt = turn.stop?.sentAt ?? null;
|
|
8629
|
+
if (sentAt !== null && Date.now() - sentAt > STOP_WAIT_MS) turn.stop = null;
|
|
8630
|
+
turn.stop ??= newStop();
|
|
8631
|
+
turn.stop.covered.push(...covered);
|
|
8632
|
+
this.sendStop(turn);
|
|
8633
|
+
let timer;
|
|
8634
|
+
const waited = new Promise((resolve6) => {
|
|
8635
|
+
timer = setTimeout(resolve6, STOP_WAIT_MS);
|
|
8636
|
+
});
|
|
8637
|
+
try {
|
|
8638
|
+
await Promise.race([turn.stop.settled, waited]);
|
|
8639
|
+
} finally {
|
|
8640
|
+
clearTimeout(timer);
|
|
8641
|
+
}
|
|
8519
8642
|
}
|
|
8520
8643
|
async setPermissionMode(mode) {
|
|
8521
8644
|
this.mode = mode;
|
|
@@ -8541,7 +8664,8 @@ ${scrubSecrets(this.stderrTail.join("")).slice(-STDERR_TAIL_MAX)}` : "";
|
|
|
8541
8664
|
this.resolvers.delete(id);
|
|
8542
8665
|
this.emit("permissionTimeout", { id });
|
|
8543
8666
|
}
|
|
8544
|
-
|
|
8667
|
+
const stoppable = this.turn?.started && this.turn.id;
|
|
8668
|
+
await (stoppable ? this.interrupt().catch(() => void 0) : void 0);
|
|
8545
8669
|
this.removeAllFiles();
|
|
8546
8670
|
this.rpc?.close();
|
|
8547
8671
|
this.child?.stdin?.end();
|
|
@@ -8590,15 +8714,15 @@ var init_types = __esm({
|
|
|
8590
8714
|
});
|
|
8591
8715
|
|
|
8592
8716
|
// src/skills/paths.ts
|
|
8593
|
-
import { realpathSync as
|
|
8594
|
-
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";
|
|
8595
8719
|
function clearSessionSkills(home, runnerId, heldBy) {
|
|
8596
8720
|
clearRunnerStaging(home, join23(home, "session-skills"), runnerId, heldBy);
|
|
8597
8721
|
}
|
|
8598
8722
|
function assertInsideScopeDir(path, scopeDir) {
|
|
8599
8723
|
let dirNow;
|
|
8600
8724
|
try {
|
|
8601
|
-
dirNow =
|
|
8725
|
+
dirNow = realpathSync13.native(scopeDir);
|
|
8602
8726
|
} catch {
|
|
8603
8727
|
throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} does not exist`);
|
|
8604
8728
|
}
|
|
@@ -8606,8 +8730,8 @@ function assertInsideScopeDir(path, scopeDir) {
|
|
|
8606
8730
|
throw new Error(`ROOT_NOT_ALLOWED: ${scopeDir} no longer resolves to itself (${dirNow})`);
|
|
8607
8731
|
}
|
|
8608
8732
|
const real = assertInsideRoots(path, [scopeDir]);
|
|
8609
|
-
const rel =
|
|
8610
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
8733
|
+
const rel = relative8(scopeDir, real);
|
|
8734
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute14(rel)) {
|
|
8611
8735
|
throw new Error(`ROOT_NOT_ALLOWED: ${path}`);
|
|
8612
8736
|
}
|
|
8613
8737
|
return real;
|
|
@@ -8755,17 +8879,17 @@ import {
|
|
|
8755
8879
|
existsSync as existsSync5,
|
|
8756
8880
|
constants as fsConstants2,
|
|
8757
8881
|
lstatSync as lstatSync8,
|
|
8758
|
-
realpathSync as
|
|
8882
|
+
realpathSync as realpathSync14
|
|
8759
8883
|
} from "fs";
|
|
8760
8884
|
import { open as open2, opendir } from "fs/promises";
|
|
8761
|
-
import { isAbsolute as
|
|
8885
|
+
import { isAbsolute as isAbsolute15, join as join24, relative as relative9, sep as sep12 } from "path";
|
|
8762
8886
|
function inside2(root, candidate) {
|
|
8763
|
-
const rel =
|
|
8764
|
-
return rel === "" || !rel.startsWith(`..${
|
|
8887
|
+
const rel = relative9(root, candidate);
|
|
8888
|
+
return rel === "" || !rel.startsWith(`..${sep12}`) && rel !== ".." && !isAbsolute15(rel);
|
|
8765
8889
|
}
|
|
8766
8890
|
function canonical2(dir) {
|
|
8767
8891
|
try {
|
|
8768
|
-
return
|
|
8892
|
+
return realpathSync14.native(dir);
|
|
8769
8893
|
} catch {
|
|
8770
8894
|
return dir;
|
|
8771
8895
|
}
|
|
@@ -8791,7 +8915,7 @@ function scopeRoots(opts) {
|
|
|
8791
8915
|
let skillsRepo;
|
|
8792
8916
|
if (opts.skillsRepo !== void 0) {
|
|
8793
8917
|
try {
|
|
8794
|
-
if (!
|
|
8918
|
+
if (!isAbsolute15(opts.skillsRepo)) throw new Error("skillsRepo must be an absolute path");
|
|
8795
8919
|
skillsRepo = resolveInsideRoots(opts.skillsRepo, opts.roots).real;
|
|
8796
8920
|
} catch (err) {
|
|
8797
8921
|
log.warn(
|
|
@@ -8803,7 +8927,7 @@ function scopeRoots(opts) {
|
|
|
8803
8927
|
for (const root of opts.roots) {
|
|
8804
8928
|
let real;
|
|
8805
8929
|
try {
|
|
8806
|
-
real =
|
|
8930
|
+
real = realpathSync14.native(root);
|
|
8807
8931
|
} catch {
|
|
8808
8932
|
continue;
|
|
8809
8933
|
}
|
|
@@ -8826,7 +8950,7 @@ function resolveScopeRoots(opts) {
|
|
|
8826
8950
|
for (const root of scopeRoots(opts)) {
|
|
8827
8951
|
let dirReal;
|
|
8828
8952
|
try {
|
|
8829
|
-
dirReal =
|
|
8953
|
+
dirReal = realpathSync14.native(root.dir);
|
|
8830
8954
|
} catch {
|
|
8831
8955
|
continue;
|
|
8832
8956
|
}
|
|
@@ -8931,7 +9055,7 @@ async function readEntry(root, entry, log, budget) {
|
|
|
8931
9055
|
if (!SKILL_SLUG.test(slug)) return skip("the filename is not a legal slug");
|
|
8932
9056
|
let real;
|
|
8933
9057
|
try {
|
|
8934
|
-
real =
|
|
9058
|
+
real = realpathSync14.native(path);
|
|
8935
9059
|
} catch {
|
|
8936
9060
|
return skip("cannot be resolved");
|
|
8937
9061
|
}
|
|
@@ -8984,7 +9108,7 @@ async function readEntry(root, entry, log, budget) {
|
|
|
8984
9108
|
...parsed.ok && parsed.data.description !== void 0 ? { description: parsed.data.description } : {},
|
|
8985
9109
|
declaredTools: parsed.ok ? parsed.data.tools : [],
|
|
8986
9110
|
permission: parsed.ok ? parsed.data.permission : "inherit",
|
|
8987
|
-
sourcePath:
|
|
9111
|
+
sourcePath: relative9(root.base, path),
|
|
8988
9112
|
...root.projectPath !== void 0 ? { projectPath: root.projectPath } : {},
|
|
8989
9113
|
contentHash: createHash4("sha256").update(bytes).digest("hex"),
|
|
8990
9114
|
...fileMtime !== void 0 ? { fileMtime } : {},
|
|
@@ -9100,7 +9224,7 @@ import {
|
|
|
9100
9224
|
rmSync as rmSync9
|
|
9101
9225
|
} from "fs";
|
|
9102
9226
|
import { open as open3, writeFile } from "fs/promises";
|
|
9103
|
-
import { dirname as dirname9, isAbsolute as
|
|
9227
|
+
import { dirname as dirname9, isAbsolute as isAbsolute16, join as join25, relative as relative10, sep as sep13 } from "path";
|
|
9104
9228
|
function isSkillRefusal(err) {
|
|
9105
9229
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9106
9230
|
return msg.startsWith("ROOT_NOT_ALLOWED") || msg.startsWith(BUDGET) || msg.includes("is not a plain directory") || msg.includes("is not part of the session skills plugin");
|
|
@@ -9311,24 +9435,24 @@ var init_materialise = __esm({
|
|
|
9311
9435
|
BUDGET = "SKILL_BUDGET:";
|
|
9312
9436
|
PRECEDENCE = { personal: 0, project: 1, workspace: 2 };
|
|
9313
9437
|
isAtOrBelow2 = (p, dir) => {
|
|
9314
|
-
const rel =
|
|
9315
|
-
return rel === "" || !rel.startsWith(`..${
|
|
9438
|
+
const rel = relative10(dir, p);
|
|
9439
|
+
return rel === "" || !rel.startsWith(`..${sep13}`) && rel !== ".." && !isAbsolute16(rel);
|
|
9316
9440
|
};
|
|
9317
9441
|
}
|
|
9318
9442
|
});
|
|
9319
9443
|
|
|
9320
9444
|
// src/sessions/managed-git-guard.ts
|
|
9321
|
-
import { realpathSync as
|
|
9322
|
-
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";
|
|
9323
9447
|
function namesGitFiles(path, managedRoot) {
|
|
9324
|
-
const rel =
|
|
9325
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
9326
|
-
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");
|
|
9327
9451
|
}
|
|
9328
9452
|
function writesManagedGit(target, managedRoot, cwd) {
|
|
9329
|
-
const absolute =
|
|
9330
|
-
const canonical3 = assertInsideRootsOrAncestor(absolute, [
|
|
9331
|
-
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);
|
|
9332
9456
|
}
|
|
9333
9457
|
function targetOf(keys, toolName2, input) {
|
|
9334
9458
|
const key = keys[toolName2];
|
|
@@ -9355,7 +9479,7 @@ function walksInside(path, roots) {
|
|
|
9355
9479
|
}
|
|
9356
9480
|
function realOrSelf(dir) {
|
|
9357
9481
|
try {
|
|
9358
|
-
return
|
|
9482
|
+
return realpathSync15.native(dir);
|
|
9359
9483
|
} catch {
|
|
9360
9484
|
return dir;
|
|
9361
9485
|
}
|
|
@@ -9364,13 +9488,13 @@ function walksStrictlyInside(path, dirs) {
|
|
|
9364
9488
|
if (dirs.length === 0) return false;
|
|
9365
9489
|
let canonical3;
|
|
9366
9490
|
try {
|
|
9367
|
-
canonical3 = assertInsideRootsOrAncestor(path, [
|
|
9491
|
+
canonical3 = assertInsideRootsOrAncestor(path, [sep14]);
|
|
9368
9492
|
} catch {
|
|
9369
9493
|
return false;
|
|
9370
9494
|
}
|
|
9371
9495
|
return dirs.some((dir) => {
|
|
9372
|
-
const rel =
|
|
9373
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
9496
|
+
const rel = relative11(realOrSelf(dir), canonical3);
|
|
9497
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep14}`) && !isAbsolute17(rel);
|
|
9374
9498
|
});
|
|
9375
9499
|
}
|
|
9376
9500
|
function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
|
|
@@ -9383,14 +9507,14 @@ function createWorktreeBoundGuard(roots, _cwd, opts = {}) {
|
|
|
9383
9507
|
const written = targetOf(WRITE_TARGET_KEYS, toolName2, input);
|
|
9384
9508
|
const target = written ?? targetOf(READ_TARGET_KEYS, toolName2, input);
|
|
9385
9509
|
if (target === null) return null;
|
|
9386
|
-
if (!isPlainToolPath(target) || !
|
|
9510
|
+
if (!isPlainToolPath(target) || !isAbsolute17(target)) return repositoryPathRefusal(target);
|
|
9387
9511
|
if (written !== null) return walksInside(target, roots) ? null : worktreeBoundRefusal(target);
|
|
9388
9512
|
return walksInside(target, readable) || walksStrictlyInside(target, readInside()) ? null : worktreeBoundRefusal(target);
|
|
9389
9513
|
};
|
|
9390
9514
|
}
|
|
9391
9515
|
function patternStaysBelow(pattern) {
|
|
9392
9516
|
const p = pattern.trim();
|
|
9393
|
-
if (
|
|
9517
|
+
if (isAbsolute17(p) || /^[\\/]/.test(p) || /^[A-Za-z]:/.test(p) || p.startsWith("~")) return false;
|
|
9394
9518
|
return !p.split(/[\\/]/).includes("..");
|
|
9395
9519
|
}
|
|
9396
9520
|
function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
|
|
@@ -9398,7 +9522,7 @@ function createRepositoryChatGuard(managedRoot, roots, cwd, opts = {}) {
|
|
|
9398
9522
|
const bound = createWorktreeBoundGuard(roots, cwd, opts);
|
|
9399
9523
|
return async (toolName2, input) => {
|
|
9400
9524
|
const target = targetOf(WRITE_TARGET_KEYS, toolName2, input) ?? targetOf(READ_TARGET_KEYS, toolName2, input);
|
|
9401
|
-
if (target !== null && (!isPlainToolPath(target) || !
|
|
9525
|
+
if (target !== null && (!isPlainToolPath(target) || !isAbsolute17(target))) {
|
|
9402
9526
|
return repositoryPathRefusal(target);
|
|
9403
9527
|
}
|
|
9404
9528
|
return await git(toolName2, input) ?? bound(toolName2, input);
|
|
@@ -9597,7 +9721,7 @@ var init_relay = __esm({
|
|
|
9597
9721
|
});
|
|
9598
9722
|
|
|
9599
9723
|
// src/sessions/repo-settings.ts
|
|
9600
|
-
import { isAbsolute as
|
|
9724
|
+
import { isAbsolute as isAbsolute18, relative as relative12, sep as sep15 } from "path";
|
|
9601
9725
|
function repoSettingsPolicy(input) {
|
|
9602
9726
|
const bypassLock = input.permissionMode === "bypassPermissions" ? {} : { permissions: { disableBypassPermissionsMode: "disable" } };
|
|
9603
9727
|
const claudeMd = () => ({
|
|
@@ -9619,8 +9743,8 @@ function repoSettingsPolicy(input) {
|
|
|
9619
9743
|
return { settingSources: ["user"], ...claudeMd(), settings: { ...bypassLock } };
|
|
9620
9744
|
}
|
|
9621
9745
|
function insideRoot2(candidate, root) {
|
|
9622
|
-
const rel =
|
|
9623
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9746
|
+
const rel = relative12(root, candidate);
|
|
9747
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep15}`) && !isAbsolute18(rel);
|
|
9624
9748
|
}
|
|
9625
9749
|
var init_repo_settings = __esm({
|
|
9626
9750
|
"src/sessions/repo-settings.ts"() {
|
|
@@ -9629,10 +9753,10 @@ var init_repo_settings = __esm({
|
|
|
9629
9753
|
});
|
|
9630
9754
|
|
|
9631
9755
|
// src/sessions/secret-files.ts
|
|
9632
|
-
import { realpathSync as
|
|
9756
|
+
import { realpathSync as realpathSync16 } from "fs";
|
|
9633
9757
|
import { realpath } from "fs/promises";
|
|
9634
9758
|
import { homedir as homedir5 } from "os";
|
|
9635
|
-
import { basename, isAbsolute as
|
|
9759
|
+
import { basename, isAbsolute as isAbsolute19, relative as relative13, resolve as resolve5, sep as sep16 } from "path";
|
|
9636
9760
|
function isSecretFileName(name) {
|
|
9637
9761
|
const n = name.toLowerCase();
|
|
9638
9762
|
if (n.startsWith(".env")) return true;
|
|
@@ -9712,23 +9836,23 @@ function createSecretGuard(cwd, protectedPaths2 = []) {
|
|
|
9712
9836
|
const home = homedir5();
|
|
9713
9837
|
const protectedReal = protectedPaths2.flatMap((p) => {
|
|
9714
9838
|
try {
|
|
9715
|
-
const real =
|
|
9839
|
+
const real = realpathSync16.native(p);
|
|
9716
9840
|
return real === p ? [p] : [p, real];
|
|
9717
9841
|
} catch {
|
|
9718
9842
|
return [p];
|
|
9719
9843
|
}
|
|
9720
9844
|
});
|
|
9721
|
-
const expand = (arg) => arg === "~" ? home : arg.startsWith("~/") ?
|
|
9845
|
+
const expand = (arg) => arg === "~" ? home : arg.startsWith("~/") ? resolve5(home, arg.slice(2)) : arg;
|
|
9722
9846
|
const pathShaped = (path) => isSecretFileName(basename(path)) || inSecretDir(path) || protectedReal.some((root) => within2(path, root));
|
|
9723
9847
|
const argTargets = (raw) => {
|
|
9724
9848
|
if (GLOB_CHARS.test(raw))
|
|
9725
|
-
return globTargetsSecrets(raw) || pathShaped(
|
|
9726
|
-
return pathShaped(raw) || pathShaped(
|
|
9849
|
+
return globTargetsSecrets(raw) || pathShaped(resolve5(cwd, expand(raw.replace(/[*?[{].*$/, ""))));
|
|
9850
|
+
return pathShaped(raw) || pathShaped(resolve5(cwd, expand(raw)));
|
|
9727
9851
|
};
|
|
9728
9852
|
const pathIsSecret = async (raw) => {
|
|
9729
9853
|
if (argTargets(raw)) return true;
|
|
9730
9854
|
try {
|
|
9731
|
-
return pathShaped(await realpath(
|
|
9855
|
+
return pathShaped(await realpath(resolve5(cwd, expand(raw))));
|
|
9732
9856
|
} catch {
|
|
9733
9857
|
return false;
|
|
9734
9858
|
}
|
|
@@ -9793,8 +9917,8 @@ var init_secret_files = __esm({
|
|
|
9793
9917
|
CONTROLS = ["x.txt", "x.ts", "x.md", "readme", ".gitignore"];
|
|
9794
9918
|
GLOB_CHARS = /[*?[{]/;
|
|
9795
9919
|
within2 = (path, root) => {
|
|
9796
|
-
const rel =
|
|
9797
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9920
|
+
const rel = relative13(root, path);
|
|
9921
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep16}`) && !isAbsolute19(rel);
|
|
9798
9922
|
};
|
|
9799
9923
|
PATH_KEYS = {
|
|
9800
9924
|
Read: ["file_path"],
|
|
@@ -9845,11 +9969,11 @@ var init_task_sessions = __esm({
|
|
|
9845
9969
|
|
|
9846
9970
|
// src/sessions/user-guards.ts
|
|
9847
9971
|
import { closeSync as closeSync4, constants as constants4, fstatSync, openSync as openSync4, readFileSync as readFileSync10 } from "fs";
|
|
9848
|
-
import { isAbsolute as
|
|
9972
|
+
import { isAbsolute as isAbsolute20, join as join27 } from "path";
|
|
9849
9973
|
function readUserGuards(configDir) {
|
|
9850
9974
|
const settingsFile = join27(configDir, "settings.json");
|
|
9851
9975
|
const none = { settingsFile, preToolUseHooks: 0, denyRules: 0 };
|
|
9852
|
-
if (!
|
|
9976
|
+
if (!isAbsolute20(configDir)) return none;
|
|
9853
9977
|
const text = readSmallFile(settingsFile);
|
|
9854
9978
|
if (text === null) return none;
|
|
9855
9979
|
let settings;
|
|
@@ -9915,8 +10039,8 @@ var init_user_guards = __esm({
|
|
|
9915
10039
|
|
|
9916
10040
|
// src/sessions/manager.ts
|
|
9917
10041
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
9918
|
-
import { realpathSync as
|
|
9919
|
-
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";
|
|
9920
10044
|
import { isDeepStrictEqual } from "util";
|
|
9921
10045
|
function releaseCreds(creds) {
|
|
9922
10046
|
if (creds?.state === "ok") creds.release();
|
|
@@ -9965,7 +10089,7 @@ function shrink(event) {
|
|
|
9965
10089
|
return null;
|
|
9966
10090
|
}
|
|
9967
10091
|
}
|
|
9968
|
-
var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
|
|
10092
|
+
var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, DRAINED_AFTER_MS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
|
|
9969
10093
|
var init_manager = __esm({
|
|
9970
10094
|
"src/sessions/manager.ts"() {
|
|
9971
10095
|
"use strict";
|
|
@@ -10006,12 +10130,13 @@ var init_manager = __esm({
|
|
|
10006
10130
|
state: "unavailable",
|
|
10007
10131
|
readOnlyText: READ_ONLY_UNAVAILABLE
|
|
10008
10132
|
};
|
|
10133
|
+
DRAINED_AFTER_MS = 2e3;
|
|
10009
10134
|
roundUsd = (usd) => Math.round(usd * 1e6) / 1e6;
|
|
10010
10135
|
UNIQUE_VIOLATION = "23505";
|
|
10011
10136
|
ROW_WRITE_TIMEOUT_MS = 3e4;
|
|
10012
10137
|
ORPHANED_STATUSES = ["running", "awaiting_permission"];
|
|
10013
10138
|
RESTARTABLE_STATUSES = /* @__PURE__ */ new Set(["idle", ...ORPHANED_STATUSES]);
|
|
10014
|
-
isAtOrBelow3 = (p, dir) => p === dir || p.startsWith(dir.endsWith(
|
|
10139
|
+
isAtOrBelow3 = (p, dir) => p === dir || p.startsWith(dir.endsWith(sep17) ? dir : `${dir}${sep17}`);
|
|
10015
10140
|
DENY_MESSAGE_MAX = 2e3;
|
|
10016
10141
|
EXIT_ERROR_MAX = 8 * 1024;
|
|
10017
10142
|
INITIAL_PROMPT_MAX = 2e5;
|
|
@@ -10379,7 +10504,7 @@ var init_manager = __esm({
|
|
|
10379
10504
|
* chat-<hex>`. Null for any other path.
|
|
10380
10505
|
*/
|
|
10381
10506
|
cloneOfWorktree(sessionId, worktree) {
|
|
10382
|
-
const parts =
|
|
10507
|
+
const parts = relative14(this.deps.managedRoot, worktree).split(sep17);
|
|
10383
10508
|
const [owner, name, dot, worktrees, leaf] = parts;
|
|
10384
10509
|
if (parts.length !== 5 || owner === void 0 || name === void 0 || dot !== ".cayenne" || worktrees !== "worktrees" || leaf !== repositoryWorktreeName(sessionId) || !RepositoryRef.shape.owner.safeParse(owner).success || !RepositoryRef.shape.name.safeParse(name).success) {
|
|
10385
10510
|
return null;
|
|
@@ -10609,6 +10734,7 @@ var init_manager = __esm({
|
|
|
10609
10734
|
);
|
|
10610
10735
|
await this.setStatus(sessionId, "running");
|
|
10611
10736
|
live.session.prompt(text, p.clientUuid, staged);
|
|
10737
|
+
this.turnHandedOver(live);
|
|
10612
10738
|
this.deps.timings?.prompted(sessionId);
|
|
10613
10739
|
} catch (err) {
|
|
10614
10740
|
staged.discard();
|
|
@@ -10958,7 +11084,7 @@ var init_manager = __esm({
|
|
|
10958
11084
|
if (stored === null) return false;
|
|
10959
11085
|
if (isAtOrBelow3(stored, target)) return true;
|
|
10960
11086
|
try {
|
|
10961
|
-
return isAtOrBelow3(
|
|
11087
|
+
return isAtOrBelow3(realpathSync17.native(stored), target);
|
|
10962
11088
|
} catch {
|
|
10963
11089
|
return false;
|
|
10964
11090
|
}
|
|
@@ -10971,12 +11097,83 @@ var init_manager = __esm({
|
|
|
10971
11097
|
async interrupt(sessionId) {
|
|
10972
11098
|
const live = this.live.get(sessionId);
|
|
10973
11099
|
if (live) {
|
|
10974
|
-
|
|
11100
|
+
const { turns } = live;
|
|
11101
|
+
turns.stops = turns.owed;
|
|
11102
|
+
turns.chased = false;
|
|
11103
|
+
turns.early = turns.owed > 0 && !turns.begun && !live.stopping && !live.dropping;
|
|
11104
|
+
if (turns.early) {
|
|
11105
|
+
this.sendStop(sessionId, live);
|
|
11106
|
+
if (live.status === "running") await this.setStatus(sessionId, "idle");
|
|
11107
|
+
return {};
|
|
11108
|
+
}
|
|
11109
|
+
try {
|
|
11110
|
+
await this.provider(() => live.session.interrupt());
|
|
11111
|
+
} catch (err) {
|
|
11112
|
+
turns.stops = 0;
|
|
11113
|
+
throw err;
|
|
11114
|
+
}
|
|
10975
11115
|
return {};
|
|
10976
11116
|
}
|
|
10977
11117
|
await this.settleOrphans([sessionId]);
|
|
10978
11118
|
return {};
|
|
10979
11119
|
}
|
|
11120
|
+
/**
|
|
11121
|
+
* A stop nobody waits on: a provider that is starting, or between turns, may answer late.
|
|
11122
|
+
* `'turn'` when it is a Stop sent again for a turn that produced output: what it covers was
|
|
11123
|
+
* settled when it was asked, and a provider with a queue of its own (Codex) would take a plain
|
|
11124
|
+
* stop for a new one and stop the prompts handed over since.
|
|
11125
|
+
*/
|
|
11126
|
+
sendStop(sessionId, live, scope) {
|
|
11127
|
+
void live.session.interrupt(scope).catch(
|
|
11128
|
+
(err) => this.deps.log.warn(
|
|
11129
|
+
{ err: err instanceof Error ? err.message : String(err), sessionId },
|
|
11130
|
+
"a stop sent ahead of its turn failed"
|
|
11131
|
+
)
|
|
11132
|
+
);
|
|
11133
|
+
}
|
|
11134
|
+
/** A prompt went to the provider. */
|
|
11135
|
+
turnHandedOver(live) {
|
|
11136
|
+
const { turns } = live;
|
|
11137
|
+
const leftover = live.provider === "claude" && turns.stops === 0 && turns.drainedAt !== null && Date.now() - turns.drainedAt > DRAINED_AFTER_MS;
|
|
11138
|
+
if (leftover) {
|
|
11139
|
+
turns.owed = 0;
|
|
11140
|
+
turns.stops = 0;
|
|
11141
|
+
}
|
|
11142
|
+
turns.drainedAt = null;
|
|
11143
|
+
turns.owed += 1;
|
|
11144
|
+
}
|
|
11145
|
+
/** The provider announced a turn (`init`): if a Stop covers it, this is where it lands. */
|
|
11146
|
+
turnBegan(sessionId, live) {
|
|
11147
|
+
if (live.provider !== "claude") return;
|
|
11148
|
+
const { turns } = live;
|
|
11149
|
+
turns.drainedAt = null;
|
|
11150
|
+
if (turns.owed > 0) turns.begun = true;
|
|
11151
|
+
if (turns.stops > 0 && turns.early) this.sendStop(sessionId, live);
|
|
11152
|
+
}
|
|
11153
|
+
/**
|
|
11154
|
+
* The provider produced output. For a turn stopped before it began that means the stop did not
|
|
11155
|
+
* land (a backend with no `init` per turn, or a stop that was lost): it is sent once more, and
|
|
11156
|
+
* `true` tells the caller the row must say `running` again, since a turn is.
|
|
11157
|
+
*/
|
|
11158
|
+
turnOutput(sessionId, live) {
|
|
11159
|
+
const { turns } = live;
|
|
11160
|
+
turns.drainedAt = null;
|
|
11161
|
+
if (turns.owed > 0) turns.begun = true;
|
|
11162
|
+
if (turns.stops === 0 || !turns.early || turns.chased) return false;
|
|
11163
|
+
turns.chased = true;
|
|
11164
|
+
this.sendStop(sessionId, live, "turn");
|
|
11165
|
+
return live.status === "idle";
|
|
11166
|
+
}
|
|
11167
|
+
/** A turn ended: whatever a Stop still covers is behind it, and has not begun. */
|
|
11168
|
+
turnEnded(live) {
|
|
11169
|
+
const { turns } = live;
|
|
11170
|
+
turns.owed = Math.max(0, turns.owed - 1);
|
|
11171
|
+
turns.stops = Math.max(0, turns.stops - 1);
|
|
11172
|
+
turns.begun = false;
|
|
11173
|
+
turns.drainedAt = Date.now();
|
|
11174
|
+
turns.early = true;
|
|
11175
|
+
turns.chased = false;
|
|
11176
|
+
}
|
|
10980
11177
|
/**
|
|
10981
11178
|
* Put rows on this runner that claim a turn in progress back to `idle`, for the chats that have
|
|
10982
11179
|
* no live session here — at boot every one of them, since nothing is live yet, and again when
|
|
@@ -11274,10 +11471,10 @@ var init_manager = __esm({
|
|
|
11274
11471
|
*/
|
|
11275
11472
|
claudeProjectDir(cwd) {
|
|
11276
11473
|
const configDir = sessionClaudeConfigDir(this.deps.claudeEnv);
|
|
11277
|
-
if (configDir === "" || !
|
|
11474
|
+
if (configDir === "" || !isAbsolute21(configDir)) return null;
|
|
11278
11475
|
let canonical3 = configDir;
|
|
11279
11476
|
try {
|
|
11280
|
-
canonical3 =
|
|
11477
|
+
canonical3 = realpathSync17.native(configDir);
|
|
11281
11478
|
} catch {
|
|
11282
11479
|
}
|
|
11283
11480
|
return join28(canonical3, "projects", claudeProjectDirName(cwd));
|
|
@@ -11500,7 +11697,7 @@ var init_manager = __esm({
|
|
|
11500
11697
|
*/
|
|
11501
11698
|
fromCwd(cwd, value) {
|
|
11502
11699
|
if (!isPlainToolPath(value)) throw new Error(notPlainPathRefusal(value));
|
|
11503
|
-
return
|
|
11700
|
+
return isAbsolute21(value) ? value : `${cwd}${sep17}${value}`;
|
|
11504
11701
|
}
|
|
11505
11702
|
/**
|
|
11506
11703
|
* §2.9: a path in `updatedInput` passes the same boundary the session's `cwd` did, and the
|
|
@@ -11809,6 +12006,7 @@ var init_manager = __esm({
|
|
|
11809
12006
|
permissions: /* @__PURE__ */ new Map(),
|
|
11810
12007
|
cwd: opts.cwd,
|
|
11811
12008
|
stopping: false,
|
|
12009
|
+
turns: { owed: 0, begun: false, drainedAt: null, stops: 0, early: false, chased: false },
|
|
11812
12010
|
dropping: null,
|
|
11813
12011
|
inflight: /* @__PURE__ */ new Set(),
|
|
11814
12012
|
writes: Promise.resolve(),
|
|
@@ -11975,9 +12173,12 @@ var init_manager = __esm({
|
|
|
11975
12173
|
live.inflight.add(task);
|
|
11976
12174
|
void task.finally(() => live.inflight.delete(task));
|
|
11977
12175
|
};
|
|
11978
|
-
|
|
11979
|
-
"
|
|
11980
|
-
|
|
12176
|
+
const output = () => {
|
|
12177
|
+
if (this.turnOutput(sessionId, live)) guard(() => this.setStatus(sessionId, "running"))();
|
|
12178
|
+
};
|
|
12179
|
+
session.on("init", (init) => {
|
|
12180
|
+
this.turnBegan(sessionId, live);
|
|
12181
|
+
return guard(async () => {
|
|
11981
12182
|
await this.updateSession(sessionId, {
|
|
11982
12183
|
provider_session_id: init.providerSessionId,
|
|
11983
12184
|
model: init.model
|
|
@@ -11988,9 +12189,10 @@ var init_manager = __esm({
|
|
|
11988
12189
|
providerSessionId: init.providerSessionId,
|
|
11989
12190
|
model: init.model
|
|
11990
12191
|
});
|
|
11991
|
-
})()
|
|
11992
|
-
);
|
|
12192
|
+
})();
|
|
12193
|
+
});
|
|
11993
12194
|
session.on("stream", (s) => {
|
|
12195
|
+
output();
|
|
11994
12196
|
this.deps.timings?.streamed(sessionId);
|
|
11995
12197
|
guard(
|
|
11996
12198
|
() => this.broadcast(sessionId, {
|
|
@@ -12001,9 +12203,9 @@ var init_manager = __esm({
|
|
|
12001
12203
|
})
|
|
12002
12204
|
)();
|
|
12003
12205
|
});
|
|
12004
|
-
session.on(
|
|
12005
|
-
"
|
|
12006
|
-
|
|
12206
|
+
session.on("message", (m) => {
|
|
12207
|
+
if (m.role === "assistant" || m.role === "tool") output();
|
|
12208
|
+
return guard(async () => {
|
|
12007
12209
|
if (m.role === "user" && m.providerUuid && live.echoed.has(m.providerUuid)) return;
|
|
12008
12210
|
await this.persist(
|
|
12009
12211
|
sessionId,
|
|
@@ -12014,8 +12216,8 @@ var init_manager = __esm({
|
|
|
12014
12216
|
m.parentToolUseId,
|
|
12015
12217
|
m.raw
|
|
12016
12218
|
);
|
|
12017
|
-
})()
|
|
12018
|
-
);
|
|
12219
|
+
})();
|
|
12220
|
+
});
|
|
12019
12221
|
session.on(
|
|
12020
12222
|
"permission",
|
|
12021
12223
|
(rawReq) => guard(async () => {
|
|
@@ -12104,6 +12306,7 @@ var init_manager = __esm({
|
|
|
12104
12306
|
})()
|
|
12105
12307
|
);
|
|
12106
12308
|
session.on("result", (r) => {
|
|
12309
|
+
this.turnEnded(live);
|
|
12107
12310
|
this.deps.timings?.finished(sessionId);
|
|
12108
12311
|
const lastSeq = live.writes.then(() => live.seq);
|
|
12109
12312
|
guard(async () => {
|
|
@@ -12594,7 +12797,7 @@ var init_mirror2 = __esm({
|
|
|
12594
12797
|
|
|
12595
12798
|
// src/skills/service.ts
|
|
12596
12799
|
import { createHash as createHash6 } from "crypto";
|
|
12597
|
-
import { constants as fsConstants4, realpathSync as
|
|
12800
|
+
import { constants as fsConstants4, realpathSync as realpathSync18 } from "fs";
|
|
12598
12801
|
import { open as open4 } from "fs/promises";
|
|
12599
12802
|
function createSkillsService(deps) {
|
|
12600
12803
|
const log = deps.log ?? logger;
|
|
@@ -12656,7 +12859,7 @@ function createSkillsService(deps) {
|
|
|
12656
12859
|
let wanted;
|
|
12657
12860
|
if (payload.scope === "project" && payload.projectPath !== void 0) {
|
|
12658
12861
|
try {
|
|
12659
|
-
wanted =
|
|
12862
|
+
wanted = realpathSync18.native(payload.projectPath);
|
|
12660
12863
|
} catch {
|
|
12661
12864
|
throw missing();
|
|
12662
12865
|
}
|
|
@@ -12814,12 +13017,12 @@ function createTerminalChannels({
|
|
|
12814
13017
|
skipped: 0
|
|
12815
13018
|
});
|
|
12816
13019
|
try {
|
|
12817
|
-
await new Promise((
|
|
13020
|
+
await new Promise((resolve6, reject) => {
|
|
12818
13021
|
let settled = false;
|
|
12819
13022
|
channel.subscribe((status, err) => {
|
|
12820
13023
|
if (status === "SUBSCRIBED") {
|
|
12821
13024
|
settled = true;
|
|
12822
|
-
|
|
13025
|
+
resolve6();
|
|
12823
13026
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
|
12824
13027
|
if (settled) {
|
|
12825
13028
|
log.warn({ terminalId, status, err: err?.message }, "terminal channel dropped");
|
|
@@ -12851,9 +13054,9 @@ var init_channels = __esm({
|
|
|
12851
13054
|
});
|
|
12852
13055
|
|
|
12853
13056
|
// src/terminal/env.ts
|
|
12854
|
-
import { readFileSync as readFileSync12, realpathSync as
|
|
13057
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync19 } from "fs";
|
|
12855
13058
|
import { lstat } from "fs/promises";
|
|
12856
|
-
import { dirname as dirname10, isAbsolute as
|
|
13059
|
+
import { dirname as dirname10, isAbsolute as isAbsolute22, relative as relative15 } from "path";
|
|
12857
13060
|
function terminalEnv(base, { runnerOwner, claudeConfigDir }) {
|
|
12858
13061
|
const env = {};
|
|
12859
13062
|
for (const [key, value] of Object.entries(base)) {
|
|
@@ -12925,8 +13128,8 @@ function tryRealpath(fs, p) {
|
|
|
12925
13128
|
}
|
|
12926
13129
|
}
|
|
12927
13130
|
function inside3(candidate, root) {
|
|
12928
|
-
const rel =
|
|
12929
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
13131
|
+
const rel = relative15(root, candidate);
|
|
13132
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute22(rel);
|
|
12930
13133
|
}
|
|
12931
13134
|
var SECRET_ENV, MEMBER_ENV_ALLOW, MEMBER_ENV_KEYS, LOCALE_ENV, nodeShellFs;
|
|
12932
13135
|
var init_env2 = __esm({
|
|
@@ -12956,7 +13159,7 @@ var init_env2 = __esm({
|
|
|
12956
13159
|
return "";
|
|
12957
13160
|
}
|
|
12958
13161
|
},
|
|
12959
|
-
realpath: (p) =>
|
|
13162
|
+
realpath: (p) => realpathSync19.native(p),
|
|
12960
13163
|
lstat: (p) => lstat(p)
|
|
12961
13164
|
};
|
|
12962
13165
|
}
|
|
@@ -13034,12 +13237,12 @@ var init_process_group = __esm({
|
|
|
13034
13237
|
defaultKill = (pid, signal) => {
|
|
13035
13238
|
process.kill(pid, signal);
|
|
13036
13239
|
};
|
|
13037
|
-
defaultRun = (file, args) => new Promise((
|
|
13240
|
+
defaultRun = (file, args) => new Promise((resolve6, reject) => {
|
|
13038
13241
|
execFile5(
|
|
13039
13242
|
file,
|
|
13040
13243
|
args,
|
|
13041
13244
|
{ encoding: "utf8", timeout: 5e3, env: { LC_ALL: "C", TZ: "UTC" } },
|
|
13042
|
-
(err, stdout) => err ? reject(err) :
|
|
13245
|
+
(err, stdout) => err ? reject(err) : resolve6(stdout)
|
|
13043
13246
|
);
|
|
13044
13247
|
});
|
|
13045
13248
|
gone = (err) => err?.code === "ESRCH";
|
|
@@ -13099,7 +13302,7 @@ var init_ring = __esm({
|
|
|
13099
13302
|
|
|
13100
13303
|
// src/terminal/manager.ts
|
|
13101
13304
|
import { constants as osConstants } from "os";
|
|
13102
|
-
import { sep as
|
|
13305
|
+
import { sep as sep18 } from "path";
|
|
13103
13306
|
function mayHoldTerminal(userId, deps) {
|
|
13104
13307
|
return userId === deps.runnerUserId ? deps.serves(userId) : deps.memberTerminalIds.includes(userId) && deps.serves(userId) && deps.rosterHealthy();
|
|
13105
13308
|
}
|
|
@@ -13122,10 +13325,10 @@ var init_manager2 = __esm({
|
|
|
13122
13325
|
MINUTE_MS = 6e4;
|
|
13123
13326
|
LIST_MAX_ATTEMPTS = 3;
|
|
13124
13327
|
LIST_RETRY_DELAY_MS = 250;
|
|
13125
|
-
delay = (ms) => new Promise((
|
|
13126
|
-
setTimeout(
|
|
13328
|
+
delay = (ms) => new Promise((resolve6) => {
|
|
13329
|
+
setTimeout(resolve6, ms).unref();
|
|
13127
13330
|
});
|
|
13128
|
-
isAtOrBelow4 = (path, dir) => path === dir || path.startsWith(dir.endsWith(
|
|
13331
|
+
isAtOrBelow4 = (path, dir) => path === dir || path.startsWith(dir.endsWith(sep18) ? dir : `${dir}${sep18}`);
|
|
13129
13332
|
signalName = (n) => {
|
|
13130
13333
|
if (!n) return null;
|
|
13131
13334
|
for (const [name, value] of Object.entries(osConstants.signals)) if (value === n) return name;
|
|
@@ -13377,8 +13580,8 @@ var init_manager2 = __esm({
|
|
|
13377
13580
|
}
|
|
13378
13581
|
const term = this.terms.get(terminalId);
|
|
13379
13582
|
if (!term || term.exited) return Promise.resolve({ closed: true });
|
|
13380
|
-
return new Promise((
|
|
13381
|
-
term.waiters.push(() =>
|
|
13583
|
+
return new Promise((resolve6) => {
|
|
13584
|
+
term.waiters.push(() => resolve6({ closed: true }));
|
|
13382
13585
|
this.kill(terminalId, term);
|
|
13383
13586
|
});
|
|
13384
13587
|
}
|
|
@@ -14715,7 +14918,7 @@ var init_start = __esm({
|
|
|
14715
14918
|
});
|
|
14716
14919
|
|
|
14717
14920
|
// src/cli.ts
|
|
14718
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync13, realpathSync as
|
|
14921
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync13, realpathSync as realpathSync20, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
14719
14922
|
import { homedir as homedir7, hostname as hostname2, userInfo } from "os";
|
|
14720
14923
|
import { dirname as dirname11, join as join31 } from "path";
|
|
14721
14924
|
import { Command } from "commander";
|
|
@@ -14767,12 +14970,22 @@ async function doctor(deps) {
|
|
|
14767
14970
|
detail: drifted ? `service uses ${deps.record?.nodePath}, current node is ${deps.execPath}` : deps.execPath,
|
|
14768
14971
|
fix: drifted ? "Run `cayenne update` to re-pin the service to this node." : void 0
|
|
14769
14972
|
});
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14973
|
+
const recorded = deps.record?.claudePath;
|
|
14974
|
+
const pinned = deps.claudeOnPath !== null && recorded !== void 0 ? recorded : null;
|
|
14975
|
+
const lost = pinned === null ? null : !deps.exists(pinned) ? "which no longer exists" : deps.temporary(pinned) ? "a temporary shim that is deleted when its terminal session ends" : null;
|
|
14976
|
+
checks.push(
|
|
14977
|
+
lost ? {
|
|
14978
|
+
name: "claude",
|
|
14979
|
+
ok: false,
|
|
14980
|
+
detail: `service uses ${pinned}, ${lost}`,
|
|
14981
|
+
fix: "Run `cayenne update` to re-pin the service to the claude on PATH."
|
|
14982
|
+
} : {
|
|
14983
|
+
name: "claude",
|
|
14984
|
+
ok: deps.claudeOnPath !== null,
|
|
14985
|
+
detail: deps.claudeOnPath ?? "not found on PATH",
|
|
14986
|
+
fix: deps.claudeOnPath ? void 0 : "Install the claude CLI: https://claude.com/claude-code"
|
|
14987
|
+
}
|
|
14988
|
+
);
|
|
14776
14989
|
const loaded = await deps.launchd.isLoaded(deps.label);
|
|
14777
14990
|
const running = loaded && await deps.launchd.isRunning(deps.label);
|
|
14778
14991
|
checks.push({
|
|
@@ -14947,10 +15160,11 @@ async function up(deps) {
|
|
|
14947
15160
|
const running = loaded && await deps.launchd.isRunning(label);
|
|
14948
15161
|
const wantsChange = Boolean(deps.roots?.length || deps.workspaceSlug || claude.changed);
|
|
14949
15162
|
if (!loaded && deps.cfg.runnerId) {
|
|
14950
|
-
const { data } = await deps.db.from("runners").select("last_seen_at, profile").eq("id", deps.cfg.runnerId).maybeSingle();
|
|
15163
|
+
const { data } = await deps.db.from("runners").select("last_seen_at, profile, status").eq("id", deps.cfg.runnerId).maybeSingle();
|
|
14951
15164
|
const seen = data?.last_seen_at;
|
|
14952
15165
|
const ours = data?.profile === deps.profile || data?.profile === UNCLAIMED_PROFILE;
|
|
14953
|
-
|
|
15166
|
+
const fresh = seen && Date.now() - new Date(seen).getTime() < RUNNER_ONLINE_WINDOW_MS;
|
|
15167
|
+
if (ours && fresh && data?.status !== "offline") {
|
|
14954
15168
|
throw new Error(
|
|
14955
15169
|
"a runner for this machine is already online without the service \u2014 stop the foreground `cayenne start` (or `cayenne down` elsewhere) before running `up`"
|
|
14956
15170
|
);
|
|
@@ -15019,14 +15233,58 @@ init_profile();
|
|
|
15019
15233
|
init_registry2();
|
|
15020
15234
|
|
|
15021
15235
|
// src/service/install.ts
|
|
15022
|
-
import {
|
|
15023
|
-
|
|
15236
|
+
import {
|
|
15237
|
+
chmodSync as chmodSync3,
|
|
15238
|
+
existsSync as existsSync3,
|
|
15239
|
+
mkdirSync as mkdirSync3,
|
|
15240
|
+
readFileSync as readFileSync3,
|
|
15241
|
+
realpathSync as realpathSync4,
|
|
15242
|
+
writeFileSync as writeFileSync3
|
|
15243
|
+
} from "fs";
|
|
15244
|
+
import { tmpdir } from "os";
|
|
15245
|
+
import { dirname as dirname2, isAbsolute as isAbsolute4, join as join6, relative, resolve, sep } from "path";
|
|
15024
15246
|
var recordPath = (cayenneHome2) => join6(cayenneHome2, "app", "install.json");
|
|
15025
15247
|
async function which(exec4, bin) {
|
|
15026
15248
|
const r = await exec4("which", [bin]);
|
|
15027
15249
|
const path = r.stdout.trim();
|
|
15028
15250
|
return r.code === 0 && path ? path : null;
|
|
15029
15251
|
}
|
|
15252
|
+
async function whichAll(exec4, bin) {
|
|
15253
|
+
const r = await exec4("which", ["-a", bin]);
|
|
15254
|
+
if (r.code !== 0) return [];
|
|
15255
|
+
const paths = r.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
15256
|
+
return [...new Set(paths)];
|
|
15257
|
+
}
|
|
15258
|
+
function realOrSpelled(path) {
|
|
15259
|
+
try {
|
|
15260
|
+
return realpathSync4(path);
|
|
15261
|
+
} catch {
|
|
15262
|
+
return resolve(path);
|
|
15263
|
+
}
|
|
15264
|
+
}
|
|
15265
|
+
var isInside = (dir, path) => {
|
|
15266
|
+
const rel = relative(dir, path);
|
|
15267
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute4(rel);
|
|
15268
|
+
};
|
|
15269
|
+
function isTemporary(path, tmpDir = tmpdir()) {
|
|
15270
|
+
const roots = [resolve(tmpDir), realOrSpelled(tmpDir)];
|
|
15271
|
+
return [resolve(path), realOrSpelled(path)].some((p) => roots.some((root) => isInside(root, p)));
|
|
15272
|
+
}
|
|
15273
|
+
async function resolveClaude(exec4, tmpDir) {
|
|
15274
|
+
const candidates = await whichAll(exec4, "claude");
|
|
15275
|
+
const lasting = candidates.find((path) => !isTemporary(path, tmpDir));
|
|
15276
|
+
if (lasting) return lasting;
|
|
15277
|
+
if (candidates[0]) {
|
|
15278
|
+
throw new Error(
|
|
15279
|
+
`claude on PATH is a temporary shim: ${candidates[0]}
|
|
15280
|
+
It is deleted when the terminal session that made it ends, and the service would lose claude with it.
|
|
15281
|
+
Put the claude CLI itself on PATH, or run this from another terminal: https://claude.com/claude-code`
|
|
15282
|
+
);
|
|
15283
|
+
}
|
|
15284
|
+
throw new Error(
|
|
15285
|
+
"claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
|
|
15286
|
+
);
|
|
15287
|
+
}
|
|
15030
15288
|
function buildPathEnv(nodePath, claudePath) {
|
|
15031
15289
|
const dirs = ["/usr/bin", "/bin", "/usr/sbin", "/sbin", dirname2(nodePath), dirname2(claudePath)];
|
|
15032
15290
|
return [...new Set(dirs)].join(":");
|
|
@@ -15049,12 +15307,7 @@ function readInstallRecord(cayenneHome2) {
|
|
|
15049
15307
|
}
|
|
15050
15308
|
}
|
|
15051
15309
|
async function installApp(deps) {
|
|
15052
|
-
const claudePath = await
|
|
15053
|
-
if (!claudePath) {
|
|
15054
|
-
throw new Error(
|
|
15055
|
-
"claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
|
|
15056
|
-
);
|
|
15057
|
-
}
|
|
15310
|
+
const claudePath = await resolveClaude(deps.exec, deps.tmpDir ?? tmpdir());
|
|
15058
15311
|
const appDir = join6(deps.cayenneHome, "app");
|
|
15059
15312
|
mkdirSync3(appDir, { recursive: true, mode: 448 });
|
|
15060
15313
|
const install = await deps.exec("npm", [
|
|
@@ -15101,10 +15354,10 @@ exec ${sh(nodePath)} ${sh(cliPath)} "$@"
|
|
|
15101
15354
|
// src/service/launchd.ts
|
|
15102
15355
|
import { execFile } from "child_process";
|
|
15103
15356
|
function nodeExec() {
|
|
15104
|
-
return (cmd, args) => new Promise((
|
|
15357
|
+
return (cmd, args) => new Promise((resolve6) => {
|
|
15105
15358
|
execFile(cmd, args, { encoding: "utf8" }, (err, stdout, stderr) => {
|
|
15106
15359
|
const code = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
|
|
15107
|
-
|
|
15360
|
+
resolve6({ code, stdout, stderr });
|
|
15108
15361
|
});
|
|
15109
15362
|
});
|
|
15110
15363
|
}
|
|
@@ -15303,6 +15556,8 @@ program.command("doctor").description("Check the install and say how to fix what
|
|
|
15303
15556
|
record: readInstallRecord(cayenneDir()),
|
|
15304
15557
|
execPath: process.execPath,
|
|
15305
15558
|
claudeOnPath: await which(exec4, "claude"),
|
|
15559
|
+
exists: existsSync7,
|
|
15560
|
+
temporary: (path) => isTemporary(path),
|
|
15306
15561
|
label,
|
|
15307
15562
|
launchd,
|
|
15308
15563
|
backendUrl: backend?.url ?? null,
|
|
@@ -15381,8 +15636,8 @@ program.command("uninstall").description("Stop the service and remove the launch
|
|
|
15381
15636
|
console.log(`removed ${label}`);
|
|
15382
15637
|
return;
|
|
15383
15638
|
}
|
|
15384
|
-
const dir =
|
|
15385
|
-
const home =
|
|
15639
|
+
const dir = realpathSync20(cayenneDir());
|
|
15640
|
+
const home = realpathSync20(homedir7());
|
|
15386
15641
|
if (dir === "/" || dir === home || home.startsWith(`${dir}/`)) {
|
|
15387
15642
|
throw new Error(`refusing to purge ${dir}: it is the home directory or an ancestor of it`);
|
|
15388
15643
|
}
|