@tablation/crew 0.100.1 → 0.100.3
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 +69 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -18451,6 +18451,16 @@ function describeRelayProbe(url, p) {
|
|
|
18451
18451
|
return p.ok ? `relay control: OK ${url}/control/status (${p.status}, ${p.latencyMs} ms)` : `relay control: FAILED ${url}/control/status \u2014 ${p.error}`;
|
|
18452
18452
|
}
|
|
18453
18453
|
|
|
18454
|
+
// src/ship-status.ts
|
|
18455
|
+
function shipIdentityJson(s) {
|
|
18456
|
+
return {
|
|
18457
|
+
id: s.id,
|
|
18458
|
+
kind: s.kind ?? null,
|
|
18459
|
+
instanceClass: s.instance_class ?? null,
|
|
18460
|
+
relayUrl: s.relay_url ?? null
|
|
18461
|
+
};
|
|
18462
|
+
}
|
|
18463
|
+
|
|
18454
18464
|
// src/passenger-containers.ts
|
|
18455
18465
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
18456
18466
|
import { createHash as createHash7 } from "crypto";
|
|
@@ -19188,6 +19198,7 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
19188
19198
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
19189
19199
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
19190
19200
|
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
19201
|
+
var RELOAD_WARN_MS = 10 * 60 * 1e3;
|
|
19191
19202
|
var defaultSleep = (ms3) => new Promise((resolve7) => setTimeout(resolve7, ms3));
|
|
19192
19203
|
async function runDaemonLoop(o) {
|
|
19193
19204
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
@@ -19198,6 +19209,20 @@ async function runDaemonLoop(o) {
|
|
|
19198
19209
|
const now = o.now ?? Date.now;
|
|
19199
19210
|
let transientRun = 0;
|
|
19200
19211
|
let downSince = 0;
|
|
19212
|
+
const staleStop = async () => {
|
|
19213
|
+
const since2 = now();
|
|
19214
|
+
let warned = false;
|
|
19215
|
+
while (o.inFlight && o.inFlight.size > 0) {
|
|
19216
|
+
if (o.signal?.aborted) return false;
|
|
19217
|
+
if (!warned || now() - since2 >= RELOAD_WARN_MS) {
|
|
19218
|
+
o.onStaleDeferred?.({ runs: o.inFlight.size, waitedMs: now() - since2 });
|
|
19219
|
+
warned = true;
|
|
19220
|
+
}
|
|
19221
|
+
const drained = Promise.all([...o.inFlight].map((p) => p.catch(() => void 0))).then(() => void 0);
|
|
19222
|
+
await Promise.race([drained, sleep(RELOAD_WARN_MS)]);
|
|
19223
|
+
}
|
|
19224
|
+
return true;
|
|
19225
|
+
};
|
|
19201
19226
|
for (; ; ) {
|
|
19202
19227
|
if (o.signal?.aborted) return "aborted";
|
|
19203
19228
|
let result;
|
|
@@ -19234,12 +19259,13 @@ async function runDaemonLoop(o) {
|
|
|
19234
19259
|
backoff = floor;
|
|
19235
19260
|
o.onPass?.(result, 0);
|
|
19236
19261
|
if (o.maxPasses !== void 0 && passes >= o.maxPasses) return "maxPasses";
|
|
19262
|
+
if (o.checkStale?.() && await staleStop()) return "stale";
|
|
19237
19263
|
continue;
|
|
19238
19264
|
}
|
|
19239
19265
|
o.onPass?.(result, backoff);
|
|
19240
19266
|
if (o.maxPasses !== void 0 && passes >= o.maxPasses) return "maxPasses";
|
|
19241
19267
|
if (o.signal?.aborted) return "aborted";
|
|
19242
|
-
if (o.checkStale?.()) return "stale";
|
|
19268
|
+
if (o.checkStale?.() && await staleStop()) return "stale";
|
|
19243
19269
|
await sleep(backoff);
|
|
19244
19270
|
backoff = Math.min(backoff * 2, ceiling);
|
|
19245
19271
|
}
|
|
@@ -19269,6 +19295,13 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
19269
19295
|
function reloadSentinelFor(stateDir) {
|
|
19270
19296
|
return join42(stateDir, "reload-requested");
|
|
19271
19297
|
}
|
|
19298
|
+
function readReloadRequestedAt(stateDir) {
|
|
19299
|
+
try {
|
|
19300
|
+
return statSync12(reloadSentinelFor(stateDir)).mtimeMs;
|
|
19301
|
+
} catch {
|
|
19302
|
+
return null;
|
|
19303
|
+
}
|
|
19304
|
+
}
|
|
19272
19305
|
function daemonStampFor(stateDir) {
|
|
19273
19306
|
return join42(stateDir, "daemon.started");
|
|
19274
19307
|
}
|
|
@@ -19443,12 +19476,12 @@ async function runOnePassUnguarded(o) {
|
|
|
19443
19476
|
live.delete(role);
|
|
19444
19477
|
continue;
|
|
19445
19478
|
}
|
|
19446
|
-
|
|
19447
|
-
let ready = () => {
|
|
19479
|
+
let settle = () => {
|
|
19448
19480
|
};
|
|
19449
19481
|
const settled = new Promise((resolve7) => {
|
|
19450
|
-
|
|
19482
|
+
settle = resolve7;
|
|
19451
19483
|
});
|
|
19484
|
+
const ready = (tookTicket2) => settle(tookTicket2);
|
|
19452
19485
|
const run2 = spawn9(role, decision, ctx, { ready }).then(() => {
|
|
19453
19486
|
breaker.recordSuccess(role);
|
|
19454
19487
|
clearShipAttention(o.state, `role_parked:${role}`);
|
|
@@ -19494,18 +19527,20 @@ async function runOnePassUnguarded(o) {
|
|
|
19494
19527
|
}
|
|
19495
19528
|
}).finally(() => {
|
|
19496
19529
|
lock.release();
|
|
19497
|
-
|
|
19530
|
+
settle(void 0);
|
|
19498
19531
|
});
|
|
19499
19532
|
if (o.inFlight) {
|
|
19500
19533
|
const set = o.inFlight;
|
|
19501
19534
|
set.add(run2);
|
|
19502
19535
|
void run2.finally(() => set.delete(run2));
|
|
19503
19536
|
}
|
|
19537
|
+
const reported = TICKET_ROLES.has(role) ? await settled : true;
|
|
19538
|
+
const tookTicket = reported !== false;
|
|
19539
|
+
if (tookTicket) started.push(role);
|
|
19504
19540
|
if (roleCap(role) <= 1) {
|
|
19505
19541
|
live.delete(role);
|
|
19506
19542
|
continue;
|
|
19507
19543
|
}
|
|
19508
|
-
const tookTicket = await settled;
|
|
19509
19544
|
if (tookTicket && count + 1 < roleCap(role)) live.set(role, count + 1);
|
|
19510
19545
|
else live.delete(role);
|
|
19511
19546
|
}
|
|
@@ -19735,6 +19770,8 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
19735
19770
|
);
|
|
19736
19771
|
return;
|
|
19737
19772
|
}
|
|
19773
|
+
} else {
|
|
19774
|
+
hooks?.ready(true);
|
|
19738
19775
|
}
|
|
19739
19776
|
}
|
|
19740
19777
|
const plan = planAgentRun({
|
|
@@ -21238,6 +21275,7 @@ function findSyncRoute(c, name) {
|
|
|
21238
21275
|
function daemonConfigPaths(c) {
|
|
21239
21276
|
const files = [c.configFile];
|
|
21240
21277
|
for (const r of c.routes) if (r.apiKeyFile && !files.includes(r.apiKeyFile)) files.push(r.apiKeyFile);
|
|
21278
|
+
for (const r of c.routes) files.push(resolvedPathFor(c.ship.stateDir, r.route));
|
|
21241
21279
|
return files;
|
|
21242
21280
|
}
|
|
21243
21281
|
var STATE_WRITING_COMMANDS = /* @__PURE__ */ new Set(["connect", "provision", "disconnect", "install", "uninstall", "reload", "release", "merge", "deploy"]);
|
|
@@ -22975,8 +23013,14 @@ ${cu.tail.trimEnd()}
|
|
|
22975
23013
|
log: (m) => emit.warn(m)
|
|
22976
23014
|
});
|
|
22977
23015
|
if (controlServer) emit.emit(`control API listening on ${controlServer.path}`);
|
|
23016
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
22978
23017
|
const stopReason = await runDaemonLoop({
|
|
23018
|
+
inFlight,
|
|
23019
|
+
onStaleDeferred: ({ runs, waitedMs }) => emit.warn(
|
|
23020
|
+
`reload or update pending for ${Math.round(waitedMs / 1e3)}s \u2014 waiting for ${runs} agent run(s) to end before relaunching (\`crew reload --now\` forces it)`
|
|
23021
|
+
),
|
|
22979
23022
|
runPass: () => runOnePass({
|
|
23023
|
+
inFlight,
|
|
22980
23024
|
route: route2,
|
|
22981
23025
|
ship: cfg.ship,
|
|
22982
23026
|
state,
|
|
@@ -25365,7 +25409,9 @@ ${where.join("\n")}`);
|
|
|
25365
25409
|
mkdirSync28(cfg.ship.stateDir, { recursive: true });
|
|
25366
25410
|
writeFileSync30(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
25367
25411
|
`);
|
|
25368
|
-
process.stdout.write(
|
|
25412
|
+
process.stdout.write(
|
|
25413
|
+
"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"
|
|
25414
|
+
);
|
|
25369
25415
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
25370
25416
|
for (const cmd of [control.stopCommand, control.startCommand]) {
|
|
25371
25417
|
const res = spawnSync8(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
@@ -25699,7 +25745,9 @@ ${where.join("\n")}`);
|
|
|
25699
25745
|
rowName: nameSync.rowName ?? null,
|
|
25700
25746
|
nameDrift: nameSync.drift,
|
|
25701
25747
|
// Additive (CREW-2803): the relay URL this route's tunnel is reachable at, or null.
|
|
25702
|
-
relayUrl: routeRelayUrl(route2)
|
|
25748
|
+
relayUrl: routeRelayUrl(route2),
|
|
25749
|
+
// Additive (CREW-2887): when a `crew reload` request was written and not yet honored, else null.
|
|
25750
|
+
reloadRequestedAt: ((at) => at === null ? null : new Date(at).toISOString())(readReloadRequestedAt(cfg.ship.stateDir))
|
|
25703
25751
|
},
|
|
25704
25752
|
// Additive (CREW-1402): what the operator owes. `version` stays 1.
|
|
25705
25753
|
todo: todo.map((i) => ({ ticket: displayKey(i.ticket), title: i.ticket.title ?? null, reasons: i.reasons })),
|
|
@@ -25763,6 +25811,7 @@ ${where.join("\n")}`);
|
|
|
25763
25811
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
25764
25812
|
},
|
|
25765
25813
|
ships: shipRows.map((s) => ({
|
|
25814
|
+
...shipIdentityJson(s),
|
|
25766
25815
|
name: s.name ?? null,
|
|
25767
25816
|
lastSeen: s.last_seen ?? null,
|
|
25768
25817
|
engaged: !!s.engaged,
|
|
@@ -25798,6 +25847,11 @@ watermark: ${state.watermark()}
|
|
|
25798
25847
|
);
|
|
25799
25848
|
for (const r of ["dev", "design", "qa"]) {
|
|
25800
25849
|
if (state.isRolePaused(r)) process.stdout.write(`role ${r}: paused
|
|
25850
|
+
`);
|
|
25851
|
+
}
|
|
25852
|
+
const reloadWaitingAt = readReloadRequestedAt(cfg.ship.stateDir);
|
|
25853
|
+
if (reloadWaitingAt !== null) {
|
|
25854
|
+
process.stdout.write(`reload: requested ${Math.max(0, Math.round((Date.now() - reloadWaitingAt) / 1e3))}s ago, not yet honored \u2014 \`crew reload --now\` forces it
|
|
25801
25855
|
`);
|
|
25802
25856
|
}
|
|
25803
25857
|
if (projects.length) process.stdout.write(`projects: ${projects.map((p) => p.label).join(", ")}
|
|
@@ -25970,6 +26024,13 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
25970
26024
|
);
|
|
25971
26025
|
}
|
|
25972
26026
|
}
|
|
26027
|
+
const reloadAt = readReloadRequestedAt(cfg.ship.stateDir);
|
|
26028
|
+
if (reloadAt !== null) {
|
|
26029
|
+
process.stdout.write(
|
|
26030
|
+
`reload request: WARN pending for ${Math.max(0, Math.round((Date.now() - reloadAt) / 1e3))}s \u2014 the daemon is waiting for its running agents to end; \`crew reload --now\` forces it
|
|
26031
|
+
`
|
|
26032
|
+
);
|
|
26033
|
+
}
|
|
25973
26034
|
const daemonStartedAt = readDaemonStarted(cfg.ship.stateDir);
|
|
25974
26035
|
if (daemonStartedAt !== null) {
|
|
25975
26036
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
package/package.json
CHANGED