squadrant 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -156,12 +156,7 @@ function getDefaultConfig() {
156
156
  // resolver fallback is the safety net).
157
157
  controlChannel: { claude: "on", opencode: "on" },
158
158
  crewRouting: {
159
- rules: [
160
- { tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
161
- { tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
162
- { tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
163
- { tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
164
- ]
159
+ rules: DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }))
165
160
  }
166
161
  },
167
162
  metrics: {
@@ -203,7 +198,7 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
203
198
  function resolveHome(p) {
204
199
  return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
205
200
  }
206
- var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH;
201
+ var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
207
202
  var init_config = __esm({
208
203
  "packages/shared/dist/config.js"() {
209
204
  init_config_io();
@@ -215,6 +210,201 @@ var init_config = __esm({
215
210
  DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
216
211
  CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
217
212
  DAEMON_SOCK_PATH = path2.join(CONFIG_DIR, "squadrant.sock");
213
+ DEFAULT_CREW_ROUTING_RULES = [
214
+ { tier: "extreme", match: "redesign|architect|rewrite|from scratch|deep reasoning", agent: "claude", model: "opus" },
215
+ { tier: "hard", match: "refactor|migrate|implement|feature|daemon|control-plane", agent: "claude", model: "sonnet" },
216
+ { tier: "mobile", match: "mobile|ios|swift|android|kotlin|react native", agent: "codex" },
217
+ { tier: "daily", match: "typo|rename|bump|docs|comment|lint|format", agent: "opencode" }
218
+ ];
219
+ }
220
+ });
221
+
222
+ // packages/shared/dist/provider-preset.js
223
+ function isProviderPresetId(v2) {
224
+ return v2 === "a" || v2 === "b" || v2 === "c" || v2 === "d";
225
+ }
226
+ function presetCrewRouting(id) {
227
+ const base = (tier) => {
228
+ const found = DEFAULT_CREW_ROUTING_RULES.find((r) => r.tier === tier);
229
+ return found ? { ...found } : { tier, match: "", agent: "claude" };
230
+ };
231
+ const codex = (tier) => ({
232
+ tier: base(tier).tier,
233
+ match: base(tier).match,
234
+ agent: "codex"
235
+ });
236
+ switch (id) {
237
+ case "a":
238
+ return DEFAULT_CREW_ROUTING_RULES.map((r) => ({ ...r }));
239
+ case "b":
240
+ return [
241
+ { ...base("extreme"), agent: "opencode", model: OPENCODE_MODEL },
242
+ { ...base("hard"), agent: "opencode", model: OPENCODE_MODEL },
243
+ codex("mobile"),
244
+ { ...base("daily"), agent: "opencode", model: OPENCODE_MODEL }
245
+ ];
246
+ case "c":
247
+ return [
248
+ { ...base("extreme"), agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL },
249
+ { ...base("hard"), agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL },
250
+ codex("mobile"),
251
+ { ...base("daily"), agent: "opencode", model: OPENCODE_MODEL }
252
+ ];
253
+ case "d":
254
+ return ["extreme", "hard", "mobile", "daily"].map(codex);
255
+ }
256
+ }
257
+ function providerPresetDefaults(id, router) {
258
+ switch (id) {
259
+ case "a":
260
+ return {
261
+ roles: {
262
+ command: { agent: "claude", model: "opus" },
263
+ captain: { agent: "claude", model: "opus" },
264
+ crew: { agent: "claude", model: "sonnet" },
265
+ exploration: { agent: "claude", model: "haiku" },
266
+ side: { agent: "claude", model: "opus" }
267
+ },
268
+ permissions: { command: "auto", captain: "auto", crew: "auto" },
269
+ crewRouting: { rules: presetCrewRouting("a") }
270
+ };
271
+ case "b": {
272
+ const opencode = { agent: "opencode", model: OPENCODE_MODEL };
273
+ return {
274
+ roles: {
275
+ command: { ...opencode },
276
+ captain: { ...opencode },
277
+ crew: { ...opencode },
278
+ exploration: { ...opencode },
279
+ side: { ...opencode }
280
+ },
281
+ permissions: { command: "auto", captain: "auto", crew: "auto" },
282
+ crewRouting: { rules: presetCrewRouting("b") }
283
+ };
284
+ }
285
+ case "c": {
286
+ if (!router) {
287
+ throw new Error("preset C (claude harness + router backend) requires a router config \u2014 set defaults.router first");
288
+ }
289
+ const routed = { agent: "claude", backend: "proxy", model: ROUTER_PRESET_MODEL };
290
+ return {
291
+ roles: {
292
+ command: { ...routed },
293
+ captain: { ...routed },
294
+ crew: { ...routed },
295
+ exploration: { ...routed },
296
+ side: { ...routed }
297
+ },
298
+ // Auto mode's classifier is hardcoded to Sonnet 5 and fails closed on a
299
+ // router upstream — every claude session must use a manual mode so the
300
+ // U7 gate owns the prompt.
301
+ permissions: { command: "default", captain: "default", crew: "default" },
302
+ crewRouting: { rules: presetCrewRouting("c") },
303
+ router,
304
+ gate: { mode: "on" }
305
+ };
306
+ }
307
+ case "d": {
308
+ const codex = { agent: "codex" };
309
+ return {
310
+ roles: {
311
+ command: { ...codex },
312
+ captain: { ...codex },
313
+ crew: { ...codex },
314
+ exploration: { ...codex },
315
+ side: { ...codex }
316
+ },
317
+ permissions: {},
318
+ crewRouting: { rules: presetCrewRouting("d") }
319
+ };
320
+ }
321
+ }
322
+ }
323
+ function detectProviderPresetId(config) {
324
+ const crew = config.defaults?.roles?.crew;
325
+ if (crew?.agent === "opencode")
326
+ return "b";
327
+ if (crew?.agent === "codex")
328
+ return "d";
329
+ if (crew?.backend && crew.backend !== "native")
330
+ return "c";
331
+ return "a";
332
+ }
333
+ function applyProviderPreset(config, id, opts) {
334
+ const preset = providerPresetDefaults(id, opts.router);
335
+ const next = structuredClone(config);
336
+ const changes = [];
337
+ if (opts.overwrite) {
338
+ next.defaults.roles = { ...preset.roles };
339
+ changes.push("defaults.roles");
340
+ } else if (next.defaults.roles === void 0) {
341
+ next.defaults.roles = { ...preset.roles };
342
+ changes.push("defaults.roles");
343
+ }
344
+ if (opts.overwrite) {
345
+ next.defaults.crewRouting = { rules: preset.crewRouting.rules.map((r) => ({ ...r })) };
346
+ changes.push("defaults.crewRouting");
347
+ } else if (next.defaults.crewRouting === void 0) {
348
+ next.defaults.crewRouting = { rules: preset.crewRouting.rules.map((r) => ({ ...r })) };
349
+ changes.push("defaults.crewRouting");
350
+ }
351
+ const existingPerms = next.defaults.permissions ?? {};
352
+ if (opts.overwrite) {
353
+ next.defaults.permissions = { ...existingPerms, ...preset.permissions };
354
+ if (Object.keys(preset.permissions).length > 0)
355
+ changes.push("defaults.permissions");
356
+ } else if (next.defaults.permissions === void 0 && Object.keys(preset.permissions).length > 0) {
357
+ next.defaults.permissions = { ...preset.permissions };
358
+ changes.push("defaults.permissions");
359
+ }
360
+ if (preset.router) {
361
+ if (opts.overwrite || next.defaults.router === void 0) {
362
+ next.defaults.router = structuredClone(preset.router);
363
+ changes.push("defaults.router");
364
+ }
365
+ }
366
+ if (preset.gate) {
367
+ if (opts.overwrite || next.defaults.gate === void 0) {
368
+ next.defaults.gate = { ...preset.gate };
369
+ changes.push("defaults.gate");
370
+ }
371
+ }
372
+ return { config: next, changes };
373
+ }
374
+ var PROVIDER_PRESETS, ROUTER_PRESET_MODEL, DEFAULT_ROUTER_KIND, DEFAULT_ROUTER_BASE_URL, OPENCODE_MODEL;
375
+ var init_provider_preset = __esm({
376
+ "packages/shared/dist/provider-preset.js"() {
377
+ init_config();
378
+ PROVIDER_PRESETS = [
379
+ {
380
+ id: "a",
381
+ label: "Claude Code (Pro/Max)",
382
+ summary: "Claude harness on your subscription/API key. Real auto mode.",
383
+ requiresAnthropic: true
384
+ },
385
+ {
386
+ id: "b",
387
+ label: "opencode (no Anthropic subscription)",
388
+ summary: "All roles run on the opencode harness. Auto mode is model-agnostic.",
389
+ requiresAnthropic: false
390
+ },
391
+ {
392
+ id: "c",
393
+ label: "Claude harness + router backend (advanced)",
394
+ summary: "Claude harness against a router upstream. The U7 gate replaces auto mode.",
395
+ requiresAnthropic: false
396
+ },
397
+ {
398
+ id: "d",
399
+ label: "Codex (ChatGPT Pro)",
400
+ summary: "All roles run on the codex harness.",
401
+ requiresAnthropic: false
402
+ }
403
+ ];
404
+ ROUTER_PRESET_MODEL = "deepseek-v4.1-flash";
405
+ DEFAULT_ROUTER_KIND = "opencode-go";
406
+ DEFAULT_ROUTER_BASE_URL = "https://opencode.ai/zen/go";
407
+ OPENCODE_MODEL = "opencode-go/deepseek-v4.1-flash";
218
408
  }
219
409
  });
220
410
 
@@ -1465,11 +1655,16 @@ __export(dist_exports, {
1465
1655
  CREW_SKILLS: () => CREW_SKILLS,
1466
1656
  DAEMON_SOCK_PATH: () => DAEMON_SOCK_PATH,
1467
1657
  DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
1658
+ DEFAULT_CREW_ROUTING_RULES: () => DEFAULT_CREW_ROUTING_RULES,
1468
1659
  DEFAULT_NOTIFY: () => DEFAULT_NOTIFY,
1660
+ DEFAULT_ROUTER_BASE_URL: () => DEFAULT_ROUTER_BASE_URL,
1661
+ DEFAULT_ROUTER_KIND: () => DEFAULT_ROUTER_KIND,
1469
1662
  GATE_MODES: () => GATE_MODES,
1470
1663
  GATE_POLICIES: () => GATE_POLICIES,
1471
1664
  MANAGED_TARGETS: () => MANAGED_TARGETS,
1665
+ PROVIDER_PRESETS: () => PROVIDER_PRESETS,
1472
1666
  ROUTER_KINDS: () => ROUTER_KINDS,
1667
+ ROUTER_PRESET_MODEL: () => ROUTER_PRESET_MODEL,
1473
1668
  SAFE_KINDS: () => SAFE_KINDS,
1474
1669
  SOCKET_CONTROL_MODE_PATH: () => SOCKET_CONTROL_MODE_PATH,
1475
1670
  SPOKE_SUBDIRS: () => SPOKE_SUBDIRS,
@@ -1478,6 +1673,7 @@ __export(dist_exports, {
1478
1673
  THINKING_LEVELS: () => THINKING_LEVELS,
1479
1674
  UPDATE_CHECK_STATE_PATH: () => UPDATE_CHECK_STATE_PATH,
1480
1675
  addWorktree: () => addWorktree,
1676
+ applyProviderPreset: () => applyProviderPreset,
1481
1677
  applySafeFixes: () => applySafeFixes,
1482
1678
  captainSessionName: () => captainSessionName,
1483
1679
  checkForUpdate: () => checkForUpdate,
@@ -1493,6 +1689,7 @@ __export(dist_exports, {
1493
1689
  defaultStatePath: () => defaultStatePath,
1494
1690
  detectDrift: () => detectDrift,
1495
1691
  detectInstallManager: () => detectInstallManager,
1692
+ detectProviderPresetId: () => detectProviderPresetId,
1496
1693
  ensureCmuxAutoConfig: () => ensureCmuxAutoConfig,
1497
1694
  ensureDirSync: () => ensureDirSync,
1498
1695
  ensureRuntimeSynced: () => ensureRuntimeSynced,
@@ -1511,6 +1708,7 @@ __export(dist_exports, {
1511
1708
  isGateMode: () => isGateMode,
1512
1709
  isGatePolicy: () => isGatePolicy,
1513
1710
  isNewerVersion: () => isNewerVersion,
1711
+ isProviderPresetId: () => isProviderPresetId,
1514
1712
  isQuieter: () => isQuieter,
1515
1713
  isRouterKind: () => isRouterKind,
1516
1714
  isThinkingLevel: () => isThinkingLevel,
@@ -1527,6 +1725,7 @@ __export(dist_exports, {
1527
1725
  parseThinkingLevel: () => parseThinkingLevel,
1528
1726
  probeCmuxDaemonDirect: () => probeCmuxDaemonDirect,
1529
1727
  projectConfigPath: () => projectConfigPath,
1728
+ providerPresetDefaults: () => providerPresetDefaults,
1530
1729
  readConfigFileSync: () => readConfigFileSync,
1531
1730
  readDailyLog: () => readDailyLog,
1532
1731
  readProjectLevelSource: () => readProjectLevelSource,
@@ -1556,6 +1755,7 @@ __export(dist_exports, {
1556
1755
  var init_dist = __esm({
1557
1756
  "packages/shared/dist/index.js"() {
1558
1757
  init_config();
1758
+ init_provider_preset();
1559
1759
  init_project_config();
1560
1760
  init_effort();
1561
1761
  init_router_model();
@@ -2793,8 +2993,8 @@ function reconcileLiveness(prev, next) {
2793
2993
  if (!prev)
2794
2994
  return next;
2795
2995
  if (next.source === "scan") {
2796
- const pidAlive = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
2797
- return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
2996
+ const pidAlive2 = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;
2997
+ return { ...prev, pidAlive: pidAlive2, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };
2798
2998
  }
2799
2999
  if (next.startedAt >= prev.startedAt || next.lastState === "end")
2800
3000
  return next;
@@ -3540,6 +3740,68 @@ function createDirectSurfaceLivenessProbe(cmux2, getCaptainTitle) {
3540
3740
  }
3541
3741
  };
3542
3742
  }
3743
+ function hasModalOptionList(screen) {
3744
+ if (!screen)
3745
+ return false;
3746
+ const lines = screen.split(/\r?\n/);
3747
+ let bottomHR = -1;
3748
+ let topHR = -1;
3749
+ for (let i = lines.length - 1; i >= 0; i--) {
3750
+ if (HR_RE.test(lines[i])) {
3751
+ if (bottomHR === -1)
3752
+ bottomHR = i;
3753
+ else {
3754
+ topHR = i;
3755
+ break;
3756
+ }
3757
+ }
3758
+ }
3759
+ if (topHR === -1)
3760
+ return false;
3761
+ return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
3762
+ }
3763
+ function classifyCaptainPane(screen, opts = {}) {
3764
+ if (screen == null || screen.trim() === "")
3765
+ return { status: "unreadable" };
3766
+ if (hasModalOptionList(screen))
3767
+ return { status: "locked" };
3768
+ if (WORKING_RE.test(screen))
3769
+ return { status: "mid-turn" };
3770
+ const idleAfterMs = opts.idleAfterMs ?? 0;
3771
+ const stale = opts.outputAgoMs !== void 0 && opts.outputAgoMs >= idleAfterMs;
3772
+ return stale ? { status: "no-session" } : { status: "mid-turn" };
3773
+ }
3774
+ function screenHash(screen) {
3775
+ let h = 2166136261;
3776
+ for (let i = 0; i < screen.length; i++) {
3777
+ h ^= screen.charCodeAt(i);
3778
+ h = Math.imul(h, 16777619) >>> 0;
3779
+ }
3780
+ return h.toString(16).padStart(8, "0");
3781
+ }
3782
+ function createCaptainPaneReader(cmux2, getCaptainTitle, opts = {}) {
3783
+ const idleAfterMs = opts.idleAfterMs ?? PANE_IDLE_AFTER_MS;
3784
+ return async (project) => {
3785
+ try {
3786
+ const wsId = await cmux2.findWorkspaceId(getCaptainTitle(project));
3787
+ if (!wsId)
3788
+ return { status: "no-session" };
3789
+ const surfaces = await cmux2.listSurfaces(wsId);
3790
+ const want = getCaptainTitle(project);
3791
+ const pane = surfaces.find((s) => s.title === want) ?? surfaces[0];
3792
+ if (!pane)
3793
+ return { status: "no-session" };
3794
+ const screen = await cmux2.readPaneScreen(pane);
3795
+ if (screen == null)
3796
+ return { status: "unreadable" };
3797
+ const outputAgoMs = opts.noteScreen?.(project, screenHash(screen));
3798
+ return classifyCaptainPane(screen, { outputAgoMs, idleAfterMs });
3799
+ } catch (e) {
3800
+ opts.log?.(`captain pane read failed project=${project}: ${e.message}`);
3801
+ return { status: "unreadable" };
3802
+ }
3803
+ };
3804
+ }
3543
3805
  function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
3544
3806
  return async (rec) => {
3545
3807
  try {
@@ -3562,11 +3824,14 @@ function createDirectCrewPaneReader(cmux2, getCaptainTitle) {
3562
3824
  }
3563
3825
  };
3564
3826
  }
3565
- var TAIL_LINES;
3827
+ var TAIL_LINES, HR_RE, WORKING_RE, PANE_IDLE_AFTER_MS;
3566
3828
  var init_crew_pane_reader = __esm({
3567
3829
  "packages/core/dist/crew-pane-reader.js"() {
3568
3830
  init_dist();
3569
3831
  TAIL_LINES = 25;
3832
+ HR_RE = /^\s*─{10,}\s*$/;
3833
+ WORKING_RE = /esc to interrupt|\btokens?\b.*\b(used|left)\b|↓\s*[\d.]+\s*k?\s*tokens?\b|\(\d+m?\s*\d+s\b/i;
3834
+ PANE_IDLE_AFTER_MS = 10 * 6e4;
3570
3835
  }
3571
3836
  });
3572
3837
 
@@ -5431,8 +5696,9 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
5431
5696
  if (mode !== "off")
5432
5697
  throw new DeferDelivery(null, "no-channel");
5433
5698
  }
5699
+ const paneOpts = { ...sendOpts, agent };
5434
5700
  try {
5435
- return await cmux2.send(surface, text, sendOpts);
5701
+ return await cmux2.send(surface, text, paneOpts);
5436
5702
  } catch (e) {
5437
5703
  if (!(e instanceof DeferDelivery) || e.reason !== "probe-failed")
5438
5704
  throw e;
@@ -5444,7 +5710,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
5444
5710
  }
5445
5711
  log(`delivery project=${project}: probe-failed on ${surface.workspaceId}/${surface.surfaceId} \u2014 re-resolved to ${next.workspaceId}/${next.surfaceId}, retrying`);
5446
5712
  surface = next;
5447
- return cmux2.send(next, text, sendOpts);
5713
+ return cmux2.send(next, text, paneOpts);
5448
5714
  }
5449
5715
  });
5450
5716
  if ("delivered" in result) {
@@ -6375,16 +6641,26 @@ function createRunCommand(cliBin) {
6375
6641
  }
6376
6642
  };
6377
6643
  }
6644
+ function captainAliveStateFromHealth(rows, project) {
6645
+ const row = rows.find((h) => h.kind === "captain" && h.project === project);
6646
+ if (!row)
6647
+ return "unknown";
6648
+ if (row.state === "alive")
6649
+ return "alive";
6650
+ if (row.state === "gone" || row.state === "stopped")
6651
+ return "dead";
6652
+ return "unknown";
6653
+ }
6378
6654
  function isCaptainAliveFromHealth(rows, project) {
6379
- return rows.some((h) => h.kind === "captain" && h.project === project && h.state === "alive");
6655
+ return captainAliveStateFromHealth(rows, project) === "alive";
6380
6656
  }
6381
6657
  function createIsCaptainAlive(sock) {
6382
6658
  return async (project) => {
6383
6659
  try {
6384
6660
  const health = await sendRequest(sock, { kind: "health", project }, 5e3);
6385
- return isCaptainAliveFromHealth(health ?? [], project);
6661
+ return captainAliveStateFromHealth(health ?? [], project);
6386
6662
  } catch {
6387
- return false;
6663
+ return "unknown";
6388
6664
  }
6389
6665
  };
6390
6666
  }
@@ -6421,16 +6697,19 @@ function createEnsureCaptainAlive(deps) {
6421
6697
  const now = deps.now ?? (() => Date.now());
6422
6698
  const inFlight = /* @__PURE__ */ new Map();
6423
6699
  async function run(project) {
6424
- if (await deps.isAlive(project))
6700
+ const first = await deps.isAlive(project);
6701
+ if (first === "alive")
6425
6702
  return "alive";
6426
6703
  await deps.launch(project);
6427
6704
  const deadline = now() + warmupTimeoutMs;
6705
+ let last = first;
6428
6706
  while (now() < deadline) {
6429
- if (await deps.isAlive(project))
6707
+ last = await deps.isAlive(project);
6708
+ if (last === "alive")
6430
6709
  return "launched";
6431
6710
  await sleep3(pollMs);
6432
6711
  }
6433
- return "timeout";
6712
+ return last === "dead" ? "timeout" : "unknown";
6434
6713
  }
6435
6714
  return function ensure(project) {
6436
6715
  const existing = inFlight.get(project);
@@ -6485,6 +6764,26 @@ ${ev.question}`;
6485
6764
  function formatInbound(text) {
6486
6765
  return `\u{1F4E9} [from Telegram] ${text}`;
6487
6766
  }
6767
+ function mediaKind(m) {
6768
+ for (const [field, name] of MEDIA_KINDS) {
6769
+ if (m[field] !== void 0)
6770
+ return name;
6771
+ }
6772
+ return void 0;
6773
+ }
6774
+ function mediaMarker(kind) {
6775
+ return `[${kind} attached - not forwarded]`;
6776
+ }
6777
+ function inboundBody(text, kind) {
6778
+ if (kind === void 0)
6779
+ return text ?? "";
6780
+ const marker = mediaMarker(kind);
6781
+ return text ? `${text}
6782
+ ${marker}` : marker;
6783
+ }
6784
+ function formatMediaReceipt(kind, hasCaption) {
6785
+ return hasCaption ? `\u{1F4CE} ${kind} not forwarded \u2014 your caption reached the captain` : `\u{1F4CE} ${kind} not forwarded \u2014 the captain was told it arrived, nothing else was sent`;
6786
+ }
6488
6787
  function formatUsageLine(u) {
6489
6788
  const reqs = u.requests === 1 ? "1 req" : `${u.requests} reqs`;
6490
6789
  const named = Object.keys(u.models).filter((m) => m !== "unknown").sort((a, b) => u.models[b].costUsd - u.models[a].costUsd);
@@ -6496,8 +6795,19 @@ function maskToken(token) {
6496
6795
  return token;
6497
6796
  return "*".repeat(token.length - 4) + token.slice(-4);
6498
6797
  }
6798
+ var MEDIA_KINDS;
6499
6799
  var init_format = __esm({
6500
6800
  "packages/core/dist/telegram/format.js"() {
6801
+ MEDIA_KINDS = [
6802
+ ["photo", "photo"],
6803
+ ["voice", "voice message"],
6804
+ ["video_note", "video message"],
6805
+ ["video", "video"],
6806
+ ["audio", "audio file"],
6807
+ ["document", "document"],
6808
+ ["animation", "animation"],
6809
+ ["sticker", "sticker"]
6810
+ ];
6501
6811
  }
6502
6812
  });
6503
6813
 
@@ -6520,6 +6830,8 @@ function loadState(stateRoot) {
6520
6830
  };
6521
6831
  if (typeof data.lastUserId === "number")
6522
6832
  result.lastUserId = data.lastUserId;
6833
+ if (data.pending && Object.keys(data.pending).length > 0)
6834
+ result.pending = data.pending;
6523
6835
  return result;
6524
6836
  } catch {
6525
6837
  return { offset: 0, topics: {}, notify: {} };
@@ -6533,6 +6845,19 @@ function setTopic(stateRoot, project, topicId, scope = "project") {
6533
6845
  s.topics[topicKey(project, scope)] = topicId;
6534
6846
  saveState(stateRoot, s);
6535
6847
  }
6848
+ function pruneTopics(stateRoot, keep) {
6849
+ const s = loadState(stateRoot);
6850
+ const removed = Object.keys(s.topics).filter((key) => {
6851
+ const sep3 = key.indexOf("::");
6852
+ return !keep(sep3 === -1 ? key : key.slice(0, sep3));
6853
+ });
6854
+ if (removed.length === 0)
6855
+ return [];
6856
+ for (const key of removed)
6857
+ delete s.topics[key];
6858
+ saveState(stateRoot, s);
6859
+ return removed;
6860
+ }
6536
6861
  function isNotifyActive(stateRoot, project) {
6537
6862
  return loadState(stateRoot).notify[project] === true;
6538
6863
  }
@@ -6546,6 +6871,42 @@ function setNotify(stateRoot, project, active) {
6546
6871
  s.notify[project] = active;
6547
6872
  saveState(stateRoot, s);
6548
6873
  }
6874
+ function loadPending(stateRoot) {
6875
+ return loadState(stateRoot).pending ?? {};
6876
+ }
6877
+ function setPending(stateRoot, project, p) {
6878
+ const s = loadState(stateRoot);
6879
+ s.pending = { ...s.pending ?? {}, [project]: p };
6880
+ saveState(stateRoot, s);
6881
+ }
6882
+ function clearPending(stateRoot, project) {
6883
+ const s = loadState(stateRoot);
6884
+ if (!s.pending || !(project in s.pending))
6885
+ return;
6886
+ delete s.pending[project];
6887
+ if (Object.keys(s.pending).length === 0)
6888
+ delete s.pending;
6889
+ saveState(stateRoot, s);
6890
+ }
6891
+ function markPendingWarned(stateRoot, project, at) {
6892
+ const s = loadState(stateRoot);
6893
+ const p = s.pending?.[project];
6894
+ if (!p)
6895
+ return;
6896
+ s.pending[project] = { ...p, warnedAt: at };
6897
+ saveState(stateRoot, s);
6898
+ }
6899
+ function notePaneScreen(stateRoot, project, hash, at) {
6900
+ const s = loadState(stateRoot);
6901
+ const p = s.pending?.[project];
6902
+ if (!p)
6903
+ return void 0;
6904
+ if (p.paneHash === hash && p.paneChangedAt !== void 0)
6905
+ return at - p.paneChangedAt;
6906
+ s.pending[project] = { ...p, paneHash: hash, paneChangedAt: at };
6907
+ saveState(stateRoot, s);
6908
+ return void 0;
6909
+ }
6549
6910
  function findProjectByThread(stateRoot, threadId) {
6550
6911
  const s = loadState(stateRoot);
6551
6912
  for (const [key, id] of Object.entries(s.topics)) {
@@ -6568,17 +6929,18 @@ var init_state = __esm({
6568
6929
  function createTelegramClient(opts) {
6569
6930
  const fetchImpl = opts.fetch ?? fetch;
6570
6931
  const base = `https://api.telegram.org/bot${opts.token}`;
6571
- async function call(method, body) {
6932
+ async function call(method, body, signal) {
6572
6933
  const res = await fetchImpl(`${base}/${method}`, {
6573
6934
  method: "POST",
6574
6935
  headers: { "content-type": "application/json" },
6575
- body: JSON.stringify(body)
6936
+ body: JSON.stringify(body),
6937
+ ...signal ? { signal } : {}
6576
6938
  });
6577
6939
  const json = await res.json();
6578
6940
  if (!res.ok || !json.ok) {
6579
6941
  const code = json.error_code ?? res.status;
6580
6942
  const desc = json.description ?? "unknown error";
6581
- throw new Error(`telegram ${method} failed (${code}): ${desc}`);
6943
+ throw new TelegramApiError(code, `telegram ${method} failed (${code}): ${desc}`, json.parameters?.retry_after);
6582
6944
  }
6583
6945
  return json.result;
6584
6946
  }
@@ -6587,8 +6949,8 @@ function createTelegramClient(opts) {
6587
6949
  const r = await call("getMe", {});
6588
6950
  return { id: r.id, username: r.username };
6589
6951
  },
6590
- getUpdates(offset, timeoutSec = 50) {
6591
- return call("getUpdates", { offset, timeout: timeoutSec });
6952
+ getUpdates(offset, timeoutSec = 50, signal) {
6953
+ return call("getUpdates", { offset, timeout: timeoutSec }, signal);
6592
6954
  },
6593
6955
  async sendMessage(chatId, threadId, text, replyMarkup) {
6594
6956
  const body = { chat_id: chatId, text };
@@ -6611,6 +6973,9 @@ function createTelegramClient(opts) {
6611
6973
  const r = await call("createForumTopic", { chat_id: chatId, name });
6612
6974
  return r.message_thread_id;
6613
6975
  },
6976
+ async deleteForumTopic(chatId, threadId) {
6977
+ await call("deleteForumTopic", { chat_id: chatId, message_thread_id: threadId });
6978
+ },
6614
6979
  async setMyCommands(commands) {
6615
6980
  await call("setMyCommands", { commands });
6616
6981
  },
@@ -6619,11 +6984,241 @@ function createTelegramClient(opts) {
6619
6984
  if (threadId !== void 0)
6620
6985
  body.message_thread_id = threadId;
6621
6986
  await call("sendChatAction", body);
6987
+ },
6988
+ async setMessageReaction(chatId, messageId, emoji) {
6989
+ await call("setMessageReaction", {
6990
+ chat_id: chatId,
6991
+ message_id: messageId,
6992
+ reaction: [{ type: "emoji", emoji }]
6993
+ });
6622
6994
  }
6623
6995
  };
6624
6996
  }
6997
+ var TelegramApiError;
6625
6998
  var init_client = __esm({
6626
6999
  "packages/core/dist/telegram/client.js"() {
7000
+ TelegramApiError = class extends Error {
7001
+ code;
7002
+ retryAfterSec;
7003
+ constructor(code, message, retryAfterSec) {
7004
+ super(message);
7005
+ this.code = code;
7006
+ this.retryAfterSec = retryAfterSec;
7007
+ this.name = "TelegramApiError";
7008
+ }
7009
+ };
7010
+ }
7011
+ });
7012
+
7013
+ // packages/core/dist/telegram/inbound-lifecycle.js
7014
+ function defaultSchedule(fn, ms) {
7015
+ const t = setTimeout(fn, ms);
7016
+ t.unref?.();
7017
+ return () => clearTimeout(t);
7018
+ }
7019
+ function formatWatchdogWarning(project, read, minutes) {
7020
+ const head = `\u23F1\uFE0F [${project}] no reply for ${minutes} minutes`;
7021
+ switch (read.status) {
7022
+ case "locked":
7023
+ return `${head} \u2014 the captain is LOCKED on a prompt, waiting for a human. Answer it in the pane (or with \`squadrant crew answer\`) and it will continue.`;
7024
+ case "mid-turn":
7025
+ return `${head} \u2014 the captain is still working (a turn is in flight), just slow. No action needed yet.`;
7026
+ case "no-session":
7027
+ return `${head} \u2014 the captain looks GONE (no live session in its pane). Run \`squadrant launch ${project}\`.`;
7028
+ case "unreadable":
7029
+ return `${head} \u2014 couldn't read the captain's pane to classify it. Check it manually.`;
7030
+ }
7031
+ }
7032
+ function createInboundLifecycle(opts) {
7033
+ const { stateRoot, cfg, sendChatAction, sendReply, readPane, log } = opts;
7034
+ const now = opts.now ?? Date.now;
7035
+ const schedule = opts.schedule ?? defaultSchedule;
7036
+ const typingMs = opts.typingMs ?? TYPING_KEEPALIVE_MS;
7037
+ const watchdogMs = opts.watchdogMs ?? WATCHDOG_MS;
7038
+ const warnAtMs = watchdogMs + (opts.watchdogMs === void 0 ? WATCHDOG_GRACE_MS : 0);
7039
+ const active = /* @__PURE__ */ new Set();
7040
+ let running = false;
7041
+ function issue(project, threadId) {
7042
+ void Promise.resolve(sendChatAction(cfg.supergroupId, threadId, "typing")).catch((e) => log(`telegram typing keep-alive failed project=${project}: ${e.message}`));
7043
+ }
7044
+ function drain(project) {
7045
+ active.add(project);
7046
+ const tick = () => {
7047
+ if (!running || !active.has(project))
7048
+ return;
7049
+ const p = loadPending(stateRoot)[project];
7050
+ if (!p) {
7051
+ active.delete(project);
7052
+ return;
7053
+ }
7054
+ if (now() - p.startedAt >= warnAtMs && p.warnedAt === void 0) {
7055
+ void warn(project, p.threadId);
7056
+ }
7057
+ issue(project, p.threadId);
7058
+ schedule(tick, typingMs);
7059
+ };
7060
+ schedule(tick, typingMs);
7061
+ }
7062
+ async function warn(project, threadId) {
7063
+ markPendingWarned(stateRoot, project, now());
7064
+ let read = { status: "unreadable" };
7065
+ try {
7066
+ if (readPane)
7067
+ read = await readPane(project);
7068
+ } catch (e) {
7069
+ log(`telegram watchdog pane read failed project=${project}: ${e.message}`);
7070
+ }
7071
+ try {
7072
+ await sendReply(threadId, formatWatchdogWarning(project, read, Math.floor(watchdogMs / 6e4)));
7073
+ } catch (e) {
7074
+ log(`telegram watchdog warning failed project=${project}: ${e.message}`);
7075
+ }
7076
+ }
7077
+ return {
7078
+ start() {
7079
+ running = true;
7080
+ for (const [project, p] of Object.entries(loadPending(stateRoot))) {
7081
+ void (async () => {
7082
+ let read = { status: "unreadable" };
7083
+ try {
7084
+ if (readPane)
7085
+ read = await readPane(project);
7086
+ } catch (e) {
7087
+ log(`telegram resume pane read failed project=${project}: ${e.message}`);
7088
+ }
7089
+ const still = loadPending(stateRoot)[project];
7090
+ if (!still || still.startedAt !== p.startedAt)
7091
+ return;
7092
+ if (read.status === "no-session") {
7093
+ clearPending(stateRoot, project);
7094
+ log(`telegram typing lifecycle cleared for ${project}: captain session gone on boot`);
7095
+ return;
7096
+ }
7097
+ log(`telegram typing lifecycle resumed for ${project} (thread ${p.threadId})`);
7098
+ issue(project, p.threadId);
7099
+ drain(project);
7100
+ })();
7101
+ }
7102
+ },
7103
+ stop() {
7104
+ running = false;
7105
+ active.clear();
7106
+ },
7107
+ begin(project, threadId) {
7108
+ setPending(stateRoot, project, { threadId, startedAt: now() });
7109
+ issue(project, threadId);
7110
+ if (running && !active.has(project))
7111
+ drain(project);
7112
+ },
7113
+ pending() {
7114
+ return loadPending(stateRoot);
7115
+ }
7116
+ };
7117
+ }
7118
+ var TYPING_KEEPALIVE_MS, WATCHDOG_MS, WATCHDOG_GRACE_MS;
7119
+ var init_inbound_lifecycle = __esm({
7120
+ "packages/core/dist/telegram/inbound-lifecycle.js"() {
7121
+ init_state();
7122
+ TYPING_KEEPALIVE_MS = 4e3;
7123
+ WATCHDOG_MS = 15 * 6e4;
7124
+ WATCHDOG_GRACE_MS = 1;
7125
+ }
7126
+ });
7127
+
7128
+ // packages/core/dist/telegram/notify.js
7129
+ function runTelegramStatus(opts) {
7130
+ const tg = opts.config.telegram;
7131
+ const env = opts.env ?? process.env;
7132
+ const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
7133
+ const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
7134
+ const sep3 = key.indexOf("::");
7135
+ return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
7136
+ });
7137
+ return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
7138
+ }
7139
+ function runTelegramNotifySet(opts) {
7140
+ setNotify(opts.stateRoot, opts.project, opts.active);
7141
+ }
7142
+ function runTelegramNotifyPref(args) {
7143
+ const { project, dimension, value, root } = args;
7144
+ if (dimension === "crew") {
7145
+ if (!["all", "alert_only", "done_only", "none"].includes(value))
7146
+ return { ok: false, message: "crew must be all|alert_only|done_only|none" };
7147
+ saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
7148
+ return { ok: true };
7149
+ }
7150
+ if (value !== "on" && value !== "off")
7151
+ return { ok: false, message: "cap must be on|off" };
7152
+ saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
7153
+ return { ok: true };
7154
+ }
7155
+ function runTelegramNotifyStatus(opts) {
7156
+ const s = loadState(opts.stateRoot);
7157
+ const projects = /* @__PURE__ */ new Set();
7158
+ for (const key of Object.keys(s.topics)) {
7159
+ const sep3 = key.indexOf("::");
7160
+ projects.add(sep3 === -1 ? key : key.slice(0, sep3));
7161
+ }
7162
+ for (const p of Object.keys(s.notify))
7163
+ projects.add(p);
7164
+ return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
7165
+ }
7166
+ function capAllowed(project, globalNotify, root) {
7167
+ return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
7168
+ }
7169
+ function confirmationText(project, before, after, dim) {
7170
+ if (dim === "active")
7171
+ return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
7172
+ if (dim === "cap")
7173
+ return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
7174
+ return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
7175
+ }
7176
+ async function runNotifyConfirmation(opts) {
7177
+ const { quieter, dim } = isQuieter(opts.before, opts.after);
7178
+ if (!quieter || dim === null)
7179
+ return false;
7180
+ const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7181
+ if (topicId === void 0)
7182
+ return false;
7183
+ const text = confirmationText(opts.project, opts.before, opts.after, dim);
7184
+ try {
7185
+ await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
7186
+ return true;
7187
+ } catch {
7188
+ console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
7189
+ return false;
7190
+ }
7191
+ }
7192
+ async function runTelegramSend(opts) {
7193
+ const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7194
+ if (topicId === void 0) {
7195
+ throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
7196
+ }
7197
+ await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
7198
+ clearPending(opts.stateRoot, opts.project);
7199
+ return { chatId: opts.cfg.supergroupId, topicId };
7200
+ }
7201
+ async function runTelegramLink(opts) {
7202
+ const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7203
+ if (existing !== void 0)
7204
+ return { topicId: existing, created: false };
7205
+ const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
7206
+ const winner = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7207
+ if (winner !== void 0) {
7208
+ try {
7209
+ await opts.client.deleteForumTopic?.(opts.cfg.supergroupId, topicId);
7210
+ } catch {
7211
+ }
7212
+ return { topicId: winner, created: false };
7213
+ }
7214
+ setTopic(opts.stateRoot, opts.project, topicId);
7215
+ return { topicId, created: true };
7216
+ }
7217
+ var init_notify = __esm({
7218
+ "packages/core/dist/telegram/notify.js"() {
7219
+ init_dist();
7220
+ init_state();
7221
+ init_format();
6627
7222
  }
6628
7223
  });
6629
7224
 
@@ -6711,8 +7306,10 @@ var init_tiers = __esm({
6711
7306
  });
6712
7307
 
6713
7308
  // packages/core/dist/telegram/bridge.js
7309
+ import fs11 from "fs";
6714
7310
  import os5 from "os";
6715
7311
  import path12 from "path";
7312
+ import crypto2 from "crypto";
6716
7313
  function formatInboundReceipt(project, outcome) {
6717
7314
  if (!outcome)
6718
7315
  return void 0;
@@ -6728,6 +7325,85 @@ function formatInboundReceipt(project, outcome) {
6728
7325
  return void 0;
6729
7326
  }
6730
7327
  }
7328
+ function telegramPollLockPath(token) {
7329
+ const hash = crypto2.createHash("sha256").update(token).digest("hex").slice(0, 16);
7330
+ return path12.join(os5.tmpdir(), `squadrant-telegram-poll-${hash}.lock`);
7331
+ }
7332
+ function pidAlive(pid) {
7333
+ try {
7334
+ process.kill(pid, 0);
7335
+ return true;
7336
+ } catch (e) {
7337
+ return e.code === "EPERM";
7338
+ }
7339
+ }
7340
+ function lockIsHeld(file) {
7341
+ let pid;
7342
+ let ageMs;
7343
+ try {
7344
+ const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
7345
+ pid = Number.isFinite(parsed) ? parsed : void 0;
7346
+ ageMs = Date.now() - fs11.statSync(file).mtimeMs;
7347
+ } catch {
7348
+ return false;
7349
+ }
7350
+ if (pid !== void 0)
7351
+ return pidAlive(pid);
7352
+ return ageMs < POLL_LOCK_STALE_MS;
7353
+ }
7354
+ function acquirePollLock(token) {
7355
+ const file = telegramPollLockPath(token);
7356
+ for (let attempt = 0; attempt < 2; attempt++) {
7357
+ try {
7358
+ const fd = fs11.openSync(file, "wx");
7359
+ fs11.writeSync(fd, String(process.pid));
7360
+ fs11.closeSync(fd);
7361
+ return () => {
7362
+ try {
7363
+ fs11.unlinkSync(file);
7364
+ } catch {
7365
+ }
7366
+ };
7367
+ } catch (e) {
7368
+ if (e.code !== "EEXIST")
7369
+ return null;
7370
+ if (attempt === 0 && !lockIsHeld(file)) {
7371
+ try {
7372
+ fs11.unlinkSync(file);
7373
+ } catch {
7374
+ }
7375
+ continue;
7376
+ }
7377
+ return null;
7378
+ }
7379
+ }
7380
+ return null;
7381
+ }
7382
+ function defaultSchedule2(fn, ms) {
7383
+ const t = setTimeout(fn, ms);
7384
+ t.unref?.();
7385
+ return () => clearTimeout(t);
7386
+ }
7387
+ function foreignPollLockHolder(token, selfPid = process.pid) {
7388
+ const file = telegramPollLockPath(token);
7389
+ let pid;
7390
+ try {
7391
+ const parsed = Number.parseInt(fs11.readFileSync(file, "utf8").trim(), 10);
7392
+ if (!Number.isFinite(parsed))
7393
+ return null;
7394
+ pid = parsed;
7395
+ } catch {
7396
+ return null;
7397
+ }
7398
+ if (pid === selfPid)
7399
+ return null;
7400
+ return pidAlive(pid) ? pid : null;
7401
+ }
7402
+ function pollBackoffMs(err, pollMs) {
7403
+ if (err instanceof TelegramApiError && err.retryAfterSec !== void 0)
7404
+ return err.retryAfterSec * 1e3;
7405
+ return pollMs;
7406
+ }
6731
7407
  function parseNotifyPref(text) {
6732
7408
  const parts = text.trim().split(/\s+/);
6733
7409
  if (stripBotMention(parts[0] ?? "").toLowerCase() !== "/notify")
@@ -6754,9 +7430,17 @@ function notifyToggle(text) {
6754
7430
  }
6755
7431
  function createTelegramBridge(opts) {
6756
7432
  const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
7433
+ const lifecycle = opts.lifecycle;
6757
7434
  const configRoot = opts.configRoot ?? path12.join(os5.homedir(), ".config", "squadrant");
6758
7435
  const pollMs = cfg.pollMs ?? 1e3;
7436
+ const schedule = opts.schedule ?? defaultSchedule2;
7437
+ const lockToken = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
7438
+ let releasePollLock = null;
7439
+ let pollAbort = null;
6759
7440
  let running = false;
7441
+ let pollStopped = false;
7442
+ let selfHealCancel = null;
7443
+ let conflictCount = 0;
6760
7444
  let lastSuccessfulPollAt = null;
6761
7445
  let lastError = null;
6762
7446
  let lastErrorAt = null;
@@ -6766,12 +7450,8 @@ function createTelegramBridge(opts) {
6766
7450
  saveState(stateRoot, s);
6767
7451
  }
6768
7452
  async function sendToTopic(project, text) {
6769
- let threadId = loadState(stateRoot).topics[topicKey(project)];
6770
- if (threadId === void 0) {
6771
- threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));
6772
- setTopic(stateRoot, project, threadId);
6773
- }
6774
- await client.sendMessage(cfg.supergroupId, threadId, text);
7453
+ const { topicId } = await runTelegramLink({ project, cfg, client, stateRoot });
7454
+ await client.sendMessage(cfg.supergroupId, topicId, text);
6775
7455
  }
6776
7456
  async function deliverOutbound(project, ev) {
6777
7457
  const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));
@@ -6958,10 +7638,13 @@ ${formatUsageLine(usage2)}`;
6958
7638
  }
6959
7639
  await runChannelCommand(text, fromId, void 0);
6960
7640
  }
6961
- async function handleProjectTopic(text, threadId, fromId) {
7641
+ async function handleProjectTopic(text, threadId, fromId, messageId, media) {
6962
7642
  const resolved = findProjectByThread(stateRoot, threadId);
6963
- if (!resolved)
7643
+ if (!resolved) {
7644
+ log(`telegram inbound in unbound topic thread=${threadId} \u2014 no project linked, message dropped`);
7645
+ await reply(threadId, "\u26A0\uFE0F this topic isn't linked to a project \u2014 run: squadrant telegram link <project>");
6964
7646
  return;
7647
+ }
6965
7648
  if (isBareSpawn(text)) {
6966
7649
  if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {
6967
7650
  await reply(threadId, "\u26D4 not authorized");
@@ -7011,21 +7694,16 @@ ${formatUsageLine(usage2)}`;
7011
7694
  await runChannelCommand(text, fromId, threadId);
7012
7695
  return;
7013
7696
  }
7014
- void client.sendChatAction(cfg.supergroupId, threadId, "typing").catch((e) => {
7015
- log(`telegram sendChatAction failed: ${e.message}`);
7016
- });
7697
+ if (messageId !== void 0) {
7698
+ void Promise.resolve(client.setMessageReaction?.(cfg.supergroupId, messageId, "\u{1F44D}")).catch((e) => log(`telegram stage-1 reaction failed project=${resolved.project}: ${e.message}`));
7699
+ }
7017
7700
  setNotify(stateRoot, resolved.project, true);
7018
7701
  if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {
7019
- try {
7020
- const r = await ensureCaptainAlive(resolved.project);
7702
+ void ensureCaptainAlive(resolved.project).then((r) => {
7021
7703
  if (r === "timeout") {
7022
- await reply(threadId, `\u274C couldn't reach ${resolved.project} captain \u2014 saved to mailbox, will deliver when you open the workspace.`);
7023
- } else {
7024
- await reply(threadId, `\u{1F4E8} delivered to ${resolved.project} captain`);
7704
+ log(`telegram auto-launch: warmup timed out project=${resolved.project} \u2014 delivery verdict (not this probe) decides the receipt`);
7025
7705
  }
7026
- } catch (e) {
7027
- log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`);
7028
- }
7706
+ }).catch((e) => log(`telegram auto-launch failed project=${resolved.project}: ${e.message}`));
7029
7707
  }
7030
7708
  let handled = false;
7031
7709
  let outcome;
@@ -7034,13 +7712,22 @@ ${formatUsageLine(usage2)}`;
7034
7712
  handled = res.handled;
7035
7713
  outcome = res.outcome;
7036
7714
  }
7715
+ const deliveryHeld = outcome?.status === "held";
7037
7716
  if (!handled) {
7038
7717
  await appendCaptainMessage2({ stateRoot, project: resolved.project, text: formatInbound(text), source: "telegram" });
7718
+ outcome = void 0;
7039
7719
  }
7040
7720
  const receipt = formatInboundReceipt(resolved.project, outcome);
7041
7721
  if (receipt && sendReply) {
7042
7722
  await sendReply(threadId, receipt).catch((e) => log(`telegram receipt failed project=${resolved.project}: ${e.message}`));
7043
7723
  }
7724
+ if (media) {
7725
+ await reply(threadId, formatMediaReceipt(media.kind, media.hasCaption));
7726
+ }
7727
+ if (deliveryHeld)
7728
+ return;
7729
+ await reply(threadId, CAPTAIN_RECEIVED_ACK);
7730
+ lifecycle?.begin(resolved.project, threadId);
7044
7731
  }
7045
7732
  async function handleUpdate(u) {
7046
7733
  if (u.callback_query) {
@@ -7048,7 +7735,11 @@ ${formatUsageLine(usage2)}`;
7048
7735
  return;
7049
7736
  }
7050
7737
  const m = u.message;
7051
- if (!m || m.text === void 0)
7738
+ if (!m)
7739
+ return;
7740
+ const kind = mediaKind(m);
7741
+ const text = m.text ?? m.caption;
7742
+ if (text === void 0 && kind === void 0)
7052
7743
  return;
7053
7744
  if (!cfg.chats.includes(m.chat.id))
7054
7745
  return;
@@ -7062,7 +7753,7 @@ ${formatUsageLine(usage2)}`;
7062
7753
  await reply(threadId, "\u26D4 not authorized");
7063
7754
  return;
7064
7755
  }
7065
- const task = m.text.trim();
7756
+ const task = (text ?? "").trim();
7066
7757
  if (!task) {
7067
7758
  await reply(threadId, "spawn cancelled \u2014 empty task");
7068
7759
  return;
@@ -7073,39 +7764,122 @@ ${formatUsageLine(usage2)}`;
7073
7764
  return;
7074
7765
  }
7075
7766
  if (m.message_thread_id === void 0) {
7767
+ if (m.text === void 0)
7768
+ return;
7076
7769
  await handleGeneral(m.text, m.from?.id);
7077
7770
  return;
7078
7771
  }
7079
- await handleProjectTopic(m.text, m.message_thread_id, m.from?.id);
7772
+ await handleProjectTopic(inboundBody(text, kind), m.message_thread_id, m.from?.id, m.message_id, kind === void 0 ? void 0 : { kind, hasCaption: text !== void 0 });
7773
+ }
7774
+ function acquirePollLockOrLog() {
7775
+ if (!lockToken)
7776
+ return true;
7777
+ releasePollLock = acquirePollLock(lockToken);
7778
+ if (releasePollLock)
7779
+ return true;
7780
+ const holder = foreignPollLockHolder(lockToken);
7781
+ log(`telegram inbound poll disabled: another consumer already polls this bot token (single-consumer guard, #830)${holder !== null ? ` \u2014 lock held by pid ${holder}` : ""}`);
7782
+ return false;
7783
+ }
7784
+ function scheduleSelfHeal() {
7785
+ selfHealCancel?.();
7786
+ selfHealCancel = schedule(() => {
7787
+ selfHealCancel = null;
7788
+ if (pollStopped || running)
7789
+ return;
7790
+ if (!acquirePollLockOrLog()) {
7791
+ scheduleSelfHeal();
7792
+ return;
7793
+ }
7794
+ running = true;
7795
+ conflictCount = 0;
7796
+ log("telegram inbound poll re-attempting after a 409 stop (#850)");
7797
+ void pollLoop();
7798
+ }, CONFLICT_409_SELF_HEAL_MS);
7080
7799
  }
7081
7800
  async function pollLoop() {
7082
7801
  while (running) {
7802
+ const ac = new AbortController();
7803
+ pollAbort = ac;
7804
+ let waitMs = pollMs;
7083
7805
  try {
7084
7806
  const offset = loadState(stateRoot).offset;
7085
- const updates = await client.getUpdates(offset, LONG_POLL_SEC);
7807
+ const updates = await client.getUpdates(offset, LONG_POLL_SEC, ac.signal);
7808
+ conflictCount = 0;
7086
7809
  for (const u of updates) {
7087
7810
  await handleUpdate(u);
7088
7811
  persistOffset(u.update_id + 1);
7089
7812
  }
7090
7813
  lastSuccessfulPollAt = Date.now();
7091
7814
  } catch (e) {
7092
- lastError = e.message;
7815
+ if (ac.signal.aborted)
7816
+ break;
7817
+ const msg = e.message;
7818
+ lastError = msg;
7093
7819
  lastErrorAt = Date.now();
7094
- log(`telegram inbound poll failed: ${e.message}`);
7820
+ if (e instanceof TelegramApiError && e.code === 409) {
7821
+ conflictCount += 1;
7822
+ if (conflictCount >= CONFLICT_409_TERMINAL_AFTER) {
7823
+ log(`telegram inbound poll stopped: repeated 409 conflict (another getUpdates consumer holds this token) \u2014 ${msg}`);
7824
+ break;
7825
+ }
7826
+ } else {
7827
+ log(`telegram inbound poll failed: ${msg}`);
7828
+ }
7829
+ waitMs = pollBackoffMs(e, pollMs);
7830
+ } finally {
7831
+ pollAbort = null;
7095
7832
  }
7096
7833
  if (running)
7097
- await sleep2(pollMs);
7834
+ await sleep2(waitMs);
7098
7835
  }
7836
+ running = false;
7837
+ releasePollLock?.();
7838
+ releasePollLock = null;
7839
+ if (!pollStopped)
7840
+ scheduleSelfHeal();
7099
7841
  }
7100
7842
  return {
7101
7843
  start() {
7102
7844
  if (running)
7103
7845
  return;
7846
+ pollStopped = false;
7847
+ selfHealCancel?.();
7848
+ selfHealCancel = null;
7849
+ if (!acquirePollLockOrLog())
7850
+ return;
7851
+ const known = opts.registeredProjects?.();
7852
+ if (known !== void 0) {
7853
+ try {
7854
+ const removed = pruneTopics(stateRoot, (p) => known.includes(p));
7855
+ if (removed.length > 0)
7856
+ log(`telegram registry pruned ${removed.length} stale topic link(s): ${removed.join(", ")}`);
7857
+ } catch (e) {
7858
+ log(`telegram registry prune failed: ${e.message}`);
7859
+ }
7860
+ }
7104
7861
  running = true;
7862
+ conflictCount = 0;
7863
+ try {
7864
+ lifecycle?.start();
7865
+ } catch (e) {
7866
+ log(`telegram lifecycle start failed: ${e.message}`);
7867
+ }
7105
7868
  void pollLoop();
7106
7869
  },
7107
7870
  stop() {
7108
7871
  running = false;
7872
+ pollStopped = true;
7873
+ selfHealCancel?.();
7874
+ selfHealCancel = null;
7875
+ try {
7876
+ lifecycle?.stop();
7877
+ } catch (e) {
7878
+ log(`telegram lifecycle stop failed: ${e.message}`);
7879
+ }
7880
+ pollAbort?.abort();
7881
+ releasePollLock?.();
7882
+ releasePollLock = null;
7109
7883
  },
7110
7884
  pushLifecycle(project, ev) {
7111
7885
  void deliverOutbound(project, ev).catch((e) => {
@@ -7122,17 +7896,23 @@ ${formatUsageLine(usage2)}`;
7122
7896
  }
7123
7897
  };
7124
7898
  }
7125
- var LONG_POLL_SEC, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
7899
+ var CAPTAIN_RECEIVED_ACK, LONG_POLL_SEC, POLL_LOCK_STALE_MS, CONFLICT_409_TERMINAL_AFTER, CONFLICT_409_SELF_HEAL_MS, sleep2, CREW_TIERS, RECOGNIZED_CHANNEL_COMMANDS;
7126
7900
  var init_bridge = __esm({
7127
7901
  "packages/core/dist/telegram/bridge.js"() {
7128
7902
  init_dist();
7903
+ init_client();
7129
7904
  init_auth();
7130
7905
  init_commands();
7131
7906
  init_format();
7907
+ init_notify();
7132
7908
  init_panels();
7133
7909
  init_state();
7134
7910
  init_tiers();
7911
+ CAPTAIN_RECEIVED_ACK = "\u2705 captain received";
7135
7912
  LONG_POLL_SEC = 50;
7913
+ POLL_LOCK_STALE_MS = 2 * 6e4;
7914
+ CONFLICT_409_TERMINAL_AFTER = 3;
7915
+ CONFLICT_409_SELF_HEAL_MS = 5 * 6e4;
7136
7916
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
7137
7917
  CREW_TIERS = ["all", "alert_only", "done_only", "none"];
7138
7918
  RECOGNIZED_CHANNEL_COMMANDS = /* @__PURE__ */ new Set(["status", "projects", "crews", "launch", "effort", "spawn"]);
@@ -7177,7 +7957,7 @@ var init_restart_daemon = __esm({
7177
7957
  });
7178
7958
 
7179
7959
  // packages/core/dist/telegram/setup.js
7180
- import fs11 from "fs";
7960
+ import fs12 from "fs";
7181
7961
  function resolveSetupGroup(existingSupergroupId, opts) {
7182
7962
  if (existingSupergroupId !== void 0 && !opts.redetect)
7183
7963
  return "reuse";
@@ -7228,7 +8008,7 @@ function writeTelegramConfig(configPath, opts) {
7228
8008
  let config;
7229
8009
  let raw = null;
7230
8010
  try {
7231
- raw = fs11.readFileSync(configPath, "utf-8");
8011
+ raw = fs12.readFileSync(configPath, "utf-8");
7232
8012
  } catch (err) {
7233
8013
  if (err.code !== "ENOENT") {
7234
8014
  throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);
@@ -7267,94 +8047,6 @@ var init_setup = __esm({
7267
8047
  }
7268
8048
  });
7269
8049
 
7270
- // packages/core/dist/telegram/notify.js
7271
- function runTelegramStatus(opts) {
7272
- const tg = opts.config.telegram;
7273
- const env = opts.env ?? process.env;
7274
- const tokenSet = !!(tg?.botToken ?? env.TELEGRAM_BOT_TOKEN);
7275
- const links = Object.entries(loadState(opts.stateRoot).topics).map(([key, topicId]) => {
7276
- const sep3 = key.indexOf("::");
7277
- return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2), topicId };
7278
- });
7279
- return { tokenSet, supergroupId: tg?.supergroupId ?? null, links };
7280
- }
7281
- function runTelegramNotifySet(opts) {
7282
- setNotify(opts.stateRoot, opts.project, opts.active);
7283
- }
7284
- function runTelegramNotifyPref(args) {
7285
- const { project, dimension, value, root } = args;
7286
- if (dimension === "crew") {
7287
- if (!["all", "alert_only", "done_only", "none"].includes(value))
7288
- return { ok: false, message: "crew must be all|alert_only|done_only|none" };
7289
- saveProjectOverride(project, { telegram: { notify: { crew: value } } }, root);
7290
- return { ok: true };
7291
- }
7292
- if (value !== "on" && value !== "off")
7293
- return { ok: false, message: "cap must be on|off" };
7294
- saveProjectOverride(project, { telegram: { notify: { cap: value === "on" } } }, root);
7295
- return { ok: true };
7296
- }
7297
- function runTelegramNotifyStatus(opts) {
7298
- const s = loadState(opts.stateRoot);
7299
- const projects = /* @__PURE__ */ new Set();
7300
- for (const key of Object.keys(s.topics)) {
7301
- const sep3 = key.indexOf("::");
7302
- projects.add(sep3 === -1 ? key : key.slice(0, sep3));
7303
- }
7304
- for (const p of Object.keys(s.notify))
7305
- projects.add(p);
7306
- return [...projects].map((project) => ({ project, active: s.notify[project] === true }));
7307
- }
7308
- function capAllowed(project, globalNotify, root) {
7309
- return resolveNotify(globalNotify, loadProjectOverride(project, root)).cap;
7310
- }
7311
- function confirmationText(project, before, after, dim) {
7312
- if (dim === "active")
7313
- return `\u{1F515} ${project} \u2014 all notifications muted here. Unmute: squadrant telegram notify ${project} on`;
7314
- if (dim === "cap")
7315
- return `\u{1F515} ${project} \u2014 captain messages muted here. Re-enable: squadrant telegram notify ${project} cap on`;
7316
- return `\u{1F515} ${project} \u2014 crew notifications now '${after.crew}' (was '${before.crew}'). Re-enable: squadrant telegram notify ${project} crew ${before.crew}`;
7317
- }
7318
- async function runNotifyConfirmation(opts) {
7319
- const { quieter, dim } = isQuieter(opts.before, opts.after);
7320
- if (!quieter || dim === null)
7321
- return false;
7322
- const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7323
- if (topicId === void 0)
7324
- return false;
7325
- const text = confirmationText(opts.project, opts.before, opts.after, dim);
7326
- try {
7327
- await opts.client.sendMessage(opts.cfg.supergroupId, topicId, text);
7328
- return true;
7329
- } catch {
7330
- console.warn(`[squadrant] mute-confirmation send failed for ${opts.project} \u2014 notification preference was still saved`);
7331
- return false;
7332
- }
7333
- }
7334
- async function runTelegramSend(opts) {
7335
- const topicId = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7336
- if (topicId === void 0) {
7337
- throw new Error(`project "${opts.project}" is not linked \u2014 run: squadrant telegram link ${opts.project}`);
7338
- }
7339
- await opts.client.sendMessage(opts.cfg.supergroupId, topicId, opts.message);
7340
- return { chatId: opts.cfg.supergroupId, topicId };
7341
- }
7342
- async function runTelegramLink(opts) {
7343
- const existing = loadState(opts.stateRoot).topics[topicKey(opts.project)];
7344
- if (existing !== void 0)
7345
- return { topicId: existing, created: false };
7346
- const topicId = await opts.client.createForumTopic(opts.cfg.supergroupId, topicName(opts.project));
7347
- setTopic(opts.stateRoot, opts.project, topicId);
7348
- return { topicId, created: true };
7349
- }
7350
- var init_notify = __esm({
7351
- "packages/core/dist/telegram/notify.js"() {
7352
- init_dist();
7353
- init_state();
7354
- init_format();
7355
- }
7356
- });
7357
-
7358
8050
  // packages/core/dist/telegram/index.js
7359
8051
  var init_telegram = __esm({
7360
8052
  "packages/core/dist/telegram/index.js"() {
@@ -7366,6 +8058,7 @@ var init_telegram = __esm({
7366
8058
  init_format();
7367
8059
  init_state();
7368
8060
  init_client();
8061
+ init_inbound_lifecycle();
7369
8062
  init_bridge();
7370
8063
  init_setup();
7371
8064
  init_notify();
@@ -8631,7 +9324,7 @@ var init_launch_workspace = __esm({
8631
9324
  });
8632
9325
 
8633
9326
  // packages/core/dist/side-session.js
8634
- import fs12 from "fs";
9327
+ import fs13 from "fs";
8635
9328
  function sideTitleFor(project, name) {
8636
9329
  return `\u{1F5D2} ${project}:${name}`;
8637
9330
  }
@@ -8733,7 +9426,7 @@ async function runSideClose(runtime, workspaceId, project, name, projPath, workt
8733
9426
  await runtime.closePane(pane);
8734
9427
  if (projPath) {
8735
9428
  const wtPath = worktreePath(projPath, worktreeDir, project, name);
8736
- if (fs12.existsSync(wtPath)) {
9429
+ if (fs13.existsSync(wtPath)) {
8737
9430
  try {
8738
9431
  removeWorktree(projPath, wtPath);
8739
9432
  } catch (e) {
@@ -9170,8 +9863,10 @@ __export(dist_exports2, {
9170
9863
  AGENT_BINS: () => AGENT_BINS,
9171
9864
  ANTHROPIC_ONLY_REQUEST_FIELDS: () => ANTHROPIC_ONLY_REQUEST_FIELDS,
9172
9865
  BOT_COMMANDS: () => BOT_COMMANDS,
9866
+ CAPTAIN_RECEIVED_ACK: () => CAPTAIN_RECEIVED_ACK,
9173
9867
  CC_SOCKS_DIR: () => CC_SOCKS_DIR,
9174
9868
  CMUX_PRESERVE_CLAUDE_AUTH_ENV: () => CMUX_PRESERVE_CLAUDE_AUTH_ENV,
9869
+ CONFLICT_409_SELF_HEAL_MS: () => CONFLICT_409_SELF_HEAL_MS,
9175
9870
  CREW_GONE_MS: () => CREW_GONE_MS,
9176
9871
  CREW_STALE_MS: () => CREW_STALE_MS,
9177
9872
  CaptainDelivery: () => CaptainDelivery,
@@ -9191,6 +9886,7 @@ __export(dist_exports2, {
9191
9886
  LivenessRegistry: () => LivenessRegistry,
9192
9887
  MONITOR_STALL_BUDGET_MS: () => MONITOR_STALL_BUDGET_MS,
9193
9888
  OPERATOR_INITIATED_COMMANDS: () => OPERATOR_INITIATED_COMMANDS,
9889
+ PANE_IDLE_AFTER_MS: () => PANE_IDLE_AFTER_MS,
9194
9890
  PROBE_QUIET_MS: () => PROBE_QUIET_MS,
9195
9891
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
9196
9892
  READ_ONLY_CREW_SUBCOMMANDS: () => READ_ONLY_CREW_SUBCOMMANDS,
@@ -9203,6 +9899,10 @@ __export(dist_exports2, {
9203
9899
  TERMINAL_RECORD_KEEP_PER_PROJECT: () => TERMINAL_RECORD_KEEP_PER_PROJECT,
9204
9900
  TERMINAL_RECORD_TTL_MS: () => TERMINAL_RECORD_TTL_MS,
9205
9901
  TOOL_STALL_BUDGET_MS: () => TOOL_STALL_BUDGET_MS,
9902
+ TYPING_KEEPALIVE_MS: () => TYPING_KEEPALIVE_MS,
9903
+ TelegramApiError: () => TelegramApiError,
9904
+ WATCHDOG_GRACE_MS: () => WATCHDOG_GRACE_MS,
9905
+ WATCHDOG_MS: () => WATCHDOG_MS,
9206
9906
  WORK_ITEM_TTL_MS: () => WORK_ITEM_TTL_MS,
9207
9907
  WRITABLE_CONFIG_KEYS: () => WRITABLE_CONFIG_KEYS,
9208
9908
  _resetRestartInFlightForTest: () => _resetRestartInFlightForTest,
@@ -9224,15 +9924,19 @@ __export(dist_exports2, {
9224
9924
  callClassifier: () => callClassifier,
9225
9925
  capAllowed: () => capAllowed,
9226
9926
  capOutput: () => capOutput,
9927
+ captainAliveStateFromHealth: () => captainAliveStateFromHealth,
9227
9928
  captainRecordPath: () => captainRecordPath,
9228
9929
  captainSocketPath: () => captainSocketPath,
9229
9930
  checkFact: () => checkFact,
9931
+ classifyCaptainPane: () => classifyCaptainPane,
9230
9932
  classifyHealth: () => classifyHealth,
9231
9933
  claudeEnvShadowsRouter: () => claudeEnvShadowsRouter,
9934
+ clearPending: () => clearPending,
9232
9935
  closeWorkItem: () => closeWorkItem,
9233
9936
  computeTemplateHash: () => computeTemplateHash,
9234
9937
  consumeExitMarker: () => consumeExitMarker,
9235
9938
  createAttach: () => createAttach,
9939
+ createCaptainPaneReader: () => createCaptainPaneReader,
9236
9940
  createCrewPaneReader: () => createCrewPaneReader,
9237
9941
  createDaemon: () => createDaemon,
9238
9942
  createDecoder: () => createDecoder,
@@ -9241,6 +9945,7 @@ __export(dist_exports2, {
9241
9945
  createDirectSurfaceLivenessProbe: () => createDirectSurfaceLivenessProbe,
9242
9946
  createEnsureCaptainAlive: () => createEnsureCaptainAlive,
9243
9947
  createEventsSource: () => createEventsSource,
9948
+ createInboundLifecycle: () => createInboundLifecycle,
9244
9949
  createInteractiveProbe: () => createInteractiveProbe,
9245
9950
  createIsCaptainAlive: () => createIsCaptainAlive,
9246
9951
  createLaunch: () => createLaunch,
@@ -9296,12 +10001,16 @@ __export(dist_exports2, {
9296
10001
  formatInbound: () => formatInbound,
9297
10002
  formatInboundReceipt: () => formatInboundReceipt,
9298
10003
  formatLifecycle: () => formatLifecycle,
10004
+ formatMediaReceipt: () => formatMediaReceipt,
9299
10005
  formatPermissionDecision: () => formatPermissionDecision,
9300
10006
  formatUsageLine: () => formatUsageLine,
10007
+ formatWatchdogWarning: () => formatWatchdogWarning,
9301
10008
  freshTrace: () => freshTrace,
9302
10009
  gateCacheKey: () => gateCacheKey,
9303
10010
  getDaemonPid: () => getDaemonPid,
10011
+ hasModalOptionList: () => hasModalOptionList,
9304
10012
  healCmdFor: () => healCmdFor,
10013
+ inboundBody: () => inboundBody,
9305
10014
  isAuthorized: () => isAuthorized,
9306
10015
  isBareSpawn: () => isBareSpawn,
9307
10016
  isCaptainAlive: () => isCaptainAlive,
@@ -9323,18 +10032,23 @@ __export(dist_exports2, {
9323
10032
  kickstartArgv: () => kickstartArgv,
9324
10033
  launchOneWorkspace: () => launchOneWorkspace,
9325
10034
  listSessions: () => listSessions,
10035
+ loadPending: () => loadPending,
9326
10036
  loadSessions: () => loadSessions,
9327
10037
  loadState: () => loadState,
9328
10038
  mailboxStats: () => mailboxStats,
9329
10039
  makeGate: () => makeGate,
10040
+ markPendingWarned: () => markPendingWarned,
9330
10041
  maskToken: () => maskToken,
9331
10042
  matchTier1Deny: () => matchTier1Deny,
10043
+ mediaKind: () => mediaKind,
10044
+ mediaMarker: () => mediaMarker,
9332
10045
  mergeClaudeEnvRouterSettings: () => mergeClaudeEnvRouterSettings,
9333
10046
  mintToken: () => mintToken,
9334
10047
  nameFromTitle: () => nameFromTitle,
9335
10048
  newestSessionInDirectory: () => newestSessionInDirectory,
9336
10049
  nextAutoName: () => nextAutoName,
9337
10050
  niceCrewCommand: () => niceCrewCommand,
10051
+ notePaneScreen: () => notePaneScreen,
9338
10052
  notifyToggle: () => notifyToggle,
9339
10053
  parseBearer: () => parseBearer,
9340
10054
  parseClassifierVerdict: () => parseClassifierVerdict,
@@ -9343,6 +10057,7 @@ __export(dist_exports2, {
9343
10057
  parseNotifyPref: () => parseNotifyPref,
9344
10058
  parseProgramArgs: () => parseProgramArgs,
9345
10059
  plistPath: () => plistPath,
10060
+ pollBackoffMs: () => pollBackoffMs,
9346
10061
  pollNewestSessionInDirectory: () => pollNewestSessionInDirectory,
9347
10062
  prepareCaptainRoute: () => prepareCaptainRoute,
9348
10063
  printForeignInstallError: () => printForeignInstallError,
@@ -9350,6 +10065,7 @@ __export(dist_exports2, {
9350
10065
  probeCaptainChannel: () => probeCaptainChannel,
9351
10066
  programArgsBlock: () => programArgsBlock,
9352
10067
  projectHealth: () => projectHealth,
10068
+ pruneTopics: () => pruneTopics,
9353
10069
  purgeExpiredWorkItems: () => purgeExpiredWorkItems,
9354
10070
  readCaptainAddress: () => readCaptainAddress,
9355
10071
  readCursor: () => readCursor,
@@ -9416,9 +10132,11 @@ __export(dist_exports2, {
9416
10132
  saveSessions: () => saveSessions,
9417
10133
  saveState: () => saveState,
9418
10134
  screenHasSplashMarker: () => screenHasSplashMarker,
10135
+ screenHash: () => screenHash,
9419
10136
  sendRequest: () => sendRequest,
9420
10137
  setLastUserId: () => setLastUserId,
9421
10138
  setNotify: () => setNotify,
10139
+ setPending: () => setPending,
9422
10140
  setTopic: () => setTopic,
9423
10141
  shellQuote: () => shellQuote,
9424
10142
  shouldBuildRouterService: () => shouldBuildRouterService,
@@ -9431,6 +10149,7 @@ __export(dist_exports2, {
9431
10149
  startServer: () => startServer,
9432
10150
  stripBotMention: () => stripBotMention,
9433
10151
  surfaceVerdict: () => surfaceVerdict,
10152
+ telegramPollLockPath: () => telegramPollLockPath,
9434
10153
  timeoutGate: () => timeoutGate,
9435
10154
  titleFor: () => titleFor,
9436
10155
  toControlEvent: () => toControlEvent,
@@ -9500,7 +10219,7 @@ var init_dist2 = __esm({
9500
10219
  });
9501
10220
 
9502
10221
  // packages/agents/dist/claude/registry.js
9503
- import fs13 from "fs";
10222
+ import fs14 from "fs";
9504
10223
  import { join as join17 } from "path";
9505
10224
  import { homedir as homedir7 } from "os";
9506
10225
  function parseRegistryDir(files, readFile7) {
@@ -9545,11 +10264,11 @@ function readClaudeStatus(task) {
9545
10264
  return void 0;
9546
10265
  let files;
9547
10266
  try {
9548
- files = fs13.readdirSync(CLAUDE_SESSIONS_DIR);
10267
+ files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
9549
10268
  } catch {
9550
10269
  return void 0;
9551
10270
  }
9552
- const entries = parseRegistryDir(files, (name) => fs13.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
10271
+ const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
9553
10272
  let entry;
9554
10273
  if (task.messagingSocketPath) {
9555
10274
  entry = entries.find((e) => e.messagingSocketPath === task.messagingSocketPath);
@@ -9564,11 +10283,11 @@ function readClaudeStatus(task) {
9564
10283
  function readClaudeStatusByCwd(cwd) {
9565
10284
  let files;
9566
10285
  try {
9567
- files = fs13.readdirSync(CLAUDE_SESSIONS_DIR);
10286
+ files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
9568
10287
  } catch {
9569
10288
  return void 0;
9570
10289
  }
9571
- const entries = parseRegistryDir(files, (name) => fs13.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
10290
+ const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
9572
10291
  const entry = entries.find((e) => e.cwd === cwd);
9573
10292
  if (!entry)
9574
10293
  return void 0;
@@ -9577,11 +10296,11 @@ function readClaudeStatusByCwd(cwd) {
9577
10296
  function readClaudeStatusBySocketPath(socketPath2) {
9578
10297
  let files;
9579
10298
  try {
9580
- files = fs13.readdirSync(CLAUDE_SESSIONS_DIR);
10299
+ files = fs14.readdirSync(CLAUDE_SESSIONS_DIR);
9581
10300
  } catch {
9582
10301
  return void 0;
9583
10302
  }
9584
- const entries = parseRegistryDir(files, (name) => fs13.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
10303
+ const entries = parseRegistryDir(files, (name) => fs14.readFileSync(join17(CLAUDE_SESSIONS_DIR, name), "utf8"));
9585
10304
  const entry = entries.find((e) => e.messagingSocketPath === socketPath2);
9586
10305
  if (!entry)
9587
10306
  return void 0;
@@ -10004,7 +10723,7 @@ var init_registry2 = __esm({
10004
10723
  });
10005
10724
 
10006
10725
  // packages/agents/dist/drivers/launch-cmd.js
10007
- import fs14 from "fs";
10726
+ import fs15 from "fs";
10008
10727
  import path13 from "path";
10009
10728
  function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model, templatesDir, messagingSocketPath, sessionName, thinking, captainBoot, settingsPath) {
10010
10729
  const driver = registry.getDriver(agentName);
@@ -10034,12 +10753,12 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
10034
10753
  if (templatesDir) {
10035
10754
  const roleFile2 = path13.join(templatesDir, `${role}.claude.md`);
10036
10755
  const legacyRoleFile = path13.join(templatesDir, `${role}.CLAUDE.md`);
10037
- const actualRoleFile = fs14.existsSync(roleFile2) ? roleFile2 : fs14.existsSync(legacyRoleFile) ? legacyRoleFile : null;
10756
+ const actualRoleFile = fs15.existsSync(roleFile2) ? roleFile2 : fs15.existsSync(legacyRoleFile) ? legacyRoleFile : null;
10038
10757
  if (actualRoleFile) {
10039
10758
  cmd += ` --append-system-prompt-file ${actualRoleFile}`;
10040
10759
  }
10041
10760
  const pluginDir = path13.join(templatesDir, "..", "plugin");
10042
- if (fs14.existsSync(pluginDir)) {
10761
+ if (fs15.existsSync(pluginDir)) {
10043
10762
  cmd += ` --plugin-dir ${pluginDir}`;
10044
10763
  }
10045
10764
  }
@@ -10055,7 +10774,7 @@ function buildAgentCmd(agentName, registry, role, fresh, permissionMode, model,
10055
10774
  role,
10056
10775
  model,
10057
10776
  autoApprove: true,
10058
- promptFile: roleFile && fs14.existsSync(roleFile) ? roleFile : void 0,
10777
+ promptFile: roleFile && fs15.existsSync(roleFile) ? roleFile : void 0,
10059
10778
  ...captainBoot && role === "captain" ? {
10060
10779
  interactive: true,
10061
10780
  ...captainBoot.port ? { port: captainBoot.port } : {},
@@ -10427,7 +11146,7 @@ var init_opencode2 = __esm({
10427
11146
  });
10428
11147
 
10429
11148
  // packages/agents/dist/projection/opencode-skills.js
10430
- import fs15 from "fs";
11149
+ import fs16 from "fs";
10431
11150
  import os10 from "os";
10432
11151
  import path18 from "path";
10433
11152
  function defaultOpencodeSkillsRoot(env = process.env) {
@@ -10446,62 +11165,62 @@ function parseSkillName(raw) {
10446
11165
  }
10447
11166
  function listSourceSkills(sourceSkillsDir) {
10448
11167
  const out = /* @__PURE__ */ new Map();
10449
- if (!fs15.existsSync(sourceSkillsDir))
11168
+ if (!fs16.existsSync(sourceSkillsDir))
10450
11169
  return out;
10451
- for (const entry of fs15.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
11170
+ for (const entry of fs16.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
10452
11171
  if (!entry.isDirectory())
10453
11172
  continue;
10454
11173
  const skillPath = path18.join(sourceSkillsDir, entry.name, "SKILL.md");
10455
- if (!fs15.existsSync(skillPath))
11174
+ if (!fs16.existsSync(skillPath))
10456
11175
  continue;
10457
- const name = parseSkillName(fs15.readFileSync(skillPath, "utf-8"));
11176
+ const name = parseSkillName(fs16.readFileSync(skillPath, "utf-8"));
10458
11177
  if (name && SAFE_NAME.test(name))
10459
11178
  out.set(name, skillPath);
10460
11179
  }
10461
11180
  return out;
10462
11181
  }
10463
11182
  function hasMarker(dir) {
10464
- return fs15.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
11183
+ return fs16.existsSync(path18.join(dir, OPENCODE_SKILL_MARKER));
10465
11184
  }
10466
11185
  function syncOpencodeSkills(opts) {
10467
11186
  const result = { written: [], unchanged: [], removed: [], skipped: [] };
10468
11187
  const wanted = listSourceSkills(opts.sourceSkillsDir);
10469
- if (wanted.size === 0 && !fs15.existsSync(opts.sourceSkillsDir))
11188
+ if (wanted.size === 0 && !fs16.existsSync(opts.sourceSkillsDir))
10470
11189
  return result;
10471
11190
  if (!opts.dryRun)
10472
- fs15.mkdirSync(opts.skillsRoot, { recursive: true });
11191
+ fs16.mkdirSync(opts.skillsRoot, { recursive: true });
10473
11192
  for (const [name, srcPath] of wanted) {
10474
11193
  const targetDir = path18.join(opts.skillsRoot, name);
10475
11194
  const targetSkill = path18.join(targetDir, "SKILL.md");
10476
11195
  const managed = hasMarker(targetDir);
10477
- if (fs15.existsSync(targetSkill) && !managed) {
11196
+ if (fs16.existsSync(targetSkill) && !managed) {
10478
11197
  result.skipped.push({ name, reason: "foreign-collision" });
10479
11198
  continue;
10480
11199
  }
10481
- const current = fs15.existsSync(targetSkill) ? fs15.readFileSync(targetSkill) : null;
10482
- if (current && current.equals(fs15.readFileSync(srcPath))) {
11200
+ const current = fs16.existsSync(targetSkill) ? fs16.readFileSync(targetSkill) : null;
11201
+ if (current && current.equals(fs16.readFileSync(srcPath))) {
10483
11202
  if (!managed && !opts.dryRun) {
10484
- fs15.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
11203
+ fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
10485
11204
  }
10486
11205
  result.unchanged.push(name);
10487
11206
  continue;
10488
11207
  }
10489
11208
  if (!opts.dryRun) {
10490
- fs15.mkdirSync(targetDir, { recursive: true });
10491
- fs15.copyFileSync(srcPath, targetSkill);
10492
- fs15.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
11209
+ fs16.mkdirSync(targetDir, { recursive: true });
11210
+ fs16.copyFileSync(srcPath, targetSkill);
11211
+ fs16.writeFileSync(path18.join(targetDir, OPENCODE_SKILL_MARKER), MARKER_BODY);
10493
11212
  }
10494
11213
  result.written.push(name);
10495
11214
  }
10496
- if (fs15.existsSync(opts.skillsRoot)) {
10497
- for (const entry of fs15.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
11215
+ if (fs16.existsSync(opts.skillsRoot)) {
11216
+ for (const entry of fs16.readdirSync(opts.skillsRoot, { withFileTypes: true })) {
10498
11217
  if (!entry.isDirectory() || wanted.has(entry.name))
10499
11218
  continue;
10500
11219
  const dir = path18.join(opts.skillsRoot, entry.name);
10501
11220
  if (!hasMarker(dir))
10502
11221
  continue;
10503
11222
  if (!opts.dryRun)
10504
- fs15.rmSync(dir, { recursive: true, force: true });
11223
+ fs16.rmSync(dir, { recursive: true, force: true });
10505
11224
  result.removed.push(entry.name);
10506
11225
  }
10507
11226
  }
@@ -12310,6 +13029,9 @@ var init_peer_channel = __esm({
12310
13029
  return { status: "accepted", via: this.name, confirmed: true };
12311
13030
  }
12312
13031
  }
13032
+ if (statusBefore === "busy" || statusBefore === "shell") {
13033
+ return { status: "queued", via: this.name };
13034
+ }
12313
13035
  if (statusBefore === "idle") {
12314
13036
  await this.deps.sleep(window);
12315
13037
  const afterInfo = this.deps.statusFor(taskId);
@@ -12455,7 +13177,7 @@ var init_receipt_listener = __esm({
12455
13177
  });
12456
13178
 
12457
13179
  // packages/agents/dist/claude/api-key-approval.js
12458
- import fs16 from "fs";
13180
+ import fs17 from "fs";
12459
13181
  import os12 from "os";
12460
13182
  import path20 from "path";
12461
13183
  function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
@@ -12465,12 +13187,12 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
12465
13187
  const file = opts.claudeJsonPath ?? path20.join(os12.homedir(), ".claude.json");
12466
13188
  const log = opts.log ?? (() => {
12467
13189
  });
12468
- if (!fs16.existsSync(file)) {
13190
+ if (!fs17.existsSync(file)) {
12469
13191
  return { changed: false, reason: "no ~/.claude.json yet \u2014 skipping routed-key pre-approval" };
12470
13192
  }
12471
13193
  let doc;
12472
13194
  try {
12473
- doc = JSON.parse(fs16.readFileSync(file, "utf8"));
13195
+ doc = JSON.parse(fs17.readFileSync(file, "utf8"));
12474
13196
  } catch {
12475
13197
  return {
12476
13198
  changed: false,
@@ -12492,11 +13214,11 @@ function ensureClaudeApiKeyApproved(apiKey, opts = {}) {
12492
13214
  };
12493
13215
  const tmp = `${file}.squadrant-tmp`;
12494
13216
  try {
12495
- fs16.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs16.statSync(file).mode & 511 });
12496
- fs16.renameSync(tmp, file);
13217
+ fs17.writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: fs17.statSync(file).mode & 511 });
13218
+ fs17.renameSync(tmp, file);
12497
13219
  } catch (e) {
12498
13220
  try {
12499
- fs16.unlinkSync(tmp);
13221
+ fs17.unlinkSync(tmp);
12500
13222
  } catch {
12501
13223
  }
12502
13224
  return {
@@ -12852,11 +13574,11 @@ function parseDraftFromScreen(screen) {
12852
13574
  if (!screen)
12853
13575
  return null;
12854
13576
  const lines = screen.split(/\r?\n/);
12855
- const HR_RE = /^\s*─{10,}\s*$/;
13577
+ const HR_RE2 = /^\s*─{10,}\s*$/;
12856
13578
  let bottomHR = -1;
12857
13579
  let topHR = -1;
12858
13580
  for (let i = lines.length - 1; i >= 0; i--) {
12859
- if (HR_RE.test(lines[i])) {
13581
+ if (HR_RE2.test(lines[i])) {
12860
13582
  if (bottomHR === -1) {
12861
13583
  bottomHR = i;
12862
13584
  } else {
@@ -12894,11 +13616,11 @@ function hasCCInputBox(screen) {
12894
13616
  if (!screen)
12895
13617
  return false;
12896
13618
  const lines = screen.split(/\r?\n/);
12897
- const HR_RE = /^\s*─{10,}\s*$/;
13619
+ const HR_RE2 = /^\s*─{10,}\s*$/;
12898
13620
  let bottomHR = -1;
12899
13621
  let topHR = -1;
12900
13622
  for (let i = lines.length - 1; i >= 0; i--) {
12901
- if (HR_RE.test(lines[i])) {
13623
+ if (HR_RE2.test(lines[i])) {
12902
13624
  if (bottomHR === -1)
12903
13625
  bottomHR = i;
12904
13626
  else {
@@ -12911,15 +13633,15 @@ function hasCCInputBox(screen) {
12911
13633
  return false;
12912
13634
  return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));
12913
13635
  }
12914
- function hasModalOptionList(screen) {
13636
+ function hasModalOptionList2(screen) {
12915
13637
  if (!screen)
12916
13638
  return false;
12917
13639
  const lines = screen.split(/\r?\n/);
12918
- const HR_RE = /^\s*─{10,}\s*$/;
13640
+ const HR_RE2 = /^\s*─{10,}\s*$/;
12919
13641
  let bottomHR = -1;
12920
13642
  let topHR = -1;
12921
13643
  for (let i = lines.length - 1; i >= 0; i--) {
12922
- if (HR_RE.test(lines[i])) {
13644
+ if (HR_RE2.test(lines[i])) {
12923
13645
  if (bottomHR === -1)
12924
13646
  bottomHR = i;
12925
13647
  else {
@@ -12933,14 +13655,14 @@ function hasModalOptionList(screen) {
12933
13655
  return lines.slice(topHR + 1, bottomHR).some((l) => /^\s*\d+\.\s/.test(l));
12934
13656
  }
12935
13657
  function parseModalOptions(screen) {
12936
- if (!hasModalOptionList(screen))
13658
+ if (!hasModalOptionList2(screen))
12937
13659
  return null;
12938
13660
  const lines = screen.split(/\r?\n/);
12939
- const HR_RE = /^\s*─{10,}\s*$/;
13661
+ const HR_RE2 = /^\s*─{10,}\s*$/;
12940
13662
  let bottomHR = -1;
12941
13663
  let topHR = -1;
12942
13664
  for (let i = lines.length - 1; i >= 0; i--) {
12943
- if (HR_RE.test(lines[i])) {
13665
+ if (HR_RE2.test(lines[i])) {
12944
13666
  if (bottomHR === -1)
12945
13667
  bottomHR = i;
12946
13668
  else {
@@ -12965,11 +13687,11 @@ function readInputBoxRaw(screen, opts) {
12965
13687
  if (!screen)
12966
13688
  return null;
12967
13689
  const lines = screen.split(/\r?\n/);
12968
- const HR_RE = /^\s*─{10,}\s*$/;
13690
+ const HR_RE2 = /^\s*─{10,}\s*$/;
12969
13691
  let bottomHR = -1;
12970
13692
  let topHR = -1;
12971
13693
  for (let i = lines.length - 1; i >= 0; i--) {
12972
- if (HR_RE.test(lines[i])) {
13694
+ if (HR_RE2.test(lines[i])) {
12973
13695
  if (bottomHR === -1)
12974
13696
  bottomHR = i;
12975
13697
  else {
@@ -13002,6 +13724,30 @@ function classifyOpencodeStartupSurface(screen) {
13002
13724
  return "loading";
13003
13725
  return OC_IDLE_MARKERS.some((m) => screenHasSplashMarker(screen, m)) ? "idle" : "loading";
13004
13726
  }
13727
+ function parseOpencodeDraftFromScreen(screen) {
13728
+ if (!screen)
13729
+ return null;
13730
+ if (OC_BUSY_RE.test(screen))
13731
+ return null;
13732
+ const lines = screen.split(/\r?\n/);
13733
+ let bottom = -1;
13734
+ for (let i = lines.length - 1; i >= 0; i--) {
13735
+ if (OC_BOX_BOTTOM_RE.test(lines[i])) {
13736
+ bottom = i;
13737
+ break;
13738
+ }
13739
+ }
13740
+ if (bottom === -1)
13741
+ return null;
13742
+ const content = [];
13743
+ for (let i = bottom - 1; i >= 0 && OC_BOX_SIDE_RE.test(lines[i]); i--) {
13744
+ content.push(lines[i].replace(/^\s*┃\s*/, "").replace(/[\s┃│]+$/, ""));
13745
+ }
13746
+ if (content.length === 0)
13747
+ return null;
13748
+ const hasDraft = content.some((l) => l !== "" && !OC_BOX_STATUS_RE.test(l) && !screenHasSplashMarker(l, "Ask anything"));
13749
+ return hasDraft ? null : "";
13750
+ }
13005
13751
  function sendDebugEnabled() {
13006
13752
  return !!process.env.SQUADRANT_DEBUG_SEND;
13007
13753
  }
@@ -13217,10 +13963,10 @@ function createCmuxDriver() {
13217
13963
  `);
13218
13964
  throw new DeferDelivery(null, "probe-failed");
13219
13965
  }
13220
- const draft = parseDraftFromScreen(screen);
13966
+ const draft = opts?.agent === "opencode" ? parseOpencodeDraftFromScreen(screen) : parseDraftFromScreen(screen);
13221
13967
  if (draft === null)
13222
13968
  throw new DeferDelivery(null, "no-box");
13223
- if (hasModalOptionList(screen))
13969
+ if (hasModalOptionList2(screen))
13224
13970
  throw new DeferDelivery(null, "modal");
13225
13971
  if (draft === "") {
13226
13972
  await deliver();
@@ -13324,7 +14070,7 @@ function createCmuxDriver() {
13324
14070
  }
13325
14071
  };
13326
14072
  }
13327
- var CMUX_TIMEOUT, CmuxTimeoutError, CC_INITIALIZED_RE, CC_WORKING_RE, OC_IDLE_MARKERS;
14073
+ var CMUX_TIMEOUT, CmuxTimeoutError, CC_INITIALIZED_RE, CC_WORKING_RE, OC_IDLE_MARKERS, OC_BOX_BOTTOM_RE, OC_BOX_SIDE_RE, OC_BOX_STATUS_RE, OC_BUSY_RE;
13328
14074
  var init_cmux = __esm({
13329
14075
  "packages/workspaces/dist/runtimes/cmux.js"() {
13330
14076
  init_dist();
@@ -13342,6 +14088,10 @@ var init_cmux = __esm({
13342
14088
  CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;
13343
14089
  CC_WORKING_RE = /↓\s*[\d.]+\s*k?\s*tokens?\b|esc to interrupt|\bshell still running\b|·\s*\d+\s*shell\b|\(\d+m?\s*\d*s\b/i;
13344
14090
  OC_IDLE_MARKERS = ["Ask anything", "ctrl+p commands"];
14091
+ OC_BOX_BOTTOM_RE = /^\s*╹[▀]{6,}\s*$/;
14092
+ OC_BOX_SIDE_RE = /^\s*┃/;
14093
+ OC_BOX_STATUS_RE = /^Build\s*·/i;
14094
+ OC_BUSY_RE = /esc\s+interrupt/i;
13345
14095
  }
13346
14096
  });
13347
14097
 
@@ -13466,7 +14216,7 @@ var init_notifiers = __esm({
13466
14216
  });
13467
14217
 
13468
14218
  // packages/workspaces/dist/workspaces/obsidian.js
13469
- import fs17 from "fs/promises";
14219
+ import fs18 from "fs/promises";
13470
14220
  import { existsSync as existsSync11 } from "fs";
13471
14221
  import path21 from "path";
13472
14222
  function resolveInRoot(root, relative) {
@@ -13491,16 +14241,16 @@ function createObsidianDriver(scope) {
13491
14241
  };
13492
14242
  },
13493
14243
  async read(rel) {
13494
- return fs17.readFile(resolveInRoot(root, rel), "utf-8");
14244
+ return fs18.readFile(resolveInRoot(root, rel), "utf-8");
13495
14245
  },
13496
14246
  async write(rel, content) {
13497
14247
  const abs = resolveInRoot(root, rel);
13498
- await fs17.mkdir(path21.dirname(abs), { recursive: true });
13499
- await fs17.writeFile(abs, content);
14248
+ await fs18.mkdir(path21.dirname(abs), { recursive: true });
14249
+ await fs18.writeFile(abs, content);
13500
14250
  },
13501
14251
  async exists(rel) {
13502
14252
  try {
13503
- await fs17.access(resolveInRoot(root, rel));
14253
+ await fs18.access(resolveInRoot(root, rel));
13504
14254
  return true;
13505
14255
  } catch {
13506
14256
  return false;
@@ -13508,13 +14258,13 @@ function createObsidianDriver(scope) {
13508
14258
  },
13509
14259
  async list(rel) {
13510
14260
  try {
13511
- return await fs17.readdir(resolveInRoot(root, rel));
14261
+ return await fs18.readdir(resolveInRoot(root, rel));
13512
14262
  } catch {
13513
14263
  return [];
13514
14264
  }
13515
14265
  },
13516
14266
  async mkdir(rel) {
13517
- await fs17.mkdir(resolveInRoot(root, rel), { recursive: true });
14267
+ await fs18.mkdir(resolveInRoot(root, rel), { recursive: true });
13518
14268
  }
13519
14269
  };
13520
14270
  }
@@ -14412,7 +15162,7 @@ async function resolveCaptainWorkspace(project) {
14412
15162
  }
14413
15163
  async function paneHasOpenModal(runtime, pane) {
14414
15164
  const screen = await runtime.readPaneScreen(pane) ?? "";
14415
- return hasModalOptionList(screen);
15165
+ return hasModalOptionList2(screen);
14416
15166
  }
14417
15167
  async function readModalOptions(runtime, pane) {
14418
15168
  const screen = await runtime.readPaneScreen(pane) ?? "";
@@ -14420,7 +15170,7 @@ async function readModalOptions(runtime, pane) {
14420
15170
  }
14421
15171
  async function confirmedSendToPane(runtime, pane, message) {
14422
15172
  const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
14423
- if (hasModalOptionList(preSendScreen)) {
15173
+ if (hasModalOptionList2(preSendScreen)) {
14424
15174
  return { delivered: false, blockedByModal: true };
14425
15175
  }
14426
15176
  await runtime.pasteToPane(pane, message);
@@ -14673,9 +15423,9 @@ async function runRuntimeSend(arg1, arg2, opts, confirmOpts) {
14673
15423
  const resolved = resolveTarget(registry, config, target, !!opts.command);
14674
15424
  await needRef(resolved);
14675
15425
  const finalProject = opts.command ? config.commandName : target;
14676
- const { join: join35, dirname: dirname11 } = await import("path");
15426
+ const { join: join36, dirname: dirname12 } = await import("path");
14677
15427
  const { DEFAULT_CONFIG_PATH: DEFAULT_CONFIG_PATH2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
14678
- const stateRoot = join35(dirname11(DEFAULT_CONFIG_PATH2), "state");
15428
+ const stateRoot = join36(dirname12(DEFAULT_CONFIG_PATH2), "state");
14679
15429
  const seq = await appendCaptainMessage2({
14680
15430
  stateRoot,
14681
15431
  project: finalProject,
@@ -14784,7 +15534,7 @@ init_dist3();
14784
15534
  import { Command as Command38 } from "commander";
14785
15535
  import { existsSync as existsSync14, readFileSync as readFileSync21 } from "fs";
14786
15536
  import { fileURLToPath as fileURLToPath6 } from "url";
14787
- import { dirname as dirname10, join as join34 } from "path";
15537
+ import { dirname as dirname11, join as join35 } from "path";
14788
15538
  import { homedir as homedir20 } from "os";
14789
15539
 
14790
15540
  // packages/cli/src/commands/doctor.ts
@@ -14794,7 +15544,7 @@ init_dist();
14794
15544
  init_dist4();
14795
15545
  import { Command } from "commander";
14796
15546
  import { execSync as execSync8 } from "child_process";
14797
- import fs18 from "fs";
15547
+ import fs19 from "fs";
14798
15548
  import { stat } from "fs/promises";
14799
15549
  import path22 from "path";
14800
15550
  import chalk3 from "chalk";
@@ -14898,7 +15648,7 @@ function settingsHaveAgentTeams() {
14898
15648
  try {
14899
15649
  const home = process.env.HOME || "";
14900
15650
  const settings = JSON.parse(
14901
- fs18.readFileSync(`${home}/.claude/settings.json`, "utf-8")
15651
+ fs19.readFileSync(`${home}/.claude/settings.json`, "utf-8")
14902
15652
  );
14903
15653
  return settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
14904
15654
  } catch {
@@ -14909,7 +15659,7 @@ function pluginInstalled(pluginKey) {
14909
15659
  try {
14910
15660
  const home = process.env.HOME || "";
14911
15661
  const plugins = JSON.parse(
14912
- fs18.readFileSync(
15662
+ fs19.readFileSync(
14913
15663
  `${home}/.claude/plugins/installed_plugins.json`,
14914
15664
  "utf-8"
14915
15665
  )
@@ -14955,7 +15705,7 @@ ${lines.join("\n")}
14955
15705
  }
14956
15706
  function readPackageVersion(packageJsonPath) {
14957
15707
  try {
14958
- const pkg2 = JSON.parse(fs18.readFileSync(packageJsonPath, "utf-8"));
15708
+ const pkg2 = JSON.parse(fs19.readFileSync(packageJsonPath, "utf-8"));
14959
15709
  return typeof pkg2.version === "string" ? pkg2.version : null;
14960
15710
  } catch {
14961
15711
  return null;
@@ -14991,7 +15741,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
14991
15741
  ));
14992
15742
  results.push(check(
14993
15743
  "Obsidian installed",
14994
- commandExists("obsidian") || fs18.existsSync("/Applications/Obsidian.app"),
15744
+ commandExists("obsidian") || fs19.existsSync("/Applications/Obsidian.app"),
14995
15745
  "Install from: https://obsidian.md"
14996
15746
  ));
14997
15747
  results.push(check(
@@ -15099,7 +15849,7 @@ var doctorCommand = new Command("doctor").description("Check system health and p
15099
15849
  results.push(
15100
15850
  check(
15101
15851
  "Squadrant config exists",
15102
- fs18.existsSync(
15852
+ fs19.existsSync(
15103
15853
  process.env.SQUADRANT_CONFIG || `${process.env.HOME}/.config/squadrant/config.json`
15104
15854
  ),
15105
15855
  "Run: squadrant init"
@@ -15183,7 +15933,7 @@ init_dist();
15183
15933
  init_dist4();
15184
15934
  init_dist();
15185
15935
  import { Command as Command2 } from "commander";
15186
- import fs19 from "fs";
15936
+ import fs20 from "fs";
15187
15937
  import path23 from "path";
15188
15938
  import os13 from "os";
15189
15939
  import readline from "readline";
@@ -15400,25 +16150,57 @@ function writePerCrewOpencodeConfig(o) {
15400
16150
  return file;
15401
16151
  }
15402
16152
 
16153
+ // packages/cli/src/lib/claude-auth.ts
16154
+ import { execSync as execSync9 } from "child_process";
16155
+ function parseClaudeAuthStatus(raw) {
16156
+ let parsed;
16157
+ try {
16158
+ parsed = JSON.parse(raw);
16159
+ } catch {
16160
+ return null;
16161
+ }
16162
+ if (typeof parsed !== "object" || parsed === null) return null;
16163
+ const obj = parsed;
16164
+ if (typeof obj.loggedIn !== "boolean") return null;
16165
+ return {
16166
+ authenticated: obj.loggedIn,
16167
+ method: typeof obj.authMethod === "string" ? obj.authMethod : void 0,
16168
+ provider: typeof obj.apiProvider === "string" ? obj.apiProvider : void 0
16169
+ };
16170
+ }
16171
+ function detectClaudeAuth(probe = {}) {
16172
+ const run = probe.run ?? (() => execSync9("claude auth status", {
16173
+ encoding: "utf8",
16174
+ stdio: ["ignore", "pipe", "ignore"],
16175
+ timeout: 1e4
16176
+ }));
16177
+ try {
16178
+ const parsed = parseClaudeAuthStatus(run());
16179
+ return parsed ?? { authenticated: false, reason: "unreadable `claude auth status` output" };
16180
+ } catch {
16181
+ return { authenticated: false, reason: "`claude auth status` unavailable" };
16182
+ }
16183
+ }
16184
+
15403
16185
  // packages/cli/src/commands/init.ts
15404
16186
  init_dist3();
15405
16187
  function findPackageRoot() {
15406
16188
  let dir = path23.dirname(new URL(import.meta.url).pathname);
15407
16189
  while (dir !== "/") {
15408
- if (fs19.existsSync(path23.join(dir, "package.json"))) return dir;
16190
+ if (fs20.existsSync(path23.join(dir, "package.json"))) return dir;
15409
16191
  dir = path23.dirname(dir);
15410
16192
  }
15411
16193
  return process.cwd();
15412
16194
  }
15413
16195
  function copyDirRecursive(src, dest) {
15414
- fs19.mkdirSync(dest, { recursive: true });
15415
- for (const entry of fs19.readdirSync(src, { withFileTypes: true })) {
16196
+ fs20.mkdirSync(dest, { recursive: true });
16197
+ for (const entry of fs20.readdirSync(src, { withFileTypes: true })) {
15416
16198
  const srcPath = path23.join(src, entry.name);
15417
16199
  const destPath = path23.join(dest, entry.name);
15418
16200
  if (entry.isDirectory()) {
15419
16201
  copyDirRecursive(srcPath, destPath);
15420
16202
  } else {
15421
- fs19.copyFileSync(srcPath, destPath);
16203
+ fs20.copyFileSync(srcPath, destPath);
15422
16204
  }
15423
16205
  }
15424
16206
  }
@@ -15435,16 +16217,70 @@ function promptLine(question) {
15435
16217
  });
15436
16218
  });
15437
16219
  }
15438
- 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) => {
16220
+ async function resolveRouter(config, opts, isTTY) {
16221
+ if (config.defaults?.router) return config.defaults.router;
16222
+ let kind = opts.routerKind;
16223
+ let baseUrl = opts.routerBaseUrl;
16224
+ let apiKeyEnv = opts.routerApiKeyEnv;
16225
+ if (isTTY && (!kind || !baseUrl)) {
16226
+ const k = await promptLine(chalk4.cyan(` Router kind [${DEFAULT_ROUTER_KIND}]: `));
16227
+ kind = kind ?? (k || DEFAULT_ROUTER_KIND);
16228
+ const b = await promptLine(chalk4.cyan(` Router base URL [${DEFAULT_ROUTER_BASE_URL}]: `));
16229
+ baseUrl = baseUrl ?? (b || DEFAULT_ROUTER_BASE_URL);
16230
+ const e = await promptLine(chalk4.cyan(" Env var holding the API key (optional): "));
16231
+ apiKeyEnv = apiKeyEnv ?? (e || void 0);
16232
+ }
16233
+ const resolvedKind = kind && isRouterKind(kind) ? kind : DEFAULT_ROUTER_KIND;
16234
+ const resolvedBase = baseUrl || DEFAULT_ROUTER_BASE_URL;
16235
+ if (!isTTY && !opts.routerBaseUrl) {
16236
+ console.log(chalk4.dim(` - no router flags given; defaulting to ${resolvedKind} (${resolvedBase})`));
16237
+ console.log(chalk4.dim(" set a credential: squadrant config set defaults.router.apiKeyEnv <VAR>"));
16238
+ }
16239
+ return { kind: resolvedKind, baseUrl: resolvedBase, ...apiKeyEnv ? { apiKeyEnv } : {} };
16240
+ }
16241
+ async function askProvider(current, auth) {
16242
+ console.log(chalk4.bold("\n Choose your provider:"));
16243
+ for (const p of PROVIDER_PRESETS) {
16244
+ const currentTag = p.id === current ? chalk4.dim(" (current)") : "";
16245
+ console.log(` ${chalk4.cyan(`${p.id})`)} ${p.label}${currentTag}`);
16246
+ console.log(chalk4.dim(` ${p.summary}`));
16247
+ }
16248
+ if (!auth.authenticated) {
16249
+ console.log(chalk4.yellow("\n \u26A0 No Anthropic credential detected \u2014 preset a will not work; consider b or c."));
16250
+ }
16251
+ const answer = (await promptLine(chalk4.cyan(`
16252
+ Provider [a/b/c/d] (default ${current}): `))).toLowerCase();
16253
+ if (!answer) return current;
16254
+ if (isProviderPresetId(answer)) return answer;
16255
+ console.log(chalk4.yellow(` \u26A0 Unknown choice '${answer}' \u2014 keeping '${current}'.`));
16256
+ return current;
16257
+ }
16258
+ function warnIfNoAnthropic(preset, auth) {
16259
+ if (preset !== "a" || auth.authenticated) return;
16260
+ console.log(chalk4.yellow(" \u26A0 No Anthropic credential detected (`claude auth status`)."));
16261
+ console.log(chalk4.dim(" Preset A (Claude Pro/Max or an API key) may not work \u2014 consider preset b or c."));
16262
+ }
16263
+ var initCommand = new Command2("init").description("Guided first-time setup: provider, hub vault, agents, plugins, projects (re-run-safe)").option("--hub <path>", "Hub vault path", "~/squadrant-hub").option("--preset <id>", "provider preset: a|b|c|d (non-interactive)").option("--router-kind <kind>", "router kind for --preset c (opencode-go|openrouter|ccr|litellm|custom)").option("--router-base-url <url>", "router base URL for --preset c").option("--router-api-key-env <env>", "env var holding the router credential for --preset c").action(async (opts) => {
15439
16264
  const hubPath = resolveHome(opts.hub);
15440
16265
  const pkgRoot = findPackageRoot();
15441
16266
  const configDir = path23.join(os13.homedir(), ".config", "squadrant");
15442
16267
  const isTTY = process.stdin.isTTY === true;
15443
16268
  console.log(chalk4.bold("\nSquadrant Init\n"));
15444
- if (!isTTY) {
16269
+ let explicitPreset;
16270
+ if (opts.preset !== void 0) {
16271
+ const candidate = opts.preset.toLowerCase();
16272
+ if (!isProviderPresetId(candidate)) {
16273
+ console.error(chalk4.red(` \u2718 Unknown --preset '${opts.preset}'. Valid values: a, b, c, d`));
16274
+ process.exitCode = 1;
16275
+ return;
16276
+ }
16277
+ explicitPreset = candidate;
16278
+ }
16279
+ if (!isTTY && !explicitPreset) {
15445
16280
  console.log(" Run these steps to get started:\n");
15446
- console.log(chalk4.bold(" 1/5 Hub vault"));
16281
+ console.log(chalk4.bold(" 1/5 Hub vault + provider"));
15447
16282
  console.log(chalk4.cyan(` squadrant init --hub ${opts.hub}`));
16283
+ console.log(chalk4.dim(" Choose a provider: squadrant init --preset a|b|c|d"));
15448
16284
  console.log(chalk4.bold("\n 2/5 Agent + projection setup"));
15449
16285
  console.log(" (handled automatically by: " + chalk4.cyan("squadrant init") + ")");
15450
16286
  console.log(chalk4.bold("\n 3/5 Plugins \u2014 open Claude Code and run:"));
@@ -15464,8 +16300,8 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
15464
16300
  }
15465
16301
  const wsRegistry = new WorkspaceRegistry({ obsidian: createObsidianDriver });
15466
16302
  try {
15467
- if (fs19.existsSync(DEFAULT_CONFIG_PATH)) {
15468
- const existing = JSON.parse(fs19.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
16303
+ if (fs20.existsSync(DEFAULT_CONFIG_PATH)) {
16304
+ const existing = JSON.parse(fs20.readFileSync(DEFAULT_CONFIG_PATH, "utf-8"));
15469
16305
  wsRegistry.get(existing.workspace ?? "obsidian");
15470
16306
  }
15471
16307
  } catch (err) {
@@ -15473,45 +16309,76 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
15473
16309
  return;
15474
16310
  }
15475
16311
  stepHeader(1, 5, "Hub vault");
15476
- if (fs19.existsSync(DEFAULT_CONFIG_PATH)) {
16312
+ const configExists = fs20.existsSync(DEFAULT_CONFIG_PATH);
16313
+ let config;
16314
+ if (configExists) {
15477
16315
  console.log(chalk4.yellow(" \u26A0 Config already exists, skipping creation"));
16316
+ config = loadConfig();
15478
16317
  } else {
15479
- const config = getDefaultConfig();
16318
+ config = getDefaultConfig();
15480
16319
  config.hubVault = hubPath;
16320
+ }
16321
+ const auth = detectClaudeAuth();
16322
+ let chosenPreset = explicitPreset;
16323
+ if (!chosenPreset && isTTY) {
16324
+ chosenPreset = await askProvider(detectProviderPresetId(config), auth);
16325
+ }
16326
+ let appliedChanges = [];
16327
+ if (chosenPreset) {
16328
+ const label = PROVIDER_PRESETS.find((p) => p.id === chosenPreset)?.label ?? chosenPreset;
16329
+ const router = chosenPreset === "c" ? await resolveRouter(config, opts, isTTY) : void 0;
16330
+ const overwrite = !configExists || explicitPreset !== void 0;
16331
+ const result = applyProviderPreset(config, chosenPreset, { router, overwrite });
16332
+ appliedChanges = result.changes;
16333
+ config = result.config;
16334
+ console.log(chalk4.bold(`
16335
+ Provider: ${chosenPreset.toUpperCase()} \u2014 ${label}`));
16336
+ if (appliedChanges.length) {
16337
+ console.log(" Will change:");
16338
+ for (const change of appliedChanges) console.log(chalk4.dim(` - ${change}`));
16339
+ } else {
16340
+ console.log(chalk4.dim(" - provider config already set (unchanged)"));
16341
+ }
16342
+ if (!isTTY) warnIfNoAnthropic(chosenPreset, auth);
16343
+ }
16344
+ if (!configExists) {
15481
16345
  saveConfig(config);
15482
16346
  console.log(chalk4.green(` \u2714 Config created at ${DEFAULT_CONFIG_PATH}`));
16347
+ } else if (appliedChanges.length) {
16348
+ saveConfig(config);
16349
+ console.log(chalk4.green(` \u2714 Config updated at ${DEFAULT_CONFIG_PATH}`));
15483
16350
  }
15484
16351
  const hubTemplate = path23.join(pkgRoot, "obsidian", "hub");
15485
- if (fs19.existsSync(hubPath)) {
16352
+ if (fs20.existsSync(hubPath)) {
15486
16353
  console.log(chalk4.yellow(` \u26A0 Hub vault already exists at ${hubPath}`));
15487
- } else if (fs19.existsSync(hubTemplate)) {
16354
+ } else if (fs20.existsSync(hubTemplate)) {
15488
16355
  copyDirRecursive(hubTemplate, hubPath);
15489
16356
  console.log(chalk4.green(` \u2714 Hub vault scaffolded at ${hubPath}`));
15490
16357
  } else {
15491
- fs19.mkdirSync(hubPath, { recursive: true });
16358
+ fs20.mkdirSync(hubPath, { recursive: true });
15492
16359
  console.log(chalk4.yellow(` \u26A0 Hub template not found; created empty directory at ${hubPath}`));
15493
16360
  }
15494
16361
  const hubDashboardSrc = path23.join(pkgRoot, "obsidian", "hub", "dashboard.md");
15495
16362
  const hubDashboardDest = path23.join(hubPath, "dashboard.md");
15496
- if (fs19.existsSync(hubDashboardSrc)) {
15497
- fs19.copyFileSync(hubDashboardSrc, hubDashboardDest);
16363
+ if (fs20.existsSync(hubDashboardSrc)) {
16364
+ fs20.copyFileSync(hubDashboardSrc, hubDashboardDest);
15498
16365
  console.log(chalk4.green(` \u2714 Dashboard refreshed`));
15499
16366
  }
15500
- fs19.mkdirSync(path23.join(hubPath, "projects"), { recursive: true });
16367
+ fs20.mkdirSync(path23.join(hubPath, "projects"), { recursive: true });
15501
16368
  ensureRuntimeSynced({ sourceRoot: pkgRoot, runtimeRoot: configDir });
15502
16369
  console.log(chalk4.green(` \u2714 Runtime assets synced to ${configDir}`));
15503
16370
  stepHeader(2, 5, "Agent + projection setup");
15504
16371
  const settingsPath = path23.join(os13.homedir(), ".claude", "settings.json");
15505
16372
  try {
15506
16373
  let settings = {};
15507
- if (fs19.existsSync(settingsPath)) {
15508
- settings = JSON.parse(fs19.readFileSync(settingsPath, "utf-8"));
16374
+ if (fs20.existsSync(settingsPath)) {
16375
+ settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
15509
16376
  }
15510
16377
  const env = settings.env || {};
15511
16378
  if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
15512
16379
  settings.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
15513
- fs19.mkdirSync(path23.dirname(settingsPath), { recursive: true });
15514
- fs19.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
16380
+ fs20.mkdirSync(path23.dirname(settingsPath), { recursive: true });
16381
+ fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
15515
16382
  console.log(chalk4.green(" \u2714 Agent Teams enabled in ~/.claude/settings.json"));
15516
16383
  } else {
15517
16384
  console.log(chalk4.green(" \u2714 Agent Teams already enabled"));
@@ -15591,7 +16458,7 @@ var initCommand = new Command2("init").description("Guided first-time setup: hub
15591
16458
  init_dist();
15592
16459
  init_dist2();
15593
16460
  import { Command as Command3 } from "commander";
15594
- import fs20 from "fs";
16461
+ import fs21 from "fs";
15595
16462
  import path24 from "path";
15596
16463
  import chalk5 from "chalk";
15597
16464
  function restartAfterProjectsAdd(opts) {
@@ -15606,20 +16473,20 @@ function restartAfterProjectsAdd(opts) {
15606
16473
  function findPackageRoot2() {
15607
16474
  let dir = path24.dirname(new URL(import.meta.url).pathname);
15608
16475
  while (dir !== "/") {
15609
- if (fs20.existsSync(path24.join(dir, "package.json"))) return dir;
16476
+ if (fs21.existsSync(path24.join(dir, "package.json"))) return dir;
15610
16477
  dir = path24.dirname(dir);
15611
16478
  }
15612
16479
  return process.cwd();
15613
16480
  }
15614
16481
  function copyDirRecursive2(src, dest) {
15615
- fs20.mkdirSync(dest, { recursive: true });
15616
- for (const entry of fs20.readdirSync(src, { withFileTypes: true })) {
16482
+ fs21.mkdirSync(dest, { recursive: true });
16483
+ for (const entry of fs21.readdirSync(src, { withFileTypes: true })) {
15617
16484
  const srcPath = path24.join(src, entry.name);
15618
16485
  const destPath = path24.join(dest, entry.name);
15619
16486
  if (entry.isDirectory()) {
15620
16487
  copyDirRecursive2(srcPath, destPath);
15621
16488
  } else {
15622
- fs20.copyFileSync(srcPath, destPath);
16489
+ fs21.copyFileSync(srcPath, destPath);
15623
16490
  }
15624
16491
  }
15625
16492
  }
@@ -15655,7 +16522,7 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
15655
16522
  process.exit(1);
15656
16523
  }
15657
16524
  const resolvedPath = resolveHome(projectPath);
15658
- if (!fs20.existsSync(path24.join(resolvedPath, ".git"))) {
16525
+ if (!fs21.existsSync(path24.join(resolvedPath, ".git"))) {
15659
16526
  console.log(chalk5.yellow(`
15660
16527
  \u26A0 No .git found at ${resolvedPath}. Make sure this is the project root, not a parent directory.
15661
16528
  `));
@@ -15724,19 +16591,19 @@ var addCmd = new Command3("add").description("Register a project").argument("<na
15724
16591
  restartAfterProjectsAdd({ noRestart: opts.restart === false });
15725
16592
  const pkgRoot = findPackageRoot2();
15726
16593
  const spokeTemplate = path24.join(pkgRoot, "obsidian", "spoke");
15727
- if (fs20.existsSync(spokeVault)) {
16594
+ if (fs21.existsSync(spokeVault)) {
15728
16595
  console.log(chalk5.yellow(` \u26A0 Spoke vault already exists at ${spokeVault}, skipping scaffold`));
15729
- } else if (fs20.existsSync(spokeTemplate)) {
16596
+ } else if (fs21.existsSync(spokeTemplate)) {
15730
16597
  copyDirRecursive2(spokeTemplate, spokeVault);
15731
16598
  const statusPath = path24.join(spokeVault, "status.md");
15732
- if (fs20.existsSync(statusPath)) {
15733
- const content = fs20.readFileSync(statusPath, "utf-8");
16599
+ if (fs21.existsSync(statusPath)) {
16600
+ const content = fs21.readFileSync(statusPath, "utf-8");
15734
16601
  const updated = content.replace(/^project: unnamed/m, `project: ${name}`);
15735
- fs20.writeFileSync(statusPath, updated);
16602
+ fs21.writeFileSync(statusPath, updated);
15736
16603
  }
15737
16604
  console.log(chalk5.green(` \u2714 Spoke vault scaffolded at ${spokeVault}`));
15738
16605
  } else {
15739
- fs20.mkdirSync(spokeVault, { recursive: true });
16606
+ fs21.mkdirSync(spokeVault, { recursive: true });
15740
16607
  console.log(chalk5.yellow(` \u26A0 Spoke template not found; created empty dir at ${spokeVault}`));
15741
16608
  }
15742
16609
  console.log("");
@@ -15804,7 +16671,7 @@ init_dist4();
15804
16671
  init_dist3();
15805
16672
  init_dist();
15806
16673
  import { Command as Command5 } from "commander";
15807
- import { execSync as execSync9 } from "child_process";
16674
+ import { execSync as execSync10 } from "child_process";
15808
16675
  import path25 from "path";
15809
16676
  import os14 from "os";
15810
16677
  import chalk7 from "chalk";
@@ -15815,7 +16682,7 @@ var TASK_PROMPTS = {
15815
16682
  "wiki-aggregate": "Run a wiki aggregation pass using the squadrant:command-ops skill. Scan each spoke wiki index, identify shared knowledge worth promoting, write hub wiki pages, and exit when done."
15816
16683
  };
15817
16684
  function detectCurrentWorkspace() {
15818
- const out = execSync9(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
16685
+ const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
15819
16686
  const match = out.match(/workspace:\d+/);
15820
16687
  if (!match) {
15821
16688
  throw new Error("Could not detect current cmux workspace. Run `squadrant command` from inside a cmux workspace.");
@@ -17133,7 +18000,7 @@ init_dist4();
17133
18000
  init_dist();
17134
18001
  init_dist2();
17135
18002
  import { Command as Command12 } from "commander";
17136
- import fs21 from "fs";
18003
+ import fs22 from "fs";
17137
18004
  import path26 from "path";
17138
18005
  import os15 from "os";
17139
18006
  import chalk12 from "chalk";
@@ -17170,7 +18037,7 @@ async function runSideSpawn2(input) {
17170
18037
  prompt: input.topic,
17171
18038
  workdir: spawnCwd,
17172
18039
  role: "side",
17173
- promptFile: fs21.existsSync(promptFile) ? promptFile : void 0,
18040
+ promptFile: fs22.existsSync(promptFile) ? promptFile : void 0,
17174
18041
  interactive: true,
17175
18042
  permissionMode: config.defaults.permissions?.crew ?? "auto",
17176
18043
  ...sideModel ? { model: sideModel } : {},
@@ -17277,7 +18144,7 @@ sideCommand.command("close").description("Close a side-session (closes its tab)"
17277
18144
  init_dist();
17278
18145
  init_dist4();
17279
18146
  import { Command as Command13 } from "commander";
17280
- import { execSync as execSync10 } from "child_process";
18147
+ import { execSync as execSync11 } from "child_process";
17281
18148
  import chalk14 from "chalk";
17282
18149
 
17283
18150
  // packages/web/dist/read-status.js
@@ -17433,7 +18300,7 @@ function renderDashboard(rows, opts) {
17433
18300
 
17434
18301
  // packages/web/dist/sync-hub.js
17435
18302
  init_dist();
17436
- import fs22 from "fs";
18303
+ import fs23 from "fs";
17437
18304
  import path27 from "path";
17438
18305
  function buildMirrorMarkdown(s) {
17439
18306
  const fenced = "```";
@@ -17460,8 +18327,8 @@ function buildMirrorMarkdown(s) {
17460
18327
  function syncHub(deps) {
17461
18328
  if (!deps.config.hubVault)
17462
18329
  return [];
17463
- const writeFile6 = deps.writeFile ?? ((p, c) => fs22.writeFileSync(p, c));
17464
- const mkdir6 = deps.mkdir ?? ((p) => fs22.mkdirSync(p, { recursive: true }));
18330
+ const writeFile6 = deps.writeFile ?? ((p, c) => fs23.writeFileSync(p, c));
18331
+ const mkdir6 = deps.mkdir ?? ((p) => fs23.mkdirSync(p, { recursive: true }));
17465
18332
  const projectsDir = path27.join(resolveHome(deps.config.hubVault), "projects");
17466
18333
  mkdir6(projectsDir);
17467
18334
  const out = [];
@@ -17481,6 +18348,7 @@ function syncHub(deps) {
17481
18348
  // packages/web/dist/web-server.js
17482
18349
  init_dist2();
17483
18350
  import { createServer as createServer5 } from "http";
18351
+ import { dirname as dirname7, join as join29 } from "path";
17484
18352
 
17485
18353
  // packages/web/dist/snapshot-merge.js
17486
18354
  function mergeSnapshot(daemon, external, now) {
@@ -17946,6 +18814,24 @@ function renderEnv(ext) {
17946
18814
  out.push(`</section>`);
17947
18815
  return out.join("");
17948
18816
  }
18817
+ function renderLogs() {
18818
+ return [
18819
+ `<section class="panel" data-panel="logs" role="tabpanel" aria-label="Logs">`,
18820
+ sectionHead("Daemon log", "Live tail of squadrantd.log \u2014 lifecycle events, delivery attempts, auto-launches and errors as the daemon writes them."),
18821
+ `<div class="log-controls">`,
18822
+ `<div class="filter-bar" role="group" aria-label="Filter log lines by severity">`,
18823
+ `<button class="filter-btn on" data-log-sev="all" aria-pressed="true">All</button>`,
18824
+ `<button class="filter-btn" data-log-sev="warn" aria-pressed="false">Warn+</button>`,
18825
+ `<button class="filter-btn" data-log-sev="error" aria-pressed="false">Errors</button>`,
18826
+ `</div>`,
18827
+ `<button class="filter-btn" data-log-pause aria-pressed="false">Pause</button>`,
18828
+ `<button class="filter-btn" data-log-copy>Copy</button>`,
18829
+ `<span class="log-status" data-log-status>connecting\u2026</span>`,
18830
+ `</div>`,
18831
+ `<div class="log-view mono" data-log-view aria-live="polite" aria-label="Daemon log lines"></div>`,
18832
+ `</section>`
18833
+ ].join("");
18834
+ }
17949
18835
  var ATTN_ORDER = { stale: 0, gone: 1, alive: 2, unknown: 3, stopped: 4 };
17950
18836
  var STATE_LABEL = { alive: "running", stale: "blocked", gone: "errored", unknown: "idle", stopped: "offline" };
17951
18837
  var STATE_CLS = { alive: "s-alive", stale: "s-stale", gone: "s-gone", unknown: "s-unknown", stopped: "s-stopped" };
@@ -18074,7 +18960,8 @@ var TABS = [
18074
18960
  ["overview", "Overview"],
18075
18961
  ["projects", "Projects"],
18076
18962
  ["daemon", "Daemon"],
18077
- ["environment", "Environment"]
18963
+ ["environment", "Environment"],
18964
+ ["logs", "Logs"]
18078
18965
  ];
18079
18966
  function tabNav() {
18080
18967
  const btns = TABS.map(([id, label], i) => `<button class="tab${i === 0 ? " on" : ""}" data-tab="${id}" role="tab" aria-selected="${i === 0 ? "true" : "false"}">${esc(label)}</button>`).join("");
@@ -18123,6 +19010,7 @@ function renderContent(snap) {
18123
19010
  out.push(renderProjects(snap, col.now));
18124
19011
  out.push(renderDaemon(snap));
18125
19012
  out.push(renderEnv(snap.external));
19013
+ out.push(renderLogs());
18126
19014
  out.push(`</div>`);
18127
19015
  out.push(metricsBlob(col, linkLost));
18128
19016
  return out.join("\n");
@@ -18364,6 +19252,17 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
18364
19252
 
18365
19253
  .empty{border:1px dashed var(--bezel);border-radius:12px;padding:24px;color:var(--ink-dim);text-align:center;background:var(--panel)}
18366
19254
 
19255
+ /* Logs (live daemon-log tail) */
19256
+ .log-controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 12px}
19257
+ .log-controls .filter-bar{margin:0}
19258
+ .log-status{margin-left:auto;color:var(--ink-dim);font-size:11px}
19259
+ .log-view{height:min(58vh,520px);overflow:auto;background:#0b1220;border:1px solid var(--bezel);border-radius:12px;
19260
+ padding:12px 14px;box-shadow:var(--shadow);font-size:12px;line-height:1.55}
19261
+ .log-line{white-space:pre-wrap;word-break:break-word;color:#c7d2e0}
19262
+ .log-line.l-warn{color:#f0c674}
19263
+ .log-line.l-error{color:#ff8a80}
19264
+ .log-view .empty-log{color:#6b7a90}
19265
+
18367
19266
  :focus-visible{outline:2px solid var(--hud);outline-offset:2px;border-radius:4px}
18368
19267
 
18369
19268
  @keyframes beat{0%,100%{box-shadow:0 0 0 0 rgba(58,210,159,.45)}50%{box-shadow:0 0 0 6px rgba(58,210,159,0)}}
@@ -18377,6 +19276,7 @@ body{margin:0;min-height:100vh;background:radial-gradient(1200px 760px at 50% -2
18377
19276
  var CLIENT_JS = `
18378
19277
  (function(){
18379
19278
  var activeTab='live';var hist={};var prev={};var currentFilter='all';var currentSort='attention';var sortAsc=true;var currentSearch='';var expandedProjects={};
19279
+ var logLines=[];var logSev='all';var logPaused=false;var logAutoscroll=true;var logConnected=false;var logScrollSave=0;var LOG_MAX=1000;
18380
19280
  function pushHist(k,v,t){var a=hist[k]||(hist[k]=[]);if(a.length&&a[a.length-1].t===t)return;a.push({t:t,v:v});if(a.length>48)a.shift();}
18381
19281
  function ingest(){var el=document.getElementById('squadrant-metrics');if(!el)return;var m;try{m=JSON.parse(el.textContent);}catch(e){return;}pushHist('errors',m.errors,m.t);pushHist('behind',m.behind,m.t);pushHist('crewAge',Math.round(m.crewAgeMs/1000),m.t);pushHist('defers',m.maxDeferCount,m.t);}
18382
19282
  function sparkSVG(a){var W=100,H=28,p=2;if(!a.length)return'';var vs=a.map(function(x){return x.v;});var mx=Math.max.apply(null,vs),mn=Math.min.apply(null,vs);if(mx===mn)mx=mn+1;var n=a.length;var pts=a.map(function(x,i){var px=n>1?p+i/(n-1)*(W-2*p):W/2;var py=H-p-(x.v-mn)/(mx-mn)*(H-2*p);return[px,py];});var d=pts.map(function(pt,i){return(i?'L':'M')+pt[0].toFixed(1)+' '+pt[1].toFixed(1);}).join(' ');var last=pts[pts.length-1];var area=d+' L'+last[0].toFixed(1)+' '+H+' L'+pts[0][0].toFixed(1)+' '+H+' Z';return'<path class="spark-area" d="'+area+'"/><path class="spark-line" d="'+d+'"/><circle class="spark-dot" cx="'+last[0].toFixed(1)+'" cy="'+last[1].toFixed(1)+'" r="2"/>';}
@@ -18389,9 +19289,19 @@ var CLIENT_JS = `
18389
19289
  function applySearch(){var q=currentSearch.toLowerCase();document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){if(!q){if(!hideByFilter(r.getAttribute('data-rollup'),currentFilter))r.hidden=false;return;}var pn=(r.getAttribute('data-project')||'').toLowerCase();var dd=((r.querySelector('.live-detail')||{}).textContent||'').toLowerCase();var rl=r.getAttribute('data-rollup');if(hideByFilter(rl,currentFilter)){r.hidden=true;return;}r.hidden=pn.indexOf(q)===-1&&dd.indexOf(q)===-1;});}
18390
19290
  function applySort(){var table=document.querySelector('[data-panel="live"] [data-live-table]');if(!table)return;var tbody=table.querySelector('tbody');if(!tbody)return;var rows=Array.prototype.slice.call(tbody.querySelectorAll('.live-row'));var dir=sortAsc?1:-1;rows.sort(function(a,b){var va,vb;if(currentSort==='name'){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();}else if(currentSort==='attention'){var order={gone:0,stale:1,alive:2,unknown:3,stopped:4};va=order[a.getAttribute('data-rollup')]??99;vb=order[b.getAttribute('data-rollup')]??99;if(va===vb){va=(a.getAttribute('data-project')||'').toLowerCase();vb=(b.getAttribute('data-project')||'').toLowerCase();return va<vb?-1:va>vb?1:0;}}else if(currentSort==='activity'){va=parseInt(a.getAttribute('data-max-age'))||0;vb=parseInt(b.getAttribute('data-max-age'))||0;}return va<vb?-dir:va>vb?dir:0;});rows.forEach(function(r){tbody.appendChild(r);});}
18391
19291
  function applyExpand(){document.querySelectorAll('[data-panel="live"] .live-row').forEach(function(r){var pn=r.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=!!expandedProjects[pn];er.hidden=!show;r.classList.toggle('expanded',show);}});}
18392
- function refresh(){ingest();applyTab();drawSparks();countUps();applyFilter();applySearch();applySort();applyExpand();}
18393
- document.addEventListener('click',function(e){var t=e.target&&e.target.closest?e.target.closest('[data-tab]'):null;if(t){activeTab=t.getAttribute('data-tab');applyTab();var c=document.getElementById('content');if(c){c.classList.remove('swap');void c.offsetWidth;c.classList.add('swap');}}var fb=e.target&&e.target.closest?e.target.closest('[data-filter]'):null;if(fb){currentFilter=fb.getAttribute('data-filter');applyFilter();applySearch();}var sh=e.target&&e.target.closest?e.target.closest('[data-sort]'):null;if(sh){var key=sh.getAttribute('data-sort');if(key===currentSort)sortAsc=!sortAsc;else{currentSort=key;sortAsc=true;}applySort();}var lr=e.target&&e.target.closest?e.target.closest('[data-panel="live"] .live-row'):null;if(lr&&!e.target.closest('[data-sort]')){var pn=lr.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=er.hidden;er.hidden=!show;lr.classList.toggle('expanded',!!show);expandedProjects[pn]=!!show;}}});
18394
- document.addEventListener('keydown',function(e){var ae=document.activeElement;if((e.key==='ArrowRight'||e.key==='ArrowLeft')&&ae&&ae.getAttribute&&ae.getAttribute('data-tab')){var o=['live','overview','projects','daemon','environment'];var i=o.indexOf(activeTab);i=(i+(e.key==='ArrowRight'?1:o.length-1))%o.length;activeTab=o[i];applyTab();var nt=document.querySelector('[data-tab="'+activeTab+'"]');if(nt)nt.focus();e.preventDefault();}});
19292
+ function logSevOf(s){return /error|failed|fatal/i.test(s)?'error':/warn/i.test(s)?'warn':'info';}
19293
+ function logMatch(l){if(logSev==='all')return true;if(logSev==='error')return logSevOf(l)==='error';if(logSev==='warn')return logSevOf(l)!=='info';return true;}
19294
+ function escHtml(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
19295
+ function logVisible(){return logLines.filter(logMatch);}
19296
+ function renderLogs(){var v=document.querySelector('[data-log-view]');if(!v)return;var vis=logVisible();var html='';for(var i=0;i<vis.length;i++){html+='<div class="log-line l-'+logSevOf(vis[i])+'">'+escHtml(vis[i])+'</div>';}v.innerHTML=html||'<div class="empty-log">waiting for log output\u2026</div>';if(logAutoscroll)v.scrollTop=v.scrollHeight;else v.scrollTop=logScrollSave;
19297
+ var sb=document.querySelectorAll('[data-log-sev]');for(var j=0;j<sb.length;j++){var on=sb[j].getAttribute('data-log-sev')===logSev;sb[j].classList.toggle('on',on);sb[j].setAttribute('aria-pressed',on?'true':'false');}
19298
+ var pb=document.querySelector('[data-log-pause]');if(pb){pb.textContent=logPaused?'Resume':'Pause';pb.classList.toggle('on',logPaused);pb.setAttribute('aria-pressed',logPaused?'true':'false');}
19299
+ var st=document.querySelector('[data-log-status]');if(st){if(!logConnected)st.textContent='log stream reconnecting\u2026';else st.textContent=(logPaused?'paused \xB7 ':'')+vis.length+(vis.length===logLines.length?'':'/'+logLines.length)+' lines';}
19300
+ }
19301
+ function ingestLog(lines){if(!lines||!lines.length)return;logLines=logLines.concat(lines);if(logLines.length>LOG_MAX)logLines=logLines.slice(logLines.length-LOG_MAX);if(activeTab==='logs'&&!logPaused)renderLogs();}
19302
+ function refresh(){ingest();applyTab();drawSparks();countUps();applyFilter();applySearch();applySort();applyExpand();if(activeTab==='logs')renderLogs();}
19303
+ document.addEventListener('click',function(e){var t=e.target&&e.target.closest?e.target.closest('[data-tab]'):null;if(t){activeTab=t.getAttribute('data-tab');applyTab();var c=document.getElementById('content');if(c){c.classList.remove('swap');void c.offsetWidth;c.classList.add('swap');}if(activeTab==='logs')renderLogs();}var fb=e.target&&e.target.closest?e.target.closest('[data-filter]'):null;if(fb){currentFilter=fb.getAttribute('data-filter');applyFilter();applySearch();}var sh=e.target&&e.target.closest?e.target.closest('[data-sort]'):null;if(sh){var key=sh.getAttribute('data-sort');if(key===currentSort)sortAsc=!sortAsc;else{currentSort=key;sortAsc=true;}applySort();}var sv=e.target&&e.target.closest?e.target.closest('[data-log-sev]'):null;if(sv){logSev=sv.getAttribute('data-log-sev');renderLogs();}var lp=e.target&&e.target.closest?e.target.closest('[data-log-pause]'):null;if(lp){logPaused=!logPaused;renderLogs();}var cp=e.target&&e.target.closest?e.target.closest('[data-log-copy]'):null;if(cp&&navigator.clipboard){navigator.clipboard.writeText(logVisible().join('\\n'));}var lr=e.target&&e.target.closest?e.target.closest('[data-panel="live"] .live-row'):null;if(lr&&!e.target.closest('[data-sort]')){var pn=lr.getAttribute('data-project');var er=document.querySelector('[data-panel="live"] .live-expand[data-project="'+pn+'"]');if(er){var show=er.hidden;er.hidden=!show;lr.classList.toggle('expanded',!!show);expandedProjects[pn]=!!show;}}});
19304
+ document.addEventListener('keydown',function(e){var ae=document.activeElement;if((e.key==='ArrowRight'||e.key==='ArrowLeft')&&ae&&ae.getAttribute&&ae.getAttribute('data-tab')){var o=['live','overview','projects','daemon','environment','logs'];var i=o.indexOf(activeTab);i=(i+(e.key==='ArrowRight'?1:o.length-1))%o.length;activeTab=o[i];applyTab();var nt=document.querySelector('[data-tab="'+activeTab+'"]');if(nt)nt.focus();e.preventDefault();}});
18395
19305
  document.addEventListener('input',function(e){var sb=e.target&&e.target.closest?e.target.closest('[data-live-search]'):null;if(sb){currentSearch=sb.value;applySearch();}});
18396
19306
  var led=document.getElementById('led');var conn=document.getElementById('conn');var updated=document.getElementById('updated');
18397
19307
  function tickAge(){if(!generatedAt)return;var s=Math.max(0,Math.round((Date.now()-generatedAt)/1000));updated.textContent='updated '+s+'s ago';}
@@ -18400,6 +19310,11 @@ var CLIENT_JS = `
18400
19310
  es.onopen=function(){conn.textContent='live';conn.className='conn live';led.className='led live';};
18401
19311
  es.onerror=function(){conn.textContent='reconnecting';conn.className='conn down';led.className='led down';};
18402
19312
  es.onmessage=function(e){try{var d=JSON.parse(e.data);document.getElementById('content').innerHTML=d.contentHtml;generatedAt=d.generatedAt;tickAge();refresh();}catch(_){}};
19313
+ document.addEventListener('scroll',function(e){var v=e.target;if(v&&v.getAttribute&&v.getAttribute('data-log-view')!==null){logAutoscroll=v.scrollTop+v.clientHeight>=v.scrollHeight-6;logScrollSave=v.scrollTop;}},true);
19314
+ var les=new EventSource('/logs');
19315
+ les.onopen=function(){logConnected=true;renderLogs();};
19316
+ les.onerror=function(){logConnected=false;renderLogs();};
19317
+ les.onmessage=function(e){try{var d=JSON.parse(e.data);if(!logPaused)ingestLog(d&&d.lines);}catch(_){}};
18403
19318
  refresh();tickAge();
18404
19319
  })();
18405
19320
  `.trim();
@@ -18422,7 +19337,100 @@ function renderHtml(snap, opts = {}) {
18422
19337
  ].join("");
18423
19338
  }
18424
19339
 
19340
+ // packages/web/dist/log-tail.js
19341
+ import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
19342
+ var LOG_TAIL_BYTES = 256 * 1024;
19343
+ var LOG_RING_SIZE = 1e3;
19344
+ function defaultSize(path39) {
19345
+ try {
19346
+ return statSync4(path39).size;
19347
+ } catch {
19348
+ return 0;
19349
+ }
19350
+ }
19351
+ function defaultRead(path39, start, len) {
19352
+ if (len <= 0)
19353
+ return "";
19354
+ try {
19355
+ const fd = openSync3(path39, "r");
19356
+ try {
19357
+ const buf = Buffer.alloc(len);
19358
+ const n = readSync2(fd, buf, 0, len, start);
19359
+ return buf.subarray(0, n).toString("utf-8");
19360
+ } finally {
19361
+ closeSync3(fd);
19362
+ }
19363
+ } catch {
19364
+ return "";
19365
+ }
19366
+ }
19367
+ var LogTailer = class {
19368
+ path;
19369
+ ringSize;
19370
+ offset = 0;
19371
+ pending = "";
19372
+ ring = [];
19373
+ size;
19374
+ read;
19375
+ constructor(path39, ringSize = LOG_RING_SIZE, deps = {}) {
19376
+ this.path = path39;
19377
+ this.ringSize = ringSize;
19378
+ this.size = deps.size ?? defaultSize;
19379
+ this.read = deps.read ?? defaultRead;
19380
+ }
19381
+ /** Seed history from the last `maxBytes` and position the cursor at EOF.
19382
+ * Idempotent; call once at startup. A trailing partial line stays pending
19383
+ * so the next append completes it without loss or duplication. */
19384
+ init(maxBytes = LOG_TAIL_BYTES) {
19385
+ const size = this.size(this.path);
19386
+ this.pending = "";
19387
+ this.ring = [];
19388
+ if (size <= 0) {
19389
+ this.offset = size;
19390
+ return;
19391
+ }
19392
+ const start = Math.max(0, size - maxBytes);
19393
+ const parts = this.read(this.path, start, size - start).split("\n");
19394
+ const rest = parts.pop() ?? "";
19395
+ if (start > 0 && parts.length)
19396
+ parts.shift();
19397
+ this.push(parts);
19398
+ this.pending = rest;
19399
+ this.offset = size;
19400
+ }
19401
+ /** Complete lines appended since the previous poll. */
19402
+ poll() {
19403
+ const size = this.size(this.path);
19404
+ if (size < this.offset) {
19405
+ this.offset = 0;
19406
+ this.pending = "";
19407
+ }
19408
+ if (size <= this.offset)
19409
+ return [];
19410
+ const text = this.read(this.path, this.offset, size - this.offset);
19411
+ if (!text)
19412
+ return [];
19413
+ this.offset += Buffer.byteLength(text);
19414
+ const parts = (this.pending + text).split("\n");
19415
+ this.pending = parts.pop() ?? "";
19416
+ this.push(parts);
19417
+ return parts;
19418
+ }
19419
+ /** The buffered backlog (oldest first) replayed to a newly-connected client. */
19420
+ backlog() {
19421
+ return this.ring.slice();
19422
+ }
19423
+ push(lines) {
19424
+ for (const l of lines)
19425
+ this.ring.push(l);
19426
+ const over = this.ring.length - this.ringSize;
19427
+ if (over > 0)
19428
+ this.ring.splice(0, over);
19429
+ }
19430
+ };
19431
+
18425
19432
  // packages/web/dist/web-server.js
19433
+ var LOG_POLL_MS = 1e3;
18426
19434
  async function querySnapshot(sockPath) {
18427
19435
  try {
18428
19436
  const reply = await sendRequest(sockPath, { kind: "snapshot" });
@@ -18435,6 +19443,9 @@ async function startWebServer(opts) {
18435
19443
  const host = opts.host ?? "127.0.0.1";
18436
19444
  const now = opts.now ?? (() => Date.now());
18437
19445
  const clients = /* @__PURE__ */ new Set();
19446
+ const logClients = /* @__PURE__ */ new Set();
19447
+ const logPath2 = opts.logPath ?? join29(dirname7(opts.sockPath), "squadrantd.log");
19448
+ const tailer = new LogTailer(logPath2);
18438
19449
  let latest = null;
18439
19450
  async function tick() {
18440
19451
  const daemon = await querySnapshot(opts.sockPath);
@@ -18450,6 +19461,19 @@ async function startWebServer(opts) {
18450
19461
  }
18451
19462
  }
18452
19463
  }
19464
+ function broadcastLog(lines) {
19465
+ if (lines.length === 0)
19466
+ return;
19467
+ const payload = `data: ${JSON.stringify({ lines })}
19468
+
19469
+ `;
19470
+ for (const res of logClients) {
19471
+ try {
19472
+ res.write(payload);
19473
+ } catch {
19474
+ }
19475
+ }
19476
+ }
18453
19477
  const server = createServer5((req, res) => {
18454
19478
  if (req.method !== "GET") {
18455
19479
  res.writeHead(405).end();
@@ -18475,6 +19499,22 @@ async function startWebServer(opts) {
18475
19499
  req.on("close", () => clients.delete(res));
18476
19500
  return;
18477
19501
  }
19502
+ if (req.url === "/logs") {
19503
+ res.writeHead(200, {
19504
+ "Content-Type": "text/event-stream",
19505
+ "Cache-Control": "no-cache",
19506
+ Connection: "keep-alive"
19507
+ });
19508
+ res.write("retry: 3000\n\n");
19509
+ const backlog = tailer.backlog();
19510
+ if (backlog.length)
19511
+ res.write(`data: ${JSON.stringify({ lines: backlog })}
19512
+
19513
+ `);
19514
+ logClients.add(res);
19515
+ req.on("close", () => logClients.delete(res));
19516
+ return;
19517
+ }
18478
19518
  res.writeHead(404).end();
18479
19519
  });
18480
19520
  await new Promise((resolve4, reject) => {
@@ -18489,17 +19529,33 @@ async function startWebServer(opts) {
18489
19529
  });
18490
19530
  }, opts.intervalMs);
18491
19531
  timer.unref?.();
19532
+ tailer.init();
19533
+ const logTimer = setInterval(() => {
19534
+ try {
19535
+ broadcastLog(tailer.poll());
19536
+ } catch {
19537
+ }
19538
+ }, LOG_POLL_MS);
19539
+ logTimer.unref?.();
18492
19540
  return {
18493
19541
  port: boundPort,
18494
19542
  close: () => new Promise((resolve4) => {
18495
19543
  clearInterval(timer);
19544
+ clearInterval(logTimer);
18496
19545
  for (const res of clients) {
18497
19546
  try {
18498
19547
  res.end();
18499
19548
  } catch {
18500
19549
  }
18501
19550
  }
19551
+ for (const res of logClients) {
19552
+ try {
19553
+ res.end();
19554
+ } catch {
19555
+ }
19556
+ }
18502
19557
  clients.clear();
19558
+ logClients.clear();
18503
19559
  server.close(() => resolve4());
18504
19560
  })
18505
19561
  };
@@ -18509,7 +19565,7 @@ async function startWebServer(opts) {
18509
19565
  init_dist();
18510
19566
  var SOCK3 = DAEMON_SOCK_PATH;
18511
19567
  function detectCurrentWorkspace2() {
18512
- const out = execSync10(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
19568
+ const out = execSync11(`"${resolveCmuxBin()}" current-workspace`, { encoding: "utf-8" }).trim();
18513
19569
  const match = out.match(/workspace:\d+/);
18514
19570
  if (!match) {
18515
19571
  throw new Error("Could not detect current cmux workspace. Run `squadrant dashboard --pane` from inside a cmux workspace.");
@@ -18592,8 +19648,8 @@ init_dist3();
18592
19648
  init_dist4();
18593
19649
  init_dist2();
18594
19650
  import { Command as Command14 } from "commander";
18595
- import { execSync as execSync11, execFileSync as execFileSync10 } from "child_process";
18596
- import fs23 from "fs";
19651
+ import { execSync as execSync12, execFileSync as execFileSync10 } from "child_process";
19652
+ import fs24 from "fs";
18597
19653
  import path28 from "path";
18598
19654
  import os16 from "os";
18599
19655
  import chalk15 from "chalk";
@@ -18724,7 +19780,7 @@ function resolveAnthropicRefusal(role, workspaceName, agentName, model, readOpen
18724
19780
  function ensureCmuxReady(headless) {
18725
19781
  if (headless || isInsideCmux()) return;
18726
19782
  console.log(chalk15.yellow("\n Not running inside cmux. Opening cmux app...\n"));
18727
- execSync11(`open "${CMUX_APP}"`, { stdio: "inherit" });
19783
+ execSync12(`open "${CMUX_APP}"`, { stdio: "inherit" });
18728
19784
  console.log(chalk15.bold(" Run `squadrant launch` from inside a cmux workspace.\n"));
18729
19785
  process.exit(0);
18730
19786
  }
@@ -18921,12 +19977,12 @@ var launchCommand = new Command14("launch").description(
18921
19977
  }
18922
19978
  if (opts.all) {
18923
19979
  const hubPath = resolveHome(config.hubVault);
18924
- fs23.mkdirSync(hubPath, { recursive: true });
19980
+ fs24.mkdirSync(hubPath, { recursive: true });
18925
19981
  console.log(chalk15.bold("\nLaunching all captain workspaces\n"));
18926
19982
  for (const [name, proj] of Object.entries(config.projects)) {
18927
19983
  const projPath = resolveHome(proj.path);
18928
19984
  const spokePath = resolveHome(proj.spokeVault);
18929
- if (!fs23.existsSync(spokePath)) {
19985
+ if (!fs24.existsSync(spokePath)) {
18930
19986
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
18931
19987
  await ensureSpokeLayout(spokeDriver);
18932
19988
  console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -18964,7 +20020,7 @@ Launching ${selected.length} captain workspace(s) in parallel
18964
20020
  const proj = config.projects[name];
18965
20021
  const projPath = resolveHome(proj.path);
18966
20022
  const spokePath = resolveHome(proj.spokeVault);
18967
- if (!fs23.existsSync(spokePath)) {
20023
+ if (!fs24.existsSync(spokePath)) {
18968
20024
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(name, config);
18969
20025
  await ensureSpokeLayout(spokeDriver);
18970
20026
  console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -18988,7 +20044,7 @@ Launching ${selected.length} captain workspace(s) in parallel
18988
20044
  const proj = config.projects[project];
18989
20045
  const projPath = resolveHome(proj.path);
18990
20046
  const spokePath = resolveHome(proj.spokeVault);
18991
- if (!fs23.existsSync(spokePath)) {
20047
+ if (!fs24.existsSync(spokePath)) {
18992
20048
  const spokeDriver = new WorkspaceRegistry({ obsidian: createObsidianDriver }).forProject(project, config);
18993
20049
  await ensureSpokeLayout(spokeDriver);
18994
20050
  console.log(chalk15.cyan(` \u2714 Created spoke vault at ${spokePath}`));
@@ -19124,24 +20180,24 @@ Shutting down captain workspace for '${project}'...
19124
20180
  // packages/cli/src/commands/feedback.ts
19125
20181
  init_dist();
19126
20182
  import { Command as Command16 } from "commander";
19127
- import fs24 from "fs";
20183
+ import fs25 from "fs";
19128
20184
  import os17 from "os";
19129
20185
  import path29 from "path";
19130
20186
  import { fileURLToPath as fileURLToPath3 } from "url";
19131
- import { execSync as execSync12 } from "child_process";
20187
+ import { execSync as execSync13 } from "child_process";
19132
20188
  import chalk17 from "chalk";
19133
20189
  var REPO_URL = "https://github.com/tu11aa/squadrant";
19134
20190
  function readPkgVersion() {
19135
20191
  try {
19136
20192
  const pkgPath = path29.join(path29.dirname(fileURLToPath3(import.meta.url)), "..", "package.json");
19137
- return JSON.parse(fs24.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
20193
+ return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version ?? "unknown";
19138
20194
  } catch {
19139
20195
  return "unknown";
19140
20196
  }
19141
20197
  }
19142
20198
  function readMetrics(metricsPath) {
19143
20199
  try {
19144
- return JSON.parse(fs24.readFileSync(metricsPath, "utf-8"));
20200
+ return JSON.parse(fs25.readFileSync(metricsPath, "utf-8"));
19145
20201
  } catch {
19146
20202
  return {};
19147
20203
  }
@@ -19187,7 +20243,7 @@ var feedbackCommand = new Command16("feedback").description("Open a pre-filled G
19187
20243
  console.log(chalk17.dim(` URL: ${issueUrl.substring(0, 80)}...
19188
20244
  `));
19189
20245
  try {
19190
- execSync12(`open "${issueUrl}"`, { stdio: "ignore" });
20246
+ execSync13(`open "${issueUrl}"`, { stdio: "ignore" });
19191
20247
  console.log(chalk17.green(" \u2714 Browser opened\n"));
19192
20248
  } catch {
19193
20249
  console.log(chalk17.yellow(" \u26A0 Could not open browser automatically."));
@@ -19589,7 +20645,7 @@ init_dist4();
19589
20645
  init_dist();
19590
20646
  import { Command as Command21 } from "commander";
19591
20647
  import chalk22 from "chalk";
19592
- import fs25 from "fs";
20648
+ import fs26 from "fs";
19593
20649
  import path30 from "path";
19594
20650
  import { fileURLToPath as fileURLToPath4 } from "url";
19595
20651
  function parseScope(v2) {
@@ -19601,7 +20657,7 @@ function parseScope(v2) {
19601
20657
  function findPackageRoot3() {
19602
20658
  let dir = path30.dirname(fileURLToPath4(import.meta.url));
19603
20659
  while (dir !== "/" && dir !== "") {
19604
- if (fs25.existsSync(path30.join(dir, "package.json"))) return dir;
20660
+ if (fs26.existsSync(path30.join(dir, "package.json"))) return dir;
19605
20661
  dir = path30.dirname(dir);
19606
20662
  }
19607
20663
  return process.cwd();
@@ -19798,12 +20854,12 @@ init_dist();
19798
20854
  init_dist();
19799
20855
  init_dist2();
19800
20856
  import { Command as Command23 } from "commander";
19801
- import fs26 from "fs";
20857
+ import fs27 from "fs";
19802
20858
  import { fileURLToPath as fileURLToPath5 } from "url";
19803
- import { dirname as dirname7, join as join29 } from "path";
20859
+ import { dirname as dirname8, join as join30 } from "path";
19804
20860
  import chalk23 from "chalk";
19805
20861
  function runConfigCheck(opts) {
19806
- const raw = JSON.parse(fs26.readFileSync(opts.configPath, "utf-8"));
20862
+ const raw = JSON.parse(fs27.readFileSync(opts.configPath, "utf-8"));
19807
20863
  const def = getDefaultConfig();
19808
20864
  const items = detectDrift(raw, def);
19809
20865
  let working = raw;
@@ -19891,7 +20947,7 @@ function printItems(items) {
19891
20947
  var configCommand = new Command23("config").description("Inspect and reconcile squadrant config");
19892
20948
  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) => {
19893
20949
  const pkgVersion = readPkgVersion2();
19894
- if (!fs26.existsSync(DEFAULT_CONFIG_PATH)) {
20950
+ if (!fs27.existsSync(DEFAULT_CONFIG_PATH)) {
19895
20951
  console.log(chalk23.yellow("No config found \u2014 run `squadrant init` first."));
19896
20952
  return;
19897
20953
  }
@@ -19936,14 +20992,14 @@ configCommand.command("set").description("Write a config value by dotted key (e.
19936
20992
  }
19937
20993
  });
19938
20994
  function readPkgVersion2() {
19939
- const pkgPath = join29(dirname7(fileURLToPath5(import.meta.url)), "..", "package.json");
19940
- return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
20995
+ const pkgPath = join30(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
20996
+ return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
19941
20997
  }
19942
20998
 
19943
20999
  // packages/cli/src/commands/heal.ts
19944
21000
  init_dist();
19945
21001
  import { execFileSync as execFileSync11 } from "child_process";
19946
- import { join as join30 } from "path";
21002
+ import { join as join31 } from "path";
19947
21003
  import { homedir as homedir17 } from "os";
19948
21004
  import { Command as Command24 } from "commander";
19949
21005
  import chalk24 from "chalk";
@@ -20183,8 +21239,8 @@ var healCommand = new Command24("heal").description("Targeted, idempotent remedi
20183
21239
  }
20184
21240
  const config = loadConfig();
20185
21241
  const projects = opts.all ? Object.keys(config.projects) : [project];
20186
- const stateRoot = join30(homedir17(), ".config", "squadrant", "state");
20187
- const registry = new LivenessRegistry({ path: join30(stateRoot, "liveness.json") });
21242
+ const stateRoot = join31(homedir17(), ".config", "squadrant", "state");
21243
+ const registry = new LivenessRegistry({ path: join31(stateRoot, "liveness.json") });
20188
21244
  registry.load();
20189
21245
  const code = await runHealCaptain(projects, {
20190
21246
  liveness: readCmuxLiveness,
@@ -20216,7 +21272,7 @@ import chalk26 from "chalk";
20216
21272
  init_dist();
20217
21273
  init_dist2();
20218
21274
  import { Command as Command25 } from "commander";
20219
- import { execSync as execSync13 } from "child_process";
21275
+ import { execSync as execSync14 } from "child_process";
20220
21276
  import chalk25 from "chalk";
20221
21277
  async function runDispatch(toProject, task, opts) {
20222
21278
  const fromProject = resolveCurrentProject(loadConfig());
@@ -20232,7 +21288,7 @@ async function runDispatch(toProject, task, opts) {
20232
21288
  warmupTimeoutMs: opts.warmupTimeout,
20233
21289
  bootCaptain: async (project) => {
20234
21290
  try {
20235
- execSync13(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
21291
+ execSync14(`squadrant launch ${project}`, { stdio: "ignore", timeout: 15e3 });
20236
21292
  } catch {
20237
21293
  throw new Error(`failed to launch captain for '${project}' \u2014 is squadrant installed?`);
20238
21294
  }
@@ -20268,7 +21324,7 @@ init_dist();
20268
21324
  init_dist2();
20269
21325
  init_runtime2();
20270
21326
  init_require_daemon();
20271
- import { join as join32, dirname as dirname8 } from "path";
21327
+ import { join as join33, dirname as dirname9 } from "path";
20272
21328
  import { homedir as homedir18 } from "os";
20273
21329
  import { Command as Command27 } from "commander";
20274
21330
  import chalk28 from "chalk";
@@ -20277,23 +21333,23 @@ import chalk28 from "chalk";
20277
21333
  init_dist3();
20278
21334
  init_dist2();
20279
21335
  import { createServer as createServer6, connect as netConnect2 } from "net";
20280
- import fs27 from "fs";
20281
- import { join as join31 } from "path";
21336
+ import fs28 from "fs";
21337
+ import { join as join32 } from "path";
20282
21338
  import { randomUUID as randomUUID7 } from "crypto";
20283
21339
  import chalk27 from "chalk";
20284
21340
  var shared;
20285
- var registryEntryPath = () => join31(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
21341
+ var registryEntryPath = () => join32(CLAUDE_SESSIONS_DIR, `${process.pid}.json`);
20286
21342
  function unregisterSenderIdentity() {
20287
21343
  try {
20288
- fs27.unlinkSync(registryEntryPath());
21344
+ fs28.unlinkSync(registryEntryPath());
20289
21345
  } catch {
20290
21346
  }
20291
21347
  }
20292
21348
  function registerSenderIdentity(socketPath2) {
20293
21349
  try {
20294
21350
  unregisterSenderIdentity();
20295
- fs27.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
20296
- fs27.writeFileSync(
21351
+ fs28.mkdirSync(CLAUDE_SESSIONS_DIR, { recursive: true });
21352
+ fs28.writeFileSync(
20297
21353
  registryEntryPath(),
20298
21354
  JSON.stringify({
20299
21355
  pid: process.pid,
@@ -20319,7 +21375,7 @@ async function sharedReceiptListener() {
20319
21375
  // must never be the reason we refuse to start.
20320
21376
  unlinkStale: (p) => {
20321
21377
  try {
20322
- fs27.unlinkSync(p);
21378
+ fs28.unlinkSync(p);
20323
21379
  } catch {
20324
21380
  }
20325
21381
  },
@@ -20389,7 +21445,7 @@ async function runPing(project, message) {
20389
21445
  await requireDaemon();
20390
21446
  await needRef(resolved);
20391
21447
  const mode = resolveCaptainChannelMode(config.defaults);
20392
- const stateRoot = join32(homedir18(), ".config", "squadrant", "state");
21448
+ const stateRoot = join33(homedir18(), ".config", "squadrant", "state");
20393
21449
  const { channels, agentFor } = mode === "off" ? { channels: {}, agentFor: () => void 0 } : await buildCaptainChannels({ stateRoot, configAgent: config.defaults.roles?.captain?.agent });
20394
21450
  const channel = channels[agentFor(project) ?? ""];
20395
21451
  const { handled, outcome } = await deliverToCaptain(project, message, {
@@ -20398,7 +21454,7 @@ async function runPing(project, message) {
20398
21454
  log: (m) => console.error(chalk28.dim(m))
20399
21455
  });
20400
21456
  if (!handled) {
20401
- const stateRoot2 = join32(dirname8(DEFAULT_CONFIG_PATH), "state");
21457
+ const stateRoot2 = join33(dirname9(DEFAULT_CONFIG_PATH), "state");
20402
21458
  await appendCaptainMessage({
20403
21459
  stateRoot: stateRoot2,
20404
21460
  project,
@@ -20477,7 +21533,7 @@ var cmuxCommand = new Command28("cmux").description("cmux integration helpers").
20477
21533
  // packages/cli/src/commands/effort.ts
20478
21534
  init_dist();
20479
21535
  init_dist2();
20480
- import fs28 from "fs";
21536
+ import fs29 from "fs";
20481
21537
  import path31 from "path";
20482
21538
  import { Command as Command29 } from "commander";
20483
21539
  import chalk30 from "chalk";
@@ -20521,7 +21577,7 @@ function effortScopeLabel(projectName) {
20521
21577
  }
20522
21578
  function canonical(p) {
20523
21579
  try {
20524
- return fs28.realpathSync(p);
21580
+ return fs29.realpathSync(p);
20525
21581
  } catch {
20526
21582
  return path31.resolve(p);
20527
21583
  }
@@ -20583,7 +21639,7 @@ var effortCommand = new Command29("effort").description("Get or set the crew tok
20583
21639
 
20584
21640
  // packages/cli/src/commands/tokens.ts
20585
21641
  init_dist();
20586
- import fs29 from "fs";
21642
+ import fs30 from "fs";
20587
21643
  import path32 from "path";
20588
21644
  import os18 from "os";
20589
21645
  import readline3 from "readline";
@@ -20639,7 +21695,7 @@ function foldTranscriptLine(agg, rawLine, state) {
20639
21695
  async function aggregateTranscriptFile(filePath) {
20640
21696
  const agg = emptySessionAggregate();
20641
21697
  const state = { lastCacheRead: null };
20642
- const rl = readline3.createInterface({ input: fs29.createReadStream(filePath), crlfDelay: Infinity });
21698
+ const rl = readline3.createInterface({ input: fs30.createReadStream(filePath), crlfDelay: Infinity });
20643
21699
  for await (const line of rl) {
20644
21700
  foldTranscriptLine(agg, line, state);
20645
21701
  }
@@ -20707,7 +21763,7 @@ function buildRoleReport(role, sessions) {
20707
21763
  }
20708
21764
  async function readdirSafe(dir) {
20709
21765
  try {
20710
- return await fs29.promises.readdir(dir);
21766
+ return await fs30.promises.readdir(dir);
20711
21767
  } catch {
20712
21768
  return [];
20713
21769
  }
@@ -20874,12 +21930,12 @@ var tokensCommand = new Command30("tokens").description(
20874
21930
  // packages/cli/src/commands/telegram.ts
20875
21931
  init_dist();
20876
21932
  init_dist2();
20877
- import { join as join33, dirname as dirname9 } from "path";
21933
+ import { join as join34, dirname as dirname10 } from "path";
20878
21934
  import { emitKeypressEvents } from "readline";
20879
21935
  import { Command as Command31 } from "commander";
20880
21936
  import chalk32 from "chalk";
20881
21937
  function defaultStateRoot() {
20882
- return join33(dirname9(DEFAULT_CONFIG_PATH), "state");
21938
+ return join34(dirname10(DEFAULT_CONFIG_PATH), "state");
20883
21939
  }
20884
21940
  async function questionMasked() {
20885
21941
  return new Promise((resolve4) => {
@@ -21208,7 +22264,7 @@ import { Command as Command33 } from "commander";
21208
22264
  import { homedir as homedir19 } from "os";
21209
22265
 
21210
22266
  // packages/cli/src/lib/captain-session-registry.ts
21211
- import fs30 from "fs";
22267
+ import fs31 from "fs";
21212
22268
  import path33 from "path";
21213
22269
 
21214
22270
  // packages/cli/src/lib/handoff-facts.ts
@@ -21260,15 +22316,15 @@ function assembleHandoffFacts(live, claudeMem, gapSessions, checkpoint, now, ext
21260
22316
  // packages/cli/src/lib/captain-session-registry.ts
21261
22317
  var CAPTAIN_SESSION_REGISTRY_FILE = "captain-sessions.jsonl";
21262
22318
  function appendCaptainSession(spokeVault, record) {
21263
- fs30.mkdirSync(spokeVault, { recursive: true });
22319
+ fs31.mkdirSync(spokeVault, { recursive: true });
21264
22320
  const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
21265
- fs30.appendFileSync(file, JSON.stringify(record) + "\n");
22321
+ fs31.appendFileSync(file, JSON.stringify(record) + "\n");
21266
22322
  }
21267
22323
  function readCaptainSessionRegistry(spokeVault) {
21268
22324
  const file = path33.join(spokeVault, CAPTAIN_SESSION_REGISTRY_FILE);
21269
- if (!fs30.existsSync(file)) return [];
22325
+ if (!fs31.existsSync(file)) return [];
21270
22326
  const records = [];
21271
- for (const line of fs30.readFileSync(file, "utf-8").split("\n")) {
22327
+ for (const line of fs31.readFileSync(file, "utf-8").split("\n")) {
21272
22328
  if (!line.trim()) continue;
21273
22329
  try {
21274
22330
  records.push(JSON.parse(line));
@@ -21645,7 +22701,7 @@ import os19 from "os";
21645
22701
  // packages/cli/src/lib/handoff-live-repo.ts
21646
22702
  init_dist();
21647
22703
  import { execFileSync as execFileSync12 } from "child_process";
21648
- import fs31 from "fs";
22704
+ import fs32 from "fs";
21649
22705
  import path35 from "path";
21650
22706
 
21651
22707
  // packages/cli/src/lib/handoff-branch-state.ts
@@ -21785,7 +22841,7 @@ function localAheadOfBase(runner, projectPath, base) {
21785
22841
  }
21786
22842
  function readFetchAgeMs(projectPath, now) {
21787
22843
  try {
21788
- const stat2 = fs31.statSync(path35.join(projectPath, ".git", "FETCH_HEAD"));
22844
+ const stat2 = fs32.statSync(path35.join(projectPath, ".git", "FETCH_HEAD"));
21789
22845
  return Math.max(0, now - stat2.mtime.getTime());
21790
22846
  } catch {
21791
22847
  return null;
@@ -21856,7 +22912,7 @@ function gatherLiveRepoState(projectPath, fallbackBaseBranch, tasks, runner = de
21856
22912
 
21857
22913
  // packages/cli/src/lib/handoff-claude-mem.ts
21858
22914
  import { createRequire } from "module";
21859
- import fs32 from "fs";
22915
+ import fs33 from "fs";
21860
22916
  var { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
21861
22917
  var CLAUDE_MEM_RECENCY_LIMIT = 20;
21862
22918
  function decisionText(row) {
@@ -21870,7 +22926,7 @@ function decisionText(row) {
21870
22926
  return row.narrative ?? "";
21871
22927
  }
21872
22928
  function queryClaudeMem(dbPath, project) {
21873
- if (!fs32.existsSync(dbPath)) return null;
22929
+ if (!fs33.existsSync(dbPath)) return null;
21874
22930
  let db;
21875
22931
  try {
21876
22932
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -21913,7 +22969,7 @@ function queryClaudeMem(dbPath, project) {
21913
22969
  }
21914
22970
 
21915
22971
  // packages/cli/src/lib/handoff-transcript.ts
21916
- import fs33 from "fs";
22972
+ import fs34 from "fs";
21917
22973
  var TRANSCRIPT_BYTE_CAP = 2e5;
21918
22974
  var DIGEST_BYTE_CAP = 8 * 1024;
21919
22975
  var PROMPT_TRUNCATE_CHARS = 300;
@@ -22022,27 +23078,27 @@ function extractMessages(tailText) {
22022
23078
  return { lastUserMessage, lastAssistantText, digest };
22023
23079
  }
22024
23080
  function extractTranscriptTail(transcriptPath, byteCap = TRANSCRIPT_BYTE_CAP) {
22025
- if (!fs33.existsSync(transcriptPath)) return null;
22026
- const content = fs33.readFileSync(transcriptPath, "utf-8");
23081
+ if (!fs34.existsSync(transcriptPath)) return null;
23082
+ const content = fs34.readFileSync(transcriptPath, "utf-8");
22027
23083
  const { lastUserMessage, lastAssistantText, digest } = extractMessages(tailOf(content, byteCap));
22028
- const mtimeIso = fs33.statSync(transcriptPath).mtime.toISOString();
23084
+ const mtimeIso = fs34.statSync(transcriptPath).mtime.toISOString();
22029
23085
  return { path: transcriptPath, mtimeIso, lastUserMessage, lastAssistantText, digest };
22030
23086
  }
22031
23087
 
22032
23088
  // packages/cli/src/lib/handoff-archive.ts
22033
- import fs34 from "fs";
23089
+ import fs35 from "fs";
22034
23090
  import path36 from "path";
22035
23091
  function readNewestArchivedHandoff(spokeVault, now) {
22036
23092
  const dir = path36.join(spokeVault, "handoffs");
22037
- if (!fs34.existsSync(dir)) return null;
22038
- const candidates = fs34.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
23093
+ if (!fs35.existsSync(dir)) return null;
23094
+ const candidates = fs35.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => {
22039
23095
  const full = path36.join(dir, e.name);
22040
- return { name: e.name, full, mtime: fs34.statSync(full).mtime };
23096
+ return { name: e.name, full, mtime: fs35.statSync(full).mtime };
22041
23097
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
22042
23098
  for (const candidate of candidates) {
22043
23099
  let content;
22044
23100
  try {
22045
- content = JSON.parse(fs34.readFileSync(candidate.full, "utf-8"));
23101
+ content = JSON.parse(fs35.readFileSync(candidate.full, "utf-8"));
22046
23102
  } catch {
22047
23103
  continue;
22048
23104
  }
@@ -22385,15 +23441,15 @@ init_dist();
22385
23441
  init_dist();
22386
23442
  init_dist();
22387
23443
  init_dist();
22388
- var __dirname = dirname10(fileURLToPath6(import.meta.url));
22389
- var pkg = JSON.parse(readFileSync21(join34(__dirname, "..", "package.json"), "utf-8"));
23444
+ var __dirname = dirname11(fileURLToPath6(import.meta.url));
23445
+ var pkg = JSON.parse(readFileSync21(join35(__dirname, "..", "package.json"), "utf-8"));
22390
23446
  ensureRuntimeSynced({
22391
- sourceRoot: join34(__dirname, ".."),
22392
- runtimeRoot: join34(homedir20(), ".config", "squadrant")
23447
+ sourceRoot: join35(__dirname, ".."),
23448
+ runtimeRoot: join35(homedir20(), ".config", "squadrant")
22393
23449
  });
22394
23450
  if (!process.env.VITEST) {
22395
23451
  try {
22396
- syncShippedOpencodeSkills({ pkgRoot: join34(__dirname, "..") });
23452
+ syncShippedOpencodeSkills({ pkgRoot: join35(__dirname, "..") });
22397
23453
  } catch (err) {
22398
23454
  process.stderr.write(
22399
23455
  `squadrant: opencode skills sync skipped: ${err.message}
@@ -22403,7 +23459,7 @@ if (!process.env.VITEST) {
22403
23459
  }
22404
23460
  if (process.argv[2] !== "config") {
22405
23461
  try {
22406
- const cfgPath = join34(homedir20(), ".config", "squadrant", "config.json");
23462
+ const cfgPath = join35(homedir20(), ".config", "squadrant", "config.json");
22407
23463
  if (existsSync14(cfgPath)) {
22408
23464
  const cfg = JSON.parse(readConfigFileSync(cfgPath));
22409
23465
  if (needsCheck(cfg, pkg.version)) {