squadrant 0.19.0 → 0.19.1
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 +859 -242
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +582 -159
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +4 -3
- 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
|
@@ -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",
|
|
@@ -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,47 @@ 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 ?? 5;
|
|
2498
|
+
const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
|
|
2499
|
+
const pidBefore = getDaemonPid(target);
|
|
2500
|
+
for (let i = 0; i < kickstartRetries; i++) {
|
|
2501
|
+
try {
|
|
2502
|
+
execFileSync3("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
|
|
2503
|
+
break;
|
|
2504
|
+
} catch (e) {
|
|
2505
|
+
if (i === kickstartRetries - 1)
|
|
2506
|
+
throw e;
|
|
2507
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, kickstartRetryDelayMs);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
let pidAfter = null;
|
|
2511
|
+
for (let i = 0; i < pollAttempts; i++) {
|
|
2512
|
+
pidAfter = getDaemonPid(target);
|
|
2513
|
+
if (pidAfter !== null && pidAfter !== pidBefore)
|
|
2514
|
+
break;
|
|
2515
|
+
if (i < pollAttempts - 1) {
|
|
2516
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, pollDelayMs);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return { target, pidBefore, pidAfter, restarted: pidAfter !== null && pidAfter !== pidBefore };
|
|
2520
|
+
}
|
|
2445
2521
|
function isOperatorInitiatedCommand(topLevelArg) {
|
|
2446
2522
|
return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
|
|
2447
2523
|
}
|
|
@@ -2483,11 +2559,18 @@ function printForeignInstallError(foreign) {
|
|
|
2483
2559
|
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
2560
|
`;
|
|
2485
2561
|
}
|
|
2486
|
-
function reregisterDaemon(nodeBin = process.execPath) {
|
|
2487
|
-
if (!tryAcquireDaemonLock())
|
|
2488
|
-
|
|
2562
|
+
function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
2563
|
+
if (!tryAcquireDaemonLock()) {
|
|
2564
|
+
throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
|
|
2565
|
+
}
|
|
2489
2566
|
try {
|
|
2490
|
-
|
|
2567
|
+
const drift = computeDaemonDrift(nodeBin);
|
|
2568
|
+
reconcilePlistAndService(drift);
|
|
2569
|
+
const result = forceKickstartAndVerify(drift.target, kickstartOpts);
|
|
2570
|
+
if (!result.restarted) {
|
|
2571
|
+
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`);
|
|
2572
|
+
}
|
|
2573
|
+
return result;
|
|
2491
2574
|
} finally {
|
|
2492
2575
|
releaseDaemonLock();
|
|
2493
2576
|
}
|
|
@@ -2875,6 +2958,10 @@ function detectTrailingQuestion(text) {
|
|
|
2875
2958
|
return lastLine;
|
|
2876
2959
|
return null;
|
|
2877
2960
|
}
|
|
2961
|
+
function isQuotedLine(raw) {
|
|
2962
|
+
const noAnsi = raw.replace(/\[[0-9;]*m/g, "");
|
|
2963
|
+
return QUOTED_PREFIX_RE.test(noAnsi);
|
|
2964
|
+
}
|
|
2878
2965
|
function stripChrome(raw) {
|
|
2879
2966
|
let line = raw.replace(/\[[0-9;]*m/g, "");
|
|
2880
2967
|
line = line.replace(/^[\s│┃▏▕|]+/, "").replace(/[\s│┃▏▕|]+$/, "");
|
|
@@ -2933,8 +3020,11 @@ function classifyPaneTail(tail) {
|
|
|
2933
3020
|
if (q)
|
|
2934
3021
|
return { kind: "question", text: q };
|
|
2935
3022
|
let errLine = null;
|
|
2936
|
-
for (
|
|
2937
|
-
|
|
3023
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
3024
|
+
const c = cleaned[i];
|
|
3025
|
+
if (c == null || isQuotedLine(raw[i]))
|
|
3026
|
+
continue;
|
|
3027
|
+
if (ERROR_BANNER_RE.some((re) => re.test(c)))
|
|
2938
3028
|
errLine = c;
|
|
2939
3029
|
}
|
|
2940
3030
|
if (errLine)
|
|
@@ -2977,6 +3067,22 @@ function createInteractiveProbe(deps) {
|
|
|
2977
3067
|
const verdict = classifyPaneTail(tail);
|
|
2978
3068
|
if (!verdict)
|
|
2979
3069
|
continue;
|
|
3070
|
+
if (verdict.kind === "error") {
|
|
3071
|
+
const alive = deps.checkAlive ? await deps.checkAlive(rec) : "unknown";
|
|
3072
|
+
if (alive !== "gone") {
|
|
3073
|
+
const message = `CREW WARN ${rec.name}: pane shows an error string \u2014 crew still ${alive}, not terminalized (pane-detected): ${verdict.text}`;
|
|
3074
|
+
deps.log(`probe -> ${message}`);
|
|
3075
|
+
if (deps.notify) {
|
|
3076
|
+
const warnEvent = { type: "task.warn", id: rec.id, message };
|
|
3077
|
+
try {
|
|
3078
|
+
await deps.notify({ project: rec.project, message, record: rec, event: warnEvent });
|
|
3079
|
+
} catch (e) {
|
|
3080
|
+
deps.log(`probe notify failed for ${rec.id}: ${e.message}`);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
2980
3086
|
const event = verdict.kind === "error" ? {
|
|
2981
3087
|
type: "task.failed",
|
|
2982
3088
|
id: rec.id,
|
|
@@ -2998,7 +3104,7 @@ function createInteractiveProbe(deps) {
|
|
|
2998
3104
|
}
|
|
2999
3105
|
return { tick };
|
|
3000
3106
|
}
|
|
3001
|
-
var STALE_THRESHOLD_MS, PROBE_QUIET_MS, ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE;
|
|
3107
|
+
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
3108
|
var init_interactive_probe = __esm({
|
|
3003
3109
|
"packages/core/dist/daemon/interactive-probe.js"() {
|
|
3004
3110
|
STALE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
@@ -3014,6 +3120,7 @@ var init_interactive_probe = __esm({
|
|
|
3014
3120
|
PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
|
|
3015
3121
|
PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
|
|
3016
3122
|
STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
|
|
3123
|
+
QUOTED_PREFIX_RE = /^\s*(?:[┃│▏▕]|>|[+-]|\d+[\t:→])\s/;
|
|
3017
3124
|
}
|
|
3018
3125
|
});
|
|
3019
3126
|
|
|
@@ -3029,6 +3136,7 @@ function createProbes(ctx) {
|
|
|
3029
3136
|
};
|
|
3030
3137
|
function buildInteractiveProbe(deps) {
|
|
3031
3138
|
const directPaneReader = createDirectCrewPaneReader(deps.cmux, captainNameForProject);
|
|
3139
|
+
const checkAlive = createDirectSurfaceLivenessProbe(deps.cmux, captainNameForProject);
|
|
3032
3140
|
const probe = createInteractiveProbe({
|
|
3033
3141
|
project: "_all_",
|
|
3034
3142
|
listTasks: async () => store.listAll(),
|
|
@@ -3040,7 +3148,9 @@ function createProbes(ctx) {
|
|
|
3040
3148
|
}
|
|
3041
3149
|
},
|
|
3042
3150
|
now: () => Date.now(),
|
|
3043
|
-
log
|
|
3151
|
+
log,
|
|
3152
|
+
checkAlive,
|
|
3153
|
+
notify: ctx.notify
|
|
3044
3154
|
});
|
|
3045
3155
|
let probing = false;
|
|
3046
3156
|
return async () => {
|
|
@@ -3420,6 +3530,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3420
3530
|
const crewRole = config.defaults.roles?.crew;
|
|
3421
3531
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
3422
3532
|
const crewModel = input.model ?? route?.model ?? configModel;
|
|
3533
|
+
const crewThinking = input.thinking ?? config.defaults.roles?.crew?.thinking;
|
|
3423
3534
|
if (agentName !== "claude") {
|
|
3424
3535
|
deps.onModelResolved?.({ agentName, model: crewModel });
|
|
3425
3536
|
}
|
|
@@ -3455,7 +3566,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3455
3566
|
// crew apart from an unrelated session instead of an auto-derived cwd
|
|
3456
3567
|
// basename (only the claude driver reads this — other agents ignore it).
|
|
3457
3568
|
sessionName: crewSessionName(input.project, name),
|
|
3458
|
-
...crewModel ? { model: crewModel } : {}
|
|
3569
|
+
...crewModel ? { model: crewModel } : {},
|
|
3570
|
+
...crewThinking ? { thinking: crewThinking } : {}
|
|
3459
3571
|
});
|
|
3460
3572
|
const direction2 = input.direction ?? "tab";
|
|
3461
3573
|
const title2 = titleFor(input.project, name);
|
|
@@ -3463,7 +3575,13 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
3463
3575
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
3464
3576
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
3465
3577
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
3466
|
-
|
|
3578
|
+
let claudeFirstTurn = firstTurnTask;
|
|
3579
|
+
if (Buffer.byteLength(claudeFirstTurn, "utf8") > FIRST_TURN_INLINE_MAX_BYTES) {
|
|
3580
|
+
const spillFile = path9.join(os4.tmpdir(), `squadrant-task-${rec.id}.md`);
|
|
3581
|
+
fs9.writeFileSync(spillFile, claudeFirstTurn, "utf8");
|
|
3582
|
+
claudeFirstTurn = `Full task is at ${spillFile} \u2014 cat it and follow it exactly.`;
|
|
3583
|
+
}
|
|
3584
|
+
const claudeResult = await deps.sendFirstTurn(pane2, `${claudeFirstTurn}
|
|
3467
3585
|
|
|
3468
3586
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
3469
3587
|
if (!claudeResult.delivered) {
|
|
@@ -3563,7 +3681,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3563
3681
|
if (!crew) {
|
|
3564
3682
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
3565
3683
|
}
|
|
3566
|
-
const blockedByModalMessage = () => `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) \u2014 message NOT delivered, to avoid confirming its default option.
|
|
3684
|
+
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
3685
|
if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
|
|
3568
3686
|
throw new Error(blockedByModalMessage());
|
|
3569
3687
|
}
|
|
@@ -3577,10 +3695,12 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3577
3695
|
const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
|
|
3578
3696
|
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
3697
|
}
|
|
3698
|
+
let reopened = false;
|
|
3580
3699
|
try {
|
|
3581
3700
|
if (task) {
|
|
3582
3701
|
if (TERMINAL_STATES.has(task.state)) {
|
|
3583
3702
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
3703
|
+
reopened = true;
|
|
3584
3704
|
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
3585
3705
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
3586
3706
|
}
|
|
@@ -3606,7 +3726,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3606
3726
|
throw new Error(`Message to crew '${name}' is held: ${outcome.reason}. Resolve it in the crew's session, then re-send.`);
|
|
3607
3727
|
}
|
|
3608
3728
|
if (!fallsBackToPane(outcome)) {
|
|
3609
|
-
return;
|
|
3729
|
+
return { reopened };
|
|
3610
3730
|
}
|
|
3611
3731
|
}
|
|
3612
3732
|
if (mode === "shadow" && channel && task) {
|
|
@@ -3629,7 +3749,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3629
3749
|
if (!paneOk) {
|
|
3630
3750
|
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
3751
|
}
|
|
3632
|
-
return;
|
|
3752
|
+
return { reopened };
|
|
3633
3753
|
}
|
|
3634
3754
|
const { delivered, blockedByModal } = await deliver(crew, message);
|
|
3635
3755
|
if (blockedByModal) {
|
|
@@ -3638,6 +3758,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
3638
3758
|
if (!delivered) {
|
|
3639
3759
|
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
3760
|
}
|
|
3761
|
+
return { reopened };
|
|
3641
3762
|
}
|
|
3642
3763
|
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
3643
3764
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
@@ -3740,7 +3861,7 @@ async function runCrewList(project, runtime, workspaceId) {
|
|
|
3740
3861
|
surfaceId: c.surfaceId
|
|
3741
3862
|
}));
|
|
3742
3863
|
}
|
|
3743
|
-
var CC_SOCKS_DIR, TEMPLATES_DIR, STATE_ROOT, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
|
|
3864
|
+
var CC_SOCKS_DIR, FIRST_TURN_INLINE_MAX_BYTES, TEMPLATES_DIR, STATE_ROOT, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
|
|
3744
3865
|
var init_crew_spawn = __esm({
|
|
3745
3866
|
"packages/core/dist/crew-spawn.js"() {
|
|
3746
3867
|
init_control_channel();
|
|
@@ -3749,6 +3870,7 @@ var init_crew_spawn = __esm({
|
|
|
3749
3870
|
init_crew_protocol();
|
|
3750
3871
|
init_crew_lifecycle();
|
|
3751
3872
|
CC_SOCKS_DIR = "/tmp/cc-socks";
|
|
3873
|
+
FIRST_TURN_INLINE_MAX_BYTES = 1200;
|
|
3752
3874
|
TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
3753
3875
|
STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
|
|
3754
3876
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
@@ -3795,13 +3917,16 @@ var init_captain_channel = __esm({
|
|
|
3795
3917
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
3796
3918
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
3797
3919
|
}
|
|
3798
|
-
function reapOrphanedCrews(store, project) {
|
|
3920
|
+
async function reapOrphanedCrews(store, project, isSurfaceAlive) {
|
|
3799
3921
|
let reaped = 0;
|
|
3800
3922
|
for (const r of store.list(project)) {
|
|
3801
3923
|
if (TERMINAL_STATES.has(r.state))
|
|
3802
3924
|
continue;
|
|
3803
3925
|
if (r.mode !== "interactive")
|
|
3804
3926
|
continue;
|
|
3927
|
+
const liveness = await isSurfaceAlive(r);
|
|
3928
|
+
if (liveness !== "gone")
|
|
3929
|
+
continue;
|
|
3805
3930
|
store.put({ ...r, state: "cancelled", lastEvent: "captain-stopped" });
|
|
3806
3931
|
reaped++;
|
|
3807
3932
|
}
|
|
@@ -3855,10 +3980,13 @@ async function runLivenessTick(deps) {
|
|
|
3855
3980
|
const prev = deps.registry.get(project);
|
|
3856
3981
|
if (prev && prev.lastState === "start")
|
|
3857
3982
|
entry.startedAt = prev.startedAt;
|
|
3983
|
+
const prevState = deriveCaptainState(prev);
|
|
3858
3984
|
deps.registry.apply(entry);
|
|
3859
3985
|
if (winner.pid != null)
|
|
3860
3986
|
deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);
|
|
3861
|
-
|
|
3987
|
+
const updated = deps.registry.get(project);
|
|
3988
|
+
if (deriveCaptainState(updated) !== prevState)
|
|
3989
|
+
logEntry(deps.log, project, updated);
|
|
3862
3990
|
}
|
|
3863
3991
|
for (const e of deps.registry.all()) {
|
|
3864
3992
|
if (e.role !== "captain" || e.lastState !== "start" || seen.has(e.project))
|
|
@@ -3876,12 +4004,13 @@ async function runLivenessTick(deps) {
|
|
|
3876
4004
|
continue;
|
|
3877
4005
|
const state = deriveCaptainState(e);
|
|
3878
4006
|
if (state === "stopped" || state === "gone")
|
|
3879
|
-
deps.reap(e.project);
|
|
4007
|
+
await deps.reap(e.project);
|
|
3880
4008
|
}
|
|
3881
4009
|
}
|
|
3882
4010
|
}
|
|
3883
|
-
function createDelivery(ctx, daemonCmux) {
|
|
4011
|
+
function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
3884
4012
|
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
4013
|
+
const surfaceProbe = isSurfaceAlive ?? (async () => "unknown");
|
|
3885
4014
|
const notifyFault = ctx.notifyFault ?? (() => {
|
|
3886
4015
|
});
|
|
3887
4016
|
const defaultNotify = async (args) => {
|
|
@@ -3904,12 +4033,22 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3904
4033
|
}
|
|
3905
4034
|
};
|
|
3906
4035
|
if (!daemonCmux) {
|
|
3907
|
-
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0 };
|
|
4036
|
+
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0, inFlightDelivery: () => null };
|
|
3908
4037
|
}
|
|
3909
4038
|
const cmux2 = daemonCmux;
|
|
3910
4039
|
const cfg = loadConfig();
|
|
3911
4040
|
const deliveries = /* @__PURE__ */ new Map();
|
|
3912
4041
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
4042
|
+
const lastDeferred = /* @__PURE__ */ new Map();
|
|
4043
|
+
const inFlightDelivery = () => {
|
|
4044
|
+
let worst = null;
|
|
4045
|
+
for (const [project, v] of lastDeferred) {
|
|
4046
|
+
if (!worst || v.deferCount > worst.deferCount)
|
|
4047
|
+
worst = { project, ...v };
|
|
4048
|
+
}
|
|
4049
|
+
return worst;
|
|
4050
|
+
};
|
|
4051
|
+
const projectBackoff = /* @__PURE__ */ new Map();
|
|
3913
4052
|
const stuckNotified = /* @__PURE__ */ new Set();
|
|
3914
4053
|
const sessionStartMs = Date.now();
|
|
3915
4054
|
let delivering = false;
|
|
@@ -3920,8 +4059,8 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3920
4059
|
isPidAlive,
|
|
3921
4060
|
now: () => Date.now(),
|
|
3922
4061
|
log,
|
|
3923
|
-
reap: (project) => {
|
|
3924
|
-
const reaped = reapOrphanedCrews(store, project);
|
|
4062
|
+
reap: async (project) => {
|
|
4063
|
+
const reaped = await reapOrphanedCrews(store, project, surfaceProbe);
|
|
3925
4064
|
if (reaped > 0) {
|
|
3926
4065
|
const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;
|
|
3927
4066
|
log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);
|
|
@@ -3937,82 +4076,116 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
3937
4076
|
cfg.commandName
|
|
3938
4077
|
])];
|
|
3939
4078
|
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
|
-
|
|
4079
|
+
try {
|
|
4080
|
+
const backoff = projectBackoff.get(project);
|
|
4081
|
+
if (backoff && Date.now() < backoff.nextAttemptAt)
|
|
4082
|
+
continue;
|
|
4083
|
+
const projCfg = cfg.projects?.[project];
|
|
4084
|
+
const captainTitle = project === cfg.commandName ? cfg.commandName : projCfg?.captainName ?? `${project}-captain`;
|
|
4085
|
+
let surface = null;
|
|
4086
|
+
const resolveCaptainSurface = async () => {
|
|
4087
|
+
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
4088
|
+
if (!wsId)
|
|
4089
|
+
return injectedSurfaces[project] ?? null;
|
|
4090
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
4091
|
+
return discoverCaptainSurface(surfaces, captainTitle) ?? injectedSurfaces[project] ?? null;
|
|
4092
|
+
};
|
|
4093
|
+
surface = await resolveCaptainSurface();
|
|
4094
|
+
if (!surface)
|
|
4095
|
+
continue;
|
|
4096
|
+
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
4097
|
+
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
4098
|
+
let d = deliveries.get(project);
|
|
4099
|
+
if (!d) {
|
|
4100
|
+
d = new CaptainDelivery({
|
|
4101
|
+
maxDefers: cfg.delivery?.maxDeferDeliveries ?? 300,
|
|
4102
|
+
stableProbePolls: cfg.delivery?.stableProbePolls ?? 3
|
|
4103
|
+
});
|
|
4104
|
+
deliveries.set(project, d);
|
|
4105
|
+
}
|
|
4106
|
+
for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {
|
|
4107
|
+
if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {
|
|
4108
|
+
if (!TERMINAL_KINDS.has(entry.kind)) {
|
|
4109
|
+
const isExemptMessage = entry.kind === "captain.message" && entry.payload?.source !== "daemon";
|
|
4110
|
+
if (!isExemptMessage) {
|
|
4111
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);
|
|
4112
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4113
|
+
continue;
|
|
4114
|
+
}
|
|
4115
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);
|
|
4116
|
+
} else {
|
|
4117
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
const result = await d.deliver(entry, async (text, sendOpts) => {
|
|
4121
|
+
let handledByChannel = false;
|
|
4122
|
+
try {
|
|
4123
|
+
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
4124
|
+
const r = await deliverToCaptain(project, text, {
|
|
4125
|
+
channel: ctx.captainChannel,
|
|
4126
|
+
mode,
|
|
4127
|
+
log
|
|
4128
|
+
});
|
|
4129
|
+
handledByChannel = r.handled;
|
|
4130
|
+
} catch (e) {
|
|
4131
|
+
log(`captain-channel ${project}: threw, falling back to pane \u2014 ${e.message}`);
|
|
4132
|
+
}
|
|
4133
|
+
if (handledByChannel) {
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
try {
|
|
4137
|
+
return await cmux2.send(surface, text, sendOpts);
|
|
4138
|
+
} catch (e) {
|
|
4139
|
+
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
4140
|
+
throw e;
|
|
4141
|
+
const next = await resolveCaptainSurface();
|
|
4142
|
+
const same = next !== null && next.workspaceId === surface.workspaceId && next.surfaceId === surface.surfaceId;
|
|
4143
|
+
if (!next || same) {
|
|
4144
|
+
log(`delivery project=${project}: probe-failed but surface re-resolution found ${next ? "the same dead surface" : "no captain surface"} \u2014 deferring`);
|
|
4145
|
+
throw e;
|
|
4146
|
+
}
|
|
4147
|
+
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
4148
|
+
surface = next;
|
|
4149
|
+
return cmux2.send(next, text, sendOpts);
|
|
3970
4150
|
}
|
|
3971
|
-
|
|
4151
|
+
});
|
|
4152
|
+
if ("delivered" in result) {
|
|
4153
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
4154
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4155
|
+
lastDeferred.delete(project);
|
|
4156
|
+
projectBackoff.delete(project);
|
|
3972
4157
|
} else {
|
|
3973
|
-
|
|
4158
|
+
const { maxDeferCount, stuck: stuck2 } = d.stats();
|
|
4159
|
+
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4160
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4161
|
+
}
|
|
4162
|
+
lastDeferred.set(project, { seq: entry.seq, deferCount: maxDeferCount });
|
|
4163
|
+
if (stuck2) {
|
|
4164
|
+
const streak = (projectBackoff.get(project)?.streak ?? 0) + 1;
|
|
4165
|
+
const backoffMs = Math.min(6e4, 1e3 * 2 ** streak);
|
|
4166
|
+
projectBackoff.set(project, { nextAttemptAt: Date.now() + backoffMs, streak });
|
|
4167
|
+
}
|
|
4168
|
+
break;
|
|
3974
4169
|
}
|
|
3975
4170
|
}
|
|
3976
|
-
const
|
|
3977
|
-
|
|
4171
|
+
const stuck = d.stats().stuck;
|
|
4172
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
4173
|
+
stuckNotified.add(project);
|
|
4174
|
+
const { maxDeferCount, reason } = d.stats();
|
|
4175
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
4176
|
+
const text = STUCK_ALERT_TEXT[reason ?? "unknown"](maxDeferCount);
|
|
3978
4177
|
try {
|
|
3979
|
-
|
|
3980
|
-
const r = await deliverToCaptain(project, text, {
|
|
3981
|
-
channel: ctx.captainChannel,
|
|
3982
|
-
mode,
|
|
3983
|
-
log
|
|
3984
|
-
});
|
|
3985
|
-
handledByChannel = r.handled;
|
|
4178
|
+
await appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
3986
4179
|
} catch (e) {
|
|
3987
|
-
log(`
|
|
3988
|
-
}
|
|
3989
|
-
if (handledByChannel) {
|
|
3990
|
-
return;
|
|
3991
|
-
}
|
|
3992
|
-
return cmux2.send(surface, text, sendOpts);
|
|
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}`);
|
|
4180
|
+
log(`delivery stuck alert failed project=${project}: ${e.message}`);
|
|
4001
4181
|
}
|
|
4002
|
-
|
|
4182
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
4183
|
+
telegramBridge?.pushRaw(project, text);
|
|
4184
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
4185
|
+
stuckNotified.delete(project);
|
|
4003
4186
|
}
|
|
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);
|
|
4187
|
+
} catch (e) {
|
|
4188
|
+
log(`delivery project=${project}: unhandled error \u2014 ${e.message}`);
|
|
4016
4189
|
}
|
|
4017
4190
|
}
|
|
4018
4191
|
};
|
|
@@ -4026,19 +4199,28 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4026
4199
|
delivering = false;
|
|
4027
4200
|
}
|
|
4028
4201
|
};
|
|
4029
|
-
return { defaultNotify, deliveryTick, deliveryStats };
|
|
4202
|
+
return { defaultNotify, deliveryTick, deliveryStats, inFlightDelivery };
|
|
4030
4203
|
}
|
|
4031
|
-
var CURSOR_SUBSCRIBER, TERMINAL_KINDS;
|
|
4204
|
+
var CURSOR_SUBSCRIBER, TERMINAL_KINDS, STUCK_ALERT_TEXT;
|
|
4032
4205
|
var init_delivery_loop = __esm({
|
|
4033
4206
|
"packages/core/dist/daemon/delivery-loop.js"() {
|
|
4034
4207
|
init_mailbox();
|
|
4035
4208
|
init_captain_delivery();
|
|
4209
|
+
init_defer_delivery();
|
|
4036
4210
|
init_dist();
|
|
4037
4211
|
init_interactive_probe();
|
|
4038
4212
|
init_liveness2();
|
|
4039
4213
|
init_captain_channel();
|
|
4040
4214
|
CURSOR_SUBSCRIBER = "captain";
|
|
4041
4215
|
TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
4216
|
+
STUCK_ALERT_TEXT = {
|
|
4217
|
+
"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.`,
|
|
4218
|
+
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.`,
|
|
4219
|
+
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.`,
|
|
4220
|
+
"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.`,
|
|
4221
|
+
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.`,
|
|
4222
|
+
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.`
|
|
4223
|
+
};
|
|
4042
4224
|
}
|
|
4043
4225
|
});
|
|
4044
4226
|
|
|
@@ -4126,10 +4308,72 @@ var init_server = __esm({
|
|
|
4126
4308
|
}
|
|
4127
4309
|
});
|
|
4128
4310
|
|
|
4311
|
+
// packages/core/dist/daemon/exit-marker.js
|
|
4312
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
|
|
4313
|
+
import { join as join11 } from "path";
|
|
4314
|
+
function exitMarkerPath(stateRoot) {
|
|
4315
|
+
return join11(stateRoot, "exit-marker.json");
|
|
4316
|
+
}
|
|
4317
|
+
function writeExitMarker(stateRoot, marker, log) {
|
|
4318
|
+
try {
|
|
4319
|
+
writeFileSync8(exitMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4320
|
+
} catch (e) {
|
|
4321
|
+
log(`exit marker write failed: ${e.message}`);
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
function consumeExitMarker(stateRoot, now = Date.now) {
|
|
4325
|
+
const p = exitMarkerPath(stateRoot);
|
|
4326
|
+
if (!existsSync9(p))
|
|
4327
|
+
return { marker: null };
|
|
4328
|
+
let marker = null;
|
|
4329
|
+
try {
|
|
4330
|
+
marker = JSON.parse(readFileSync8(p, "utf-8"));
|
|
4331
|
+
} catch {
|
|
4332
|
+
marker = null;
|
|
4333
|
+
}
|
|
4334
|
+
try {
|
|
4335
|
+
unlinkSync3(p);
|
|
4336
|
+
} catch {
|
|
4337
|
+
}
|
|
4338
|
+
if (!marker)
|
|
4339
|
+
return { marker: null };
|
|
4340
|
+
const gapMs = Math.max(0, now() - new Date(marker.ts).getTime());
|
|
4341
|
+
return { marker, gapMs };
|
|
4342
|
+
}
|
|
4343
|
+
function runningMarkerPath(stateRoot) {
|
|
4344
|
+
return join11(stateRoot, "running-marker.json");
|
|
4345
|
+
}
|
|
4346
|
+
function writeRunningMarker(stateRoot, marker, log) {
|
|
4347
|
+
try {
|
|
4348
|
+
writeFileSync8(runningMarkerPath(stateRoot), JSON.stringify(marker));
|
|
4349
|
+
} catch (e) {
|
|
4350
|
+
log(`running marker write failed: ${e.message}`);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
function readRunningMarker(stateRoot) {
|
|
4354
|
+
try {
|
|
4355
|
+
return JSON.parse(readFileSync8(runningMarkerPath(stateRoot), "utf-8"));
|
|
4356
|
+
} catch {
|
|
4357
|
+
return null;
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
function removeRunningMarker(stateRoot, log) {
|
|
4361
|
+
try {
|
|
4362
|
+
unlinkSync3(runningMarkerPath(stateRoot));
|
|
4363
|
+
} catch (e) {
|
|
4364
|
+
if (e.code !== "ENOENT")
|
|
4365
|
+
log(`running marker remove failed: ${e.message}`);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
var init_exit_marker = __esm({
|
|
4369
|
+
"packages/core/dist/daemon/exit-marker.js"() {
|
|
4370
|
+
}
|
|
4371
|
+
});
|
|
4372
|
+
|
|
4129
4373
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
4130
4374
|
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
|
|
4375
|
+
import { join as join12 } from "path";
|
|
4376
|
+
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
4133
4377
|
function distBuiltAt() {
|
|
4134
4378
|
try {
|
|
4135
4379
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -4182,7 +4426,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4182
4426
|
for (const r of store.list(project))
|
|
4183
4427
|
byState[r.state] = (byState[r.state] ?? 0) + 1;
|
|
4184
4428
|
let corruptCount = 0;
|
|
4185
|
-
const dir =
|
|
4429
|
+
const dir = join12(stateRoot, project);
|
|
4186
4430
|
try {
|
|
4187
4431
|
for (const n of readdirSync3(dir)) {
|
|
4188
4432
|
if (n.includes(".corrupt.")) {
|
|
@@ -4192,7 +4436,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4192
4436
|
if (!n.endsWith(".json"))
|
|
4193
4437
|
continue;
|
|
4194
4438
|
try {
|
|
4195
|
-
JSON.parse(
|
|
4439
|
+
JSON.parse(readFileSync9(join12(dir, n), "utf-8"));
|
|
4196
4440
|
} catch {
|
|
4197
4441
|
corruptCount++;
|
|
4198
4442
|
}
|
|
@@ -4207,7 +4451,7 @@ function gatherResults(resultsDir) {
|
|
|
4207
4451
|
try {
|
|
4208
4452
|
for (const n of readdirSync3(resultsDir)) {
|
|
4209
4453
|
try {
|
|
4210
|
-
const s = statSync3(
|
|
4454
|
+
const s = statSync3(join12(resultsDir, n));
|
|
4211
4455
|
if (s.isFile()) {
|
|
4212
4456
|
fileCount++;
|
|
4213
4457
|
totalBytes += s.size;
|
|
@@ -4227,19 +4471,20 @@ var init_snapshot_gather = __esm({
|
|
|
4227
4471
|
});
|
|
4228
4472
|
|
|
4229
4473
|
// packages/core/dist/daemon/start.js
|
|
4230
|
-
import { join as
|
|
4474
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
4231
4475
|
import { readdir } from "fs/promises";
|
|
4232
4476
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4233
4477
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
4234
4478
|
const { daemonCmux } = ctx;
|
|
4235
4479
|
const probes = createProbes(ctx);
|
|
4236
|
-
const
|
|
4480
|
+
const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);
|
|
4481
|
+
const { defaultNotify, deliveryTick: initialDeliveryTick, deliveryStats, inFlightDelivery } = createDelivery(ctx, daemonCmux, surfaceProbe);
|
|
4237
4482
|
const baseNotify = opts.notify ?? defaultNotify;
|
|
4238
4483
|
const notify = ctx.telegramBridge ? async (args) => {
|
|
4239
4484
|
await baseNotify(args);
|
|
4240
4485
|
ctx.telegramBridge.pushLifecycle(args.project, args.event);
|
|
4241
4486
|
} : baseNotify;
|
|
4242
|
-
|
|
4487
|
+
ctx.notify = notify;
|
|
4243
4488
|
const ingest = (project) => (e) => void ctx.d.handle({ kind: "event", project, event: e });
|
|
4244
4489
|
const d = createDaemon({
|
|
4245
4490
|
store,
|
|
@@ -4303,7 +4548,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4303
4548
|
return out;
|
|
4304
4549
|
}
|
|
4305
4550
|
async function gatherSnapshotInputs(now) {
|
|
4306
|
-
const logPath2 =
|
|
4551
|
+
const logPath2 = join13(dirname3(stateRoot), "squadrantd.log");
|
|
4307
4552
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4308
4553
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4309
4554
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -4396,6 +4641,33 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4396
4641
|
})();
|
|
4397
4642
|
const server = createServer2(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });
|
|
4398
4643
|
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
4644
|
+
const bootTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
4645
|
+
{
|
|
4646
|
+
const sendDownAlert = (minutes, reasonText) => {
|
|
4647
|
+
const text = `\u26A0\uFE0F daemon was down for ${minutes} min (last exit reason=${reasonText})`;
|
|
4648
|
+
const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4649
|
+
for (const project of alertProjects) {
|
|
4650
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
const { marker, gapMs } = consumeExitMarker(stateRoot);
|
|
4654
|
+
const prevRunning = readRunningMarker(stateRoot);
|
|
4655
|
+
if (marker) {
|
|
4656
|
+
log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
|
|
4657
|
+
if ((gapMs ?? 0) > 6e4)
|
|
4658
|
+
sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
4659
|
+
} else if (prevRunning) {
|
|
4660
|
+
const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
|
|
4661
|
+
const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
|
|
4662
|
+
log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
|
|
4663
|
+
if (uncleanGapMs > 6e4) {
|
|
4664
|
+
sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
|
|
4665
|
+
}
|
|
4666
|
+
} else {
|
|
4667
|
+
log("previous exit: none (clean or first boot)");
|
|
4668
|
+
}
|
|
4669
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: bootTs }, log);
|
|
4670
|
+
}
|
|
4399
4671
|
let deliveryTick = initialDeliveryTick;
|
|
4400
4672
|
let probeTick;
|
|
4401
4673
|
if (daemonCmux) {
|
|
@@ -4436,9 +4708,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4436
4708
|
keepCount: opts.mailboxConfig?.keepCount ?? 3
|
|
4437
4709
|
};
|
|
4438
4710
|
let rotationTimer;
|
|
4711
|
+
let rotationTick;
|
|
4439
4712
|
if (rotationInterval > 0) {
|
|
4440
|
-
const inboxPath =
|
|
4441
|
-
|
|
4713
|
+
const inboxPath = join13(stateRoot, "inbox");
|
|
4714
|
+
rotationTick = async () => {
|
|
4442
4715
|
try {
|
|
4443
4716
|
let entries;
|
|
4444
4717
|
try {
|
|
@@ -4451,13 +4724,23 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4451
4724
|
await rotateIfNeeded({ stateRoot, project, ...mboxCfg });
|
|
4452
4725
|
} catch (e) {
|
|
4453
4726
|
log(`rotation timer error: ${e.message}`);
|
|
4727
|
+
} finally {
|
|
4728
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: (/* @__PURE__ */ new Date()).toISOString() }, log);
|
|
4454
4729
|
}
|
|
4730
|
+
};
|
|
4731
|
+
rotationTimer = setInterval(() => {
|
|
4732
|
+
void rotationTick();
|
|
4455
4733
|
}, rotationInterval);
|
|
4456
4734
|
rotationTimer.unref?.();
|
|
4457
4735
|
}
|
|
4458
4736
|
return {
|
|
4459
4737
|
stop(reason = "requested") {
|
|
4460
|
-
|
|
4738
|
+
const ppid = process.ppid;
|
|
4739
|
+
const uptimeMs = Math.round(process.uptime() * 1e3);
|
|
4740
|
+
const inFlight = inFlightDelivery();
|
|
4741
|
+
log(`exit pid=${process.pid} reason=${reason} ppid=${ppid} launchd=${ppid === 1} uptimeMs=${uptimeMs} inFlightDelivery=${inFlight ? `${inFlight.project}#${inFlight.seq}(defers=${inFlight.deferCount})` : "none"}`);
|
|
4742
|
+
writeExitMarker(stateRoot, { ts: (/* @__PURE__ */ new Date()).toISOString(), pid: process.pid, reason, ppid, uptimeMs, inFlightDelivery: inFlight }, log);
|
|
4743
|
+
removeRunningMarker(stateRoot, log);
|
|
4461
4744
|
if (deliveryTimer)
|
|
4462
4745
|
clearInterval(deliveryTimer);
|
|
4463
4746
|
if (probeTimer)
|
|
@@ -4486,7 +4769,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4486
4769
|
}));
|
|
4487
4770
|
},
|
|
4488
4771
|
tickDelivery: deliveryTick,
|
|
4489
|
-
tickProbe: probeTick
|
|
4772
|
+
tickProbe: probeTick,
|
|
4773
|
+
tickRotation: rotationTick
|
|
4490
4774
|
};
|
|
4491
4775
|
}
|
|
4492
4776
|
var CURSOR_SUBSCRIBER2, SNAPSHOT_LOG_WINDOW_MS;
|
|
@@ -4498,6 +4782,7 @@ var init_start = __esm({
|
|
|
4498
4782
|
init_gates();
|
|
4499
4783
|
init_server();
|
|
4500
4784
|
init_mailbox();
|
|
4785
|
+
init_exit_marker();
|
|
4501
4786
|
init_liveness2();
|
|
4502
4787
|
init_dist();
|
|
4503
4788
|
init_snapshot_gather();
|
|
@@ -5492,9 +5777,9 @@ var init_bridge = __esm({
|
|
|
5492
5777
|
|
|
5493
5778
|
// packages/core/dist/restart-daemon.js
|
|
5494
5779
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5495
|
-
import { existsSync as
|
|
5780
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5496
5781
|
function defaultIsRunning() {
|
|
5497
|
-
return
|
|
5782
|
+
return existsSync10(DAEMON_SOCK_PATH);
|
|
5498
5783
|
}
|
|
5499
5784
|
function defaultRunKickstart() {
|
|
5500
5785
|
const uid = process.getuid?.() ?? 0;
|
|
@@ -6032,6 +6317,76 @@ var init_side_session = __esm({
|
|
|
6032
6317
|
}
|
|
6033
6318
|
});
|
|
6034
6319
|
|
|
6320
|
+
// packages/core/dist/crew-answer.js
|
|
6321
|
+
function describeOptions(options) {
|
|
6322
|
+
return options.map((o) => ` ${o.highlighted ? "\u276F" : " "} ${o.index}. ${o.label}`).join("\n");
|
|
6323
|
+
}
|
|
6324
|
+
function resolveOption(options, selector) {
|
|
6325
|
+
const trimmed = selector.trim();
|
|
6326
|
+
if (/^\d+$/.test(trimmed)) {
|
|
6327
|
+
const byIndex = options.find((o) => o.index === Number(trimmed));
|
|
6328
|
+
if (!byIndex) {
|
|
6329
|
+
throw new Error(`No option ${trimmed} in the visible prompt. Visible options:
|
|
6330
|
+
${describeOptions(options)}`);
|
|
6331
|
+
}
|
|
6332
|
+
return byIndex;
|
|
6333
|
+
}
|
|
6334
|
+
const lower = trimmed.toLowerCase();
|
|
6335
|
+
const exact = options.filter((o) => o.label.toLowerCase() === lower);
|
|
6336
|
+
if (exact.length === 1)
|
|
6337
|
+
return exact[0];
|
|
6338
|
+
if (exact.length > 1) {
|
|
6339
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
6340
|
+
${describeOptions(exact)}`);
|
|
6341
|
+
}
|
|
6342
|
+
const prefix = options.filter((o) => o.label.toLowerCase().startsWith(lower));
|
|
6343
|
+
if (prefix.length === 1)
|
|
6344
|
+
return prefix[0];
|
|
6345
|
+
if (prefix.length > 1) {
|
|
6346
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
6347
|
+
${describeOptions(prefix)}`);
|
|
6348
|
+
}
|
|
6349
|
+
throw new Error(`No option matches "${selector}". Visible options:
|
|
6350
|
+
${describeOptions(options)}`);
|
|
6351
|
+
}
|
|
6352
|
+
async function runCrewAnswer(project, name, option, runtime, workspaceId, deps, opts) {
|
|
6353
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
6354
|
+
if (!crew) {
|
|
6355
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6356
|
+
}
|
|
6357
|
+
const options = await deps.readModalOptions(crew);
|
|
6358
|
+
if (!options) {
|
|
6359
|
+
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.`);
|
|
6360
|
+
}
|
|
6361
|
+
const target = resolveOption(options, option);
|
|
6362
|
+
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
6363
|
+
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}'.
|
|
6364
|
+
Visible options:
|
|
6365
|
+
${describeOptions(options)}`);
|
|
6366
|
+
}
|
|
6367
|
+
const log = deps.log ?? (() => {
|
|
6368
|
+
});
|
|
6369
|
+
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
6370
|
+
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
6371
|
+
const steps = target.index - current.index;
|
|
6372
|
+
const key = steps >= 0 ? "Down" : "Up";
|
|
6373
|
+
for (let i = 0; i < Math.abs(steps); i++) {
|
|
6374
|
+
await runtime.sendKeyToPane(crew, key);
|
|
6375
|
+
}
|
|
6376
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
6377
|
+
if (opts?.text) {
|
|
6378
|
+
await runtime.pasteToPane(crew, opts.text);
|
|
6379
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
6380
|
+
}
|
|
6381
|
+
const after = await deps.readModalOptions(crew);
|
|
6382
|
+
return { selected: target, closed: after === null };
|
|
6383
|
+
}
|
|
6384
|
+
var init_crew_answer = __esm({
|
|
6385
|
+
"packages/core/dist/crew-answer.js"() {
|
|
6386
|
+
init_crew_spawn();
|
|
6387
|
+
}
|
|
6388
|
+
});
|
|
6389
|
+
|
|
6035
6390
|
// packages/core/dist/lifecycle-source.js
|
|
6036
6391
|
function reduceLifecycle(prev, next) {
|
|
6037
6392
|
if (next.origin === "agent") {
|
|
@@ -6066,6 +6421,7 @@ __export(dist_exports, {
|
|
|
6066
6421
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
6067
6422
|
DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
|
|
6068
6423
|
DeferDelivery: () => DeferDelivery,
|
|
6424
|
+
FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
|
|
6069
6425
|
GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
|
|
6070
6426
|
GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
|
|
6071
6427
|
IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
|
|
@@ -6098,6 +6454,7 @@ __export(dist_exports, {
|
|
|
6098
6454
|
classifyHealth: () => classifyHealth,
|
|
6099
6455
|
closeWorkItem: () => closeWorkItem,
|
|
6100
6456
|
computeTemplateHash: () => computeTemplateHash,
|
|
6457
|
+
consumeExitMarker: () => consumeExitMarker,
|
|
6101
6458
|
createAttach: () => createAttach,
|
|
6102
6459
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
6103
6460
|
createDaemon: () => createDaemon,
|
|
@@ -6138,13 +6495,17 @@ __export(dist_exports, {
|
|
|
6138
6495
|
encodeMsg: () => encodeMsg,
|
|
6139
6496
|
ensureDaemon: () => ensureDaemon,
|
|
6140
6497
|
evaluateStall: () => evaluateStall,
|
|
6498
|
+
exitMarkerPath: () => exitMarkerPath,
|
|
6141
6499
|
fallsBackToPane: () => fallsBackToPane,
|
|
6500
|
+
findCrewPane: () => findCrewPane,
|
|
6142
6501
|
findOpenChildren: () => findOpenChildren,
|
|
6143
6502
|
findProjectByThread: () => findProjectByThread,
|
|
6144
6503
|
findWorkItemById: () => findWorkItemById,
|
|
6504
|
+
forceKickstartAndVerify: () => forceKickstartAndVerify,
|
|
6145
6505
|
formatInbound: () => formatInbound,
|
|
6146
6506
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
6147
6507
|
formatLifecycle: () => formatLifecycle,
|
|
6508
|
+
getDaemonPid: () => getDaemonPid,
|
|
6148
6509
|
healCmdFor: () => healCmdFor,
|
|
6149
6510
|
isAuthorized: () => isAuthorized,
|
|
6150
6511
|
isBareSpawn: () => isBareSpawn,
|
|
@@ -6179,6 +6540,7 @@ __export(dist_exports, {
|
|
|
6179
6540
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
6180
6541
|
readCursor: () => readCursor,
|
|
6181
6542
|
readFromCursor: () => readFromCursor,
|
|
6543
|
+
readRunningMarker: () => readRunningMarker,
|
|
6182
6544
|
reapCrewChildren: () => reapCrewChildren,
|
|
6183
6545
|
reapOrphanedCrews: () => reapOrphanedCrews,
|
|
6184
6546
|
reconcileLiveness: () => reconcileLiveness,
|
|
@@ -6187,6 +6549,7 @@ __export(dist_exports, {
|
|
|
6187
6549
|
reduce: () => reduce,
|
|
6188
6550
|
reduceLifecycle: () => reduceLifecycle,
|
|
6189
6551
|
releaseDaemonLock: () => releaseDaemonLock,
|
|
6552
|
+
removeRunningMarker: () => removeRunningMarker,
|
|
6190
6553
|
renderPlist: () => renderPlist,
|
|
6191
6554
|
reregisterDaemon: () => reregisterDaemon,
|
|
6192
6555
|
resolveAgentBinDirs: () => resolveAgentBinDirs,
|
|
@@ -6198,6 +6561,7 @@ __export(dist_exports, {
|
|
|
6198
6561
|
resolveSetupUserId: () => resolveSetupUserId,
|
|
6199
6562
|
restartDaemonIfRunning: () => restartDaemonIfRunning,
|
|
6200
6563
|
rotateIfNeeded: () => rotateIfNeeded,
|
|
6564
|
+
runCrewAnswer: () => runCrewAnswer,
|
|
6201
6565
|
runCrewClose: () => runCrewClose,
|
|
6202
6566
|
runCrewList: () => runCrewList,
|
|
6203
6567
|
runCrewRead: () => runCrewRead,
|
|
@@ -6217,6 +6581,7 @@ __export(dist_exports, {
|
|
|
6217
6581
|
runTelegramPostSetup: () => runTelegramPostSetup,
|
|
6218
6582
|
runTelegramSend: () => runTelegramSend,
|
|
6219
6583
|
runTelegramStatus: () => runTelegramStatus,
|
|
6584
|
+
runningMarkerPath: () => runningMarkerPath,
|
|
6220
6585
|
sanitizePathForPlist: () => sanitizePathForPlist,
|
|
6221
6586
|
saveSessions: () => saveSessions,
|
|
6222
6587
|
saveState: () => saveState,
|
|
@@ -6242,6 +6607,8 @@ __export(dist_exports, {
|
|
|
6242
6607
|
waitForCaptainDelivery: () => waitForCaptainDelivery,
|
|
6243
6608
|
waitForWarmup: () => waitForWarmup,
|
|
6244
6609
|
writeCursor: () => writeCursor,
|
|
6610
|
+
writeExitMarker: () => writeExitMarker,
|
|
6611
|
+
writeRunningMarker: () => writeRunningMarker,
|
|
6245
6612
|
writeTelegramConfig: () => writeTelegramConfig
|
|
6246
6613
|
});
|
|
6247
6614
|
var init_dist2 = __esm({
|
|
@@ -6264,6 +6631,7 @@ var init_dist2 = __esm({
|
|
|
6264
6631
|
init_attach();
|
|
6265
6632
|
init_start();
|
|
6266
6633
|
init_delivery_loop();
|
|
6634
|
+
init_exit_marker();
|
|
6267
6635
|
init_interactive_probe();
|
|
6268
6636
|
init_captain_delivery();
|
|
6269
6637
|
init_defer_delivery();
|
|
@@ -6277,6 +6645,7 @@ var init_dist2 = __esm({
|
|
|
6277
6645
|
init_launch_workspace();
|
|
6278
6646
|
init_side_session();
|
|
6279
6647
|
init_crew_spawn();
|
|
6648
|
+
init_crew_answer();
|
|
6280
6649
|
init_lifecycle_source();
|
|
6281
6650
|
init_control_channel();
|
|
6282
6651
|
init_captain_channel();
|
|
@@ -6294,9 +6663,10 @@ init_dist2();
|
|
|
6294
6663
|
init_dist2();
|
|
6295
6664
|
init_dist2();
|
|
6296
6665
|
init_dist2();
|
|
6297
|
-
import { join as
|
|
6666
|
+
import { join as join22, dirname as dirname4, resolve as resolve3 } from "path";
|
|
6667
|
+
import { homedir as homedir13 } from "os";
|
|
6298
6668
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6299
|
-
import { readFileSync as
|
|
6669
|
+
import { readFileSync as readFileSync15, statSync as statSync4, existsSync as existsSync13 } from "fs";
|
|
6300
6670
|
|
|
6301
6671
|
// packages/agents/dist/drivers/claude.js
|
|
6302
6672
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -6612,10 +6982,10 @@ function toSnapshot(ev) {
|
|
|
6612
6982
|
// packages/agents/dist/codex/config.js
|
|
6613
6983
|
import { readFile as readFile6 } from "fs/promises";
|
|
6614
6984
|
import { homedir as homedir7 } from "os";
|
|
6615
|
-
import { join as
|
|
6985
|
+
import { join as join14 } from "path";
|
|
6616
6986
|
async function resolveCodexModel() {
|
|
6617
|
-
const home = process.env["CODEX_HOME"] ??
|
|
6618
|
-
const configPath =
|
|
6987
|
+
const home = process.env["CODEX_HOME"] ?? join14(homedir7(), ".codex");
|
|
6988
|
+
const configPath = join14(home, "config.toml");
|
|
6619
6989
|
let text;
|
|
6620
6990
|
try {
|
|
6621
6991
|
text = await readFile6(configPath, "utf8");
|
|
@@ -7105,9 +7475,9 @@ var OpencodeSseBridge = class {
|
|
|
7105
7475
|
|
|
7106
7476
|
// packages/agents/dist/interactive/claude.js
|
|
7107
7477
|
import { execSync as execSync6 } from "child_process";
|
|
7108
|
-
import { readFileSync as
|
|
7478
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7109
7479
|
import { homedir as homedir8 } from "os";
|
|
7110
|
-
import { join as
|
|
7480
|
+
import { join as join15 } from "path";
|
|
7111
7481
|
var nextAskUserQuestionRequestId = Date.now();
|
|
7112
7482
|
|
|
7113
7483
|
// packages/agents/dist/headless/types.js
|
|
@@ -7271,14 +7641,14 @@ function runHeadless(opts) {
|
|
|
7271
7641
|
}
|
|
7272
7642
|
|
|
7273
7643
|
// packages/agents/dist/claude/peer-registry-source.js
|
|
7274
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
7275
|
-
import { join as
|
|
7644
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
7645
|
+
import { join as join17 } from "path";
|
|
7276
7646
|
|
|
7277
7647
|
// packages/agents/dist/claude/registry.js
|
|
7278
7648
|
import fs14 from "fs";
|
|
7279
|
-
import { join as
|
|
7649
|
+
import { join as join16 } from "path";
|
|
7280
7650
|
import { homedir as homedir9 } from "os";
|
|
7281
|
-
var CLAUDE_SESSIONS_DIR =
|
|
7651
|
+
var CLAUDE_SESSIONS_DIR = join16(homedir9(), ".claude", "sessions");
|
|
7282
7652
|
var PID_JSON = /^(\d+)\.json$/;
|
|
7283
7653
|
function parseRegistryDir(files, readFile7) {
|
|
7284
7654
|
const out = [];
|
|
@@ -7324,7 +7694,7 @@ function readClaudeStatusBySocketPath(socketPath) {
|
|
|
7324
7694
|
} catch {
|
|
7325
7695
|
return void 0;
|
|
7326
7696
|
}
|
|
7327
|
-
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(
|
|
7697
|
+
const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join16(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
7328
7698
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath);
|
|
7329
7699
|
if (!entry)
|
|
7330
7700
|
return void 0;
|
|
@@ -7356,7 +7726,7 @@ var ClaudePeerRegistrySource = class {
|
|
|
7356
7726
|
log;
|
|
7357
7727
|
constructor(o = {}) {
|
|
7358
7728
|
this.readdir = o.readdir ?? (() => readdirSync4(CLAUDE_SESSIONS_DIR));
|
|
7359
|
-
this.readFile = o.readFile ?? ((n) =>
|
|
7729
|
+
this.readFile = o.readFile ?? ((n) => readFileSync11(join17(CLAUDE_SESSIONS_DIR, n), "utf8"));
|
|
7360
7730
|
this.isAlive = o.isAlive ?? defaultIsAlive;
|
|
7361
7731
|
this.now = o.now ?? Date.now;
|
|
7362
7732
|
this.pollMs = o.pollMs ?? 2e3;
|
|
@@ -8133,7 +8503,10 @@ function createCmuxDriver() {
|
|
|
8133
8503
|
let screen = "";
|
|
8134
8504
|
try {
|
|
8135
8505
|
screen = await cmux(["read-screen", "--workspace", ws, "--surface", sf]);
|
|
8136
|
-
} catch {
|
|
8506
|
+
} catch (e) {
|
|
8507
|
+
process.stderr.write(`[squadrant] read-screen failed for ${ws}/${sf}: ${e.message}
|
|
8508
|
+
`);
|
|
8509
|
+
throw new DeferDelivery(null, "probe-failed");
|
|
8137
8510
|
}
|
|
8138
8511
|
const draft = parseDraftFromScreen(screen);
|
|
8139
8512
|
if (draft === null)
|
|
@@ -8333,7 +8706,7 @@ var NotifierRegistry = class {
|
|
|
8333
8706
|
|
|
8334
8707
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
8335
8708
|
import fs15 from "fs/promises";
|
|
8336
|
-
import { existsSync as
|
|
8709
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8337
8710
|
import path19 from "path";
|
|
8338
8711
|
|
|
8339
8712
|
// packages/workspaces/dist/workspaces/registry.js
|
|
@@ -8449,6 +8822,16 @@ var CmuxEventsBridge = class {
|
|
|
8449
8822
|
}
|
|
8450
8823
|
if (f?.type !== "event" || f.category !== "agent")
|
|
8451
8824
|
return;
|
|
8825
|
+
if (f.name === "agent.hook.PostToolUse") {
|
|
8826
|
+
const p2 = f.payload ?? {};
|
|
8827
|
+
if (p2.phase === "received")
|
|
8828
|
+
return;
|
|
8829
|
+
const rec2 = this.deps.resolve({ cwd: p2.cwd, source: p2._source ?? f.source, sessionId: p2.session_id });
|
|
8830
|
+
if (!rec2)
|
|
8831
|
+
return;
|
|
8832
|
+
this.deps.emit({ type: "task.progress", id: rec2.id, note: f.name });
|
|
8833
|
+
return;
|
|
8834
|
+
}
|
|
8452
8835
|
const runState = f.name ? deriveRunState(f.name) : null;
|
|
8453
8836
|
if (!runState)
|
|
8454
8837
|
return;
|
|
@@ -8477,8 +8860,8 @@ var CmuxEventsBridge = class {
|
|
|
8477
8860
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
8478
8861
|
init_dist2();
|
|
8479
8862
|
init_dist();
|
|
8480
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8481
|
-
import { join as
|
|
8863
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
8864
|
+
import { join as join18 } from "path";
|
|
8482
8865
|
import { homedir as homedir10 } from "os";
|
|
8483
8866
|
|
|
8484
8867
|
// packages/workspaces/dist/cmux-daemon/store-fingerprint.js
|
|
@@ -8576,7 +8959,7 @@ function readLivenessSnapshot(files, readFile7, projects, argvRecovery = {}) {
|
|
|
8576
8959
|
|
|
8577
8960
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
8578
8961
|
async function readCmuxLiveness() {
|
|
8579
|
-
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
8962
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join18(homedir10(), ".cmuxterm");
|
|
8580
8963
|
const projects = loadConfig().projects;
|
|
8581
8964
|
let files;
|
|
8582
8965
|
try {
|
|
@@ -8584,7 +8967,7 @@ async function readCmuxLiveness() {
|
|
|
8584
8967
|
} catch (e) {
|
|
8585
8968
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
8586
8969
|
}
|
|
8587
|
-
return readLivenessSnapshot(files, (f) =>
|
|
8970
|
+
return readLivenessSnapshot(files, (f) => readFileSync12(join18(dir, f), "utf-8"), projects);
|
|
8588
8971
|
}
|
|
8589
8972
|
var DaemonCmux = class {
|
|
8590
8973
|
driver;
|
|
@@ -8647,9 +9030,9 @@ var DaemonCmux = class {
|
|
|
8647
9030
|
};
|
|
8648
9031
|
|
|
8649
9032
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
8650
|
-
import { join as
|
|
9033
|
+
import { join as join19 } from "path";
|
|
8651
9034
|
import { homedir as homedir11 } from "os";
|
|
8652
|
-
import { watch, readdirSync as readdirSync6, readFileSync as
|
|
9035
|
+
import { watch, readdirSync as readdirSync6, readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
8653
9036
|
var CmuxStoreSource = class {
|
|
8654
9037
|
name = "cmux-store";
|
|
8655
9038
|
stateDir;
|
|
@@ -8670,12 +9053,12 @@ var CmuxStoreSource = class {
|
|
|
8670
9053
|
active = false;
|
|
8671
9054
|
lastError = null;
|
|
8672
9055
|
constructor(opts = {}) {
|
|
8673
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
9056
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join19(homedir11(), ".cmuxterm");
|
|
8674
9057
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
8675
9058
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
8676
9059
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
8677
9060
|
this.readFile = opts.readFile ?? defaultReadFile;
|
|
8678
|
-
this.fileExists = opts.fileExists ??
|
|
9061
|
+
this.fileExists = opts.fileExists ?? existsSync12;
|
|
8679
9062
|
this.watchDir = opts.watchDir ?? defaultWatchDir;
|
|
8680
9063
|
this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
|
|
8681
9064
|
this.cancelTimer = opts.cancelTimer ?? clearTimeout;
|
|
@@ -8732,7 +9115,7 @@ var CmuxStoreSource = class {
|
|
|
8732
9115
|
}
|
|
8733
9116
|
scanFile(filename) {
|
|
8734
9117
|
const deps = this.deps;
|
|
8735
|
-
const filePath =
|
|
9118
|
+
const filePath = join19(this.stateDir, filename);
|
|
8736
9119
|
const lockPath = `${filePath}.lock`;
|
|
8737
9120
|
if (this.fileExists(lockPath)) {
|
|
8738
9121
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -8805,7 +9188,7 @@ function defaultListFiles(dir) {
|
|
|
8805
9188
|
}
|
|
8806
9189
|
function defaultReadFile(path21) {
|
|
8807
9190
|
try {
|
|
8808
|
-
return
|
|
9191
|
+
return readFileSync13(path21, "utf-8");
|
|
8809
9192
|
} catch {
|
|
8810
9193
|
return void 0;
|
|
8811
9194
|
}
|
|
@@ -8820,9 +9203,9 @@ function defaultWatchDir(dir, cb) {
|
|
|
8820
9203
|
}
|
|
8821
9204
|
|
|
8822
9205
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
8823
|
-
import { join as
|
|
9206
|
+
import { join as join20 } from "path";
|
|
8824
9207
|
import { homedir as homedir12 } from "os";
|
|
8825
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
9208
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
8826
9209
|
var CLAUDE_HOOK_EVENTS = [
|
|
8827
9210
|
["SessionStart", "session-start"],
|
|
8828
9211
|
["UserPromptSubmit", "prompt-submit"],
|
|
@@ -8834,7 +9217,7 @@ var CLAUDE_HOOK_EVENTS = [
|
|
|
8834
9217
|
];
|
|
8835
9218
|
var DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
8836
9219
|
function installClaudeHooks(opts = {}) {
|
|
8837
|
-
const settingsPath = opts.settingsPath ??
|
|
9220
|
+
const settingsPath = opts.settingsPath ?? join20(homedir12(), ".claude", "settings.json");
|
|
8838
9221
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
8839
9222
|
const readFile7 = opts.readFile ?? defaultReadFile2;
|
|
8840
9223
|
const writeFile6 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -9004,14 +9387,14 @@ function extractDetail(sub, payload) {
|
|
|
9004
9387
|
}
|
|
9005
9388
|
function defaultReadFile2(path21) {
|
|
9006
9389
|
try {
|
|
9007
|
-
return
|
|
9390
|
+
return readFileSync14(path21, "utf-8");
|
|
9008
9391
|
} catch {
|
|
9009
9392
|
return void 0;
|
|
9010
9393
|
}
|
|
9011
9394
|
}
|
|
9012
9395
|
function defaultWriteFile(path21, content) {
|
|
9013
9396
|
mkdirSync6(path21.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
9014
|
-
|
|
9397
|
+
writeFileSync9(path21, content, "utf-8");
|
|
9015
9398
|
}
|
|
9016
9399
|
|
|
9017
9400
|
// packages/workspaces/dist/crew-pane.js
|
|
@@ -9139,27 +9522,56 @@ async function maybeBroadcastDaemonRestart(opts) {
|
|
|
9139
9522
|
|
|
9140
9523
|
// packages/cli/src/lib/captain-channel-factory.ts
|
|
9141
9524
|
import { createServer as createServer3, connect as netConnect } from "net";
|
|
9142
|
-
import
|
|
9525
|
+
import fs16 from "fs";
|
|
9526
|
+
import { join as join21 } from "path";
|
|
9143
9527
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
9144
9528
|
import chalk2 from "chalk";
|
|
9145
9529
|
init_dist2();
|
|
9146
9530
|
var shared;
|
|
9531
|
+
var registryEntryPath = () => join21(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
9532
|
+
function unregisterSenderIdentity() {
|
|
9533
|
+
try {
|
|
9534
|
+
fs16.unlinkSync(registryEntryPath());
|
|
9535
|
+
} catch {
|
|
9536
|
+
}
|
|
9537
|
+
}
|
|
9538
|
+
function registerSenderIdentity(socketPath) {
|
|
9539
|
+
try {
|
|
9540
|
+
unregisterSenderIdentity();
|
|
9541
|
+
fs16.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
9542
|
+
fs16.writeFileSync(
|
|
9543
|
+
registryEntryPath(),
|
|
9544
|
+
JSON.stringify({
|
|
9545
|
+
pid: process.pid,
|
|
9546
|
+
sessionId: randomUUID5(),
|
|
9547
|
+
name: "squadrantd",
|
|
9548
|
+
messagingSocketPath: socketPath,
|
|
9549
|
+
kind: "daemon",
|
|
9550
|
+
peerProtocol: 1
|
|
9551
|
+
})
|
|
9552
|
+
);
|
|
9553
|
+
process.on("exit", unregisterSenderIdentity);
|
|
9554
|
+
} catch {
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9147
9557
|
async function sharedReceiptListener() {
|
|
9148
9558
|
if (shared) return shared;
|
|
9559
|
+
const socketPath = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
|
|
9149
9560
|
const listener = new ClaudeReceiptListener({
|
|
9150
|
-
socketPath
|
|
9561
|
+
socketPath,
|
|
9151
9562
|
createServer: (h) => createServer3(h),
|
|
9152
9563
|
// A UDS path is not cleaned up when a process is killed, so our own leftover
|
|
9153
9564
|
// must never be the reason we refuse to start.
|
|
9154
9565
|
unlinkStale: (p) => {
|
|
9155
9566
|
try {
|
|
9156
|
-
|
|
9567
|
+
fs16.unlinkSync(p);
|
|
9157
9568
|
} catch {
|
|
9158
9569
|
}
|
|
9159
9570
|
},
|
|
9160
9571
|
log: (m) => console.error(chalk2.dim(m))
|
|
9161
9572
|
});
|
|
9162
9573
|
await listener.start();
|
|
9574
|
+
registerSenderIdentity(socketPath);
|
|
9163
9575
|
shared = listener;
|
|
9164
9576
|
return shared;
|
|
9165
9577
|
}
|
|
@@ -9207,11 +9619,11 @@ async function buildCaptainChannelWithRetry(opts = {}) {
|
|
|
9207
9619
|
|
|
9208
9620
|
// packages/cli/src/squadrantd.ts
|
|
9209
9621
|
var SELF_PATH2 = fileURLToPath3(import.meta.url);
|
|
9210
|
-
var CLI_BIN =
|
|
9622
|
+
var CLI_BIN = join22(dirname4(SELF_PATH2), "index.js");
|
|
9211
9623
|
function readPkgVersion() {
|
|
9212
9624
|
try {
|
|
9213
|
-
const pkgPath =
|
|
9214
|
-
return JSON.parse(
|
|
9625
|
+
const pkgPath = join22(dirname4(SELF_PATH2), "..", "package.json");
|
|
9626
|
+
return JSON.parse(readFileSync15(pkgPath, "utf-8")).version ?? "unknown";
|
|
9215
9627
|
} catch {
|
|
9216
9628
|
return "unknown";
|
|
9217
9629
|
}
|
|
@@ -9328,7 +9740,7 @@ function startSquadrantd(opts = {}) {
|
|
|
9328
9740
|
(r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
|
|
9329
9741
|
);
|
|
9330
9742
|
},
|
|
9331
|
-
cursorFile:
|
|
9743
|
+
cursorFile: join22(stateRoot, "cmux-events.seq"),
|
|
9332
9744
|
log
|
|
9333
9745
|
});
|
|
9334
9746
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
@@ -9584,9 +9996,20 @@ function logCrashMarker(kind, err) {
|
|
|
9584
9996
|
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
9585
9997
|
process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
|
|
9586
9998
|
`);
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9999
|
+
const stateRoot = join22(homedir13(), ".config", "squadrant", "state");
|
|
10000
|
+
writeExitMarker(stateRoot, {
|
|
10001
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10002
|
+
pid: process.pid,
|
|
10003
|
+
reason: kind,
|
|
10004
|
+
ppid: process.ppid,
|
|
10005
|
+
uptimeMs: Math.round(process.uptime() * 1e3),
|
|
10006
|
+
inFlightDelivery: null
|
|
10007
|
+
// crash path has no access to the delivery loop's live state
|
|
10008
|
+
}, (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
|
|
10009
|
+
`));
|
|
10010
|
+
}
|
|
10011
|
+
function isMonorepoCheckout(scriptPath, dirExists = existsSync13) {
|
|
10012
|
+
return dirExists(join22(dirname4(resolve3(scriptPath)), "..", "packages"));
|
|
9590
10013
|
}
|
|
9591
10014
|
function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
9592
10015
|
try {
|
|
@@ -9595,7 +10018,7 @@ function isLinkedWorktree(scriptPath, statFile = (p) => {
|
|
|
9595
10018
|
return void 0;
|
|
9596
10019
|
}
|
|
9597
10020
|
}) {
|
|
9598
|
-
const dotGit =
|
|
10021
|
+
const dotGit = join22(dirname4(resolve3(scriptPath)), "..", ".git");
|
|
9599
10022
|
return statFile(dotGit)?.isFile === true;
|
|
9600
10023
|
}
|
|
9601
10024
|
if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
|