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/squadrantd.js
CHANGED
|
@@ -1457,13 +1457,14 @@ function projectHealth(input) {
|
|
|
1457
1457
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
1458
1458
|
const out = [];
|
|
1459
1459
|
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
1460
|
+
const deferral = input.captainDeferral;
|
|
1460
1461
|
out.push({
|
|
1461
1462
|
kind: "captain",
|
|
1462
1463
|
project,
|
|
1463
1464
|
ref: captainName,
|
|
1464
1465
|
state: captainState,
|
|
1465
1466
|
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
|
|
1467
|
+
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
1468
|
});
|
|
1468
1469
|
if (commandPresent !== null) {
|
|
1469
1470
|
out.push({
|
|
@@ -1797,6 +1798,8 @@ function buildContext(opts) {
|
|
|
1797
1798
|
opencodeBridge: null,
|
|
1798
1799
|
cmuxEventsBridge: null,
|
|
1799
1800
|
telegramBridge: void 0,
|
|
1801
|
+
notifyFault: opts.notifyFault ?? (() => {
|
|
1802
|
+
}),
|
|
1800
1803
|
lifecycleSources: opts.lifecycleSources ?? [],
|
|
1801
1804
|
broadcast: () => {
|
|
1802
1805
|
},
|
|
@@ -2107,7 +2110,7 @@ var CaptainDelivery = class {
|
|
|
2107
2110
|
const seq = entry.seq;
|
|
2108
2111
|
const deferCount = this.deferCounts.get(seq) ?? 0;
|
|
2109
2112
|
const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;
|
|
2110
|
-
const probe = stable
|
|
2113
|
+
const probe = stable;
|
|
2111
2114
|
try {
|
|
2112
2115
|
await send(msg, probe ? { probe: true } : void 0);
|
|
2113
2116
|
this.deferCounts.delete(seq);
|
|
@@ -2231,7 +2234,9 @@ async function runLivenessTick(deps) {
|
|
|
2231
2234
|
}
|
|
2232
2235
|
}
|
|
2233
2236
|
function createDelivery(ctx, daemonCmux) {
|
|
2234
|
-
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
2237
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
2238
|
+
const notifyFault = ctx.notifyFault ?? (() => {
|
|
2239
|
+
});
|
|
2235
2240
|
const defaultNotify = async (args) => {
|
|
2236
2241
|
try {
|
|
2237
2242
|
await appendToMailbox({
|
|
@@ -2254,6 +2259,7 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2254
2259
|
const cfg = loadConfig();
|
|
2255
2260
|
const deliveries = /* @__PURE__ */ new Map();
|
|
2256
2261
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
2262
|
+
const stuckNotified = /* @__PURE__ */ new Set();
|
|
2257
2263
|
const sessionStartMs = Date.now();
|
|
2258
2264
|
let delivering = false;
|
|
2259
2265
|
const deliveryCore = async () => {
|
|
@@ -2325,6 +2331,18 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2325
2331
|
break;
|
|
2326
2332
|
}
|
|
2327
2333
|
}
|
|
2334
|
+
const stuck = d.stats().stuck;
|
|
2335
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
2336
|
+
stuckNotified.add(project);
|
|
2337
|
+
const { maxDeferCount } = d.stats();
|
|
2338
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
|
|
2339
|
+
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.`;
|
|
2340
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
2341
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
2342
|
+
telegramBridge?.pushRaw(project, text);
|
|
2343
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
2344
|
+
stuckNotified.delete(project);
|
|
2345
|
+
}
|
|
2328
2346
|
}
|
|
2329
2347
|
};
|
|
2330
2348
|
const deliveryTick = async () => {
|
|
@@ -2575,7 +2593,12 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2575
2593
|
captainStopped: null,
|
|
2576
2594
|
captainState: deriveCaptainState(capEntry),
|
|
2577
2595
|
commandPresent: null,
|
|
2578
|
-
crews: store.list(project)
|
|
2596
|
+
crews: store.list(project),
|
|
2597
|
+
// #579/#484 Gap 3: surface the same deferral stats already exposed to
|
|
2598
|
+
// the snapshot (line ~135 below) on the health row too, so `squadrant
|
|
2599
|
+
// doctor` / `squadrant status --detailed` show a stuck delivery with
|
|
2600
|
+
// zero configuration.
|
|
2601
|
+
captainDeferral: deliveryStats(project)
|
|
2579
2602
|
}));
|
|
2580
2603
|
}
|
|
2581
2604
|
return out;
|
|
@@ -3259,6 +3282,14 @@ function createTelegramBridge(opts) {
|
|
|
3259
3282
|
s.offset = next;
|
|
3260
3283
|
saveState(stateRoot, s);
|
|
3261
3284
|
}
|
|
3285
|
+
async function sendToTopic(project, text) {
|
|
3286
|
+
let threadId = loadState(stateRoot).topics[topicKey(project)];
|
|
3287
|
+
if (threadId === void 0) {
|
|
3288
|
+
threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
|
|
3289
|
+
setTopic(stateRoot, project, threadId);
|
|
3290
|
+
}
|
|
3291
|
+
await client.sendMessage(cfg.supergroupId, threadId, text);
|
|
3292
|
+
}
|
|
3262
3293
|
async function deliverOutbound(project, ev) {
|
|
3263
3294
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
3264
3295
|
const live = loadState(stateRoot).notify[project];
|
|
@@ -3267,12 +3298,10 @@ function createTelegramBridge(opts) {
|
|
|
3267
3298
|
return;
|
|
3268
3299
|
if (!tierIncludes(resolved.crew, ev.type))
|
|
3269
3300
|
return;
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
}
|
|
3275
|
-
await client.sendMessage(cfg.supergroupId, threadId, formatLifecycle(project, ev));
|
|
3301
|
+
await sendToTopic(project, formatLifecycle(project, ev));
|
|
3302
|
+
}
|
|
3303
|
+
async function deliverRawOutbound(project, text) {
|
|
3304
|
+
await sendToTopic(project, text);
|
|
3276
3305
|
}
|
|
3277
3306
|
function resolveLiveNotify(project) {
|
|
3278
3307
|
const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
|
|
@@ -3580,6 +3609,11 @@ function createTelegramBridge(opts) {
|
|
|
3580
3609
|
log(`telegram outbound failed project=${project}: ${e.message}`);
|
|
3581
3610
|
});
|
|
3582
3611
|
},
|
|
3612
|
+
pushRaw(project, text) {
|
|
3613
|
+
void deliverRawOutbound(project, text).catch((e) => {
|
|
3614
|
+
log(`telegram raw push failed project=${project}: ${e.message}`);
|
|
3615
|
+
});
|
|
3616
|
+
},
|
|
3583
3617
|
health() {
|
|
3584
3618
|
return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };
|
|
3585
3619
|
}
|
|
@@ -5090,7 +5124,60 @@ var RuntimeRegistry = class {
|
|
|
5090
5124
|
};
|
|
5091
5125
|
|
|
5092
5126
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
5093
|
-
import {
|
|
5127
|
+
import { execFile as execFileCb, execSync as execSync7 } from "child_process";
|
|
5128
|
+
import { promisify as promisify2 } from "util";
|
|
5129
|
+
var execFile3 = promisify2(execFileCb);
|
|
5130
|
+
function createCmuxNotifier(_scope) {
|
|
5131
|
+
return {
|
|
5132
|
+
name: "cmux",
|
|
5133
|
+
async probe() {
|
|
5134
|
+
try {
|
|
5135
|
+
execSync7("squadrant runtime status --command", { encoding: "utf-8", stdio: "pipe" });
|
|
5136
|
+
return { installed: true, reachable: true };
|
|
5137
|
+
} catch (err) {
|
|
5138
|
+
const code = err.code;
|
|
5139
|
+
if (code === "ENOENT") {
|
|
5140
|
+
return { installed: false, reachable: false };
|
|
5141
|
+
}
|
|
5142
|
+
return { installed: true, reachable: false };
|
|
5143
|
+
}
|
|
5144
|
+
},
|
|
5145
|
+
async notify(message) {
|
|
5146
|
+
await execFile3("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
// packages/workspaces/dist/notifiers/registry.js
|
|
5152
|
+
var DEFAULT_NOTIFIER = "cmux";
|
|
5153
|
+
var NotifierRegistry = class {
|
|
5154
|
+
factories;
|
|
5155
|
+
constructor(factories) {
|
|
5156
|
+
this.factories = factories;
|
|
5157
|
+
}
|
|
5158
|
+
get(config) {
|
|
5159
|
+
const name = config.notifier ?? DEFAULT_NOTIFIER;
|
|
5160
|
+
return this.getFactory(name)({});
|
|
5161
|
+
}
|
|
5162
|
+
getFactory(name) {
|
|
5163
|
+
const factory = this.factories[name];
|
|
5164
|
+
if (!factory) {
|
|
5165
|
+
throw new Error(`Unknown notifier provider '${name}' \u2014 no factory registered`);
|
|
5166
|
+
}
|
|
5167
|
+
return factory;
|
|
5168
|
+
}
|
|
5169
|
+
async probeAll() {
|
|
5170
|
+
const results = {};
|
|
5171
|
+
for (const [name, factory] of Object.entries(this.factories)) {
|
|
5172
|
+
try {
|
|
5173
|
+
results[name] = await factory({}).probe();
|
|
5174
|
+
} catch {
|
|
5175
|
+
results[name] = { installed: false, reachable: false };
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
return results;
|
|
5179
|
+
}
|
|
5180
|
+
};
|
|
5094
5181
|
|
|
5095
5182
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
5096
5183
|
import fs16 from "fs/promises";
|
|
@@ -5868,6 +5955,16 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
5868
5955
|
sendReply
|
|
5869
5956
|
});
|
|
5870
5957
|
}
|
|
5958
|
+
function buildNotifyFault(log) {
|
|
5959
|
+
const registry = new NotifierRegistry({ cmux: createCmuxNotifier });
|
|
5960
|
+
return async (project, text) => {
|
|
5961
|
+
try {
|
|
5962
|
+
await registry.get(loadConfig()).notify(`[${project}] ${text}`);
|
|
5963
|
+
} catch (e) {
|
|
5964
|
+
log(`fault notify failed project=${project}: ${e.message}`);
|
|
5965
|
+
}
|
|
5966
|
+
};
|
|
5967
|
+
}
|
|
5871
5968
|
function startSquadrantd(opts = {}) {
|
|
5872
5969
|
const ctx = buildContext(opts);
|
|
5873
5970
|
const { stateRoot, store, log, spawn: spawn2, writeResult, inFlightHeadlessIds, activeHeadlessKills } = ctx;
|
|
@@ -5931,6 +6028,8 @@ function startSquadrantd(opts = {}) {
|
|
|
5931
6028
|
ctx.lifecycleSources = [cmuxStoreSource, nativeHookSource, codexAppServerSource];
|
|
5932
6029
|
const tgCfg = loadConfig().telegram;
|
|
5933
6030
|
ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : void 0);
|
|
6031
|
+
if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;
|
|
6032
|
+
else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);
|
|
5934
6033
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
5935
6034
|
const resendRuntime = createCmuxDriver();
|
|
5936
6035
|
ctx.resendFirstTurn = opts.resendFirstTurn ?? (async (rec) => {
|