squadrant 0.16.3 → 0.16.4
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 +70 -33
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +110 -11
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2332,13 +2332,14 @@ function projectHealth(input) {
|
|
|
2332
2332
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
2333
2333
|
const out = [];
|
|
2334
2334
|
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
2335
|
+
const deferral = input.captainDeferral;
|
|
2335
2336
|
out.push({
|
|
2336
2337
|
kind: "captain",
|
|
2337
2338
|
project,
|
|
2338
2339
|
ref: captainName,
|
|
2339
2340
|
state: captainState,
|
|
2340
2341
|
lastSeenMs: null,
|
|
2341
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
2342
|
+
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
|
|
2342
2343
|
});
|
|
2343
2344
|
if (commandPresent !== null) {
|
|
2344
2345
|
out.push({
|
|
@@ -3005,6 +3006,8 @@ function buildContext(opts) {
|
|
|
3005
3006
|
opencodeBridge: null,
|
|
3006
3007
|
cmuxEventsBridge: null,
|
|
3007
3008
|
telegramBridge: void 0,
|
|
3009
|
+
notifyFault: opts.notifyFault ?? (() => {
|
|
3010
|
+
}),
|
|
3008
3011
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
3009
3012
|
broadcast: () => {
|
|
3010
3013
|
},
|
|
@@ -3345,7 +3348,7 @@ var init_captain_delivery = __esm({
|
|
|
3345
3348
|
const seq = entry.seq;
|
|
3346
3349
|
const deferCount = this.deferCounts.get(seq) ?? 0;
|
|
3347
3350
|
const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;
|
|
3348
|
-
const probe = stable
|
|
3351
|
+
const probe = stable;
|
|
3349
3352
|
try {
|
|
3350
3353
|
await send(msg, probe ? { probe: true } : void 0);
|
|
3351
3354
|
this.deferCounts.delete(seq);
|
|
@@ -3469,7 +3472,9 @@ async function runLivenessTick(deps) {
|
|
|
3469
3472
|
}
|
|
3470
3473
|
}
|
|
3471
3474
|
function createDelivery(ctx, daemonCmux) {
|
|
3472
|
-
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
3475
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
3476
|
+
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3477
|
+
});
|
|
3473
3478
|
const defaultNotify = async (args) => {
|
|
3474
3479
|
try {
|
|
3475
3480
|
await appendToMailbox({
|
|
@@ -3492,6 +3497,7 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3492
3497
|
const cfg = loadConfig();
|
|
3493
3498
|
const deliveries = /* @__PURE__ */ new Map();
|
|
3494
3499
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
3500
|
+
const stuckNotified = /* @__PURE__ */ new Set();
|
|
3495
3501
|
const sessionStartMs = Date.now();
|
|
3496
3502
|
let delivering = false;
|
|
3497
3503
|
const deliveryCore = async () => {
|
|
@@ -3563,6 +3569,18 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3563
3569
|
break;
|
|
3564
3570
|
}
|
|
3565
3571
|
}
|
|
3572
|
+
const stuck = d.stats().stuck;
|
|
3573
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
3574
|
+
stuckNotified.add(project);
|
|
3575
|
+
const { maxDeferCount } = d.stats();
|
|
3576
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
3577
|
+
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.`;
|
|
3578
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
3579
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
3580
|
+
telegramBridge?.pushRaw(project, text);
|
|
3581
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
3582
|
+
stuckNotified.delete(project);
|
|
3583
|
+
}
|
|
3566
3584
|
}
|
|
3567
3585
|
};
|
|
3568
3586
|
const deliveryTick = async () => {
|
|
@@ -3839,7 +3857,12 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
3839
3857
|
captainStopped: null,
|
|
3840
3858
|
captainState: deriveCaptainState(capEntry),
|
|
3841
3859
|
commandPresent: null,
|
|
3842
|
-
crews: store.list(project)
|
|
3860
|
+
crews: store.list(project),
|
|
3861
|
+
// #579/#484 Gap 3: surface the same deferral stats already exposed to
|
|
3862
|
+
// the snapshot (line ~135 below) on the health row too, so `squadrant
|
|
3863
|
+
// doctor` / `squadrant status --detailed` show a stuck delivery with
|
|
3864
|
+
// zero configuration.
|
|
3865
|
+
captainDeferral: deliveryStats(project)
|
|
3843
3866
|
}));
|
|
3844
3867
|
}
|
|
3845
3868
|
return out;
|
|
@@ -4740,6 +4763,14 @@ function createTelegramBridge(opts) {
|
|
|
4740
4763
|
s.offset = next;
|
|
4741
4764
|
saveState(stateRoot, s);
|
|
4742
4765
|
}
|
|
4766
|
+
async function sendToTopic(project, text) {
|
|
4767
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
4768
|
+
if (threadId === void 0) {
|
|
4769
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
4770
|
+
setTopic(stateRoot, project, threadId);
|
|
4771
|
+
}
|
|
4772
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
4773
|
+
}
|
|
4743
4774
|
async function deliverOutbound(project, ev) {
|
|
4744
4775
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
4745
4776
|
const live = loadState(stateRoot).notify[project];
|
|
@@ -4748,12 +4779,10 @@ function createTelegramBridge(opts) {
|
|
|
4748
4779
|
return;
|
|
4749
4780
|
if (!tierIncludes(resolved.crew, ev.type))
|
|
4750
4781
|
return;
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
}
|
|
4756
|
-
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
4782
|
+
await sendToTopic(project, formatLifecycle(project, ev));
|
|
4783
|
+
}
|
|
4784
|
+
async function deliverRawOutbound(project, text) {
|
|
4785
|
+
await sendToTopic(project, text);
|
|
4757
4786
|
}
|
|
4758
4787
|
function resolveLiveNotify(project) {
|
|
4759
4788
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -5061,6 +5090,11 @@ function createTelegramBridge(opts) {
|
|
|
5061
5090
|
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
5062
5091
|
});
|
|
5063
5092
|
},
|
|
5093
|
+
pushRaw(project, text) {
|
|
5094
|
+
void deliverRawOutbound(project, text).catch((e) => {
|
|
5095
|
+
log(`telegram raw push failed project=${project}: ${e.message}`);
|
|
5096
|
+
});
|
|
5097
|
+
},
|
|
5064
5098
|
health() {
|
|
5065
5099
|
return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };
|
|
5066
5100
|
}
|
|
@@ -6738,7 +6772,8 @@ var init_runtimes = __esm({
|
|
|
6738
6772
|
});
|
|
6739
6773
|
|
|
6740
6774
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
6741
|
-
import {
|
|
6775
|
+
import { execFile as execFileCb, execSync as execSync2 } from "child_process";
|
|
6776
|
+
import { promisify as promisify2 } from "util";
|
|
6742
6777
|
function createCmuxNotifier(_scope) {
|
|
6743
6778
|
return {
|
|
6744
6779
|
name: "cmux",
|
|
@@ -6755,13 +6790,15 @@ function createCmuxNotifier(_scope) {
|
|
|
6755
6790
|
}
|
|
6756
6791
|
},
|
|
6757
6792
|
async notify(message) {
|
|
6758
|
-
|
|
6793
|
+
await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
6759
6794
|
}
|
|
6760
6795
|
};
|
|
6761
6796
|
}
|
|
6797
|
+
var execFile3;
|
|
6762
6798
|
var init_cmux2 = __esm({
|
|
6763
6799
|
"packages/workspaces/dist/notifiers/cmux.js"() {
|
|
6764
6800
|
init_cmux();
|
|
6801
|
+
execFile3 = promisify2(execFileCb);
|
|
6765
6802
|
}
|
|
6766
6803
|
});
|
|
6767
6804
|
|
|
@@ -10799,9 +10836,18 @@ function progressBar(completed, total) {
|
|
|
10799
10836
|
}
|
|
10800
10837
|
function captainIndicator(state) {
|
|
10801
10838
|
if (state === "alive" || state === "stale") return chalk6.green("\u25CF");
|
|
10839
|
+
if (state === "stopped") return chalk6.magenta("\u23FB");
|
|
10802
10840
|
if (state === void 0 || state === "unknown") return chalk6.dim("?");
|
|
10803
10841
|
return chalk6.dim("\u25CB");
|
|
10804
10842
|
}
|
|
10843
|
+
function formatProjectRow(name, captainName, fm, statusMdState, captainState) {
|
|
10844
|
+
const sessionIndicator = captainIndicator(captainState);
|
|
10845
|
+
const captainDisplay = `${captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10846
|
+
const crew = statusMdState === "ok" ? String(fm.active_crew ?? 0).padEnd(6) : chalk6.dim("?").padEnd(6);
|
|
10847
|
+
const progress = statusMdState === "ok" ? progressBar(fm.tasks_completed ?? 0, fm.tasks_total ?? 0).padEnd(25) : statusMdState === "unreadable" ? chalk6.red("status.md unreadable").padEnd(25) : chalk6.dim("no notes").padEnd(25);
|
|
10848
|
+
const updated = statusMdState === "ok" ? timeAgo(fm.last_updated) : chalk6.dim("\u2014");
|
|
10849
|
+
return ` ${name.padEnd(18)} ${captainDisplay} ${crew} ${progress} ${updated}`;
|
|
10850
|
+
}
|
|
10805
10851
|
var statusCommand = new Command4("status").description("Show status of all projects from spoke vault status files").option("--detailed", "also show live per-component service health from the daemon (#77)").action(async (opts) => {
|
|
10806
10852
|
const config = loadConfig();
|
|
10807
10853
|
const projects = Object.entries(config.projects);
|
|
@@ -10826,28 +10872,19 @@ var statusCommand = new Command4("status").description("Show status of all proje
|
|
|
10826
10872
|
console.log(chalk6.dim(" " + "\u2500".repeat(85)));
|
|
10827
10873
|
for (const [name, project] of projects) {
|
|
10828
10874
|
const workspace = registry.forProject(name, config);
|
|
10829
|
-
if (!await workspace.exists("status.md")) {
|
|
10830
|
-
console.log(` ${name.padEnd(18)} ${chalk6.dim("no status.md")}`);
|
|
10831
|
-
continue;
|
|
10832
|
-
}
|
|
10833
10875
|
let fm = {};
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10876
|
+
let statusMdState = "missing";
|
|
10877
|
+
if (await workspace.exists("status.md")) {
|
|
10878
|
+
try {
|
|
10879
|
+
const raw = await workspace.read("status.md");
|
|
10880
|
+
fm = matter2(raw).data;
|
|
10881
|
+
statusMdState = "ok";
|
|
10882
|
+
} catch {
|
|
10883
|
+
statusMdState = "unreadable";
|
|
10884
|
+
}
|
|
10840
10885
|
}
|
|
10841
|
-
const sessionIndicator = captainIndicator(captainStateByProject.get(name));
|
|
10842
|
-
const captainDisplay = `${project.captainName.padEnd(11)} ${sessionIndicator}`;
|
|
10843
|
-
const crew = String(fm.active_crew ?? 0).padEnd(6);
|
|
10844
|
-
const progress = progressBar(
|
|
10845
|
-
fm.tasks_completed ?? 0,
|
|
10846
|
-
fm.tasks_total ?? 0
|
|
10847
|
-
).padEnd(25);
|
|
10848
|
-
const updated = timeAgo(fm.last_updated);
|
|
10849
10886
|
console.log(
|
|
10850
|
-
|
|
10887
|
+
formatProjectRow(name, project.captainName, fm, statusMdState, captainStateByProject.get(name))
|
|
10851
10888
|
);
|
|
10852
10889
|
}
|
|
10853
10890
|
console.log("");
|
|
@@ -11982,7 +12019,7 @@ init_dist();
|
|
|
11982
12019
|
import { join as join22 } from "path";
|
|
11983
12020
|
import { homedir as homedir17 } from "os";
|
|
11984
12021
|
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
11985
|
-
import { execFile as
|
|
12022
|
+
import { execFile as execFile4 } from "child_process";
|
|
11986
12023
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
11987
12024
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
11988
12025
|
function withTimeout2(p, ms) {
|
|
@@ -12099,7 +12136,7 @@ function defaultProbeRunners() {
|
|
|
12099
12136
|
return {
|
|
12100
12137
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
12101
12138
|
try {
|
|
12102
|
-
|
|
12139
|
+
execFile4(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
12103
12140
|
} catch {
|
|
12104
12141
|
resolve3(false);
|
|
12105
12142
|
}
|