squadrant 0.14.2 → 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 +757 -328
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +627 -131
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +10 -7
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,
|
|
@@ -588,6 +591,8 @@ function recoverStall(rec, now) {
|
|
|
588
591
|
}
|
|
589
592
|
|
|
590
593
|
// packages/core/dist/daemon/reduce.js
|
|
594
|
+
var DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 12e4;
|
|
595
|
+
var DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 6e4;
|
|
591
596
|
var DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
592
597
|
var TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
593
598
|
var TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
@@ -708,6 +713,71 @@ function createDaemon(deps) {
|
|
|
708
713
|
const { store, now } = deps;
|
|
709
714
|
const lastCaptainTurnAt = /* @__PURE__ */ new Map();
|
|
710
715
|
const quietNotifiedAt = /* @__PURE__ */ new Map();
|
|
716
|
+
const resendAttemptedAt = /* @__PURE__ */ new Map();
|
|
717
|
+
const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;
|
|
718
|
+
const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;
|
|
719
|
+
async function applyEvent(project, event) {
|
|
720
|
+
if (!KNOWN_EVENT_TYPES.has(event.type)) {
|
|
721
|
+
throw new Error(`unknown event type '${event.type}' \u2014 not a valid ControlEvent`);
|
|
722
|
+
}
|
|
723
|
+
const cur = store.get(project, event.id);
|
|
724
|
+
if (!cur)
|
|
725
|
+
throw new Error(`unknown task ${event.id}`);
|
|
726
|
+
if (event.type === "task.started")
|
|
727
|
+
lastCaptainTurnAt.set(event.id, now());
|
|
728
|
+
if (event.type === "task.session.ended" && !TERMINAL_STATES.has(cur.state)) {
|
|
729
|
+
const liveness = deps.isSurfaceAlive ? await deps.isSurfaceAlive(cur) : "unknown";
|
|
730
|
+
if (liveness !== "gone")
|
|
731
|
+
return cur;
|
|
732
|
+
}
|
|
733
|
+
const next = reduce(cur, event, now());
|
|
734
|
+
if (next !== cur) {
|
|
735
|
+
store.put(next);
|
|
736
|
+
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));
|
|
737
|
+
}
|
|
738
|
+
return next;
|
|
739
|
+
}
|
|
740
|
+
async function attemptFirstTurnRecovery(r, undeliveredMs) {
|
|
741
|
+
const lastAttempt = resendAttemptedAt.get(r.id);
|
|
742
|
+
if (lastAttempt != null && now() - lastAttempt < firstTurnResendCooldownMs)
|
|
743
|
+
return;
|
|
744
|
+
resendAttemptedAt.set(r.id, now());
|
|
745
|
+
const fresh = store.get(r.project, r.id);
|
|
746
|
+
if (!fresh || fresh.firstTurnConfirmedAt)
|
|
747
|
+
return;
|
|
748
|
+
const tag = crewTag(fresh);
|
|
749
|
+
const fireNotify = (message) => {
|
|
750
|
+
if (!deps.notify)
|
|
751
|
+
return;
|
|
752
|
+
const synthEvent = { type: "task.quiet", id: fresh.id, quietMs: undeliveredMs };
|
|
753
|
+
try {
|
|
754
|
+
const p = deps.notify({ project: fresh.project, message, record: store.get(fresh.project, fresh.id) ?? fresh, event: synthEvent });
|
|
755
|
+
if (p && typeof p.catch === "function")
|
|
756
|
+
p.catch(() => {
|
|
757
|
+
});
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
if (!deps.resendFirstTurn) {
|
|
762
|
+
fireNotify(`\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) \u2014 re-send the task or check the spawn.`);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
let result;
|
|
766
|
+
try {
|
|
767
|
+
result = await deps.resendFirstTurn(fresh);
|
|
768
|
+
} catch {
|
|
769
|
+
result = { delivered: false };
|
|
770
|
+
}
|
|
771
|
+
if (result.delivered) {
|
|
772
|
+
try {
|
|
773
|
+
await applyEvent(fresh.project, { type: "task.first-turn.confirmed", id: fresh.id });
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
fireNotify(`\u{1F501} CREW FIRST-TURN AUTO-RESENT ${tag}: first turn had not landed after ${Math.round(undeliveredMs / 1e3)}s \u2014 re-sent automatically.`);
|
|
777
|
+
} else {
|
|
778
|
+
fireNotify(`\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed \u2014 auto-resend attempted but the pane wasn't ready; will retry.`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
711
781
|
return {
|
|
712
782
|
async handle(req) {
|
|
713
783
|
switch (req.kind) {
|
|
@@ -754,22 +824,7 @@ function createDaemon(deps) {
|
|
|
754
824
|
if (!KNOWN_EVENT_TYPES.has(req.event.type)) {
|
|
755
825
|
throw new Error(`unknown event type '${req.event.type}' \u2014 not a valid ControlEvent`);
|
|
756
826
|
}
|
|
757
|
-
|
|
758
|
-
if (!cur)
|
|
759
|
-
throw new Error(`unknown task ${req.event.id}`);
|
|
760
|
-
if (req.event.type === "task.started")
|
|
761
|
-
lastCaptainTurnAt.set(req.event.id, now());
|
|
762
|
-
if (req.event.type === "task.session.ended" && !TERMINAL_STATES.has(cur.state)) {
|
|
763
|
-
const liveness = deps.isSurfaceAlive ? await deps.isSurfaceAlive(cur) : "unknown";
|
|
764
|
-
if (liveness !== "gone")
|
|
765
|
-
return cur;
|
|
766
|
-
}
|
|
767
|
-
const next = reduce(cur, req.event, now());
|
|
768
|
-
if (next !== cur) {
|
|
769
|
-
store.put(next);
|
|
770
|
-
firePush(deps, req.project, cur.state, next, req.event, lastCaptainTurnAt.get(next.id));
|
|
771
|
-
}
|
|
772
|
-
return next;
|
|
827
|
+
return applyEvent(req.project, req.event);
|
|
773
828
|
}
|
|
774
829
|
case "status": {
|
|
775
830
|
const r = store.get(req.project, req.id);
|
|
@@ -828,6 +883,7 @@ function createDaemon(deps) {
|
|
|
828
883
|
store.delete(r.project, r.id);
|
|
829
884
|
}
|
|
830
885
|
}
|
|
886
|
+
const recoveryPromises = [];
|
|
831
887
|
for (const r of store.listAll()) {
|
|
832
888
|
if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
|
|
833
889
|
store.delete(r.project, r.id);
|
|
@@ -869,21 +925,9 @@ function createDaemon(deps) {
|
|
|
869
925
|
}
|
|
870
926
|
}
|
|
871
927
|
if (r.mode === "interactive" && r.state === "submitted" && !r.firstTurnConfirmedAt) {
|
|
872
|
-
const
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
quietNotifiedAt.set(r.id, r.lastHeartbeat);
|
|
876
|
-
const tag = crewTag(r);
|
|
877
|
-
const synthEvent = { type: "task.quiet", id: r.id, quietMs: elapsed };
|
|
878
|
-
const message = `\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (crew never started) \u2014 re-send the task or check the spawn.`;
|
|
879
|
-
try {
|
|
880
|
-
const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
|
|
881
|
-
if (p && typeof p.catch === "function")
|
|
882
|
-
p.catch(() => {
|
|
883
|
-
});
|
|
884
|
-
} catch {
|
|
885
|
-
}
|
|
886
|
-
}
|
|
928
|
+
const undeliveredMs = t - r.createdAt;
|
|
929
|
+
if (undeliveredMs > firstTurnUndeliveredBudgetMs) {
|
|
930
|
+
recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));
|
|
887
931
|
continue;
|
|
888
932
|
}
|
|
889
933
|
}
|
|
@@ -894,7 +938,14 @@ function createDaemon(deps) {
|
|
|
894
938
|
firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
|
|
895
939
|
continue;
|
|
896
940
|
}
|
|
897
|
-
if (r.mode === "interactive" && r.state === "working" && !r.pendingTool) {
|
|
941
|
+
if (r.mode === "interactive" && r.state === "working" && !r.pendingTool && !r.firstTurnConfirmedAt) {
|
|
942
|
+
const undeliveredMs = t - r.createdAt;
|
|
943
|
+
if (undeliveredMs > firstTurnUndeliveredBudgetMs) {
|
|
944
|
+
recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (r.mode === "interactive" && r.state === "working" && !r.pendingTool && r.firstTurnConfirmedAt) {
|
|
898
949
|
const liveness = r.attempts.at(-1)?.lastHeartbeatAt ?? r.lastHeartbeat;
|
|
899
950
|
const quiet = t - liveness;
|
|
900
951
|
if (quiet > r.heartbeatBudgetMs) {
|
|
@@ -902,13 +953,8 @@ function createDaemon(deps) {
|
|
|
902
953
|
quietNotifiedAt.set(r.id, liveness);
|
|
903
954
|
const tag = crewTag(r);
|
|
904
955
|
const synthEvent = { type: "task.quiet", id: r.id, quietMs: quiet };
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
message = `\u26A0\uFE0F CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) \u2014 re-send the task or check the spawn.`;
|
|
908
|
-
} else {
|
|
909
|
-
const mins = Math.max(1, Math.round(quiet / 6e4));
|
|
910
|
-
message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity \u2014 likely deep thinking (no reply expected yet).`;
|
|
911
|
-
}
|
|
956
|
+
const mins = Math.max(1, Math.round(quiet / 6e4));
|
|
957
|
+
const message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity \u2014 likely deep thinking (no reply expected yet).`;
|
|
912
958
|
try {
|
|
913
959
|
const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
|
|
914
960
|
if (p && typeof p.catch === "function")
|
|
@@ -926,6 +972,7 @@ function createDaemon(deps) {
|
|
|
926
972
|
if (recovered && t - r.lastHeartbeat <= r.heartbeatBudgetMs)
|
|
927
973
|
store.put(recovered);
|
|
928
974
|
}
|
|
975
|
+
await Promise.all(recoveryPromises);
|
|
929
976
|
},
|
|
930
977
|
async reconcile() {
|
|
931
978
|
const alive = deps.isPidAlive ?? (() => true);
|
|
@@ -1399,14 +1446,14 @@ var TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
|
1399
1446
|
function projectHealth(input) {
|
|
1400
1447
|
const { project, now, captainName, captainStopped, commandPresent, crews } = input;
|
|
1401
1448
|
const out = [];
|
|
1402
|
-
const captainState = captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown";
|
|
1449
|
+
const captainState = input.captainState ?? (captainStopped === true ? "stopped" : captainStopped === false ? "alive" : "unknown");
|
|
1403
1450
|
out.push({
|
|
1404
1451
|
kind: "captain",
|
|
1405
1452
|
project,
|
|
1406
1453
|
ref: captainName,
|
|
1407
1454
|
state: captainState,
|
|
1408
1455
|
lastSeenMs: null,
|
|
1409
|
-
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
|
|
1410
1457
|
});
|
|
1411
1458
|
if (commandPresent !== null) {
|
|
1412
1459
|
out.push({
|
|
@@ -1437,6 +1484,26 @@ function presence(p) {
|
|
|
1437
1484
|
return "unknown";
|
|
1438
1485
|
return p ? "alive" : "gone";
|
|
1439
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
|
+
}
|
|
1440
1507
|
|
|
1441
1508
|
// packages/core/dist/store.js
|
|
1442
1509
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
|
|
@@ -1595,7 +1662,73 @@ function makeGate(opts) {
|
|
|
1595
1662
|
import { homedir as homedir5 } from "os";
|
|
1596
1663
|
import { join as join8 } from "path";
|
|
1597
1664
|
import { spawn as realSpawn } from "child_process";
|
|
1598
|
-
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
|
|
1599
1732
|
function defaultIsPidAlive(pid) {
|
|
1600
1733
|
try {
|
|
1601
1734
|
process.kill(pid, 0);
|
|
@@ -1616,7 +1749,7 @@ function buildContext(opts) {
|
|
|
1616
1749
|
mkdirSync5(resultsDir, { recursive: true });
|
|
1617
1750
|
const writeResult = (id, payload) => {
|
|
1618
1751
|
const p = join8(resultsDir, `${id}.txt`);
|
|
1619
|
-
|
|
1752
|
+
writeFileSync7(p, payload);
|
|
1620
1753
|
return p;
|
|
1621
1754
|
};
|
|
1622
1755
|
const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
@@ -1637,8 +1770,12 @@ function buildContext(opts) {
|
|
|
1637
1770
|
attachConns: /* @__PURE__ */ new Map(),
|
|
1638
1771
|
inFlightHeadlessIds: /* @__PURE__ */ new Set(),
|
|
1639
1772
|
activeHeadlessKills: /* @__PURE__ */ new Set(),
|
|
1640
|
-
|
|
1641
|
-
|
|
1773
|
+
livenessRegistry: (() => {
|
|
1774
|
+
const r = new LivenessRegistry({ path: join8(stateRoot, "liveness.json") });
|
|
1775
|
+
r.load();
|
|
1776
|
+
return r;
|
|
1777
|
+
})(),
|
|
1778
|
+
resendFirstTurn: opts.resendFirstTurn,
|
|
1642
1779
|
// Late-bound — start.ts fills these before first use:
|
|
1643
1780
|
d: null,
|
|
1644
1781
|
notify: null,
|
|
@@ -1991,7 +2128,6 @@ var CaptainDelivery = class {
|
|
|
1991
2128
|
|
|
1992
2129
|
// packages/core/dist/daemon/delivery-loop.js
|
|
1993
2130
|
var CURSOR_SUBSCRIBER = "captain";
|
|
1994
|
-
var CAPTAIN_GONE_STREAK_K = 3;
|
|
1995
2131
|
var TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
1996
2132
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
1997
2133
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
@@ -2008,8 +2144,62 @@ function reapOrphanedCrews(store, project) {
|
|
|
2008
2144
|
}
|
|
2009
2145
|
return reaped;
|
|
2010
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
|
+
}
|
|
2011
2201
|
function createDelivery(ctx, daemonCmux) {
|
|
2012
|
-
const { stateRoot, store, log,
|
|
2202
|
+
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts } = ctx;
|
|
2013
2203
|
const defaultNotify = async (args) => {
|
|
2014
2204
|
try {
|
|
2015
2205
|
await appendToMailbox({
|
|
@@ -2035,6 +2225,21 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2035
2225
|
const sessionStartMs = Date.now();
|
|
2036
2226
|
let delivering = false;
|
|
2037
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
|
+
});
|
|
2038
2243
|
const injectedSurfaces = opts.captainSurfaces ?? {};
|
|
2039
2244
|
const allProjects = [.../* @__PURE__ */ new Set([
|
|
2040
2245
|
...Object.keys(cfg.projects ?? {}),
|
|
@@ -2046,34 +2251,14 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
2046
2251
|
const captainTitle = projCfg?.captainName ?? `${project}-captain`;
|
|
2047
2252
|
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
2048
2253
|
let surface = null;
|
|
2049
|
-
let surfacesLength = 0;
|
|
2050
2254
|
if (wsId) {
|
|
2051
2255
|
const surfaces = await cmux2.listSurfaces(wsId);
|
|
2052
|
-
surfacesLength = surfaces.length;
|
|
2053
2256
|
surface = discoverCaptainSurface(surfaces, captainTitle);
|
|
2054
2257
|
}
|
|
2055
2258
|
if (!surface)
|
|
2056
2259
|
surface = injectedSurfaces[project] ?? null;
|
|
2057
|
-
if (surface)
|
|
2058
|
-
if (stoppedProjects.has(project)) {
|
|
2059
|
-
stoppedProjects.delete(project);
|
|
2060
|
-
captainMissingStreak.set(project, 0);
|
|
2061
|
-
}
|
|
2062
|
-
captainMissingStreak.set(project, 0);
|
|
2063
|
-
} else {
|
|
2064
|
-
if (surfacesLength > 0) {
|
|
2065
|
-
const streak = (captainMissingStreak.get(project) ?? 0) + 1;
|
|
2066
|
-
captainMissingStreak.set(project, streak);
|
|
2067
|
-
if (streak >= CAPTAIN_GONE_STREAK_K) {
|
|
2068
|
-
if (!stoppedProjects.has(project)) {
|
|
2069
|
-
stoppedProjects.add(project);
|
|
2070
|
-
const reaped = reapOrphanedCrews(store, project);
|
|
2071
|
-
log(`captain ${captainTitle}: surface gone for ${CAPTAIN_GONE_STREAK_K} sweeps \u2014 stopping delivery${reaped > 0 ? `, reaped ${reaped} orphaned crew(s)` : ""}`);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2260
|
+
if (!surface)
|
|
2075
2261
|
continue;
|
|
2076
|
-
}
|
|
2077
2262
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
2078
2263
|
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
2079
2264
|
let d = deliveries.get(project);
|
|
@@ -2195,7 +2380,7 @@ function createServer2(ctx, handlers) {
|
|
|
2195
2380
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
2196
2381
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2197
2382
|
import { join as join9 } from "path";
|
|
2198
|
-
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";
|
|
2199
2384
|
var SELF_PATH = fileURLToPath2(import.meta.url);
|
|
2200
2385
|
function distBuiltAt() {
|
|
2201
2386
|
try {
|
|
@@ -2204,10 +2389,10 @@ function distBuiltAt() {
|
|
|
2204
2389
|
return 0;
|
|
2205
2390
|
}
|
|
2206
2391
|
}
|
|
2207
|
-
function gatherLogStats(
|
|
2392
|
+
function gatherLogStats(path18, now, windowMs) {
|
|
2208
2393
|
let sizeBytes = 0;
|
|
2209
2394
|
try {
|
|
2210
|
-
sizeBytes = statSync2(
|
|
2395
|
+
sizeBytes = statSync2(path18).size;
|
|
2211
2396
|
} catch {
|
|
2212
2397
|
return { errorCount: 0, sizeBytes: 0, windowMs };
|
|
2213
2398
|
}
|
|
@@ -2218,7 +2403,7 @@ function gatherLogStats(path17, now, windowMs) {
|
|
|
2218
2403
|
const len = sizeBytes - start;
|
|
2219
2404
|
let text = "";
|
|
2220
2405
|
try {
|
|
2221
|
-
const fd = openSync2(
|
|
2406
|
+
const fd = openSync2(path18, "r");
|
|
2222
2407
|
try {
|
|
2223
2408
|
const buf = Buffer.alloc(len);
|
|
2224
2409
|
readSync(fd, buf, 0, len, start);
|
|
@@ -2259,7 +2444,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
2259
2444
|
if (!n.endsWith(".json"))
|
|
2260
2445
|
continue;
|
|
2261
2446
|
try {
|
|
2262
|
-
JSON.parse(
|
|
2447
|
+
JSON.parse(readFileSync8(join9(dir, n), "utf-8"));
|
|
2263
2448
|
} catch {
|
|
2264
2449
|
corruptCount++;
|
|
2265
2450
|
}
|
|
@@ -2309,6 +2494,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2309
2494
|
notify,
|
|
2310
2495
|
taskTimeoutMs,
|
|
2311
2496
|
isSurfaceAlive: surfaceProbe,
|
|
2497
|
+
resendFirstTurn: ctx.resendFirstTurn,
|
|
2312
2498
|
launchHeadless: opts.launchHeadless,
|
|
2313
2499
|
isHeadlessInFlight: (id) => inFlightHeadlessIds.has(id),
|
|
2314
2500
|
launchInteractive: async (rec) => {
|
|
@@ -2343,14 +2529,13 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
2343
2529
|
for (const project of names) {
|
|
2344
2530
|
const proj = config.projects[project];
|
|
2345
2531
|
const captainName = proj?.captainName ?? `${project}-captain`;
|
|
2346
|
-
const
|
|
2347
|
-
const streak = ctx.captainMissingStreak.get(project);
|
|
2348
|
-
const captainStopped = stopped ? true : streak === 0 ? false : null;
|
|
2532
|
+
const capEntry = ctx.livenessRegistry.get(project);
|
|
2349
2533
|
out.push(...projectHealth({
|
|
2350
2534
|
project,
|
|
2351
2535
|
now,
|
|
2352
2536
|
captainName,
|
|
2353
|
-
captainStopped,
|
|
2537
|
+
captainStopped: null,
|
|
2538
|
+
captainState: deriveCaptainState(capEntry),
|
|
2354
2539
|
commandPresent: null,
|
|
2355
2540
|
crews: store.list(project)
|
|
2356
2541
|
}));
|
|
@@ -2549,6 +2734,21 @@ import crypto from "crypto";
|
|
|
2549
2734
|
import fs8 from "fs";
|
|
2550
2735
|
import path7 from "path";
|
|
2551
2736
|
|
|
2737
|
+
// packages/core/dist/crew-protocol.js
|
|
2738
|
+
function buildCompletionProtocol(taskId, project) {
|
|
2739
|
+
return [
|
|
2740
|
+
"---",
|
|
2741
|
+
"COMPLETION PROTOCOL (required): When this task is fully complete, your FINAL action MUST be to run exactly:",
|
|
2742
|
+
` squadrant crew signal done --task-id ${taskId} --project ${project} --message "<one-line summary>"`,
|
|
2743
|
+
"Run it as a discrete final step AFTER you report your results. If you are blocked or need a decision, instead run:",
|
|
2744
|
+
` squadrant crew signal blocked --task-id ${taskId} --project ${project} --question "<your question>"`,
|
|
2745
|
+
"If this task failed because of a defect in squadrant itself (not an API/infra blip, a config/user error, or an expected failure), say so in your signal done/blocked message so the captain can check tu11aa/squadrant and file it. Don't file issues from the crew."
|
|
2746
|
+
].join("\n");
|
|
2747
|
+
}
|
|
2748
|
+
function titleFor(project, name) {
|
|
2749
|
+
return `\u{1F527} ${project}:${name}`;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2552
2752
|
// packages/core/dist/crew-lifecycle.js
|
|
2553
2753
|
import { exec as nodeExec } from "child_process";
|
|
2554
2754
|
|
|
@@ -2692,21 +2892,33 @@ function createRunCommand(cliBin) {
|
|
|
2692
2892
|
}
|
|
2693
2893
|
};
|
|
2694
2894
|
}
|
|
2895
|
+
function isCaptainAliveFromHealth(rows, project) {
|
|
2896
|
+
return rows.some((h) => h.kind === "captain" && h.project === project && h.state === "alive");
|
|
2897
|
+
}
|
|
2695
2898
|
function createIsCaptainAlive(sock) {
|
|
2696
2899
|
return async (project) => {
|
|
2697
2900
|
try {
|
|
2698
2901
|
const health = await sendRequest(sock, { kind: "health", project }, 5e3);
|
|
2699
|
-
|
|
2700
|
-
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
2902
|
+
return isCaptainAliveFromHealth(health ?? [], project);
|
|
2701
2903
|
} catch {
|
|
2702
2904
|
return false;
|
|
2703
2905
|
}
|
|
2704
2906
|
};
|
|
2705
2907
|
}
|
|
2706
|
-
function createLaunch(cliBin) {
|
|
2707
|
-
return
|
|
2708
|
-
|
|
2709
|
-
|
|
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
|
+
});
|
|
2710
2922
|
}
|
|
2711
2923
|
|
|
2712
2924
|
// packages/core/dist/telegram/ensure-captain.js
|
|
@@ -3248,8 +3460,11 @@ function createTelegramBridge(opts) {
|
|
|
3248
3460
|
if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
|
|
3249
3461
|
try {
|
|
3250
3462
|
const r = await ensureCaptainAlive(resolved.project);
|
|
3251
|
-
if (r === "timeout")
|
|
3252
|
-
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
|
+
}
|
|
3253
3468
|
} catch (e) {
|
|
3254
3469
|
log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
|
|
3255
3470
|
}
|
|
@@ -4079,7 +4294,7 @@ var OpencodeSseBridge = class {
|
|
|
4079
4294
|
const fetchImpl = this.deps.fetchImpl ?? fetch;
|
|
4080
4295
|
const sleep3 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4081
4296
|
const reconnectMs = this.deps.reconnectMs ?? 500;
|
|
4082
|
-
const maxBoot = this.deps.maxBootAttempts ??
|
|
4297
|
+
const maxBoot = this.deps.maxBootAttempts ?? 240;
|
|
4083
4298
|
const url = `http://127.0.0.1:${port}/event`;
|
|
4084
4299
|
let booted = false;
|
|
4085
4300
|
let bootAttempts = 0;
|
|
@@ -4176,7 +4391,7 @@ var OpencodeSseBridge = class {
|
|
|
4176
4391
|
|
|
4177
4392
|
// packages/agents/dist/interactive/claude.js
|
|
4178
4393
|
import { execSync as execSync6 } from "child_process";
|
|
4179
|
-
import { readFileSync as
|
|
4394
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4180
4395
|
import { homedir as homedir9 } from "os";
|
|
4181
4396
|
import { join as join14 } from "path";
|
|
4182
4397
|
|
|
@@ -4433,6 +4648,27 @@ function parseDraftFromScreen(screen) {
|
|
|
4433
4648
|
}
|
|
4434
4649
|
return "";
|
|
4435
4650
|
}
|
|
4651
|
+
function hasCCInputBox(screen) {
|
|
4652
|
+
if (!screen)
|
|
4653
|
+
return false;
|
|
4654
|
+
const lines = screen.split(/\r?\n/);
|
|
4655
|
+
const HR_RE = /^\s*─{10,}\s*$/;
|
|
4656
|
+
let bottomHR = -1;
|
|
4657
|
+
let topHR = -1;
|
|
4658
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
4659
|
+
if (HR_RE.test(lines[i])) {
|
|
4660
|
+
if (bottomHR === -1)
|
|
4661
|
+
bottomHR = i;
|
|
4662
|
+
else {
|
|
4663
|
+
topHR = i;
|
|
4664
|
+
break;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
if (topHR === -1)
|
|
4669
|
+
return false;
|
|
4670
|
+
return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
|
|
4671
|
+
}
|
|
4436
4672
|
function hasModalOptionList(screen) {
|
|
4437
4673
|
if (!screen)
|
|
4438
4674
|
return false;
|
|
@@ -4483,6 +4719,15 @@ function readInputBoxRaw(screen, opts) {
|
|
|
4483
4719
|
const joined = parts.join("");
|
|
4484
4720
|
return opts?.trim === false ? joined : joined.replace(/\s+$/, "");
|
|
4485
4721
|
}
|
|
4722
|
+
var CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;
|
|
4723
|
+
var CC_WORKING_RE = /↓\s*[\d.]+\s*k?\s*tokens?\b|esc to interrupt|\bshell still running\b|·\s*\d+\s*shell\b|\(\d+m?\s*\d*s\b/i;
|
|
4724
|
+
function classifyStartupSurface(screen) {
|
|
4725
|
+
if (CC_WORKING_RE.test(screen))
|
|
4726
|
+
return "working";
|
|
4727
|
+
if (CC_INITIALIZED_RE.test(screen))
|
|
4728
|
+
return "idle";
|
|
4729
|
+
return "loading";
|
|
4730
|
+
}
|
|
4486
4731
|
function sendDebugEnabled() {
|
|
4487
4732
|
return !!process.env.SQUADRANT_DEBUG_SEND;
|
|
4488
4733
|
}
|
|
@@ -4772,6 +5017,38 @@ function createCmuxDriver() {
|
|
|
4772
5017
|
};
|
|
4773
5018
|
}
|
|
4774
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
|
+
|
|
4775
5052
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
4776
5053
|
import { execFileSync as execFileSync6, execSync as execSync7 } from "child_process";
|
|
4777
5054
|
|
|
@@ -4914,6 +5191,71 @@ var CmuxEventsBridge = class {
|
|
|
4914
5191
|
}
|
|
4915
5192
|
};
|
|
4916
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
|
+
|
|
4917
5259
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
4918
5260
|
var DaemonCmux = class {
|
|
4919
5261
|
driver;
|
|
@@ -4965,12 +5307,28 @@ var DaemonCmux = class {
|
|
|
4965
5307
|
return false;
|
|
4966
5308
|
}
|
|
4967
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
|
+
}
|
|
4968
5326
|
};
|
|
4969
5327
|
|
|
4970
5328
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
4971
|
-
import { join as
|
|
4972
|
-
import { homedir as
|
|
4973
|
-
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";
|
|
4974
5332
|
var CmuxStoreSource = class {
|
|
4975
5333
|
name = "cmux-store";
|
|
4976
5334
|
stateDir;
|
|
@@ -4991,7 +5349,7 @@ var CmuxStoreSource = class {
|
|
|
4991
5349
|
active = false;
|
|
4992
5350
|
lastError = null;
|
|
4993
5351
|
constructor(opts = {}) {
|
|
4994
|
-
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");
|
|
4995
5353
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
4996
5354
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
4997
5355
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
@@ -5053,7 +5411,7 @@ var CmuxStoreSource = class {
|
|
|
5053
5411
|
}
|
|
5054
5412
|
scanFile(filename) {
|
|
5055
5413
|
const deps = this.deps;
|
|
5056
|
-
const filePath =
|
|
5414
|
+
const filePath = join16(this.stateDir, filename);
|
|
5057
5415
|
const lockPath = `${filePath}.lock`;
|
|
5058
5416
|
if (this.fileExists(lockPath)) {
|
|
5059
5417
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -5119,14 +5477,14 @@ function defaultIsPidAlive2(pid) {
|
|
|
5119
5477
|
}
|
|
5120
5478
|
function defaultListFiles(dir) {
|
|
5121
5479
|
try {
|
|
5122
|
-
return
|
|
5480
|
+
return readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
|
|
5123
5481
|
} catch {
|
|
5124
5482
|
return [];
|
|
5125
5483
|
}
|
|
5126
5484
|
}
|
|
5127
|
-
function defaultReadFile(
|
|
5485
|
+
function defaultReadFile(path18) {
|
|
5128
5486
|
try {
|
|
5129
|
-
return
|
|
5487
|
+
return readFileSync11(path18, "utf-8");
|
|
5130
5488
|
} catch {
|
|
5131
5489
|
return void 0;
|
|
5132
5490
|
}
|
|
@@ -5141,9 +5499,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
5141
5499
|
}
|
|
5142
5500
|
|
|
5143
5501
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
5144
|
-
import { join as
|
|
5145
|
-
import { homedir as
|
|
5146
|
-
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";
|
|
5147
5505
|
var CLAUDE_HOOK_EVENTS = [
|
|
5148
5506
|
["SessionStart", "session-start"],
|
|
5149
5507
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -5155,7 +5513,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
5155
5513
|
];
|
|
5156
5514
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
5157
5515
|
function installClaudeHooks(opts = {}) {
|
|
5158
|
-
const settingsPath = opts.settingsPath ??
|
|
5516
|
+
const settingsPath = opts.settingsPath ?? join17(homedir12(), ".claude", "settings.json");
|
|
5159
5517
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
5160
5518
|
const readFile6 = opts.readFile ?? defaultReadFile2;
|
|
5161
5519
|
const writeFile5 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -5301,29 +5659,141 @@ function extractDetail(sub, payload) {
|
|
|
5301
5659
|
}
|
|
5302
5660
|
return void 0;
|
|
5303
5661
|
}
|
|
5304
|
-
function defaultReadFile2(
|
|
5662
|
+
function defaultReadFile2(path18) {
|
|
5305
5663
|
try {
|
|
5306
|
-
return
|
|
5664
|
+
return readFileSync12(path18, "utf-8");
|
|
5307
5665
|
} catch {
|
|
5308
5666
|
return void 0;
|
|
5309
5667
|
}
|
|
5310
5668
|
}
|
|
5311
|
-
function defaultWriteFile(
|
|
5312
|
-
mkdirSync6(
|
|
5313
|
-
|
|
5669
|
+
function defaultWriteFile(path18, content) {
|
|
5670
|
+
mkdirSync6(path18.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
5671
|
+
writeFileSync8(path18, content, "utf-8");
|
|
5314
5672
|
}
|
|
5315
5673
|
|
|
5316
5674
|
// packages/workspaces/dist/crew-pane.js
|
|
5317
5675
|
import net from "net";
|
|
5676
|
+
var POST_SEND_CHECK_MS = 750;
|
|
5677
|
+
var SETTLE_POLL_MS = 400;
|
|
5678
|
+
var SETTLE_MAX_POLLS = 8;
|
|
5679
|
+
var SUBMIT_RETRY_LIMIT = 4;
|
|
5680
|
+
async function settleInputBox(runtime, pane) {
|
|
5681
|
+
let prev = await runtime.readPaneScreen(pane) ?? "";
|
|
5682
|
+
let sawContent = parseDraftFromScreen(prev) !== "" && parseDraftFromScreen(prev) !== null;
|
|
5683
|
+
for (let i = 0; i < SETTLE_MAX_POLLS; i++) {
|
|
5684
|
+
await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));
|
|
5685
|
+
const cur = await runtime.readPaneScreen(pane) ?? "";
|
|
5686
|
+
const draft = parseDraftFromScreen(cur);
|
|
5687
|
+
if (draft !== "" && draft !== null)
|
|
5688
|
+
sawContent = true;
|
|
5689
|
+
if (cur === prev)
|
|
5690
|
+
return sawContent;
|
|
5691
|
+
prev = cur;
|
|
5692
|
+
}
|
|
5693
|
+
return sawContent;
|
|
5694
|
+
}
|
|
5695
|
+
async function confirmedSendToPane(runtime, pane, message) {
|
|
5696
|
+
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
5697
|
+
await runtime.pasteToPane(pane, message);
|
|
5698
|
+
let sawDraft = await settleInputBox(runtime, pane);
|
|
5699
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
5700
|
+
let repasted = false;
|
|
5701
|
+
for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
|
|
5702
|
+
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
5703
|
+
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
5704
|
+
const draft = parseDraftFromScreen(afterScreen);
|
|
5705
|
+
if (draft !== "" && draft !== null)
|
|
5706
|
+
sawDraft = true;
|
|
5707
|
+
if (draft === "" && sawDraft)
|
|
5708
|
+
return { delivered: true };
|
|
5709
|
+
if (draft === null && afterScreen !== preSendScreen && sawDraft)
|
|
5710
|
+
return { delivered: true };
|
|
5711
|
+
const settled = await settleInputBox(runtime, pane);
|
|
5712
|
+
if (settled)
|
|
5713
|
+
sawDraft = true;
|
|
5714
|
+
if (!sawDraft && !repasted) {
|
|
5715
|
+
repasted = true;
|
|
5716
|
+
await runtime.pasteToPane(pane, message);
|
|
5717
|
+
}
|
|
5718
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
5719
|
+
}
|
|
5720
|
+
return { delivered: false };
|
|
5721
|
+
}
|
|
5722
|
+
async function resendCrewFirstTurn(runtime, captainName, project, name, message) {
|
|
5723
|
+
const captain = await runtime.status(captainName);
|
|
5724
|
+
if (!captain)
|
|
5725
|
+
return { delivered: false };
|
|
5726
|
+
const surfaces = await runtime.listSurfaces(captain.id);
|
|
5727
|
+
const want = titleFor(project, name);
|
|
5728
|
+
const pane = surfaces.find((s) => s.title === want);
|
|
5729
|
+
if (!pane)
|
|
5730
|
+
return { delivered: false };
|
|
5731
|
+
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
5732
|
+
if (!hasCCInputBox(screen) || classifyStartupSurface(screen) !== "idle") {
|
|
5733
|
+
return { delivered: false };
|
|
5734
|
+
}
|
|
5735
|
+
return confirmedSendToPane(runtime, pane, message);
|
|
5736
|
+
}
|
|
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
|
+
}
|
|
5318
5788
|
|
|
5319
5789
|
// packages/cli/src/squadrantd.ts
|
|
5320
5790
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
5321
|
-
var CLI_BIN =
|
|
5322
|
-
var DAEMON_SOCK =
|
|
5791
|
+
var CLI_BIN = join18(dirname5(SELF_PATH2), "index.js");
|
|
5792
|
+
var DAEMON_SOCK = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5323
5793
|
function readPkgVersion() {
|
|
5324
5794
|
try {
|
|
5325
|
-
const pkgPath =
|
|
5326
|
-
return JSON.parse(
|
|
5795
|
+
const pkgPath = join18(dirname5(SELF_PATH2), "..", "package.json");
|
|
5796
|
+
return JSON.parse(readFileSync13(pkgPath, "utf-8")).version ?? "unknown";
|
|
5327
5797
|
} catch {
|
|
5328
5798
|
return "unknown";
|
|
5329
5799
|
}
|
|
@@ -5338,7 +5808,7 @@ function buildTelegramBridge(cfg, stateRoot, log) {
|
|
|
5338
5808
|
const client = createTelegramClient({ token });
|
|
5339
5809
|
const ensureCaptainAlive = createEnsureCaptainAlive({
|
|
5340
5810
|
isAlive: createIsCaptainAlive(DAEMON_SOCK),
|
|
5341
|
-
launch: createLaunch(CLI_BIN)
|
|
5811
|
+
launch: createLaunch(CLI_BIN, log)
|
|
5342
5812
|
});
|
|
5343
5813
|
const runCommand = createRunCommand(CLI_BIN);
|
|
5344
5814
|
const sendReply = (threadId, text, replyMarkup) => client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);
|
|
@@ -5406,7 +5876,7 @@ function startSquadrantd(opts = {}) {
|
|
|
5406
5876
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
5407
5877
|
);
|
|
5408
5878
|
},
|
|
5409
|
-
cursorFile:
|
|
5879
|
+
cursorFile: join18(stateRoot, "cmux-events.seq"),
|
|
5410
5880
|
log
|
|
5411
5881
|
});
|
|
5412
5882
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -5418,6 +5888,16 @@ function startSquadrantd(opts = {}) {
|
|
|
5418
5888
|
const tgCfg = loadConfig().telegram;
|
|
5419
5889
|
ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : void 0);
|
|
5420
5890
|
ctx.daemonCmux = opts.daemonCmux ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();
|
|
5891
|
+
const resendRuntime = createCmuxDriver();
|
|
5892
|
+
ctx.resendFirstTurn = opts.resendFirstTurn ?? (async (rec) => {
|
|
5893
|
+
if (rec.provider !== "claude" || !rec.name) return { delivered: false };
|
|
5894
|
+
const proj = loadConfig().projects[rec.project];
|
|
5895
|
+
const captainName = proj?.captainName ?? `${rec.project}-captain`;
|
|
5896
|
+
const message = `${rec.task}
|
|
5897
|
+
|
|
5898
|
+
${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
5899
|
+
return resendCrewFirstTurn(resendRuntime, captainName, rec.project, rec.name, message);
|
|
5900
|
+
});
|
|
5421
5901
|
const launchHeadless = opts.launchHeadless ?? (async (rec) => {
|
|
5422
5902
|
const ingest = (e) => void ctx.d.handle({ kind: "event", project: rec.project, event: e });
|
|
5423
5903
|
const handle = runHeadless({
|
|
@@ -5551,6 +6031,22 @@ function startSquadrantd(opts = {}) {
|
|
|
5551
6031
|
log(`codex app-server source start failed: ${e.message}`);
|
|
5552
6032
|
}
|
|
5553
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
|
+
}
|
|
5554
6050
|
const origStop = h.stop.bind(h);
|
|
5555
6051
|
h.stop = async () => {
|
|
5556
6052
|
try {
|
|
@@ -5576,7 +6072,7 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|
|
|
5576
6072
|
process.stdout.write("squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\n");
|
|
5577
6073
|
process.exit(0);
|
|
5578
6074
|
}
|
|
5579
|
-
const sock =
|
|
6075
|
+
const sock = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
|
|
5580
6076
|
if (await isDaemonSocketLive(sock)) {
|
|
5581
6077
|
process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}
|
|
5582
6078
|
`);
|