squadrant 0.11.3 → 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/dist/index.js +1215 -1007
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +126 -95
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/scripts/gen-codex-types.sh +2 -10
package/dist/index.js
CHANGED
|
@@ -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 });
|
|
@@ -4951,7 +5748,7 @@ function settingsHaveAgentTeams() {
|
|
|
4951
5748
|
try {
|
|
4952
5749
|
const home = process.env.HOME || "";
|
|
4953
5750
|
const settings = JSON.parse(
|
|
4954
|
-
|
|
5751
|
+
fs15.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
4955
5752
|
);
|
|
4956
5753
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
4957
5754
|
} catch {
|
|
@@ -4962,7 +5759,7 @@ function pluginInstalled(pluginKey) {
|
|
|
4962
5759
|
try {
|
|
4963
5760
|
const home = process.env.HOME || "";
|
|
4964
5761
|
const plugins = JSON.parse(
|
|
4965
|
-
|
|
5762
|
+
fs15.readFileSync(
|
|
4966
5763
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
4967
5764
|
"utf-8"
|
|
4968
5765
|
)
|
|
@@ -5007,7 +5804,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5007
5804
|
));
|
|
5008
5805
|
results.push(check(
|
|
5009
5806
|
"Obsidian installed",
|
|
5010
|
-
commandExists("obsidian") ||
|
|
5807
|
+
commandExists("obsidian") || fs15.existsSync("/Applications/Obsidian.app"),
|
|
5011
5808
|
"Install from: https://obsidian.md"
|
|
5012
5809
|
));
|
|
5013
5810
|
results.push(check(
|
|
@@ -5101,7 +5898,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5101
5898
|
const emitter = projectionRegistry.get(name);
|
|
5102
5899
|
const [userDest] = emitter.destinations("user");
|
|
5103
5900
|
if (!userDest) continue;
|
|
5104
|
-
const dir =
|
|
5901
|
+
const dir = path17.dirname(userDest.path);
|
|
5105
5902
|
let status;
|
|
5106
5903
|
try {
|
|
5107
5904
|
await stat(dir);
|
|
@@ -5114,7 +5911,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5114
5911
|
results.push(
|
|
5115
5912
|
check(
|
|
5116
5913
|
"Squadrant config exists",
|
|
5117
|
-
|
|
5914
|
+
fs15.existsSync(
|
|
5118
5915
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
5119
5916
|
),
|
|
5120
5917
|
"Run: squadrant init"
|
|
@@ -5189,28 +5986,28 @@ init_dist3();
|
|
|
5189
5986
|
init_dist();
|
|
5190
5987
|
init_dist4();
|
|
5191
5988
|
import { Command as Command2 } from "commander";
|
|
5192
|
-
import
|
|
5193
|
-
import
|
|
5194
|
-
import
|
|
5989
|
+
import fs16 from "fs";
|
|
5990
|
+
import path18 from "path";
|
|
5991
|
+
import os9 from "os";
|
|
5195
5992
|
import readline from "readline";
|
|
5196
5993
|
import chalk4 from "chalk";
|
|
5197
5994
|
function findPackageRoot() {
|
|
5198
|
-
let dir =
|
|
5995
|
+
let dir = path18.dirname(new URL(import.meta.url).pathname);
|
|
5199
5996
|
while (dir !== "/") {
|
|
5200
|
-
if (
|
|
5201
|
-
dir =
|
|
5997
|
+
if (fs16.existsSync(path18.join(dir, "package.json"))) return dir;
|
|
5998
|
+
dir = path18.dirname(dir);
|
|
5202
5999
|
}
|
|
5203
6000
|
return process.cwd();
|
|
5204
6001
|
}
|
|
5205
6002
|
function copyDirRecursive(src, dest) {
|
|
5206
|
-
|
|
5207
|
-
for (const entry of
|
|
5208
|
-
const srcPath =
|
|
5209
|
-
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);
|
|
5210
6007
|
if (entry.isDirectory()) {
|
|
5211
6008
|
copyDirRecursive(srcPath, destPath);
|
|
5212
6009
|
} else {
|
|
5213
|
-
|
|
6010
|
+
fs16.copyFileSync(srcPath, destPath);
|
|
5214
6011
|
}
|
|
5215
6012
|
}
|
|
5216
6013
|
}
|
|
@@ -5230,7 +6027,7 @@ function promptLine(question) {
|
|
|
5230
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) => {
|
|
5231
6028
|
const hubPath = resolveHome(opts.hub);
|
|
5232
6029
|
const pkgRoot = findPackageRoot();
|
|
5233
|
-
const configDir =
|
|
6030
|
+
const configDir = path18.join(os9.homedir(), ".config", "squadrant");
|
|
5234
6031
|
const isTTY = process.stdin.isTTY === true;
|
|
5235
6032
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
5236
6033
|
if (!isTTY) {
|
|
@@ -5253,8 +6050,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5253
6050
|
}
|
|
5254
6051
|
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
5255
6052
|
try {
|
|
5256
|
-
if (
|
|
5257
|
-
const existing = JSON.parse(
|
|
6053
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
6054
|
+
const existing = JSON.parse(fs16.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
5258
6055
|
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
5259
6056
|
}
|
|
5260
6057
|
} catch (err) {
|
|
@@ -5262,7 +6059,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5262
6059
|
return;
|
|
5263
6060
|
}
|
|
5264
6061
|
stepHeader(1, 5, "Hub vault");
|
|
5265
|
-
if (
|
|
6062
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
5266
6063
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
5267
6064
|
} else {
|
|
5268
6065
|
const config = getDefaultConfig();
|
|
@@ -5270,37 +6067,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5270
6067
|
saveConfig(config);
|
|
5271
6068
|
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
5272
6069
|
}
|
|
5273
|
-
const hubTemplate =
|
|
5274
|
-
if (
|
|
6070
|
+
const hubTemplate = path18.join(pkgRoot, "obsidian", "hub");
|
|
6071
|
+
if (fs16.existsSync(hubPath)) {
|
|
5275
6072
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
5276
|
-
} else if (
|
|
6073
|
+
} else if (fs16.existsSync(hubTemplate)) {
|
|
5277
6074
|
copyDirRecursive(hubTemplate, hubPath);
|
|
5278
6075
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
5279
6076
|
} else {
|
|
5280
|
-
|
|
6077
|
+
fs16.mkdirSync(hubPath, { recursive: true });
|
|
5281
6078
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
5282
6079
|
}
|
|
5283
|
-
const hubDashboardSrc =
|
|
5284
|
-
const hubDashboardDest =
|
|
5285
|
-
if (
|
|
5286
|
-
|
|
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);
|
|
5287
6084
|
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
5288
6085
|
}
|
|
5289
|
-
|
|
6086
|
+
fs16.mkdirSync(path18.join(hubPath, "projects"), { recursive: true });
|
|
5290
6087
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
5291
6088
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
5292
6089
|
stepHeader(2, 5, "Agent + projection setup");
|
|
5293
|
-
const settingsPath =
|
|
6090
|
+
const settingsPath = path18.join(os9.homedir(), ".claude", "settings.json");
|
|
5294
6091
|
try {
|
|
5295
6092
|
let settings = {};
|
|
5296
|
-
if (
|
|
5297
|
-
settings = JSON.parse(
|
|
6093
|
+
if (fs16.existsSync(settingsPath)) {
|
|
6094
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
5298
6095
|
}
|
|
5299
6096
|
const env = settings.env || {};
|
|
5300
6097
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
5301
6098
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
5302
|
-
|
|
5303
|
-
|
|
6099
|
+
fs16.mkdirSync(path18.dirname(settingsPath), { recursive: true });
|
|
6100
|
+
fs16.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5304
6101
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
5305
6102
|
} else {
|
|
5306
6103
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -5343,7 +6140,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5343
6140
|
chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
|
|
5344
6141
|
);
|
|
5345
6142
|
if (projectPath) {
|
|
5346
|
-
const projectName =
|
|
6143
|
+
const projectName = path18.basename(projectPath);
|
|
5347
6144
|
console.log(chalk4.bold(`
|
|
5348
6145
|
Run this to register it:`));
|
|
5349
6146
|
console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
|
|
@@ -5369,45 +6166,11 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5369
6166
|
|
|
5370
6167
|
// packages/cli/src/commands/projects.ts
|
|
5371
6168
|
init_dist();
|
|
6169
|
+
init_dist2();
|
|
5372
6170
|
import { Command as Command3 } from "commander";
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
6171
|
+
import fs17 from "fs";
|
|
6172
|
+
import path19 from "path";
|
|
5375
6173
|
import chalk5 from "chalk";
|
|
5376
|
-
|
|
5377
|
-
// packages/cli/src/control/restart-daemon.ts
|
|
5378
|
-
init_dist2();
|
|
5379
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
5380
|
-
import { existsSync as existsSync9 } from "fs";
|
|
5381
|
-
import { homedir as homedir9 } from "os";
|
|
5382
|
-
import { join as join14 } from "path";
|
|
5383
|
-
var DEFAULT_SOCK_PATH = join14(homedir9(), ".config", "squadrant", "squadrant.sock");
|
|
5384
|
-
function defaultIsRunning() {
|
|
5385
|
-
return existsSync9(DEFAULT_SOCK_PATH);
|
|
5386
|
-
}
|
|
5387
|
-
function defaultRunKickstart() {
|
|
5388
|
-
const uid = process.getuid?.() ?? 0;
|
|
5389
|
-
const target = `gui/${uid}/${LABEL}`;
|
|
5390
|
-
if (tryAcquireDaemonLock()) {
|
|
5391
|
-
try {
|
|
5392
|
-
execFileSync6("launchctl", kickstartArgv(target, true), { stdio: "ignore" });
|
|
5393
|
-
} finally {
|
|
5394
|
-
releaseDaemonLock();
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
}
|
|
5398
|
-
function restartDaemonIfRunning(opts) {
|
|
5399
|
-
const env = opts.env ?? process.env;
|
|
5400
|
-
if (env["VITEST"] || opts.noRestart) return "skipped-opt-out";
|
|
5401
|
-
const isRunning = opts.isRunning ?? defaultIsRunning;
|
|
5402
|
-
if (!isRunning()) return "skipped-not-running";
|
|
5403
|
-
const log = opts.log ?? console.log;
|
|
5404
|
-
log(`\u21BB restarting daemon to apply ${opts.reason}\u2026`);
|
|
5405
|
-
const runKickstart = opts.runKickstart ?? defaultRunKickstart;
|
|
5406
|
-
runKickstart();
|
|
5407
|
-
return "restarted";
|
|
5408
|
-
}
|
|
5409
|
-
|
|
5410
|
-
// packages/cli/src/commands/projects.ts
|
|
5411
6174
|
function restartAfterProjectsAdd(opts) {
|
|
5412
6175
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
5413
6176
|
const outcome = doRestart({ reason: "project registration", noRestart: opts.noRestart });
|
|
@@ -5418,22 +6181,22 @@ function restartAfterProjectsAdd(opts) {
|
|
|
5418
6181
|
}
|
|
5419
6182
|
}
|
|
5420
6183
|
function findPackageRoot2() {
|
|
5421
|
-
let dir =
|
|
6184
|
+
let dir = path19.dirname(new URL(import.meta.url).pathname);
|
|
5422
6185
|
while (dir !== "/") {
|
|
5423
|
-
if (
|
|
5424
|
-
dir =
|
|
6186
|
+
if (fs17.existsSync(path19.join(dir, "package.json"))) return dir;
|
|
6187
|
+
dir = path19.dirname(dir);
|
|
5425
6188
|
}
|
|
5426
6189
|
return process.cwd();
|
|
5427
6190
|
}
|
|
5428
6191
|
function copyDirRecursive2(src, dest) {
|
|
5429
|
-
|
|
5430
|
-
for (const entry of
|
|
5431
|
-
const srcPath =
|
|
5432
|
-
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);
|
|
5433
6196
|
if (entry.isDirectory()) {
|
|
5434
6197
|
copyDirRecursive2(srcPath, destPath);
|
|
5435
6198
|
} else {
|
|
5436
|
-
|
|
6199
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
5437
6200
|
}
|
|
5438
6201
|
}
|
|
5439
6202
|
}
|
|
@@ -5469,7 +6232,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5469
6232
|
process.exit(1);
|
|
5470
6233
|
}
|
|
5471
6234
|
const resolvedPath = resolveHome(projectPath);
|
|
5472
|
-
if (!
|
|
6235
|
+
if (!fs17.existsSync(path19.join(resolvedPath, ".git"))) {
|
|
5473
6236
|
console.log(chalk5.yellow(`
|
|
5474
6237
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
5475
6238
|
`));
|
|
@@ -5522,7 +6285,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5522
6285
|
\u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
|
|
5523
6286
|
}
|
|
5524
6287
|
}
|
|
5525
|
-
const spokeVault = opts.spoke ? resolveHome(opts.spoke) :
|
|
6288
|
+
const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path19.join(config.hubVault, "spokes", name);
|
|
5526
6289
|
const project = {
|
|
5527
6290
|
path: resolvedPath,
|
|
5528
6291
|
captainName,
|
|
@@ -5537,20 +6300,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5537
6300
|
\u2714 Project '${name}' registered`));
|
|
5538
6301
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
5539
6302
|
const pkgRoot = findPackageRoot2();
|
|
5540
|
-
const spokeTemplate =
|
|
5541
|
-
if (
|
|
6303
|
+
const spokeTemplate = path19.join(pkgRoot, "obsidian", "spoke");
|
|
6304
|
+
if (fs17.existsSync(spokeVault)) {
|
|
5542
6305
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
5543
|
-
} else if (
|
|
6306
|
+
} else if (fs17.existsSync(spokeTemplate)) {
|
|
5544
6307
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
5545
|
-
const statusPath =
|
|
5546
|
-
if (
|
|
5547
|
-
const content =
|
|
6308
|
+
const statusPath = path19.join(spokeVault, "status.md");
|
|
6309
|
+
if (fs17.existsSync(statusPath)) {
|
|
6310
|
+
const content = fs17.readFileSync(statusPath, "utf-8");
|
|
5548
6311
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
5549
|
-
|
|
6312
|
+
fs17.writeFileSync(statusPath, updated);
|
|
5550
6313
|
}
|
|
5551
6314
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
5552
6315
|
} else {
|
|
5553
|
-
|
|
6316
|
+
fs17.mkdirSync(spokeVault, { recursive: true });
|
|
5554
6317
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
5555
6318
|
}
|
|
5556
6319
|
console.log("");
|
|
@@ -5651,10 +6414,10 @@ init_dist4();
|
|
|
5651
6414
|
init_dist();
|
|
5652
6415
|
import { Command as Command5 } from "commander";
|
|
5653
6416
|
import { execSync as execSync9 } from "child_process";
|
|
5654
|
-
import
|
|
5655
|
-
import
|
|
6417
|
+
import path20 from "path";
|
|
6418
|
+
import os10 from "os";
|
|
5656
6419
|
import chalk7 from "chalk";
|
|
5657
|
-
var
|
|
6420
|
+
var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
|
|
5658
6421
|
var TASK_PROMPTS = {
|
|
5659
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.",
|
|
5660
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.",
|
|
@@ -5687,7 +6450,7 @@ async function runCommandSpawn(input) {
|
|
|
5687
6450
|
if (!agent) {
|
|
5688
6451
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
5689
6452
|
}
|
|
5690
|
-
const promptFile =
|
|
6453
|
+
const promptFile = path20.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
|
|
5691
6454
|
const cliCommand = agent.buildCommand({
|
|
5692
6455
|
prompt,
|
|
5693
6456
|
workdir: process.cwd(),
|
|
@@ -5712,35 +6475,11 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
|
|
|
5712
6475
|
|
|
5713
6476
|
// packages/cli/src/commands/crew.ts
|
|
5714
6477
|
init_dist();
|
|
5715
|
-
init_dist();
|
|
5716
|
-
import { Command as Command9 } from "commander";
|
|
5717
|
-
import fs16 from "fs";
|
|
5718
|
-
import path20 from "path";
|
|
5719
|
-
import os10 from "os";
|
|
5720
|
-
import chalk9 from "chalk";
|
|
5721
|
-
|
|
5722
|
-
// packages/cli/src/control/crew-routing.ts
|
|
5723
|
-
function resolveCrewRoute(taskText, config) {
|
|
5724
|
-
const rules = config.defaults.crewRouting?.rules;
|
|
5725
|
-
if (!rules || rules.length === 0) return null;
|
|
5726
|
-
for (const rule2 of rules) {
|
|
5727
|
-
const re = new RegExp(rule2.match, "i");
|
|
5728
|
-
if (re.test(taskText)) {
|
|
5729
|
-
return {
|
|
5730
|
-
agent: rule2.agent,
|
|
5731
|
-
...rule2.model !== void 0 ? { model: rule2.model } : {},
|
|
5732
|
-
tier: rule2.tier,
|
|
5733
|
-
matchedRule: rule2.match
|
|
5734
|
-
};
|
|
5735
|
-
}
|
|
5736
|
-
}
|
|
5737
|
-
return null;
|
|
5738
|
-
}
|
|
5739
|
-
|
|
5740
|
-
// packages/cli/src/commands/crew.ts
|
|
5741
|
-
init_dist3();
|
|
5742
6478
|
init_dist3();
|
|
5743
6479
|
init_dist4();
|
|
6480
|
+
init_dist2();
|
|
6481
|
+
import { Command as Command9 } from "commander";
|
|
6482
|
+
import chalk9 from "chalk";
|
|
5744
6483
|
|
|
5745
6484
|
// packages/cli/src/commands/crew-control.ts
|
|
5746
6485
|
init_dist2();
|
|
@@ -5748,9 +6487,9 @@ init_dist2();
|
|
|
5748
6487
|
init_dist4();
|
|
5749
6488
|
import { Command as Command8 } from "commander";
|
|
5750
6489
|
import { createConnection as createConnection3 } from "net";
|
|
5751
|
-
import { randomUUID as
|
|
5752
|
-
import { homedir as
|
|
5753
|
-
import { join as
|
|
6490
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6491
|
+
import { homedir as homedir12 } from "os";
|
|
6492
|
+
import { join as join17 } from "path";
|
|
5754
6493
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5755
6494
|
|
|
5756
6495
|
// packages/cli/src/commands/crew-output.ts
|
|
@@ -5809,11 +6548,11 @@ init_dist2();
|
|
|
5809
6548
|
import { Command as Command6 } from "commander";
|
|
5810
6549
|
import chalk8 from "chalk";
|
|
5811
6550
|
import { createConnection as createConnection2 } from "net";
|
|
5812
|
-
import { homedir as
|
|
5813
|
-
import { join as
|
|
6551
|
+
import { homedir as homedir11 } from "os";
|
|
6552
|
+
import { join as join16 } from "path";
|
|
5814
6553
|
import { createInterface } from "readline";
|
|
5815
6554
|
function socketPath() {
|
|
5816
|
-
return process.env.SQUADRANTD_SOCK ??
|
|
6555
|
+
return process.env.SQUADRANTD_SOCK ?? join16(homedir11(), ".config", "squadrant", "squadrant.sock");
|
|
5817
6556
|
}
|
|
5818
6557
|
function rule(width, ch = "\u2500") {
|
|
5819
6558
|
return ch.repeat(Math.max(0, width));
|
|
@@ -6038,7 +6777,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
6038
6777
|
if (opts.provider !== "codex") {
|
|
6039
6778
|
throw new Error(`crew chat is implemented for provider=codex only (got '${opts.provider}')`);
|
|
6040
6779
|
}
|
|
6041
|
-
const pane = await
|
|
6780
|
+
const pane = await runCrewSpawn2({
|
|
6042
6781
|
project: opts.project,
|
|
6043
6782
|
task: "(interactive)",
|
|
6044
6783
|
agent: "codex",
|
|
@@ -6049,7 +6788,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
6049
6788
|
});
|
|
6050
6789
|
|
|
6051
6790
|
// packages/cli/src/commands/crew-control.ts
|
|
6052
|
-
var SOCK2 =
|
|
6791
|
+
var SOCK2 = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
|
|
6053
6792
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
6054
6793
|
async function sendCodexFirstTurn(taskId, text) {
|
|
6055
6794
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -6076,11 +6815,11 @@ async function sendCodexFirstTurn(taskId, text) {
|
|
|
6076
6815
|
}
|
|
6077
6816
|
function buildDispatchRequest(o) {
|
|
6078
6817
|
const now = Date.now();
|
|
6079
|
-
const attemptId =
|
|
6818
|
+
const attemptId = randomUUID4();
|
|
6080
6819
|
return {
|
|
6081
6820
|
kind: "dispatch",
|
|
6082
6821
|
record: {
|
|
6083
|
-
id:
|
|
6822
|
+
id: randomUUID4(),
|
|
6084
6823
|
project: o.project,
|
|
6085
6824
|
provider: o.provider,
|
|
6086
6825
|
mode: o.mode,
|
|
@@ -6154,9 +6893,9 @@ function buildSignalRequest(signal, o) {
|
|
|
6154
6893
|
return { kind: "event", project, event };
|
|
6155
6894
|
}
|
|
6156
6895
|
function defaultWriteResult(id, payload) {
|
|
6157
|
-
const dir =
|
|
6896
|
+
const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
|
|
6158
6897
|
mkdirSync6(dir, { recursive: true });
|
|
6159
|
-
const file =
|
|
6898
|
+
const file = join17(dir, `${id}.txt`);
|
|
6160
6899
|
writeFileSync7(file, payload);
|
|
6161
6900
|
return file;
|
|
6162
6901
|
}
|
|
@@ -6257,7 +6996,7 @@ addControlPlaneCrewCommands(crewControlCommand);
|
|
|
6257
6996
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
6258
6997
|
init_dist4();
|
|
6259
6998
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
6260
|
-
import { join as
|
|
6999
|
+
import { join as join18 } from "path";
|
|
6261
7000
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
6262
7001
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
6263
7002
|
"Bash(git status:*)",
|
|
@@ -6348,9 +7087,9 @@ function mergeCrewPermissions(settings) {
|
|
|
6348
7087
|
return next;
|
|
6349
7088
|
}
|
|
6350
7089
|
function writePerCrewSettingsLocal(o) {
|
|
6351
|
-
const dir =
|
|
7090
|
+
const dir = join18(o.projectCwd, ".claude");
|
|
6352
7091
|
mkdirSync7(dir, { recursive: true });
|
|
6353
|
-
const file =
|
|
7092
|
+
const file = join18(dir, "settings.local.json");
|
|
6354
7093
|
let existing = {};
|
|
6355
7094
|
try {
|
|
6356
7095
|
const raw = healStaleCockpitRefs(readFileSync9(file, "utf-8"));
|
|
@@ -6363,304 +7102,86 @@ function writePerCrewSettingsLocal(o) {
|
|
|
6363
7102
|
return file;
|
|
6364
7103
|
}
|
|
6365
7104
|
function writePerCrewOpencodeConfig(o) {
|
|
6366
|
-
const dir =
|
|
7105
|
+
const dir = join18(o.stateRoot, o.project, o.taskId);
|
|
6367
7106
|
mkdirSync7(dir, { recursive: true });
|
|
6368
|
-
const file =
|
|
7107
|
+
const file = join18(dir, "opencode.json");
|
|
6369
7108
|
const config = {
|
|
6370
7109
|
permission: {
|
|
6371
7110
|
read: "allow",
|
|
6372
7111
|
edit: "allow",
|
|
6373
|
-
glob: "allow",
|
|
6374
|
-
grep: "allow",
|
|
6375
|
-
bash: o.gateBash ? "ask" : "allow",
|
|
6376
|
-
webfetch: "allow",
|
|
6377
|
-
websearch: "allow",
|
|
6378
|
-
task: "allow",
|
|
6379
|
-
lsp: "allow",
|
|
6380
|
-
external_directory: { "**": "allow" }
|
|
6381
|
-
}
|
|
6382
|
-
};
|
|
6383
|
-
writeFileSync8(file, JSON.stringify(config, null, 2));
|
|
6384
|
-
return file;
|
|
6385
|
-
}
|
|
6386
|
-
|
|
6387
|
-
// packages/cli/src/commands/crew.ts
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
const config = loadConfig();
|
|
6392
|
-
const proj = config.projects[input.project];
|
|
6393
|
-
if (!proj) {
|
|
6394
|
-
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
6395
|
-
}
|
|
6396
|
-
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(input.project, config);
|
|
6397
|
-
const captain = await runtime.status(proj.captainName);
|
|
6398
|
-
if (!captain) {
|
|
6399
|
-
throw new Error(
|
|
6400
|
-
`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`
|
|
6401
|
-
);
|
|
6402
|
-
}
|
|
6403
|
-
const existing = await listProjectCrews(runtime, captain.id, input.project);
|
|
6404
|
-
const existingTitles = existing.map((s) => s.title);
|
|
6405
|
-
if (input.name) {
|
|
6406
|
-
const wantTitle = titleFor(input.project, input.name);
|
|
6407
|
-
if (existingTitles.includes(wantTitle)) {
|
|
6408
|
-
throw new Error(
|
|
6409
|
-
`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.`
|
|
6410
|
-
);
|
|
6411
|
-
}
|
|
6412
|
-
}
|
|
6413
|
-
const name = input.name ?? nextAutoName(existingTitles, input.project);
|
|
6414
|
-
const spawnCwd = !input.shared ? addWorktree({
|
|
6415
|
-
repoRoot: proj.path,
|
|
6416
|
-
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6417
|
-
project: input.project,
|
|
6418
|
-
name,
|
|
6419
|
-
base: resolveWorktreeBase(proj.path)
|
|
6420
|
-
}) : proj.path;
|
|
6421
|
-
const route = !input.agentExplicit && !input.model ? resolveCrewRoute(input.task, config) : null;
|
|
6422
|
-
if (route) {
|
|
6423
|
-
console.log(chalk9.dim(`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`));
|
|
6424
|
-
}
|
|
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);
|
|
6425
7130
|
const agents = new CapabilityRegistry({
|
|
6426
7131
|
claude: createClaudeDriver(),
|
|
6427
7132
|
codex: createCodexDriver(),
|
|
6428
7133
|
gemini: createGeminiDriver(),
|
|
6429
7134
|
opencode: createOpencodeDriver()
|
|
6430
7135
|
});
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
}
|
|
6451
|
-
const promptFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
|
|
6452
|
-
const interactive = agent.name === "claude" || agent.name === "opencode";
|
|
6453
|
-
const crewRole = config.defaults.roles?.crew;
|
|
6454
|
-
const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : void 0;
|
|
6455
|
-
const crewModel = input.model ?? route?.model ?? configModel;
|
|
6456
|
-
if (agentName === "claude") {
|
|
6457
|
-
const req = buildDispatchRequest({
|
|
6458
|
-
provider: "claude",
|
|
6459
|
-
mode: "interactive",
|
|
6460
|
-
project: input.project,
|
|
6461
|
-
cwd: spawnCwd,
|
|
6462
|
-
task: input.task,
|
|
6463
|
-
name
|
|
6464
|
-
});
|
|
6465
|
-
const rec = await squadrantdCall(req);
|
|
6466
|
-
writePerCrewSettingsLocal({ projectCwd: spawnCwd });
|
|
6467
|
-
const cliCommand2 = agent.buildCommand({
|
|
6468
|
-
prompt: input.task,
|
|
6469
|
-
workdir: spawnCwd,
|
|
6470
|
-
role: "crew",
|
|
6471
|
-
promptFile,
|
|
6472
|
-
interactive: true,
|
|
6473
|
-
// Permission mode is config-driven (defaults.permissions.crew) so squadrant
|
|
6474
|
-
// can default crews to 'auto' or keep the semi-automatic 'acceptEdits'
|
|
6475
|
-
// gate (auto-accept edits, still prompt for risky ops). Falls back to
|
|
6476
|
-
// 'acceptEdits' when unset.
|
|
6477
|
-
permissionMode: config.defaults.permissions?.crew ?? "acceptEdits",
|
|
6478
|
-
...crewModel ? { model: crewModel } : {}
|
|
6479
|
-
});
|
|
6480
|
-
const direction2 = input.direction ?? "tab";
|
|
6481
|
-
const title2 = titleFor(input.project, name);
|
|
6482
|
-
const pane2 = await runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
6483
|
-
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
6484
|
-
await runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${cliCommand2}`);
|
|
6485
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane2) ?? "";
|
|
6486
|
-
await sendFirstTurnWhenReady(runtime, pane2, `${input.task}
|
|
6487
|
-
|
|
6488
|
-
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);
|
|
6489
|
-
return { ...pane2, title: title2 };
|
|
6490
|
-
}
|
|
6491
|
-
if (agentName === "opencode") {
|
|
6492
|
-
const serverPort = await getFreePort();
|
|
6493
|
-
const req = buildDispatchRequest({
|
|
6494
|
-
provider: "opencode",
|
|
6495
|
-
mode: "interactive",
|
|
6496
|
-
project: input.project,
|
|
6497
|
-
cwd: spawnCwd,
|
|
6498
|
-
task: input.task,
|
|
6499
|
-
name,
|
|
6500
|
-
// opencode has no heartbeat hook, so a normal budget would false-stall
|
|
6501
|
-
// every crew after 5min; use a 24h budget to effectively disable stall
|
|
6502
|
-
// detection. The SSE bridge (serverPort) provides turn-end liveness.
|
|
6503
|
-
budgetMs: 864e5,
|
|
6504
|
-
serverPort
|
|
6505
|
-
});
|
|
6506
|
-
const rec = await squadrantdCall(req);
|
|
6507
|
-
const opencodeConfigPath = writePerCrewOpencodeConfig({
|
|
6508
|
-
stateRoot: path20.join(os10.homedir(), ".config", "squadrant", "state"),
|
|
6509
|
-
project: input.project,
|
|
6510
|
-
taskId: rec.id,
|
|
6511
|
-
// CP3 opt-in: --approval gates bash so the captain approves shell commands.
|
|
6512
|
-
// Without it, bash stays auto-approved (default behavior unchanged).
|
|
6513
|
-
...input.approval ? { gateBash: true } : {}
|
|
6514
|
-
});
|
|
6515
|
-
const cliCommand2 = agent.buildCommand({
|
|
6516
|
-
prompt: input.task,
|
|
6517
|
-
workdir: spawnCwd,
|
|
6518
|
-
role: "crew",
|
|
6519
|
-
promptFile,
|
|
6520
|
-
interactive: true,
|
|
6521
|
-
model: crewModel,
|
|
6522
|
-
port: serverPort
|
|
6523
|
-
});
|
|
6524
|
-
const direction2 = input.direction ?? "tab";
|
|
6525
|
-
const title2 = titleFor(input.project, name);
|
|
6526
|
-
const pane2 = await runtime.newPane({ workspaceId: captain.id, direction: direction2, title: title2 });
|
|
6527
|
-
const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;
|
|
6528
|
-
await runtime.sendToPane(pane2, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${cliCommand2}`);
|
|
6529
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane2) ?? "";
|
|
6530
|
-
await sendFirstTurnWhenReady(runtime, pane2, `${input.task}
|
|
6531
|
-
|
|
6532
|
-
${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {
|
|
6533
|
-
// #235: opencode's idle splash ("Ask anything…") keeps mutating (cursor
|
|
6534
|
-
// blink, status line toggle), so the old screen-changed check would always
|
|
6535
|
-
// see a *different* screen and never re-send a dropped turn. The splashMarker
|
|
6536
|
-
// confirms the TUI actually left splash before declaring acceptance.
|
|
6537
|
-
splashMarker: "Ask anything\u2026",
|
|
6538
|
-
// opencode has a wider boot-race window than claude, so allow 3 retries
|
|
6539
|
-
// instead of the default 2.
|
|
6540
|
-
retryLimit: 3
|
|
6541
|
-
});
|
|
6542
|
-
return { ...pane2, title: title2 };
|
|
6543
|
-
}
|
|
6544
|
-
const cliCommand = agent.buildCommand({
|
|
6545
|
-
prompt: input.task,
|
|
6546
|
-
workdir: spawnCwd,
|
|
6547
|
-
role: "crew",
|
|
6548
|
-
promptFile,
|
|
6549
|
-
interactive,
|
|
6550
|
-
model: crewModel
|
|
6551
|
-
});
|
|
6552
|
-
const direction = input.direction ?? "tab";
|
|
6553
|
-
const title = titleFor(input.project, name);
|
|
6554
|
-
const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
6555
|
-
await runtime.sendToPane(pane, cliCommand);
|
|
6556
|
-
if (interactive) {
|
|
6557
|
-
const preLaunchScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
6558
|
-
await sendFirstTurnWhenReady(runtime, pane, input.task, preLaunchScreen);
|
|
6559
|
-
}
|
|
6560
|
-
return { ...pane, title };
|
|
6561
|
-
}
|
|
6562
|
-
async function runCodexInteractiveSpawn(o) {
|
|
6563
|
-
const req = buildDispatchRequest({
|
|
6564
|
-
provider: "codex",
|
|
6565
|
-
mode: "interactive",
|
|
6566
|
-
project: o.project,
|
|
6567
|
-
cwd: o.cwd,
|
|
6568
|
-
task: o.task,
|
|
6569
|
-
name: o.name,
|
|
6570
|
-
...o.approvalPolicy ? { approvalPolicy: o.approvalPolicy } : {},
|
|
6571
|
-
...o.roleInstructions ? { roleInstructions: o.roleInstructions } : {}
|
|
6572
|
-
});
|
|
6573
|
-
const rec = await squadrantdCall(req);
|
|
6574
|
-
const title = titleFor(o.project, o.name);
|
|
6575
|
-
const pane = await o.runtime.newPane({
|
|
6576
|
-
workspaceId: o.workspaceId,
|
|
6577
|
-
direction: o.direction,
|
|
6578
|
-
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
|
+
)
|
|
6579
7155
|
});
|
|
6580
|
-
await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);
|
|
6581
|
-
if (o.task && o.task !== "(interactive)") {
|
|
6582
|
-
void sendCodexFirstTurn(rec.id, o.task).catch((e) => {
|
|
6583
|
-
process.stderr.write(`(first-turn delivery failed: ${e.message})
|
|
6584
|
-
`);
|
|
6585
|
-
});
|
|
6586
|
-
}
|
|
6587
|
-
return { ...pane, title };
|
|
6588
7156
|
}
|
|
6589
|
-
async function
|
|
7157
|
+
async function runCrewSend2(project, name, message) {
|
|
6590
7158
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
try {
|
|
6596
|
-
const tasks = await squadrantdCall({ kind: "list", project });
|
|
6597
|
-
const task = tasks.find((t) => t.name === name);
|
|
6598
|
-
if (task) {
|
|
6599
|
-
if (TERMINAL_STATES.has(task.state)) {
|
|
6600
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.reopened", id: task.id } });
|
|
6601
|
-
} else if (task.state === "blocked" || task.state === "awaiting-input") {
|
|
6602
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.started", id: task.id } });
|
|
6603
|
-
}
|
|
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 });
|
|
6604
7163
|
}
|
|
6605
|
-
}
|
|
6606
|
-
}
|
|
6607
|
-
await runtime.sendToPane(crew, message);
|
|
7164
|
+
});
|
|
6608
7165
|
}
|
|
6609
|
-
async function
|
|
7166
|
+
async function runCrewRead2(project, name) {
|
|
6610
7167
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6611
|
-
|
|
6612
|
-
if (!crew) {
|
|
6613
|
-
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6614
|
-
}
|
|
6615
|
-
return runtime.readPaneScreen(crew);
|
|
7168
|
+
return runCrewRead(project, name, runtime, workspaceId);
|
|
6616
7169
|
}
|
|
6617
|
-
async function
|
|
7170
|
+
async function runCrewClose2(project, name) {
|
|
6618
7171
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
taskId = task.id;
|
|
6627
|
-
if (task.cwd && projRoot && task.cwd !== projRoot) {
|
|
6628
|
-
worktreeCwd = task.cwd;
|
|
6629
|
-
}
|
|
6630
|
-
if (!TERMINAL_STATES.has(task.state)) {
|
|
6631
|
-
await squadrantdCall({ kind: "event", project, event: { type: "task.cancelled", id: task.id, reason: "closed by captain" } });
|
|
6632
|
-
}
|
|
6633
|
-
if (task.provider === "codex") {
|
|
6634
|
-
await squadrantdCall({ kind: "codex-close", taskId: task.id });
|
|
6635
|
-
}
|
|
6636
|
-
}
|
|
6637
|
-
} catch {
|
|
6638
|
-
}
|
|
6639
|
-
const crew = await findCrew(runtime, workspaceId, project, name);
|
|
6640
|
-
if (crew) {
|
|
6641
|
-
await runtime.closePane(crew);
|
|
6642
|
-
} else if (taskId === void 0) {
|
|
6643
|
-
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
6644
|
-
}
|
|
6645
|
-
if (taskId !== void 0) {
|
|
6646
|
-
await reapCrewChildren(taskId);
|
|
6647
|
-
}
|
|
6648
|
-
if (worktreeCwd && projRoot) {
|
|
6649
|
-
try {
|
|
6650
|
-
removeWorktree(projRoot, worktreeCwd);
|
|
6651
|
-
} catch (e) {
|
|
6652
|
-
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6653
|
-
`);
|
|
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 });
|
|
6654
7179
|
}
|
|
6655
|
-
}
|
|
7180
|
+
});
|
|
6656
7181
|
}
|
|
6657
|
-
async function
|
|
7182
|
+
async function runCrewList2(project) {
|
|
6658
7183
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6659
|
-
|
|
6660
|
-
return crews.map((c) => ({
|
|
6661
|
-
name: nameFromTitle(project, c.title),
|
|
6662
|
-
surfaceId: c.surfaceId
|
|
6663
|
-
}));
|
|
7184
|
+
return runCrewList(project, runtime, workspaceId);
|
|
6664
7185
|
}
|
|
6665
7186
|
var crewCommand = new Command9("crew").description(
|
|
6666
7187
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
@@ -6672,7 +7193,7 @@ crewCommand.command("spawn").description(
|
|
|
6672
7193
|
try {
|
|
6673
7194
|
const resolvedTask = await resolveTextInput({ positional: task, filePath: opts.taskFile, label: "task" });
|
|
6674
7195
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
6675
|
-
const pane = await
|
|
7196
|
+
const pane = await runCrewSpawn2({
|
|
6676
7197
|
project,
|
|
6677
7198
|
task: resolvedTask,
|
|
6678
7199
|
name: opts.name,
|
|
@@ -6694,7 +7215,7 @@ crewCommand.command("spawn").description(
|
|
|
6694
7215
|
);
|
|
6695
7216
|
crewCommand.command("list").description("List live crew sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6696
7217
|
try {
|
|
6697
|
-
const crews = await
|
|
7218
|
+
const crews = await runCrewList2(project);
|
|
6698
7219
|
if (crews.length === 0) {
|
|
6699
7220
|
console.log(chalk9.yellow(`No live crew sessions for ${project}.`));
|
|
6700
7221
|
return;
|
|
@@ -6710,7 +7231,7 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
6710
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) => {
|
|
6711
7232
|
try {
|
|
6712
7233
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
6713
|
-
await
|
|
7234
|
+
await runCrewSend2(project, name, resolvedMessage);
|
|
6714
7235
|
console.log(chalk9.green(`\u2714 Sent to ${project}:${name}`));
|
|
6715
7236
|
} catch (err) {
|
|
6716
7237
|
console.error(chalk9.red(err.message));
|
|
@@ -6719,7 +7240,7 @@ crewCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6719
7240
|
});
|
|
6720
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) => {
|
|
6721
7242
|
try {
|
|
6722
|
-
const screen = await
|
|
7243
|
+
const screen = await runCrewRead2(project, name);
|
|
6723
7244
|
const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
|
|
6724
7245
|
console.log(out);
|
|
6725
7246
|
} catch (err) {
|
|
@@ -6729,7 +7250,7 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
|
|
|
6729
7250
|
});
|
|
6730
7251
|
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
|
|
6731
7252
|
try {
|
|
6732
|
-
await
|
|
7253
|
+
await runCrewClose2(project, name);
|
|
6733
7254
|
console.log(chalk9.green(`\u2714 Closed ${project}:${name}`));
|
|
6734
7255
|
} catch (err) {
|
|
6735
7256
|
console.error(chalk9.red(err.message));
|
|
@@ -6743,90 +7264,23 @@ init_dist3();
|
|
|
6743
7264
|
init_dist4();
|
|
6744
7265
|
init_dist3();
|
|
6745
7266
|
init_dist();
|
|
6746
|
-
|
|
7267
|
+
init_dist2();
|
|
6747
7268
|
import { Command as Command10 } from "commander";
|
|
6748
|
-
import
|
|
7269
|
+
import fs18 from "fs";
|
|
6749
7270
|
import path21 from "path";
|
|
6750
7271
|
import os11 from "os";
|
|
6751
7272
|
import chalk10 from "chalk";
|
|
6752
7273
|
var TEMPLATES_DIR3 = path21.join(os11.homedir(), ".config", "squadrant", "templates");
|
|
6753
|
-
|
|
6754
|
-
function shellQuote2(p) {
|
|
6755
|
-
return "'" + p.replace(/'/g, "'\\''") + "'";
|
|
6756
|
-
}
|
|
6757
|
-
function titleFor2(project, name) {
|
|
6758
|
-
return `\u{1F5D2} ${project}:${name}`;
|
|
6759
|
-
}
|
|
6760
|
-
function isSideTitle(project, title) {
|
|
6761
|
-
return title.startsWith(`\u{1F5D2} ${project}:`);
|
|
6762
|
-
}
|
|
6763
|
-
function nameFromTitle2(project, title) {
|
|
6764
|
-
return title.slice(`\u{1F5D2} ${project}:`.length);
|
|
6765
|
-
}
|
|
6766
|
-
function nextAutoName2(existingTitles, project) {
|
|
6767
|
-
const used = /* @__PURE__ */ new Set();
|
|
6768
|
-
for (const title of existingTitles) {
|
|
6769
|
-
const n = nameFromTitle2(project, title).match(/^side-(\d+)$/);
|
|
6770
|
-
if (n) used.add(Number(n[1]));
|
|
6771
|
-
}
|
|
6772
|
-
let i = 1;
|
|
6773
|
-
while (used.has(i)) i++;
|
|
6774
|
-
return `side-${i}`;
|
|
6775
|
-
}
|
|
6776
|
-
function buildSideFirstTurn(topic, project, role, spokeVault, scratchWorktree) {
|
|
6777
|
-
const lines = [
|
|
6778
|
-
topic,
|
|
6779
|
-
"",
|
|
6780
|
-
"---",
|
|
6781
|
-
"Side-session context (for handoff use):",
|
|
6782
|
-
`Project: ${project}`,
|
|
6783
|
-
`Role: ${role}`,
|
|
6784
|
-
`Spoke vault: ${spokeVault}`
|
|
6785
|
-
];
|
|
6786
|
-
if (scratchWorktree) {
|
|
6787
|
-
lines.push(`Scratch worktree: ${scratchWorktree}`);
|
|
6788
|
-
}
|
|
6789
|
-
return lines.join("\n");
|
|
6790
|
-
}
|
|
6791
|
-
async function runSideSpawn(input) {
|
|
7274
|
+
async function runSideSpawn2(input) {
|
|
6792
7275
|
const config = loadConfig();
|
|
6793
7276
|
const proj = config.projects[input.project];
|
|
6794
7277
|
if (!proj) {
|
|
6795
7278
|
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
6796
7279
|
}
|
|
6797
|
-
if (!SIDE_ROLES.includes(input.role)) {
|
|
6798
|
-
throw new Error(
|
|
6799
|
-
`Unknown side role '${input.role}'. Valid roles: ${SIDE_ROLES.join(", ")}.`
|
|
6800
|
-
);
|
|
6801
|
-
}
|
|
6802
7280
|
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(
|
|
6803
7281
|
input.project,
|
|
6804
7282
|
config
|
|
6805
7283
|
);
|
|
6806
|
-
const captain = await runtime.status(proj.captainName);
|
|
6807
|
-
if (!captain) {
|
|
6808
|
-
throw new Error(
|
|
6809
|
-
`Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`
|
|
6810
|
-
);
|
|
6811
|
-
}
|
|
6812
|
-
const existing = await runtime.listSurfaces(captain.id);
|
|
6813
|
-
const existingTitles = existing.filter((s) => s.title && isSideTitle(input.project, s.title)).map((s) => s.title);
|
|
6814
|
-
if (input.name) {
|
|
6815
|
-
const wantTitle = titleFor2(input.project, input.name);
|
|
6816
|
-
if (existingTitles.includes(wantTitle)) {
|
|
6817
|
-
throw new Error(
|
|
6818
|
-
`Side session '${input.name}' already exists for ${input.project}.`
|
|
6819
|
-
);
|
|
6820
|
-
}
|
|
6821
|
-
}
|
|
6822
|
-
const name = input.name ?? nextAutoName2(existingTitles, input.project);
|
|
6823
|
-
const spawnCwd = input.role === "debug" ? addWorktree({
|
|
6824
|
-
repoRoot: proj.path,
|
|
6825
|
-
worktreeDir: config.defaults.worktreeDir ?? ".worktrees",
|
|
6826
|
-
project: input.project,
|
|
6827
|
-
name,
|
|
6828
|
-
base: resolveWorktreeBase(proj.path)
|
|
6829
|
-
}) : proj.path;
|
|
6830
7284
|
const agents = new CapabilityRegistry({
|
|
6831
7285
|
claude: createClaudeDriver(),
|
|
6832
7286
|
codex: createCodexDriver(),
|
|
@@ -6843,80 +7297,39 @@ async function runSideSpawn(input) {
|
|
|
6843
7297
|
const promptFile = path21.join(
|
|
6844
7298
|
TEMPLATES_DIR3,
|
|
6845
7299
|
`side.${input.role}.${agent.templateSuffix}.md`
|
|
6846
|
-
);
|
|
6847
|
-
const
|
|
6848
|
-
const title = titleFor2(input.project, name);
|
|
6849
|
-
const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });
|
|
6850
|
-
const cliCommand = agent.buildCommand({
|
|
7300
|
+
);
|
|
7301
|
+
const agentCmdFactory = (spawnCwd) => agent.buildCommand({
|
|
6851
7302
|
prompt: input.topic,
|
|
6852
7303
|
workdir: spawnCwd,
|
|
6853
7304
|
role: "side",
|
|
6854
|
-
promptFile:
|
|
7305
|
+
promptFile: fs18.existsSync(promptFile) ? promptFile : void 0,
|
|
6855
7306
|
interactive: true,
|
|
6856
7307
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
6857
7308
|
...sideModel ? { model: sideModel } : {}
|
|
6858
7309
|
});
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
const firstTurn = buildSideFirstTurn(
|
|
6862
|
-
input.topic,
|
|
6863
|
-
input.project,
|
|
6864
|
-
input.role,
|
|
6865
|
-
proj.spokeVault ?? "",
|
|
6866
|
-
input.role === "debug" ? spawnCwd : void 0
|
|
6867
|
-
);
|
|
6868
|
-
await sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen);
|
|
6869
|
-
return { ...pane, title };
|
|
7310
|
+
const sendFirstTurn = (pane, firstTurn, preLaunchScreen) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen);
|
|
7311
|
+
return runSideSpawn(input, config, { runtime, agentCmdFactory, sendFirstTurn });
|
|
6870
7312
|
}
|
|
6871
|
-
async function
|
|
7313
|
+
async function runSideSend2(project, name, message) {
|
|
6872
7314
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6873
|
-
|
|
6874
|
-
const surfaces = await runtime.listSurfaces(workspaceId);
|
|
6875
|
-
const pane = surfaces.find((s) => s.title === want) ?? null;
|
|
6876
|
-
if (!pane) {
|
|
6877
|
-
throw new Error(
|
|
6878
|
-
`Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`
|
|
6879
|
-
);
|
|
6880
|
-
}
|
|
6881
|
-
await runtime.sendToPane(pane, message);
|
|
7315
|
+
await runSideSend(runtime, workspaceId, project, name, message);
|
|
6882
7316
|
}
|
|
6883
|
-
async function
|
|
7317
|
+
async function runSideList2(project) {
|
|
6884
7318
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6885
|
-
|
|
6886
|
-
return surfaces.filter((s) => s.title && isSideTitle(project, s.title)).map((s) => ({
|
|
6887
|
-
name: nameFromTitle2(project, s.title),
|
|
6888
|
-
surfaceId: s.surfaceId
|
|
6889
|
-
}));
|
|
7319
|
+
return runSideList(runtime, workspaceId, project);
|
|
6890
7320
|
}
|
|
6891
|
-
async function
|
|
7321
|
+
async function runSideClose2(project, name) {
|
|
6892
7322
|
const config = loadConfig();
|
|
6893
7323
|
const proj = config.projects[project];
|
|
6894
7324
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
await runtime.closePane(pane);
|
|
6904
|
-
if (proj) {
|
|
6905
|
-
const wtPath = worktreePath(
|
|
6906
|
-
proj.path,
|
|
6907
|
-
config.defaults.worktreeDir ?? ".worktrees",
|
|
6908
|
-
project,
|
|
6909
|
-
name
|
|
6910
|
-
);
|
|
6911
|
-
if (fs17.existsSync(wtPath)) {
|
|
6912
|
-
try {
|
|
6913
|
-
removeWorktree(proj.path, wtPath);
|
|
6914
|
-
} catch (e) {
|
|
6915
|
-
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
6916
|
-
`);
|
|
6917
|
-
}
|
|
6918
|
-
}
|
|
6919
|
-
}
|
|
7325
|
+
await runSideClose(
|
|
7326
|
+
runtime,
|
|
7327
|
+
workspaceId,
|
|
7328
|
+
project,
|
|
7329
|
+
name,
|
|
7330
|
+
proj?.path,
|
|
7331
|
+
config.defaults.worktreeDir ?? ".worktrees"
|
|
7332
|
+
);
|
|
6920
7333
|
}
|
|
6921
7334
|
var sideCommand = new Command10("side").description(
|
|
6922
7335
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
@@ -6935,7 +7348,7 @@ sideCommand.command("spawn").description(
|
|
|
6935
7348
|
filePath: opts.topicFile,
|
|
6936
7349
|
label: "topic"
|
|
6937
7350
|
});
|
|
6938
|
-
const pane = await
|
|
7351
|
+
const pane = await runSideSpawn2({
|
|
6939
7352
|
project,
|
|
6940
7353
|
topic: resolvedTopic,
|
|
6941
7354
|
role: opts.role,
|
|
@@ -6952,7 +7365,7 @@ sideCommand.command("spawn").description(
|
|
|
6952
7365
|
);
|
|
6953
7366
|
sideCommand.command("list").description("List live side-sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6954
7367
|
try {
|
|
6955
|
-
const sessions = await
|
|
7368
|
+
const sessions = await runSideList2(project);
|
|
6956
7369
|
if (sessions.length === 0) {
|
|
6957
7370
|
console.log(chalk10.yellow(`No live side-sessions for ${project}.`));
|
|
6958
7371
|
return;
|
|
@@ -6973,7 +7386,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6973
7386
|
filePath: opts.messageFile,
|
|
6974
7387
|
label: "message"
|
|
6975
7388
|
});
|
|
6976
|
-
await
|
|
7389
|
+
await runSideSend2(project, name, resolvedMessage);
|
|
6977
7390
|
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
6978
7391
|
} catch (err) {
|
|
6979
7392
|
console.error(chalk10.red(err.message));
|
|
@@ -6983,7 +7396,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6983
7396
|
);
|
|
6984
7397
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
6985
7398
|
try {
|
|
6986
|
-
await
|
|
7399
|
+
await runSideClose2(project, name);
|
|
6987
7400
|
console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
|
|
6988
7401
|
} catch (err) {
|
|
6989
7402
|
console.error(chalk10.red(err.message));
|
|
@@ -6996,8 +7409,8 @@ init_dist();
|
|
|
6996
7409
|
init_dist3();
|
|
6997
7410
|
import { Command as Command11 } from "commander";
|
|
6998
7411
|
import { execSync as execSync10 } from "child_process";
|
|
6999
|
-
import { homedir as
|
|
7000
|
-
import { join as
|
|
7412
|
+
import { homedir as homedir14 } from "os";
|
|
7413
|
+
import { join as join20 } from "path";
|
|
7001
7414
|
import chalk12 from "chalk";
|
|
7002
7415
|
|
|
7003
7416
|
// packages/web/dist/read-status.js
|
|
@@ -7136,7 +7549,7 @@ function renderDashboard(rows, opts) {
|
|
|
7136
7549
|
|
|
7137
7550
|
// packages/web/dist/sync-hub.js
|
|
7138
7551
|
init_dist();
|
|
7139
|
-
import
|
|
7552
|
+
import fs19 from "fs";
|
|
7140
7553
|
import path22 from "path";
|
|
7141
7554
|
function buildMirrorMarkdown(s) {
|
|
7142
7555
|
const fenced = "```";
|
|
@@ -7163,8 +7576,8 @@ function buildMirrorMarkdown(s) {
|
|
|
7163
7576
|
function syncHub(deps) {
|
|
7164
7577
|
if (!deps.config.hubVault)
|
|
7165
7578
|
return [];
|
|
7166
|
-
const writeFile5 = deps.writeFile ?? ((p, c) =>
|
|
7167
|
-
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 }));
|
|
7168
7581
|
const projectsDir = path22.join(resolveHome(deps.config.hubVault), "projects");
|
|
7169
7582
|
mkdir5(projectsDir);
|
|
7170
7583
|
const out = [];
|
|
@@ -7193,10 +7606,10 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
7193
7606
|
// packages/web/dist/probes.js
|
|
7194
7607
|
init_dist();
|
|
7195
7608
|
init_dist();
|
|
7196
|
-
import { join as
|
|
7197
|
-
import { homedir as
|
|
7609
|
+
import { join as join19 } from "path";
|
|
7610
|
+
import { homedir as homedir13 } from "os";
|
|
7198
7611
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
7199
|
-
import { execFile as
|
|
7612
|
+
import { execFile as execFile3 } from "child_process";
|
|
7200
7613
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
7201
7614
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
7202
7615
|
function withTimeout2(p, ms) {
|
|
@@ -7231,7 +7644,7 @@ function vaultProbe(run, dir) {
|
|
|
7231
7644
|
return { state: "unknown", detail: "no vault configured" };
|
|
7232
7645
|
if (!run.pathExists(dir))
|
|
7233
7646
|
return { state: "gone", detail: "vault directory missing" };
|
|
7234
|
-
if (!run.pathExists(
|
|
7647
|
+
if (!run.pathExists(join19(dir, ".obsidian")))
|
|
7235
7648
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
7236
7649
|
return { state: "alive" };
|
|
7237
7650
|
} catch {
|
|
@@ -7299,10 +7712,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
7299
7712
|
const sessions = probeSessions(run);
|
|
7300
7713
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
7301
7714
|
}
|
|
7302
|
-
var SESSIONS_PATH =
|
|
7715
|
+
var SESSIONS_PATH = join19(homedir13(), ".config", "squadrant", "sessions.json");
|
|
7303
7716
|
function onPath(cli) {
|
|
7304
7717
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
7305
|
-
return dirs.some((d) => existsSync10(
|
|
7718
|
+
return dirs.some((d) => existsSync10(join19(d, cli)));
|
|
7306
7719
|
}
|
|
7307
7720
|
function readSessionsHashes() {
|
|
7308
7721
|
const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
|
|
@@ -7313,7 +7726,7 @@ function defaultProbeRunners() {
|
|
|
7313
7726
|
return {
|
|
7314
7727
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
7315
7728
|
try {
|
|
7316
|
-
|
|
7729
|
+
execFile3(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
7317
7730
|
} catch {
|
|
7318
7731
|
resolve3(false);
|
|
7319
7732
|
}
|
|
@@ -7972,7 +8385,7 @@ async function startWebServer(opts) {
|
|
|
7972
8385
|
|
|
7973
8386
|
// packages/cli/src/commands/dashboard.ts
|
|
7974
8387
|
init_dist();
|
|
7975
|
-
var SOCK3 =
|
|
8388
|
+
var SOCK3 = join20(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
7976
8389
|
function detectCurrentWorkspace2() {
|
|
7977
8390
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
7978
8391
|
const match = out.match(/workspace:\d+/);
|
|
@@ -8055,12 +8468,11 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
8055
8468
|
init_dist();
|
|
8056
8469
|
init_dist4();
|
|
8057
8470
|
init_dist3();
|
|
8058
|
-
|
|
8059
|
-
init_dist3();
|
|
8471
|
+
init_dist2();
|
|
8060
8472
|
init_dist2();
|
|
8061
8473
|
import { Command as Command12 } from "commander";
|
|
8062
8474
|
import { execSync as execSync11 } from "child_process";
|
|
8063
|
-
import
|
|
8475
|
+
import fs20 from "fs";
|
|
8064
8476
|
import path23 from "path";
|
|
8065
8477
|
import os12 from "os";
|
|
8066
8478
|
import chalk13 from "chalk";
|
|
@@ -8074,64 +8486,6 @@ function ensureCmuxReady() {
|
|
|
8074
8486
|
console.log(chalk13.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
8075
8487
|
process.exit(0);
|
|
8076
8488
|
}
|
|
8077
|
-
async function deliverStartupPrompt(runtime, refId, prompt, opts = {}) {
|
|
8078
|
-
const readyTimeoutMs = opts.readyTimeoutMs ?? 3e4;
|
|
8079
|
-
const settleMs = opts.settleMs ?? 2500;
|
|
8080
|
-
const pollMs = opts.pollMs ?? 1e3;
|
|
8081
|
-
const maxAttempts = opts.maxAttempts ?? 3;
|
|
8082
|
-
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8083
|
-
const read = async () => runtime.readScreen(refId).catch(() => "");
|
|
8084
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
8085
|
-
const deadline = Date.now() + readyTimeoutMs;
|
|
8086
|
-
let preSend = await read();
|
|
8087
|
-
let state = classifyStartupSurface(preSend);
|
|
8088
|
-
while (state === "loading" && Date.now() < deadline) {
|
|
8089
|
-
await sleep2(pollMs);
|
|
8090
|
-
preSend = await read();
|
|
8091
|
-
state = classifyStartupSurface(preSend);
|
|
8092
|
-
}
|
|
8093
|
-
if (state === "working") return;
|
|
8094
|
-
await runtime.send(refId, prompt).catch(() => {
|
|
8095
|
-
});
|
|
8096
|
-
if (state === "loading") return;
|
|
8097
|
-
await sleep2(settleMs);
|
|
8098
|
-
const after = await read();
|
|
8099
|
-
if (after !== preSend) return;
|
|
8100
|
-
}
|
|
8101
|
-
}
|
|
8102
|
-
async function launchWorkspace(runtime, name, agentCmd, cwd, navigate = false, forceFresh = false, pinToTop = false, initialPrompt) {
|
|
8103
|
-
ensureCmuxReady();
|
|
8104
|
-
const existing = await runtime.status(name);
|
|
8105
|
-
if (existing && forceFresh) {
|
|
8106
|
-
console.log(chalk13.yellow(` Closing stale workspace '${name}' for fresh start`));
|
|
8107
|
-
await runtime.stop(existing.id);
|
|
8108
|
-
} else if (existing) {
|
|
8109
|
-
console.log(chalk13.yellow(` Workspace '${name}' already exists \u2014 switching to it`));
|
|
8110
|
-
cmuxLocal(["select-workspace", "--workspace", existing.id]);
|
|
8111
|
-
return;
|
|
8112
|
-
}
|
|
8113
|
-
let currentRef;
|
|
8114
|
-
try {
|
|
8115
|
-
const cur = cmuxLocal(["current-workspace"]);
|
|
8116
|
-
currentRef = cur.match(/workspace:\d+/)?.[0];
|
|
8117
|
-
} catch {
|
|
8118
|
-
}
|
|
8119
|
-
const ref = await runtime.spawn({
|
|
8120
|
-
name,
|
|
8121
|
-
workdir: cwd ?? process.cwd(),
|
|
8122
|
-
command: agentCmd,
|
|
8123
|
-
pinToTop
|
|
8124
|
-
});
|
|
8125
|
-
if (initialPrompt) {
|
|
8126
|
-
void deliverStartupPrompt(runtime, ref.id, initialPrompt);
|
|
8127
|
-
}
|
|
8128
|
-
if (navigate) {
|
|
8129
|
-
cmuxLocal(["select-workspace", "--workspace", ref.id]);
|
|
8130
|
-
} else if (currentRef) {
|
|
8131
|
-
cmuxLocal(["select-workspace", "--workspace", currentRef]);
|
|
8132
|
-
}
|
|
8133
|
-
console.log(chalk13.green(` \u2714 Workspace '${name}' created`));
|
|
8134
|
-
}
|
|
8135
8489
|
var launchCommand = new Command12("launch").description(
|
|
8136
8490
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
8137
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) => {
|
|
@@ -8145,19 +8499,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
8145
8499
|
const registry = new CapabilityRegistry(drivers);
|
|
8146
8500
|
const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
8147
8501
|
async function launchOne(workspaceName, role, cwd, permissionMode, navigate, pinToTop = false, projectName) {
|
|
8148
|
-
|
|
8149
|
-
if (!forceFresh) {
|
|
8150
|
-
const auto = shouldStartFresh(workspaceName, role, { sessionsPath: SESSIONS_PATH2, templatesDir: TEMPLATES_DIR4 });
|
|
8151
|
-
if (auto.fresh) {
|
|
8152
|
-
console.log(chalk13.cyan(` \u21BB ${auto.reason}`));
|
|
8153
|
-
forceFresh = true;
|
|
8154
|
-
}
|
|
8155
|
-
}
|
|
8502
|
+
ensureCmuxReady();
|
|
8156
8503
|
const roleConfig = config.defaults.roles?.[role];
|
|
8157
8504
|
const agentName = roleConfig?.agent || "claude";
|
|
8158
8505
|
const model = roleConfig?.model || config.defaults.models?.[role];
|
|
8159
|
-
const agentCmd = buildAgentCmd(agentName, registry, role, forceFresh, permissionMode, model, TEMPLATES_DIR4);
|
|
8160
|
-
recordSession(workspaceName, role, { sessionsPath: SESSIONS_PATH2, templatesDir: TEMPLATES_DIR4 });
|
|
8161
8506
|
let initialPrompt;
|
|
8162
8507
|
if (role === "captain") {
|
|
8163
8508
|
initialPrompt = "Run your startup checklist: use the squadrant:captain-ops skill, complete all startup steps, then report ready.";
|
|
@@ -8166,19 +8511,44 @@ var launchCommand = new Command12("launch").description(
|
|
|
8166
8511
|
}
|
|
8167
8512
|
const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
|
|
8168
8513
|
try {
|
|
8169
|
-
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
|
+
});
|
|
8170
8540
|
} catch (err) {
|
|
8171
8541
|
console.error(chalk13.red(` \u2718 Failed: ${err.message}`));
|
|
8172
8542
|
}
|
|
8173
8543
|
}
|
|
8174
8544
|
if (opts.all) {
|
|
8175
8545
|
const hubPath = resolveHome(config.hubVault);
|
|
8176
|
-
|
|
8546
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
8177
8547
|
console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
|
|
8178
8548
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
8179
8549
|
const projPath = resolveHome(proj.path);
|
|
8180
8550
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8181
|
-
if (!
|
|
8551
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8182
8552
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
8183
8553
|
await ensureSpokeLayout(spokeDriver);
|
|
8184
8554
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8209,7 +8579,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
8209
8579
|
const proj = config.projects[project];
|
|
8210
8580
|
const projPath = resolveHome(proj.path);
|
|
8211
8581
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8212
|
-
if (!
|
|
8582
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8213
8583
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
8214
8584
|
await ensureSpokeLayout(spokeDriver);
|
|
8215
8585
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8344,7 +8714,7 @@ Shutting down captain workspace for '${project}'...
|
|
|
8344
8714
|
// packages/cli/src/commands/feedback.ts
|
|
8345
8715
|
init_dist();
|
|
8346
8716
|
import { Command as Command14 } from "commander";
|
|
8347
|
-
import
|
|
8717
|
+
import fs21 from "fs";
|
|
8348
8718
|
import os13 from "os";
|
|
8349
8719
|
import path24 from "path";
|
|
8350
8720
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -8354,14 +8724,14 @@ var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
|
8354
8724
|
function readPkgVersion() {
|
|
8355
8725
|
try {
|
|
8356
8726
|
const pkgPath = path24.join(path24.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8357
|
-
return JSON.parse(
|
|
8727
|
+
return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
8358
8728
|
} catch {
|
|
8359
8729
|
return "unknown";
|
|
8360
8730
|
}
|
|
8361
8731
|
}
|
|
8362
8732
|
function readMetrics(metricsPath) {
|
|
8363
8733
|
try {
|
|
8364
|
-
return JSON.parse(
|
|
8734
|
+
return JSON.parse(fs21.readFileSync(metricsPath, "utf-8"));
|
|
8365
8735
|
} catch {
|
|
8366
8736
|
return {};
|
|
8367
8737
|
}
|
|
@@ -8421,7 +8791,7 @@ init_dist();
|
|
|
8421
8791
|
init_dist();
|
|
8422
8792
|
init_dist3();
|
|
8423
8793
|
import { Command as Command15 } from "commander";
|
|
8424
|
-
import
|
|
8794
|
+
import fs22 from "fs";
|
|
8425
8795
|
import path25 from "path";
|
|
8426
8796
|
import chalk16 from "chalk";
|
|
8427
8797
|
import matter3 from "gray-matter";
|
|
@@ -8433,9 +8803,9 @@ async function getProjectStandup(name, project, dateStr, registry, config) {
|
|
|
8433
8803
|
const spokeVault = resolveHome(project.spokeVault);
|
|
8434
8804
|
const statusFile = path25.join(spokeVault, "status.md");
|
|
8435
8805
|
let status = {};
|
|
8436
|
-
if (
|
|
8806
|
+
if (fs22.existsSync(statusFile)) {
|
|
8437
8807
|
try {
|
|
8438
|
-
status = matter3(
|
|
8808
|
+
status = matter3(fs22.readFileSync(statusFile, "utf-8")).data;
|
|
8439
8809
|
} catch {
|
|
8440
8810
|
}
|
|
8441
8811
|
}
|
|
@@ -8553,15 +8923,15 @@ init_dist();
|
|
|
8553
8923
|
init_dist();
|
|
8554
8924
|
init_dist3();
|
|
8555
8925
|
import { Command as Command16 } from "commander";
|
|
8556
|
-
import
|
|
8926
|
+
import fs23 from "fs";
|
|
8557
8927
|
import path26 from "path";
|
|
8558
8928
|
import chalk17 from "chalk";
|
|
8559
8929
|
import matter4 from "gray-matter";
|
|
8560
8930
|
function readStatus(spokeVault) {
|
|
8561
8931
|
const statusFile = path26.join(spokeVault, "status.md");
|
|
8562
|
-
if (!
|
|
8932
|
+
if (!fs23.existsSync(statusFile)) return {};
|
|
8563
8933
|
try {
|
|
8564
|
-
return matter4(
|
|
8934
|
+
return matter4(fs23.readFileSync(statusFile, "utf-8")).data;
|
|
8565
8935
|
} catch {
|
|
8566
8936
|
return {};
|
|
8567
8937
|
}
|
|
@@ -8981,7 +9351,7 @@ init_dist3();
|
|
|
8981
9351
|
init_dist();
|
|
8982
9352
|
import { Command as Command20 } from "commander";
|
|
8983
9353
|
import chalk21 from "chalk";
|
|
8984
|
-
import
|
|
9354
|
+
import fs24 from "fs";
|
|
8985
9355
|
import path27 from "path";
|
|
8986
9356
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8987
9357
|
function parseScope(v) {
|
|
@@ -8993,7 +9363,7 @@ function parseScope(v) {
|
|
|
8993
9363
|
function findPackageRoot3() {
|
|
8994
9364
|
let dir = path27.dirname(fileURLToPath4(import.meta.url));
|
|
8995
9365
|
while (dir !== "/" && dir !== "") {
|
|
8996
|
-
if (
|
|
9366
|
+
if (fs24.existsSync(path27.join(dir, "package.json"))) return dir;
|
|
8997
9367
|
dir = path27.dirname(dir);
|
|
8998
9368
|
}
|
|
8999
9369
|
return process.cwd();
|
|
@@ -9187,13 +9557,14 @@ ${transcript.join("\n")}`
|
|
|
9187
9557
|
init_dist();
|
|
9188
9558
|
init_dist();
|
|
9189
9559
|
init_dist();
|
|
9560
|
+
init_dist2();
|
|
9190
9561
|
import { Command as Command22 } from "commander";
|
|
9191
|
-
import
|
|
9562
|
+
import fs25 from "fs";
|
|
9192
9563
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9193
|
-
import { dirname as dirname5, join as
|
|
9564
|
+
import { dirname as dirname5, join as join21 } from "path";
|
|
9194
9565
|
import chalk22 from "chalk";
|
|
9195
9566
|
function runConfigCheck(opts) {
|
|
9196
|
-
const raw = JSON.parse(
|
|
9567
|
+
const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
|
|
9197
9568
|
const def = getDefaultConfig();
|
|
9198
9569
|
const items = detectDrift(raw, def);
|
|
9199
9570
|
let working = raw;
|
|
@@ -9210,7 +9581,7 @@ function runConfigCheck(opts) {
|
|
|
9210
9581
|
stamped = true;
|
|
9211
9582
|
}
|
|
9212
9583
|
if (opts.fix || opts.accept || stamped) {
|
|
9213
|
-
|
|
9584
|
+
fs25.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
|
|
9214
9585
|
}
|
|
9215
9586
|
return { items, applied, remaining, stamped };
|
|
9216
9587
|
}
|
|
@@ -9281,7 +9652,7 @@ function printItems(items) {
|
|
|
9281
9652
|
var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
|
|
9282
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) => {
|
|
9283
9654
|
const pkgVersion = readPkgVersion2();
|
|
9284
|
-
if (!
|
|
9655
|
+
if (!fs25.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
9285
9656
|
console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
|
|
9286
9657
|
return;
|
|
9287
9658
|
}
|
|
@@ -9326,14 +9697,15 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
9326
9697
|
}
|
|
9327
9698
|
});
|
|
9328
9699
|
function readPkgVersion2() {
|
|
9329
|
-
const pkgPath =
|
|
9330
|
-
return JSON.parse(
|
|
9700
|
+
const pkgPath = join21(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
9701
|
+
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
9331
9702
|
}
|
|
9332
9703
|
|
|
9333
9704
|
// packages/cli/src/commands/heal.ts
|
|
9334
9705
|
import { Command as Command23 } from "commander";
|
|
9335
9706
|
import chalk23 from "chalk";
|
|
9336
9707
|
init_dist2();
|
|
9708
|
+
init_dist2();
|
|
9337
9709
|
function buildHealStatus(components) {
|
|
9338
9710
|
if (components === null) {
|
|
9339
9711
|
return { healthy: false, daemonUnreachable: true, components: [] };
|
|
@@ -9424,90 +9796,10 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
9424
9796
|
// packages/cli/src/commands/group.ts
|
|
9425
9797
|
init_dist();
|
|
9426
9798
|
init_dist2();
|
|
9799
|
+
init_dist2();
|
|
9427
9800
|
import { Command as Command24 } from "commander";
|
|
9428
9801
|
import { execSync as execSync13 } from "child_process";
|
|
9429
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
9430
|
-
import { homedir as homedir14 } from "os";
|
|
9431
|
-
import { join as join21 } from "path";
|
|
9432
9802
|
import chalk24 from "chalk";
|
|
9433
|
-
var SOCK4 = join21(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
9434
|
-
var WARMUP_TIMEOUT_MS = 12e4;
|
|
9435
|
-
var WARMUP_POLL_MS = 1e3;
|
|
9436
|
-
function resolveCurrentProject(config) {
|
|
9437
|
-
const cwd = process.cwd();
|
|
9438
|
-
for (const [name, proj] of Object.entries(config.projects)) {
|
|
9439
|
-
const resolvedPath = resolveHome(proj.path);
|
|
9440
|
-
if (cwd.startsWith(resolvedPath)) return name;
|
|
9441
|
-
}
|
|
9442
|
-
return null;
|
|
9443
|
-
}
|
|
9444
|
-
async function isCaptainAlive(project) {
|
|
9445
|
-
try {
|
|
9446
|
-
const health = await sendRequest(SOCK4, { kind: "health", project }, 5e3);
|
|
9447
|
-
const captain = health?.find((h) => h.kind === "captain" && h.project === project);
|
|
9448
|
-
return captain != null && captain.state !== "gone" && captain.state !== "unknown";
|
|
9449
|
-
} catch {
|
|
9450
|
-
return false;
|
|
9451
|
-
}
|
|
9452
|
-
}
|
|
9453
|
-
async function waitForWarmup(project, timeoutMs = WARMUP_TIMEOUT_MS, pollMs = WARMUP_POLL_MS) {
|
|
9454
|
-
const deadline = Date.now() + timeoutMs;
|
|
9455
|
-
while (Date.now() < deadline) {
|
|
9456
|
-
if (await isCaptainAlive(project)) return true;
|
|
9457
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
9458
|
-
}
|
|
9459
|
-
return false;
|
|
9460
|
-
}
|
|
9461
|
-
async function runGroupDispatch(opts) {
|
|
9462
|
-
const config = loadConfig();
|
|
9463
|
-
const fromCfg = config.projects[opts.fromProject];
|
|
9464
|
-
const toCfg = config.projects[opts.toProject];
|
|
9465
|
-
if (!toCfg) {
|
|
9466
|
-
throw new Error(`target project '${opts.toProject}' not found in config`);
|
|
9467
|
-
}
|
|
9468
|
-
if (!fromCfg.group || !toCfg.group || fromCfg.group !== toCfg.group) {
|
|
9469
|
-
throw new Error(
|
|
9470
|
-
`cannot dispatch: '${opts.toProject}' (group: ${toCfg.group ?? "none"}) is not in the same group as '${opts.fromProject}' (group: ${fromCfg.group ?? "none"})`
|
|
9471
|
-
);
|
|
9472
|
-
}
|
|
9473
|
-
if (toCfg.acceptDelegations === false) {
|
|
9474
|
-
throw new Error(
|
|
9475
|
-
`cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`
|
|
9476
|
-
);
|
|
9477
|
-
}
|
|
9478
|
-
const alive = await isCaptainAlive(opts.toProject);
|
|
9479
|
-
if (!alive) {
|
|
9480
|
-
try {
|
|
9481
|
-
execSync13(`squadrant launch ${opts.toProject}`, { stdio: "ignore", timeout: 15e3 });
|
|
9482
|
-
} catch {
|
|
9483
|
-
throw new Error(`failed to launch captain for '${opts.toProject}' \u2014 is squadrant installed?`);
|
|
9484
|
-
}
|
|
9485
|
-
const warmed = await waitForWarmup(opts.toProject, opts.warmupTimeoutMs, opts.warmupPollMs);
|
|
9486
|
-
if (!warmed) {
|
|
9487
|
-
throw new Error(
|
|
9488
|
-
`dispatch to '${opts.toProject}' timed out waiting for captain warmup (>${(opts.warmupTimeoutMs ?? WARMUP_TIMEOUT_MS) / 1e3}s)`
|
|
9489
|
-
);
|
|
9490
|
-
}
|
|
9491
|
-
}
|
|
9492
|
-
const now = Date.now();
|
|
9493
|
-
const attemptId = randomUUID4();
|
|
9494
|
-
const record = {
|
|
9495
|
-
id: randomUUID4(),
|
|
9496
|
-
project: opts.toProject,
|
|
9497
|
-
originProject: opts.fromProject,
|
|
9498
|
-
provider: opts.provider ?? "claude",
|
|
9499
|
-
mode: opts.mode ?? "headless",
|
|
9500
|
-
state: "submitted",
|
|
9501
|
-
task: opts.task,
|
|
9502
|
-
createdAt: now,
|
|
9503
|
-
lastHeartbeat: now,
|
|
9504
|
-
lastEvent: "dispatch",
|
|
9505
|
-
heartbeatBudgetMs: 3e5,
|
|
9506
|
-
attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }]
|
|
9507
|
-
};
|
|
9508
|
-
const result = await sendRequest(SOCK4, { kind: "dispatch", record });
|
|
9509
|
-
return result;
|
|
9510
|
-
}
|
|
9511
9803
|
var groupCommand = new Command24("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
9512
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) => {
|
|
9513
9805
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
@@ -9516,13 +9808,20 @@ var groupCommand = new Command24("group").description("Cross-project intra-group
|
|
|
9516
9808
|
process.exit(1);
|
|
9517
9809
|
}
|
|
9518
9810
|
try {
|
|
9519
|
-
const result = await
|
|
9811
|
+
const result = await dispatchToSibling({
|
|
9520
9812
|
fromProject,
|
|
9521
9813
|
toProject,
|
|
9522
9814
|
task,
|
|
9523
9815
|
provider: opts.provider,
|
|
9524
9816
|
mode: opts.mode,
|
|
9525
|
-
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
|
+
}
|
|
9526
9825
|
});
|
|
9527
9826
|
console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
|
|
9528
9827
|
console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
|
|
@@ -9592,7 +9891,7 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
|
|
|
9592
9891
|
|
|
9593
9892
|
// packages/cli/src/commands/effort.ts
|
|
9594
9893
|
init_dist();
|
|
9595
|
-
import
|
|
9894
|
+
import fs26 from "fs";
|
|
9596
9895
|
import path28 from "path";
|
|
9597
9896
|
import { Command as Command26 } from "commander";
|
|
9598
9897
|
import chalk26 from "chalk";
|
|
@@ -9620,7 +9919,7 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
9620
9919
|
}
|
|
9621
9920
|
function canonical(p) {
|
|
9622
9921
|
try {
|
|
9623
|
-
return
|
|
9922
|
+
return fs26.realpathSync(p);
|
|
9624
9923
|
} catch {
|
|
9625
9924
|
return path28.resolve(p);
|
|
9626
9925
|
}
|
|
@@ -9676,59 +9975,6 @@ import chalk27 from "chalk";
|
|
|
9676
9975
|
function defaultStateRoot() {
|
|
9677
9976
|
return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
9678
9977
|
}
|
|
9679
|
-
function runTelegramStatus(opts) {
|
|
9680
|
-
const tg = opts.config.telegram;
|
|
9681
|
-
const env = opts.env ?? process.env;
|
|
9682
|
-
const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
|
|
9683
|
-
const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
|
|
9684
|
-
const sep2 = key.indexOf("::");
|
|
9685
|
-
return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2), topicId };
|
|
9686
|
-
});
|
|
9687
|
-
return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
|
|
9688
|
-
}
|
|
9689
|
-
async function runTelegramSend(opts) {
|
|
9690
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9691
|
-
if (topicId === void 0) {
|
|
9692
|
-
throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
|
|
9693
|
-
}
|
|
9694
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
|
|
9695
|
-
return { chatId: opts.cfg.supergroupId, topicId };
|
|
9696
|
-
}
|
|
9697
|
-
function runTelegramNotifySet(opts) {
|
|
9698
|
-
setNotify(opts.stateRoot, opts.project, opts.active);
|
|
9699
|
-
}
|
|
9700
|
-
function runTelegramNotifyPref(args) {
|
|
9701
|
-
const { project, dimension, value, root } = args;
|
|
9702
|
-
if (dimension === "crew") {
|
|
9703
|
-
if (!["all", "alert_only", "done_only", "none"].includes(value))
|
|
9704
|
-
return { ok: false, message: "crew must be all|alert_only|done_only|none" };
|
|
9705
|
-
saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
|
|
9706
|
-
return { ok: true };
|
|
9707
|
-
}
|
|
9708
|
-
if (value !== "on" && value !== "off") return { ok: false, message: "cap must be on|off" };
|
|
9709
|
-
saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
|
|
9710
|
-
return { ok: true };
|
|
9711
|
-
}
|
|
9712
|
-
function capAllowed(project, globalNotify, root) {
|
|
9713
|
-
return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
|
|
9714
|
-
}
|
|
9715
|
-
function runTelegramNotifyStatus(opts) {
|
|
9716
|
-
const s = loadState(opts.stateRoot);
|
|
9717
|
-
const projects = /* @__PURE__ */ new Set();
|
|
9718
|
-
for (const key of Object.keys(s.topics)) {
|
|
9719
|
-
const sep2 = key.indexOf("::");
|
|
9720
|
-
projects.add(sep2 === -1 ? key : key.slice(0, sep2));
|
|
9721
|
-
}
|
|
9722
|
-
for (const p of Object.keys(s.notify)) projects.add(p);
|
|
9723
|
-
return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
|
|
9724
|
-
}
|
|
9725
|
-
async function runTelegramLink(opts) {
|
|
9726
|
-
const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9727
|
-
if (existing !== void 0) return { topicId: existing, created: false };
|
|
9728
|
-
const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
|
|
9729
|
-
setTopic(opts.stateRoot, opts.project, topicId);
|
|
9730
|
-
return { topicId, created: true };
|
|
9731
|
-
}
|
|
9732
9978
|
async function questionMasked() {
|
|
9733
9979
|
return new Promise((resolve3) => {
|
|
9734
9980
|
emitKeypressEvents(process.stdin);
|
|
@@ -9772,44 +10018,6 @@ async function questionYesNo(prompt) {
|
|
|
9772
10018
|
});
|
|
9773
10019
|
});
|
|
9774
10020
|
}
|
|
9775
|
-
function confirmationText(project, before, after, dim) {
|
|
9776
|
-
if (dim === "active") return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
|
|
9777
|
-
if (dim === "cap") return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
|
|
9778
|
-
return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
|
|
9779
|
-
}
|
|
9780
|
-
async function runNotifyConfirmation(opts) {
|
|
9781
|
-
const { quieter, dim } = isQuieter(opts.before, opts.after);
|
|
9782
|
-
if (!quieter || dim === null) return false;
|
|
9783
|
-
const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
|
|
9784
|
-
if (topicId === void 0) return false;
|
|
9785
|
-
const text = confirmationText(opts.project, opts.before, opts.after, dim);
|
|
9786
|
-
try {
|
|
9787
|
-
await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
|
|
9788
|
-
return true;
|
|
9789
|
-
} catch {
|
|
9790
|
-
console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
|
|
9791
|
-
return false;
|
|
9792
|
-
}
|
|
9793
|
-
}
|
|
9794
|
-
function resolveSetupToken(existingToken, opts) {
|
|
9795
|
-
if (opts.resetToken || !existingToken) return "prompt";
|
|
9796
|
-
return "try-reuse";
|
|
9797
|
-
}
|
|
9798
|
-
function resolveSetupUserId(flagUserId, detectedUserId, stateRoot) {
|
|
9799
|
-
return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;
|
|
9800
|
-
}
|
|
9801
|
-
async function runRegisterCommands(opts) {
|
|
9802
|
-
await opts.client.setMyCommands(BOT_COMMANDS);
|
|
9803
|
-
}
|
|
9804
|
-
function runTelegramPostSetup(opts) {
|
|
9805
|
-
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
9806
|
-
const outcome = doRestart({ reason: "telegram config" });
|
|
9807
|
-
if (outcome === "skipped-not-running") {
|
|
9808
|
-
console.log(chalk27.dim("(daemon not running \u2014 change applies on next start)"));
|
|
9809
|
-
} else if (outcome === "skipped-opt-out") {
|
|
9810
|
-
console.log(chalk27.dim("(run 'squadrant heal daemon' to apply)"));
|
|
9811
|
-
}
|
|
9812
|
-
}
|
|
9813
10021
|
var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
9814
10022
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
9815
10023
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|