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 CHANGED
@@ -34,14 +34,14 @@ function getDefaultConfig() {
34
34
  models: {
35
35
  command: "opus",
36
36
  captain: "opus",
37
- crew: "opus",
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: "opus" },
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: ["sonnet"] },
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 init_daemon = __esm({
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 init_delivery = __esm({
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
- init_daemon();
1492
+ init_reduce();
1493
1493
  init_probes();
1494
- init_delivery();
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/index.js
1926
- var init_dist2 = __esm({
1927
- "packages/core/dist/index.js"() {
1928
- init_daemon();
1929
- init_mailbox();
1930
- init_protocol();
1931
- init_state_machine();
1932
- init_liveness();
1933
- init_watchdog();
1934
- init_store();
1935
- init_snapshot();
1936
- init_launchd();
1937
- init_crew_pane_reader();
1938
- init_interfaces();
1939
- init_gate();
1940
- init_context();
1941
- init_attach();
1942
- init_start();
1943
- init_delivery();
1944
- init_interactive_probe();
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/workspaces/dist/runtimes/cmux.js
1955
- import { execFile, execFileSync as execFileSync4 } from "child_process";
1956
- function isInsideCmux() {
1957
- return !!process.env.CMUX_WORKSPACE_ID;
1958
- }
1959
- function cmuxLocal(args) {
1960
- return execFileSync4(resolveCmuxBin(), args, {
1961
- encoding: "utf-8",
1962
- stdio: ["ignore", "pipe", "pipe"],
1963
- timeout: CMUX_TIMEOUT
1964
- }).trim();
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 parseList(output) {
1987
- let parsed;
2131
+ async function isCaptainAlive(project, sockPath = DEFAULT_SOCK_PATH2) {
1988
2132
  try {
1989
- parsed = JSON.parse(output);
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 sanitizeForCmuxSend(text) {
2006
- return text.replace(/\\[nrt]/g, " ").replace(/[\n\r\t]+/g, " ").replace(/ {2,}/g, " ").trim();
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 parseDraftFromScreen(screen) {
2009
- if (!screen)
2010
- return null;
2011
- const lines = screen.split(/\r?\n/);
2012
- const HR_RE = /^\s*─{10,}\s*$/;
2013
- let bottomHR = -1;
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 (topHR === -1)
2026
- return null;
2027
- const inputLines = lines.slice(topHR + 1, bottomHR);
2028
- for (const line of inputLines) {
2029
- let extracted;
2030
- const boxMatch = line.match(/│\s*[>❯]\s+(.*?)\s*│/);
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 cmux(["send", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, sanitizeForCmuxSend(message)]);
2220
- await cmux(["send-key", "--workspace", pane.workspaceId, "--surface", pane.surfaceId, "Enter"]);
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 execFileSync5, execSync as execSync2 } from "child_process";
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
- execFileSync5("squadrant", ["runtime", "send", "--command", message], { encoding: "utf-8", timeout: CMUX_TIMEOUT });
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 fs11 from "fs/promises";
2470
- import { existsSync as existsSync8 } from "fs";
2471
- import path10 from "path";
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 = path10.resolve(root, relative);
2474
- const normalized = path10.resolve(root) + path10.sep;
2475
- if (joined !== path10.resolve(root) && !joined.startsWith(normalized)) {
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: existsSync8(root)
3280
+ rootExists: existsSync9(root)
2491
3281
  };
2492
3282
  },
2493
3283
  async read(rel) {
2494
- return fs11.readFile(resolveInRoot(root, rel), "utf-8");
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 fs11.mkdir(path10.dirname(abs), { recursive: true });
2499
- await fs11.writeFile(abs, content);
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 fs11.access(resolveInRoot(root, rel));
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 fs11.readdir(resolveInRoot(root, rel));
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 fs11.mkdir(resolveInRoot(root, rel), { recursive: true });
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
- init_cmux();
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
- await runtime.sendToPane(pane, task);
2825
- const retryLimit = acceptanceConfig?.retryLimit ?? 2;
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
- if (isTurnAccepted(preSendScreen, afterScreen, acceptanceConfig)) {
3662
+ const draft = parseDraftFromScreen(afterScreen);
3663
+ if (draft === "")
2830
3664
  return;
2831
- }
2832
- if (attempt < retryLimit - 1) {
2833
- await runtime.sendToPane(pane, task);
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 fs12 from "fs";
3187
- import path11 from "path";
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 = path11.join(templatesDir, `${role}.claude.md`);
3204
- const legacyRoleFile = path11.join(templatesDir, `${role}.CLAUDE.md`);
3205
- const actualRoleFile = fs12.existsSync(roleFile2) ? roleFile2 : fs12.existsSync(legacyRoleFile) ? legacyRoleFile : null;
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 = path11.join(templatesDir, "..", "plugin");
3210
- if (fs12.existsSync(pluginDir)) {
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 ? path11.join(templatesDir, `${role}.${driver.templateSuffix}.md`) : void 0;
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 && fs12.existsSync(roleFile) ? roleFile : void 0
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 path12 from "path";
3247
- import os4 from "os";
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: path12.join(os4.homedir(), ".cursor/rules/squadrant-global.mdc"),
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: path12.join(projectRoot, ".cursor/rules/squadrant.mdc"),
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(path12.dirname(dest.path), { recursive: true });
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 path13 from "path";
3374
- import os5 from "os";
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: path13.join(os5.homedir(), ".codex/AGENTS.md"),
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: path13.join(projectRoot, "AGENTS.md"),
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(path13.dirname(dest.path), { recursive: true });
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 path14 from "path";
3446
- import os6 from "os";
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: path14.join(os6.homedir(), ".gemini/GEMINI.md"),
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: path14.join(projectRoot, "GEMINI.md"),
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(path14.dirname(dest.path), { recursive: true });
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 path15 from "path";
3518
- import os7 from "os";
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: path15.join(os7.homedir(), ".config", "opencode", "AGENTS.md"),
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: path15.join(projectRoot, "AGENTS.md"),
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(path15.dirname(dest.path), { recursive: true });
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 homedir7 } from "os";
3813
- import { join as join12 } from "path";
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"] ?? join12(homedir7(), ".codex");
3816
- const configPath = join12(home, "config.toml");
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 homedir8 } from "os";
4339
- import { join as join13 } from "path";
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 join13(homedir8(), ".claude", "projects", escaped, `${sessionId}.jsonl`);
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 fs13 from "fs";
5688
+ import fs15 from "fs";
4850
5689
  import { stat } from "fs/promises";
4851
- import path16 from "path";
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 homedir6 } from "os";
4858
- import { join as join11 } from "path";
5696
+ import { homedir as homedir8 } from "os";
5697
+ import { join as join13 } from "path";
4859
5698
  import chalk2 from "chalk";
4860
- var SOCK = join11(homedir6(), ".config", "squadrant", "squadrant.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
- fs13.readFileSync(`${home}/.claude/settings.json`, "utf-8")
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
- fs13.readFileSync(
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") || fs13.existsSync("/Applications/Obsidian.app"),
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 = path16.dirname(userDest.path);
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
- fs13.existsSync(
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 fs14 from "fs";
5193
- import path17 from "path";
5194
- import os8 from "os";
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 = path17.dirname(new URL(import.meta.url).pathname);
6037
+ let dir = path18.dirname(new URL(import.meta.url).pathname);
5199
6038
  while (dir !== "/") {
5200
- if (fs14.existsSync(path17.join(dir, "package.json"))) return dir;
5201
- dir = path17.dirname(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
- fs14.mkdirSync(dest, { recursive: true });
5207
- for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
5208
- const srcPath = path17.join(src, entry.name);
5209
- const destPath = path17.join(dest, entry.name);
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
- fs14.copyFileSync(srcPath, destPath);
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 = path17.join(os8.homedir(), ".config", "squadrant");
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 (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
5257
- const existing = JSON.parse(fs14.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
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 (fs14.existsSync(DEFAULT_CONFIG_PATH)) {
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 = path17.join(pkgRoot, "obsidian", "hub");
5274
- if (fs14.existsSync(hubPath)) {
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 (fs14.existsSync(hubTemplate)) {
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
- fs14.mkdirSync(hubPath, { recursive: true });
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 = path17.join(pkgRoot, "obsidian", "hub", "dashboard.md");
5284
- const hubDashboardDest = path17.join(hubPath, "dashboard.md");
5285
- if (fs14.existsSync(hubDashboardSrc)) {
5286
- fs14.copyFileSync(hubDashboardSrc, hubDashboardDest);
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
- fs14.mkdirSync(path17.join(hubPath, "projects"), { recursive: true });
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 = path17.join(os8.homedir(), ".claude", "settings.json");
6132
+ const settingsPath = path18.join(os9.homedir(), ".claude", "settings.json");
5294
6133
  try {
5295
6134
  let settings = {};
5296
- if (fs14.existsSync(settingsPath)) {
5297
- settings = JSON.parse(fs14.readFileSync(settingsPath, "utf-8"));
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
- fs14.mkdirSync(path17.dirname(settingsPath), { recursive: true });
5303
- fs14.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
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 = path17.basename(projectPath);
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 fs15 from "fs";
5374
- import path18 from "path";
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 = path18.dirname(new URL(import.meta.url).pathname);
6226
+ let dir = path19.dirname(new URL(import.meta.url).pathname);
5422
6227
  while (dir !== "/") {
5423
- if (fs15.existsSync(path18.join(dir, "package.json"))) return dir;
5424
- dir = path18.dirname(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
- fs15.mkdirSync(dest, { recursive: true });
5430
- for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
5431
- const srcPath = path18.join(src, entry.name);
5432
- const destPath = path18.join(dest, entry.name);
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
- fs15.copyFileSync(srcPath, destPath);
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 (!fs15.existsSync(path18.join(resolvedPath, ".git"))) {
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) : path18.join(config.hubVault, "spokes", name);
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 = path18.join(pkgRoot, "obsidian", "spoke");
5541
- if (fs15.existsSync(spokeVault)) {
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 (fs15.existsSync(spokeTemplate)) {
6348
+ } else if (fs17.existsSync(spokeTemplate)) {
5544
6349
  copyDirRecursive2(spokeTemplate, spokeVault);
5545
- const statusPath = path18.join(spokeVault, "status.md");
5546
- if (fs15.existsSync(statusPath)) {
5547
- const content = fs15.readFileSync(statusPath, "utf-8");
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
- fs15.writeFileSync(statusPath, updated);
6354
+ fs17.writeFileSync(statusPath, updated);
5550
6355
  }
5551
6356
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
5552
6357
  } else {
5553
- fs15.mkdirSync(spokeVault, { recursive: true });
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 path19 from "path";
5655
- import os9 from "os";
6459
+ import path20 from "path";
6460
+ import os10 from "os";
5656
6461
  import chalk7 from "chalk";
5657
- var TEMPLATES_DIR = path19.join(os9.homedir(), ".config", "squadrant", "templates");
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 = path19.join(TEMPLATES_DIR, `command.${agent.templateSuffix}.md`);
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 randomUUID3 } from "crypto";
5752
- import { homedir as homedir11 } from "os";
5753
- import { join as join16 } from "path";
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 homedir10 } from "os";
5813
- import { join as join15 } from "path";
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 ?? join15(homedir10(), ".config", "squadrant", "squadrant.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 runCrewSpawn({
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 = join16(homedir11(), ".config", "squadrant", "squadrant.sock");
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 = randomUUID3();
6860
+ const attemptId = randomUUID4();
6080
6861
  return {
6081
6862
  kind: "dispatch",
6082
6863
  record: {
6083
- id: randomUUID3(),
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 = join16(homedir11(), ".config", "squadrant", "state", "_results");
6938
+ const dir = join17(homedir12(), ".config", "squadrant", "state", "_results");
6158
6939
  mkdirSync6(dir, { recursive: true });
6159
- const file = join16(dir, `${id}.txt`);
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 join17 } from "path";
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 = join17(o.projectCwd, ".claude");
7132
+ const dir = join18(o.projectCwd, ".claude");
6352
7133
  mkdirSync7(dir, { recursive: true });
6353
- const file = join17(dir, "settings.local.json");
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 = join17(o.stateRoot, o.project, o.taskId);
7147
+ const dir = join18(o.stateRoot, o.project, o.taskId);
6367
7148
  mkdirSync7(dir, { recursive: true });
6368
- const file = join17(dir, "opencode.json");
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
- init_dist2();
6389
- var TEMPLATES_DIR2 = path20.join(os10.homedir(), ".config", "squadrant", "templates");
6390
- async function runCrewSpawn(input) {
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
- const agentName = route?.agent ?? input.agent ?? "claude";
6432
- const agent = agents.get(agentName);
6433
- if (!agent) {
6434
- throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);
6435
- }
6436
- if (agentName === "codex") {
6437
- const codexRoleFile = path20.join(TEMPLATES_DIR2, `crew.${agent.templateSuffix}.md`);
6438
- const roleInstructions = fs16.existsSync(codexRoleFile) ? fs16.readFileSync(codexRoleFile, "utf8") : void 0;
6439
- return runCodexInteractiveSpawn({
6440
- project: input.project,
6441
- task: input.task,
6442
- cwd: spawnCwd,
6443
- runtime,
6444
- workspaceId: captain.id,
6445
- name,
6446
- direction: input.direction ?? "tab",
6447
- approvalPolicy: input.approvalPolicy,
6448
- roleInstructions
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 runCrewSend(project, name, message) {
7199
+ async function runCrewSend2(project, name, message) {
6590
7200
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6591
- const crew = await findCrew(runtime, workspaceId, project, name);
6592
- if (!crew) {
6593
- throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
6594
- }
6595
- try {
6596
- const tasks = await squadrantdCall({ kind: "list", project });
6597
- const task = tasks.find((t) => t.name === name);
6598
- if (task) {
6599
- if (TERMINAL_STATES.has(task.state)) {
6600
- await squadrantdCall({ kind: "event", project, event: { type: "task.reopened", id: task.id } });
6601
- } else if (task.state === "blocked" || task.state === "awaiting-input") {
6602
- await squadrantdCall({ kind: "event", project, event: { type: "task.started", id: task.id } });
6603
- }
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 runCrewRead(project, name) {
7211
+ async function runCrewRead2(project, name) {
6610
7212
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6611
- const crew = await findCrew(runtime, workspaceId, project, name);
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 runCrewClose(project, name) {
7215
+ async function runCrewClose2(project, name) {
6618
7216
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6619
- const projRoot = loadConfig().projects[project]?.path;
6620
- let taskId;
6621
- let worktreeCwd;
6622
- try {
6623
- const tasks = await squadrantdCall({ kind: "list", project });
6624
- const task = tasks.find((t) => t.name === name);
6625
- if (task) {
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 runCrewList(project) {
7227
+ async function runCrewList2(project) {
6658
7228
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6659
- const crews = await listProjectCrews(runtime, workspaceId, project);
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 runCrewSpawn({
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 runCrewList(project);
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 runCrewSend(project, name, resolvedMessage);
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 runCrewRead(project, name);
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 runCrewClose(project, name);
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
- init_dist();
7312
+ init_dist2();
6747
7313
  import { Command as Command10 } from "commander";
6748
- import fs17 from "fs";
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
- var SIDE_ROLES = ["research", "debug"];
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 direction = input.direction ?? "tab";
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: fs17.existsSync(promptFile) ? promptFile : void 0,
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
- await runtime.sendToPane(pane, `cd ${shellQuote2(spawnCwd)} && ${cliCommand}`);
6860
- const preLaunchScreen = await runtime.readPaneScreen(pane) ?? "";
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 runSideSend(project, name, message) {
7358
+ async function runSideSend2(project, name, message) {
6872
7359
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6873
- const want = titleFor2(project, name);
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 runSideList(project) {
7362
+ async function runSideList2(project) {
6884
7363
  const { runtime, workspaceId } = await resolveCaptainWorkspace(project);
6885
- const surfaces = await runtime.listSurfaces(workspaceId);
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 runSideClose(project, name) {
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
- const want = titleFor2(project, name);
6896
- const surfaces = await runtime.listSurfaces(workspaceId);
6897
- const pane = surfaces.find((s) => s.title === want) ?? null;
6898
- if (!pane) {
6899
- throw new Error(
6900
- `Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`
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 runSideSpawn({
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 runSideList(project);
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 runSideSend(project, name, resolvedMessage);
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 runSideClose(project, name);
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 homedir13 } from "os";
7000
- import { join as join19 } from "path";
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 fs18 from "fs";
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) => fs18.writeFileSync(p, c));
7167
- const mkdir5 = deps.mkdir ?? ((p) => fs18.mkdirSync(p, { recursive: true }));
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 join18 } from "path";
7197
- import { homedir as homedir12 } from "os";
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 execFile2 } from "child_process";
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(join18(dir, ".obsidian")))
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 = join18(homedir12(), ".config", "squadrant", "sessions.json");
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(join18(d, cli)));
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
- execFile2(resolveCmuxBin(), ["--version"], { timeout: 1500 }, (err) => resolve3(!err));
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 = join19(homedir13(), ".config", "squadrant", "squadrant.sock");
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
- init_dist();
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 fs19 from "fs";
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
- let forceFresh = !!opts.fresh;
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 launchWorkspace(runtime, workspaceName, agentCmd, cwd, navigate, forceFresh, pinToTop, initialPrompt);
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
- fs19.mkdirSync(hubPath, { recursive: true });
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 (!fs19.existsSync(spokePath)) {
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 (!fs19.existsSync(spokePath)) {
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 fs20 from "fs";
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(fs20.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
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(fs20.readFileSync(metricsPath, "utf-8"));
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 fs21 from "fs";
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 (fs21.existsSync(statusFile)) {
8851
+ if (fs22.existsSync(statusFile)) {
8437
8852
  try {
8438
- status = matter3(fs21.readFileSync(statusFile, "utf-8")).data;
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 fs22 from "fs";
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 (!fs22.existsSync(statusFile)) return {};
8977
+ if (!fs23.existsSync(statusFile)) return {};
8563
8978
  try {
8564
- return matter4(fs22.readFileSync(statusFile, "utf-8")).data;
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 fs23 from "fs";
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 (fs23.existsSync(path27.join(dir, "package.json"))) return dir;
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 fs24 from "fs";
9607
+ import fs25 from "fs";
9192
9608
  import { fileURLToPath as fileURLToPath5 } from "url";
9193
- import { dirname as dirname5, join as join20 } from "path";
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(fs24.readFileSync(opts.configPath, "utf-8"));
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
- fs24.writeFileSync(opts.configPath, JSON.stringify(working, null, 2) + "\n");
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 (!fs24.existsSync(DEFAULT_CONFIG_PATH)) {
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 = join20(dirname5(fileURLToPath5(import.meta.url)), "..", "package.json");
9330
- return JSON.parse(fs24.readFileSync(pkgPath, "utf-8")).version;
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 runGroupDispatch({
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 fs25 from "fs";
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 fs25.realpathSync(p);
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() });