squadrant 0.11.3 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1264 -1011
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +136 -99
- 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
|
@@ -34,14 +34,14 @@ function getDefaultConfig() {
|
|
|
34
34
|
models: {
|
|
35
35
|
command: "opus",
|
|
36
36
|
captain: "opus",
|
|
37
|
-
crew: "
|
|
37
|
+
crew: "sonnet",
|
|
38
38
|
exploration: "haiku",
|
|
39
39
|
review: "opus"
|
|
40
40
|
},
|
|
41
41
|
roles: {
|
|
42
42
|
command: { agent: "claude", model: "opus" },
|
|
43
43
|
captain: { agent: "claude", model: "opus" },
|
|
44
|
-
crew: { agent: "claude", model: "
|
|
44
|
+
crew: { agent: "claude", model: "sonnet" },
|
|
45
45
|
exploration: { agent: "claude", model: "haiku" },
|
|
46
46
|
side: { agent: "claude", model: "opus" }
|
|
47
47
|
},
|
|
@@ -579,7 +579,7 @@ var init_config_drift = __esm({
|
|
|
579
579
|
}
|
|
580
580
|
];
|
|
581
581
|
KNOWN_DEFAULT_HISTORY = [
|
|
582
|
-
{ path: "defaults.roles.crew.model", oldDefaults: ["
|
|
582
|
+
{ path: "defaults.roles.crew.model", oldDefaults: ["opus"] },
|
|
583
583
|
{ path: "defaults.roles.captain.model", oldDefaults: ["sonnet"] }
|
|
584
584
|
];
|
|
585
585
|
KNOWN_DRIVERS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "opencode"]);
|
|
@@ -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,737 @@ 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
|
+
const deliver = deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg));
|
|
2636
|
+
await deliver(crew, message);
|
|
2637
|
+
}
|
|
2638
|
+
async function runCrewRead(project, name, runtime, workspaceId) {
|
|
2639
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
2640
|
+
if (!crew) {
|
|
2641
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
2642
|
+
}
|
|
2643
|
+
return runtime.readPaneScreen(crew);
|
|
2644
|
+
}
|
|
2645
|
+
async function runCrewClose(project, name, runtime, workspaceId, deps) {
|
|
2646
|
+
const projRoot = loadConfig().projects[project]?.path;
|
|
2647
|
+
let taskId;
|
|
2648
|
+
let worktreeCwd;
|
|
2649
|
+
try {
|
|
2650
|
+
const tasks = await deps.listTasks(project);
|
|
2651
|
+
const task = tasks.find((t) => t.name === name);
|
|
2652
|
+
if (task) {
|
|
2653
|
+
taskId = task.id;
|
|
2654
|
+
if (task.cwd && projRoot && task.cwd !== projRoot) {
|
|
2655
|
+
worktreeCwd = task.cwd;
|
|
2656
|
+
}
|
|
2657
|
+
if (!TERMINAL_STATES.has(task.state)) {
|
|
2658
|
+
await deps.emitEvent(project, { type: "task.cancelled", id: task.id, reason: "closed by captain" });
|
|
2659
|
+
}
|
|
2660
|
+
if (task.provider === "codex") {
|
|
2661
|
+
await deps.closeCodexThread(task.id);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
} catch {
|
|
2665
|
+
}
|
|
2666
|
+
const crew = await findCrewPane(runtime, workspaceId, project, name);
|
|
2667
|
+
if (crew) {
|
|
2668
|
+
await runtime.closePane(crew);
|
|
2669
|
+
} else if (taskId === void 0) {
|
|
2670
|
+
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
2671
|
+
}
|
|
2672
|
+
if (taskId !== void 0) {
|
|
2673
|
+
await reapCrewChildren(taskId);
|
|
2674
|
+
}
|
|
2675
|
+
if (worktreeCwd && projRoot) {
|
|
2676
|
+
try {
|
|
2677
|
+
removeWorktree(projRoot, worktreeCwd);
|
|
2678
|
+
} catch (e) {
|
|
2679
|
+
process.stderr.write(`(worktree remove failed: ${e.message})
|
|
2680
|
+
`);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
async function runCrewList(project, runtime, workspaceId) {
|
|
2685
|
+
const crews = await listCrewPanes(runtime, workspaceId, project);
|
|
2686
|
+
return crews.map((c) => ({
|
|
2687
|
+
name: nameFromTitle(project, c.title),
|
|
2688
|
+
surfaceId: c.surfaceId
|
|
2689
|
+
}));
|
|
2690
|
+
}
|
|
2691
|
+
var TEMPLATES_DIR, STATE_ROOT;
|
|
2692
|
+
var init_crew_spawn = __esm({
|
|
2693
|
+
"packages/core/dist/crew-spawn.js"() {
|
|
2694
|
+
init_dist();
|
|
2695
|
+
init_crew_routing();
|
|
2696
|
+
init_crew_protocol();
|
|
2697
|
+
init_crew_lifecycle();
|
|
2698
|
+
TEMPLATES_DIR = path10.join(os4.homedir(), ".config", "squadrant", "templates");
|
|
2699
|
+
STATE_ROOT = path10.join(os4.homedir(), ".config", "squadrant", "state");
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2703
|
+
// packages/core/dist/index.js
|
|
2704
|
+
var init_dist2 = __esm({
|
|
2705
|
+
"packages/core/dist/index.js"() {
|
|
2706
|
+
init_reduce();
|
|
2707
|
+
init_mailbox();
|
|
2708
|
+
init_protocol();
|
|
2709
|
+
init_state_machine();
|
|
2710
|
+
init_liveness();
|
|
2711
|
+
init_watchdog();
|
|
2712
|
+
init_store();
|
|
2713
|
+
init_snapshot();
|
|
2714
|
+
init_launchd();
|
|
2715
|
+
init_crew_pane_reader();
|
|
2716
|
+
init_interfaces();
|
|
2717
|
+
init_gate();
|
|
2718
|
+
init_context();
|
|
2719
|
+
init_attach();
|
|
2720
|
+
init_start();
|
|
2721
|
+
init_delivery_loop();
|
|
2722
|
+
init_interactive_probe();
|
|
2723
|
+
init_captain_delivery();
|
|
2724
|
+
init_defer_delivery();
|
|
2725
|
+
init_session_freshness();
|
|
2726
|
+
init_crew_protocol();
|
|
2727
|
+
init_crew_lifecycle();
|
|
2728
|
+
init_telegram();
|
|
2729
|
+
init_crew_routing();
|
|
2730
|
+
init_restart_daemon();
|
|
2731
|
+
init_group_dispatch();
|
|
2732
|
+
init_launch_workspace();
|
|
2733
|
+
init_side_session();
|
|
2734
|
+
init_crew_spawn();
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2737
|
+
|
|
2738
|
+
// packages/workspaces/dist/runtimes/cmux.js
|
|
2739
|
+
import { execFile as execFile2, execFileSync as execFileSync5 } from "child_process";
|
|
2740
|
+
function isInsideCmux() {
|
|
2741
|
+
return !!process.env.CMUX_WORKSPACE_ID;
|
|
2742
|
+
}
|
|
2743
|
+
function cmuxLocal(args) {
|
|
2744
|
+
return execFileSync5(resolveCmuxBin(), args, {
|
|
2745
|
+
encoding: "utf-8",
|
|
2746
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2747
|
+
timeout: CMUX_TIMEOUT
|
|
2748
|
+
}).trim();
|
|
2749
|
+
}
|
|
2750
|
+
function cmux(args) {
|
|
2751
|
+
return new Promise((resolve3, reject) => {
|
|
2752
|
+
execFile2(
|
|
2753
|
+
resolveCmuxBin(),
|
|
2754
|
+
args,
|
|
2755
|
+
// CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the
|
|
2756
|
+
// "list-workspaces is now an alias for cmux workspace list" notice). Those
|
|
2757
|
+
// notices print to the command's stdout and would otherwise pollute the
|
|
2758
|
+
// output we parse. Inherit the rest of the environment unchanged.
|
|
2759
|
+
{ encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } },
|
|
2760
|
+
(err, stdout) => {
|
|
2761
|
+
if (err) {
|
|
2762
|
+
reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
resolve3(stdout.trim());
|
|
2766
|
+
}
|
|
2767
|
+
);
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
function parseList(output) {
|
|
2771
|
+
let parsed;
|
|
2772
|
+
try {
|
|
2773
|
+
parsed = JSON.parse(output);
|
|
2774
|
+
} catch {
|
|
2775
|
+
return [];
|
|
2776
|
+
}
|
|
2777
|
+
const refs = [];
|
|
2778
|
+
for (const ws of parsed.workspaces ?? []) {
|
|
2779
|
+
if (!ws.ref)
|
|
2780
|
+
continue;
|
|
2781
|
+
refs.push({
|
|
2782
|
+
id: ws.ref,
|
|
2783
|
+
name: ws.has_custom_title && ws.custom_title ? ws.custom_title : ws.current_directory ?? ws.ref,
|
|
2784
|
+
status: "running"
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
return refs;
|
|
2788
|
+
}
|
|
2789
|
+
function sanitizeForCmuxSend(text) {
|
|
2790
|
+
return text.replace(/\\[nrt]/g, " ").replace(/[\n\r\t]+/g, " ").replace(/ {2,}/g, " ").trim();
|
|
2791
|
+
}
|
|
2792
|
+
function parseDraftFromScreen(screen) {
|
|
2793
|
+
if (!screen)
|
|
2794
|
+
return null;
|
|
2795
|
+
const lines = screen.split(/\r?\n/);
|
|
2796
|
+
const HR_RE = /^\s*─{10,}\s*$/;
|
|
2797
|
+
let bottomHR = -1;
|
|
2798
|
+
let topHR = -1;
|
|
2799
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2800
|
+
if (HR_RE.test(lines[i])) {
|
|
2801
|
+
if (bottomHR === -1) {
|
|
2802
|
+
bottomHR = i;
|
|
2803
|
+
} else {
|
|
2804
|
+
topHR = i;
|
|
2805
|
+
break;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (topHR === -1)
|
|
2810
|
+
return null;
|
|
2811
|
+
const inputLines = lines.slice(topHR + 1, bottomHR);
|
|
2812
|
+
for (const line of inputLines) {
|
|
2813
|
+
let extracted;
|
|
2814
|
+
const boxMatch = line.match(/│\s*[>❯]\s+(.*?)\s*│/);
|
|
2031
2815
|
if (boxMatch) {
|
|
2032
2816
|
extracted = boxMatch[1].trim();
|
|
2033
2817
|
} else {
|
|
@@ -2216,8 +3000,14 @@ function createCmuxDriver() {
|
|
|
2216
3000
|
}
|
|
2217
3001
|
},
|
|
2218
3002
|
async sendToPane(pane, message) {
|
|
2219
|
-
await
|
|
2220
|
-
await
|
|
3003
|
+
await this.pasteToPane(pane, message);
|
|
3004
|
+
await this.sendKeyToPane(pane, "Enter");
|
|
3005
|
+
},
|
|
3006
|
+
async pasteToPane(pane, text) {
|
|
3007
|
+
await cmux(["send", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, sanitizeForCmuxSend(text)]);
|
|
3008
|
+
},
|
|
3009
|
+
async sendKeyToPane(pane, key) {
|
|
3010
|
+
await cmux(["send-key", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, key]);
|
|
2221
3011
|
},
|
|
2222
3012
|
async readPaneScreen(pane) {
|
|
2223
3013
|
try {
|
|
@@ -2394,7 +3184,7 @@ var init_runtimes = __esm({
|
|
|
2394
3184
|
});
|
|
2395
3185
|
|
|
2396
3186
|
// packages/workspaces/dist/notifiers/cmux.js
|
|
2397
|
-
import { execFileSync as
|
|
3187
|
+
import { execFileSync as execFileSync6, execSync as execSync2 } from "child_process";
|
|
2398
3188
|
function createCmuxNotifier(_scope) {
|
|
2399
3189
|
return {
|
|
2400
3190
|
name: "cmux",
|
|
@@ -2411,7 +3201,7 @@ function createCmuxNotifier(_scope) {
|
|
|
2411
3201
|
}
|
|
2412
3202
|
},
|
|
2413
3203
|
async notify(message) {
|
|
2414
|
-
|
|
3204
|
+
execFileSync6("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
|
|
2415
3205
|
}
|
|
2416
3206
|
};
|
|
2417
3207
|
}
|
|
@@ -2466,13 +3256,13 @@ var init_notifiers = __esm({
|
|
|
2466
3256
|
});
|
|
2467
3257
|
|
|
2468
3258
|
// packages/workspaces/dist/workspaces/obsidian.js
|
|
2469
|
-
import
|
|
2470
|
-
import { existsSync as
|
|
2471
|
-
import
|
|
3259
|
+
import fs13 from "fs/promises";
|
|
3260
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3261
|
+
import path11 from "path";
|
|
2472
3262
|
function resolveInRoot(root, relative) {
|
|
2473
|
-
const joined =
|
|
2474
|
-
const normalized =
|
|
2475
|
-
if (joined !==
|
|
3263
|
+
const joined = path11.resolve(root, relative);
|
|
3264
|
+
const normalized = path11.resolve(root) + path11.sep;
|
|
3265
|
+
if (joined !== path11.resolve(root) && !joined.startsWith(normalized)) {
|
|
2476
3266
|
throw new Error(`Path '${relative}' escapes workspace root`);
|
|
2477
3267
|
}
|
|
2478
3268
|
return joined;
|
|
@@ -2487,20 +3277,20 @@ function createObsidianDriver(scope) {
|
|
|
2487
3277
|
async probe() {
|
|
2488
3278
|
return {
|
|
2489
3279
|
installed: true,
|
|
2490
|
-
rootExists:
|
|
3280
|
+
rootExists: existsSync9(root)
|
|
2491
3281
|
};
|
|
2492
3282
|
},
|
|
2493
3283
|
async read(rel) {
|
|
2494
|
-
return
|
|
3284
|
+
return fs13.readFile(resolveInRoot(root, rel), "utf-8");
|
|
2495
3285
|
},
|
|
2496
3286
|
async write(rel, content) {
|
|
2497
3287
|
const abs = resolveInRoot(root, rel);
|
|
2498
|
-
await
|
|
2499
|
-
await
|
|
3288
|
+
await fs13.mkdir(path11.dirname(abs), { recursive: true });
|
|
3289
|
+
await fs13.writeFile(abs, content);
|
|
2500
3290
|
},
|
|
2501
3291
|
async exists(rel) {
|
|
2502
3292
|
try {
|
|
2503
|
-
await
|
|
3293
|
+
await fs13.access(resolveInRoot(root, rel));
|
|
2504
3294
|
return true;
|
|
2505
3295
|
} catch {
|
|
2506
3296
|
return false;
|
|
@@ -2508,13 +3298,13 @@ function createObsidianDriver(scope) {
|
|
|
2508
3298
|
},
|
|
2509
3299
|
async list(rel) {
|
|
2510
3300
|
try {
|
|
2511
|
-
return await
|
|
3301
|
+
return await fs13.readdir(resolveInRoot(root, rel));
|
|
2512
3302
|
} catch {
|
|
2513
3303
|
return [];
|
|
2514
3304
|
}
|
|
2515
3305
|
},
|
|
2516
3306
|
async mkdir(rel) {
|
|
2517
|
-
await
|
|
3307
|
+
await fs13.mkdir(resolveInRoot(root, rel), { recursive: true });
|
|
2518
3308
|
}
|
|
2519
3309
|
};
|
|
2520
3310
|
}
|
|
@@ -2572,7 +3362,7 @@ var init_workspaces2 = __esm({
|
|
|
2572
3362
|
}
|
|
2573
3363
|
});
|
|
2574
3364
|
|
|
2575
|
-
// packages/workspaces/dist/cmux/events-bridge.js
|
|
3365
|
+
// packages/workspaces/dist/cmux-daemon/events-bridge.js
|
|
2576
3366
|
import { spawn as nodeSpawn } from "child_process";
|
|
2577
3367
|
function deriveRunState(eventName) {
|
|
2578
3368
|
switch (eventName) {
|
|
@@ -2587,7 +3377,7 @@ function deriveRunState(eventName) {
|
|
|
2587
3377
|
}
|
|
2588
3378
|
var CmuxEventsBridge;
|
|
2589
3379
|
var init_events_bridge = __esm({
|
|
2590
|
-
"packages/workspaces/dist/cmux/events-bridge.js"() {
|
|
3380
|
+
"packages/workspaces/dist/cmux-daemon/events-bridge.js"() {
|
|
2591
3381
|
init_dist();
|
|
2592
3382
|
CmuxEventsBridge = class {
|
|
2593
3383
|
child = null;
|
|
@@ -2712,11 +3502,11 @@ var init_events_bridge = __esm({
|
|
|
2712
3502
|
}
|
|
2713
3503
|
});
|
|
2714
3504
|
|
|
2715
|
-
// packages/workspaces/dist/cmux/daemon-cmux.js
|
|
3505
|
+
// packages/workspaces/dist/cmux-daemon/daemon-cmux.js
|
|
2716
3506
|
var DaemonCmux;
|
|
2717
3507
|
var init_daemon_cmux = __esm({
|
|
2718
|
-
"packages/workspaces/dist/cmux/daemon-cmux.js"() {
|
|
2719
|
-
|
|
3508
|
+
"packages/workspaces/dist/cmux-daemon/daemon-cmux.js"() {
|
|
3509
|
+
init_dist2();
|
|
2720
3510
|
DaemonCmux = class {
|
|
2721
3511
|
driver;
|
|
2722
3512
|
constructor(driver) {
|
|
@@ -2773,6 +3563,16 @@ var init_daemon_cmux = __esm({
|
|
|
2773
3563
|
|
|
2774
3564
|
// packages/workspaces/dist/crew-pane.js
|
|
2775
3565
|
import net from "net";
|
|
3566
|
+
async function settleInputBox(runtime, pane) {
|
|
3567
|
+
let prev = await runtime.readPaneScreen(pane) ?? "";
|
|
3568
|
+
for (let i = 0; i < SETTLE_MAX_POLLS; i++) {
|
|
3569
|
+
await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));
|
|
3570
|
+
const cur = await runtime.readPaneScreen(pane) ?? "";
|
|
3571
|
+
if (cur === prev)
|
|
3572
|
+
return;
|
|
3573
|
+
prev = cur;
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
2776
3576
|
function getFreePort() {
|
|
2777
3577
|
return new Promise((resolve3, reject) => {
|
|
2778
3578
|
const srv = net.createServer();
|
|
@@ -2806,6 +3606,23 @@ async function resolveCaptainWorkspace(project) {
|
|
|
2806
3606
|
}
|
|
2807
3607
|
return { runtime, workspaceId: captain.id };
|
|
2808
3608
|
}
|
|
3609
|
+
async function confirmedSendToPane(runtime, pane, message) {
|
|
3610
|
+
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
3611
|
+
await runtime.pasteToPane(pane, message);
|
|
3612
|
+
await settleInputBox(runtime, pane);
|
|
3613
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
3614
|
+
for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {
|
|
3615
|
+
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
3616
|
+
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
3617
|
+
const draft = parseDraftFromScreen(afterScreen);
|
|
3618
|
+
if (draft === "")
|
|
3619
|
+
return;
|
|
3620
|
+
if (draft === null && afterScreen !== preSendScreen)
|
|
3621
|
+
return;
|
|
3622
|
+
await settleInputBox(runtime, pane);
|
|
3623
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
2809
3626
|
async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acceptanceConfig) {
|
|
2810
3627
|
await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));
|
|
2811
3628
|
const maxPolls = Math.floor((SEND_FIRST_TURN_TIMEOUT_MS - SEND_FIRST_TURN_FLOOR_MS) / POLL_INTERVAL_MS);
|
|
@@ -2821,20 +3638,37 @@ async function sendFirstTurnWhenReady(runtime, pane, task, preLaunchScreen, acce
|
|
|
2821
3638
|
}
|
|
2822
3639
|
}
|
|
2823
3640
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
2824
|
-
|
|
2825
|
-
|
|
3641
|
+
if (acceptanceConfig?.splashMarker) {
|
|
3642
|
+
await runtime.sendToPane(pane, task);
|
|
3643
|
+
for (let check2 = 0; check2 < SPLASH_MAX_CHECKS; check2++) {
|
|
3644
|
+
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
3645
|
+
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
3646
|
+
if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
|
|
3647
|
+
return;
|
|
3648
|
+
}
|
|
3649
|
+
if ((check2 + 1) % SPLASH_RESEND_EVERY_N === 0 && check2 < SPLASH_MAX_CHECKS - 1) {
|
|
3650
|
+
await runtime.sendToPane(pane, task);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
await runtime.pasteToPane(pane, task);
|
|
3656
|
+
await settleInputBox(runtime, pane);
|
|
3657
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
3658
|
+
const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;
|
|
2826
3659
|
for (let attempt = 0; attempt < retryLimit; attempt++) {
|
|
2827
3660
|
await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));
|
|
2828
3661
|
const afterScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
2829
|
-
|
|
3662
|
+
const draft = parseDraftFromScreen(afterScreen);
|
|
3663
|
+
if (draft === "")
|
|
2830
3664
|
return;
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3665
|
+
if (draft === null && afterScreen !== preSendScreen)
|
|
3666
|
+
return;
|
|
3667
|
+
await settleInputBox(runtime, pane);
|
|
3668
|
+
await runtime.sendKeyToPane(pane, "Enter");
|
|
2835
3669
|
}
|
|
2836
3670
|
}
|
|
2837
|
-
var SEND_FIRST_TURN_FLOOR_MS, POLL_INTERVAL_MS, SEND_FIRST_TURN_TIMEOUT_MS, POST_SEND_CHECK_MS;
|
|
3671
|
+
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, SETTLE_POLL_MS, SETTLE_MAX_POLLS, SUBMIT_RETRY_LIMIT;
|
|
2838
3672
|
var init_crew_pane = __esm({
|
|
2839
3673
|
"packages/workspaces/dist/crew-pane.js"() {
|
|
2840
3674
|
init_dist();
|
|
@@ -2845,6 +3679,11 @@ var init_crew_pane = __esm({
|
|
|
2845
3679
|
POLL_INTERVAL_MS = 750;
|
|
2846
3680
|
SEND_FIRST_TURN_TIMEOUT_MS = 2e4;
|
|
2847
3681
|
POST_SEND_CHECK_MS = 750;
|
|
3682
|
+
SPLASH_MAX_CHECKS = 20;
|
|
3683
|
+
SPLASH_RESEND_EVERY_N = 4;
|
|
3684
|
+
SETTLE_POLL_MS = 400;
|
|
3685
|
+
SETTLE_MAX_POLLS = 8;
|
|
3686
|
+
SUBMIT_RETRY_LIMIT = 4;
|
|
2848
3687
|
}
|
|
2849
3688
|
});
|
|
2850
3689
|
|
|
@@ -2854,12 +3693,12 @@ __export(dist_exports, {
|
|
|
2854
3693
|
CMUX_TIMEOUT: () => CMUX_TIMEOUT,
|
|
2855
3694
|
CmuxEventsBridge: () => CmuxEventsBridge,
|
|
2856
3695
|
DaemonCmux: () => DaemonCmux,
|
|
2857
|
-
DeferDelivery: () => DeferDelivery,
|
|
2858
3696
|
NotifierRegistry: () => NotifierRegistry,
|
|
2859
3697
|
RuntimeRegistry: () => RuntimeRegistry,
|
|
2860
3698
|
WorkspaceRegistry: () => WorkspaceRegistry,
|
|
2861
3699
|
classifyStartupSurface: () => classifyStartupSurface,
|
|
2862
3700
|
cmuxLocal: () => cmuxLocal,
|
|
3701
|
+
confirmedSendToPane: () => confirmedSendToPane,
|
|
2863
3702
|
createCmuxDriver: () => createCmuxDriver,
|
|
2864
3703
|
createCmuxNotifier: () => createCmuxNotifier,
|
|
2865
3704
|
createObsidianDriver: () => createObsidianDriver,
|
|
@@ -3183,8 +4022,8 @@ var init_registry4 = __esm({
|
|
|
3183
4022
|
});
|
|
3184
4023
|
|
|
3185
4024
|
// packages/agents/dist/drivers/launch-cmd.js
|
|
3186
|
-
import
|
|
3187
|
-
import
|
|
4025
|
+
import fs14 from "fs";
|
|
4026
|
+
import path12 from "path";
|
|
3188
4027
|
function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir) {
|
|
3189
4028
|
const driver = registry.getDriver(agentName);
|
|
3190
4029
|
if (driver.name === "claude") {
|
|
@@ -3200,27 +4039,27 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
|
|
|
3200
4039
|
cmd += ` --model ${model}`;
|
|
3201
4040
|
}
|
|
3202
4041
|
if (templatesDir) {
|
|
3203
|
-
const roleFile2 =
|
|
3204
|
-
const legacyRoleFile =
|
|
3205
|
-
const actualRoleFile =
|
|
4042
|
+
const roleFile2 = path12.join(templatesDir, `${role}.claude.md`);
|
|
4043
|
+
const legacyRoleFile = path12.join(templatesDir, `${role}.CLAUDE.md`);
|
|
4044
|
+
const actualRoleFile = fs14.existsSync(roleFile2) ? roleFile2 : fs14.existsSync(legacyRoleFile) ? legacyRoleFile : null;
|
|
3206
4045
|
if (actualRoleFile) {
|
|
3207
4046
|
cmd += ` --append-system-prompt-file ${actualRoleFile}`;
|
|
3208
4047
|
}
|
|
3209
|
-
const pluginDir =
|
|
3210
|
-
if (
|
|
4048
|
+
const pluginDir = path12.join(templatesDir, "..", "plugin");
|
|
4049
|
+
if (fs14.existsSync(pluginDir)) {
|
|
3211
4050
|
cmd += ` --plugin-dir ${pluginDir}`;
|
|
3212
4051
|
}
|
|
3213
4052
|
}
|
|
3214
4053
|
return cmd;
|
|
3215
4054
|
}
|
|
3216
|
-
const roleFile = templatesDir ?
|
|
4055
|
+
const roleFile = templatesDir ? path12.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
|
|
3217
4056
|
return driver.buildCommand({
|
|
3218
4057
|
prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,
|
|
3219
4058
|
workdir: process.cwd(),
|
|
3220
4059
|
role,
|
|
3221
4060
|
model,
|
|
3222
4061
|
autoApprove: true,
|
|
3223
|
-
promptFile: roleFile &&
|
|
4062
|
+
promptFile: roleFile && fs14.existsSync(roleFile) ? roleFile : void 0
|
|
3224
4063
|
});
|
|
3225
4064
|
}
|
|
3226
4065
|
var init_launch_cmd = __esm({
|
|
@@ -3243,8 +4082,8 @@ var init_drivers = __esm({
|
|
|
3243
4082
|
|
|
3244
4083
|
// packages/agents/dist/projection/cursor.js
|
|
3245
4084
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3246
|
-
import
|
|
3247
|
-
import
|
|
4085
|
+
import path13 from "path";
|
|
4086
|
+
import os5 from "os";
|
|
3248
4087
|
function renderMdc(source) {
|
|
3249
4088
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3250
4089
|
|
|
@@ -3292,7 +4131,7 @@ function createCursorEmitter() {
|
|
|
3292
4131
|
if (scope === "user") {
|
|
3293
4132
|
return [
|
|
3294
4133
|
{
|
|
3295
|
-
path:
|
|
4134
|
+
path: path13.join(os5.homedir(), ".cursor/rules/squadrant-global.mdc"),
|
|
3296
4135
|
shared: false,
|
|
3297
4136
|
format: "mdc"
|
|
3298
4137
|
}
|
|
@@ -3302,7 +4141,7 @@ function createCursorEmitter() {
|
|
|
3302
4141
|
return [];
|
|
3303
4142
|
return [
|
|
3304
4143
|
{
|
|
3305
|
-
path:
|
|
4144
|
+
path: path13.join(projectRoot, ".cursor/rules/squadrant.mdc"),
|
|
3306
4145
|
shared: false,
|
|
3307
4146
|
format: "mdc"
|
|
3308
4147
|
}
|
|
@@ -3319,7 +4158,7 @@ function createCursorEmitter() {
|
|
|
3319
4158
|
diff: buildDiff(existing, generated)
|
|
3320
4159
|
};
|
|
3321
4160
|
}
|
|
3322
|
-
await mkdir(
|
|
4161
|
+
await mkdir(path13.dirname(dest.path), { recursive: true });
|
|
3323
4162
|
await writeFile(dest.path, generated, "utf-8");
|
|
3324
4163
|
return {
|
|
3325
4164
|
written: true,
|
|
@@ -3370,8 +4209,8 @@ var init_marker = __esm({
|
|
|
3370
4209
|
|
|
3371
4210
|
// packages/agents/dist/projection/codex.js
|
|
3372
4211
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3373
|
-
import
|
|
3374
|
-
import
|
|
4212
|
+
import path14 from "path";
|
|
4213
|
+
import os6 from "os";
|
|
3375
4214
|
function renderMarkdown(source) {
|
|
3376
4215
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3377
4216
|
|
|
@@ -3395,7 +4234,7 @@ function createCodexEmitter() {
|
|
|
3395
4234
|
destinations(scope, projectRoot) {
|
|
3396
4235
|
if (scope === "user") {
|
|
3397
4236
|
return [{
|
|
3398
|
-
path:
|
|
4237
|
+
path: path14.join(os6.homedir(), ".codex/AGENTS.md"),
|
|
3399
4238
|
shared: true,
|
|
3400
4239
|
format: "markdown"
|
|
3401
4240
|
}];
|
|
@@ -3403,7 +4242,7 @@ function createCodexEmitter() {
|
|
|
3403
4242
|
if (!projectRoot)
|
|
3404
4243
|
return [];
|
|
3405
4244
|
return [{
|
|
3406
|
-
path:
|
|
4245
|
+
path: path14.join(projectRoot, "AGENTS.md"),
|
|
3407
4246
|
shared: true,
|
|
3408
4247
|
format: "markdown"
|
|
3409
4248
|
}];
|
|
@@ -3424,7 +4263,7 @@ ${existing ?? ""}
|
|
|
3424
4263
|
${generated}`
|
|
3425
4264
|
};
|
|
3426
4265
|
}
|
|
3427
|
-
await mkdir2(
|
|
4266
|
+
await mkdir2(path14.dirname(dest.path), { recursive: true });
|
|
3428
4267
|
await writeFile2(dest.path, generated, "utf-8");
|
|
3429
4268
|
return {
|
|
3430
4269
|
written: true,
|
|
@@ -3442,8 +4281,8 @@ var init_codex2 = __esm({
|
|
|
3442
4281
|
|
|
3443
4282
|
// packages/agents/dist/projection/gemini.js
|
|
3444
4283
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
3445
|
-
import
|
|
3446
|
-
import
|
|
4284
|
+
import path15 from "path";
|
|
4285
|
+
import os7 from "os";
|
|
3447
4286
|
function renderMarkdown2(source) {
|
|
3448
4287
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3449
4288
|
|
|
@@ -3467,7 +4306,7 @@ function createGeminiEmitter() {
|
|
|
3467
4306
|
destinations(scope, projectRoot) {
|
|
3468
4307
|
if (scope === "user") {
|
|
3469
4308
|
return [{
|
|
3470
|
-
path:
|
|
4309
|
+
path: path15.join(os7.homedir(), ".gemini/GEMINI.md"),
|
|
3471
4310
|
shared: true,
|
|
3472
4311
|
format: "markdown"
|
|
3473
4312
|
}];
|
|
@@ -3475,7 +4314,7 @@ function createGeminiEmitter() {
|
|
|
3475
4314
|
if (!projectRoot)
|
|
3476
4315
|
return [];
|
|
3477
4316
|
return [{
|
|
3478
|
-
path:
|
|
4317
|
+
path: path15.join(projectRoot, "GEMINI.md"),
|
|
3479
4318
|
shared: true,
|
|
3480
4319
|
format: "markdown"
|
|
3481
4320
|
}];
|
|
@@ -3496,7 +4335,7 @@ ${existing ?? ""}
|
|
|
3496
4335
|
${generated}`
|
|
3497
4336
|
};
|
|
3498
4337
|
}
|
|
3499
|
-
await mkdir3(
|
|
4338
|
+
await mkdir3(path15.dirname(dest.path), { recursive: true });
|
|
3500
4339
|
await writeFile3(dest.path, generated, "utf-8");
|
|
3501
4340
|
return {
|
|
3502
4341
|
written: true,
|
|
@@ -3514,8 +4353,8 @@ var init_gemini2 = __esm({
|
|
|
3514
4353
|
|
|
3515
4354
|
// packages/agents/dist/projection/opencode.js
|
|
3516
4355
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
3517
|
-
import
|
|
3518
|
-
import
|
|
4356
|
+
import path16 from "path";
|
|
4357
|
+
import os8 from "os";
|
|
3519
4358
|
function renderMarkdown3(source) {
|
|
3520
4359
|
const skillSections = source.skills.map((s) => `## Skill: ${s.name}
|
|
3521
4360
|
|
|
@@ -3539,7 +4378,7 @@ function createOpencodeEmitter() {
|
|
|
3539
4378
|
destinations(scope, projectRoot) {
|
|
3540
4379
|
if (scope === "user") {
|
|
3541
4380
|
return [{
|
|
3542
|
-
path:
|
|
4381
|
+
path: path16.join(os8.homedir(), ".config", "opencode", "AGENTS.md"),
|
|
3543
4382
|
shared: true,
|
|
3544
4383
|
format: "markdown"
|
|
3545
4384
|
}];
|
|
@@ -3547,7 +4386,7 @@ function createOpencodeEmitter() {
|
|
|
3547
4386
|
if (!projectRoot)
|
|
3548
4387
|
return [];
|
|
3549
4388
|
return [{
|
|
3550
|
-
path:
|
|
4389
|
+
path: path16.join(projectRoot, "AGENTS.md"),
|
|
3551
4390
|
shared: true,
|
|
3552
4391
|
format: "markdown"
|
|
3553
4392
|
}];
|
|
@@ -3568,7 +4407,7 @@ ${existing ?? ""}
|
|
|
3568
4407
|
${generated}`
|
|
3569
4408
|
};
|
|
3570
4409
|
}
|
|
3571
|
-
await mkdir4(
|
|
4410
|
+
await mkdir4(path16.dirname(dest.path), { recursive: true });
|
|
3572
4411
|
await writeFile4(dest.path, generated, "utf-8");
|
|
3573
4412
|
return {
|
|
3574
4413
|
written: true,
|
|
@@ -3809,11 +4648,11 @@ var init_app_server_client = __esm({
|
|
|
3809
4648
|
|
|
3810
4649
|
// packages/agents/dist/codex/config.js
|
|
3811
4650
|
import { readFile as readFile5 } from "fs/promises";
|
|
3812
|
-
import { homedir as
|
|
3813
|
-
import { join as
|
|
4651
|
+
import { homedir as homedir9 } from "os";
|
|
4652
|
+
import { join as join14 } from "path";
|
|
3814
4653
|
async function resolveCodexModel() {
|
|
3815
|
-
const home = process.env["CODEX_HOME"] ??
|
|
3816
|
-
const configPath =
|
|
4654
|
+
const home = process.env["CODEX_HOME"] ?? join14(homedir9(), ".codex");
|
|
4655
|
+
const configPath = join14(home, "config.toml");
|
|
3817
4656
|
let text;
|
|
3818
4657
|
try {
|
|
3819
4658
|
text = await readFile5(configPath, "utf8");
|
|
@@ -4335,8 +5174,8 @@ var init_sse_bridge = __esm({
|
|
|
4335
5174
|
// packages/agents/dist/interactive/claude.js
|
|
4336
5175
|
import { execSync as execSync7 } from "child_process";
|
|
4337
5176
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4338
|
-
import { homedir as
|
|
4339
|
-
import { join as
|
|
5177
|
+
import { homedir as homedir10 } from "os";
|
|
5178
|
+
import { join as join15 } from "path";
|
|
4340
5179
|
function probeClaudeSettingsFlag() {
|
|
4341
5180
|
try {
|
|
4342
5181
|
const help = execSync7("claude --help", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -4387,7 +5226,7 @@ function deriveTranscriptPath(sessionId, cwd) {
|
|
|
4387
5226
|
if (!sessionId || !cwd)
|
|
4388
5227
|
return null;
|
|
4389
5228
|
const escaped = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
4390
|
-
return
|
|
5229
|
+
return join15(homedir10(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
|
|
4391
5230
|
}
|
|
4392
5231
|
function readLastAssistantText(transcriptPath) {
|
|
4393
5232
|
try {
|
|
@@ -4846,18 +5685,18 @@ init_dist();
|
|
|
4846
5685
|
init_dist3();
|
|
4847
5686
|
import { Command } from "commander";
|
|
4848
5687
|
import { execSync as execSync8 } from "child_process";
|
|
4849
|
-
import
|
|
5688
|
+
import fs15 from "fs";
|
|
4850
5689
|
import { stat } from "fs/promises";
|
|
4851
|
-
import
|
|
5690
|
+
import path17 from "path";
|
|
4852
5691
|
import chalk3 from "chalk";
|
|
4853
5692
|
|
|
4854
5693
|
// packages/cli/src/commands/health-view.ts
|
|
4855
5694
|
init_dist2();
|
|
4856
5695
|
init_dist2();
|
|
4857
|
-
import { homedir as
|
|
4858
|
-
import { join as
|
|
5696
|
+
import { homedir as homedir8 } from "os";
|
|
5697
|
+
import { join as join13 } from "path";
|
|
4859
5698
|
import chalk2 from "chalk";
|
|
4860
|
-
var SOCK =
|
|
5699
|
+
var SOCK = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
|
|
4861
5700
|
async function queryHealth(project) {
|
|
4862
5701
|
try {
|
|
4863
5702
|
const reply = await sendRequest(SOCK, { kind: "health", project });
|
|
@@ -4951,7 +5790,7 @@ function settingsHaveAgentTeams() {
|
|
|
4951
5790
|
try {
|
|
4952
5791
|
const home = process.env.HOME || "";
|
|
4953
5792
|
const settings = JSON.parse(
|
|
4954
|
-
|
|
5793
|
+
fs15.readFileSync(`${home}/.claude/settings.json`, "utf-8")
|
|
4955
5794
|
);
|
|
4956
5795
|
return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
4957
5796
|
} catch {
|
|
@@ -4962,7 +5801,7 @@ function pluginInstalled(pluginKey) {
|
|
|
4962
5801
|
try {
|
|
4963
5802
|
const home = process.env.HOME || "";
|
|
4964
5803
|
const plugins = JSON.parse(
|
|
4965
|
-
|
|
5804
|
+
fs15.readFileSync(
|
|
4966
5805
|
`${home}/.claude/plugins/installed_plugins.json`,
|
|
4967
5806
|
"utf-8"
|
|
4968
5807
|
)
|
|
@@ -5007,7 +5846,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5007
5846
|
));
|
|
5008
5847
|
results.push(check(
|
|
5009
5848
|
"Obsidian installed",
|
|
5010
|
-
commandExists("obsidian") ||
|
|
5849
|
+
commandExists("obsidian") || fs15.existsSync("/Applications/Obsidian.app"),
|
|
5011
5850
|
"Install from: https://obsidian.md"
|
|
5012
5851
|
));
|
|
5013
5852
|
results.push(check(
|
|
@@ -5101,7 +5940,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5101
5940
|
const emitter = projectionRegistry.get(name);
|
|
5102
5941
|
const [userDest] = emitter.destinations("user");
|
|
5103
5942
|
if (!userDest) continue;
|
|
5104
|
-
const dir =
|
|
5943
|
+
const dir = path17.dirname(userDest.path);
|
|
5105
5944
|
let status;
|
|
5106
5945
|
try {
|
|
5107
5946
|
await stat(dir);
|
|
@@ -5114,7 +5953,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
|
|
|
5114
5953
|
results.push(
|
|
5115
5954
|
check(
|
|
5116
5955
|
"Squadrant config exists",
|
|
5117
|
-
|
|
5956
|
+
fs15.existsSync(
|
|
5118
5957
|
process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
|
|
5119
5958
|
),
|
|
5120
5959
|
"Run: squadrant init"
|
|
@@ -5189,28 +6028,28 @@ init_dist3();
|
|
|
5189
6028
|
init_dist();
|
|
5190
6029
|
init_dist4();
|
|
5191
6030
|
import { Command as Command2 } from "commander";
|
|
5192
|
-
import
|
|
5193
|
-
import
|
|
5194
|
-
import
|
|
6031
|
+
import fs16 from "fs";
|
|
6032
|
+
import path18 from "path";
|
|
6033
|
+
import os9 from "os";
|
|
5195
6034
|
import readline from "readline";
|
|
5196
6035
|
import chalk4 from "chalk";
|
|
5197
6036
|
function findPackageRoot() {
|
|
5198
|
-
let dir =
|
|
6037
|
+
let dir = path18.dirname(new URL(import.meta.url).pathname);
|
|
5199
6038
|
while (dir !== "/") {
|
|
5200
|
-
if (
|
|
5201
|
-
dir =
|
|
6039
|
+
if (fs16.existsSync(path18.join(dir, "package.json"))) return dir;
|
|
6040
|
+
dir = path18.dirname(dir);
|
|
5202
6041
|
}
|
|
5203
6042
|
return process.cwd();
|
|
5204
6043
|
}
|
|
5205
6044
|
function copyDirRecursive(src, dest) {
|
|
5206
|
-
|
|
5207
|
-
for (const entry of
|
|
5208
|
-
const srcPath =
|
|
5209
|
-
const destPath =
|
|
6045
|
+
fs16.mkdirSync(dest, { recursive: true });
|
|
6046
|
+
for (const entry of fs16.readdirSync(src, { withFileTypes: true })) {
|
|
6047
|
+
const srcPath = path18.join(src, entry.name);
|
|
6048
|
+
const destPath = path18.join(dest, entry.name);
|
|
5210
6049
|
if (entry.isDirectory()) {
|
|
5211
6050
|
copyDirRecursive(srcPath, destPath);
|
|
5212
6051
|
} else {
|
|
5213
|
-
|
|
6052
|
+
fs16.copyFileSync(srcPath, destPath);
|
|
5214
6053
|
}
|
|
5215
6054
|
}
|
|
5216
6055
|
}
|
|
@@ -5230,7 +6069,7 @@ function promptLine(question) {
|
|
|
5230
6069
|
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
6070
|
const hubPath = resolveHome(opts.hub);
|
|
5232
6071
|
const pkgRoot = findPackageRoot();
|
|
5233
|
-
const configDir =
|
|
6072
|
+
const configDir = path18.join(os9.homedir(), ".config", "squadrant");
|
|
5234
6073
|
const isTTY = process.stdin.isTTY === true;
|
|
5235
6074
|
console.log(chalk4.bold("\nSquadrant Init\n"));
|
|
5236
6075
|
if (!isTTY) {
|
|
@@ -5253,8 +6092,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5253
6092
|
}
|
|
5254
6093
|
const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
|
|
5255
6094
|
try {
|
|
5256
|
-
if (
|
|
5257
|
-
const existing = JSON.parse(
|
|
6095
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
6096
|
+
const existing = JSON.parse(fs16.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
|
|
5258
6097
|
wsRegistry.get(existing.workspace ?? "obsidian");
|
|
5259
6098
|
}
|
|
5260
6099
|
} catch (err) {
|
|
@@ -5262,7 +6101,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5262
6101
|
return;
|
|
5263
6102
|
}
|
|
5264
6103
|
stepHeader(1, 5, "Hub vault");
|
|
5265
|
-
if (
|
|
6104
|
+
if (fs16.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
5266
6105
|
console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
|
|
5267
6106
|
} else {
|
|
5268
6107
|
const config = getDefaultConfig();
|
|
@@ -5270,37 +6109,37 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5270
6109
|
saveConfig(config);
|
|
5271
6110
|
console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
|
|
5272
6111
|
}
|
|
5273
|
-
const hubTemplate =
|
|
5274
|
-
if (
|
|
6112
|
+
const hubTemplate = path18.join(pkgRoot, "obsidian", "hub");
|
|
6113
|
+
if (fs16.existsSync(hubPath)) {
|
|
5275
6114
|
console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
|
|
5276
|
-
} else if (
|
|
6115
|
+
} else if (fs16.existsSync(hubTemplate)) {
|
|
5277
6116
|
copyDirRecursive(hubTemplate, hubPath);
|
|
5278
6117
|
console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
|
|
5279
6118
|
} else {
|
|
5280
|
-
|
|
6119
|
+
fs16.mkdirSync(hubPath, { recursive: true });
|
|
5281
6120
|
console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
|
|
5282
6121
|
}
|
|
5283
|
-
const hubDashboardSrc =
|
|
5284
|
-
const hubDashboardDest =
|
|
5285
|
-
if (
|
|
5286
|
-
|
|
6122
|
+
const hubDashboardSrc = path18.join(pkgRoot, "obsidian", "hub", "dashboard.md");
|
|
6123
|
+
const hubDashboardDest = path18.join(hubPath, "dashboard.md");
|
|
6124
|
+
if (fs16.existsSync(hubDashboardSrc)) {
|
|
6125
|
+
fs16.copyFileSync(hubDashboardSrc, hubDashboardDest);
|
|
5287
6126
|
console.log(chalk4.green(` \u2714 Dashboard refreshed`));
|
|
5288
6127
|
}
|
|
5289
|
-
|
|
6128
|
+
fs16.mkdirSync(path18.join(hubPath, "projects"), { recursive: true });
|
|
5290
6129
|
ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
|
|
5291
6130
|
console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
|
|
5292
6131
|
stepHeader(2, 5, "Agent + projection setup");
|
|
5293
|
-
const settingsPath =
|
|
6132
|
+
const settingsPath = path18.join(os9.homedir(), ".claude", "settings.json");
|
|
5294
6133
|
try {
|
|
5295
6134
|
let settings = {};
|
|
5296
|
-
if (
|
|
5297
|
-
settings = JSON.parse(
|
|
6135
|
+
if (fs16.existsSync(settingsPath)) {
|
|
6136
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
5298
6137
|
}
|
|
5299
6138
|
const env = settings.env || {};
|
|
5300
6139
|
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
5301
6140
|
settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
5302
|
-
|
|
5303
|
-
|
|
6141
|
+
fs16.mkdirSync(path18.dirname(settingsPath), { recursive: true });
|
|
6142
|
+
fs16.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5304
6143
|
console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
|
|
5305
6144
|
} else {
|
|
5306
6145
|
console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
|
|
@@ -5343,7 +6182,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5343
6182
|
chalk4.cyan(" Absolute path to your first project (Enter to skip): ")
|
|
5344
6183
|
);
|
|
5345
6184
|
if (projectPath) {
|
|
5346
|
-
const projectName =
|
|
6185
|
+
const projectName = path18.basename(projectPath);
|
|
5347
6186
|
console.log(chalk4.bold(`
|
|
5348
6187
|
Run this to register it:`));
|
|
5349
6188
|
console.log(chalk4.cyan(` squadrant projects add ${projectName} ${projectPath}
|
|
@@ -5369,45 +6208,11 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
|
|
|
5369
6208
|
|
|
5370
6209
|
// packages/cli/src/commands/projects.ts
|
|
5371
6210
|
init_dist();
|
|
6211
|
+
init_dist2();
|
|
5372
6212
|
import { Command as Command3 } from "commander";
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
6213
|
+
import fs17 from "fs";
|
|
6214
|
+
import path19 from "path";
|
|
5375
6215
|
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
6216
|
function restartAfterProjectsAdd(opts) {
|
|
5412
6217
|
const doRestart = opts.doRestart ?? restartDaemonIfRunning;
|
|
5413
6218
|
const outcome = doRestart({ reason: "project registration", noRestart: opts.noRestart });
|
|
@@ -5418,22 +6223,22 @@ function restartAfterProjectsAdd(opts) {
|
|
|
5418
6223
|
}
|
|
5419
6224
|
}
|
|
5420
6225
|
function findPackageRoot2() {
|
|
5421
|
-
let dir =
|
|
6226
|
+
let dir = path19.dirname(new URL(import.meta.url).pathname);
|
|
5422
6227
|
while (dir !== "/") {
|
|
5423
|
-
if (
|
|
5424
|
-
dir =
|
|
6228
|
+
if (fs17.existsSync(path19.join(dir, "package.json"))) return dir;
|
|
6229
|
+
dir = path19.dirname(dir);
|
|
5425
6230
|
}
|
|
5426
6231
|
return process.cwd();
|
|
5427
6232
|
}
|
|
5428
6233
|
function copyDirRecursive2(src, dest) {
|
|
5429
|
-
|
|
5430
|
-
for (const entry of
|
|
5431
|
-
const srcPath =
|
|
5432
|
-
const destPath =
|
|
6234
|
+
fs17.mkdirSync(dest, { recursive: true });
|
|
6235
|
+
for (const entry of fs17.readdirSync(src, { withFileTypes: true })) {
|
|
6236
|
+
const srcPath = path19.join(src, entry.name);
|
|
6237
|
+
const destPath = path19.join(dest, entry.name);
|
|
5433
6238
|
if (entry.isDirectory()) {
|
|
5434
6239
|
copyDirRecursive2(srcPath, destPath);
|
|
5435
6240
|
} else {
|
|
5436
|
-
|
|
6241
|
+
fs17.copyFileSync(srcPath, destPath);
|
|
5437
6242
|
}
|
|
5438
6243
|
}
|
|
5439
6244
|
}
|
|
@@ -5469,7 +6274,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5469
6274
|
process.exit(1);
|
|
5470
6275
|
}
|
|
5471
6276
|
const resolvedPath = resolveHome(projectPath);
|
|
5472
|
-
if (!
|
|
6277
|
+
if (!fs17.existsSync(path19.join(resolvedPath, ".git"))) {
|
|
5473
6278
|
console.log(chalk5.yellow(`
|
|
5474
6279
|
\u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
|
|
5475
6280
|
`));
|
|
@@ -5522,7 +6327,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5522
6327
|
\u26A0 Group '${group}' already has '${primary[0]}' as primary. Overriding.`));
|
|
5523
6328
|
}
|
|
5524
6329
|
}
|
|
5525
|
-
const spokeVault = opts.spoke ? resolveHome(opts.spoke) :
|
|
6330
|
+
const spokeVault = opts.spoke ? resolveHome(opts.spoke) : path19.join(config.hubVault, "spokes", name);
|
|
5526
6331
|
const project = {
|
|
5527
6332
|
path: resolvedPath,
|
|
5528
6333
|
captainName,
|
|
@@ -5537,20 +6342,20 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
|
|
|
5537
6342
|
\u2714 Project '${name}' registered`));
|
|
5538
6343
|
restartAfterProjectsAdd({ noRestart: opts.restart === false });
|
|
5539
6344
|
const pkgRoot = findPackageRoot2();
|
|
5540
|
-
const spokeTemplate =
|
|
5541
|
-
if (
|
|
6345
|
+
const spokeTemplate = path19.join(pkgRoot, "obsidian", "spoke");
|
|
6346
|
+
if (fs17.existsSync(spokeVault)) {
|
|
5542
6347
|
console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
|
|
5543
|
-
} else if (
|
|
6348
|
+
} else if (fs17.existsSync(spokeTemplate)) {
|
|
5544
6349
|
copyDirRecursive2(spokeTemplate, spokeVault);
|
|
5545
|
-
const statusPath =
|
|
5546
|
-
if (
|
|
5547
|
-
const content =
|
|
6350
|
+
const statusPath = path19.join(spokeVault, "status.md");
|
|
6351
|
+
if (fs17.existsSync(statusPath)) {
|
|
6352
|
+
const content = fs17.readFileSync(statusPath, "utf-8");
|
|
5548
6353
|
const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
|
|
5549
|
-
|
|
6354
|
+
fs17.writeFileSync(statusPath, updated);
|
|
5550
6355
|
}
|
|
5551
6356
|
console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
|
|
5552
6357
|
} else {
|
|
5553
|
-
|
|
6358
|
+
fs17.mkdirSync(spokeVault, { recursive: true });
|
|
5554
6359
|
console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
|
|
5555
6360
|
}
|
|
5556
6361
|
console.log("");
|
|
@@ -5651,10 +6456,10 @@ init_dist4();
|
|
|
5651
6456
|
init_dist();
|
|
5652
6457
|
import { Command as Command5 } from "commander";
|
|
5653
6458
|
import { execSync as execSync9 } from "child_process";
|
|
5654
|
-
import
|
|
5655
|
-
import
|
|
6459
|
+
import path20 from "path";
|
|
6460
|
+
import os10 from "os";
|
|
5656
6461
|
import chalk7 from "chalk";
|
|
5657
|
-
var
|
|
6462
|
+
var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
|
|
5658
6463
|
var TASK_PROMPTS = {
|
|
5659
6464
|
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
6465
|
"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 +6492,7 @@ async function runCommandSpawn(input) {
|
|
|
5687
6492
|
if (!agent) {
|
|
5688
6493
|
throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
|
|
5689
6494
|
}
|
|
5690
|
-
const promptFile =
|
|
6495
|
+
const promptFile = path20.join(TEMPLATES_DIR2, `command.${agent.templateSuffix}.md`);
|
|
5691
6496
|
const cliCommand = agent.buildCommand({
|
|
5692
6497
|
prompt,
|
|
5693
6498
|
workdir: process.cwd(),
|
|
@@ -5712,35 +6517,11 @@ var commandCommand = new Command5("command").description("Spawn a one-shot Comma
|
|
|
5712
6517
|
|
|
5713
6518
|
// packages/cli/src/commands/crew.ts
|
|
5714
6519
|
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
6520
|
init_dist3();
|
|
5743
6521
|
init_dist4();
|
|
6522
|
+
init_dist2();
|
|
6523
|
+
import { Command as Command9 } from "commander";
|
|
6524
|
+
import chalk9 from "chalk";
|
|
5744
6525
|
|
|
5745
6526
|
// packages/cli/src/commands/crew-control.ts
|
|
5746
6527
|
init_dist2();
|
|
@@ -5748,9 +6529,9 @@ init_dist2();
|
|
|
5748
6529
|
init_dist4();
|
|
5749
6530
|
import { Command as Command8 } from "commander";
|
|
5750
6531
|
import { createConnection as createConnection3 } from "net";
|
|
5751
|
-
import { randomUUID as
|
|
5752
|
-
import { homedir as
|
|
5753
|
-
import { join as
|
|
6532
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6533
|
+
import { homedir as homedir12 } from "os";
|
|
6534
|
+
import { join as join17 } from "path";
|
|
5754
6535
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5755
6536
|
|
|
5756
6537
|
// packages/cli/src/commands/crew-output.ts
|
|
@@ -5809,11 +6590,11 @@ init_dist2();
|
|
|
5809
6590
|
import { Command as Command6 } from "commander";
|
|
5810
6591
|
import chalk8 from "chalk";
|
|
5811
6592
|
import { createConnection as createConnection2 } from "net";
|
|
5812
|
-
import { homedir as
|
|
5813
|
-
import { join as
|
|
6593
|
+
import { homedir as homedir11 } from "os";
|
|
6594
|
+
import { join as join16 } from "path";
|
|
5814
6595
|
import { createInterface } from "readline";
|
|
5815
6596
|
function socketPath() {
|
|
5816
|
-
return process.env.SQUADRANTD_SOCK ??
|
|
6597
|
+
return process.env.SQUADRANTD_SOCK ?? join16(homedir11(), ".config", "squadrant", "squadrant.sock");
|
|
5817
6598
|
}
|
|
5818
6599
|
function rule(width, ch = "\u2500") {
|
|
5819
6600
|
return ch.repeat(Math.max(0, width));
|
|
@@ -6038,7 +6819,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
6038
6819
|
if (opts.provider !== "codex") {
|
|
6039
6820
|
throw new Error(`crew chat is implemented for provider=codex only (got '${opts.provider}')`);
|
|
6040
6821
|
}
|
|
6041
|
-
const pane = await
|
|
6822
|
+
const pane = await runCrewSpawn2({
|
|
6042
6823
|
project: opts.project,
|
|
6043
6824
|
task: "(interactive)",
|
|
6044
6825
|
agent: "codex",
|
|
@@ -6049,7 +6830,7 @@ var crewChatCommand = new Command7("chat").description("[DEPRECATED] alias for `
|
|
|
6049
6830
|
});
|
|
6050
6831
|
|
|
6051
6832
|
// packages/cli/src/commands/crew-control.ts
|
|
6052
|
-
var SOCK2 =
|
|
6833
|
+
var SOCK2 = join17(homedir12(), ".config", "squadrant", "squadrant.sock");
|
|
6053
6834
|
var CODEX_FIRST_TURN_DELAY_MS = 1500;
|
|
6054
6835
|
async function sendCodexFirstTurn(taskId, text) {
|
|
6055
6836
|
await new Promise((r) => setTimeout(r, CODEX_FIRST_TURN_DELAY_MS));
|
|
@@ -6076,11 +6857,11 @@ async function sendCodexFirstTurn(taskId, text) {
|
|
|
6076
6857
|
}
|
|
6077
6858
|
function buildDispatchRequest(o) {
|
|
6078
6859
|
const now = Date.now();
|
|
6079
|
-
const attemptId =
|
|
6860
|
+
const attemptId = randomUUID4();
|
|
6080
6861
|
return {
|
|
6081
6862
|
kind: "dispatch",
|
|
6082
6863
|
record: {
|
|
6083
|
-
id:
|
|
6864
|
+
id: randomUUID4(),
|
|
6084
6865
|
project: o.project,
|
|
6085
6866
|
provider: o.provider,
|
|
6086
6867
|
mode: o.mode,
|
|
@@ -6154,9 +6935,9 @@ function buildSignalRequest(signal, o) {
|
|
|
6154
6935
|
return { kind: "event", project, event };
|
|
6155
6936
|
}
|
|
6156
6937
|
function defaultWriteResult(id, payload) {
|
|
6157
|
-
const dir =
|
|
6938
|
+
const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
|
|
6158
6939
|
mkdirSync6(dir, { recursive: true });
|
|
6159
|
-
const file =
|
|
6940
|
+
const file = join17(dir, `${id}.txt`);
|
|
6160
6941
|
writeFileSync7(file, payload);
|
|
6161
6942
|
return file;
|
|
6162
6943
|
}
|
|
@@ -6257,7 +7038,7 @@ addControlPlaneCrewCommands(crewControlCommand);
|
|
|
6257
7038
|
// packages/cli/src/lib/per-crew-settings.ts
|
|
6258
7039
|
init_dist4();
|
|
6259
7040
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
6260
|
-
import { join as
|
|
7041
|
+
import { join as join18 } from "path";
|
|
6261
7042
|
var CREW_PERMISSION_ALLOWLIST = [
|
|
6262
7043
|
// git — read + safe mutations (reset/clean/config intentionally excluded)
|
|
6263
7044
|
"Bash(git status:*)",
|
|
@@ -6348,9 +7129,9 @@ function mergeCrewPermissions(settings) {
|
|
|
6348
7129
|
return next;
|
|
6349
7130
|
}
|
|
6350
7131
|
function writePerCrewSettingsLocal(o) {
|
|
6351
|
-
const dir =
|
|
7132
|
+
const dir = join18(o.projectCwd, ".claude");
|
|
6352
7133
|
mkdirSync7(dir, { recursive: true });
|
|
6353
|
-
const file =
|
|
7134
|
+
const file = join18(dir, "settings.local.json");
|
|
6354
7135
|
let existing = {};
|
|
6355
7136
|
try {
|
|
6356
7137
|
const raw = healStaleCockpitRefs(readFileSync9(file, "utf-8"));
|
|
@@ -6363,304 +7144,89 @@ function writePerCrewSettingsLocal(o) {
|
|
|
6363
7144
|
return file;
|
|
6364
7145
|
}
|
|
6365
7146
|
function writePerCrewOpencodeConfig(o) {
|
|
6366
|
-
const dir =
|
|
7147
|
+
const dir = join18(o.stateRoot, o.project, o.taskId);
|
|
6367
7148
|
mkdirSync7(dir, { recursive: true });
|
|
6368
|
-
const file =
|
|
7149
|
+
const file = join18(dir, "opencode.json");
|
|
6369
7150
|
const config = {
|
|
6370
7151
|
permission: {
|
|
6371
7152
|
read: "allow",
|
|
6372
7153
|
edit: "allow",
|
|
6373
7154
|
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
|
-
}
|
|
7155
|
+
grep: "allow",
|
|
7156
|
+
bash: o.gateBash ? "ask" : "allow",
|
|
7157
|
+
webfetch: "allow",
|
|
7158
|
+
websearch: "allow",
|
|
7159
|
+
task: "allow",
|
|
7160
|
+
lsp: "allow",
|
|
7161
|
+
external_directory: { "**": "allow" }
|
|
7162
|
+
}
|
|
7163
|
+
};
|
|
7164
|
+
writeFileSync8(file, JSON.stringify(config, null, 2));
|
|
7165
|
+
return file;
|
|
7166
|
+
}
|
|
7167
|
+
|
|
7168
|
+
// packages/cli/src/commands/crew.ts
|
|
7169
|
+
async function runCrewSpawn2(input) {
|
|
7170
|
+
const config = loadConfig();
|
|
7171
|
+
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(input.project, config);
|
|
6425
7172
|
const agents = new CapabilityRegistry({
|
|
6426
7173
|
claude: createClaudeDriver(),
|
|
6427
7174
|
codex: createCodexDriver(),
|
|
6428
7175
|
gemini: createGeminiDriver(),
|
|
6429
7176
|
opencode: createOpencodeDriver()
|
|
6430
7177
|
});
|
|
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
|
|
7178
|
+
return runCrewSpawn(input, config, {
|
|
7179
|
+
runtime,
|
|
7180
|
+
// AgentDriver satisfies ResolvedAgent structurally; `role: any` in ResolvedAgent
|
|
7181
|
+
// bridges the Role vs string gap — only "crew" is ever passed at call sites.
|
|
7182
|
+
resolveAgent: (name) => agents.get(name) ?? null,
|
|
7183
|
+
dispatchCrew: async (o) => {
|
|
7184
|
+
const req = buildDispatchRequest(o);
|
|
7185
|
+
return await squadrantdCall(req);
|
|
7186
|
+
},
|
|
7187
|
+
writeSettingsLocal: (cwd) => writePerCrewSettingsLocal({ projectCwd: cwd }),
|
|
7188
|
+
writeOpencodeConfig: writePerCrewOpencodeConfig,
|
|
7189
|
+
sendFirstTurn: (pane, firstTurn, preLaunchScreen, opts) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen, opts),
|
|
7190
|
+
getFreePort,
|
|
7191
|
+
sendCodexFirstTurn,
|
|
7192
|
+
onRouted: (route) => console.log(
|
|
7193
|
+
chalk9.dim(
|
|
7194
|
+
`routed: tier=${route.tier} \u2192 ${route.agent}${route.model ? `/${route.model}` : ""} (rule: "${route.matchedRule}")`
|
|
7195
|
+
)
|
|
7196
|
+
)
|
|
6579
7197
|
});
|
|
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
7198
|
}
|
|
6589
|
-
async function
|
|
7199
|
+
async function runCrewSend2(project, name, message) {
|
|
6590
7200
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
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
|
-
}
|
|
6604
|
-
}
|
|
6605
|
-
} catch {
|
|
6606
|
-
}
|
|
6607
|
-
await runtime.sendToPane(crew, message);
|
|
7201
|
+
return runCrewSend(project, name, message, runtime, workspaceId, {
|
|
7202
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
7203
|
+
emitEvent: async (p, event) => {
|
|
7204
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
7205
|
+
},
|
|
7206
|
+
// #448: use paste-settle-Enter confirmation for follow-up sends (same guard
|
|
7207
|
+
// as first-turn #447) so large messages don't strand in paste mode.
|
|
7208
|
+
sendToPane: (pane, msg) => confirmedSendToPane(runtime, pane, msg)
|
|
7209
|
+
});
|
|
6608
7210
|
}
|
|
6609
|
-
async function
|
|
7211
|
+
async function runCrewRead2(project, name) {
|
|
6610
7212
|
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);
|
|
7213
|
+
return runCrewRead(project, name, runtime, workspaceId);
|
|
6616
7214
|
}
|
|
6617
|
-
async function
|
|
7215
|
+
async function runCrewClose2(project, name) {
|
|
6618
7216
|
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
|
-
`);
|
|
7217
|
+
return runCrewClose(project, name, runtime, workspaceId, {
|
|
7218
|
+
listTasks: async (p) => await squadrantdCall({ kind: "list", project: p }),
|
|
7219
|
+
emitEvent: async (p, event) => {
|
|
7220
|
+
await squadrantdCall({ kind: "event", project: p, event });
|
|
7221
|
+
},
|
|
7222
|
+
closeCodexThread: async (taskId) => {
|
|
7223
|
+
await squadrantdCall({ kind: "codex-close", taskId });
|
|
6654
7224
|
}
|
|
6655
|
-
}
|
|
7225
|
+
});
|
|
6656
7226
|
}
|
|
6657
|
-
async function
|
|
7227
|
+
async function runCrewList2(project) {
|
|
6658
7228
|
const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
|
|
6659
|
-
|
|
6660
|
-
return crews.map((c) => ({
|
|
6661
|
-
name: nameFromTitle(project, c.title),
|
|
6662
|
-
surfaceId: c.surfaceId
|
|
6663
|
-
}));
|
|
7229
|
+
return runCrewList(project, runtime, workspaceId);
|
|
6664
7230
|
}
|
|
6665
7231
|
var crewCommand = new Command9("crew").description(
|
|
6666
7232
|
"Spawn and manage interactive crew sessions next to the project's captain"
|
|
@@ -6672,7 +7238,7 @@ crewCommand.command("spawn").description(
|
|
|
6672
7238
|
try {
|
|
6673
7239
|
const resolvedTask = await resolveTextInput({ positional: task, filePath: opts.taskFile, label: "task" });
|
|
6674
7240
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
6675
|
-
const pane = await
|
|
7241
|
+
const pane = await runCrewSpawn2({
|
|
6676
7242
|
project,
|
|
6677
7243
|
task: resolvedTask,
|
|
6678
7244
|
name: opts.name,
|
|
@@ -6694,7 +7260,7 @@ crewCommand.command("spawn").description(
|
|
|
6694
7260
|
);
|
|
6695
7261
|
crewCommand.command("list").description("List live crew sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6696
7262
|
try {
|
|
6697
|
-
const crews = await
|
|
7263
|
+
const crews = await runCrewList2(project);
|
|
6698
7264
|
if (crews.length === 0) {
|
|
6699
7265
|
console.log(chalk9.yellow(`No live crew sessions for ${project}.`));
|
|
6700
7266
|
return;
|
|
@@ -6710,7 +7276,7 @@ crewCommand.command("list").description("List live crew sessions for a project")
|
|
|
6710
7276
|
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
7277
|
try {
|
|
6712
7278
|
const resolvedMessage = await resolveTextInput({ positional: message, filePath: opts.messageFile, label: "message" });
|
|
6713
|
-
await
|
|
7279
|
+
await runCrewSend2(project, name, resolvedMessage);
|
|
6714
7280
|
console.log(chalk9.green(`\u2714 Sent to ${project}:${name}`));
|
|
6715
7281
|
} catch (err) {
|
|
6716
7282
|
console.error(chalk9.red(err.message));
|
|
@@ -6719,7 +7285,7 @@ crewCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6719
7285
|
});
|
|
6720
7286
|
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
7287
|
try {
|
|
6722
|
-
const screen = await
|
|
7288
|
+
const screen = await runCrewRead2(project, name);
|
|
6723
7289
|
const out = opts.full ? screen : tailLines(screen, Number(opts.lines ?? 40));
|
|
6724
7290
|
console.log(out);
|
|
6725
7291
|
} catch (err) {
|
|
@@ -6729,7 +7295,7 @@ crewCommand.command("read").description("Read the current screen of a crew sessi
|
|
|
6729
7295
|
});
|
|
6730
7296
|
crewCommand.command("close").description("Shutdown a crew session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Crew name").action(async (project, name) => {
|
|
6731
7297
|
try {
|
|
6732
|
-
await
|
|
7298
|
+
await runCrewClose2(project, name);
|
|
6733
7299
|
console.log(chalk9.green(`\u2714 Closed ${project}:${name}`));
|
|
6734
7300
|
} catch (err) {
|
|
6735
7301
|
console.error(chalk9.red(err.message));
|
|
@@ -6743,90 +7309,23 @@ init_dist3();
|
|
|
6743
7309
|
init_dist4();
|
|
6744
7310
|
init_dist3();
|
|
6745
7311
|
init_dist();
|
|
6746
|
-
|
|
7312
|
+
init_dist2();
|
|
6747
7313
|
import { Command as Command10 } from "commander";
|
|
6748
|
-
import
|
|
7314
|
+
import fs18 from "fs";
|
|
6749
7315
|
import path21 from "path";
|
|
6750
7316
|
import os11 from "os";
|
|
6751
7317
|
import chalk10 from "chalk";
|
|
6752
7318
|
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) {
|
|
7319
|
+
async function runSideSpawn2(input) {
|
|
6792
7320
|
const config = loadConfig();
|
|
6793
7321
|
const proj = config.projects[input.project];
|
|
6794
7322
|
if (!proj) {
|
|
6795
7323
|
throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);
|
|
6796
7324
|
}
|
|
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
7325
|
const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(
|
|
6803
7326
|
input.project,
|
|
6804
7327
|
config
|
|
6805
7328
|
);
|
|
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
7329
|
const agents = new CapabilityRegistry({
|
|
6831
7330
|
claude: createClaudeDriver(),
|
|
6832
7331
|
codex: createCodexDriver(),
|
|
@@ -6843,80 +7342,39 @@ async function runSideSpawn(input) {
|
|
|
6843
7342
|
const promptFile = path21.join(
|
|
6844
7343
|
TEMPLATES_DIR3,
|
|
6845
7344
|
`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({
|
|
7345
|
+
);
|
|
7346
|
+
const agentCmdFactory = (spawnCwd) => agent.buildCommand({
|
|
6851
7347
|
prompt: input.topic,
|
|
6852
7348
|
workdir: spawnCwd,
|
|
6853
7349
|
role: "side",
|
|
6854
|
-
promptFile:
|
|
7350
|
+
promptFile: fs18.existsSync(promptFile) ? promptFile : void 0,
|
|
6855
7351
|
interactive: true,
|
|
6856
7352
|
permissionMode: config.defaults.permissions?.crew ?? "auto",
|
|
6857
7353
|
...sideModel ? { model: sideModel } : {}
|
|
6858
7354
|
});
|
|
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 };
|
|
7355
|
+
const sendFirstTurn = (pane, firstTurn, preLaunchScreen) => sendFirstTurnWhenReady(runtime, pane, firstTurn, preLaunchScreen);
|
|
7356
|
+
return runSideSpawn(input, config, { runtime, agentCmdFactory, sendFirstTurn });
|
|
6870
7357
|
}
|
|
6871
|
-
async function
|
|
7358
|
+
async function runSideSend2(project, name, message) {
|
|
6872
7359
|
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);
|
|
7360
|
+
await runSideSend(runtime, workspaceId, project, name, message);
|
|
6882
7361
|
}
|
|
6883
|
-
async function
|
|
7362
|
+
async function runSideList2(project) {
|
|
6884
7363
|
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
|
-
}));
|
|
7364
|
+
return runSideList(runtime, workspaceId, project);
|
|
6890
7365
|
}
|
|
6891
|
-
async function
|
|
7366
|
+
async function runSideClose2(project, name) {
|
|
6892
7367
|
const config = loadConfig();
|
|
6893
7368
|
const proj = config.projects[project];
|
|
6894
7369
|
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
|
-
}
|
|
7370
|
+
await runSideClose(
|
|
7371
|
+
runtime,
|
|
7372
|
+
workspaceId,
|
|
7373
|
+
project,
|
|
7374
|
+
name,
|
|
7375
|
+
proj?.path,
|
|
7376
|
+
config.defaults.worktreeDir ?? ".worktrees"
|
|
7377
|
+
);
|
|
6920
7378
|
}
|
|
6921
7379
|
var sideCommand = new Command10("side").description(
|
|
6922
7380
|
"Spawn and manage side-sessions (research/debug) \u2014 fresh-context tabs off the daemon lifecycle"
|
|
@@ -6935,7 +7393,7 @@ sideCommand.command("spawn").description(
|
|
|
6935
7393
|
filePath: opts.topicFile,
|
|
6936
7394
|
label: "topic"
|
|
6937
7395
|
});
|
|
6938
|
-
const pane = await
|
|
7396
|
+
const pane = await runSideSpawn2({
|
|
6939
7397
|
project,
|
|
6940
7398
|
topic: resolvedTopic,
|
|
6941
7399
|
role: opts.role,
|
|
@@ -6952,7 +7410,7 @@ sideCommand.command("spawn").description(
|
|
|
6952
7410
|
);
|
|
6953
7411
|
sideCommand.command("list").description("List live side-sessions for a project").argument("<project>", "Project name").action(async (project) => {
|
|
6954
7412
|
try {
|
|
6955
|
-
const sessions = await
|
|
7413
|
+
const sessions = await runSideList2(project);
|
|
6956
7414
|
if (sessions.length === 0) {
|
|
6957
7415
|
console.log(chalk10.yellow(`No live side-sessions for ${project}.`));
|
|
6958
7416
|
return;
|
|
@@ -6973,7 +7431,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6973
7431
|
filePath: opts.messageFile,
|
|
6974
7432
|
label: "message"
|
|
6975
7433
|
});
|
|
6976
|
-
await
|
|
7434
|
+
await runSideSend2(project, name, resolvedMessage);
|
|
6977
7435
|
console.log(chalk10.green(`\u2714 Sent to ${project}:${name}`));
|
|
6978
7436
|
} catch (err) {
|
|
6979
7437
|
console.error(chalk10.red(err.message));
|
|
@@ -6983,7 +7441,7 @@ sideCommand.command("send").description("Send a follow-up message to an existing
|
|
|
6983
7441
|
);
|
|
6984
7442
|
sideCommand.command("close").description("Close a side-session (closes its tab)").argument("<project>", "Project name").argument("<name>", "Session name").action(async (project, name) => {
|
|
6985
7443
|
try {
|
|
6986
|
-
await
|
|
7444
|
+
await runSideClose2(project, name);
|
|
6987
7445
|
console.log(chalk10.green(`\u2714 Closed ${project}:${name}`));
|
|
6988
7446
|
} catch (err) {
|
|
6989
7447
|
console.error(chalk10.red(err.message));
|
|
@@ -6996,8 +7454,8 @@ init_dist();
|
|
|
6996
7454
|
init_dist3();
|
|
6997
7455
|
import { Command as Command11 } from "commander";
|
|
6998
7456
|
import { execSync as execSync10 } from "child_process";
|
|
6999
|
-
import { homedir as
|
|
7000
|
-
import { join as
|
|
7457
|
+
import { homedir as homedir14 } from "os";
|
|
7458
|
+
import { join as join20 } from "path";
|
|
7001
7459
|
import chalk12 from "chalk";
|
|
7002
7460
|
|
|
7003
7461
|
// packages/web/dist/read-status.js
|
|
@@ -7136,7 +7594,7 @@ function renderDashboard(rows, opts) {
|
|
|
7136
7594
|
|
|
7137
7595
|
// packages/web/dist/sync-hub.js
|
|
7138
7596
|
init_dist();
|
|
7139
|
-
import
|
|
7597
|
+
import fs19 from "fs";
|
|
7140
7598
|
import path22 from "path";
|
|
7141
7599
|
function buildMirrorMarkdown(s) {
|
|
7142
7600
|
const fenced = "```";
|
|
@@ -7163,8 +7621,8 @@ function buildMirrorMarkdown(s) {
|
|
|
7163
7621
|
function syncHub(deps) {
|
|
7164
7622
|
if (!deps.config.hubVault)
|
|
7165
7623
|
return [];
|
|
7166
|
-
const writeFile5 = deps.writeFile ?? ((p, c) =>
|
|
7167
|
-
const mkdir5 = deps.mkdir ?? ((p) =>
|
|
7624
|
+
const writeFile5 = deps.writeFile ?? ((p, c) => fs19.writeFileSync(p, c));
|
|
7625
|
+
const mkdir5 = deps.mkdir ?? ((p) => fs19.mkdirSync(p, { recursive: true }));
|
|
7168
7626
|
const projectsDir = path22.join(resolveHome(deps.config.hubVault), "projects");
|
|
7169
7627
|
mkdir5(projectsDir);
|
|
7170
7628
|
const out = [];
|
|
@@ -7193,10 +7651,10 @@ function mergeSnapshot(daemon, external, now) {
|
|
|
7193
7651
|
// packages/web/dist/probes.js
|
|
7194
7652
|
init_dist();
|
|
7195
7653
|
init_dist();
|
|
7196
|
-
import { join as
|
|
7197
|
-
import { homedir as
|
|
7654
|
+
import { join as join19 } from "path";
|
|
7655
|
+
import { homedir as homedir13 } from "os";
|
|
7198
7656
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
7199
|
-
import { execFile as
|
|
7657
|
+
import { execFile as execFile3 } from "child_process";
|
|
7200
7658
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
7201
7659
|
var AGENT_CLIS = ["claude", "codex", "gemini", "opencode"];
|
|
7202
7660
|
function withTimeout2(p, ms) {
|
|
@@ -7231,7 +7689,7 @@ function vaultProbe(run, dir) {
|
|
|
7231
7689
|
return { state: "unknown", detail: "no vault configured" };
|
|
7232
7690
|
if (!run.pathExists(dir))
|
|
7233
7691
|
return { state: "gone", detail: "vault directory missing" };
|
|
7234
|
-
if (!run.pathExists(
|
|
7692
|
+
if (!run.pathExists(join19(dir, ".obsidian")))
|
|
7235
7693
|
return { state: "gone", detail: "no .obsidian/ (not a vault)" };
|
|
7236
7694
|
return { state: "alive" };
|
|
7237
7695
|
} catch {
|
|
@@ -7299,10 +7757,10 @@ async function runExternalProbes(run, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
7299
7757
|
const sessions = probeSessions(run);
|
|
7300
7758
|
return { cmux: cmux2, agentClis, vaults, config: { parseable, projectPaths, sessions } };
|
|
7301
7759
|
}
|
|
7302
|
-
var SESSIONS_PATH =
|
|
7760
|
+
var SESSIONS_PATH = join19(homedir13(), ".config", "squadrant", "sessions.json");
|
|
7303
7761
|
function onPath(cli) {
|
|
7304
7762
|
const dirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
7305
|
-
return dirs.some((d) => existsSync10(
|
|
7763
|
+
return dirs.some((d) => existsSync10(join19(d, cli)));
|
|
7306
7764
|
}
|
|
7307
7765
|
function readSessionsHashes() {
|
|
7308
7766
|
const raw = JSON.parse(readFileSync10(SESSIONS_PATH, "utf-8"));
|
|
@@ -7313,7 +7771,7 @@ function defaultProbeRunners() {
|
|
|
7313
7771
|
return {
|
|
7314
7772
|
probeCmuxBin: () => new Promise((resolve3) => {
|
|
7315
7773
|
try {
|
|
7316
|
-
|
|
7774
|
+
execFile3(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
|
|
7317
7775
|
} catch {
|
|
7318
7776
|
resolve3(false);
|
|
7319
7777
|
}
|
|
@@ -7972,7 +8430,7 @@ async function startWebServer(opts) {
|
|
|
7972
8430
|
|
|
7973
8431
|
// packages/cli/src/commands/dashboard.ts
|
|
7974
8432
|
init_dist();
|
|
7975
|
-
var SOCK3 =
|
|
8433
|
+
var SOCK3 = join20(homedir14(), ".config", "squadrant", "squadrant.sock");
|
|
7976
8434
|
function detectCurrentWorkspace2() {
|
|
7977
8435
|
const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
|
|
7978
8436
|
const match = out.match(/workspace:\d+/);
|
|
@@ -8055,12 +8513,11 @@ dashboardCommand.command("sync-hub").description("Mirror each spoke status.md in
|
|
|
8055
8513
|
init_dist();
|
|
8056
8514
|
init_dist4();
|
|
8057
8515
|
init_dist3();
|
|
8058
|
-
|
|
8059
|
-
init_dist3();
|
|
8516
|
+
init_dist2();
|
|
8060
8517
|
init_dist2();
|
|
8061
8518
|
import { Command as Command12 } from "commander";
|
|
8062
8519
|
import { execSync as execSync11 } from "child_process";
|
|
8063
|
-
import
|
|
8520
|
+
import fs20 from "fs";
|
|
8064
8521
|
import path23 from "path";
|
|
8065
8522
|
import os12 from "os";
|
|
8066
8523
|
import chalk13 from "chalk";
|
|
@@ -8074,64 +8531,6 @@ function ensureCmuxReady() {
|
|
|
8074
8531
|
console.log(chalk13.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
|
|
8075
8532
|
process.exit(0);
|
|
8076
8533
|
}
|
|
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
8534
|
var launchCommand = new Command12("launch").description(
|
|
8136
8535
|
"Launch a project captain (with project arg) or all captains (--all). Use `squadrant command` for one-shot Command tasks."
|
|
8137
8536
|
).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 +8544,10 @@ var launchCommand = new Command12("launch").description(
|
|
|
8145
8544
|
const registry = new CapabilityRegistry(drivers);
|
|
8146
8545
|
const runtimes = new RuntimeRegistry({ cmux: createCmuxDriver() });
|
|
8147
8546
|
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
|
-
}
|
|
8547
|
+
ensureCmuxReady();
|
|
8156
8548
|
const roleConfig = config.defaults.roles?.[role];
|
|
8157
8549
|
const agentName = roleConfig?.agent || "claude";
|
|
8158
8550
|
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
8551
|
let initialPrompt;
|
|
8162
8552
|
if (role === "captain") {
|
|
8163
8553
|
initialPrompt = "Run your startup checklist: use the squadrant:captain-ops skill, complete all startup steps, then report ready.";
|
|
@@ -8166,19 +8556,44 @@ var launchCommand = new Command12("launch").description(
|
|
|
8166
8556
|
}
|
|
8167
8557
|
const runtime = projectName ? runtimes.forProject(projectName, config) : runtimes.global(config);
|
|
8168
8558
|
try {
|
|
8169
|
-
await
|
|
8559
|
+
await launchOneWorkspace({
|
|
8560
|
+
workspaceName,
|
|
8561
|
+
role,
|
|
8562
|
+
cwd,
|
|
8563
|
+
forceFreshOverride: opts.fresh,
|
|
8564
|
+
sessionsPath: SESSIONS_PATH2,
|
|
8565
|
+
templatesDir: TEMPLATES_DIR4,
|
|
8566
|
+
agentCmdFactory: (forceFresh) => buildAgentCmd(agentName, registry, role, forceFresh, permissionMode, model, TEMPLATES_DIR4),
|
|
8567
|
+
initialPrompt,
|
|
8568
|
+
runtime,
|
|
8569
|
+
navigate,
|
|
8570
|
+
pinToTop,
|
|
8571
|
+
classifyScreen: classifyStartupSurface,
|
|
8572
|
+
selectWorkspace: (id) => cmuxLocal(["select-workspace", "--workspace", id]),
|
|
8573
|
+
getCurrentWorkspace: () => {
|
|
8574
|
+
try {
|
|
8575
|
+
return cmuxLocal(["current-workspace"]);
|
|
8576
|
+
} catch {
|
|
8577
|
+
return null;
|
|
8578
|
+
}
|
|
8579
|
+
},
|
|
8580
|
+
onFreshReason: (reason) => console.log(chalk13.cyan(` \u21BB ${reason}`)),
|
|
8581
|
+
onStoppingStale: (name) => console.log(chalk13.yellow(` Closing stale workspace '${name}' for fresh start`)),
|
|
8582
|
+
onAlreadyExists: (name) => console.log(chalk13.yellow(` Workspace '${name}' already exists \u2014 switching to it`)),
|
|
8583
|
+
onCreated: (name) => console.log(chalk13.green(` \u2714 Workspace '${name}' created`))
|
|
8584
|
+
});
|
|
8170
8585
|
} catch (err) {
|
|
8171
8586
|
console.error(chalk13.red(` \u2718 Failed: ${err.message}`));
|
|
8172
8587
|
}
|
|
8173
8588
|
}
|
|
8174
8589
|
if (opts.all) {
|
|
8175
8590
|
const hubPath = resolveHome(config.hubVault);
|
|
8176
|
-
|
|
8591
|
+
fs20.mkdirSync(hubPath, { recursive: true });
|
|
8177
8592
|
console.log(chalk13.bold("\nLaunching all captain workspaces\n"));
|
|
8178
8593
|
for (const [name, proj] of Object.entries(config.projects)) {
|
|
8179
8594
|
const projPath = resolveHome(proj.path);
|
|
8180
8595
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8181
|
-
if (!
|
|
8596
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8182
8597
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
|
|
8183
8598
|
await ensureSpokeLayout(spokeDriver);
|
|
8184
8599
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8209,7 +8624,7 @@ var launchCommand = new Command12("launch").description(
|
|
|
8209
8624
|
const proj = config.projects[project];
|
|
8210
8625
|
const projPath = resolveHome(proj.path);
|
|
8211
8626
|
const spokePath = resolveHome(proj.spokeVault);
|
|
8212
|
-
if (!
|
|
8627
|
+
if (!fs20.existsSync(spokePath)) {
|
|
8213
8628
|
const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
|
|
8214
8629
|
await ensureSpokeLayout(spokeDriver);
|
|
8215
8630
|
console.log(chalk13.cyan(` \u2714 Created spoke vault at ${spokePath}`));
|
|
@@ -8344,7 +8759,7 @@ Shutting down captain workspace for '${project}'...
|
|
|
8344
8759
|
// packages/cli/src/commands/feedback.ts
|
|
8345
8760
|
init_dist();
|
|
8346
8761
|
import { Command as Command14 } from "commander";
|
|
8347
|
-
import
|
|
8762
|
+
import fs21 from "fs";
|
|
8348
8763
|
import os13 from "os";
|
|
8349
8764
|
import path24 from "path";
|
|
8350
8765
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -8354,14 +8769,14 @@ var REPO_URL = "https://github.com/tu11aa/squadrant";
|
|
|
8354
8769
|
function readPkgVersion() {
|
|
8355
8770
|
try {
|
|
8356
8771
|
const pkgPath = path24.join(path24.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8357
|
-
return JSON.parse(
|
|
8772
|
+
return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
8358
8773
|
} catch {
|
|
8359
8774
|
return "unknown";
|
|
8360
8775
|
}
|
|
8361
8776
|
}
|
|
8362
8777
|
function readMetrics(metricsPath) {
|
|
8363
8778
|
try {
|
|
8364
|
-
return JSON.parse(
|
|
8779
|
+
return JSON.parse(fs21.readFileSync(metricsPath, "utf-8"));
|
|
8365
8780
|
} catch {
|
|
8366
8781
|
return {};
|
|
8367
8782
|
}
|
|
@@ -8421,7 +8836,7 @@ init_dist();
|
|
|
8421
8836
|
init_dist();
|
|
8422
8837
|
init_dist3();
|
|
8423
8838
|
import { Command as Command15 } from "commander";
|
|
8424
|
-
import
|
|
8839
|
+
import fs22 from "fs";
|
|
8425
8840
|
import path25 from "path";
|
|
8426
8841
|
import chalk16 from "chalk";
|
|
8427
8842
|
import matter3 from "gray-matter";
|
|
@@ -8433,9 +8848,9 @@ async function getProjectStandup(name, project, dateStr, registry, config) {
|
|
|
8433
8848
|
const spokeVault = resolveHome(project.spokeVault);
|
|
8434
8849
|
const statusFile = path25.join(spokeVault, "status.md");
|
|
8435
8850
|
let status = {};
|
|
8436
|
-
if (
|
|
8851
|
+
if (fs22.existsSync(statusFile)) {
|
|
8437
8852
|
try {
|
|
8438
|
-
status = matter3(
|
|
8853
|
+
status = matter3(fs22.readFileSync(statusFile, "utf-8")).data;
|
|
8439
8854
|
} catch {
|
|
8440
8855
|
}
|
|
8441
8856
|
}
|
|
@@ -8553,15 +8968,15 @@ init_dist();
|
|
|
8553
8968
|
init_dist();
|
|
8554
8969
|
init_dist3();
|
|
8555
8970
|
import { Command as Command16 } from "commander";
|
|
8556
|
-
import
|
|
8971
|
+
import fs23 from "fs";
|
|
8557
8972
|
import path26 from "path";
|
|
8558
8973
|
import chalk17 from "chalk";
|
|
8559
8974
|
import matter4 from "gray-matter";
|
|
8560
8975
|
function readStatus(spokeVault) {
|
|
8561
8976
|
const statusFile = path26.join(spokeVault, "status.md");
|
|
8562
|
-
if (!
|
|
8977
|
+
if (!fs23.existsSync(statusFile)) return {};
|
|
8563
8978
|
try {
|
|
8564
|
-
return matter4(
|
|
8979
|
+
return matter4(fs23.readFileSync(statusFile, "utf-8")).data;
|
|
8565
8980
|
} catch {
|
|
8566
8981
|
return {};
|
|
8567
8982
|
}
|
|
@@ -8981,7 +9396,7 @@ init_dist3();
|
|
|
8981
9396
|
init_dist();
|
|
8982
9397
|
import { Command as Command20 } from "commander";
|
|
8983
9398
|
import chalk21 from "chalk";
|
|
8984
|
-
import
|
|
9399
|
+
import fs24 from "fs";
|
|
8985
9400
|
import path27 from "path";
|
|
8986
9401
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8987
9402
|
function parseScope(v) {
|
|
@@ -8993,7 +9408,7 @@ function parseScope(v) {
|
|
|
8993
9408
|
function findPackageRoot3() {
|
|
8994
9409
|
let dir = path27.dirname(fileURLToPath4(import.meta.url));
|
|
8995
9410
|
while (dir !== "/" && dir !== "") {
|
|
8996
|
-
if (
|
|
9411
|
+
if (fs24.existsSync(path27.join(dir, "package.json"))) return dir;
|
|
8997
9412
|
dir = path27.dirname(dir);
|
|
8998
9413
|
}
|
|
8999
9414
|
return process.cwd();
|
|
@@ -9187,13 +9602,14 @@ ${transcript.join("\n")}`
|
|
|
9187
9602
|
init_dist();
|
|
9188
9603
|
init_dist();
|
|
9189
9604
|
init_dist();
|
|
9605
|
+
init_dist2();
|
|
9190
9606
|
import { Command as Command22 } from "commander";
|
|
9191
|
-
import
|
|
9607
|
+
import fs25 from "fs";
|
|
9192
9608
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9193
|
-
import { dirname as dirname5, join as
|
|
9609
|
+
import { dirname as dirname5, join as join21 } from "path";
|
|
9194
9610
|
import chalk22 from "chalk";
|
|
9195
9611
|
function runConfigCheck(opts) {
|
|
9196
|
-
const raw = JSON.parse(
|
|
9612
|
+
const raw = JSON.parse(fs25.readFileSync(opts.configPath, "utf-8"));
|
|
9197
9613
|
const def = getDefaultConfig();
|
|
9198
9614
|
const items = detectDrift(raw, def);
|
|
9199
9615
|
let working = raw;
|
|
@@ -9210,7 +9626,7 @@ function runConfigCheck(opts) {
|
|
|
9210
9626
|
stamped = true;
|
|
9211
9627
|
}
|
|
9212
9628
|
if (opts.fix || opts.accept || stamped) {
|
|
9213
|
-
|
|
9629
|
+
fs25.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
|
|
9214
9630
|
}
|
|
9215
9631
|
return { items, applied, remaining, stamped };
|
|
9216
9632
|
}
|
|
@@ -9281,7 +9697,7 @@ function printItems(items) {
|
|
|
9281
9697
|
var configCommand = new Command22("config").description("Inspect and reconcile squadrant config");
|
|
9282
9698
|
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
9699
|
const pkgVersion = readPkgVersion2();
|
|
9284
|
-
if (!
|
|
9700
|
+
if (!fs25.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
9285
9701
|
console.log(chalk22.yellow("No config found \u2014 run `squadrant init` first."));
|
|
9286
9702
|
return;
|
|
9287
9703
|
}
|
|
@@ -9326,14 +9742,15 @@ configCommand.command("set").description("Write a config value by dotted key (e.
|
|
|
9326
9742
|
}
|
|
9327
9743
|
});
|
|
9328
9744
|
function readPkgVersion2() {
|
|
9329
|
-
const pkgPath =
|
|
9330
|
-
return JSON.parse(
|
|
9745
|
+
const pkgPath = join21(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
9746
|
+
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
9331
9747
|
}
|
|
9332
9748
|
|
|
9333
9749
|
// packages/cli/src/commands/heal.ts
|
|
9334
9750
|
import { Command as Command23 } from "commander";
|
|
9335
9751
|
import chalk23 from "chalk";
|
|
9336
9752
|
init_dist2();
|
|
9753
|
+
init_dist2();
|
|
9337
9754
|
function buildHealStatus(components) {
|
|
9338
9755
|
if (components === null) {
|
|
9339
9756
|
return { healthy: false, daemonUnreachable: true, components: [] };
|
|
@@ -9424,90 +9841,10 @@ var healCommand = new Command23("heal").description("Targeted, idempotent remedi
|
|
|
9424
9841
|
// packages/cli/src/commands/group.ts
|
|
9425
9842
|
init_dist();
|
|
9426
9843
|
init_dist2();
|
|
9844
|
+
init_dist2();
|
|
9427
9845
|
import { Command as Command24 } from "commander";
|
|
9428
9846
|
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
9847
|
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
9848
|
var groupCommand = new Command24("group").description("Cross-project intra-group operations (Phase 1: dispatch)").addCommand(
|
|
9512
9849
|
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
9850
|
const fromProject = resolveCurrentProject(loadConfig());
|
|
@@ -9516,13 +9853,20 @@ var groupCommand = new Command24("group").description("Cross-project intra-group
|
|
|
9516
9853
|
process.exit(1);
|
|
9517
9854
|
}
|
|
9518
9855
|
try {
|
|
9519
|
-
const result = await
|
|
9856
|
+
const result = await dispatchToSibling({
|
|
9520
9857
|
fromProject,
|
|
9521
9858
|
toProject,
|
|
9522
9859
|
task,
|
|
9523
9860
|
provider: opts.provider,
|
|
9524
9861
|
mode: opts.mode,
|
|
9525
|
-
warmupTimeoutMs: opts.warmupTimeout
|
|
9862
|
+
warmupTimeoutMs: opts.warmupTimeout,
|
|
9863
|
+
bootCaptain: async (project) => {
|
|
9864
|
+
try {
|
|
9865
|
+
execSync13(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
|
|
9866
|
+
} catch {
|
|
9867
|
+
throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9526
9870
|
});
|
|
9527
9871
|
console.log(chalk24.green(`\u2714 Dispatched to '${toProject}' (task ${result.id.slice(0, 8)})`));
|
|
9528
9872
|
console.log(chalk24.dim(` originProject: ${result.originProject ?? "none"}`));
|
|
@@ -9592,7 +9936,7 @@ var cmuxCommand = new Command25("cmux").description("cmux integration helpers").
|
|
|
9592
9936
|
|
|
9593
9937
|
// packages/cli/src/commands/effort.ts
|
|
9594
9938
|
init_dist();
|
|
9595
|
-
import
|
|
9939
|
+
import fs26 from "fs";
|
|
9596
9940
|
import path28 from "path";
|
|
9597
9941
|
import { Command as Command26 } from "commander";
|
|
9598
9942
|
import chalk26 from "chalk";
|
|
@@ -9620,7 +9964,7 @@ function runEffortSet(value, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
9620
9964
|
}
|
|
9621
9965
|
function canonical(p) {
|
|
9622
9966
|
try {
|
|
9623
|
-
return
|
|
9967
|
+
return fs26.realpathSync(p);
|
|
9624
9968
|
} catch {
|
|
9625
9969
|
return path28.resolve(p);
|
|
9626
9970
|
}
|
|
@@ -9676,59 +10020,6 @@ import chalk27 from "chalk";
|
|
|
9676
10020
|
function defaultStateRoot() {
|
|
9677
10021
|
return join22(dirname6(DEFAULT_CONFIG_PATH), "state");
|
|
9678
10022
|
}
|
|
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
10023
|
async function questionMasked() {
|
|
9733
10024
|
return new Promise((resolve3) => {
|
|
9734
10025
|
emitKeypressEvents(process.stdin);
|
|
@@ -9772,44 +10063,6 @@ async function questionYesNo(prompt) {
|
|
|
9772
10063
|
});
|
|
9773
10064
|
});
|
|
9774
10065
|
}
|
|
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
10066
|
var telegramCommand = new Command27("telegram").description("Two-way Telegram integration: push crew events to a topic and reply into the captain pane");
|
|
9814
10067
|
telegramCommand.command("status").description("Show Telegram config and linked projects").action(() => {
|
|
9815
10068
|
const { tokenSet, supergroupId, links } = runTelegramStatus({ config: loadConfig(), stateRoot: defaultStateRoot() });
|