@tablation/crew 0.81.0 → 0.82.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 +315 -237
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6479,7 +6479,7 @@ var Tracker = class {
|
|
|
6479
6479
|
* `beatIdle` in a `finally` — the same shape as `setCrewStatus`'s
|
|
6480
6480
|
* working/idle pair, one level up (ship, not seat).
|
|
6481
6481
|
*/
|
|
6482
|
-
async beatEngaged(shipName2, connectionName, ticketRecordId) {
|
|
6482
|
+
async beatEngaged(shipName2, connectionName, ticketRecordId, since2) {
|
|
6483
6483
|
const row = await this.myShipRow(shipName2);
|
|
6484
6484
|
if (!row) return;
|
|
6485
6485
|
await this.client.records.update(this.route.resolved.shipsModelId, row.id, {
|
|
@@ -6487,7 +6487,7 @@ var Tracker = class {
|
|
|
6487
6487
|
host: hostname(),
|
|
6488
6488
|
pid: process.pid,
|
|
6489
6489
|
engaged: true,
|
|
6490
|
-
engaged_since: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6490
|
+
engaged_since: since2 ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6491
6491
|
engaged_connection: connectionName,
|
|
6492
6492
|
engaged_ticket_id: ticketRecordId,
|
|
6493
6493
|
...this.attentionPatch(row)
|
|
@@ -8561,6 +8561,8 @@ function planAgentRun(o) {
|
|
|
8561
8561
|
eventsPath: join19(streamsDir, `${base}.events.jsonl`),
|
|
8562
8562
|
ticket: o.ticket,
|
|
8563
8563
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
8564
|
+
repo: o.repo ?? null,
|
|
8565
|
+
routeName: o.route.route,
|
|
8564
8566
|
agentLog,
|
|
8565
8567
|
promptVersion: o.route.resolved?.agentPersonas?.[o.role]?.historyId,
|
|
8566
8568
|
promptSha
|
|
@@ -8975,6 +8977,8 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
8975
8977
|
route: plan.route,
|
|
8976
8978
|
ticket: plan.ticket ?? null,
|
|
8977
8979
|
ticketRecordId: plan.ticketRecordId ?? null,
|
|
8980
|
+
repo: plan.repo ?? null,
|
|
8981
|
+
routeName: plan.routeName,
|
|
8978
8982
|
model: plan.model,
|
|
8979
8983
|
toolchain: id.toolchain,
|
|
8980
8984
|
harness: id.harness,
|
|
@@ -9546,7 +9550,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
9546
9550
|
}
|
|
9547
9551
|
|
|
9548
9552
|
// src/cli.ts
|
|
9549
|
-
import { resolve as resolve5, dirname as dirname20, join as
|
|
9553
|
+
import { resolve as resolve5, dirname as dirname20, join as join38, basename as basename12 } from "path";
|
|
9550
9554
|
import { fileURLToPath } from "url";
|
|
9551
9555
|
import { hostname as hostname2, homedir as homedir10 } from "os";
|
|
9552
9556
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -13379,6 +13383,164 @@ Passed: ${passed}.${willClose ? " Closing this alert." : ""}`,
|
|
|
13379
13383
|
return out;
|
|
13380
13384
|
}
|
|
13381
13385
|
|
|
13386
|
+
// src/live-runs.ts
|
|
13387
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
|
|
13388
|
+
import { join as join28 } from "path";
|
|
13389
|
+
|
|
13390
|
+
// src/interrupted.ts
|
|
13391
|
+
import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
|
|
13392
|
+
import { join as join27 } from "path";
|
|
13393
|
+
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
13394
|
+
function interruptedComment(b) {
|
|
13395
|
+
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.";
|
|
13396
|
+
return `${INTERRUPTED_MARKER}
|
|
13397
|
+
|
|
13398
|
+
The agent run on this ticket was cut short (${b.reason}) before it handed on${b.branch ? `; its branch is \`${b.branch}\`` : ""}. ${tree} The next run on this seat resumes from the worktree rather than starting over.`;
|
|
13399
|
+
}
|
|
13400
|
+
var handled = /* @__PURE__ */ new Set();
|
|
13401
|
+
var pidAlive = (pid) => {
|
|
13402
|
+
try {
|
|
13403
|
+
process.kill(pid, 0);
|
|
13404
|
+
return true;
|
|
13405
|
+
} catch (e) {
|
|
13406
|
+
return e.code === "EPERM";
|
|
13407
|
+
}
|
|
13408
|
+
};
|
|
13409
|
+
function runFinished(streamPath) {
|
|
13410
|
+
const path = `${streamPath}.meta.json`;
|
|
13411
|
+
if (!existsSync23(path)) return false;
|
|
13412
|
+
try {
|
|
13413
|
+
return JSON.parse(readFileSync21(path, "utf8")).running !== true;
|
|
13414
|
+
} catch {
|
|
13415
|
+
return true;
|
|
13416
|
+
}
|
|
13417
|
+
}
|
|
13418
|
+
function orphanedStreams(o) {
|
|
13419
|
+
const dir = join27(o.stateDir, "streams");
|
|
13420
|
+
if (!existsSync23(dir)) return [];
|
|
13421
|
+
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
13422
|
+
const out = [];
|
|
13423
|
+
for (const name of readdirSync9(dir)) {
|
|
13424
|
+
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
13425
|
+
const streamPath = join27(dir, name);
|
|
13426
|
+
if (runFinished(streamPath)) continue;
|
|
13427
|
+
const sidecar = readRunSidecar(streamPath);
|
|
13428
|
+
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
13429
|
+
if (ownerAlive(sidecar.ownerPid)) continue;
|
|
13430
|
+
out.push({ streamPath, sidecar });
|
|
13431
|
+
}
|
|
13432
|
+
return out;
|
|
13433
|
+
}
|
|
13434
|
+
async function reconcileRuns(o) {
|
|
13435
|
+
let adopted = 0;
|
|
13436
|
+
let interrupted = 0;
|
|
13437
|
+
for (const { streamPath, sidecar } of orphanedStreams(o)) {
|
|
13438
|
+
if (handled.has(streamPath)) continue;
|
|
13439
|
+
handled.add(streamPath);
|
|
13440
|
+
const isAlive = o.isAlive ?? runIsAlive;
|
|
13441
|
+
const lock = isAlive(sidecar) ? o.state.acquireRun(sidecar.role, o.maxConcurrentAgents, o.roleConcurrency?.[sidecar.role] ?? 1) : null;
|
|
13442
|
+
const ticketLock = isAlive(sidecar) && sidecar.ticketRecordId ? o.state.acquireTicket(sidecar.ticketRecordId) : null;
|
|
13443
|
+
const work = (async () => {
|
|
13444
|
+
try {
|
|
13445
|
+
const result = await reattachRun(streamPath, sidecar, o.emit, {
|
|
13446
|
+
agentLogKey: await o.agentLogKey?.(sidecar.role).catch(() => void 0),
|
|
13447
|
+
isAlive: o.isAlive,
|
|
13448
|
+
pollMs: o.pollMs,
|
|
13449
|
+
onAdopted: () => o.emit.emit(`adopted ${sidecar.role} run (pid ${sidecar.pid}) left by a previous crew process`, { step: "agent", role: sidecar.role })
|
|
13450
|
+
});
|
|
13451
|
+
if (result.adopted) adopted++;
|
|
13452
|
+
else interrupted++;
|
|
13453
|
+
try {
|
|
13454
|
+
await o.afterRun?.(sidecar, result);
|
|
13455
|
+
} catch (e) {
|
|
13456
|
+
o.emit.warn(`could not follow up on ${sidecar.role} run ${streamPath}: ${e.message}`, { step: "agent" });
|
|
13457
|
+
}
|
|
13458
|
+
} catch (e) {
|
|
13459
|
+
o.emit.warn(`could not reattach ${sidecar.role} run ${streamPath}: ${e.message}`, { step: "agent" });
|
|
13460
|
+
} finally {
|
|
13461
|
+
if (lock?.ok) lock.release();
|
|
13462
|
+
if (ticketLock?.ok) ticketLock.release();
|
|
13463
|
+
}
|
|
13464
|
+
})();
|
|
13465
|
+
if (isAlive(sidecar)) {
|
|
13466
|
+
if (o.inFlight) {
|
|
13467
|
+
o.inFlight.add(work);
|
|
13468
|
+
void work.finally(() => o.inFlight.delete(work));
|
|
13469
|
+
}
|
|
13470
|
+
} else {
|
|
13471
|
+
await work;
|
|
13472
|
+
}
|
|
13473
|
+
}
|
|
13474
|
+
return { adopted, interrupted };
|
|
13475
|
+
}
|
|
13476
|
+
|
|
13477
|
+
// src/live-runs.ts
|
|
13478
|
+
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
13479
|
+
const dir = join28(stateDir, "streams");
|
|
13480
|
+
if (!existsSync24(dir)) return [];
|
|
13481
|
+
const out = [];
|
|
13482
|
+
for (const name of readdirSync10(dir)) {
|
|
13483
|
+
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
13484
|
+
const streamPath = join28(dir, name);
|
|
13485
|
+
if (runFinished(streamPath)) continue;
|
|
13486
|
+
const sidecar = readRunSidecar(streamPath);
|
|
13487
|
+
if (!sidecar || !isAlive(sidecar)) continue;
|
|
13488
|
+
out.push({ ...sidecar, streamPath });
|
|
13489
|
+
}
|
|
13490
|
+
return out.sort((a, b) => a.spawnedAt.localeCompare(b.spawnedAt));
|
|
13491
|
+
}
|
|
13492
|
+
function siblingRuns(runs, ownTicketRecordId) {
|
|
13493
|
+
return runs.filter((r) => !ownTicketRecordId || r.ticketRecordId !== ownTicketRecordId);
|
|
13494
|
+
}
|
|
13495
|
+
async function settleRunEnd(o) {
|
|
13496
|
+
const { tracker, emit } = o;
|
|
13497
|
+
const all = o.runs ?? liveRuns(o.stateDir);
|
|
13498
|
+
const slug = routeSlug(o.route.route);
|
|
13499
|
+
const seatSiblings = siblingRuns(all.filter((r) => r.role === o.role && r.route === slug), o.ownTicketRecordId);
|
|
13500
|
+
const shipSiblings = siblingRuns(all, o.ownTicketRecordId);
|
|
13501
|
+
const newest = (rs) => rs[rs.length - 1];
|
|
13502
|
+
if (o.memberId) {
|
|
13503
|
+
try {
|
|
13504
|
+
if (seatSiblings.length) await tracker.setCrewStatus(o.memberId, "working", newest(seatSiblings).ticketRecordId);
|
|
13505
|
+
else await tracker.setCrewStatus(o.memberId, "idle");
|
|
13506
|
+
} catch (e) {
|
|
13507
|
+
emit.warn(`could not clear crew status: ${e.message}`, { step: "agent" });
|
|
13508
|
+
}
|
|
13509
|
+
}
|
|
13510
|
+
try {
|
|
13511
|
+
if (shipSiblings.length) {
|
|
13512
|
+
const n = newest(shipSiblings);
|
|
13513
|
+
await tracker.beatEngaged(o.shipName, n.routeName ?? n.route, n.ticketRecordId, n.spawnedAt);
|
|
13514
|
+
} else {
|
|
13515
|
+
await tracker.beatIdle(o.shipName);
|
|
13516
|
+
}
|
|
13517
|
+
} catch (e) {
|
|
13518
|
+
emit.warn(`could not beat ship idle: ${e.message}`, { step: "agent" });
|
|
13519
|
+
}
|
|
13520
|
+
}
|
|
13521
|
+
function describeRun(run2, route, seatIds, worktreeOf = (d, n) => {
|
|
13522
|
+
try {
|
|
13523
|
+
return worktreeForNumber(d, n)?.path ?? null;
|
|
13524
|
+
} catch {
|
|
13525
|
+
return null;
|
|
13526
|
+
}
|
|
13527
|
+
}) {
|
|
13528
|
+
const ownRoute = run2.route === routeSlug(route.route);
|
|
13529
|
+
const repoDir = run2.repo ? reposOf(route).find((t) => t.name === run2.repo)?.dir : void 0;
|
|
13530
|
+
const num = run2.ticket?.replace(/^\D+/, "") ?? "";
|
|
13531
|
+
return {
|
|
13532
|
+
role: run2.role,
|
|
13533
|
+
seat: ownRoute ? seatIds[run2.role] ?? null : null,
|
|
13534
|
+
ticket: run2.ticket,
|
|
13535
|
+
ticketRecordId: run2.ticketRecordId,
|
|
13536
|
+
repo: run2.repo ?? null,
|
|
13537
|
+
worktree: repoDir && num ? worktreeOf(repoDir, num) : null,
|
|
13538
|
+
stream: run2.streamPath,
|
|
13539
|
+
pid: run2.pid,
|
|
13540
|
+
startedAt: run2.spawnedAt
|
|
13541
|
+
};
|
|
13542
|
+
}
|
|
13543
|
+
|
|
13382
13544
|
// src/projects-cmd.ts
|
|
13383
13545
|
async function buildProjectsReport(routes, open) {
|
|
13384
13546
|
return Promise.all(routes.map(async (r) => {
|
|
@@ -13588,7 +13750,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
13588
13750
|
}
|
|
13589
13751
|
|
|
13590
13752
|
// src/watch.ts
|
|
13591
|
-
import { watch as watchFile, existsSync as
|
|
13753
|
+
import { watch as watchFile, existsSync as existsSync25, statSync as statSync7, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
13592
13754
|
var E = "\x1B[";
|
|
13593
13755
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
13594
13756
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -13689,7 +13851,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
13689
13851
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
13690
13852
|
}
|
|
13691
13853
|
function readFrom(file, from) {
|
|
13692
|
-
if (!
|
|
13854
|
+
if (!existsSync25(file)) return { events: [], offset: from };
|
|
13693
13855
|
const size = statSync7(file).size;
|
|
13694
13856
|
if (size < from) return readFrom(file, 0);
|
|
13695
13857
|
if (size === from) return { events: [], offset: from };
|
|
@@ -13932,17 +14094,17 @@ function nextRoles(decision, ran) {
|
|
|
13932
14094
|
}
|
|
13933
14095
|
|
|
13934
14096
|
// src/ship-name.ts
|
|
13935
|
-
import { existsSync as
|
|
14097
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync23, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
|
|
13936
14098
|
import { dirname as dirname14 } from "path";
|
|
13937
14099
|
import { parseDocument } from "yaml";
|
|
13938
14100
|
|
|
13939
14101
|
// src/machine-id.ts
|
|
13940
14102
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
13941
14103
|
import { createHash as createHash4 } from "crypto";
|
|
13942
|
-
import { existsSync as
|
|
13943
|
-
import { dirname as dirname13, join as
|
|
14104
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync15, readFileSync as readFileSync22, writeFileSync as writeFileSync17 } from "fs";
|
|
14105
|
+
import { dirname as dirname13, join as join29 } from "path";
|
|
13944
14106
|
function machineIdPathFor(stateDir) {
|
|
13945
|
-
return
|
|
14107
|
+
return join29(stateDir, "machine-id");
|
|
13946
14108
|
}
|
|
13947
14109
|
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
13948
14110
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -13955,8 +14117,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13955
14117
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13956
14118
|
}
|
|
13957
14119
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13958
|
-
if (
|
|
13959
|
-
const id =
|
|
14120
|
+
if (existsSync26(p)) {
|
|
14121
|
+
const id = readFileSync22(p, "utf8").trim();
|
|
13960
14122
|
if (id) return id;
|
|
13961
14123
|
}
|
|
13962
14124
|
}
|
|
@@ -13967,8 +14129,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13967
14129
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13968
14130
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13969
14131
|
const path = machineIdPathFor(stateDir);
|
|
13970
|
-
if (
|
|
13971
|
-
const cached =
|
|
14132
|
+
if (existsSync26(path)) {
|
|
14133
|
+
const cached = readFileSync22(path, "utf8").trim();
|
|
13972
14134
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13973
14135
|
}
|
|
13974
14136
|
const raw = (opts.read ?? readRawMachineId)();
|
|
@@ -14046,19 +14208,19 @@ async function syncRouteName(i) {
|
|
|
14046
14208
|
return { status: "ok", ...base, action, name: i.local };
|
|
14047
14209
|
}
|
|
14048
14210
|
function readSyncedName(resolvedFile) {
|
|
14049
|
-
if (!
|
|
14211
|
+
if (!existsSync27(resolvedFile)) return void 0;
|
|
14050
14212
|
try {
|
|
14051
|
-
const v = JSON.parse(
|
|
14213
|
+
const v = JSON.parse(readFileSync23(resolvedFile, "utf8")).shipName;
|
|
14052
14214
|
return typeof v === "string" ? v : void 0;
|
|
14053
14215
|
} catch {
|
|
14054
14216
|
return void 0;
|
|
14055
14217
|
}
|
|
14056
14218
|
}
|
|
14057
14219
|
function writeSyncedName(resolvedFile, name) {
|
|
14058
|
-
if (!
|
|
14220
|
+
if (!existsSync27(resolvedFile)) return;
|
|
14059
14221
|
let raw;
|
|
14060
14222
|
try {
|
|
14061
|
-
raw = JSON.parse(
|
|
14223
|
+
raw = JSON.parse(readFileSync23(resolvedFile, "utf8"));
|
|
14062
14224
|
} catch {
|
|
14063
14225
|
return;
|
|
14064
14226
|
}
|
|
@@ -14073,7 +14235,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
14073
14235
|
async function reconcileShipName(i) {
|
|
14074
14236
|
let local = i.ship.name;
|
|
14075
14237
|
try {
|
|
14076
|
-
local = shipNameOfYaml(
|
|
14238
|
+
local = shipNameOfYaml(readFileSync23(i.configFile, "utf8")) ?? local;
|
|
14077
14239
|
} catch {
|
|
14078
14240
|
}
|
|
14079
14241
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -14097,7 +14259,7 @@ async function reconcileShipName(i) {
|
|
|
14097
14259
|
}
|
|
14098
14260
|
if (result.status !== "ok") return result;
|
|
14099
14261
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
14100
|
-
const text =
|
|
14262
|
+
const text = readFileSync23(i.configFile, "utf8");
|
|
14101
14263
|
writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
|
|
14102
14264
|
i.ship.name = result.name;
|
|
14103
14265
|
if (result.action === "row-wins") {
|
|
@@ -14743,7 +14905,7 @@ function describeCapability(i) {
|
|
|
14743
14905
|
}
|
|
14744
14906
|
|
|
14745
14907
|
// src/agents-sync-run.ts
|
|
14746
|
-
import { existsSync as
|
|
14908
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync24, writeFileSync as writeFileSync19 } from "fs";
|
|
14747
14909
|
import { dirname as dirname15 } from "path";
|
|
14748
14910
|
async function runAgentsSync(target, o) {
|
|
14749
14911
|
const dryRun = o.dryRun ?? false;
|
|
@@ -14777,7 +14939,7 @@ async function runAgentsSync(target, o) {
|
|
|
14777
14939
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
14778
14940
|
if (!dryRun) {
|
|
14779
14941
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
14780
|
-
const raw =
|
|
14942
|
+
const raw = existsSync28(resolvedPath) ? JSON.parse(readFileSync24(resolvedPath, "utf8")) : {};
|
|
14781
14943
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
14782
14944
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
14783
14945
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
@@ -14886,7 +15048,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
14886
15048
|
|
|
14887
15049
|
// src/repos-check.ts
|
|
14888
15050
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
14889
|
-
import { readFileSync as
|
|
15051
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
14890
15052
|
import { basename as basename8 } from "path";
|
|
14891
15053
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
14892
15054
|
function checkHookSyntax(script, shell) {
|
|
@@ -14927,7 +15089,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
14927
15089
|
}
|
|
14928
15090
|
let parsed;
|
|
14929
15091
|
try {
|
|
14930
|
-
parsed = parseRepoConfig(
|
|
15092
|
+
parsed = parseRepoConfig(readFileSync25(file, "utf8"), file);
|
|
14931
15093
|
} catch (e) {
|
|
14932
15094
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
14933
15095
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -14984,7 +15146,7 @@ import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml"
|
|
|
14984
15146
|
|
|
14985
15147
|
// src/repos-cmd.ts
|
|
14986
15148
|
import { homedir as homedir9 } from "os";
|
|
14987
|
-
import { basename as basename9, join as
|
|
15149
|
+
import { basename as basename9, join as join30, resolve as resolve3 } from "path";
|
|
14988
15150
|
import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
|
|
14989
15151
|
var ReposError = class extends Error {
|
|
14990
15152
|
};
|
|
@@ -15262,7 +15424,7 @@ function planRepoClone(i) {
|
|
|
15262
15424
|
}
|
|
15263
15425
|
const ship = doc.get("ship", true);
|
|
15264
15426
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
15265
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
15427
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join30(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
15266
15428
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
15267
15429
|
return { ...add, remote, target };
|
|
15268
15430
|
}
|
|
@@ -15661,7 +15823,7 @@ ${run2.err}` });
|
|
|
15661
15823
|
}
|
|
15662
15824
|
|
|
15663
15825
|
// src/sample-ticket.ts
|
|
15664
|
-
import { basename as basename10, dirname as dirname17, join as
|
|
15826
|
+
import { basename as basename10, dirname as dirname17, join as join31 } from "path";
|
|
15665
15827
|
var SAMPLE_REPO = "crew-sample";
|
|
15666
15828
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
15667
15829
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -15684,8 +15846,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
15684
15846
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
15685
15847
|
`;
|
|
15686
15848
|
function sampleDir(existing, reposBase, workspace) {
|
|
15687
|
-
if (existing.length > 0) return
|
|
15688
|
-
return
|
|
15849
|
+
if (existing.length > 0) return join31(dirname17(existing[0]), SAMPLE_REPO);
|
|
15850
|
+
return join31(reposBase, workspace, SAMPLE_REPO);
|
|
15689
15851
|
}
|
|
15690
15852
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
15691
15853
|
function sampleDescription() {
|
|
@@ -15777,8 +15939,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
15777
15939
|
}
|
|
15778
15940
|
|
|
15779
15941
|
// src/worktree-sweep.ts
|
|
15780
|
-
import { existsSync as
|
|
15781
|
-
import { basename as basename11, dirname as dirname18, join as
|
|
15942
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, readdirSync as readdirSync11 } from "fs";
|
|
15943
|
+
import { basename as basename11, dirname as dirname18, join as join32, resolve as resolve4 } from "path";
|
|
15782
15944
|
function planWorktreeSweep(target, tickets, contract) {
|
|
15783
15945
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
15784
15946
|
const verified = contract.statuses.verified;
|
|
@@ -15837,7 +15999,7 @@ function siblingWorktreeDirs(target) {
|
|
|
15837
15999
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
15838
16000
|
let names;
|
|
15839
16001
|
try {
|
|
15840
|
-
names =
|
|
16002
|
+
names = readdirSync11(parent);
|
|
15841
16003
|
} catch {
|
|
15842
16004
|
return [];
|
|
15843
16005
|
}
|
|
@@ -15845,7 +16007,7 @@ function siblingWorktreeDirs(target) {
|
|
|
15845
16007
|
for (const name of names) {
|
|
15846
16008
|
if (!name.startsWith(stem)) continue;
|
|
15847
16009
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
15848
|
-
const path =
|
|
16010
|
+
const path = join32(parent, name);
|
|
15849
16011
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
15850
16012
|
}
|
|
15851
16013
|
return out;
|
|
@@ -15880,7 +16042,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
15880
16042
|
let removed = 0;
|
|
15881
16043
|
let keptBranches = 0;
|
|
15882
16044
|
for (const a of actions) {
|
|
15883
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
16045
|
+
if (!dryRun && base && !a.keepBranch && existsSync29(a.path) && isHandBackMerge(a.path, base)) {
|
|
15884
16046
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
15885
16047
|
log.emit(
|
|
15886
16048
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -15888,7 +16050,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
15888
16050
|
);
|
|
15889
16051
|
}
|
|
15890
16052
|
}
|
|
15891
|
-
if (!dryRun &&
|
|
16053
|
+
if (!dryRun && existsSync29(a.path)) {
|
|
15892
16054
|
let dirty = false;
|
|
15893
16055
|
try {
|
|
15894
16056
|
dirty = status(a.path).length > 0;
|
|
@@ -15899,7 +16061,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
15899
16061
|
continue;
|
|
15900
16062
|
}
|
|
15901
16063
|
}
|
|
15902
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
16064
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync29(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
15903
16065
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
15904
16066
|
continue;
|
|
15905
16067
|
}
|
|
@@ -15948,7 +16110,7 @@ function isHandBackMerge(path, base) {
|
|
|
15948
16110
|
if (!msgPath) return false;
|
|
15949
16111
|
let first;
|
|
15950
16112
|
try {
|
|
15951
|
-
first =
|
|
16113
|
+
first = readFileSync26(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
15952
16114
|
} catch {
|
|
15953
16115
|
return false;
|
|
15954
16116
|
}
|
|
@@ -16053,14 +16215,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
16053
16215
|
}
|
|
16054
16216
|
|
|
16055
16217
|
// src/stream-sweep.ts
|
|
16056
|
-
import { existsSync as
|
|
16057
|
-
import { join as
|
|
16218
|
+
import { existsSync as existsSync30, readdirSync as readdirSync12, rmSync as rmSync13, statSync as statSync8 } from "fs";
|
|
16219
|
+
import { join as join33 } from "path";
|
|
16058
16220
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
16059
16221
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
16060
|
-
const dir =
|
|
16061
|
-
if (!
|
|
16222
|
+
const dir = join33(stateDir, "streams");
|
|
16223
|
+
if (!existsSync30(dir)) return [];
|
|
16062
16224
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
16063
|
-
return
|
|
16225
|
+
return readdirSync12(dir).map((name) => join33(dir, name)).filter((path) => {
|
|
16064
16226
|
try {
|
|
16065
16227
|
return statSync8(path).mtimeMs < cutoff;
|
|
16066
16228
|
} catch {
|
|
@@ -16086,9 +16248,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
16086
16248
|
return removed;
|
|
16087
16249
|
}
|
|
16088
16250
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
16089
|
-
if (!
|
|
16251
|
+
if (!existsSync30(stateDir)) return [];
|
|
16090
16252
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
16091
|
-
return
|
|
16253
|
+
return readdirSync12(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join33(stateDir, name)).filter((path) => {
|
|
16092
16254
|
try {
|
|
16093
16255
|
return statSync8(path).mtimeMs < cutoff;
|
|
16094
16256
|
} catch {
|
|
@@ -16098,23 +16260,23 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
16098
16260
|
}
|
|
16099
16261
|
|
|
16100
16262
|
// src/cli.ts
|
|
16101
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync31, existsSync as
|
|
16263
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync31, existsSync as existsSync35, mkdirSync as mkdirSync22, writeFileSync as writeFileSync23, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
|
|
16102
16264
|
import { createInterface } from "readline/promises";
|
|
16103
16265
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
16104
16266
|
|
|
16105
16267
|
// src/ssh-keys.ts
|
|
16106
16268
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16107
|
-
import { existsSync as
|
|
16108
|
-
import { dirname as dirname19, join as
|
|
16269
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync18, readFileSync as readFileSync27, chmodSync } from "fs";
|
|
16270
|
+
import { dirname as dirname19, join as join34 } from "path";
|
|
16109
16271
|
var SshKeyError = class extends Error {
|
|
16110
16272
|
};
|
|
16111
16273
|
function shipSshKeyPathFor(stateDir) {
|
|
16112
|
-
return
|
|
16274
|
+
return join34(stateDir, "ssh", "id_ed25519");
|
|
16113
16275
|
}
|
|
16114
16276
|
function ensureShipSshKeypair(stateDir) {
|
|
16115
16277
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
16116
16278
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
16117
|
-
if (!
|
|
16279
|
+
if (!existsSync31(privateKeyPath)) {
|
|
16118
16280
|
mkdirSync18(dirname19(privateKeyPath), { recursive: true });
|
|
16119
16281
|
try {
|
|
16120
16282
|
execFileSync6("ssh-keygen", [
|
|
@@ -16135,10 +16297,10 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
16135
16297
|
}
|
|
16136
16298
|
chmodSync(privateKeyPath, 384);
|
|
16137
16299
|
}
|
|
16138
|
-
if (!
|
|
16300
|
+
if (!existsSync31(publicKeyPath)) {
|
|
16139
16301
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
16140
16302
|
}
|
|
16141
|
-
const publicKey =
|
|
16303
|
+
const publicKey = readFileSync27(publicKeyPath, "utf8").trim();
|
|
16142
16304
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
16143
16305
|
}
|
|
16144
16306
|
function sshKeygenAvailable() {
|
|
@@ -16355,9 +16517,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
16355
16517
|
|
|
16356
16518
|
// src/tunnel.ts
|
|
16357
16519
|
import { spawn as spawn5 } from "child_process";
|
|
16358
|
-
import { existsSync as
|
|
16520
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync19, readFileSync as readFileSync28, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
16359
16521
|
import { randomInt } from "crypto";
|
|
16360
|
-
import { join as
|
|
16522
|
+
import { join as join35 } from "path";
|
|
16361
16523
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
16362
16524
|
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
16363
16525
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -16424,20 +16586,20 @@ function startTunnel(opts) {
|
|
|
16424
16586
|
};
|
|
16425
16587
|
}
|
|
16426
16588
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
16427
|
-
return
|
|
16589
|
+
return join35(stateDir, "passengers", `${workspaceId}.json`);
|
|
16428
16590
|
}
|
|
16429
16591
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
16430
16592
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
16431
|
-
if (!
|
|
16593
|
+
if (!existsSync32(path)) return void 0;
|
|
16432
16594
|
try {
|
|
16433
|
-
return JSON.parse(
|
|
16595
|
+
return JSON.parse(readFileSync28(path, "utf8"));
|
|
16434
16596
|
} catch {
|
|
16435
16597
|
return void 0;
|
|
16436
16598
|
}
|
|
16437
16599
|
}
|
|
16438
16600
|
function writePersistedTunnel(stateDir, state) {
|
|
16439
16601
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
16440
|
-
mkdirSync19(
|
|
16602
|
+
mkdirSync19(join35(stateDir, "passengers"), { recursive: true });
|
|
16441
16603
|
writeFileSync20(path, `${JSON.stringify(state, null, 2)}
|
|
16442
16604
|
`);
|
|
16443
16605
|
}
|
|
@@ -16535,9 +16697,9 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
16535
16697
|
}
|
|
16536
16698
|
}
|
|
16537
16699
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
16538
|
-
const dir =
|
|
16539
|
-
if (!
|
|
16540
|
-
return
|
|
16700
|
+
const dir = join35(stateDir, "passengers");
|
|
16701
|
+
if (!existsSync32(dir)) return [];
|
|
16702
|
+
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
16541
16703
|
}
|
|
16542
16704
|
|
|
16543
16705
|
// src/mcp-endpoint-registry.ts
|
|
@@ -16657,8 +16819,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
16657
16819
|
import { createServer as createHttpServer } from "http";
|
|
16658
16820
|
import { spawn as spawn6 } from "child_process";
|
|
16659
16821
|
import { randomBytes } from "crypto";
|
|
16660
|
-
import { existsSync as
|
|
16661
|
-
import { join as
|
|
16822
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync20, readFileSync as readFileSync29, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync14 } from "fs";
|
|
16823
|
+
import { join as join36 } from "path";
|
|
16662
16824
|
|
|
16663
16825
|
// src/secret-compare.ts
|
|
16664
16826
|
import { timingSafeEqual } from "crypto";
|
|
@@ -16747,20 +16909,20 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
16747
16909
|
return child;
|
|
16748
16910
|
};
|
|
16749
16911
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
16750
|
-
return
|
|
16912
|
+
return join36(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
16751
16913
|
}
|
|
16752
16914
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
16753
16915
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
16754
|
-
if (!
|
|
16916
|
+
if (!existsSync33(path)) return void 0;
|
|
16755
16917
|
try {
|
|
16756
|
-
return JSON.parse(
|
|
16918
|
+
return JSON.parse(readFileSync29(path, "utf8"));
|
|
16757
16919
|
} catch {
|
|
16758
16920
|
return void 0;
|
|
16759
16921
|
}
|
|
16760
16922
|
}
|
|
16761
16923
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
16762
16924
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
16763
|
-
mkdirSync20(
|
|
16925
|
+
mkdirSync20(join36(stateDir, "passengers"), { recursive: true });
|
|
16764
16926
|
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
16765
16927
|
`);
|
|
16766
16928
|
}
|
|
@@ -16781,10 +16943,10 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
16781
16943
|
}
|
|
16782
16944
|
}
|
|
16783
16945
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
16784
|
-
const dir =
|
|
16785
|
-
if (!
|
|
16946
|
+
const dir = join36(stateDir, "passengers");
|
|
16947
|
+
if (!existsSync33(dir)) return [];
|
|
16786
16948
|
const suffix = "-sync.json";
|
|
16787
|
-
return
|
|
16949
|
+
return readdirSync14(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
16788
16950
|
}
|
|
16789
16951
|
function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
16790
16952
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
@@ -16870,98 +17032,9 @@ function describeCause(err) {
|
|
|
16870
17032
|
}
|
|
16871
17033
|
|
|
16872
17034
|
// src/daemon.ts
|
|
16873
|
-
import { existsSync as
|
|
16874
|
-
import { join as
|
|
17035
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync30, readdirSync as readdirSync15, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
|
|
17036
|
+
import { join as join37 } from "path";
|
|
16875
17037
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
16876
|
-
|
|
16877
|
-
// src/interrupted.ts
|
|
16878
|
-
import { existsSync as existsSync32, readdirSync as readdirSync13, readFileSync as readFileSync29 } from "fs";
|
|
16879
|
-
import { join as join35 } from "path";
|
|
16880
|
-
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
16881
|
-
function interruptedComment(b) {
|
|
16882
|
-
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.";
|
|
16883
|
-
return `${INTERRUPTED_MARKER}
|
|
16884
|
-
|
|
16885
|
-
The agent run on this ticket was cut short (${b.reason}) before it handed on${b.branch ? `; its branch is \`${b.branch}\`` : ""}. ${tree} The next run on this seat resumes from the worktree rather than starting over.`;
|
|
16886
|
-
}
|
|
16887
|
-
var handled = /* @__PURE__ */ new Set();
|
|
16888
|
-
var pidAlive = (pid) => {
|
|
16889
|
-
try {
|
|
16890
|
-
process.kill(pid, 0);
|
|
16891
|
-
return true;
|
|
16892
|
-
} catch (e) {
|
|
16893
|
-
return e.code === "EPERM";
|
|
16894
|
-
}
|
|
16895
|
-
};
|
|
16896
|
-
function runFinished(streamPath) {
|
|
16897
|
-
const path = `${streamPath}.meta.json`;
|
|
16898
|
-
if (!existsSync32(path)) return false;
|
|
16899
|
-
try {
|
|
16900
|
-
return JSON.parse(readFileSync29(path, "utf8")).running !== true;
|
|
16901
|
-
} catch {
|
|
16902
|
-
return true;
|
|
16903
|
-
}
|
|
16904
|
-
}
|
|
16905
|
-
function orphanedStreams(o) {
|
|
16906
|
-
const dir = join35(o.stateDir, "streams");
|
|
16907
|
-
if (!existsSync32(dir)) return [];
|
|
16908
|
-
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
16909
|
-
const out = [];
|
|
16910
|
-
for (const name of readdirSync13(dir)) {
|
|
16911
|
-
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
16912
|
-
const streamPath = join35(dir, name);
|
|
16913
|
-
if (runFinished(streamPath)) continue;
|
|
16914
|
-
const sidecar = readRunSidecar(streamPath);
|
|
16915
|
-
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
16916
|
-
if (ownerAlive(sidecar.ownerPid)) continue;
|
|
16917
|
-
out.push({ streamPath, sidecar });
|
|
16918
|
-
}
|
|
16919
|
-
return out;
|
|
16920
|
-
}
|
|
16921
|
-
async function reconcileRuns(o) {
|
|
16922
|
-
let adopted = 0;
|
|
16923
|
-
let interrupted = 0;
|
|
16924
|
-
for (const { streamPath, sidecar } of orphanedStreams(o)) {
|
|
16925
|
-
if (handled.has(streamPath)) continue;
|
|
16926
|
-
handled.add(streamPath);
|
|
16927
|
-
const isAlive = o.isAlive ?? runIsAlive;
|
|
16928
|
-
const lock = isAlive(sidecar) ? o.state.acquireRun(sidecar.role, o.maxConcurrentAgents, o.roleConcurrency?.[sidecar.role] ?? 1) : null;
|
|
16929
|
-
const ticketLock = isAlive(sidecar) && sidecar.ticketRecordId ? o.state.acquireTicket(sidecar.ticketRecordId) : null;
|
|
16930
|
-
const work = (async () => {
|
|
16931
|
-
try {
|
|
16932
|
-
const result = await reattachRun(streamPath, sidecar, o.emit, {
|
|
16933
|
-
agentLogKey: await o.agentLogKey?.(sidecar.role).catch(() => void 0),
|
|
16934
|
-
isAlive: o.isAlive,
|
|
16935
|
-
pollMs: o.pollMs,
|
|
16936
|
-
onAdopted: () => o.emit.emit(`adopted ${sidecar.role} run (pid ${sidecar.pid}) left by a previous crew process`, { step: "agent", role: sidecar.role })
|
|
16937
|
-
});
|
|
16938
|
-
if (result.adopted) adopted++;
|
|
16939
|
-
else interrupted++;
|
|
16940
|
-
try {
|
|
16941
|
-
await o.afterRun?.(sidecar, result);
|
|
16942
|
-
} catch (e) {
|
|
16943
|
-
o.emit.warn(`could not follow up on ${sidecar.role} run ${streamPath}: ${e.message}`, { step: "agent" });
|
|
16944
|
-
}
|
|
16945
|
-
} catch (e) {
|
|
16946
|
-
o.emit.warn(`could not reattach ${sidecar.role} run ${streamPath}: ${e.message}`, { step: "agent" });
|
|
16947
|
-
} finally {
|
|
16948
|
-
if (lock?.ok) lock.release();
|
|
16949
|
-
if (ticketLock?.ok) ticketLock.release();
|
|
16950
|
-
}
|
|
16951
|
-
})();
|
|
16952
|
-
if (isAlive(sidecar)) {
|
|
16953
|
-
if (o.inFlight) {
|
|
16954
|
-
o.inFlight.add(work);
|
|
16955
|
-
void work.finally(() => o.inFlight.delete(work));
|
|
16956
|
-
}
|
|
16957
|
-
} else {
|
|
16958
|
-
await work;
|
|
16959
|
-
}
|
|
16960
|
-
}
|
|
16961
|
-
return { adopted, interrupted };
|
|
16962
|
-
}
|
|
16963
|
-
|
|
16964
|
-
// src/daemon.ts
|
|
16965
17038
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
16966
17039
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
16967
17040
|
var defaultSleep = (ms2) => new Promise((resolve6) => setTimeout(resolve6, ms2));
|
|
@@ -17030,8 +17103,8 @@ function latestMtime(path) {
|
|
|
17030
17103
|
if (stat.isFile()) return stat.mtimeMs;
|
|
17031
17104
|
if (!stat.isDirectory()) return 0;
|
|
17032
17105
|
let latest = stat.mtimeMs;
|
|
17033
|
-
for (const entry of
|
|
17034
|
-
latest = Math.max(latest, latestMtime(
|
|
17106
|
+
for (const entry of readdirSync15(path, { withFileTypes: true })) {
|
|
17107
|
+
latest = Math.max(latest, latestMtime(join37(path, entry.name)));
|
|
17035
17108
|
}
|
|
17036
17109
|
return latest;
|
|
17037
17110
|
}
|
|
@@ -17040,13 +17113,13 @@ function computeTreeSignature(paths) {
|
|
|
17040
17113
|
}
|
|
17041
17114
|
function makeStaleChecker(paths, opts = {}) {
|
|
17042
17115
|
const initial = computeTreeSignature(paths);
|
|
17043
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
17116
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync34(opts.reloadSentinel);
|
|
17044
17117
|
}
|
|
17045
17118
|
function reloadSentinelFor(stateDir) {
|
|
17046
|
-
return
|
|
17119
|
+
return join37(stateDir, "reload-requested");
|
|
17047
17120
|
}
|
|
17048
17121
|
function daemonStampFor(stateDir) {
|
|
17049
|
-
return
|
|
17122
|
+
return join37(stateDir, "daemon.started");
|
|
17050
17123
|
}
|
|
17051
17124
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
17052
17125
|
mkdirSync21(stateDir, { recursive: true });
|
|
@@ -17309,13 +17382,15 @@ async function reconcileForRoute(ctx, o) {
|
|
|
17309
17382
|
await guardRunHandoff(route, ship, tracker, sc.role, sc.ticketRecordId, emit, sc.spawnedAt);
|
|
17310
17383
|
}
|
|
17311
17384
|
} finally {
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17385
|
+
await settleRunEnd({
|
|
17386
|
+
tracker,
|
|
17387
|
+
shipName: ship.name,
|
|
17388
|
+
route,
|
|
17389
|
+
role: sc.role,
|
|
17390
|
+
memberId,
|
|
17391
|
+
ownTicketRecordId: sc.ticketRecordId,
|
|
17392
|
+
stateDir: ship.stateDir,
|
|
17393
|
+
emit
|
|
17319
17394
|
});
|
|
17320
17395
|
}
|
|
17321
17396
|
}
|
|
@@ -17469,18 +17544,16 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
17469
17544
|
await spawnAgent(plan, emit, hintMoved);
|
|
17470
17545
|
await guardRunHandoff(route, ship, tracker, role, workingId, emit, runStart);
|
|
17471
17546
|
} finally {
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
}
|
|
17482
|
-
emit.warn(`could not beat ship idle: ${e.message}`, { step: "agent" });
|
|
17483
|
-
}
|
|
17547
|
+
await settleRunEnd({
|
|
17548
|
+
tracker,
|
|
17549
|
+
shipName: ship.name,
|
|
17550
|
+
route,
|
|
17551
|
+
role,
|
|
17552
|
+
memberId,
|
|
17553
|
+
ownTicketRecordId: workingId,
|
|
17554
|
+
stateDir: ship.stateDir,
|
|
17555
|
+
emit
|
|
17556
|
+
});
|
|
17484
17557
|
}
|
|
17485
17558
|
}
|
|
17486
17559
|
async function buildEnvironment(route, ship, ticket) {
|
|
@@ -17922,7 +17995,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17922
17995
|
} else {
|
|
17923
17996
|
const toleratesNoRoutes = command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
|
|
17924
17997
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset");
|
|
17925
|
-
if (configless && !
|
|
17998
|
+
if (configless && !existsSync35(findConfigFile(CREW_HOME))) {
|
|
17926
17999
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
17927
18000
|
} else {
|
|
17928
18001
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -18160,8 +18233,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
18160
18233
|
}
|
|
18161
18234
|
if (host === "linux" && hasSystemd) {
|
|
18162
18235
|
const label = labelFor(CREW_HOME, "run");
|
|
18163
|
-
const staleTimer =
|
|
18164
|
-
if (
|
|
18236
|
+
const staleTimer = join38(homedir10(), ".config", "systemd", "user", `${label}.timer`);
|
|
18237
|
+
if (existsSync35(staleTimer)) {
|
|
18165
18238
|
process.stdout.write(
|
|
18166
18239
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
18167
18240
|
`
|
|
@@ -18188,7 +18261,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
18188
18261
|
const tracker = new Tracker(c, cfg.ship);
|
|
18189
18262
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
18190
18263
|
for (const r of await resolvedRepos(c)) {
|
|
18191
|
-
if (!
|
|
18264
|
+
if (!existsSync35(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
18192
18265
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
18193
18266
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
18194
18267
|
}
|
|
@@ -18206,7 +18279,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
18206
18279
|
}
|
|
18207
18280
|
async function doctorFix(o) {
|
|
18208
18281
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
18209
|
-
const unitExists = (p) =>
|
|
18282
|
+
const unitExists = (p) => existsSync35(p);
|
|
18210
18283
|
const crontab = o.host === "linux" ? spawnSync7("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
18211
18284
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
|
|
18212
18285
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -18995,18 +19068,16 @@ ${cu.tail.trimEnd()}
|
|
|
18995
19068
|
await guardRunHandoff(w.route, cfg.ship, fleetTracker, w.role, fleetWorkingId, wemit, runStart);
|
|
18996
19069
|
} finally {
|
|
18997
19070
|
dropLock();
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
}
|
|
19008
|
-
wemit.warn(`could not beat ship idle: ${e.message}`, { step: "agent" });
|
|
19009
|
-
}
|
|
19071
|
+
await settleRunEnd({
|
|
19072
|
+
tracker: fleetTracker,
|
|
19073
|
+
shipName: cfg.ship.name,
|
|
19074
|
+
route: w.route,
|
|
19075
|
+
role: w.role,
|
|
19076
|
+
memberId: fleetMemberId,
|
|
19077
|
+
ownTicketRecordId: fleetWorkingId,
|
|
19078
|
+
stateDir: cfg.ship.stateDir,
|
|
19079
|
+
emit: wemit
|
|
19080
|
+
});
|
|
19010
19081
|
}
|
|
19011
19082
|
if (!skipInlineRelease) await releaseFleet();
|
|
19012
19083
|
break;
|
|
@@ -19236,18 +19307,16 @@ ${cu.tail.trimEnd()}
|
|
|
19236
19307
|
await guardRunHandoff(route, cfg.ship, tracker2, current, workingId, emit, runStart);
|
|
19237
19308
|
} finally {
|
|
19238
19309
|
dropLock();
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
19248
|
-
}
|
|
19249
|
-
emit.warn(`could not beat ship idle: ${e.message}`, { step: "agent" });
|
|
19250
|
-
}
|
|
19310
|
+
await settleRunEnd({
|
|
19311
|
+
tracker: tracker2,
|
|
19312
|
+
shipName: cfg.ship.name,
|
|
19313
|
+
route,
|
|
19314
|
+
role: current,
|
|
19315
|
+
memberId,
|
|
19316
|
+
ownTicketRecordId: workingId,
|
|
19317
|
+
stateDir: cfg.ship.stateDir,
|
|
19318
|
+
emit
|
|
19319
|
+
});
|
|
19251
19320
|
}
|
|
19252
19321
|
ran.push(current);
|
|
19253
19322
|
if (ran.length >= maxRoles || explicitRole) break;
|
|
@@ -19331,7 +19400,7 @@ ${cu.tail.trimEnd()}
|
|
|
19331
19400
|
process.exit(1);
|
|
19332
19401
|
}
|
|
19333
19402
|
requireArmed("run the daemon loop");
|
|
19334
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
19403
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join38(CREW_HOME, "src"), join38(CREW_HOME, "dist"), join38(CREW_HOME, "bin")];
|
|
19335
19404
|
const configPaths = daemonConfigPaths(cfg);
|
|
19336
19405
|
watchPaths.push(...configPaths);
|
|
19337
19406
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -19560,7 +19629,7 @@ ${cu.tail.trimEnd()}
|
|
|
19560
19629
|
`);
|
|
19561
19630
|
if (!dryRun) {
|
|
19562
19631
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
19563
|
-
const raw =
|
|
19632
|
+
const raw = existsSync35(resolvedPath) ? JSON.parse(readFileSync31(resolvedPath, "utf8")) : {};
|
|
19564
19633
|
raw.agentSkills = result.agentSkills;
|
|
19565
19634
|
mkdirSync22(dirOf(resolvedPath), { recursive: true });
|
|
19566
19635
|
writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -19664,10 +19733,10 @@ ${cu.tail.trimEnd()}
|
|
|
19664
19733
|
try {
|
|
19665
19734
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
19666
19735
|
const dir = repoDirOf(configText, dirname20(cfg.configFile), a.route, a.name);
|
|
19667
|
-
if (!
|
|
19668
|
-
const files = { exists: (r) =>
|
|
19736
|
+
if (!existsSync35(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
19737
|
+
const files = { exists: (r) => existsSync35(resolvePath(dir, r)), list: (r) => {
|
|
19669
19738
|
try {
|
|
19670
|
-
return
|
|
19739
|
+
return readdirSync16(resolvePath(dir, r));
|
|
19671
19740
|
} catch {
|
|
19672
19741
|
return [];
|
|
19673
19742
|
}
|
|
@@ -19783,7 +19852,7 @@ ${cu.tail.trimEnd()}
|
|
|
19783
19852
|
if (sub === "list" && !routeName) usage2();
|
|
19784
19853
|
if (sub === "add") {
|
|
19785
19854
|
try {
|
|
19786
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
19855
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync35(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
|
|
19787
19856
|
routeName = a.route;
|
|
19788
19857
|
addPath = a.path;
|
|
19789
19858
|
addName = a.name;
|
|
@@ -19812,7 +19881,7 @@ ${cu.tail.trimEnd()}
|
|
|
19812
19881
|
}
|
|
19813
19882
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
19814
19883
|
let resolvedRemotes;
|
|
19815
|
-
if (
|
|
19884
|
+
if (existsSync35(resolvedFile)) {
|
|
19816
19885
|
try {
|
|
19817
19886
|
resolvedRemotes = JSON.parse(readFileSync31(resolvedFile, "utf8"));
|
|
19818
19887
|
} catch {
|
|
@@ -19821,7 +19890,7 @@ ${cu.tail.trimEnd()}
|
|
|
19821
19890
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
19822
19891
|
const text = configText;
|
|
19823
19892
|
const base = dirname20(cfg.configFile);
|
|
19824
|
-
const isCheckout = (d) =>
|
|
19893
|
+
const isCheckout = (d) => existsSync35(`${d}/.git`);
|
|
19825
19894
|
try {
|
|
19826
19895
|
if (sub === "list") {
|
|
19827
19896
|
if (flag("json")) {
|
|
@@ -19831,7 +19900,7 @@ ${cu.tail.trimEnd()}
|
|
|
19831
19900
|
base,
|
|
19832
19901
|
resolved: resolvedRemotes,
|
|
19833
19902
|
originOf,
|
|
19834
|
-
exists: (d) =>
|
|
19903
|
+
exists: (d) => existsSync35(d),
|
|
19835
19904
|
isCheckout,
|
|
19836
19905
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
19837
19906
|
try {
|
|
@@ -19852,7 +19921,7 @@ ${cu.tail.trimEnd()}
|
|
|
19852
19921
|
for (const r of repos) {
|
|
19853
19922
|
const origin2 = originOf(r.dir);
|
|
19854
19923
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
19855
|
-
const note = !
|
|
19924
|
+
const note = !existsSync35(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
19856
19925
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
19857
19926
|
`);
|
|
19858
19927
|
}
|
|
@@ -19864,9 +19933,9 @@ ${cu.tail.trimEnd()}
|
|
|
19864
19933
|
` : `${human}
|
|
19865
19934
|
`);
|
|
19866
19935
|
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
19867
|
-
if (
|
|
19936
|
+
if (existsSync35(plan2.target) && (existsSync35(`${plan2.target}/.git`) || readdirSync16(plan2.target).length > 0)) {
|
|
19868
19937
|
throw new ReposError(
|
|
19869
|
-
|
|
19938
|
+
existsSync35(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
19870
19939
|
);
|
|
19871
19940
|
}
|
|
19872
19941
|
if (dryRun) {
|
|
@@ -19891,7 +19960,7 @@ ${cu.tail.trimEnd()}
|
|
|
19891
19960
|
break;
|
|
19892
19961
|
}
|
|
19893
19962
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
|
|
19894
|
-
const top =
|
|
19963
|
+
const top = existsSync35(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
19895
19964
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
19896
19965
|
const origin = originOf(top);
|
|
19897
19966
|
const plan = planRepoAdd({
|
|
@@ -19943,7 +20012,7 @@ ${cu.tail.trimEnd()}
|
|
|
19943
20012
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
19944
20013
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
19945
20014
|
`);
|
|
19946
|
-
if (
|
|
20015
|
+
if (existsSync35(resolvedFile)) {
|
|
19947
20016
|
const raw = JSON.parse(readFileSync31(resolvedFile, "utf8"));
|
|
19948
20017
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
19949
20018
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
@@ -20360,7 +20429,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20360
20429
|
}
|
|
20361
20430
|
}
|
|
20362
20431
|
let previous;
|
|
20363
|
-
const hadPreviousFile =
|
|
20432
|
+
const hadPreviousFile = existsSync35(resolvedPath);
|
|
20364
20433
|
if (hadPreviousFile) {
|
|
20365
20434
|
try {
|
|
20366
20435
|
previous = JSON.parse(readFileSync31(resolvedPath, "utf8"));
|
|
@@ -21402,7 +21471,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21402
21471
|
}
|
|
21403
21472
|
if (row) {
|
|
21404
21473
|
await tracker.deleteRepoRow(row.id);
|
|
21405
|
-
if (
|
|
21474
|
+
if (existsSync35(resolvedFile)) {
|
|
21406
21475
|
const raw = JSON.parse(readFileSync31(resolvedFile, "utf8"));
|
|
21407
21476
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
21408
21477
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
@@ -21410,21 +21479,21 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21410
21479
|
`);
|
|
21411
21480
|
}
|
|
21412
21481
|
}
|
|
21413
|
-
if (
|
|
21482
|
+
if (existsSync35(join38(dir, ".git")) && basename12(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
|
|
21414
21483
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
21415
21484
|
break;
|
|
21416
21485
|
}
|
|
21417
21486
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
21418
|
-
const fresh = !
|
|
21419
|
-
if (
|
|
21487
|
+
const fresh = !existsSync35(join38(dir, ".git"));
|
|
21488
|
+
if (existsSync35(dir) && fresh && readdirSync16(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
21420
21489
|
if (dryRun) {
|
|
21421
21490
|
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`);
|
|
21422
21491
|
break;
|
|
21423
21492
|
}
|
|
21424
21493
|
if (fresh) {
|
|
21425
21494
|
mkdirSync22(dir, { recursive: true });
|
|
21426
|
-
writeFileSync23(
|
|
21427
|
-
writeFileSync23(
|
|
21495
|
+
writeFileSync23(join38(dir, "README.md"), SAMPLE_README);
|
|
21496
|
+
writeFileSync23(join38(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
21428
21497
|
const g = (...a) => {
|
|
21429
21498
|
const r = spawnSync7("git", a, { cwd: dir, encoding: "utf8" });
|
|
21430
21499
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -21442,7 +21511,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21442
21511
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
21443
21512
|
});
|
|
21444
21513
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
21445
|
-
if (
|
|
21514
|
+
if (existsSync35(resolvedFile)) {
|
|
21446
21515
|
const raw = JSON.parse(readFileSync31(resolvedFile, "utf8"));
|
|
21447
21516
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
21448
21517
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
@@ -21573,6 +21642,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21573
21642
|
const todo = await statusTracker.openTickets().then((open) => operatorTodo(open, statusTracker.contract, route.resolved?.operator ?? "")).catch(() => []);
|
|
21574
21643
|
const projects = projectModeLabels(await statusTracker.projectRowsWithModes().catch(() => []));
|
|
21575
21644
|
const crewMinVersion = await readCrewFloor(route, cfg.ship) ?? null;
|
|
21645
|
+
const runs = liveRuns(cfg.ship.stateDir);
|
|
21576
21646
|
if (flag("json")) {
|
|
21577
21647
|
const nameSync = await reconcileRouteShipName({ route, ship: cfg.ship, configFile: cfg.configFile, readOnly: true });
|
|
21578
21648
|
const repos = await resolvedRepos(route);
|
|
@@ -21590,7 +21660,9 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21590
21660
|
label: ROLE_LABEL[r],
|
|
21591
21661
|
crewId: seatId ?? null,
|
|
21592
21662
|
name: crewRow?.name ?? null,
|
|
21593
|
-
personaName
|
|
21663
|
+
personaName,
|
|
21664
|
+
// Additive (CREW-2822): how many runs this seat has live right now.
|
|
21665
|
+
running: runs.filter((x) => x.role === r && x.route === routeSlug(route.route)).length
|
|
21594
21666
|
};
|
|
21595
21667
|
});
|
|
21596
21668
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -21633,6 +21705,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21633
21705
|
roles: ROLE_NAMES.map((r) => ({ name: r, label: ROLE_LABEL[r], paused: state.isRolePaused(r) })),
|
|
21634
21706
|
seats: seats2
|
|
21635
21707
|
},
|
|
21708
|
+
// Additive (CREW-2822): every live run on this ship. `version` stays 1.
|
|
21709
|
+
runs: runs.map((x) => describeRun(x, route, seatIds)),
|
|
21636
21710
|
watermark: state.watermark(),
|
|
21637
21711
|
route: {
|
|
21638
21712
|
name: route.route,
|
|
@@ -21745,6 +21819,10 @@ watermark: ${state.watermark()}
|
|
|
21745
21819
|
`
|
|
21746
21820
|
);
|
|
21747
21821
|
}
|
|
21822
|
+
for (const x of runs) {
|
|
21823
|
+
process.stdout.write(`running: ${x.role} \u2014 ${x.ticket ?? "(no ticket)"}${x.repo ? ` in ${x.repo}` : ""} (pid ${x.pid}, since ${x.spawnedAt})
|
|
21824
|
+
`);
|
|
21825
|
+
}
|
|
21748
21826
|
if (shipRows.length) {
|
|
21749
21827
|
process.stdout.write("ships:\n");
|
|
21750
21828
|
for (const s of shipRows) {
|
|
@@ -21773,7 +21851,7 @@ watermark: ${state.watermark()}
|
|
|
21773
21851
|
}
|
|
21774
21852
|
for (const r of repos) {
|
|
21775
21853
|
const remote = r.config.branch.remote;
|
|
21776
|
-
if (
|
|
21854
|
+
if (existsSync35(r.dir)) {
|
|
21777
21855
|
pushAuth.push({
|
|
21778
21856
|
route: c.route,
|
|
21779
21857
|
repo: r.name,
|
|
@@ -21834,7 +21912,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
21834
21912
|
`);
|
|
21835
21913
|
try {
|
|
21836
21914
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
21837
|
-
const evSize =
|
|
21915
|
+
const evSize = existsSync35(evFile) ? statSync10(evFile).size : 0;
|
|
21838
21916
|
process.stdout.write(
|
|
21839
21917
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
21840
21918
|
` : "events log: ok\n"
|
package/package.json
CHANGED