squadrant 0.16.3 → 0.16.5
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/index.js +662 -310
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +180 -17
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +17 -0
- package/plugin/skills/explainer-reel/SKILL.md +160 -0
- package/plugin/skills/explainer-reel/assets/scene-kit.js +196 -0
- package/plugin/skills/explainer-reel/assets/swimlane-preset.html +391 -0
- package/plugin/skills/explainer-reel/assets/theme.css +99 -0
- package/plugin/skills/explainer-reel/examples/jwt-reel.gif +0 -0
- package/plugin/skills/explainer-reel/examples/jwt-reel.html +233 -0
- package/plugin/skills/explainer-reel/references/component-library.md +75 -0
- package/plugin/skills/explainer-reel/references/design-tokens.md +72 -0
- package/plugin/skills/explainer-reel/references/interactive-mode.md +62 -0
- package/plugin/skills/explainer-reel/scripts/README.md +30 -0
- package/plugin/skills/explainer-reel/scripts/contact-sheet.sh +20 -0
- package/plugin/skills/explainer-reel/scripts/render-gif.sh +44 -0
- package/plugin/skills/explainer-reel/scripts/seek-shot.sh +24 -0
package/dist/squadrantd.js
CHANGED
|
@@ -490,6 +490,9 @@ function stampAttempt(rec, patch, now) {
|
|
|
490
490
|
attempts.push(last);
|
|
491
491
|
return { ...rec, attempts };
|
|
492
492
|
}
|
|
493
|
+
function isStickyAttention(state) {
|
|
494
|
+
return state === "blocked" || state === "review";
|
|
495
|
+
}
|
|
493
496
|
function nextPendingTool(current, ev, now) {
|
|
494
497
|
if (ev.note === "agent.hook.PreToolUse")
|
|
495
498
|
return { name: ev.tool ?? "tool", since: now };
|
|
@@ -518,7 +521,7 @@ function reduce(rec, ev, now) {
|
|
|
518
521
|
};
|
|
519
522
|
case "task.progress": {
|
|
520
523
|
const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
|
|
521
|
-
if (rec.state
|
|
524
|
+
if (isStickyAttention(rec.state))
|
|
522
525
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };
|
|
523
526
|
const b = { ...base, pendingTool };
|
|
524
527
|
if (rec.state === "awaiting-input" || rec.state === "stalled")
|
|
@@ -526,7 +529,7 @@ function reduce(rec, ev, now) {
|
|
|
526
529
|
return stampAttempt(b, {}, now);
|
|
527
530
|
}
|
|
528
531
|
case "heartbeat":
|
|
529
|
-
if (rec.state
|
|
532
|
+
if (isStickyAttention(rec.state))
|
|
530
533
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
531
534
|
if (rec.state === "awaiting-input")
|
|
532
535
|
return { ...base, state: "working" };
|
|
@@ -535,7 +538,12 @@ function reduce(rec, ev, now) {
|
|
|
535
538
|
if (rec.state === "blocked")
|
|
536
539
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
537
540
|
return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
|
|
541
|
+
case "task.review":
|
|
542
|
+
return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0 };
|
|
538
543
|
case "task.done":
|
|
544
|
+
if (rec.state === "review" && ev.source !== "approve") {
|
|
545
|
+
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
546
|
+
}
|
|
539
547
|
return { ...base, state: "done", resultRef: ev.resultRef, parseWarning: ev.parseWarning };
|
|
540
548
|
case "task.failed":
|
|
541
549
|
return { ...base, state: "failed", error: ev.error, exitCode: ev.exitCode };
|
|
@@ -548,7 +556,7 @@ function reduce(rec, ev, now) {
|
|
|
548
556
|
case "task.turn.started":
|
|
549
557
|
return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
|
|
550
558
|
case "task.turn.completed":
|
|
551
|
-
if (rec.state
|
|
559
|
+
if (isStickyAttention(rec.state))
|
|
552
560
|
return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
|
|
553
561
|
if (rec.pendingTool)
|
|
554
562
|
return stampAttempt(base, {}, now);
|
|
@@ -606,8 +614,8 @@ var DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 6e4;
|
|
|
606
614
|
var DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
607
615
|
var TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
608
616
|
var TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
609
|
-
var ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "failed", "stalled", "awaiting-input"]);
|
|
610
|
-
var REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked"]);
|
|
617
|
+
var ATTENTION_STATES = /* @__PURE__ */ new Set(["done", "blocked", "review", "failed", "stalled", "awaiting-input"]);
|
|
618
|
+
var REAPABLE_SURFACE_STATES = /* @__PURE__ */ new Set(["working", "stalled", "awaiting-input", "blocked", "review"]);
|
|
611
619
|
var IDLE_DEBOUNCE_MS = 12e3;
|
|
612
620
|
function shortId(id) {
|
|
613
621
|
return id.slice(0, 8);
|
|
@@ -629,6 +637,10 @@ function formatMessage(rec, event) {
|
|
|
629
637
|
}
|
|
630
638
|
case "blocked":
|
|
631
639
|
return `CREW BLOCKED ${tag}: ${(rec.question ?? "(no question)").trim()}`;
|
|
640
|
+
case "review": {
|
|
641
|
+
const note = (rec.reviewNote ?? "").trim();
|
|
642
|
+
return `CREW REVIEW ${tag}: ${note || "ready for review"} \u2014 run 'squadrant diff ${rec.project} ${rec.name ?? rec.id}' then 'squadrant crew approve' or send feedback.`;
|
|
643
|
+
}
|
|
632
644
|
case "failed":
|
|
633
645
|
return `CREW FAILED ${tag}: ${(rec.error ?? "(no error)").trim()}`;
|
|
634
646
|
case "stalled": {
|
|
@@ -699,6 +711,7 @@ var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
699
711
|
"task.progress",
|
|
700
712
|
"heartbeat",
|
|
701
713
|
"task.blocked",
|
|
714
|
+
"task.review",
|
|
702
715
|
"task.done",
|
|
703
716
|
"task.failed",
|
|
704
717
|
"task.session",
|
|
@@ -1457,13 +1470,14 @@ function projectHealth(input) {
|
|
|
1457
1470
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
1458
1471
|
const out = [];
|
|
1459
1472
|
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
1473
|
+
const deferral = input.captainDeferral;
|
|
1460
1474
|
out.push({
|
|
1461
1475
|
kind: "captain",
|
|
1462
1476
|
project,
|
|
1463
1477
|
ref: captainName,
|
|
1464
1478
|
state: captainState,
|
|
1465
1479
|
lastSeenMs: null,
|
|
1466
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
1480
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries) \u2014 draft/ghost text blocking captain pane; input never touched, delivers automatically once cleared` : void 0
|
|
1467
1481
|
});
|
|
1468
1482
|
if (commandPresent !== null) {
|
|
1469
1483
|
out.push({
|
|
@@ -1797,6 +1811,8 @@ function buildContext(opts) {
|
|
|
1797
1811
|
opencodeBridge: null,
|
|
1798
1812
|
cmuxEventsBridge: null,
|
|
1799
1813
|
telegramBridge: void 0,
|
|
1814
|
+
notifyFault: opts.notifyFault ?? (() => {
|
|
1815
|
+
}),
|
|
1800
1816
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
1801
1817
|
broadcast: () => {
|
|
1802
1818
|
},
|
|
@@ -2107,7 +2123,7 @@ var CaptainDelivery = class {
|
|
|
2107
2123
|
const seq = entry.seq;
|
|
2108
2124
|
const deferCount = this.deferCounts.get(seq) ?? 0;
|
|
2109
2125
|
const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;
|
|
2110
|
-
const probe = stable
|
|
2126
|
+
const probe = stable;
|
|
2111
2127
|
try {
|
|
2112
2128
|
await send(msg, probe ? { probe: true } : void 0);
|
|
2113
2129
|
this.deferCounts.delete(seq);
|
|
@@ -2231,7 +2247,9 @@ async function runLivenessTick(deps) {
|
|
|
2231
2247
|
}
|
|
2232
2248
|
}
|
|
2233
2249
|
function createDelivery(ctx, daemonCmux) {
|
|
2234
|
-
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
2250
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
2251
|
+
const notifyFault = ctx.notifyFault ?? (() => {
|
|
2252
|
+
});
|
|
2235
2253
|
const defaultNotify = async (args) => {
|
|
2236
2254
|
try {
|
|
2237
2255
|
await appendToMailbox({
|
|
@@ -2254,6 +2272,7 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2254
2272
|
const cfg = loadConfig();
|
|
2255
2273
|
const deliveries = /* @__PURE__ */ new Map();
|
|
2256
2274
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
2275
|
+
const stuckNotified = /* @__PURE__ */ new Set();
|
|
2257
2276
|
const sessionStartMs = Date.now();
|
|
2258
2277
|
let delivering = false;
|
|
2259
2278
|
const deliveryCore = async () => {
|
|
@@ -2325,6 +2344,18 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2325
2344
|
break;
|
|
2326
2345
|
}
|
|
2327
2346
|
}
|
|
2347
|
+
const stuck = d.stats().stuck;
|
|
2348
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
2349
|
+
stuckNotified.add(project);
|
|
2350
|
+
const { maxDeferCount } = d.stats();
|
|
2351
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
2352
|
+
const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
2353
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
2354
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
2355
|
+
telegramBridge?.pushRaw(project, text);
|
|
2356
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
2357
|
+
stuckNotified.delete(project);
|
|
2358
|
+
}
|
|
2328
2359
|
}
|
|
2329
2360
|
};
|
|
2330
2361
|
const deliveryTick = async () => {
|
|
@@ -2575,7 +2606,12 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2575
2606
|
captainStopped: null,
|
|
2576
2607
|
captainState: deriveCaptainState(capEntry),
|
|
2577
2608
|
commandPresent: null,
|
|
2578
|
-
crews: store.list(project)
|
|
2609
|
+
crews: store.list(project),
|
|
2610
|
+
// #579/#484 Gap 3: surface the same deferral stats already exposed to
|
|
2611
|
+
// the snapshot (line ~135 below) on the health row too, so `squadrant
|
|
2612
|
+
// doctor` / `squadrant status --detailed` show a stuck delivery with
|
|
2613
|
+
// zero configuration.
|
|
2614
|
+
captainDeferral: deliveryStats(project)
|
|
2579
2615
|
}));
|
|
2580
2616
|
}
|
|
2581
2617
|
return out;
|
|
@@ -2818,8 +2854,12 @@ var REGISTRY = {
|
|
|
2818
2854
|
build: (a) => a[0] ? ok("crews", ["crew", "list", a[0]]) : usage("crews", "usage: /crews <project>")
|
|
2819
2855
|
},
|
|
2820
2856
|
launch: {
|
|
2857
|
+
// --headless (#586, same reason as #520 on the boot-if-down path): runCommand
|
|
2858
|
+
// execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no
|
|
2859
|
+
// terminal — a plain `launch` would open the cmux GUI app and exit 0 before
|
|
2860
|
+
// the workspace is ever launched.
|
|
2821
2861
|
usage: "/launch <project>",
|
|
2822
|
-
build: (a) => a[0] ? ok("launch", ["launch", a[0]]) : usage("launch", "usage: /launch <project>")
|
|
2862
|
+
build: (a) => a[0] ? ok("launch", ["launch", a[0], "--headless"]) : usage("launch", "usage: /launch <project>")
|
|
2823
2863
|
},
|
|
2824
2864
|
effort: {
|
|
2825
2865
|
usage: "/effort [max|balance|low]",
|
|
@@ -3004,6 +3044,9 @@ ${ev.message}` : "");
|
|
|
3004
3044
|
case "task.blocked":
|
|
3005
3045
|
return `\u{1F6A7} [${project}] CREW BLOCKED \xB7 ${ev.id}
|
|
3006
3046
|
${ev.question}`;
|
|
3047
|
+
case "task.review":
|
|
3048
|
+
return `\u{1F440} [${project}] CREW REVIEW \xB7 ${ev.id}` + (ev.message ? `
|
|
3049
|
+
${ev.message}` : "");
|
|
3007
3050
|
case "task.idle":
|
|
3008
3051
|
return `\u{1F4A4} [${project}] CREW IDLE \xB7 ${ev.id}`;
|
|
3009
3052
|
case "task.failed":
|
|
@@ -3200,6 +3243,7 @@ var DONE_ONLY = /* @__PURE__ */ new Set(["task.done", "task.failed"]);
|
|
|
3200
3243
|
var ALERTS = /* @__PURE__ */ new Set([
|
|
3201
3244
|
...DONE_ONLY,
|
|
3202
3245
|
"task.blocked",
|
|
3246
|
+
"task.review",
|
|
3203
3247
|
"task.approval.requested",
|
|
3204
3248
|
"task.input.requested",
|
|
3205
3249
|
"task.timeout"
|
|
@@ -3259,6 +3303,14 @@ function createTelegramBridge(opts) {
|
|
|
3259
3303
|
s.offset = next;
|
|
3260
3304
|
saveState(stateRoot, s);
|
|
3261
3305
|
}
|
|
3306
|
+
async function sendToTopic(project, text) {
|
|
3307
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
3308
|
+
if (threadId === void 0) {
|
|
3309
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
3310
|
+
setTopic(stateRoot, project, threadId);
|
|
3311
|
+
}
|
|
3312
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
3313
|
+
}
|
|
3262
3314
|
async function deliverOutbound(project, ev) {
|
|
3263
3315
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
3264
3316
|
const live = loadState(stateRoot).notify[project];
|
|
@@ -3267,12 +3319,10 @@ function createTelegramBridge(opts) {
|
|
|
3267
3319
|
return;
|
|
3268
3320
|
if (!tierIncludes(resolved.crew, ev.type))
|
|
3269
3321
|
return;
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
}
|
|
3275
|
-
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
3322
|
+
await sendToTopic(project, formatLifecycle(project, ev));
|
|
3323
|
+
}
|
|
3324
|
+
async function deliverRawOutbound(project, text) {
|
|
3325
|
+
await sendToTopic(project, text);
|
|
3276
3326
|
}
|
|
3277
3327
|
function resolveLiveNotify(project) {
|
|
3278
3328
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -3580,6 +3630,11 @@ function createTelegramBridge(opts) {
|
|
|
3580
3630
|
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
3581
3631
|
});
|
|
3582
3632
|
},
|
|
3633
|
+
pushRaw(project, text) {
|
|
3634
|
+
void deliverRawOutbound(project, text).catch((e) => {
|
|
3635
|
+
log(`telegram raw push failed project=${project}: ${e.message}`);
|
|
3636
|
+
});
|
|
3637
|
+
},
|
|
3583
3638
|
health() {
|
|
3584
3639
|
return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };
|
|
3585
3640
|
}
|
|
@@ -4624,6 +4679,18 @@ function cmux(args) {
|
|
|
4624
4679
|
);
|
|
4625
4680
|
});
|
|
4626
4681
|
}
|
|
4682
|
+
function cmuxStdin(args, input) {
|
|
4683
|
+
return new Promise((resolve2, reject) => {
|
|
4684
|
+
const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
|
|
4685
|
+
if (err) {
|
|
4686
|
+
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4689
|
+
resolve2(stdout.trim());
|
|
4690
|
+
});
|
|
4691
|
+
child.stdin.end(input);
|
|
4692
|
+
});
|
|
4693
|
+
}
|
|
4627
4694
|
function parseList(output) {
|
|
4628
4695
|
let parsed;
|
|
4629
4696
|
try {
|
|
@@ -5025,6 +5092,37 @@ function createCmuxDriver() {
|
|
|
5025
5092
|
}
|
|
5026
5093
|
throw new DeferDelivery(draft);
|
|
5027
5094
|
},
|
|
5095
|
+
async showDiff(opts) {
|
|
5096
|
+
const source = opts.source ?? "branch";
|
|
5097
|
+
const args = ["diff"];
|
|
5098
|
+
if (source === "staged") {
|
|
5099
|
+
args.push("--staged");
|
|
5100
|
+
} else if (source === "unstaged") {
|
|
5101
|
+
args.push("--unstaged");
|
|
5102
|
+
} else {
|
|
5103
|
+
args.push("--branch", "--base", opts.base);
|
|
5104
|
+
if (opts.lastTurn)
|
|
5105
|
+
args.push("--last-turn");
|
|
5106
|
+
}
|
|
5107
|
+
args.push("--cwd", opts.cwd, "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split");
|
|
5108
|
+
if (opts.title)
|
|
5109
|
+
args.push("--title", opts.title);
|
|
5110
|
+
if (opts.focus === false)
|
|
5111
|
+
args.push("--no-focus");
|
|
5112
|
+
else
|
|
5113
|
+
args.push("--focus", "true");
|
|
5114
|
+
await cmux(args);
|
|
5115
|
+
},
|
|
5116
|
+
async showPatch(opts) {
|
|
5117
|
+
const args = ["diff", "-", "--workspace", opts.workspaceId, "--layout", opts.layout ?? "split"];
|
|
5118
|
+
if (opts.title)
|
|
5119
|
+
args.push("--title", opts.title);
|
|
5120
|
+
if (opts.focus === false)
|
|
5121
|
+
args.push("--no-focus");
|
|
5122
|
+
else
|
|
5123
|
+
args.push("--focus", "true");
|
|
5124
|
+
await cmuxStdin(args, opts.patch);
|
|
5125
|
+
},
|
|
5028
5126
|
async listSurfaces(workspaceId) {
|
|
5029
5127
|
let output;
|
|
5030
5128
|
try {
|
|
@@ -5090,7 +5188,60 @@ var RuntimeRegistry = class {
|
|
|
5090
5188
|
};
|
|
5091
5189
|
|
|
5092
5190
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
5093
|
-
import {
|
|
5191
|
+
import { execFile as execFileCb, execSync as execSync7 } from "child_process";
|
|
5192
|
+
import { promisify as promisify2 } from "util";
|
|
5193
|
+
var execFile3 = promisify2(execFileCb);
|
|
5194
|
+
function createCmuxNotifier(_scope) {
|
|
5195
|
+
return {
|
|
5196
|
+
name: "cmux",
|
|
5197
|
+
async probe() {
|
|
5198
|
+
try {
|
|
5199
|
+
execSync7("squadrant runtime status --command", { encoding: "utf-8", stdio: "pipe" });
|
|
5200
|
+
return { installed: true, reachable: true };
|
|
5201
|
+
} catch (err) {
|
|
5202
|
+
const code = err.code;
|
|
5203
|
+
if (code === "ENOENT") {
|
|
5204
|
+
return { installed: false, reachable: false };
|
|
5205
|
+
}
|
|
5206
|
+
return { installed: true, reachable: false };
|
|
5207
|
+
}
|
|
5208
|
+
},
|
|
5209
|
+
async notify(message) {
|
|
5210
|
+
await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
5211
|
+
}
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
// packages/workspaces/dist/notifiers/registry.js
|
|
5216
|
+
var DEFAULT_NOTIFIER = "cmux";
|
|
5217
|
+
var NotifierRegistry = class {
|
|
5218
|
+
factories;
|
|
5219
|
+
constructor(factories) {
|
|
5220
|
+
this.factories = factories;
|
|
5221
|
+
}
|
|
5222
|
+
get(config) {
|
|
5223
|
+
const name = config.notifier ?? DEFAULT_NOTIFIER;
|
|
5224
|
+
return this.getFactory(name)({});
|
|
5225
|
+
}
|
|
5226
|
+
getFactory(name) {
|
|
5227
|
+
const factory = this.factories[name];
|
|
5228
|
+
if (!factory) {
|
|
5229
|
+
throw new Error(`Unknown notifier provider '${name}' \u2014 no factory registered`);
|
|
5230
|
+
}
|
|
5231
|
+
return factory;
|
|
5232
|
+
}
|
|
5233
|
+
async probeAll() {
|
|
5234
|
+
const results = {};
|
|
5235
|
+
for (const [name, factory] of Object.entries(this.factories)) {
|
|
5236
|
+
try {
|
|
5237
|
+
results[name] = await factory({}).probe();
|
|
5238
|
+
} catch {
|
|
5239
|
+
results[name] = { installed: false, reachable: false };
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
return results;
|
|
5243
|
+
}
|
|
5244
|
+
};
|
|
5094
5245
|
|
|
5095
5246
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
5096
5247
|
import fs16 from "fs/promises";
|
|
@@ -5868,6 +6019,16 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
5868
6019
|
sendReply
|
|
5869
6020
|
});
|
|
5870
6021
|
}
|
|
6022
|
+
function buildNotifyFault(log) {
|
|
6023
|
+
const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
|
|
6024
|
+
return async (project, text) => {
|
|
6025
|
+
try {
|
|
6026
|
+
await registry.get(loadConfig()).notify(`[${project}] ${text}`);
|
|
6027
|
+
} catch (e) {
|
|
6028
|
+
log(`fault notify failed project=${project}: ${e.message}`);
|
|
6029
|
+
}
|
|
6030
|
+
};
|
|
6031
|
+
}
|
|
5871
6032
|
function startSquadrantd(opts = {}) {
|
|
5872
6033
|
const ctx = buildContext(opts);
|
|
5873
6034
|
const { stateRoot, store, log, spawn: spawn2, writeResult, inFlightHeadlessIds, activeHeadlessKills } = ctx;
|
|
@@ -5931,6 +6092,8 @@ function startSquadrantd(opts = {}) {
|
|
|
5931
6092
|
ctx.lifecycleSources = [cmuxStoreSource, nativeHookSource, codexAppServerSource];
|
|
5932
6093
|
const tgCfg = loadConfig().telegram;
|
|
5933
6094
|
ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : void 0);
|
|
6095
|
+
if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;
|
|
6096
|
+
else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);
|
|
5934
6097
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
5935
6098
|
const resendRuntime = createCmuxDriver();
|
|
5936
6099
|
ctx.resendFirstTurn = opts.resendFirstTurn ?? (async (rec) => {
|