@tablation/crew 0.102.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 +592 -319
- 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 } : {} };
|
|
@@ -12387,7 +12620,17 @@ async function runProvision(o, deps, write, human) {
|
|
|
12387
12620
|
emit("problem", message, data);
|
|
12388
12621
|
return { code, summary: { ...summary, ok: false, state: "failed" } };
|
|
12389
12622
|
};
|
|
12390
|
-
|
|
12623
|
+
const keyFor = (route2) => {
|
|
12624
|
+
const ws = route2.split("/")[0].toLowerCase();
|
|
12625
|
+
const hit = Object.entries(o.keys ?? {}).find(([k, v]) => k.toLowerCase() === ws && v);
|
|
12626
|
+
return hit?.[1] || o.key || void 0;
|
|
12627
|
+
};
|
|
12628
|
+
for (const route2 of o.routes) {
|
|
12629
|
+
if (!keyFor(route2)) {
|
|
12630
|
+
return fail(PROVISION_EXIT.problem, `no connect key for ${route2}: pass --key or set CREW_CONNECT_KEY_${envKey(route2.split("/")[0])} or CREW_CONNECT_KEY`, { stage: "input" });
|
|
12631
|
+
}
|
|
12632
|
+
}
|
|
12633
|
+
if (o.routes.length === 0 && !o.key) return fail(PROVISION_EXIT.problem, "no connect key: pass --key or set CREW_CONNECT_KEY", { stage: "input" });
|
|
12391
12634
|
if (o.routes.length === 0) {
|
|
12392
12635
|
return fail(PROVISION_EXIT.problem, "no routes to serve: pass --route WORKSPACE[/PROJECT] (repeatable) or set CREW_PROVISION_ROUTES", { stage: "input" });
|
|
12393
12636
|
}
|
|
@@ -12413,7 +12656,7 @@ async function runProvision(o, deps, write, human) {
|
|
|
12413
12656
|
...o.dryRun ? ["--dry-run"] : [],
|
|
12414
12657
|
...o.choices.flatMap((c) => ["--choose", c])
|
|
12415
12658
|
];
|
|
12416
|
-
const res = deps.crew(args, { env: { CREW_CONNECT_KEY:
|
|
12659
|
+
const res = deps.crew(args, { env: { CREW_CONNECT_KEY: keyFor(route2) } });
|
|
12417
12660
|
const events = parseEvents(res.stdout);
|
|
12418
12661
|
for (const e of events) if (o.json) write(`${JSON.stringify({ ...e, phase: "connect" })}
|
|
12419
12662
|
`);
|
|
@@ -12503,6 +12746,15 @@ function tokensFromEnv(hosts, env) {
|
|
|
12503
12746
|
}
|
|
12504
12747
|
return out;
|
|
12505
12748
|
}
|
|
12749
|
+
function keysFromEnv(routes, env) {
|
|
12750
|
+
const out = {};
|
|
12751
|
+
for (const r of routes) {
|
|
12752
|
+
const ws = r.split("/")[0];
|
|
12753
|
+
const v = env[`CREW_CONNECT_KEY_${envKey(ws)}`]?.trim();
|
|
12754
|
+
if (v) out[ws] = v;
|
|
12755
|
+
}
|
|
12756
|
+
return out;
|
|
12757
|
+
}
|
|
12506
12758
|
function repeated(argv2, name) {
|
|
12507
12759
|
const out = [];
|
|
12508
12760
|
for (let i = 0; i < argv2.length; i++) {
|
|
@@ -12523,9 +12775,11 @@ function provisionOptionsFrom(argv2, env) {
|
|
|
12523
12775
|
const forgeFlags = repeated(argv2, "forge-host");
|
|
12524
12776
|
const tokenAvailable = !!env.CREW_FORGE_TOKEN?.trim() || Object.keys(env).some((k) => k.startsWith("CREW_FORGE_TOKEN_") && env[k]?.trim());
|
|
12525
12777
|
const forgeHosts = (forgeFlags.length ? forgeFlags : tokenAvailable ? splitList(env.CREW_FORGE_HOSTS ?? "github.com") : []).map((h) => h.toLowerCase());
|
|
12778
|
+
const routeList = routes.length ? routes : splitList(env.CREW_PROVISION_ROUTES);
|
|
12526
12779
|
return {
|
|
12527
12780
|
key: one("key") ?? env.CREW_CONNECT_KEY,
|
|
12528
|
-
|
|
12781
|
+
keys: keysFromEnv(routeList, env),
|
|
12782
|
+
routes: routeList,
|
|
12529
12783
|
shipName: one("ship-name") ?? env.CREW_SHIP_NAME,
|
|
12530
12784
|
shipKind: one("ship-kind"),
|
|
12531
12785
|
instanceClass: one("instance-class"),
|
|
@@ -12546,6 +12800,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12546
12800
|
const [bin, ...pre] = crewInvocation(crewHome);
|
|
12547
12801
|
const env = { ...process.env, ...o.env };
|
|
12548
12802
|
delete env.CREW_FORGE_TOKEN;
|
|
12803
|
+
for (const k of Object.keys(env)) if (k.startsWith("CREW_CONNECT_KEY_")) delete env[k];
|
|
12549
12804
|
const res = spawnSync7(bin, [...pre, ...args], { cwd: crewHome, encoding: "utf8", env, input: o.input, maxBuffer: 64 * 1024 * 1024 });
|
|
12550
12805
|
return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: `${res.stderr ?? ""}${res.error ? String(res.error) : ""}` };
|
|
12551
12806
|
};
|
|
@@ -12565,7 +12820,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12565
12820
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
12566
12821
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
12567
12822
|
if (entry.get("enabled") === true) return false;
|
|
12568
|
-
|
|
12823
|
+
writeFileSync20(file, planEnable(text2, route2));
|
|
12569
12824
|
return true;
|
|
12570
12825
|
},
|
|
12571
12826
|
installState() {
|
|
@@ -12612,7 +12867,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12612
12867
|
const doc = parseDocument3(readFileSync26(file, "utf8"));
|
|
12613
12868
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
12614
12869
|
doc.setIn(["ship", key], value2);
|
|
12615
|
-
|
|
12870
|
+
writeFileSync20(file, doc.toString());
|
|
12616
12871
|
return true;
|
|
12617
12872
|
},
|
|
12618
12873
|
storedToken(host) {
|
|
@@ -12628,12 +12883,12 @@ host=${host}
|
|
|
12628
12883
|
}
|
|
12629
12884
|
|
|
12630
12885
|
// src/tunnel.ts
|
|
12631
|
-
import { spawn as
|
|
12632
|
-
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";
|
|
12633
12888
|
import { randomInt } from "crypto";
|
|
12634
|
-
import { join as
|
|
12889
|
+
import { join as join32 } from "path";
|
|
12635
12890
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
12636
|
-
var defaultSpawn = (cmd, args) =>
|
|
12891
|
+
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
12637
12892
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
12638
12893
|
var SLUG_LENGTH = 12;
|
|
12639
12894
|
function generateTunnelSlug() {
|
|
@@ -12703,7 +12958,7 @@ function startTunnel(opts) {
|
|
|
12703
12958
|
};
|
|
12704
12959
|
}
|
|
12705
12960
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
12706
|
-
return
|
|
12961
|
+
return join32(stateDir, "passengers", `${workspaceId}.json`);
|
|
12707
12962
|
}
|
|
12708
12963
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
12709
12964
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
@@ -12716,8 +12971,8 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
12716
12971
|
}
|
|
12717
12972
|
function writePersistedTunnel(stateDir, state) {
|
|
12718
12973
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
12719
|
-
mkdirSync18(
|
|
12720
|
-
|
|
12974
|
+
mkdirSync18(join32(stateDir, "passengers"), { recursive: true });
|
|
12975
|
+
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
12721
12976
|
`);
|
|
12722
12977
|
}
|
|
12723
12978
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -12819,7 +13074,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
12819
13074
|
}
|
|
12820
13075
|
}
|
|
12821
13076
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
12822
|
-
const dir =
|
|
13077
|
+
const dir = join32(stateDir, "passengers");
|
|
12823
13078
|
if (!existsSync29(dir)) return [];
|
|
12824
13079
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
12825
13080
|
}
|
|
@@ -12851,6 +13106,7 @@ function realPoolMemberDeps(o) {
|
|
|
12851
13106
|
const lines2 = [];
|
|
12852
13107
|
const outcome = await runProvision({
|
|
12853
13108
|
key: lease.connectKey,
|
|
13109
|
+
keys: Object.fromEntries((lease.connectKeys ?? []).map((k) => [k.workspace, k.key])),
|
|
12854
13110
|
routes: lease.routes.map((r) => `${r.workspace}/${r.project}`),
|
|
12855
13111
|
shipName: lease.shipName,
|
|
12856
13112
|
shipKind,
|
|
@@ -13685,8 +13941,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
13685
13941
|
}
|
|
13686
13942
|
|
|
13687
13943
|
// src/poll.ts
|
|
13688
|
-
import { writeFileSync as
|
|
13689
|
-
import { join as
|
|
13944
|
+
import { writeFileSync as writeFileSync22 } from "fs";
|
|
13945
|
+
import { join as join33 } from "path";
|
|
13690
13946
|
|
|
13691
13947
|
// src/stalled.ts
|
|
13692
13948
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -13993,7 +14249,7 @@ async function decideCycle(o) {
|
|
|
13993
14249
|
}
|
|
13994
14250
|
return decision;
|
|
13995
14251
|
}
|
|
13996
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
14252
|
+
var digestPath = (stateDir, route2, role, runId) => join33(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
13997
14253
|
function otherRunsRemoved(tickets, own) {
|
|
13998
14254
|
if (!own) return tickets;
|
|
13999
14255
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -14071,7 +14327,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
14071
14327
|
|
|
14072
14328
|
${body}` : body;
|
|
14073
14329
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
14074
|
-
|
|
14330
|
+
writeFileSync22(path, text2);
|
|
14075
14331
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
14076
14332
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
14077
14333
|
});
|
|
@@ -14179,7 +14435,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
14179
14435
|
}
|
|
14180
14436
|
|
|
14181
14437
|
// src/repo-refresh.ts
|
|
14182
|
-
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";
|
|
14183
14439
|
import { dirname as dirname14 } from "path";
|
|
14184
14440
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
14185
14441
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -14213,7 +14469,7 @@ function persistRepoRows(stateDir, routeName, rows) {
|
|
|
14213
14469
|
const raw = JSON.parse(readFileSync28(path, "utf8"));
|
|
14214
14470
|
applyRepoRows(raw, rows);
|
|
14215
14471
|
mkdirSync19(dirname14(path), { recursive: true });
|
|
14216
|
-
|
|
14472
|
+
writeFileSync23(path, `${JSON.stringify(raw, null, 2)}
|
|
14217
14473
|
`);
|
|
14218
14474
|
}
|
|
14219
14475
|
async function refreshUnknownRepos(o) {
|
|
@@ -14663,8 +14919,8 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
14663
14919
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
14664
14920
|
|
|
14665
14921
|
// src/connect-wizard.ts
|
|
14666
|
-
import { delimiter, dirname as dirname15, join as
|
|
14667
|
-
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";
|
|
14668
14924
|
import { stringify as stringify2 } from "yaml";
|
|
14669
14925
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
14670
14926
|
function shouldRunWizard(opts) {
|
|
@@ -14686,7 +14942,7 @@ function defaultIsExecutable(p) {
|
|
|
14686
14942
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
14687
14943
|
for (const dir of pathEnv.split(delimiter)) {
|
|
14688
14944
|
if (!dir) continue;
|
|
14689
|
-
const candidate =
|
|
14945
|
+
const candidate = join34(dir, name);
|
|
14690
14946
|
if (isExecutable(candidate)) return candidate;
|
|
14691
14947
|
}
|
|
14692
14948
|
return void 0;
|
|
@@ -14749,7 +15005,7 @@ ${routeBlock}`;
|
|
|
14749
15005
|
function writeNewConfig(path, text2) {
|
|
14750
15006
|
mkdirSync20(dirname15(path), { recursive: true });
|
|
14751
15007
|
try {
|
|
14752
|
-
|
|
15008
|
+
writeFileSync24(path, text2, { mode: 384, flag: "wx" });
|
|
14753
15009
|
return true;
|
|
14754
15010
|
} catch (e) {
|
|
14755
15011
|
if (e.code === "EEXIST") return false;
|
|
@@ -14783,7 +15039,7 @@ Wrote ${o.configPath}.
|
|
|
14783
15039
|
// src/connect-json.ts
|
|
14784
15040
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
14785
15041
|
import { homedir as homedir10 } from "os";
|
|
14786
|
-
import { dirname as dirname16, join as
|
|
15042
|
+
import { dirname as dirname16, join as join35 } from "path";
|
|
14787
15043
|
import { delimiter as delimiter2 } from "path";
|
|
14788
15044
|
var CHOICE_EXIT_CODE = 3;
|
|
14789
15045
|
function createEmitter(json2, write) {
|
|
@@ -14822,12 +15078,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
14822
15078
|
const dirs = [
|
|
14823
15079
|
...pathEnv.split(delimiter2),
|
|
14824
15080
|
...(extraPath ?? "").split(delimiter2),
|
|
14825
|
-
|
|
14826
|
-
|
|
15081
|
+
join35(home, ".local/bin"),
|
|
15082
|
+
join35(home, ".claude/local"),
|
|
14827
15083
|
"/opt/homebrew/bin",
|
|
14828
15084
|
"/usr/local/bin",
|
|
14829
|
-
|
|
14830
|
-
|
|
15085
|
+
join35(home, ".npm-global/bin"),
|
|
15086
|
+
join35(home, ".volta/bin")
|
|
14831
15087
|
];
|
|
14832
15088
|
return [...new Set(dirs.filter(Boolean))];
|
|
14833
15089
|
}
|
|
@@ -14863,9 +15119,9 @@ function flagWizardAnswers(f, env) {
|
|
|
14863
15119
|
}
|
|
14864
15120
|
|
|
14865
15121
|
// src/release-run.ts
|
|
14866
|
-
import { readFileSync as readFileSync29, writeFileSync as
|
|
14867
|
-
import { dirname as dirname17, join as
|
|
14868
|
-
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";
|
|
14869
15125
|
|
|
14870
15126
|
// src/release.ts
|
|
14871
15127
|
function describeUnplaceable(unplaceable, verifiedStatus) {
|
|
@@ -15605,7 +15861,7 @@ async function bump(o, size) {
|
|
|
15605
15861
|
}
|
|
15606
15862
|
return printed;
|
|
15607
15863
|
}
|
|
15608
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
15864
|
+
const files = o.repo.release.versionFiles.map((f) => join36(o.cwd, f));
|
|
15609
15865
|
const first = files[0];
|
|
15610
15866
|
if (!existsSync33(first)) {
|
|
15611
15867
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
@@ -15630,21 +15886,21 @@ async function bump(o, size) {
|
|
|
15630
15886
|
o.emit.error(`no version field in ${f}`);
|
|
15631
15887
|
return null;
|
|
15632
15888
|
}
|
|
15633
|
-
|
|
15889
|
+
writeFileSync25(f, out);
|
|
15634
15890
|
}
|
|
15635
15891
|
return version;
|
|
15636
15892
|
}
|
|
15637
15893
|
function writeChangelog(o, version, entries) {
|
|
15638
15894
|
const file = o.repo.release.changelog;
|
|
15639
15895
|
if (!file || entries.length === 0) return;
|
|
15640
|
-
const path =
|
|
15896
|
+
const path = join36(o.cwd, file);
|
|
15641
15897
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
15642
15898
|
if (o.dryRun) {
|
|
15643
15899
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
15644
15900
|
return;
|
|
15645
15901
|
}
|
|
15646
15902
|
const existing = existsSync33(path) ? readFileSync29(path, "utf8") : "# Changelog\n";
|
|
15647
|
-
|
|
15903
|
+
writeFileSync25(path, insertChangelogSection(existing, section));
|
|
15648
15904
|
}
|
|
15649
15905
|
async function confirm(o, expected) {
|
|
15650
15906
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -15727,7 +15983,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
15727
15983
|
run2(cwd, ["worktree", "remove", "--force", dir]);
|
|
15728
15984
|
} finally {
|
|
15729
15985
|
try {
|
|
15730
|
-
|
|
15986
|
+
rmSync16(root, { recursive: true, force: true });
|
|
15731
15987
|
} finally {
|
|
15732
15988
|
run2(cwd, ["worktree", "prune"]);
|
|
15733
15989
|
run2(cwd, ["branch", "-D", branch]);
|
|
@@ -15737,8 +15993,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
15737
15993
|
function openReleaseCheckout(o) {
|
|
15738
15994
|
const { remote, base } = o.repo.branch;
|
|
15739
15995
|
const branch = `crew/release-${Date.now()}`;
|
|
15740
|
-
const root =
|
|
15741
|
-
const dir =
|
|
15996
|
+
const root = mkdtempSync3(join36(tmpdir4(), "crew-release-"));
|
|
15997
|
+
const dir = join36(root, "checkout");
|
|
15742
15998
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
15743
15999
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
15744
16000
|
try {
|
|
@@ -15748,10 +16004,10 @@ function openReleaseCheckout(o) {
|
|
|
15748
16004
|
throw e;
|
|
15749
16005
|
}
|
|
15750
16006
|
for (const f of o.repo.worktrees.copy) {
|
|
15751
|
-
const src =
|
|
16007
|
+
const src = join36(o.cwd, f);
|
|
15752
16008
|
if (!existsSync33(src)) continue;
|
|
15753
|
-
mkdirSync21(dirname17(
|
|
15754
|
-
copyFileSync(src,
|
|
16009
|
+
mkdirSync21(dirname17(join36(dir, f)), { recursive: true });
|
|
16010
|
+
copyFileSync(src, join36(dir, f));
|
|
15755
16011
|
}
|
|
15756
16012
|
return { dir, branch, close };
|
|
15757
16013
|
}
|
|
@@ -15896,7 +16152,7 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
|
15896
16152
|
let checkout;
|
|
15897
16153
|
const stranded = recoverStrandedReleaseCheckouts(o.cwd);
|
|
15898
16154
|
if (stranded.length > 0) o.emit.warn(scoped(`dropped release checkout(s) stranded by a crash: ${stranded.join(", ")}`));
|
|
15899
|
-
const swept = sweepStaleReleaseRoots(
|
|
16155
|
+
const swept = sweepStaleReleaseRoots(tmpdir4());
|
|
15900
16156
|
if (swept.length > 0) o.emit.emit(scoped(`removed ${swept.length} stale crew-release-* temp dir(s)`), { step: "release" });
|
|
15901
16157
|
try {
|
|
15902
16158
|
checkout = openReleaseCheckout(o);
|
|
@@ -17069,7 +17325,7 @@ function nextRoles(decision, ran) {
|
|
|
17069
17325
|
}
|
|
17070
17326
|
|
|
17071
17327
|
// src/ship-name.ts
|
|
17072
|
-
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";
|
|
17073
17329
|
import { dirname as dirname18 } from "path";
|
|
17074
17330
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
17075
17331
|
var ShipNameError = class extends Error {
|
|
@@ -17151,7 +17407,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
17151
17407
|
raw.shipName = name;
|
|
17152
17408
|
mkdirSync22(dirname18(resolvedFile), { recursive: true });
|
|
17153
17409
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
17154
|
-
|
|
17410
|
+
writeFileSync26(tmp, `${JSON.stringify(raw, null, 2)}
|
|
17155
17411
|
`);
|
|
17156
17412
|
renameSync4(tmp, resolvedFile);
|
|
17157
17413
|
}
|
|
@@ -17183,7 +17439,7 @@ async function reconcileShipName(i) {
|
|
|
17183
17439
|
if (result.status !== "ok") return result;
|
|
17184
17440
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
17185
17441
|
const text2 = readFileSync30(i.configFile, "utf8");
|
|
17186
|
-
|
|
17442
|
+
writeFileSync26(i.configFile, setShipNameInYaml(text2, result.name));
|
|
17187
17443
|
i.ship.name = result.name;
|
|
17188
17444
|
if (result.action === "row-wins") {
|
|
17189
17445
|
i.emit?.warn(
|
|
@@ -17842,7 +18098,7 @@ function describeCapability(i) {
|
|
|
17842
18098
|
}
|
|
17843
18099
|
|
|
17844
18100
|
// src/agents-sync-run.ts
|
|
17845
|
-
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";
|
|
17846
18102
|
import { dirname as dirname19 } from "path";
|
|
17847
18103
|
async function runAgentsSync(target, o) {
|
|
17848
18104
|
const dryRun = o.dryRun ?? false;
|
|
@@ -17881,7 +18137,7 @@ async function runAgentsSync(target, o) {
|
|
|
17881
18137
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
17882
18138
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
17883
18139
|
mkdirSync23(dirname19(resolvedPath), { recursive: true });
|
|
17884
|
-
|
|
18140
|
+
writeFileSync27(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
17885
18141
|
`);
|
|
17886
18142
|
if (target.resolved) {
|
|
17887
18143
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -17986,11 +18242,11 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
17986
18242
|
// src/repos-check.ts
|
|
17987
18243
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
17988
18244
|
import { readFileSync as readFileSync32 } from "fs";
|
|
17989
|
-
import { basename as
|
|
18245
|
+
import { basename as basename10 } from "path";
|
|
17990
18246
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
17991
18247
|
function checkHookSyntax(script, shell) {
|
|
17992
18248
|
const spec = resolveShell(shell);
|
|
17993
|
-
const name =
|
|
18249
|
+
const name = basename10(spec.bin).replace(/\.exe$/i, "").toLowerCase();
|
|
17994
18250
|
let run2;
|
|
17995
18251
|
if (POSIX_SHELL.test(name)) {
|
|
17996
18252
|
run2 = spawnSync9(spec.bin, ["-n"], { input: script, encoding: "utf8" });
|
|
@@ -18009,7 +18265,7 @@ function checkHookSyntax(script, shell) {
|
|
|
18009
18265
|
return { status: "error", message };
|
|
18010
18266
|
}
|
|
18011
18267
|
function renderSamples(cfg, dir) {
|
|
18012
|
-
const checkout =
|
|
18268
|
+
const checkout = basename10(dir.replace(/[/\\]+$/, ""));
|
|
18013
18269
|
const tagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", tag: "TABL-123", prefix: "tabl", lowercase: cfg.branch.lowercase };
|
|
18014
18270
|
const untagged = { key: "ISSUE-123", title: "Fix the widget", role: "dev", lowercase: cfg.branch.lowercase };
|
|
18015
18271
|
return [["with a project tag", tagged], ["without a project tag", untagged]].map(([label, ctx]) => {
|
|
@@ -18301,7 +18557,7 @@ function existingRepoConfig(f) {
|
|
|
18301
18557
|
}
|
|
18302
18558
|
|
|
18303
18559
|
// src/repos-hooks-agent.ts
|
|
18304
|
-
import { spawn as
|
|
18560
|
+
import { spawn as spawn6, execFileSync as execFileSync8 } from "child_process";
|
|
18305
18561
|
import { realpathSync as realpathSync2 } from "fs";
|
|
18306
18562
|
import { isAbsolute as isAbsolute2, relative } from "path";
|
|
18307
18563
|
var AgentSuggestError = class extends Error {
|
|
@@ -18404,7 +18660,7 @@ function classifyFailure(o) {
|
|
|
18404
18660
|
}
|
|
18405
18661
|
function porcelain(dir) {
|
|
18406
18662
|
try {
|
|
18407
|
-
return
|
|
18663
|
+
return execFileSync8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
18408
18664
|
} catch {
|
|
18409
18665
|
return void 0;
|
|
18410
18666
|
}
|
|
@@ -18422,7 +18678,7 @@ async function runAgentSuggest(o) {
|
|
|
18422
18678
|
const run2 = await new Promise((resolve7) => {
|
|
18423
18679
|
let out = "";
|
|
18424
18680
|
let err3 = "";
|
|
18425
|
-
const child =
|
|
18681
|
+
const child = spawn6(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
18426
18682
|
let settled = false;
|
|
18427
18683
|
const settle = (r) => {
|
|
18428
18684
|
if (settled) return;
|
|
@@ -18473,7 +18729,7 @@ ${run2.err}` });
|
|
|
18473
18729
|
}
|
|
18474
18730
|
|
|
18475
18731
|
// src/sample-ticket.ts
|
|
18476
|
-
import { basename as
|
|
18732
|
+
import { basename as basename11, dirname as dirname20, join as join37 } from "path";
|
|
18477
18733
|
var SAMPLE_REPO = "crew-sample";
|
|
18478
18734
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
18479
18735
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -18496,8 +18752,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
18496
18752
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
18497
18753
|
`;
|
|
18498
18754
|
function sampleDir(existing, reposBase, workspace) {
|
|
18499
|
-
if (existing.length > 0) return
|
|
18500
|
-
return
|
|
18755
|
+
if (existing.length > 0) return join37(dirname20(existing[0]), SAMPLE_REPO);
|
|
18756
|
+
return join37(reposBase, workspace, SAMPLE_REPO);
|
|
18501
18757
|
}
|
|
18502
18758
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
18503
18759
|
function sampleDescription() {
|
|
@@ -18542,7 +18798,7 @@ function planSampleTicket(i) {
|
|
|
18542
18798
|
|
|
18543
18799
|
// src/worktree-sweep.ts
|
|
18544
18800
|
import { existsSync as existsSync37, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "fs";
|
|
18545
|
-
import { basename as
|
|
18801
|
+
import { basename as basename12, dirname as dirname21, join as join38, resolve as resolve4 } from "path";
|
|
18546
18802
|
function planWorktreeSweep(target, tickets, contract) {
|
|
18547
18803
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
18548
18804
|
const verified = contract.statuses.verified;
|
|
@@ -18583,7 +18839,7 @@ function ticketForWorktree(w, byNumber, contract) {
|
|
|
18583
18839
|
const t = num ? byNumber.get(num) : void 0;
|
|
18584
18840
|
if (t && ticketStems(t).has(stem)) return t;
|
|
18585
18841
|
}
|
|
18586
|
-
const dir =
|
|
18842
|
+
const dir = basename12(w.path);
|
|
18587
18843
|
const m = DIR_NUMBER.exec(dir);
|
|
18588
18844
|
if (m && byNumber.has(m[1])) return byNumber.get(m[1]);
|
|
18589
18845
|
if (!w.branch) {
|
|
@@ -18599,7 +18855,7 @@ function worktreeTicketNumbers(target) {
|
|
|
18599
18855
|
const n = /-(\d+)$/.exec(BRANCH_STEM.exec(w.branch)?.[1] ?? "")?.[1];
|
|
18600
18856
|
if (n) nums.add(n);
|
|
18601
18857
|
}
|
|
18602
|
-
const m = DIR_NUMBER_SUFFIXED.exec(
|
|
18858
|
+
const m = DIR_NUMBER_SUFFIXED.exec(basename12(w.path));
|
|
18603
18859
|
if (m) nums.add(m[1]);
|
|
18604
18860
|
}
|
|
18605
18861
|
for (const d of siblingWorktreeDirs(target)) nums.add(d.number);
|
|
@@ -18607,7 +18863,7 @@ function worktreeTicketNumbers(target) {
|
|
|
18607
18863
|
}
|
|
18608
18864
|
function siblingWorktreeDirs(target) {
|
|
18609
18865
|
const parent = dirname21(target.dir.replace(/[/\\]+$/, ""));
|
|
18610
|
-
const stem = `${
|
|
18866
|
+
const stem = `${basename12(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
18611
18867
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
18612
18868
|
let names;
|
|
18613
18869
|
try {
|
|
@@ -18619,7 +18875,7 @@ function siblingWorktreeDirs(target) {
|
|
|
18619
18875
|
for (const name of names) {
|
|
18620
18876
|
if (!name.startsWith(stem)) continue;
|
|
18621
18877
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
18622
|
-
const path =
|
|
18878
|
+
const path = join38(parent, name);
|
|
18623
18879
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
18624
18880
|
}
|
|
18625
18881
|
return out;
|
|
@@ -18632,7 +18888,7 @@ function orphanedWorktreeDirs(target, tickets, contract) {
|
|
|
18632
18888
|
}
|
|
18633
18889
|
function warnOrphanedWorktreeDirs(paths, log, held) {
|
|
18634
18890
|
for (const p of paths) {
|
|
18635
|
-
const number = DIR_NUMBER_SUFFIXED.exec(
|
|
18891
|
+
const number = DIR_NUMBER_SUFFIXED.exec(basename12(p))?.[1];
|
|
18636
18892
|
const when = held && number ? noteHeldWorktree(held.state, { repo: held.repo, path: p, ticket: number, status: "deployed", reason: "orphaned directory" }) : "first";
|
|
18637
18893
|
if (when) {
|
|
18638
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" });
|
|
@@ -18776,7 +19032,7 @@ function isTicketBranch(name, base, stems) {
|
|
|
18776
19032
|
}
|
|
18777
19033
|
function sweepCandidatesExist(cwd, base) {
|
|
18778
19034
|
const linked = worktrees(cwd).slice(1);
|
|
18779
|
-
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;
|
|
18780
19036
|
if (siblingWorktreeDirs({ name: "", dir: cwd }).length) return true;
|
|
18781
19037
|
return localBranches(cwd).some((b) => b !== base && /-\d+(?:-.*)?$/.test(b));
|
|
18782
19038
|
}
|
|
@@ -18827,14 +19083,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
18827
19083
|
}
|
|
18828
19084
|
|
|
18829
19085
|
// src/stream-sweep.ts
|
|
18830
|
-
import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as
|
|
18831
|
-
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";
|
|
18832
19088
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
18833
19089
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
18834
|
-
const dir =
|
|
19090
|
+
const dir = join39(stateDir, "streams");
|
|
18835
19091
|
if (!existsSync38(dir)) return [];
|
|
18836
19092
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
18837
|
-
return readdirSync15(dir).map((name) =>
|
|
19093
|
+
return readdirSync15(dir).map((name) => join39(dir, name)).filter((path) => {
|
|
18838
19094
|
try {
|
|
18839
19095
|
return statSync10(path).mtimeMs < cutoff;
|
|
18840
19096
|
} catch {
|
|
@@ -18850,7 +19106,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
18850
19106
|
continue;
|
|
18851
19107
|
}
|
|
18852
19108
|
try {
|
|
18853
|
-
|
|
19109
|
+
rmSync17(path, { force: true });
|
|
18854
19110
|
removed++;
|
|
18855
19111
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
18856
19112
|
} catch (e) {
|
|
@@ -18862,7 +19118,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
18862
19118
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
18863
19119
|
if (!existsSync38(stateDir)) return [];
|
|
18864
19120
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
18865
|
-
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) => {
|
|
18866
19122
|
try {
|
|
18867
19123
|
return statSync10(path).mtimeMs < cutoff;
|
|
18868
19124
|
} catch {
|
|
@@ -18872,12 +19128,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
18872
19128
|
}
|
|
18873
19129
|
|
|
18874
19130
|
// src/cli.ts
|
|
18875
|
-
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";
|
|
18876
19132
|
import { createInterface } from "readline/promises";
|
|
18877
19133
|
|
|
18878
19134
|
// src/periscope-cmd.ts
|
|
18879
|
-
import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync34, rmSync as
|
|
18880
|
-
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";
|
|
18881
19137
|
var PeriscopeError = class extends Error {
|
|
18882
19138
|
};
|
|
18883
19139
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -18887,15 +19143,15 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
18887
19143
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
18888
19144
|
}
|
|
18889
19145
|
};
|
|
18890
|
-
var priorDir = (stateDir) =>
|
|
18891
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
19146
|
+
var priorDir = (stateDir) => join40(stateDir, "periscope-priors");
|
|
19147
|
+
var priorPath = (stateDir, ticketRecordId) => join40(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
18892
19148
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
18893
|
-
|
|
19149
|
+
rmSync18(priorPath(stateDir, ticketRecordId), { force: true });
|
|
18894
19150
|
}
|
|
18895
19151
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
18896
19152
|
try {
|
|
18897
19153
|
mkdirSync24(priorDir(stateDir), { recursive: true });
|
|
18898
|
-
|
|
19154
|
+
writeFileSync28(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
18899
19155
|
} catch {
|
|
18900
19156
|
}
|
|
18901
19157
|
}
|
|
@@ -19299,14 +19555,14 @@ ${tail2(preview.output)}`);
|
|
|
19299
19555
|
}
|
|
19300
19556
|
|
|
19301
19557
|
// src/periscope-idle.ts
|
|
19302
|
-
import { execFileSync as
|
|
19558
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
19303
19559
|
import { statSync as statSync11 } from "fs";
|
|
19304
|
-
import { join as
|
|
19560
|
+
import { join as join41 } from "path";
|
|
19305
19561
|
var MAX_SCANNED_FILES = 2e4;
|
|
19306
19562
|
function newestWorktreeMtime(root) {
|
|
19307
19563
|
let listing;
|
|
19308
19564
|
try {
|
|
19309
|
-
listing =
|
|
19565
|
+
listing = execFileSync9("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19310
19566
|
cwd: root,
|
|
19311
19567
|
encoding: "utf8",
|
|
19312
19568
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -19321,7 +19577,7 @@ function newestWorktreeMtime(root) {
|
|
|
19321
19577
|
if (!rel) continue;
|
|
19322
19578
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
19323
19579
|
try {
|
|
19324
|
-
const m = statSync11(
|
|
19580
|
+
const m = statSync11(join41(root, rel)).mtimeMs;
|
|
19325
19581
|
if (newest === null || m > newest) newest = m;
|
|
19326
19582
|
} catch {
|
|
19327
19583
|
}
|
|
@@ -19396,13 +19652,13 @@ async function runPeriscopeIdlePass(d) {
|
|
|
19396
19652
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
19397
19653
|
|
|
19398
19654
|
// src/ssh-keys.ts
|
|
19399
|
-
import { execFileSync as
|
|
19655
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
19400
19656
|
import { existsSync as existsSync40, mkdirSync as mkdirSync25, readFileSync as readFileSync35, chmodSync as chmodSync3 } from "fs";
|
|
19401
|
-
import { dirname as dirname22, join as
|
|
19657
|
+
import { dirname as dirname22, join as join42 } from "path";
|
|
19402
19658
|
var SshKeyError = class extends Error {
|
|
19403
19659
|
};
|
|
19404
19660
|
function shipSshKeyPathFor(stateDir) {
|
|
19405
|
-
return
|
|
19661
|
+
return join42(stateDir, "ssh", "id_ed25519");
|
|
19406
19662
|
}
|
|
19407
19663
|
function ensureShipSshKeypair(stateDir) {
|
|
19408
19664
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
@@ -19410,7 +19666,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
19410
19666
|
if (!existsSync40(privateKeyPath)) {
|
|
19411
19667
|
mkdirSync25(dirname22(privateKeyPath), { recursive: true });
|
|
19412
19668
|
try {
|
|
19413
|
-
|
|
19669
|
+
execFileSync10("ssh-keygen", [
|
|
19414
19670
|
"-t",
|
|
19415
19671
|
"ed25519",
|
|
19416
19672
|
"-N",
|
|
@@ -19436,7 +19692,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
19436
19692
|
}
|
|
19437
19693
|
function sshKeygenAvailable() {
|
|
19438
19694
|
try {
|
|
19439
|
-
|
|
19695
|
+
execFileSync10("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
19440
19696
|
return true;
|
|
19441
19697
|
} catch (e) {
|
|
19442
19698
|
return e.code !== "ENOENT";
|
|
@@ -19493,6 +19749,8 @@ async function call(api, method, url, body) {
|
|
|
19493
19749
|
}
|
|
19494
19750
|
function bodyFor(d, forPatch) {
|
|
19495
19751
|
const body = { label: d.label, url: d.url };
|
|
19752
|
+
if (d.controlUrl !== void 0) body.controlUrl = d.controlUrl;
|
|
19753
|
+
if (d.discoverable !== void 0) body.discoverable = d.discoverable;
|
|
19496
19754
|
if (d.mcpSecret) body.authorizationHeader = `Bearer ${d.mcpSecret}`;
|
|
19497
19755
|
else if (forPatch) body.authorizationHeader = null;
|
|
19498
19756
|
return body;
|
|
@@ -19541,15 +19799,17 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
19541
19799
|
}
|
|
19542
19800
|
}
|
|
19543
19801
|
var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
|
|
19544
|
-
function registrationKey(url, mcpSecret) {
|
|
19802
|
+
function registrationKey(url, mcpSecret, controlUrl) {
|
|
19545
19803
|
return createHash8("sha256").update(`${url}
|
|
19546
|
-
${mcpSecret ?? ""}
|
|
19804
|
+
${mcpSecret ?? ""}
|
|
19805
|
+
${controlUrl ?? ""}`).digest("hex").slice(0, 16);
|
|
19547
19806
|
}
|
|
19548
19807
|
async function reconcileEndpoint(workspaceId, d) {
|
|
19549
19808
|
const st = readPersistedTunnel(d.stateDir, workspaceId);
|
|
19550
19809
|
if (!st || st.status !== "connected" || !st.slug) return;
|
|
19551
19810
|
const url = d.urlFor(st.slug);
|
|
19552
|
-
const
|
|
19811
|
+
const controlUrl = st.controlSocket && d.controlUrlFor ? d.controlUrlFor(st.slug) : null;
|
|
19812
|
+
const key = registrationKey(url, st.mcpSecret, controlUrl);
|
|
19553
19813
|
if (st.endpointId && st.registeredKey === key) return;
|
|
19554
19814
|
const api = d.apiFor(workspaceId);
|
|
19555
19815
|
if (!api) {
|
|
@@ -19559,7 +19819,8 @@ async function reconcileEndpoint(workspaceId, d) {
|
|
|
19559
19819
|
const out = await registerEndpoint(api, workspaceId, st.endpointId, {
|
|
19560
19820
|
label: `Ship: ${d.shipName}`,
|
|
19561
19821
|
url,
|
|
19562
|
-
mcpSecret: st.mcpSecret
|
|
19822
|
+
mcpSecret: st.mcpSecret,
|
|
19823
|
+
controlUrl
|
|
19563
19824
|
});
|
|
19564
19825
|
if (out.kind === "ok") {
|
|
19565
19826
|
updatePersistedTunnel(d.stateDir, workspaceId, { endpointId: out.endpointId, registeredKey: key });
|
|
@@ -19588,10 +19849,10 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
19588
19849
|
|
|
19589
19850
|
// src/passenger-sync-daemon.ts
|
|
19590
19851
|
import { createServer as createHttpServer } from "http";
|
|
19591
|
-
import { spawn as
|
|
19852
|
+
import { spawn as spawn7 } from "child_process";
|
|
19592
19853
|
import { randomBytes } from "crypto";
|
|
19593
|
-
import { existsSync as existsSync41, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as
|
|
19594
|
-
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";
|
|
19595
19856
|
|
|
19596
19857
|
// src/secret-compare.ts
|
|
19597
19858
|
import { timingSafeEqual } from "crypto";
|
|
@@ -19675,12 +19936,12 @@ function runSyncDaemonFromEnv(env = process.env) {
|
|
|
19675
19936
|
return server;
|
|
19676
19937
|
}
|
|
19677
19938
|
var defaultSpawn2 = (cmd, args, env) => {
|
|
19678
|
-
const child =
|
|
19939
|
+
const child = spawn7(cmd, args, { detached: true, stdio: "ignore", env });
|
|
19679
19940
|
child.unref();
|
|
19680
19941
|
return child;
|
|
19681
19942
|
};
|
|
19682
19943
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
19683
|
-
return
|
|
19944
|
+
return join43(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
19684
19945
|
}
|
|
19685
19946
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
19686
19947
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
@@ -19693,8 +19954,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
19693
19954
|
}
|
|
19694
19955
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
19695
19956
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
19696
|
-
mkdirSync26(
|
|
19697
|
-
|
|
19957
|
+
mkdirSync26(join43(stateDir, "passengers"), { recursive: true });
|
|
19958
|
+
writeFileSync29(path, `${JSON.stringify(state, null, 2)}
|
|
19698
19959
|
`);
|
|
19699
19960
|
}
|
|
19700
19961
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -19714,7 +19975,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
19714
19975
|
}
|
|
19715
19976
|
}
|
|
19716
19977
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
19717
|
-
const dir =
|
|
19978
|
+
const dir = join43(stateDir, "passengers");
|
|
19718
19979
|
if (!existsSync41(dir)) return [];
|
|
19719
19980
|
const suffix = "-sync.json";
|
|
19720
19981
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -19803,8 +20064,8 @@ function describeCause(err3) {
|
|
|
19803
20064
|
}
|
|
19804
20065
|
|
|
19805
20066
|
// src/daemon.ts
|
|
19806
|
-
import { existsSync as existsSync42, mkdirSync as mkdirSync27, readFileSync as readFileSync37, readdirSync as readdirSync17, rmSync as
|
|
19807
|
-
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";
|
|
19808
20069
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
19809
20070
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
19810
20071
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -19892,7 +20153,7 @@ function latestMtime(path) {
|
|
|
19892
20153
|
if (!stat2.isDirectory()) return 0;
|
|
19893
20154
|
let latest = stat2.mtimeMs;
|
|
19894
20155
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
19895
|
-
latest = Math.max(latest, latestMtime(
|
|
20156
|
+
latest = Math.max(latest, latestMtime(join44(path, entry.name)));
|
|
19896
20157
|
}
|
|
19897
20158
|
return latest;
|
|
19898
20159
|
}
|
|
@@ -19904,7 +20165,7 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
19904
20165
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync42(opts.reloadSentinel);
|
|
19905
20166
|
}
|
|
19906
20167
|
function reloadSentinelFor(stateDir) {
|
|
19907
|
-
return
|
|
20168
|
+
return join44(stateDir, "reload-requested");
|
|
19908
20169
|
}
|
|
19909
20170
|
function readReloadRequestedAt(stateDir) {
|
|
19910
20171
|
try {
|
|
@@ -19914,12 +20175,12 @@ function readReloadRequestedAt(stateDir) {
|
|
|
19914
20175
|
}
|
|
19915
20176
|
}
|
|
19916
20177
|
function daemonStampFor(stateDir) {
|
|
19917
|
-
return
|
|
20178
|
+
return join44(stateDir, "daemon.started");
|
|
19918
20179
|
}
|
|
19919
20180
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
19920
20181
|
mkdirSync27(stateDir, { recursive: true });
|
|
19921
|
-
|
|
19922
|
-
|
|
20182
|
+
rmSync19(reloadSentinelFor(stateDir), { force: true });
|
|
20183
|
+
writeFileSync30(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
19923
20184
|
`);
|
|
19924
20185
|
}
|
|
19925
20186
|
function readDaemonStarted(stateDir) {
|
|
@@ -19989,7 +20250,7 @@ async function runOnePassUnguarded(o) {
|
|
|
19989
20250
|
await hydrateApiKeys([o.route], o.getSessionStore);
|
|
19990
20251
|
const emit = o.newEmitter();
|
|
19991
20252
|
const decide = o.decide ?? decideCycle;
|
|
19992
|
-
const
|
|
20253
|
+
const spawn10 = o.spawnRoleAgent ?? runRoleAgent;
|
|
19993
20254
|
const breaker = o.spawnBreaker ?? new SpawnBreaker();
|
|
19994
20255
|
const ctx = { route: o.route, ship: o.ship, state: o.state, emit, getSessionStore: o.getSessionStore };
|
|
19995
20256
|
if (o.configFile) await reconcileRouteShipName({ route: o.route, ship: o.ship, configFile: o.configFile, emit });
|
|
@@ -20093,7 +20354,7 @@ async function runOnePassUnguarded(o) {
|
|
|
20093
20354
|
settle = resolve7;
|
|
20094
20355
|
});
|
|
20095
20356
|
const ready = (tookTicket2) => settle(tookTicket2);
|
|
20096
|
-
const run2 =
|
|
20357
|
+
const run2 = spawn10(role, decision, ctx, { ready }).then(() => {
|
|
20097
20358
|
breaker.recordSuccess(role);
|
|
20098
20359
|
clearShipAttention(o.state, `role_parked:${role}`);
|
|
20099
20360
|
clearShipAttention(o.state, workspaceAuthKey(role));
|
|
@@ -20488,17 +20749,17 @@ import {
|
|
|
20488
20749
|
openSync as openSync5,
|
|
20489
20750
|
readSync as readSync3,
|
|
20490
20751
|
readdirSync as readdirSync18,
|
|
20491
|
-
rmSync as
|
|
20752
|
+
rmSync as rmSync20,
|
|
20492
20753
|
statSync as statSync13,
|
|
20493
20754
|
watch
|
|
20494
20755
|
} from "fs";
|
|
20495
20756
|
import { createConnection } from "net";
|
|
20496
20757
|
import { createServer } from "http";
|
|
20497
|
-
import { basename as
|
|
20498
|
-
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";
|
|
20499
20760
|
|
|
20500
20761
|
// src/periscope-control.ts
|
|
20501
|
-
import { spawn as
|
|
20762
|
+
import { spawn as spawn8 } from "child_process";
|
|
20502
20763
|
var reply = (status2, body) => ({ status: status2, body });
|
|
20503
20764
|
var err = (status2, message) => reply(status2, { error: message });
|
|
20504
20765
|
var TICKET_RE = /^(?:[A-Za-z]+-)?\d+$/;
|
|
@@ -20689,7 +20950,7 @@ async function handlePeriscopes(h, q) {
|
|
|
20689
20950
|
}
|
|
20690
20951
|
function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
20691
20952
|
return new Promise((resolve7) => {
|
|
20692
|
-
const child =
|
|
20953
|
+
const child = spawn8(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
20693
20954
|
let stdout = "";
|
|
20694
20955
|
let stderr = "";
|
|
20695
20956
|
let pending = "";
|
|
@@ -20723,7 +20984,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
20723
20984
|
// src/control-api.ts
|
|
20724
20985
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
20725
20986
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
20726
|
-
var controlSocketFor = (stateDir) =>
|
|
20987
|
+
var controlSocketFor = (stateDir) => join45(stateDir, CONTROL_SOCKET_NAME);
|
|
20727
20988
|
var MAX_SOCKET_PATH = 100;
|
|
20728
20989
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
20729
20990
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -20810,7 +21071,7 @@ function candidates(live, rotated) {
|
|
|
20810
21071
|
const out = [live];
|
|
20811
21072
|
if (!rotated) return out;
|
|
20812
21073
|
try {
|
|
20813
|
-
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));
|
|
20814
21075
|
} catch {
|
|
20815
21076
|
}
|
|
20816
21077
|
return out;
|
|
@@ -20933,7 +21194,7 @@ function pauseDoc(state) {
|
|
|
20933
21194
|
}
|
|
20934
21195
|
function describeStream(r) {
|
|
20935
21196
|
return {
|
|
20936
|
-
run:
|
|
21197
|
+
run: basename13(r.streamPath).replace(/\.jsonl$/, ""),
|
|
20937
21198
|
pid: r.pid,
|
|
20938
21199
|
ticket: r.ticket,
|
|
20939
21200
|
role: r.role,
|
|
@@ -21010,7 +21271,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
21010
21271
|
if (p.startsWith("/streams/") && get2) {
|
|
21011
21272
|
const run2 = decodeURIComponent(p.slice("/streams/".length));
|
|
21012
21273
|
if (!RUN_NAME_RE.test(run2) || run2.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run2)) return err2(400, "not a run name");
|
|
21013
|
-
const file =
|
|
21274
|
+
const file = join45(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
|
|
21014
21275
|
if (!existsSync43(file)) return err2(404, "no such run");
|
|
21015
21276
|
return tailReply(file, url.searchParams, false, abort);
|
|
21016
21277
|
}
|
|
@@ -21073,7 +21334,7 @@ async function startControlServer(deps) {
|
|
|
21073
21334
|
log(`control API not started: socket path is too long for a unix socket (${path})`);
|
|
21074
21335
|
return null;
|
|
21075
21336
|
}
|
|
21076
|
-
if (await socketIsStale(path))
|
|
21337
|
+
if (await socketIsStale(path)) rmSync20(path, { force: true });
|
|
21077
21338
|
else if (existsSync43(path)) {
|
|
21078
21339
|
log(`control API not started: ${path} is in use by another process`);
|
|
21079
21340
|
return null;
|
|
@@ -21120,7 +21381,7 @@ async function startControlServer(deps) {
|
|
|
21120
21381
|
path,
|
|
21121
21382
|
close: () => new Promise((done) => {
|
|
21122
21383
|
server.close(() => {
|
|
21123
|
-
|
|
21384
|
+
rmSync20(path, { force: true });
|
|
21124
21385
|
done();
|
|
21125
21386
|
});
|
|
21126
21387
|
})
|
|
@@ -21129,7 +21390,7 @@ async function startControlServer(deps) {
|
|
|
21129
21390
|
});
|
|
21130
21391
|
}
|
|
21131
21392
|
var shq2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
21132
|
-
function runDetached(commands, spawnFn =
|
|
21393
|
+
function runDetached(commands, spawnFn = spawn9) {
|
|
21133
21394
|
const script = `sleep 1; ${commands.map((c) => c.map(shq2).join(" ")).join("; ")}`;
|
|
21134
21395
|
const child = spawnFn("/bin/sh", ["-c", script], { detached: true, stdio: "ignore" });
|
|
21135
21396
|
child.on("error", () => {
|
|
@@ -21147,7 +21408,7 @@ function schedulerHandler(control, run2 = runDetached) {
|
|
|
21147
21408
|
}
|
|
21148
21409
|
function crewJson(args, timeoutMs = 6e4) {
|
|
21149
21410
|
return new Promise((resolve7) => {
|
|
21150
|
-
|
|
21411
|
+
execFile2(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
21151
21412
|
try {
|
|
21152
21413
|
const v = JSON.parse(String(stdout));
|
|
21153
21414
|
resolve7(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
@@ -21160,24 +21421,24 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
21160
21421
|
|
|
21161
21422
|
// src/periscope-mcp.ts
|
|
21162
21423
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
21163
|
-
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";
|
|
21164
21425
|
import { z } from "zod";
|
|
21165
21426
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21166
21427
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
21167
21428
|
|
|
21168
21429
|
// src/periscope-endpoints.ts
|
|
21169
21430
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
21170
|
-
import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync28, readdirSync as readdirSync19, readFileSync as readFileSync38, rmSync as
|
|
21171
|
-
import { join as
|
|
21172
|
-
var secretDir = (stateDir) =>
|
|
21173
|
-
var secretFile = (stateDir, id) =>
|
|
21174
|
-
var endpointDir = (stateDir) =>
|
|
21175
|
-
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`);
|
|
21176
21437
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
21177
21438
|
mkdirSync28(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
21178
21439
|
const secret = randomBytes3(32).toString("base64url");
|
|
21179
21440
|
const file = secretFile(stateDir, ticketRecordId);
|
|
21180
|
-
|
|
21441
|
+
writeFileSync31(file, `${JSON.stringify({ secret })}
|
|
21181
21442
|
`, { mode: 384 });
|
|
21182
21443
|
chmodSync5(file, 384);
|
|
21183
21444
|
return secret;
|
|
@@ -21191,7 +21452,7 @@ function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
21191
21452
|
}
|
|
21192
21453
|
}
|
|
21193
21454
|
function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
21194
|
-
|
|
21455
|
+
rmSync21(secretFile(stateDir, ticketRecordId), { force: true });
|
|
21195
21456
|
}
|
|
21196
21457
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
21197
21458
|
try {
|
|
@@ -21202,11 +21463,11 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
21202
21463
|
}
|
|
21203
21464
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
21204
21465
|
mkdirSync28(endpointDir(stateDir), { recursive: true });
|
|
21205
|
-
|
|
21466
|
+
writeFileSync31(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
21206
21467
|
`);
|
|
21207
21468
|
}
|
|
21208
21469
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
21209
|
-
|
|
21470
|
+
rmSync21(endpointFile(stateDir, ticketRecordId), { force: true });
|
|
21210
21471
|
}
|
|
21211
21472
|
function listPeriscopeEndpoints(stateDir) {
|
|
21212
21473
|
const dir = endpointDir(stateDir);
|
|
@@ -21232,7 +21493,8 @@ async function registerPeriscopeEndpoint(d, p) {
|
|
|
21232
21493
|
const out = await registerEndpoint(api, p.workspaceId, stored?.endpointId, {
|
|
21233
21494
|
label: `Periscope: ${d.shipName} ${p.ticket}`,
|
|
21234
21495
|
url,
|
|
21235
|
-
mcpSecret: secret
|
|
21496
|
+
mcpSecret: secret,
|
|
21497
|
+
discoverable: false
|
|
21236
21498
|
});
|
|
21237
21499
|
if (out.kind !== "ok") {
|
|
21238
21500
|
return { ok: false, message: out.kind === "forbidden" ? "registering the endpoint was refused (403): the route key lacks REGISTER_MCP_ENDPOINTS" : out.message };
|
|
@@ -21302,7 +21564,7 @@ async function realAncestor(p) {
|
|
|
21302
21564
|
}
|
|
21303
21565
|
const parent = dirname24(cur);
|
|
21304
21566
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
21305
|
-
tail3.push(
|
|
21567
|
+
tail3.push(basename14(cur));
|
|
21306
21568
|
cur = parent;
|
|
21307
21569
|
}
|
|
21308
21570
|
}
|
|
@@ -21312,13 +21574,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
21312
21574
|
let abs;
|
|
21313
21575
|
if (isAbsolute3(requested)) {
|
|
21314
21576
|
abs = resolve5(requested);
|
|
21315
|
-
if (isInside(root, abs)) abs =
|
|
21577
|
+
if (isInside(root, abs)) abs = join47(realRoot, relative2(root, abs));
|
|
21316
21578
|
} else {
|
|
21317
21579
|
abs = resolve5(realRoot, requested);
|
|
21318
21580
|
}
|
|
21319
21581
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
21320
21582
|
const { real, rest } = await realAncestor(abs);
|
|
21321
|
-
const final = rest ?
|
|
21583
|
+
const final = rest ? join47(real, rest) : real;
|
|
21322
21584
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
21323
21585
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
21324
21586
|
return final;
|
|
@@ -21386,7 +21648,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
21386
21648
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
21387
21649
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
21388
21650
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
21389
|
-
const size = (await lstat(
|
|
21651
|
+
const size = (await lstat(join47(abs, e.name))).size;
|
|
21390
21652
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
21391
21653
|
}));
|
|
21392
21654
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -21452,8 +21714,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
21452
21714
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
21453
21715
|
}, async ({ path }) => {
|
|
21454
21716
|
const parent = await inTree(dirname24(path) === "" ? "." : dirname24(path));
|
|
21455
|
-
const abs =
|
|
21456
|
-
if (
|
|
21717
|
+
const abs = join47(parent, basename14(path));
|
|
21718
|
+
if (basename14(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
21457
21719
|
const st = await lstat(abs).catch(() => void 0);
|
|
21458
21720
|
if (!st) return text(`${path} does not exist`, true);
|
|
21459
21721
|
if (st.isDirectory()) return text(`${path} is a directory; only files are deleted`, true);
|
|
@@ -22047,6 +22309,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22047
22309
|
shipName: cfg.ship.name,
|
|
22048
22310
|
apiFor: endpointApiFor(routes),
|
|
22049
22311
|
urlFor: (slug) => publicUrlFor(slug, relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain),
|
|
22312
|
+
controlUrlFor: (slug) => `${relayBaseUrlFor(slug, relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain)}/control`,
|
|
22050
22313
|
warn: (m) => emit.warn(m, { step: "passengers" })
|
|
22051
22314
|
};
|
|
22052
22315
|
}, releaseRefused = function() {
|
|
@@ -22151,7 +22414,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22151
22414
|
stateDir: cfg.ship.stateDir,
|
|
22152
22415
|
configFile: cfg.configFile,
|
|
22153
22416
|
readFile: (p) => readFileSync39(p, "utf8"),
|
|
22154
|
-
writeFile: (p, t) =>
|
|
22417
|
+
writeFile: (p, t) => writeFileSync32(p, t),
|
|
22155
22418
|
stdin: () => readFileSync39(0, "utf8"),
|
|
22156
22419
|
env: process.env
|
|
22157
22420
|
}
|
|
@@ -22435,7 +22698,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22435
22698
|
}
|
|
22436
22699
|
if (host === "linux" && hasSystemd) {
|
|
22437
22700
|
const label = labelFor(CREW_HOME, "run");
|
|
22438
|
-
const staleTimer =
|
|
22701
|
+
const staleTimer = join48(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
22439
22702
|
if (existsSync45(staleTimer)) {
|
|
22440
22703
|
process.stdout.write(
|
|
22441
22704
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -22498,7 +22761,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22498
22761
|
for (const a of plan.actions) {
|
|
22499
22762
|
if (a.kind === "enable") {
|
|
22500
22763
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
22501
|
-
|
|
22764
|
+
writeFileSync32(cfg.configFile, planEnable(readFileSync39(cfg.configFile, "utf8"), a.route));
|
|
22502
22765
|
enabledSound.add(a.route);
|
|
22503
22766
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
22504
22767
|
} else {
|
|
@@ -23621,7 +23884,7 @@ ${cu.tail.trimEnd()}
|
|
|
23621
23884
|
process.exit(1);
|
|
23622
23885
|
}
|
|
23623
23886
|
requireArmed("run the daemon loop");
|
|
23624
|
-
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")];
|
|
23625
23888
|
const configPaths = daemonConfigPaths(cfg);
|
|
23626
23889
|
watchPaths.push(...configPaths);
|
|
23627
23890
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -23920,7 +24183,7 @@ ${cu.tail.trimEnd()}
|
|
|
23920
24183
|
const raw = existsSync45(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
|
|
23921
24184
|
raw.agentSkills = result.agentSkills;
|
|
23922
24185
|
mkdirSync29(dirOf2(resolvedPath), { recursive: true });
|
|
23923
|
-
|
|
24186
|
+
writeFileSync32(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
23924
24187
|
`);
|
|
23925
24188
|
}
|
|
23926
24189
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -23943,7 +24206,7 @@ ${cu.tail.trimEnd()}
|
|
|
23943
24206
|
throw e;
|
|
23944
24207
|
}
|
|
23945
24208
|
const oldName = cfg.ship.name;
|
|
23946
|
-
|
|
24209
|
+
writeFileSync32(cfg.configFile, setShipNameInYaml(readFileSync39(cfg.configFile, "utf8"), newName));
|
|
23947
24210
|
cfg.ship.name = newName;
|
|
23948
24211
|
let renameMachineId;
|
|
23949
24212
|
try {
|
|
@@ -24072,19 +24335,19 @@ ${cu.tail.trimEnd()}
|
|
|
24072
24335
|
let cleared;
|
|
24073
24336
|
if (clear2) {
|
|
24074
24337
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
24075
|
-
if (plan.removed.length > 0)
|
|
24338
|
+
if (plan.removed.length > 0) writeFileSync32(cfg.configFile, plan.text);
|
|
24076
24339
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
24077
24340
|
}
|
|
24078
24341
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
24079
24342
|
if (write === "route") {
|
|
24080
24343
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
24081
|
-
|
|
24344
|
+
writeFileSync32(cfg.configFile, plan.text);
|
|
24082
24345
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
24083
24346
|
} else if (write === "repo") {
|
|
24084
24347
|
const have = existingRepoConfig(files);
|
|
24085
24348
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
24086
24349
|
const file = resolvePath(dir, ".crew.yaml");
|
|
24087
|
-
|
|
24350
|
+
writeFileSync32(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
24088
24351
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
24089
24352
|
}
|
|
24090
24353
|
if (flag("json")) {
|
|
@@ -24251,7 +24514,7 @@ ${cu.tail.trimEnd()}
|
|
|
24251
24514
|
throw new ReposError(`${e.message}${authHint}
|
|
24252
24515
|
crew.yaml was not changed`);
|
|
24253
24516
|
}
|
|
24254
|
-
|
|
24517
|
+
writeFileSync32(cfg.configFile, plan2.text);
|
|
24255
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:
|
|
24256
24519
|
`);
|
|
24257
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}`);
|
|
@@ -24314,11 +24577,11 @@ ${cu.tail.trimEnd()}
|
|
|
24314
24577
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
24315
24578
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
24316
24579
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
24317
|
-
|
|
24580
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
24318
24581
|
`);
|
|
24319
24582
|
}
|
|
24320
24583
|
}
|
|
24321
|
-
|
|
24584
|
+
writeFileSync32(cfg.configFile, plan.text);
|
|
24322
24585
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
24323
24586
|
`);
|
|
24324
24587
|
} catch (e) {
|
|
@@ -24687,7 +24950,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24687
24950
|
process.exit(2);
|
|
24688
24951
|
}
|
|
24689
24952
|
if (found.shipNameAdopted && !dryRun) {
|
|
24690
|
-
|
|
24953
|
+
writeFileSync32(cfg.configFile, setShipNameInYaml(readFileSync39(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
24691
24954
|
cfg.ship.name = found.shipNameAdopted;
|
|
24692
24955
|
}
|
|
24693
24956
|
if (found.shipRow) {
|
|
@@ -24866,7 +25129,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24866
25129
|
mkdirSync29(dirname25(resolvedPath), { recursive: true });
|
|
24867
25130
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
24868
25131
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
24869
|
-
|
|
25132
|
+
writeFileSync32(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
24870
25133
|
`);
|
|
24871
25134
|
renameSync5(tmpPath, resolvedPath);
|
|
24872
25135
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -24913,7 +25176,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
24913
25176
|
`);
|
|
24914
25177
|
} else {
|
|
24915
25178
|
mkdirSync29(dirname25(apiKeyFile), { recursive: true });
|
|
24916
|
-
|
|
25179
|
+
writeFileSync32(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
24917
25180
|
`, { mode: 384 });
|
|
24918
25181
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
24919
25182
|
`);
|
|
@@ -25743,10 +26006,10 @@ ${where.join("\n")}`);
|
|
|
25743
26006
|
},
|
|
25744
26007
|
prepare: async (path) => {
|
|
25745
26008
|
for (const f of repoP.config.worktrees.copy) {
|
|
25746
|
-
const src =
|
|
26009
|
+
const src = join48(repoP.dir, f);
|
|
25747
26010
|
if (!existsSync45(src)) continue;
|
|
25748
|
-
mkdirSync29(dirOf2(
|
|
25749
|
-
copyFileSync2(src,
|
|
26011
|
+
mkdirSync29(dirOf2(join48(path, f)), { recursive: true });
|
|
26012
|
+
copyFileSync2(src, join48(path, f));
|
|
25750
26013
|
}
|
|
25751
26014
|
const setup = repoP.config.hooks.setup;
|
|
25752
26015
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -26095,7 +26358,7 @@ ${where.join("\n")}`);
|
|
|
26095
26358
|
break;
|
|
26096
26359
|
}
|
|
26097
26360
|
mkdirSync29(cfg.ship.stateDir, { recursive: true });
|
|
26098
|
-
|
|
26361
|
+
writeFileSync32(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
26099
26362
|
`);
|
|
26100
26363
|
process.stdout.write(
|
|
26101
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"
|
|
@@ -26200,7 +26463,7 @@ ${where.join("\n")}`);
|
|
|
26200
26463
|
}
|
|
26201
26464
|
if (configured2.some((r) => r.dir === dir)) {
|
|
26202
26465
|
try {
|
|
26203
|
-
|
|
26466
|
+
writeFileSync32(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
26204
26467
|
} catch (e) {
|
|
26205
26468
|
if (e instanceof ReposError) fail(e.message);
|
|
26206
26469
|
throw e;
|
|
@@ -26212,16 +26475,16 @@ ${where.join("\n")}`);
|
|
|
26212
26475
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
26213
26476
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
26214
26477
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
26215
|
-
|
|
26478
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26216
26479
|
`);
|
|
26217
26480
|
}
|
|
26218
26481
|
}
|
|
26219
|
-
if (existsSync45(
|
|
26482
|
+
if (existsSync45(join48(dir, ".git")) && basename15(dir) === SAMPLE_REPO) rmSync22(dir, { recursive: true, force: true });
|
|
26220
26483
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
26221
26484
|
break;
|
|
26222
26485
|
}
|
|
26223
26486
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
26224
|
-
const fresh = !existsSync45(
|
|
26487
|
+
const fresh = !existsSync45(join48(dir, ".git"));
|
|
26225
26488
|
if (existsSync45(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
26226
26489
|
if (dryRun) {
|
|
26227
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`);
|
|
@@ -26229,8 +26492,8 @@ ${where.join("\n")}`);
|
|
|
26229
26492
|
}
|
|
26230
26493
|
if (fresh) {
|
|
26231
26494
|
mkdirSync29(dir, { recursive: true });
|
|
26232
|
-
|
|
26233
|
-
|
|
26495
|
+
writeFileSync32(join48(dir, "README.md"), SAMPLE_README);
|
|
26496
|
+
writeFileSync32(join48(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
26234
26497
|
const g = (...a) => {
|
|
26235
26498
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
26236
26499
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -26252,13 +26515,13 @@ ${where.join("\n")}`);
|
|
|
26252
26515
|
const raw = JSON.parse(readFileSync39(resolvedFile, "utf8"));
|
|
26253
26516
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
26254
26517
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
26255
|
-
|
|
26518
|
+
writeFileSync32(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26256
26519
|
`);
|
|
26257
26520
|
}
|
|
26258
26521
|
}
|
|
26259
26522
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
26260
26523
|
try {
|
|
26261
|
-
|
|
26524
|
+
writeFileSync32(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
26262
26525
|
} catch (e) {
|
|
26263
26526
|
if (e instanceof ReposError) fail(e.message);
|
|
26264
26527
|
throw e;
|
|
@@ -26493,7 +26756,7 @@ ${where.join("\n")}`);
|
|
|
26493
26756
|
// `synthesis-tabl-123`. `worktreePrefix` above is kept for
|
|
26494
26757
|
// config-provenance display; this is the field that actually
|
|
26495
26758
|
// describes where a worktree lands by default.
|
|
26496
|
-
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>`),
|
|
26497
26760
|
worktreeParent: resolvePath(r.dir, "..")
|
|
26498
26761
|
})),
|
|
26499
26762
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
@@ -26751,7 +27014,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
26751
27014
|
);
|
|
26752
27015
|
for (const r of await resolvedRepos(route2)) {
|
|
26753
27016
|
const { config } = r;
|
|
26754
|
-
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)}`);
|
|
26755
27018
|
process.stdout.write(
|
|
26756
27019
|
`${`repo ${r.name}:`.padEnd(19)}${r.dir}
|
|
26757
27020
|
worktrees at ../${worktreePattern} (${config.provenance["worktrees.name"] ?? config.provenance["worktrees.prefix"] ?? "default"}), branch ${config.branch.name}
|
|
@@ -27073,6 +27336,13 @@ ${foreign.join("\n")}
|
|
|
27073
27336
|
`);
|
|
27074
27337
|
}
|
|
27075
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
|
+
}
|
|
27076
27346
|
const passengerRoutes = cfg.routes.filter((r) => r.enabled && r.hostPassengers);
|
|
27077
27347
|
if (passengerRoutes.length === 0) {
|
|
27078
27348
|
process.stdout.write("host passengers: off (no enabled route sets hostPassengers: true)\n");
|
|
@@ -27122,6 +27392,9 @@ ${foreign.join("\n")}
|
|
|
27122
27392
|
` ${r.route} url: ${slug ? publicUrlFor(slug, cfg.ship.relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain) : "not minted yet (first tunnel cycle creates it)"}
|
|
27123
27393
|
`
|
|
27124
27394
|
);
|
|
27395
|
+
const controlUrl = slug && readPersistedTunnel(cfg.ship.stateDir, ws)?.controlSocket ? `${relayBaseUrlFor(slug, cfg.ship.relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain)}/control` : void 0;
|
|
27396
|
+
process.stdout.write(` ${r.route} control url: ${controlUrl ?? "none (the tunnel carries passengers only)"}
|
|
27397
|
+
`);
|
|
27125
27398
|
const api = endpointApiFor([r])(ws);
|
|
27126
27399
|
if (!api) continue;
|
|
27127
27400
|
const probe = await probeEndpointAccess(api, ws);
|
|
@@ -27140,7 +27413,7 @@ ${foreign.join("\n")}
|
|
|
27140
27413
|
}
|
|
27141
27414
|
for (const e of listPeriscopeEndpoints(cfg.ship.stateDir)) {
|
|
27142
27415
|
const secret = readPeriscopeSecret(cfg.ship.stateDir, e.ticketRecordId) ? "live" : "ended, awaiting removal";
|
|
27143
|
-
process.stdout.write(`periscope mcp: ${e.ticket} ${e.route} ${e.url} (${secret})
|
|
27416
|
+
process.stdout.write(`periscope mcp: ${e.ticket} ${e.route} ${e.url} (${secret}, attached only)
|
|
27144
27417
|
`);
|
|
27145
27418
|
}
|
|
27146
27419
|
if (cfg.ship.relayHost && !flag("offline")) {
|