@tablation/crew 0.103.0 → 0.104.0
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 +550 -310
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2678,6 +2678,23 @@ function parseCloneScheme(v) {
|
|
|
2678
2678
|
if (v === "ssh" || v === "https") return v;
|
|
2679
2679
|
throw new ConfigError(`ship.cloneScheme must be "ssh" or "https", got ${JSON.stringify(v)}`);
|
|
2680
2680
|
}
|
|
2681
|
+
function parseShipRunner(v) {
|
|
2682
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
2683
|
+
if (v === "host" || v === "sandbox") return v;
|
|
2684
|
+
throw new ConfigError(`ship.runner must be "host" or "sandbox", got ${JSON.stringify(v)}`);
|
|
2685
|
+
}
|
|
2686
|
+
var SANDBOX_KEYS = ["bin", "image", "policy", "cacheVolume", "mirrorDir", "workDir", "agentBin", "forgeTokenVar"];
|
|
2687
|
+
function parseShipSandbox(v) {
|
|
2688
|
+
if (v === void 0 || v === null) return void 0;
|
|
2689
|
+
if (typeof v !== "object" || Array.isArray(v)) throw new ConfigError("ship.sandbox must be a map of settings");
|
|
2690
|
+
const out = {};
|
|
2691
|
+
for (const [k, val] of Object.entries(v)) {
|
|
2692
|
+
if (!SANDBOX_KEYS.includes(k)) throw new ConfigError(`ship.sandbox: unknown setting "${k}" (known: ${SANDBOX_KEYS.join(", ")})`);
|
|
2693
|
+
if (typeof val !== "string" || !val) throw new ConfigError(`ship.sandbox.${k} must be a non-empty string`);
|
|
2694
|
+
out[k] = val;
|
|
2695
|
+
}
|
|
2696
|
+
return out;
|
|
2697
|
+
}
|
|
2681
2698
|
function parseShipEnv(v) {
|
|
2682
2699
|
if (v === void 0 || v === null) return void 0;
|
|
2683
2700
|
if (typeof v !== "object" || Array.isArray(v)) throw new ConfigError("ship.env must be a map of variable name to value");
|
|
@@ -3124,6 +3141,8 @@ Create the first of those:
|
|
|
3124
3141
|
if (dupe) missing.add(`routes: duplicate route "${dupe}" \u2014 a route addresses itself, so it must be unique`);
|
|
3125
3142
|
missing.throwIfAny(file);
|
|
3126
3143
|
configureSecretStore({ mode: secretStore, stateDir });
|
|
3144
|
+
const shipRunner = parseShipRunner(shipRaw.runner);
|
|
3145
|
+
const shipSandbox = parseShipSandbox(shipRaw.sandbox);
|
|
3127
3146
|
return {
|
|
3128
3147
|
ship: {
|
|
3129
3148
|
name: shipRaw.name ?? "this ship",
|
|
@@ -3133,6 +3152,7 @@ Create the first of those:
|
|
|
3133
3152
|
model: shipRaw.agent?.model ?? "claude-sonnet-5",
|
|
3134
3153
|
maxThinkingTokens
|
|
3135
3154
|
},
|
|
3155
|
+
...shipRunner ? { runner: shipRunner, ...shipSandbox ? { sandbox: shipSandbox } : {} } : {},
|
|
3136
3156
|
...toolchains.length > 0 ? { toolchains } : {},
|
|
3137
3157
|
...shipToolchain !== void 0 ? { toolchain: shipToolchain } : {},
|
|
3138
3158
|
...pair ? { pair } : {},
|
|
@@ -5418,17 +5438,17 @@ function roleCapWarnings(ship) {
|
|
|
5418
5438
|
}
|
|
5419
5439
|
|
|
5420
5440
|
// src/selftest.ts
|
|
5421
|
-
import { spawn as
|
|
5422
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync, readFileSync as readFileSync20, rmSync as
|
|
5423
|
-
import { tmpdir as
|
|
5424
|
-
import { join as
|
|
5441
|
+
import { spawn as spawn4 } from "child_process";
|
|
5442
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync20, rmSync as rmSync13, writeFileSync as writeFileSync15 } from "fs";
|
|
5443
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
5444
|
+
import { join as join24 } from "path";
|
|
5425
5445
|
|
|
5426
5446
|
// src/agent.ts
|
|
5427
|
-
import {
|
|
5428
|
-
import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync12, writeFileSync as
|
|
5447
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
5448
|
+
import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync12, writeFileSync as writeFileSync14, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5429
5449
|
import { StringDecoder } from "string_decoder";
|
|
5430
5450
|
import { finished } from "stream/promises";
|
|
5431
|
-
import { join as
|
|
5451
|
+
import { join as join23, dirname as dirname8, basename as basename8 } from "path";
|
|
5432
5452
|
import { createHash as createHash4 } from "crypto";
|
|
5433
5453
|
|
|
5434
5454
|
// src/state.ts
|
|
@@ -9457,6 +9477,13 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9457
9477
|
return out;
|
|
9458
9478
|
}
|
|
9459
9479
|
|
|
9480
|
+
// src/runner.ts
|
|
9481
|
+
import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
|
|
9482
|
+
import { mkdtempSync, writeFileSync as writeFileSync13, rmSync as rmSync12 } from "fs";
|
|
9483
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
9484
|
+
import { basename as basename7, join as join22 } from "path";
|
|
9485
|
+
import { promisify } from "util";
|
|
9486
|
+
|
|
9460
9487
|
// src/run-limit.ts
|
|
9461
9488
|
var DEFAULT_MAX_RUN_MINUTES = 240;
|
|
9462
9489
|
var KILL_GRACE_MS = 5e3;
|
|
@@ -9506,6 +9533,186 @@ function runKilledComment(b) {
|
|
|
9506
9533
|
The agent run on this ticket was stopped at its wall-clock limit of ${b.limitMinutes} minutes before it handed on${b.branch ? `; its branch is \`${b.branch}\`` : ""}. ${tree} The worktree is left as it was. The next run on this seat resumes from it rather than starting over.`;
|
|
9507
9534
|
}
|
|
9508
9535
|
|
|
9536
|
+
// src/runner.ts
|
|
9537
|
+
var execFileP = promisify(execFile);
|
|
9538
|
+
var DEFAULT_SANDBOX_CONFIG = {
|
|
9539
|
+
bin: "openshell",
|
|
9540
|
+
mirrorDir: "/cache",
|
|
9541
|
+
workDir: "/sandbox/work"
|
|
9542
|
+
};
|
|
9543
|
+
var SANDBOX_STDIN_LIMIT = 4 * 1024 * 1024 - 64 * 1024;
|
|
9544
|
+
var SANDBOX_PROMPT_PATH = "/sandbox/prompt.txt";
|
|
9545
|
+
var HostRunner = class {
|
|
9546
|
+
kind = "host";
|
|
9547
|
+
start(plan, o) {
|
|
9548
|
+
const child = spawn2(plan.bin, plan.args, {
|
|
9549
|
+
cwd: plan.cwd,
|
|
9550
|
+
env: o.env,
|
|
9551
|
+
stdio: ["pipe", o.streamFd, "inherit"],
|
|
9552
|
+
detached: true
|
|
9553
|
+
});
|
|
9554
|
+
return { child, handle: { runner: "host", pid: child.pid } };
|
|
9555
|
+
}
|
|
9556
|
+
async kill(handle) {
|
|
9557
|
+
if (handle.pid !== void 0) await killRunGroup(handle.pid);
|
|
9558
|
+
}
|
|
9559
|
+
isAlive(handle) {
|
|
9560
|
+
if (handle.pid === void 0) return false;
|
|
9561
|
+
try {
|
|
9562
|
+
process.kill(handle.pid, 0);
|
|
9563
|
+
return true;
|
|
9564
|
+
} catch (e) {
|
|
9565
|
+
return e.code === "EPERM";
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
reattach(meta) {
|
|
9569
|
+
return { runner: "host", pid: meta.pid };
|
|
9570
|
+
}
|
|
9571
|
+
async release() {
|
|
9572
|
+
}
|
|
9573
|
+
};
|
|
9574
|
+
function shQuote2(s) {
|
|
9575
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9576
|
+
}
|
|
9577
|
+
function sandboxName(plan, now = Date.now()) {
|
|
9578
|
+
const base = [plan.route, plan.role, plan.ticket ?? ""].join("-").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9579
|
+
return `crew-${base.slice(0, 36)}-${now.toString(36)}`.replace(/-+/g, "-");
|
|
9580
|
+
}
|
|
9581
|
+
var SandboxRunner = class {
|
|
9582
|
+
kind = "sandbox";
|
|
9583
|
+
cfg;
|
|
9584
|
+
/** Injected for tests; the real one reads the plan's checkout. */
|
|
9585
|
+
originOf;
|
|
9586
|
+
constructor(cfg = {}, originOf = gitOrigin) {
|
|
9587
|
+
this.cfg = { ...DEFAULT_SANDBOX_CONFIG, ...cfg };
|
|
9588
|
+
this.originOf = originOf;
|
|
9589
|
+
}
|
|
9590
|
+
os(args, o = {}) {
|
|
9591
|
+
return execFileP(this.cfg.bin, args, { env: o.env ?? process.env, maxBuffer: 16 * 1024 * 1024 });
|
|
9592
|
+
}
|
|
9593
|
+
async start(plan, o) {
|
|
9594
|
+
const origin = this.originOf(plan.cwd);
|
|
9595
|
+
if (!origin) throw new Error(`cannot find the origin remote of ${plan.cwd}, so there is nothing to clone into the sandbox`);
|
|
9596
|
+
const repoName = basename7(origin).replace(/\.git$/, "");
|
|
9597
|
+
const name = sandboxName(plan);
|
|
9598
|
+
const { cfg } = this;
|
|
9599
|
+
const secretKeys = Object.keys(plan.setEnv);
|
|
9600
|
+
if (cfg.forgeTokenVar && o.env[cfg.forgeTokenVar] !== void 0 && !secretKeys.includes(cfg.forgeTokenVar)) secretKeys.push(cfg.forgeTokenVar);
|
|
9601
|
+
const createEnv = { ...process.env, ...plan.setEnv };
|
|
9602
|
+
if (cfg.forgeTokenVar && o.env[cfg.forgeTokenVar] !== void 0) createEnv[cfg.forgeTokenVar] = o.env[cfg.forgeTokenVar];
|
|
9603
|
+
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9604
|
+
if (cfg.policy) create2.push("--policy", cfg.policy);
|
|
9605
|
+
if (cfg.image) create2.push("--from", cfg.image);
|
|
9606
|
+
if (cfg.cacheVolume) {
|
|
9607
|
+
create2.push("--driver-config-json", JSON.stringify({
|
|
9608
|
+
docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
|
|
9609
|
+
}));
|
|
9610
|
+
}
|
|
9611
|
+
for (const k of secretKeys) create2.push("--env-from", k);
|
|
9612
|
+
const maxSeconds = Math.max(60, Math.ceil((plan.maxRunMinutes ?? 0) * 60) + 600);
|
|
9613
|
+
create2.push("--", "sleep", String(plan.maxRunMinutes ? maxSeconds : 86400));
|
|
9614
|
+
const t0 = Date.now();
|
|
9615
|
+
try {
|
|
9616
|
+
await this.os(create2, { env: createEnv });
|
|
9617
|
+
} catch (e) {
|
|
9618
|
+
await this.deleteQuietly(name);
|
|
9619
|
+
throw new Error(`openshell could not create the sandbox: ${describeExecError(e)}`);
|
|
9620
|
+
}
|
|
9621
|
+
o.emit.emit(`sandbox_created: ${name}`, { role: plan.role, data: { event: "sandbox_created", sandbox: name, ms: Date.now() - t0 } });
|
|
9622
|
+
const handle = { runner: "sandbox", sandbox: name, sandboxBin: cfg.bin };
|
|
9623
|
+
try {
|
|
9624
|
+
const branch = `crew-run-${name.slice(-8)}`;
|
|
9625
|
+
const clone = [
|
|
9626
|
+
`git clone --quiet --shared ${shQuote2(`${cfg.mirrorDir}/${repoName}.git`)} ${shQuote2(cfg.workDir)}`,
|
|
9627
|
+
`cd ${shQuote2(cfg.workDir)}`,
|
|
9628
|
+
`git remote set-url origin ${shQuote2(origin)}`,
|
|
9629
|
+
`git checkout --quiet -b ${shQuote2(branch)}`
|
|
9630
|
+
].join(" && ");
|
|
9631
|
+
await this.os(["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone]);
|
|
9632
|
+
const harness = cfg.agentBin ?? basename7(plan.bin);
|
|
9633
|
+
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9634
|
+
if (viaUpload) {
|
|
9635
|
+
const tmp = mkdtempSync(join22(tmpdir2(), "crew-prompt-"));
|
|
9636
|
+
try {
|
|
9637
|
+
const file = join22(tmp, "prompt.txt");
|
|
9638
|
+
writeFileSync13(file, plan.prompt, { mode: 384 });
|
|
9639
|
+
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9640
|
+
} finally {
|
|
9641
|
+
rmSync12(tmp, { recursive: true, force: true });
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
const command2 = viaUpload ? ["sh", "-c", `exec ${[harness, ...plan.args].map(shQuote2).join(" ")} < ${SANDBOX_PROMPT_PATH}`] : [harness, ...plan.args];
|
|
9645
|
+
const child = spawn2(cfg.bin, ["sandbox", "exec", "-n", name, "--no-login-shell", "--workdir", cfg.workDir, "--", ...command2], {
|
|
9646
|
+
env: process.env,
|
|
9647
|
+
stdio: ["pipe", o.streamFd, "inherit"],
|
|
9648
|
+
detached: true
|
|
9649
|
+
});
|
|
9650
|
+
return { child, handle: { ...handle, pid: child.pid }, ...viaUpload ? { promptDelivered: true } : {} };
|
|
9651
|
+
} catch (e) {
|
|
9652
|
+
await this.release(handle, o.emit);
|
|
9653
|
+
throw new Error(`could not prepare the sandbox ${name}: ${describeExecError(e)}`);
|
|
9654
|
+
}
|
|
9655
|
+
}
|
|
9656
|
+
async kill(handle) {
|
|
9657
|
+
if (handle.sandbox) await this.deleteQuietly(handle.sandbox, handle.sandboxBin);
|
|
9658
|
+
if (handle.pid !== void 0) await killRunGroup(handle.pid, { graceMs: 2e3 });
|
|
9659
|
+
}
|
|
9660
|
+
isAlive(handle) {
|
|
9661
|
+
return new HostRunner().isAlive(handle);
|
|
9662
|
+
}
|
|
9663
|
+
reattach(meta) {
|
|
9664
|
+
return { runner: "sandbox", pid: meta.pid, sandbox: meta.sandbox, sandboxBin: meta.sandboxBin };
|
|
9665
|
+
}
|
|
9666
|
+
async release(handle, emit) {
|
|
9667
|
+
if (!handle.sandbox) return;
|
|
9668
|
+
const t0 = Date.now();
|
|
9669
|
+
await this.deleteQuietly(handle.sandbox, handle.sandboxBin);
|
|
9670
|
+
emit.emit(`sandbox_deleted: ${handle.sandbox}`, { data: { event: "sandbox_deleted", sandbox: handle.sandbox, ms: Date.now() - t0 } });
|
|
9671
|
+
}
|
|
9672
|
+
async deleteQuietly(name, bin = this.cfg.bin) {
|
|
9673
|
+
try {
|
|
9674
|
+
await execFileP(bin, ["sandbox", "delete", name], { timeout: 6e4 });
|
|
9675
|
+
} catch {
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
};
|
|
9679
|
+
function describeExecError(e) {
|
|
9680
|
+
const err3 = e;
|
|
9681
|
+
return (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ");
|
|
9682
|
+
}
|
|
9683
|
+
function gitOrigin(cwd) {
|
|
9684
|
+
try {
|
|
9685
|
+
return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
9686
|
+
} catch {
|
|
9687
|
+
return null;
|
|
9688
|
+
}
|
|
9689
|
+
}
|
|
9690
|
+
function runnerFor(kind, sandbox) {
|
|
9691
|
+
return kind === "sandbox" ? new SandboxRunner(sandbox) : new HostRunner();
|
|
9692
|
+
}
|
|
9693
|
+
function runnerOfSidecar(sc) {
|
|
9694
|
+
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
9695
|
+
}
|
|
9696
|
+
function checkSandboxRunner(cfg = {}, run2 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
9697
|
+
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
9698
|
+
let version;
|
|
9699
|
+
try {
|
|
9700
|
+
version = run2(bin, ["--version"]).trim();
|
|
9701
|
+
} catch (e) {
|
|
9702
|
+
return { ok: false, detail: `${bin} is not installed or not on the scheduler PATH (${e.message.split("\n")[0]})` };
|
|
9703
|
+
}
|
|
9704
|
+
try {
|
|
9705
|
+
run2(bin, ["status"]);
|
|
9706
|
+
} catch (e) {
|
|
9707
|
+
const err3 = e;
|
|
9708
|
+
return {
|
|
9709
|
+
ok: false,
|
|
9710
|
+
detail: `${version}: the gateway does not answer (${(err3.stderr || err3.message).trim().split("\n").filter(Boolean).slice(0, 2).join(" / ")}). Start it; on Docker Desktop its docker section needs supervisor_network_mode = "bridge", and allow_driver_config = true for the cache volume mount.`
|
|
9711
|
+
};
|
|
9712
|
+
}
|
|
9713
|
+
return { ok: true, detail: version };
|
|
9714
|
+
}
|
|
9715
|
+
|
|
9509
9716
|
// src/agent.ts
|
|
9510
9717
|
var DIGEST_MAX_AGE_SECONDS = 600;
|
|
9511
9718
|
var LEGACY_TOOLCHAIN_HARNESS = "claude";
|
|
@@ -9556,13 +9763,13 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
9556
9763
|
}
|
|
9557
9764
|
return false;
|
|
9558
9765
|
}
|
|
9559
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
9766
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join23(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
9560
9767
|
function planAgentRun(o) {
|
|
9561
|
-
const briefPath =
|
|
9768
|
+
const briefPath = join23(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
9562
9769
|
if (!existsSync21(briefPath)) {
|
|
9563
9770
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
9564
9771
|
}
|
|
9565
|
-
const commonPath =
|
|
9772
|
+
const commonPath = join23(o.route.promptsDir, "personas", "common.md");
|
|
9566
9773
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync21(commonPath);
|
|
9567
9774
|
const now = o.now ?? (() => Date.now());
|
|
9568
9775
|
const routeLabel = routeSlug(o.route.route);
|
|
@@ -9597,7 +9804,7 @@ function planAgentRun(o) {
|
|
|
9597
9804
|
digest
|
|
9598
9805
|
});
|
|
9599
9806
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
9600
|
-
const streamsDir =
|
|
9807
|
+
const streamsDir = join23(o.stateDir, "streams");
|
|
9601
9808
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
9602
9809
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
9603
9810
|
const { model } = seat;
|
|
@@ -9658,6 +9865,7 @@ function planAgentRun(o) {
|
|
|
9658
9865
|
workspaceAuth: toolchain.auth === "workspace",
|
|
9659
9866
|
provider: toolchainProvider(toolchain),
|
|
9660
9867
|
maxRunMinutes: resolveMaxRunMinutes(o.route.maxRunMinutes, toolchain.maxRunMinutes),
|
|
9868
|
+
...o.ship.runner === "sandbox" ? { runner: "sandbox", ...o.ship.sandbox ? { sandboxConfig: o.ship.sandbox } : {} } : {},
|
|
9661
9869
|
resolution: { toolchain: seat.toolchainWhy, model: seat.modelWhy },
|
|
9662
9870
|
warnings: [...seat.warnings ?? [], ...expanded.warnings],
|
|
9663
9871
|
prompt,
|
|
@@ -9682,8 +9890,8 @@ function planAgentRun(o) {
|
|
|
9682
9890
|
digestAttached: digest !== void 0,
|
|
9683
9891
|
digestAgeSeconds: ageSeconds,
|
|
9684
9892
|
model,
|
|
9685
|
-
streamPath:
|
|
9686
|
-
eventsPath:
|
|
9893
|
+
streamPath: join23(streamsDir, `${base}.jsonl`),
|
|
9894
|
+
eventsPath: join23(streamsDir, `${base}.events.jsonl`),
|
|
9687
9895
|
ticket: o.ticket,
|
|
9688
9896
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
9689
9897
|
repo: o.repo ?? null,
|
|
@@ -9747,7 +9955,7 @@ function identityOf(plan, adapterHarness) {
|
|
|
9747
9955
|
promptSha: plan.promptSha
|
|
9748
9956
|
};
|
|
9749
9957
|
}
|
|
9750
|
-
async function killForDeadline(pid, emit, role, ticket, spawnedAt, limitMinutes) {
|
|
9958
|
+
async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMinutes) {
|
|
9751
9959
|
const elapsedMs = Date.now() - Date.parse(spawnedAt);
|
|
9752
9960
|
const limit = limitMinutes ?? Math.round(elapsedMs / 6e4);
|
|
9753
9961
|
emit.emit(`run_killed: ${role} run exceeded its wall-clock limit of ${limit} min (ran ${Math.round(elapsedMs / 6e4)} min)`, {
|
|
@@ -9755,7 +9963,7 @@ async function killForDeadline(pid, emit, role, ticket, spawnedAt, limitMinutes)
|
|
|
9755
9963
|
...ticket ? { ticket } : {},
|
|
9756
9964
|
data: { event: "run_killed", reason: WALL_CLOCK_REASON, limitMinutes: limit, elapsedMs, pid }
|
|
9757
9965
|
});
|
|
9758
|
-
await
|
|
9966
|
+
await kill();
|
|
9759
9967
|
return { limitMinutes: limit, elapsedMs };
|
|
9760
9968
|
}
|
|
9761
9969
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
@@ -9943,7 +10151,7 @@ var RunObserver = class {
|
|
|
9943
10151
|
};
|
|
9944
10152
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
9945
10153
|
try {
|
|
9946
|
-
|
|
10154
|
+
writeFileSync14(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
9947
10155
|
route: id.route,
|
|
9948
10156
|
role: id.role,
|
|
9949
10157
|
hint: id.ticket,
|
|
@@ -10050,7 +10258,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10050
10258
|
if (sent.counterWarning) emit.warn(sent.counterWarning, { step: "agent" });
|
|
10051
10259
|
if (sent.counterRetry) {
|
|
10052
10260
|
try {
|
|
10053
|
-
queueTicketCounters(stateDirOfStream(id.streamPath),
|
|
10261
|
+
queueTicketCounters(stateDirOfStream(id.streamPath), basename8(id.streamPath, ".jsonl"), {
|
|
10054
10262
|
version: 1,
|
|
10055
10263
|
route: id.route,
|
|
10056
10264
|
role: id.role,
|
|
@@ -10065,7 +10273,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10065
10273
|
} catch (e) {
|
|
10066
10274
|
if (e instanceof AgentLogUnreachableError) {
|
|
10067
10275
|
try {
|
|
10068
|
-
queueAgentRun(stateDirOfStream(id.streamPath),
|
|
10276
|
+
queueAgentRun(stateDirOfStream(id.streamPath), basename8(id.streamPath, ".jsonl"), {
|
|
10069
10277
|
version: 1,
|
|
10070
10278
|
route: id.route,
|
|
10071
10279
|
role: id.role,
|
|
@@ -10095,102 +10303,125 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved)
|
|
|
10095
10303
|
}
|
|
10096
10304
|
function spawnAgent(plan, emit, hintMoved) {
|
|
10097
10305
|
const adapter = adapterFor(plan.harness ?? LEGACY_TOOLCHAIN_HARNESS);
|
|
10306
|
+
const runner = runnerFor(plan.runner, plan.sandboxConfig);
|
|
10098
10307
|
const started = Date.now();
|
|
10099
10308
|
const startedAt = new Date(started).toISOString();
|
|
10100
10309
|
for (const w of plan.warnings ?? []) emit.warn(w, { step: "agent", role: plan.role });
|
|
10101
10310
|
const eventsSink = openSink(plan.eventsPath);
|
|
10102
10311
|
const id = identityOf(plan, adapter.harness);
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10312
|
+
let streamFd;
|
|
10313
|
+
try {
|
|
10314
|
+
mkdirSync12(dirname8(plan.streamPath), { recursive: true });
|
|
10315
|
+
streamFd = openSync3(plan.streamPath, "a");
|
|
10316
|
+
} catch (e) {
|
|
10317
|
+
void endSink(eventsSink);
|
|
10318
|
+
return Promise.reject(new AgentSpawnError(`cannot open the stream file ${plan.streamPath}: ${e.message}`));
|
|
10319
|
+
}
|
|
10320
|
+
const failed = (e) => {
|
|
10321
|
+
void endSink(eventsSink);
|
|
10322
|
+
return Promise.reject(new AgentSpawnError(`cannot run ${plan.bin}: ${e.message}`));
|
|
10323
|
+
};
|
|
10324
|
+
let begun;
|
|
10325
|
+
try {
|
|
10326
|
+
begun = runner.start(plan, { env: { ...scrubbedEnv(process.env, adapter.harness), ...plan.setEnv }, streamFd, emit });
|
|
10327
|
+
} catch (e) {
|
|
10119
10328
|
closeSync3(streamFd);
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
ticketRecordId: plan.ticketRecordId ?? null,
|
|
10132
|
-
repo: plan.repo ?? null,
|
|
10133
|
-
routeName: plan.routeName,
|
|
10134
|
-
model: plan.model,
|
|
10135
|
-
toolchain: id.toolchain,
|
|
10136
|
-
harness: id.harness,
|
|
10137
|
-
provider: id.provider,
|
|
10138
|
-
eventsPath: plan.eventsPath,
|
|
10139
|
-
agentLog: id.agentLog,
|
|
10140
|
-
promptVersion: plan.promptVersion,
|
|
10141
|
-
promptSha: plan.promptSha
|
|
10142
|
-
};
|
|
10143
|
-
try {
|
|
10144
|
-
writeFileSync13(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10145
|
-
} catch {
|
|
10329
|
+
return failed(e);
|
|
10330
|
+
}
|
|
10331
|
+
if (begun instanceof Promise) {
|
|
10332
|
+
return begun.then(
|
|
10333
|
+
(run2) => {
|
|
10334
|
+
closeSync3(streamFd);
|
|
10335
|
+
return supervise(run2);
|
|
10336
|
+
},
|
|
10337
|
+
(e) => {
|
|
10338
|
+
closeSync3(streamFd);
|
|
10339
|
+
return failed(e);
|
|
10146
10340
|
}
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10341
|
+
);
|
|
10342
|
+
}
|
|
10343
|
+
closeSync3(streamFd);
|
|
10344
|
+
return supervise(begun);
|
|
10345
|
+
function supervise({ child, handle, promptDelivered }) {
|
|
10346
|
+
return new Promise((resolve7, reject) => {
|
|
10347
|
+
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10348
|
+
const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
|
|
10349
|
+
if (child.pid !== void 0) {
|
|
10350
|
+
const sidecar = {
|
|
10351
|
+
pid: child.pid,
|
|
10352
|
+
ownerPid: process.pid,
|
|
10353
|
+
spawnedAt,
|
|
10354
|
+
role: plan.role,
|
|
10355
|
+
route: plan.route,
|
|
10356
|
+
...deadline ? { deadline, maxRunMinutes: plan.maxRunMinutes } : {},
|
|
10357
|
+
...handle.runner === "sandbox" ? { runner: "sandbox", sandbox: handle.sandbox, sandboxBin: handle.sandboxBin } : {},
|
|
10358
|
+
ticket: plan.ticket ?? null,
|
|
10359
|
+
ticketRecordId: plan.ticketRecordId ?? null,
|
|
10360
|
+
repo: plan.repo ?? null,
|
|
10361
|
+
routeName: plan.routeName,
|
|
10362
|
+
model: plan.model,
|
|
10363
|
+
toolchain: id.toolchain,
|
|
10364
|
+
harness: id.harness,
|
|
10365
|
+
provider: id.provider,
|
|
10366
|
+
eventsPath: plan.eventsPath,
|
|
10367
|
+
agentLog: id.agentLog,
|
|
10368
|
+
promptVersion: plan.promptVersion,
|
|
10369
|
+
promptSha: plan.promptSha
|
|
10370
|
+
};
|
|
10371
|
+
try {
|
|
10372
|
+
writeFileSync14(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10373
|
+
} catch {
|
|
10374
|
+
}
|
|
10177
10375
|
}
|
|
10178
|
-
const
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10376
|
+
const obs = new RunObserver(id, adapter, eventsSink, emit.cycle);
|
|
10377
|
+
const meta = { pid: child.pid ?? null, ...deadline ? { deadline } : {} };
|
|
10378
|
+
obs.onClaim = () => writeRunMeta(id, startedAt, obs.liveTicket, meta);
|
|
10379
|
+
if (child.pid !== void 0) writeRunMeta(id, startedAt, obs.liveTicket, meta);
|
|
10380
|
+
const tail3 = new FileTail(plan.streamPath);
|
|
10381
|
+
const timer = setInterval(() => obs.feed(tail3.poll()), TAIL_POLL_MS);
|
|
10382
|
+
let killed;
|
|
10383
|
+
const limitTimer = child.pid !== void 0 && deadline ? setTimeout(() => {
|
|
10384
|
+
void killForDeadline(() => runner.kill(handle, WALL_CLOCK_REASON), child.pid, emit, plan.role, obs.liveTicket ?? plan.ticket ?? null, spawnedAt, plan.maxRunMinutes).then((k) => {
|
|
10385
|
+
killed = k;
|
|
10386
|
+
});
|
|
10387
|
+
}, Math.max(0, msUntilDeadline(deadline) ?? 0)) : void 0;
|
|
10388
|
+
let spawnFailed = false;
|
|
10389
|
+
child.on("error", (err3) => {
|
|
10390
|
+
spawnFailed = true;
|
|
10391
|
+
clearInterval(timer);
|
|
10392
|
+
clearTimeout(limitTimer);
|
|
10393
|
+
void runner.release(handle, emit);
|
|
10394
|
+
reject(new AgentSpawnError(`cannot run ${plan.bin}: ${err3.message}`));
|
|
10395
|
+
});
|
|
10396
|
+
child.on("close", async (code) => {
|
|
10397
|
+
clearInterval(timer);
|
|
10398
|
+
clearTimeout(limitTimer);
|
|
10399
|
+
obs.feed(tail3.poll());
|
|
10400
|
+
obs.end();
|
|
10401
|
+
await endSink(eventsSink);
|
|
10402
|
+
if (spawnFailed) return;
|
|
10403
|
+
await runner.release(handle, emit);
|
|
10404
|
+
if (limitTimer && !killed && !obs.sawResult && msUntilDeadline(deadline) <= 0) {
|
|
10405
|
+
killed = { limitMinutes: plan.maxRunMinutes ?? 0, elapsedMs: Date.now() - Date.parse(spawnedAt) };
|
|
10406
|
+
}
|
|
10407
|
+
const { ms: ms3, handoffPending } = await finishRun(
|
|
10408
|
+
id,
|
|
10409
|
+
obs,
|
|
10410
|
+
emit,
|
|
10411
|
+
startedAt,
|
|
10412
|
+
{ code: code ?? 1, ...killed ? { interrupted: WALL_CLOCK_REASON } : {} },
|
|
10413
|
+
plan.agentLog?.apiKey,
|
|
10414
|
+
hintMoved
|
|
10415
|
+
);
|
|
10416
|
+
resolve7({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
|
|
10417
|
+
});
|
|
10418
|
+
child.stdin?.on("error", (err3) => {
|
|
10419
|
+
emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
|
|
10420
|
+
});
|
|
10421
|
+
if (promptDelivered) child.stdin?.end();
|
|
10422
|
+
else child.stdin?.end(plan.prompt);
|
|
10191
10423
|
});
|
|
10192
|
-
|
|
10193
|
-
});
|
|
10424
|
+
}
|
|
10194
10425
|
}
|
|
10195
10426
|
async function reattachRun(streamPath, sc, emit, o = {}) {
|
|
10196
10427
|
const adapter = adapterFor(sc.harness);
|
|
@@ -10202,6 +10433,7 @@ async function reattachRun(streamPath, sc, emit, o = {}) {
|
|
|
10202
10433
|
obs.muted = true;
|
|
10203
10434
|
obs.feed(tail3.poll());
|
|
10204
10435
|
obs.muted = false;
|
|
10436
|
+
const runner = runnerOfSidecar(sc);
|
|
10205
10437
|
const adopted = isAlive(sc);
|
|
10206
10438
|
let killed;
|
|
10207
10439
|
if (adopted) {
|
|
@@ -10210,7 +10442,7 @@ async function reattachRun(streamPath, sc, emit, o = {}) {
|
|
|
10210
10442
|
while (isAlive(sc)) {
|
|
10211
10443
|
obs.feed(tail3.poll());
|
|
10212
10444
|
if (!killed && (msUntilDeadline(sc.deadline) ?? 1) <= 0) {
|
|
10213
|
-
killed = await killForDeadline(sc.pid, emit, sc.role, obs.liveTicket ?? sc.ticket, sc.spawnedAt, sc.maxRunMinutes);
|
|
10445
|
+
killed = await killForDeadline(() => runner.kill(runner.reattach(sc), WALL_CLOCK_REASON), sc.pid, emit, sc.role, obs.liveTicket ?? sc.ticket, sc.spawnedAt, sc.maxRunMinutes);
|
|
10214
10446
|
continue;
|
|
10215
10447
|
}
|
|
10216
10448
|
await new Promise((r) => setTimeout(r, pollMs));
|
|
@@ -10219,6 +10451,7 @@ async function reattachRun(streamPath, sc, emit, o = {}) {
|
|
|
10219
10451
|
obs.feed(tail3.poll());
|
|
10220
10452
|
obs.end();
|
|
10221
10453
|
await endSink(eventsSink);
|
|
10454
|
+
await runner.release(runner.reattach(sc), emit);
|
|
10222
10455
|
const interrupted = killed ? WALL_CLOCK_REASON : obs.sawResult ? void 0 : adopted ? "no-result" : "daemon-exit";
|
|
10223
10456
|
const { ms: ms3, ticket } = await finishRun(
|
|
10224
10457
|
id,
|
|
@@ -10528,7 +10761,7 @@ var realDeps = {
|
|
|
10528
10761
|
resolve7(r);
|
|
10529
10762
|
}
|
|
10530
10763
|
};
|
|
10531
|
-
const child =
|
|
10764
|
+
const child = spawn4(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
10532
10765
|
const killGroup = () => {
|
|
10533
10766
|
try {
|
|
10534
10767
|
if (child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
@@ -10673,7 +10906,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
10673
10906
|
...adapter.runEnv(spec),
|
|
10674
10907
|
[KEY_VAR]: i.apiKey
|
|
10675
10908
|
};
|
|
10676
|
-
const cwd =
|
|
10909
|
+
const cwd = mkdtempSync2(join24(tmpdir3(), "crew-selftest-"));
|
|
10677
10910
|
try {
|
|
10678
10911
|
const run2 = await deps.run({
|
|
10679
10912
|
bin: invocation.bin,
|
|
@@ -10695,7 +10928,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
10695
10928
|
return finish({ ok: false, failure, detail, ...usage2 });
|
|
10696
10929
|
} finally {
|
|
10697
10930
|
try {
|
|
10698
|
-
|
|
10931
|
+
rmSync13(cwd, { recursive: true, force: true });
|
|
10699
10932
|
} catch {
|
|
10700
10933
|
}
|
|
10701
10934
|
}
|
|
@@ -10706,7 +10939,7 @@ function formatResult(r) {
|
|
|
10706
10939
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
10707
10940
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
10708
10941
|
}
|
|
10709
|
-
var fileFor2 = (stateDir) =>
|
|
10942
|
+
var fileFor2 = (stateDir) => join24(stateDir, "selftest.json");
|
|
10710
10943
|
function readSelftest(stateDir) {
|
|
10711
10944
|
try {
|
|
10712
10945
|
const p = fileFor2(stateDir);
|
|
@@ -10719,7 +10952,7 @@ function recordSelftest(stateDir, results) {
|
|
|
10719
10952
|
const all = readSelftest(stateDir);
|
|
10720
10953
|
for (const r of results) all[r.toolchain] = r;
|
|
10721
10954
|
mkdirSync13(stateDir, { recursive: true });
|
|
10722
|
-
|
|
10955
|
+
writeFileSync15(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
10723
10956
|
`);
|
|
10724
10957
|
}
|
|
10725
10958
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -10731,7 +10964,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
10731
10964
|
}
|
|
10732
10965
|
|
|
10733
10966
|
// src/cli.ts
|
|
10734
|
-
import { resolve as resolve6, dirname as dirname25, join as
|
|
10967
|
+
import { resolve as resolve6, dirname as dirname25, join as join48, basename as basename15 } from "path";
|
|
10735
10968
|
import { fileURLToPath } from "url";
|
|
10736
10969
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
10737
10970
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -10928,10 +11161,10 @@ function rosterMarkdown(roster, meId) {
|
|
|
10928
11161
|
}
|
|
10929
11162
|
|
|
10930
11163
|
// src/connect-resume.ts
|
|
10931
|
-
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as
|
|
10932
|
-
import { dirname as dirname9, join as
|
|
11164
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "fs";
|
|
11165
|
+
import { dirname as dirname9, join as join25 } from "path";
|
|
10933
11166
|
function lastSessionFile(stateDir) {
|
|
10934
|
-
return
|
|
11167
|
+
return join25(stateDir, "connect-session.json");
|
|
10935
11168
|
}
|
|
10936
11169
|
function readPointers(stateDir) {
|
|
10937
11170
|
try {
|
|
@@ -10950,7 +11183,7 @@ function readPointers(stateDir) {
|
|
|
10950
11183
|
function writePointers(stateDir, pointers) {
|
|
10951
11184
|
const file = lastSessionFile(stateDir);
|
|
10952
11185
|
mkdirSync14(dirname9(file), { recursive: true });
|
|
10953
|
-
|
|
11186
|
+
writeFileSync16(file, `${JSON.stringify(pointers, null, 2)}
|
|
10954
11187
|
`, { mode: 384 });
|
|
10955
11188
|
}
|
|
10956
11189
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -10967,7 +11200,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
|
|
|
10967
11200
|
const { [host]: _old, ...rest } = all;
|
|
10968
11201
|
const next = left.length ? { ...rest, [host]: left } : rest;
|
|
10969
11202
|
if (Object.keys(next).length) writePointers(stateDir, next);
|
|
10970
|
-
else
|
|
11203
|
+
else rmSync14(lastSessionFile(stateDir), { force: true });
|
|
10971
11204
|
} catch {
|
|
10972
11205
|
}
|
|
10973
11206
|
}
|
|
@@ -11007,7 +11240,7 @@ function forgetSession(stateDir, host) {
|
|
|
11007
11240
|
try {
|
|
11008
11241
|
const { [host]: _gone, ...rest } = readPointers(stateDir);
|
|
11009
11242
|
if (Object.keys(rest).length) writePointers(stateDir, rest);
|
|
11010
|
-
else
|
|
11243
|
+
else rmSync14(lastSessionFile(stateDir), { force: true });
|
|
11011
11244
|
} catch {
|
|
11012
11245
|
}
|
|
11013
11246
|
}
|
|
@@ -11035,11 +11268,11 @@ async function signOutSessions(o) {
|
|
|
11035
11268
|
}
|
|
11036
11269
|
|
|
11037
11270
|
// src/reset.ts
|
|
11038
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync22, readdirSync as readdirSync9, rmSync as
|
|
11039
|
-
import { dirname as dirname10, join as
|
|
11271
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync22, readdirSync as readdirSync9, rmSync as rmSync15, statSync as statSync8, writeFileSync as writeFileSync17 } from "fs";
|
|
11272
|
+
import { dirname as dirname10, join as join26 } from "path";
|
|
11040
11273
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
11041
11274
|
function clonedCheckoutsFile(stateDir) {
|
|
11042
|
-
return
|
|
11275
|
+
return join26(stateDir, "cloned-checkouts.json");
|
|
11043
11276
|
}
|
|
11044
11277
|
function readClonedCheckouts(stateDir) {
|
|
11045
11278
|
try {
|
|
@@ -11054,7 +11287,7 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
11054
11287
|
if (known.includes(dir)) return;
|
|
11055
11288
|
const file = clonedCheckoutsFile(stateDir);
|
|
11056
11289
|
mkdirSync15(dirname10(file), { recursive: true });
|
|
11057
|
-
|
|
11290
|
+
writeFileSync17(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
11058
11291
|
`);
|
|
11059
11292
|
}
|
|
11060
11293
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -11109,7 +11342,7 @@ async function runReset(o, fx) {
|
|
|
11109
11342
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
11110
11343
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
11111
11344
|
if (existsSync23(o.configFile)) {
|
|
11112
|
-
if (!o.dryRun)
|
|
11345
|
+
if (!o.dryRun) rmSync15(o.configFile, { force: true });
|
|
11113
11346
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
11114
11347
|
} else {
|
|
11115
11348
|
steps.push({ step: "config", target: o.configFile, action: "absent" });
|
|
@@ -11125,21 +11358,21 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
11125
11358
|
if (linkedWorktrees.length) {
|
|
11126
11359
|
return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
|
|
11127
11360
|
}
|
|
11128
|
-
if (!dryRun)
|
|
11361
|
+
if (!dryRun) rmSync15(dir, { recursive: true, force: true });
|
|
11129
11362
|
return { step: "checkout", target: dir, action: mark(dryRun) };
|
|
11130
11363
|
}
|
|
11131
11364
|
function removeState(stateDir, dryRun) {
|
|
11132
11365
|
const out = [];
|
|
11133
|
-
const targets = [
|
|
11366
|
+
const targets = [join26(stateDir, "resolved"), join26(stateDir, "keys"), clonedCheckoutsFile(stateDir), join26(stateDir, "connect-session.json"), join26(stateDir, "reload-requested"), join26(stateDir, "daemon.started")];
|
|
11134
11367
|
try {
|
|
11135
11368
|
for (const name of readdirSync9(stateDir)) {
|
|
11136
|
-
if (name.startsWith(".") && statSync8(
|
|
11369
|
+
if (name.startsWith(".") && statSync8(join26(stateDir, name)).isFile()) targets.push(join26(stateDir, name));
|
|
11137
11370
|
}
|
|
11138
11371
|
} catch {
|
|
11139
11372
|
}
|
|
11140
11373
|
for (const t of targets) {
|
|
11141
11374
|
if (!existsSync23(t)) continue;
|
|
11142
|
-
if (!dryRun)
|
|
11375
|
+
if (!dryRun) rmSync15(t, { recursive: true, force: true });
|
|
11143
11376
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
11144
11377
|
}
|
|
11145
11378
|
if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
|
|
@@ -11153,7 +11386,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
11153
11386
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
11154
11387
|
}
|
|
11155
11388
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
11156
|
-
if (!dryRun) for (const n of names)
|
|
11389
|
+
if (!dryRun) for (const n of names) rmSync15(join26(stateDir, n), { recursive: true, force: true });
|
|
11157
11390
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
11158
11391
|
}
|
|
11159
11392
|
function describeResetStep(s) {
|
|
@@ -11239,7 +11472,7 @@ import { existsSync as existsSync26, readdirSync as readdirSync12 } from "fs";
|
|
|
11239
11472
|
|
|
11240
11473
|
// src/interrupted.ts
|
|
11241
11474
|
import { existsSync as existsSync24, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
|
|
11242
|
-
import { join as
|
|
11475
|
+
import { join as join27 } from "path";
|
|
11243
11476
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
11244
11477
|
function interruptedComment(b) {
|
|
11245
11478
|
const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
|
|
@@ -11266,13 +11499,13 @@ function runFinished(streamPath) {
|
|
|
11266
11499
|
}
|
|
11267
11500
|
}
|
|
11268
11501
|
function orphanedStreams(o) {
|
|
11269
|
-
const dir =
|
|
11502
|
+
const dir = join27(o.stateDir, "streams");
|
|
11270
11503
|
if (!existsSync24(dir)) return [];
|
|
11271
11504
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
11272
11505
|
const out = [];
|
|
11273
11506
|
for (const name of readdirSync10(dir)) {
|
|
11274
11507
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11275
|
-
const streamPath =
|
|
11508
|
+
const streamPath = join27(dir, name);
|
|
11276
11509
|
if (runFinished(streamPath)) continue;
|
|
11277
11510
|
const sidecar = readRunSidecar(streamPath);
|
|
11278
11511
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -11326,14 +11559,14 @@ async function reconcileRuns(o) {
|
|
|
11326
11559
|
|
|
11327
11560
|
// src/live-runs.ts
|
|
11328
11561
|
import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
|
|
11329
|
-
import { join as
|
|
11562
|
+
import { join as join28 } from "path";
|
|
11330
11563
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
11331
|
-
const dir =
|
|
11564
|
+
const dir = join28(stateDir, "streams");
|
|
11332
11565
|
if (!existsSync25(dir)) return [];
|
|
11333
11566
|
const out = [];
|
|
11334
11567
|
for (const name of readdirSync11(dir)) {
|
|
11335
11568
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11336
|
-
const streamPath =
|
|
11569
|
+
const streamPath = join28(dir, name);
|
|
11337
11570
|
if (runFinished(streamPath)) continue;
|
|
11338
11571
|
const sidecar = readRunSidecar(streamPath);
|
|
11339
11572
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -11395,11 +11628,11 @@ function describeRun(run2, route2, seatIds, worktreeOf = (d, n) => {
|
|
|
11395
11628
|
}
|
|
11396
11629
|
|
|
11397
11630
|
// src/passenger-containers.ts
|
|
11398
|
-
import { execFileSync as
|
|
11631
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
11399
11632
|
import { createHash as createHash5 } from "crypto";
|
|
11400
11633
|
var realExec = (cmd, args, opts) => {
|
|
11401
11634
|
try {
|
|
11402
|
-
return
|
|
11635
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11403
11636
|
} catch (e) {
|
|
11404
11637
|
const err3 = e;
|
|
11405
11638
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11708,9 +11941,9 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11708
11941
|
}
|
|
11709
11942
|
|
|
11710
11943
|
// src/pool-member.ts
|
|
11711
|
-
import { mkdirSync as mkdirSync16, readFileSync as readFileSync24, renameSync as renameSync3, writeFileSync as
|
|
11944
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync24, renameSync as renameSync3, writeFileSync as writeFileSync18 } from "fs";
|
|
11712
11945
|
import { homedir as homedir8 } from "os";
|
|
11713
|
-
import { dirname as dirname11, join as
|
|
11946
|
+
import { dirname as dirname11, join as join29 } from "path";
|
|
11714
11947
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
11715
11948
|
function backoffMs(attempt) {
|
|
11716
11949
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -11899,10 +12132,10 @@ async function runPoolMember(deps) {
|
|
|
11899
12132
|
}
|
|
11900
12133
|
}
|
|
11901
12134
|
function defaultMemberDir(env = process.env) {
|
|
11902
|
-
return env.CREW_POOL_DIR ??
|
|
12135
|
+
return env.CREW_POOL_DIR ?? join29(env.XDG_STATE_HOME ?? join29(homedir8(), ".local", "state"), "crew-pool-member");
|
|
11903
12136
|
}
|
|
11904
12137
|
function fileMemberStore(dir) {
|
|
11905
|
-
const file =
|
|
12138
|
+
const file = join29(dir, "member.json");
|
|
11906
12139
|
return {
|
|
11907
12140
|
read() {
|
|
11908
12141
|
try {
|
|
@@ -11914,7 +12147,7 @@ function fileMemberStore(dir) {
|
|
|
11914
12147
|
write(f) {
|
|
11915
12148
|
mkdirSync16(dirname11(file), { recursive: true, mode: 448 });
|
|
11916
12149
|
const tmp = `${file}.tmp`;
|
|
11917
|
-
|
|
12150
|
+
writeFileSync18(tmp, `${JSON.stringify(f)}
|
|
11918
12151
|
`, { mode: 384 });
|
|
11919
12152
|
renameSync3(tmp, file);
|
|
11920
12153
|
}
|
|
@@ -11970,18 +12203,18 @@ import { existsSync as existsSync30 } from "fs";
|
|
|
11970
12203
|
import { hostname as hostname3 } from "os";
|
|
11971
12204
|
|
|
11972
12205
|
// src/machine-id.ts
|
|
11973
|
-
import { execFileSync as
|
|
12206
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
11974
12207
|
import { createHash as createHash6 } from "crypto";
|
|
11975
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync25, writeFileSync as
|
|
11976
|
-
import { dirname as dirname12, join as
|
|
12208
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
12209
|
+
import { dirname as dirname12, join as join30 } from "path";
|
|
11977
12210
|
function machineIdPathFor(stateDir) {
|
|
11978
|
-
return
|
|
12211
|
+
return join30(stateDir, "machine-id");
|
|
11979
12212
|
}
|
|
11980
12213
|
var sha256 = (s) => createHash6("sha256").update(s).digest("hex");
|
|
11981
12214
|
function readRawMachineId(platform2 = process.platform) {
|
|
11982
12215
|
try {
|
|
11983
12216
|
if (platform2 === "darwin") {
|
|
11984
|
-
const out =
|
|
12217
|
+
const out = execFileSync7("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
11985
12218
|
encoding: "utf8",
|
|
11986
12219
|
stdio: ["ignore", "pipe", "ignore"]
|
|
11987
12220
|
});
|
|
@@ -12008,7 +12241,7 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12008
12241
|
if (!raw) return void 0;
|
|
12009
12242
|
const hashed = sha256(raw);
|
|
12010
12243
|
mkdirSync17(dirname12(path), { recursive: true });
|
|
12011
|
-
|
|
12244
|
+
writeFileSync19(path, `${hashed}
|
|
12012
12245
|
`, { mode: 384 });
|
|
12013
12246
|
return hashed;
|
|
12014
12247
|
}
|
|
@@ -12021,7 +12254,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
12021
12254
|
|
|
12022
12255
|
// src/provision.ts
|
|
12023
12256
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
12024
|
-
import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as
|
|
12257
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
12025
12258
|
import { hostname as hostname2 } from "os";
|
|
12026
12259
|
import { parseDocument as parseDocument3 } from "yaml";
|
|
12027
12260
|
|
|
@@ -12030,7 +12263,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
12030
12263
|
|
|
12031
12264
|
// src/repos-cmd.ts
|
|
12032
12265
|
import { homedir as homedir9 } from "os";
|
|
12033
|
-
import { basename as
|
|
12266
|
+
import { basename as basename9, join as join31, resolve as resolve3 } from "path";
|
|
12034
12267
|
import { isMap, isSeq, parseDocument, stringify } from "yaml";
|
|
12035
12268
|
var ReposError = class extends Error {
|
|
12036
12269
|
};
|
|
@@ -12102,7 +12335,7 @@ function configuredRepos(text2, route2, base) {
|
|
|
12102
12335
|
});
|
|
12103
12336
|
}
|
|
12104
12337
|
const dir = m.get("dir");
|
|
12105
|
-
return dir ? [{ name:
|
|
12338
|
+
return dir ? [{ name: basename9(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
12106
12339
|
}
|
|
12107
12340
|
function cloneBaseOf(text2, route2, base, isCheckout) {
|
|
12108
12341
|
const m = routeMapOf(parseDocument(text2), route2);
|
|
@@ -12191,7 +12424,7 @@ function planRepoAdd(i) {
|
|
|
12191
12424
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
12192
12425
|
}
|
|
12193
12426
|
const matched = matchRepoName(i.origin, i.resolved);
|
|
12194
|
-
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ??
|
|
12427
|
+
const name = matched ?? i.name ?? normalizeRemote(i.origin)?.split("/").pop() ?? basename9(target);
|
|
12195
12428
|
let warning;
|
|
12196
12429
|
if (!matched) {
|
|
12197
12430
|
const known = Object.values(i.resolved?.repoNames ?? {});
|
|
@@ -12308,7 +12541,7 @@ function planRepoClone(i) {
|
|
|
12308
12541
|
}
|
|
12309
12542
|
const ship = doc.get("ship", true);
|
|
12310
12543
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
12311
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
12544
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join31(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
12312
12545
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
12313
12546
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
12314
12547
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -12587,7 +12820,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12587
12820
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
12588
12821
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
12589
12822
|
if (entry.get("enabled") === true) return false;
|
|
12590
|
-
|
|
12823
|
+
writeFileSync20(file, planEnable(text2, route2));
|
|
12591
12824
|
return true;
|
|
12592
12825
|
},
|
|
12593
12826
|
installState() {
|
|
@@ -12634,7 +12867,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12634
12867
|
const doc = parseDocument3(readFileSync26(file, "utf8"));
|
|
12635
12868
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
12636
12869
|
doc.setIn(["ship", key], value2);
|
|
12637
|
-
|
|
12870
|
+
writeFileSync20(file, doc.toString());
|
|
12638
12871
|
return true;
|
|
12639
12872
|
},
|
|
12640
12873
|
storedToken(host) {
|
|
@@ -12650,12 +12883,12 @@ host=${host}
|
|
|
12650
12883
|
}
|
|
12651
12884
|
|
|
12652
12885
|
// src/tunnel.ts
|
|
12653
|
-
import { spawn as
|
|
12654
|
-
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync27, writeFileSync as
|
|
12886
|
+
import { spawn as spawn5 } from "child_process";
|
|
12887
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync27, writeFileSync as writeFileSync21, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
12655
12888
|
import { randomInt } from "crypto";
|
|
12656
|
-
import { join as
|
|
12889
|
+
import { join as join32 } from "path";
|
|
12657
12890
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
12658
|
-
var defaultSpawn = (cmd, args) =>
|
|
12891
|
+
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
12659
12892
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
12660
12893
|
var SLUG_LENGTH = 12;
|
|
12661
12894
|
function generateTunnelSlug() {
|
|
@@ -12725,7 +12958,7 @@ function startTunnel(opts) {
|
|
|
12725
12958
|
};
|
|
12726
12959
|
}
|
|
12727
12960
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
12728
|
-
return
|
|
12961
|
+
return join32(stateDir, "passengers", `${workspaceId}.json`);
|
|
12729
12962
|
}
|
|
12730
12963
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
12731
12964
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
@@ -12738,8 +12971,8 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
12738
12971
|
}
|
|
12739
12972
|
function writePersistedTunnel(stateDir, state) {
|
|
12740
12973
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
12741
|
-
mkdirSync18(
|
|
12742
|
-
|
|
12974
|
+
mkdirSync18(join32(stateDir, "passengers"), { recursive: true });
|
|
12975
|
+
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
12743
12976
|
`);
|
|
12744
12977
|
}
|
|
12745
12978
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -12841,7 +13074,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
12841
13074
|
}
|
|
12842
13075
|
}
|
|
12843
13076
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
12844
|
-
const dir =
|
|
13077
|
+
const dir = join32(stateDir, "passengers");
|
|
12845
13078
|
if (!existsSync29(dir)) return [];
|
|
12846
13079
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
12847
13080
|
}
|
|
@@ -13708,8 +13941,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
13708
13941
|
}
|
|
13709
13942
|
|
|
13710
13943
|
// src/poll.ts
|
|
13711
|
-
import { writeFileSync as
|
|
13712
|
-
import { join as
|
|
13944
|
+
import { writeFileSync as writeFileSync22 } from "fs";
|
|
13945
|
+
import { join as join33 } from "path";
|
|
13713
13946
|
|
|
13714
13947
|
// src/stalled.ts
|
|
13715
13948
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -14016,7 +14249,7 @@ async function decideCycle(o) {
|
|
|
14016
14249
|
}
|
|
14017
14250
|
return decision;
|
|
14018
14251
|
}
|
|
14019
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
14252
|
+
var digestPath = (stateDir, route2, role, runId) => join33(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
14020
14253
|
function otherRunsRemoved(tickets, own) {
|
|
14021
14254
|
if (!own) return tickets;
|
|
14022
14255
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -14094,7 +14327,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
14094
14327
|
|
|
14095
14328
|
${body}` : body;
|
|
14096
14329
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
14097
|
-
|
|
14330
|
+
writeFileSync22(path, text2);
|
|
14098
14331
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
14099
14332
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
14100
14333
|
});
|
|
@@ -14202,7 +14435,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
14202
14435
|
}
|
|
14203
14436
|
|
|
14204
14437
|
// src/repo-refresh.ts
|
|
14205
|
-
import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync28, writeFileSync as
|
|
14438
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "fs";
|
|
14206
14439
|
import { dirname as dirname14 } from "path";
|
|
14207
14440
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
14208
14441
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -14236,7 +14469,7 @@ function persistRepoRows(stateDir, routeName, rows) {
|
|
|
14236
14469
|
const raw = JSON.parse(readFileSync28(path, "utf8"));
|
|
14237
14470
|
applyRepoRows(raw, rows);
|
|
14238
14471
|
mkdirSync19(dirname14(path), { recursive: true });
|
|
14239
|
-
|
|
14472
|
+
writeFileSync23(path, `${JSON.stringify(raw, null, 2)}
|
|
14240
14473
|
`);
|
|
14241
14474
|
}
|
|
14242
14475
|
async function refreshUnknownRepos(o) {
|
|
@@ -14686,8 +14919,8 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
14686
14919
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
14687
14920
|
|
|
14688
14921
|
// src/connect-wizard.ts
|
|
14689
|
-
import { delimiter, dirname as dirname15, join as
|
|
14690
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync20, writeFileSync as
|
|
14922
|
+
import { delimiter, dirname as dirname15, join as join34 } from "path";
|
|
14923
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync20, writeFileSync as writeFileSync24 } from "fs";
|
|
14691
14924
|
import { stringify as stringify2 } from "yaml";
|
|
14692
14925
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
14693
14926
|
function shouldRunWizard(opts) {
|
|
@@ -14709,7 +14942,7 @@ function defaultIsExecutable(p) {
|
|
|
14709
14942
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
14710
14943
|
for (const dir of pathEnv.split(delimiter)) {
|
|
14711
14944
|
if (!dir) continue;
|
|
14712
|
-
const candidate =
|
|
14945
|
+
const candidate = join34(dir, name);
|
|
14713
14946
|
if (isExecutable(candidate)) return candidate;
|
|
14714
14947
|
}
|
|
14715
14948
|
return void 0;
|
|
@@ -14772,7 +15005,7 @@ ${routeBlock}`;
|
|
|
14772
15005
|
function writeNewConfig(path, text2) {
|
|
14773
15006
|
mkdirSync20(dirname15(path), { recursive: true });
|
|
14774
15007
|
try {
|
|
14775
|
-
|
|
15008
|
+
writeFileSync24(path, text2, { mode: 384, flag: "wx" });
|
|
14776
15009
|
return true;
|
|
14777
15010
|
} catch (e) {
|
|
14778
15011
|
if (e.code === "EEXIST") return false;
|
|
@@ -14806,7 +15039,7 @@ Wrote ${o.configPath}.
|
|
|
14806
15039
|
// src/connect-json.ts
|
|
14807
15040
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
14808
15041
|
import { homedir as homedir10 } from "os";
|
|
14809
|
-
import { dirname as dirname16, join as
|
|
15042
|
+
import { dirname as dirname16, join as join35 } from "path";
|
|
14810
15043
|
import { delimiter as delimiter2 } from "path";
|
|
14811
15044
|
var CHOICE_EXIT_CODE = 3;
|
|
14812
15045
|
function createEmitter(json2, write) {
|
|
@@ -14845,12 +15078,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
14845
15078
|
const dirs = [
|
|
14846
15079
|
...pathEnv.split(delimiter2),
|
|
14847
15080
|
...(extraPath ?? "").split(delimiter2),
|
|
14848
|
-
|
|
14849
|
-
|
|
15081
|
+
join35(home, ".local/bin"),
|
|
15082
|
+
join35(home, ".claude/local"),
|
|
14850
15083
|
"/opt/homebrew/bin",
|
|
14851
15084
|
"/usr/local/bin",
|
|
14852
|
-
|
|
14853
|
-
|
|
15085
|
+
join35(home, ".npm-global/bin"),
|
|
15086
|
+
join35(home, ".volta/bin")
|
|
14854
15087
|
];
|
|
14855
15088
|
return [...new Set(dirs.filter(Boolean))];
|
|
14856
15089
|
}
|
|
@@ -14886,9 +15119,9 @@ function flagWizardAnswers(f, env) {
|
|
|
14886
15119
|
}
|
|
14887
15120
|
|
|
14888
15121
|
// src/release-run.ts
|
|
14889
|
-
import { readFileSync as readFileSync29, writeFileSync as
|
|
14890
|
-
import { dirname as dirname17, join as
|
|
14891
|
-
import { tmpdir as
|
|
15122
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync33, copyFileSync, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync3, rmSync as rmSync16 } from "fs";
|
|
15123
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
15124
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
14892
15125
|
|
|
14893
15126
|
// src/release.ts
|
|
14894
15127
|
function describeUnplaceable(unplaceable, verifiedStatus) {
|
|
@@ -15628,7 +15861,7 @@ async function bump(o, size) {
|
|
|
15628
15861
|
}
|
|
15629
15862
|
return printed;
|
|
15630
15863
|
}
|
|
15631
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
15864
|
+
const files = o.repo.release.versionFiles.map((f) => join36(o.cwd, f));
|
|
15632
15865
|
const first = files[0];
|
|
15633
15866
|
if (!existsSync33(first)) {
|
|
15634
15867
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
@@ -15653,21 +15886,21 @@ async function bump(o, size) {
|
|
|
15653
15886
|
o.emit.error(`no version field in ${f}`);
|
|
15654
15887
|
return null;
|
|
15655
15888
|
}
|
|
15656
|
-
|
|
15889
|
+
writeFileSync25(f, out);
|
|
15657
15890
|
}
|
|
15658
15891
|
return version;
|
|
15659
15892
|
}
|
|
15660
15893
|
function writeChangelog(o, version, entries) {
|
|
15661
15894
|
const file = o.repo.release.changelog;
|
|
15662
15895
|
if (!file || entries.length === 0) return;
|
|
15663
|
-
const path =
|
|
15896
|
+
const path = join36(o.cwd, file);
|
|
15664
15897
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
15665
15898
|
if (o.dryRun) {
|
|
15666
15899
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
15667
15900
|
return;
|
|
15668
15901
|
}
|
|
15669
15902
|
const existing = existsSync33(path) ? readFileSync29(path, "utf8") : "# Changelog\n";
|
|
15670
|
-
|
|
15903
|
+
writeFileSync25(path, insertChangelogSection(existing, section));
|
|
15671
15904
|
}
|
|
15672
15905
|
async function confirm(o, expected) {
|
|
15673
15906
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -15750,7 +15983,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
15750
15983
|
run2(cwd, ["worktree", "remove", "--force", dir]);
|
|
15751
15984
|
} finally {
|
|
15752
15985
|
try {
|
|
15753
|
-
|
|
15986
|
+
rmSync16(root, { recursive: true, force: true });
|
|
15754
15987
|
} finally {
|
|
15755
15988
|
run2(cwd, ["worktree", "prune"]);
|
|
15756
15989
|
run2(cwd, ["branch", "-D", branch]);
|
|
@@ -15760,8 +15993,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
15760
15993
|
function openReleaseCheckout(o) {
|
|
15761
15994
|
const { remote, base } = o.repo.branch;
|
|
15762
15995
|
const branch = `crew/release-${Date.now()}`;
|
|
15763
|
-
const root =
|
|
15764
|
-
const dir =
|
|
15996
|
+
const root = mkdtempSync3(join36(tmpdir4(), "crew-release-"));
|
|
15997
|
+
const dir = join36(root, "checkout");
|
|
15765
15998
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
15766
15999
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
15767
16000
|
try {
|
|
@@ -15771,10 +16004,10 @@ function openReleaseCheckout(o) {
|
|
|
15771
16004
|
throw e;
|
|
15772
16005
|
}
|
|
15773
16006
|
for (const f of o.repo.worktrees.copy) {
|
|
15774
|
-
const src =
|
|
16007
|
+
const src = join36(o.cwd, f);
|
|
15775
16008
|
if (!existsSync33(src)) continue;
|
|
15776
|
-
mkdirSync21(dirname17(
|
|
15777
|
-
copyFileSync(src,
|
|
16009
|
+
mkdirSync21(dirname17(join36(dir, f)), { recursive: true });
|
|
16010
|
+
copyFileSync(src, join36(dir, f));
|
|
15778
16011
|
}
|
|
15779
16012
|
return { dir, branch, close };
|
|
15780
16013
|
}
|
|
@@ -15919,7 +16152,7 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
|
15919
16152
|
let checkout;
|
|
15920
16153
|
const stranded = recoverStrandedReleaseCheckouts(o.cwd);
|
|
15921
16154
|
if (stranded.length > 0) o.emit.warn(scoped(`dropped release checkout(s) stranded by a crash: ${stranded.join(", ")}`));
|
|
15922
|
-
const swept = sweepStaleReleaseRoots(
|
|
16155
|
+
const swept = sweepStaleReleaseRoots(tmpdir4());
|
|
15923
16156
|
if (swept.length > 0) o.emit.emit(scoped(`removed ${swept.length} stale crew-release-* temp dir(s)`), { step: "release" });
|
|
15924
16157
|
try {
|
|
15925
16158
|
checkout = openReleaseCheckout(o);
|
|
@@ -17092,7 +17325,7 @@ function nextRoles(decision, ran) {
|
|
|
17092
17325
|
}
|
|
17093
17326
|
|
|
17094
17327
|
// src/ship-name.ts
|
|
17095
|
-
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync30, renameSync as renameSync4, writeFileSync as
|
|
17328
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync30, renameSync as renameSync4, writeFileSync as writeFileSync26 } from "fs";
|
|
17096
17329
|
import { dirname as dirname18 } from "path";
|
|
17097
17330
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
17098
17331
|
var ShipNameError = class extends Error {
|
|
@@ -17174,7 +17407,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
17174
17407
|
raw.shipName = name;
|
|
17175
17408
|
mkdirSync22(dirname18(resolvedFile), { recursive: true });
|
|
17176
17409
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
17177
|
-
|
|
17410
|
+
writeFileSync26(tmp, `${JSON.stringify(raw, null, 2)}
|
|
17178
17411
|
`);
|
|
17179
17412
|
renameSync4(tmp, resolvedFile);
|
|
17180
17413
|
}
|
|
@@ -17206,7 +17439,7 @@ async function reconcileShipName(i) {
|
|
|
17206
17439
|
if (result.status !== "ok") return result;
|
|
17207
17440
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
17208
17441
|
const text2 = readFileSync30(i.configFile, "utf8");
|
|
17209
|
-
|
|
17442
|
+
writeFileSync26(i.configFile, setShipNameInYaml(text2, result.name));
|
|
17210
17443
|
i.ship.name = result.name;
|
|
17211
17444
|
if (result.action === "row-wins") {
|
|
17212
17445
|
i.emit?.warn(
|
|
@@ -17865,7 +18098,7 @@ function describeCapability(i) {
|
|
|
17865
18098
|
}
|
|
17866
18099
|
|
|
17867
18100
|
// src/agents-sync-run.ts
|
|
17868
|
-
import { existsSync as existsSync36, mkdirSync as mkdirSync23, readFileSync as readFileSync31, writeFileSync as
|
|
18101
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync23, readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "fs";
|
|
17869
18102
|
import { dirname as dirname19 } from "path";
|
|
17870
18103
|
async function runAgentsSync(target, o) {
|
|
17871
18104
|
const dryRun = o.dryRun ?? false;
|
|
@@ -17904,7 +18137,7 @@ async function runAgentsSync(target, o) {
|
|
|
17904
18137
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
17905
18138
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
17906
18139
|
mkdirSync23(dirname19(resolvedPath), { recursive: true });
|
|
17907
|
-
|
|
18140
|
+
writeFileSync27(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
17908
18141
|
`);
|
|
17909
18142
|
if (target.resolved) {
|
|
17910
18143
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -18009,11 +18242,11 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
18009
18242
|
// src/repos-check.ts
|
|
18010
18243
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
18011
18244
|
import { readFileSync as readFileSync32 } from "fs";
|
|
18012
|
-
import { basename as
|
|
18245
|
+
import { basename as basename10 } from "path";
|
|
18013
18246
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
18014
18247
|
function checkHookSyntax(script, shell) {
|
|
18015
18248
|
const spec = resolveShell(shell);
|
|
18016
|
-
const name =
|
|
18249
|
+
const name = basename10(spec.bin).replace(/\.exe$/i, "").toLowerCase();
|
|
18017
18250
|
let run2;
|
|
18018
18251
|
if (POSIX_SHELL.test(name)) {
|
|
18019
18252
|
run2 = spawnSync9(spec.bin, ["-n"], { input: script, encoding: "utf8" });
|
|
@@ -18032,7 +18265,7 @@ function checkHookSyntax(script, shell) {
|
|
|
18032
18265
|
return { status: "error", message };
|
|
18033
18266
|
}
|
|
18034
18267
|
function renderSamples(cfg, dir) {
|
|
18035
|
-
const checkout =
|
|
18268
|
+
const checkout = basename10(dir.replace(/[/\\]+$/, ""));
|
|
18036
18269
|
const tagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", tag: "TABL-123", prefix: "tabl", lowercase: cfg.branch.lowercase };
|
|
18037
18270
|
const untagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", lowercase: cfg.branch.lowercase };
|
|
18038
18271
|
return [["with a project tag", tagged], ["without a project tag", untagged]].map(([label, ctx]) => {
|
|
@@ -18324,7 +18557,7 @@ function existingRepoConfig(f) {
|
|
|
18324
18557
|
}
|
|
18325
18558
|
|
|
18326
18559
|
// src/repos-hooks-agent.ts
|
|
18327
|
-
import { spawn as
|
|
18560
|
+
import { spawn as spawn6, execFileSync as execFileSync8 } from "child_process";
|
|
18328
18561
|
import { realpathSync as realpathSync2 } from "fs";
|
|
18329
18562
|
import { isAbsolute as isAbsolute2, relative } from "path";
|
|
18330
18563
|
var AgentSuggestError = class extends Error {
|
|
@@ -18427,7 +18660,7 @@ function classifyFailure(o) {
|
|
|
18427
18660
|
}
|
|
18428
18661
|
function porcelain(dir) {
|
|
18429
18662
|
try {
|
|
18430
|
-
return
|
|
18663
|
+
return execFileSync8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
18431
18664
|
} catch {
|
|
18432
18665
|
return void 0;
|
|
18433
18666
|
}
|
|
@@ -18445,7 +18678,7 @@ async function runAgentSuggest(o) {
|
|
|
18445
18678
|
const run2 = await new Promise((resolve7) => {
|
|
18446
18679
|
let out = "";
|
|
18447
18680
|
let err3 = "";
|
|
18448
|
-
const child =
|
|
18681
|
+
const child = spawn6(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
18449
18682
|
let settled = false;
|
|
18450
18683
|
const settle = (r) => {
|
|
18451
18684
|
if (settled) return;
|
|
@@ -18496,7 +18729,7 @@ ${run2.err}` });
|
|
|
18496
18729
|
}
|
|
18497
18730
|
|
|
18498
18731
|
// src/sample-ticket.ts
|
|
18499
|
-
import { basename as
|
|
18732
|
+
import { basename as basename11, dirname as dirname20, join as join37 } from "path";
|
|
18500
18733
|
var SAMPLE_REPO = "crew-sample";
|
|
18501
18734
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
18502
18735
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -18519,8 +18752,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
18519
18752
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
18520
18753
|
`;
|
|
18521
18754
|
function sampleDir(existing, reposBase, workspace) {
|
|
18522
|
-
if (existing.length > 0) return
|
|
18523
|
-
return
|
|
18755
|
+
if (existing.length > 0) return join37(dirname20(existing[0]), SAMPLE_REPO);
|
|
18756
|
+
return join37(reposBase, workspace, SAMPLE_REPO);
|
|
18524
18757
|
}
|
|
18525
18758
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
18526
18759
|
function sampleDescription() {
|
|
@@ -18565,7 +18798,7 @@ function planSampleTicket(i) {
|
|
|
18565
18798
|
|
|
18566
18799
|
// src/worktree-sweep.ts
|
|
18567
18800
|
import { existsSync as existsSync37, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "fs";
|
|
18568
|
-
import { basename as
|
|
18801
|
+
import { basename as basename12, dirname as dirname21, join as join38, resolve as resolve4 } from "path";
|
|
18569
18802
|
function planWorktreeSweep(target, tickets, contract) {
|
|
18570
18803
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
18571
18804
|
const verified = contract.statuses.verified;
|
|
@@ -18606,7 +18839,7 @@ function ticketForWorktree(w, byNumber, contract) {
|
|
|
18606
18839
|
const t = num ? byNumber.get(num) : void 0;
|
|
18607
18840
|
if (t && ticketStems(t).has(stem)) return t;
|
|
18608
18841
|
}
|
|
18609
|
-
const dir =
|
|
18842
|
+
const dir = basename12(w.path);
|
|
18610
18843
|
const m = DIR_NUMBER.exec(dir);
|
|
18611
18844
|
if (m && byNumber.has(m[1])) return byNumber.get(m[1]);
|
|
18612
18845
|
if (!w.branch) {
|
|
@@ -18622,7 +18855,7 @@ function worktreeTicketNumbers(target) {
|
|
|
18622
18855
|
const n = /-(\d+)$/.exec(BRANCH_STEM.exec(w.branch)?.[1] ?? "")?.[1];
|
|
18623
18856
|
if (n) nums.add(n);
|
|
18624
18857
|
}
|
|
18625
|
-
const m = DIR_NUMBER_SUFFIXED.exec(
|
|
18858
|
+
const m = DIR_NUMBER_SUFFIXED.exec(basename12(w.path));
|
|
18626
18859
|
if (m) nums.add(m[1]);
|
|
18627
18860
|
}
|
|
18628
18861
|
for (const d of siblingWorktreeDirs(target)) nums.add(d.number);
|
|
@@ -18630,7 +18863,7 @@ function worktreeTicketNumbers(target) {
|
|
|
18630
18863
|
}
|
|
18631
18864
|
function siblingWorktreeDirs(target) {
|
|
18632
18865
|
const parent = dirname21(target.dir.replace(/[/\\]+$/, ""));
|
|
18633
|
-
const stem = `${
|
|
18866
|
+
const stem = `${basename12(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
18634
18867
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
18635
18868
|
let names;
|
|
18636
18869
|
try {
|
|
@@ -18642,7 +18875,7 @@ function siblingWorktreeDirs(target) {
|
|
|
18642
18875
|
for (const name of names) {
|
|
18643
18876
|
if (!name.startsWith(stem)) continue;
|
|
18644
18877
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
18645
|
-
const path =
|
|
18878
|
+
const path = join38(parent, name);
|
|
18646
18879
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
18647
18880
|
}
|
|
18648
18881
|
return out;
|
|
@@ -18655,7 +18888,7 @@ function orphanedWorktreeDirs(target, tickets, contract) {
|
|
|
18655
18888
|
}
|
|
18656
18889
|
function warnOrphanedWorktreeDirs(paths, log, held) {
|
|
18657
18890
|
for (const p of paths) {
|
|
18658
|
-
const number = DIR_NUMBER_SUFFIXED.exec(
|
|
18891
|
+
const number = DIR_NUMBER_SUFFIXED.exec(basename12(p))?.[1];
|
|
18659
18892
|
const when = held && number ? noteHeldWorktree(held.state, { repo: held.repo, path: p, ticket: number, status: "deployed", reason: "orphaned directory" }) : "first";
|
|
18660
18893
|
if (when) {
|
|
18661
18894
|
log.warn(`orphaned directory ${p}: its ticket is deployed but it is not a registered worktree; the sweep will not delete it \u2014 remove it by hand`, { step: "worktree" });
|
|
@@ -18799,7 +19032,7 @@ function isTicketBranch(name, base, stems) {
|
|
|
18799
19032
|
}
|
|
18800
19033
|
function sweepCandidatesExist(cwd, base) {
|
|
18801
19034
|
const linked = worktrees(cwd).slice(1);
|
|
18802
|
-
if (linked.some((w) => DIR_NUMBER_SUFFIXED.test(
|
|
19035
|
+
if (linked.some((w) => DIR_NUMBER_SUFFIXED.test(basename12(w.path)) || w.branch && BRANCH_STEM.test(w.branch))) return true;
|
|
18803
19036
|
if (siblingWorktreeDirs({ name: "", dir: cwd }).length) return true;
|
|
18804
19037
|
return localBranches(cwd).some((b) => b !== base && /-\d+(?:-.*)?$/.test(b));
|
|
18805
19038
|
}
|
|
@@ -18850,14 +19083,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
18850
19083
|
}
|
|
18851
19084
|
|
|
18852
19085
|
// src/stream-sweep.ts
|
|
18853
|
-
import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as
|
|
18854
|
-
import { join as
|
|
19086
|
+
import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as rmSync17, statSync as statSync10 } from "fs";
|
|
19087
|
+
import { join as join39 } from "path";
|
|
18855
19088
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
18856
19089
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
18857
|
-
const dir =
|
|
19090
|
+
const dir = join39(stateDir, "streams");
|
|
18858
19091
|
if (!existsSync38(dir)) return [];
|
|
18859
19092
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
18860
|
-
return readdirSync15(dir).map((name) =>
|
|
19093
|
+
return readdirSync15(dir).map((name) => join39(dir, name)).filter((path) => {
|
|
18861
19094
|
try {
|
|
18862
19095
|
return statSync10(path).mtimeMs < cutoff;
|
|
18863
19096
|
} catch {
|
|
@@ -18873,7 +19106,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
18873
19106
|
continue;
|
|
18874
19107
|
}
|
|
18875
19108
|
try {
|
|
18876
|
-
|
|
19109
|
+
rmSync17(path, { force: true });
|
|
18877
19110
|
removed++;
|
|
18878
19111
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
18879
19112
|
} catch (e) {
|
|
@@ -18885,7 +19118,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
18885
19118
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
18886
19119
|
if (!existsSync38(stateDir)) return [];
|
|
18887
19120
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
18888
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
19121
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join39(stateDir, name)).filter((path) => {
|
|
18889
19122
|
try {
|
|
18890
19123
|
return statSync10(path).mtimeMs < cutoff;
|
|
18891
19124
|
} catch {
|
|
@@ -18895,12 +19128,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
18895
19128
|
}
|
|
18896
19129
|
|
|
18897
19130
|
// src/cli.ts
|
|
18898
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync39, existsSync as existsSync45, mkdirSync as mkdirSync29, writeFileSync as
|
|
19131
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync39, existsSync as existsSync45, mkdirSync as mkdirSync29, writeFileSync as writeFileSync32, rmSync as rmSync22, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync14, readdirSync as readdirSync20 } from "fs";
|
|
18899
19132
|
import { createInterface } from "readline/promises";
|
|
18900
19133
|
|
|
18901
19134
|
// src/periscope-cmd.ts
|
|
18902
|
-
import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync34, rmSync as
|
|
18903
|
-
import { join as
|
|
19135
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync34, rmSync as rmSync18, writeFileSync as writeFileSync28 } from "fs";
|
|
19136
|
+
import { join as join40 } from "path";
|
|
18904
19137
|
var PeriscopeError = class extends Error {
|
|
18905
19138
|
};
|
|
18906
19139
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -18910,15 +19143,15 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
18910
19143
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
18911
19144
|
}
|
|
18912
19145
|
};
|
|
18913
|
-
var priorDir = (stateDir) =>
|
|
18914
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
19146
|
+
var priorDir = (stateDir) => join40(stateDir, "periscope-priors");
|
|
19147
|
+
var priorPath = (stateDir, ticketRecordId) => join40(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
18915
19148
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
18916
|
-
|
|
19149
|
+
rmSync18(priorPath(stateDir, ticketRecordId), { force: true });
|
|
18917
19150
|
}
|
|
18918
19151
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
18919
19152
|
try {
|
|
18920
19153
|
mkdirSync24(priorDir(stateDir), { recursive: true });
|
|
18921
|
-
|
|
19154
|
+
writeFileSync28(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
18922
19155
|
} catch {
|
|
18923
19156
|
}
|
|
18924
19157
|
}
|
|
@@ -19322,14 +19555,14 @@ ${tail2(preview.output)}`);
|
|
|
19322
19555
|
}
|
|
19323
19556
|
|
|
19324
19557
|
// src/periscope-idle.ts
|
|
19325
|
-
import { execFileSync as
|
|
19558
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
19326
19559
|
import { statSync as statSync11 } from "fs";
|
|
19327
|
-
import { join as
|
|
19560
|
+
import { join as join41 } from "path";
|
|
19328
19561
|
var MAX_SCANNED_FILES = 2e4;
|
|
19329
19562
|
function newestWorktreeMtime(root) {
|
|
19330
19563
|
let listing;
|
|
19331
19564
|
try {
|
|
19332
|
-
listing =
|
|
19565
|
+
listing = execFileSync9("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19333
19566
|
cwd: root,
|
|
19334
19567
|
encoding: "utf8",
|
|
19335
19568
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -19344,7 +19577,7 @@ function newestWorktreeMtime(root) {
|
|
|
19344
19577
|
if (!rel) continue;
|
|
19345
19578
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
19346
19579
|
try {
|
|
19347
|
-
const m = statSync11(
|
|
19580
|
+
const m = statSync11(join41(root, rel)).mtimeMs;
|
|
19348
19581
|
if (newest === null || m > newest) newest = m;
|
|
19349
19582
|
} catch {
|
|
19350
19583
|
}
|
|
@@ -19419,13 +19652,13 @@ async function runPeriscopeIdlePass(d) {
|
|
|
19419
19652
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
19420
19653
|
|
|
19421
19654
|
// src/ssh-keys.ts
|
|
19422
|
-
import { execFileSync as
|
|
19655
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
19423
19656
|
import { existsSync as existsSync40, mkdirSync as mkdirSync25, readFileSync as readFileSync35, chmodSync as chmodSync3 } from "fs";
|
|
19424
|
-
import { dirname as dirname22, join as
|
|
19657
|
+
import { dirname as dirname22, join as join42 } from "path";
|
|
19425
19658
|
var SshKeyError = class extends Error {
|
|
19426
19659
|
};
|
|
19427
19660
|
function shipSshKeyPathFor(stateDir) {
|
|
19428
|
-
return
|
|
19661
|
+
return join42(stateDir, "ssh", "id_ed25519");
|
|
19429
19662
|
}
|
|
19430
19663
|
function ensureShipSshKeypair(stateDir) {
|
|
19431
19664
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
@@ -19433,7 +19666,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
19433
19666
|
if (!existsSync40(privateKeyPath)) {
|
|
19434
19667
|
mkdirSync25(dirname22(privateKeyPath), { recursive: true });
|
|
19435
19668
|
try {
|
|
19436
|
-
|
|
19669
|
+
execFileSync10("ssh-keygen", [
|
|
19437
19670
|
"-t",
|
|
19438
19671
|
"ed25519",
|
|
19439
19672
|
"-N",
|
|
@@ -19459,7 +19692,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
19459
19692
|
}
|
|
19460
19693
|
function sshKeygenAvailable() {
|
|
19461
19694
|
try {
|
|
19462
|
-
|
|
19695
|
+
execFileSync10("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
19463
19696
|
return true;
|
|
19464
19697
|
} catch (e) {
|
|
19465
19698
|
return e.code !== "ENOENT";
|
|
@@ -19616,10 +19849,10 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
19616
19849
|
|
|
19617
19850
|
// src/passenger-sync-daemon.ts
|
|
19618
19851
|
import { createServer as createHttpServer } from "http";
|
|
19619
|
-
import { spawn as
|
|
19852
|
+
import { spawn as spawn7 } from "child_process";
|
|
19620
19853
|
import { randomBytes } from "crypto";
|
|
19621
|
-
import { existsSync as existsSync41, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as
|
|
19622
|
-
import { join as
|
|
19854
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
19855
|
+
import { join as join43 } from "path";
|
|
19623
19856
|
|
|
19624
19857
|
// src/secret-compare.ts
|
|
19625
19858
|
import { timingSafeEqual } from "crypto";
|
|
@@ -19703,12 +19936,12 @@ function runSyncDaemonFromEnv(env = process.env) {
|
|
|
19703
19936
|
return server;
|
|
19704
19937
|
}
|
|
19705
19938
|
var defaultSpawn2 = (cmd, args, env) => {
|
|
19706
|
-
const child =
|
|
19939
|
+
const child = spawn7(cmd, args, { detached: true, stdio: "ignore", env });
|
|
19707
19940
|
child.unref();
|
|
19708
19941
|
return child;
|
|
19709
19942
|
};
|
|
19710
19943
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
19711
|
-
return
|
|
19944
|
+
return join43(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
19712
19945
|
}
|
|
19713
19946
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
19714
19947
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
@@ -19721,8 +19954,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
19721
19954
|
}
|
|
19722
19955
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
19723
19956
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
19724
|
-
mkdirSync26(
|
|
19725
|
-
|
|
19957
|
+
mkdirSync26(join43(stateDir, "passengers"), { recursive: true });
|
|
19958
|
+
writeFileSync29(path, `${JSON.stringify(state, null, 2)}
|
|
19726
19959
|
`);
|
|
19727
19960
|
}
|
|
19728
19961
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -19742,7 +19975,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
19742
19975
|
}
|
|
19743
19976
|
}
|
|
19744
19977
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
19745
|
-
const dir =
|
|
19978
|
+
const dir = join43(stateDir, "passengers");
|
|
19746
19979
|
if (!existsSync41(dir)) return [];
|
|
19747
19980
|
const suffix = "-sync.json";
|
|
19748
19981
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -19831,8 +20064,8 @@ function describeCause(err3) {
|
|
|
19831
20064
|
}
|
|
19832
20065
|
|
|
19833
20066
|
// src/daemon.ts
|
|
19834
|
-
import { existsSync as existsSync42, mkdirSync as mkdirSync27, readFileSync as readFileSync37, readdirSync as readdirSync17, rmSync as
|
|
19835
|
-
import { join as
|
|
20067
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync27, readFileSync as readFileSync37, readdirSync as readdirSync17, rmSync as rmSync19, statSync as statSync12, writeFileSync as writeFileSync30 } from "fs";
|
|
20068
|
+
import { join as join44 } from "path";
|
|
19836
20069
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
19837
20070
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
19838
20071
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -19920,7 +20153,7 @@ function latestMtime(path) {
|
|
|
19920
20153
|
if (!stat2.isDirectory()) return 0;
|
|
19921
20154
|
let latest = stat2.mtimeMs;
|
|
19922
20155
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
19923
|
-
latest = Math.max(latest, latestMtime(
|
|
20156
|
+
latest = Math.max(latest, latestMtime(join44(path, entry.name)));
|
|
19924
20157
|
}
|
|
19925
20158
|
return latest;
|
|
19926
20159
|
}
|
|
@@ -19932,7 +20165,7 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
19932
20165
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync42(opts.reloadSentinel);
|
|
19933
20166
|
}
|
|
19934
20167
|
function reloadSentinelFor(stateDir) {
|
|
19935
|
-
return
|
|
20168
|
+
return join44(stateDir, "reload-requested");
|
|
19936
20169
|
}
|
|
19937
20170
|
function readReloadRequestedAt(stateDir) {
|
|
19938
20171
|
try {
|
|
@@ -19942,12 +20175,12 @@ function readReloadRequestedAt(stateDir) {
|
|
|
19942
20175
|
}
|
|
19943
20176
|
}
|
|
19944
20177
|
function daemonStampFor(stateDir) {
|
|
19945
|
-
return
|
|
20178
|
+
return join44(stateDir, "daemon.started");
|
|
19946
20179
|
}
|
|
19947
20180
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
19948
20181
|
mkdirSync27(stateDir, { recursive: true });
|
|
19949
|
-
|
|
19950
|
-
|
|
20182
|
+
rmSync19(reloadSentinelFor(stateDir), { force: true });
|
|
20183
|
+
writeFileSync30(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
19951
20184
|
`);
|
|
19952
20185
|
}
|
|
19953
20186
|
function readDaemonStarted(stateDir) {
|
|
@@ -20017,7 +20250,7 @@ async function runOnePassUnguarded(o) {
|
|
|
20017
20250
|
await hydrateApiKeys([o.route], o.getSessionStore);
|
|
20018
20251
|
const emit = o.newEmitter();
|
|
20019
20252
|
const decide = o.decide ?? decideCycle;
|
|
20020
|
-
const
|
|
20253
|
+
const spawn10 = o.spawnRoleAgent ?? runRoleAgent;
|
|
20021
20254
|
const breaker = o.spawnBreaker ?? new SpawnBreaker();
|
|
20022
20255
|
const ctx = { route: o.route, ship: o.ship, state: o.state, emit, getSessionStore: o.getSessionStore };
|
|
20023
20256
|
if (o.configFile) await reconcileRouteShipName({ route: o.route, ship: o.ship, configFile: o.configFile, emit });
|
|
@@ -20121,7 +20354,7 @@ async function runOnePassUnguarded(o) {
|
|
|
20121
20354
|
settle = resolve7;
|
|
20122
20355
|
});
|
|
20123
20356
|
const ready = (tookTicket2) => settle(tookTicket2);
|
|
20124
|
-
const run2 =
|
|
20357
|
+
const run2 = spawn10(role, decision, ctx, { ready }).then(() => {
|
|
20125
20358
|
breaker.recordSuccess(role);
|
|
20126
20359
|
clearShipAttention(o.state, `role_parked:${role}`);
|
|
20127
20360
|
clearShipAttention(o.state, workspaceAuthKey(role));
|
|
@@ -20516,17 +20749,17 @@ import {
|
|
|
20516
20749
|
openSync as openSync5,
|
|
20517
20750
|
readSync as readSync3,
|
|
20518
20751
|
readdirSync as readdirSync18,
|
|
20519
|
-
rmSync as
|
|
20752
|
+
rmSync as rmSync20,
|
|
20520
20753
|
statSync as statSync13,
|
|
20521
20754
|
watch
|
|
20522
20755
|
} from "fs";
|
|
20523
20756
|
import { createConnection } from "net";
|
|
20524
20757
|
import { createServer } from "http";
|
|
20525
|
-
import { basename as
|
|
20526
|
-
import { execFile, spawn as
|
|
20758
|
+
import { basename as basename13, dirname as dirname23, join as join45 } from "path";
|
|
20759
|
+
import { execFile as execFile2, spawn as spawn9 } from "child_process";
|
|
20527
20760
|
|
|
20528
20761
|
// src/periscope-control.ts
|
|
20529
|
-
import { spawn as
|
|
20762
|
+
import { spawn as spawn8 } from "child_process";
|
|
20530
20763
|
var reply = (status2, body) => ({ status: status2, body });
|
|
20531
20764
|
var err = (status2, message) => reply(status2, { error: message });
|
|
20532
20765
|
var TICKET_RE = /^(?:[A-Za-z]+-)?\d+$/;
|
|
@@ -20717,7 +20950,7 @@ async function handlePeriscopes(h, q) {
|
|
|
20717
20950
|
}
|
|
20718
20951
|
function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
20719
20952
|
return new Promise((resolve7) => {
|
|
20720
|
-
const child =
|
|
20953
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
20721
20954
|
let stdout = "";
|
|
20722
20955
|
let stderr = "";
|
|
20723
20956
|
let pending = "";
|
|
@@ -20751,7 +20984,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
20751
20984
|
// src/control-api.ts
|
|
20752
20985
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
20753
20986
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
20754
|
-
var controlSocketFor = (stateDir) =>
|
|
20987
|
+
var controlSocketFor = (stateDir) => join45(stateDir, CONTROL_SOCKET_NAME);
|
|
20755
20988
|
var MAX_SOCKET_PATH = 100;
|
|
20756
20989
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
20757
20990
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -20838,7 +21071,7 @@ function candidates(live, rotated) {
|
|
|
20838
21071
|
const out = [live];
|
|
20839
21072
|
if (!rotated) return out;
|
|
20840
21073
|
try {
|
|
20841
|
-
for (const n of readdirSync18(dirname23(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(
|
|
21074
|
+
for (const n of readdirSync18(dirname23(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join45(dirname23(live), n));
|
|
20842
21075
|
} catch {
|
|
20843
21076
|
}
|
|
20844
21077
|
return out;
|
|
@@ -20961,7 +21194,7 @@ function pauseDoc(state) {
|
|
|
20961
21194
|
}
|
|
20962
21195
|
function describeStream(r) {
|
|
20963
21196
|
return {
|
|
20964
|
-
run:
|
|
21197
|
+
run: basename13(r.streamPath).replace(/\.jsonl$/, ""),
|
|
20965
21198
|
pid: r.pid,
|
|
20966
21199
|
ticket: r.ticket,
|
|
20967
21200
|
role: r.role,
|
|
@@ -21038,7 +21271,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
21038
21271
|
if (p.startsWith("/streams/") && get2) {
|
|
21039
21272
|
const run2 = decodeURIComponent(p.slice("/streams/".length));
|
|
21040
21273
|
if (!RUN_NAME_RE.test(run2) || run2.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run2)) return err2(400, "not a run name");
|
|
21041
|
-
const file =
|
|
21274
|
+
const file = join45(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
|
|
21042
21275
|
if (!existsSync43(file)) return err2(404, "no such run");
|
|
21043
21276
|
return tailReply(file, url.searchParams, false, abort);
|
|
21044
21277
|
}
|
|
@@ -21101,7 +21334,7 @@ async function startControlServer(deps) {
|
|
|
21101
21334
|
log(`control API not started: socket path is too long for a unix socket (${path})`);
|
|
21102
21335
|
return null;
|
|
21103
21336
|
}
|
|
21104
|
-
if (await socketIsStale(path))
|
|
21337
|
+
if (await socketIsStale(path)) rmSync20(path, { force: true });
|
|
21105
21338
|
else if (existsSync43(path)) {
|
|
21106
21339
|
log(`control API not started: ${path} is in use by another process`);
|
|
21107
21340
|
return null;
|
|
@@ -21148,7 +21381,7 @@ async function startControlServer(deps) {
|
|
|
21148
21381
|
path,
|
|
21149
21382
|
close: () => new Promise((done) => {
|
|
21150
21383
|
server.close(() => {
|
|
21151
|
-
|
|
21384
|
+
rmSync20(path, { force: true });
|
|
21152
21385
|
done();
|
|
21153
21386
|
});
|
|
21154
21387
|
})
|
|
@@ -21157,7 +21390,7 @@ async function startControlServer(deps) {
|
|
|
21157
21390
|
});
|
|
21158
21391
|
}
|
|
21159
21392
|
var shq2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
21160
|
-
function runDetached(commands, spawnFn =
|
|
21393
|
+
function runDetached(commands, spawnFn = spawn9) {
|
|
21161
21394
|
const script = `sleep 1; ${commands.map((c) => c.map(shq2).join(" ")).join("; ")}`;
|
|
21162
21395
|
const child = spawnFn("/bin/sh", ["-c", script], { detached: true, stdio: "ignore" });
|
|
21163
21396
|
child.on("error", () => {
|
|
@@ -21175,7 +21408,7 @@ function schedulerHandler(control, run2 = runDetached) {
|
|
|
21175
21408
|
}
|
|
21176
21409
|
function crewJson(args, timeoutMs = 6e4) {
|
|
21177
21410
|
return new Promise((resolve7) => {
|
|
21178
|
-
|
|
21411
|
+
execFile2(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
21179
21412
|
try {
|
|
21180
21413
|
const v = JSON.parse(String(stdout));
|
|
21181
21414
|
resolve7(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
@@ -21188,24 +21421,24 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
21188
21421
|
|
|
21189
21422
|
// src/periscope-mcp.ts
|
|
21190
21423
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
21191
|
-
import { basename as
|
|
21424
|
+
import { basename as basename14, dirname as dirname24, isAbsolute as isAbsolute3, join as join47, relative as relative2, resolve as resolve5, sep } from "path";
|
|
21192
21425
|
import { z } from "zod";
|
|
21193
21426
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21194
21427
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
21195
21428
|
|
|
21196
21429
|
// src/periscope-endpoints.ts
|
|
21197
21430
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
21198
|
-
import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync28, readdirSync as readdirSync19, readFileSync as readFileSync38, rmSync as
|
|
21199
|
-
import { join as
|
|
21200
|
-
var secretDir = (stateDir) =>
|
|
21201
|
-
var secretFile = (stateDir, id) =>
|
|
21202
|
-
var endpointDir = (stateDir) =>
|
|
21203
|
-
var endpointFile = (stateDir, id) =>
|
|
21431
|
+
import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync28, readdirSync as readdirSync19, readFileSync as readFileSync38, rmSync as rmSync21, writeFileSync as writeFileSync31 } from "fs";
|
|
21432
|
+
import { join as join46 } from "path";
|
|
21433
|
+
var secretDir = (stateDir) => join46(stateDir, "periscope-mcp");
|
|
21434
|
+
var secretFile = (stateDir, id) => join46(secretDir(stateDir), `${id}.json`);
|
|
21435
|
+
var endpointDir = (stateDir) => join46(stateDir, "periscope-endpoints");
|
|
21436
|
+
var endpointFile = (stateDir, id) => join46(endpointDir(stateDir), `${id}.json`);
|
|
21204
21437
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
21205
21438
|
mkdirSync28(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
21206
21439
|
const secret = randomBytes3(32).toString("base64url");
|
|
21207
21440
|
const file = secretFile(stateDir, ticketRecordId);
|
|
21208
|
-
|
|
21441
|
+
writeFileSync31(file, `${JSON.stringify({ secret })}
|
|
21209
21442
|
`, { mode: 384 });
|
|
21210
21443
|
chmodSync5(file, 384);
|
|
21211
21444
|
return secret;
|
|
@@ -21219,7 +21452,7 @@ function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
21219
21452
|
}
|
|
21220
21453
|
}
|
|
21221
21454
|
function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
21222
|
-
|
|
21455
|
+
rmSync21(secretFile(stateDir, ticketRecordId), { force: true });
|
|
21223
21456
|
}
|
|
21224
21457
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
21225
21458
|
try {
|
|
@@ -21230,11 +21463,11 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
21230
21463
|
}
|
|
21231
21464
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
21232
21465
|
mkdirSync28(endpointDir(stateDir), { recursive: true });
|
|
21233
|
-
|
|
21466
|
+
writeFileSync31(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
21234
21467
|
`);
|
|
21235
21468
|
}
|
|
21236
21469
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
21237
|
-
|
|
21470
|
+
rmSync21(endpointFile(stateDir, ticketRecordId), { force: true });
|
|
21238
21471
|
}
|
|
21239
21472
|
function listPeriscopeEndpoints(stateDir) {
|
|
21240
21473
|
const dir = endpointDir(stateDir);
|
|
@@ -21331,7 +21564,7 @@ async function realAncestor(p) {
|
|
|
21331
21564
|
}
|
|
21332
21565
|
const parent = dirname24(cur);
|
|
21333
21566
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
21334
|
-
tail3.push(
|
|
21567
|
+
tail3.push(basename14(cur));
|
|
21335
21568
|
cur = parent;
|
|
21336
21569
|
}
|
|
21337
21570
|
}
|
|
@@ -21341,13 +21574,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
21341
21574
|
let abs;
|
|
21342
21575
|
if (isAbsolute3(requested)) {
|
|
21343
21576
|
abs = resolve5(requested);
|
|
21344
|
-
if (isInside(root, abs)) abs =
|
|
21577
|
+
if (isInside(root, abs)) abs = join47(realRoot, relative2(root, abs));
|
|
21345
21578
|
} else {
|
|
21346
21579
|
abs = resolve5(realRoot, requested);
|
|
21347
21580
|
}
|
|
21348
21581
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
21349
21582
|
const { real, rest } = await realAncestor(abs);
|
|
21350
|
-
const final = rest ?
|
|
21583
|
+
const final = rest ? join47(real, rest) : real;
|
|
21351
21584
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
21352
21585
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
21353
21586
|
return final;
|
|
@@ -21415,7 +21648,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
21415
21648
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
21416
21649
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
21417
21650
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
21418
|
-
const size = (await lstat(
|
|
21651
|
+
const size = (await lstat(join47(abs, e.name))).size;
|
|
21419
21652
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
21420
21653
|
}));
|
|
21421
21654
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -21481,8 +21714,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
21481
21714
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
21482
21715
|
}, async ({ path }) => {
|
|
21483
21716
|
const parent = await inTree(dirname24(path) === "" ? "." : dirname24(path));
|
|
21484
|
-
const abs =
|
|
21485
|
-
if (
|
|
21717
|
+
const abs = join47(parent, basename14(path));
|
|
21718
|
+
if (basename14(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
21486
21719
|
const st = await lstat(abs).catch(() => void 0);
|
|
21487
21720
|
if (!st) return text(`${path} does not exist`, true);
|
|
21488
21721
|
if (st.isDirectory()) return text(`${path} is a directory; only files are deleted`, true);
|
|
@@ -22181,7 +22414,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22181
22414
|
stateDir: cfg.ship.stateDir,
|
|
22182
22415
|
configFile: cfg.configFile,
|
|
22183
22416
|
readFile: (p) => readFileSync39(p, "utf8"),
|
|
22184
|
-
writeFile: (p, t) =>
|
|
22417
|
+
writeFile: (p, t) => writeFileSync32(p, t),
|
|
22185
22418
|
stdin: () => readFileSync39(0, "utf8"),
|
|
22186
22419
|
env: process.env
|
|
22187
22420
|
}
|
|
@@ -22465,7 +22698,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22465
22698
|
}
|
|
22466
22699
|
if (host === "linux" && hasSystemd) {
|
|
22467
22700
|
const label = labelFor(CREW_HOME, "run");
|
|
22468
|
-
const staleTimer =
|
|
22701
|
+
const staleTimer = join48(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
22469
22702
|
if (existsSync45(staleTimer)) {
|
|
22470
22703
|
process.stdout.write(
|
|
22471
22704
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -22528,7 +22761,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22528
22761
|
for (const a of plan.actions) {
|
|
22529
22762
|
if (a.kind === "enable") {
|
|
22530
22763
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
22531
|
-
|
|
22764
|
+
writeFileSync32(cfg.configFile, planEnable(readFileSync39(cfg.configFile, "utf8"), a.route));
|
|
22532
22765
|
enabledSound.add(a.route);
|
|
22533
22766
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
22534
22767
|
} else {
|
|
@@ -23651,7 +23884,7 @@ ${cu.tail.trimEnd()}
|
|
|
23651
23884
|
process.exit(1);
|
|
23652
23885
|
}
|
|
23653
23886
|
requireArmed("run the daemon loop");
|
|
23654
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
23887
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join48(CREW_HOME, "src"), join48(CREW_HOME, "dist"), join48(CREW_HOME, "bin")];
|
|
23655
23888
|
const configPaths = daemonConfigPaths(cfg);
|
|
23656
23889
|
watchPaths.push(...configPaths);
|
|
23657
23890
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -23950,7 +24183,7 @@ ${cu.tail.trimEnd()}
|
|
|
23950
24183
|
const raw = existsSync45(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
|
|
23951
24184
|
raw.agentSkills = result.agentSkills;
|
|
23952
24185
|
mkdirSync29(dirOf2(resolvedPath), { recursive: true });
|
|
23953
|
-
|
|
24186
|
+
writeFileSync32(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
23954
24187
|
`);
|
|
23955
24188
|
}
|
|
23956
24189
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -23973,7 +24206,7 @@ ${cu.tail.trimEnd()}
|
|
|
23973
24206
|
throw e;
|
|
23974
24207
|
}
|
|
23975
24208
|
const oldName = cfg.ship.name;
|
|
23976
|
-
|
|
24209
|
+
writeFileSync32(cfg.configFile, setShipNameInYaml(readFileSync39(cfg.configFile, "utf8"), newName));
|
|
23977
24210
|
cfg.ship.name = newName;
|
|
23978
24211
|
let renameMachineId;
|
|
23979
24212
|
try {
|
|
@@ -24102,19 +24335,19 @@ ${cu.tail.trimEnd()}
|
|
|
24102
24335
|
let cleared;
|
|
24103
24336
|
if (clear2) {
|
|
24104
24337
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
24105
|
-
if (plan.removed.length > 0)
|
|
24338
|
+
if (plan.removed.length > 0) writeFileSync32(cfg.configFile, plan.text);
|
|
24106
24339
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
24107
24340
|
}
|
|
24108
24341
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
24109
24342
|
if (write === "route") {
|
|
24110
24343
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
24111
|
-
|
|
24344
|
+
writeFileSync32(cfg.configFile, plan.text);
|
|
24112
24345
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
24113
24346
|
} else if (write === "repo") {
|
|
24114
24347
|
const have = existingRepoConfig(files);
|
|
24115
24348
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
24116
24349
|
const file = resolvePath(dir, ".crew.yaml");
|
|
24117
|
-
|
|
24350
|
+
writeFileSync32(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
24118
24351
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
24119
24352
|
}
|
|
24120
24353
|
if (flag("json")) {
|
|
@@ -24281,7 +24514,7 @@ ${cu.tail.trimEnd()}
|
|
|
24281
24514
|
throw new ReposError(`${e.message}${authHint}
|
|
24282
24515
|
crew.yaml was not changed`);
|
|
24283
24516
|
}
|
|
24284
|
-
|
|
24517
|
+
writeFileSync32(cfg.configFile, plan2.text);
|
|
24285
24518
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
24286
24519
|
`);
|
|
24287
24520
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
@@ -24344,11 +24577,11 @@ ${cu.tail.trimEnd()}
|
|
|
24344
24577
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
24345
24578
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
24346
24579
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
24347
|
-
|
|
24580
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
24348
24581
|
`);
|
|
24349
24582
|
}
|
|
24350
24583
|
}
|
|
24351
|
-
|
|
24584
|
+
writeFileSync32(cfg.configFile, plan.text);
|
|
24352
24585
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
24353
24586
|
`);
|
|
24354
24587
|
} catch (e) {
|
|
@@ -24717,7 +24950,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24717
24950
|
process.exit(2);
|
|
24718
24951
|
}
|
|
24719
24952
|
if (found.shipNameAdopted && !dryRun) {
|
|
24720
|
-
|
|
24953
|
+
writeFileSync32(cfg.configFile, setShipNameInYaml(readFileSync39(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
24721
24954
|
cfg.ship.name = found.shipNameAdopted;
|
|
24722
24955
|
}
|
|
24723
24956
|
if (found.shipRow) {
|
|
@@ -24896,7 +25129,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24896
25129
|
mkdirSync29(dirname25(resolvedPath), { recursive: true });
|
|
24897
25130
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
24898
25131
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
24899
|
-
|
|
25132
|
+
writeFileSync32(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
24900
25133
|
`);
|
|
24901
25134
|
renameSync5(tmpPath, resolvedPath);
|
|
24902
25135
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -24943,7 +25176,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24943
25176
|
`);
|
|
24944
25177
|
} else {
|
|
24945
25178
|
mkdirSync29(dirname25(apiKeyFile), { recursive: true });
|
|
24946
|
-
|
|
25179
|
+
writeFileSync32(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
24947
25180
|
`, { mode: 384 });
|
|
24948
25181
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
24949
25182
|
`);
|
|
@@ -25773,10 +26006,10 @@ ${where.join("\n")}`);
|
|
|
25773
26006
|
},
|
|
25774
26007
|
prepare: async (path) => {
|
|
25775
26008
|
for (const f of repoP.config.worktrees.copy) {
|
|
25776
|
-
const src =
|
|
26009
|
+
const src = join48(repoP.dir, f);
|
|
25777
26010
|
if (!existsSync45(src)) continue;
|
|
25778
|
-
mkdirSync29(dirOf2(
|
|
25779
|
-
copyFileSync2(src,
|
|
26011
|
+
mkdirSync29(dirOf2(join48(path, f)), { recursive: true });
|
|
26012
|
+
copyFileSync2(src, join48(path, f));
|
|
25780
26013
|
}
|
|
25781
26014
|
const setup = repoP.config.hooks.setup;
|
|
25782
26015
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -26125,7 +26358,7 @@ ${where.join("\n")}`);
|
|
|
26125
26358
|
break;
|
|
26126
26359
|
}
|
|
26127
26360
|
mkdirSync29(cfg.ship.stateDir, { recursive: true });
|
|
26128
|
-
|
|
26361
|
+
writeFileSync32(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
26129
26362
|
`);
|
|
26130
26363
|
process.stdout.write(
|
|
26131
26364
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -26230,7 +26463,7 @@ ${where.join("\n")}`);
|
|
|
26230
26463
|
}
|
|
26231
26464
|
if (configured2.some((r) => r.dir === dir)) {
|
|
26232
26465
|
try {
|
|
26233
|
-
|
|
26466
|
+
writeFileSync32(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
26234
26467
|
} catch (e) {
|
|
26235
26468
|
if (e instanceof ReposError) fail(e.message);
|
|
26236
26469
|
throw e;
|
|
@@ -26242,16 +26475,16 @@ ${where.join("\n")}`);
|
|
|
26242
26475
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
26243
26476
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
26244
26477
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
26245
|
-
|
|
26478
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26246
26479
|
`);
|
|
26247
26480
|
}
|
|
26248
26481
|
}
|
|
26249
|
-
if (existsSync45(
|
|
26482
|
+
if (existsSync45(join48(dir, ".git")) && basename15(dir) === SAMPLE_REPO) rmSync22(dir, { recursive: true, force: true });
|
|
26250
26483
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
26251
26484
|
break;
|
|
26252
26485
|
}
|
|
26253
26486
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
26254
|
-
const fresh = !existsSync45(
|
|
26487
|
+
const fresh = !existsSync45(join48(dir, ".git"));
|
|
26255
26488
|
if (existsSync45(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
26256
26489
|
if (dryRun) {
|
|
26257
26490
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
@@ -26259,8 +26492,8 @@ ${where.join("\n")}`);
|
|
|
26259
26492
|
}
|
|
26260
26493
|
if (fresh) {
|
|
26261
26494
|
mkdirSync29(dir, { recursive: true });
|
|
26262
|
-
|
|
26263
|
-
|
|
26495
|
+
writeFileSync32(join48(dir, "README.md"), SAMPLE_README);
|
|
26496
|
+
writeFileSync32(join48(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
26264
26497
|
const g = (...a) => {
|
|
26265
26498
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
26266
26499
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -26282,13 +26515,13 @@ ${where.join("\n")}`);
|
|
|
26282
26515
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
26283
26516
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
26284
26517
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
26285
|
-
|
|
26518
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26286
26519
|
`);
|
|
26287
26520
|
}
|
|
26288
26521
|
}
|
|
26289
26522
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
26290
26523
|
try {
|
|
26291
|
-
|
|
26524
|
+
writeFileSync32(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
26292
26525
|
} catch (e) {
|
|
26293
26526
|
if (e instanceof ReposError) fail(e.message);
|
|
26294
26527
|
throw e;
|
|
@@ -26523,7 +26756,7 @@ ${where.join("\n")}`);
|
|
|
26523
26756
|
// `synthesis-tabl-123`. `worktreePrefix` above is kept for
|
|
26524
26757
|
// config-provenance display; this is the field that actually
|
|
26525
26758
|
// describes where a worktree lands by default.
|
|
26526
|
-
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${
|
|
26759
|
+
worktreeDirPattern: r.config.worktrees.name ?? (r.config.provenance["worktrees.prefix"] !== "default" ? `${r.config.worktrees.prefix}<number>` : `${basename15(r.dir.replace(/[/\\]+$/, ""))}-<branch>`),
|
|
26527
26760
|
worktreeParent: resolvePath(r.dir, "..")
|
|
26528
26761
|
})),
|
|
26529
26762
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
@@ -26781,7 +27014,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
26781
27014
|
);
|
|
26782
27015
|
for (const r of await resolvedRepos(route2)) {
|
|
26783
27016
|
const { config } = r;
|
|
26784
|
-
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${
|
|
27017
|
+
const worktreePattern = config.worktrees.name ?? (config.provenance["worktrees.prefix"] !== "default" ? `${config.worktrees.prefix}<number>` : `${basename15(r.dir.replace(/[/\\]+$/, ""))}-${effectiveBranchTemplate(config, void 0)}`);
|
|
26785
27018
|
process.stdout.write(
|
|
26786
27019
|
`${`repo ${r.name}:`.padEnd(19)}${r.dir}
|
|
26787
27020
|
worktrees at ../${worktreePattern} (${config.provenance["worktrees.name"] ?? config.provenance["worktrees.prefix"] ?? "default"}), branch ${config.branch.name}
|
|
@@ -27103,6 +27336,13 @@ ${foreign.join("\n")}
|
|
|
27103
27336
|
`);
|
|
27104
27337
|
}
|
|
27105
27338
|
}
|
|
27339
|
+
if (cfg.ship.runner === "sandbox") {
|
|
27340
|
+
const sb = checkSandboxRunner(cfg.ship.sandbox);
|
|
27341
|
+
process.stdout.write(`sandbox runner: ${sb.ok ? `OK (${sb.detail})` : `NOT READY \u2014 ${sb.detail}`}
|
|
27342
|
+
`);
|
|
27343
|
+
} else {
|
|
27344
|
+
process.stdout.write("sandbox runner: off (ship.runner is host)\n");
|
|
27345
|
+
}
|
|
27106
27346
|
const passengerRoutes = cfg.routes.filter((r) => r.enabled && r.hostPassengers);
|
|
27107
27347
|
if (passengerRoutes.length === 0) {
|
|
27108
27348
|
process.stdout.write("host passengers: off (no enabled route sets hostPassengers: true)\n");
|