squadrant 0.14.3 → 0.15.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 +591 -217
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +425 -91
- 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 path18 = opts.path ?? defaultCmuxConfigPath();
|
|
237
|
+
if (!existsSync(path18)) {
|
|
238
|
+
mkdirSync(dirname(path18), { recursive: true });
|
|
239
|
+
writeFileSync(path18, MINIMAL_TEMPLATE);
|
|
240
|
+
return { path: path18, changed: true, alreadySet: false };
|
|
238
241
|
}
|
|
239
|
-
const text = readFileSync(
|
|
242
|
+
const text = readFileSync(path18, "utf-8");
|
|
240
243
|
const current = parse(text)?.automation?.socketControlMode;
|
|
241
244
|
if (current === AUTOMATION_MODE) {
|
|
242
|
-
return { path:
|
|
245
|
+
return { path: path18, 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(path18, applyEdits(text, edits));
|
|
251
|
+
return { path: path18, 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(path18) {
|
|
371
374
|
try {
|
|
372
|
-
return JSON.parse(readFileSync4(
|
|
375
|
+
return JSON.parse(readFileSync4(path18, "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,
|
|
@@ -1443,14 +1446,14 @@ var TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
|
1443
1446
|
function projectHealth(input) {
|
|
1444
1447
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
1445
1448
|
const out = [];
|
|
1446
|
-
const captainState = captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown";
|
|
1449
|
+
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
1447
1450
|
out.push({
|
|
1448
1451
|
kind: "captain",
|
|
1449
1452
|
project,
|
|
1450
1453
|
ref: captainName,
|
|
1451
1454
|
state: captainState,
|
|
1452
1455
|
lastSeenMs: null,
|
|
1453
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : void 0
|
|
1456
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : void 0
|
|
1454
1457
|
});
|
|
1455
1458
|
if (commandPresent !== null) {
|
|
1456
1459
|
out.push({
|
|
@@ -1481,6 +1484,26 @@ function presence(p) {
|
|
|
1481
1484
|
return "unknown";
|
|
1482
1485
|
return p ? "alive" : "gone";
|
|
1483
1486
|
}
|
|
1487
|
+
function deriveCaptainState(e) {
|
|
1488
|
+
if (!e)
|
|
1489
|
+
return "unknown";
|
|
1490
|
+
if (e.lastState === "end")
|
|
1491
|
+
return "stopped";
|
|
1492
|
+
if (!e.pidAlive)
|
|
1493
|
+
return "gone";
|
|
1494
|
+
return "alive";
|
|
1495
|
+
}
|
|
1496
|
+
function reconcileLiveness(prev, next) {
|
|
1497
|
+
if (!prev)
|
|
1498
|
+
return next;
|
|
1499
|
+
if (next.source === "scan") {
|
|
1500
|
+
const pidAlive = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
|
|
1501
|
+
return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
|
|
1502
|
+
}
|
|
1503
|
+
if (next.startedAt >= prev.startedAt || next.lastState === "end")
|
|
1504
|
+
return next;
|
|
1505
|
+
return prev;
|
|
1506
|
+
}
|
|
1484
1507
|
|
|
1485
1508
|
// packages/core/dist/store.js
|
|
1486
1509
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
|
|
@@ -1639,7 +1662,73 @@ function makeGate(opts) {
|
|
|
1639
1662
|
import { homedir as homedir5 } from "os";
|
|
1640
1663
|
import { join as join8 } from "path";
|
|
1641
1664
|
import { spawn as realSpawn } from "child_process";
|
|
1642
|
-
import { writeFileSync as
|
|
1665
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
1666
|
+
|
|
1667
|
+
// packages/core/dist/daemon/liveness-registry.js
|
|
1668
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync2 } from "fs";
|
|
1669
|
+
var LivenessRegistry = class {
|
|
1670
|
+
path;
|
|
1671
|
+
readFile;
|
|
1672
|
+
writeFile;
|
|
1673
|
+
map = /* @__PURE__ */ new Map();
|
|
1674
|
+
constructor(opts) {
|
|
1675
|
+
this.path = opts.path;
|
|
1676
|
+
this.readFile = opts.readFile ?? ((p) => {
|
|
1677
|
+
try {
|
|
1678
|
+
return readFileSync7(p, "utf-8");
|
|
1679
|
+
} catch {
|
|
1680
|
+
return void 0;
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
this.writeFile = opts.writeFile ?? ((p, c) => {
|
|
1684
|
+
writeFileSync6(`${p}.tmp`, c);
|
|
1685
|
+
renameSync2(`${p}.tmp`, p);
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
load() {
|
|
1689
|
+
const raw = this.readFile(this.path);
|
|
1690
|
+
if (!raw)
|
|
1691
|
+
return;
|
|
1692
|
+
try {
|
|
1693
|
+
const arr = JSON.parse(raw);
|
|
1694
|
+
this.map = new Map(arr.map((e) => [e.project, e]));
|
|
1695
|
+
} catch {
|
|
1696
|
+
this.map = /* @__PURE__ */ new Map();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
get(project) {
|
|
1700
|
+
return this.map.get(project);
|
|
1701
|
+
}
|
|
1702
|
+
all() {
|
|
1703
|
+
return [...this.map.values()];
|
|
1704
|
+
}
|
|
1705
|
+
apply(next) {
|
|
1706
|
+
this.map.set(next.project, reconcileLiveness(this.map.get(next.project), next));
|
|
1707
|
+
this.persist();
|
|
1708
|
+
}
|
|
1709
|
+
markEnded(project, at) {
|
|
1710
|
+
const e = this.map.get(project);
|
|
1711
|
+
if (!e)
|
|
1712
|
+
return;
|
|
1713
|
+
this.map.set(project, { ...e, lastState: "end", lastSeenAt: at });
|
|
1714
|
+
this.persist();
|
|
1715
|
+
}
|
|
1716
|
+
setPidAlive(project, alive, at) {
|
|
1717
|
+
const e = this.map.get(project);
|
|
1718
|
+
if (!e)
|
|
1719
|
+
return;
|
|
1720
|
+
this.map.set(project, { ...e, pidAlive: alive, lastSeenAt: at });
|
|
1721
|
+
this.persist();
|
|
1722
|
+
}
|
|
1723
|
+
persist() {
|
|
1724
|
+
try {
|
|
1725
|
+
this.writeFile(this.path, JSON.stringify(this.all(), null, 2));
|
|
1726
|
+
} catch {
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
// packages/core/dist/daemon/context.js
|
|
1643
1732
|
function defaultIsPidAlive(pid) {
|
|
1644
1733
|
try {
|
|
1645
1734
|
process.kill(pid, 0);
|
|
@@ -1660,7 +1749,7 @@ function buildContext(opts) {
|
|
|
1660
1749
|
mkdirSync5(resultsDir, { recursive: true });
|
|
1661
1750
|
const writeResult = (id, payload) => {
|
|
1662
1751
|
const p = join8(resultsDir, `${id}.txt`);
|
|
1663
|
-
|
|
1752
|
+
writeFileSync7(p, payload);
|
|
1664
1753
|
return p;
|
|
1665
1754
|
};
|
|
1666
1755
|
const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
@@ -1681,8 +1770,11 @@ function buildContext(opts) {
|
|
|
1681
1770
|
attachConns: /* @__PURE__ */ new Map(),
|
|
1682
1771
|
inFlightHeadlessIds: /* @__PURE__ */ new Set(),
|
|
1683
1772
|
activeHeadlessKills: /* @__PURE__ */ new Set(),
|
|
1684
|
-
|
|
1685
|
-
|
|
1773
|
+
livenessRegistry: (() => {
|
|
1774
|
+
const r = new LivenessRegistry({ path: join8(stateRoot, "liveness.json") });
|
|
1775
|
+
r.load();
|
|
1776
|
+
return r;
|
|
1777
|
+
})(),
|
|
1686
1778
|
resendFirstTurn: opts.resendFirstTurn,
|
|
1687
1779
|
// Late-bound — start.ts fills these before first use:
|
|
1688
1780
|
d: null,
|
|
@@ -2036,7 +2128,6 @@ var CaptainDelivery = class {
|
|
|
2036
2128
|
|
|
2037
2129
|
// packages/core/dist/daemon/delivery-loop.js
|
|
2038
2130
|
var CURSOR_SUBSCRIBER = "captain";
|
|
2039
|
-
var CAPTAIN_GONE_STREAK_K = 3;
|
|
2040
2131
|
var TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
2041
2132
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
2042
2133
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
@@ -2053,8 +2144,62 @@ function reapOrphanedCrews(store, project) {
|
|
|
2053
2144
|
}
|
|
2054
2145
|
return reaped;
|
|
2055
2146
|
}
|
|
2147
|
+
function logEntry(log, project, e) {
|
|
2148
|
+
if (!log || !e)
|
|
2149
|
+
return;
|
|
2150
|
+
log(`[${e.role}/${e.source}] ${project} pid=${e.pid} \u2192 ${deriveCaptainState(e)}`);
|
|
2151
|
+
}
|
|
2152
|
+
async function runLivenessTick(deps) {
|
|
2153
|
+
const now = deps.now();
|
|
2154
|
+
let records = [];
|
|
2155
|
+
try {
|
|
2156
|
+
records = await deps.liveness();
|
|
2157
|
+
} catch {
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2161
|
+
for (const r of records) {
|
|
2162
|
+
if (r.role !== "captain")
|
|
2163
|
+
continue;
|
|
2164
|
+
seen.add(r.project);
|
|
2165
|
+
const entry = {
|
|
2166
|
+
project: r.project,
|
|
2167
|
+
role: "captain",
|
|
2168
|
+
pid: r.pid,
|
|
2169
|
+
sessionId: r.sessionId,
|
|
2170
|
+
startedAt: now,
|
|
2171
|
+
lastState: "start",
|
|
2172
|
+
lastSeenAt: now,
|
|
2173
|
+
pidAlive: r.pid != null ? deps.isPidAlive(r.pid) : true,
|
|
2174
|
+
// pid:null hibernated → alive-unknown
|
|
2175
|
+
source: "runtime"
|
|
2176
|
+
};
|
|
2177
|
+
const prev = deps.registry.get(r.project);
|
|
2178
|
+
if (prev && prev.lastState === "start")
|
|
2179
|
+
entry.startedAt = prev.startedAt;
|
|
2180
|
+
deps.registry.apply(entry);
|
|
2181
|
+
if (r.pid != null)
|
|
2182
|
+
deps.registry.setPidAlive(r.project, deps.isPidAlive(r.pid), now);
|
|
2183
|
+
logEntry(deps.log, r.project, deps.registry.get(r.project));
|
|
2184
|
+
}
|
|
2185
|
+
for (const e of deps.registry.all()) {
|
|
2186
|
+
if (e.role === "captain" && e.lastState === "start" && !seen.has(e.project)) {
|
|
2187
|
+
deps.registry.markEnded(e.project, now);
|
|
2188
|
+
logEntry(deps.log, e.project, deps.registry.get(e.project));
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (deps.reap) {
|
|
2192
|
+
for (const e of deps.registry.all()) {
|
|
2193
|
+
if (e.role !== "captain")
|
|
2194
|
+
continue;
|
|
2195
|
+
const state = deriveCaptainState(e);
|
|
2196
|
+
if (state === "stopped" || state === "gone")
|
|
2197
|
+
deps.reap(e.project);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2056
2201
|
function createDelivery(ctx, daemonCmux) {
|
|
2057
|
-
const { stateRoot, store, log,
|
|
2202
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
2058
2203
|
const defaultNotify = async (args) => {
|
|
2059
2204
|
try {
|
|
2060
2205
|
await appendToMailbox({
|
|
@@ -2080,6 +2225,21 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2080
2225
|
const sessionStartMs = Date.now();
|
|
2081
2226
|
let delivering = false;
|
|
2082
2227
|
const deliveryCore = async () => {
|
|
2228
|
+
await runLivenessTick({
|
|
2229
|
+
registry: livenessRegistry,
|
|
2230
|
+
liveness: () => cmux2.liveness ? cmux2.liveness() : Promise.resolve([]),
|
|
2231
|
+
isPidAlive,
|
|
2232
|
+
now: () => Date.now(),
|
|
2233
|
+
log,
|
|
2234
|
+
reap: (project) => {
|
|
2235
|
+
const reaped = reapOrphanedCrews(store, project);
|
|
2236
|
+
if (reaped > 0) {
|
|
2237
|
+
const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;
|
|
2238
|
+
log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);
|
|
2239
|
+
}
|
|
2240
|
+
return reaped;
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2083
2243
|
const injectedSurfaces = opts.captainSurfaces ?? {};
|
|
2084
2244
|
const allProjects = [.../* @__PURE__ */ new Set([
|
|
2085
2245
|
...Object.keys(cfg.projects ?? {}),
|
|
@@ -2091,34 +2251,14 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2091
2251
|
const captainTitle = projCfg?.captainName ?? `${project}-captain`;
|
|
2092
2252
|
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
2093
2253
|
let surface = null;
|
|
2094
|
-
let surfacesLength = 0;
|
|
2095
2254
|
if (wsId) {
|
|
2096
2255
|
const surfaces = await cmux2.listSurfaces(wsId);
|
|
2097
|
-
surfacesLength = surfaces.length;
|
|
2098
2256
|
surface = discoverCaptainSurface(surfaces, captainTitle);
|
|
2099
2257
|
}
|
|
2100
2258
|
if (!surface)
|
|
2101
2259
|
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
|
-
}
|
|
2260
|
+
if (!surface)
|
|
2120
2261
|
continue;
|
|
2121
|
-
}
|
|
2122
2262
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
2123
2263
|
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
2124
2264
|
let d = deliveries.get(project);
|
|
@@ -2240,7 +2380,7 @@ function createServer2(ctx, handlers) {
|
|
|
2240
2380
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
2241
2381
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2242
2382
|
import { join as join9 } from "path";
|
|
2243
|
-
import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as
|
|
2383
|
+
import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
2244
2384
|
var SELF_PATH = fileURLToPath2(import.meta.url);
|
|
2245
2385
|
function distBuiltAt() {
|
|
2246
2386
|
try {
|
|
@@ -2249,10 +2389,10 @@ function distBuiltAt() {
|
|
|
2249
2389
|
return 0;
|
|
2250
2390
|
}
|
|
2251
2391
|
}
|
|
2252
|
-
function gatherLogStats(
|
|
2392
|
+
function gatherLogStats(path18, now, windowMs) {
|
|
2253
2393
|
let sizeBytes = 0;
|
|
2254
2394
|
try {
|
|
2255
|
-
sizeBytes = statSync2(
|
|
2395
|
+
sizeBytes = statSync2(path18).size;
|
|
2256
2396
|
} catch {
|
|
2257
2397
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
2258
2398
|
}
|
|
@@ -2263,7 +2403,7 @@ function gatherLogStats(path17, now, windowMs) {
|
|
|
2263
2403
|
const len = sizeBytes - start;
|
|
2264
2404
|
let text = "";
|
|
2265
2405
|
try {
|
|
2266
|
-
const fd = openSync2(
|
|
2406
|
+
const fd = openSync2(path18, "r");
|
|
2267
2407
|
try {
|
|
2268
2408
|
const buf = Buffer.alloc(len);
|
|
2269
2409
|
readSync(fd, buf, 0, len, start);
|
|
@@ -2304,7 +2444,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
2304
2444
|
if (!n.endsWith(".json"))
|
|
2305
2445
|
continue;
|
|
2306
2446
|
try {
|
|
2307
|
-
JSON.parse(
|
|
2447
|
+
JSON.parse(readFileSync8(join9(dir, n), "utf-8"));
|
|
2308
2448
|
} catch {
|
|
2309
2449
|
corruptCount++;
|
|
2310
2450
|
}
|
|
@@ -2389,14 +2529,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2389
2529
|
for (const project of names) {
|
|
2390
2530
|
const proj = config.projects[project];
|
|
2391
2531
|
const captainName = proj?.captainName ?? `${project}-captain`;
|
|
2392
|
-
const
|
|
2393
|
-
const streak = ctx.captainMissingStreak.get(project);
|
|
2394
|
-
const captainStopped = stopped ? true : streak === 0 ? false : null;
|
|
2532
|
+
const capEntry = ctx.livenessRegistry.get(project);
|
|
2395
2533
|
out.push(...projectHealth({
|
|
2396
2534
|
project,
|
|
2397
2535
|
now,
|
|
2398
2536
|
captainName,
|
|
2399
|
-
captainStopped,
|
|
2537
|
+
captainStopped: null,
|
|
2538
|
+
captainState: deriveCaptainState(capEntry),
|
|
2400
2539
|
commandPresent: null,
|
|
2401
2540
|
crews: store.list(project)
|
|
2402
2541
|
}));
|
|
@@ -2753,21 +2892,33 @@ function createRunCommand(cliBin) {
|
|
|
2753
2892
|
}
|
|
2754
2893
|
};
|
|
2755
2894
|
}
|
|
2895
|
+
function isCaptainAliveFromHealth(rows, project) {
|
|
2896
|
+
return rows.some((h) => h.kind === "captain" && h.project === project && h.state === "alive");
|
|
2897
|
+
}
|
|
2756
2898
|
function createIsCaptainAlive(sock) {
|
|
2757
2899
|
return async (project) => {
|
|
2758
2900
|
try {
|
|
2759
2901
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
2760
|
-
|
|
2761
|
-
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
2902
|
+
return isCaptainAliveFromHealth(health ?? [], project);
|
|
2762
2903
|
} catch {
|
|
2763
2904
|
return false;
|
|
2764
2905
|
}
|
|
2765
2906
|
};
|
|
2766
2907
|
}
|
|
2767
|
-
function createLaunch(cliBin) {
|
|
2768
|
-
return
|
|
2769
|
-
|
|
2770
|
-
|
|
2908
|
+
function createLaunch(cliBin, log) {
|
|
2909
|
+
return (project) => new Promise((resolve2, reject) => {
|
|
2910
|
+
execFile(process.execPath, [cliBin, "launch", project, "--headless"], { timeout: 3e4 }, (err, stdout, stderr) => {
|
|
2911
|
+
const output = capOutput(stdout ?? "", stderr ?? "");
|
|
2912
|
+
if (err) {
|
|
2913
|
+
log?.(`launch ${project} failed: ${output}`);
|
|
2914
|
+
reject(err);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
if (output !== "(no output)")
|
|
2918
|
+
log?.(`launch ${project}: ${output}`);
|
|
2919
|
+
resolve2();
|
|
2920
|
+
});
|
|
2921
|
+
});
|
|
2771
2922
|
}
|
|
2772
2923
|
|
|
2773
2924
|
// packages/core/dist/telegram/ensure-captain.js
|
|
@@ -3309,8 +3460,11 @@ function createTelegramBridge(opts) {
|
|
|
3309
3460
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
3310
3461
|
try {
|
|
3311
3462
|
const r = await ensureCaptainAlive(resolved.project);
|
|
3312
|
-
if (r === "timeout")
|
|
3313
|
-
await reply(threadId,
|
|
3463
|
+
if (r === "timeout") {
|
|
3464
|
+
await reply(threadId, `\u274C couldn't reach ${resolved.project} captain \u2014 saved to mailbox, will deliver when you open the workspace.`);
|
|
3465
|
+
} else {
|
|
3466
|
+
await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
|
|
3467
|
+
}
|
|
3314
3468
|
} catch (e) {
|
|
3315
3469
|
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
3316
3470
|
}
|
|
@@ -4237,7 +4391,7 @@ var OpencodeSseBridge = class {
|
|
|
4237
4391
|
|
|
4238
4392
|
// packages/agents/dist/interactive/claude.js
|
|
4239
4393
|
import { execSync as execSync6 } from "child_process";
|
|
4240
|
-
import { readFileSync as
|
|
4394
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4241
4395
|
import { homedir as homedir9 } from "os";
|
|
4242
4396
|
import { join as join14 } from "path";
|
|
4243
4397
|
|
|
@@ -4863,6 +5017,38 @@ function createCmuxDriver() {
|
|
|
4863
5017
|
};
|
|
4864
5018
|
}
|
|
4865
5019
|
|
|
5020
|
+
// packages/workspaces/dist/runtimes/registry.js
|
|
5021
|
+
var DEFAULT_RUNTIME = "cmux";
|
|
5022
|
+
var RuntimeRegistry = class {
|
|
5023
|
+
drivers;
|
|
5024
|
+
constructor(drivers) {
|
|
5025
|
+
this.drivers = drivers;
|
|
5026
|
+
}
|
|
5027
|
+
forProject(projectName, config) {
|
|
5028
|
+
const projectRuntime = config.projects[projectName]?.runtime;
|
|
5029
|
+
const runtimeName = projectRuntime ?? config.runtime ?? DEFAULT_RUNTIME;
|
|
5030
|
+
return this.get(runtimeName);
|
|
5031
|
+
}
|
|
5032
|
+
global(config) {
|
|
5033
|
+
const runtimeName = config.runtime ?? DEFAULT_RUNTIME;
|
|
5034
|
+
return this.get(runtimeName);
|
|
5035
|
+
}
|
|
5036
|
+
get(name) {
|
|
5037
|
+
const driver = this.drivers[name];
|
|
5038
|
+
if (!driver) {
|
|
5039
|
+
throw new Error(`Unknown runtime '${name}' \u2014 no driver registered`);
|
|
5040
|
+
}
|
|
5041
|
+
return driver;
|
|
5042
|
+
}
|
|
5043
|
+
async probeAll() {
|
|
5044
|
+
const results = {};
|
|
5045
|
+
for (const [name, driver] of Object.entries(this.drivers)) {
|
|
5046
|
+
results[name] = await driver.probe();
|
|
5047
|
+
}
|
|
5048
|
+
return results;
|
|
5049
|
+
}
|
|
5050
|
+
};
|
|
5051
|
+
|
|
4866
5052
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
4867
5053
|
import { execFileSync as execFileSync6, execSync as execSync7 } from "child_process";
|
|
4868
5054
|
|
|
@@ -5005,6 +5191,71 @@ var CmuxEventsBridge = class {
|
|
|
5005
5191
|
}
|
|
5006
5192
|
};
|
|
5007
5193
|
|
|
5194
|
+
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
5195
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
5196
|
+
import { join as join15 } from "path";
|
|
5197
|
+
import { homedir as homedir10 } from "os";
|
|
5198
|
+
|
|
5199
|
+
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
5200
|
+
function roleFromTemplate(args) {
|
|
5201
|
+
const i = args?.indexOf("--append-system-prompt-file") ?? -1;
|
|
5202
|
+
const tmpl = i >= 0 && args ? (args[i + 1] ?? "").split("/").pop() ?? "" : "";
|
|
5203
|
+
if (tmpl.startsWith("captain"))
|
|
5204
|
+
return "captain";
|
|
5205
|
+
if (tmpl.startsWith("crew"))
|
|
5206
|
+
return "crew";
|
|
5207
|
+
if (tmpl.startsWith("command"))
|
|
5208
|
+
return "command";
|
|
5209
|
+
return "unknown";
|
|
5210
|
+
}
|
|
5211
|
+
function projectFromCwd(cwd, projects) {
|
|
5212
|
+
for (const [name, p] of Object.entries(projects)) {
|
|
5213
|
+
const projPath = resolveHome(p.path);
|
|
5214
|
+
if (cwd === projPath || cwd.startsWith(`${projPath}/`))
|
|
5215
|
+
return name;
|
|
5216
|
+
}
|
|
5217
|
+
return void 0;
|
|
5218
|
+
}
|
|
5219
|
+
function parseStoreRecords(fileContent, projects) {
|
|
5220
|
+
let parsed;
|
|
5221
|
+
try {
|
|
5222
|
+
parsed = JSON.parse(fileContent);
|
|
5223
|
+
} catch (e) {
|
|
5224
|
+
throw new Error(`parseStoreRecords: invalid JSON: ${e.message}`);
|
|
5225
|
+
}
|
|
5226
|
+
const out = [];
|
|
5227
|
+
for (const s of Object.values(parsed.sessions ?? {})) {
|
|
5228
|
+
const cwd = s.cwd ?? s.launchCommand?.workingDirectory ?? "";
|
|
5229
|
+
const project = projectFromCwd(cwd, projects);
|
|
5230
|
+
if (!project || !s.sessionId)
|
|
5231
|
+
continue;
|
|
5232
|
+
out.push({
|
|
5233
|
+
role: roleFromTemplate(s.launchCommand?.arguments),
|
|
5234
|
+
project,
|
|
5235
|
+
pid: typeof s.pid === "number" ? s.pid : null,
|
|
5236
|
+
sessionId: s.sessionId,
|
|
5237
|
+
present: true,
|
|
5238
|
+
isRestorable: s.isRestorable
|
|
5239
|
+
});
|
|
5240
|
+
}
|
|
5241
|
+
return out;
|
|
5242
|
+
}
|
|
5243
|
+
function readLivenessSnapshot(files, readFile6, projects) {
|
|
5244
|
+
const out = [];
|
|
5245
|
+
let successes = 0;
|
|
5246
|
+
for (const f of files) {
|
|
5247
|
+
try {
|
|
5248
|
+
out.push(...parseStoreRecords(readFile6(f), projects));
|
|
5249
|
+
successes++;
|
|
5250
|
+
} catch {
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
if (files.length > 0 && successes === 0) {
|
|
5254
|
+
throw new Error(`readLivenessSnapshot: all ${files.length} store file(s) unreadable/corrupt this tick`);
|
|
5255
|
+
}
|
|
5256
|
+
return out;
|
|
5257
|
+
}
|
|
5258
|
+
|
|
5008
5259
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
5009
5260
|
var DaemonCmux = class {
|
|
5010
5261
|
driver;
|
|
@@ -5056,12 +5307,28 @@ var DaemonCmux = class {
|
|
|
5056
5307
|
return false;
|
|
5057
5308
|
}
|
|
5058
5309
|
}
|
|
5310
|
+
/**
|
|
5311
|
+
* Ground-truth liveness from cmux's own hook-sessions store (§5.4).
|
|
5312
|
+
* THROWS (does not return []) when the dir can't be listed, or every store
|
|
5313
|
+
* file failed to read/parse — see the class doc above.
|
|
5314
|
+
*/
|
|
5315
|
+
async liveness() {
|
|
5316
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join15(homedir10(), ".cmuxterm");
|
|
5317
|
+
const projects = loadConfig().projects;
|
|
5318
|
+
let files;
|
|
5319
|
+
try {
|
|
5320
|
+
files = readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
5321
|
+
} catch (e) {
|
|
5322
|
+
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
5323
|
+
}
|
|
5324
|
+
return readLivenessSnapshot(files, (f) => readFileSync10(join15(dir, f), "utf-8"), projects);
|
|
5325
|
+
}
|
|
5059
5326
|
};
|
|
5060
5327
|
|
|
5061
5328
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
5062
|
-
import { join as
|
|
5063
|
-
import { homedir as
|
|
5064
|
-
import { watch, readdirSync as
|
|
5329
|
+
import { join as join16 } from "path";
|
|
5330
|
+
import { homedir as homedir11 } from "os";
|
|
5331
|
+
import { watch, readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
5065
5332
|
var CmuxStoreSource = class {
|
|
5066
5333
|
name = "cmux-store";
|
|
5067
5334
|
stateDir;
|
|
@@ -5082,7 +5349,7 @@ var CmuxStoreSource = class {
|
|
|
5082
5349
|
active = false;
|
|
5083
5350
|
lastError = null;
|
|
5084
5351
|
constructor(opts = {}) {
|
|
5085
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
5352
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join16(homedir11(), ".cmuxterm");
|
|
5086
5353
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
5087
5354
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
5088
5355
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
@@ -5144,7 +5411,7 @@ var CmuxStoreSource = class {
|
|
|
5144
5411
|
}
|
|
5145
5412
|
scanFile(filename) {
|
|
5146
5413
|
const deps = this.deps;
|
|
5147
|
-
const filePath =
|
|
5414
|
+
const filePath = join16(this.stateDir, filename);
|
|
5148
5415
|
const lockPath = `${filePath}.lock`;
|
|
5149
5416
|
if (this.fileExists(lockPath)) {
|
|
5150
5417
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -5210,14 +5477,14 @@ function defaultIsPidAlive2(pid) {
|
|
|
5210
5477
|
}
|
|
5211
5478
|
function defaultListFiles(dir) {
|
|
5212
5479
|
try {
|
|
5213
|
-
return
|
|
5480
|
+
return readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
5214
5481
|
} catch {
|
|
5215
5482
|
return [];
|
|
5216
5483
|
}
|
|
5217
5484
|
}
|
|
5218
|
-
function defaultReadFile(
|
|
5485
|
+
function defaultReadFile(path18) {
|
|
5219
5486
|
try {
|
|
5220
|
-
return
|
|
5487
|
+
return readFileSync11(path18, "utf-8");
|
|
5221
5488
|
} catch {
|
|
5222
5489
|
return void 0;
|
|
5223
5490
|
}
|
|
@@ -5232,9 +5499,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
5232
5499
|
}
|
|
5233
5500
|
|
|
5234
5501
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
5235
|
-
import { join as
|
|
5236
|
-
import { homedir as
|
|
5237
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
5502
|
+
import { join as join17 } from "path";
|
|
5503
|
+
import { homedir as homedir12 } from "os";
|
|
5504
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
5238
5505
|
var CLAUDE_HOOK_EVENTS = [
|
|
5239
5506
|
["SessionStart", "session-start"],
|
|
5240
5507
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -5246,7 +5513,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
5246
5513
|
];
|
|
5247
5514
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
5248
5515
|
function installClaudeHooks(opts = {}) {
|
|
5249
|
-
const settingsPath = opts.settingsPath ??
|
|
5516
|
+
const settingsPath = opts.settingsPath ?? join17(homedir12(), ".claude", "settings.json");
|
|
5250
5517
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
5251
5518
|
const readFile6 = opts.readFile ?? defaultReadFile2;
|
|
5252
5519
|
const writeFile5 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -5392,16 +5659,16 @@ function extractDetail(sub, payload) {
|
|
|
5392
5659
|
}
|
|
5393
5660
|
return void 0;
|
|
5394
5661
|
}
|
|
5395
|
-
function defaultReadFile2(
|
|
5662
|
+
function defaultReadFile2(path18) {
|
|
5396
5663
|
try {
|
|
5397
|
-
return
|
|
5664
|
+
return readFileSync12(path18, "utf-8");
|
|
5398
5665
|
} catch {
|
|
5399
5666
|
return void 0;
|
|
5400
5667
|
}
|
|
5401
5668
|
}
|
|
5402
|
-
function defaultWriteFile(
|
|
5403
|
-
mkdirSync6(
|
|
5404
|
-
|
|
5669
|
+
function defaultWriteFile(path18, content) {
|
|
5670
|
+
mkdirSync6(path18.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
5671
|
+
writeFileSync8(path18, content, "utf-8");
|
|
5405
5672
|
}
|
|
5406
5673
|
|
|
5407
5674
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -5468,14 +5735,65 @@ async function resendCrewFirstTurn(runtime, captainName, project, name, message)
|
|
|
5468
5735
|
return confirmedSendToPane(runtime, pane, message);
|
|
5469
5736
|
}
|
|
5470
5737
|
|
|
5738
|
+
// packages/cli/src/lib/daemon-restart-broadcast.ts
|
|
5739
|
+
import fs15 from "fs";
|
|
5740
|
+
import path17 from "path";
|
|
5741
|
+
function statePath2(stateRoot) {
|
|
5742
|
+
return path17.join(stateRoot, "daemon-restart-state.json");
|
|
5743
|
+
}
|
|
5744
|
+
function computeRestartSignature(version, buildMtimeMs) {
|
|
5745
|
+
return `${version}::${buildMtimeMs}`;
|
|
5746
|
+
}
|
|
5747
|
+
function readPersistedRestartSignature(stateRoot) {
|
|
5748
|
+
try {
|
|
5749
|
+
const raw = fs15.readFileSync(statePath2(stateRoot), "utf-8");
|
|
5750
|
+
const data = JSON.parse(raw);
|
|
5751
|
+
return typeof data.signature === "string" ? data.signature : null;
|
|
5752
|
+
} catch {
|
|
5753
|
+
return null;
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
function writePersistedRestartSignature(stateRoot, signature) {
|
|
5757
|
+
fs15.mkdirSync(stateRoot, { recursive: true });
|
|
5758
|
+
fs15.writeFileSync(statePath2(stateRoot), JSON.stringify({ signature }, null, 2) + "\n");
|
|
5759
|
+
}
|
|
5760
|
+
function restartNotice(version, isDevRebuild) {
|
|
5761
|
+
const suffix = isDevRebuild ? " (dev build)" : "";
|
|
5762
|
+
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.`;
|
|
5763
|
+
}
|
|
5764
|
+
async function notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild = false) {
|
|
5765
|
+
const notice = restartNotice(version, isDevRebuild);
|
|
5766
|
+
for (const [, proj] of Object.entries(config.projects)) {
|
|
5767
|
+
try {
|
|
5768
|
+
const ref = await driver.status(proj.captainName);
|
|
5769
|
+
if (ref) {
|
|
5770
|
+
await driver.send(ref.id, notice);
|
|
5771
|
+
}
|
|
5772
|
+
} catch {
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
async function maybeBroadcastDaemonRestart(opts) {
|
|
5777
|
+
try {
|
|
5778
|
+
const { version, buildMtimeMs, stateRoot, config, driver } = opts;
|
|
5779
|
+
const signature = computeRestartSignature(version, buildMtimeMs);
|
|
5780
|
+
const previous = readPersistedRestartSignature(stateRoot);
|
|
5781
|
+
if (previous === signature) return;
|
|
5782
|
+
const isDevRebuild = previous !== null && previous.split("::")[0] === version;
|
|
5783
|
+
await notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild);
|
|
5784
|
+
writePersistedRestartSignature(stateRoot, signature);
|
|
5785
|
+
} catch {
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5471
5789
|
// packages/cli/src/squadrantd.ts
|
|
5472
5790
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
5473
|
-
var CLI_BIN =
|
|
5474
|
-
var DAEMON_SOCK =
|
|
5791
|
+
var CLI_BIN = join18(dirname5(SELF_PATH2), "index.js");
|
|
5792
|
+
var DAEMON_SOCK = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5475
5793
|
function readPkgVersion() {
|
|
5476
5794
|
try {
|
|
5477
|
-
const pkgPath =
|
|
5478
|
-
return JSON.parse(
|
|
5795
|
+
const pkgPath = join18(dirname5(SELF_PATH2), "..", "package.json");
|
|
5796
|
+
return JSON.parse(readFileSync13(pkgPath, "utf-8")).version ?? "unknown";
|
|
5479
5797
|
} catch {
|
|
5480
5798
|
return "unknown";
|
|
5481
5799
|
}
|
|
@@ -5490,7 +5808,7 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
5490
5808
|
const client = createTelegramClient({ token });
|
|
5491
5809
|
const ensureCaptainAlive = createEnsureCaptainAlive({
|
|
5492
5810
|
isAlive: createIsCaptainAlive(DAEMON_SOCK),
|
|
5493
|
-
launch: createLaunch(CLI_BIN)
|
|
5811
|
+
launch: createLaunch(CLI_BIN, log)
|
|
5494
5812
|
});
|
|
5495
5813
|
const runCommand = createRunCommand(CLI_BIN);
|
|
5496
5814
|
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
@@ -5558,7 +5876,7 @@ function startSquadrantd(opts = {}) {
|
|
|
5558
5876
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
5559
5877
|
);
|
|
5560
5878
|
},
|
|
5561
|
-
cursorFile:
|
|
5879
|
+
cursorFile: join18(stateRoot, "cmux-events.seq"),
|
|
5562
5880
|
log
|
|
5563
5881
|
});
|
|
5564
5882
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -5713,6 +6031,22 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
5713
6031
|
log(`codex app-server source start failed: ${e.message}`);
|
|
5714
6032
|
}
|
|
5715
6033
|
}
|
|
6034
|
+
if (!process.env.VITEST) {
|
|
6035
|
+
try {
|
|
6036
|
+
const buildMtimeMs = statSync3(SELF_PATH2).mtimeMs;
|
|
6037
|
+
const restartConfig = loadConfig();
|
|
6038
|
+
const registry = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
6039
|
+
void maybeBroadcastDaemonRestart({
|
|
6040
|
+
version: PKG_VERSION,
|
|
6041
|
+
buildMtimeMs,
|
|
6042
|
+
stateRoot,
|
|
6043
|
+
config: restartConfig,
|
|
6044
|
+
driver: registry.global(restartConfig)
|
|
6045
|
+
});
|
|
6046
|
+
} catch (e) {
|
|
6047
|
+
log(`daemon-restart broadcast setup failed: ${e.message}`);
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
5716
6050
|
const origStop = h.stop.bind(h);
|
|
5717
6051
|
h.stop = async () => {
|
|
5718
6052
|
try {
|
|
@@ -5738,7 +6072,7 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|
|
|
5738
6072
|
process.stdout.write("squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\n");
|
|
5739
6073
|
process.exit(0);
|
|
5740
6074
|
}
|
|
5741
|
-
const sock =
|
|
6075
|
+
const sock = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5742
6076
|
if (await isDaemonSocketLive(sock)) {
|
|
5743
6077
|
process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}
|
|
5744
6078
|
`);
|