squadrant 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1372 -1050
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +127 -96
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/scripts/gen-codex-types.sh +2 -10
- package/scripts/migrate-to-squadrant.sh +49 -23
package/dist/index.js
CHANGED
|
@@ -434,7 +434,7 @@ var init_compat_manifest = __esm({
|
|
|
434
434
|
// presence-checked; no floor enforced yet
|
|
435
435
|
codex: { lastVerified: "0.139.0" },
|
|
436
436
|
gemini: { lastVerified: "0.38.2" },
|
|
437
|
-
opencode: { lastVerified: "1.17.
|
|
437
|
+
opencode: { lastVerified: "1.17.9" }
|
|
438
438
|
}
|
|
439
439
|
};
|
|
440
440
|
}
|
|
@@ -1043,10 +1043,10 @@ var init_watchdog = __esm({
|
|
|
1043
1043
|
}
|
|
1044
1044
|
});
|
|
1045
1045
|
|
|
1046
|
-
// packages/core/dist/daemon.js
|
|
1046
|
+
// packages/core/dist/daemon/reduce.js
|
|
1047
1047
|
var DEFAULT_TASK_TIMEOUT_MS, TERMINAL_RECORD_TTL_MS;
|
|
1048
|
-
var
|
|
1049
|
-
"packages/core/dist/daemon.js"() {
|
|
1048
|
+
var init_reduce = __esm({
|
|
1049
|
+
"packages/core/dist/daemon/reduce.js"() {
|
|
1050
1050
|
init_dist();
|
|
1051
1051
|
init_state_machine();
|
|
1052
1052
|
init_watchdog();
|
|
@@ -1449,9 +1449,9 @@ var init_captain_delivery = __esm({
|
|
|
1449
1449
|
}
|
|
1450
1450
|
});
|
|
1451
1451
|
|
|
1452
|
-
// packages/core/dist/daemon/delivery.js
|
|
1453
|
-
var
|
|
1454
|
-
"packages/core/dist/daemon/delivery.js"() {
|
|
1452
|
+
// packages/core/dist/daemon/delivery-loop.js
|
|
1453
|
+
var init_delivery_loop = __esm({
|
|
1454
|
+
"packages/core/dist/daemon/delivery-loop.js"() {
|
|
1455
1455
|
init_mailbox();
|
|
1456
1456
|
init_captain_delivery();
|
|
1457
1457
|
init_dist();
|
|
@@ -1489,9 +1489,9 @@ import { readdir } from "fs/promises";
|
|
|
1489
1489
|
var SNAPSHOT_LOG_WINDOW_MS;
|
|
1490
1490
|
var init_start = __esm({
|
|
1491
1491
|
"packages/core/dist/daemon/start.js"() {
|
|
1492
|
-
|
|
1492
|
+
init_reduce();
|
|
1493
1493
|
init_probes();
|
|
1494
|
-
|
|
1494
|
+
init_delivery_loop();
|
|
1495
1495
|
init_gates();
|
|
1496
1496
|
init_server();
|
|
1497
1497
|
init_mailbox();
|
|
@@ -1683,6 +1683,17 @@ var init_commands = __esm({
|
|
|
1683
1683
|
}
|
|
1684
1684
|
});
|
|
1685
1685
|
|
|
1686
|
+
// packages/core/dist/telegram/control.js
|
|
1687
|
+
import { execFile } from "child_process";
|
|
1688
|
+
import { promisify } from "util";
|
|
1689
|
+
var pExecFile;
|
|
1690
|
+
var init_control2 = __esm({
|
|
1691
|
+
"packages/core/dist/telegram/control.js"() {
|
|
1692
|
+
init_protocol();
|
|
1693
|
+
pExecFile = promisify(execFile);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1686
1697
|
// packages/core/dist/telegram/ensure-captain.js
|
|
1687
1698
|
var init_ensure_captain = __esm({
|
|
1688
1699
|
"packages/core/dist/telegram/ensure-captain.js"() {
|
|
@@ -1799,6 +1810,12 @@ function createTelegramClient(opts) {
|
|
|
1799
1810
|
},
|
|
1800
1811
|
async setMyCommands(commands) {
|
|
1801
1812
|
await call("setMyCommands", { commands });
|
|
1813
|
+
},
|
|
1814
|
+
async sendChatAction(chatId, threadId, action) {
|
|
1815
|
+
const body = { chat_id: chatId, action };
|
|
1816
|
+
if (threadId !== void 0)
|
|
1817
|
+
body.message_thread_id = threadId;
|
|
1818
|
+
await call("sendChatAction", body);
|
|
1802
1819
|
}
|
|
1803
1820
|
};
|
|
1804
1821
|
}
|
|
@@ -1843,6 +1860,46 @@ var init_bridge = __esm({
|
|
|
1843
1860
|
}
|
|
1844
1861
|
});
|
|
1845
1862
|
|
|
1863
|
+
// packages/core/dist/restart-daemon.js
|
|
1864
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
1865
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1866
|
+
import { homedir as homedir6 } from "os";
|
|
1867
|
+
import { join as join11 } from "path";
|
|
1868
|
+
function defaultIsRunning() {
|
|
1869
|
+
return existsSync8(DEFAULT_SOCK_PATH);
|
|
1870
|
+
}
|
|
1871
|
+
function defaultRunKickstart() {
|
|
1872
|
+
const uid = process.getuid?.() ?? 0;
|
|
1873
|
+
const target = `gui/${uid}/${LABEL}`;
|
|
1874
|
+
if (tryAcquireDaemonLock()) {
|
|
1875
|
+
try {
|
|
1876
|
+
execFileSync4("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
1877
|
+
} finally {
|
|
1878
|
+
releaseDaemonLock();
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function restartDaemonIfRunning(opts) {
|
|
1883
|
+
const env = opts.env ?? process.env;
|
|
1884
|
+
if (env["VITEST"] || opts.noRestart)
|
|
1885
|
+
return "skipped-opt-out";
|
|
1886
|
+
const isRunning = opts.isRunning ?? defaultIsRunning;
|
|
1887
|
+
if (!isRunning())
|
|
1888
|
+
return "skipped-not-running";
|
|
1889
|
+
const log = opts.log ?? console.log;
|
|
1890
|
+
log(`\u21BB restarting daemon to apply ${opts.reason}\u2026`);
|
|
1891
|
+
const runKickstart = opts.runKickstart ?? defaultRunKickstart;
|
|
1892
|
+
runKickstart();
|
|
1893
|
+
return "restarted";
|
|
1894
|
+
}
|
|
1895
|
+
var DEFAULT_SOCK_PATH;
|
|
1896
|
+
var init_restart_daemon = __esm({
|
|
1897
|
+
"packages/core/dist/restart-daemon.js"() {
|
|
1898
|
+
init_launchd();
|
|
1899
|
+
DEFAULT_SOCK_PATH = join11(homedir6(), ".config", "squadrant", "squadrant.sock");
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1846
1903
|
// packages/core/dist/telegram/setup.js
|
|
1847
1904
|
import fs10 from "fs";
|
|
1848
1905
|
function resolveSetupGroup(existingSupergroupId, opts) {
|
|
@@ -1868,6 +1925,26 @@ async function detectGroupAndUser(client, opts = {}) {
|
|
|
1868
1925
|
}
|
|
1869
1926
|
throw new Error("Timed out waiting for the bot to receive a message in a supergroup");
|
|
1870
1927
|
}
|
|
1928
|
+
function resolveSetupToken(existingToken, opts) {
|
|
1929
|
+
if (opts.resetToken || !existingToken)
|
|
1930
|
+
return "prompt";
|
|
1931
|
+
return "try-reuse";
|
|
1932
|
+
}
|
|
1933
|
+
function resolveSetupUserId(flagUserId, detectedUserId, stateRoot) {
|
|
1934
|
+
return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;
|
|
1935
|
+
}
|
|
1936
|
+
async function runRegisterCommands(opts) {
|
|
1937
|
+
await opts.client.setMyCommands(BOT_COMMANDS);
|
|
1938
|
+
}
|
|
1939
|
+
function runTelegramPostSetup(opts) {
|
|
1940
|
+
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
1941
|
+
const outcome = doRestart({ reason: "telegram config" });
|
|
1942
|
+
if (outcome === "skipped-not-running") {
|
|
1943
|
+
console.log("(daemon not running \u2014 change applies on next start)");
|
|
1944
|
+
} else if (outcome === "skipped-opt-out") {
|
|
1945
|
+
console.log("(run 'squadrant heal daemon' to apply)");
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1871
1948
|
function writeTelegramConfig(configPath, opts) {
|
|
1872
1949
|
let config;
|
|
1873
1950
|
let raw = null;
|
|
@@ -1904,6 +1981,97 @@ function writeTelegramConfig(configPath, opts) {
|
|
|
1904
1981
|
}
|
|
1905
1982
|
var init_setup = __esm({
|
|
1906
1983
|
"packages/core/dist/telegram/setup.js"() {
|
|
1984
|
+
init_state();
|
|
1985
|
+
init_bot_commands();
|
|
1986
|
+
init_restart_daemon();
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
// packages/core/dist/telegram/notify.js
|
|
1991
|
+
function runTelegramStatus(opts) {
|
|
1992
|
+
const tg = opts.config.telegram;
|
|
1993
|
+
const env = opts.env ?? process.env;
|
|
1994
|
+
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
1995
|
+
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
1996
|
+
const sep2 = key.indexOf("::");
|
|
1997
|
+
return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
|
|
1998
|
+
});
|
|
1999
|
+
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
2000
|
+
}
|
|
2001
|
+
function runTelegramNotifySet(opts) {
|
|
2002
|
+
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
2003
|
+
}
|
|
2004
|
+
function runTelegramNotifyPref(args) {
|
|
2005
|
+
const { project, dimension, value, root } = args;
|
|
2006
|
+
if (dimension === "crew") {
|
|
2007
|
+
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
2008
|
+
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
2009
|
+
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
2010
|
+
return { ok: true };
|
|
2011
|
+
}
|
|
2012
|
+
if (value !== "on" && value !== "off")
|
|
2013
|
+
return { ok: false, message: "cap must be on|off" };
|
|
2014
|
+
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
2015
|
+
return { ok: true };
|
|
2016
|
+
}
|
|
2017
|
+
function runTelegramNotifyStatus(opts) {
|
|
2018
|
+
const s = loadState(opts.stateRoot);
|
|
2019
|
+
const projects = /* @__PURE__ */ new Set();
|
|
2020
|
+
for (const key of Object.keys(s.topics)) {
|
|
2021
|
+
const sep2 = key.indexOf("::");
|
|
2022
|
+
projects.add(sep2 === -1 ? key : key.slice(0, sep2));
|
|
2023
|
+
}
|
|
2024
|
+
for (const p of Object.keys(s.notify))
|
|
2025
|
+
projects.add(p);
|
|
2026
|
+
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
2027
|
+
}
|
|
2028
|
+
function capAllowed(project, globalNotify, root) {
|
|
2029
|
+
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
2030
|
+
}
|
|
2031
|
+
function confirmationText(project, before, after, dim) {
|
|
2032
|
+
if (dim === "active")
|
|
2033
|
+
return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
2034
|
+
if (dim === "cap")
|
|
2035
|
+
return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
2036
|
+
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
2037
|
+
}
|
|
2038
|
+
async function runNotifyConfirmation(opts) {
|
|
2039
|
+
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
2040
|
+
if (!quieter || dim === null)
|
|
2041
|
+
return false;
|
|
2042
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
2043
|
+
if (topicId === void 0)
|
|
2044
|
+
return false;
|
|
2045
|
+
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
2046
|
+
try {
|
|
2047
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
2048
|
+
return true;
|
|
2049
|
+
} catch {
|
|
2050
|
+
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
2051
|
+
return false;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
async function runTelegramSend(opts) {
|
|
2055
|
+
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
2056
|
+
if (topicId === void 0) {
|
|
2057
|
+
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
2058
|
+
}
|
|
2059
|
+
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
2060
|
+
return { chatId: opts.cfg.supergroupId, topicId };
|
|
2061
|
+
}
|
|
2062
|
+
async function runTelegramLink(opts) {
|
|
2063
|
+
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
2064
|
+
if (existing !== void 0)
|
|
2065
|
+
return { topicId: existing, created: false };
|
|
2066
|
+
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
2067
|
+
setTopic(opts.stateRoot, opts.project, topicId);
|
|
2068
|
+
return { topicId, created: true };
|
|
2069
|
+
}
|
|
2070
|
+
var init_notify = __esm({
|
|
2071
|
+
"packages/core/dist/telegram/notify.js"() {
|
|
2072
|
+
init_dist();
|
|
2073
|
+
init_state();
|
|
2074
|
+
init_format();
|
|
1907
2075
|
}
|
|
1908
2076
|
});
|
|
1909
2077
|
|
|
@@ -1913,121 +2081,736 @@ var init_telegram = __esm({
|
|
|
1913
2081
|
init_bot_commands();
|
|
1914
2082
|
init_auth();
|
|
1915
2083
|
init_commands();
|
|
2084
|
+
init_control2();
|
|
1916
2085
|
init_ensure_captain();
|
|
1917
2086
|
init_format();
|
|
1918
2087
|
init_state();
|
|
1919
2088
|
init_client();
|
|
1920
2089
|
init_bridge();
|
|
1921
2090
|
init_setup();
|
|
2091
|
+
init_notify();
|
|
1922
2092
|
}
|
|
1923
2093
|
});
|
|
1924
2094
|
|
|
1925
|
-
// packages/core/dist/
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
init_captain_delivery();
|
|
1946
|
-
init_defer_delivery();
|
|
1947
|
-
init_session_freshness();
|
|
1948
|
-
init_crew_protocol();
|
|
1949
|
-
init_crew_lifecycle();
|
|
1950
|
-
init_telegram();
|
|
2095
|
+
// packages/core/dist/crew-routing.js
|
|
2096
|
+
function resolveCrewRoute(taskText, config) {
|
|
2097
|
+
const rules = config.defaults.crewRouting?.rules;
|
|
2098
|
+
if (!rules || rules.length === 0)
|
|
2099
|
+
return null;
|
|
2100
|
+
for (const rule2 of rules) {
|
|
2101
|
+
const re = new RegExp(rule2.match, "i");
|
|
2102
|
+
if (re.test(taskText)) {
|
|
2103
|
+
return {
|
|
2104
|
+
agent: rule2.agent,
|
|
2105
|
+
...rule2.model !== void 0 ? { model: rule2.model } : {},
|
|
2106
|
+
tier: rule2.tier,
|
|
2107
|
+
matchedRule: rule2.match
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
return null;
|
|
2112
|
+
}
|
|
2113
|
+
var init_crew_routing = __esm({
|
|
2114
|
+
"packages/core/dist/crew-routing.js"() {
|
|
1951
2115
|
}
|
|
1952
2116
|
});
|
|
1953
2117
|
|
|
1954
|
-
// packages/
|
|
1955
|
-
import {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
function cmux(args) {
|
|
1967
|
-
return new Promise((resolve3, reject) => {
|
|
1968
|
-
execFile(
|
|
1969
|
-
resolveCmuxBin(),
|
|
1970
|
-
args,
|
|
1971
|
-
// CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the
|
|
1972
|
-
// "list-workspaces is now an alias for cmux workspace list" notice). Those
|
|
1973
|
-
// notices print to the command's stdout and would otherwise pollute the
|
|
1974
|
-
// output we parse. Inherit the rest of the environment unchanged.
|
|
1975
|
-
{ encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } },
|
|
1976
|
-
(err, stdout) => {
|
|
1977
|
-
if (err) {
|
|
1978
|
-
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
resolve3(stdout.trim());
|
|
1982
|
-
}
|
|
1983
|
-
);
|
|
1984
|
-
});
|
|
2118
|
+
// packages/core/dist/group-dispatch.js
|
|
2119
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2120
|
+
import { homedir as homedir7 } from "os";
|
|
2121
|
+
import { join as join12 } from "path";
|
|
2122
|
+
function resolveCurrentProject(config) {
|
|
2123
|
+
const cwd = process.cwd();
|
|
2124
|
+
for (const [name, proj] of Object.entries(config.projects)) {
|
|
2125
|
+
const resolvedPath = resolveHome(proj.path);
|
|
2126
|
+
if (cwd.startsWith(resolvedPath))
|
|
2127
|
+
return name;
|
|
2128
|
+
}
|
|
2129
|
+
return null;
|
|
1985
2130
|
}
|
|
1986
|
-
function
|
|
1987
|
-
let parsed;
|
|
2131
|
+
async function isCaptainAlive(project, sockPath = DEFAULT_SOCK_PATH2) {
|
|
1988
2132
|
try {
|
|
1989
|
-
|
|
2133
|
+
const health = await sendRequest(sockPath, { kind: "health", project }, 5e3);
|
|
2134
|
+
const captain = health?.find((h) => h.kind === "captain" && h.project === project);
|
|
2135
|
+
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
1990
2136
|
} catch {
|
|
1991
|
-
return
|
|
1992
|
-
}
|
|
1993
|
-
const refs = [];
|
|
1994
|
-
for (const ws of parsed.workspaces ?? []) {
|
|
1995
|
-
if (!ws.ref)
|
|
1996
|
-
continue;
|
|
1997
|
-
refs.push({
|
|
1998
|
-
id: ws.ref,
|
|
1999
|
-
name: ws.has_custom_title && ws.custom_title ? ws.custom_title : ws.current_directory ?? ws.ref,
|
|
2000
|
-
status: "running"
|
|
2001
|
-
});
|
|
2137
|
+
return false;
|
|
2002
2138
|
}
|
|
2003
|
-
return refs;
|
|
2004
2139
|
}
|
|
2005
|
-
function
|
|
2006
|
-
|
|
2140
|
+
async function waitForWarmup(project, sockPath = DEFAULT_SOCK_PATH2, timeoutMs = GROUP_DISPATCH_WARMUP_TIMEOUT_MS, pollMs = GROUP_DISPATCH_WARMUP_POLL_MS) {
|
|
2141
|
+
const deadline = Date.now() + timeoutMs;
|
|
2142
|
+
while (Date.now() < deadline) {
|
|
2143
|
+
if (await isCaptainAlive(project, sockPath))
|
|
2144
|
+
return true;
|
|
2145
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
2146
|
+
}
|
|
2147
|
+
return false;
|
|
2007
2148
|
}
|
|
2008
|
-
function
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
let topHR = -1;
|
|
2015
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2016
|
-
if (HR_RE.test(lines[i])) {
|
|
2017
|
-
if (bottomHR === -1) {
|
|
2018
|
-
bottomHR = i;
|
|
2019
|
-
} else {
|
|
2020
|
-
topHR = i;
|
|
2021
|
-
break;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2149
|
+
async function dispatchToSibling(opts) {
|
|
2150
|
+
const config = loadConfig();
|
|
2151
|
+
const fromCfg = config.projects[opts.fromProject];
|
|
2152
|
+
const toCfg = config.projects[opts.toProject];
|
|
2153
|
+
if (!toCfg) {
|
|
2154
|
+
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
2024
2155
|
}
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2156
|
+
if (!fromCfg.group || !toCfg.group || fromCfg.group !== toCfg.group) {
|
|
2157
|
+
throw new Error(`cannot dispatch: '${opts.toProject}' (group: ${toCfg.group ?? "none"}) is not in the same group as '${opts.fromProject}' (group: ${fromCfg.group ?? "none"})`);
|
|
2158
|
+
}
|
|
2159
|
+
if (toCfg.acceptDelegations === false) {
|
|
2160
|
+
throw new Error(`cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`);
|
|
2161
|
+
}
|
|
2162
|
+
const sockPath = opts.sockPath ?? DEFAULT_SOCK_PATH2;
|
|
2163
|
+
const alive = await isCaptainAlive(opts.toProject, sockPath);
|
|
2164
|
+
if (!alive) {
|
|
2165
|
+
if (opts.bootCaptain) {
|
|
2166
|
+
await opts.bootCaptain(opts.toProject);
|
|
2167
|
+
}
|
|
2168
|
+
const warmed = await waitForWarmup(opts.toProject, sockPath, opts.warmupTimeoutMs, opts.warmupPollMs);
|
|
2169
|
+
if (!warmed) {
|
|
2170
|
+
throw new Error(`dispatch to '${opts.toProject}' timed out waiting for captain warmup (>${(opts.warmupTimeoutMs ?? GROUP_DISPATCH_WARMUP_TIMEOUT_MS) / 1e3}s)`);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
const now = Date.now();
|
|
2174
|
+
const attemptId = randomUUID3();
|
|
2175
|
+
const record = {
|
|
2176
|
+
id: randomUUID3(),
|
|
2177
|
+
project: opts.toProject,
|
|
2178
|
+
originProject: opts.fromProject,
|
|
2179
|
+
provider: opts.provider ?? "claude",
|
|
2180
|
+
mode: opts.mode ?? "headless",
|
|
2181
|
+
state: "submitted",
|
|
2182
|
+
task: opts.task,
|
|
2183
|
+
createdAt: now,
|
|
2184
|
+
lastHeartbeat: now,
|
|
2185
|
+
lastEvent: "dispatch",
|
|
2186
|
+
heartbeatBudgetMs: 3e5,
|
|
2187
|
+
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
2188
|
+
};
|
|
2189
|
+
const result = await sendRequest(sockPath, { kind: "dispatch", record });
|
|
2190
|
+
return result;
|
|
2191
|
+
}
|
|
2192
|
+
var DEFAULT_SOCK_PATH2, GROUP_DISPATCH_WARMUP_TIMEOUT_MS, GROUP_DISPATCH_WARMUP_POLL_MS;
|
|
2193
|
+
var init_group_dispatch = __esm({
|
|
2194
|
+
"packages/core/dist/group-dispatch.js"() {
|
|
2195
|
+
init_dist();
|
|
2196
|
+
init_protocol();
|
|
2197
|
+
DEFAULT_SOCK_PATH2 = join12(homedir7(), ".config", "squadrant", "squadrant.sock");
|
|
2198
|
+
GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 12e4;
|
|
2199
|
+
GROUP_DISPATCH_WARMUP_POLL_MS = 1e3;
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
|
|
2203
|
+
// packages/core/dist/launch-workspace.js
|
|
2204
|
+
async function deliverStartupPrompt(runtime, refId, prompt, opts = {}) {
|
|
2205
|
+
const classify = opts.classifyScreen ?? (() => "idle");
|
|
2206
|
+
const readyTimeoutMs = opts.readyTimeoutMs ?? 3e4;
|
|
2207
|
+
const settleMs = opts.settleMs ?? 2500;
|
|
2208
|
+
const pollMs = opts.pollMs ?? 1e3;
|
|
2209
|
+
const maxAttempts = opts.maxAttempts ?? 3;
|
|
2210
|
+
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2211
|
+
const read = async () => runtime.readScreen(refId).catch(() => "");
|
|
2212
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2213
|
+
const deadline = Date.now() + readyTimeoutMs;
|
|
2214
|
+
let preSend = await read();
|
|
2215
|
+
let state = classify(preSend);
|
|
2216
|
+
while (state === "loading" && Date.now() < deadline) {
|
|
2217
|
+
await sleep2(pollMs);
|
|
2218
|
+
preSend = await read();
|
|
2219
|
+
state = classify(preSend);
|
|
2220
|
+
}
|
|
2221
|
+
if (state === "working")
|
|
2222
|
+
return;
|
|
2223
|
+
await runtime.send(refId, prompt).catch(() => {
|
|
2224
|
+
});
|
|
2225
|
+
if (state === "loading")
|
|
2226
|
+
return;
|
|
2227
|
+
await sleep2(settleMs);
|
|
2228
|
+
const after = await read();
|
|
2229
|
+
if (after !== preSend)
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
async function bootWorkspace(opts) {
|
|
2234
|
+
const { runtime, workspaceName, agentCmd, cwd, navigate = false, forceFresh = false, pinToTop = false, initialPrompt } = opts;
|
|
2235
|
+
const existing = await runtime.status(workspaceName);
|
|
2236
|
+
if (existing && forceFresh) {
|
|
2237
|
+
opts.onStoppingStale?.(workspaceName);
|
|
2238
|
+
await runtime.stop(existing.id);
|
|
2239
|
+
} else if (existing) {
|
|
2240
|
+
opts.onAlreadyExists?.(workspaceName);
|
|
2241
|
+
opts.selectWorkspace?.(existing.id);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const rawCurrent = opts.getCurrentWorkspace?.() ?? null;
|
|
2245
|
+
const currentRef = rawCurrent?.match(/workspace:\d+/)?.[0];
|
|
2246
|
+
const ref = await runtime.spawn({
|
|
2247
|
+
name: workspaceName,
|
|
2248
|
+
workdir: cwd ?? process.cwd(),
|
|
2249
|
+
command: agentCmd,
|
|
2250
|
+
pinToTop
|
|
2251
|
+
});
|
|
2252
|
+
if (initialPrompt) {
|
|
2253
|
+
void deliverStartupPrompt(runtime, ref.id, initialPrompt, {
|
|
2254
|
+
classifyScreen: opts.classifyScreen
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
if (navigate) {
|
|
2258
|
+
opts.selectWorkspace?.(ref.id);
|
|
2259
|
+
} else if (currentRef) {
|
|
2260
|
+
opts.selectWorkspace?.(currentRef);
|
|
2261
|
+
}
|
|
2262
|
+
opts.onCreated?.(workspaceName);
|
|
2263
|
+
}
|
|
2264
|
+
async function launchOneWorkspace(opts) {
|
|
2265
|
+
let forceFresh = !!opts.forceFreshOverride;
|
|
2266
|
+
if (!forceFresh) {
|
|
2267
|
+
const auto = shouldStartFresh(opts.workspaceName, opts.role, {
|
|
2268
|
+
sessionsPath: opts.sessionsPath,
|
|
2269
|
+
templatesDir: opts.templatesDir
|
|
2270
|
+
});
|
|
2271
|
+
if (auto.fresh) {
|
|
2272
|
+
opts.onFreshReason?.(auto.reason ?? "starting fresh");
|
|
2273
|
+
forceFresh = true;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const agentCmd = opts.agentCmdFactory(forceFresh);
|
|
2277
|
+
recordSession(opts.workspaceName, opts.role, {
|
|
2278
|
+
sessionsPath: opts.sessionsPath,
|
|
2279
|
+
templatesDir: opts.templatesDir
|
|
2280
|
+
});
|
|
2281
|
+
await bootWorkspace({
|
|
2282
|
+
runtime: opts.runtime,
|
|
2283
|
+
workspaceName: opts.workspaceName,
|
|
2284
|
+
agentCmd,
|
|
2285
|
+
cwd: opts.cwd,
|
|
2286
|
+
navigate: opts.navigate,
|
|
2287
|
+
forceFresh,
|
|
2288
|
+
pinToTop: opts.pinToTop,
|
|
2289
|
+
initialPrompt: opts.initialPrompt,
|
|
2290
|
+
classifyScreen: opts.classifyScreen,
|
|
2291
|
+
selectWorkspace: opts.selectWorkspace,
|
|
2292
|
+
getCurrentWorkspace: opts.getCurrentWorkspace,
|
|
2293
|
+
onStoppingStale: opts.onStoppingStale,
|
|
2294
|
+
onAlreadyExists: opts.onAlreadyExists,
|
|
2295
|
+
onCreated: opts.onCreated
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
var init_launch_workspace = __esm({
|
|
2299
|
+
"packages/core/dist/launch-workspace.js"() {
|
|
2300
|
+
init_session_freshness();
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
// packages/core/dist/side-session.js
|
|
2305
|
+
import fs11 from "fs";
|
|
2306
|
+
function sideTitleFor(project, name) {
|
|
2307
|
+
return `\u{1F5D2} ${project}:${name}`;
|
|
2308
|
+
}
|
|
2309
|
+
function isSideTitle(project, title) {
|
|
2310
|
+
return title.startsWith(`\u{1F5D2} ${project}:`);
|
|
2311
|
+
}
|
|
2312
|
+
function sideNameFromTitle(project, title) {
|
|
2313
|
+
return title.slice(`\u{1F5D2} ${project}:`.length);
|
|
2314
|
+
}
|
|
2315
|
+
function sideNextAutoName(existingTitles, project) {
|
|
2316
|
+
const used = /* @__PURE__ */ new Set();
|
|
2317
|
+
for (const title of existingTitles) {
|
|
2318
|
+
const n = sideNameFromTitle(project, title).match(/^side-(\d+)$/);
|
|
2319
|
+
if (n)
|
|
2320
|
+
used.add(Number(n[1]));
|
|
2321
|
+
}
|
|
2322
|
+
let i = 1;
|
|
2323
|
+
while (used.has(i))
|
|
2324
|
+
i++;
|
|
2325
|
+
return `side-${i}`;
|
|
2326
|
+
}
|
|
2327
|
+
function buildSideFirstTurn(topic, project, role, spokeVault, scratchWorktree) {
|
|
2328
|
+
const lines = [
|
|
2329
|
+
topic,
|
|
2330
|
+
"",
|
|
2331
|
+
"---",
|
|
2332
|
+
"Side-session context (for handoff use):",
|
|
2333
|
+
`Project: ${project}`,
|
|
2334
|
+
`Role: ${role}`,
|
|
2335
|
+
`Spoke vault: ${spokeVault}`
|
|
2336
|
+
];
|
|
2337
|
+
if (scratchWorktree) {
|
|
2338
|
+
lines.push(`Scratch worktree: ${scratchWorktree}`);
|
|
2339
|
+
}
|
|
2340
|
+
return lines.join("\n");
|
|
2341
|
+
}
|
|
2342
|
+
async function runSideSpawn(input, config, deps) {
|
|
2343
|
+
const proj = config.projects[input.project];
|
|
2344
|
+
if (!proj) {
|
|
2345
|
+
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
2346
|
+
}
|
|
2347
|
+
if (!SIDE_ROLES.includes(input.role)) {
|
|
2348
|
+
throw new Error(`Unknown side role '${input.role}'. Valid roles: ${SIDE_ROLES.join(", ")}.`);
|
|
2349
|
+
}
|
|
2350
|
+
const { runtime } = deps;
|
|
2351
|
+
const captain = await runtime.status(proj.captainName);
|
|
2352
|
+
if (!captain) {
|
|
2353
|
+
throw new Error(`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`);
|
|
2354
|
+
}
|
|
2355
|
+
const existing = await runtime.listSurfaces(captain.id);
|
|
2356
|
+
const existingTitles = existing.filter((s) => s.title && isSideTitle(input.project, s.title)).map((s) => s.title);
|
|
2357
|
+
if (input.name) {
|
|
2358
|
+
const wantTitle = sideTitleFor(input.project, input.name);
|
|
2359
|
+
if (existingTitles.includes(wantTitle)) {
|
|
2360
|
+
throw new Error(`Side session '${input.name}' already exists for ${input.project}.`);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
const name = input.name ?? sideNextAutoName(existingTitles, input.project);
|
|
2364
|
+
const spawnCwd = input.role === "debug" ? addWorktree({
|
|
2365
|
+
repoRoot: proj.path,
|
|
2366
|
+
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
2367
|
+
project: input.project,
|
|
2368
|
+
name,
|
|
2369
|
+
base: resolveWorktreeBase(proj.path)
|
|
2370
|
+
}) : proj.path;
|
|
2371
|
+
const agentCmd = deps.agentCmdFactory(spawnCwd);
|
|
2372
|
+
const direction = input.direction ?? "tab";
|
|
2373
|
+
const title = sideTitleFor(input.project, name);
|
|
2374
|
+
const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
2375
|
+
await runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${agentCmd}`);
|
|
2376
|
+
const preLaunchScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
2377
|
+
const firstTurn = buildSideFirstTurn(input.topic, input.project, input.role, proj.spokeVault ?? "", input.role === "debug" ? spawnCwd : void 0);
|
|
2378
|
+
await deps.sendFirstTurn(pane, firstTurn, preLaunchScreen);
|
|
2379
|
+
return { ...pane, title };
|
|
2380
|
+
}
|
|
2381
|
+
async function runSideSend(runtime, workspaceId, project, name, message) {
|
|
2382
|
+
const want = sideTitleFor(project, name);
|
|
2383
|
+
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
2384
|
+
const pane = surfaces.find((s) => s.title === want) ?? null;
|
|
2385
|
+
if (!pane) {
|
|
2386
|
+
throw new Error(`Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`);
|
|
2387
|
+
}
|
|
2388
|
+
await runtime.sendToPane(pane, message);
|
|
2389
|
+
}
|
|
2390
|
+
async function runSideList(runtime, workspaceId, project) {
|
|
2391
|
+
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
2392
|
+
return surfaces.filter((s) => s.title && isSideTitle(project, s.title)).map((s) => ({
|
|
2393
|
+
name: sideNameFromTitle(project, s.title),
|
|
2394
|
+
surfaceId: s.surfaceId
|
|
2395
|
+
}));
|
|
2396
|
+
}
|
|
2397
|
+
async function runSideClose(runtime, workspaceId, project, name, projPath, worktreeDir) {
|
|
2398
|
+
const want = sideTitleFor(project, name);
|
|
2399
|
+
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
2400
|
+
const pane = surfaces.find((s) => s.title === want) ?? null;
|
|
2401
|
+
if (!pane) {
|
|
2402
|
+
throw new Error(`Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`);
|
|
2403
|
+
}
|
|
2404
|
+
await runtime.closePane(pane);
|
|
2405
|
+
if (projPath) {
|
|
2406
|
+
const wtPath = worktreePath(projPath, worktreeDir, project, name);
|
|
2407
|
+
if (fs11.existsSync(wtPath)) {
|
|
2408
|
+
try {
|
|
2409
|
+
removeWorktree(projPath, wtPath);
|
|
2410
|
+
} catch (e) {
|
|
2411
|
+
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
2412
|
+
`);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
var SIDE_ROLES;
|
|
2418
|
+
var init_side_session = __esm({
|
|
2419
|
+
"packages/core/dist/side-session.js"() {
|
|
2420
|
+
init_dist();
|
|
2421
|
+
init_crew_protocol();
|
|
2422
|
+
SIDE_ROLES = ["research", "debug"];
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
// packages/core/dist/crew-spawn.js
|
|
2427
|
+
import fs12 from "fs";
|
|
2428
|
+
import os4 from "os";
|
|
2429
|
+
import path10 from "path";
|
|
2430
|
+
async function listCrewPanes(runtime, workspaceId, project) {
|
|
2431
|
+
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
2432
|
+
return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));
|
|
2433
|
+
}
|
|
2434
|
+
async function findCrewPane(runtime, workspaceId, project, name) {
|
|
2435
|
+
const want = titleFor(project, name);
|
|
2436
|
+
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
2437
|
+
return surfaces.find((s) => s.title === want) ?? null;
|
|
2438
|
+
}
|
|
2439
|
+
async function runCodexInteractiveSpawn(o) {
|
|
2440
|
+
const rec = await o.dispatchCrew({
|
|
2441
|
+
provider: "codex",
|
|
2442
|
+
mode: "interactive",
|
|
2443
|
+
project: o.project,
|
|
2444
|
+
cwd: o.cwd,
|
|
2445
|
+
task: o.task,
|
|
2446
|
+
name: o.name,
|
|
2447
|
+
...o.approvalPolicy ? { approvalPolicy: o.approvalPolicy } : {},
|
|
2448
|
+
...o.roleInstructions ? { roleInstructions: o.roleInstructions } : {}
|
|
2449
|
+
});
|
|
2450
|
+
const title = titleFor(o.project, o.name);
|
|
2451
|
+
const pane = await o.runtime.newPane({
|
|
2452
|
+
workspaceId: o.workspaceId,
|
|
2453
|
+
direction: o.direction,
|
|
2454
|
+
title
|
|
2455
|
+
});
|
|
2456
|
+
await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);
|
|
2457
|
+
if (o.task && o.task !== "(interactive)") {
|
|
2458
|
+
void o.sendCodexFirstTurn(rec.id, o.task).catch((e) => {
|
|
2459
|
+
process.stderr.write(`(first-turn delivery failed: ${e.message})
|
|
2460
|
+
`);
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
return { ...pane, title };
|
|
2464
|
+
}
|
|
2465
|
+
async function runCrewSpawn(input, config, deps) {
|
|
2466
|
+
const proj = config.projects[input.project];
|
|
2467
|
+
if (!proj) {
|
|
2468
|
+
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
2469
|
+
}
|
|
2470
|
+
const captain = await deps.runtime.status(proj.captainName);
|
|
2471
|
+
if (!captain) {
|
|
2472
|
+
throw new Error(`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`);
|
|
2473
|
+
}
|
|
2474
|
+
const existing = await listCrewPanes(deps.runtime, captain.id, input.project);
|
|
2475
|
+
const existingTitles = existing.map((s) => s.title);
|
|
2476
|
+
if (input.name) {
|
|
2477
|
+
const wantTitle = titleFor(input.project, input.name);
|
|
2478
|
+
if (existingTitles.includes(wantTitle)) {
|
|
2479
|
+
throw new Error(`Crew '${input.name}' already exists for ${input.project}. Use 'squadrant crew send ${input.project} ${input.name}' to send a follow-up, or pick a different --name.`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const name = input.name ?? nextAutoName(existingTitles, input.project);
|
|
2483
|
+
const spawnCwd = !input.shared ? addWorktree({
|
|
2484
|
+
repoRoot: proj.path,
|
|
2485
|
+
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
2486
|
+
project: input.project,
|
|
2487
|
+
name,
|
|
2488
|
+
base: resolveWorktreeBase(proj.path)
|
|
2489
|
+
}) : proj.path;
|
|
2490
|
+
const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
|
|
2491
|
+
if (route) {
|
|
2492
|
+
deps.onRouted?.(route);
|
|
2493
|
+
}
|
|
2494
|
+
const agentName = route?.agent ?? input.agent ?? "claude";
|
|
2495
|
+
const agent = deps.resolveAgent(agentName);
|
|
2496
|
+
if (!agent) {
|
|
2497
|
+
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
2498
|
+
}
|
|
2499
|
+
if (agentName === "codex") {
|
|
2500
|
+
const codexRoleFile = path10.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
2501
|
+
const roleInstructions = fs12.existsSync(codexRoleFile) ? fs12.readFileSync(codexRoleFile, "utf8") : void 0;
|
|
2502
|
+
return runCodexInteractiveSpawn({
|
|
2503
|
+
project: input.project,
|
|
2504
|
+
task: input.task,
|
|
2505
|
+
cwd: spawnCwd,
|
|
2506
|
+
runtime: deps.runtime,
|
|
2507
|
+
workspaceId: captain.id,
|
|
2508
|
+
name,
|
|
2509
|
+
direction: input.direction ?? "tab",
|
|
2510
|
+
approvalPolicy: input.approvalPolicy,
|
|
2511
|
+
roleInstructions,
|
|
2512
|
+
dispatchCrew: deps.dispatchCrew,
|
|
2513
|
+
sendCodexFirstTurn: deps.sendCodexFirstTurn
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
const promptFile = path10.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);
|
|
2517
|
+
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
2518
|
+
const crewRole = config.defaults.roles?.crew;
|
|
2519
|
+
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
2520
|
+
const crewModel = input.model ?? route?.model ?? configModel;
|
|
2521
|
+
if (agentName === "claude") {
|
|
2522
|
+
const rec = await deps.dispatchCrew({
|
|
2523
|
+
provider: "claude",
|
|
2524
|
+
mode: "interactive",
|
|
2525
|
+
project: input.project,
|
|
2526
|
+
cwd: spawnCwd,
|
|
2527
|
+
task: input.task,
|
|
2528
|
+
name
|
|
2529
|
+
});
|
|
2530
|
+
deps.writeSettingsLocal(spawnCwd);
|
|
2531
|
+
const cliCommand2 = agent.buildCommand({
|
|
2532
|
+
prompt: input.task,
|
|
2533
|
+
workdir: spawnCwd,
|
|
2534
|
+
role: "crew",
|
|
2535
|
+
promptFile,
|
|
2536
|
+
interactive: true,
|
|
2537
|
+
// Permission mode is config-driven so squadrant can default crews to 'auto'
|
|
2538
|
+
// or keep the semi-automatic 'acceptEdits' gate. Falls back to 'acceptEdits'.
|
|
2539
|
+
permissionMode: config.defaults.permissions?.crew ?? "acceptEdits",
|
|
2540
|
+
...crewModel ? { model: crewModel } : {}
|
|
2541
|
+
});
|
|
2542
|
+
const direction2 = input.direction ?? "tab";
|
|
2543
|
+
const title2 = titleFor(input.project, name);
|
|
2544
|
+
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
2545
|
+
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
2546
|
+
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${cliCommand2}`);
|
|
2547
|
+
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
2548
|
+
await deps.sendFirstTurn(pane2, `${input.task}
|
|
2549
|
+
|
|
2550
|
+
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
2551
|
+
return { ...pane2, title: title2 };
|
|
2552
|
+
}
|
|
2553
|
+
if (agentName === "opencode") {
|
|
2554
|
+
const serverPort = await deps.getFreePort();
|
|
2555
|
+
const rec = await deps.dispatchCrew({
|
|
2556
|
+
provider: "opencode",
|
|
2557
|
+
mode: "interactive",
|
|
2558
|
+
project: input.project,
|
|
2559
|
+
cwd: spawnCwd,
|
|
2560
|
+
task: input.task,
|
|
2561
|
+
name,
|
|
2562
|
+
// opencode has no heartbeat hook, so a normal budget would false-stall
|
|
2563
|
+
// every crew after 5min; use a 24h budget to effectively disable stall
|
|
2564
|
+
// detection. The SSE bridge (serverPort) provides turn-end liveness.
|
|
2565
|
+
budgetMs: 864e5,
|
|
2566
|
+
serverPort
|
|
2567
|
+
});
|
|
2568
|
+
const opencodeConfigPath = deps.writeOpencodeConfig({
|
|
2569
|
+
stateRoot: STATE_ROOT,
|
|
2570
|
+
project: input.project,
|
|
2571
|
+
taskId: rec.id,
|
|
2572
|
+
// CP3 opt-in: --approval gates bash so the captain approves shell commands.
|
|
2573
|
+
...input.approval ? { gateBash: true } : {}
|
|
2574
|
+
});
|
|
2575
|
+
const cliCommand2 = agent.buildCommand({
|
|
2576
|
+
prompt: input.task,
|
|
2577
|
+
workdir: spawnCwd,
|
|
2578
|
+
role: "crew",
|
|
2579
|
+
promptFile,
|
|
2580
|
+
interactive: true,
|
|
2581
|
+
model: crewModel,
|
|
2582
|
+
port: serverPort
|
|
2583
|
+
});
|
|
2584
|
+
const direction2 = input.direction ?? "tab";
|
|
2585
|
+
const title2 = titleFor(input.project, name);
|
|
2586
|
+
const pane2 = await deps.runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
2587
|
+
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
2588
|
+
await deps.runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${cliCommand2}`);
|
|
2589
|
+
const preLaunchScreen = await deps.runtime.readPaneScreen(pane2) ?? "";
|
|
2590
|
+
await deps.sendFirstTurn(pane2, `${input.task}
|
|
2591
|
+
|
|
2592
|
+
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
2593
|
+
// #235: confirm-on-delivery — sendFirstTurnWhenReady polls until "Ask
|
|
2594
|
+
// anything…" leaves the screen, re-sending every ~3s to cover slow boots
|
|
2595
|
+
// without duplicating the task. See crew-pane.ts SPLASH_MAX_CHECKS/EVERY_N.
|
|
2596
|
+
splashMarker: "Ask anything\u2026"
|
|
2597
|
+
});
|
|
2598
|
+
return { ...pane2, title: title2 };
|
|
2599
|
+
}
|
|
2600
|
+
const cliCommand = agent.buildCommand({
|
|
2601
|
+
prompt: input.task,
|
|
2602
|
+
workdir: spawnCwd,
|
|
2603
|
+
role: "crew",
|
|
2604
|
+
promptFile,
|
|
2605
|
+
interactive,
|
|
2606
|
+
model: crewModel
|
|
2607
|
+
});
|
|
2608
|
+
const direction = input.direction ?? "tab";
|
|
2609
|
+
const title = titleFor(input.project, name);
|
|
2610
|
+
const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
2611
|
+
await deps.runtime.sendToPane(pane, cliCommand);
|
|
2612
|
+
if (interactive) {
|
|
2613
|
+
const preLaunchScreen = await deps.runtime.readPaneScreen(pane) ?? "";
|
|
2614
|
+
await deps.sendFirstTurn(pane, input.task, preLaunchScreen);
|
|
2615
|
+
}
|
|
2616
|
+
return { ...pane, title };
|
|
2617
|
+
}
|
|
2618
|
+
async function runCrewSend(project, name, message, runtime, workspaceId, deps) {
|
|
2619
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
2620
|
+
if (!crew) {
|
|
2621
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
2622
|
+
}
|
|
2623
|
+
try {
|
|
2624
|
+
const tasks = await deps.listTasks(project);
|
|
2625
|
+
const task = tasks.find((t) => t.name === name);
|
|
2626
|
+
if (task) {
|
|
2627
|
+
if (TERMINAL_STATES.has(task.state)) {
|
|
2628
|
+
await deps.emitEvent(project, { type: "task.reopened", id: task.id });
|
|
2629
|
+
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
2630
|
+
await deps.emitEvent(project, { type: "task.started", id: task.id });
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
} catch {
|
|
2634
|
+
}
|
|
2635
|
+
await runtime.sendToPane(crew, message);
|
|
2636
|
+
}
|
|
2637
|
+
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
2638
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
2639
|
+
if (!crew) {
|
|
2640
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
2641
|
+
}
|
|
2642
|
+
return runtime.readPaneScreen(crew);
|
|
2643
|
+
}
|
|
2644
|
+
async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
2645
|
+
const projRoot = loadConfig().projects[project]?.path;
|
|
2646
|
+
let taskId;
|
|
2647
|
+
let worktreeCwd;
|
|
2648
|
+
try {
|
|
2649
|
+
const tasks = await deps.listTasks(project);
|
|
2650
|
+
const task = tasks.find((t) => t.name === name);
|
|
2651
|
+
if (task) {
|
|
2652
|
+
taskId = task.id;
|
|
2653
|
+
if (task.cwd && projRoot && task.cwd !== projRoot) {
|
|
2654
|
+
worktreeCwd = task.cwd;
|
|
2655
|
+
}
|
|
2656
|
+
if (!TERMINAL_STATES.has(task.state)) {
|
|
2657
|
+
await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
|
|
2658
|
+
}
|
|
2659
|
+
if (task.provider === "codex") {
|
|
2660
|
+
await deps.closeCodexThread(task.id);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
} catch {
|
|
2664
|
+
}
|
|
2665
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
2666
|
+
if (crew) {
|
|
2667
|
+
await runtime.closePane(crew);
|
|
2668
|
+
} else if (taskId === void 0) {
|
|
2669
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
2670
|
+
}
|
|
2671
|
+
if (taskId !== void 0) {
|
|
2672
|
+
await reapCrewChildren(taskId);
|
|
2673
|
+
}
|
|
2674
|
+
if (worktreeCwd && projRoot) {
|
|
2675
|
+
try {
|
|
2676
|
+
removeWorktree(projRoot, worktreeCwd);
|
|
2677
|
+
} catch (e) {
|
|
2678
|
+
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
2679
|
+
`);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
async function runCrewList(project, runtime, workspaceId) {
|
|
2684
|
+
const crews = await listCrewPanes(runtime, workspaceId, project);
|
|
2685
|
+
return crews.map((c) => ({
|
|
2686
|
+
name: nameFromTitle(project, c.title),
|
|
2687
|
+
surfaceId: c.surfaceId
|
|
2688
|
+
}));
|
|
2689
|
+
}
|
|
2690
|
+
var TEMPLATES_DIR, STATE_ROOT;
|
|
2691
|
+
var init_crew_spawn = __esm({
|
|
2692
|
+
"packages/core/dist/crew-spawn.js"() {
|
|
2693
|
+
init_dist();
|
|
2694
|
+
init_crew_routing();
|
|
2695
|
+
init_crew_protocol();
|
|
2696
|
+
init_crew_lifecycle();
|
|
2697
|
+
TEMPLATES_DIR = path10.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
2698
|
+
STATE_ROOT = path10.join(os4.homedir(), ".config", "squadrant", "state");
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
|
|
2702
|
+
// packages/core/dist/index.js
|
|
2703
|
+
var init_dist2 = __esm({
|
|
2704
|
+
"packages/core/dist/index.js"() {
|
|
2705
|
+
init_reduce();
|
|
2706
|
+
init_mailbox();
|
|
2707
|
+
init_protocol();
|
|
2708
|
+
init_state_machine();
|
|
2709
|
+
init_liveness();
|
|
2710
|
+
init_watchdog();
|
|
2711
|
+
init_store();
|
|
2712
|
+
init_snapshot();
|
|
2713
|
+
init_launchd();
|
|
2714
|
+
init_crew_pane_reader();
|
|
2715
|
+
init_interfaces();
|
|
2716
|
+
init_gate();
|
|
2717
|
+
init_context();
|
|
2718
|
+
init_attach();
|
|
2719
|
+
init_start();
|
|
2720
|
+
init_delivery_loop();
|
|
2721
|
+
init_interactive_probe();
|
|
2722
|
+
init_captain_delivery();
|
|
2723
|
+
init_defer_delivery();
|
|
2724
|
+
init_session_freshness();
|
|
2725
|
+
init_crew_protocol();
|
|
2726
|
+
init_crew_lifecycle();
|
|
2727
|
+
init_telegram();
|
|
2728
|
+
init_crew_routing();
|
|
2729
|
+
init_restart_daemon();
|
|
2730
|
+
init_group_dispatch();
|
|
2731
|
+
init_launch_workspace();
|
|
2732
|
+
init_side_session();
|
|
2733
|
+
init_crew_spawn();
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
// packages/workspaces/dist/runtimes/cmux.js
|
|
2738
|
+
import { execFile as execFile2, execFileSync as execFileSync5 } from "child_process";
|
|
2739
|
+
function isInsideCmux() {
|
|
2740
|
+
return !!process.env.CMUX_WORKSPACE_ID;
|
|
2741
|
+
}
|
|
2742
|
+
function cmuxLocal(args) {
|
|
2743
|
+
return execFileSync5(resolveCmuxBin(), args, {
|
|
2744
|
+
encoding: "utf-8",
|
|
2745
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2746
|
+
timeout: CMUX_TIMEOUT
|
|
2747
|
+
}).trim();
|
|
2748
|
+
}
|
|
2749
|
+
function cmux(args) {
|
|
2750
|
+
return new Promise((resolve3, reject) => {
|
|
2751
|
+
execFile2(
|
|
2752
|
+
resolveCmuxBin(),
|
|
2753
|
+
args,
|
|
2754
|
+
// CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the
|
|
2755
|
+
// "list-workspaces is now an alias for cmux workspace list" notice). Those
|
|
2756
|
+
// notices print to the command's stdout and would otherwise pollute the
|
|
2757
|
+
// output we parse. Inherit the rest of the environment unchanged.
|
|
2758
|
+
{ encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } },
|
|
2759
|
+
(err, stdout) => {
|
|
2760
|
+
if (err) {
|
|
2761
|
+
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
resolve3(stdout.trim());
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
function parseList(output) {
|
|
2770
|
+
let parsed;
|
|
2771
|
+
try {
|
|
2772
|
+
parsed = JSON.parse(output);
|
|
2773
|
+
} catch {
|
|
2774
|
+
return [];
|
|
2775
|
+
}
|
|
2776
|
+
const refs = [];
|
|
2777
|
+
for (const ws of parsed.workspaces ?? []) {
|
|
2778
|
+
if (!ws.ref)
|
|
2779
|
+
continue;
|
|
2780
|
+
refs.push({
|
|
2781
|
+
id: ws.ref,
|
|
2782
|
+
name: ws.has_custom_title && ws.custom_title ? ws.custom_title : ws.current_directory ?? ws.ref,
|
|
2783
|
+
status: "running"
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
return refs;
|
|
2787
|
+
}
|
|
2788
|
+
function sanitizeForCmuxSend(text) {
|
|
2789
|
+
return text.replace(/\\[nrt]/g, " ").replace(/[\n\r\t]+/g, " ").replace(/ {2,}/g, " ").trim();
|
|
2790
|
+
}
|
|
2791
|
+
function parseDraftFromScreen(screen) {
|
|
2792
|
+
if (!screen)
|
|
2793
|
+
return null;
|
|
2794
|
+
const lines = screen.split(/\r?\n/);
|
|
2795
|
+
const HR_RE = /^\s*─{10,}\s*$/;
|
|
2796
|
+
let bottomHR = -1;
|
|
2797
|
+
let topHR = -1;
|
|
2798
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2799
|
+
if (HR_RE.test(lines[i])) {
|
|
2800
|
+
if (bottomHR === -1) {
|
|
2801
|
+
bottomHR = i;
|
|
2802
|
+
} else {
|
|
2803
|
+
topHR = i;
|
|
2804
|
+
break;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
if (topHR === -1)
|
|
2809
|
+
return null;
|
|
2810
|
+
const inputLines = lines.slice(topHR + 1, bottomHR);
|
|
2811
|
+
for (const line of inputLines) {
|
|
2812
|
+
let extracted;
|
|
2813
|
+
const boxMatch = line.match(/│\s*[>❯]\s+(.*?)\s*│/);
|
|
2031
2814
|
if (boxMatch) {
|
|
2032
2815
|
extracted = boxMatch[1].trim();
|
|
2033
2816
|
} else {
|
|
@@ -2394,7 +3177,7 @@ var init_runtimes = __esm({
|
|
|
2394
3177
|
});
|
|
2395
3178
|
|
|
2396
3179
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
2397
|
-
import { execFileSync as
|
|
3180
|
+
import { execFileSync as execFileSync6, execSync as execSync2 } from "child_process";
|
|
2398
3181
|
function createCmuxNotifier(_scope) {
|
|
2399
3182
|
return {
|
|
2400
3183
|
name: "cmux",
|
|
@@ -2411,7 +3194,7 @@ function createCmuxNotifier(_scope) {
|
|
|
2411
3194
|
}
|
|
2412
3195
|
},
|
|
2413
3196
|
async notify(message) {
|
|
2414
|
-
|
|
3197
|
+
execFileSync6("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
2415
3198
|
}
|
|
2416
3199
|
};
|
|
2417
3200
|
}
|
|
@@ -2466,13 +3249,13 @@ var init_notifiers = __esm({
|
|
|
2466
3249
|
});
|
|
2467
3250
|
|
|
2468
3251
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
2469
|
-
import
|
|
2470
|
-
import { existsSync as
|
|
2471
|
-
import
|
|
3252
|
+
import fs13 from "fs/promises";
|
|
3253
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3254
|
+
import path11 from "path";
|
|
2472
3255
|
function resolveInRoot(root, relative) {
|
|
2473
|
-
const joined =
|
|
2474
|
-
const normalized =
|
|
2475
|
-
if (joined !==
|
|
3256
|
+
const joined = path11.resolve(root, relative);
|
|
3257
|
+
const normalized = path11.resolve(root) + path11.sep;
|
|
3258
|
+
if (joined !== path11.resolve(root) && !joined.startsWith(normalized)) {
|
|
2476
3259
|
throw new Error(`Path '${relative}' escapes workspace root`);
|
|
2477
3260
|
}
|
|
2478
3261
|
return joined;
|
|
@@ -2487,20 +3270,20 @@ function createObsidianDriver(scope) {
|
|
|
2487
3270
|
async probe() {
|
|
2488
3271
|
return {
|
|
2489
3272
|
installed: true,
|
|
2490
|
-
rootExists:
|
|
3273
|
+
rootExists: existsSync9(root)
|
|
2491
3274
|
};
|
|
2492
3275
|
},
|
|
2493
3276
|
async read(rel) {
|
|
2494
|
-
return
|
|
3277
|
+
return fs13.readFile(resolveInRoot(root, rel), "utf-8");
|
|
2495
3278
|
},
|
|
2496
3279
|
async write(rel, content) {
|
|
2497
3280
|
const abs = resolveInRoot(root, rel);
|
|
2498
|
-
await
|
|
2499
|
-
await
|
|
3281
|
+
await fs13.mkdir(path11.dirname(abs), { recursive: true });
|
|
3282
|
+
await fs13.writeFile(abs, content);
|
|
2500
3283
|
},
|
|
2501
3284
|
async exists(rel) {
|
|
2502
3285
|
try {
|
|
2503
|
-
await
|
|
3286
|
+
await fs13.access(resolveInRoot(root, rel));
|
|
2504
3287
|
return true;
|
|
2505
3288
|
} catch {
|
|
2506
3289
|
return false;
|
|
@@ -2508,13 +3291,13 @@ function createObsidianDriver(scope) {
|
|
|
2508
3291
|
},
|
|
2509
3292
|
async list(rel) {
|
|
2510
3293
|
try {
|
|
2511
|
-
return await
|
|
3294
|
+
return await fs13.readdir(resolveInRoot(root, rel));
|
|
2512
3295
|
} catch {
|
|
2513
3296
|
return [];
|
|
2514
3297
|
}
|
|
2515
3298
|
},
|
|
2516
3299
|
async mkdir(rel) {
|
|
2517
|
-
await
|
|
3300
|
+
await fs13.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
2518
3301
|
}
|
|
2519
3302
|
};
|
|
2520
3303
|
}
|
|
@@ -2572,7 +3355,7 @@ var init_workspaces2 = __esm({
|
|
|
2572
3355
|
}
|
|
2573
3356
|
});
|
|
2574
3357
|
|
|
2575
|
-
// packages/workspaces/dist/cmux/events-bridge.js
|
|
3358
|
+
// packages/workspaces/dist/cmux-daemon/events-bridge.js
|
|
2576
3359
|
import { spawn as nodeSpawn } from "child_process";
|
|
2577
3360
|
function deriveRunState(eventName) {
|
|
2578
3361
|
switch (eventName) {
|
|
@@ -2587,7 +3370,7 @@ function deriveRunState(eventName) {
|
|
|
2587
3370
|
}
|
|
2588
3371
|
var CmuxEventsBridge;
|
|
2589
3372
|
var init_events_bridge = __esm({
|
|
2590
|
-
"packages/workspaces/dist/cmux/events-bridge.js"() {
|
|
3373
|
+
"packages/workspaces/dist/cmux-daemon/events-bridge.js"() {
|
|
2591
3374
|
init_dist();
|
|
2592
3375
|
CmuxEventsBridge = class {
|
|
2593
3376
|
child = null;
|
|
@@ -2712,11 +3495,11 @@ var init_events_bridge = __esm({
|
|
|
2712
3495
|
}
|
|
2713
3496
|
});
|
|
2714
3497
|
|
|
2715
|
-
// packages/workspaces/dist/cmux/daemon-cmux.js
|
|
3498
|
+
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
2716
3499
|
var DaemonCmux;
|
|
2717
3500
|
var init_daemon_cmux = __esm({
|
|
2718
|
-
"packages/workspaces/dist/cmux/daemon-cmux.js"() {
|
|
2719
|
-
|
|
3501
|
+
"packages/workspaces/dist/cmux-daemon/daemon-cmux.js"() {
|
|
3502
|
+
init_dist2();
|
|
2720
3503
|
DaemonCmux = class {
|
|
2721
3504
|
driver;
|
|
2722
3505
|
constructor(driver) {
|
|
@@ -2822,19 +3605,32 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
2822
3605
|
}
|
|
2823
3606
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
2824
3607
|
await runtime.sendToPane(pane, task);
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
3608
|
+
if (acceptanceConfig?.splashMarker) {
|
|
3609
|
+
for (let check2 = 0; check2 < SPLASH_MAX_CHECKS; check2++) {
|
|
3610
|
+
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
3611
|
+
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
3612
|
+
if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
if ((check2 + 1) % SPLASH_RESEND_EVERY_N === 0 && check2 < SPLASH_MAX_CHECKS - 1) {
|
|
3616
|
+
await runtime.sendToPane(pane, task);
|
|
3617
|
+
}
|
|
2831
3618
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
3619
|
+
} else {
|
|
3620
|
+
const retryLimit = acceptanceConfig?.retryLimit ?? 2;
|
|
3621
|
+
for (let attempt = 0; attempt < retryLimit; attempt++) {
|
|
3622
|
+
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
3623
|
+
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
3624
|
+
if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
if (attempt < retryLimit - 1) {
|
|
3628
|
+
await runtime.sendToPane(pane, task);
|
|
3629
|
+
}
|
|
2834
3630
|
}
|
|
2835
3631
|
}
|
|
2836
3632
|
}
|
|
2837
|
-
var SEND_FIRST_TURN_FLOOR_MS, POLL_INTERVAL_MS, SEND_FIRST_TURN_TIMEOUT_MS, POST_SEND_CHECK_MS;
|
|
3633
|
+
var SEND_FIRST_TURN_FLOOR_MS, POLL_INTERVAL_MS, SEND_FIRST_TURN_TIMEOUT_MS, POST_SEND_CHECK_MS, SPLASH_MAX_CHECKS, SPLASH_RESEND_EVERY_N;
|
|
2838
3634
|
var init_crew_pane = __esm({
|
|
2839
3635
|
"packages/workspaces/dist/crew-pane.js"() {
|
|
2840
3636
|
init_dist();
|
|
@@ -2845,6 +3641,8 @@ var init_crew_pane = __esm({
|
|
|
2845
3641
|
POLL_INTERVAL_MS = 750;
|
|
2846
3642
|
SEND_FIRST_TURN_TIMEOUT_MS = 2e4;
|
|
2847
3643
|
POST_SEND_CHECK_MS = 750;
|
|
3644
|
+
SPLASH_MAX_CHECKS = 20;
|
|
3645
|
+
SPLASH_RESEND_EVERY_N = 4;
|
|
2848
3646
|
}
|
|
2849
3647
|
});
|
|
2850
3648
|
|
|
@@ -2854,7 +3652,6 @@ __export(dist_exports, {
|
|
|
2854
3652
|
CMUX_TIMEOUT: () => CMUX_TIMEOUT,
|
|
2855
3653
|
CmuxEventsBridge: () => CmuxEventsBridge,
|
|
2856
3654
|
DaemonCmux: () => DaemonCmux,
|
|
2857
|
-
DeferDelivery: () => DeferDelivery,
|
|
2858
3655
|
NotifierRegistry: () => NotifierRegistry,
|
|
2859
3656
|
RuntimeRegistry: () => RuntimeRegistry,
|
|
2860
3657
|
WorkspaceRegistry: () => WorkspaceRegistry,
|
|
@@ -3183,8 +3980,8 @@ var init_registry4 = __esm({
|
|
|
3183
3980
|
});
|
|
3184
3981
|
|
|
3185
3982
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
3186
|
-
import
|
|
3187
|
-
import
|
|
3983
|
+
import fs14 from "fs";
|
|
3984
|
+
import path12 from "path";
|
|
3188
3985
|
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
|
|
3189
3986
|
const driver = registry.getDriver(agentName);
|
|
3190
3987
|
if (driver.name === "claude") {
|
|
@@ -3200,27 +3997,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
3200
3997
|
cmd += ` --model ${model}`;
|
|
3201
3998
|
}
|
|
3202
3999
|
if (templatesDir) {
|
|
3203
|
-
const roleFile2 =
|
|
3204
|
-
const legacyRoleFile =
|
|
3205
|
-
const actualRoleFile =
|
|
4000
|
+
const roleFile2 = path12.join(templatesDir, `${role}.claude.md`);
|
|
4001
|
+
const legacyRoleFile = path12.join(templatesDir, `${role}.CLAUDE.md`);
|
|
4002
|
+
const actualRoleFile = fs14.existsSync(roleFile2) ? roleFile2 : fs14.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
3206
4003
|
if (actualRoleFile) {
|
|
3207
4004
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
3208
4005
|
}
|
|
3209
|
-
const pluginDir =
|
|
3210
|
-
if (
|
|
4006
|
+
const pluginDir = path12.join(templatesDir, "..", "plugin");
|
|
4007
|
+
if (fs14.existsSync(pluginDir)) {
|
|
3211
4008
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
3212
4009
|
}
|
|
3213
4010
|
}
|
|
3214
4011
|
return cmd;
|
|
3215
4012
|
}
|
|
3216
|
-
const roleFile = templatesDir ?
|
|
4013
|
+
const roleFile = templatesDir ? path12.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
|
|
3217
4014
|
return driver.buildCommand({
|
|
3218
4015
|
prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
|
|
3219
4016
|
workdir: process.cwd(),
|
|
3220
4017
|
role,
|
|
3221
4018
|
model,
|
|
3222
4019
|
autoApprove: true,
|
|
3223
|
-
promptFile: roleFile &&
|
|
4020
|
+
promptFile: roleFile && fs14.existsSync(roleFile) ? roleFile : void 0
|
|
3224
4021
|
});
|
|
3225
4022
|
}
|
|
3226
4023
|
var init_launch_cmd = __esm({
|
|
@@ -3243,8 +4040,8 @@ var init_drivers = __esm({
|
|
|
3243
4040
|
|
|
3244
4041
|
// packages/agents/dist/projection/cursor.js
|
|
3245
4042
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3246
|
-
import
|
|
3247
|
-
import
|
|
4043
|
+
import path13 from "path";
|
|
4044
|
+
import os5 from "os";
|
|
3248
4045
|
function renderMdc(source) {
|
|
3249
4046
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3250
4047
|
|
|
@@ -3292,7 +4089,7 @@ function createCursorEmitter() {
|
|
|
3292
4089
|
if (scope === "user") {
|
|
3293
4090
|
return [
|
|
3294
4091
|
{
|
|
3295
|
-
path:
|
|
4092
|
+
path: path13.join(os5.homedir(), ".cursor/rules/squadrant-global.mdc"),
|
|
3296
4093
|
shared: false,
|
|
3297
4094
|
format: "mdc"
|
|
3298
4095
|
}
|
|
@@ -3302,7 +4099,7 @@ function createCursorEmitter() {
|
|
|
3302
4099
|
return [];
|
|
3303
4100
|
return [
|
|
3304
4101
|
{
|
|
3305
|
-
path:
|
|
4102
|
+
path: path13.join(projectRoot, ".cursor/rules/squadrant.mdc"),
|
|
3306
4103
|
shared: false,
|
|
3307
4104
|
format: "mdc"
|
|
3308
4105
|
}
|
|
@@ -3319,7 +4116,7 @@ function createCursorEmitter() {
|
|
|
3319
4116
|
diff: buildDiff(existing, generated)
|
|
3320
4117
|
};
|
|
3321
4118
|
}
|
|
3322
|
-
await mkdir(
|
|
4119
|
+
await mkdir(path13.dirname(dest.path), { recursive: true });
|
|
3323
4120
|
await writeFile(dest.path, generated, "utf-8");
|
|
3324
4121
|
return {
|
|
3325
4122
|
written: true,
|
|
@@ -3370,8 +4167,8 @@ var init_marker = __esm({
|
|
|
3370
4167
|
|
|
3371
4168
|
// packages/agents/dist/projection/codex.js
|
|
3372
4169
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3373
|
-
import
|
|
3374
|
-
import
|
|
4170
|
+
import path14 from "path";
|
|
4171
|
+
import os6 from "os";
|
|
3375
4172
|
function renderMarkdown(source) {
|
|
3376
4173
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3377
4174
|
|
|
@@ -3395,7 +4192,7 @@ function createCodexEmitter() {
|
|
|
3395
4192
|
destinations(scope, projectRoot) {
|
|
3396
4193
|
if (scope === "user") {
|
|
3397
4194
|
return [{
|
|
3398
|
-
path:
|
|
4195
|
+
path: path14.join(os6.homedir(), ".codex/AGENTS.md"),
|
|
3399
4196
|
shared: true,
|
|
3400
4197
|
format: "markdown"
|
|
3401
4198
|
}];
|
|
@@ -3403,7 +4200,7 @@ function createCodexEmitter() {
|
|
|
3403
4200
|
if (!projectRoot)
|
|
3404
4201
|
return [];
|
|
3405
4202
|
return [{
|
|
3406
|
-
path:
|
|
4203
|
+
path: path14.join(projectRoot, "AGENTS.md"),
|
|
3407
4204
|
shared: true,
|
|
3408
4205
|
format: "markdown"
|
|
3409
4206
|
}];
|
|
@@ -3424,7 +4221,7 @@ ${existing ?? ""}
|
|
|
3424
4221
|
${generated}`
|
|
3425
4222
|
};
|
|
3426
4223
|
}
|
|
3427
|
-
await mkdir2(
|
|
4224
|
+
await mkdir2(path14.dirname(dest.path), { recursive: true });
|
|
3428
4225
|
await writeFile2(dest.path, generated, "utf-8");
|
|
3429
4226
|
return {
|
|
3430
4227
|
written: true,
|
|
@@ -3442,8 +4239,8 @@ var init_codex2 = __esm({
|
|
|
3442
4239
|
|
|
3443
4240
|
// packages/agents/dist/projection/gemini.js
|
|
3444
4241
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
3445
|
-
import
|
|
3446
|
-
import
|
|
4242
|
+
import path15 from "path";
|
|
4243
|
+
import os7 from "os";
|
|
3447
4244
|
function renderMarkdown2(source) {
|
|
3448
4245
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3449
4246
|
|
|
@@ -3467,7 +4264,7 @@ function createGeminiEmitter() {
|
|
|
3467
4264
|
destinations(scope, projectRoot) {
|
|
3468
4265
|
if (scope === "user") {
|
|
3469
4266
|
return [{
|
|
3470
|
-
path:
|
|
4267
|
+
path: path15.join(os7.homedir(), ".gemini/GEMINI.md"),
|
|
3471
4268
|
shared: true,
|
|
3472
4269
|
format: "markdown"
|
|
3473
4270
|
}];
|
|
@@ -3475,7 +4272,7 @@ function createGeminiEmitter() {
|
|
|
3475
4272
|
if (!projectRoot)
|
|
3476
4273
|
return [];
|
|
3477
4274
|
return [{
|
|
3478
|
-
path:
|
|
4275
|
+
path: path15.join(projectRoot, "GEMINI.md"),
|
|
3479
4276
|
shared: true,
|
|
3480
4277
|
format: "markdown"
|
|
3481
4278
|
}];
|
|
@@ -3496,7 +4293,7 @@ ${existing ?? ""}
|
|
|
3496
4293
|
${generated}`
|
|
3497
4294
|
};
|
|
3498
4295
|
}
|
|
3499
|
-
await mkdir3(
|
|
4296
|
+
await mkdir3(path15.dirname(dest.path), { recursive: true });
|
|
3500
4297
|
await writeFile3(dest.path, generated, "utf-8");
|
|
3501
4298
|
return {
|
|
3502
4299
|
written: true,
|
|
@@ -3514,8 +4311,8 @@ var init_gemini2 = __esm({
|
|
|
3514
4311
|
|
|
3515
4312
|
// packages/agents/dist/projection/opencode.js
|
|
3516
4313
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
3517
|
-
import
|
|
3518
|
-
import
|
|
4314
|
+
import path16 from "path";
|
|
4315
|
+
import os8 from "os";
|
|
3519
4316
|
function renderMarkdown3(source) {
|
|
3520
4317
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3521
4318
|
|
|
@@ -3539,7 +4336,7 @@ function createOpencodeEmitter() {
|
|
|
3539
4336
|
destinations(scope, projectRoot) {
|
|
3540
4337
|
if (scope === "user") {
|
|
3541
4338
|
return [{
|
|
3542
|
-
path:
|
|
4339
|
+
path: path16.join(os8.homedir(), ".config", "opencode", "AGENTS.md"),
|
|
3543
4340
|
shared: true,
|
|
3544
4341
|
format: "markdown"
|
|
3545
4342
|
}];
|
|
@@ -3547,7 +4344,7 @@ function createOpencodeEmitter() {
|
|
|
3547
4344
|
if (!projectRoot)
|
|
3548
4345
|
return [];
|
|
3549
4346
|
return [{
|
|
3550
|
-
path:
|
|
4347
|
+
path: path16.join(projectRoot, "AGENTS.md"),
|
|
3551
4348
|
shared: true,
|
|
3552
4349
|
format: "markdown"
|
|
3553
4350
|
}];
|
|
@@ -3568,7 +4365,7 @@ ${existing ?? ""}
|
|
|
3568
4365
|
${generated}`
|
|
3569
4366
|
};
|
|
3570
4367
|
}
|
|
3571
|
-
await mkdir4(
|
|
4368
|
+
await mkdir4(path16.dirname(dest.path), { recursive: true });
|
|
3572
4369
|
await writeFile4(dest.path, generated, "utf-8");
|
|
3573
4370
|
return {
|
|
3574
4371
|
written: true,
|
|
@@ -3809,11 +4606,11 @@ var init_app_server_client = __esm({
|
|
|
3809
4606
|
|
|
3810
4607
|
// packages/agents/dist/codex/config.js
|
|
3811
4608
|
import { readFile as readFile5 } from "fs/promises";
|
|
3812
|
-
import { homedir as
|
|
3813
|
-
import { join as
|
|
4609
|
+
import { homedir as homedir9 } from "os";
|
|
4610
|
+
import { join as join14 } from "path";
|
|
3814
4611
|
async function resolveCodexModel() {
|
|
3815
|
-
const home = process.env["CODEX_HOME"] ??
|
|
3816
|
-
const configPath =
|
|
4612
|
+
const home = process.env["CODEX_HOME"] ?? join14(homedir9(), ".codex");
|
|
4613
|
+
const configPath = join14(home, "config.toml");
|
|
3817
4614
|
let text;
|
|
3818
4615
|
try {
|
|
3819
4616
|
text = await readFile5(configPath, "utf8");
|
|
@@ -4335,8 +5132,8 @@ var init_sse_bridge = __esm({
|
|
|
4335
5132
|
// packages/agents/dist/interactive/claude.js
|
|
4336
5133
|
import { execSync as execSync7 } from "child_process";
|
|
4337
5134
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4338
|
-
import { homedir as
|
|
4339
|
-
import { join as
|
|
5135
|
+
import { homedir as homedir10 } from "os";
|
|
5136
|
+
import { join as join15 } from "path";
|
|
4340
5137
|
function probeClaudeSettingsFlag() {
|
|
4341
5138
|
try {
|
|
4342
5139
|
const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -4387,7 +5184,7 @@ function deriveTranscriptPath(sessionId, cwd) {
|
|
|
4387
5184
|
if (!sessionId || !cwd)
|
|
4388
5185
|
return null;
|
|
4389
5186
|
const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
4390
|
-
return
|
|
5187
|
+
return join15(homedir10(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
|
|
4391
5188
|
}
|
|
4392
5189
|
function readLastAssistantText(transcriptPath) {
|
|
4393
5190
|
try {
|
|
@@ -4846,18 +5643,18 @@ init_dist();
|
|
|
4846
5643
|
init_dist3();
|
|
4847
5644
|
import { Command } from "commander";
|
|
4848
5645
|
import { execSync as execSync8 } from "child_process";
|
|
4849
|
-
import
|
|
5646
|
+
import fs15 from "fs";
|
|
4850
5647
|
import { stat } from "fs/promises";
|
|
4851
|
-
import
|
|
5648
|
+
import path17 from "path";
|
|
4852
5649
|
import chalk3 from "chalk";
|
|
4853
5650
|
|
|
4854
5651
|
// packages/cli/src/commands/health-view.ts
|
|
4855
5652
|
init_dist2();
|
|
4856
5653
|
init_dist2();
|
|
4857
|
-
import { homedir as
|
|
4858
|
-
import { join as
|
|
5654
|
+
import { homedir as homedir8 } from "os";
|
|
5655
|
+
import { join as join13 } from "path";
|
|
4859
5656
|
import chalk2 from "chalk";
|
|
4860
|
-
var SOCK =
|
|
5657
|
+
var SOCK = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
|
|
4861
5658
|
async function queryHealth(project) {
|
|
4862
5659
|
try {
|
|
4863
5660
|
const reply = await sendRequest(SOCK, { kind: "health", project });
|
|
@@ -4902,7 +5699,8 @@ function healthRow(c, now) {
|
|
|
4902
5699
|
function printServiceHealth(rows, now = Date.now()) {
|
|
4903
5700
|
console.log(chalk2.bold("\nService Health\n"));
|
|
4904
5701
|
if (rows == null) {
|
|
4905
|
-
console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown
|
|
5702
|
+
console.log(` ${chalk2.red("\u2718")} daemon unreachable \u2014 squadrant liveness is unknown`);
|
|
5703
|
+
console.log(` ${chalk2.cyan("\u2192")} ${chalk2.dim("Run: squadrant heal daemon")}`);
|
|
4906
5704
|
return;
|
|
4907
5705
|
}
|
|
4908
5706
|
if (rows.length === 0) {
|
|
@@ -4950,7 +5748,7 @@ function settingsHaveAgentTeams() {
|
|
|
4950
5748
|
try {
|
|
4951
5749
|
const home = process.env.HOME || "";
|
|
4952
5750
|
const settings = JSON.parse(
|
|
4953
|
-
|
|
5751
|
+
fs15.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
4954
5752
|
);
|
|
4955
5753
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
4956
5754
|
} catch {
|
|
@@ -4961,7 +5759,7 @@ function pluginInstalled(pluginKey) {
|
|
|
4961
5759
|
try {
|
|
4962
5760
|
const home = process.env.HOME || "";
|
|
4963
5761
|
const plugins = JSON.parse(
|
|
4964
|
-
|
|
5762
|
+
fs15.readFileSync(
|
|
4965
5763
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
4966
5764
|
"utf-8"
|
|
4967
5765
|
)
|
|
@@ -4983,29 +5781,61 @@ function tryGetVersion(cmd) {
|
|
|
4983
5781
|
return "";
|
|
4984
5782
|
}
|
|
4985
5783
|
}
|
|
4986
|
-
function check(label, pass) {
|
|
5784
|
+
function check(label, pass, hint) {
|
|
4987
5785
|
const icon = pass ? chalk3.green("\u2714 PASS") : chalk3.red("\u2718 FAIL");
|
|
4988
5786
|
console.log(` ${icon} ${label}`);
|
|
5787
|
+
if (!pass && hint) {
|
|
5788
|
+
console.log(` ${chalk3.cyan("\u2192")} ${chalk3.dim(hint)}`);
|
|
5789
|
+
}
|
|
4989
5790
|
return pass;
|
|
4990
5791
|
}
|
|
4991
5792
|
var doctorCommand = new Command("doctor").description("Check system health and prerequisites").action(async () => {
|
|
4992
5793
|
console.log(chalk3.bold("\nSquadrant Doctor\n"));
|
|
4993
5794
|
const results = [];
|
|
4994
|
-
results.push(check(
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
5795
|
+
results.push(check(
|
|
5796
|
+
"Claude Code installed",
|
|
5797
|
+
commandExists("claude"),
|
|
5798
|
+
"Install from: https://claude.ai/code"
|
|
5799
|
+
));
|
|
5800
|
+
results.push(check(
|
|
5801
|
+
`Claude Code version >= ${compatManifest.tools.claude.min}`,
|
|
5802
|
+
claudeVersionOk(),
|
|
5803
|
+
`Update Claude Code to >= ${compatManifest.tools.claude.min}`
|
|
5804
|
+
));
|
|
5805
|
+
results.push(check(
|
|
5806
|
+
"Obsidian installed",
|
|
5807
|
+
commandExists("obsidian") || fs15.existsSync("/Applications/Obsidian.app"),
|
|
5808
|
+
"Install from: https://obsidian.md"
|
|
5809
|
+
));
|
|
5810
|
+
results.push(check(
|
|
5811
|
+
"Node.js >= 18",
|
|
5812
|
+
nodeVersionOk(),
|
|
5813
|
+
"Install from: https://nodejs.org"
|
|
5814
|
+
));
|
|
4998
5815
|
results.push(
|
|
4999
5816
|
check(
|
|
5000
5817
|
"Agent Teams enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)",
|
|
5001
|
-
settingsHaveAgentTeams()
|
|
5818
|
+
settingsHaveAgentTeams(),
|
|
5819
|
+
"Run: squadrant init (enables automatically), or add to shell profile: export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
|
|
5002
5820
|
)
|
|
5003
5821
|
);
|
|
5004
|
-
results.push(check(
|
|
5822
|
+
results.push(check(
|
|
5823
|
+
"Plugin: superpowers",
|
|
5824
|
+
pluginInstalled("superpowers@claude-plugins-official"),
|
|
5825
|
+
"In Claude Code, run: /plugin marketplace add superpowers"
|
|
5826
|
+
));
|
|
5005
5827
|
results.push(
|
|
5006
|
-
check(
|
|
5828
|
+
check(
|
|
5829
|
+
"Plugin: claude-mem",
|
|
5830
|
+
pluginInstalled("claude-mem@thedotmack"),
|
|
5831
|
+
"In Claude Code, run: /plugin marketplace add thedotmack/claude-mem"
|
|
5832
|
+
)
|
|
5007
5833
|
);
|
|
5008
|
-
results.push(check(
|
|
5834
|
+
results.push(check(
|
|
5835
|
+
"Plugin: context7",
|
|
5836
|
+
pluginInstalled("context7@claude-plugins-official"),
|
|
5837
|
+
"In Claude Code, run: /plugin marketplace add context7"
|
|
5838
|
+
));
|
|
5009
5839
|
const config = loadConfig();
|
|
5010
5840
|
const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
5011
5841
|
const probeResults = await runtimes.probeAll();
|
|
@@ -5013,7 +5843,8 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5013
5843
|
const globalProbe = probeResults[globalRuntimeName];
|
|
5014
5844
|
results.push(check(
|
|
5015
5845
|
`Runtime '${globalRuntimeName}' installed`,
|
|
5016
|
-
!!globalProbe?.installed
|
|
5846
|
+
!!globalProbe?.installed,
|
|
5847
|
+
globalRuntimeName === "cmux" ? "Install: npm install -g cmux or download from https://cmux.dev" : void 0
|
|
5017
5848
|
));
|
|
5018
5849
|
const overrides = /* @__PURE__ */ new Set();
|
|
5019
5850
|
for (const proj of Object.values(config.projects)) {
|
|
@@ -5031,7 +5862,8 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5031
5862
|
const hubProbe = await hubDriver.probe();
|
|
5032
5863
|
results.push(check(
|
|
5033
5864
|
`Workspace '${config.workspace ?? "obsidian"}' \u2014 hub reachable`,
|
|
5034
|
-
hubProbe.installed && hubProbe.rootExists
|
|
5865
|
+
hubProbe.installed && hubProbe.rootExists,
|
|
5866
|
+
"Run: squadrant init to scaffold the hub vault"
|
|
5035
5867
|
));
|
|
5036
5868
|
for (const [name] of Object.entries(config.projects)) {
|
|
5037
5869
|
const spokeDriver = workspaces.forProject(name, config);
|
|
@@ -5066,7 +5898,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5066
5898
|
const emitter = projectionRegistry.get(name);
|
|
5067
5899
|
const [userDest] = emitter.destinations("user");
|
|
5068
5900
|
if (!userDest) continue;
|
|
5069
|
-
const dir =
|
|
5901
|
+
const dir = path17.dirname(userDest.path);
|
|
5070
5902
|
let status;
|
|
5071
5903
|
try {
|
|
5072
5904
|
await stat(dir);
|
|
@@ -5079,9 +5911,10 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5079
5911
|
results.push(
|
|
5080
5912
|
check(
|
|
5081
5913
|
"Squadrant config exists",
|
|
5082
|
-
|
|
5914
|
+
fs15.existsSync(
|
|
5083
5915
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
5084
|
-
)
|
|
5916
|
+
),
|
|
5917
|
+
"Run: squadrant init"
|
|
5085
5918
|
)
|
|
5086
5919
|
);
|
|
5087
5920
|
const passed = results.filter(Boolean).length;
|
|
@@ -5151,152 +5984,193 @@ ${passed === total ? chalk3.green("All checks passed") : chalk3.yellow(`${passed
|
|
|
5151
5984
|
init_dist();
|
|
5152
5985
|
init_dist3();
|
|
5153
5986
|
init_dist();
|
|
5987
|
+
init_dist4();
|
|
5154
5988
|
import { Command as Command2 } from "commander";
|
|
5155
|
-
import
|
|
5156
|
-
import
|
|
5157
|
-
import
|
|
5989
|
+
import fs16 from "fs";
|
|
5990
|
+
import path18 from "path";
|
|
5991
|
+
import os9 from "os";
|
|
5992
|
+
import readline from "readline";
|
|
5158
5993
|
import chalk4 from "chalk";
|
|
5159
5994
|
function findPackageRoot() {
|
|
5160
|
-
let dir =
|
|
5995
|
+
let dir = path18.dirname(new URL(import.meta.url).pathname);
|
|
5161
5996
|
while (dir !== "/") {
|
|
5162
|
-
if (
|
|
5163
|
-
dir =
|
|
5997
|
+
if (fs16.existsSync(path18.join(dir, "package.json"))) return dir;
|
|
5998
|
+
dir = path18.dirname(dir);
|
|
5164
5999
|
}
|
|
5165
6000
|
return process.cwd();
|
|
5166
6001
|
}
|
|
5167
6002
|
function copyDirRecursive(src, dest) {
|
|
5168
|
-
|
|
5169
|
-
for (const entry of
|
|
5170
|
-
const srcPath =
|
|
5171
|
-
const destPath =
|
|
6003
|
+
fs16.mkdirSync(dest, { recursive: true });
|
|
6004
|
+
for (const entry of fs16.readdirSync(src, { withFileTypes: true })) {
|
|
6005
|
+
const srcPath = path18.join(src, entry.name);
|
|
6006
|
+
const destPath = path18.join(dest, entry.name);
|
|
5172
6007
|
if (entry.isDirectory()) {
|
|
5173
6008
|
copyDirRecursive(srcPath, destPath);
|
|
5174
6009
|
} else {
|
|
5175
|
-
|
|
6010
|
+
fs16.copyFileSync(srcPath, destPath);
|
|
5176
6011
|
}
|
|
5177
6012
|
}
|
|
5178
6013
|
}
|
|
5179
|
-
|
|
6014
|
+
function stepHeader(n, total, label) {
|
|
6015
|
+
console.log(chalk4.bold(`
|
|
6016
|
+
${n}/${total} ${label}`));
|
|
6017
|
+
}
|
|
6018
|
+
function promptLine(question) {
|
|
6019
|
+
return new Promise((resolve3) => {
|
|
6020
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6021
|
+
rl.question(question, (answer) => {
|
|
6022
|
+
rl.close();
|
|
6023
|
+
resolve3(answer.trim());
|
|
6024
|
+
});
|
|
6025
|
+
});
|
|
6026
|
+
}
|
|
6027
|
+
var initCommand = new Command2("init").description("Guided first-time setup: hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").action(async (opts) => {
|
|
5180
6028
|
const hubPath = resolveHome(opts.hub);
|
|
5181
6029
|
const pkgRoot = findPackageRoot();
|
|
5182
|
-
const configDir =
|
|
6030
|
+
const configDir = path18.join(os9.homedir(), ".config", "squadrant");
|
|
6031
|
+
const isTTY = process.stdin.isTTY === true;
|
|
5183
6032
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
5184
|
-
|
|
6033
|
+
if (!isTTY) {
|
|
6034
|
+
console.log(" Run these steps to get started:\n");
|
|
6035
|
+
console.log(chalk4.bold(" 1/5 Hub vault"));
|
|
6036
|
+
console.log(chalk4.cyan(` squadrant init --hub ${opts.hub}`));
|
|
6037
|
+
console.log(chalk4.bold("\n 2/5 Agent + projection setup"));
|
|
6038
|
+
console.log(" (handled automatically by: " + chalk4.cyan("squadrant init") + ")");
|
|
6039
|
+
console.log(chalk4.bold("\n 3/5 Plugins \u2014 open Claude Code and run:"));
|
|
6040
|
+
console.log(chalk4.cyan(" /plugin marketplace add superpowers"));
|
|
6041
|
+
console.log(chalk4.cyan(" /plugin marketplace add thedotmack/claude-mem"));
|
|
6042
|
+
console.log(chalk4.cyan(" /plugin marketplace add context7"));
|
|
6043
|
+
console.log(chalk4.bold("\n 4/5 Register first project"));
|
|
6044
|
+
console.log(chalk4.cyan(" squadrant projects add <name> <path>"));
|
|
6045
|
+
console.log(chalk4.bold("\n 5/5 Telegram (optional)"));
|
|
6046
|
+
console.log(chalk4.cyan(" squadrant telegram setup"));
|
|
6047
|
+
console.log(chalk4.bold("\n Then:"));
|
|
6048
|
+
console.log(chalk4.cyan(" squadrant launch <projectname>\n"));
|
|
6049
|
+
return;
|
|
6050
|
+
}
|
|
6051
|
+
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
5185
6052
|
try {
|
|
5186
|
-
if (
|
|
5187
|
-
const existing = JSON.parse(
|
|
5188
|
-
|
|
5189
|
-
registry.get(wsName);
|
|
6053
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
6054
|
+
const existing = JSON.parse(fs16.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
6055
|
+
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
5190
6056
|
}
|
|
5191
6057
|
} catch (err) {
|
|
5192
6058
|
console.log(chalk4.red(` \u2718 ${err.message}`));
|
|
5193
6059
|
return;
|
|
5194
6060
|
}
|
|
5195
|
-
|
|
5196
|
-
|
|
6061
|
+
stepHeader(1, 5, "Hub vault");
|
|
6062
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
6063
|
+
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
5197
6064
|
} else {
|
|
5198
6065
|
const config = getDefaultConfig();
|
|
5199
6066
|
config.hubVault = hubPath;
|
|
5200
6067
|
saveConfig(config);
|
|
5201
|
-
console.log(chalk4.green(`
|
|
6068
|
+
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
5202
6069
|
}
|
|
5203
|
-
const hubTemplate =
|
|
5204
|
-
if (
|
|
5205
|
-
console.log(chalk4.yellow(`
|
|
5206
|
-
} else if (
|
|
6070
|
+
const hubTemplate = path18.join(pkgRoot, "obsidian", "hub");
|
|
6071
|
+
if (fs16.existsSync(hubPath)) {
|
|
6072
|
+
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
6073
|
+
} else if (fs16.existsSync(hubTemplate)) {
|
|
5207
6074
|
copyDirRecursive(hubTemplate, hubPath);
|
|
5208
|
-
console.log(chalk4.green(`
|
|
6075
|
+
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
5209
6076
|
} else {
|
|
5210
|
-
|
|
5211
|
-
console.log(chalk4.yellow(`
|
|
6077
|
+
fs16.mkdirSync(hubPath, { recursive: true });
|
|
6078
|
+
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
5212
6079
|
}
|
|
5213
|
-
const hubDashboardSrc =
|
|
5214
|
-
const hubDashboardDest =
|
|
5215
|
-
if (
|
|
5216
|
-
|
|
5217
|
-
console.log(chalk4.green(`
|
|
6080
|
+
const hubDashboardSrc = path18.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
6081
|
+
const hubDashboardDest = path18.join(hubPath, "dashboard.md");
|
|
6082
|
+
if (fs16.existsSync(hubDashboardSrc)) {
|
|
6083
|
+
fs16.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
6084
|
+
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
5218
6085
|
}
|
|
5219
|
-
|
|
5220
|
-
fs14.mkdirSync(projectsDir, { recursive: true });
|
|
6086
|
+
fs16.mkdirSync(path18.join(hubPath, "projects"), { recursive: true });
|
|
5221
6087
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
5222
|
-
console.log(chalk4.green(`
|
|
5223
|
-
|
|
6088
|
+
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
6089
|
+
stepHeader(2, 5, "Agent + projection setup");
|
|
6090
|
+
const settingsPath = path18.join(os9.homedir(), ".claude", "settings.json");
|
|
5224
6091
|
try {
|
|
5225
6092
|
let settings = {};
|
|
5226
|
-
if (
|
|
5227
|
-
settings = JSON.parse(
|
|
6093
|
+
if (fs16.existsSync(settingsPath)) {
|
|
6094
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
5228
6095
|
}
|
|
5229
6096
|
const env = settings.env || {};
|
|
5230
6097
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
5231
6098
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
console.log(chalk4.green("
|
|
6099
|
+
fs16.mkdirSync(path18.dirname(settingsPath), { recursive: true });
|
|
6100
|
+
fs16.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6101
|
+
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
5235
6102
|
} else {
|
|
5236
|
-
console.log(chalk4.green("
|
|
6103
|
+
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
5237
6104
|
}
|
|
5238
6105
|
} catch {
|
|
5239
|
-
console.log(chalk4.yellow("
|
|
6106
|
+
console.log(chalk4.yellow(" \u26A0 Could not update ~/.claude/settings.json"));
|
|
6107
|
+
console.log(chalk4.dim(" Add manually to shell profile: export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"));
|
|
5240
6108
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
6109
|
+
try {
|
|
6110
|
+
const projRegistry = new ProjectionRegistry({
|
|
6111
|
+
cursor: createCursorEmitter,
|
|
6112
|
+
codex: createCodexEmitter,
|
|
6113
|
+
gemini: createGeminiEmitter,
|
|
6114
|
+
opencode: createOpencodeEmitter
|
|
6115
|
+
});
|
|
6116
|
+
const workspace = createObsidianDriver({ root: process.cwd() });
|
|
6117
|
+
const source = await readUserLevelSource(workspace, { pkgRoot });
|
|
6118
|
+
for (const name of projRegistry.list()) {
|
|
6119
|
+
const emitter = projRegistry.get(name);
|
|
6120
|
+
for (const dest of emitter.destinations("user")) {
|
|
6121
|
+
const result = await emitter.emit(source, dest);
|
|
6122
|
+
if (result.written) {
|
|
6123
|
+
console.log(chalk4.green(` \u2714 ${name} \u2192 ${dest.path}`));
|
|
6124
|
+
} else {
|
|
6125
|
+
console.log(chalk4.dim(` - ${name} \u2192 ${dest.path} (unchanged)`));
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
} catch (err) {
|
|
6130
|
+
console.log(chalk4.yellow(` \u26A0 Projection emit skipped: ${err.message}`));
|
|
6131
|
+
}
|
|
6132
|
+
stepHeader(3, 5, "Plugins");
|
|
6133
|
+
console.log(" Install these plugins inside Claude Code:\n");
|
|
6134
|
+
console.log(chalk4.cyan(" /plugin marketplace add superpowers"));
|
|
6135
|
+
console.log(chalk4.cyan(" /plugin marketplace add thedotmack/claude-mem"));
|
|
6136
|
+
console.log(chalk4.cyan(" /plugin marketplace add context7\n"));
|
|
6137
|
+
console.log(chalk4.dim(" Squadrant never auto-installs plugins \u2014 open Claude Code and run the commands above."));
|
|
6138
|
+
stepHeader(4, 5, "Register first project");
|
|
6139
|
+
const projectPath = await promptLine(
|
|
6140
|
+
chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
|
|
6141
|
+
);
|
|
6142
|
+
if (projectPath) {
|
|
6143
|
+
const projectName = path18.basename(projectPath);
|
|
6144
|
+
console.log(chalk4.bold(`
|
|
6145
|
+
Run this to register it:`));
|
|
6146
|
+
console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
|
|
6147
|
+
`));
|
|
6148
|
+
} else {
|
|
6149
|
+
console.log(chalk4.dim(" Skipped. Register later with:"));
|
|
6150
|
+
console.log(chalk4.cyan(" squadrant projects add <name> <path>"));
|
|
6151
|
+
}
|
|
6152
|
+
stepHeader(5, 5, "Telegram (optional)");
|
|
6153
|
+
console.log(" Get notified on your phone when crews complete tasks:");
|
|
6154
|
+
console.log(chalk4.cyan(" squadrant telegram setup\n"));
|
|
6155
|
+
let launchTarget = "<projectname>";
|
|
6156
|
+
try {
|
|
6157
|
+
const cfg = loadConfig();
|
|
6158
|
+
const first = Object.keys(cfg.projects)[0];
|
|
6159
|
+
if (first) launchTarget = first;
|
|
6160
|
+
} catch {
|
|
5256
6161
|
}
|
|
6162
|
+
console.log(chalk4.bold.green("\n \u2714 You are ready!\n"));
|
|
6163
|
+
console.log(` Run: ${chalk4.cyan(`squadrant launch ${launchTarget}`)}
|
|
6164
|
+
`);
|
|
5257
6165
|
});
|
|
5258
6166
|
|
|
5259
6167
|
// packages/cli/src/commands/projects.ts
|
|
5260
6168
|
init_dist();
|
|
6169
|
+
init_dist2();
|
|
5261
6170
|
import { Command as Command3 } from "commander";
|
|
5262
|
-
import
|
|
5263
|
-
import
|
|
6171
|
+
import fs17 from "fs";
|
|
6172
|
+
import path19 from "path";
|
|
5264
6173
|
import chalk5 from "chalk";
|
|
5265
|
-
|
|
5266
|
-
// packages/cli/src/control/restart-daemon.ts
|
|
5267
|
-
init_dist2();
|
|
5268
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
5269
|
-
import { existsSync as existsSync9 } from "fs";
|
|
5270
|
-
import { homedir as homedir9 } from "os";
|
|
5271
|
-
import { join as join14 } from "path";
|
|
5272
|
-
var DEFAULT_SOCK_PATH = join14(homedir9(), ".config", "squadrant", "squadrant.sock");
|
|
5273
|
-
function defaultIsRunning() {
|
|
5274
|
-
return existsSync9(DEFAULT_SOCK_PATH);
|
|
5275
|
-
}
|
|
5276
|
-
function defaultRunKickstart() {
|
|
5277
|
-
const uid = process.getuid?.() ?? 0;
|
|
5278
|
-
const target = `gui/${uid}/${LABEL}`;
|
|
5279
|
-
if (tryAcquireDaemonLock()) {
|
|
5280
|
-
try {
|
|
5281
|
-
execFileSync6("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
5282
|
-
} finally {
|
|
5283
|
-
releaseDaemonLock();
|
|
5284
|
-
}
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
function restartDaemonIfRunning(opts) {
|
|
5288
|
-
const env = opts.env ?? process.env;
|
|
5289
|
-
if (env["VITEST"] || opts.noRestart) return "skipped-opt-out";
|
|
5290
|
-
const isRunning = opts.isRunning ?? defaultIsRunning;
|
|
5291
|
-
if (!isRunning()) return "skipped-not-running";
|
|
5292
|
-
const log = opts.log ?? console.log;
|
|
5293
|
-
log(`\u21BB restarting daemon to apply ${opts.reason}\u2026`);
|
|
5294
|
-
const runKickstart = opts.runKickstart ?? defaultRunKickstart;
|
|
5295
|
-
runKickstart();
|
|
5296
|
-
return "restarted";
|
|
5297
|
-
}
|
|
5298
|
-
|
|
5299
|
-
// packages/cli/src/commands/projects.ts
|
|
5300
6174
|
function restartAfterProjectsAdd(opts) {
|
|
5301
6175
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
5302
6176
|
const outcome = doRestart({ reason: "project registration", noRestart: opts.noRestart });
|
|
@@ -5307,22 +6181,22 @@ function restartAfterProjectsAdd(opts) {
|
|
|
5307
6181
|
}
|
|
5308
6182
|
}
|
|
5309
6183
|
function findPackageRoot2() {
|
|
5310
|
-
let dir =
|
|
6184
|
+
let dir = path19.dirname(new URL(import.meta.url).pathname);
|
|
5311
6185
|
while (dir !== "/") {
|
|
5312
|
-
if (
|
|
5313
|
-
dir =
|
|
6186
|
+
if (fs17.existsSync(path19.join(dir, "package.json"))) return dir;
|
|
6187
|
+
dir = path19.dirname(dir);
|
|
5314
6188
|
}
|
|
5315
6189
|
return process.cwd();
|
|
5316
6190
|
}
|
|
5317
6191
|
function copyDirRecursive2(src, dest) {
|
|
5318
|
-
|
|
5319
|
-
for (const entry of
|
|
5320
|
-
const srcPath =
|
|
5321
|
-
const destPath =
|
|
6192
|
+
fs17.mkdirSync(dest, { recursive: true });
|
|
6193
|
+
for (const entry of fs17.readdirSync(src, { withFileTypes: true })) {
|
|
6194
|
+
const srcPath = path19.join(src, entry.name);
|
|
6195
|
+
const destPath = path19.join(dest, entry.name);
|
|
5322
6196
|
if (entry.isDirectory()) {
|
|
5323
6197
|
copyDirRecursive2(srcPath, destPath);
|
|
5324
6198
|
} else {
|
|
5325
|
-
|
|
6199
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
5326
6200
|
}
|
|
5327
6201
|
}
|
|
5328
6202
|
}
|
|
@@ -5358,7 +6232,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5358
6232
|
process.exit(1);
|
|
5359
6233
|
}
|
|
5360
6234
|
const resolvedPath = resolveHome(projectPath);
|
|
5361
|
-
if (!
|
|
6235
|
+
if (!fs17.existsSync(path19.join(resolvedPath, ".git"))) {
|
|
5362
6236
|
console.log(chalk5.yellow(`
|
|
5363
6237
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
5364
6238
|
`));
|
|
@@ -5411,7 +6285,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5411
6285
|
\u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
|
|
5412
6286
|
}
|
|
5413
6287
|
}
|
|
5414
|
-
const spokeVault = opts.spoke ? resolveHome(opts.spoke) :
|
|
6288
|
+
const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path19.join(config.hubVault, "spokes", name);
|
|
5415
6289
|
const project = {
|
|
5416
6290
|
path: resolvedPath,
|
|
5417
6291
|
captainName,
|
|
@@ -5426,20 +6300,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5426
6300
|
\u2714 Project '${name}' registered`));
|
|
5427
6301
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
5428
6302
|
const pkgRoot = findPackageRoot2();
|
|
5429
|
-
const spokeTemplate =
|
|
5430
|
-
if (
|
|
6303
|
+
const spokeTemplate = path19.join(pkgRoot, "obsidian", "spoke");
|
|
6304
|
+
if (fs17.existsSync(spokeVault)) {
|
|
5431
6305
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
5432
|
-
} else if (
|
|
6306
|
+
} else if (fs17.existsSync(spokeTemplate)) {
|
|
5433
6307
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
5434
|
-
const statusPath =
|
|
5435
|
-
if (
|
|
5436
|
-
const content =
|
|
6308
|
+
const statusPath = path19.join(spokeVault, "status.md");
|
|
6309
|
+
if (fs17.existsSync(statusPath)) {
|
|
6310
|
+
const content = fs17.readFileSync(statusPath, "utf-8");
|
|
5437
6311
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
5438
|
-
|
|
6312
|
+
fs17.writeFileSync(statusPath, updated);
|
|
5439
6313
|
}
|
|
5440
6314
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
5441
6315
|
} else {
|
|
5442
|
-
|
|
6316
|
+
fs17.mkdirSync(spokeVault, { recursive: true });
|
|
5443
6317
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
5444
6318
|
}
|
|
5445
6319
|
console.log("");
|
|
@@ -5540,10 +6414,10 @@ init_dist4();
|
|
|
5540
6414
|
init_dist();
|
|
5541
6415
|
import { Command as Command5 } from "commander";
|
|
5542
6416
|
import { execSync as execSync9 } from "child_process";
|
|
5543
|
-
import
|
|
5544
|
-
import
|
|
6417
|
+
import path20 from "path";
|
|
6418
|
+
import os10 from "os";
|
|
5545
6419
|
import chalk7 from "chalk";
|
|
5546
|
-
var
|
|
6420
|
+
var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
|
|
5547
6421
|
var TASK_PROMPTS = {
|
|
5548
6422
|
briefing: "Run your daily briefing using the squadrant:command-ops skill. Read all spoke handoffs, yesterday's logs, current status; produce a concise cross-project briefing; save to {hubVault}/daily-logs/YYYY-MM-DD.md; then exit.",
|
|
5549
6423
|
"learnings-review": "Run a learnings review using the squadrant:command-ops skill. Scan {spokeVault}/learnings across all projects, identify cross-project patterns, propose captured-skill or fix actions, and exit when done.",
|
|
@@ -5576,7 +6450,7 @@ async function runCommandSpawn(input) {
|
|
|
5576
6450
|
if (!agent) {
|
|
5577
6451
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
5578
6452
|
}
|
|
5579
|
-
const promptFile =
|
|
6453
|
+
const promptFile = path20.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
|
|
5580
6454
|
const cliCommand = agent.buildCommand({
|
|
5581
6455
|
prompt,
|
|
5582
6456
|
workdir: process.cwd(),
|
|
@@ -5601,35 +6475,11 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
|
|
|
5601
6475
|
|
|
5602
6476
|
// packages/cli/src/commands/crew.ts
|
|
5603
6477
|
init_dist();
|
|
5604
|
-
init_dist();
|
|
5605
|
-
import { Command as Command9 } from "commander";
|
|
5606
|
-
import fs16 from "fs";
|
|
5607
|
-
import path20 from "path";
|
|
5608
|
-
import os10 from "os";
|
|
5609
|
-
import chalk9 from "chalk";
|
|
5610
|
-
|
|
5611
|
-
// packages/cli/src/control/crew-routing.ts
|
|
5612
|
-
function resolveCrewRoute(taskText, config) {
|
|
5613
|
-
const rules = config.defaults.crewRouting?.rules;
|
|
5614
|
-
if (!rules || rules.length === 0) return null;
|
|
5615
|
-
for (const rule2 of rules) {
|
|
5616
|
-
const re = new RegExp(rule2.match, "i");
|
|
5617
|
-
if (re.test(taskText)) {
|
|
5618
|
-
return {
|
|
5619
|
-
agent: rule2.agent,
|
|
5620
|
-
...rule2.model !== void 0 ? { model: rule2.model } : {},
|
|
5621
|
-
tier: rule2.tier,
|
|
5622
|
-
matchedRule: rule2.match
|
|
5623
|
-
};
|
|
5624
|
-
}
|
|
5625
|
-
}
|
|
5626
|
-
return null;
|
|
5627
|
-
}
|
|
5628
|
-
|
|
5629
|
-
// packages/cli/src/commands/crew.ts
|
|
5630
|
-
init_dist3();
|
|
5631
6478
|
init_dist3();
|
|
5632
6479
|
init_dist4();
|
|
6480
|
+
init_dist2();
|
|
6481
|
+
import { Command as Command9 } from "commander";
|
|
6482
|
+
import chalk9 from "chalk";
|
|
5633
6483
|
|
|
5634
6484
|
// packages/cli/src/commands/crew-control.ts
|
|
5635
6485
|
init_dist2();
|
|
@@ -5637,9 +6487,9 @@ init_dist2();
|
|
|
5637
6487
|
init_dist4();
|
|
5638
6488
|
import { Command as Command8 } from "commander";
|
|
5639
6489
|
import { createConnection as createConnection3 } from "net";
|
|
5640
|
-
import { randomUUID as
|
|
5641
|
-
import { homedir as
|
|
5642
|
-
import { join as
|
|
6490
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6491
|
+
import { homedir as homedir12 } from "os";
|
|
6492
|
+
import { join as join17 } from "path";
|
|
5643
6493
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5644
6494
|
|
|
5645
6495
|
// packages/cli/src/commands/crew-output.ts
|
|
@@ -5698,11 +6548,11 @@ init_dist2();
|
|
|
5698
6548
|
import { Command as Command6 } from "commander";
|
|
5699
6549
|
import chalk8 from "chalk";
|
|
5700
6550
|
import { createConnection as createConnection2 } from "net";
|
|
5701
|
-
import { homedir as
|
|
5702
|
-
import { join as
|
|
6551
|
+
import { homedir as homedir11 } from "os";
|
|
6552
|
+
import { join as join16 } from "path";
|
|
5703
6553
|
import { createInterface } from "readline";
|
|
5704
6554
|
function socketPath() {
|
|
5705
|
-
return process.env.SQUADRANTD_SOCK ??
|
|
6555
|
+
return process.env.SQUADRANTD_SOCK ?? join16(homedir11(), ".config", "squadrant", "squadrant.sock");
|
|
5706
6556
|
}
|
|
5707
6557
|
function rule(width, ch = "\u2500") {
|
|
5708
6558
|
return ch.repeat(Math.max(0, width));
|
|
@@ -5927,7 +6777,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
5927
6777
|
if (opts.provider !== "codex") {
|
|
5928
6778
|
throw new Error(`crew chat is implemented for provider=codex only (got '${opts.provider}')`);
|
|
5929
6779
|
}
|
|
5930
|
-
const pane = await
|
|
6780
|
+
const pane = await runCrewSpawn2({
|
|
5931
6781
|
project: opts.project,
|
|
5932
6782
|
task: "(interactive)",
|
|
5933
6783
|
agent: "codex",
|
|
@@ -5938,7 +6788,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
5938
6788
|
});
|
|
5939
6789
|
|
|
5940
6790
|
// packages/cli/src/commands/crew-control.ts
|
|
5941
|
-
var SOCK2 =
|
|
6791
|
+
var SOCK2 = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
|
|
5942
6792
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
5943
6793
|
async function sendCodexFirstTurn(taskId, text) {
|
|
5944
6794
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -5965,11 +6815,11 @@ async function sendCodexFirstTurn(taskId, text) {
|
|
|
5965
6815
|
}
|
|
5966
6816
|
function buildDispatchRequest(o) {
|
|
5967
6817
|
const now = Date.now();
|
|
5968
|
-
const attemptId =
|
|
6818
|
+
const attemptId = randomUUID4();
|
|
5969
6819
|
return {
|
|
5970
6820
|
kind: "dispatch",
|
|
5971
6821
|
record: {
|
|
5972
|
-
id:
|
|
6822
|
+
id: randomUUID4(),
|
|
5973
6823
|
project: o.project,
|
|
5974
6824
|
provider: o.provider,
|
|
5975
6825
|
mode: o.mode,
|
|
@@ -6043,9 +6893,9 @@ function buildSignalRequest(signal, o) {
|
|
|
6043
6893
|
return { kind: "event", project, event };
|
|
6044
6894
|
}
|
|
6045
6895
|
function defaultWriteResult(id, payload) {
|
|
6046
|
-
const dir =
|
|
6896
|
+
const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
|
|
6047
6897
|
mkdirSync6(dir, { recursive: true });
|
|
6048
|
-
const file =
|
|
6898
|
+
const file = join17(dir, `${id}.txt`);
|
|
6049
6899
|
writeFileSync7(file, payload);
|
|
6050
6900
|
return file;
|
|
6051
6901
|
}
|
|
@@ -6146,7 +6996,7 @@ addControlPlaneCrewCommands(crewControlCommand);
|
|
|
6146
6996
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
6147
6997
|
init_dist4();
|
|
6148
6998
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
6149
|
-
import { join as
|
|
6999
|
+
import { join as join18 } from "path";
|
|
6150
7000
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
6151
7001
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
6152
7002
|
"Bash(git status:*)",
|
|
@@ -6226,6 +7076,9 @@ var CREW_PERMISSION_ALLOWLIST = [
|
|
|
6226
7076
|
"Bash(sleep:*)",
|
|
6227
7077
|
"Bash(printf:*)"
|
|
6228
7078
|
];
|
|
7079
|
+
function healStaleCockpitRefs(raw) {
|
|
7080
|
+
return raw.replaceAll("cockpit crew _hook", "squadrant crew _hook").replaceAll(".config/cockpit", ".config/squadrant").replaceAll("cockpit-hub", "squadrant-hub");
|
|
7081
|
+
}
|
|
6229
7082
|
function mergeCrewPermissions(settings) {
|
|
6230
7083
|
const next = structuredClone(settings ?? {});
|
|
6231
7084
|
const permissions = next.permissions ??= {};
|
|
@@ -6234,12 +7087,12 @@ function mergeCrewPermissions(settings) {
|
|
|
6234
7087
|
return next;
|
|
6235
7088
|
}
|
|
6236
7089
|
function writePerCrewSettingsLocal(o) {
|
|
6237
|
-
const dir =
|
|
7090
|
+
const dir = join18(o.projectCwd, ".claude");
|
|
6238
7091
|
mkdirSync7(dir, { recursive: true });
|
|
6239
|
-
const file =
|
|
7092
|
+
const file = join18(dir, "settings.local.json");
|
|
6240
7093
|
let existing = {};
|
|
6241
7094
|
try {
|
|
6242
|
-
const raw = readFileSync9(file, "utf-8");
|
|
7095
|
+
const raw = healStaleCockpitRefs(readFileSync9(file, "utf-8"));
|
|
6243
7096
|
existing = JSON.parse(raw);
|
|
6244
7097
|
} catch {
|
|
6245
7098
|
}
|
|
@@ -6249,304 +7102,86 @@ function writePerCrewSettingsLocal(o) {
|
|
|
6249
7102
|
return file;
|
|
6250
7103
|
}
|
|
6251
7104
|
function writePerCrewOpencodeConfig(o) {
|
|
6252
|
-
const dir =
|
|
7105
|
+
const dir = join18(o.stateRoot, o.project, o.taskId);
|
|
6253
7106
|
mkdirSync7(dir, { recursive: true });
|
|
6254
|
-
const file =
|
|
7107
|
+
const file = join18(dir, "opencode.json");
|
|
6255
7108
|
const config = {
|
|
6256
7109
|
permission: {
|
|
6257
7110
|
read: "allow",
|
|
6258
7111
|
edit: "allow",
|
|
6259
|
-
glob: "allow",
|
|
6260
|
-
grep: "allow",
|
|
6261
|
-
bash: o.gateBash ? "ask" : "allow",
|
|
6262
|
-
webfetch: "allow",
|
|
6263
|
-
websearch: "allow",
|
|
6264
|
-
task: "allow",
|
|
6265
|
-
lsp: "allow",
|
|
6266
|
-
external_directory: { "**": "allow" }
|
|
6267
|
-
}
|
|
6268
|
-
};
|
|
6269
|
-
writeFileSync8(file, JSON.stringify(config, null, 2));
|
|
6270
|
-
return file;
|
|
6271
|
-
}
|
|
6272
|
-
|
|
6273
|
-
// packages/cli/src/commands/crew.ts
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
const config = loadConfig();
|
|
6278
|
-
const proj = config.projects[input.project];
|
|
6279
|
-
if (!proj) {
|
|
6280
|
-
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
6281
|
-
}
|
|
6282
|
-
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(input.project, config);
|
|
6283
|
-
const captain = await runtime.status(proj.captainName);
|
|
6284
|
-
if (!captain) {
|
|
6285
|
-
throw new Error(
|
|
6286
|
-
`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`
|
|
6287
|
-
);
|
|
6288
|
-
}
|
|
6289
|
-
const existing = await listProjectCrews(runtime, captain.id, input.project);
|
|
6290
|
-
const existingTitles = existing.map((s) => s.title);
|
|
6291
|
-
if (input.name) {
|
|
6292
|
-
const wantTitle = titleFor(input.project, input.name);
|
|
6293
|
-
if (existingTitles.includes(wantTitle)) {
|
|
6294
|
-
throw new Error(
|
|
6295
|
-
`Crew '${input.name}' already exists for ${input.project}. Use 'squadrant crew send ${input.project} ${input.name}' to send a follow-up, or pick a different --name.`
|
|
6296
|
-
);
|
|
6297
|
-
}
|
|
6298
|
-
}
|
|
6299
|
-
const name = input.name ?? nextAutoName(existingTitles, input.project);
|
|
6300
|
-
const spawnCwd = !input.shared ? addWorktree({
|
|
6301
|
-
repoRoot: proj.path,
|
|
6302
|
-
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6303
|
-
project: input.project,
|
|
6304
|
-
name,
|
|
6305
|
-
base: resolveWorktreeBase(proj.path)
|
|
6306
|
-
}) : proj.path;
|
|
6307
|
-
const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
|
|
6308
|
-
if (route) {
|
|
6309
|
-
console.log(chalk9.dim(`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`));
|
|
6310
|
-
}
|
|
7112
|
+
glob: "allow",
|
|
7113
|
+
grep: "allow",
|
|
7114
|
+
bash: o.gateBash ? "ask" : "allow",
|
|
7115
|
+
webfetch: "allow",
|
|
7116
|
+
websearch: "allow",
|
|
7117
|
+
task: "allow",
|
|
7118
|
+
lsp: "allow",
|
|
7119
|
+
external_directory: { "**": "allow" }
|
|
7120
|
+
}
|
|
7121
|
+
};
|
|
7122
|
+
writeFileSync8(file, JSON.stringify(config, null, 2));
|
|
7123
|
+
return file;
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
// packages/cli/src/commands/crew.ts
|
|
7127
|
+
async function runCrewSpawn2(input) {
|
|
7128
|
+
const config = loadConfig();
|
|
7129
|
+
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(input.project, config);
|
|
6311
7130
|
const agents = new CapabilityRegistry({
|
|
6312
7131
|
claude: createClaudeDriver(),
|
|
6313
7132
|
codex: createCodexDriver(),
|
|
6314
7133
|
gemini: createGeminiDriver(),
|
|
6315
7134
|
opencode: createOpencodeDriver()
|
|
6316
7135
|
});
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
}
|
|
6337
|
-
const promptFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
|
|
6338
|
-
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
6339
|
-
const crewRole = config.defaults.roles?.crew;
|
|
6340
|
-
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
6341
|
-
const crewModel = input.model ?? route?.model ?? configModel;
|
|
6342
|
-
if (agentName === "claude") {
|
|
6343
|
-
const req = buildDispatchRequest({
|
|
6344
|
-
provider: "claude",
|
|
6345
|
-
mode: "interactive",
|
|
6346
|
-
project: input.project,
|
|
6347
|
-
cwd: spawnCwd,
|
|
6348
|
-
task: input.task,
|
|
6349
|
-
name
|
|
6350
|
-
});
|
|
6351
|
-
const rec = await squadrantdCall(req);
|
|
6352
|
-
writePerCrewSettingsLocal({ projectCwd: spawnCwd });
|
|
6353
|
-
const cliCommand2 = agent.buildCommand({
|
|
6354
|
-
prompt: input.task,
|
|
6355
|
-
workdir: spawnCwd,
|
|
6356
|
-
role: "crew",
|
|
6357
|
-
promptFile,
|
|
6358
|
-
interactive: true,
|
|
6359
|
-
// Permission mode is config-driven (defaults.permissions.crew) so squadrant
|
|
6360
|
-
// can default crews to 'auto' or keep the semi-automatic 'acceptEdits'
|
|
6361
|
-
// gate (auto-accept edits, still prompt for risky ops). Falls back to
|
|
6362
|
-
// 'acceptEdits' when unset.
|
|
6363
|
-
permissionMode: config.defaults.permissions?.crew ?? "acceptEdits",
|
|
6364
|
-
...crewModel ? { model: crewModel } : {}
|
|
6365
|
-
});
|
|
6366
|
-
const direction2 = input.direction ?? "tab";
|
|
6367
|
-
const title2 = titleFor(input.project, name);
|
|
6368
|
-
const pane2 = await runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
6369
|
-
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
6370
|
-
await runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${cliCommand2}`);
|
|
6371
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane2) ?? "";
|
|
6372
|
-
await sendFirstTurnWhenReady(runtime, pane2, `${input.task}
|
|
6373
|
-
|
|
6374
|
-
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
6375
|
-
return { ...pane2, title: title2 };
|
|
6376
|
-
}
|
|
6377
|
-
if (agentName === "opencode") {
|
|
6378
|
-
const serverPort = await getFreePort();
|
|
6379
|
-
const req = buildDispatchRequest({
|
|
6380
|
-
provider: "opencode",
|
|
6381
|
-
mode: "interactive",
|
|
6382
|
-
project: input.project,
|
|
6383
|
-
cwd: spawnCwd,
|
|
6384
|
-
task: input.task,
|
|
6385
|
-
name,
|
|
6386
|
-
// opencode has no heartbeat hook, so a normal budget would false-stall
|
|
6387
|
-
// every crew after 5min; use a 24h budget to effectively disable stall
|
|
6388
|
-
// detection. The SSE bridge (serverPort) provides turn-end liveness.
|
|
6389
|
-
budgetMs: 864e5,
|
|
6390
|
-
serverPort
|
|
6391
|
-
});
|
|
6392
|
-
const rec = await squadrantdCall(req);
|
|
6393
|
-
const opencodeConfigPath = writePerCrewOpencodeConfig({
|
|
6394
|
-
stateRoot: path20.join(os10.homedir(), ".config", "squadrant", "state"),
|
|
6395
|
-
project: input.project,
|
|
6396
|
-
taskId: rec.id,
|
|
6397
|
-
// CP3 opt-in: --approval gates bash so the captain approves shell commands.
|
|
6398
|
-
// Without it, bash stays auto-approved (default behavior unchanged).
|
|
6399
|
-
...input.approval ? { gateBash: true } : {}
|
|
6400
|
-
});
|
|
6401
|
-
const cliCommand2 = agent.buildCommand({
|
|
6402
|
-
prompt: input.task,
|
|
6403
|
-
workdir: spawnCwd,
|
|
6404
|
-
role: "crew",
|
|
6405
|
-
promptFile,
|
|
6406
|
-
interactive: true,
|
|
6407
|
-
model: crewModel,
|
|
6408
|
-
port: serverPort
|
|
6409
|
-
});
|
|
6410
|
-
const direction2 = input.direction ?? "tab";
|
|
6411
|
-
const title2 = titleFor(input.project, name);
|
|
6412
|
-
const pane2 = await runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
6413
|
-
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
6414
|
-
await runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${cliCommand2}`);
|
|
6415
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane2) ?? "";
|
|
6416
|
-
await sendFirstTurnWhenReady(runtime, pane2, `${input.task}
|
|
6417
|
-
|
|
6418
|
-
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
6419
|
-
// #235: opencode's idle splash ("Ask anything…") keeps mutating (cursor
|
|
6420
|
-
// blink, status line toggle), so the old screen-changed check would always
|
|
6421
|
-
// see a *different* screen and never re-send a dropped turn. The splashMarker
|
|
6422
|
-
// confirms the TUI actually left splash before declaring acceptance.
|
|
6423
|
-
splashMarker: "Ask anything\u2026",
|
|
6424
|
-
// opencode has a wider boot-race window than claude, so allow 3 retries
|
|
6425
|
-
// instead of the default 2.
|
|
6426
|
-
retryLimit: 3
|
|
6427
|
-
});
|
|
6428
|
-
return { ...pane2, title: title2 };
|
|
6429
|
-
}
|
|
6430
|
-
const cliCommand = agent.buildCommand({
|
|
6431
|
-
prompt: input.task,
|
|
6432
|
-
workdir: spawnCwd,
|
|
6433
|
-
role: "crew",
|
|
6434
|
-
promptFile,
|
|
6435
|
-
interactive,
|
|
6436
|
-
model: crewModel
|
|
6437
|
-
});
|
|
6438
|
-
const direction = input.direction ?? "tab";
|
|
6439
|
-
const title = titleFor(input.project, name);
|
|
6440
|
-
const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
6441
|
-
await runtime.sendToPane(pane, cliCommand);
|
|
6442
|
-
if (interactive) {
|
|
6443
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
6444
|
-
await sendFirstTurnWhenReady(runtime, pane, input.task, preLaunchScreen);
|
|
6445
|
-
}
|
|
6446
|
-
return { ...pane, title };
|
|
6447
|
-
}
|
|
6448
|
-
async function runCodexInteractiveSpawn(o) {
|
|
6449
|
-
const req = buildDispatchRequest({
|
|
6450
|
-
provider: "codex",
|
|
6451
|
-
mode: "interactive",
|
|
6452
|
-
project: o.project,
|
|
6453
|
-
cwd: o.cwd,
|
|
6454
|
-
task: o.task,
|
|
6455
|
-
name: o.name,
|
|
6456
|
-
...o.approvalPolicy ? { approvalPolicy: o.approvalPolicy } : {},
|
|
6457
|
-
...o.roleInstructions ? { roleInstructions: o.roleInstructions } : {}
|
|
6458
|
-
});
|
|
6459
|
-
const rec = await squadrantdCall(req);
|
|
6460
|
-
const title = titleFor(o.project, o.name);
|
|
6461
|
-
const pane = await o.runtime.newPane({
|
|
6462
|
-
workspaceId: o.workspaceId,
|
|
6463
|
-
direction: o.direction,
|
|
6464
|
-
title
|
|
7136
|
+
return runCrewSpawn(input, config, {
|
|
7137
|
+
runtime,
|
|
7138
|
+
// AgentDriver satisfies ResolvedAgent structurally; `role: any` in ResolvedAgent
|
|
7139
|
+
// bridges the Role vs string gap — only "crew" is ever passed at call sites.
|
|
7140
|
+
resolveAgent: (name) => agents.get(name) ?? null,
|
|
7141
|
+
dispatchCrew: async (o) => {
|
|
7142
|
+
const req = buildDispatchRequest(o);
|
|
7143
|
+
return await squadrantdCall(req);
|
|
7144
|
+
},
|
|
7145
|
+
writeSettingsLocal: (cwd) => writePerCrewSettingsLocal({ projectCwd: cwd }),
|
|
7146
|
+
writeOpencodeConfig: writePerCrewOpencodeConfig,
|
|
7147
|
+
sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
|
|
7148
|
+
getFreePort,
|
|
7149
|
+
sendCodexFirstTurn,
|
|
7150
|
+
onRouted: (route) => console.log(
|
|
7151
|
+
chalk9.dim(
|
|
7152
|
+
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
7153
|
+
)
|
|
7154
|
+
)
|
|
6465
7155
|
});
|
|
6466
|
-
await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);
|
|
6467
|
-
if (o.task && o.task !== "(interactive)") {
|
|
6468
|
-
void sendCodexFirstTurn(rec.id, o.task).catch((e) => {
|
|
6469
|
-
process.stderr.write(`(first-turn delivery failed: ${e.message})
|
|
6470
|
-
`);
|
|
6471
|
-
});
|
|
6472
|
-
}
|
|
6473
|
-
return { ...pane, title };
|
|
6474
7156
|
}
|
|
6475
|
-
async function
|
|
7157
|
+
async function runCrewSend2(project, name, message) {
|
|
6476
7158
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
try {
|
|
6482
|
-
const tasks = await squadrantdCall({ kind: "list", project });
|
|
6483
|
-
const task = tasks.find((t) => t.name === name);
|
|
6484
|
-
if (task) {
|
|
6485
|
-
if (TERMINAL_STATES.has(task.state)) {
|
|
6486
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.reopened", id: task.id } });
|
|
6487
|
-
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
6488
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.started", id: task.id } });
|
|
6489
|
-
}
|
|
7159
|
+
return runCrewSend(project, name, message, runtime, workspaceId, {
|
|
7160
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
7161
|
+
emitEvent: async (p, event) => {
|
|
7162
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
6490
7163
|
}
|
|
6491
|
-
}
|
|
6492
|
-
}
|
|
6493
|
-
await runtime.sendToPane(crew, message);
|
|
7164
|
+
});
|
|
6494
7165
|
}
|
|
6495
|
-
async function
|
|
7166
|
+
async function runCrewRead2(project, name) {
|
|
6496
7167
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6497
|
-
|
|
6498
|
-
if (!crew) {
|
|
6499
|
-
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6500
|
-
}
|
|
6501
|
-
return runtime.readPaneScreen(crew);
|
|
7168
|
+
return runCrewRead(project, name, runtime, workspaceId);
|
|
6502
7169
|
}
|
|
6503
|
-
async function
|
|
7170
|
+
async function runCrewClose2(project, name) {
|
|
6504
7171
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
taskId = task.id;
|
|
6513
|
-
if (task.cwd && projRoot && task.cwd !== projRoot) {
|
|
6514
|
-
worktreeCwd = task.cwd;
|
|
6515
|
-
}
|
|
6516
|
-
if (!TERMINAL_STATES.has(task.state)) {
|
|
6517
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.cancelled", id: task.id, reason: "closed by captain" } });
|
|
6518
|
-
}
|
|
6519
|
-
if (task.provider === "codex") {
|
|
6520
|
-
await squadrantdCall({ kind: "codex-close", taskId: task.id });
|
|
6521
|
-
}
|
|
6522
|
-
}
|
|
6523
|
-
} catch {
|
|
6524
|
-
}
|
|
6525
|
-
const crew = await findCrew(runtime, workspaceId, project, name);
|
|
6526
|
-
if (crew) {
|
|
6527
|
-
await runtime.closePane(crew);
|
|
6528
|
-
} else if (taskId === void 0) {
|
|
6529
|
-
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6530
|
-
}
|
|
6531
|
-
if (taskId !== void 0) {
|
|
6532
|
-
await reapCrewChildren(taskId);
|
|
6533
|
-
}
|
|
6534
|
-
if (worktreeCwd && projRoot) {
|
|
6535
|
-
try {
|
|
6536
|
-
removeWorktree(projRoot, worktreeCwd);
|
|
6537
|
-
} catch (e) {
|
|
6538
|
-
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6539
|
-
`);
|
|
7172
|
+
return runCrewClose(project, name, runtime, workspaceId, {
|
|
7173
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
7174
|
+
emitEvent: async (p, event) => {
|
|
7175
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
7176
|
+
},
|
|
7177
|
+
closeCodexThread: async (taskId) => {
|
|
7178
|
+
await squadrantdCall({ kind: "codex-close", taskId });
|
|
6540
7179
|
}
|
|
6541
|
-
}
|
|
7180
|
+
});
|
|
6542
7181
|
}
|
|
6543
|
-
async function
|
|
7182
|
+
async function runCrewList2(project) {
|
|
6544
7183
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6545
|
-
|
|
6546
|
-
return crews.map((c) => ({
|
|
6547
|
-
name: nameFromTitle(project, c.title),
|
|
6548
|
-
surfaceId: c.surfaceId
|
|
6549
|
-
}));
|
|
7184
|
+
return runCrewList(project, runtime, workspaceId);
|
|
6550
7185
|
}
|
|
6551
7186
|
var crewCommand = new Command9("crew").description(
|
|
6552
7187
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
@@ -6558,7 +7193,7 @@ crewCommand.command("spawn").description(
|
|
|
6558
7193
|
try {
|
|
6559
7194
|
const resolvedTask = await resolveTextInput({ positional: task, filePath: opts.taskFile, label: "task" });
|
|
6560
7195
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
6561
|
-
const pane = await
|
|
7196
|
+
const pane = await runCrewSpawn2({
|
|
6562
7197
|
project,
|
|
6563
7198
|
task: resolvedTask,
|
|
6564
7199
|
name: opts.name,
|
|
@@ -6580,7 +7215,7 @@ crewCommand.command("spawn").description(
|
|
|
6580
7215
|
);
|
|
6581
7216
|
crewCommand.command("list").description("List live crew sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6582
7217
|
try {
|
|
6583
|
-
const crews = await
|
|
7218
|
+
const crews = await runCrewList2(project);
|
|
6584
7219
|
if (crews.length === 0) {
|
|
6585
7220
|
console.log(chalk9.yellow(`No live crew sessions for ${project}.`));
|
|
6586
7221
|
return;
|
|
@@ -6596,7 +7231,7 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
6596
7231
|
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)").action(async (project, name, message, opts) => {
|
|
6597
7232
|
try {
|
|
6598
7233
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
6599
|
-
await
|
|
7234
|
+
await runCrewSend2(project, name, resolvedMessage);
|
|
6600
7235
|
console.log(chalk9.green(`\u2714 Sent to ${project}:${name}`));
|
|
6601
7236
|
} catch (err) {
|
|
6602
7237
|
console.error(chalk9.red(err.message));
|
|
@@ -6605,7 +7240,7 @@ crewCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6605
7240
|
});
|
|
6606
7241
|
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) => {
|
|
6607
7242
|
try {
|
|
6608
|
-
const screen = await
|
|
7243
|
+
const screen = await runCrewRead2(project, name);
|
|
6609
7244
|
const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
|
|
6610
7245
|
console.log(out);
|
|
6611
7246
|
} catch (err) {
|
|
@@ -6615,7 +7250,7 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
|
|
|
6615
7250
|
});
|
|
6616
7251
|
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
|
|
6617
7252
|
try {
|
|
6618
|
-
await
|
|
7253
|
+
await runCrewClose2(project, name);
|
|
6619
7254
|
console.log(chalk9.green(`\u2714 Closed ${project}:${name}`));
|
|
6620
7255
|
} catch (err) {
|
|
6621
7256
|
console.error(chalk9.red(err.message));
|
|
@@ -6629,90 +7264,23 @@ init_dist3();
|
|
|
6629
7264
|
init_dist4();
|
|
6630
7265
|
init_dist3();
|
|
6631
7266
|
init_dist();
|
|
6632
|
-
|
|
7267
|
+
init_dist2();
|
|
6633
7268
|
import { Command as Command10 } from "commander";
|
|
6634
|
-
import
|
|
7269
|
+
import fs18 from "fs";
|
|
6635
7270
|
import path21 from "path";
|
|
6636
7271
|
import os11 from "os";
|
|
6637
7272
|
import chalk10 from "chalk";
|
|
6638
7273
|
var TEMPLATES_DIR3 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
|
|
6639
|
-
|
|
6640
|
-
function shellQuote2(p) {
|
|
6641
|
-
return "'" + p.replace(/'/g, "'\\''") + "'";
|
|
6642
|
-
}
|
|
6643
|
-
function titleFor2(project, name) {
|
|
6644
|
-
return `\u{1F5D2} ${project}:${name}`;
|
|
6645
|
-
}
|
|
6646
|
-
function isSideTitle(project, title) {
|
|
6647
|
-
return title.startsWith(`\u{1F5D2} ${project}:`);
|
|
6648
|
-
}
|
|
6649
|
-
function nameFromTitle2(project, title) {
|
|
6650
|
-
return title.slice(`\u{1F5D2} ${project}:`.length);
|
|
6651
|
-
}
|
|
6652
|
-
function nextAutoName2(existingTitles, project) {
|
|
6653
|
-
const used = /* @__PURE__ */ new Set();
|
|
6654
|
-
for (const title of existingTitles) {
|
|
6655
|
-
const n = nameFromTitle2(project, title).match(/^side-(\d+)$/);
|
|
6656
|
-
if (n) used.add(Number(n[1]));
|
|
6657
|
-
}
|
|
6658
|
-
let i = 1;
|
|
6659
|
-
while (used.has(i)) i++;
|
|
6660
|
-
return `side-${i}`;
|
|
6661
|
-
}
|
|
6662
|
-
function buildSideFirstTurn(topic, project, role, spokeVault, scratchWorktree) {
|
|
6663
|
-
const lines = [
|
|
6664
|
-
topic,
|
|
6665
|
-
"",
|
|
6666
|
-
"---",
|
|
6667
|
-
"Side-session context (for handoff use):",
|
|
6668
|
-
`Project: ${project}`,
|
|
6669
|
-
`Role: ${role}`,
|
|
6670
|
-
`Spoke vault: ${spokeVault}`
|
|
6671
|
-
];
|
|
6672
|
-
if (scratchWorktree) {
|
|
6673
|
-
lines.push(`Scratch worktree: ${scratchWorktree}`);
|
|
6674
|
-
}
|
|
6675
|
-
return lines.join("\n");
|
|
6676
|
-
}
|
|
6677
|
-
async function runSideSpawn(input) {
|
|
7274
|
+
async function runSideSpawn2(input) {
|
|
6678
7275
|
const config = loadConfig();
|
|
6679
7276
|
const proj = config.projects[input.project];
|
|
6680
7277
|
if (!proj) {
|
|
6681
7278
|
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
6682
7279
|
}
|
|
6683
|
-
if (!SIDE_ROLES.includes(input.role)) {
|
|
6684
|
-
throw new Error(
|
|
6685
|
-
`Unknown side role '${input.role}'. Valid roles: ${SIDE_ROLES.join(", ")}.`
|
|
6686
|
-
);
|
|
6687
|
-
}
|
|
6688
7280
|
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(
|
|
6689
7281
|
input.project,
|
|
6690
7282
|
config
|
|
6691
7283
|
);
|
|
6692
|
-
const captain = await runtime.status(proj.captainName);
|
|
6693
|
-
if (!captain) {
|
|
6694
|
-
throw new Error(
|
|
6695
|
-
`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`
|
|
6696
|
-
);
|
|
6697
|
-
}
|
|
6698
|
-
const existing = await runtime.listSurfaces(captain.id);
|
|
6699
|
-
const existingTitles = existing.filter((s) => s.title && isSideTitle(input.project, s.title)).map((s) => s.title);
|
|
6700
|
-
if (input.name) {
|
|
6701
|
-
const wantTitle = titleFor2(input.project, input.name);
|
|
6702
|
-
if (existingTitles.includes(wantTitle)) {
|
|
6703
|
-
throw new Error(
|
|
6704
|
-
`Side session '${input.name}' already exists for ${input.project}.`
|
|
6705
|
-
);
|
|
6706
|
-
}
|
|
6707
|
-
}
|
|
6708
|
-
const name = input.name ?? nextAutoName2(existingTitles, input.project);
|
|
6709
|
-
const spawnCwd = input.role === "debug" ? addWorktree({
|
|
6710
|
-
repoRoot: proj.path,
|
|
6711
|
-
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6712
|
-
project: input.project,
|
|
6713
|
-
name,
|
|
6714
|
-
base: resolveWorktreeBase(proj.path)
|
|
6715
|
-
}) : proj.path;
|
|
6716
7284
|
const agents = new CapabilityRegistry({
|
|
6717
7285
|
claude: createClaudeDriver(),
|
|
6718
7286
|
codex: createCodexDriver(),
|
|
@@ -6729,80 +7297,39 @@ async function runSideSpawn(input) {
|
|
|
6729
7297
|
const promptFile = path21.join(
|
|
6730
7298
|
TEMPLATES_DIR3,
|
|
6731
7299
|
`side.${input.role}.${agent.templateSuffix}.md`
|
|
6732
|
-
);
|
|
6733
|
-
const
|
|
6734
|
-
const title = titleFor2(input.project, name);
|
|
6735
|
-
const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
6736
|
-
const cliCommand = agent.buildCommand({
|
|
7300
|
+
);
|
|
7301
|
+
const agentCmdFactory = (spawnCwd) => agent.buildCommand({
|
|
6737
7302
|
prompt: input.topic,
|
|
6738
7303
|
workdir: spawnCwd,
|
|
6739
7304
|
role: "side",
|
|
6740
|
-
promptFile:
|
|
7305
|
+
promptFile: fs18.existsSync(promptFile) ? promptFile : void 0,
|
|
6741
7306
|
interactive: true,
|
|
6742
7307
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
6743
7308
|
...sideModel ? { model: sideModel } : {}
|
|
6744
7309
|
});
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
const firstTurn = buildSideFirstTurn(
|
|
6748
|
-
input.topic,
|
|
6749
|
-
input.project,
|
|
6750
|
-
input.role,
|
|
6751
|
-
proj.spokeVault ?? "",
|
|
6752
|
-
input.role === "debug" ? spawnCwd : void 0
|
|
6753
|
-
);
|
|
6754
|
-
await sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen);
|
|
6755
|
-
return { ...pane, title };
|
|
7310
|
+
const sendFirstTurn = (pane, firstTurn, preLaunchScreen) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen);
|
|
7311
|
+
return runSideSpawn(input, config, { runtime, agentCmdFactory, sendFirstTurn });
|
|
6756
7312
|
}
|
|
6757
|
-
async function
|
|
7313
|
+
async function runSideSend2(project, name, message) {
|
|
6758
7314
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6759
|
-
|
|
6760
|
-
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
6761
|
-
const pane = surfaces.find((s) => s.title === want) ?? null;
|
|
6762
|
-
if (!pane) {
|
|
6763
|
-
throw new Error(
|
|
6764
|
-
`Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`
|
|
6765
|
-
);
|
|
6766
|
-
}
|
|
6767
|
-
await runtime.sendToPane(pane, message);
|
|
7315
|
+
await runSideSend(runtime, workspaceId, project, name, message);
|
|
6768
7316
|
}
|
|
6769
|
-
async function
|
|
7317
|
+
async function runSideList2(project) {
|
|
6770
7318
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6771
|
-
|
|
6772
|
-
return surfaces.filter((s) => s.title && isSideTitle(project, s.title)).map((s) => ({
|
|
6773
|
-
name: nameFromTitle2(project, s.title),
|
|
6774
|
-
surfaceId: s.surfaceId
|
|
6775
|
-
}));
|
|
7319
|
+
return runSideList(runtime, workspaceId, project);
|
|
6776
7320
|
}
|
|
6777
|
-
async function
|
|
7321
|
+
async function runSideClose2(project, name) {
|
|
6778
7322
|
const config = loadConfig();
|
|
6779
7323
|
const proj = config.projects[project];
|
|
6780
7324
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
await runtime.closePane(pane);
|
|
6790
|
-
if (proj) {
|
|
6791
|
-
const wtPath = worktreePath(
|
|
6792
|
-
proj.path,
|
|
6793
|
-
config.defaults.worktreeDir ?? ".worktrees",
|
|
6794
|
-
project,
|
|
6795
|
-
name
|
|
6796
|
-
);
|
|
6797
|
-
if (fs17.existsSync(wtPath)) {
|
|
6798
|
-
try {
|
|
6799
|
-
removeWorktree(proj.path, wtPath);
|
|
6800
|
-
} catch (e) {
|
|
6801
|
-
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6802
|
-
`);
|
|
6803
|
-
}
|
|
6804
|
-
}
|
|
6805
|
-
}
|
|
7325
|
+
await runSideClose(
|
|
7326
|
+
runtime,
|
|
7327
|
+
workspaceId,
|
|
7328
|
+
project,
|
|
7329
|
+
name,
|
|
7330
|
+
proj?.path,
|
|
7331
|
+
config.defaults.worktreeDir ?? ".worktrees"
|
|
7332
|
+
);
|
|
6806
7333
|
}
|
|
6807
7334
|
var sideCommand = new Command10("side").description(
|
|
6808
7335
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
@@ -6821,7 +7348,7 @@ sideCommand.command("spawn").description(
|
|
|
6821
7348
|
filePath: opts.topicFile,
|
|
6822
7349
|
label: "topic"
|
|
6823
7350
|
});
|
|
6824
|
-
const pane = await
|
|
7351
|
+
const pane = await runSideSpawn2({
|
|
6825
7352
|
project,
|
|
6826
7353
|
topic: resolvedTopic,
|
|
6827
7354
|
role: opts.role,
|
|
@@ -6838,7 +7365,7 @@ sideCommand.command("spawn").description(
|
|
|
6838
7365
|
);
|
|
6839
7366
|
sideCommand.command("list").description("List live side-sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6840
7367
|
try {
|
|
6841
|
-
const sessions = await
|
|
7368
|
+
const sessions = await runSideList2(project);
|
|
6842
7369
|
if (sessions.length === 0) {
|
|
6843
7370
|
console.log(chalk10.yellow(`No live side-sessions for ${project}.`));
|
|
6844
7371
|
return;
|
|
@@ -6859,7 +7386,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6859
7386
|
filePath: opts.messageFile,
|
|
6860
7387
|
label: "message"
|
|
6861
7388
|
});
|
|
6862
|
-
await
|
|
7389
|
+
await runSideSend2(project, name, resolvedMessage);
|
|
6863
7390
|
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
6864
7391
|
} catch (err) {
|
|
6865
7392
|
console.error(chalk10.red(err.message));
|
|
@@ -6869,7 +7396,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6869
7396
|
);
|
|
6870
7397
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
6871
7398
|
try {
|
|
6872
|
-
await
|
|
7399
|
+
await runSideClose2(project, name);
|
|
6873
7400
|
console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
|
|
6874
7401
|
} catch (err) {
|
|
6875
7402
|
console.error(chalk10.red(err.message));
|
|
@@ -6882,8 +7409,8 @@ init_dist();
|
|
|
6882
7409
|
init_dist3();
|
|
6883
7410
|
import { Command as Command11 } from "commander";
|
|
6884
7411
|
import { execSync as execSync10 } from "child_process";
|
|
6885
|
-
import { homedir as
|
|
6886
|
-
import { join as
|
|
7412
|
+
import { homedir as homedir14 } from "os";
|
|
7413
|
+
import { join as join20 } from "path";
|
|
6887
7414
|
import chalk12 from "chalk";
|
|
6888
7415
|
|
|
6889
7416
|
// packages/web/dist/read-status.js
|
|
@@ -7022,7 +7549,7 @@ function renderDashboard(rows, opts) {
|
|
|
7022
7549
|
|
|
7023
7550
|
// packages/web/dist/sync-hub.js
|
|
7024
7551
|
init_dist();
|
|
7025
|
-
import
|
|
7552
|
+
import fs19 from "fs";
|
|
7026
7553
|
import path22 from "path";
|
|
7027
7554
|
function buildMirrorMarkdown(s) {
|
|
7028
7555
|
const fenced = "```";
|
|
@@ -7049,8 +7576,8 @@ function buildMirrorMarkdown(s) {
|
|
|
7049
7576
|
function syncHub(deps) {
|
|
7050
7577
|
if (!deps.config.hubVault)
|
|
7051
7578
|
return [];
|
|
7052
|
-
const writeFile5 = deps.writeFile ?? ((p, c) =>
|
|
7053
|
-
const mkdir5 = deps.mkdir ?? ((p) =>
|
|
7579
|
+
const writeFile5 = deps.writeFile ?? ((p, c) => fs19.writeFileSync(p, c));
|
|
7580
|
+
const mkdir5 = deps.mkdir ?? ((p) => fs19.mkdirSync(p, { recursive: true }));
|
|
7054
7581
|
const projectsDir = path22.join(resolveHome(deps.config.hubVault), "projects");
|
|
7055
7582
|
mkdir5(projectsDir);
|
|
7056
7583
|
const out = [];
|
|
@@ -7079,10 +7606,10 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
7079
7606
|
// packages/web/dist/probes.js
|
|
7080
7607
|
init_dist();
|
|
7081
7608
|
init_dist();
|
|
7082
|
-
import { join as
|
|
7083
|
-
import { homedir as
|
|
7609
|
+
import { join as join19 } from "path";
|
|
7610
|
+
import { homedir as homedir13 } from "os";
|
|
7084
7611
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
7085
|
-
import { execFile as
|
|
7612
|
+
import { execFile as execFile3 } from "child_process";
|
|
7086
7613
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
7087
7614
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
7088
7615
|
function withTimeout2(p, ms) {
|
|
@@ -7117,7 +7644,7 @@ function vaultProbe(run, dir) {
|
|
|
7117
7644
|
return { state: "unknown", detail: "no vault configured" };
|
|
7118
7645
|
if (!run.pathExists(dir))
|
|
7119
7646
|
return { state: "gone", detail: "vault directory missing" };
|
|
7120
|
-
if (!run.pathExists(
|
|
7647
|
+
if (!run.pathExists(join19(dir, ".obsidian")))
|
|
7121
7648
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
7122
7649
|
return { state: "alive" };
|
|
7123
7650
|
} catch {
|
|
@@ -7185,10 +7712,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
7185
7712
|
const sessions = probeSessions(run);
|
|
7186
7713
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
7187
7714
|
}
|
|
7188
|
-
var SESSIONS_PATH =
|
|
7715
|
+
var SESSIONS_PATH = join19(homedir13(), ".config", "squadrant", "sessions.json");
|
|
7189
7716
|
function onPath(cli) {
|
|
7190
7717
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
7191
|
-
return dirs.some((d) => existsSync10(
|
|
7718
|
+
return dirs.some((d) => existsSync10(join19(d, cli)));
|
|
7192
7719
|
}
|
|
7193
7720
|
function readSessionsHashes() {
|
|
7194
7721
|
const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
|
|
@@ -7199,7 +7726,7 @@ function defaultProbeRunners() {
|
|
|
7199
7726
|
return {
|
|
7200
7727
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
7201
7728
|
try {
|
|
7202
|
-
|
|
7729
|
+
execFile3(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
7203
7730
|
} catch {
|
|
7204
7731
|
resolve3(false);
|
|
7205
7732
|
}
|
|
@@ -7858,7 +8385,7 @@ async function startWebServer(opts) {
|
|
|
7858
8385
|
|
|
7859
8386
|
// packages/cli/src/commands/dashboard.ts
|
|
7860
8387
|
init_dist();
|
|
7861
|
-
var SOCK3 =
|
|
8388
|
+
var SOCK3 = join20(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
7862
8389
|
function detectCurrentWorkspace2() {
|
|
7863
8390
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
7864
8391
|
const match = out.match(/workspace:\d+/);
|
|
@@ -7941,12 +8468,11 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
7941
8468
|
init_dist();
|
|
7942
8469
|
init_dist4();
|
|
7943
8470
|
init_dist3();
|
|
7944
|
-
|
|
7945
|
-
init_dist3();
|
|
8471
|
+
init_dist2();
|
|
7946
8472
|
init_dist2();
|
|
7947
8473
|
import { Command as Command12 } from "commander";
|
|
7948
8474
|
import { execSync as execSync11 } from "child_process";
|
|
7949
|
-
import
|
|
8475
|
+
import fs20 from "fs";
|
|
7950
8476
|
import path23 from "path";
|
|
7951
8477
|
import os12 from "os";
|
|
7952
8478
|
import chalk13 from "chalk";
|
|
@@ -7960,64 +8486,6 @@ function ensureCmuxReady() {
|
|
|
7960
8486
|
console.log(chalk13.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
7961
8487
|
process.exit(0);
|
|
7962
8488
|
}
|
|
7963
|
-
async function deliverStartupPrompt(runtime, refId, prompt, opts = {}) {
|
|
7964
|
-
const readyTimeoutMs = opts.readyTimeoutMs ?? 3e4;
|
|
7965
|
-
const settleMs = opts.settleMs ?? 2500;
|
|
7966
|
-
const pollMs = opts.pollMs ?? 1e3;
|
|
7967
|
-
const maxAttempts = opts.maxAttempts ?? 3;
|
|
7968
|
-
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7969
|
-
const read = async () => runtime.readScreen(refId).catch(() => "");
|
|
7970
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
7971
|
-
const deadline = Date.now() + readyTimeoutMs;
|
|
7972
|
-
let preSend = await read();
|
|
7973
|
-
let state = classifyStartupSurface(preSend);
|
|
7974
|
-
while (state === "loading" && Date.now() < deadline) {
|
|
7975
|
-
await sleep2(pollMs);
|
|
7976
|
-
preSend = await read();
|
|
7977
|
-
state = classifyStartupSurface(preSend);
|
|
7978
|
-
}
|
|
7979
|
-
if (state === "working") return;
|
|
7980
|
-
await runtime.send(refId, prompt).catch(() => {
|
|
7981
|
-
});
|
|
7982
|
-
if (state === "loading") return;
|
|
7983
|
-
await sleep2(settleMs);
|
|
7984
|
-
const after = await read();
|
|
7985
|
-
if (after !== preSend) return;
|
|
7986
|
-
}
|
|
7987
|
-
}
|
|
7988
|
-
async function launchWorkspace(runtime, name, agentCmd, cwd, navigate = false, forceFresh = false, pinToTop = false, initialPrompt) {
|
|
7989
|
-
ensureCmuxReady();
|
|
7990
|
-
const existing = await runtime.status(name);
|
|
7991
|
-
if (existing && forceFresh) {
|
|
7992
|
-
console.log(chalk13.yellow(` Closing stale workspace '${name}' for fresh start`));
|
|
7993
|
-
await runtime.stop(existing.id);
|
|
7994
|
-
} else if (existing) {
|
|
7995
|
-
console.log(chalk13.yellow(` Workspace '${name}' already exists \u2014 switching to it`));
|
|
7996
|
-
cmuxLocal(["select-workspace", "--workspace", existing.id]);
|
|
7997
|
-
return;
|
|
7998
|
-
}
|
|
7999
|
-
let currentRef;
|
|
8000
|
-
try {
|
|
8001
|
-
const cur = cmuxLocal(["current-workspace"]);
|
|
8002
|
-
currentRef = cur.match(/workspace:\d+/)?.[0];
|
|
8003
|
-
} catch {
|
|
8004
|
-
}
|
|
8005
|
-
const ref = await runtime.spawn({
|
|
8006
|
-
name,
|
|
8007
|
-
workdir: cwd ?? process.cwd(),
|
|
8008
|
-
command: agentCmd,
|
|
8009
|
-
pinToTop
|
|
8010
|
-
});
|
|
8011
|
-
if (initialPrompt) {
|
|
8012
|
-
void deliverStartupPrompt(runtime, ref.id, initialPrompt);
|
|
8013
|
-
}
|
|
8014
|
-
if (navigate) {
|
|
8015
|
-
cmuxLocal(["select-workspace", "--workspace", ref.id]);
|
|
8016
|
-
} else if (currentRef) {
|
|
8017
|
-
cmuxLocal(["select-workspace", "--workspace", currentRef]);
|
|
8018
|
-
}
|
|
8019
|
-
console.log(chalk13.green(` \u2714 Workspace '${name}' created`));
|
|
8020
|
-
}
|
|
8021
8489
|
var launchCommand = new Command12("launch").description(
|
|
8022
8490
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
8023
8491
|
).argument("[project]", "Project name to launch captain for").option("--fresh", "Start a new session instead of resuming the last one").option("--all", "Launch all captain workspaces").action(async (project, opts) => {
|
|
@@ -8031,19 +8499,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
8031
8499
|
const registry = new CapabilityRegistry(drivers);
|
|
8032
8500
|
const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
8033
8501
|
async function launchOne(workspaceName, role, cwd, permissionMode, navigate, pinToTop = false, projectName) {
|
|
8034
|
-
|
|
8035
|
-
if (!forceFresh) {
|
|
8036
|
-
const auto = shouldStartFresh(workspaceName, role, { sessionsPath: SESSIONS_PATH2, templatesDir: TEMPLATES_DIR4 });
|
|
8037
|
-
if (auto.fresh) {
|
|
8038
|
-
console.log(chalk13.cyan(` \u21BB ${auto.reason}`));
|
|
8039
|
-
forceFresh = true;
|
|
8040
|
-
}
|
|
8041
|
-
}
|
|
8502
|
+
ensureCmuxReady();
|
|
8042
8503
|
const roleConfig = config.defaults.roles?.[role];
|
|
8043
8504
|
const agentName = roleConfig?.agent || "claude";
|
|
8044
8505
|
const model = roleConfig?.model || config.defaults.models?.[role];
|
|
8045
|
-
const agentCmd = buildAgentCmd(agentName, registry, role, forceFresh, permissionMode, model, TEMPLATES_DIR4);
|
|
8046
|
-
recordSession(workspaceName, role, { sessionsPath: SESSIONS_PATH2, templatesDir: TEMPLATES_DIR4 });
|
|
8047
8506
|
let initialPrompt;
|
|
8048
8507
|
if (role === "captain") {
|
|
8049
8508
|
initialPrompt = "Run your startup checklist: use the squadrant:captain-ops skill, complete all startup steps, then report ready.";
|
|
@@ -8052,19 +8511,44 @@ var launchCommand = new Command12("launch").description(
|
|
|
8052
8511
|
}
|
|
8053
8512
|
const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
|
|
8054
8513
|
try {
|
|
8055
|
-
await
|
|
8514
|
+
await launchOneWorkspace({
|
|
8515
|
+
workspaceName,
|
|
8516
|
+
role,
|
|
8517
|
+
cwd,
|
|
8518
|
+
forceFreshOverride: opts.fresh,
|
|
8519
|
+
sessionsPath: SESSIONS_PATH2,
|
|
8520
|
+
templatesDir: TEMPLATES_DIR4,
|
|
8521
|
+
agentCmdFactory: (forceFresh) => buildAgentCmd(agentName, registry, role, forceFresh, permissionMode, model, TEMPLATES_DIR4),
|
|
8522
|
+
initialPrompt,
|
|
8523
|
+
runtime,
|
|
8524
|
+
navigate,
|
|
8525
|
+
pinToTop,
|
|
8526
|
+
classifyScreen: classifyStartupSurface,
|
|
8527
|
+
selectWorkspace: (id) => cmuxLocal(["select-workspace", "--workspace", id]),
|
|
8528
|
+
getCurrentWorkspace: () => {
|
|
8529
|
+
try {
|
|
8530
|
+
return cmuxLocal(["current-workspace"]);
|
|
8531
|
+
} catch {
|
|
8532
|
+
return null;
|
|
8533
|
+
}
|
|
8534
|
+
},
|
|
8535
|
+
onFreshReason: (reason) => console.log(chalk13.cyan(` \u21BB ${reason}`)),
|
|
8536
|
+
onStoppingStale: (name) => console.log(chalk13.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
8537
|
+
onAlreadyExists: (name) => console.log(chalk13.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
8538
|
+
onCreated: (name) => console.log(chalk13.green(` \u2714 Workspace '${name}' created`))
|
|
8539
|
+
});
|
|
8056
8540
|
} catch (err) {
|
|
8057
8541
|
console.error(chalk13.red(` \u2718 Failed: ${err.message}`));
|
|
8058
8542
|
}
|
|
8059
8543
|
}
|
|
8060
8544
|
if (opts.all) {
|
|
8061
8545
|
const hubPath = resolveHome(config.hubVault);
|
|
8062
|
-
|
|
8546
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
8063
8547
|
console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
|
|
8064
8548
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
8065
8549
|
const projPath = resolveHome(proj.path);
|
|
8066
8550
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8067
|
-
if (!
|
|
8551
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8068
8552
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
8069
8553
|
await ensureSpokeLayout(spokeDriver);
|
|
8070
8554
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8095,7 +8579,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
8095
8579
|
const proj = config.projects[project];
|
|
8096
8580
|
const projPath = resolveHome(proj.path);
|
|
8097
8581
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8098
|
-
if (!
|
|
8582
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8099
8583
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
8100
8584
|
await ensureSpokeLayout(spokeDriver);
|
|
8101
8585
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8230,7 +8714,7 @@ Shutting down captain workspace for '${project}'...
|
|
|
8230
8714
|
// packages/cli/src/commands/feedback.ts
|
|
8231
8715
|
init_dist();
|
|
8232
8716
|
import { Command as Command14 } from "commander";
|
|
8233
|
-
import
|
|
8717
|
+
import fs21 from "fs";
|
|
8234
8718
|
import os13 from "os";
|
|
8235
8719
|
import path24 from "path";
|
|
8236
8720
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -8240,14 +8724,14 @@ var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
|
8240
8724
|
function readPkgVersion() {
|
|
8241
8725
|
try {
|
|
8242
8726
|
const pkgPath = path24.join(path24.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8243
|
-
return JSON.parse(
|
|
8727
|
+
return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
8244
8728
|
} catch {
|
|
8245
8729
|
return "unknown";
|
|
8246
8730
|
}
|
|
8247
8731
|
}
|
|
8248
8732
|
function readMetrics(metricsPath) {
|
|
8249
8733
|
try {
|
|
8250
|
-
return JSON.parse(
|
|
8734
|
+
return JSON.parse(fs21.readFileSync(metricsPath, "utf-8"));
|
|
8251
8735
|
} catch {
|
|
8252
8736
|
return {};
|
|
8253
8737
|
}
|
|
@@ -8307,7 +8791,7 @@ init_dist();
|
|
|
8307
8791
|
init_dist();
|
|
8308
8792
|
init_dist3();
|
|
8309
8793
|
import { Command as Command15 } from "commander";
|
|
8310
|
-
import
|
|
8794
|
+
import fs22 from "fs";
|
|
8311
8795
|
import path25 from "path";
|
|
8312
8796
|
import chalk16 from "chalk";
|
|
8313
8797
|
import matter3 from "gray-matter";
|
|
@@ -8319,9 +8803,9 @@ async function getProjectStandup(name, project, dateStr, registry, config) {
|
|
|
8319
8803
|
const spokeVault = resolveHome(project.spokeVault);
|
|
8320
8804
|
const statusFile = path25.join(spokeVault, "status.md");
|
|
8321
8805
|
let status = {};
|
|
8322
|
-
if (
|
|
8806
|
+
if (fs22.existsSync(statusFile)) {
|
|
8323
8807
|
try {
|
|
8324
|
-
status = matter3(
|
|
8808
|
+
status = matter3(fs22.readFileSync(statusFile, "utf-8")).data;
|
|
8325
8809
|
} catch {
|
|
8326
8810
|
}
|
|
8327
8811
|
}
|
|
@@ -8439,15 +8923,15 @@ init_dist();
|
|
|
8439
8923
|
init_dist();
|
|
8440
8924
|
init_dist3();
|
|
8441
8925
|
import { Command as Command16 } from "commander";
|
|
8442
|
-
import
|
|
8926
|
+
import fs23 from "fs";
|
|
8443
8927
|
import path26 from "path";
|
|
8444
8928
|
import chalk17 from "chalk";
|
|
8445
8929
|
import matter4 from "gray-matter";
|
|
8446
8930
|
function readStatus(spokeVault) {
|
|
8447
8931
|
const statusFile = path26.join(spokeVault, "status.md");
|
|
8448
|
-
if (!
|
|
8932
|
+
if (!fs23.existsSync(statusFile)) return {};
|
|
8449
8933
|
try {
|
|
8450
|
-
return matter4(
|
|
8934
|
+
return matter4(fs23.readFileSync(statusFile, "utf-8")).data;
|
|
8451
8935
|
} catch {
|
|
8452
8936
|
return {};
|
|
8453
8937
|
}
|
|
@@ -8867,7 +9351,7 @@ init_dist3();
|
|
|
8867
9351
|
init_dist();
|
|
8868
9352
|
import { Command as Command20 } from "commander";
|
|
8869
9353
|
import chalk21 from "chalk";
|
|
8870
|
-
import
|
|
9354
|
+
import fs24 from "fs";
|
|
8871
9355
|
import path27 from "path";
|
|
8872
9356
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8873
9357
|
function parseScope(v) {
|
|
@@ -8879,7 +9363,7 @@ function parseScope(v) {
|
|
|
8879
9363
|
function findPackageRoot3() {
|
|
8880
9364
|
let dir = path27.dirname(fileURLToPath4(import.meta.url));
|
|
8881
9365
|
while (dir !== "/" && dir !== "") {
|
|
8882
|
-
if (
|
|
9366
|
+
if (fs24.existsSync(path27.join(dir, "package.json"))) return dir;
|
|
8883
9367
|
dir = path27.dirname(dir);
|
|
8884
9368
|
}
|
|
8885
9369
|
return process.cwd();
|
|
@@ -9073,13 +9557,14 @@ ${transcript.join("\n")}`
|
|
|
9073
9557
|
init_dist();
|
|
9074
9558
|
init_dist();
|
|
9075
9559
|
init_dist();
|
|
9560
|
+
init_dist2();
|
|
9076
9561
|
import { Command as Command22 } from "commander";
|
|
9077
|
-
import
|
|
9562
|
+
import fs25 from "fs";
|
|
9078
9563
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9079
|
-
import { dirname as dirname5, join as
|
|
9564
|
+
import { dirname as dirname5, join as join21 } from "path";
|
|
9080
9565
|
import chalk22 from "chalk";
|
|
9081
9566
|
function runConfigCheck(opts) {
|
|
9082
|
-
const raw = JSON.parse(
|
|
9567
|
+
const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
|
|
9083
9568
|
const def = getDefaultConfig();
|
|
9084
9569
|
const items = detectDrift(raw, def);
|
|
9085
9570
|
let working = raw;
|
|
@@ -9096,7 +9581,7 @@ function runConfigCheck(opts) {
|
|
|
9096
9581
|
stamped = true;
|
|
9097
9582
|
}
|
|
9098
9583
|
if (opts.fix || opts.accept || stamped) {
|
|
9099
|
-
|
|
9584
|
+
fs25.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
|
|
9100
9585
|
}
|
|
9101
9586
|
return { items, applied, remaining, stamped };
|
|
9102
9587
|
}
|
|
@@ -9167,7 +9652,7 @@ function printItems(items) {
|
|
|
9167
9652
|
var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
|
|
9168
9653
|
configCommand.command("check").description("Detect config drift vs the current default schema").option("--fix", "Apply the safe tier (add missing, remove deprecated)", false).option("--accept", "Stamp the current version without changing config (dismiss advisories)", false).option("--json", "Output drift items as JSON", false).action((opts) => {
|
|
9169
9654
|
const pkgVersion = readPkgVersion2();
|
|
9170
|
-
if (!
|
|
9655
|
+
if (!fs25.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
9171
9656
|
console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
|
|
9172
9657
|
return;
|
|
9173
9658
|
}
|
|
@@ -9212,14 +9697,15 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
9212
9697
|
}
|
|
9213
9698
|
});
|
|
9214
9699
|
function readPkgVersion2() {
|
|
9215
|
-
const pkgPath =
|
|
9216
|
-
return JSON.parse(
|
|
9700
|
+
const pkgPath = join21(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
9701
|
+
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
9217
9702
|
}
|
|
9218
9703
|
|
|
9219
9704
|
// packages/cli/src/commands/heal.ts
|
|
9220
9705
|
import { Command as Command23 } from "commander";
|
|
9221
9706
|
import chalk23 from "chalk";
|
|
9222
9707
|
init_dist2();
|
|
9708
|
+
init_dist2();
|
|
9223
9709
|
function buildHealStatus(components) {
|
|
9224
9710
|
if (components === null) {
|
|
9225
9711
|
return { healthy: false, daemonUnreachable: true, components: [] };
|
|
@@ -9310,90 +9796,10 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
9310
9796
|
// packages/cli/src/commands/group.ts
|
|
9311
9797
|
init_dist();
|
|
9312
9798
|
init_dist2();
|
|
9799
|
+
init_dist2();
|
|
9313
9800
|
import { Command as Command24 } from "commander";
|
|
9314
9801
|
import { execSync as execSync13 } from "child_process";
|
|
9315
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
9316
|
-
import { homedir as homedir14 } from "os";
|
|
9317
|
-
import { join as join21 } from "path";
|
|
9318
9802
|
import chalk24 from "chalk";
|
|
9319
|
-
var SOCK4 = join21(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
9320
|
-
var WARMUP_TIMEOUT_MS = 12e4;
|
|
9321
|
-
var WARMUP_POLL_MS = 1e3;
|
|
9322
|
-
function resolveCurrentProject(config) {
|
|
9323
|
-
const cwd = process.cwd();
|
|
9324
|
-
for (const [name, proj] of Object.entries(config.projects)) {
|
|
9325
|
-
const resolvedPath = resolveHome(proj.path);
|
|
9326
|
-
if (cwd.startsWith(resolvedPath)) return name;
|
|
9327
|
-
}
|
|
9328
|
-
return null;
|
|
9329
|
-
}
|
|
9330
|
-
async function isCaptainAlive(project) {
|
|
9331
|
-
try {
|
|
9332
|
-
const health = await sendRequest(SOCK4, { kind: "health", project }, 5e3);
|
|
9333
|
-
const captain = health?.find((h) => h.kind === "captain" && h.project === project);
|
|
9334
|
-
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
9335
|
-
} catch {
|
|
9336
|
-
return false;
|
|
9337
|
-
}
|
|
9338
|
-
}
|
|
9339
|
-
async function waitForWarmup(project, timeoutMs = WARMUP_TIMEOUT_MS, pollMs = WARMUP_POLL_MS) {
|
|
9340
|
-
const deadline = Date.now() + timeoutMs;
|
|
9341
|
-
while (Date.now() < deadline) {
|
|
9342
|
-
if (await isCaptainAlive(project)) return true;
|
|
9343
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
9344
|
-
}
|
|
9345
|
-
return false;
|
|
9346
|
-
}
|
|
9347
|
-
async function runGroupDispatch(opts) {
|
|
9348
|
-
const config = loadConfig();
|
|
9349
|
-
const fromCfg = config.projects[opts.fromProject];
|
|
9350
|
-
const toCfg = config.projects[opts.toProject];
|
|
9351
|
-
if (!toCfg) {
|
|
9352
|
-
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
9353
|
-
}
|
|
9354
|
-
if (!fromCfg.group || !toCfg.group || fromCfg.group !== toCfg.group) {
|
|
9355
|
-
throw new Error(
|
|
9356
|
-
`cannot dispatch: '${opts.toProject}' (group: ${toCfg.group ?? "none"}) is not in the same group as '${opts.fromProject}' (group: ${fromCfg.group ?? "none"})`
|
|
9357
|
-
);
|
|
9358
|
-
}
|
|
9359
|
-
if (toCfg.acceptDelegations === false) {
|
|
9360
|
-
throw new Error(
|
|
9361
|
-
`cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`
|
|
9362
|
-
);
|
|
9363
|
-
}
|
|
9364
|
-
const alive = await isCaptainAlive(opts.toProject);
|
|
9365
|
-
if (!alive) {
|
|
9366
|
-
try {
|
|
9367
|
-
execSync13(`squadrant launch ${opts.toProject}`, { stdio: "ignore", timeout: 15e3 });
|
|
9368
|
-
} catch {
|
|
9369
|
-
throw new Error(`failed to launch captain for '${opts.toProject}' \u2014 is squadrant installed?`);
|
|
9370
|
-
}
|
|
9371
|
-
const warmed = await waitForWarmup(opts.toProject, opts.warmupTimeoutMs, opts.warmupPollMs);
|
|
9372
|
-
if (!warmed) {
|
|
9373
|
-
throw new Error(
|
|
9374
|
-
`dispatch to '${opts.toProject}' timed out waiting for captain warmup (>${(opts.warmupTimeoutMs ?? WARMUP_TIMEOUT_MS) / 1e3}s)`
|
|
9375
|
-
);
|
|
9376
|
-
}
|
|
9377
|
-
}
|
|
9378
|
-
const now = Date.now();
|
|
9379
|
-
const attemptId = randomUUID4();
|
|
9380
|
-
const record = {
|
|
9381
|
-
id: randomUUID4(),
|
|
9382
|
-
project: opts.toProject,
|
|
9383
|
-
originProject: opts.fromProject,
|
|
9384
|
-
provider: opts.provider ?? "claude",
|
|
9385
|
-
mode: opts.mode ?? "headless",
|
|
9386
|
-
state: "submitted",
|
|
9387
|
-
task: opts.task,
|
|
9388
|
-
createdAt: now,
|
|
9389
|
-
lastHeartbeat: now,
|
|
9390
|
-
lastEvent: "dispatch",
|
|
9391
|
-
heartbeatBudgetMs: 3e5,
|
|
9392
|
-
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
9393
|
-
};
|
|
9394
|
-
const result = await sendRequest(SOCK4, { kind: "dispatch", record });
|
|
9395
|
-
return result;
|
|
9396
|
-
}
|
|
9397
9803
|
var groupCommand = new Command24("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
9398
9804
|
new Command24("dispatch").description("Dispatch a task to a sibling project in the same group").argument("<to-project>", "Target project name (must be in the same group)").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 relay to boot (default: 120)", (v) => parseInt(v, 10) * 1e3).action(async (toProject, task, opts) => {
|
|
9399
9805
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
@@ -9402,13 +9808,20 @@ var groupCommand = new Command24("group").description("Cross-project intra-group
|
|
|
9402
9808
|
process.exit(1);
|
|
9403
9809
|
}
|
|
9404
9810
|
try {
|
|
9405
|
-
const result = await
|
|
9811
|
+
const result = await dispatchToSibling({
|
|
9406
9812
|
fromProject,
|
|
9407
9813
|
toProject,
|
|
9408
9814
|
task,
|
|
9409
9815
|
provider: opts.provider,
|
|
9410
9816
|
mode: opts.mode,
|
|
9411
|
-
warmupTimeoutMs: opts.warmupTimeout
|
|
9817
|
+
warmupTimeoutMs: opts.warmupTimeout,
|
|
9818
|
+
bootCaptain: async (project) => {
|
|
9819
|
+
try {
|
|
9820
|
+
execSync13(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
|
|
9821
|
+
} catch {
|
|
9822
|
+
throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
|
|
9823
|
+
}
|
|
9824
|
+
}
|
|
9412
9825
|
});
|
|
9413
9826
|
console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
|
|
9414
9827
|
console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
|
|
@@ -9478,7 +9891,7 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
|
|
|
9478
9891
|
|
|
9479
9892
|
// packages/cli/src/commands/effort.ts
|
|
9480
9893
|
init_dist();
|
|
9481
|
-
import
|
|
9894
|
+
import fs26 from "fs";
|
|
9482
9895
|
import path28 from "path";
|
|
9483
9896
|
import { Command as Command26 } from "commander";
|
|
9484
9897
|
import chalk26 from "chalk";
|
|
@@ -9506,7 +9919,7 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
9506
9919
|
}
|
|
9507
9920
|
function canonical(p) {
|
|
9508
9921
|
try {
|
|
9509
|
-
return
|
|
9922
|
+
return fs26.realpathSync(p);
|
|
9510
9923
|
} catch {
|
|
9511
9924
|
return path28.resolve(p);
|
|
9512
9925
|
}
|
|
@@ -9562,59 +9975,6 @@ import chalk27 from "chalk";
|
|
|
9562
9975
|
function defaultStateRoot() {
|
|
9563
9976
|
return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
9564
9977
|
}
|
|
9565
|
-
function runTelegramStatus(opts) {
|
|
9566
|
-
const tg = opts.config.telegram;
|
|
9567
|
-
const env = opts.env ?? process.env;
|
|
9568
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
9569
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
9570
|
-
const sep2 = key.indexOf("::");
|
|
9571
|
-
return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
|
|
9572
|
-
});
|
|
9573
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
9574
|
-
}
|
|
9575
|
-
async function runTelegramSend(opts) {
|
|
9576
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9577
|
-
if (topicId === void 0) {
|
|
9578
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
9579
|
-
}
|
|
9580
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
9581
|
-
return { chatId: opts.cfg.supergroupId, topicId };
|
|
9582
|
-
}
|
|
9583
|
-
function runTelegramNotifySet(opts) {
|
|
9584
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
9585
|
-
}
|
|
9586
|
-
function runTelegramNotifyPref(args) {
|
|
9587
|
-
const { project, dimension, value, root } = args;
|
|
9588
|
-
if (dimension === "crew") {
|
|
9589
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
9590
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
9591
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
9592
|
-
return { ok: true };
|
|
9593
|
-
}
|
|
9594
|
-
if (value !== "on" && value !== "off") return { ok: false, message: "cap must be on|off" };
|
|
9595
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
9596
|
-
return { ok: true };
|
|
9597
|
-
}
|
|
9598
|
-
function capAllowed(project, globalNotify, root) {
|
|
9599
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
9600
|
-
}
|
|
9601
|
-
function runTelegramNotifyStatus(opts) {
|
|
9602
|
-
const s = loadState(opts.stateRoot);
|
|
9603
|
-
const projects = /* @__PURE__ */ new Set();
|
|
9604
|
-
for (const key of Object.keys(s.topics)) {
|
|
9605
|
-
const sep2 = key.indexOf("::");
|
|
9606
|
-
projects.add(sep2 === -1 ? key : key.slice(0, sep2));
|
|
9607
|
-
}
|
|
9608
|
-
for (const p of Object.keys(s.notify)) projects.add(p);
|
|
9609
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
9610
|
-
}
|
|
9611
|
-
async function runTelegramLink(opts) {
|
|
9612
|
-
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9613
|
-
if (existing !== void 0) return { topicId: existing, created: false };
|
|
9614
|
-
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
9615
|
-
setTopic(opts.stateRoot, opts.project, topicId);
|
|
9616
|
-
return { topicId, created: true };
|
|
9617
|
-
}
|
|
9618
9978
|
async function questionMasked() {
|
|
9619
9979
|
return new Promise((resolve3) => {
|
|
9620
9980
|
emitKeypressEvents(process.stdin);
|
|
@@ -9658,44 +10018,6 @@ async function questionYesNo(prompt) {
|
|
|
9658
10018
|
});
|
|
9659
10019
|
});
|
|
9660
10020
|
}
|
|
9661
|
-
function confirmationText(project, before, after, dim) {
|
|
9662
|
-
if (dim === "active") return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
9663
|
-
if (dim === "cap") return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
9664
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
9665
|
-
}
|
|
9666
|
-
async function runNotifyConfirmation(opts) {
|
|
9667
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
9668
|
-
if (!quieter || dim === null) return false;
|
|
9669
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9670
|
-
if (topicId === void 0) return false;
|
|
9671
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
9672
|
-
try {
|
|
9673
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
9674
|
-
return true;
|
|
9675
|
-
} catch {
|
|
9676
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
9677
|
-
return false;
|
|
9678
|
-
}
|
|
9679
|
-
}
|
|
9680
|
-
function resolveSetupToken(existingToken, opts) {
|
|
9681
|
-
if (opts.resetToken || !existingToken) return "prompt";
|
|
9682
|
-
return "try-reuse";
|
|
9683
|
-
}
|
|
9684
|
-
function resolveSetupUserId(flagUserId, detectedUserId, stateRoot) {
|
|
9685
|
-
return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;
|
|
9686
|
-
}
|
|
9687
|
-
async function runRegisterCommands(opts) {
|
|
9688
|
-
await opts.client.setMyCommands(BOT_COMMANDS);
|
|
9689
|
-
}
|
|
9690
|
-
function runTelegramPostSetup(opts) {
|
|
9691
|
-
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
9692
|
-
const outcome = doRestart({ reason: "telegram config" });
|
|
9693
|
-
if (outcome === "skipped-not-running") {
|
|
9694
|
-
console.log(chalk27.dim("(daemon not running \u2014 change applies on next start)"));
|
|
9695
|
-
} else if (outcome === "skipped-opt-out") {
|
|
9696
|
-
console.log(chalk27.dim("(run 'squadrant heal daemon' to apply)"));
|
|
9697
|
-
}
|
|
9698
|
-
}
|
|
9699
10021
|
var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
9700
10022
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
9701
10023
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|