squadrant 0.19.0 → 0.19.2
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 +1341 -346
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +1097 -249
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +4 -3
- package/scripts/control-event-table.mjs +227 -0
- package/templates/captain.claude.md +1 -0
- package/templates/captain.generic.md +1 -0
- package/templates/crew.claude.md +2 -0
- package/templates/crew.generic.md +2 -0
- package/templates/crew.opencode.md +2 -0
package/dist/squadrantd.js
CHANGED
|
@@ -199,8 +199,8 @@ function deepMerge(base, patch) {
|
|
|
199
199
|
if (patch === null || typeof patch !== "object" || Array.isArray(patch))
|
|
200
200
|
return patch ?? base;
|
|
201
201
|
const out = { ...base };
|
|
202
|
-
for (const [k,
|
|
203
|
-
out[k] = deepMerge(out[k],
|
|
202
|
+
for (const [k, v2] of Object.entries(patch)) {
|
|
203
|
+
out[k] = deepMerge(out[k], v2);
|
|
204
204
|
}
|
|
205
205
|
return out;
|
|
206
206
|
}
|
|
@@ -676,8 +676,8 @@ var init_runtime_sync = __esm({
|
|
|
676
676
|
});
|
|
677
677
|
|
|
678
678
|
// packages/shared/dist/lib/tool-compat.js
|
|
679
|
-
function parseSemVer(
|
|
680
|
-
const m =
|
|
679
|
+
function parseSemVer(v2) {
|
|
680
|
+
const m = v2.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
681
681
|
if (!m)
|
|
682
682
|
return null;
|
|
683
683
|
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
@@ -804,8 +804,9 @@ function isStickyAttention(state) {
|
|
|
804
804
|
function nextPendingTool(current, ev, now) {
|
|
805
805
|
if (ev.note === "agent.hook.PreToolUse")
|
|
806
806
|
return { name: ev.tool ?? "tool", since: now };
|
|
807
|
-
if (ev.note === "posttooluse" || ev.note === "agent.hook.UserPromptSubmit")
|
|
807
|
+
if (ev.note === "posttooluse" || ev.note === "agent.hook.PostToolUse" || ev.note === "agent.hook.UserPromptSubmit") {
|
|
808
808
|
return void 0;
|
|
809
|
+
}
|
|
809
810
|
return current;
|
|
810
811
|
}
|
|
811
812
|
function nextPendingMonitor(current, ev, now) {
|
|
@@ -908,6 +909,7 @@ function reduce(rec, ev, now) {
|
|
|
908
909
|
case "task.stalled":
|
|
909
910
|
case "task.idle":
|
|
910
911
|
case "task.quiet":
|
|
912
|
+
case "task.warn":
|
|
911
913
|
case "task.timeout":
|
|
912
914
|
case "task.reconcile-failed":
|
|
913
915
|
return rec;
|
|
@@ -929,6 +931,15 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStal
|
|
|
929
931
|
if (rec.pendingTool) {
|
|
930
932
|
if (now - rec.pendingTool.since <= toolStallMs)
|
|
931
933
|
return null;
|
|
934
|
+
if (rec.lastEvent === "task.turn.completed") {
|
|
935
|
+
return {
|
|
936
|
+
...rec,
|
|
937
|
+
state: "awaiting-input",
|
|
938
|
+
pendingTool: void 0,
|
|
939
|
+
pendingMonitor: void 0,
|
|
940
|
+
lastEvent: "watchdog.tool-stall-recovered"
|
|
941
|
+
};
|
|
942
|
+
}
|
|
932
943
|
return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
|
|
933
944
|
}
|
|
934
945
|
if (rec.pendingMonitor) {
|
|
@@ -1250,7 +1261,7 @@ function createDaemon(deps) {
|
|
|
1250
1261
|
}
|
|
1251
1262
|
}
|
|
1252
1263
|
}
|
|
1253
|
-
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1264
|
+
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state) && r.state !== "awaiting-input") {
|
|
1254
1265
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1255
1266
|
const refTime = r.workingStretchStartedAt ?? r.createdAt;
|
|
1256
1267
|
if (t - refTime > ceiling) {
|
|
@@ -1296,6 +1307,11 @@ function createDaemon(deps) {
|
|
|
1296
1307
|
const idle = evaluateStall(r, t);
|
|
1297
1308
|
if (idle) {
|
|
1298
1309
|
store.put(idle);
|
|
1310
|
+
if (idle.state === "awaiting-input") {
|
|
1311
|
+
const recoveredEvent = { type: "task.turn.completed", id: r.id, turnId: "watchdog-recover" };
|
|
1312
|
+
firePush(deps, r.project, r.state, idle, recoveredEvent, lastCaptainTurnAt.get(r.id));
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1299
1315
|
const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : idle.pendingMonitor ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: "Monitor", elapsedMs: t - idle.pendingMonitor.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
|
|
1300
1316
|
firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
|
|
1301
1317
|
continue;
|
|
@@ -1403,6 +1419,7 @@ var init_reduce = __esm({
|
|
|
1403
1419
|
"task.stalled",
|
|
1404
1420
|
"task.idle",
|
|
1405
1421
|
"task.quiet",
|
|
1422
|
+
"task.warn",
|
|
1406
1423
|
"task.timeout",
|
|
1407
1424
|
"task.reconcile-failed",
|
|
1408
1425
|
"task.cancelled",
|
|
@@ -1806,12 +1823,12 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
|
|
|
1806
1823
|
return;
|
|
1807
1824
|
}
|
|
1808
1825
|
const conn = createConnection(sockPath);
|
|
1809
|
-
const finish = (
|
|
1826
|
+
const finish = (v2) => {
|
|
1810
1827
|
try {
|
|
1811
1828
|
conn.destroy();
|
|
1812
1829
|
} catch {
|
|
1813
1830
|
}
|
|
1814
|
-
resolve4(
|
|
1831
|
+
resolve4(v2);
|
|
1815
1832
|
};
|
|
1816
1833
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
1817
1834
|
conn.on("connect", () => {
|
|
@@ -1901,7 +1918,18 @@ function projectHealth(input) {
|
|
|
1901
1918
|
ref: captainName,
|
|
1902
1919
|
state: captainState,
|
|
1903
1920
|
lastSeenMs: null,
|
|
1904
|
-
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries
|
|
1921
|
+
detail: captainState === "stopped" ? "captain workspace closed \u2014 crews reaped; delivery paused" : captainState === "gone" ? "captain process died (crash) \u2014 crews reaped" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries, reason: ${deferral.reason ?? "unknown"})` : void 0
|
|
1922
|
+
});
|
|
1923
|
+
const deliveryState = captainState === "stopped" ? "stopped" : deferral?.stuck || deferral && deferral.maxDeferCount > 0 ? "stale" : "alive";
|
|
1924
|
+
const deliveryDetail = captainState === "stopped" ? "delivery paused (captain stopped)" : deferral?.stuck ? `\u26A0\uFE0F delivery stuck (${deferral.maxDeferCount}+ retries, reason: ${deferral.reason ?? "unknown"})` : deferral && deferral.maxDeferCount > 0 ? `delivery deferred (${deferral.maxDeferCount} retries, reason: ${deferral.reason ?? "unknown"})` : void 0;
|
|
1925
|
+
out.push({
|
|
1926
|
+
kind: "delivery",
|
|
1927
|
+
project,
|
|
1928
|
+
ref: "delivery",
|
|
1929
|
+
state: deliveryState,
|
|
1930
|
+
lastSeenMs: null,
|
|
1931
|
+
detail: deliveryDetail,
|
|
1932
|
+
stuck: deferral?.stuck
|
|
1905
1933
|
});
|
|
1906
1934
|
if (commandPresent !== null) {
|
|
1907
1935
|
out.push({
|
|
@@ -2003,8 +2031,23 @@ function createStore(root) {
|
|
|
2003
2031
|
};
|
|
2004
2032
|
const projDir = (p) => assertUnderRoot(join6(root, safeSegment("project", p)));
|
|
2005
2033
|
const taskFile = (p, id) => assertUnderRoot(join6(projDir(p), `${safeSegment("id", id)}.json`));
|
|
2034
|
+
const readRecord = (project, id) => {
|
|
2035
|
+
const f = taskFile(project, id);
|
|
2036
|
+
if (!existsSync6(f))
|
|
2037
|
+
return void 0;
|
|
2038
|
+
try {
|
|
2039
|
+
return JSON.parse(readFileSync4(f, "utf-8"));
|
|
2040
|
+
} catch {
|
|
2041
|
+
return void 0;
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2006
2044
|
return {
|
|
2007
2045
|
put(rec) {
|
|
2046
|
+
const existing = readRecord(rec.project, rec.id);
|
|
2047
|
+
if (existing && TERMINAL_STATES.has(existing.state) && TERMINAL_STATES.has(rec.state) && (existing.state !== rec.state || existing.lastEvent !== rec.lastEvent)) {
|
|
2048
|
+
console.error(`[squadrant] REJECTED terminal\u2192terminal overwrite of ${rec.project}/${rec.id}: already ${existing.state}/${existing.lastEvent} \u2014 refusing ${rec.state}/${rec.lastEvent}; original terminal record preserved (#595)`);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2008
2051
|
mkdirSync2(projDir(rec.project), { recursive: true });
|
|
2009
2052
|
const dest = taskFile(rec.project, rec.id);
|
|
2010
2053
|
const tmp = `${dest}.tmp`;
|
|
@@ -2012,14 +2055,7 @@ function createStore(root) {
|
|
|
2012
2055
|
renameSync(tmp, dest);
|
|
2013
2056
|
},
|
|
2014
2057
|
get(project, id) {
|
|
2015
|
-
|
|
2016
|
-
if (!existsSync6(f))
|
|
2017
|
-
return void 0;
|
|
2018
|
-
try {
|
|
2019
|
-
return JSON.parse(readFileSync4(f, "utf-8"));
|
|
2020
|
-
} catch {
|
|
2021
|
-
return void 0;
|
|
2022
|
-
}
|
|
2058
|
+
return readRecord(project, id);
|
|
2023
2059
|
},
|
|
2024
2060
|
list(project) {
|
|
2025
2061
|
const d = projDir(project);
|
|
@@ -2058,6 +2094,7 @@ function createStore(root) {
|
|
|
2058
2094
|
}
|
|
2059
2095
|
var init_store = __esm({
|
|
2060
2096
|
"packages/core/dist/store.js"() {
|
|
2097
|
+
init_dist();
|
|
2061
2098
|
}
|
|
2062
2099
|
});
|
|
2063
2100
|
|
|
@@ -2424,7 +2461,7 @@ function computeDaemonDrift(nodeBin) {
|
|
|
2424
2461
|
const foreignInstall = detectForeignInstall(parsedCurrent, entry, parsedCurrent !== null && existsSync8(parsedCurrent.daemonEntry));
|
|
2425
2462
|
return { plistPath: p, target, desired, current, changed, programChanged, foreignInstall };
|
|
2426
2463
|
}
|
|
2427
|
-
function
|
|
2464
|
+
function reconcilePlistAndService(drift) {
|
|
2428
2465
|
if (drift.changed) {
|
|
2429
2466
|
mkdirSync4(dirname2(drift.plistPath), { recursive: true });
|
|
2430
2467
|
writeFileSync5(drift.plistPath, drift.desired);
|
|
@@ -2440,8 +2477,59 @@ function applyDaemonDrift(drift) {
|
|
|
2440
2477
|
execFileSync3("launchctl", ["bootstrap", `gui/${uid}`, drift.plistPath], { stdio: "ignore" });
|
|
2441
2478
|
} catch {
|
|
2442
2479
|
}
|
|
2480
|
+
}
|
|
2481
|
+
function applyDaemonDrift(drift) {
|
|
2482
|
+
reconcilePlistAndService(drift);
|
|
2443
2483
|
execFileSync3("launchctl", ["kickstart", drift.target], { stdio: "ignore" });
|
|
2444
2484
|
}
|
|
2485
|
+
function getDaemonPid(target) {
|
|
2486
|
+
try {
|
|
2487
|
+
const out = execFileSync3("launchctl", ["print", target], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
2488
|
+
const m = out.match(/\bpid\s*=\s*(\d+)/);
|
|
2489
|
+
return m ? parseInt(m[1], 10) : null;
|
|
2490
|
+
} catch {
|
|
2491
|
+
return null;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function forceKickstartAndVerify(target, opts = {}) {
|
|
2495
|
+
const pollAttempts = opts.pollAttempts ?? 15;
|
|
2496
|
+
const pollDelayMs = opts.pollDelayMs ?? 300;
|
|
2497
|
+
const kickstartRetries = opts.kickstartRetries ?? 10;
|
|
2498
|
+
const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
|
|
2499
|
+
const pidBefore = getDaemonPid(target);
|
|
2500
|
+
let kickstartError = null;
|
|
2501
|
+
for (let i = 0; i < kickstartRetries; i++) {
|
|
2502
|
+
try {
|
|
2503
|
+
execFileSync3("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
|
|
2504
|
+
kickstartError = null;
|
|
2505
|
+
break;
|
|
2506
|
+
} catch (e) {
|
|
2507
|
+
kickstartError = e;
|
|
2508
|
+
if (i < kickstartRetries - 1) {
|
|
2509
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, kickstartRetryDelayMs);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
let pidAfter = null;
|
|
2514
|
+
for (let i = 0; i < pollAttempts; i++) {
|
|
2515
|
+
pidAfter = getDaemonPid(target);
|
|
2516
|
+
if (pidAfter !== null && pidAfter !== pidBefore)
|
|
2517
|
+
break;
|
|
2518
|
+
if (i < pollAttempts - 1) {
|
|
2519
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, pollDelayMs);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const restarted = pidAfter !== null && pidAfter !== pidBefore;
|
|
2523
|
+
if (kickstartError && !restarted)
|
|
2524
|
+
throw kickstartError;
|
|
2525
|
+
return {
|
|
2526
|
+
target,
|
|
2527
|
+
pidBefore,
|
|
2528
|
+
pidAfter,
|
|
2529
|
+
restarted,
|
|
2530
|
+
...kickstartError ? { note: "kickstart -k refused; daemon restarted by bootstrap" } : {}
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2445
2533
|
function isOperatorInitiatedCommand(topLevelArg) {
|
|
2446
2534
|
return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
|
|
2447
2535
|
}
|
|
@@ -2483,11 +2571,18 @@ function printForeignInstallError(foreign) {
|
|
|
2483
2571
|
Two squadrant installs on this machine will keep fighting over the daemon (#670). Uninstall the one you don't use, then run \`squadrant heal daemon\` to reconcile.
|
|
2484
2572
|
`;
|
|
2485
2573
|
}
|
|
2486
|
-
function reregisterDaemon(nodeBin = process.execPath) {
|
|
2487
|
-
if (!tryAcquireDaemonLock())
|
|
2488
|
-
|
|
2574
|
+
function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
2575
|
+
if (!tryAcquireDaemonLock()) {
|
|
2576
|
+
throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
|
|
2577
|
+
}
|
|
2489
2578
|
try {
|
|
2490
|
-
|
|
2579
|
+
const drift = computeDaemonDrift(nodeBin);
|
|
2580
|
+
reconcilePlistAndService(drift);
|
|
2581
|
+
const result = forceKickstartAndVerify(drift.target, kickstartOpts);
|
|
2582
|
+
if (!result.restarted) {
|
|
2583
|
+
throw new Error(`daemon did not restart (pid before=${result.pidBefore ?? "none"}, after=${result.pidAfter ?? "none"}) \u2014 \`launchctl kickstart -k ${drift.target}\` ran but the pid never changed`);
|
|
2584
|
+
}
|
|
2585
|
+
return result;
|
|
2491
2586
|
} finally {
|
|
2492
2587
|
releaseDaemonLock();
|
|
2493
2588
|
}
|
|
@@ -2875,6 +2970,10 @@ function detectTrailingQuestion(text) {
|
|
|
2875
2970
|
return lastLine;
|
|
2876
2971
|
return null;
|
|
2877
2972
|
}
|
|
2973
|
+
function isQuotedLine(raw) {
|
|
2974
|
+
const noAnsi = raw.replace(/\[[0-9;]*m/g, "");
|
|
2975
|
+
return QUOTED_PREFIX_RE.test(noAnsi);
|
|
2976
|
+
}
|
|
2878
2977
|
function stripChrome(raw) {
|
|
2879
2978
|
let line = raw.replace(/\[[0-9;]*m/g, "");
|
|
2880
2979
|
line = line.replace(/^[\s│┃▏▕|]+/, "").replace(/[\s│┃▏▕|]+$/, "");
|
|
@@ -2933,8 +3032,11 @@ function classifyPaneTail(tail) {
|
|
|
2933
3032
|
if (q)
|
|
2934
3033
|
return { kind: "question", text: q };
|
|
2935
3034
|
let errLine = null;
|
|
2936
|
-
for (
|
|
2937
|
-
|
|
3035
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
3036
|
+
const c = cleaned[i];
|
|
3037
|
+
if (c == null || isQuotedLine(raw[i]))
|
|
3038
|
+
continue;
|
|
3039
|
+
if (ERROR_BANNER_RE.some((re) => re.test(c)))
|
|
2938
3040
|
errLine = c;
|
|
2939
3041
|
}
|
|
2940
3042
|
if (errLine)
|
|
@@ -2977,6 +3079,22 @@ function createInteractiveProbe(deps) {
|
|
|
2977
3079
|
const verdict = classifyPaneTail(tail);
|
|
2978
3080
|
if (!verdict)
|
|
2979
3081
|
continue;
|
|
3082
|
+
if (verdict.kind === "error") {
|
|
3083
|
+
const alive = deps.checkAlive ? await deps.checkAlive(rec) : "unknown";
|
|
3084
|
+
if (alive !== "gone") {
|
|
3085
|
+
const message = `CREW WARN ${rec.name}: pane shows an error string \u2014 crew still ${alive}, not terminalized (pane-detected): ${verdict.text}`;
|
|
3086
|
+
deps.log(`probe -> ${message}`);
|
|
3087
|
+
if (deps.notify) {
|
|
3088
|
+
const warnEvent = { type: "task.warn", id: rec.id, message };
|
|
3089
|
+
try {
|
|
3090
|
+
await deps.notify({ project: rec.project, message, record: rec, event: warnEvent });
|
|
3091
|
+
} catch (e) {
|
|
3092
|
+
deps.log(`probe notify failed for ${rec.id}: ${e.message}`);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2980
3098
|
const event = verdict.kind === "error" ? {
|
|
2981
3099
|
type: "task.failed",
|
|
2982
3100
|
id: rec.id,
|
|
@@ -2998,7 +3116,7 @@ function createInteractiveProbe(deps) {
|
|
|
2998
3116
|
}
|
|
2999
3117
|
return { tick };
|
|
3000
3118
|
}
|
|
3001
|
-
var STALE_THRESHOLD_MS, PROBE_QUIET_MS, ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE;
|
|
3119
|
+
var STALE_THRESHOLD_MS, PROBE_QUIET_MS, ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE, QUOTED_PREFIX_RE;
|
|
3002
3120
|
var init_interactive_probe = __esm({
|
|
3003
3121
|
"packages/core/dist/daemon/interactive-probe.js"() {
|
|
3004
3122
|
STALE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
@@ -3014,6 +3132,7 @@ var init_interactive_probe = __esm({
|
|
|
3014
3132
|
PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
|
|
3015
3133
|
PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
|
|
3016
3134
|
STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
|
|
3135
|
+
QUOTED_PREFIX_RE = /^\s*(?:[┃│▏▕]|>|[+-]|\d+[\t:→])\s/;
|
|
3017
3136
|
}
|
|
3018
3137
|
});
|
|
3019
3138
|
|
|
@@ -3029,6 +3148,7 @@ function createProbes(ctx) {
|
|
|
3029
3148
|
};
|
|
3030
3149
|
function buildInteractiveProbe(deps) {
|
|
3031
3150
|
const directPaneReader = createDirectCrewPaneReader(deps.cmux, captainNameForProject);
|
|
3151
|
+
const checkAlive = createDirectSurfaceLivenessProbe(deps.cmux, captainNameForProject);
|
|
3032
3152
|
const probe = createInteractiveProbe({
|
|
3033
3153
|
project: "_all_",
|
|
3034
3154
|
listTasks: async () => store.listAll(),
|
|
@@ -3040,7 +3160,9 @@ function createProbes(ctx) {
|
|
|
3040
3160
|
}
|
|
3041
3161
|
},
|
|
3042
3162
|
now: () => Date.now(),
|
|
3043
|
-
log
|
|
3163
|
+
log,
|
|
3164
|
+
checkAlive,
|
|
3165
|
+
notify: ctx.notify
|
|
3044
3166
|
});
|
|
3045
3167
|
let probing = false;
|
|
3046
3168
|
return async () => {
|
|
@@ -3315,6 +3437,33 @@ import fs9 from "fs";
|
|
|
3315
3437
|
import os4 from "os";
|
|
3316
3438
|
import path9 from "path";
|
|
3317
3439
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3440
|
+
async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
|
|
3441
|
+
const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
|
|
3442
|
+
for (; ; ) {
|
|
3443
|
+
const rec = await getTaskRecord(project, id).catch(() => void 0);
|
|
3444
|
+
if (rec?.firstTurnConfirmedAt)
|
|
3445
|
+
return true;
|
|
3446
|
+
if (Date.now() >= deadline)
|
|
3447
|
+
return false;
|
|
3448
|
+
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
function firstTrueOrBothFalse(a, b) {
|
|
3452
|
+
return new Promise((resolve4) => {
|
|
3453
|
+
let settledFalseCount = 0;
|
|
3454
|
+
const onSettle = (ok2) => {
|
|
3455
|
+
if (ok2) {
|
|
3456
|
+
resolve4(true);
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
settledFalseCount++;
|
|
3460
|
+
if (settledFalseCount === 2)
|
|
3461
|
+
resolve4(false);
|
|
3462
|
+
};
|
|
3463
|
+
a.then(onSettle, () => onSettle(false));
|
|
3464
|
+
b.then(onSettle, () => onSettle(false));
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3318
3467
|
async function listCrewPanes(runtime, workspaceId, project) {
|
|
3319
3468
|
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
3320
3469
|
return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
|
|
@@ -3420,6 +3569,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3420
3569
|
const crewRole = config.defaults.roles?.crew;
|
|
3421
3570
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
3422
3571
|
const crewModel = input.model ?? route?.model ?? configModel;
|
|
3572
|
+
const crewThinking = input.thinking ?? config.defaults.roles?.crew?.thinking;
|
|
3423
3573
|
if (agentName !== "claude") {
|
|
3424
3574
|
deps.onModelResolved?.({ agentName, model: crewModel });
|
|
3425
3575
|
}
|
|
@@ -3455,7 +3605,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3455
3605
|
// crew apart from an unrelated session instead of an auto-derived cwd
|
|
3456
3606
|
// basename (only the claude driver reads this — other agents ignore it).
|
|
3457
3607
|
sessionName: crewSessionName(input.project, name),
|
|
3458
|
-
...crewModel ? { model: crewModel } : {}
|
|
3608
|
+
...crewModel ? { model: crewModel } : {},
|
|
3609
|
+
...crewThinking ? { thinking: crewThinking } : {}
|
|
3459
3610
|
});
|
|
3460
3611
|
const direction2 = input.direction ?? "tab";
|
|
3461
3612
|
const title2 = titleFor(input.project, name);
|
|
@@ -3463,10 +3614,18 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3463
3614
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
3464
3615
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
3465
3616
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
3466
|
-
|
|
3617
|
+
let claudeFirstTurn = firstTurnTask;
|
|
3618
|
+
if (Buffer.byteLength(claudeFirstTurn, "utf8") > FIRST_TURN_INLINE_MAX_BYTES) {
|
|
3619
|
+
const spillFile = path9.join(os4.tmpdir(), `squadrant-task-${rec.id}.md`);
|
|
3620
|
+
fs9.writeFileSync(spillFile, claudeFirstTurn, "utf8");
|
|
3621
|
+
claudeFirstTurn = `Full task is at ${spillFile} \u2014 cat it and follow it exactly.`;
|
|
3622
|
+
}
|
|
3623
|
+
const sendPromise = deps.sendFirstTurn(pane2, `${claudeFirstTurn}
|
|
3467
3624
|
|
|
3468
3625
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
3469
|
-
|
|
3626
|
+
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
3627
|
+
const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id)) : await scrapeDelivered;
|
|
3628
|
+
if (!delivered) {
|
|
3470
3629
|
process.stderr.write(`\u26A0\uFE0F First turn not delivered for crew '${name}' \u2014 use 'squadrant crew send ${input.project} ${name}' to re-send the task.
|
|
3471
3630
|
`);
|
|
3472
3631
|
} else if (!hooksInstalled) {
|
|
@@ -3563,7 +3722,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3563
3722
|
if (!crew) {
|
|
3564
3723
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
3565
3724
|
}
|
|
3566
|
-
const blockedByModalMessage = () => `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) \u2014 message NOT delivered, to avoid confirming its default option.
|
|
3725
|
+
const blockedByModalMessage = () => `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) \u2014 message NOT delivered, to avoid confirming its default option. To answer it deliberately: squadrant crew read ${project} ${name} to see the options, then squadrant crew answer ${project} ${name} <n>.`;
|
|
3567
3726
|
if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
|
|
3568
3727
|
throw new Error(blockedByModalMessage());
|
|
3569
3728
|
}
|
|
@@ -3577,10 +3736,16 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3577
3736
|
const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
|
|
3578
3737
|
throw new Error(`Crew '${name}' is under operator takeover (held ${heldForMin}m${task.operatorHold.note ? `: ${task.operatorHold.note}` : ""}). The operator is working in that tab \u2014 sending a message disrupts their conversation. Ask them to run 'squadrant crew handback ${project} ${name}', or pass --force if they told you to.`);
|
|
3579
3738
|
}
|
|
3739
|
+
const isAttentionState = task?.state === "blocked" || task?.state === "awaiting-input" || task?.state === "review";
|
|
3740
|
+
if (task && !isAttentionState && task.firstTurnConfirmedAt && task.task === message) {
|
|
3741
|
+
throw new Error(`Crew '${name}' already confirmed receipt of this task \u2014 its first turn was delivered and is not being re-sent to avoid running it twice. If you have new instructions, send different text.`);
|
|
3742
|
+
}
|
|
3743
|
+
let reopened = false;
|
|
3580
3744
|
try {
|
|
3581
3745
|
if (task) {
|
|
3582
3746
|
if (TERMINAL_STATES.has(task.state)) {
|
|
3583
3747
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
3748
|
+
reopened = true;
|
|
3584
3749
|
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
3585
3750
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
3586
3751
|
}
|
|
@@ -3606,7 +3771,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3606
3771
|
throw new Error(`Message to crew '${name}' is held: ${outcome.reason}. Resolve it in the crew's session, then re-send.`);
|
|
3607
3772
|
}
|
|
3608
3773
|
if (!fallsBackToPane(outcome)) {
|
|
3609
|
-
return;
|
|
3774
|
+
return { reopened };
|
|
3610
3775
|
}
|
|
3611
3776
|
}
|
|
3612
3777
|
if (mode === "shadow" && channel && task) {
|
|
@@ -3629,7 +3794,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3629
3794
|
if (!paneOk) {
|
|
3630
3795
|
throw new Error(`Message not delivered to crew '${name}' \u2014 the paste/submit could not be confirmed. Re-send with 'squadrant crew send ${project} ${name}'.`);
|
|
3631
3796
|
}
|
|
3632
|
-
return;
|
|
3797
|
+
return { reopened };
|
|
3633
3798
|
}
|
|
3634
3799
|
const { delivered, blockedByModal } = await deliver(crew, message);
|
|
3635
3800
|
if (blockedByModal) {
|
|
@@ -3638,6 +3803,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3638
3803
|
if (!delivered) {
|
|
3639
3804
|
throw new Error(`Message not delivered to crew '${name}' \u2014 the paste/submit could not be confirmed. Re-send with 'squadrant crew send ${project} ${name}'.`);
|
|
3640
3805
|
}
|
|
3806
|
+
return { reopened };
|
|
3641
3807
|
}
|
|
3642
3808
|
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
3643
3809
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
@@ -3740,7 +3906,7 @@ async function runCrewList(project, runtime, workspaceId) {
|
|
|
3740
3906
|
surfaceId: c.surfaceId
|
|
3741
3907
|
}));
|
|
3742
3908
|
}
|
|
3743
|
-
var CC_SOCKS_DIR, TEMPLATES_DIR, STATE_ROOT, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
|
|
3909
|
+
var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, FIRST_TURN_HOOK_CONFIRM_WINDOW_MS, FIRST_TURN_HOOK_POLL_INTERVAL_MS, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
|
|
3744
3910
|
var init_crew_spawn = __esm({
|
|
3745
3911
|
"packages/core/dist/crew-spawn.js"() {
|
|
3746
3912
|
init_control_channel();
|
|
@@ -3749,8 +3915,11 @@ var init_crew_spawn = __esm({
|
|
|
3749
3915
|
init_crew_protocol();
|
|
3750
3916
|
init_crew_lifecycle();
|
|
3751
3917
|
CC_SOCKS_DIR = "/tmp/cc-socks";
|
|
3918
|
+
FIRST_TURN_INLINE_MAX_BYTES = 1200;
|
|
3752
3919
|
TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
3753
3920
|
STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
|
|
3921
|
+
FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
|
|
3922
|
+
FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
|
|
3754
3923
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
3755
3924
|
CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
|
|
3756
3925
|
}
|
|
@@ -3795,13 +3964,16 @@ var init_captain_channel = __esm({
|
|
|
3795
3964
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
3796
3965
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
3797
3966
|
}
|
|
3798
|
-
function reapOrphanedCrews(store, project) {
|
|
3967
|
+
async function reapOrphanedCrews(store, project, isSurfaceAlive) {
|
|
3799
3968
|
let reaped = 0;
|
|
3800
3969
|
for (const r of store.list(project)) {
|
|
3801
3970
|
if (TERMINAL_STATES.has(r.state))
|
|
3802
3971
|
continue;
|
|
3803
3972
|
if (r.mode !== "interactive")
|
|
3804
3973
|
continue;
|
|
3974
|
+
const liveness = await isSurfaceAlive(r);
|
|
3975
|
+
if (liveness !== "gone")
|
|
3976
|
+
continue;
|
|
3805
3977
|
store.put({ ...r, state: "cancelled", lastEvent: "captain-stopped" });
|
|
3806
3978
|
reaped++;
|
|
3807
3979
|
}
|
|
@@ -3855,10 +4027,13 @@ async function runLivenessTick(deps) {
|
|
|
3855
4027
|
const prev = deps.registry.get(project);
|
|
3856
4028
|
if (prev && prev.lastState === "start")
|
|
3857
4029
|
entry.startedAt = prev.startedAt;
|
|
4030
|
+
const prevState = deriveCaptainState(prev);
|
|
3858
4031
|
deps.registry.apply(entry);
|
|
3859
4032
|
if (winner.pid != null)
|
|
3860
4033
|
deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);
|
|
3861
|
-
|
|
4034
|
+
const updated = deps.registry.get(project);
|
|
4035
|
+
if (deriveCaptainState(updated) !== prevState)
|
|
4036
|
+
logEntry(deps.log, project, updated);
|
|
3862
4037
|
}
|
|
3863
4038
|
for (const e of deps.registry.all()) {
|
|
3864
4039
|
if (e.role !== "captain" || e.lastState !== "start" || seen.has(e.project))
|
|
@@ -3876,12 +4051,13 @@ async function runLivenessTick(deps) {
|
|
|
3876
4051
|
continue;
|
|
3877
4052
|
const state = deriveCaptainState(e);
|
|
3878
4053
|
if (state === "stopped" || state === "gone")
|
|
3879
|
-
deps.reap(e.project);
|
|
4054
|
+
await deps.reap(e.project);
|
|
3880
4055
|
}
|
|
3881
4056
|
}
|
|
3882
4057
|
}
|
|
3883
|
-
function createDelivery(ctx, daemonCmux) {
|
|
4058
|
+
function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
3884
4059
|
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
4060
|
+
const surfaceProbe = isSurfaceAlive ?? (async () => "unknown");
|
|
3885
4061
|
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3886
4062
|
});
|
|
3887
4063
|
const defaultNotify = async (args) => {
|
|
@@ -3904,12 +4080,22 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3904
4080
|
}
|
|
3905
4081
|
};
|
|
3906
4082
|
if (!daemonCmux) {
|
|
3907
|
-
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0 };
|
|
4083
|
+
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0, inFlightDelivery: () => null };
|
|
3908
4084
|
}
|
|
3909
4085
|
const cmux2 = daemonCmux;
|
|
3910
4086
|
const cfg = loadConfig();
|
|
3911
4087
|
const deliveries = /* @__PURE__ */ new Map();
|
|
3912
4088
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
4089
|
+
const lastDeferred = /* @__PURE__ */ new Map();
|
|
4090
|
+
const inFlightDelivery = () => {
|
|
4091
|
+
let worst = null;
|
|
4092
|
+
for (const [project, v2] of lastDeferred) {
|
|
4093
|
+
if (!worst || v2.deferCount > worst.deferCount)
|
|
4094
|
+
worst = { project, ...v2 };
|
|
4095
|
+
}
|
|
4096
|
+
return worst;
|
|
4097
|
+
};
|
|
4098
|
+
const projectBackoff = /* @__PURE__ */ new Map();
|
|
3913
4099
|
const stuckNotified = /* @__PURE__ */ new Set();
|
|
3914
4100
|
const sessionStartMs = Date.now();
|
|
3915
4101
|
let delivering = false;
|
|
@@ -3920,8 +4106,8 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3920
4106
|
isPidAlive,
|
|
3921
4107
|
now: () => Date.now(),
|
|
3922
4108
|
log,
|
|
3923
|
-
reap: (project) => {
|
|
3924
|
-
const reaped = reapOrphanedCrews(store, project);
|
|
4109
|
+
reap: async (project) => {
|
|
4110
|
+
const reaped = await reapOrphanedCrews(store, project, surfaceProbe);
|
|
3925
4111
|
if (reaped > 0) {
|
|
3926
4112
|
const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;
|
|
3927
4113
|
log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);
|
|
@@ -3937,82 +4123,116 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3937
4123
|
cfg.commandName
|
|
3938
4124
|
])];
|
|
3939
4125
|
for (const project of allProjects) {
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
const
|
|
3946
|
-
surface =
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
4126
|
+
try {
|
|
4127
|
+
const backoff = projectBackoff.get(project);
|
|
4128
|
+
if (backoff && Date.now() < backoff.nextAttemptAt)
|
|
4129
|
+
continue;
|
|
4130
|
+
const projCfg = cfg.projects?.[project];
|
|
4131
|
+
const captainTitle = project === cfg.commandName ? cfg.commandName : projCfg?.captainName ?? `${project}-captain`;
|
|
4132
|
+
let surface = null;
|
|
4133
|
+
const resolveCaptainSurface = async () => {
|
|
4134
|
+
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
4135
|
+
if (!wsId)
|
|
4136
|
+
return injectedSurfaces[project] ?? null;
|
|
4137
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
4138
|
+
return discoverCaptainSurface(surfaces, captainTitle) ?? injectedSurfaces[project] ?? null;
|
|
4139
|
+
};
|
|
4140
|
+
surface = await resolveCaptainSurface();
|
|
4141
|
+
if (!surface)
|
|
4142
|
+
continue;
|
|
4143
|
+
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
4144
|
+
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
4145
|
+
let d = deliveries.get(project);
|
|
4146
|
+
if (!d) {
|
|
4147
|
+
d = new CaptainDelivery({
|
|
4148
|
+
maxDefers: cfg.delivery?.maxDeferDeliveries ?? 300,
|
|
4149
|
+
stableProbePolls: cfg.delivery?.stableProbePolls ?? 3
|
|
4150
|
+
});
|
|
4151
|
+
deliveries.set(project, d);
|
|
4152
|
+
}
|
|
4153
|
+
for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {
|
|
4154
|
+
if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {
|
|
4155
|
+
if (!TERMINAL_KINDS.has(entry.kind)) {
|
|
4156
|
+
const isExemptMessage = entry.kind === "captain.message" && entry.payload?.source !== "daemon";
|
|
4157
|
+
if (!isExemptMessage) {
|
|
4158
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);
|
|
4159
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);
|
|
4163
|
+
} else {
|
|
4164
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
const result = await d.deliver(entry, async (text, sendOpts) => {
|
|
4168
|
+
let handledByChannel = false;
|
|
4169
|
+
try {
|
|
4170
|
+
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
4171
|
+
const r = await deliverToCaptain(project, text, {
|
|
4172
|
+
channel: ctx.captainChannel,
|
|
4173
|
+
mode,
|
|
4174
|
+
log
|
|
4175
|
+
});
|
|
4176
|
+
handledByChannel = r.handled;
|
|
4177
|
+
} catch (e) {
|
|
4178
|
+
log(`captain-channel ${project}: threw, falling back to pane \u2014 ${e.message}`);
|
|
4179
|
+
}
|
|
4180
|
+
if (handledByChannel) {
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
try {
|
|
4184
|
+
return await cmux2.send(surface, text, sendOpts);
|
|
4185
|
+
} catch (e) {
|
|
4186
|
+
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
4187
|
+
throw e;
|
|
4188
|
+
const next = await resolveCaptainSurface();
|
|
4189
|
+
const same = next !== null && next.workspaceId === surface.workspaceId && next.surfaceId === surface.surfaceId;
|
|
4190
|
+
if (!next || same) {
|
|
4191
|
+
log(`delivery project=${project}: probe-failed but surface re-resolution found ${next ? "the same dead surface" : "no captain surface"} \u2014 deferring`);
|
|
4192
|
+
throw e;
|
|
4193
|
+
}
|
|
4194
|
+
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
4195
|
+
surface = next;
|
|
4196
|
+
return cmux2.send(next, text, sendOpts);
|
|
3970
4197
|
}
|
|
3971
|
-
|
|
4198
|
+
});
|
|
4199
|
+
if ("delivered" in result) {
|
|
4200
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
4201
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4202
|
+
lastDeferred.delete(project);
|
|
4203
|
+
projectBackoff.delete(project);
|
|
3972
4204
|
} else {
|
|
3973
|
-
|
|
4205
|
+
const { maxDeferCount, stuck: stuck2 } = d.stats();
|
|
4206
|
+
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4207
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4208
|
+
}
|
|
4209
|
+
lastDeferred.set(project, { seq: entry.seq, deferCount: maxDeferCount });
|
|
4210
|
+
if (stuck2) {
|
|
4211
|
+
const streak = (projectBackoff.get(project)?.streak ?? 0) + 1;
|
|
4212
|
+
const backoffMs = Math.min(6e4, 1e3 * 2 ** streak);
|
|
4213
|
+
projectBackoff.set(project, { nextAttemptAt: Date.now() + backoffMs, streak });
|
|
4214
|
+
}
|
|
4215
|
+
break;
|
|
3974
4216
|
}
|
|
3975
4217
|
}
|
|
3976
|
-
const
|
|
3977
|
-
|
|
4218
|
+
const stuck = d.stats().stuck;
|
|
4219
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
4220
|
+
stuckNotified.add(project);
|
|
4221
|
+
const { maxDeferCount, reason } = d.stats();
|
|
4222
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
4223
|
+
const text = STUCK_ALERT_TEXT[reason ?? "unknown"](maxDeferCount);
|
|
3978
4224
|
try {
|
|
3979
|
-
|
|
3980
|
-
const r = await deliverToCaptain(project, text, {
|
|
3981
|
-
channel: ctx.captainChannel,
|
|
3982
|
-
mode,
|
|
3983
|
-
log
|
|
3984
|
-
});
|
|
3985
|
-
handledByChannel = r.handled;
|
|
4225
|
+
await appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
3986
4226
|
} catch (e) {
|
|
3987
|
-
log(`
|
|
4227
|
+
log(`delivery stuck alert failed project=${project}: ${e.message}`);
|
|
3988
4228
|
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
});
|
|
3994
|
-
if ("delivered" in result) {
|
|
3995
|
-
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
3996
|
-
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
3997
|
-
} else {
|
|
3998
|
-
const { maxDeferCount } = d.stats();
|
|
3999
|
-
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4000
|
-
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4001
|
-
}
|
|
4002
|
-
break;
|
|
4229
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
4230
|
+
telegramBridge?.pushRaw(project, text);
|
|
4231
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
4232
|
+
stuckNotified.delete(project);
|
|
4003
4233
|
}
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
if (stuck && !stuckNotified.has(project)) {
|
|
4007
|
-
stuckNotified.add(project);
|
|
4008
|
-
const { maxDeferCount, reason } = d.stats();
|
|
4009
|
-
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
4010
|
-
const text = reason === "modal" ? `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.` : `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
|
|
4011
|
-
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
4012
|
-
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
4013
|
-
telegramBridge?.pushRaw(project, text);
|
|
4014
|
-
} else if (!stuck && stuckNotified.has(project)) {
|
|
4015
|
-
stuckNotified.delete(project);
|
|
4234
|
+
} catch (e) {
|
|
4235
|
+
log(`delivery project=${project}: unhandled error \u2014 ${e.message}`);
|
|
4016
4236
|
}
|
|
4017
4237
|
}
|
|
4018
4238
|
};
|
|
@@ -4026,19 +4246,28 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4026
4246
|
delivering = false;
|
|
4027
4247
|
}
|
|
4028
4248
|
};
|
|
4029
|
-
return { defaultNotify, deliveryTick, deliveryStats };
|
|
4249
|
+
return { defaultNotify, deliveryTick, deliveryStats, inFlightDelivery };
|
|
4030
4250
|
}
|
|
4031
|
-
var CURSOR_SUBSCRIBER, TERMINAL_KINDS;
|
|
4251
|
+
var CURSOR_SUBSCRIBER, TERMINAL_KINDS, STUCK_ALERT_TEXT;
|
|
4032
4252
|
var init_delivery_loop = __esm({
|
|
4033
4253
|
"packages/core/dist/daemon/delivery-loop.js"() {
|
|
4034
4254
|
init_mailbox();
|
|
4035
4255
|
init_captain_delivery();
|
|
4256
|
+
init_defer_delivery();
|
|
4036
4257
|
init_dist();
|
|
4037
4258
|
init_interactive_probe();
|
|
4038
4259
|
init_liveness2();
|
|
4039
4260
|
init_captain_channel();
|
|
4040
4261
|
CURSOR_SUBSCRIBER = "captain";
|
|
4041
4262
|
TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
4263
|
+
STUCK_ALERT_TEXT = {
|
|
4264
|
+
"no-box": (n) => `\u26A0\uFE0F DELIVERY STUCK: your captain pane's input box could not be confirmed visible (an overlay, menu, or scrolled view may be covering it) and has blocked pending notification(s) for ${n}+ retries. This keeps retrying safely and will deliver automatically once the input box is visible again.`,
|
|
4265
|
+
modal: (n) => `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${n}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.`,
|
|
4266
|
+
draft: (n) => `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${n}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`,
|
|
4267
|
+
"probe-failed": (n) => `\u26A0\uFE0F DELIVERY STUCK: reading your captain pane failed (stale/dead surface reference or cmux unavailable) and has blocked pending notification(s) for ${n}+ retries. Delivery re-resolves the pane automatically; if this persists after a captain restart, bounce the daemon to refresh its surface references.`,
|
|
4268
|
+
stable: (n) => `\u26A0\uFE0F DELIVERY STUCK: pending notification(s) have been blocked for ${n}+ retries. This keeps retrying safely and will deliver automatically once the blocker clears.`,
|
|
4269
|
+
unknown: (n) => `\u26A0\uFE0F DELIVERY STUCK: pending notification(s) have been blocked for ${n}+ retries. This keeps retrying safely and will deliver automatically once the blocker clears.`
|
|
4270
|
+
};
|
|
4042
4271
|
}
|
|
4043
4272
|
});
|
|
4044
4273
|
|
|
@@ -4126,10 +4355,72 @@ var init_server = __esm({
|
|
|
4126
4355
|
}
|
|
4127
4356
|
});
|
|
4128
4357
|
|
|
4358
|
+
// packages/core/dist/daemon/exit-marker.js
|
|
4359
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
|
|
4360
|
+
import { join as join11 } from "path";
|
|
4361
|
+
function exitMarkerPath(stateRoot) {
|
|
4362
|
+
return join11(stateRoot, "exit-marker.json");
|
|
4363
|
+
}
|
|
4364
|
+
function writeExitMarker(stateRoot, marker, log) {
|
|
4365
|
+
try {
|
|
4366
|
+
writeFileSync8(exitMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4367
|
+
} catch (e) {
|
|
4368
|
+
log(`exit marker write failed: ${e.message}`);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
function consumeExitMarker(stateRoot, now = Date.now) {
|
|
4372
|
+
const p = exitMarkerPath(stateRoot);
|
|
4373
|
+
if (!existsSync9(p))
|
|
4374
|
+
return { marker: null };
|
|
4375
|
+
let marker = null;
|
|
4376
|
+
try {
|
|
4377
|
+
marker = JSON.parse(readFileSync8(p, "utf-8"));
|
|
4378
|
+
} catch {
|
|
4379
|
+
marker = null;
|
|
4380
|
+
}
|
|
4381
|
+
try {
|
|
4382
|
+
unlinkSync3(p);
|
|
4383
|
+
} catch {
|
|
4384
|
+
}
|
|
4385
|
+
if (!marker)
|
|
4386
|
+
return { marker: null };
|
|
4387
|
+
const gapMs = Math.max(0, now() - new Date(marker.ts).getTime());
|
|
4388
|
+
return { marker, gapMs };
|
|
4389
|
+
}
|
|
4390
|
+
function runningMarkerPath(stateRoot) {
|
|
4391
|
+
return join11(stateRoot, "running-marker.json");
|
|
4392
|
+
}
|
|
4393
|
+
function writeRunningMarker(stateRoot, marker, log) {
|
|
4394
|
+
try {
|
|
4395
|
+
writeFileSync8(runningMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4396
|
+
} catch (e) {
|
|
4397
|
+
log(`running marker write failed: ${e.message}`);
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
function readRunningMarker(stateRoot) {
|
|
4401
|
+
try {
|
|
4402
|
+
return JSON.parse(readFileSync8(runningMarkerPath(stateRoot), "utf-8"));
|
|
4403
|
+
} catch {
|
|
4404
|
+
return null;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
function removeRunningMarker(stateRoot, log) {
|
|
4408
|
+
try {
|
|
4409
|
+
unlinkSync3(runningMarkerPath(stateRoot));
|
|
4410
|
+
} catch (e) {
|
|
4411
|
+
if (e.code !== "ENOENT")
|
|
4412
|
+
log(`running marker remove failed: ${e.message}`);
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
var init_exit_marker = __esm({
|
|
4416
|
+
"packages/core/dist/daemon/exit-marker.js"() {
|
|
4417
|
+
}
|
|
4418
|
+
});
|
|
4419
|
+
|
|
4129
4420
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
4130
4421
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4131
|
-
import { join as
|
|
4132
|
-
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as
|
|
4422
|
+
import { join as join12 } from "path";
|
|
4423
|
+
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
4133
4424
|
function distBuiltAt() {
|
|
4134
4425
|
try {
|
|
4135
4426
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -4182,7 +4473,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4182
4473
|
for (const r of store.list(project))
|
|
4183
4474
|
byState[r.state] = (byState[r.state] ?? 0) + 1;
|
|
4184
4475
|
let corruptCount = 0;
|
|
4185
|
-
const dir =
|
|
4476
|
+
const dir = join12(stateRoot, project);
|
|
4186
4477
|
try {
|
|
4187
4478
|
for (const n of readdirSync3(dir)) {
|
|
4188
4479
|
if (n.includes(".corrupt.")) {
|
|
@@ -4192,7 +4483,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4192
4483
|
if (!n.endsWith(".json"))
|
|
4193
4484
|
continue;
|
|
4194
4485
|
try {
|
|
4195
|
-
JSON.parse(
|
|
4486
|
+
JSON.parse(readFileSync9(join12(dir, n), "utf-8"));
|
|
4196
4487
|
} catch {
|
|
4197
4488
|
corruptCount++;
|
|
4198
4489
|
}
|
|
@@ -4207,7 +4498,7 @@ function gatherResults(resultsDir) {
|
|
|
4207
4498
|
try {
|
|
4208
4499
|
for (const n of readdirSync3(resultsDir)) {
|
|
4209
4500
|
try {
|
|
4210
|
-
const s = statSync3(
|
|
4501
|
+
const s = statSync3(join12(resultsDir, n));
|
|
4211
4502
|
if (s.isFile()) {
|
|
4212
4503
|
fileCount++;
|
|
4213
4504
|
totalBytes += s.size;
|
|
@@ -4227,19 +4518,20 @@ var init_snapshot_gather = __esm({
|
|
|
4227
4518
|
});
|
|
4228
4519
|
|
|
4229
4520
|
// packages/core/dist/daemon/start.js
|
|
4230
|
-
import { join as
|
|
4521
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
4231
4522
|
import { readdir } from "fs/promises";
|
|
4232
4523
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4233
4524
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
4234
4525
|
const { daemonCmux } = ctx;
|
|
4235
4526
|
const probes = createProbes(ctx);
|
|
4236
|
-
const
|
|
4527
|
+
const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);
|
|
4528
|
+
const { defaultNotify, deliveryTick: initialDeliveryTick, deliveryStats, inFlightDelivery } = createDelivery(ctx, daemonCmux, surfaceProbe);
|
|
4237
4529
|
const baseNotify = opts.notify ?? defaultNotify;
|
|
4238
4530
|
const notify = ctx.telegramBridge ? async (args) => {
|
|
4239
4531
|
await baseNotify(args);
|
|
4240
4532
|
ctx.telegramBridge.pushLifecycle(args.project, args.event);
|
|
4241
4533
|
} : baseNotify;
|
|
4242
|
-
|
|
4534
|
+
ctx.notify = notify;
|
|
4243
4535
|
const ingest = (project) => (e) => void ctx.d.handle({ kind: "event", project, event: e });
|
|
4244
4536
|
const d = createDaemon({
|
|
4245
4537
|
store,
|
|
@@ -4303,7 +4595,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4303
4595
|
return out;
|
|
4304
4596
|
}
|
|
4305
4597
|
async function gatherSnapshotInputs(now) {
|
|
4306
|
-
const logPath2 =
|
|
4598
|
+
const logPath2 = join13(dirname3(stateRoot), "squadrantd.log");
|
|
4307
4599
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4308
4600
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4309
4601
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -4396,6 +4688,33 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4396
4688
|
})();
|
|
4397
4689
|
const server = createServer2(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });
|
|
4398
4690
|
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
4691
|
+
const bootTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
4692
|
+
{
|
|
4693
|
+
const sendDownAlert = (minutes, reasonText) => {
|
|
4694
|
+
const text = `\u26A0\uFE0F daemon was down for ${minutes} min (last exit reason=${reasonText})`;
|
|
4695
|
+
const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4696
|
+
for (const project of alertProjects) {
|
|
4697
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
const { marker, gapMs } = consumeExitMarker(stateRoot);
|
|
4701
|
+
const prevRunning = readRunningMarker(stateRoot);
|
|
4702
|
+
if (marker) {
|
|
4703
|
+
log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
|
|
4704
|
+
if ((gapMs ?? 0) > 6e4)
|
|
4705
|
+
sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
4706
|
+
} else if (prevRunning) {
|
|
4707
|
+
const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
|
|
4708
|
+
const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
|
|
4709
|
+
log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
|
|
4710
|
+
if (uncleanGapMs > 6e4) {
|
|
4711
|
+
sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
|
|
4712
|
+
}
|
|
4713
|
+
} else {
|
|
4714
|
+
log("previous exit: none (clean or first boot)");
|
|
4715
|
+
}
|
|
4716
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: bootTs }, log);
|
|
4717
|
+
}
|
|
4399
4718
|
let deliveryTick = initialDeliveryTick;
|
|
4400
4719
|
let probeTick;
|
|
4401
4720
|
if (daemonCmux) {
|
|
@@ -4436,9 +4755,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4436
4755
|
keepCount: opts.mailboxConfig?.keepCount ?? 3
|
|
4437
4756
|
};
|
|
4438
4757
|
let rotationTimer;
|
|
4758
|
+
let rotationTick;
|
|
4439
4759
|
if (rotationInterval > 0) {
|
|
4440
|
-
const inboxPath =
|
|
4441
|
-
|
|
4760
|
+
const inboxPath = join13(stateRoot, "inbox");
|
|
4761
|
+
rotationTick = async () => {
|
|
4442
4762
|
try {
|
|
4443
4763
|
let entries;
|
|
4444
4764
|
try {
|
|
@@ -4451,13 +4771,23 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4451
4771
|
await rotateIfNeeded({ stateRoot, project, ...mboxCfg });
|
|
4452
4772
|
} catch (e) {
|
|
4453
4773
|
log(`rotation timer error: ${e.message}`);
|
|
4774
|
+
} finally {
|
|
4775
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: (/* @__PURE__ */ new Date()).toISOString() }, log);
|
|
4454
4776
|
}
|
|
4777
|
+
};
|
|
4778
|
+
rotationTimer = setInterval(() => {
|
|
4779
|
+
void rotationTick();
|
|
4455
4780
|
}, rotationInterval);
|
|
4456
4781
|
rotationTimer.unref?.();
|
|
4457
4782
|
}
|
|
4458
4783
|
return {
|
|
4459
4784
|
stop(reason = "requested") {
|
|
4460
|
-
|
|
4785
|
+
const ppid = process.ppid;
|
|
4786
|
+
const uptimeMs = Math.round(process.uptime() * 1e3);
|
|
4787
|
+
const inFlight = inFlightDelivery();
|
|
4788
|
+
log(`exit pid=${process.pid} reason=${reason} ppid=${ppid} launchd=${ppid === 1} uptimeMs=${uptimeMs} inFlightDelivery=${inFlight ? `${inFlight.project}#${inFlight.seq}(defers=${inFlight.deferCount})` : "none"}`);
|
|
4789
|
+
writeExitMarker(stateRoot, { ts: (/* @__PURE__ */ new Date()).toISOString(), pid: process.pid, reason, ppid, uptimeMs, inFlightDelivery: inFlight }, log);
|
|
4790
|
+
removeRunningMarker(stateRoot, log);
|
|
4461
4791
|
if (deliveryTimer)
|
|
4462
4792
|
clearInterval(deliveryTimer);
|
|
4463
4793
|
if (probeTimer)
|
|
@@ -4486,7 +4816,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4486
4816
|
}));
|
|
4487
4817
|
},
|
|
4488
4818
|
tickDelivery: deliveryTick,
|
|
4489
|
-
tickProbe: probeTick
|
|
4819
|
+
tickProbe: probeTick,
|
|
4820
|
+
tickRotation: rotationTick
|
|
4490
4821
|
};
|
|
4491
4822
|
}
|
|
4492
4823
|
var CURSOR_SUBSCRIBER2, SNAPSHOT_LOG_WINDOW_MS;
|
|
@@ -4498,6 +4829,7 @@ var init_start = __esm({
|
|
|
4498
4829
|
init_gates();
|
|
4499
4830
|
init_server();
|
|
4500
4831
|
init_mailbox();
|
|
4832
|
+
init_exit_marker();
|
|
4501
4833
|
init_liveness2();
|
|
4502
4834
|
init_dist();
|
|
4503
4835
|
init_snapshot_gather();
|
|
@@ -5492,9 +5824,9 @@ var init_bridge = __esm({
|
|
|
5492
5824
|
|
|
5493
5825
|
// packages/core/dist/restart-daemon.js
|
|
5494
5826
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5495
|
-
import { existsSync as
|
|
5827
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5496
5828
|
function defaultIsRunning() {
|
|
5497
|
-
return
|
|
5829
|
+
return existsSync10(DAEMON_SOCK_PATH);
|
|
5498
5830
|
}
|
|
5499
5831
|
function defaultRunKickstart() {
|
|
5500
5832
|
const uid = process.getuid?.() ?? 0;
|
|
@@ -6032,6 +6364,76 @@ var init_side_session = __esm({
|
|
|
6032
6364
|
}
|
|
6033
6365
|
});
|
|
6034
6366
|
|
|
6367
|
+
// packages/core/dist/crew-answer.js
|
|
6368
|
+
function describeOptions(options) {
|
|
6369
|
+
return options.map((o) => ` ${o.highlighted ? "\u276F" : " "} ${o.index}. ${o.label}`).join("\n");
|
|
6370
|
+
}
|
|
6371
|
+
function resolveOption(options, selector) {
|
|
6372
|
+
const trimmed = selector.trim();
|
|
6373
|
+
if (/^\d+$/.test(trimmed)) {
|
|
6374
|
+
const byIndex = options.find((o) => o.index === Number(trimmed));
|
|
6375
|
+
if (!byIndex) {
|
|
6376
|
+
throw new Error(`No option ${trimmed} in the visible prompt. Visible options:
|
|
6377
|
+
${describeOptions(options)}`);
|
|
6378
|
+
}
|
|
6379
|
+
return byIndex;
|
|
6380
|
+
}
|
|
6381
|
+
const lower = trimmed.toLowerCase();
|
|
6382
|
+
const exact = options.filter((o) => o.label.toLowerCase() === lower);
|
|
6383
|
+
if (exact.length === 1)
|
|
6384
|
+
return exact[0];
|
|
6385
|
+
if (exact.length > 1) {
|
|
6386
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
6387
|
+
${describeOptions(exact)}`);
|
|
6388
|
+
}
|
|
6389
|
+
const prefix = options.filter((o) => o.label.toLowerCase().startsWith(lower));
|
|
6390
|
+
if (prefix.length === 1)
|
|
6391
|
+
return prefix[0];
|
|
6392
|
+
if (prefix.length > 1) {
|
|
6393
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
6394
|
+
${describeOptions(prefix)}`);
|
|
6395
|
+
}
|
|
6396
|
+
throw new Error(`No option matches "${selector}". Visible options:
|
|
6397
|
+
${describeOptions(options)}`);
|
|
6398
|
+
}
|
|
6399
|
+
async function runCrewAnswer(project, name, option, runtime, workspaceId, deps, opts) {
|
|
6400
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6401
|
+
if (!crew) {
|
|
6402
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6403
|
+
}
|
|
6404
|
+
const options = await deps.readModalOptions(crew);
|
|
6405
|
+
if (!options) {
|
|
6406
|
+
throw new Error(`Crew '${name}' has no interactive option prompt visible right now \u2014 nothing to answer. Read its screen with 'squadrant crew read ${project} ${name}' to check its state.`);
|
|
6407
|
+
}
|
|
6408
|
+
const target = resolveOption(options, option);
|
|
6409
|
+
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
6410
|
+
throw new Error(`Refusing: option ${target.index} is "${target.label}", which does not contain expected text "${opts.expect}". Option order is model-generated and can shift between renders \u2014 re-check with 'squadrant crew read ${project} ${name}'.
|
|
6411
|
+
Visible options:
|
|
6412
|
+
${describeOptions(options)}`);
|
|
6413
|
+
}
|
|
6414
|
+
const log = deps.log ?? (() => {
|
|
6415
|
+
});
|
|
6416
|
+
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
6417
|
+
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
6418
|
+
const steps = target.index - current.index;
|
|
6419
|
+
const key = steps >= 0 ? "Down" : "Up";
|
|
6420
|
+
for (let i = 0; i < Math.abs(steps); i++) {
|
|
6421
|
+
await runtime.sendKeyToPane(crew, key);
|
|
6422
|
+
}
|
|
6423
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
6424
|
+
if (opts?.text) {
|
|
6425
|
+
await runtime.pasteToPane(crew, opts.text);
|
|
6426
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
6427
|
+
}
|
|
6428
|
+
const after = await deps.readModalOptions(crew);
|
|
6429
|
+
return { selected: target, closed: after === null };
|
|
6430
|
+
}
|
|
6431
|
+
var init_crew_answer = __esm({
|
|
6432
|
+
"packages/core/dist/crew-answer.js"() {
|
|
6433
|
+
init_crew_spawn();
|
|
6434
|
+
}
|
|
6435
|
+
});
|
|
6436
|
+
|
|
6035
6437
|
// packages/core/dist/lifecycle-source.js
|
|
6036
6438
|
function reduceLifecycle(prev, next) {
|
|
6037
6439
|
if (next.origin === "agent") {
|
|
@@ -6053,6 +6455,326 @@ var init_lifecycle_source = __esm({
|
|
|
6053
6455
|
}
|
|
6054
6456
|
});
|
|
6055
6457
|
|
|
6458
|
+
// packages/core/dist/events/fact.js
|
|
6459
|
+
function stampFact(raw, id) {
|
|
6460
|
+
return { ...raw, ...id };
|
|
6461
|
+
}
|
|
6462
|
+
var init_fact = __esm({
|
|
6463
|
+
"packages/core/dist/events/fact.js"() {
|
|
6464
|
+
}
|
|
6465
|
+
});
|
|
6466
|
+
|
|
6467
|
+
// packages/core/dist/events/log.js
|
|
6468
|
+
var FactLog;
|
|
6469
|
+
var init_log = __esm({
|
|
6470
|
+
"packages/core/dist/events/log.js"() {
|
|
6471
|
+
FactLog = class {
|
|
6472
|
+
capacity;
|
|
6473
|
+
buffers = /* @__PURE__ */ new Map();
|
|
6474
|
+
constructor(opts = {}) {
|
|
6475
|
+
this.capacity = opts.capacity ?? 256;
|
|
6476
|
+
}
|
|
6477
|
+
push(fact) {
|
|
6478
|
+
let buf = this.buffers.get(fact.taskId);
|
|
6479
|
+
if (!buf) {
|
|
6480
|
+
buf = [];
|
|
6481
|
+
this.buffers.set(fact.taskId, buf);
|
|
6482
|
+
}
|
|
6483
|
+
buf.push(fact);
|
|
6484
|
+
while (buf.length > this.capacity)
|
|
6485
|
+
buf.shift();
|
|
6486
|
+
}
|
|
6487
|
+
/** Oldest-first snapshot. A fresh array; later pushes never grow it. */
|
|
6488
|
+
recent(taskId) {
|
|
6489
|
+
return [...this.buffers.get(taskId) ?? []];
|
|
6490
|
+
}
|
|
6491
|
+
/** Newline-delimited JSON, one fact per line, oldest first. */
|
|
6492
|
+
serialize(taskId) {
|
|
6493
|
+
return this.recent(taskId).map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
6494
|
+
}
|
|
6495
|
+
/** Release a finished crew's buffer. */
|
|
6496
|
+
drop(taskId) {
|
|
6497
|
+
this.buffers.delete(taskId);
|
|
6498
|
+
}
|
|
6499
|
+
};
|
|
6500
|
+
}
|
|
6501
|
+
});
|
|
6502
|
+
|
|
6503
|
+
// packages/core/dist/events/invariant.js
|
|
6504
|
+
function freshTrace() {
|
|
6505
|
+
return {
|
|
6506
|
+
depth: 0,
|
|
6507
|
+
oldestOpenAt: null,
|
|
6508
|
+
stallReported: false,
|
|
6509
|
+
unknownSeen: 0,
|
|
6510
|
+
liveness: /* @__PURE__ */ new Map()
|
|
6511
|
+
};
|
|
6512
|
+
}
|
|
6513
|
+
function checkFact(trace, fact, opts) {
|
|
6514
|
+
const out = [];
|
|
6515
|
+
if (fact.origin === "inferred" && TERMINALISING.has(fact.kind)) {
|
|
6516
|
+
out.push(v("I4", `inferred fact "${fact.kind}" from ${fact.source} cannot terminalise alone`, fact));
|
|
6517
|
+
}
|
|
6518
|
+
switch (fact.kind) {
|
|
6519
|
+
case "tool.opened":
|
|
6520
|
+
if (trace.depth === 0) {
|
|
6521
|
+
trace.oldestOpenAt = fact.at;
|
|
6522
|
+
trace.stallReported = false;
|
|
6523
|
+
}
|
|
6524
|
+
trace.depth += 1;
|
|
6525
|
+
break;
|
|
6526
|
+
case "tool.closed":
|
|
6527
|
+
if (trace.depth === 0) {
|
|
6528
|
+
out.push(v("I1", `tool.closed from ${fact.source} with no open tool`, fact));
|
|
6529
|
+
} else {
|
|
6530
|
+
trace.depth -= 1;
|
|
6531
|
+
if (trace.depth === 0) {
|
|
6532
|
+
trace.oldestOpenAt = null;
|
|
6533
|
+
trace.stallReported = false;
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
break;
|
|
6537
|
+
case "turn.ended":
|
|
6538
|
+
if (trace.depth > 0) {
|
|
6539
|
+
out.push(v("I2", `turn.ended with ${trace.depth} tool call(s) still open`, fact));
|
|
6540
|
+
trace.depth = 0;
|
|
6541
|
+
trace.oldestOpenAt = null;
|
|
6542
|
+
trace.stallReported = false;
|
|
6543
|
+
}
|
|
6544
|
+
break;
|
|
6545
|
+
case "unknown":
|
|
6546
|
+
trace.unknownSeen += 1;
|
|
6547
|
+
out.push(v("I5", `unrecognised frame "${fact.name}" from ${fact.source}`, fact));
|
|
6548
|
+
break;
|
|
6549
|
+
case "process.observed": {
|
|
6550
|
+
const prior = [...trace.liveness.entries()].find(([src, s]) => src !== fact.source && s.alive !== fact.alive && fact.at - s.at <= (opts.disagreeWindowMs ?? -1));
|
|
6551
|
+
if (prior) {
|
|
6552
|
+
out.push(v("I6", `liveness disagreement: ${prior[0]} said alive=${prior[1].alive}, ${fact.source} says alive=${fact.alive}`, fact));
|
|
6553
|
+
}
|
|
6554
|
+
trace.liveness.set(fact.source, { alive: fact.alive, at: fact.at });
|
|
6555
|
+
break;
|
|
6556
|
+
}
|
|
6557
|
+
default:
|
|
6558
|
+
break;
|
|
6559
|
+
}
|
|
6560
|
+
if (opts.stallBudgetMs !== void 0 && trace.depth > 0 && trace.oldestOpenAt !== null && !trace.stallReported && fact.at - trace.oldestOpenAt > opts.stallBudgetMs) {
|
|
6561
|
+
trace.stallReported = true;
|
|
6562
|
+
out.push(v("I3", `tool open for ${fact.at - trace.oldestOpenAt}ms, past the stall budget`, fact));
|
|
6563
|
+
}
|
|
6564
|
+
return out;
|
|
6565
|
+
}
|
|
6566
|
+
var v, TERMINALISING;
|
|
6567
|
+
var init_invariant = __esm({
|
|
6568
|
+
"packages/core/dist/events/invariant.js"() {
|
|
6569
|
+
v = (code, message, f) => ({ code, message, taskId: f.taskId, at: f.at });
|
|
6570
|
+
TERMINALISING = /* @__PURE__ */ new Set(["session.ended"]);
|
|
6571
|
+
}
|
|
6572
|
+
});
|
|
6573
|
+
|
|
6574
|
+
// packages/core/dist/events/to-control-event.js
|
|
6575
|
+
function toControlEvent(fact) {
|
|
6576
|
+
if (fact.origin === "inferred" && TERMINALISING2.has(fact.kind))
|
|
6577
|
+
return [];
|
|
6578
|
+
switch (fact.kind) {
|
|
6579
|
+
case "turn.ended":
|
|
6580
|
+
return [{ type: "task.turn.completed", id: fact.taskId, turnId: fact.turnId ?? fact.source }];
|
|
6581
|
+
case "permission.requested":
|
|
6582
|
+
return [{
|
|
6583
|
+
type: "task.approval.requested",
|
|
6584
|
+
id: fact.taskId,
|
|
6585
|
+
requestId: fact.requestId,
|
|
6586
|
+
question: fact.question,
|
|
6587
|
+
kind: fact.tool
|
|
6588
|
+
}];
|
|
6589
|
+
case "input.requested":
|
|
6590
|
+
return [{
|
|
6591
|
+
type: "task.input.requested",
|
|
6592
|
+
id: fact.taskId,
|
|
6593
|
+
requestId: fact.requestId,
|
|
6594
|
+
question: fact.question
|
|
6595
|
+
}];
|
|
6596
|
+
case "session.ended":
|
|
6597
|
+
return [{ type: "task.session.ended", id: fact.taskId }];
|
|
6598
|
+
case "session.started":
|
|
6599
|
+
return [{
|
|
6600
|
+
type: "task.started",
|
|
6601
|
+
id: fact.taskId,
|
|
6602
|
+
...fact.pid === void 0 ? {} : { pid: fact.pid },
|
|
6603
|
+
...fact.sessionId === void 0 ? {} : { sessionId: fact.sessionId }
|
|
6604
|
+
}];
|
|
6605
|
+
case "prompt.submitted":
|
|
6606
|
+
return [{ type: "task.first-turn.confirmed", id: fact.taskId }];
|
|
6607
|
+
// Liveness-only. The facade still feeds these to reduceLifecycle; they
|
|
6608
|
+
// simply carry no ControlEvent of their own.
|
|
6609
|
+
case "tool.opened":
|
|
6610
|
+
case "tool.closed":
|
|
6611
|
+
case "activity":
|
|
6612
|
+
case "process.observed":
|
|
6613
|
+
case "unknown":
|
|
6614
|
+
return [];
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
var TERMINALISING2;
|
|
6618
|
+
var init_to_control_event = __esm({
|
|
6619
|
+
"packages/core/dist/events/to-control-event.js"() {
|
|
6620
|
+
TERMINALISING2 = /* @__PURE__ */ new Set(["session.ended"]);
|
|
6621
|
+
}
|
|
6622
|
+
});
|
|
6623
|
+
|
|
6624
|
+
// packages/core/dist/events/conformance.js
|
|
6625
|
+
function assert(cond, msg) {
|
|
6626
|
+
if (!cond)
|
|
6627
|
+
throw new Error(`conformance: ${msg}`);
|
|
6628
|
+
}
|
|
6629
|
+
function runAdapterConformance(adapter, samples) {
|
|
6630
|
+
const call = (raw) => adapter.translate(raw);
|
|
6631
|
+
return [
|
|
6632
|
+
{
|
|
6633
|
+
name: `${adapter.name}: never throws on garbage`,
|
|
6634
|
+
run: () => {
|
|
6635
|
+
for (const g of GARBAGE) {
|
|
6636
|
+
try {
|
|
6637
|
+
call(g);
|
|
6638
|
+
} catch (e) {
|
|
6639
|
+
throw new Error(`threw on ${JSON.stringify(g)}: ${String(e)}`);
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
},
|
|
6644
|
+
{
|
|
6645
|
+
name: `${adapter.name}: never returns null or undefined`,
|
|
6646
|
+
run: () => {
|
|
6647
|
+
for (const g of [...GARBAGE, ...samples]) {
|
|
6648
|
+
const out = call(g);
|
|
6649
|
+
assert(Array.isArray(out), `returned a non-array for ${JSON.stringify(g)}`);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
},
|
|
6653
|
+
{
|
|
6654
|
+
name: `${adapter.name}: an unrecognised frame yields unknown, not an empty array`,
|
|
6655
|
+
run: () => {
|
|
6656
|
+
const out = call({ type: "definitely-not-a-real-event-name" });
|
|
6657
|
+
assert(out.length > 0, "silently dropped an unrecognised frame (the #542 shape)");
|
|
6658
|
+
assert(out.every((f) => f.kind === "unknown"), "an unrecognised frame must translate to kind 'unknown'");
|
|
6659
|
+
}
|
|
6660
|
+
},
|
|
6661
|
+
{
|
|
6662
|
+
name: `${adapter.name}: recognises its own samples`,
|
|
6663
|
+
run: () => {
|
|
6664
|
+
for (const s of samples) {
|
|
6665
|
+
const out = call(s);
|
|
6666
|
+
assert(out.length > 0, `produced nothing for its own sample ${JSON.stringify(s)}`);
|
|
6667
|
+
assert(out.some((f) => f.kind !== "unknown"), `failed to recognise its own sample ${JSON.stringify(s)}`);
|
|
6668
|
+
}
|
|
6669
|
+
}
|
|
6670
|
+
},
|
|
6671
|
+
{
|
|
6672
|
+
name: `${adapter.name}: declares a constant origin`,
|
|
6673
|
+
run: () => {
|
|
6674
|
+
assert(adapter.origin === "agent" || adapter.origin === "scan" || adapter.origin === "inferred", `invalid origin "${String(adapter.origin)}"`);
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
];
|
|
6678
|
+
}
|
|
6679
|
+
var GARBAGE;
|
|
6680
|
+
var init_conformance = __esm({
|
|
6681
|
+
"packages/core/dist/events/conformance.js"() {
|
|
6682
|
+
GARBAGE = [
|
|
6683
|
+
null,
|
|
6684
|
+
void 0,
|
|
6685
|
+
0,
|
|
6686
|
+
"",
|
|
6687
|
+
"not json",
|
|
6688
|
+
[],
|
|
6689
|
+
{},
|
|
6690
|
+
{ type: 42 },
|
|
6691
|
+
{ type: "definitely-not-a-real-event-name" }
|
|
6692
|
+
];
|
|
6693
|
+
}
|
|
6694
|
+
});
|
|
6695
|
+
|
|
6696
|
+
// packages/core/dist/events/source.js
|
|
6697
|
+
function createEventsSource(opts) {
|
|
6698
|
+
const now = opts.now ?? (() => Date.now());
|
|
6699
|
+
const log = new FactLog({ capacity: opts.capacity });
|
|
6700
|
+
const adapters = new Map(opts.adapters.map((a) => [a.name, a]));
|
|
6701
|
+
const traces = /* @__PURE__ */ new Map();
|
|
6702
|
+
const seqs = /* @__PURE__ */ new Map();
|
|
6703
|
+
let deps;
|
|
6704
|
+
const traceFor = (taskId) => {
|
|
6705
|
+
let t = traces.get(taskId);
|
|
6706
|
+
if (!t) {
|
|
6707
|
+
t = freshTrace();
|
|
6708
|
+
traces.set(taskId, t);
|
|
6709
|
+
}
|
|
6710
|
+
return t;
|
|
6711
|
+
};
|
|
6712
|
+
const nextSeq = (taskId) => {
|
|
6713
|
+
const n = seqs.get(taskId) ?? 0;
|
|
6714
|
+
seqs.set(taskId, n + 1);
|
|
6715
|
+
return n;
|
|
6716
|
+
};
|
|
6717
|
+
return {
|
|
6718
|
+
name: "events",
|
|
6719
|
+
start(d) {
|
|
6720
|
+
deps = d;
|
|
6721
|
+
},
|
|
6722
|
+
stop() {
|
|
6723
|
+
deps = void 0;
|
|
6724
|
+
},
|
|
6725
|
+
health() {
|
|
6726
|
+
return { active: deps !== void 0, error: null };
|
|
6727
|
+
},
|
|
6728
|
+
recent(taskId) {
|
|
6729
|
+
return log.recent(taskId);
|
|
6730
|
+
},
|
|
6731
|
+
dump(taskId) {
|
|
6732
|
+
return log.serialize(taskId);
|
|
6733
|
+
},
|
|
6734
|
+
ingest(source, raw, hint) {
|
|
6735
|
+
const adapter = adapters.get(source);
|
|
6736
|
+
if (!adapter || !deps)
|
|
6737
|
+
return;
|
|
6738
|
+
const rec = deps.resolve(hint);
|
|
6739
|
+
if (!rec)
|
|
6740
|
+
return;
|
|
6741
|
+
const taskId = rec.id;
|
|
6742
|
+
const at = now();
|
|
6743
|
+
let produced;
|
|
6744
|
+
try {
|
|
6745
|
+
const out = adapter.translate(raw);
|
|
6746
|
+
produced = Array.isArray(out) ? out : [{ kind: "unknown", name: `${source} returned non-array` }];
|
|
6747
|
+
} catch (e) {
|
|
6748
|
+
opts.log?.(`events: adapter ${source} threw: ${String(e)}`);
|
|
6749
|
+
produced = [{ kind: "unknown", name: `${source} threw` }];
|
|
6750
|
+
}
|
|
6751
|
+
for (const rawFact of produced) {
|
|
6752
|
+
const fact = stampFact(rawFact, {
|
|
6753
|
+
seq: nextSeq(taskId),
|
|
6754
|
+
taskId,
|
|
6755
|
+
at,
|
|
6756
|
+
source,
|
|
6757
|
+
origin: adapter.origin
|
|
6758
|
+
});
|
|
6759
|
+
log.push(fact);
|
|
6760
|
+
for (const v2 of checkFact(traceFor(taskId), fact, opts.check ?? {})) {
|
|
6761
|
+
opts.onViolation(v2);
|
|
6762
|
+
}
|
|
6763
|
+
for (const ev of toControlEvent(fact))
|
|
6764
|
+
opts.emit(ev);
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
};
|
|
6768
|
+
}
|
|
6769
|
+
var init_source = __esm({
|
|
6770
|
+
"packages/core/dist/events/source.js"() {
|
|
6771
|
+
init_fact();
|
|
6772
|
+
init_log();
|
|
6773
|
+
init_invariant();
|
|
6774
|
+
init_to_control_event();
|
|
6775
|
+
}
|
|
6776
|
+
});
|
|
6777
|
+
|
|
6056
6778
|
// packages/core/dist/index.js
|
|
6057
6779
|
var dist_exports = {};
|
|
6058
6780
|
__export(dist_exports, {
|
|
@@ -6066,6 +6788,8 @@ __export(dist_exports, {
|
|
|
6066
6788
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
6067
6789
|
DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
|
|
6068
6790
|
DeferDelivery: () => DeferDelivery,
|
|
6791
|
+
FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
|
|
6792
|
+
FactLog: () => FactLog,
|
|
6069
6793
|
GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
|
|
6070
6794
|
GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
|
|
6071
6795
|
IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
|
|
@@ -6095,9 +6819,11 @@ __export(dist_exports, {
|
|
|
6095
6819
|
capAllowed: () => capAllowed,
|
|
6096
6820
|
capOutput: () => capOutput,
|
|
6097
6821
|
captainSocketPath: () => captainSocketPath,
|
|
6822
|
+
checkFact: () => checkFact,
|
|
6098
6823
|
classifyHealth: () => classifyHealth,
|
|
6099
6824
|
closeWorkItem: () => closeWorkItem,
|
|
6100
6825
|
computeTemplateHash: () => computeTemplateHash,
|
|
6826
|
+
consumeExitMarker: () => consumeExitMarker,
|
|
6101
6827
|
createAttach: () => createAttach,
|
|
6102
6828
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
6103
6829
|
createDaemon: () => createDaemon,
|
|
@@ -6106,6 +6832,7 @@ __export(dist_exports, {
|
|
|
6106
6832
|
createDirectCrewPaneReader: () => createDirectCrewPaneReader,
|
|
6107
6833
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
6108
6834
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
6835
|
+
createEventsSource: () => createEventsSource,
|
|
6109
6836
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
6110
6837
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
6111
6838
|
createLaunch: () => createLaunch,
|
|
@@ -6138,13 +6865,18 @@ __export(dist_exports, {
|
|
|
6138
6865
|
encodeMsg: () => encodeMsg,
|
|
6139
6866
|
ensureDaemon: () => ensureDaemon,
|
|
6140
6867
|
evaluateStall: () => evaluateStall,
|
|
6868
|
+
exitMarkerPath: () => exitMarkerPath,
|
|
6141
6869
|
fallsBackToPane: () => fallsBackToPane,
|
|
6870
|
+
findCrewPane: () => findCrewPane,
|
|
6142
6871
|
findOpenChildren: () => findOpenChildren,
|
|
6143
6872
|
findProjectByThread: () => findProjectByThread,
|
|
6144
6873
|
findWorkItemById: () => findWorkItemById,
|
|
6874
|
+
forceKickstartAndVerify: () => forceKickstartAndVerify,
|
|
6145
6875
|
formatInbound: () => formatInbound,
|
|
6146
6876
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
6147
6877
|
formatLifecycle: () => formatLifecycle,
|
|
6878
|
+
freshTrace: () => freshTrace,
|
|
6879
|
+
getDaemonPid: () => getDaemonPid,
|
|
6148
6880
|
healCmdFor: () => healCmdFor,
|
|
6149
6881
|
isAuthorized: () => isAuthorized,
|
|
6150
6882
|
isBareSpawn: () => isBareSpawn,
|
|
@@ -6179,6 +6911,7 @@ __export(dist_exports, {
|
|
|
6179
6911
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
6180
6912
|
readCursor: () => readCursor,
|
|
6181
6913
|
readFromCursor: () => readFromCursor,
|
|
6914
|
+
readRunningMarker: () => readRunningMarker,
|
|
6182
6915
|
reapCrewChildren: () => reapCrewChildren,
|
|
6183
6916
|
reapOrphanedCrews: () => reapOrphanedCrews,
|
|
6184
6917
|
reconcileLiveness: () => reconcileLiveness,
|
|
@@ -6187,6 +6920,7 @@ __export(dist_exports, {
|
|
|
6187
6920
|
reduce: () => reduce,
|
|
6188
6921
|
reduceLifecycle: () => reduceLifecycle,
|
|
6189
6922
|
releaseDaemonLock: () => releaseDaemonLock,
|
|
6923
|
+
removeRunningMarker: () => removeRunningMarker,
|
|
6190
6924
|
renderPlist: () => renderPlist,
|
|
6191
6925
|
reregisterDaemon: () => reregisterDaemon,
|
|
6192
6926
|
resolveAgentBinDirs: () => resolveAgentBinDirs,
|
|
@@ -6198,6 +6932,8 @@ __export(dist_exports, {
|
|
|
6198
6932
|
resolveSetupUserId: () => resolveSetupUserId,
|
|
6199
6933
|
restartDaemonIfRunning: () => restartDaemonIfRunning,
|
|
6200
6934
|
rotateIfNeeded: () => rotateIfNeeded,
|
|
6935
|
+
runAdapterConformance: () => runAdapterConformance,
|
|
6936
|
+
runCrewAnswer: () => runCrewAnswer,
|
|
6201
6937
|
runCrewClose: () => runCrewClose,
|
|
6202
6938
|
runCrewList: () => runCrewList,
|
|
6203
6939
|
runCrewRead: () => runCrewRead,
|
|
@@ -6217,6 +6953,7 @@ __export(dist_exports, {
|
|
|
6217
6953
|
runTelegramPostSetup: () => runTelegramPostSetup,
|
|
6218
6954
|
runTelegramSend: () => runTelegramSend,
|
|
6219
6955
|
runTelegramStatus: () => runTelegramStatus,
|
|
6956
|
+
runningMarkerPath: () => runningMarkerPath,
|
|
6220
6957
|
sanitizePathForPlist: () => sanitizePathForPlist,
|
|
6221
6958
|
saveSessions: () => saveSessions,
|
|
6222
6959
|
saveState: () => saveState,
|
|
@@ -6230,18 +6967,22 @@ __export(dist_exports, {
|
|
|
6230
6967
|
sideNameFromTitle: () => sideNameFromTitle,
|
|
6231
6968
|
sideNextAutoName: () => sideNextAutoName,
|
|
6232
6969
|
sideTitleFor: () => sideTitleFor,
|
|
6970
|
+
stampFact: () => stampFact,
|
|
6233
6971
|
startDaemon: () => startDaemon,
|
|
6234
6972
|
startServer: () => startServer,
|
|
6235
6973
|
stripBotMention: () => stripBotMention,
|
|
6236
6974
|
surfaceVerdict: () => surfaceVerdict,
|
|
6237
6975
|
timeoutGate: () => timeoutGate,
|
|
6238
6976
|
titleFor: () => titleFor,
|
|
6977
|
+
toControlEvent: () => toControlEvent,
|
|
6239
6978
|
topicKey: () => topicKey,
|
|
6240
6979
|
topicName: () => topicName,
|
|
6241
6980
|
tryAcquireDaemonLock: () => tryAcquireDaemonLock,
|
|
6242
6981
|
waitForCaptainDelivery: () => waitForCaptainDelivery,
|
|
6243
6982
|
waitForWarmup: () => waitForWarmup,
|
|
6244
6983
|
writeCursor: () => writeCursor,
|
|
6984
|
+
writeExitMarker: () => writeExitMarker,
|
|
6985
|
+
writeRunningMarker: () => writeRunningMarker,
|
|
6245
6986
|
writeTelegramConfig: () => writeTelegramConfig
|
|
6246
6987
|
});
|
|
6247
6988
|
var init_dist2 = __esm({
|
|
@@ -6264,6 +7005,7 @@ var init_dist2 = __esm({
|
|
|
6264
7005
|
init_attach();
|
|
6265
7006
|
init_start();
|
|
6266
7007
|
init_delivery_loop();
|
|
7008
|
+
init_exit_marker();
|
|
6267
7009
|
init_interactive_probe();
|
|
6268
7010
|
init_captain_delivery();
|
|
6269
7011
|
init_defer_delivery();
|
|
@@ -6277,7 +7019,14 @@ var init_dist2 = __esm({
|
|
|
6277
7019
|
init_launch_workspace();
|
|
6278
7020
|
init_side_session();
|
|
6279
7021
|
init_crew_spawn();
|
|
7022
|
+
init_crew_answer();
|
|
6280
7023
|
init_lifecycle_source();
|
|
7024
|
+
init_fact();
|
|
7025
|
+
init_log();
|
|
7026
|
+
init_invariant();
|
|
7027
|
+
init_to_control_event();
|
|
7028
|
+
init_conformance();
|
|
7029
|
+
init_source();
|
|
6281
7030
|
init_control_channel();
|
|
6282
7031
|
init_captain_channel();
|
|
6283
7032
|
}
|
|
@@ -6294,9 +7043,11 @@ init_dist2();
|
|
|
6294
7043
|
init_dist2();
|
|
6295
7044
|
init_dist2();
|
|
6296
7045
|
init_dist2();
|
|
6297
|
-
|
|
7046
|
+
init_dist2();
|
|
7047
|
+
import { join as join22, dirname as dirname4, resolve as resolve3 } from "path";
|
|
7048
|
+
import { homedir as homedir13 } from "os";
|
|
6298
7049
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6299
|
-
import { readFileSync as
|
|
7050
|
+
import { readFileSync as readFileSync15, statSync as statSync4, existsSync as existsSync13 } from "fs";
|
|
6300
7051
|
|
|
6301
7052
|
// packages/agents/dist/drivers/claude.js
|
|
6302
7053
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -6612,10 +7363,10 @@ function toSnapshot(ev) {
|
|
|
6612
7363
|
// packages/agents/dist/codex/config.js
|
|
6613
7364
|
import { readFile as readFile6 } from "fs/promises";
|
|
6614
7365
|
import { homedir as homedir7 } from "os";
|
|
6615
|
-
import { join as
|
|
7366
|
+
import { join as join14 } from "path";
|
|
6616
7367
|
async function resolveCodexModel() {
|
|
6617
|
-
const home = process.env["CODEX_HOME"] ??
|
|
6618
|
-
const configPath =
|
|
7368
|
+
const home = process.env["CODEX_HOME"] ?? join14(homedir7(), ".codex");
|
|
7369
|
+
const configPath = join14(home, "config.toml");
|
|
6619
7370
|
let text;
|
|
6620
7371
|
try {
|
|
6621
7372
|
text = await readFile6(configPath, "utf8");
|
|
@@ -6931,9 +7682,9 @@ ${directive}` : directive;
|
|
|
6931
7682
|
function withTimeout(p, ms, msg) {
|
|
6932
7683
|
return new Promise((resolve4, reject) => {
|
|
6933
7684
|
const t = setTimeout(() => reject(new Error(msg)), ms);
|
|
6934
|
-
p.then((
|
|
7685
|
+
p.then((v2) => {
|
|
6935
7686
|
clearTimeout(t);
|
|
6936
|
-
resolve4(
|
|
7687
|
+
resolve4(v2);
|
|
6937
7688
|
}, (e) => {
|
|
6938
7689
|
clearTimeout(t);
|
|
6939
7690
|
reject(e);
|
|
@@ -6942,6 +7693,7 @@ function withTimeout(p, ms, msg) {
|
|
|
6942
7693
|
}
|
|
6943
7694
|
|
|
6944
7695
|
// packages/agents/dist/opencode/sse-bridge.js
|
|
7696
|
+
var IGNORED_FRAME = /^(message|storage|file|lsp|installation)\./;
|
|
6945
7697
|
var OpencodeSseBridge = class {
|
|
6946
7698
|
controllers = /* @__PURE__ */ new Map();
|
|
6947
7699
|
/** taskId → the crew's opencode server port (for permission-reply POSTs). */
|
|
@@ -7078,6 +7830,10 @@ var OpencodeSseBridge = class {
|
|
|
7078
7830
|
return;
|
|
7079
7831
|
}
|
|
7080
7832
|
if (json?.type === "session.idle") {
|
|
7833
|
+
if (this.deps.ingest) {
|
|
7834
|
+
this.deps.ingest(json, taskId);
|
|
7835
|
+
return;
|
|
7836
|
+
}
|
|
7081
7837
|
this.deps.emit({
|
|
7082
7838
|
type: "task.turn.completed",
|
|
7083
7839
|
id: taskId,
|
|
@@ -7087,6 +7843,10 @@ var OpencodeSseBridge = class {
|
|
|
7087
7843
|
const p = json.properties;
|
|
7088
7844
|
if (p?.id && p?.sessionID) {
|
|
7089
7845
|
this.pendingPermByTask.set(taskId, { permID: p.id, sessionID: p.sessionID });
|
|
7846
|
+
if (this.deps.ingest) {
|
|
7847
|
+
this.deps.ingest(json, taskId);
|
|
7848
|
+
return;
|
|
7849
|
+
}
|
|
7090
7850
|
const tool = p.permission ?? "a tool";
|
|
7091
7851
|
const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
|
|
7092
7852
|
this.deps.emit({
|
|
@@ -7096,18 +7856,31 @@ var OpencodeSseBridge = class {
|
|
|
7096
7856
|
question: `opencode requests permission to run ${tool}${cmd}`,
|
|
7097
7857
|
kind: tool
|
|
7098
7858
|
});
|
|
7859
|
+
} else if (this.deps.ingest) {
|
|
7860
|
+
this.deps.ingest(json, taskId);
|
|
7099
7861
|
}
|
|
7100
7862
|
} else if (json?.type === "permission.replied") {
|
|
7101
7863
|
this.pendingPermByTask.delete(taskId);
|
|
7864
|
+
this.deps.ingest?.(json, taskId);
|
|
7865
|
+
} else if (!IGNORED_FRAME.test(json?.type ?? "")) {
|
|
7866
|
+
this.deps.ingest?.(json, taskId);
|
|
7102
7867
|
}
|
|
7103
7868
|
}
|
|
7869
|
+
/** Test seam: exercise handleLine without an SSE stream. */
|
|
7870
|
+
handleLineForTest(rawLine, taskId) {
|
|
7871
|
+
this.handleLine(taskId, rawLine);
|
|
7872
|
+
}
|
|
7873
|
+
/** Test seam: read pendingPermByTask without exposing it publicly. */
|
|
7874
|
+
pendingPermForTest(taskId) {
|
|
7875
|
+
return this.pendingPermByTask.get(taskId);
|
|
7876
|
+
}
|
|
7104
7877
|
};
|
|
7105
7878
|
|
|
7106
7879
|
// packages/agents/dist/interactive/claude.js
|
|
7107
7880
|
import { execSync as execSync6 } from "child_process";
|
|
7108
|
-
import { readFileSync as
|
|
7881
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7109
7882
|
import { homedir as homedir8 } from "os";
|
|
7110
|
-
import { join as
|
|
7883
|
+
import { join as join15 } from "path";
|
|
7111
7884
|
var nextAskUserQuestionRequestId = Date.now();
|
|
7112
7885
|
|
|
7113
7886
|
// packages/agents/dist/headless/types.js
|
|
@@ -7271,14 +8044,14 @@ function runHeadless(opts) {
|
|
|
7271
8044
|
}
|
|
7272
8045
|
|
|
7273
8046
|
// packages/agents/dist/claude/peer-registry-source.js
|
|
7274
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
7275
|
-
import { join as
|
|
8047
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
8048
|
+
import { join as join17 } from "path";
|
|
7276
8049
|
|
|
7277
8050
|
// packages/agents/dist/claude/registry.js
|
|
7278
8051
|
import fs14 from "fs";
|
|
7279
|
-
import { join as
|
|
8052
|
+
import { join as join16 } from "path";
|
|
7280
8053
|
import { homedir as homedir9 } from "os";
|
|
7281
|
-
var CLAUDE_SESSIONS_DIR =
|
|
8054
|
+
var CLAUDE_SESSIONS_DIR = join16(homedir9(), ".claude", "sessions");
|
|
7282
8055
|
var PID_JSON = /^(\d+)\.json$/;
|
|
7283
8056
|
function parseRegistryDir(files, readFile7) {
|
|
7284
8057
|
const out = [];
|
|
@@ -7324,7 +8097,7 @@ function readClaudeStatusBySocketPath(socketPath) {
|
|
|
7324
8097
|
} catch {
|
|
7325
8098
|
return void 0;
|
|
7326
8099
|
}
|
|
7327
|
-
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(
|
|
8100
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join16(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
7328
8101
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
7329
8102
|
if (!entry)
|
|
7330
8103
|
return void 0;
|
|
@@ -7356,7 +8129,7 @@ var ClaudePeerRegistrySource = class {
|
|
|
7356
8129
|
log;
|
|
7357
8130
|
constructor(o = {}) {
|
|
7358
8131
|
this.readdir = o.readdir ?? (() => readdirSync4(CLAUDE_SESSIONS_DIR));
|
|
7359
|
-
this.readFile = o.readFile ?? ((n) =>
|
|
8132
|
+
this.readFile = o.readFile ?? ((n) => readFileSync11(join17(CLAUDE_SESSIONS_DIR, n), "utf8"));
|
|
7360
8133
|
this.isAlive = o.isAlive ?? defaultIsAlive;
|
|
7361
8134
|
this.now = o.now ?? Date.now;
|
|
7362
8135
|
this.pollMs = o.pollMs ?? 2e3;
|
|
@@ -7668,70 +8441,41 @@ var ClaudeReceiptListener = class {
|
|
|
7668
8441
|
}
|
|
7669
8442
|
};
|
|
7670
8443
|
|
|
7671
|
-
// packages/agents/dist/opencode/
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
return;
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
return;
|
|
7707
|
-
this.cache.set(snap.taskId, snap);
|
|
7708
|
-
this.deps.report(snap);
|
|
7709
|
-
}
|
|
7710
|
-
};
|
|
7711
|
-
function toSnapshot2(ev) {
|
|
7712
|
-
const now = Date.now();
|
|
7713
|
-
switch (ev.type) {
|
|
7714
|
-
// A permission was answered on the bus and the turn resumed.
|
|
7715
|
-
case "task.started":
|
|
7716
|
-
return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
|
|
7717
|
-
// session.idle — the turn finished. Liveness, NOT completion (anti-#2576).
|
|
7718
|
-
case "task.turn.completed":
|
|
7719
|
-
return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
|
|
7720
|
-
// permission.asked — opencode STATES it is gated. No guessing from pixels.
|
|
7721
|
-
case "task.approval.requested":
|
|
7722
|
-
return {
|
|
7723
|
-
taskId: ev.id,
|
|
7724
|
-
state: "needsInput",
|
|
7725
|
-
alive: true,
|
|
7726
|
-
origin: "agent",
|
|
7727
|
-
at: now,
|
|
7728
|
-
detail: { note: ev.question, reason: ev.kind }
|
|
7729
|
-
};
|
|
7730
|
-
// Terminal (task.done/blocked/cancelled) and notify-only events are ignored:
|
|
7731
|
-
// terminal state comes exclusively from `squadrant crew signal`.
|
|
7732
|
-
default:
|
|
7733
|
-
return null;
|
|
7734
|
-
}
|
|
8444
|
+
// packages/agents/dist/opencode/fact-adapter.js
|
|
8445
|
+
function createOpencodeFactAdapter(deps) {
|
|
8446
|
+
return {
|
|
8447
|
+
name: "opencode-sse",
|
|
8448
|
+
origin: "agent",
|
|
8449
|
+
translate(raw) {
|
|
8450
|
+
const f = typeof raw === "object" && raw !== null ? raw : {};
|
|
8451
|
+
const type = typeof f.type === "string" ? f.type : void 0;
|
|
8452
|
+
if (type === void 0)
|
|
8453
|
+
return [{ kind: "unknown", name: "non-object" }];
|
|
8454
|
+
const p = f.properties ?? {};
|
|
8455
|
+
if (type === "session.idle") {
|
|
8456
|
+
return [{
|
|
8457
|
+
kind: "turn.ended",
|
|
8458
|
+
turnId: typeof p.sessionID === "string" ? p.sessionID : void 0
|
|
8459
|
+
}];
|
|
8460
|
+
}
|
|
8461
|
+
if (type === "permission.asked") {
|
|
8462
|
+
if (typeof p.id !== "string" || typeof p.sessionID !== "string") {
|
|
8463
|
+
return [{ kind: "unknown", name: "permission.asked:incomplete" }];
|
|
8464
|
+
}
|
|
8465
|
+
const tool = typeof p.permission === "string" ? p.permission : "a tool";
|
|
8466
|
+
const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
|
|
8467
|
+
return [{
|
|
8468
|
+
kind: "permission.requested",
|
|
8469
|
+
question: `opencode requests permission to run ${tool}${cmd}`,
|
|
8470
|
+
requestId: deps.nextRequestId(),
|
|
8471
|
+
tool
|
|
8472
|
+
}];
|
|
8473
|
+
}
|
|
8474
|
+
if (type === "permission.replied")
|
|
8475
|
+
return [{ kind: "activity" }];
|
|
8476
|
+
return [{ kind: "unknown", name: type }];
|
|
8477
|
+
}
|
|
8478
|
+
};
|
|
7735
8479
|
}
|
|
7736
8480
|
|
|
7737
8481
|
// packages/workspaces/dist/runtimes/cmux.js
|
|
@@ -8133,7 +8877,10 @@ function createCmuxDriver() {
|
|
|
8133
8877
|
let screen = "";
|
|
8134
8878
|
try {
|
|
8135
8879
|
screen = await cmux(["read-screen", "--workspace", ws, "--surface", sf]);
|
|
8136
|
-
} catch {
|
|
8880
|
+
} catch (e) {
|
|
8881
|
+
process.stderr.write(`[squadrant] read-screen failed for ${ws}/${sf}: ${e.message}
|
|
8882
|
+
`);
|
|
8883
|
+
throw new DeferDelivery(null, "probe-failed");
|
|
8137
8884
|
}
|
|
8138
8885
|
const draft = parseDraftFromScreen(screen);
|
|
8139
8886
|
if (draft === null)
|
|
@@ -8333,7 +9080,7 @@ var NotifierRegistry = class {
|
|
|
8333
9080
|
|
|
8334
9081
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
8335
9082
|
import fs15 from "fs/promises";
|
|
8336
|
-
import { existsSync as
|
|
9083
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8337
9084
|
import path19 from "path";
|
|
8338
9085
|
|
|
8339
9086
|
// packages/workspaces/dist/workspaces/registry.js
|
|
@@ -8449,6 +9196,16 @@ var CmuxEventsBridge = class {
|
|
|
8449
9196
|
}
|
|
8450
9197
|
if (f?.type !== "event" || f.category !== "agent")
|
|
8451
9198
|
return;
|
|
9199
|
+
if (f.name === "agent.hook.PostToolUse") {
|
|
9200
|
+
const p2 = f.payload ?? {};
|
|
9201
|
+
if (p2.phase === "received")
|
|
9202
|
+
return;
|
|
9203
|
+
const rec2 = this.deps.resolve({ cwd: p2.cwd, source: p2._source ?? f.source, sessionId: p2.session_id });
|
|
9204
|
+
if (!rec2)
|
|
9205
|
+
return;
|
|
9206
|
+
this.deps.emit({ type: "task.progress", id: rec2.id, note: f.name });
|
|
9207
|
+
return;
|
|
9208
|
+
}
|
|
8452
9209
|
const runState = f.name ? deriveRunState(f.name) : null;
|
|
8453
9210
|
if (!runState)
|
|
8454
9211
|
return;
|
|
@@ -8477,8 +9234,8 @@ var CmuxEventsBridge = class {
|
|
|
8477
9234
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
8478
9235
|
init_dist2();
|
|
8479
9236
|
init_dist();
|
|
8480
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8481
|
-
import { join as
|
|
9237
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
9238
|
+
import { join as join18 } from "path";
|
|
8482
9239
|
import { homedir as homedir10 } from "os";
|
|
8483
9240
|
|
|
8484
9241
|
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
@@ -8576,7 +9333,7 @@ function readLivenessSnapshot(files, readFile7, projects, argvRecovery = {}) {
|
|
|
8576
9333
|
|
|
8577
9334
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
8578
9335
|
async function readCmuxLiveness() {
|
|
8579
|
-
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
9336
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join18(homedir10(), ".cmuxterm");
|
|
8580
9337
|
const projects = loadConfig().projects;
|
|
8581
9338
|
let files;
|
|
8582
9339
|
try {
|
|
@@ -8584,7 +9341,7 @@ async function readCmuxLiveness() {
|
|
|
8584
9341
|
} catch (e) {
|
|
8585
9342
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
8586
9343
|
}
|
|
8587
|
-
return readLivenessSnapshot(files, (f) =>
|
|
9344
|
+
return readLivenessSnapshot(files, (f) => readFileSync12(join18(dir, f), "utf-8"), projects);
|
|
8588
9345
|
}
|
|
8589
9346
|
var DaemonCmux = class {
|
|
8590
9347
|
driver;
|
|
@@ -8647,9 +9404,9 @@ var DaemonCmux = class {
|
|
|
8647
9404
|
};
|
|
8648
9405
|
|
|
8649
9406
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
8650
|
-
import { join as
|
|
9407
|
+
import { join as join19 } from "path";
|
|
8651
9408
|
import { homedir as homedir11 } from "os";
|
|
8652
|
-
import { watch, readdirSync as readdirSync6, readFileSync as
|
|
9409
|
+
import { watch, readdirSync as readdirSync6, readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
8653
9410
|
var CmuxStoreSource = class {
|
|
8654
9411
|
name = "cmux-store";
|
|
8655
9412
|
stateDir;
|
|
@@ -8670,12 +9427,12 @@ var CmuxStoreSource = class {
|
|
|
8670
9427
|
active = false;
|
|
8671
9428
|
lastError = null;
|
|
8672
9429
|
constructor(opts = {}) {
|
|
8673
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
9430
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join19(homedir11(), ".cmuxterm");
|
|
8674
9431
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
8675
9432
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
8676
9433
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
8677
9434
|
this.readFile = opts.readFile ?? defaultReadFile;
|
|
8678
|
-
this.fileExists = opts.fileExists ??
|
|
9435
|
+
this.fileExists = opts.fileExists ?? existsSync12;
|
|
8679
9436
|
this.watchDir = opts.watchDir ?? defaultWatchDir;
|
|
8680
9437
|
this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
|
|
8681
9438
|
this.cancelTimer = opts.cancelTimer ?? clearTimeout;
|
|
@@ -8732,7 +9489,7 @@ var CmuxStoreSource = class {
|
|
|
8732
9489
|
}
|
|
8733
9490
|
scanFile(filename) {
|
|
8734
9491
|
const deps = this.deps;
|
|
8735
|
-
const filePath =
|
|
9492
|
+
const filePath = join19(this.stateDir, filename);
|
|
8736
9493
|
const lockPath = `${filePath}.lock`;
|
|
8737
9494
|
if (this.fileExists(lockPath)) {
|
|
8738
9495
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -8805,7 +9562,7 @@ function defaultListFiles(dir) {
|
|
|
8805
9562
|
}
|
|
8806
9563
|
function defaultReadFile(path21) {
|
|
8807
9564
|
try {
|
|
8808
|
-
return
|
|
9565
|
+
return readFileSync13(path21, "utf-8");
|
|
8809
9566
|
} catch {
|
|
8810
9567
|
return void 0;
|
|
8811
9568
|
}
|
|
@@ -8820,9 +9577,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
8820
9577
|
}
|
|
8821
9578
|
|
|
8822
9579
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
8823
|
-
import { join as
|
|
9580
|
+
import { join as join20 } from "path";
|
|
8824
9581
|
import { homedir as homedir12 } from "os";
|
|
8825
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
9582
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
8826
9583
|
var CLAUDE_HOOK_EVENTS = [
|
|
8827
9584
|
["SessionStart", "session-start"],
|
|
8828
9585
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -8834,7 +9591,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
8834
9591
|
];
|
|
8835
9592
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
8836
9593
|
function installClaudeHooks(opts = {}) {
|
|
8837
|
-
const settingsPath = opts.settingsPath ??
|
|
9594
|
+
const settingsPath = opts.settingsPath ?? join20(homedir12(), ".claude", "settings.json");
|
|
8838
9595
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
8839
9596
|
const readFile7 = opts.readFile ?? defaultReadFile2;
|
|
8840
9597
|
const writeFile6 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -9004,14 +9761,14 @@ function extractDetail(sub, payload) {
|
|
|
9004
9761
|
}
|
|
9005
9762
|
function defaultReadFile2(path21) {
|
|
9006
9763
|
try {
|
|
9007
|
-
return
|
|
9764
|
+
return readFileSync14(path21, "utf-8");
|
|
9008
9765
|
} catch {
|
|
9009
9766
|
return void 0;
|
|
9010
9767
|
}
|
|
9011
9768
|
}
|
|
9012
9769
|
function defaultWriteFile(path21, content) {
|
|
9013
9770
|
mkdirSync6(path21.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
9014
|
-
|
|
9771
|
+
writeFileSync9(path21, content, "utf-8");
|
|
9015
9772
|
}
|
|
9016
9773
|
|
|
9017
9774
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -9139,27 +9896,56 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
9139
9896
|
|
|
9140
9897
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
9141
9898
|
import { createServer as createServer3, connect as netConnect } from "net";
|
|
9142
|
-
import
|
|
9899
|
+
import fs16 from "fs";
|
|
9900
|
+
import { join as join21 } from "path";
|
|
9143
9901
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
9144
9902
|
import chalk2 from "chalk";
|
|
9145
9903
|
init_dist2();
|
|
9146
9904
|
var shared;
|
|
9905
|
+
var registryEntryPath = () => join21(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
9906
|
+
function unregisterSenderIdentity() {
|
|
9907
|
+
try {
|
|
9908
|
+
fs16.unlinkSync(registryEntryPath());
|
|
9909
|
+
} catch {
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9912
|
+
function registerSenderIdentity(socketPath) {
|
|
9913
|
+
try {
|
|
9914
|
+
unregisterSenderIdentity();
|
|
9915
|
+
fs16.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
9916
|
+
fs16.writeFileSync(
|
|
9917
|
+
registryEntryPath(),
|
|
9918
|
+
JSON.stringify({
|
|
9919
|
+
pid: process.pid,
|
|
9920
|
+
sessionId: randomUUID5(),
|
|
9921
|
+
name: "squadrantd",
|
|
9922
|
+
messagingSocketPath: socketPath,
|
|
9923
|
+
kind: "daemon",
|
|
9924
|
+
peerProtocol: 1
|
|
9925
|
+
})
|
|
9926
|
+
);
|
|
9927
|
+
process.on("exit", unregisterSenderIdentity);
|
|
9928
|
+
} catch {
|
|
9929
|
+
}
|
|
9930
|
+
}
|
|
9147
9931
|
async function sharedReceiptListener() {
|
|
9148
9932
|
if (shared) return shared;
|
|
9933
|
+
const socketPath = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
|
|
9149
9934
|
const listener = new ClaudeReceiptListener({
|
|
9150
|
-
socketPath
|
|
9935
|
+
socketPath,
|
|
9151
9936
|
createServer: (h) => createServer3(h),
|
|
9152
9937
|
// A UDS path is not cleaned up when a process is killed, so our own leftover
|
|
9153
9938
|
// must never be the reason we refuse to start.
|
|
9154
9939
|
unlinkStale: (p) => {
|
|
9155
9940
|
try {
|
|
9156
|
-
|
|
9941
|
+
fs16.unlinkSync(p);
|
|
9157
9942
|
} catch {
|
|
9158
9943
|
}
|
|
9159
9944
|
},
|
|
9160
9945
|
log: (m) => console.error(chalk2.dim(m))
|
|
9161
9946
|
});
|
|
9162
9947
|
await listener.start();
|
|
9948
|
+
registerSenderIdentity(socketPath);
|
|
9163
9949
|
shared = listener;
|
|
9164
9950
|
return shared;
|
|
9165
9951
|
}
|
|
@@ -9207,11 +9993,11 @@ async function buildCaptainChannelWithRetry(opts = {}) {
|
|
|
9207
9993
|
|
|
9208
9994
|
// packages/cli/src/squadrantd.ts
|
|
9209
9995
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
9210
|
-
var CLI_BIN =
|
|
9996
|
+
var CLI_BIN = join22(dirname4(SELF_PATH2), "index.js");
|
|
9211
9997
|
function readPkgVersion() {
|
|
9212
9998
|
try {
|
|
9213
|
-
const pkgPath =
|
|
9214
|
-
return JSON.parse(
|
|
9999
|
+
const pkgPath = join22(dirname4(SELF_PATH2), "..", "package.json");
|
|
10000
|
+
return JSON.parse(readFileSync15(pkgPath, "utf-8")).version ?? "unknown";
|
|
9215
10001
|
} catch {
|
|
9216
10002
|
return "unknown";
|
|
9217
10003
|
}
|
|
@@ -9304,7 +10090,37 @@ function startSquadrantd(opts = {}) {
|
|
|
9304
10090
|
codexAppServerSource.observe(ev);
|
|
9305
10091
|
}
|
|
9306
10092
|
});
|
|
9307
|
-
const
|
|
10093
|
+
const violationLogState = /* @__PURE__ */ new Map();
|
|
10094
|
+
const VIOLATION_LOG_SUMMARY_MS = 6e4;
|
|
10095
|
+
const onEventsViolation = (v2) => {
|
|
10096
|
+
const key = `${v2.taskId}:${v2.code}`;
|
|
10097
|
+
const prev = violationLogState.get(key);
|
|
10098
|
+
if (!prev) {
|
|
10099
|
+
violationLogState.set(key, { count: 1, loggedAt: Date.now() });
|
|
10100
|
+
log(`[events] ${v2.code} ${v2.taskId}: ${v2.message}`);
|
|
10101
|
+
return;
|
|
10102
|
+
}
|
|
10103
|
+
prev.count++;
|
|
10104
|
+
const now = Date.now();
|
|
10105
|
+
if (now - prev.loggedAt > VIOLATION_LOG_SUMMARY_MS) {
|
|
10106
|
+
log(`[events] ${v2.code} ${v2.taskId}: seen ${prev.count}x since last log (latest: ${v2.message})`);
|
|
10107
|
+
prev.count = 0;
|
|
10108
|
+
prev.loggedAt = now;
|
|
10109
|
+
}
|
|
10110
|
+
};
|
|
10111
|
+
const opencodeRequestIds = { n: 1 };
|
|
10112
|
+
const eventsSource = createEventsSource({
|
|
10113
|
+
adapters: [createOpencodeFactAdapter({ nextRequestId: () => opencodeRequestIds.n++ })],
|
|
10114
|
+
emit: (ev) => {
|
|
10115
|
+
const found = store.listAll().find((r) => r.id === ev.id);
|
|
10116
|
+
if (!found) return;
|
|
10117
|
+
void ctx.d.handle({ kind: "event", project: found.project, event: ev });
|
|
10118
|
+
if (ev.type === "task.approval.requested")
|
|
10119
|
+
ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
|
|
10120
|
+
},
|
|
10121
|
+
onViolation: onEventsViolation,
|
|
10122
|
+
check: { stallBudgetMs: 6e4, disagreeWindowMs: 5e3 }
|
|
10123
|
+
});
|
|
9308
10124
|
const opencodeBridge = opts.opencodeBridge ?? new OpencodeSseBridge({
|
|
9309
10125
|
emit: (ev) => {
|
|
9310
10126
|
const found = store.listAll().find((r) => r.id === ev.id);
|
|
@@ -9312,8 +10128,8 @@ function startSquadrantd(opts = {}) {
|
|
|
9312
10128
|
void ctx.d.handle({ kind: "event", project: found.project, event: ev });
|
|
9313
10129
|
if (ev.type === "task.approval.requested")
|
|
9314
10130
|
ctx.schedulePromotion(ev.id, ev.requestId, "approval", ev.question);
|
|
9315
|
-
opencodeControlSource.observe(ev);
|
|
9316
10131
|
},
|
|
10132
|
+
ingest: (raw, taskId) => eventsSource.ingest("opencode-sse", raw, { taskId }),
|
|
9317
10133
|
log
|
|
9318
10134
|
});
|
|
9319
10135
|
const cmuxEventsBridge = opts.cmuxEventsBridge ?? new CmuxEventsBridge({
|
|
@@ -9328,7 +10144,7 @@ function startSquadrantd(opts = {}) {
|
|
|
9328
10144
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
9329
10145
|
);
|
|
9330
10146
|
},
|
|
9331
|
-
cursorFile:
|
|
10147
|
+
cursorFile: join22(stateRoot, "cmux-events.seq"),
|
|
9332
10148
|
log
|
|
9333
10149
|
});
|
|
9334
10150
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -9338,11 +10154,11 @@ function startSquadrantd(opts = {}) {
|
|
|
9338
10154
|
ctx.cmuxEventsBridge = cmuxEventsBridge;
|
|
9339
10155
|
const claudePeerRegistrySource = new ClaudePeerRegistrySource({ log });
|
|
9340
10156
|
ctx.lifecycleSources = [
|
|
10157
|
+
eventsSource,
|
|
9341
10158
|
cmuxStoreSource,
|
|
9342
10159
|
nativeHookSource,
|
|
9343
10160
|
codexAppServerSource,
|
|
9344
|
-
claudePeerRegistrySource
|
|
9345
|
-
opencodeControlSource
|
|
10161
|
+
claudePeerRegistrySource
|
|
9346
10162
|
];
|
|
9347
10163
|
const tgCfg = loadConfig().telegram;
|
|
9348
10164
|
ctx.telegramBridge = opts.telegramBridge ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(
|
|
@@ -9398,6 +10214,37 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
9398
10214
|
}
|
|
9399
10215
|
});
|
|
9400
10216
|
const h = startDaemon(ctx, { ...opts, launchHeadless }, PKG_VERSION);
|
|
10217
|
+
const startEventsLifecycleSource = () => {
|
|
10218
|
+
let eventsTaskIndex;
|
|
10219
|
+
let eventsTaskIndexAt = 0;
|
|
10220
|
+
const EVENTS_TASK_INDEX_TTL_MS = 500;
|
|
10221
|
+
const eventsSourceDeps = {
|
|
10222
|
+
resolve: (hint) => {
|
|
10223
|
+
if (!hint.taskId) return void 0;
|
|
10224
|
+
const now = Date.now();
|
|
10225
|
+
if (!eventsTaskIndex || now - eventsTaskIndexAt > EVENTS_TASK_INDEX_TTL_MS) {
|
|
10226
|
+
eventsTaskIndex = /* @__PURE__ */ new Map();
|
|
10227
|
+
for (const r of store.listAll()) {
|
|
10228
|
+
if (!TERMINAL_STATES.has(r.state)) eventsTaskIndex.set(r.id, { id: r.id });
|
|
10229
|
+
}
|
|
10230
|
+
eventsTaskIndexAt = now;
|
|
10231
|
+
}
|
|
10232
|
+
const cached = eventsTaskIndex.get(hint.taskId);
|
|
10233
|
+
if (cached) return cached;
|
|
10234
|
+
return store.listAll().find(
|
|
10235
|
+
(r) => r.id === hint.taskId && !TERMINAL_STATES.has(r.state)
|
|
10236
|
+
);
|
|
10237
|
+
},
|
|
10238
|
+
report: () => {
|
|
10239
|
+
},
|
|
10240
|
+
log
|
|
10241
|
+
};
|
|
10242
|
+
try {
|
|
10243
|
+
eventsSource.start(eventsSourceDeps);
|
|
10244
|
+
} catch (e) {
|
|
10245
|
+
log(`events source start failed: ${e.message}`);
|
|
10246
|
+
}
|
|
10247
|
+
};
|
|
9401
10248
|
if (!process.env.VITEST) {
|
|
9402
10249
|
const prevSnaps = /* @__PURE__ */ new Map();
|
|
9403
10250
|
const storeDeps = {
|
|
@@ -9524,19 +10371,9 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
9524
10371
|
} catch (e) {
|
|
9525
10372
|
log(`claude peer registry source start failed: ${e.message}`);
|
|
9526
10373
|
}
|
|
9527
|
-
|
|
9528
|
-
resolve: () => void 0,
|
|
9529
|
-
report: () => {
|
|
9530
|
-
},
|
|
9531
|
-
// read-only slice: caching is internal to the source
|
|
9532
|
-
log
|
|
9533
|
-
};
|
|
9534
|
-
try {
|
|
9535
|
-
opencodeControlSource.start(opencodeSourceDeps);
|
|
9536
|
-
} catch (e) {
|
|
9537
|
-
log(`opencode control source start failed: ${e.message}`);
|
|
9538
|
-
}
|
|
10374
|
+
startEventsLifecycleSource();
|
|
9539
10375
|
}
|
|
10376
|
+
if (opts.forceStartEventsSource) startEventsLifecycleSource();
|
|
9540
10377
|
if (!process.env.VITEST) {
|
|
9541
10378
|
try {
|
|
9542
10379
|
const buildMtimeMs = statSync4(SELF_PATH2).mtimeMs;
|
|
@@ -9573,7 +10410,7 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
|
|
|
9573
10410
|
} catch {
|
|
9574
10411
|
}
|
|
9575
10412
|
try {
|
|
9576
|
-
|
|
10413
|
+
eventsSource.stop();
|
|
9577
10414
|
} catch {
|
|
9578
10415
|
}
|
|
9579
10416
|
return origStop(reason);
|
|
@@ -9584,9 +10421,20 @@ function logCrashMarker(kind, err) {
|
|
|
9584
10421
|
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
9585
10422
|
process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
|
|
9586
10423
|
`);
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
10424
|
+
const stateRoot = join22(homedir13(), ".config", "squadrant", "state");
|
|
10425
|
+
writeExitMarker(stateRoot, {
|
|
10426
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10427
|
+
pid: process.pid,
|
|
10428
|
+
reason: kind,
|
|
10429
|
+
ppid: process.ppid,
|
|
10430
|
+
uptimeMs: Math.round(process.uptime() * 1e3),
|
|
10431
|
+
inFlightDelivery: null
|
|
10432
|
+
// crash path has no access to the delivery loop's live state
|
|
10433
|
+
}, (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
10434
|
+
`));
|
|
10435
|
+
}
|
|
10436
|
+
function isMonorepoCheckout(scriptPath, dirExists = existsSync13) {
|
|
10437
|
+
return dirExists(join22(dirname4(resolve3(scriptPath)), "..", "packages"));
|
|
9590
10438
|
}
|
|
9591
10439
|
function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
9592
10440
|
try {
|
|
@@ -9595,7 +10443,7 @@ function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
|
9595
10443
|
return void 0;
|
|
9596
10444
|
}
|
|
9597
10445
|
}) {
|
|
9598
|
-
const dotGit =
|
|
10446
|
+
const dotGit = join22(dirname4(resolve3(scriptPath)), "..", ".git");
|
|
9599
10447
|
return statFile(dotGit)?.isFile === true;
|
|
9600
10448
|
}
|
|
9601
10449
|
if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|