@tablation/crew 0.69.1 → 0.71.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 +68 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15265,6 +15265,37 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
15265
15265
|
return endpoints;
|
|
15266
15266
|
}
|
|
15267
15267
|
|
|
15268
|
+
// src/outage.ts
|
|
15269
|
+
var TRANSIENT_BACKOFF_MS = [15e3, 3e4, 6e4];
|
|
15270
|
+
var OUTAGE_ESCALATE_AFTER = 5;
|
|
15271
|
+
var TRACKER_OUTAGE_KEY = "tracker_unreachable";
|
|
15272
|
+
var NETWORK_CODES = ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "EAI_AGAIN"];
|
|
15273
|
+
function transientBackoffMs(n) {
|
|
15274
|
+
return TRANSIENT_BACKOFF_MS[Math.min(Math.max(n, 1), TRANSIENT_BACKOFF_MS.length) - 1];
|
|
15275
|
+
}
|
|
15276
|
+
function codeOf(e) {
|
|
15277
|
+
const code = e?.code;
|
|
15278
|
+
return typeof code === "string" ? code : void 0;
|
|
15279
|
+
}
|
|
15280
|
+
function isTransientError(err) {
|
|
15281
|
+
if (!(err instanceof Error)) return false;
|
|
15282
|
+
const msg = err.message;
|
|
15283
|
+
if (msg === "fetch failed" || msg.startsWith("fetch failed")) return true;
|
|
15284
|
+
for (let e = err, depth = 0; e && depth < 4; e = e.cause, depth++) {
|
|
15285
|
+
const code = codeOf(e);
|
|
15286
|
+
if (code && NETWORK_CODES.includes(code)) return true;
|
|
15287
|
+
}
|
|
15288
|
+
if (NETWORK_CODES.some((c) => msg.includes(c))) return true;
|
|
15289
|
+
if (/\b(?:HTTP|status)\s*:?\s*(502|503|504)\b/i.test(msg) || /^(502|503|504)\b/.test(msg)) return true;
|
|
15290
|
+
if (err instanceof SyntaxError && /Unexpected token '?<|is not valid JSON|Unexpected end of JSON/.test(msg)) return true;
|
|
15291
|
+
return false;
|
|
15292
|
+
}
|
|
15293
|
+
function describeCause(err) {
|
|
15294
|
+
const first = (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
15295
|
+
const code = codeOf(err?.cause);
|
|
15296
|
+
return code && !first.includes(code) ? `${first} (${code})` : first;
|
|
15297
|
+
}
|
|
15298
|
+
|
|
15268
15299
|
// src/daemon.ts
|
|
15269
15300
|
import { existsSync as existsSync32, mkdirSync as mkdirSync19, readFileSync as readFileSync26, readdirSync as readdirSync13, rmSync as rmSync13, statSync as statSync9, writeFileSync as writeFileSync20 } from "fs";
|
|
15270
15301
|
import { join as join33 } from "path";
|
|
@@ -15364,12 +15395,32 @@ async function runDaemonLoop(o) {
|
|
|
15364
15395
|
const sleep = o.sleep ?? defaultSleep;
|
|
15365
15396
|
let backoff = floor;
|
|
15366
15397
|
let passes = 0;
|
|
15398
|
+
const now = o.now ?? Date.now;
|
|
15399
|
+
let transientRun = 0;
|
|
15400
|
+
let downSince = 0;
|
|
15367
15401
|
for (; ; ) {
|
|
15368
15402
|
if (o.signal?.aborted) return "aborted";
|
|
15369
15403
|
let result;
|
|
15370
15404
|
try {
|
|
15371
15405
|
result = await o.runPass();
|
|
15406
|
+
if (transientRun > 0) {
|
|
15407
|
+
o.onRecovered?.({ downMs: now() - downSince });
|
|
15408
|
+
transientRun = 0;
|
|
15409
|
+
}
|
|
15372
15410
|
} catch (err) {
|
|
15411
|
+
if (isTransientError(err)) {
|
|
15412
|
+
passes++;
|
|
15413
|
+
if (transientRun === 0) downSince = now();
|
|
15414
|
+
transientRun++;
|
|
15415
|
+
const wait = transientBackoffMs(transientRun);
|
|
15416
|
+
const downMs = now() - downSince;
|
|
15417
|
+
o.onTransient?.({ err, consecutive: transientRun, backoffMs: wait, downMs });
|
|
15418
|
+
if (transientRun === OUTAGE_ESCALATE_AFTER) o.onOutage?.({ err, consecutive: transientRun, downMs });
|
|
15419
|
+
if (o.maxPasses !== void 0 && passes >= o.maxPasses) return "maxPasses";
|
|
15420
|
+
if (o.signal?.aborted) return "aborted";
|
|
15421
|
+
await sleep(wait);
|
|
15422
|
+
continue;
|
|
15423
|
+
}
|
|
15373
15424
|
passes++;
|
|
15374
15425
|
o.onPassError?.(err);
|
|
15375
15426
|
if (o.maxPasses !== void 0 && passes >= o.maxPasses) return "maxPasses";
|
|
@@ -17466,6 +17517,23 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17466
17517
|
`)
|
|
17467
17518
|
})
|
|
17468
17519
|
}),
|
|
17520
|
+
onTransient: ({ err, consecutive, backoffMs }) => emit.warn(
|
|
17521
|
+
`tracker unavailable (${describeCause(err)}); attempt ${consecutive}, retrying in ${Math.round(backoffMs / 1e3)}s`
|
|
17522
|
+
),
|
|
17523
|
+
onOutage: ({ err, downMs }) => {
|
|
17524
|
+
const msg = `tracker unreachable for ${Math.max(1, Math.round(downMs / 6e4))} minutes: ${describeCause(err)}`;
|
|
17525
|
+
emit.error(msg);
|
|
17526
|
+
void raiseShipAttention({ state, emit, route, ship: cfg.ship }, {
|
|
17527
|
+
kind: "tracker_unreachable",
|
|
17528
|
+
key: TRACKER_OUTAGE_KEY,
|
|
17529
|
+
message: msg,
|
|
17530
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
17531
|
+
});
|
|
17532
|
+
},
|
|
17533
|
+
onRecovered: ({ downMs }) => {
|
|
17534
|
+
clearShipAttention(state, TRACKER_OUTAGE_KEY);
|
|
17535
|
+
emit.emit(`tracker is back after ${Math.max(1, Math.round(downMs / 1e3))}s`);
|
|
17536
|
+
},
|
|
17469
17537
|
onPassError: (err) => emit.error(
|
|
17470
17538
|
`daemon pass failed, continuing: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
|
|
17471
17539
|
),
|
package/package.json
CHANGED