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/index.js
CHANGED
|
@@ -86,13 +86,22 @@ var init_config_io = __esm({
|
|
|
86
86
|
import path2 from "path";
|
|
87
87
|
import os from "os";
|
|
88
88
|
import chalk from "chalk";
|
|
89
|
+
function isThinkingLevel(v2) {
|
|
90
|
+
return THINKING_LEVELS.includes(v2);
|
|
91
|
+
}
|
|
92
|
+
function parseThinkingLevel(v2) {
|
|
93
|
+
if (!isThinkingLevel(v2)) {
|
|
94
|
+
throw new Error(`Invalid --thinking value '${v2}'. Valid values: ${THINKING_LEVELS.join(", ")}`);
|
|
95
|
+
}
|
|
96
|
+
return v2;
|
|
97
|
+
}
|
|
89
98
|
function resolveControlChannelMode(cfg, agent) {
|
|
90
|
-
const
|
|
91
|
-
return
|
|
99
|
+
const v2 = cfg?.[agent];
|
|
100
|
+
return v2 && CONTROL_CHANNEL_MODES.has(v2) ? v2 : "off";
|
|
92
101
|
}
|
|
93
102
|
function resolveCaptainChannelMode(defaults) {
|
|
94
|
-
const
|
|
95
|
-
return
|
|
103
|
+
const v2 = defaults?.captainChannel;
|
|
104
|
+
return v2 && CONTROL_CHANNEL_MODES.has(v2) ? v2 : "off";
|
|
96
105
|
}
|
|
97
106
|
function getDefaultConfig() {
|
|
98
107
|
return {
|
|
@@ -178,10 +187,11 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
178
187
|
function resolveHome(p) {
|
|
179
188
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
180
189
|
}
|
|
181
|
-
var CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
|
|
190
|
+
var THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
|
|
182
191
|
var init_config = __esm({
|
|
183
192
|
"packages/shared/dist/config.js"() {
|
|
184
193
|
init_config_io();
|
|
194
|
+
THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
185
195
|
CONTROL_CHANNEL_MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
|
|
186
196
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
187
197
|
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
@@ -209,8 +219,8 @@ function deepMerge(base, patch) {
|
|
|
209
219
|
if (patch === null || typeof patch !== "object" || Array.isArray(patch))
|
|
210
220
|
return patch ?? base;
|
|
211
221
|
const out = { ...base };
|
|
212
|
-
for (const [k,
|
|
213
|
-
out[k] = deepMerge(out[k],
|
|
222
|
+
for (const [k, v2] of Object.entries(patch)) {
|
|
223
|
+
out[k] = deepMerge(out[k], v2);
|
|
214
224
|
}
|
|
215
225
|
return out;
|
|
216
226
|
}
|
|
@@ -720,7 +730,7 @@ function isCacheStale(state, now, intervalMs = CHECK_INTERVAL_MS, failureInterva
|
|
|
720
730
|
return now - state.lastChecked >= (state.lastCheckFailed ? failureIntervalMs : intervalMs);
|
|
721
731
|
}
|
|
722
732
|
function isNewerVersion(latest, current) {
|
|
723
|
-
const parse2 = (
|
|
733
|
+
const parse2 = (v2) => v2.trim().replace(/^v/, "").split("-")[0].split(".").map((n) => Number(n) || 0);
|
|
724
734
|
const [la = 0, lb = 0, lc = 0] = parse2(latest);
|
|
725
735
|
const [ca = 0, cb = 0, cc = 0] = parse2(current);
|
|
726
736
|
if (la !== ca)
|
|
@@ -1101,8 +1111,8 @@ var init_runtime_sync = __esm({
|
|
|
1101
1111
|
});
|
|
1102
1112
|
|
|
1103
1113
|
// packages/shared/dist/lib/tool-compat.js
|
|
1104
|
-
function parseSemVer(
|
|
1105
|
-
const m =
|
|
1114
|
+
function parseSemVer(v2) {
|
|
1115
|
+
const m = v2.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
1106
1116
|
if (!m)
|
|
1107
1117
|
return null;
|
|
1108
1118
|
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
@@ -1374,6 +1384,7 @@ __export(dist_exports, {
|
|
|
1374
1384
|
SPOKE_SUBDIRS: () => SPOKE_SUBDIRS,
|
|
1375
1385
|
TERMINAL_STATES: () => TERMINAL_STATES,
|
|
1376
1386
|
TERMINAL_WORK_STATES: () => TERMINAL_WORK_STATES,
|
|
1387
|
+
THINKING_LEVELS: () => THINKING_LEVELS,
|
|
1377
1388
|
UPDATE_CHECK_STATE_PATH: () => UPDATE_CHECK_STATE_PATH,
|
|
1378
1389
|
addWorktree: () => addWorktree,
|
|
1379
1390
|
applySafeFixes: () => applySafeFixes,
|
|
@@ -1407,6 +1418,7 @@ __export(dist_exports, {
|
|
|
1407
1418
|
isDaemonCachedKey: () => isDaemonCachedKey,
|
|
1408
1419
|
isNewerVersion: () => isNewerVersion,
|
|
1409
1420
|
isQuieter: () => isQuieter,
|
|
1421
|
+
isThinkingLevel: () => isThinkingLevel,
|
|
1410
1422
|
isUpdateCheckDisabled: () => isUpdateCheckDisabled,
|
|
1411
1423
|
iso: () => iso,
|
|
1412
1424
|
loadConfig: () => loadConfig,
|
|
@@ -1417,6 +1429,7 @@ __export(dist_exports, {
|
|
|
1417
1429
|
needsCheck: () => needsCheck,
|
|
1418
1430
|
notifyIfUpdateAvailable: () => notifyIfUpdateAvailable,
|
|
1419
1431
|
parseSection: () => parseSection,
|
|
1432
|
+
parseThinkingLevel: () => parseThinkingLevel,
|
|
1420
1433
|
probeCmuxDaemonDirect: () => probeCmuxDaemonDirect,
|
|
1421
1434
|
projectConfigPath: () => projectConfigPath,
|
|
1422
1435
|
readConfigFileSync: () => readConfigFileSync,
|
|
@@ -1491,8 +1504,9 @@ function isStickyAttention(state) {
|
|
|
1491
1504
|
function nextPendingTool(current, ev, now) {
|
|
1492
1505
|
if (ev.note === "agent.hook.PreToolUse")
|
|
1493
1506
|
return { name: ev.tool ?? "tool", since: now };
|
|
1494
|
-
if (ev.note === "posttooluse" || ev.note === "agent.hook.UserPromptSubmit")
|
|
1507
|
+
if (ev.note === "posttooluse" || ev.note === "agent.hook.PostToolUse" || ev.note === "agent.hook.UserPromptSubmit") {
|
|
1495
1508
|
return void 0;
|
|
1509
|
+
}
|
|
1496
1510
|
return current;
|
|
1497
1511
|
}
|
|
1498
1512
|
function nextPendingMonitor(current, ev, now) {
|
|
@@ -1595,6 +1609,7 @@ function reduce(rec, ev, now) {
|
|
|
1595
1609
|
case "task.stalled":
|
|
1596
1610
|
case "task.idle":
|
|
1597
1611
|
case "task.quiet":
|
|
1612
|
+
case "task.warn":
|
|
1598
1613
|
case "task.timeout":
|
|
1599
1614
|
case "task.reconcile-failed":
|
|
1600
1615
|
return rec;
|
|
@@ -1616,6 +1631,15 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStal
|
|
|
1616
1631
|
if (rec.pendingTool) {
|
|
1617
1632
|
if (now - rec.pendingTool.since <= toolStallMs)
|
|
1618
1633
|
return null;
|
|
1634
|
+
if (rec.lastEvent === "task.turn.completed") {
|
|
1635
|
+
return {
|
|
1636
|
+
...rec,
|
|
1637
|
+
state: "awaiting-input",
|
|
1638
|
+
pendingTool: void 0,
|
|
1639
|
+
pendingMonitor: void 0,
|
|
1640
|
+
lastEvent: "watchdog.tool-stall-recovered"
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1619
1643
|
return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
|
|
1620
1644
|
}
|
|
1621
1645
|
if (rec.pendingMonitor) {
|
|
@@ -1937,7 +1961,7 @@ function createDaemon(deps) {
|
|
|
1937
1961
|
}
|
|
1938
1962
|
}
|
|
1939
1963
|
}
|
|
1940
|
-
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
|
|
1964
|
+
if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state) && r.state !== "awaiting-input") {
|
|
1941
1965
|
const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
1942
1966
|
const refTime = r.workingStretchStartedAt ?? r.createdAt;
|
|
1943
1967
|
if (t - refTime > ceiling) {
|
|
@@ -1983,6 +2007,11 @@ function createDaemon(deps) {
|
|
|
1983
2007
|
const idle = evaluateStall(r, t);
|
|
1984
2008
|
if (idle) {
|
|
1985
2009
|
store.put(idle);
|
|
2010
|
+
if (idle.state === "awaiting-input") {
|
|
2011
|
+
const recoveredEvent = { type: "task.turn.completed", id: r.id, turnId: "watchdog-recover" };
|
|
2012
|
+
firePush(deps, r.project, r.state, idle, recoveredEvent, lastCaptainTurnAt.get(r.id));
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
1986
2015
|
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 };
|
|
1987
2016
|
firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
|
|
1988
2017
|
continue;
|
|
@@ -2090,6 +2119,7 @@ var init_reduce = __esm({
|
|
|
2090
2119
|
"task.stalled",
|
|
2091
2120
|
"task.idle",
|
|
2092
2121
|
"task.quiet",
|
|
2122
|
+
"task.warn",
|
|
2093
2123
|
"task.timeout",
|
|
2094
2124
|
"task.reconcile-failed",
|
|
2095
2125
|
"task.cancelled",
|
|
@@ -2493,12 +2523,12 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
|
|
|
2493
2523
|
return;
|
|
2494
2524
|
}
|
|
2495
2525
|
const conn = createConnection(sockPath);
|
|
2496
|
-
const finish = (
|
|
2526
|
+
const finish = (v2) => {
|
|
2497
2527
|
try {
|
|
2498
2528
|
conn.destroy();
|
|
2499
2529
|
} catch {
|
|
2500
2530
|
}
|
|
2501
|
-
resolve4(
|
|
2531
|
+
resolve4(v2);
|
|
2502
2532
|
};
|
|
2503
2533
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
2504
2534
|
conn.on("connect", () => {
|
|
@@ -2588,7 +2618,18 @@ function projectHealth(input) {
|
|
|
2588
2618
|
ref: captainName,
|
|
2589
2619
|
state: captainState,
|
|
2590
2620
|
lastSeenMs: null,
|
|
2591
|
-
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
|
|
2621
|
+
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
|
|
2622
|
+
});
|
|
2623
|
+
const deliveryState = captainState === "stopped" ? "stopped" : deferral?.stuck || deferral && deferral.maxDeferCount > 0 ? "stale" : "alive";
|
|
2624
|
+
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;
|
|
2625
|
+
out.push({
|
|
2626
|
+
kind: "delivery",
|
|
2627
|
+
project,
|
|
2628
|
+
ref: "delivery",
|
|
2629
|
+
state: deliveryState,
|
|
2630
|
+
lastSeenMs: null,
|
|
2631
|
+
detail: deliveryDetail,
|
|
2632
|
+
stuck: deferral?.stuck
|
|
2592
2633
|
});
|
|
2593
2634
|
if (commandPresent !== null) {
|
|
2594
2635
|
out.push({
|
|
@@ -2690,8 +2731,23 @@ function createStore(root) {
|
|
|
2690
2731
|
};
|
|
2691
2732
|
const projDir = (p) => assertUnderRoot(join6(root, safeSegment("project", p)));
|
|
2692
2733
|
const taskFile = (p, id) => assertUnderRoot(join6(projDir(p), `${safeSegment("id", id)}.json`));
|
|
2734
|
+
const readRecord = (project, id) => {
|
|
2735
|
+
const f = taskFile(project, id);
|
|
2736
|
+
if (!existsSync6(f))
|
|
2737
|
+
return void 0;
|
|
2738
|
+
try {
|
|
2739
|
+
return JSON.parse(readFileSync4(f, "utf-8"));
|
|
2740
|
+
} catch {
|
|
2741
|
+
return void 0;
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2693
2744
|
return {
|
|
2694
2745
|
put(rec) {
|
|
2746
|
+
const existing = readRecord(rec.project, rec.id);
|
|
2747
|
+
if (existing && TERMINAL_STATES.has(existing.state) && TERMINAL_STATES.has(rec.state) && (existing.state !== rec.state || existing.lastEvent !== rec.lastEvent)) {
|
|
2748
|
+
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)`);
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2695
2751
|
mkdirSync2(projDir(rec.project), { recursive: true });
|
|
2696
2752
|
const dest = taskFile(rec.project, rec.id);
|
|
2697
2753
|
const tmp = `${dest}.tmp`;
|
|
@@ -2699,14 +2755,7 @@ function createStore(root) {
|
|
|
2699
2755
|
renameSync(tmp, dest);
|
|
2700
2756
|
},
|
|
2701
2757
|
get(project, id) {
|
|
2702
|
-
|
|
2703
|
-
if (!existsSync6(f))
|
|
2704
|
-
return void 0;
|
|
2705
|
-
try {
|
|
2706
|
-
return JSON.parse(readFileSync4(f, "utf-8"));
|
|
2707
|
-
} catch {
|
|
2708
|
-
return void 0;
|
|
2709
|
-
}
|
|
2758
|
+
return readRecord(project, id);
|
|
2710
2759
|
},
|
|
2711
2760
|
list(project) {
|
|
2712
2761
|
const d = projDir(project);
|
|
@@ -2745,6 +2794,7 @@ function createStore(root) {
|
|
|
2745
2794
|
}
|
|
2746
2795
|
var init_store = __esm({
|
|
2747
2796
|
"packages/core/dist/store.js"() {
|
|
2797
|
+
init_dist();
|
|
2748
2798
|
}
|
|
2749
2799
|
});
|
|
2750
2800
|
|
|
@@ -3111,7 +3161,7 @@ function computeDaemonDrift(nodeBin) {
|
|
|
3111
3161
|
const foreignInstall = detectForeignInstall(parsedCurrent, entry, parsedCurrent !== null && existsSync8(parsedCurrent.daemonEntry));
|
|
3112
3162
|
return { plistPath: p, target, desired, current, changed, programChanged, foreignInstall };
|
|
3113
3163
|
}
|
|
3114
|
-
function
|
|
3164
|
+
function reconcilePlistAndService(drift) {
|
|
3115
3165
|
if (drift.changed) {
|
|
3116
3166
|
mkdirSync4(dirname2(drift.plistPath), { recursive: true });
|
|
3117
3167
|
writeFileSync5(drift.plistPath, drift.desired);
|
|
@@ -3127,8 +3177,59 @@ function applyDaemonDrift(drift) {
|
|
|
3127
3177
|
execFileSync3("launchctl", ["bootstrap", `gui/${uid}`, drift.plistPath], { stdio: "ignore" });
|
|
3128
3178
|
} catch {
|
|
3129
3179
|
}
|
|
3180
|
+
}
|
|
3181
|
+
function applyDaemonDrift(drift) {
|
|
3182
|
+
reconcilePlistAndService(drift);
|
|
3130
3183
|
execFileSync3("launchctl", ["kickstart", drift.target], { stdio: "ignore" });
|
|
3131
3184
|
}
|
|
3185
|
+
function getDaemonPid(target) {
|
|
3186
|
+
try {
|
|
3187
|
+
const out = execFileSync3("launchctl", ["print", target], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
3188
|
+
const m = out.match(/\bpid\s*=\s*(\d+)/);
|
|
3189
|
+
return m ? parseInt(m[1], 10) : null;
|
|
3190
|
+
} catch {
|
|
3191
|
+
return null;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
function forceKickstartAndVerify(target, opts = {}) {
|
|
3195
|
+
const pollAttempts = opts.pollAttempts ?? 15;
|
|
3196
|
+
const pollDelayMs = opts.pollDelayMs ?? 300;
|
|
3197
|
+
const kickstartRetries = opts.kickstartRetries ?? 10;
|
|
3198
|
+
const kickstartRetryDelayMs = opts.kickstartRetryDelayMs ?? 300;
|
|
3199
|
+
const pidBefore = getDaemonPid(target);
|
|
3200
|
+
let kickstartError = null;
|
|
3201
|
+
for (let i = 0; i < kickstartRetries; i++) {
|
|
3202
|
+
try {
|
|
3203
|
+
execFileSync3("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
|
|
3204
|
+
kickstartError = null;
|
|
3205
|
+
break;
|
|
3206
|
+
} catch (e) {
|
|
3207
|
+
kickstartError = e;
|
|
3208
|
+
if (i < kickstartRetries - 1) {
|
|
3209
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, kickstartRetryDelayMs);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
let pidAfter = null;
|
|
3214
|
+
for (let i = 0; i < pollAttempts; i++) {
|
|
3215
|
+
pidAfter = getDaemonPid(target);
|
|
3216
|
+
if (pidAfter !== null && pidAfter !== pidBefore)
|
|
3217
|
+
break;
|
|
3218
|
+
if (i < pollAttempts - 1) {
|
|
3219
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, pollDelayMs);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
const restarted = pidAfter !== null && pidAfter !== pidBefore;
|
|
3223
|
+
if (kickstartError && !restarted)
|
|
3224
|
+
throw kickstartError;
|
|
3225
|
+
return {
|
|
3226
|
+
target,
|
|
3227
|
+
pidBefore,
|
|
3228
|
+
pidAfter,
|
|
3229
|
+
restarted,
|
|
3230
|
+
...kickstartError ? { note: "kickstart -k refused; daemon restarted by bootstrap" } : {}
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3132
3233
|
function isOperatorInitiatedCommand(topLevelArg) {
|
|
3133
3234
|
return topLevelArg !== void 0 && OPERATOR_INITIATED_COMMANDS.has(topLevelArg);
|
|
3134
3235
|
}
|
|
@@ -3170,11 +3271,18 @@ function printForeignInstallError(foreign) {
|
|
|
3170
3271
|
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.
|
|
3171
3272
|
`;
|
|
3172
3273
|
}
|
|
3173
|
-
function reregisterDaemon(nodeBin = process.execPath) {
|
|
3174
|
-
if (!tryAcquireDaemonLock())
|
|
3175
|
-
|
|
3274
|
+
function reregisterDaemon(nodeBin = process.execPath, kickstartOpts = {}) {
|
|
3275
|
+
if (!tryAcquireDaemonLock()) {
|
|
3276
|
+
throw new Error("could not acquire the daemon lock \u2014 another squadrant process is already restarting the daemon");
|
|
3277
|
+
}
|
|
3176
3278
|
try {
|
|
3177
|
-
|
|
3279
|
+
const drift = computeDaemonDrift(nodeBin);
|
|
3280
|
+
reconcilePlistAndService(drift);
|
|
3281
|
+
const result = forceKickstartAndVerify(drift.target, kickstartOpts);
|
|
3282
|
+
if (!result.restarted) {
|
|
3283
|
+
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`);
|
|
3284
|
+
}
|
|
3285
|
+
return result;
|
|
3178
3286
|
} finally {
|
|
3179
3287
|
releaseDaemonLock();
|
|
3180
3288
|
}
|
|
@@ -3562,6 +3670,10 @@ function detectTrailingQuestion(text) {
|
|
|
3562
3670
|
return lastLine;
|
|
3563
3671
|
return null;
|
|
3564
3672
|
}
|
|
3673
|
+
function isQuotedLine(raw) {
|
|
3674
|
+
const noAnsi = raw.replace(/\[[0-9;]*m/g, "");
|
|
3675
|
+
return QUOTED_PREFIX_RE.test(noAnsi);
|
|
3676
|
+
}
|
|
3565
3677
|
function stripChrome(raw) {
|
|
3566
3678
|
let line = raw.replace(/\[[0-9;]*m/g, "");
|
|
3567
3679
|
line = line.replace(/^[\s│┃▏▕|]+/, "").replace(/[\s│┃▏▕|]+$/, "");
|
|
@@ -3620,8 +3732,11 @@ function classifyPaneTail(tail) {
|
|
|
3620
3732
|
if (q)
|
|
3621
3733
|
return { kind: "question", text: q };
|
|
3622
3734
|
let errLine = null;
|
|
3623
|
-
for (
|
|
3624
|
-
|
|
3735
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
3736
|
+
const c = cleaned[i];
|
|
3737
|
+
if (c == null || isQuotedLine(raw[i]))
|
|
3738
|
+
continue;
|
|
3739
|
+
if (ERROR_BANNER_RE.some((re) => re.test(c)))
|
|
3625
3740
|
errLine = c;
|
|
3626
3741
|
}
|
|
3627
3742
|
if (errLine)
|
|
@@ -3664,6 +3779,22 @@ function createInteractiveProbe(deps) {
|
|
|
3664
3779
|
const verdict = classifyPaneTail(tail);
|
|
3665
3780
|
if (!verdict)
|
|
3666
3781
|
continue;
|
|
3782
|
+
if (verdict.kind === "error") {
|
|
3783
|
+
const alive = deps.checkAlive ? await deps.checkAlive(rec) : "unknown";
|
|
3784
|
+
if (alive !== "gone") {
|
|
3785
|
+
const message = `CREW WARN ${rec.name}: pane shows an error string \u2014 crew still ${alive}, not terminalized (pane-detected): ${verdict.text}`;
|
|
3786
|
+
deps.log(`probe -> ${message}`);
|
|
3787
|
+
if (deps.notify) {
|
|
3788
|
+
const warnEvent = { type: "task.warn", id: rec.id, message };
|
|
3789
|
+
try {
|
|
3790
|
+
await deps.notify({ project: rec.project, message, record: rec, event: warnEvent });
|
|
3791
|
+
} catch (e) {
|
|
3792
|
+
deps.log(`probe notify failed for ${rec.id}: ${e.message}`);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
continue;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3667
3798
|
const event = verdict.kind === "error" ? {
|
|
3668
3799
|
type: "task.failed",
|
|
3669
3800
|
id: rec.id,
|
|
@@ -3685,7 +3816,7 @@ function createInteractiveProbe(deps) {
|
|
|
3685
3816
|
}
|
|
3686
3817
|
return { tick };
|
|
3687
3818
|
}
|
|
3688
|
-
var STALE_THRESHOLD_MS, PROBE_QUIET_MS, ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE;
|
|
3819
|
+
var STALE_THRESHOLD_MS, PROBE_QUIET_MS, ERROR_BANNER_RE, OPTION_RE, PICKER_FOOTER_RE, PURE_CHROME_RE, STATUS_LINE_RE, QUOTED_PREFIX_RE;
|
|
3689
3820
|
var init_interactive_probe = __esm({
|
|
3690
3821
|
"packages/core/dist/daemon/interactive-probe.js"() {
|
|
3691
3822
|
STALE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
@@ -3701,6 +3832,7 @@ var init_interactive_probe = __esm({
|
|
|
3701
3832
|
PICKER_FOOTER_RE = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
|
|
3702
3833
|
PURE_CHROME_RE = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
|
|
3703
3834
|
STATUS_LINE_RE = /accept edits on|shift\+tab|⏵⏵|\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;
|
|
3835
|
+
QUOTED_PREFIX_RE = /^\s*(?:[┃│▏▕]|>|[+-]|\d+[\t:→])\s/;
|
|
3704
3836
|
}
|
|
3705
3837
|
});
|
|
3706
3838
|
|
|
@@ -3716,6 +3848,7 @@ function createProbes(ctx) {
|
|
|
3716
3848
|
};
|
|
3717
3849
|
function buildInteractiveProbe(deps) {
|
|
3718
3850
|
const directPaneReader = createDirectCrewPaneReader(deps.cmux, captainNameForProject);
|
|
3851
|
+
const checkAlive = createDirectSurfaceLivenessProbe(deps.cmux, captainNameForProject);
|
|
3719
3852
|
const probe = createInteractiveProbe({
|
|
3720
3853
|
project: "_all_",
|
|
3721
3854
|
listTasks: async () => store.listAll(),
|
|
@@ -3727,7 +3860,9 @@ function createProbes(ctx) {
|
|
|
3727
3860
|
}
|
|
3728
3861
|
},
|
|
3729
3862
|
now: () => Date.now(),
|
|
3730
|
-
log
|
|
3863
|
+
log,
|
|
3864
|
+
checkAlive,
|
|
3865
|
+
notify: ctx.notify
|
|
3731
3866
|
});
|
|
3732
3867
|
let probing = false;
|
|
3733
3868
|
return async () => {
|
|
@@ -4002,6 +4137,33 @@ import fs9 from "fs";
|
|
|
4002
4137
|
import os4 from "os";
|
|
4003
4138
|
import path9 from "path";
|
|
4004
4139
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4140
|
+
async function pollFirstTurnConfirmedAt(getTaskRecord, project, id) {
|
|
4141
|
+
const deadline = Date.now() + FIRST_TURN_HOOK_CONFIRM_WINDOW_MS;
|
|
4142
|
+
for (; ; ) {
|
|
4143
|
+
const rec = await getTaskRecord(project, id).catch(() => void 0);
|
|
4144
|
+
if (rec?.firstTurnConfirmedAt)
|
|
4145
|
+
return true;
|
|
4146
|
+
if (Date.now() >= deadline)
|
|
4147
|
+
return false;
|
|
4148
|
+
await new Promise((r) => setTimeout(r, FIRST_TURN_HOOK_POLL_INTERVAL_MS));
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
function firstTrueOrBothFalse(a, b) {
|
|
4152
|
+
return new Promise((resolve4) => {
|
|
4153
|
+
let settledFalseCount = 0;
|
|
4154
|
+
const onSettle = (ok2) => {
|
|
4155
|
+
if (ok2) {
|
|
4156
|
+
resolve4(true);
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
settledFalseCount++;
|
|
4160
|
+
if (settledFalseCount === 2)
|
|
4161
|
+
resolve4(false);
|
|
4162
|
+
};
|
|
4163
|
+
a.then(onSettle, () => onSettle(false));
|
|
4164
|
+
b.then(onSettle, () => onSettle(false));
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4005
4167
|
async function listCrewPanes(runtime, workspaceId, project) {
|
|
4006
4168
|
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
4007
4169
|
return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
|
|
@@ -4107,6 +4269,7 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4107
4269
|
const crewRole = config.defaults.roles?.crew;
|
|
4108
4270
|
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
4109
4271
|
const crewModel = input.model ?? route?.model ?? configModel;
|
|
4272
|
+
const crewThinking = input.thinking ?? config.defaults.roles?.crew?.thinking;
|
|
4110
4273
|
if (agentName !== "claude") {
|
|
4111
4274
|
deps.onModelResolved?.({ agentName, model: crewModel });
|
|
4112
4275
|
}
|
|
@@ -4142,7 +4305,8 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4142
4305
|
// crew apart from an unrelated session instead of an auto-derived cwd
|
|
4143
4306
|
// basename (only the claude driver reads this — other agents ignore it).
|
|
4144
4307
|
sessionName: crewSessionName(input.project, name),
|
|
4145
|
-
...crewModel ? { model: crewModel } : {}
|
|
4308
|
+
...crewModel ? { model: crewModel } : {},
|
|
4309
|
+
...crewThinking ? { thinking: crewThinking } : {}
|
|
4146
4310
|
});
|
|
4147
4311
|
const direction2 = input.direction ?? "tab";
|
|
4148
4312
|
const title2 = titleFor(input.project, name);
|
|
@@ -4150,10 +4314,18 @@ async function runCrewSpawn(input, config, deps) {
|
|
|
4150
4314
|
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
4151
4315
|
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${niceCrewCommand(cliCommand2)}`);
|
|
4152
4316
|
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
4153
|
-
|
|
4317
|
+
let claudeFirstTurn = firstTurnTask;
|
|
4318
|
+
if (Buffer.byteLength(claudeFirstTurn, "utf8") > FIRST_TURN_INLINE_MAX_BYTES) {
|
|
4319
|
+
const spillFile = path9.join(os4.tmpdir(), `squadrant-task-${rec.id}.md`);
|
|
4320
|
+
fs9.writeFileSync(spillFile, claudeFirstTurn, "utf8");
|
|
4321
|
+
claudeFirstTurn = `Full task is at ${spillFile} \u2014 cat it and follow it exactly.`;
|
|
4322
|
+
}
|
|
4323
|
+
const sendPromise = deps.sendFirstTurn(pane2, `${claudeFirstTurn}
|
|
4154
4324
|
|
|
4155
4325
|
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
4156
|
-
|
|
4326
|
+
const scrapeDelivered = sendPromise.then((r) => r.delivered).catch(() => false);
|
|
4327
|
+
const delivered = hooksInstalled && deps.getTaskRecord ? await firstTrueOrBothFalse(scrapeDelivered, pollFirstTurnConfirmedAt(deps.getTaskRecord, input.project, rec.id)) : await scrapeDelivered;
|
|
4328
|
+
if (!delivered) {
|
|
4157
4329
|
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.
|
|
4158
4330
|
`);
|
|
4159
4331
|
} else if (!hooksInstalled) {
|
|
@@ -4250,7 +4422,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
4250
4422
|
if (!crew) {
|
|
4251
4423
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
4252
4424
|
}
|
|
4253
|
-
const blockedByModalMessage = () => `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) \u2014 message NOT delivered, to avoid confirming its default option.
|
|
4425
|
+
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>.`;
|
|
4254
4426
|
if (deps.isBlockedByModal && await deps.isBlockedByModal(crew)) {
|
|
4255
4427
|
throw new Error(blockedByModalMessage());
|
|
4256
4428
|
}
|
|
@@ -4264,10 +4436,16 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
4264
4436
|
const heldForMin = Math.round((Date.now() - task.operatorHold.since) / 6e4);
|
|
4265
4437
|
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.`);
|
|
4266
4438
|
}
|
|
4439
|
+
const isAttentionState = task?.state === "blocked" || task?.state === "awaiting-input" || task?.state === "review";
|
|
4440
|
+
if (task && !isAttentionState && task.firstTurnConfirmedAt && task.task === message) {
|
|
4441
|
+
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.`);
|
|
4442
|
+
}
|
|
4443
|
+
let reopened = false;
|
|
4267
4444
|
try {
|
|
4268
4445
|
if (task) {
|
|
4269
4446
|
if (TERMINAL_STATES.has(task.state)) {
|
|
4270
4447
|
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
4448
|
+
reopened = true;
|
|
4271
4449
|
} else if (task.state === "blocked" || task.state === "awaiting-input" || task.state === "review") {
|
|
4272
4450
|
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
4273
4451
|
}
|
|
@@ -4293,7 +4471,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
4293
4471
|
throw new Error(`Message to crew '${name}' is held: ${outcome.reason}. Resolve it in the crew's session, then re-send.`);
|
|
4294
4472
|
}
|
|
4295
4473
|
if (!fallsBackToPane(outcome)) {
|
|
4296
|
-
return;
|
|
4474
|
+
return { reopened };
|
|
4297
4475
|
}
|
|
4298
4476
|
}
|
|
4299
4477
|
if (mode === "shadow" && channel && task) {
|
|
@@ -4316,7 +4494,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
4316
4494
|
if (!paneOk) {
|
|
4317
4495
|
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}'.`);
|
|
4318
4496
|
}
|
|
4319
|
-
return;
|
|
4497
|
+
return { reopened };
|
|
4320
4498
|
}
|
|
4321
4499
|
const { delivered, blockedByModal } = await deliver(crew, message);
|
|
4322
4500
|
if (blockedByModal) {
|
|
@@ -4325,6 +4503,7 @@ async function runCrewSend(project, name, message, runtime, workspaceId, deps, o
|
|
|
4325
4503
|
if (!delivered) {
|
|
4326
4504
|
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}'.`);
|
|
4327
4505
|
}
|
|
4506
|
+
return { reopened };
|
|
4328
4507
|
}
|
|
4329
4508
|
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
4330
4509
|
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
@@ -4427,7 +4606,7 @@ async function runCrewList(project, runtime, workspaceId) {
|
|
|
4427
4606
|
surfaceId: c.surfaceId
|
|
4428
4607
|
}));
|
|
4429
4608
|
}
|
|
4430
|
-
var CC_SOCKS_DIR, TEMPLATES_DIR, STATE_ROOT, CLOSE_LOOKUP_RETRIES, CLOSE_LOOKUP_RETRY_DELAY_MS;
|
|
4609
|
+
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;
|
|
4431
4610
|
var init_crew_spawn = __esm({
|
|
4432
4611
|
"packages/core/dist/crew-spawn.js"() {
|
|
4433
4612
|
init_control_channel();
|
|
@@ -4436,8 +4615,11 @@ var init_crew_spawn = __esm({
|
|
|
4436
4615
|
init_crew_protocol();
|
|
4437
4616
|
init_crew_lifecycle();
|
|
4438
4617
|
CC_SOCKS_DIR = "/tmp/cc-socks";
|
|
4618
|
+
FIRST_TURN_INLINE_MAX_BYTES = 1200;
|
|
4439
4619
|
TEMPLATES_DIR = path9.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
4440
4620
|
STATE_ROOT = path9.join(os4.homedir(), ".config", "squadrant", "state");
|
|
4621
|
+
FIRST_TURN_HOOK_CONFIRM_WINDOW_MS = 1e5;
|
|
4622
|
+
FIRST_TURN_HOOK_POLL_INTERVAL_MS = 2e3;
|
|
4441
4623
|
CLOSE_LOOKUP_RETRIES = 3;
|
|
4442
4624
|
CLOSE_LOOKUP_RETRY_DELAY_MS = 150;
|
|
4443
4625
|
}
|
|
@@ -4482,13 +4664,16 @@ var init_captain_channel = __esm({
|
|
|
4482
4664
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
4483
4665
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
4484
4666
|
}
|
|
4485
|
-
function reapOrphanedCrews(store, project) {
|
|
4667
|
+
async function reapOrphanedCrews(store, project, isSurfaceAlive) {
|
|
4486
4668
|
let reaped = 0;
|
|
4487
4669
|
for (const r of store.list(project)) {
|
|
4488
4670
|
if (TERMINAL_STATES.has(r.state))
|
|
4489
4671
|
continue;
|
|
4490
4672
|
if (r.mode !== "interactive")
|
|
4491
4673
|
continue;
|
|
4674
|
+
const liveness = await isSurfaceAlive(r);
|
|
4675
|
+
if (liveness !== "gone")
|
|
4676
|
+
continue;
|
|
4492
4677
|
store.put({ ...r, state: "cancelled", lastEvent: "captain-stopped" });
|
|
4493
4678
|
reaped++;
|
|
4494
4679
|
}
|
|
@@ -4542,10 +4727,13 @@ async function runLivenessTick(deps) {
|
|
|
4542
4727
|
const prev = deps.registry.get(project);
|
|
4543
4728
|
if (prev && prev.lastState === "start")
|
|
4544
4729
|
entry.startedAt = prev.startedAt;
|
|
4730
|
+
const prevState = deriveCaptainState(prev);
|
|
4545
4731
|
deps.registry.apply(entry);
|
|
4546
4732
|
if (winner.pid != null)
|
|
4547
4733
|
deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);
|
|
4548
|
-
|
|
4734
|
+
const updated = deps.registry.get(project);
|
|
4735
|
+
if (deriveCaptainState(updated) !== prevState)
|
|
4736
|
+
logEntry(deps.log, project, updated);
|
|
4549
4737
|
}
|
|
4550
4738
|
for (const e of deps.registry.all()) {
|
|
4551
4739
|
if (e.role !== "captain" || e.lastState !== "start" || seen.has(e.project))
|
|
@@ -4563,12 +4751,13 @@ async function runLivenessTick(deps) {
|
|
|
4563
4751
|
continue;
|
|
4564
4752
|
const state = deriveCaptainState(e);
|
|
4565
4753
|
if (state === "stopped" || state === "gone")
|
|
4566
|
-
deps.reap(e.project);
|
|
4754
|
+
await deps.reap(e.project);
|
|
4567
4755
|
}
|
|
4568
4756
|
}
|
|
4569
4757
|
}
|
|
4570
|
-
function createDelivery(ctx, daemonCmux) {
|
|
4758
|
+
function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
4571
4759
|
const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;
|
|
4760
|
+
const surfaceProbe = isSurfaceAlive ?? (async () => "unknown");
|
|
4572
4761
|
const notifyFault = ctx.notifyFault ?? (() => {
|
|
4573
4762
|
});
|
|
4574
4763
|
const defaultNotify = async (args) => {
|
|
@@ -4591,12 +4780,22 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4591
4780
|
}
|
|
4592
4781
|
};
|
|
4593
4782
|
if (!daemonCmux) {
|
|
4594
|
-
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0 };
|
|
4783
|
+
return { defaultNotify, deliveryTick: void 0, deliveryStats: () => void 0, inFlightDelivery: () => null };
|
|
4595
4784
|
}
|
|
4596
4785
|
const cmux2 = daemonCmux;
|
|
4597
4786
|
const cfg = loadConfig();
|
|
4598
4787
|
const deliveries = /* @__PURE__ */ new Map();
|
|
4599
4788
|
const deliveryStats = (project) => deliveries.get(project)?.stats();
|
|
4789
|
+
const lastDeferred = /* @__PURE__ */ new Map();
|
|
4790
|
+
const inFlightDelivery = () => {
|
|
4791
|
+
let worst2 = null;
|
|
4792
|
+
for (const [project, v2] of lastDeferred) {
|
|
4793
|
+
if (!worst2 || v2.deferCount > worst2.deferCount)
|
|
4794
|
+
worst2 = { project, ...v2 };
|
|
4795
|
+
}
|
|
4796
|
+
return worst2;
|
|
4797
|
+
};
|
|
4798
|
+
const projectBackoff = /* @__PURE__ */ new Map();
|
|
4600
4799
|
const stuckNotified = /* @__PURE__ */ new Set();
|
|
4601
4800
|
const sessionStartMs = Date.now();
|
|
4602
4801
|
let delivering = false;
|
|
@@ -4607,8 +4806,8 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4607
4806
|
isPidAlive,
|
|
4608
4807
|
now: () => Date.now(),
|
|
4609
4808
|
log,
|
|
4610
|
-
reap: (project) => {
|
|
4611
|
-
const reaped = reapOrphanedCrews(store, project);
|
|
4809
|
+
reap: async (project) => {
|
|
4810
|
+
const reaped = await reapOrphanedCrews(store, project, surfaceProbe);
|
|
4612
4811
|
if (reaped > 0) {
|
|
4613
4812
|
const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;
|
|
4614
4813
|
log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);
|
|
@@ -4624,82 +4823,116 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4624
4823
|
cfg.commandName
|
|
4625
4824
|
])];
|
|
4626
4825
|
for (const project of allProjects) {
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
const
|
|
4633
|
-
surface =
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4826
|
+
try {
|
|
4827
|
+
const backoff = projectBackoff.get(project);
|
|
4828
|
+
if (backoff && Date.now() < backoff.nextAttemptAt)
|
|
4829
|
+
continue;
|
|
4830
|
+
const projCfg = cfg.projects?.[project];
|
|
4831
|
+
const captainTitle = project === cfg.commandName ? cfg.commandName : projCfg?.captainName ?? `${project}-captain`;
|
|
4832
|
+
let surface = null;
|
|
4833
|
+
const resolveCaptainSurface = async () => {
|
|
4834
|
+
const wsId = cmux2.findWorkspaceId ? await cmux2.findWorkspaceId(captainTitle) : null;
|
|
4835
|
+
if (!wsId)
|
|
4836
|
+
return injectedSurfaces[project] ?? null;
|
|
4837
|
+
const surfaces = await cmux2.listSurfaces(wsId);
|
|
4838
|
+
return discoverCaptainSurface(surfaces, captainTitle) ?? injectedSurfaces[project] ?? null;
|
|
4839
|
+
};
|
|
4840
|
+
surface = await resolveCaptainSurface();
|
|
4841
|
+
if (!surface)
|
|
4842
|
+
continue;
|
|
4843
|
+
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });
|
|
4844
|
+
const lastAcked = cursor?.lastAckedSeq ?? 0;
|
|
4845
|
+
let d = deliveries.get(project);
|
|
4846
|
+
if (!d) {
|
|
4847
|
+
d = new CaptainDelivery({
|
|
4848
|
+
maxDefers: cfg.delivery?.maxDeferDeliveries ?? 300,
|
|
4849
|
+
stableProbePolls: cfg.delivery?.stableProbePolls ?? 3
|
|
4850
|
+
});
|
|
4851
|
+
deliveries.set(project, d);
|
|
4852
|
+
}
|
|
4853
|
+
for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {
|
|
4854
|
+
if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {
|
|
4855
|
+
if (!TERMINAL_KINDS.has(entry.kind)) {
|
|
4856
|
+
const isExemptMessage = entry.kind === "captain.message" && entry.payload?.source !== "daemon";
|
|
4857
|
+
if (!isExemptMessage) {
|
|
4858
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);
|
|
4859
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4860
|
+
continue;
|
|
4861
|
+
}
|
|
4862
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);
|
|
4863
|
+
} else {
|
|
4864
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);
|
|
4657
4865
|
}
|
|
4658
|
-
|
|
4866
|
+
}
|
|
4867
|
+
const result = await d.deliver(entry, async (text, sendOpts) => {
|
|
4868
|
+
let handledByChannel = false;
|
|
4869
|
+
try {
|
|
4870
|
+
const mode = ctx.captainChannelMode?.() ?? "off";
|
|
4871
|
+
const r = await deliverToCaptain(project, text, {
|
|
4872
|
+
channel: ctx.captainChannel,
|
|
4873
|
+
mode,
|
|
4874
|
+
log
|
|
4875
|
+
});
|
|
4876
|
+
handledByChannel = r.handled;
|
|
4877
|
+
} catch (e) {
|
|
4878
|
+
log(`captain-channel ${project}: threw, falling back to pane \u2014 ${e.message}`);
|
|
4879
|
+
}
|
|
4880
|
+
if (handledByChannel) {
|
|
4881
|
+
return;
|
|
4882
|
+
}
|
|
4883
|
+
try {
|
|
4884
|
+
return await cmux2.send(surface, text, sendOpts);
|
|
4885
|
+
} catch (e) {
|
|
4886
|
+
if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
|
|
4887
|
+
throw e;
|
|
4888
|
+
const next = await resolveCaptainSurface();
|
|
4889
|
+
const same = next !== null && next.workspaceId === surface.workspaceId && next.surfaceId === surface.surfaceId;
|
|
4890
|
+
if (!next || same) {
|
|
4891
|
+
log(`delivery project=${project}: probe-failed but surface re-resolution found ${next ? "the same dead surface" : "no captain surface"} \u2014 deferring`);
|
|
4892
|
+
throw e;
|
|
4893
|
+
}
|
|
4894
|
+
log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
|
|
4895
|
+
surface = next;
|
|
4896
|
+
return cmux2.send(next, text, sendOpts);
|
|
4897
|
+
}
|
|
4898
|
+
});
|
|
4899
|
+
if ("delivered" in result) {
|
|
4900
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
4901
|
+
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4902
|
+
lastDeferred.delete(project);
|
|
4903
|
+
projectBackoff.delete(project);
|
|
4659
4904
|
} else {
|
|
4660
|
-
|
|
4905
|
+
const { maxDeferCount, stuck: stuck2 } = d.stats();
|
|
4906
|
+
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4907
|
+
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4908
|
+
}
|
|
4909
|
+
lastDeferred.set(project, { seq: entry.seq, deferCount: maxDeferCount });
|
|
4910
|
+
if (stuck2) {
|
|
4911
|
+
const streak = (projectBackoff.get(project)?.streak ?? 0) + 1;
|
|
4912
|
+
const backoffMs = Math.min(6e4, 1e3 * 2 ** streak);
|
|
4913
|
+
projectBackoff.set(project, { nextAttemptAt: Date.now() + backoffMs, streak });
|
|
4914
|
+
}
|
|
4915
|
+
break;
|
|
4661
4916
|
}
|
|
4662
4917
|
}
|
|
4663
|
-
const
|
|
4664
|
-
|
|
4918
|
+
const stuck = d.stats().stuck;
|
|
4919
|
+
if (stuck && !stuckNotified.has(project)) {
|
|
4920
|
+
stuckNotified.add(project);
|
|
4921
|
+
const { maxDeferCount, reason } = d.stats();
|
|
4922
|
+
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
4923
|
+
const text = STUCK_ALERT_TEXT[reason ?? "unknown"](maxDeferCount);
|
|
4665
4924
|
try {
|
|
4666
|
-
|
|
4667
|
-
const r = await deliverToCaptain(project, text, {
|
|
4668
|
-
channel: ctx.captainChannel,
|
|
4669
|
-
mode,
|
|
4670
|
-
log
|
|
4671
|
-
});
|
|
4672
|
-
handledByChannel = r.handled;
|
|
4925
|
+
await appendCaptainMessage({ stateRoot, project, text, source: "daemon" });
|
|
4673
4926
|
} catch (e) {
|
|
4674
|
-
log(`
|
|
4675
|
-
}
|
|
4676
|
-
if (handledByChannel) {
|
|
4677
|
-
return;
|
|
4678
|
-
}
|
|
4679
|
-
return cmux2.send(surface, text, sendOpts);
|
|
4680
|
-
});
|
|
4681
|
-
if ("delivered" in result) {
|
|
4682
|
-
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
|
|
4683
|
-
await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
|
|
4684
|
-
} else {
|
|
4685
|
-
const { maxDeferCount } = d.stats();
|
|
4686
|
-
if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
|
|
4687
|
-
log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
|
|
4927
|
+
log(`delivery stuck alert failed project=${project}: ${e.message}`);
|
|
4688
4928
|
}
|
|
4689
|
-
|
|
4929
|
+
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
4930
|
+
telegramBridge?.pushRaw(project, text);
|
|
4931
|
+
} else if (!stuck && stuckNotified.has(project)) {
|
|
4932
|
+
stuckNotified.delete(project);
|
|
4690
4933
|
}
|
|
4691
|
-
}
|
|
4692
|
-
|
|
4693
|
-
if (stuck && !stuckNotified.has(project)) {
|
|
4694
|
-
stuckNotified.add(project);
|
|
4695
|
-
const { maxDeferCount, reason } = d.stats();
|
|
4696
|
-
log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
|
|
4697
|
-
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.`;
|
|
4698
|
-
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
|
|
4699
|
-
Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
|
|
4700
|
-
telegramBridge?.pushRaw(project, text);
|
|
4701
|
-
} else if (!stuck && stuckNotified.has(project)) {
|
|
4702
|
-
stuckNotified.delete(project);
|
|
4934
|
+
} catch (e) {
|
|
4935
|
+
log(`delivery project=${project}: unhandled error \u2014 ${e.message}`);
|
|
4703
4936
|
}
|
|
4704
4937
|
}
|
|
4705
4938
|
};
|
|
@@ -4713,19 +4946,28 @@ function createDelivery(ctx, daemonCmux) {
|
|
|
4713
4946
|
delivering = false;
|
|
4714
4947
|
}
|
|
4715
4948
|
};
|
|
4716
|
-
return { defaultNotify, deliveryTick, deliveryStats };
|
|
4949
|
+
return { defaultNotify, deliveryTick, deliveryStats, inFlightDelivery };
|
|
4717
4950
|
}
|
|
4718
|
-
var CURSOR_SUBSCRIBER, TERMINAL_KINDS;
|
|
4951
|
+
var CURSOR_SUBSCRIBER, TERMINAL_KINDS, STUCK_ALERT_TEXT;
|
|
4719
4952
|
var init_delivery_loop = __esm({
|
|
4720
4953
|
"packages/core/dist/daemon/delivery-loop.js"() {
|
|
4721
4954
|
init_mailbox();
|
|
4722
4955
|
init_captain_delivery();
|
|
4956
|
+
init_defer_delivery();
|
|
4723
4957
|
init_dist();
|
|
4724
4958
|
init_interactive_probe();
|
|
4725
4959
|
init_liveness2();
|
|
4726
4960
|
init_captain_channel();
|
|
4727
4961
|
CURSOR_SUBSCRIBER = "captain";
|
|
4728
4962
|
TERMINAL_KINDS = /* @__PURE__ */ new Set(["task.done", "task.failed", "task.cancelled", "task.blocked"]);
|
|
4963
|
+
STUCK_ALERT_TEXT = {
|
|
4964
|
+
"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.`,
|
|
4965
|
+
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.`,
|
|
4966
|
+
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.`,
|
|
4967
|
+
"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.`,
|
|
4968
|
+
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.`,
|
|
4969
|
+
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.`
|
|
4970
|
+
};
|
|
4729
4971
|
}
|
|
4730
4972
|
});
|
|
4731
4973
|
|
|
@@ -4813,10 +5055,72 @@ var init_server = __esm({
|
|
|
4813
5055
|
}
|
|
4814
5056
|
});
|
|
4815
5057
|
|
|
5058
|
+
// packages/core/dist/daemon/exit-marker.js
|
|
5059
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
|
|
5060
|
+
import { join as join11 } from "path";
|
|
5061
|
+
function exitMarkerPath(stateRoot) {
|
|
5062
|
+
return join11(stateRoot, "exit-marker.json");
|
|
5063
|
+
}
|
|
5064
|
+
function writeExitMarker(stateRoot, marker, log) {
|
|
5065
|
+
try {
|
|
5066
|
+
writeFileSync8(exitMarkerPath(stateRoot), JSON.stringify(marker));
|
|
5067
|
+
} catch (e) {
|
|
5068
|
+
log(`exit marker write failed: ${e.message}`);
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
function consumeExitMarker(stateRoot, now = Date.now) {
|
|
5072
|
+
const p = exitMarkerPath(stateRoot);
|
|
5073
|
+
if (!existsSync9(p))
|
|
5074
|
+
return { marker: null };
|
|
5075
|
+
let marker = null;
|
|
5076
|
+
try {
|
|
5077
|
+
marker = JSON.parse(readFileSync8(p, "utf-8"));
|
|
5078
|
+
} catch {
|
|
5079
|
+
marker = null;
|
|
5080
|
+
}
|
|
5081
|
+
try {
|
|
5082
|
+
unlinkSync3(p);
|
|
5083
|
+
} catch {
|
|
5084
|
+
}
|
|
5085
|
+
if (!marker)
|
|
5086
|
+
return { marker: null };
|
|
5087
|
+
const gapMs = Math.max(0, now() - new Date(marker.ts).getTime());
|
|
5088
|
+
return { marker, gapMs };
|
|
5089
|
+
}
|
|
5090
|
+
function runningMarkerPath(stateRoot) {
|
|
5091
|
+
return join11(stateRoot, "running-marker.json");
|
|
5092
|
+
}
|
|
5093
|
+
function writeRunningMarker(stateRoot, marker, log) {
|
|
5094
|
+
try {
|
|
5095
|
+
writeFileSync8(runningMarkerPath(stateRoot), JSON.stringify(marker));
|
|
5096
|
+
} catch (e) {
|
|
5097
|
+
log(`running marker write failed: ${e.message}`);
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
function readRunningMarker(stateRoot) {
|
|
5101
|
+
try {
|
|
5102
|
+
return JSON.parse(readFileSync8(runningMarkerPath(stateRoot), "utf-8"));
|
|
5103
|
+
} catch {
|
|
5104
|
+
return null;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
function removeRunningMarker(stateRoot, log) {
|
|
5108
|
+
try {
|
|
5109
|
+
unlinkSync3(runningMarkerPath(stateRoot));
|
|
5110
|
+
} catch (e) {
|
|
5111
|
+
if (e.code !== "ENOENT")
|
|
5112
|
+
log(`running marker remove failed: ${e.message}`);
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
var init_exit_marker = __esm({
|
|
5116
|
+
"packages/core/dist/daemon/exit-marker.js"() {
|
|
5117
|
+
}
|
|
5118
|
+
});
|
|
5119
|
+
|
|
4816
5120
|
// packages/core/dist/daemon/snapshot-gather.js
|
|
4817
5121
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4818
|
-
import { join as
|
|
4819
|
-
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as
|
|
5122
|
+
import { join as join12 } from "path";
|
|
5123
|
+
import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
4820
5124
|
function distBuiltAt() {
|
|
4821
5125
|
try {
|
|
4822
5126
|
return statSync3(SELF_PATH).mtimeMs;
|
|
@@ -4869,7 +5173,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4869
5173
|
for (const r of store.list(project))
|
|
4870
5174
|
byState[r.state] = (byState[r.state] ?? 0) + 1;
|
|
4871
5175
|
let corruptCount = 0;
|
|
4872
|
-
const dir =
|
|
5176
|
+
const dir = join12(stateRoot, project);
|
|
4873
5177
|
try {
|
|
4874
5178
|
for (const n of readdirSync3(dir)) {
|
|
4875
5179
|
if (n.includes(".corrupt.")) {
|
|
@@ -4879,7 +5183,7 @@ function gatherStoreStats(store, stateRoot, project) {
|
|
|
4879
5183
|
if (!n.endsWith(".json"))
|
|
4880
5184
|
continue;
|
|
4881
5185
|
try {
|
|
4882
|
-
JSON.parse(
|
|
5186
|
+
JSON.parse(readFileSync9(join12(dir, n), "utf-8"));
|
|
4883
5187
|
} catch {
|
|
4884
5188
|
corruptCount++;
|
|
4885
5189
|
}
|
|
@@ -4894,7 +5198,7 @@ function gatherResults(resultsDir) {
|
|
|
4894
5198
|
try {
|
|
4895
5199
|
for (const n of readdirSync3(resultsDir)) {
|
|
4896
5200
|
try {
|
|
4897
|
-
const s = statSync3(
|
|
5201
|
+
const s = statSync3(join12(resultsDir, n));
|
|
4898
5202
|
if (s.isFile()) {
|
|
4899
5203
|
fileCount++;
|
|
4900
5204
|
totalBytes += s.size;
|
|
@@ -4914,19 +5218,20 @@ var init_snapshot_gather = __esm({
|
|
|
4914
5218
|
});
|
|
4915
5219
|
|
|
4916
5220
|
// packages/core/dist/daemon/start.js
|
|
4917
|
-
import { join as
|
|
5221
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
4918
5222
|
import { readdir } from "fs/promises";
|
|
4919
5223
|
function startDaemon(ctx, opts, pkgVersion) {
|
|
4920
5224
|
const { stateRoot, store, log, isPidAlive, resultsDir, taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills, broadcast, cancelPromotionsFor } = ctx;
|
|
4921
5225
|
const { daemonCmux } = ctx;
|
|
4922
5226
|
const probes = createProbes(ctx);
|
|
4923
|
-
const
|
|
5227
|
+
const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);
|
|
5228
|
+
const { defaultNotify, deliveryTick: initialDeliveryTick, deliveryStats, inFlightDelivery } = createDelivery(ctx, daemonCmux, surfaceProbe);
|
|
4924
5229
|
const baseNotify = opts.notify ?? defaultNotify;
|
|
4925
5230
|
const notify = ctx.telegramBridge ? async (args) => {
|
|
4926
5231
|
await baseNotify(args);
|
|
4927
5232
|
ctx.telegramBridge.pushLifecycle(args.project, args.event);
|
|
4928
5233
|
} : baseNotify;
|
|
4929
|
-
|
|
5234
|
+
ctx.notify = notify;
|
|
4930
5235
|
const ingest = (project) => (e) => void ctx.d.handle({ kind: "event", project, event: e });
|
|
4931
5236
|
const d = createDaemon({
|
|
4932
5237
|
store,
|
|
@@ -4990,7 +5295,7 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
4990
5295
|
return out;
|
|
4991
5296
|
}
|
|
4992
5297
|
async function gatherSnapshotInputs(now) {
|
|
4993
|
-
const logPath2 =
|
|
5298
|
+
const logPath2 = join13(dirname3(stateRoot), "squadrantd.log");
|
|
4994
5299
|
const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
4995
5300
|
const projects = await Promise.all(tier2Projects.map(async (project) => {
|
|
4996
5301
|
const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
|
|
@@ -5083,6 +5388,33 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5083
5388
|
})();
|
|
5084
5389
|
const server = createServer2(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });
|
|
5085
5390
|
log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);
|
|
5391
|
+
const bootTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
5392
|
+
{
|
|
5393
|
+
const sendDownAlert = (minutes, reasonText) => {
|
|
5394
|
+
const text = `\u26A0\uFE0F daemon was down for ${minutes} min (last exit reason=${reasonText})`;
|
|
5395
|
+
const alertProjects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
|
|
5396
|
+
for (const project of alertProjects) {
|
|
5397
|
+
appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`boot-gap alert failed project=${project}: ${e.message}`));
|
|
5398
|
+
}
|
|
5399
|
+
};
|
|
5400
|
+
const { marker, gapMs } = consumeExitMarker(stateRoot);
|
|
5401
|
+
const prevRunning = readRunningMarker(stateRoot);
|
|
5402
|
+
if (marker) {
|
|
5403
|
+
log(`previous exit ts=${marker.ts} reason=${marker.reason} gap=${((gapMs ?? 0) / 1e3).toFixed(1)}s`);
|
|
5404
|
+
if ((gapMs ?? 0) > 6e4)
|
|
5405
|
+
sendDownAlert(Math.round((gapMs ?? 0) / 6e4), marker.reason);
|
|
5406
|
+
} else if (prevRunning) {
|
|
5407
|
+
const lastHeartbeatMs = new Date(prevRunning.lastHeartbeatTs).getTime();
|
|
5408
|
+
const uncleanGapMs = Math.max(0, Date.now() - lastHeartbeatMs);
|
|
5409
|
+
log(`previous exit: UNCLEAN (no marker; last heartbeat ${prevRunning.lastHeartbeatTs}, gap=${(uncleanGapMs / 1e3).toFixed(1)}s)`);
|
|
5410
|
+
if (uncleanGapMs > 6e4) {
|
|
5411
|
+
sendDownAlert(Math.round(uncleanGapMs / 6e4), "unclean/unknown \u2014 no exit marker, likely SIGKILL/OOM/power-loss");
|
|
5412
|
+
}
|
|
5413
|
+
} else {
|
|
5414
|
+
log("previous exit: none (clean or first boot)");
|
|
5415
|
+
}
|
|
5416
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: bootTs }, log);
|
|
5417
|
+
}
|
|
5086
5418
|
let deliveryTick = initialDeliveryTick;
|
|
5087
5419
|
let probeTick;
|
|
5088
5420
|
if (daemonCmux) {
|
|
@@ -5123,9 +5455,10 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5123
5455
|
keepCount: opts.mailboxConfig?.keepCount ?? 3
|
|
5124
5456
|
};
|
|
5125
5457
|
let rotationTimer;
|
|
5458
|
+
let rotationTick;
|
|
5126
5459
|
if (rotationInterval > 0) {
|
|
5127
|
-
const inboxPath =
|
|
5128
|
-
|
|
5460
|
+
const inboxPath = join13(stateRoot, "inbox");
|
|
5461
|
+
rotationTick = async () => {
|
|
5129
5462
|
try {
|
|
5130
5463
|
let entries;
|
|
5131
5464
|
try {
|
|
@@ -5138,13 +5471,23 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5138
5471
|
await rotateIfNeeded({ stateRoot, project, ...mboxCfg });
|
|
5139
5472
|
} catch (e) {
|
|
5140
5473
|
log(`rotation timer error: ${e.message}`);
|
|
5474
|
+
} finally {
|
|
5475
|
+
writeRunningMarker(stateRoot, { pid: process.pid, bootTs, lastHeartbeatTs: (/* @__PURE__ */ new Date()).toISOString() }, log);
|
|
5141
5476
|
}
|
|
5477
|
+
};
|
|
5478
|
+
rotationTimer = setInterval(() => {
|
|
5479
|
+
void rotationTick();
|
|
5142
5480
|
}, rotationInterval);
|
|
5143
5481
|
rotationTimer.unref?.();
|
|
5144
5482
|
}
|
|
5145
5483
|
return {
|
|
5146
5484
|
stop(reason = "requested") {
|
|
5147
|
-
|
|
5485
|
+
const ppid = process.ppid;
|
|
5486
|
+
const uptimeMs = Math.round(process.uptime() * 1e3);
|
|
5487
|
+
const inFlight = inFlightDelivery();
|
|
5488
|
+
log(`exit pid=${process.pid} reason=${reason} ppid=${ppid} launchd=${ppid === 1} uptimeMs=${uptimeMs} inFlightDelivery=${inFlight ? `${inFlight.project}#${inFlight.seq}(defers=${inFlight.deferCount})` : "none"}`);
|
|
5489
|
+
writeExitMarker(stateRoot, { ts: (/* @__PURE__ */ new Date()).toISOString(), pid: process.pid, reason, ppid, uptimeMs, inFlightDelivery: inFlight }, log);
|
|
5490
|
+
removeRunningMarker(stateRoot, log);
|
|
5148
5491
|
if (deliveryTimer)
|
|
5149
5492
|
clearInterval(deliveryTimer);
|
|
5150
5493
|
if (probeTimer)
|
|
@@ -5173,7 +5516,8 @@ function startDaemon(ctx, opts, pkgVersion) {
|
|
|
5173
5516
|
}));
|
|
5174
5517
|
},
|
|
5175
5518
|
tickDelivery: deliveryTick,
|
|
5176
|
-
tickProbe: probeTick
|
|
5519
|
+
tickProbe: probeTick,
|
|
5520
|
+
tickRotation: rotationTick
|
|
5177
5521
|
};
|
|
5178
5522
|
}
|
|
5179
5523
|
var CURSOR_SUBSCRIBER2, SNAPSHOT_LOG_WINDOW_MS;
|
|
@@ -5185,6 +5529,7 @@ var init_start = __esm({
|
|
|
5185
5529
|
init_gates();
|
|
5186
5530
|
init_server();
|
|
5187
5531
|
init_mailbox();
|
|
5532
|
+
init_exit_marker();
|
|
5188
5533
|
init_liveness2();
|
|
5189
5534
|
init_dist();
|
|
5190
5535
|
init_snapshot_gather();
|
|
@@ -6179,9 +6524,9 @@ var init_bridge = __esm({
|
|
|
6179
6524
|
|
|
6180
6525
|
// packages/core/dist/restart-daemon.js
|
|
6181
6526
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6182
|
-
import { existsSync as
|
|
6527
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6183
6528
|
function defaultIsRunning() {
|
|
6184
|
-
return
|
|
6529
|
+
return existsSync10(DAEMON_SOCK_PATH);
|
|
6185
6530
|
}
|
|
6186
6531
|
function defaultRunKickstart() {
|
|
6187
6532
|
const uid = process.getuid?.() ?? 0;
|
|
@@ -6719,6 +7064,76 @@ var init_side_session = __esm({
|
|
|
6719
7064
|
}
|
|
6720
7065
|
});
|
|
6721
7066
|
|
|
7067
|
+
// packages/core/dist/crew-answer.js
|
|
7068
|
+
function describeOptions(options) {
|
|
7069
|
+
return options.map((o) => ` ${o.highlighted ? "\u276F" : " "} ${o.index}. ${o.label}`).join("\n");
|
|
7070
|
+
}
|
|
7071
|
+
function resolveOption(options, selector) {
|
|
7072
|
+
const trimmed = selector.trim();
|
|
7073
|
+
if (/^\d+$/.test(trimmed)) {
|
|
7074
|
+
const byIndex = options.find((o) => o.index === Number(trimmed));
|
|
7075
|
+
if (!byIndex) {
|
|
7076
|
+
throw new Error(`No option ${trimmed} in the visible prompt. Visible options:
|
|
7077
|
+
${describeOptions(options)}`);
|
|
7078
|
+
}
|
|
7079
|
+
return byIndex;
|
|
7080
|
+
}
|
|
7081
|
+
const lower = trimmed.toLowerCase();
|
|
7082
|
+
const exact = options.filter((o) => o.label.toLowerCase() === lower);
|
|
7083
|
+
if (exact.length === 1)
|
|
7084
|
+
return exact[0];
|
|
7085
|
+
if (exact.length > 1) {
|
|
7086
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
7087
|
+
${describeOptions(exact)}`);
|
|
7088
|
+
}
|
|
7089
|
+
const prefix = options.filter((o) => o.label.toLowerCase().startsWith(lower));
|
|
7090
|
+
if (prefix.length === 1)
|
|
7091
|
+
return prefix[0];
|
|
7092
|
+
if (prefix.length > 1) {
|
|
7093
|
+
throw new Error(`Option text "${selector}" matches multiple options ambiguously:
|
|
7094
|
+
${describeOptions(prefix)}`);
|
|
7095
|
+
}
|
|
7096
|
+
throw new Error(`No option matches "${selector}". Visible options:
|
|
7097
|
+
${describeOptions(options)}`);
|
|
7098
|
+
}
|
|
7099
|
+
async function runCrewAnswer(project, name, option, runtime, workspaceId, deps, opts) {
|
|
7100
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
7101
|
+
if (!crew) {
|
|
7102
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
7103
|
+
}
|
|
7104
|
+
const options = await deps.readModalOptions(crew);
|
|
7105
|
+
if (!options) {
|
|
7106
|
+
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.`);
|
|
7107
|
+
}
|
|
7108
|
+
const target = resolveOption(options, option);
|
|
7109
|
+
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
7110
|
+
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}'.
|
|
7111
|
+
Visible options:
|
|
7112
|
+
${describeOptions(options)}`);
|
|
7113
|
+
}
|
|
7114
|
+
const log = deps.log ?? (() => {
|
|
7115
|
+
});
|
|
7116
|
+
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
7117
|
+
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
7118
|
+
const steps = target.index - current.index;
|
|
7119
|
+
const key = steps >= 0 ? "Down" : "Up";
|
|
7120
|
+
for (let i = 0; i < Math.abs(steps); i++) {
|
|
7121
|
+
await runtime.sendKeyToPane(crew, key);
|
|
7122
|
+
}
|
|
7123
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
7124
|
+
if (opts?.text) {
|
|
7125
|
+
await runtime.pasteToPane(crew, opts.text);
|
|
7126
|
+
await runtime.sendKeyToPane(crew, "Enter");
|
|
7127
|
+
}
|
|
7128
|
+
const after = await deps.readModalOptions(crew);
|
|
7129
|
+
return { selected: target, closed: after === null };
|
|
7130
|
+
}
|
|
7131
|
+
var init_crew_answer = __esm({
|
|
7132
|
+
"packages/core/dist/crew-answer.js"() {
|
|
7133
|
+
init_crew_spawn();
|
|
7134
|
+
}
|
|
7135
|
+
});
|
|
7136
|
+
|
|
6722
7137
|
// packages/core/dist/lifecycle-source.js
|
|
6723
7138
|
function reduceLifecycle(prev, next) {
|
|
6724
7139
|
if (next.origin === "agent") {
|
|
@@ -6740,6 +7155,326 @@ var init_lifecycle_source = __esm({
|
|
|
6740
7155
|
}
|
|
6741
7156
|
});
|
|
6742
7157
|
|
|
7158
|
+
// packages/core/dist/events/fact.js
|
|
7159
|
+
function stampFact(raw, id) {
|
|
7160
|
+
return { ...raw, ...id };
|
|
7161
|
+
}
|
|
7162
|
+
var init_fact = __esm({
|
|
7163
|
+
"packages/core/dist/events/fact.js"() {
|
|
7164
|
+
}
|
|
7165
|
+
});
|
|
7166
|
+
|
|
7167
|
+
// packages/core/dist/events/log.js
|
|
7168
|
+
var FactLog;
|
|
7169
|
+
var init_log = __esm({
|
|
7170
|
+
"packages/core/dist/events/log.js"() {
|
|
7171
|
+
FactLog = class {
|
|
7172
|
+
capacity;
|
|
7173
|
+
buffers = /* @__PURE__ */ new Map();
|
|
7174
|
+
constructor(opts = {}) {
|
|
7175
|
+
this.capacity = opts.capacity ?? 256;
|
|
7176
|
+
}
|
|
7177
|
+
push(fact) {
|
|
7178
|
+
let buf = this.buffers.get(fact.taskId);
|
|
7179
|
+
if (!buf) {
|
|
7180
|
+
buf = [];
|
|
7181
|
+
this.buffers.set(fact.taskId, buf);
|
|
7182
|
+
}
|
|
7183
|
+
buf.push(fact);
|
|
7184
|
+
while (buf.length > this.capacity)
|
|
7185
|
+
buf.shift();
|
|
7186
|
+
}
|
|
7187
|
+
/** Oldest-first snapshot. A fresh array; later pushes never grow it. */
|
|
7188
|
+
recent(taskId) {
|
|
7189
|
+
return [...this.buffers.get(taskId) ?? []];
|
|
7190
|
+
}
|
|
7191
|
+
/** Newline-delimited JSON, one fact per line, oldest first. */
|
|
7192
|
+
serialize(taskId) {
|
|
7193
|
+
return this.recent(taskId).map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
7194
|
+
}
|
|
7195
|
+
/** Release a finished crew's buffer. */
|
|
7196
|
+
drop(taskId) {
|
|
7197
|
+
this.buffers.delete(taskId);
|
|
7198
|
+
}
|
|
7199
|
+
};
|
|
7200
|
+
}
|
|
7201
|
+
});
|
|
7202
|
+
|
|
7203
|
+
// packages/core/dist/events/invariant.js
|
|
7204
|
+
function freshTrace() {
|
|
7205
|
+
return {
|
|
7206
|
+
depth: 0,
|
|
7207
|
+
oldestOpenAt: null,
|
|
7208
|
+
stallReported: false,
|
|
7209
|
+
unknownSeen: 0,
|
|
7210
|
+
liveness: /* @__PURE__ */ new Map()
|
|
7211
|
+
};
|
|
7212
|
+
}
|
|
7213
|
+
function checkFact(trace, fact, opts) {
|
|
7214
|
+
const out = [];
|
|
7215
|
+
if (fact.origin === "inferred" && TERMINALISING.has(fact.kind)) {
|
|
7216
|
+
out.push(v("I4", `inferred fact "${fact.kind}" from ${fact.source} cannot terminalise alone`, fact));
|
|
7217
|
+
}
|
|
7218
|
+
switch (fact.kind) {
|
|
7219
|
+
case "tool.opened":
|
|
7220
|
+
if (trace.depth === 0) {
|
|
7221
|
+
trace.oldestOpenAt = fact.at;
|
|
7222
|
+
trace.stallReported = false;
|
|
7223
|
+
}
|
|
7224
|
+
trace.depth += 1;
|
|
7225
|
+
break;
|
|
7226
|
+
case "tool.closed":
|
|
7227
|
+
if (trace.depth === 0) {
|
|
7228
|
+
out.push(v("I1", `tool.closed from ${fact.source} with no open tool`, fact));
|
|
7229
|
+
} else {
|
|
7230
|
+
trace.depth -= 1;
|
|
7231
|
+
if (trace.depth === 0) {
|
|
7232
|
+
trace.oldestOpenAt = null;
|
|
7233
|
+
trace.stallReported = false;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
break;
|
|
7237
|
+
case "turn.ended":
|
|
7238
|
+
if (trace.depth > 0) {
|
|
7239
|
+
out.push(v("I2", `turn.ended with ${trace.depth} tool call(s) still open`, fact));
|
|
7240
|
+
trace.depth = 0;
|
|
7241
|
+
trace.oldestOpenAt = null;
|
|
7242
|
+
trace.stallReported = false;
|
|
7243
|
+
}
|
|
7244
|
+
break;
|
|
7245
|
+
case "unknown":
|
|
7246
|
+
trace.unknownSeen += 1;
|
|
7247
|
+
out.push(v("I5", `unrecognised frame "${fact.name}" from ${fact.source}`, fact));
|
|
7248
|
+
break;
|
|
7249
|
+
case "process.observed": {
|
|
7250
|
+
const prior = [...trace.liveness.entries()].find(([src, s]) => src !== fact.source && s.alive !== fact.alive && fact.at - s.at <= (opts.disagreeWindowMs ?? -1));
|
|
7251
|
+
if (prior) {
|
|
7252
|
+
out.push(v("I6", `liveness disagreement: ${prior[0]} said alive=${prior[1].alive}, ${fact.source} says alive=${fact.alive}`, fact));
|
|
7253
|
+
}
|
|
7254
|
+
trace.liveness.set(fact.source, { alive: fact.alive, at: fact.at });
|
|
7255
|
+
break;
|
|
7256
|
+
}
|
|
7257
|
+
default:
|
|
7258
|
+
break;
|
|
7259
|
+
}
|
|
7260
|
+
if (opts.stallBudgetMs !== void 0 && trace.depth > 0 && trace.oldestOpenAt !== null && !trace.stallReported && fact.at - trace.oldestOpenAt > opts.stallBudgetMs) {
|
|
7261
|
+
trace.stallReported = true;
|
|
7262
|
+
out.push(v("I3", `tool open for ${fact.at - trace.oldestOpenAt}ms, past the stall budget`, fact));
|
|
7263
|
+
}
|
|
7264
|
+
return out;
|
|
7265
|
+
}
|
|
7266
|
+
var v, TERMINALISING;
|
|
7267
|
+
var init_invariant = __esm({
|
|
7268
|
+
"packages/core/dist/events/invariant.js"() {
|
|
7269
|
+
v = (code, message, f) => ({ code, message, taskId: f.taskId, at: f.at });
|
|
7270
|
+
TERMINALISING = /* @__PURE__ */ new Set(["session.ended"]);
|
|
7271
|
+
}
|
|
7272
|
+
});
|
|
7273
|
+
|
|
7274
|
+
// packages/core/dist/events/to-control-event.js
|
|
7275
|
+
function toControlEvent(fact) {
|
|
7276
|
+
if (fact.origin === "inferred" && TERMINALISING2.has(fact.kind))
|
|
7277
|
+
return [];
|
|
7278
|
+
switch (fact.kind) {
|
|
7279
|
+
case "turn.ended":
|
|
7280
|
+
return [{ type: "task.turn.completed", id: fact.taskId, turnId: fact.turnId ?? fact.source }];
|
|
7281
|
+
case "permission.requested":
|
|
7282
|
+
return [{
|
|
7283
|
+
type: "task.approval.requested",
|
|
7284
|
+
id: fact.taskId,
|
|
7285
|
+
requestId: fact.requestId,
|
|
7286
|
+
question: fact.question,
|
|
7287
|
+
kind: fact.tool
|
|
7288
|
+
}];
|
|
7289
|
+
case "input.requested":
|
|
7290
|
+
return [{
|
|
7291
|
+
type: "task.input.requested",
|
|
7292
|
+
id: fact.taskId,
|
|
7293
|
+
requestId: fact.requestId,
|
|
7294
|
+
question: fact.question
|
|
7295
|
+
}];
|
|
7296
|
+
case "session.ended":
|
|
7297
|
+
return [{ type: "task.session.ended", id: fact.taskId }];
|
|
7298
|
+
case "session.started":
|
|
7299
|
+
return [{
|
|
7300
|
+
type: "task.started",
|
|
7301
|
+
id: fact.taskId,
|
|
7302
|
+
...fact.pid === void 0 ? {} : { pid: fact.pid },
|
|
7303
|
+
...fact.sessionId === void 0 ? {} : { sessionId: fact.sessionId }
|
|
7304
|
+
}];
|
|
7305
|
+
case "prompt.submitted":
|
|
7306
|
+
return [{ type: "task.first-turn.confirmed", id: fact.taskId }];
|
|
7307
|
+
// Liveness-only. The facade still feeds these to reduceLifecycle; they
|
|
7308
|
+
// simply carry no ControlEvent of their own.
|
|
7309
|
+
case "tool.opened":
|
|
7310
|
+
case "tool.closed":
|
|
7311
|
+
case "activity":
|
|
7312
|
+
case "process.observed":
|
|
7313
|
+
case "unknown":
|
|
7314
|
+
return [];
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
var TERMINALISING2;
|
|
7318
|
+
var init_to_control_event = __esm({
|
|
7319
|
+
"packages/core/dist/events/to-control-event.js"() {
|
|
7320
|
+
TERMINALISING2 = /* @__PURE__ */ new Set(["session.ended"]);
|
|
7321
|
+
}
|
|
7322
|
+
});
|
|
7323
|
+
|
|
7324
|
+
// packages/core/dist/events/conformance.js
|
|
7325
|
+
function assert(cond, msg) {
|
|
7326
|
+
if (!cond)
|
|
7327
|
+
throw new Error(`conformance: ${msg}`);
|
|
7328
|
+
}
|
|
7329
|
+
function runAdapterConformance(adapter, samples) {
|
|
7330
|
+
const call = (raw) => adapter.translate(raw);
|
|
7331
|
+
return [
|
|
7332
|
+
{
|
|
7333
|
+
name: `${adapter.name}: never throws on garbage`,
|
|
7334
|
+
run: () => {
|
|
7335
|
+
for (const g of GARBAGE) {
|
|
7336
|
+
try {
|
|
7337
|
+
call(g);
|
|
7338
|
+
} catch (e) {
|
|
7339
|
+
throw new Error(`threw on ${JSON.stringify(g)}: ${String(e)}`);
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
},
|
|
7344
|
+
{
|
|
7345
|
+
name: `${adapter.name}: never returns null or undefined`,
|
|
7346
|
+
run: () => {
|
|
7347
|
+
for (const g of [...GARBAGE, ...samples]) {
|
|
7348
|
+
const out = call(g);
|
|
7349
|
+
assert(Array.isArray(out), `returned a non-array for ${JSON.stringify(g)}`);
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
},
|
|
7353
|
+
{
|
|
7354
|
+
name: `${adapter.name}: an unrecognised frame yields unknown, not an empty array`,
|
|
7355
|
+
run: () => {
|
|
7356
|
+
const out = call({ type: "definitely-not-a-real-event-name" });
|
|
7357
|
+
assert(out.length > 0, "silently dropped an unrecognised frame (the #542 shape)");
|
|
7358
|
+
assert(out.every((f) => f.kind === "unknown"), "an unrecognised frame must translate to kind 'unknown'");
|
|
7359
|
+
}
|
|
7360
|
+
},
|
|
7361
|
+
{
|
|
7362
|
+
name: `${adapter.name}: recognises its own samples`,
|
|
7363
|
+
run: () => {
|
|
7364
|
+
for (const s of samples) {
|
|
7365
|
+
const out = call(s);
|
|
7366
|
+
assert(out.length > 0, `produced nothing for its own sample ${JSON.stringify(s)}`);
|
|
7367
|
+
assert(out.some((f) => f.kind !== "unknown"), `failed to recognise its own sample ${JSON.stringify(s)}`);
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
},
|
|
7371
|
+
{
|
|
7372
|
+
name: `${adapter.name}: declares a constant origin`,
|
|
7373
|
+
run: () => {
|
|
7374
|
+
assert(adapter.origin === "agent" || adapter.origin === "scan" || adapter.origin === "inferred", `invalid origin "${String(adapter.origin)}"`);
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
];
|
|
7378
|
+
}
|
|
7379
|
+
var GARBAGE;
|
|
7380
|
+
var init_conformance = __esm({
|
|
7381
|
+
"packages/core/dist/events/conformance.js"() {
|
|
7382
|
+
GARBAGE = [
|
|
7383
|
+
null,
|
|
7384
|
+
void 0,
|
|
7385
|
+
0,
|
|
7386
|
+
"",
|
|
7387
|
+
"not json",
|
|
7388
|
+
[],
|
|
7389
|
+
{},
|
|
7390
|
+
{ type: 42 },
|
|
7391
|
+
{ type: "definitely-not-a-real-event-name" }
|
|
7392
|
+
];
|
|
7393
|
+
}
|
|
7394
|
+
});
|
|
7395
|
+
|
|
7396
|
+
// packages/core/dist/events/source.js
|
|
7397
|
+
function createEventsSource(opts) {
|
|
7398
|
+
const now = opts.now ?? (() => Date.now());
|
|
7399
|
+
const log = new FactLog({ capacity: opts.capacity });
|
|
7400
|
+
const adapters = new Map(opts.adapters.map((a) => [a.name, a]));
|
|
7401
|
+
const traces = /* @__PURE__ */ new Map();
|
|
7402
|
+
const seqs = /* @__PURE__ */ new Map();
|
|
7403
|
+
let deps;
|
|
7404
|
+
const traceFor = (taskId) => {
|
|
7405
|
+
let t = traces.get(taskId);
|
|
7406
|
+
if (!t) {
|
|
7407
|
+
t = freshTrace();
|
|
7408
|
+
traces.set(taskId, t);
|
|
7409
|
+
}
|
|
7410
|
+
return t;
|
|
7411
|
+
};
|
|
7412
|
+
const nextSeq = (taskId) => {
|
|
7413
|
+
const n = seqs.get(taskId) ?? 0;
|
|
7414
|
+
seqs.set(taskId, n + 1);
|
|
7415
|
+
return n;
|
|
7416
|
+
};
|
|
7417
|
+
return {
|
|
7418
|
+
name: "events",
|
|
7419
|
+
start(d) {
|
|
7420
|
+
deps = d;
|
|
7421
|
+
},
|
|
7422
|
+
stop() {
|
|
7423
|
+
deps = void 0;
|
|
7424
|
+
},
|
|
7425
|
+
health() {
|
|
7426
|
+
return { active: deps !== void 0, error: null };
|
|
7427
|
+
},
|
|
7428
|
+
recent(taskId) {
|
|
7429
|
+
return log.recent(taskId);
|
|
7430
|
+
},
|
|
7431
|
+
dump(taskId) {
|
|
7432
|
+
return log.serialize(taskId);
|
|
7433
|
+
},
|
|
7434
|
+
ingest(source, raw, hint) {
|
|
7435
|
+
const adapter = adapters.get(source);
|
|
7436
|
+
if (!adapter || !deps)
|
|
7437
|
+
return;
|
|
7438
|
+
const rec = deps.resolve(hint);
|
|
7439
|
+
if (!rec)
|
|
7440
|
+
return;
|
|
7441
|
+
const taskId = rec.id;
|
|
7442
|
+
const at = now();
|
|
7443
|
+
let produced;
|
|
7444
|
+
try {
|
|
7445
|
+
const out = adapter.translate(raw);
|
|
7446
|
+
produced = Array.isArray(out) ? out : [{ kind: "unknown", name: `${source} returned non-array` }];
|
|
7447
|
+
} catch (e) {
|
|
7448
|
+
opts.log?.(`events: adapter ${source} threw: ${String(e)}`);
|
|
7449
|
+
produced = [{ kind: "unknown", name: `${source} threw` }];
|
|
7450
|
+
}
|
|
7451
|
+
for (const rawFact of produced) {
|
|
7452
|
+
const fact = stampFact(rawFact, {
|
|
7453
|
+
seq: nextSeq(taskId),
|
|
7454
|
+
taskId,
|
|
7455
|
+
at,
|
|
7456
|
+
source,
|
|
7457
|
+
origin: adapter.origin
|
|
7458
|
+
});
|
|
7459
|
+
log.push(fact);
|
|
7460
|
+
for (const v2 of checkFact(traceFor(taskId), fact, opts.check ?? {})) {
|
|
7461
|
+
opts.onViolation(v2);
|
|
7462
|
+
}
|
|
7463
|
+
for (const ev of toControlEvent(fact))
|
|
7464
|
+
opts.emit(ev);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
};
|
|
7468
|
+
}
|
|
7469
|
+
var init_source = __esm({
|
|
7470
|
+
"packages/core/dist/events/source.js"() {
|
|
7471
|
+
init_fact();
|
|
7472
|
+
init_log();
|
|
7473
|
+
init_invariant();
|
|
7474
|
+
init_to_control_event();
|
|
7475
|
+
}
|
|
7476
|
+
});
|
|
7477
|
+
|
|
6743
7478
|
// packages/core/dist/index.js
|
|
6744
7479
|
var dist_exports2 = {};
|
|
6745
7480
|
__export(dist_exports2, {
|
|
@@ -6753,6 +7488,8 @@ __export(dist_exports2, {
|
|
|
6753
7488
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
6754
7489
|
DEFAULT_TASK_TIMEOUT_MS: () => DEFAULT_TASK_TIMEOUT_MS,
|
|
6755
7490
|
DeferDelivery: () => DeferDelivery,
|
|
7491
|
+
FIRST_TURN_INLINE_MAX_BYTES: () => FIRST_TURN_INLINE_MAX_BYTES,
|
|
7492
|
+
FactLog: () => FactLog,
|
|
6756
7493
|
GROUP_DISPATCH_WARMUP_POLL_MS: () => GROUP_DISPATCH_WARMUP_POLL_MS,
|
|
6757
7494
|
GROUP_DISPATCH_WARMUP_TIMEOUT_MS: () => GROUP_DISPATCH_WARMUP_TIMEOUT_MS,
|
|
6758
7495
|
IDLE_DEBOUNCE_MS: () => IDLE_DEBOUNCE_MS,
|
|
@@ -6782,9 +7519,11 @@ __export(dist_exports2, {
|
|
|
6782
7519
|
capAllowed: () => capAllowed,
|
|
6783
7520
|
capOutput: () => capOutput,
|
|
6784
7521
|
captainSocketPath: () => captainSocketPath,
|
|
7522
|
+
checkFact: () => checkFact,
|
|
6785
7523
|
classifyHealth: () => classifyHealth,
|
|
6786
7524
|
closeWorkItem: () => closeWorkItem,
|
|
6787
7525
|
computeTemplateHash: () => computeTemplateHash,
|
|
7526
|
+
consumeExitMarker: () => consumeExitMarker,
|
|
6788
7527
|
createAttach: () => createAttach,
|
|
6789
7528
|
createCrewPaneReader: () => createCrewPaneReader,
|
|
6790
7529
|
createDaemon: () => createDaemon,
|
|
@@ -6793,6 +7532,7 @@ __export(dist_exports2, {
|
|
|
6793
7532
|
createDirectCrewPaneReader: () => createDirectCrewPaneReader,
|
|
6794
7533
|
createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
|
|
6795
7534
|
createEnsureCaptainAlive: () => createEnsureCaptainAlive,
|
|
7535
|
+
createEventsSource: () => createEventsSource,
|
|
6796
7536
|
createInteractiveProbe: () => createInteractiveProbe,
|
|
6797
7537
|
createIsCaptainAlive: () => createIsCaptainAlive,
|
|
6798
7538
|
createLaunch: () => createLaunch,
|
|
@@ -6825,13 +7565,18 @@ __export(dist_exports2, {
|
|
|
6825
7565
|
encodeMsg: () => encodeMsg,
|
|
6826
7566
|
ensureDaemon: () => ensureDaemon,
|
|
6827
7567
|
evaluateStall: () => evaluateStall,
|
|
7568
|
+
exitMarkerPath: () => exitMarkerPath,
|
|
6828
7569
|
fallsBackToPane: () => fallsBackToPane,
|
|
7570
|
+
findCrewPane: () => findCrewPane,
|
|
6829
7571
|
findOpenChildren: () => findOpenChildren,
|
|
6830
7572
|
findProjectByThread: () => findProjectByThread,
|
|
6831
7573
|
findWorkItemById: () => findWorkItemById,
|
|
7574
|
+
forceKickstartAndVerify: () => forceKickstartAndVerify,
|
|
6832
7575
|
formatInbound: () => formatInbound,
|
|
6833
7576
|
formatInboundReceipt: () => formatInboundReceipt,
|
|
6834
7577
|
formatLifecycle: () => formatLifecycle,
|
|
7578
|
+
freshTrace: () => freshTrace,
|
|
7579
|
+
getDaemonPid: () => getDaemonPid,
|
|
6835
7580
|
healCmdFor: () => healCmdFor,
|
|
6836
7581
|
isAuthorized: () => isAuthorized,
|
|
6837
7582
|
isBareSpawn: () => isBareSpawn,
|
|
@@ -6866,6 +7611,7 @@ __export(dist_exports2, {
|
|
|
6866
7611
|
purgeExpiredWorkItems: () => purgeExpiredWorkItems,
|
|
6867
7612
|
readCursor: () => readCursor,
|
|
6868
7613
|
readFromCursor: () => readFromCursor,
|
|
7614
|
+
readRunningMarker: () => readRunningMarker,
|
|
6869
7615
|
reapCrewChildren: () => reapCrewChildren,
|
|
6870
7616
|
reapOrphanedCrews: () => reapOrphanedCrews,
|
|
6871
7617
|
reconcileLiveness: () => reconcileLiveness,
|
|
@@ -6874,6 +7620,7 @@ __export(dist_exports2, {
|
|
|
6874
7620
|
reduce: () => reduce,
|
|
6875
7621
|
reduceLifecycle: () => reduceLifecycle,
|
|
6876
7622
|
releaseDaemonLock: () => releaseDaemonLock,
|
|
7623
|
+
removeRunningMarker: () => removeRunningMarker,
|
|
6877
7624
|
renderPlist: () => renderPlist,
|
|
6878
7625
|
reregisterDaemon: () => reregisterDaemon,
|
|
6879
7626
|
resolveAgentBinDirs: () => resolveAgentBinDirs,
|
|
@@ -6885,6 +7632,8 @@ __export(dist_exports2, {
|
|
|
6885
7632
|
resolveSetupUserId: () => resolveSetupUserId,
|
|
6886
7633
|
restartDaemonIfRunning: () => restartDaemonIfRunning,
|
|
6887
7634
|
rotateIfNeeded: () => rotateIfNeeded,
|
|
7635
|
+
runAdapterConformance: () => runAdapterConformance,
|
|
7636
|
+
runCrewAnswer: () => runCrewAnswer,
|
|
6888
7637
|
runCrewClose: () => runCrewClose,
|
|
6889
7638
|
runCrewList: () => runCrewList,
|
|
6890
7639
|
runCrewRead: () => runCrewRead,
|
|
@@ -6904,6 +7653,7 @@ __export(dist_exports2, {
|
|
|
6904
7653
|
runTelegramPostSetup: () => runTelegramPostSetup,
|
|
6905
7654
|
runTelegramSend: () => runTelegramSend,
|
|
6906
7655
|
runTelegramStatus: () => runTelegramStatus,
|
|
7656
|
+
runningMarkerPath: () => runningMarkerPath,
|
|
6907
7657
|
sanitizePathForPlist: () => sanitizePathForPlist,
|
|
6908
7658
|
saveSessions: () => saveSessions,
|
|
6909
7659
|
saveState: () => saveState,
|
|
@@ -6917,18 +7667,22 @@ __export(dist_exports2, {
|
|
|
6917
7667
|
sideNameFromTitle: () => sideNameFromTitle,
|
|
6918
7668
|
sideNextAutoName: () => sideNextAutoName,
|
|
6919
7669
|
sideTitleFor: () => sideTitleFor,
|
|
7670
|
+
stampFact: () => stampFact,
|
|
6920
7671
|
startDaemon: () => startDaemon,
|
|
6921
7672
|
startServer: () => startServer,
|
|
6922
7673
|
stripBotMention: () => stripBotMention,
|
|
6923
7674
|
surfaceVerdict: () => surfaceVerdict,
|
|
6924
7675
|
timeoutGate: () => timeoutGate,
|
|
6925
7676
|
titleFor: () => titleFor,
|
|
7677
|
+
toControlEvent: () => toControlEvent,
|
|
6926
7678
|
topicKey: () => topicKey,
|
|
6927
7679
|
topicName: () => topicName,
|
|
6928
7680
|
tryAcquireDaemonLock: () => tryAcquireDaemonLock,
|
|
6929
7681
|
waitForCaptainDelivery: () => waitForCaptainDelivery,
|
|
6930
7682
|
waitForWarmup: () => waitForWarmup,
|
|
6931
7683
|
writeCursor: () => writeCursor,
|
|
7684
|
+
writeExitMarker: () => writeExitMarker,
|
|
7685
|
+
writeRunningMarker: () => writeRunningMarker,
|
|
6932
7686
|
writeTelegramConfig: () => writeTelegramConfig
|
|
6933
7687
|
});
|
|
6934
7688
|
var init_dist2 = __esm({
|
|
@@ -6951,6 +7705,7 @@ var init_dist2 = __esm({
|
|
|
6951
7705
|
init_attach();
|
|
6952
7706
|
init_start();
|
|
6953
7707
|
init_delivery_loop();
|
|
7708
|
+
init_exit_marker();
|
|
6954
7709
|
init_interactive_probe();
|
|
6955
7710
|
init_captain_delivery();
|
|
6956
7711
|
init_defer_delivery();
|
|
@@ -6964,7 +7719,14 @@ var init_dist2 = __esm({
|
|
|
6964
7719
|
init_launch_workspace();
|
|
6965
7720
|
init_side_session();
|
|
6966
7721
|
init_crew_spawn();
|
|
7722
|
+
init_crew_answer();
|
|
6967
7723
|
init_lifecycle_source();
|
|
7724
|
+
init_fact();
|
|
7725
|
+
init_log();
|
|
7726
|
+
init_invariant();
|
|
7727
|
+
init_to_control_event();
|
|
7728
|
+
init_conformance();
|
|
7729
|
+
init_source();
|
|
6968
7730
|
init_control_channel();
|
|
6969
7731
|
init_captain_channel();
|
|
6970
7732
|
}
|
|
@@ -7120,6 +7882,35 @@ function hasModalOptionList(screen) {
|
|
|
7120
7882
|
return false;
|
|
7121
7883
|
return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
|
|
7122
7884
|
}
|
|
7885
|
+
function parseModalOptions(screen) {
|
|
7886
|
+
if (!hasModalOptionList(screen))
|
|
7887
|
+
return null;
|
|
7888
|
+
const lines = screen.split(/\r?\n/);
|
|
7889
|
+
const HR_RE = /^\s*─{10,}\s*$/;
|
|
7890
|
+
let bottomHR = -1;
|
|
7891
|
+
let topHR = -1;
|
|
7892
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
7893
|
+
if (HR_RE.test(lines[i])) {
|
|
7894
|
+
if (bottomHR === -1)
|
|
7895
|
+
bottomHR = i;
|
|
7896
|
+
else {
|
|
7897
|
+
topHR = i;
|
|
7898
|
+
break;
|
|
7899
|
+
}
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
if (topHR === -1)
|
|
7903
|
+
return null;
|
|
7904
|
+
const OPTION_RE3 = /^\s*(❯)?\s*(\d+)\.\s*(.*?)\s*$/;
|
|
7905
|
+
const options = [];
|
|
7906
|
+
for (const line of lines.slice(topHR + 1, bottomHR)) {
|
|
7907
|
+
const m = line.match(OPTION_RE3);
|
|
7908
|
+
if (!m)
|
|
7909
|
+
continue;
|
|
7910
|
+
options.push({ index: Number(m[2]), label: m[3], highlighted: m[1] === "\u276F" });
|
|
7911
|
+
}
|
|
7912
|
+
return options.length > 0 ? options : null;
|
|
7913
|
+
}
|
|
7123
7914
|
function readInputBoxRaw(screen, opts) {
|
|
7124
7915
|
if (!screen)
|
|
7125
7916
|
return null;
|
|
@@ -7366,7 +8157,10 @@ function createCmuxDriver() {
|
|
|
7366
8157
|
let screen = "";
|
|
7367
8158
|
try {
|
|
7368
8159
|
screen = await cmux(["read-screen", "--workspace", ws, "--surface", sf]);
|
|
7369
|
-
} catch {
|
|
8160
|
+
} catch (e) {
|
|
8161
|
+
process.stderr.write(`[squadrant] read-screen failed for ${ws}/${sf}: ${e.message}
|
|
8162
|
+
`);
|
|
8163
|
+
throw new DeferDelivery(null, "probe-failed");
|
|
7370
8164
|
}
|
|
7371
8165
|
const draft = parseDraftFromScreen(screen);
|
|
7372
8166
|
if (draft === null)
|
|
@@ -7616,7 +8410,7 @@ var init_notifiers = __esm({
|
|
|
7616
8410
|
|
|
7617
8411
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
7618
8412
|
import fs13 from "fs/promises";
|
|
7619
|
-
import { existsSync as
|
|
8413
|
+
import { existsSync as existsSync11 } from "fs";
|
|
7620
8414
|
import path13 from "path";
|
|
7621
8415
|
function resolveInRoot(root, relative) {
|
|
7622
8416
|
const joined = path13.resolve(root, relative);
|
|
@@ -7636,7 +8430,7 @@ function createObsidianDriver(scope) {
|
|
|
7636
8430
|
async probe() {
|
|
7637
8431
|
return {
|
|
7638
8432
|
installed: true,
|
|
7639
|
-
rootExists:
|
|
8433
|
+
rootExists: existsSync11(root)
|
|
7640
8434
|
};
|
|
7641
8435
|
},
|
|
7642
8436
|
async read(rel) {
|
|
@@ -7834,6 +8628,16 @@ var init_events_bridge = __esm({
|
|
|
7834
8628
|
}
|
|
7835
8629
|
if (f?.type !== "event" || f.category !== "agent")
|
|
7836
8630
|
return;
|
|
8631
|
+
if (f.name === "agent.hook.PostToolUse") {
|
|
8632
|
+
const p2 = f.payload ?? {};
|
|
8633
|
+
if (p2.phase === "received")
|
|
8634
|
+
return;
|
|
8635
|
+
const rec2 = this.deps.resolve({ cwd: p2.cwd, source: p2._source ?? f.source, sessionId: p2.session_id });
|
|
8636
|
+
if (!rec2)
|
|
8637
|
+
return;
|
|
8638
|
+
this.deps.emit({ type: "task.progress", id: rec2.id, note: f.name });
|
|
8639
|
+
return;
|
|
8640
|
+
}
|
|
7837
8641
|
const runState = f.name ? deriveRunState(f.name) : null;
|
|
7838
8642
|
if (!runState)
|
|
7839
8643
|
return;
|
|
@@ -7960,11 +8764,11 @@ var init_store_fingerprint = __esm({
|
|
|
7960
8764
|
});
|
|
7961
8765
|
|
|
7962
8766
|
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
7963
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
7964
|
-
import { join as
|
|
8767
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
|
|
8768
|
+
import { join as join14 } from "path";
|
|
7965
8769
|
import { homedir as homedir7 } from "os";
|
|
7966
8770
|
async function readCmuxLiveness() {
|
|
7967
|
-
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
8771
|
+
const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join14(homedir7(), ".cmuxterm");
|
|
7968
8772
|
const projects = loadConfig().projects;
|
|
7969
8773
|
let files;
|
|
7970
8774
|
try {
|
|
@@ -7972,7 +8776,7 @@ async function readCmuxLiveness() {
|
|
|
7972
8776
|
} catch (e) {
|
|
7973
8777
|
throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
|
|
7974
8778
|
}
|
|
7975
|
-
return readLivenessSnapshot(files, (f) =>
|
|
8779
|
+
return readLivenessSnapshot(files, (f) => readFileSync10(join14(dir, f), "utf-8"), projects);
|
|
7976
8780
|
}
|
|
7977
8781
|
var DaemonCmux;
|
|
7978
8782
|
var init_daemon_cmux = __esm({
|
|
@@ -8043,9 +8847,9 @@ var init_daemon_cmux = __esm({
|
|
|
8043
8847
|
});
|
|
8044
8848
|
|
|
8045
8849
|
// packages/workspaces/dist/cmux-daemon/cmux-store-source.js
|
|
8046
|
-
import { join as
|
|
8850
|
+
import { join as join15 } from "path";
|
|
8047
8851
|
import { homedir as homedir8 } from "os";
|
|
8048
|
-
import { watch, readdirSync as readdirSync5, readFileSync as
|
|
8852
|
+
import { watch, readdirSync as readdirSync5, readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
8049
8853
|
function parseLifecycleState(s) {
|
|
8050
8854
|
if (s === "running" || s === "idle" || s === "needsInput" || s === "unknown") {
|
|
8051
8855
|
return s;
|
|
@@ -8069,7 +8873,7 @@ function defaultListFiles(dir) {
|
|
|
8069
8873
|
}
|
|
8070
8874
|
function defaultReadFile(path36) {
|
|
8071
8875
|
try {
|
|
8072
|
-
return
|
|
8876
|
+
return readFileSync11(path36, "utf-8");
|
|
8073
8877
|
} catch {
|
|
8074
8878
|
return void 0;
|
|
8075
8879
|
}
|
|
@@ -8105,12 +8909,12 @@ var init_cmux_store_source = __esm({
|
|
|
8105
8909
|
active = false;
|
|
8106
8910
|
lastError = null;
|
|
8107
8911
|
constructor(opts = {}) {
|
|
8108
|
-
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ??
|
|
8912
|
+
this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join15(homedir8(), ".cmuxterm");
|
|
8109
8913
|
this.debounceMs = opts.debounceMs ?? 50;
|
|
8110
8914
|
this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
|
|
8111
8915
|
this.listFiles = opts.listFiles ?? defaultListFiles;
|
|
8112
8916
|
this.readFile = opts.readFile ?? defaultReadFile;
|
|
8113
|
-
this.fileExists = opts.fileExists ??
|
|
8917
|
+
this.fileExists = opts.fileExists ?? existsSync12;
|
|
8114
8918
|
this.watchDir = opts.watchDir ?? defaultWatchDir;
|
|
8115
8919
|
this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
|
|
8116
8920
|
this.cancelTimer = opts.cancelTimer ?? clearTimeout;
|
|
@@ -8167,7 +8971,7 @@ var init_cmux_store_source = __esm({
|
|
|
8167
8971
|
}
|
|
8168
8972
|
scanFile(filename) {
|
|
8169
8973
|
const deps = this.deps;
|
|
8170
|
-
const filePath =
|
|
8974
|
+
const filePath = join15(this.stateDir, filename);
|
|
8171
8975
|
const lockPath = `${filePath}.lock`;
|
|
8172
8976
|
if (this.fileExists(lockPath)) {
|
|
8173
8977
|
this.log(`cmux-store: skipping ${filename} (locked)`);
|
|
@@ -8221,11 +9025,11 @@ var init_cmux_store_source = __esm({
|
|
|
8221
9025
|
});
|
|
8222
9026
|
|
|
8223
9027
|
// packages/workspaces/dist/native-hooks/native-hook-source.js
|
|
8224
|
-
import { join as
|
|
9028
|
+
import { join as join16 } from "path";
|
|
8225
9029
|
import { homedir as homedir9 } from "os";
|
|
8226
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
9030
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
8227
9031
|
function installClaudeHooks(opts = {}) {
|
|
8228
|
-
const settingsPath = opts.settingsPath ??
|
|
9032
|
+
const settingsPath = opts.settingsPath ?? join16(homedir9(), ".claude", "settings.json");
|
|
8229
9033
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
8230
9034
|
const readFile7 = opts.readFile ?? defaultReadFile2;
|
|
8231
9035
|
const writeFile6 = opts.writeFile ?? defaultWriteFile;
|
|
@@ -8321,14 +9125,14 @@ function extractDetail(sub, payload) {
|
|
|
8321
9125
|
}
|
|
8322
9126
|
function defaultReadFile2(path36) {
|
|
8323
9127
|
try {
|
|
8324
|
-
return
|
|
9128
|
+
return readFileSync12(path36, "utf-8");
|
|
8325
9129
|
} catch {
|
|
8326
9130
|
return void 0;
|
|
8327
9131
|
}
|
|
8328
9132
|
}
|
|
8329
9133
|
function defaultWriteFile(path36, content) {
|
|
8330
9134
|
mkdirSync6(path36.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
8331
|
-
|
|
9135
|
+
writeFileSync9(path36, content, "utf-8");
|
|
8332
9136
|
}
|
|
8333
9137
|
var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, NativeHookSource;
|
|
8334
9138
|
var init_native_hook_source = __esm({
|
|
@@ -8474,6 +9278,10 @@ async function paneHasOpenModal(runtime, pane) {
|
|
|
8474
9278
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
8475
9279
|
return hasModalOptionList(screen);
|
|
8476
9280
|
}
|
|
9281
|
+
async function readModalOptions(runtime, pane) {
|
|
9282
|
+
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
9283
|
+
return parseModalOptions(screen);
|
|
9284
|
+
}
|
|
8477
9285
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
8478
9286
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
8479
9287
|
if (hasModalOptionList(preSendScreen)) {
|
|
@@ -8629,6 +9437,7 @@ __export(dist_exports3, {
|
|
|
8629
9437
|
mapSubToLifecycle: () => mapSubToLifecycle,
|
|
8630
9438
|
paneHasOpenModal: () => paneHasOpenModal,
|
|
8631
9439
|
readCmuxLiveness: () => readCmuxLiveness,
|
|
9440
|
+
readModalOptions: () => readModalOptions,
|
|
8632
9441
|
resendCrewFirstTurn: () => resendCrewFirstTurn,
|
|
8633
9442
|
resolveCaptainWorkspace: () => resolveCaptainWorkspace,
|
|
8634
9443
|
sendFirstTurnWhenReady: () => sendFirstTurnWhenReady
|
|
@@ -8680,6 +9489,9 @@ function createClaudeDriver() {
|
|
|
8680
9489
|
if (opts.model) {
|
|
8681
9490
|
cmd += ` --model ${opts.model}`;
|
|
8682
9491
|
}
|
|
9492
|
+
if (opts.thinking) {
|
|
9493
|
+
cmd += ` --effort ${opts.thinking}`;
|
|
9494
|
+
}
|
|
8683
9495
|
if (opts.autoApprove) {
|
|
8684
9496
|
cmd += " --dangerously-skip-permissions";
|
|
8685
9497
|
} else if (opts.permissionMode) {
|
|
@@ -8956,7 +9768,7 @@ var init_registry4 = __esm({
|
|
|
8956
9768
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
8957
9769
|
import fs14 from "fs";
|
|
8958
9770
|
import path14 from "path";
|
|
8959
|
-
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName) {
|
|
9771
|
+
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking) {
|
|
8960
9772
|
const driver = registry.getDriver(agentName);
|
|
8961
9773
|
if (driver.name === "claude") {
|
|
8962
9774
|
let cmd = fresh ? "claude" : "claude -c";
|
|
@@ -8973,6 +9785,9 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
8973
9785
|
if (model) {
|
|
8974
9786
|
cmd += ` --model ${model}`;
|
|
8975
9787
|
}
|
|
9788
|
+
if (thinking) {
|
|
9789
|
+
cmd += ` --effort ${thinking}`;
|
|
9790
|
+
}
|
|
8976
9791
|
if (templatesDir) {
|
|
8977
9792
|
const roleFile2 = path14.join(templatesDir, `${role}.claude.md`);
|
|
8978
9793
|
const legacyRoleFile = path14.join(templatesDir, `${role}.CLAUDE.md`);
|
|
@@ -9753,10 +10568,10 @@ var init_codex_app_server_source = __esm({
|
|
|
9753
10568
|
// packages/agents/dist/codex/config.js
|
|
9754
10569
|
import { readFile as readFile6 } from "fs/promises";
|
|
9755
10570
|
import { homedir as homedir10 } from "os";
|
|
9756
|
-
import { join as
|
|
10571
|
+
import { join as join17 } from "path";
|
|
9757
10572
|
async function resolveCodexModel() {
|
|
9758
|
-
const home = process.env["CODEX_HOME"] ??
|
|
9759
|
-
const configPath =
|
|
10573
|
+
const home = process.env["CODEX_HOME"] ?? join17(homedir10(), ".codex");
|
|
10574
|
+
const configPath = join17(home, "config.toml");
|
|
9760
10575
|
let text;
|
|
9761
10576
|
try {
|
|
9762
10577
|
text = await readFile6(configPath, "utf8");
|
|
@@ -9873,9 +10688,9 @@ ${directive}` : directive;
|
|
|
9873
10688
|
function withTimeout(p, ms, msg) {
|
|
9874
10689
|
return new Promise((resolve4, reject) => {
|
|
9875
10690
|
const t = setTimeout(() => reject(new Error(msg)), ms);
|
|
9876
|
-
p.then((
|
|
10691
|
+
p.then((v2) => {
|
|
9877
10692
|
clearTimeout(t);
|
|
9878
|
-
resolve4(
|
|
10693
|
+
resolve4(v2);
|
|
9879
10694
|
}, (e) => {
|
|
9880
10695
|
clearTimeout(t);
|
|
9881
10696
|
reject(e);
|
|
@@ -10109,9 +10924,10 @@ var init_driver = __esm({
|
|
|
10109
10924
|
});
|
|
10110
10925
|
|
|
10111
10926
|
// packages/agents/dist/opencode/sse-bridge.js
|
|
10112
|
-
var OpencodeSseBridge;
|
|
10927
|
+
var IGNORED_FRAME, OpencodeSseBridge;
|
|
10113
10928
|
var init_sse_bridge = __esm({
|
|
10114
10929
|
"packages/agents/dist/opencode/sse-bridge.js"() {
|
|
10930
|
+
IGNORED_FRAME = /^(message|storage|file|lsp|installation)\./;
|
|
10115
10931
|
OpencodeSseBridge = class {
|
|
10116
10932
|
controllers = /* @__PURE__ */ new Map();
|
|
10117
10933
|
/** taskId → the crew's opencode server port (for permission-reply POSTs). */
|
|
@@ -10248,6 +11064,10 @@ var init_sse_bridge = __esm({
|
|
|
10248
11064
|
return;
|
|
10249
11065
|
}
|
|
10250
11066
|
if (json?.type === "session.idle") {
|
|
11067
|
+
if (this.deps.ingest) {
|
|
11068
|
+
this.deps.ingest(json, taskId);
|
|
11069
|
+
return;
|
|
11070
|
+
}
|
|
10251
11071
|
this.deps.emit({
|
|
10252
11072
|
type: "task.turn.completed",
|
|
10253
11073
|
id: taskId,
|
|
@@ -10257,6 +11077,10 @@ var init_sse_bridge = __esm({
|
|
|
10257
11077
|
const p = json.properties;
|
|
10258
11078
|
if (p?.id && p?.sessionID) {
|
|
10259
11079
|
this.pendingPermByTask.set(taskId, { permID: p.id, sessionID: p.sessionID });
|
|
11080
|
+
if (this.deps.ingest) {
|
|
11081
|
+
this.deps.ingest(json, taskId);
|
|
11082
|
+
return;
|
|
11083
|
+
}
|
|
10260
11084
|
const tool = p.permission ?? "a tool";
|
|
10261
11085
|
const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
|
|
10262
11086
|
this.deps.emit({
|
|
@@ -10266,20 +11090,33 @@ var init_sse_bridge = __esm({
|
|
|
10266
11090
|
question: `opencode requests permission to run ${tool}${cmd}`,
|
|
10267
11091
|
kind: tool
|
|
10268
11092
|
});
|
|
11093
|
+
} else if (this.deps.ingest) {
|
|
11094
|
+
this.deps.ingest(json, taskId);
|
|
10269
11095
|
}
|
|
10270
11096
|
} else if (json?.type === "permission.replied") {
|
|
10271
11097
|
this.pendingPermByTask.delete(taskId);
|
|
11098
|
+
this.deps.ingest?.(json, taskId);
|
|
11099
|
+
} else if (!IGNORED_FRAME.test(json?.type ?? "")) {
|
|
11100
|
+
this.deps.ingest?.(json, taskId);
|
|
10272
11101
|
}
|
|
10273
11102
|
}
|
|
11103
|
+
/** Test seam: exercise handleLine without an SSE stream. */
|
|
11104
|
+
handleLineForTest(rawLine, taskId) {
|
|
11105
|
+
this.handleLine(taskId, rawLine);
|
|
11106
|
+
}
|
|
11107
|
+
/** Test seam: read pendingPermByTask without exposing it publicly. */
|
|
11108
|
+
pendingPermForTest(taskId) {
|
|
11109
|
+
return this.pendingPermByTask.get(taskId);
|
|
11110
|
+
}
|
|
10274
11111
|
};
|
|
10275
11112
|
}
|
|
10276
11113
|
});
|
|
10277
11114
|
|
|
10278
11115
|
// packages/agents/dist/interactive/claude.js
|
|
10279
11116
|
import { execSync as execSync7 } from "child_process";
|
|
10280
|
-
import { readFileSync as
|
|
11117
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
10281
11118
|
import { homedir as homedir11 } from "os";
|
|
10282
|
-
import { join as
|
|
11119
|
+
import { join as join18 } from "path";
|
|
10283
11120
|
function probeClaudeSettingsFlag() {
|
|
10284
11121
|
try {
|
|
10285
11122
|
const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -10337,11 +11174,11 @@ function deriveTranscriptPath(sessionId, cwd) {
|
|
|
10337
11174
|
if (!sessionId || !cwd)
|
|
10338
11175
|
return null;
|
|
10339
11176
|
const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
10340
|
-
return
|
|
11177
|
+
return join18(homedir11(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
|
|
10341
11178
|
}
|
|
10342
11179
|
function readLastAssistantText(transcriptPath) {
|
|
10343
11180
|
try {
|
|
10344
|
-
const raw =
|
|
11181
|
+
const raw = readFileSync13(transcriptPath, "utf-8");
|
|
10345
11182
|
const lines = raw.split(/\r?\n/);
|
|
10346
11183
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10347
11184
|
const line = lines[i].trim();
|
|
@@ -10407,6 +11244,29 @@ function formatAskUserQuestionPrompt(toolInput) {
|
|
|
10407
11244
|
}
|
|
10408
11245
|
return parts.length > 0 ? parts.join(" | ") : null;
|
|
10409
11246
|
}
|
|
11247
|
+
function decideCaptainMemoryWrite(toolName, toolInput, env, homeDir) {
|
|
11248
|
+
if (!env.SQUADRANT_CREW_TASK_ID)
|
|
11249
|
+
return { decision: "allow" };
|
|
11250
|
+
if (toolName === "Bash") {
|
|
11251
|
+
const command = toolInput?.command;
|
|
11252
|
+
if (typeof command === "string" && CAPTAIN_MEMORY_PATH_RE.test(command)) {
|
|
11253
|
+
return { decision: "deny", reason: DENY_REASON };
|
|
11254
|
+
}
|
|
11255
|
+
return { decision: "allow" };
|
|
11256
|
+
}
|
|
11257
|
+
const field = FILE_PATH_FIELD_BY_TOOL[toolName];
|
|
11258
|
+
if (!field)
|
|
11259
|
+
return { decision: "allow" };
|
|
11260
|
+
const filePath = toolInput?.[field];
|
|
11261
|
+
if (typeof filePath !== "string" || !filePath)
|
|
11262
|
+
return { decision: "allow" };
|
|
11263
|
+
const home = homeDir.endsWith("/") ? homeDir.slice(0, -1) : homeDir;
|
|
11264
|
+
if (!filePath.startsWith(home))
|
|
11265
|
+
return { decision: "allow" };
|
|
11266
|
+
if (!CAPTAIN_MEMORY_PATH_RE.test(filePath))
|
|
11267
|
+
return { decision: "allow" };
|
|
11268
|
+
return { decision: "deny", reason: DENY_REASON };
|
|
11269
|
+
}
|
|
10410
11270
|
function mapClaudeHookToEvent(event, payload, taskId) {
|
|
10411
11271
|
switch (event) {
|
|
10412
11272
|
case "PreToolUse": {
|
|
@@ -10442,7 +11302,7 @@ function mapClaudeHookToEvent(event, payload, taskId) {
|
|
|
10442
11302
|
return null;
|
|
10443
11303
|
}
|
|
10444
11304
|
}
|
|
10445
|
-
var EVENTS, MATCHED_EVENTS, nextAskUserQuestionRequestId, claudeInteractive;
|
|
11305
|
+
var EVENTS, MATCHED_EVENTS, nextAskUserQuestionRequestId, CAPTAIN_MEMORY_PATH_RE, DENY_REASON, FILE_PATH_FIELD_BY_TOOL, claudeInteractive;
|
|
10446
11306
|
var init_claude3 = __esm({
|
|
10447
11307
|
"packages/agents/dist/interactive/claude.js"() {
|
|
10448
11308
|
EVENTS = ["Stop", "SubagentStop", "SessionEnd", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
@@ -10450,6 +11310,14 @@ var init_claude3 = __esm({
|
|
|
10450
11310
|
["PreToolUse", "AskUserQuestion"]
|
|
10451
11311
|
];
|
|
10452
11312
|
nextAskUserQuestionRequestId = Date.now();
|
|
11313
|
+
CAPTAIN_MEMORY_PATH_RE = /\.claude\/projects\/.*\/memory\//;
|
|
11314
|
+
DENY_REASON = "Crews do not write captain memory. Put the finding in your done/blocked message; the captain decides what is durable (#556).";
|
|
11315
|
+
FILE_PATH_FIELD_BY_TOOL = {
|
|
11316
|
+
Write: "file_path",
|
|
11317
|
+
Edit: "file_path",
|
|
11318
|
+
MultiEdit: "file_path",
|
|
11319
|
+
NotebookEdit: "notebook_path"
|
|
11320
|
+
};
|
|
10453
11321
|
claudeInteractive = {
|
|
10454
11322
|
provider: "claude",
|
|
10455
11323
|
tier: "strong",
|
|
@@ -10505,8 +11373,11 @@ function classifyPaneTail2(tail) {
|
|
|
10505
11373
|
if (q)
|
|
10506
11374
|
return { kind: "question", text: q };
|
|
10507
11375
|
let errLine = null;
|
|
10508
|
-
for (
|
|
10509
|
-
|
|
11376
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
11377
|
+
const c = cleaned[i];
|
|
11378
|
+
if (c == null || isQuotedLine2(raw[i]))
|
|
11379
|
+
continue;
|
|
11380
|
+
if (ERROR_BANNER_RE2.some((re) => re.test(c)))
|
|
10510
11381
|
errLine = c;
|
|
10511
11382
|
}
|
|
10512
11383
|
if (errLine) {
|
|
@@ -10518,6 +11389,10 @@ function classifyPaneTail2(tail) {
|
|
|
10518
11389
|
}
|
|
10519
11390
|
return null;
|
|
10520
11391
|
}
|
|
11392
|
+
function isQuotedLine2(raw) {
|
|
11393
|
+
const noAnsi = raw.replace(/\[[0-9;]*m/g, "");
|
|
11394
|
+
return QUOTED_PREFIX_RE2.test(noAnsi);
|
|
11395
|
+
}
|
|
10521
11396
|
function stripChrome2(raw) {
|
|
10522
11397
|
let line = raw.replace(/\[[0-9;]*m/g, "");
|
|
10523
11398
|
line = line.replace(/^[\s│┃▏▕|]+/, "").replace(/[\s│┃▏▕|]+$/, "");
|
|
@@ -10532,7 +11407,7 @@ function stripChrome2(raw) {
|
|
|
10532
11407
|
return null;
|
|
10533
11408
|
return trimmed;
|
|
10534
11409
|
}
|
|
10535
|
-
var ERROR_BANNER_RE2, RETRYING_RE, EXHAUSTED_RE, OPTION_RE2, PICKER_FOOTER_RE2, PURE_CHROME_RE2, STATUS_LINE_RE2;
|
|
11410
|
+
var ERROR_BANNER_RE2, RETRYING_RE, EXHAUSTED_RE, QUOTED_PREFIX_RE2, OPTION_RE2, PICKER_FOOTER_RE2, PURE_CHROME_RE2, STATUS_LINE_RE2;
|
|
10536
11411
|
var init_pane_classifier = __esm({
|
|
10537
11412
|
"packages/agents/dist/interactive/pane-classifier.js"() {
|
|
10538
11413
|
init_claude3();
|
|
@@ -10547,6 +11422,7 @@ var init_pane_classifier = __esm({
|
|
|
10547
11422
|
];
|
|
10548
11423
|
RETRYING_RE = /\bRetrying\b|\battempt\s+\d+\s*\/\s*\d+/i;
|
|
10549
11424
|
EXHAUSTED_RE = /\bretr(?:y|ies)\s+(?:exhausted|limit\s+(?:reached|exceeded))\b|\bmaximum\s+retries\b/i;
|
|
11425
|
+
QUOTED_PREFIX_RE2 = /^\s*(?:[┃│▏▕]|>|[+-]|\d+[\t:→])\s/;
|
|
10550
11426
|
OPTION_RE2 = /^[❯>›]?\s*(\d+)\.\s+(.*\S)\s*$/;
|
|
10551
11427
|
PICKER_FOOTER_RE2 = /↑↓\s*select|enter\s+submit|esc\s+dismiss/i;
|
|
10552
11428
|
PURE_CHROME_RE2 = /^[\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;
|
|
@@ -10776,7 +11652,7 @@ var init_headless_launcher = __esm({
|
|
|
10776
11652
|
|
|
10777
11653
|
// packages/agents/dist/claude/registry.js
|
|
10778
11654
|
import fs15 from "fs";
|
|
10779
|
-
import { join as
|
|
11655
|
+
import { join as join19 } from "path";
|
|
10780
11656
|
import { homedir as homedir12 } from "os";
|
|
10781
11657
|
function parseRegistryDir(files, readFile7) {
|
|
10782
11658
|
const out = [];
|
|
@@ -10824,7 +11700,7 @@ function readClaudeStatus(task) {
|
|
|
10824
11700
|
} catch {
|
|
10825
11701
|
return void 0;
|
|
10826
11702
|
}
|
|
10827
|
-
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(
|
|
11703
|
+
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(join19(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
10828
11704
|
let entry;
|
|
10829
11705
|
if (task.messagingSocketPath) {
|
|
10830
11706
|
entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
|
|
@@ -10843,7 +11719,7 @@ function readClaudeStatusByCwd(cwd) {
|
|
|
10843
11719
|
} catch {
|
|
10844
11720
|
return void 0;
|
|
10845
11721
|
}
|
|
10846
|
-
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(
|
|
11722
|
+
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(join19(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
10847
11723
|
const entry = entries.find((e) => e.cwd === cwd);
|
|
10848
11724
|
if (!entry)
|
|
10849
11725
|
return void 0;
|
|
@@ -10856,7 +11732,7 @@ function readClaudeStatusBySocketPath(socketPath2) {
|
|
|
10856
11732
|
} catch {
|
|
10857
11733
|
return void 0;
|
|
10858
11734
|
}
|
|
10859
|
-
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(
|
|
11735
|
+
const entries = parseRegistryDir(files, (name) => fs15.readFileSync(join19(CLAUDE_SESSIONS_DIR, name), "utf8"));
|
|
10860
11736
|
const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
|
|
10861
11737
|
if (!entry)
|
|
10862
11738
|
return void 0;
|
|
@@ -10865,14 +11741,14 @@ function readClaudeStatusBySocketPath(socketPath2) {
|
|
|
10865
11741
|
var CLAUDE_SESSIONS_DIR, PID_JSON;
|
|
10866
11742
|
var init_registry8 = __esm({
|
|
10867
11743
|
"packages/agents/dist/claude/registry.js"() {
|
|
10868
|
-
CLAUDE_SESSIONS_DIR =
|
|
11744
|
+
CLAUDE_SESSIONS_DIR = join19(homedir12(), ".claude", "sessions");
|
|
10869
11745
|
PID_JSON = /^(\d+)\.json$/;
|
|
10870
11746
|
}
|
|
10871
11747
|
});
|
|
10872
11748
|
|
|
10873
11749
|
// packages/agents/dist/claude/peer-registry-source.js
|
|
10874
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
10875
|
-
import { join as
|
|
11750
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
|
|
11751
|
+
import { join as join20 } from "path";
|
|
10876
11752
|
function defaultIsAlive(pid) {
|
|
10877
11753
|
try {
|
|
10878
11754
|
process.kill(pid, 0);
|
|
@@ -10901,7 +11777,7 @@ var init_peer_registry_source = __esm({
|
|
|
10901
11777
|
log;
|
|
10902
11778
|
constructor(o = {}) {
|
|
10903
11779
|
this.readdir = o.readdir ?? (() => readdirSync6(CLAUDE_SESSIONS_DIR));
|
|
10904
|
-
this.readFile = o.readFile ?? ((n) =>
|
|
11780
|
+
this.readFile = o.readFile ?? ((n) => readFileSync14(join20(CLAUDE_SESSIONS_DIR, n), "utf8"));
|
|
10905
11781
|
this.isAlive = o.isAlive ?? defaultIsAlive;
|
|
10906
11782
|
this.now = o.now ?? Date.now;
|
|
10907
11783
|
this.pollMs = o.pollMs ?? 2e3;
|
|
@@ -11231,77 +12107,6 @@ var init_receipt_listener = __esm({
|
|
|
11231
12107
|
}
|
|
11232
12108
|
});
|
|
11233
12109
|
|
|
11234
|
-
// packages/agents/dist/opencode/control-source.js
|
|
11235
|
-
function toSnapshot2(ev) {
|
|
11236
|
-
const now = Date.now();
|
|
11237
|
-
switch (ev.type) {
|
|
11238
|
-
// A permission was answered on the bus and the turn resumed.
|
|
11239
|
-
case "task.started":
|
|
11240
|
-
return { taskId: ev.id, state: "running", alive: true, origin: "agent", at: now };
|
|
11241
|
-
// session.idle — the turn finished. Liveness, NOT completion (anti-#2576).
|
|
11242
|
-
case "task.turn.completed":
|
|
11243
|
-
return { taskId: ev.id, state: "idle", alive: true, origin: "agent", at: now };
|
|
11244
|
-
// permission.asked — opencode STATES it is gated. No guessing from pixels.
|
|
11245
|
-
case "task.approval.requested":
|
|
11246
|
-
return {
|
|
11247
|
-
taskId: ev.id,
|
|
11248
|
-
state: "needsInput",
|
|
11249
|
-
alive: true,
|
|
11250
|
-
origin: "agent",
|
|
11251
|
-
at: now,
|
|
11252
|
-
detail: { note: ev.question, reason: ev.kind }
|
|
11253
|
-
};
|
|
11254
|
-
// Terminal (task.done/blocked/cancelled) and notify-only events are ignored:
|
|
11255
|
-
// terminal state comes exclusively from `squadrant crew signal`.
|
|
11256
|
-
default:
|
|
11257
|
-
return null;
|
|
11258
|
-
}
|
|
11259
|
-
}
|
|
11260
|
-
var OpencodeControlSource;
|
|
11261
|
-
var init_control_source = __esm({
|
|
11262
|
-
"packages/agents/dist/opencode/control-source.js"() {
|
|
11263
|
-
OpencodeControlSource = class {
|
|
11264
|
-
name = "opencode-control";
|
|
11265
|
-
deps;
|
|
11266
|
-
active = false;
|
|
11267
|
-
cache = /* @__PURE__ */ new Map();
|
|
11268
|
-
start(deps) {
|
|
11269
|
-
this.deps = deps;
|
|
11270
|
-
this.active = true;
|
|
11271
|
-
}
|
|
11272
|
-
stop() {
|
|
11273
|
-
this.deps = void 0;
|
|
11274
|
-
this.active = false;
|
|
11275
|
-
this.cache.clear();
|
|
11276
|
-
}
|
|
11277
|
-
/** Push-only source — no fallible startup of its own. */
|
|
11278
|
-
health() {
|
|
11279
|
-
return { active: this.active, error: null };
|
|
11280
|
-
}
|
|
11281
|
-
/** Liveness floor: origin must be "scan" and must not assert needsInput. */
|
|
11282
|
-
snapshot(taskId) {
|
|
11283
|
-
const s = this.cache.get(taskId);
|
|
11284
|
-
if (!s)
|
|
11285
|
-
return void 0;
|
|
11286
|
-
return { ...s, origin: "scan", state: s.state === "needsInput" ? "running" : s.state };
|
|
11287
|
-
}
|
|
11288
|
-
/**
|
|
11289
|
-
* Feed one ControlEvent from OpencodeSseBridge into the port.
|
|
11290
|
-
* Wired in squadrantd.ts as: emit = (ev) => { source.observe(ev); …existing… }
|
|
11291
|
-
*/
|
|
11292
|
-
observe(ev) {
|
|
11293
|
-
if (!this.deps)
|
|
11294
|
-
return;
|
|
11295
|
-
const snap = toSnapshot2(ev);
|
|
11296
|
-
if (!snap)
|
|
11297
|
-
return;
|
|
11298
|
-
this.cache.set(snap.taskId, snap);
|
|
11299
|
-
this.deps.report(snap);
|
|
11300
|
-
}
|
|
11301
|
-
};
|
|
11302
|
-
}
|
|
11303
|
-
});
|
|
11304
|
-
|
|
11305
12110
|
// packages/agents/dist/opencode/http-channel.js
|
|
11306
12111
|
var OpencodeHttpChannel;
|
|
11307
12112
|
var init_http_channel = __esm({
|
|
@@ -11413,6 +12218,47 @@ var init_http_channel = __esm({
|
|
|
11413
12218
|
}
|
|
11414
12219
|
});
|
|
11415
12220
|
|
|
12221
|
+
// packages/agents/dist/opencode/fact-adapter.js
|
|
12222
|
+
function createOpencodeFactAdapter(deps) {
|
|
12223
|
+
return {
|
|
12224
|
+
name: "opencode-sse",
|
|
12225
|
+
origin: "agent",
|
|
12226
|
+
translate(raw) {
|
|
12227
|
+
const f = typeof raw === "object" && raw !== null ? raw : {};
|
|
12228
|
+
const type = typeof f.type === "string" ? f.type : void 0;
|
|
12229
|
+
if (type === void 0)
|
|
12230
|
+
return [{ kind: "unknown", name: "non-object" }];
|
|
12231
|
+
const p = f.properties ?? {};
|
|
12232
|
+
if (type === "session.idle") {
|
|
12233
|
+
return [{
|
|
12234
|
+
kind: "turn.ended",
|
|
12235
|
+
turnId: typeof p.sessionID === "string" ? p.sessionID : void 0
|
|
12236
|
+
}];
|
|
12237
|
+
}
|
|
12238
|
+
if (type === "permission.asked") {
|
|
12239
|
+
if (typeof p.id !== "string" || typeof p.sessionID !== "string") {
|
|
12240
|
+
return [{ kind: "unknown", name: "permission.asked:incomplete" }];
|
|
12241
|
+
}
|
|
12242
|
+
const tool = typeof p.permission === "string" ? p.permission : "a tool";
|
|
12243
|
+
const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(" ")}` : "";
|
|
12244
|
+
return [{
|
|
12245
|
+
kind: "permission.requested",
|
|
12246
|
+
question: `opencode requests permission to run ${tool}${cmd}`,
|
|
12247
|
+
requestId: deps.nextRequestId(),
|
|
12248
|
+
tool
|
|
12249
|
+
}];
|
|
12250
|
+
}
|
|
12251
|
+
if (type === "permission.replied")
|
|
12252
|
+
return [{ kind: "activity" }];
|
|
12253
|
+
return [{ kind: "unknown", name: type }];
|
|
12254
|
+
}
|
|
12255
|
+
};
|
|
12256
|
+
}
|
|
12257
|
+
var init_fact_adapter = __esm({
|
|
12258
|
+
"packages/agents/dist/opencode/fact-adapter.js"() {
|
|
12259
|
+
}
|
|
12260
|
+
});
|
|
12261
|
+
|
|
11416
12262
|
// packages/agents/dist/index.js
|
|
11417
12263
|
var dist_exports4 = {};
|
|
11418
12264
|
__export(dist_exports4, {
|
|
@@ -11427,7 +12273,6 @@ __export(dist_exports4, {
|
|
|
11427
12273
|
HEADLESS_ERROR_TAIL: () => HEADLESS_ERROR_TAIL,
|
|
11428
12274
|
MARKER_END: () => MARKER_END,
|
|
11429
12275
|
MARKER_START: () => MARKER_START,
|
|
11430
|
-
OpencodeControlSource: () => OpencodeControlSource,
|
|
11431
12276
|
OpencodeHttpChannel: () => OpencodeHttpChannel,
|
|
11432
12277
|
OpencodeSseBridge: () => OpencodeSseBridge,
|
|
11433
12278
|
ProjectionRegistry: () => ProjectionRegistry,
|
|
@@ -11447,6 +12292,8 @@ __export(dist_exports4, {
|
|
|
11447
12292
|
createGeminiEmitter: () => createGeminiEmitter,
|
|
11448
12293
|
createOpencodeDriver: () => createOpencodeDriver,
|
|
11449
12294
|
createOpencodeEmitter: () => createOpencodeEmitter,
|
|
12295
|
+
createOpencodeFactAdapter: () => createOpencodeFactAdapter,
|
|
12296
|
+
decideCaptainMemoryWrite: () => decideCaptainMemoryWrite,
|
|
11450
12297
|
deriveTranscriptPath: () => deriveTranscriptPath,
|
|
11451
12298
|
detectTrailingQuestion: () => detectTrailingQuestion2,
|
|
11452
12299
|
formatAskUserQuestionPrompt: () => formatAskUserQuestionPrompt,
|
|
@@ -11488,8 +12335,8 @@ var init_dist4 = __esm({
|
|
|
11488
12335
|
init_receipt_listener();
|
|
11489
12336
|
init_peer_wire();
|
|
11490
12337
|
init_registry8();
|
|
11491
|
-
init_control_source();
|
|
11492
12338
|
init_http_channel();
|
|
12339
|
+
init_fact_adapter();
|
|
11493
12340
|
}
|
|
11494
12341
|
});
|
|
11495
12342
|
|
|
@@ -11568,9 +12415,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
|
|
|
11568
12415
|
const resolved = resolveTarget(registry, config, target, !!opts.command);
|
|
11569
12416
|
await needRef(resolved);
|
|
11570
12417
|
const finalProject = opts.command ? config.commandName : target;
|
|
11571
|
-
const { join:
|
|
12418
|
+
const { join: join30, dirname: dirname9 } = await import("path");
|
|
11572
12419
|
const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
11573
|
-
const stateRoot =
|
|
12420
|
+
const stateRoot = join30(dirname9(DEFAULT_CONFIG_PATH2), "state");
|
|
11574
12421
|
const seq = await appendCaptainMessage2({
|
|
11575
12422
|
stateRoot,
|
|
11576
12423
|
project: finalProject,
|
|
@@ -11676,10 +12523,10 @@ var init_runtime2 = __esm({
|
|
|
11676
12523
|
init_dist();
|
|
11677
12524
|
init_dist2();
|
|
11678
12525
|
import { Command as Command35 } from "commander";
|
|
11679
|
-
import { existsSync as
|
|
12526
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
11680
12527
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
11681
|
-
import { dirname as dirname8, join as
|
|
11682
|
-
import { homedir as
|
|
12528
|
+
import { dirname as dirname8, join as join29 } from "path";
|
|
12529
|
+
import { homedir as homedir18 } from "os";
|
|
11683
12530
|
|
|
11684
12531
|
// packages/cli/src/commands/doctor.ts
|
|
11685
12532
|
init_dist();
|
|
@@ -12085,8 +12932,8 @@ import chalk4 from "chalk";
|
|
|
12085
12932
|
|
|
12086
12933
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
12087
12934
|
init_dist4();
|
|
12088
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
12089
|
-
import { dirname as dirname4, join as
|
|
12935
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
12936
|
+
import { dirname as dirname4, join as join21 } from "path";
|
|
12090
12937
|
import { homedir as homedir13 } from "os";
|
|
12091
12938
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
12092
12939
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
@@ -12178,21 +13025,21 @@ function mergeCrewPermissions(settings) {
|
|
|
12178
13025
|
return next;
|
|
12179
13026
|
}
|
|
12180
13027
|
function writePerCrewSettingsLocal(o) {
|
|
12181
|
-
const dir =
|
|
13028
|
+
const dir = join21(o.projectCwd, ".claude");
|
|
12182
13029
|
mkdirSync7(dir, { recursive: true });
|
|
12183
|
-
const file =
|
|
13030
|
+
const file = join21(dir, "settings.local.json");
|
|
12184
13031
|
let existing = {};
|
|
12185
13032
|
try {
|
|
12186
|
-
const raw = healStaleCockpitRefs(
|
|
13033
|
+
const raw = healStaleCockpitRefs(readFileSync15(file, "utf-8"));
|
|
12187
13034
|
existing = JSON.parse(raw);
|
|
12188
13035
|
} catch {
|
|
12189
13036
|
}
|
|
12190
13037
|
const withHooks = mergeClaudeHooks(existing, o.hookCmd ?? "squadrant crew _hook");
|
|
12191
13038
|
const merged = mergeCrewPermissions(withHooks);
|
|
12192
|
-
|
|
13039
|
+
writeFileSync10(file, JSON.stringify(merged, null, 2));
|
|
12193
13040
|
return file;
|
|
12194
13041
|
}
|
|
12195
|
-
var DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH =
|
|
13042
|
+
var DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH = join21(homedir13(), ".config", "opencode", "opencode.json");
|
|
12196
13043
|
function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH) {
|
|
12197
13044
|
mkdirSync7(dirname4(configPath), { recursive: true });
|
|
12198
13045
|
const defaultConfig = {
|
|
@@ -12200,7 +13047,7 @@ function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_
|
|
|
12200
13047
|
model: "anthropic/claude-sonnet-4-5"
|
|
12201
13048
|
};
|
|
12202
13049
|
try {
|
|
12203
|
-
|
|
13050
|
+
writeFileSync10(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", { flag: "wx" });
|
|
12204
13051
|
return configPath;
|
|
12205
13052
|
} catch (err) {
|
|
12206
13053
|
if (err.code === "EEXIST") return null;
|
|
@@ -12209,16 +13056,16 @@ function ensureGlobalOpencodeConfig(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_
|
|
|
12209
13056
|
}
|
|
12210
13057
|
function readGlobalOpencodeModel(configPath = DEFAULT_GLOBAL_OPENCODE_CONFIG_PATH) {
|
|
12211
13058
|
try {
|
|
12212
|
-
const parsed = JSON.parse(
|
|
13059
|
+
const parsed = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
12213
13060
|
return typeof parsed.model === "string" ? parsed.model : void 0;
|
|
12214
13061
|
} catch {
|
|
12215
13062
|
return void 0;
|
|
12216
13063
|
}
|
|
12217
13064
|
}
|
|
12218
13065
|
function writePerCrewOpencodeConfig(o) {
|
|
12219
|
-
const dir =
|
|
13066
|
+
const dir = join21(o.stateRoot, o.project, o.taskId);
|
|
12220
13067
|
mkdirSync7(dir, { recursive: true });
|
|
12221
|
-
const file =
|
|
13068
|
+
const file = join21(dir, "opencode.json");
|
|
12222
13069
|
const config = {
|
|
12223
13070
|
permission: {
|
|
12224
13071
|
read: "allow",
|
|
@@ -12233,7 +13080,7 @@ function writePerCrewOpencodeConfig(o) {
|
|
|
12233
13080
|
external_directory: { "**": "allow" }
|
|
12234
13081
|
}
|
|
12235
13082
|
};
|
|
12236
|
-
|
|
13083
|
+
writeFileSync10(file, JSON.stringify(config, null, 2));
|
|
12237
13084
|
return file;
|
|
12238
13085
|
}
|
|
12239
13086
|
|
|
@@ -12721,8 +13568,8 @@ import { createConnection as createConnection3 } from "net";
|
|
|
12721
13568
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
12722
13569
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
12723
13570
|
import { homedir as homedir14 } from "os";
|
|
12724
|
-
import { join as
|
|
12725
|
-
import { mkdirSync as mkdirSync8, writeFileSync as
|
|
13571
|
+
import { join as join22 } from "path";
|
|
13572
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
12726
13573
|
|
|
12727
13574
|
// packages/cli/src/commands/crew-output.ts
|
|
12728
13575
|
function tailLines(text, maxLines = 40, maxBytes = 4096) {
|
|
@@ -13180,10 +14027,10 @@ function buildSignalRequest(signal, o) {
|
|
|
13180
14027
|
return { kind: "event", project, event };
|
|
13181
14028
|
}
|
|
13182
14029
|
function defaultWriteResult(id, payload) {
|
|
13183
|
-
const dir =
|
|
14030
|
+
const dir = join22(homedir14(), ".config", "squadrant", "state", "_results");
|
|
13184
14031
|
mkdirSync8(dir, { recursive: true });
|
|
13185
|
-
const file =
|
|
13186
|
-
|
|
14032
|
+
const file = join22(dir, `${id}.txt`);
|
|
14033
|
+
writeFileSync11(file, payload);
|
|
13187
14034
|
return file;
|
|
13188
14035
|
}
|
|
13189
14036
|
async function runCrewSignal(signal, o, deps) {
|
|
@@ -13196,7 +14043,7 @@ async function runCrewSignal(signal, o, deps) {
|
|
|
13196
14043
|
const current = await deps.call(buildStatusRequest(project, taskId));
|
|
13197
14044
|
if (current && TERMINAL_STATES.has(current.state)) {
|
|
13198
14045
|
throw new Error(
|
|
13199
|
-
`Task ${taskId} is already terminal (state=${current.state}) \u2014 signal '${signal}' would be silently ignored by the daemon. Stop here: your task record was never reopened for this turn. Ask the captain to run 'squadrant crew send' to reopen it before signaling again.`
|
|
14046
|
+
`Task ${taskId} is already terminal (state=${current.state}) \u2014 signal '${signal}' would be silently ignored by the daemon. Stop here: your task record was never reopened for this turn. Ask the captain to run 'squadrant crew send' to reopen it \u2014 its output confirms with "\u21BB Task was terminal \u2014 reopened to working" (#595) \u2014 before signaling again.`
|
|
13200
14047
|
);
|
|
13201
14048
|
}
|
|
13202
14049
|
const req = buildSignalRequest(signal, { ...o, writeResult: o.writeResult ?? defaultWriteResult });
|
|
@@ -13259,6 +14106,20 @@ async function runCrewApprove(project, crew, deps) {
|
|
|
13259
14106
|
});
|
|
13260
14107
|
return prUrl;
|
|
13261
14108
|
}
|
|
14109
|
+
async function runCrewReply(project, id, message, deps) {
|
|
14110
|
+
const tasks = await deps.listTasks(project);
|
|
14111
|
+
const matches = tasks.filter((t) => t.id === id || t.id.startsWith(id));
|
|
14112
|
+
if (matches.length === 0) throw new Error(`unknown task ${id}`);
|
|
14113
|
+
if (matches.length > 1) {
|
|
14114
|
+
throw new Error(`task id '${id}' is ambiguous \u2014 matches ${matches.map((t) => t.id).join(", ")}`);
|
|
14115
|
+
}
|
|
14116
|
+
const target = matches[0];
|
|
14117
|
+
if (!target.name) {
|
|
14118
|
+
throw new Error(`task ${id} has no crew name on record \u2014 cannot deliver via 'crew reply'`);
|
|
14119
|
+
}
|
|
14120
|
+
await deps.sendCrew(project, target.name, message);
|
|
14121
|
+
return deps.getStatus(project, target.id);
|
|
14122
|
+
}
|
|
13262
14123
|
function addControlPlaneCrewCommands(crew) {
|
|
13263
14124
|
crew.command("dispatch <project> <task>").description("Dispatch a crew task via the control-plane daemon").requiredOption("--provider <p>", "claude|opencode|codex (gemini: experimental, headless not supported)").option("--mode <m>", "headless|interactive", "interactive").option("--cwd <dir>", "working dir for the crew (project/worktree); required for codex to edit code").action(async (project, task, opts) => {
|
|
13264
14125
|
const req = buildDispatchRequest({ project, task, provider: opts.provider, mode: opts.mode, cwd: opts.cwd });
|
|
@@ -13295,14 +14156,18 @@ function addControlPlaneCrewCommands(crew) {
|
|
|
13295
14156
|
const compact = opts.json !== true;
|
|
13296
14157
|
process.stdout.write(formatCompactTasks(records, { compact }) + "\n");
|
|
13297
14158
|
});
|
|
13298
|
-
crew.command("reply <project> <id>
|
|
14159
|
+
crew.command("reply <project> <id> [message]").description("Reply to a blocked crew task \u2014 delivers the message first, then transitions state; never transitions on a dropped message. Resolve a gate via --gate.").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").option("--gate <gateId>", "resolve a pending gate by id (codex interactive, spec \xA74.9)").action(async (project, id, message, opts) => {
|
|
14160
|
+
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
13299
14161
|
if (opts.gate) {
|
|
13300
|
-
const r2 = await squadrantdCall(buildGateResolveRequest({ project, gateId: opts.gate, message }));
|
|
14162
|
+
const r2 = await squadrantdCall(buildGateResolveRequest({ project, gateId: opts.gate, message: resolvedMessage }));
|
|
13301
14163
|
process.stdout.write(JSON.stringify(r2) + "\n");
|
|
13302
14164
|
return;
|
|
13303
14165
|
}
|
|
13304
|
-
|
|
13305
|
-
|
|
14166
|
+
const r = await runCrewReply(project, id, resolvedMessage, {
|
|
14167
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
14168
|
+
sendCrew: (p, name, msg) => runCrewSend2(p, name, msg),
|
|
14169
|
+
getStatus: (p, taskId) => squadrantdCall(buildStatusRequest(p, taskId))
|
|
14170
|
+
});
|
|
13306
14171
|
process.stdout.write(JSON.stringify(r) + "\n");
|
|
13307
14172
|
});
|
|
13308
14173
|
crew.command("_hook <event>", { hidden: true }).description("internal: bridge from claude Stop/SubagentStop/SessionEnd hooks to squadrantd").action(async (event) => {
|
|
@@ -13470,6 +14335,10 @@ async function runCrewSpawn2(input) {
|
|
|
13470
14335
|
emitEvent: async (p, event) => {
|
|
13471
14336
|
await squadrantdCall({ kind: "event", project: p, event });
|
|
13472
14337
|
},
|
|
14338
|
+
// #745: check the daemon's hook-confirmed state before reporting a false
|
|
14339
|
+
// "first turn not delivered" — swallow errors (offline/unreachable daemon)
|
|
14340
|
+
// so this optional check never itself breaks the spawn.
|
|
14341
|
+
getTaskRecord: async (p, id) => await squadrantdCall(buildStatusRequest(p, id)).catch(() => void 0),
|
|
13473
14342
|
onRouted: (route) => console.log(
|
|
13474
14343
|
chalk10.dim(
|
|
13475
14344
|
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
@@ -13556,14 +14425,30 @@ async function runCrewList2(project) {
|
|
|
13556
14425
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
13557
14426
|
return runCrewList(project, runtime, workspaceId);
|
|
13558
14427
|
}
|
|
14428
|
+
async function runCrewAnswer2(project, name, option, opts) {
|
|
14429
|
+
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
14430
|
+
return runCrewAnswer(
|
|
14431
|
+
project,
|
|
14432
|
+
name,
|
|
14433
|
+
option,
|
|
14434
|
+
runtime,
|
|
14435
|
+
workspaceId,
|
|
14436
|
+
{
|
|
14437
|
+
readModalOptions: (pane) => readModalOptions(runtime, pane),
|
|
14438
|
+
log: (m) => console.log(chalk10.dim(m))
|
|
14439
|
+
},
|
|
14440
|
+
opts
|
|
14441
|
+
);
|
|
14442
|
+
}
|
|
13559
14443
|
var crewCommand = new Command10("crew").description(
|
|
13560
14444
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
13561
14445
|
);
|
|
13562
14446
|
crewCommand.command("spawn").description(
|
|
13563
14447
|
"Spawn an interactive crew session as a tab in the captain's workspace (use --direction to split into a pane instead)"
|
|
13564
|
-
).argument("<project>", "Project name (must be registered)").argument("[task]", "Initial task prompt for the crew session (omit with --task-file)").option("--name <name>", "Crew name (default: auto-generated crew-N)").option("--direction <dir>", "Placement: tab (default) or split direction (right|left|up|down)", "tab").option("--agent <name>", "Agent CLI to use (claude|codex|gemini|opencode)", "claude").option("--approval", "gate risky tools so the captain approves them (codex: approvalPolicy='untrusted'; opencode: bash:'ask')", false).option("--shared", "run the crew in the root checkout instead of an isolated worktree (for small/one-off tasks)", false).option("--task-file <path>", "Read task prompt from file instead of positional arg ('-' for stdin)").option("--model <alias>", "Override crew model for this spawn (e.g. sonnet, opus); takes precedence over config defaults.roles.crew.model").action(
|
|
14448
|
+
).argument("<project>", "Project name (must be registered)").argument("[task]", "Initial task prompt for the crew session (omit with --task-file)").option("--name <name>", "Crew name (default: auto-generated crew-N)").option("--direction <dir>", "Placement: tab (default) or split direction (right|left|up|down)", "tab").option("--agent <name>", "Agent CLI to use (claude|codex|gemini|opencode)", "claude").option("--approval", "gate risky tools so the captain approves them (codex: approvalPolicy='untrusted'; opencode: bash:'ask')", false).option("--shared", "run the crew in the root checkout instead of an isolated worktree (for small/one-off tasks)", false).option("--task-file <path>", "Read task prompt from file instead of positional arg ('-' for stdin)").option("--model <alias>", "Override crew model for this spawn (e.g. sonnet, opus); takes precedence over config defaults.roles.crew.model").option("--thinking <level>", `Override crew thinking level for this spawn (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over config defaults.roles.crew.thinking`).action(
|
|
13565
14449
|
async (project, task, opts, cmd) => {
|
|
13566
14450
|
try {
|
|
14451
|
+
const thinking = opts.thinking ? parseThinkingLevel(opts.thinking) : void 0;
|
|
13567
14452
|
const resolvedTask = await resolveTextInput({ positional: task, filePath: opts.taskFile, label: "task" });
|
|
13568
14453
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
13569
14454
|
const pane = await runCrewSpawn2({
|
|
@@ -13578,6 +14463,7 @@ crewCommand.command("spawn").description(
|
|
|
13578
14463
|
...opts.approval ? { approvalPolicy: "untrusted", approval: true } : {},
|
|
13579
14464
|
...opts.shared ? { shared: true } : {},
|
|
13580
14465
|
...opts.model ? { model: opts.model } : {},
|
|
14466
|
+
...thinking ? { thinking } : {},
|
|
13581
14467
|
// #458: pass the raw file path (not stdin) so runCrewSpawn can copy it
|
|
13582
14468
|
// into the isolated worktree root for relative-path access.
|
|
13583
14469
|
...opts.taskFile && opts.taskFile !== "-" ? { taskFile: opts.taskFile } : {}
|
|
@@ -13631,13 +14517,29 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
13631
14517
|
crewCommand.command("send").description("Send a follow-up message to an existing crew session").argument("<project>", "Project name").argument("<name>", "Crew name (e.g. crew-1)").argument("[message]", "Message to send (omit with --message-file)").option("--message-file <path>", "Read message from file instead of positional arg ('-' for stdin)").option("--force", "override an operator takeover (only when the operator told you to)", false).action(async (project, name, message, opts) => {
|
|
13632
14518
|
try {
|
|
13633
14519
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
13634
|
-
await runCrewSend2(project, name, resolvedMessage, opts);
|
|
14520
|
+
const { reopened } = await runCrewSend2(project, name, resolvedMessage, opts);
|
|
14521
|
+
if (reopened) console.log(chalk10.cyan(`\u21BB Task was terminal \u2014 reopened to working`));
|
|
13635
14522
|
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
13636
14523
|
} catch (e) {
|
|
13637
14524
|
console.error(chalk10.red(e.message));
|
|
13638
14525
|
process.exit(1);
|
|
13639
14526
|
}
|
|
13640
14527
|
});
|
|
14528
|
+
crewCommand.command("answer").description(
|
|
14529
|
+
"Deliberately answer a crew's open AskUserQuestion/permission prompt (#592) \u2014 never an implicit default"
|
|
14530
|
+
).argument("<project>", "Project name").argument("<name>", "Crew name").argument("<option>", "1-based option index, or an exact/prefix match of the option's text").option("--expect <text>", "Refuse unless the resolved option's label contains this text (guards against option order shifting)").option("--text <answer>", "For a free-text option (e.g. 'Type something.'): select it, then type this answer and submit").action(async (project, name, option, opts) => {
|
|
14531
|
+
try {
|
|
14532
|
+
const { selected, closed } = await runCrewAnswer2(project, name, option, opts);
|
|
14533
|
+
if (closed) {
|
|
14534
|
+
console.log(chalk10.green(`\u2714 Answered ${project}:${name} with ${selected.index}. "${selected.label}" \u2014 prompt closed`));
|
|
14535
|
+
} else {
|
|
14536
|
+
console.log(chalk10.yellow(`\u26A0 Sent ${selected.index}. "${selected.label}" to ${project}:${name}, but the prompt still appears open \u2014 read it again with 'squadrant crew read ${project} ${name}'`));
|
|
14537
|
+
}
|
|
14538
|
+
} catch (err) {
|
|
14539
|
+
console.error(chalk10.red(err.message));
|
|
14540
|
+
process.exit(1);
|
|
14541
|
+
}
|
|
14542
|
+
});
|
|
13641
14543
|
crewCommand.command("read").description("Read the current screen of a crew session (tail by default; use --full for the entire scrollback)").argument("<project>", "Project name").argument("<name>", "Crew name").option("--lines <N>", "Number of trailing lines to show", "40").option("--full", "Show the entire scrollback (overrides --lines)").action(async (project, name, opts) => {
|
|
13642
14544
|
try {
|
|
13643
14545
|
const screen = await runCrewRead2(project, name);
|
|
@@ -14214,9 +15116,9 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
14214
15116
|
// packages/web/dist/probes.js
|
|
14215
15117
|
init_dist();
|
|
14216
15118
|
init_dist();
|
|
14217
|
-
import { join as
|
|
15119
|
+
import { join as join23 } from "path";
|
|
14218
15120
|
import { homedir as homedir15 } from "os";
|
|
14219
|
-
import { existsSync as
|
|
15121
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
14220
15122
|
import { execFile as execFile4 } from "child_process";
|
|
14221
15123
|
var DEFAULT_TIMEOUT_MS2 = 2e3;
|
|
14222
15124
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
@@ -14252,7 +15154,7 @@ function vaultProbe(run, dir) {
|
|
|
14252
15154
|
return { state: "unknown", detail: "no vault configured" };
|
|
14253
15155
|
if (!run.pathExists(dir))
|
|
14254
15156
|
return { state: "gone", detail: "vault directory missing" };
|
|
14255
|
-
if (!run.pathExists(
|
|
15157
|
+
if (!run.pathExists(join23(dir, ".obsidian")))
|
|
14256
15158
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
14257
15159
|
return { state: "alive" };
|
|
14258
15160
|
} catch {
|
|
@@ -14320,13 +15222,13 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS2) {
|
|
|
14320
15222
|
const sessions = probeSessions(run);
|
|
14321
15223
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
14322
15224
|
}
|
|
14323
|
-
var SESSIONS_PATH =
|
|
15225
|
+
var SESSIONS_PATH = join23(homedir15(), ".config", "squadrant", "sessions.json");
|
|
14324
15226
|
function onPath(cli) {
|
|
14325
15227
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
14326
|
-
return dirs.some((d) =>
|
|
15228
|
+
return dirs.some((d) => existsSync13(join23(d, cli)));
|
|
14327
15229
|
}
|
|
14328
15230
|
function readSessionsHashes() {
|
|
14329
|
-
const raw = JSON.parse(
|
|
15231
|
+
const raw = JSON.parse(readFileSync16(SESSIONS_PATH, "utf-8"));
|
|
14330
15232
|
const hashes = Object.values(raw.workspaces ?? {}).map((w) => w.templateHash).filter((h) => typeof h === "string" && h.length > 0);
|
|
14331
15233
|
return [...new Set(hashes)];
|
|
14332
15234
|
}
|
|
@@ -14340,7 +15242,7 @@ function defaultProbeRunners() {
|
|
|
14340
15242
|
}
|
|
14341
15243
|
}),
|
|
14342
15244
|
probeOnPath: async (cli) => onPath(cli),
|
|
14343
|
-
pathExists: (p) =>
|
|
15245
|
+
pathExists: (p) => existsSync13(p),
|
|
14344
15246
|
loadConfig: () => loadConfig(),
|
|
14345
15247
|
loadSessionsHashes: () => readSessionsHashes()
|
|
14346
15248
|
};
|
|
@@ -14415,12 +15317,12 @@ function donut(t) {
|
|
|
14415
15317
|
const C = 2 * Math.PI * r;
|
|
14416
15318
|
let acc = 0;
|
|
14417
15319
|
const segs = DONUT_ORDER.map((k) => {
|
|
14418
|
-
const
|
|
14419
|
-
if (
|
|
15320
|
+
const v2 = t[k];
|
|
15321
|
+
if (v2 <= 0)
|
|
14420
15322
|
return "";
|
|
14421
|
-
const len = t.total ?
|
|
15323
|
+
const len = t.total ? v2 / t.total * C : 0;
|
|
14422
15324
|
const rot = t.total ? acc / t.total * 360 : 0;
|
|
14423
|
-
acc +=
|
|
15325
|
+
acc += v2;
|
|
14424
15326
|
return `<circle class="seg s-${k}" cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke-width="${w}" stroke-dasharray="${len.toFixed(2)} ${(C - len).toFixed(2)}" transform="rotate(${(rot - 90).toFixed(2)} ${cx} ${cy})"></circle>`;
|
|
14425
15327
|
}).join("");
|
|
14426
15328
|
const track = `<circle class="donut-track" cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke-width="${w}"></circle>`;
|
|
@@ -14724,7 +15626,7 @@ function renderLiveGrid(snap, now) {
|
|
|
14724
15626
|
const headerLabels = STATE_ORDER.map((s) => `${STATE_LABEL[s]}`);
|
|
14725
15627
|
out.push(`<div class="live-header" data-live-header="">`);
|
|
14726
15628
|
out.push(headerLabels.map((l) => {
|
|
14727
|
-
const raw = Object.entries(STATE_LABEL).find(([,
|
|
15629
|
+
const raw = Object.entries(STATE_LABEL).find(([, v2]) => v2 === l)[0];
|
|
14728
15630
|
const c = stateCounts[raw];
|
|
14729
15631
|
const cls = c === 0 ? "zero" : "";
|
|
14730
15632
|
return `<span class="live-stat ${cls}"><span class="pdot ${STATE_CLS[raw]}"></span>${c} ${l}</span>`;
|
|
@@ -15276,7 +16178,7 @@ async function runDashboardWeb(input) {
|
|
|
15276
16178
|
console.log(chalk14.green(`\u2714 Squadrant system dashboard \u2192 http://127.0.0.1:${handle.port}`));
|
|
15277
16179
|
console.log(chalk14.dim(` polling the daemon every ${input.interval}s \xB7 localhost only \xB7 read-only \xB7 Ctrl-C to stop`));
|
|
15278
16180
|
}
|
|
15279
|
-
var dashboardCommand = new Command13("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (
|
|
16181
|
+
var dashboardCommand = new Command13("dashboard").description("Live status grid of all projects (derived from daemon task state)").option("--once", "Print one snapshot and exit (used by --pane's refresh loop)").option("--pane", "Open a refreshing sidebar pane in the current cmux workspace").option("--web", "Serve the live system-health web dashboard on 127.0.0.1 (HTTP + SSE)").option("--port <port>", "Port for --web (default 7878)", (v2) => parseInt(v2, 10), 7878).option("--direction <dir>", "Pane split direction (right|left|up|down)", "right").option("--interval <seconds>", "Daemon poll interval for --web (default 5); refresh interval for --pane (default 10)", (v2) => parseInt(v2, 10)).action(async (opts) => {
|
|
15280
16182
|
try {
|
|
15281
16183
|
if (opts.web) {
|
|
15282
16184
|
await runDashboardWeb({ port: opts.port, interval: opts.interval ?? 5 });
|
|
@@ -15398,7 +16300,9 @@ async function selectCaptainsInteractive(entries, yesterday = getYesterday()) {
|
|
|
15398
16300
|
function resolveLaunchAgent(overrides, roleConfig, roleModelDefault) {
|
|
15399
16301
|
return {
|
|
15400
16302
|
agentName: overrides.agent ?? roleConfig?.agent ?? "claude",
|
|
15401
|
-
model: overrides.model ?? roleConfig?.model ?? roleModelDefault
|
|
16303
|
+
model: overrides.model ?? roleConfig?.model ?? roleModelDefault,
|
|
16304
|
+
// No built-in default: unset ⇒ flag omitted ⇒ the agent's own effort.
|
|
16305
|
+
thinking: overrides.thinking ?? roleConfig?.thinking
|
|
15402
16306
|
};
|
|
15403
16307
|
}
|
|
15404
16308
|
|
|
@@ -15435,11 +16339,20 @@ function ensureCmuxReady(headless) {
|
|
|
15435
16339
|
}
|
|
15436
16340
|
var launchCommand = new Command14("launch").description(
|
|
15437
16341
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
15438
|
-
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").action(async (project, opts) => {
|
|
16342
|
+
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--keep", "Resume the latest session even on a new day / after a template change").option("--all", "Launch all captain workspaces").option("--headless", "Skip the interactive cmux-app requirement (used by the daemon to boot captains without a terminal)").option("--agent <name>", "Override captain agent for this launch (claude|codex|gemini|opencode); takes precedence over defaults.roles.captain.agent").option("--model <name>", "Override captain model for this launch; takes precedence over defaults.roles.captain.model").option("--thinking <level>", `Override captain thinking level for this launch (${THINKING_LEVELS.join("|")}) \u2192 claude --effort; takes precedence over defaults.roles.captain.thinking`).action(async (project, opts) => {
|
|
15439
16343
|
if (opts.fresh && opts.keep) {
|
|
15440
16344
|
console.error(chalk15.red("\n \u2718 --fresh and --keep are mutually exclusive\n"));
|
|
15441
16345
|
process.exit(1);
|
|
15442
16346
|
}
|
|
16347
|
+
let thinkingOverride;
|
|
16348
|
+
try {
|
|
16349
|
+
thinkingOverride = opts.thinking ? parseThinkingLevel(opts.thinking) : void 0;
|
|
16350
|
+
} catch (err) {
|
|
16351
|
+
console.error(chalk15.red(`
|
|
16352
|
+
\u2718 ${err.message}
|
|
16353
|
+
`));
|
|
16354
|
+
process.exit(1);
|
|
16355
|
+
}
|
|
15443
16356
|
const config = loadConfig();
|
|
15444
16357
|
let hadFailure = false;
|
|
15445
16358
|
const drivers = {
|
|
@@ -15452,8 +16365,8 @@ var launchCommand = new Command14("launch").description(
|
|
|
15452
16365
|
const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
15453
16366
|
async function launchOne(workspaceName, role, cwd, permissionMode, navigate, pinToTop = false, projectName) {
|
|
15454
16367
|
const roleConfig = config.defaults.roles?.[role];
|
|
15455
|
-
const { agentName, model } = resolveLaunchAgent(
|
|
15456
|
-
{ agent: opts.agent, model: opts.model },
|
|
16368
|
+
const { agentName, model, thinking } = resolveLaunchAgent(
|
|
16369
|
+
{ agent: opts.agent, model: opts.model, thinking: thinkingOverride },
|
|
15457
16370
|
roleConfig,
|
|
15458
16371
|
config.defaults.models?.[role]
|
|
15459
16372
|
);
|
|
@@ -15496,7 +16409,8 @@ var launchCommand = new Command14("launch").description(
|
|
|
15496
16409
|
model,
|
|
15497
16410
|
TEMPLATES_DIR4,
|
|
15498
16411
|
resolveCaptainSocketPath(captainChannelEnabled, projectName, workspaceName),
|
|
15499
|
-
resolveCaptainSessionName(agentName, projectName)
|
|
16412
|
+
resolveCaptainSessionName(agentName, projectName),
|
|
16413
|
+
thinking
|
|
15500
16414
|
);
|
|
15501
16415
|
},
|
|
15502
16416
|
initialPrompt,
|
|
@@ -16195,11 +17109,11 @@ import chalk22 from "chalk";
|
|
|
16195
17109
|
import fs23 from "fs";
|
|
16196
17110
|
import path28 from "path";
|
|
16197
17111
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
16198
|
-
function parseScope(
|
|
16199
|
-
if (
|
|
17112
|
+
function parseScope(v2) {
|
|
17113
|
+
if (v2 !== "user" && v2 !== "project") {
|
|
16200
17114
|
throw new Error("--scope must be 'user' or 'project'");
|
|
16201
17115
|
}
|
|
16202
|
-
return
|
|
17116
|
+
return v2;
|
|
16203
17117
|
}
|
|
16204
17118
|
function findPackageRoot3() {
|
|
16205
17119
|
let dir = path28.dirname(fileURLToPath4(import.meta.url));
|
|
@@ -16403,7 +17317,7 @@ init_dist2();
|
|
|
16403
17317
|
import { Command as Command23 } from "commander";
|
|
16404
17318
|
import fs24 from "fs";
|
|
16405
17319
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
16406
|
-
import { dirname as dirname5, join as
|
|
17320
|
+
import { dirname as dirname5, join as join24 } from "path";
|
|
16407
17321
|
import chalk23 from "chalk";
|
|
16408
17322
|
function runConfigCheck(opts) {
|
|
16409
17323
|
const raw = JSON.parse(fs24.readFileSync(opts.configPath, "utf-8"));
|
|
@@ -16539,14 +17453,14 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
16539
17453
|
}
|
|
16540
17454
|
});
|
|
16541
17455
|
function readPkgVersion2() {
|
|
16542
|
-
const pkgPath =
|
|
17456
|
+
const pkgPath = join24(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
16543
17457
|
return JSON.parse(fs24.readFileSync(pkgPath, "utf-8")).version;
|
|
16544
17458
|
}
|
|
16545
17459
|
|
|
16546
17460
|
// packages/cli/src/commands/heal.ts
|
|
16547
17461
|
init_dist();
|
|
16548
17462
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
16549
|
-
import { join as
|
|
17463
|
+
import { join as join25 } from "path";
|
|
16550
17464
|
import { homedir as homedir16 } from "os";
|
|
16551
17465
|
import { Command as Command24 } from "commander";
|
|
16552
17466
|
import chalk24 from "chalk";
|
|
@@ -16562,9 +17476,16 @@ function buildHealStatus(components) {
|
|
|
16562
17476
|
project: c.project,
|
|
16563
17477
|
ref: c.ref,
|
|
16564
17478
|
state: c.state,
|
|
17479
|
+
detail: c.detail,
|
|
17480
|
+
stuck: c.stuck,
|
|
16565
17481
|
healCmd: healCmdFor(c)
|
|
16566
17482
|
}));
|
|
16567
|
-
const healthy = out.every((c) =>
|
|
17483
|
+
const healthy = out.every((c) => {
|
|
17484
|
+
if (c.kind === "delivery") {
|
|
17485
|
+
return c.stuck !== true;
|
|
17486
|
+
}
|
|
17487
|
+
return c.healCmd === null;
|
|
17488
|
+
});
|
|
16568
17489
|
return { healthy, components: out };
|
|
16569
17490
|
}
|
|
16570
17491
|
async function runHealStatus(opts) {
|
|
@@ -16601,15 +17522,37 @@ async function runHealStatus(opts) {
|
|
|
16601
17522
|
}
|
|
16602
17523
|
if (result.healthy) {
|
|
16603
17524
|
stdout.write(chalk24.green("\u2714 all components healthy\n"));
|
|
17525
|
+
for (const c of result.components) {
|
|
17526
|
+
if (c.kind === "delivery" && c.state === "stale") {
|
|
17527
|
+
const glyph = chalk24.yellow("\u2022");
|
|
17528
|
+
const stateColor2 = chalk24.yellow;
|
|
17529
|
+
stdout.write(` ${glyph} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${stateColor2(c.state.padEnd(8))} ${c.project}
|
|
17530
|
+
`);
|
|
17531
|
+
if (c.detail) {
|
|
17532
|
+
stdout.write(` detail: ${c.detail}
|
|
17533
|
+
`);
|
|
17534
|
+
}
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
16604
17537
|
return 0;
|
|
16605
17538
|
}
|
|
16606
17539
|
stdout.write(chalk24.bold("Unhealthy components:\n\n"));
|
|
16607
17540
|
for (const c of result.components) {
|
|
16608
|
-
|
|
16609
|
-
|
|
17541
|
+
const isUnhealthy = c.kind === "delivery" ? c.stuck === true : c.healCmd !== null;
|
|
17542
|
+
const isAdvisoryDelivery = c.kind === "delivery" && c.state === "stale" && c.stuck !== true;
|
|
17543
|
+
if (isUnhealthy || isAdvisoryDelivery) {
|
|
17544
|
+
const glyph = c.state === "stale" || isAdvisoryDelivery ? chalk24.yellow("\u2022") : chalk24.red("\u2718");
|
|
17545
|
+
const stateColor2 = c.state === "stale" || isAdvisoryDelivery ? chalk24.yellow : chalk24.red;
|
|
17546
|
+
stdout.write(` ${glyph} ${c.kind.padEnd(8)} ${c.ref.padEnd(16)} ${stateColor2(c.state.padEnd(8))} ${c.project}
|
|
16610
17547
|
`);
|
|
16611
|
-
|
|
17548
|
+
if (c.detail) {
|
|
17549
|
+
stdout.write(` detail: ${c.detail}
|
|
16612
17550
|
`);
|
|
17551
|
+
}
|
|
17552
|
+
if (c.healCmd) {
|
|
17553
|
+
stdout.write(` heal: ${chalk24.cyan(c.healCmd)}
|
|
17554
|
+
`);
|
|
17555
|
+
}
|
|
16613
17556
|
}
|
|
16614
17557
|
}
|
|
16615
17558
|
return 2;
|
|
@@ -16618,8 +17561,10 @@ async function runHealDaemon(opts) {
|
|
|
16618
17561
|
const { stdout, stderr } = opts;
|
|
16619
17562
|
stdout.write("restarting squadrantd via launchd kickstart...\n");
|
|
16620
17563
|
try {
|
|
16621
|
-
opts.ensureDaemon();
|
|
16622
|
-
|
|
17564
|
+
const result = opts.ensureDaemon();
|
|
17565
|
+
const noteSuffix = result?.note ? ` (${result.note})` : "";
|
|
17566
|
+
stdout.write(chalk24.green(`\u2714 daemon kickstart complete${noteSuffix}
|
|
17567
|
+
`));
|
|
16623
17568
|
return 0;
|
|
16624
17569
|
} catch (e) {
|
|
16625
17570
|
stderr.write(`heal daemon failed: ${e.message}
|
|
@@ -16755,8 +17700,8 @@ var healCommand = new Command24("heal").description("Targeted, idempotent remedi
|
|
|
16755
17700
|
}
|
|
16756
17701
|
const config = loadConfig();
|
|
16757
17702
|
const projects = opts.all ? Object.keys(config.projects) : [project];
|
|
16758
|
-
const stateRoot =
|
|
16759
|
-
const registry = new LivenessRegistry({ path:
|
|
17703
|
+
const stateRoot = join25(homedir16(), ".config", "squadrant", "state");
|
|
17704
|
+
const registry = new LivenessRegistry({ path: join25(stateRoot, "liveness.json") });
|
|
16760
17705
|
registry.load();
|
|
16761
17706
|
const code = await runHealCaptain(projects, {
|
|
16762
17707
|
liveness: readCmuxLiveness,
|
|
@@ -16822,12 +17767,12 @@ async function dispatchAction(toProject, task, opts) {
|
|
|
16822
17767
|
process.exit(1);
|
|
16823
17768
|
}
|
|
16824
17769
|
}
|
|
16825
|
-
var dispatchCommand = new Command25("dispatch").description("Dispatch a task to any registered project (tracked, reports back on settle)").argument("<project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (same-group only; default: 120)", (
|
|
17770
|
+
var dispatchCommand = new Command25("dispatch").description("Dispatch a task to any registered project (tracked, reports back on settle)").argument("<project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (same-group only; default: 120)", (v2) => parseInt(v2, 10) * 1e3).action(dispatchAction);
|
|
16826
17771
|
|
|
16827
17772
|
// packages/cli/src/commands/group.ts
|
|
16828
17773
|
init_dist2();
|
|
16829
17774
|
var groupCommand = new Command26("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
16830
|
-
new Command26("dispatch").description("[DEPRECATED \u2014 use 'squadrant dispatch'] Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (default: 120)", (
|
|
17775
|
+
new Command26("dispatch").description("[DEPRECATED \u2014 use 'squadrant dispatch'] Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name").argument("<task>", "Task description to dispatch").option("--provider <p>", "claude|opencode|codex", "claude").option("--mode <m>", "headless|interactive", "headless").option("--warmup-timeout <s>", "seconds to wait for target captain to boot (default: 120)", (v2) => parseInt(v2, 10) * 1e3).action(async (toProject, task, opts) => {
|
|
16831
17776
|
console.error(chalk26.yellow(
|
|
16832
17777
|
`\u26A0 'squadrant group dispatch' is deprecated \u2014 use 'squadrant dispatch <project> "<task>"' instead.`
|
|
16833
17778
|
));
|
|
@@ -16840,7 +17785,7 @@ init_dist();
|
|
|
16840
17785
|
init_dist2();
|
|
16841
17786
|
init_runtime2();
|
|
16842
17787
|
init_require_daemon();
|
|
16843
|
-
import { join as
|
|
17788
|
+
import { join as join27, dirname as dirname6 } from "path";
|
|
16844
17789
|
import { Command as Command27 } from "commander";
|
|
16845
17790
|
import chalk28 from "chalk";
|
|
16846
17791
|
|
|
@@ -16848,26 +17793,55 @@ import chalk28 from "chalk";
|
|
|
16848
17793
|
init_dist4();
|
|
16849
17794
|
init_dist2();
|
|
16850
17795
|
import { createServer as createServer5, connect as netConnect2 } from "net";
|
|
16851
|
-
import
|
|
17796
|
+
import fs25 from "fs";
|
|
17797
|
+
import { join as join26 } from "path";
|
|
16852
17798
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
16853
17799
|
import chalk27 from "chalk";
|
|
16854
17800
|
var shared;
|
|
17801
|
+
var registryEntryPath = () => join26(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
|
|
17802
|
+
function unregisterSenderIdentity() {
|
|
17803
|
+
try {
|
|
17804
|
+
fs25.unlinkSync(registryEntryPath());
|
|
17805
|
+
} catch {
|
|
17806
|
+
}
|
|
17807
|
+
}
|
|
17808
|
+
function registerSenderIdentity(socketPath2) {
|
|
17809
|
+
try {
|
|
17810
|
+
unregisterSenderIdentity();
|
|
17811
|
+
fs25.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
|
|
17812
|
+
fs25.writeFileSync(
|
|
17813
|
+
registryEntryPath(),
|
|
17814
|
+
JSON.stringify({
|
|
17815
|
+
pid: process.pid,
|
|
17816
|
+
sessionId: randomUUID7(),
|
|
17817
|
+
name: "squadrantd",
|
|
17818
|
+
messagingSocketPath: socketPath2,
|
|
17819
|
+
kind: "daemon",
|
|
17820
|
+
peerProtocol: 1
|
|
17821
|
+
})
|
|
17822
|
+
);
|
|
17823
|
+
process.on("exit", unregisterSenderIdentity);
|
|
17824
|
+
} catch {
|
|
17825
|
+
}
|
|
17826
|
+
}
|
|
16855
17827
|
async function sharedReceiptListener() {
|
|
16856
17828
|
if (shared) return shared;
|
|
17829
|
+
const socketPath2 = `${CC_SOCKS_DIR}/squadrantd-${process.pid}.sock`;
|
|
16857
17830
|
const listener = new ClaudeReceiptListener({
|
|
16858
|
-
socketPath:
|
|
17831
|
+
socketPath: socketPath2,
|
|
16859
17832
|
createServer: (h) => createServer5(h),
|
|
16860
17833
|
// A UDS path is not cleaned up when a process is killed, so our own leftover
|
|
16861
17834
|
// must never be the reason we refuse to start.
|
|
16862
17835
|
unlinkStale: (p) => {
|
|
16863
17836
|
try {
|
|
16864
|
-
|
|
17837
|
+
fs25.unlinkSync(p);
|
|
16865
17838
|
} catch {
|
|
16866
17839
|
}
|
|
16867
17840
|
},
|
|
16868
17841
|
log: (m) => console.error(chalk27.dim(m))
|
|
16869
17842
|
});
|
|
16870
17843
|
await listener.start();
|
|
17844
|
+
registerSenderIdentity(socketPath2);
|
|
16871
17845
|
shared = listener;
|
|
16872
17846
|
return shared;
|
|
16873
17847
|
}
|
|
@@ -16922,7 +17896,7 @@ async function runPing(project, message) {
|
|
|
16922
17896
|
log: (m) => console.error(chalk28.dim(m))
|
|
16923
17897
|
});
|
|
16924
17898
|
if (!handled) {
|
|
16925
|
-
const stateRoot =
|
|
17899
|
+
const stateRoot = join27(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
16926
17900
|
await appendCaptainMessage({
|
|
16927
17901
|
stateRoot,
|
|
16928
17902
|
project,
|
|
@@ -17001,7 +17975,7 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
|
|
|
17001
17975
|
// packages/cli/src/commands/effort.ts
|
|
17002
17976
|
init_dist();
|
|
17003
17977
|
init_dist2();
|
|
17004
|
-
import
|
|
17978
|
+
import fs26 from "fs";
|
|
17005
17979
|
import path29 from "path";
|
|
17006
17980
|
import { Command as Command29 } from "commander";
|
|
17007
17981
|
import chalk30 from "chalk";
|
|
@@ -17045,7 +18019,7 @@ function effortScopeLabel(projectName) {
|
|
|
17045
18019
|
}
|
|
17046
18020
|
function canonical(p) {
|
|
17047
18021
|
try {
|
|
17048
|
-
return
|
|
18022
|
+
return fs26.realpathSync(p);
|
|
17049
18023
|
} catch {
|
|
17050
18024
|
return path29.resolve(p);
|
|
17051
18025
|
}
|
|
@@ -17107,7 +18081,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
|
|
|
17107
18081
|
|
|
17108
18082
|
// packages/cli/src/commands/tokens.ts
|
|
17109
18083
|
init_dist();
|
|
17110
|
-
import
|
|
18084
|
+
import fs27 from "fs";
|
|
17111
18085
|
import path30 from "path";
|
|
17112
18086
|
import os16 from "os";
|
|
17113
18087
|
import readline3 from "readline";
|
|
@@ -17163,7 +18137,7 @@ function foldTranscriptLine(agg, rawLine, state) {
|
|
|
17163
18137
|
async function aggregateTranscriptFile(filePath) {
|
|
17164
18138
|
const agg = emptySessionAggregate();
|
|
17165
18139
|
const state = { lastCacheRead: null };
|
|
17166
|
-
const rl = readline3.createInterface({ input:
|
|
18140
|
+
const rl = readline3.createInterface({ input: fs27.createReadStream(filePath), crlfDelay: Infinity });
|
|
17167
18141
|
for await (const line of rl) {
|
|
17168
18142
|
foldTranscriptLine(agg, line, state);
|
|
17169
18143
|
}
|
|
@@ -17231,7 +18205,7 @@ function buildRoleReport(role, sessions) {
|
|
|
17231
18205
|
}
|
|
17232
18206
|
async function readdirSafe(dir) {
|
|
17233
18207
|
try {
|
|
17234
|
-
return await
|
|
18208
|
+
return await fs27.promises.readdir(dir);
|
|
17235
18209
|
} catch {
|
|
17236
18210
|
return [];
|
|
17237
18211
|
}
|
|
@@ -17398,12 +18372,12 @@ var tokensCommand = new Command30("tokens").description(
|
|
|
17398
18372
|
// packages/cli/src/commands/telegram.ts
|
|
17399
18373
|
init_dist();
|
|
17400
18374
|
init_dist2();
|
|
17401
|
-
import { join as
|
|
18375
|
+
import { join as join28, dirname as dirname7 } from "path";
|
|
17402
18376
|
import { emitKeypressEvents } from "readline";
|
|
17403
18377
|
import { Command as Command31 } from "commander";
|
|
17404
18378
|
import chalk32 from "chalk";
|
|
17405
18379
|
function defaultStateRoot() {
|
|
17406
|
-
return
|
|
18380
|
+
return join28(dirname7(DEFAULT_CONFIG_PATH), "state");
|
|
17407
18381
|
}
|
|
17408
18382
|
async function questionMasked() {
|
|
17409
18383
|
return new Promise((resolve4) => {
|
|
@@ -17474,7 +18448,7 @@ telegramCommand.command("link").argument("<project>", "project to bind to a Tele
|
|
|
17474
18448
|
const { topicId, created } = await runTelegramLink({ project, cfg, client, stateRoot: defaultStateRoot() });
|
|
17475
18449
|
console.log(chalk32.green(`${created ? "linked" : "already linked"}: ${project} \u2192 topic ${topicId}`));
|
|
17476
18450
|
});
|
|
17477
|
-
telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (
|
|
18451
|
+
telegramCommand.command("setup").description("Interactive wizard \u2014 bot token, validate, auto-detect supergroup, write config").option("--reset-token", "force re-entry of the bot token even if one already exists").option("--redetect", "force group re-detection even when a supergroup is already configured").option("--user-id <id>", "allowlist user-id \u2014 enables remote control on a re-run without getUpdates detection", (v2) => parseInt(v2, 10)).action(async (opts) => {
|
|
17478
18452
|
if (!process.stdin.isTTY) {
|
|
17479
18453
|
console.error(chalk32.red("setup requires a TTY \u2014 pipe input is not supported"));
|
|
17480
18454
|
process.exit(1);
|
|
@@ -17729,9 +18703,10 @@ init_dist2();
|
|
|
17729
18703
|
init_dist4();
|
|
17730
18704
|
init_dist();
|
|
17731
18705
|
import { Command as Command32 } from "commander";
|
|
18706
|
+
import { homedir as homedir17 } from "os";
|
|
17732
18707
|
|
|
17733
18708
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
17734
|
-
import
|
|
18709
|
+
import fs28 from "fs";
|
|
17735
18710
|
import path31 from "path";
|
|
17736
18711
|
|
|
17737
18712
|
// packages/cli/src/lib/handoff-facts.ts
|
|
@@ -17783,15 +18758,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
|
|
|
17783
18758
|
// packages/cli/src/lib/captain-session-registry.ts
|
|
17784
18759
|
var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
|
|
17785
18760
|
function appendCaptainSession(spokeVault, record) {
|
|
17786
|
-
|
|
18761
|
+
fs28.mkdirSync(spokeVault, { recursive: true });
|
|
17787
18762
|
const file = path31.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
17788
|
-
|
|
18763
|
+
fs28.appendFileSync(file, JSON.stringify(record) + "\n");
|
|
17789
18764
|
}
|
|
17790
18765
|
function readCaptainSessionRegistry(spokeVault) {
|
|
17791
18766
|
const file = path31.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
|
|
17792
|
-
if (!
|
|
18767
|
+
if (!fs28.existsSync(file)) return [];
|
|
17793
18768
|
const records = [];
|
|
17794
|
-
for (const line of
|
|
18769
|
+
for (const line of fs28.readFileSync(file, "utf-8").split("\n")) {
|
|
17795
18770
|
if (!line.trim()) continue;
|
|
17796
18771
|
try {
|
|
17797
18772
|
records.push(JSON.parse(line));
|
|
@@ -17878,6 +18853,26 @@ function hooksCommand() {
|
|
|
17878
18853
|
if (!taskId || !project) {
|
|
17879
18854
|
process.exit(0);
|
|
17880
18855
|
}
|
|
18856
|
+
if (sub === "pre-tool-use") {
|
|
18857
|
+
const p = payload;
|
|
18858
|
+
const guard = decideCaptainMemoryWrite(
|
|
18859
|
+
typeof p?.tool_name === "string" ? p.tool_name : "",
|
|
18860
|
+
p?.tool_input,
|
|
18861
|
+
process.env,
|
|
18862
|
+
homedir17()
|
|
18863
|
+
);
|
|
18864
|
+
if (guard.decision === "deny") {
|
|
18865
|
+
console.error(`[squadrant] denied crew write into captain memory (task ${taskId}): ${guard.reason}`);
|
|
18866
|
+
process.stdout.write(JSON.stringify({
|
|
18867
|
+
hookSpecificOutput: {
|
|
18868
|
+
hookEventName: "PreToolUse",
|
|
18869
|
+
permissionDecision: "deny",
|
|
18870
|
+
permissionDecisionReason: guard.reason
|
|
18871
|
+
}
|
|
18872
|
+
}));
|
|
18873
|
+
process.exit(0);
|
|
18874
|
+
}
|
|
18875
|
+
}
|
|
17881
18876
|
const ev = mapHookSub(sub, payload, taskId);
|
|
17882
18877
|
if (!ev) {
|
|
17883
18878
|
process.exit(0);
|
|
@@ -17963,7 +18958,7 @@ function printTree(items) {
|
|
|
17963
18958
|
function printFlat(items) {
|
|
17964
18959
|
for (const item of items) printItem(item, 0);
|
|
17965
18960
|
}
|
|
17966
|
-
var startCmd = new Command33("start").description("Start a new work item").argument("<title>", "what you're doing").option("--project <name>", "project this work belongs to (defaults to the current registered project)").option("--parent <id>", "id of the wave/parent item this nests under").option("--tag <tag>", "attach a tag (repeatable)", (
|
|
18961
|
+
var startCmd = new Command33("start").description("Start a new work item").argument("<title>", "what you're doing").option("--project <name>", "project this work belongs to (defaults to the current registered project)").option("--parent <id>", "id of the wave/parent item this nests under").option("--tag <tag>", "attach a tag (repeatable)", (v2, prev) => [...prev, v2], []).action((title, opts) => {
|
|
17967
18962
|
const config = loadConfig();
|
|
17968
18963
|
const store = createWorkStore();
|
|
17969
18964
|
purgeExpiredWorkItems(store);
|
|
@@ -18038,7 +19033,7 @@ import os17 from "os";
|
|
|
18038
19033
|
// packages/cli/src/lib/handoff-live-repo.ts
|
|
18039
19034
|
init_dist();
|
|
18040
19035
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
18041
|
-
import
|
|
19036
|
+
import fs29 from "fs";
|
|
18042
19037
|
import path33 from "path";
|
|
18043
19038
|
|
|
18044
19039
|
// packages/cli/src/lib/handoff-branch-state.ts
|
|
@@ -18178,7 +19173,7 @@ function localAheadOfBase(runner, projectPath, base) {
|
|
|
18178
19173
|
}
|
|
18179
19174
|
function readFetchAgeMs(projectPath, now) {
|
|
18180
19175
|
try {
|
|
18181
|
-
const stat2 =
|
|
19176
|
+
const stat2 = fs29.statSync(path33.join(projectPath, ".git", "FETCH_HEAD"));
|
|
18182
19177
|
return Math.max(0, now - stat2.mtime.getTime());
|
|
18183
19178
|
} catch {
|
|
18184
19179
|
return null;
|
|
@@ -18249,7 +19244,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
|
|
|
18249
19244
|
|
|
18250
19245
|
// packages/cli/src/lib/handoff-claude-mem.ts
|
|
18251
19246
|
import { createRequire } from "module";
|
|
18252
|
-
import
|
|
19247
|
+
import fs30 from "fs";
|
|
18253
19248
|
var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
|
|
18254
19249
|
var CLAUDE_MEM_RECENCY_LIMIT = 20;
|
|
18255
19250
|
function decisionText(row) {
|
|
@@ -18263,7 +19258,7 @@ function decisionText(row) {
|
|
|
18263
19258
|
return row.narrative ?? "";
|
|
18264
19259
|
}
|
|
18265
19260
|
function queryClaudeMem(dbPath, project) {
|
|
18266
|
-
if (!
|
|
19261
|
+
if (!fs30.existsSync(dbPath)) return null;
|
|
18267
19262
|
let db;
|
|
18268
19263
|
try {
|
|
18269
19264
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -18285,7 +19280,7 @@ function queryClaudeMem(dbPath, project) {
|
|
|
18285
19280
|
createdAt: r.created_at
|
|
18286
19281
|
}));
|
|
18287
19282
|
const candidates = [summaryRow?.created_at, ...decisionRows.map((r) => r.created_at)].filter(
|
|
18288
|
-
(
|
|
19283
|
+
(v2) => !!v2
|
|
18289
19284
|
);
|
|
18290
19285
|
const oldestCreatedAt = candidates.length > 0 ? candidates.reduce((a, b) => a < b ? a : b) : null;
|
|
18291
19286
|
return {
|
|
@@ -18306,7 +19301,7 @@ function queryClaudeMem(dbPath, project) {
|
|
|
18306
19301
|
}
|
|
18307
19302
|
|
|
18308
19303
|
// packages/cli/src/lib/handoff-transcript.ts
|
|
18309
|
-
import
|
|
19304
|
+
import fs31 from "fs";
|
|
18310
19305
|
var TRANSCRIPT_BYTE_CAP = 2e5;
|
|
18311
19306
|
function tailOf(content, byteCap) {
|
|
18312
19307
|
const buf = Buffer.from(content, "utf-8");
|
|
@@ -18335,27 +19330,27 @@ function extractMessages(tailText) {
|
|
|
18335
19330
|
return { lastUserMessage, lastAssistantText };
|
|
18336
19331
|
}
|
|
18337
19332
|
function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
|
|
18338
|
-
if (!
|
|
18339
|
-
const content =
|
|
19333
|
+
if (!fs31.existsSync(transcriptPath)) return null;
|
|
19334
|
+
const content = fs31.readFileSync(transcriptPath, "utf-8");
|
|
18340
19335
|
const { lastUserMessage, lastAssistantText } = extractMessages(tailOf(content, byteCap));
|
|
18341
|
-
const mtimeIso =
|
|
19336
|
+
const mtimeIso = fs31.statSync(transcriptPath).mtime.toISOString();
|
|
18342
19337
|
return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText };
|
|
18343
19338
|
}
|
|
18344
19339
|
|
|
18345
19340
|
// packages/cli/src/lib/handoff-archive.ts
|
|
18346
|
-
import
|
|
19341
|
+
import fs32 from "fs";
|
|
18347
19342
|
import path34 from "path";
|
|
18348
19343
|
function readNewestArchivedHandoff(spokeVault, now) {
|
|
18349
19344
|
const dir = path34.join(spokeVault, "handoffs");
|
|
18350
|
-
if (!
|
|
18351
|
-
const candidates =
|
|
19345
|
+
if (!fs32.existsSync(dir)) return null;
|
|
19346
|
+
const candidates = fs32.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
|
|
18352
19347
|
const full = path34.join(dir, e.name);
|
|
18353
|
-
return { name: e.name, full, mtime:
|
|
19348
|
+
return { name: e.name, full, mtime: fs32.statSync(full).mtime };
|
|
18354
19349
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
18355
19350
|
for (const candidate of candidates) {
|
|
18356
19351
|
let content;
|
|
18357
19352
|
try {
|
|
18358
|
-
content = JSON.parse(
|
|
19353
|
+
content = JSON.parse(fs32.readFileSync(candidate.full, "utf-8"));
|
|
18359
19354
|
} catch {
|
|
18360
19355
|
continue;
|
|
18361
19356
|
}
|
|
@@ -18425,15 +19420,15 @@ init_dist();
|
|
|
18425
19420
|
init_dist();
|
|
18426
19421
|
init_dist();
|
|
18427
19422
|
var __dirname = dirname8(fileURLToPath6(import.meta.url));
|
|
18428
|
-
var pkg = JSON.parse(
|
|
19423
|
+
var pkg = JSON.parse(readFileSync17(join29(__dirname, "..", "package.json"), "utf-8"));
|
|
18429
19424
|
ensureRuntimeSynced({
|
|
18430
|
-
sourceRoot:
|
|
18431
|
-
runtimeRoot:
|
|
19425
|
+
sourceRoot: join29(__dirname, ".."),
|
|
19426
|
+
runtimeRoot: join29(homedir18(), ".config", "squadrant")
|
|
18432
19427
|
});
|
|
18433
19428
|
if (process.argv[2] !== "config") {
|
|
18434
19429
|
try {
|
|
18435
|
-
const cfgPath =
|
|
18436
|
-
if (
|
|
19430
|
+
const cfgPath = join29(homedir18(), ".config", "squadrant", "config.json");
|
|
19431
|
+
if (existsSync14(cfgPath)) {
|
|
18437
19432
|
const cfg = JSON.parse(readConfigFileSync(cfgPath));
|
|
18438
19433
|
if (needsCheck(cfg, pkg.version)) {
|
|
18439
19434
|
const items = detectDrift(cfg, getDefaultConfig());
|