@tablation/crew 0.100.1 → 0.100.2
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 +60 -3
- 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
|
}
|
|
@@ -21238,6 +21271,7 @@ function findSyncRoute(c, name) {
|
|
|
21238
21271
|
function daemonConfigPaths(c) {
|
|
21239
21272
|
const files = [c.configFile];
|
|
21240
21273
|
for (const r of c.routes) if (r.apiKeyFile && !files.includes(r.apiKeyFile)) files.push(r.apiKeyFile);
|
|
21274
|
+
for (const r of c.routes) files.push(resolvedPathFor(c.ship.stateDir, r.route));
|
|
21241
21275
|
return files;
|
|
21242
21276
|
}
|
|
21243
21277
|
var STATE_WRITING_COMMANDS = /* @__PURE__ */ new Set(["connect", "provision", "disconnect", "install", "uninstall", "reload", "release", "merge", "deploy"]);
|
|
@@ -22975,8 +23009,14 @@ ${cu.tail.trimEnd()}
|
|
|
22975
23009
|
log: (m) => emit.warn(m)
|
|
22976
23010
|
});
|
|
22977
23011
|
if (controlServer) emit.emit(`control API listening on ${controlServer.path}`);
|
|
23012
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
22978
23013
|
const stopReason = await runDaemonLoop({
|
|
23014
|
+
inFlight,
|
|
23015
|
+
onStaleDeferred: ({ runs, waitedMs }) => emit.warn(
|
|
23016
|
+
`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)`
|
|
23017
|
+
),
|
|
22979
23018
|
runPass: () => runOnePass({
|
|
23019
|
+
inFlight,
|
|
22980
23020
|
route: route2,
|
|
22981
23021
|
ship: cfg.ship,
|
|
22982
23022
|
state,
|
|
@@ -25365,7 +25405,9 @@ ${where.join("\n")}`);
|
|
|
25365
25405
|
mkdirSync28(cfg.ship.stateDir, { recursive: true });
|
|
25366
25406
|
writeFileSync30(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
25367
25407
|
`);
|
|
25368
|
-
process.stdout.write(
|
|
25408
|
+
process.stdout.write(
|
|
25409
|
+
"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"
|
|
25410
|
+
);
|
|
25369
25411
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
25370
25412
|
for (const cmd of [control.stopCommand, control.startCommand]) {
|
|
25371
25413
|
const res = spawnSync8(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
@@ -25699,7 +25741,9 @@ ${where.join("\n")}`);
|
|
|
25699
25741
|
rowName: nameSync.rowName ?? null,
|
|
25700
25742
|
nameDrift: nameSync.drift,
|
|
25701
25743
|
// Additive (CREW-2803): the relay URL this route's tunnel is reachable at, or null.
|
|
25702
|
-
relayUrl: routeRelayUrl(route2)
|
|
25744
|
+
relayUrl: routeRelayUrl(route2),
|
|
25745
|
+
// Additive (CREW-2887): when a `crew reload` request was written and not yet honored, else null.
|
|
25746
|
+
reloadRequestedAt: ((at) => at === null ? null : new Date(at).toISOString())(readReloadRequestedAt(cfg.ship.stateDir))
|
|
25703
25747
|
},
|
|
25704
25748
|
// Additive (CREW-1402): what the operator owes. `version` stays 1.
|
|
25705
25749
|
todo: todo.map((i) => ({ ticket: displayKey(i.ticket), title: i.ticket.title ?? null, reasons: i.reasons })),
|
|
@@ -25763,6 +25807,7 @@ ${where.join("\n")}`);
|
|
|
25763
25807
|
waiting: waiting ? { ticket: waiting.ticket, since: waiting.since, streak: fairness.streak() } : null
|
|
25764
25808
|
},
|
|
25765
25809
|
ships: shipRows.map((s) => ({
|
|
25810
|
+
...shipIdentityJson(s),
|
|
25766
25811
|
name: s.name ?? null,
|
|
25767
25812
|
lastSeen: s.last_seen ?? null,
|
|
25768
25813
|
engaged: !!s.engaged,
|
|
@@ -25798,6 +25843,11 @@ watermark: ${state.watermark()}
|
|
|
25798
25843
|
);
|
|
25799
25844
|
for (const r of ["dev", "design", "qa"]) {
|
|
25800
25845
|
if (state.isRolePaused(r)) process.stdout.write(`role ${r}: paused
|
|
25846
|
+
`);
|
|
25847
|
+
}
|
|
25848
|
+
const reloadWaitingAt = readReloadRequestedAt(cfg.ship.stateDir);
|
|
25849
|
+
if (reloadWaitingAt !== null) {
|
|
25850
|
+
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
25851
|
`);
|
|
25802
25852
|
}
|
|
25803
25853
|
if (projects.length) process.stdout.write(`projects: ${projects.map((p) => p.label).join(", ")}
|
|
@@ -25970,6 +26020,13 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
25970
26020
|
);
|
|
25971
26021
|
}
|
|
25972
26022
|
}
|
|
26023
|
+
const reloadAt = readReloadRequestedAt(cfg.ship.stateDir);
|
|
26024
|
+
if (reloadAt !== null) {
|
|
26025
|
+
process.stdout.write(
|
|
26026
|
+
`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
|
|
26027
|
+
`
|
|
26028
|
+
);
|
|
26029
|
+
}
|
|
25973
26030
|
const daemonStartedAt = readDaemonStarted(cfg.ship.stateDir);
|
|
25974
26031
|
if (daemonStartedAt !== null) {
|
|
25975
26032
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
package/package.json
CHANGED