squadrant 0.14.3 → 0.16.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/index.js +4375 -633
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +543 -165
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/squadrantd.js
CHANGED
|
@@ -60,10 +60,10 @@ var init_snapshot = __esm({
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
// packages/cli/src/squadrantd.ts
|
|
63
|
-
import { join as
|
|
64
|
-
import { homedir as
|
|
63
|
+
import { join as join18, dirname as dirname5 } from "path";
|
|
64
|
+
import { homedir as homedir13 } from "os";
|
|
65
65
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
66
|
-
import { readFileSync as
|
|
66
|
+
import { readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
67
67
|
|
|
68
68
|
// packages/shared/dist/config.js
|
|
69
69
|
import fs from "fs";
|
|
@@ -154,6 +154,9 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
154
154
|
fs.mkdirSync(dir, { recursive: true });
|
|
155
155
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
156
156
|
}
|
|
157
|
+
function resolveHome(p) {
|
|
158
|
+
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
159
|
+
}
|
|
157
160
|
|
|
158
161
|
// packages/shared/dist/project-config.js
|
|
159
162
|
import fs2 from "fs";
|
|
@@ -230,22 +233,22 @@ var MINIMAL_TEMPLATE = [
|
|
|
230
233
|
``
|
|
231
234
|
].join("\n");
|
|
232
235
|
function ensureSocketAutomation(opts = {}) {
|
|
233
|
-
const
|
|
234
|
-
if (!existsSync(
|
|
235
|
-
mkdirSync(dirname(
|
|
236
|
-
writeFileSync(
|
|
237
|
-
return { path:
|
|
236
|
+
const path19 = opts.path ?? defaultCmuxConfigPath();
|
|
237
|
+
if (!existsSync(path19)) {
|
|
238
|
+
mkdirSync(dirname(path19), { recursive: true });
|
|
239
|
+
writeFileSync(path19, MINIMAL_TEMPLATE);
|
|
240
|
+
return { path: path19, changed: true, alreadySet: false };
|
|
238
241
|
}
|
|
239
|
-
const text = readFileSync(
|
|
242
|
+
const text = readFileSync(path19, "utf-8");
|
|
240
243
|
const current = parse(text)?.automation?.socketControlMode;
|
|
241
244
|
if (current === AUTOMATION_MODE) {
|
|
242
|
-
return { path:
|
|
245
|
+
return { path: path19, changed: false, alreadySet: true };
|
|
243
246
|
}
|
|
244
247
|
const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
|
|
245
248
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
246
249
|
});
|
|
247
|
-
writeFileSync(
|
|
248
|
-
return { path:
|
|
250
|
+
writeFileSync(path19, applyEdits(text, edits));
|
|
251
|
+
return { path: path19, changed: true, alreadySet: false };
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
// packages/shared/dist/lib/cmux-probe.js
|
|
@@ -367,15 +370,15 @@ function sleep(ms) {
|
|
|
367
370
|
function defaultStatePath() {
|
|
368
371
|
return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
|
|
369
372
|
}
|
|
370
|
-
function readState(
|
|
373
|
+
function readState(path19) {
|
|
371
374
|
try {
|
|
372
|
-
return JSON.parse(readFileSync4(
|
|
375
|
+
return JSON.parse(readFileSync4(path19, "utf-8"));
|
|
373
376
|
} catch {
|
|
374
377
|
return {};
|
|
375
378
|
}
|
|
376
379
|
}
|
|
377
380
|
async function ensureCmuxAutoConfig(opts = {}) {
|
|
378
|
-
const
|
|
381
|
+
const statePath3 = opts.statePath ?? defaultStatePath();
|
|
379
382
|
const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;
|
|
380
383
|
const probe = opts.probe ?? probeCmuxDaemonDirect;
|
|
381
384
|
const cfg = ensureConfig({ path: opts.configPath });
|
|
@@ -383,15 +386,15 @@ async function ensureCmuxAutoConfig(opts = {}) {
|
|
|
383
386
|
const needsRestart = verdict === "denied";
|
|
384
387
|
let promptedThisRun = false;
|
|
385
388
|
if (needsRestart) {
|
|
386
|
-
const already = readState(
|
|
389
|
+
const already = readState(statePath3).promptedRestart === true;
|
|
387
390
|
if (!already) {
|
|
388
|
-
mkdirSync2(dirname2(
|
|
389
|
-
writeFileSync3(
|
|
391
|
+
mkdirSync2(dirname2(statePath3), { recursive: true });
|
|
392
|
+
writeFileSync3(statePath3, JSON.stringify({ promptedRestart: true }));
|
|
390
393
|
promptedThisRun = true;
|
|
391
394
|
}
|
|
392
395
|
} else if (verdict === "reachable") {
|
|
393
|
-
if (existsSync4(
|
|
394
|
-
rmSync2(
|
|
396
|
+
if (existsSync4(statePath3))
|
|
397
|
+
rmSync2(statePath3, { force: true });
|
|
395
398
|
}
|
|
396
399
|
return {
|
|
397
400
|
configPath: cfg.path,
|
|
@@ -416,16 +419,25 @@ var compatManifest = {
|
|
|
416
419
|
}
|
|
417
420
|
};
|
|
418
421
|
|
|
422
|
+
// packages/shared/dist/lib/update-check.js
|
|
423
|
+
import fs3 from "fs";
|
|
424
|
+
import path3 from "path";
|
|
425
|
+
import os3 from "os";
|
|
426
|
+
import https from "https";
|
|
427
|
+
var UPDATE_CHECK_STATE_PATH = path3.join(os3.homedir(), ".config", "squadrant", "update-check.json");
|
|
428
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
429
|
+
var FAILURE_RETRY_MS = 60 * 60 * 1e3;
|
|
430
|
+
|
|
419
431
|
// packages/shared/dist/lib/git-worktree.js
|
|
420
432
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
421
|
-
import
|
|
433
|
+
import path4 from "path";
|
|
422
434
|
|
|
423
435
|
// packages/shared/dist/lib/resolve-text-input.js
|
|
424
|
-
import
|
|
436
|
+
import fs4 from "fs";
|
|
425
437
|
|
|
426
438
|
// packages/shared/dist/lib/runtime-sync.js
|
|
427
|
-
import
|
|
428
|
-
import
|
|
439
|
+
import fs5 from "fs";
|
|
440
|
+
import path5 from "path";
|
|
429
441
|
|
|
430
442
|
// packages/shared/dist/lib/tool-compat.js
|
|
431
443
|
function parseSemVer(v) {
|
|
@@ -459,13 +471,13 @@ function checkToolCompat(name, rawVersion, entry) {
|
|
|
459
471
|
}
|
|
460
472
|
|
|
461
473
|
// packages/shared/dist/lib/canonical-source.js
|
|
462
|
-
import
|
|
463
|
-
import
|
|
474
|
+
import fs6 from "fs";
|
|
475
|
+
import path6 from "path";
|
|
464
476
|
|
|
465
477
|
// packages/shared/dist/lib/daily-logs.js
|
|
466
478
|
import { execSync } from "child_process";
|
|
467
|
-
import
|
|
468
|
-
import
|
|
479
|
+
import fs7 from "fs";
|
|
480
|
+
import path7 from "path";
|
|
469
481
|
import matter from "gray-matter";
|
|
470
482
|
|
|
471
483
|
// packages/core/dist/state-machine.js
|
|
@@ -626,7 +638,7 @@ function formatMessage(rec, event) {
|
|
|
626
638
|
return `CREW STALLED ${tag}: no heartbeat in ${rec.heartbeatBudgetMs}ms`;
|
|
627
639
|
}
|
|
628
640
|
case "awaiting-input":
|
|
629
|
-
return `CREW IDLE ${tag}: turn ended
|
|
641
|
+
return `CREW IDLE ${tag}: turn ended, awaiting your reply.`;
|
|
630
642
|
default:
|
|
631
643
|
return null;
|
|
632
644
|
}
|
|
@@ -1007,7 +1019,7 @@ function createDaemon(deps) {
|
|
|
1007
1019
|
}
|
|
1008
1020
|
|
|
1009
1021
|
// packages/core/dist/mailbox.js
|
|
1010
|
-
import { promises as
|
|
1022
|
+
import { promises as fs8 } from "fs";
|
|
1011
1023
|
import { join as join5 } from "path";
|
|
1012
1024
|
import { randomUUID } from "crypto";
|
|
1013
1025
|
function inboxDir(stateRoot) {
|
|
@@ -1024,7 +1036,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
1024
1036
|
const dir = inboxDir(stateRoot);
|
|
1025
1037
|
let entries;
|
|
1026
1038
|
try {
|
|
1027
|
-
entries = await
|
|
1039
|
+
entries = await fs8.readdir(dir);
|
|
1028
1040
|
} catch {
|
|
1029
1041
|
return [];
|
|
1030
1042
|
}
|
|
@@ -1033,7 +1045,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
|
|
|
1033
1045
|
}
|
|
1034
1046
|
async function readMaxSeqFromFile(file) {
|
|
1035
1047
|
try {
|
|
1036
|
-
const buf = await
|
|
1048
|
+
const buf = await fs8.readFile(file, "utf-8");
|
|
1037
1049
|
if (!buf.trim())
|
|
1038
1050
|
return 0;
|
|
1039
1051
|
const lines = buf.trim().split("\n");
|
|
@@ -1075,12 +1087,12 @@ function withProjectLock(project, fn) {
|
|
|
1075
1087
|
function appendEntry(stateRoot, project, build) {
|
|
1076
1088
|
return withProjectLock(project, async () => {
|
|
1077
1089
|
const dir = inboxDir(stateRoot);
|
|
1078
|
-
await
|
|
1090
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
1079
1091
|
const file = logPath(stateRoot, project);
|
|
1080
1092
|
const lastSeq = await readMaxSeq(stateRoot, project);
|
|
1081
1093
|
const seq = lastSeq + 1;
|
|
1082
1094
|
const entry = build(seq);
|
|
1083
|
-
await
|
|
1095
|
+
await fs8.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
|
|
1084
1096
|
return seq;
|
|
1085
1097
|
});
|
|
1086
1098
|
}
|
|
@@ -1111,7 +1123,7 @@ function cursorPath(stateRoot, project, subscriber) {
|
|
|
1111
1123
|
async function readCursor(opts) {
|
|
1112
1124
|
let buf;
|
|
1113
1125
|
try {
|
|
1114
|
-
buf = await
|
|
1126
|
+
buf = await fs8.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
|
|
1115
1127
|
} catch (e) {
|
|
1116
1128
|
if (e.code === "ENOENT")
|
|
1117
1129
|
return null;
|
|
@@ -1126,7 +1138,7 @@ async function readCursor(opts) {
|
|
|
1126
1138
|
}
|
|
1127
1139
|
}
|
|
1128
1140
|
async function writeCursor(opts) {
|
|
1129
|
-
await
|
|
1141
|
+
await fs8.mkdir(inboxDir(opts.stateRoot), { recursive: true });
|
|
1130
1142
|
const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
|
|
1131
1143
|
const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
|
|
1132
1144
|
const data = {
|
|
@@ -1134,7 +1146,7 @@ async function writeCursor(opts) {
|
|
|
1134
1146
|
subscriber: opts.subscriber,
|
|
1135
1147
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1136
1148
|
};
|
|
1137
|
-
const handle = await
|
|
1149
|
+
const handle = await fs8.open(tmp, "w");
|
|
1138
1150
|
try {
|
|
1139
1151
|
await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
|
|
1140
1152
|
await handle.sync();
|
|
@@ -1142,9 +1154,9 @@ async function writeCursor(opts) {
|
|
|
1142
1154
|
await handle.close();
|
|
1143
1155
|
}
|
|
1144
1156
|
try {
|
|
1145
|
-
await
|
|
1157
|
+
await fs8.rename(tmp, dest);
|
|
1146
1158
|
} catch (e) {
|
|
1147
|
-
await
|
|
1159
|
+
await fs8.unlink(tmp).catch(() => {
|
|
1148
1160
|
});
|
|
1149
1161
|
throw e;
|
|
1150
1162
|
}
|
|
@@ -1155,7 +1167,7 @@ async function* readFromCursor(opts) {
|
|
|
1155
1167
|
for (const file of files) {
|
|
1156
1168
|
let buf;
|
|
1157
1169
|
try {
|
|
1158
|
-
buf = await
|
|
1170
|
+
buf = await fs8.readFile(file, "utf-8");
|
|
1159
1171
|
} catch (e) {
|
|
1160
1172
|
if (e.code === "ENOENT")
|
|
1161
1173
|
continue;
|
|
@@ -1181,7 +1193,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1181
1193
|
let sizeBytes = 0;
|
|
1182
1194
|
for (const f of [file, ...rotated]) {
|
|
1183
1195
|
try {
|
|
1184
|
-
sizeBytes += (await
|
|
1196
|
+
sizeBytes += (await fs8.stat(f)).size;
|
|
1185
1197
|
} catch (e) {
|
|
1186
1198
|
if (e.code !== "ENOENT")
|
|
1187
1199
|
throw e;
|
|
@@ -1197,7 +1209,7 @@ async function mailboxStats(stateRoot, project) {
|
|
|
1197
1209
|
}
|
|
1198
1210
|
async function oldestEntryAgeMs(file) {
|
|
1199
1211
|
try {
|
|
1200
|
-
const buf = await
|
|
1212
|
+
const buf = await fs8.readFile(file, "utf-8");
|
|
1201
1213
|
const firstLine = buf.split("\n").find((l) => l.trim());
|
|
1202
1214
|
if (!firstLine)
|
|
1203
1215
|
return 0;
|
|
@@ -1212,7 +1224,7 @@ async function rotateIfNeeded(opts) {
|
|
|
1212
1224
|
const file = logPath(opts.stateRoot, opts.project);
|
|
1213
1225
|
let size = 0;
|
|
1214
1226
|
try {
|
|
1215
|
-
size = (await
|
|
1227
|
+
size = (await fs8.stat(file)).size;
|
|
1216
1228
|
} catch (e) {
|
|
1217
1229
|
if (e.code === "ENOENT")
|
|
1218
1230
|
return { rotated: false };
|
|
@@ -1228,22 +1240,22 @@ async function rotateIfNeeded(opts) {
|
|
|
1228
1240
|
const dst = `${file}.${n + 1}`;
|
|
1229
1241
|
if (n + 1 > opts.keepCount) {
|
|
1230
1242
|
try {
|
|
1231
|
-
await
|
|
1243
|
+
await fs8.unlink(src);
|
|
1232
1244
|
} catch (e) {
|
|
1233
1245
|
if (e.code !== "ENOENT")
|
|
1234
1246
|
throw e;
|
|
1235
1247
|
}
|
|
1236
1248
|
} else {
|
|
1237
1249
|
try {
|
|
1238
|
-
await
|
|
1250
|
+
await fs8.rename(src, dst);
|
|
1239
1251
|
} catch (e) {
|
|
1240
1252
|
if (e.code !== "ENOENT")
|
|
1241
1253
|
throw e;
|
|
1242
1254
|
}
|
|
1243
1255
|
}
|
|
1244
1256
|
}
|
|
1245
|
-
await
|
|
1246
|
-
await
|
|
1257
|
+
await fs8.rename(file, `${file}.1`);
|
|
1258
|
+
await fs8.writeFile(file, "", { encoding: "utf-8" });
|
|
1247
1259
|
return { rotated: true, from: file, to: `${file}.1` };
|
|
1248
1260
|
});
|
|
1249
1261
|
}
|
|
@@ -1443,14 +1455,14 @@ var TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
|
1443
1455
|
function projectHealth(input) {
|
|
1444
1456
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
1445
1457
|
const out = [];
|
|
1446
|
-
const captainState = captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown";
|
|
1458
|
+
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
1447
1459
|
out.push({
|
|
1448
1460
|
kind: "captain",
|
|
1449
1461
|
project,
|
|
1450
1462
|
ref: captainName,
|
|
1451
1463
|
state: captainState,
|
|
1452
1464
|
lastSeenMs: null,
|
|
1453
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : void 0
|
|
1465
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
1454
1466
|
});
|
|
1455
1467
|
if (commandPresent !== null) {
|
|
1456
1468
|
out.push({
|
|
@@ -1481,6 +1493,26 @@ function presence(p) {
|
|
|
1481
1493
|
return "unknown";
|
|
1482
1494
|
return p ? "alive" : "gone";
|
|
1483
1495
|
}
|
|
1496
|
+
function deriveCaptainState(e) {
|
|
1497
|
+
if (!e)
|
|
1498
|
+
return "unknown";
|
|
1499
|
+
if (e.lastState === "end")
|
|
1500
|
+
return "stopped";
|
|
1501
|
+
if (!e.pidAlive)
|
|
1502
|
+
return "gone";
|
|
1503
|
+
return "alive";
|
|
1504
|
+
}
|
|
1505
|
+
function reconcileLiveness(prev, next) {
|
|
1506
|
+
if (!prev)
|
|
1507
|
+
return next;
|
|
1508
|
+
if (next.source === "scan") {
|
|
1509
|
+
const pidAlive = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
|
|
1510
|
+
return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
1511
|
+
}
|
|
1512
|
+
if (next.startedAt >= prev.startedAt || next.lastState === "end")
|
|
1513
|
+
return next;
|
|
1514
|
+
return prev;
|
|
1515
|
+
}
|
|
1484
1516
|
|
|
1485
1517
|
// packages/core/dist/store.js
|
|
1486
1518
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
|
|
@@ -1639,7 +1671,73 @@ function makeGate(opts) {
|
|
|
1639
1671
|
import { homedir as homedir5 } from "os";
|
|
1640
1672
|
import { join as join8 } from "path";
|
|
1641
1673
|
import { spawn as realSpawn } from "child_process";
|
|
1642
|
-
import { writeFileSync as
|
|
1674
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
1675
|
+
|
|
1676
|
+
// packages/core/dist/daemon/liveness-registry.js
|
|
1677
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync2 } from "fs";
|
|
1678
|
+
var LivenessRegistry = class {
|
|
1679
|
+
path;
|
|
1680
|
+
readFile;
|
|
1681
|
+
writeFile;
|
|
1682
|
+
map = /* @__PURE__ */ new Map();
|
|
1683
|
+
constructor(opts) {
|
|
1684
|
+
this.path = opts.path;
|
|
1685
|
+
this.readFile = opts.readFile ?? ((p) => {
|
|
1686
|
+
try {
|
|
1687
|
+
return readFileSync7(p, "utf-8");
|
|
1688
|
+
} catch {
|
|
1689
|
+
return void 0;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
this.writeFile = opts.writeFile ?? ((p, c) => {
|
|
1693
|
+
writeFileSync6(`${p}.tmp`, c);
|
|
1694
|
+
renameSync2(`${p}.tmp`, p);
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
load() {
|
|
1698
|
+
const raw = this.readFile(this.path);
|
|
1699
|
+
if (!raw)
|
|
1700
|
+
return;
|
|
1701
|
+
try {
|
|
1702
|
+
const arr = JSON.parse(raw);
|
|
1703
|
+
this.map = new Map(arr.map((e) => [e.project, e]));
|
|
1704
|
+
} catch {
|
|
1705
|
+
this.map = /* @__PURE__ */ new Map();
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
get(project) {
|
|
1709
|
+
return this.map.get(project);
|
|
1710
|
+
}
|
|
1711
|
+
all() {
|
|
1712
|
+
return [...this.map.values()];
|
|
1713
|
+
}
|
|
1714
|
+
apply(next) {
|
|
1715
|
+
this.map.set(next.project, reconcileLiveness(this.map.get(next.project), next));
|
|
1716
|
+
this.persist();
|
|
1717
|
+
}
|
|
1718
|
+
markEnded(project, at) {
|
|
1719
|
+
const e = this.map.get(project);
|
|
1720
|
+
if (!e)
|
|
1721
|
+
return;
|
|
1722
|
+
this.map.set(project, { ...e, lastState: "end", lastSeenAt: at });
|
|
1723
|
+
this.persist();
|
|
1724
|
+
}
|
|
1725
|
+
setPidAlive(project, alive, at) {
|
|
1726
|
+
const e = this.map.get(project);
|
|
1727
|
+
if (!e)
|
|
1728
|
+
return;
|
|
1729
|
+
this.map.set(project, { ...e, pidAlive: alive, lastSeenAt: at });
|
|
1730
|
+
this.persist();
|
|
1731
|
+
}
|
|
1732
|
+
persist() {
|
|
1733
|
+
try {
|
|
1734
|
+
this.writeFile(this.path, JSON.stringify(this.all(), null, 2));
|
|
1735
|
+
} catch {
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
// packages/core/dist/daemon/context.js
|
|
1643
1741
|
function defaultIsPidAlive(pid) {
|
|
1644
1742
|
try {
|
|
1645
1743
|
process.kill(pid, 0);
|
|
@@ -1660,7 +1758,7 @@ function buildContext(opts) {
|
|
|
1660
1758
|
mkdirSync5(resultsDir, { recursive: true });
|
|
1661
1759
|
const writeResult = (id, payload) => {
|
|
1662
1760
|
const p = join8(resultsDir, `${id}.txt`);
|
|
1663
|
-
|
|
1761
|
+
writeFileSync7(p, payload);
|
|
1664
1762
|
return p;
|
|
1665
1763
|
};
|
|
1666
1764
|
const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
@@ -1681,8 +1779,11 @@ function buildContext(opts) {
|
|
|
1681
1779
|
attachConns: /* @__PURE__ */ new Map(),
|
|
1682
1780
|
inFlightHeadlessIds: /* @__PURE__ */ new Set(),
|
|
1683
1781
|
activeHeadlessKills: /* @__PURE__ */ new Set(),
|
|
1684
|
-
|
|
1685
|
-
|
|
1782
|
+
livenessRegistry: (() => {
|
|
1783
|
+
const r = new LivenessRegistry({ path: join8(stateRoot, "liveness.json") });
|
|
1784
|
+
r.load();
|
|
1785
|
+
return r;
|
|
1786
|
+
})(),
|
|
1686
1787
|
resendFirstTurn: opts.resendFirstTurn,
|
|
1687
1788
|
// Late-bound — start.ts fills these before first use:
|
|
1688
1789
|
d: null,
|
|
@@ -2036,7 +2137,6 @@ var CaptainDelivery = class {
|
|
|
2036
2137
|
|
|
2037
2138
|
// packages/core/dist/daemon/delivery-loop.js
|
|
2038
2139
|
var CURSOR_SUBSCRIBER = "captain";
|
|
2039
|
-
var CAPTAIN_GONE_STREAK_K = 3;
|
|
2040
2140
|
var TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
2041
2141
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
2042
2142
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
@@ -2053,8 +2153,71 @@ function reapOrphanedCrews(store, project) {
|
|
|
2053
2153
|
}
|
|
2054
2154
|
return reaped;
|
|
2055
2155
|
}
|
|
2156
|
+
function logEntry(log, project, e) {
|
|
2157
|
+
if (!log || !e)
|
|
2158
|
+
return;
|
|
2159
|
+
log(`[${e.role}/${e.source}] ${project} pid=${e.pid} \u2192 ${deriveCaptainState(e)}`);
|
|
2160
|
+
}
|
|
2161
|
+
async function runLivenessTick(deps) {
|
|
2162
|
+
const now = deps.now();
|
|
2163
|
+
let records = [];
|
|
2164
|
+
try {
|
|
2165
|
+
records = await deps.liveness();
|
|
2166
|
+
} catch {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2170
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2171
|
+
for (const r of records) {
|
|
2172
|
+
if (r.role !== "captain")
|
|
2173
|
+
continue;
|
|
2174
|
+
let arr = byProject.get(r.project);
|
|
2175
|
+
if (!arr) {
|
|
2176
|
+
arr = [];
|
|
2177
|
+
byProject.set(r.project, arr);
|
|
2178
|
+
}
|
|
2179
|
+
arr.push(r);
|
|
2180
|
+
}
|
|
2181
|
+
for (const [project, recs] of byProject) {
|
|
2182
|
+
seen.add(project);
|
|
2183
|
+
const winner = recs.find((r) => r.pid == null || deps.isPidAlive(r.pid)) ?? recs[0];
|
|
2184
|
+
const entry = {
|
|
2185
|
+
project,
|
|
2186
|
+
role: "captain",
|
|
2187
|
+
pid: winner.pid,
|
|
2188
|
+
sessionId: winner.sessionId,
|
|
2189
|
+
startedAt: now,
|
|
2190
|
+
lastState: "start",
|
|
2191
|
+
lastSeenAt: now,
|
|
2192
|
+
pidAlive: winner.pid != null ? deps.isPidAlive(winner.pid) : true,
|
|
2193
|
+
source: "runtime"
|
|
2194
|
+
};
|
|
2195
|
+
const prev = deps.registry.get(project);
|
|
2196
|
+
if (prev && prev.lastState === "start")
|
|
2197
|
+
entry.startedAt = prev.startedAt;
|
|
2198
|
+
deps.registry.apply(entry);
|
|
2199
|
+
if (winner.pid != null)
|
|
2200
|
+
deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);
|
|
2201
|
+
logEntry(deps.log, project, deps.registry.get(project));
|
|
2202
|
+
}
|
|
2203
|
+
for (const e of deps.registry.all()) {
|
|
2204
|
+
if (e.role === "captain" && e.lastState === "start" && !seen.has(e.project)) {
|
|
2205
|
+
deps.registry.markEnded(e.project, now);
|
|
2206
|
+
logEntry(deps.log, e.project, deps.registry.get(e.project));
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (deps.reap) {
|
|
2210
|
+
for (const e of deps.registry.all()) {
|
|
2211
|
+
if (e.role !== "captain")
|
|
2212
|
+
continue;
|
|
2213
|
+
const state = deriveCaptainState(e);
|
|
2214
|
+
if (state === "stopped" || state === "gone")
|
|
2215
|
+
deps.reap(e.project);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2056
2219
|
function createDelivery(ctx, daemonCmux) {
|
|
2057
|
-
const { stateRoot, store, log,
|
|
2220
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
2058
2221
|
const defaultNotify = async (args) => {
|
|
2059
2222
|
try {
|
|
2060
2223
|
await appendToMailbox({
|
|
@@ -2080,45 +2243,41 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2080
2243
|
const sessionStartMs = Date.now();
|
|
2081
2244
|
let delivering = false;
|
|
2082
2245
|
const deliveryCore = async () => {
|
|
2246
|
+
await runLivenessTick({
|
|
2247
|
+
registry: livenessRegistry,
|
|
2248
|
+
liveness: () => cmux2.liveness ? cmux2.liveness() : Promise.resolve([]),
|
|
2249
|
+
isPidAlive,
|
|
2250
|
+
now: () => Date.now(),
|
|
2251
|
+
log,
|
|
2252
|
+
reap: (project) => {
|
|
2253
|
+
const reaped = reapOrphanedCrews(store, project);
|
|
2254
|
+
if (reaped > 0) {
|
|
2255
|
+
const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;
|
|
2256
|
+
log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);
|
|
2257
|
+
}
|
|
2258
|
+
return reaped;
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2083
2261
|
const injectedSurfaces = opts.captainSurfaces ?? {};
|
|
2084
2262
|
const allProjects = [.../* @__PURE__ */ new Set([
|
|
2085
2263
|
...Object.keys(cfg.projects ?? {}),
|
|
2086
2264
|
...Object.keys(injectedSurfaces),
|
|
2087
|
-
...store.listAll().map((t) => t.project)
|
|
2265
|
+
...store.listAll().map((t) => t.project),
|
|
2266
|
+
cfg.commandName
|
|
2088
2267
|
])];
|
|
2089
2268
|
for (const project of allProjects) {
|
|
2090
2269
|
const projCfg = cfg.projects?.[project];
|
|
2091
|
-
const captainTitle = projCfg?.captainName ?? `${project}-captain`;
|
|
2270
|
+
const captainTitle = project === cfg.commandName ? cfg.commandName : projCfg?.captainName ?? `${project}-captain`;
|
|
2092
2271
|
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
2093
2272
|
let surface = null;
|
|
2094
|
-
let surfacesLength = 0;
|
|
2095
2273
|
if (wsId) {
|
|
2096
2274
|
const surfaces = await cmux2.listSurfaces(wsId);
|
|
2097
|
-
surfacesLength = surfaces.length;
|
|
2098
2275
|
surface = discoverCaptainSurface(surfaces, captainTitle);
|
|
2099
2276
|
}
|
|
2100
2277
|
if (!surface)
|
|
2101
2278
|
surface = injectedSurfaces[project] ?? null;
|
|
2102
|
-
if (surface)
|
|
2103
|
-
if (stoppedProjects.has(project)) {
|
|
2104
|
-
stoppedProjects.delete(project);
|
|
2105
|
-
captainMissingStreak.set(project, 0);
|
|
2106
|
-
}
|
|
2107
|
-
captainMissingStreak.set(project, 0);
|
|
2108
|
-
} else {
|
|
2109
|
-
if (surfacesLength > 0) {
|
|
2110
|
-
const streak = (captainMissingStreak.get(project) ?? 0) + 1;
|
|
2111
|
-
captainMissingStreak.set(project, streak);
|
|
2112
|
-
if (streak >= CAPTAIN_GONE_STREAK_K) {
|
|
2113
|
-
if (!stoppedProjects.has(project)) {
|
|
2114
|
-
stoppedProjects.add(project);
|
|
2115
|
-
const reaped = reapOrphanedCrews(store, project);
|
|
2116
|
-
log(`captain ${captainTitle}: surface gone for ${CAPTAIN_GONE_STREAK_K} sweeps \u2014 stopping delivery${reaped > 0 ? `, reaped ${reaped} orphaned crew(s)` : ""}`);
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2279
|
+
if (!surface)
|
|
2120
2280
|
continue;
|
|
2121
|
-
}
|
|
2122
2281
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
2123
2282
|
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
2124
2283
|
let d = deliveries.get(project);
|
|
@@ -2132,11 +2291,16 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2132
2291
|
for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {
|
|
2133
2292
|
if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {
|
|
2134
2293
|
if (!TERMINAL_KINDS.has(entry.kind)) {
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2294
|
+
const isExemptMessage = entry.kind === "captain.message" && entry.payload?.source !== "daemon";
|
|
2295
|
+
if (!isExemptMessage) {
|
|
2296
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);
|
|
2297
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);
|
|
2301
|
+
} else {
|
|
2302
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
2138
2303
|
}
|
|
2139
|
-
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
2140
2304
|
}
|
|
2141
2305
|
const result = await d.deliver(entry, (text, sendOpts) => cmux2.send(surface, text, sendOpts));
|
|
2142
2306
|
if ("delivered" in result) {
|
|
@@ -2240,7 +2404,7 @@ function createServer2(ctx, handlers) {
|
|
|
2240
2404
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
2241
2405
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2242
2406
|
import { join as join9 } from "path";
|
|
2243
|
-
import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as
|
|
2407
|
+
import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
2244
2408
|
var SELF_PATH = fileURLToPath2(import.meta.url);
|
|
2245
2409
|
function distBuiltAt() {
|
|
2246
2410
|
try {
|
|
@@ -2249,10 +2413,10 @@ function distBuiltAt() {
|
|
|
2249
2413
|
return 0;
|
|
2250
2414
|
}
|
|
2251
2415
|
}
|
|
2252
|
-
function gatherLogStats(
|
|
2416
|
+
function gatherLogStats(path19, now, windowMs) {
|
|
2253
2417
|
let sizeBytes = 0;
|
|
2254
2418
|
try {
|
|
2255
|
-
sizeBytes = statSync2(
|
|
2419
|
+
sizeBytes = statSync2(path19).size;
|
|
2256
2420
|
} catch {
|
|
2257
2421
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
2258
2422
|
}
|
|
@@ -2263,7 +2427,7 @@ function gatherLogStats(path17, now, windowMs) {
|
|
|
2263
2427
|
const len = sizeBytes - start;
|
|
2264
2428
|
let text = "";
|
|
2265
2429
|
try {
|
|
2266
|
-
const fd = openSync2(
|
|
2430
|
+
const fd = openSync2(path19, "r");
|
|
2267
2431
|
try {
|
|
2268
2432
|
const buf = Buffer.alloc(len);
|
|
2269
2433
|
readSync(fd, buf, 0, len, start);
|
|
@@ -2304,7 +2468,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
2304
2468
|
if (!n.endsWith(".json"))
|
|
2305
2469
|
continue;
|
|
2306
2470
|
try {
|
|
2307
|
-
JSON.parse(
|
|
2471
|
+
JSON.parse(readFileSync8(join9(dir, n), "utf-8"));
|
|
2308
2472
|
} catch {
|
|
2309
2473
|
corruptCount++;
|
|
2310
2474
|
}
|
|
@@ -2389,14 +2553,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2389
2553
|
for (const project of names) {
|
|
2390
2554
|
const proj = config.projects[project];
|
|
2391
2555
|
const captainName = proj?.captainName ?? `${project}-captain`;
|
|
2392
|
-
const
|
|
2393
|
-
const streak = ctx.captainMissingStreak.get(project);
|
|
2394
|
-
const captainStopped = stopped ? true : streak === 0 ? false : null;
|
|
2556
|
+
const capEntry = ctx.livenessRegistry.get(project);
|
|
2395
2557
|
out.push(...projectHealth({
|
|
2396
2558
|
project,
|
|
2397
2559
|
now,
|
|
2398
2560
|
captainName,
|
|
2399
|
-
captainStopped,
|
|
2561
|
+
captainStopped: null,
|
|
2562
|
+
captainState: deriveCaptainState(capEntry),
|
|
2400
2563
|
commandPresent: null,
|
|
2401
2564
|
crews: store.list(project)
|
|
2402
2565
|
}));
|
|
@@ -2496,7 +2659,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2496
2659
|
}
|
|
2497
2660
|
})();
|
|
2498
2661
|
const server = createServer2(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });
|
|
2499
|
-
log(`
|
|
2662
|
+
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
2500
2663
|
let deliveryTick = initialDeliveryTick;
|
|
2501
2664
|
let probeTick;
|
|
2502
2665
|
if (daemonCmux) {
|
|
@@ -2557,7 +2720,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2557
2720
|
rotationTimer.unref?.();
|
|
2558
2721
|
}
|
|
2559
2722
|
return {
|
|
2560
|
-
stop() {
|
|
2723
|
+
stop(reason = "requested") {
|
|
2724
|
+
log(`exit pid=${process.pid} reason=${reason}`);
|
|
2561
2725
|
if (deliveryTimer)
|
|
2562
2726
|
clearInterval(deliveryTimer);
|
|
2563
2727
|
if (probeTimer)
|
|
@@ -2581,7 +2745,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2581
2745
|
for (const kill of ctx.activeHeadlessKills)
|
|
2582
2746
|
kill();
|
|
2583
2747
|
return new Promise((resolve2) => server.close(() => {
|
|
2584
|
-
log(
|
|
2748
|
+
log(`exit-complete pid=${process.pid}`);
|
|
2585
2749
|
resolve2();
|
|
2586
2750
|
}));
|
|
2587
2751
|
},
|
|
@@ -2592,8 +2756,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2592
2756
|
|
|
2593
2757
|
// packages/core/dist/session-freshness.js
|
|
2594
2758
|
import crypto from "crypto";
|
|
2595
|
-
import
|
|
2596
|
-
import
|
|
2759
|
+
import fs9 from "fs";
|
|
2760
|
+
import path8 from "path";
|
|
2597
2761
|
|
|
2598
2762
|
// packages/core/dist/crew-protocol.js
|
|
2599
2763
|
function buildCompletionProtocol(taskId, project) {
|
|
@@ -2753,21 +2917,33 @@ function createRunCommand(cliBin) {
|
|
|
2753
2917
|
}
|
|
2754
2918
|
};
|
|
2755
2919
|
}
|
|
2920
|
+
function isCaptainAliveFromHealth(rows, project) {
|
|
2921
|
+
return rows.some((h) => h.kind === "captain" && h.project === project && h.state === "alive");
|
|
2922
|
+
}
|
|
2756
2923
|
function createIsCaptainAlive(sock) {
|
|
2757
2924
|
return async (project) => {
|
|
2758
2925
|
try {
|
|
2759
2926
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
2760
|
-
|
|
2761
|
-
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
2927
|
+
return isCaptainAliveFromHealth(health ?? [], project);
|
|
2762
2928
|
} catch {
|
|
2763
2929
|
return false;
|
|
2764
2930
|
}
|
|
2765
2931
|
};
|
|
2766
2932
|
}
|
|
2767
|
-
function createLaunch(cliBin) {
|
|
2768
|
-
return
|
|
2769
|
-
|
|
2770
|
-
|
|
2933
|
+
function createLaunch(cliBin, log) {
|
|
2934
|
+
return (project) => new Promise((resolve2, reject) => {
|
|
2935
|
+
execFile(process.execPath, [cliBin, "launch", project, "--headless"], { timeout: 3e4 }, (err, stdout, stderr) => {
|
|
2936
|
+
const output = capOutput(stdout ?? "", stderr ?? "");
|
|
2937
|
+
if (err) {
|
|
2938
|
+
log?.(`launch ${project} failed: ${output}`);
|
|
2939
|
+
reject(err);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
if (output !== "(no output)")
|
|
2943
|
+
log?.(`launch ${project}: ${output}`);
|
|
2944
|
+
resolve2();
|
|
2945
|
+
});
|
|
2946
|
+
});
|
|
2771
2947
|
}
|
|
2772
2948
|
|
|
2773
2949
|
// packages/core/dist/telegram/ensure-captain.js
|
|
@@ -2836,17 +3012,17 @@ function formatInbound(text) {
|
|
|
2836
3012
|
}
|
|
2837
3013
|
|
|
2838
3014
|
// packages/core/dist/telegram/state.js
|
|
2839
|
-
import
|
|
2840
|
-
import
|
|
3015
|
+
import fs10 from "fs";
|
|
3016
|
+
import path9 from "path";
|
|
2841
3017
|
function statePath(stateRoot) {
|
|
2842
|
-
return
|
|
3018
|
+
return path9.join(stateRoot, "telegram-state.json");
|
|
2843
3019
|
}
|
|
2844
3020
|
function topicKey(project, scope = "project") {
|
|
2845
3021
|
return `${project}::${scope}`;
|
|
2846
3022
|
}
|
|
2847
3023
|
function loadState(stateRoot) {
|
|
2848
3024
|
try {
|
|
2849
|
-
const raw =
|
|
3025
|
+
const raw = fs10.readFileSync(statePath(stateRoot), "utf-8");
|
|
2850
3026
|
const data = JSON.parse(raw);
|
|
2851
3027
|
const result = {
|
|
2852
3028
|
offset: typeof data.offset === "number" ? data.offset : 0,
|
|
@@ -2861,8 +3037,8 @@ function loadState(stateRoot) {
|
|
|
2861
3037
|
}
|
|
2862
3038
|
}
|
|
2863
3039
|
function saveState(stateRoot, s) {
|
|
2864
|
-
|
|
2865
|
-
|
|
3040
|
+
fs10.mkdirSync(stateRoot, { recursive: true });
|
|
3041
|
+
fs10.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
|
|
2866
3042
|
}
|
|
2867
3043
|
function setTopic(stateRoot, project, topicId, scope = "project") {
|
|
2868
3044
|
const s = loadState(stateRoot);
|
|
@@ -2952,8 +3128,8 @@ function createTelegramClient(opts) {
|
|
|
2952
3128
|
}
|
|
2953
3129
|
|
|
2954
3130
|
// packages/core/dist/telegram/bridge.js
|
|
2955
|
-
import
|
|
2956
|
-
import
|
|
3131
|
+
import os4 from "os";
|
|
3132
|
+
import path10 from "path";
|
|
2957
3133
|
|
|
2958
3134
|
// packages/core/dist/telegram/panels.js
|
|
2959
3135
|
var mark = (on, label) => on ? `\u2022 ${label}` : label;
|
|
@@ -3058,7 +3234,7 @@ function notifyToggle(text) {
|
|
|
3058
3234
|
}
|
|
3059
3235
|
function createTelegramBridge(opts) {
|
|
3060
3236
|
const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
|
|
3061
|
-
const configRoot = opts.configRoot ??
|
|
3237
|
+
const configRoot = opts.configRoot ?? path10.join(os4.homedir(), ".config", "squadrant");
|
|
3062
3238
|
const pollMs = cfg.pollMs ?? 1e3;
|
|
3063
3239
|
let running = false;
|
|
3064
3240
|
let lastSuccessfulPollAt = null;
|
|
@@ -3183,14 +3359,14 @@ function createTelegramBridge(opts) {
|
|
|
3183
3359
|
}
|
|
3184
3360
|
function currentEffort() {
|
|
3185
3361
|
try {
|
|
3186
|
-
return loadConfig(
|
|
3362
|
+
return loadConfig(path10.join(configRoot, "config.json")).defaults.effort ?? "balance";
|
|
3187
3363
|
} catch {
|
|
3188
3364
|
return "balance";
|
|
3189
3365
|
}
|
|
3190
3366
|
}
|
|
3191
3367
|
function projectNames() {
|
|
3192
3368
|
try {
|
|
3193
|
-
return Object.keys(loadConfig(
|
|
3369
|
+
return Object.keys(loadConfig(path10.join(configRoot, "config.json")).projects);
|
|
3194
3370
|
} catch {
|
|
3195
3371
|
return [];
|
|
3196
3372
|
}
|
|
@@ -3309,8 +3485,11 @@ function createTelegramBridge(opts) {
|
|
|
3309
3485
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
3310
3486
|
try {
|
|
3311
3487
|
const r = await ensureCaptainAlive(resolved.project);
|
|
3312
|
-
if (r === "timeout")
|
|
3313
|
-
await reply(threadId,
|
|
3488
|
+
if (r === "timeout") {
|
|
3489
|
+
await reply(threadId, `\u274C couldn't reach ${resolved.project} captain \u2014 saved to mailbox, will deliver when you open the workspace.`);
|
|
3490
|
+
} else {
|
|
3491
|
+
await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
|
|
3492
|
+
}
|
|
3314
3493
|
} catch (e) {
|
|
3315
3494
|
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
3316
3495
|
}
|
|
@@ -3394,7 +3573,7 @@ function createTelegramBridge(opts) {
|
|
|
3394
3573
|
}
|
|
3395
3574
|
|
|
3396
3575
|
// packages/core/dist/telegram/setup.js
|
|
3397
|
-
import
|
|
3576
|
+
import fs11 from "fs";
|
|
3398
3577
|
|
|
3399
3578
|
// packages/core/dist/restart-daemon.js
|
|
3400
3579
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -3410,14 +3589,14 @@ import { join as join12 } from "path";
|
|
|
3410
3589
|
var DEFAULT_SOCK_PATH2 = join12(homedir7(), ".config", "squadrant", "squadrant.sock");
|
|
3411
3590
|
|
|
3412
3591
|
// packages/core/dist/side-session.js
|
|
3413
|
-
import
|
|
3592
|
+
import fs12 from "fs";
|
|
3414
3593
|
|
|
3415
3594
|
// packages/core/dist/crew-spawn.js
|
|
3416
|
-
import
|
|
3417
|
-
import
|
|
3418
|
-
import
|
|
3419
|
-
var TEMPLATES_DIR =
|
|
3420
|
-
var STATE_ROOT =
|
|
3595
|
+
import fs13 from "fs";
|
|
3596
|
+
import os5 from "os";
|
|
3597
|
+
import path11 from "path";
|
|
3598
|
+
var TEMPLATES_DIR = path11.join(os5.homedir(), ".config", "squadrant", "templates");
|
|
3599
|
+
var STATE_ROOT = path11.join(os5.homedir(), ".config", "squadrant", "state");
|
|
3421
3600
|
|
|
3422
3601
|
// packages/core/dist/lifecycle-source.js
|
|
3423
3602
|
function reduceLifecycle(prev, next) {
|
|
@@ -3449,28 +3628,28 @@ import { execSync as execSync4 } from "child_process";
|
|
|
3449
3628
|
import { execSync as execSync5 } from "child_process";
|
|
3450
3629
|
|
|
3451
3630
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
3452
|
-
import
|
|
3453
|
-
import
|
|
3631
|
+
import fs14 from "fs";
|
|
3632
|
+
import path12 from "path";
|
|
3454
3633
|
|
|
3455
3634
|
// packages/agents/dist/projection/cursor.js
|
|
3456
3635
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3457
|
-
import
|
|
3458
|
-
import
|
|
3636
|
+
import path13 from "path";
|
|
3637
|
+
import os6 from "os";
|
|
3459
3638
|
|
|
3460
3639
|
// packages/agents/dist/projection/codex.js
|
|
3461
3640
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3462
|
-
import
|
|
3463
|
-
import
|
|
3641
|
+
import path14 from "path";
|
|
3642
|
+
import os7 from "os";
|
|
3464
3643
|
|
|
3465
3644
|
// packages/agents/dist/projection/gemini.js
|
|
3466
3645
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
3467
|
-
import
|
|
3468
|
-
import
|
|
3646
|
+
import path15 from "path";
|
|
3647
|
+
import os8 from "os";
|
|
3469
3648
|
|
|
3470
3649
|
// packages/agents/dist/projection/opencode.js
|
|
3471
3650
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
3472
|
-
import
|
|
3473
|
-
import
|
|
3651
|
+
import path16 from "path";
|
|
3652
|
+
import os9 from "os";
|
|
3474
3653
|
|
|
3475
3654
|
// packages/agents/dist/codex/app-server-client.js
|
|
3476
3655
|
import { EventEmitter } from "events";
|
|
@@ -4237,7 +4416,7 @@ var OpencodeSseBridge = class {
|
|
|
4237
4416
|
|
|
4238
4417
|
// packages/agents/dist/interactive/claude.js
|
|
4239
4418
|
import { execSync as execSync6 } from "child_process";
|
|
4240
|
-
import { readFileSync as
|
|
4419
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4241
4420
|
import { homedir as homedir9 } from "os";
|
|
4242
4421
|
import { join as join14 } from "path";
|
|
4243
4422
|
|
|
@@ -4863,13 +5042,45 @@ function createCmuxDriver() {
|
|
|
4863
5042
|
};
|
|
4864
5043
|
}
|
|
4865
5044
|
|
|
5045
|
+
// packages/workspaces/dist/runtimes/registry.js
|
|
5046
|
+
var DEFAULT_RUNTIME = "cmux";
|
|
5047
|
+
var RuntimeRegistry = class {
|
|
5048
|
+
drivers;
|
|
5049
|
+
constructor(drivers) {
|
|
5050
|
+
this.drivers = drivers;
|
|
5051
|
+
}
|
|
5052
|
+
forProject(projectName, config) {
|
|
5053
|
+
const projectRuntime = config.projects[projectName]?.runtime;
|
|
5054
|
+
const runtimeName = projectRuntime ?? config.runtime ?? DEFAULT_RUNTIME;
|
|
5055
|
+
return this.get(runtimeName);
|
|
5056
|
+
}
|
|
5057
|
+
global(config) {
|
|
5058
|
+
const runtimeName = config.runtime ?? DEFAULT_RUNTIME;
|
|
5059
|
+
return this.get(runtimeName);
|
|
5060
|
+
}
|
|
5061
|
+
get(name) {
|
|
5062
|
+
const driver = this.drivers[name];
|
|
5063
|
+
if (!driver) {
|
|
5064
|
+
throw new Error(`Unknown runtime '${name}' \u2014 no driver registered`);
|
|
5065
|
+
}
|
|
5066
|
+
return driver;
|
|
5067
|
+
}
|
|
5068
|
+
async probeAll() {
|
|
5069
|
+
const results = {};
|
|
5070
|
+
for (const [name, driver] of Object.entries(this.drivers)) {
|
|
5071
|
+
results[name] = await driver.probe();
|
|
5072
|
+
}
|
|
5073
|
+
return results;
|
|
5074
|
+
}
|
|
5075
|
+
};
|
|
5076
|
+
|
|
4866
5077
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
4867
5078
|
import { execFileSync as execFileSync6, execSync as execSync7 } from "child_process";
|
|
4868
5079
|
|
|
4869
5080
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
4870
|
-
import
|
|
5081
|
+
import fs15 from "fs/promises";
|
|
4871
5082
|
import { existsSync as existsSync9 } from "fs";
|
|
4872
|
-
import
|
|
5083
|
+
import path17 from "path";
|
|
4873
5084
|
|
|
4874
5085
|
// packages/workspaces/dist/cmux-daemon/events-bridge.js
|
|
4875
5086
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
@@ -5005,6 +5216,71 @@ var CmuxEventsBridge = class {
|
|
|
5005
5216
|
}
|
|
5006
5217
|
};
|
|
5007
5218
|
|
|
5219
|
+
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
5220
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
5221
|
+
import { join as join15 } from "path";
|
|
5222
|
+
import { homedir as homedir10 } from "os";
|
|
5223
|
+
|
|
5224
|
+
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
5225
|
+
function roleFromTemplate(args) {
|
|
5226
|
+
const i = args?.indexOf("--append-system-prompt-file") ?? -1;
|
|
5227
|
+
const tmpl = i >= 0 && args ? (args[i + 1] ?? "").split("/").pop() ?? "" : "";
|
|
5228
|
+
if (tmpl.startsWith("captain"))
|
|
5229
|
+
return "captain";
|
|
5230
|
+
if (tmpl.startsWith("crew"))
|
|
5231
|
+
return "crew";
|
|
5232
|
+
if (tmpl.startsWith("command"))
|
|
5233
|
+
return "command";
|
|
5234
|
+
return "unknown";
|
|
5235
|
+
}
|
|
5236
|
+
function projectFromCwd(cwd, projects) {
|
|
5237
|
+
for (const [name, p] of Object.entries(projects)) {
|
|
5238
|
+
const projPath = resolveHome(p.path);
|
|
5239
|
+
if (cwd === projPath || cwd.startsWith(`${projPath}/`))
|
|
5240
|
+
return name;
|
|
5241
|
+
}
|
|
5242
|
+
return void 0;
|
|
5243
|
+
}
|
|
5244
|
+
function parseStoreRecords(fileContent, projects) {
|
|
5245
|
+
let parsed;
|
|
5246
|
+
try {
|
|
5247
|
+
parsed = JSON.parse(fileContent);
|
|
5248
|
+
} catch (e) {
|
|
5249
|
+
throw new Error(`parseStoreRecords: invalid JSON: ${e.message}`);
|
|
5250
|
+
}
|
|
5251
|
+
const out = [];
|
|
5252
|
+
for (const s of Object.values(parsed.sessions ?? {})) {
|
|
5253
|
+
const cwd = s.cwd ?? s.launchCommand?.workingDirectory ?? "";
|
|
5254
|
+
const project = projectFromCwd(cwd, projects);
|
|
5255
|
+
if (!project || !s.sessionId)
|
|
5256
|
+
continue;
|
|
5257
|
+
out.push({
|
|
5258
|
+
role: roleFromTemplate(s.launchCommand?.arguments),
|
|
5259
|
+
project,
|
|
5260
|
+
pid: typeof s.pid === "number" ? s.pid : null,
|
|
5261
|
+
sessionId: s.sessionId,
|
|
5262
|
+
present: true,
|
|
5263
|
+
isRestorable: s.isRestorable
|
|
5264
|
+
});
|
|
5265
|
+
}
|
|
5266
|
+
return out;
|
|
5267
|
+
}
|
|
5268
|
+
function readLivenessSnapshot(files, readFile6, projects) {
|
|
5269
|
+
const out = [];
|
|
5270
|
+
let successes = 0;
|
|
5271
|
+
for (const f of files) {
|
|
5272
|
+
try {
|
|
5273
|
+
out.push(...parseStoreRecords(readFile6(f), projects));
|
|
5274
|
+
successes++;
|
|
5275
|
+
} catch {
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
if (files.length > 0 && successes === 0) {
|
|
5279
|
+
throw new Error(`readLivenessSnapshot: all ${files.length} store file(s) unreadable/corrupt this tick`);
|
|
5280
|
+
}
|
|
5281
|
+
return out;
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5008
5284
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
5009
5285
|
var DaemonCmux = class {
|
|
5010
5286
|
driver;
|
|
@@ -5056,12 +5332,28 @@ var DaemonCmux = class {
|
|
|
5056
5332
|
return false;
|
|
5057
5333
|
}
|
|
5058
5334
|
}
|
|
5335
|
+
/**
|
|
5336
|
+
* Ground-truth liveness from cmux's own hook-sessions store (§5.4).
|
|
5337
|
+
* THROWS (does not return []) when the dir can't be listed, or every store
|
|
5338
|
+
* file failed to read/parse — see the class doc above.
|
|
5339
|
+
*/
|
|
5340
|
+
async liveness() {
|
|
5341
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join15(homedir10(), ".cmuxterm");
|
|
5342
|
+
const projects = loadConfig().projects;
|
|
5343
|
+
let files;
|
|
5344
|
+
try {
|
|
5345
|
+
files = readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
5346
|
+
} catch (e) {
|
|
5347
|
+
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
5348
|
+
}
|
|
5349
|
+
return readLivenessSnapshot(files, (f) => readFileSync10(join15(dir, f), "utf-8"), projects);
|
|
5350
|
+
}
|
|
5059
5351
|
};
|
|
5060
5352
|
|
|
5061
5353
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
5062
|
-
import { join as
|
|
5063
|
-
import { homedir as
|
|
5064
|
-
import { watch, readdirSync as
|
|
5354
|
+
import { join as join16 } from "path";
|
|
5355
|
+
import { homedir as homedir11 } from "os";
|
|
5356
|
+
import { watch, readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
5065
5357
|
var CmuxStoreSource = class {
|
|
5066
5358
|
name = "cmux-store";
|
|
5067
5359
|
stateDir;
|
|
@@ -5082,7 +5374,7 @@ var CmuxStoreSource = class {
|
|
|
5082
5374
|
active = false;
|
|
5083
5375
|
lastError = null;
|
|
5084
5376
|
constructor(opts = {}) {
|
|
5085
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
5377
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join16(homedir11(), ".cmuxterm");
|
|
5086
5378
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
5087
5379
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
5088
5380
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
@@ -5144,7 +5436,7 @@ var CmuxStoreSource = class {
|
|
|
5144
5436
|
}
|
|
5145
5437
|
scanFile(filename) {
|
|
5146
5438
|
const deps = this.deps;
|
|
5147
|
-
const filePath =
|
|
5439
|
+
const filePath = join16(this.stateDir, filename);
|
|
5148
5440
|
const lockPath = `${filePath}.lock`;
|
|
5149
5441
|
if (this.fileExists(lockPath)) {
|
|
5150
5442
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -5210,14 +5502,14 @@ function defaultIsPidAlive2(pid) {
|
|
|
5210
5502
|
}
|
|
5211
5503
|
function defaultListFiles(dir) {
|
|
5212
5504
|
try {
|
|
5213
|
-
return
|
|
5505
|
+
return readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
5214
5506
|
} catch {
|
|
5215
5507
|
return [];
|
|
5216
5508
|
}
|
|
5217
5509
|
}
|
|
5218
|
-
function defaultReadFile(
|
|
5510
|
+
function defaultReadFile(path19) {
|
|
5219
5511
|
try {
|
|
5220
|
-
return
|
|
5512
|
+
return readFileSync11(path19, "utf-8");
|
|
5221
5513
|
} catch {
|
|
5222
5514
|
return void 0;
|
|
5223
5515
|
}
|
|
@@ -5232,9 +5524,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
5232
5524
|
}
|
|
5233
5525
|
|
|
5234
5526
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
5235
|
-
import { join as
|
|
5236
|
-
import { homedir as
|
|
5237
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
5527
|
+
import { join as join17 } from "path";
|
|
5528
|
+
import { homedir as homedir12 } from "os";
|
|
5529
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
5238
5530
|
var CLAUDE_HOOK_EVENTS = [
|
|
5239
5531
|
["SessionStart", "session-start"],
|
|
5240
5532
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -5246,7 +5538,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
5246
5538
|
];
|
|
5247
5539
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
5248
5540
|
function installClaudeHooks(opts = {}) {
|
|
5249
|
-
const settingsPath = opts.settingsPath ??
|
|
5541
|
+
const settingsPath = opts.settingsPath ?? join17(homedir12(), ".claude", "settings.json");
|
|
5250
5542
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
5251
5543
|
const readFile6 = opts.readFile ?? defaultReadFile2;
|
|
5252
5544
|
const writeFile5 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -5392,16 +5684,16 @@ function extractDetail(sub, payload) {
|
|
|
5392
5684
|
}
|
|
5393
5685
|
return void 0;
|
|
5394
5686
|
}
|
|
5395
|
-
function defaultReadFile2(
|
|
5687
|
+
function defaultReadFile2(path19) {
|
|
5396
5688
|
try {
|
|
5397
|
-
return
|
|
5689
|
+
return readFileSync12(path19, "utf-8");
|
|
5398
5690
|
} catch {
|
|
5399
5691
|
return void 0;
|
|
5400
5692
|
}
|
|
5401
5693
|
}
|
|
5402
|
-
function defaultWriteFile(
|
|
5403
|
-
mkdirSync6(
|
|
5404
|
-
|
|
5694
|
+
function defaultWriteFile(path19, content) {
|
|
5695
|
+
mkdirSync6(path19.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
5696
|
+
writeFileSync8(path19, content, "utf-8");
|
|
5405
5697
|
}
|
|
5406
5698
|
|
|
5407
5699
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -5427,6 +5719,9 @@ async function settleInputBox(runtime, pane) {
|
|
|
5427
5719
|
}
|
|
5428
5720
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
5429
5721
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
5722
|
+
if (hasModalOptionList(preSendScreen)) {
|
|
5723
|
+
return { delivered: false, blockedByModal: true };
|
|
5724
|
+
}
|
|
5430
5725
|
await runtime.pasteToPane(pane, message);
|
|
5431
5726
|
let sawDraft = await settleInputBox(runtime, pane);
|
|
5432
5727
|
await runtime.sendKeyToPane(pane, "Enter");
|
|
@@ -5468,14 +5763,66 @@ async function resendCrewFirstTurn(runtime, captainName, project, name, message)
|
|
|
5468
5763
|
return confirmedSendToPane(runtime, pane, message);
|
|
5469
5764
|
}
|
|
5470
5765
|
|
|
5766
|
+
// packages/cli/src/lib/daemon-restart-broadcast.ts
|
|
5767
|
+
import fs16 from "fs";
|
|
5768
|
+
import path18 from "path";
|
|
5769
|
+
function statePath2(stateRoot) {
|
|
5770
|
+
return path18.join(stateRoot, "daemon-restart-state.json");
|
|
5771
|
+
}
|
|
5772
|
+
function computeRestartSignature(version, buildMtimeMs) {
|
|
5773
|
+
return `${version}::${buildMtimeMs}`;
|
|
5774
|
+
}
|
|
5775
|
+
function readPersistedRestartSignature(stateRoot) {
|
|
5776
|
+
try {
|
|
5777
|
+
const raw = fs16.readFileSync(statePath2(stateRoot), "utf-8");
|
|
5778
|
+
const data = JSON.parse(raw);
|
|
5779
|
+
return typeof data.signature === "string" ? data.signature : null;
|
|
5780
|
+
} catch {
|
|
5781
|
+
return null;
|
|
5782
|
+
}
|
|
5783
|
+
}
|
|
5784
|
+
function writePersistedRestartSignature(stateRoot, signature) {
|
|
5785
|
+
fs16.mkdirSync(stateRoot, { recursive: true });
|
|
5786
|
+
fs16.writeFileSync(statePath2(stateRoot), JSON.stringify({ signature }, null, 2) + "\n");
|
|
5787
|
+
}
|
|
5788
|
+
function restartNotice(version, isDevRebuild) {
|
|
5789
|
+
const suffix = isDevRebuild ? " (dev build)" : "";
|
|
5790
|
+
return `\u26A0\uFE0F Daemon restarted \u2192 v${version}${suffix} (control-plane bounced). Re-verify in-flight crews \u2014 a crew mid-first-turn may need a crew send.`;
|
|
5791
|
+
}
|
|
5792
|
+
async function notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild = false, appendCaptainMessage2) {
|
|
5793
|
+
const notice = restartNotice(version, isDevRebuild);
|
|
5794
|
+
for (const [projName] of Object.entries(config.projects)) {
|
|
5795
|
+
try {
|
|
5796
|
+
const proj = config.projects[projName];
|
|
5797
|
+
const ref = await driver.status(proj.captainName);
|
|
5798
|
+
if (ref) {
|
|
5799
|
+
await appendCaptainMessage2(projName, notice);
|
|
5800
|
+
}
|
|
5801
|
+
} catch {
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
async function maybeBroadcastDaemonRestart(opts) {
|
|
5806
|
+
try {
|
|
5807
|
+
const { version, buildMtimeMs, stateRoot, config, driver, appendCaptainMessage: appendCaptainMessage2 } = opts;
|
|
5808
|
+
const signature = computeRestartSignature(version, buildMtimeMs);
|
|
5809
|
+
const previous = readPersistedRestartSignature(stateRoot);
|
|
5810
|
+
if (previous === signature) return;
|
|
5811
|
+
const isDevRebuild = previous !== null && previous.split("::")[0] === version;
|
|
5812
|
+
await notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild, appendCaptainMessage2);
|
|
5813
|
+
writePersistedRestartSignature(stateRoot, signature);
|
|
5814
|
+
} catch {
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5471
5818
|
// packages/cli/src/squadrantd.ts
|
|
5472
5819
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
5473
|
-
var CLI_BIN =
|
|
5474
|
-
var DAEMON_SOCK =
|
|
5820
|
+
var CLI_BIN = join18(dirname5(SELF_PATH2), "index.js");
|
|
5821
|
+
var DAEMON_SOCK = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5475
5822
|
function readPkgVersion() {
|
|
5476
5823
|
try {
|
|
5477
|
-
const pkgPath =
|
|
5478
|
-
return JSON.parse(
|
|
5824
|
+
const pkgPath = join18(dirname5(SELF_PATH2), "..", "package.json");
|
|
5825
|
+
return JSON.parse(readFileSync13(pkgPath, "utf-8")).version ?? "unknown";
|
|
5479
5826
|
} catch {
|
|
5480
5827
|
return "unknown";
|
|
5481
5828
|
}
|
|
@@ -5490,7 +5837,7 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
5490
5837
|
const client = createTelegramClient({ token });
|
|
5491
5838
|
const ensureCaptainAlive = createEnsureCaptainAlive({
|
|
5492
5839
|
isAlive: createIsCaptainAlive(DAEMON_SOCK),
|
|
5493
|
-
launch: createLaunch(CLI_BIN)
|
|
5840
|
+
launch: createLaunch(CLI_BIN, log)
|
|
5494
5841
|
});
|
|
5495
5842
|
const runCommand = createRunCommand(CLI_BIN);
|
|
5496
5843
|
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
@@ -5558,7 +5905,7 @@ function startSquadrantd(opts = {}) {
|
|
|
5558
5905
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
5559
5906
|
);
|
|
5560
5907
|
},
|
|
5561
|
-
cursorFile:
|
|
5908
|
+
cursorFile: join18(stateRoot, "cmux-events.seq"),
|
|
5562
5909
|
log
|
|
5563
5910
|
});
|
|
5564
5911
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -5713,8 +6060,25 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
5713
6060
|
log(`codex app-server source start failed: ${e.message}`);
|
|
5714
6061
|
}
|
|
5715
6062
|
}
|
|
6063
|
+
if (!process.env.VITEST) {
|
|
6064
|
+
try {
|
|
6065
|
+
const buildMtimeMs = statSync3(SELF_PATH2).mtimeMs;
|
|
6066
|
+
const restartConfig = loadConfig();
|
|
6067
|
+
const registry = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
6068
|
+
void maybeBroadcastDaemonRestart({
|
|
6069
|
+
version: PKG_VERSION,
|
|
6070
|
+
buildMtimeMs,
|
|
6071
|
+
stateRoot,
|
|
6072
|
+
config: restartConfig,
|
|
6073
|
+
driver: registry.global(restartConfig),
|
|
6074
|
+
appendCaptainMessage: (project, text) => appendCaptainMessage({ stateRoot, project, text, source: "daemon" })
|
|
6075
|
+
});
|
|
6076
|
+
} catch (e) {
|
|
6077
|
+
log(`daemon-restart broadcast setup failed: ${e.message}`);
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
5716
6080
|
const origStop = h.stop.bind(h);
|
|
5717
|
-
h.stop = async () => {
|
|
6081
|
+
h.stop = async (reason) => {
|
|
5718
6082
|
try {
|
|
5719
6083
|
cmuxStoreSource.stop();
|
|
5720
6084
|
} catch {
|
|
@@ -5727,28 +6091,42 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
5727
6091
|
codexAppServerSource.stop();
|
|
5728
6092
|
} catch {
|
|
5729
6093
|
}
|
|
5730
|
-
return origStop();
|
|
6094
|
+
return origStop(reason);
|
|
5731
6095
|
};
|
|
5732
6096
|
return h;
|
|
5733
6097
|
}
|
|
6098
|
+
function logCrashMarker(kind, err) {
|
|
6099
|
+
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
6100
|
+
process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
|
|
6101
|
+
`);
|
|
6102
|
+
}
|
|
5734
6103
|
if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|
|
6104
|
+
process.on("uncaughtException", (err) => {
|
|
6105
|
+
logCrashMarker("uncaughtException", err);
|
|
6106
|
+
process.exit(1);
|
|
6107
|
+
});
|
|
6108
|
+
process.on("unhandledRejection", (reason) => {
|
|
6109
|
+
logCrashMarker("unhandledRejection", reason);
|
|
6110
|
+
process.exit(1);
|
|
6111
|
+
});
|
|
5735
6112
|
void (async () => {
|
|
5736
6113
|
const arg = process.argv[2];
|
|
5737
6114
|
if (arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v") {
|
|
5738
6115
|
process.stdout.write("squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\n");
|
|
5739
6116
|
process.exit(0);
|
|
5740
6117
|
}
|
|
5741
|
-
const sock =
|
|
6118
|
+
const sock = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5742
6119
|
if (await isDaemonSocketLive(sock)) {
|
|
5743
6120
|
process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}
|
|
5744
6121
|
`);
|
|
5745
6122
|
process.exit(0);
|
|
5746
6123
|
}
|
|
5747
6124
|
const h = startSquadrantd({ sweepMs: 3e4 });
|
|
5748
|
-
|
|
5749
|
-
h.stop();
|
|
5750
|
-
|
|
5751
|
-
|
|
6125
|
+
const shutdown = (signal) => {
|
|
6126
|
+
void h.stop(signal).finally(() => process.exit(0));
|
|
6127
|
+
};
|
|
6128
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
6129
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
5752
6130
|
})();
|
|
5753
6131
|
}
|
|
5754
6132
|
export {
|