squadrant 0.16.5 → 0.17.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.
@@ -60,10 +60,10 @@ var init_snapshot = __esm({
60
60
  });
61
61
 
62
62
  // packages/cli/src/squadrantd.ts
63
- import { join as join18, dirname as dirname5 } from "path";
64
- import { homedir as homedir13 } from "os";
63
+ import { join as join19, dirname as dirname5 } from "path";
64
+ import { homedir as homedir14 } from "os";
65
65
  import { fileURLToPath as fileURLToPath3 } from "url";
66
- import { readFileSync as readFileSync13, statSync as statSync3 } from "fs";
66
+ import { readFileSync as readFileSync14, statSync as statSync4 } from "fs";
67
67
 
68
68
  // packages/shared/dist/config.js
69
69
  import fs from "fs";
@@ -500,6 +500,11 @@ function nextPendingTool(current, ev, now) {
500
500
  return void 0;
501
501
  return current;
502
502
  }
503
+ function nextPendingMonitor(current, ev, now) {
504
+ if (ev.note === "agent.hook.PreToolUse" && ev.tool === "Monitor")
505
+ return { since: now };
506
+ return current;
507
+ }
503
508
  function reduce(rec, ev, now) {
504
509
  if (ev.type === "task.reopened") {
505
510
  return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
@@ -516,14 +521,17 @@ function reduce(rec, ev, now) {
516
521
  sessionId: ev.sessionId ?? rec.sessionId,
517
522
  question: void 0,
518
523
  // resuming after a blocked→reply clears the question
519
- pendingTool: void 0
524
+ pendingTool: void 0,
520
525
  // #354: a new turn closes any prior tool window
526
+ pendingMonitor: void 0
527
+ // #594a: same reset — a new turn moots any prior watch
521
528
  };
522
529
  case "task.progress": {
523
530
  const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
531
+ const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);
524
532
  if (isStickyAttention(rec.state))
525
- return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };
526
- const b = { ...base, pendingTool };
533
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };
534
+ const b = { ...base, pendingTool, pendingMonitor };
527
535
  if (rec.state === "awaiting-input" || rec.state === "stalled")
528
536
  return { ...stampAttempt(b, {}, now), state: "working" };
529
537
  return stampAttempt(b, {}, now);
@@ -537,9 +545,9 @@ function reduce(rec, ev, now) {
537
545
  case "task.blocked":
538
546
  if (rec.state === "blocked")
539
547
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
540
- return { ...base, state: "blocked", question: ev.question, pendingTool: void 0 };
548
+ return { ...base, state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
541
549
  case "task.review":
542
- return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0 };
550
+ return { ...base, state: "review", reviewNote: ev.message, pendingTool: void 0, pendingMonitor: void 0 };
543
551
  case "task.done":
544
552
  if (rec.state === "review" && ev.source !== "approve") {
545
553
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
@@ -554,19 +562,19 @@ function reduce(rec, ev, now) {
554
562
  case "task.session":
555
563
  return stampAttempt(base, { resumeRef: ev.resumeRef }, now);
556
564
  case "task.turn.started":
557
- return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0 };
565
+ return { ...stampAttempt(base, {}, now), state: "working", pendingTool: void 0, pendingMonitor: void 0 };
558
566
  case "task.turn.completed":
559
567
  if (isStickyAttention(rec.state))
560
568
  return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
561
- if (rec.pendingTool)
569
+ if (rec.pendingTool || rec.pendingMonitor)
562
570
  return stampAttempt(base, {}, now);
563
- return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0 };
571
+ return { ...stampAttempt(base, {}, now), state: "awaiting-input", pendingTool: void 0, pendingMonitor: void 0 };
564
572
  case "task.delta":
565
573
  return stampAttempt(base, {}, now);
566
574
  // heartbeat-only
567
575
  case "task.input.requested":
568
576
  case "task.approval.requested":
569
- return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0 };
577
+ return { ...stampAttempt(base, {}, now), state: "blocked", question: ev.question, pendingTool: void 0, pendingMonitor: void 0 };
570
578
  case "task.reattached":
571
579
  return stampAttempt(base, {}, now);
572
580
  case "task.first-turn.confirmed":
@@ -587,15 +595,22 @@ function reduce(rec, ev, now) {
587
595
 
588
596
  // packages/core/dist/watchdog.js
589
597
  var TOOL_STALL_BUDGET_MS = 10 * 60 * 1e3;
590
- function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
598
+ var MONITOR_STALL_BUDGET_MS = 60 * 60 * 1e3;
599
+ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS, monitorStallMs = MONITOR_STALL_BUDGET_MS) {
591
600
  if (rec.state !== "working")
592
601
  return null;
593
602
  if (rec.mode === "interactive") {
594
- if (!rec.pendingTool)
595
- return null;
596
- if (now - rec.pendingTool.since <= toolStallMs)
597
- return null;
598
- return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
603
+ if (rec.pendingTool) {
604
+ if (now - rec.pendingTool.since <= toolStallMs)
605
+ return null;
606
+ return { ...rec, state: "stalled", lastEvent: "watchdog.tool-stall" };
607
+ }
608
+ if (rec.pendingMonitor) {
609
+ if (now - rec.pendingMonitor.since <= monitorStallMs)
610
+ return null;
611
+ return { ...rec, state: "stalled", lastEvent: "watchdog.monitor-stall" };
612
+ }
613
+ return null;
599
614
  }
600
615
  const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;
601
616
  if (now - liveness <= rec.heartbeatBudgetMs)
@@ -605,7 +620,7 @@ function evaluateStall(rec, now, toolStallMs = TOOL_STALL_BUDGET_MS) {
605
620
  function recoverStall(rec, now) {
606
621
  if (rec.state !== "stalled")
607
622
  return null;
608
- return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0 };
623
+ return { ...rec, state: "working", lastHeartbeat: now, lastEvent: "watchdog.recover", pendingTool: void 0, pendingMonitor: void 0 };
609
624
  }
610
625
 
611
626
  // packages/core/dist/daemon/reduce.js
@@ -912,7 +927,7 @@ function createDaemon(deps) {
912
927
  store.delete(r.project, r.id);
913
928
  continue;
914
929
  }
915
- if (!TERMINAL_STATES.has(r.state)) {
930
+ if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
916
931
  const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
917
932
  if (t - r.createdAt > ceiling) {
918
933
  const prevState = r.state;
@@ -957,7 +972,7 @@ function createDaemon(deps) {
957
972
  const idle = evaluateStall(r, t);
958
973
  if (idle) {
959
974
  store.put(idle);
960
- const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
975
+ const synthEvent = idle.pendingTool ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since } : idle.pendingMonitor ? { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: "Monitor", elapsedMs: t - idle.pendingMonitor.since } : { type: "task.stalled", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };
961
976
  firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));
962
977
  continue;
963
978
  }
@@ -1394,9 +1409,9 @@ function startServer(sockPath, handlerOrCallbacks, onListenError = defaultListen
1394
1409
  return server;
1395
1410
  }
1396
1411
  function isDaemonSocketLive(sockPath, timeoutMs = 500) {
1397
- return new Promise((resolve2) => {
1412
+ return new Promise((resolve3) => {
1398
1413
  if (!existsSync5(sockPath)) {
1399
- resolve2(false);
1414
+ resolve3(false);
1400
1415
  return;
1401
1416
  }
1402
1417
  const conn = createConnection(sockPath);
@@ -1405,7 +1420,7 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
1405
1420
  conn.destroy();
1406
1421
  } catch {
1407
1422
  }
1408
- resolve2(v);
1423
+ resolve3(v);
1409
1424
  };
1410
1425
  const timer = setTimeout(() => finish(false), timeoutMs);
1411
1426
  conn.on("connect", () => {
@@ -1419,7 +1434,7 @@ function isDaemonSocketLive(sockPath, timeoutMs = 500) {
1419
1434
  });
1420
1435
  }
1421
1436
  function sendRequest(sockPath, msg, timeoutMs = 5e3) {
1422
- return new Promise((resolve2, reject) => {
1437
+ return new Promise((resolve3, reject) => {
1423
1438
  const conn = createConnection(sockPath);
1424
1439
  const dec = createDecoder();
1425
1440
  const timer = setTimeout(() => {
@@ -1435,7 +1450,7 @@ function sendRequest(sockPath, msg, timeoutMs = 5e3) {
1435
1450
  if (m._v !== void 0 && m._v !== PROTOCOL_VERSION) {
1436
1451
  reject(new Error(`squadrantd protocol v${m._v}, this client expects v${PROTOCOL_VERSION} \u2014 upgrade squadrantd or this CLI`));
1437
1452
  } else if (m.ok) {
1438
- resolve2(m.reply);
1453
+ resolve3(m.reply);
1439
1454
  } else {
1440
1455
  reject(new Error(m.error));
1441
1456
  }
@@ -1611,14 +1626,21 @@ function createStore(root) {
1611
1626
  };
1612
1627
  }
1613
1628
 
1629
+ // packages/core/dist/work-store.js
1630
+ import { homedir as homedir4 } from "os";
1631
+ import { join as join7, resolve as resolve2, sep as sep2 } from "path";
1632
+ import { randomBytes } from "crypto";
1633
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync5, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
1634
+ var WORK_ITEM_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
1635
+
1614
1636
  // packages/core/dist/index.js
1615
1637
  init_snapshot();
1616
1638
 
1617
1639
  // packages/core/dist/launchd.js
1618
1640
  import { execFileSync as execFileSync3 } from "child_process";
1619
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync7, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
1620
- import { homedir as homedir4 } from "os";
1621
- import { dirname as dirname3, join as join7 } from "path";
1641
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, readFileSync as readFileSync7, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
1642
+ import { homedir as homedir5 } from "os";
1643
+ import { dirname as dirname3, join as join8 } from "path";
1622
1644
  import { fileURLToPath } from "url";
1623
1645
 
1624
1646
  // packages/core/dist/crew-pane-reader.js
@@ -1686,13 +1708,13 @@ function makeGate(opts) {
1686
1708
  }
1687
1709
 
1688
1710
  // packages/core/dist/daemon/context.js
1689
- import { homedir as homedir5 } from "os";
1690
- import { join as join8 } from "path";
1711
+ import { homedir as homedir6 } from "os";
1712
+ import { join as join9 } from "path";
1691
1713
  import { spawn as realSpawn } from "child_process";
1692
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
1714
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
1693
1715
 
1694
1716
  // packages/core/dist/daemon/liveness-registry.js
1695
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync2 } from "fs";
1717
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, renameSync as renameSync3 } from "fs";
1696
1718
  var LivenessRegistry = class {
1697
1719
  path;
1698
1720
  readFile;
@@ -1702,14 +1724,14 @@ var LivenessRegistry = class {
1702
1724
  this.path = opts.path;
1703
1725
  this.readFile = opts.readFile ?? ((p) => {
1704
1726
  try {
1705
- return readFileSync7(p, "utf-8");
1727
+ return readFileSync8(p, "utf-8");
1706
1728
  } catch {
1707
1729
  return void 0;
1708
1730
  }
1709
1731
  });
1710
1732
  this.writeFile = opts.writeFile ?? ((p, c) => {
1711
- writeFileSync6(`${p}.tmp`, c);
1712
- renameSync2(`${p}.tmp`, p);
1733
+ writeFileSync7(`${p}.tmp`, c);
1734
+ renameSync3(`${p}.tmp`, p);
1713
1735
  });
1714
1736
  }
1715
1737
  load() {
@@ -1765,18 +1787,18 @@ function defaultIsPidAlive(pid) {
1765
1787
  }
1766
1788
  }
1767
1789
  function buildContext(opts) {
1768
- const stateRoot = opts.stateRoot ?? join8(homedir5(), ".config", "squadrant", "state");
1769
- const sockPath = opts.sockPath ?? join8(homedir5(), ".config", "squadrant", "squadrant.sock");
1790
+ const stateRoot = opts.stateRoot ?? join9(homedir6(), ".config", "squadrant", "state");
1791
+ const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
1770
1792
  const store = createStore(stateRoot);
1771
1793
  const bootedAt = Date.now();
1772
1794
  const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;
1773
1795
  const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
1774
1796
  const spawn2 = opts.spawn ?? realSpawn;
1775
- const resultsDir = join8(stateRoot, "_results");
1776
- mkdirSync5(resultsDir, { recursive: true });
1797
+ const resultsDir = join9(stateRoot, "_results");
1798
+ mkdirSync6(resultsDir, { recursive: true });
1777
1799
  const writeResult = (id, payload) => {
1778
- const p = join8(resultsDir, `${id}.txt`);
1779
- writeFileSync7(p, payload);
1800
+ const p = join9(resultsDir, `${id}.txt`);
1801
+ writeFileSync8(p, payload);
1780
1802
  return p;
1781
1803
  };
1782
1804
  const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
@@ -1798,7 +1820,7 @@ function buildContext(opts) {
1798
1820
  inFlightHeadlessIds: /* @__PURE__ */ new Set(),
1799
1821
  activeHeadlessKills: /* @__PURE__ */ new Set(),
1800
1822
  livenessRegistry: (() => {
1801
- const r = new LivenessRegistry({ path: join8(stateRoot, "liveness.json") });
1823
+ const r = new LivenessRegistry({ path: join9(stateRoot, "liveness.json") });
1802
1824
  r.load();
1803
1825
  return r;
1804
1826
  })(),
@@ -1876,7 +1898,7 @@ function createAttach(ctx) {
1876
1898
  }
1877
1899
 
1878
1900
  // packages/core/dist/daemon/start.js
1879
- import { join as join10, dirname as dirname4 } from "path";
1901
+ import { join as join11, dirname as dirname4 } from "path";
1880
1902
  import { readdir } from "fs/promises";
1881
1903
 
1882
1904
  // packages/core/dist/daemon/interactive-probe.js
@@ -2087,9 +2109,11 @@ function buildSurfaceProbe(ctx, probes, daemonCmux) {
2087
2109
  // packages/core/dist/delivery/defer-delivery.js
2088
2110
  var DeferDelivery = class extends Error {
2089
2111
  draft;
2090
- constructor(draft = null) {
2112
+ reason;
2113
+ constructor(draft = null, reason = "draft") {
2091
2114
  super("deferred: captain composing");
2092
2115
  this.draft = draft;
2116
+ this.reason = reason;
2093
2117
  this.name = "DeferDelivery";
2094
2118
  }
2095
2119
  };
@@ -2107,6 +2131,7 @@ var CaptainDelivery = class {
2107
2131
  deferCounts = /* @__PURE__ */ new Map();
2108
2132
  lastContent = /* @__PURE__ */ new Map();
2109
2133
  stableCounts = /* @__PURE__ */ new Map();
2134
+ lastReason = /* @__PURE__ */ new Map();
2110
2135
  constructor(opts) {
2111
2136
  this.opts = opts;
2112
2137
  }
@@ -2129,29 +2154,40 @@ var CaptainDelivery = class {
2129
2154
  this.deferCounts.delete(seq);
2130
2155
  this.stableCounts.delete(seq);
2131
2156
  this.lastContent.delete(seq);
2157
+ this.lastReason.delete(seq);
2132
2158
  return { delivered: true };
2133
2159
  } catch (e) {
2134
2160
  if (e instanceof DeferDelivery) {
2135
2161
  this.deferCounts.set(seq, deferCount + 1);
2136
2162
  const content = e.draft;
2163
+ let stableCount;
2137
2164
  if (content && content === this.lastContent.get(seq)) {
2138
- this.stableCounts.set(seq, (this.stableCounts.get(seq) ?? 0) + 1);
2165
+ stableCount = (this.stableCounts.get(seq) ?? 0) + 1;
2166
+ this.stableCounts.set(seq, stableCount);
2139
2167
  } else {
2168
+ stableCount = 0;
2140
2169
  this.stableCounts.set(seq, 0);
2141
2170
  }
2142
2171
  this.lastContent.set(seq, content);
2143
- return { deferred: true };
2172
+ const reason = e.reason !== "draft" ? e.reason : stableCount >= this.opts.stableProbePolls ? "stable" : "draft";
2173
+ this.lastReason.set(seq, reason);
2174
+ return { deferred: true, reason };
2144
2175
  }
2145
- return { deferred: true };
2176
+ this.lastReason.set(seq, "unknown");
2177
+ return { deferred: true, reason: "unknown" };
2146
2178
  }
2147
2179
  }
2148
2180
  /** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */
2149
2181
  stats() {
2150
2182
  let maxDeferCount = 0;
2151
- for (const c of this.deferCounts.values())
2152
- if (c > maxDeferCount)
2183
+ let reason;
2184
+ for (const [seq, c] of this.deferCounts) {
2185
+ if (c > maxDeferCount) {
2153
2186
  maxDeferCount = c;
2154
- return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers };
2187
+ reason = this.lastReason.get(seq);
2188
+ }
2189
+ }
2190
+ return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers, reason };
2155
2191
  }
2156
2192
  };
2157
2193
 
@@ -2251,6 +2287,10 @@ function createDelivery(ctx, daemonCmux) {
2251
2287
  const notifyFault = ctx.notifyFault ?? (() => {
2252
2288
  });
2253
2289
  const defaultNotify = async (args) => {
2290
+ const fresh = store.get(args.project, args.record.id);
2291
+ if (fresh && TERMINAL_STATES.has(fresh.state) && fresh.state !== args.record.state) {
2292
+ return;
2293
+ }
2254
2294
  try {
2255
2295
  await appendToMailbox({
2256
2296
  stateRoot,
@@ -2340,16 +2380,19 @@ function createDelivery(ctx, daemonCmux) {
2340
2380
  log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);
2341
2381
  await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });
2342
2382
  } else {
2343
- log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred`);
2383
+ const { maxDeferCount } = d.stats();
2384
+ if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {
2385
+ log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);
2386
+ }
2344
2387
  break;
2345
2388
  }
2346
2389
  }
2347
2390
  const stuck = d.stats().stuck;
2348
2391
  if (stuck && !stuckNotified.has(project)) {
2349
2392
  stuckNotified.add(project);
2350
- const { maxDeferCount } = d.stats();
2351
- log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);
2352
- const text = `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
2393
+ const { maxDeferCount, reason } = d.stats();
2394
+ log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? "unknown"}`);
2395
+ const text = reason === "modal" ? `\u26A0\uFE0F DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.` : `\u26A0\uFE0F DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched \u2014 this keeps retrying safely and will deliver automatically once you submit or clear it.`;
2353
2396
  appendCaptainMessage({ stateRoot, project, text, source: "daemon" }).catch((e) => log(`delivery stuck alert failed project=${project}: ${e.message}`));
2354
2397
  Promise.resolve(notifyFault(project, text)).catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${e.message}`));
2355
2398
  telegramBridge?.pushRaw(project, text);
@@ -2448,12 +2491,12 @@ function createServer2(ctx, handlers) {
2448
2491
 
2449
2492
  // packages/core/dist/daemon/snapshot-gather.js
2450
2493
  import { fileURLToPath as fileURLToPath2 } from "url";
2451
- import { join as join9 } from "path";
2452
- import { statSync as statSync2, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
2494
+ import { join as join10 } from "path";
2495
+ import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
2453
2496
  var SELF_PATH = fileURLToPath2(import.meta.url);
2454
2497
  function distBuiltAt() {
2455
2498
  try {
2456
- return statSync2(SELF_PATH).mtimeMs;
2499
+ return statSync3(SELF_PATH).mtimeMs;
2457
2500
  } catch {
2458
2501
  return 0;
2459
2502
  }
@@ -2461,7 +2504,7 @@ function distBuiltAt() {
2461
2504
  function gatherLogStats(path19, now, windowMs) {
2462
2505
  let sizeBytes = 0;
2463
2506
  try {
2464
- sizeBytes = statSync2(path19).size;
2507
+ sizeBytes = statSync3(path19).size;
2465
2508
  } catch {
2466
2509
  return { errorCount: 0, sizeBytes: 0, windowMs };
2467
2510
  }
@@ -2503,9 +2546,9 @@ function gatherStoreStats(store, stateRoot, project) {
2503
2546
  for (const r of store.list(project))
2504
2547
  byState[r.state] = (byState[r.state] ?? 0) + 1;
2505
2548
  let corruptCount = 0;
2506
- const dir = join9(stateRoot, project);
2549
+ const dir = join10(stateRoot, project);
2507
2550
  try {
2508
- for (const n of readdirSync2(dir)) {
2551
+ for (const n of readdirSync3(dir)) {
2509
2552
  if (n.includes(".corrupt.")) {
2510
2553
  corruptCount++;
2511
2554
  continue;
@@ -2513,7 +2556,7 @@ function gatherStoreStats(store, stateRoot, project) {
2513
2556
  if (!n.endsWith(".json"))
2514
2557
  continue;
2515
2558
  try {
2516
- JSON.parse(readFileSync8(join9(dir, n), "utf-8"));
2559
+ JSON.parse(readFileSync9(join10(dir, n), "utf-8"));
2517
2560
  } catch {
2518
2561
  corruptCount++;
2519
2562
  }
@@ -2526,9 +2569,9 @@ function gatherResults(resultsDir) {
2526
2569
  let fileCount = 0;
2527
2570
  let totalBytes = 0;
2528
2571
  try {
2529
- for (const n of readdirSync2(resultsDir)) {
2572
+ for (const n of readdirSync3(resultsDir)) {
2530
2573
  try {
2531
- const s = statSync2(join9(resultsDir, n));
2574
+ const s = statSync3(join10(resultsDir, n));
2532
2575
  if (s.isFile()) {
2533
2576
  fileCount++;
2534
2577
  totalBytes += s.size;
@@ -2617,7 +2660,7 @@ function startDaemon(ctx, opts, pkgVersion) {
2617
2660
  return out;
2618
2661
  }
2619
2662
  async function gatherSnapshotInputs(now) {
2620
- const logPath2 = join10(dirname4(stateRoot), "squadrantd.log");
2663
+ const logPath2 = join11(dirname4(stateRoot), "squadrantd.log");
2621
2664
  const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
2622
2665
  const projects = await Promise.all(tier2Projects.map(async (project) => {
2623
2666
  const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
@@ -2751,7 +2794,7 @@ function startDaemon(ctx, opts, pkgVersion) {
2751
2794
  };
2752
2795
  let rotationTimer;
2753
2796
  if (rotationInterval > 0) {
2754
- const inboxPath = join10(stateRoot, "inbox");
2797
+ const inboxPath = join11(stateRoot, "inbox");
2755
2798
  rotationTimer = setInterval(async () => {
2756
2799
  try {
2757
2800
  let entries;
@@ -2794,9 +2837,9 @@ function startDaemon(ctx, opts, pkgVersion) {
2794
2837
  }
2795
2838
  for (const kill of ctx.activeHeadlessKills)
2796
2839
  kill();
2797
- return new Promise((resolve2) => server.close(() => {
2840
+ return new Promise((resolve3) => server.close(() => {
2798
2841
  log(`exit-complete pid=${process.pid}`);
2799
- resolve2();
2842
+ resolve3();
2800
2843
  }));
2801
2844
  },
2802
2845
  tickDelivery: deliveryTick,
@@ -2985,7 +3028,7 @@ function createIsCaptainAlive(sock) {
2985
3028
  };
2986
3029
  }
2987
3030
  function createLaunch(cliBin, log) {
2988
- return (project) => new Promise((resolve2, reject) => {
3031
+ return (project) => new Promise((resolve3, reject) => {
2989
3032
  execFile(process.execPath, [cliBin, "launch", project, "--headless"], { timeout: 3e4 }, (err, stdout, stderr) => {
2990
3033
  const output = capOutput(stdout ?? "", stderr ?? "");
2991
3034
  if (err) {
@@ -2995,7 +3038,7 @@ function createLaunch(cliBin, log) {
2995
3038
  }
2996
3039
  if (output !== "(no output)")
2997
3040
  log?.(`launch ${project}: ${output}`);
2998
- resolve2();
3041
+ resolve3();
2999
3042
  });
3000
3043
  });
3001
3044
  }
@@ -3117,10 +3160,10 @@ function findProjectByThread(stateRoot, threadId) {
3117
3160
  for (const [key, id] of Object.entries(s.topics)) {
3118
3161
  if (id !== threadId)
3119
3162
  continue;
3120
- const sep2 = key.indexOf("::");
3121
- if (sep2 === -1)
3163
+ const sep3 = key.indexOf("::");
3164
+ if (sep3 === -1)
3122
3165
  continue;
3123
- return { project: key.slice(0, sep2), scope: key.slice(sep2 + 2) };
3166
+ return { project: key.slice(0, sep3), scope: key.slice(sep3 + 2) };
3124
3167
  }
3125
3168
  return null;
3126
3169
  }
@@ -3646,16 +3689,16 @@ import fs12 from "fs";
3646
3689
 
3647
3690
  // packages/core/dist/restart-daemon.js
3648
3691
  import { execFileSync as execFileSync4 } from "child_process";
3649
- import { existsSync as existsSync8 } from "fs";
3650
- import { homedir as homedir6 } from "os";
3651
- import { join as join11 } from "path";
3652
- var DEFAULT_SOCK_PATH = join11(homedir6(), ".config", "squadrant", "squadrant.sock");
3692
+ import { existsSync as existsSync9 } from "fs";
3693
+ import { homedir as homedir7 } from "os";
3694
+ import { join as join12 } from "path";
3695
+ var DEFAULT_SOCK_PATH = join12(homedir7(), ".config", "squadrant", "squadrant.sock");
3653
3696
 
3654
3697
  // packages/core/dist/group-dispatch.js
3655
3698
  import { randomUUID as randomUUID3 } from "crypto";
3656
- import { homedir as homedir7 } from "os";
3657
- import { join as join12 } from "path";
3658
- var DEFAULT_SOCK_PATH2 = join12(homedir7(), ".config", "squadrant", "squadrant.sock");
3699
+ import { homedir as homedir8 } from "os";
3700
+ import { join as join13 } from "path";
3701
+ var DEFAULT_SOCK_PATH2 = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
3659
3702
 
3660
3703
  // packages/core/dist/side-session.js
3661
3704
  import fs13 from "fs";
@@ -3772,8 +3815,8 @@ var AppServerClient = class extends EventEmitter {
3772
3815
  const info = this.opts.clientInfo ?? { name: "squadrant", version: "0" };
3773
3816
  const id = this.nextId++;
3774
3817
  const env = { jsonrpc: "2.0", id, method: "initialize", params: { clientInfo: info } };
3775
- const res = await new Promise((resolve2, reject) => {
3776
- this.pending.set(id, { resolve: resolve2, reject });
3818
+ const res = await new Promise((resolve3, reject) => {
3819
+ this.pending.set(id, { resolve: resolve3, reject });
3777
3820
  this.proc.stdin.write(JSON.stringify(env) + "\n");
3778
3821
  });
3779
3822
  this.proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "initialized" }) + "\n");
@@ -3806,13 +3849,13 @@ var AppServerClient = class extends EventEmitter {
3806
3849
  const turnId = ack?.turn?.id;
3807
3850
  if (typeof turnId !== "string")
3808
3851
  throw new Error(`turn/start: unexpected ack shape (no turn.id): ${JSON.stringify(ack).slice(0, 200)}`);
3809
- return new Promise((resolve2, reject) => {
3852
+ return new Promise((resolve3, reject) => {
3810
3853
  const onNote = (n) => {
3811
3854
  if (n.params?.turn?.id !== turnId)
3812
3855
  return;
3813
3856
  if (n.method === "turn/completed") {
3814
3857
  cleanup();
3815
- resolve2({ turnId });
3858
+ resolve3({ turnId });
3816
3859
  }
3817
3860
  if (n.method === "turn/failed") {
3818
3861
  cleanup();
@@ -3863,8 +3906,8 @@ var AppServerClient = class extends EventEmitter {
3863
3906
  throw new Error("AppServerClient not started");
3864
3907
  const id = this.nextId++;
3865
3908
  const env = { jsonrpc: "2.0", id, method, params: params ?? {} };
3866
- return new Promise((resolve2, reject) => {
3867
- this.pending.set(id, { resolve: resolve2, reject });
3909
+ return new Promise((resolve3, reject) => {
3910
+ this.pending.set(id, { resolve: resolve3, reject });
3868
3911
  this.proc.stdin.write(JSON.stringify(env) + "\n");
3869
3912
  });
3870
3913
  }
@@ -3992,11 +4035,11 @@ function toSnapshot(ev) {
3992
4035
 
3993
4036
  // packages/agents/dist/codex/config.js
3994
4037
  import { readFile as readFile5 } from "fs/promises";
3995
- import { homedir as homedir8 } from "os";
3996
- import { join as join13 } from "path";
4038
+ import { homedir as homedir9 } from "os";
4039
+ import { join as join14 } from "path";
3997
4040
  async function resolveCodexModel() {
3998
- const home = process.env["CODEX_HOME"] ?? join13(homedir8(), ".codex");
3999
- const configPath = join13(home, "config.toml");
4041
+ const home = process.env["CODEX_HOME"] ?? join14(homedir9(), ".codex");
4042
+ const configPath = join14(home, "config.toml");
4000
4043
  let text;
4001
4044
  try {
4002
4045
  text = await readFile5(configPath, "utf8");
@@ -4309,11 +4352,11 @@ function buildCodexDeveloperInstructions(rec) {
4309
4352
  ${directive}` : directive;
4310
4353
  }
4311
4354
  function withTimeout(p, ms, msg) {
4312
- return new Promise((resolve2, reject) => {
4355
+ return new Promise((resolve3, reject) => {
4313
4356
  const t = setTimeout(() => reject(new Error(msg)), ms);
4314
4357
  p.then((v) => {
4315
4358
  clearTimeout(t);
4316
- resolve2(v);
4359
+ resolve3(v);
4317
4360
  }, (e) => {
4318
4361
  clearTimeout(t);
4319
4362
  reject(e);
@@ -4485,9 +4528,9 @@ var OpencodeSseBridge = class {
4485
4528
 
4486
4529
  // packages/agents/dist/interactive/claude.js
4487
4530
  import { execSync as execSync6 } from "child_process";
4488
- import { readFileSync as readFileSync9 } from "fs";
4489
- import { homedir as homedir9 } from "os";
4490
- import { join as join14 } from "path";
4531
+ import { readFileSync as readFileSync10 } from "fs";
4532
+ import { homedir as homedir10 } from "os";
4533
+ import { join as join15 } from "path";
4491
4534
  var nextAskUserQuestionRequestId = Date.now();
4492
4535
 
4493
4536
  // packages/agents/dist/headless/types.js
@@ -4620,14 +4663,14 @@ function runHeadless(opts) {
4620
4663
  if (err.length > ERR_CAP)
4621
4664
  err = err.slice(err.length - ERR_CAP);
4622
4665
  });
4623
- const result = new Promise((resolve2) => {
4666
+ const result = new Promise((resolve3) => {
4624
4667
  child.once("error", (e) => {
4625
4668
  if (debounceTimer) {
4626
4669
  clearTimeout(debounceTimer);
4627
4670
  debounceTimer = null;
4628
4671
  }
4629
4672
  opts.emit({ type: "task.failed", id: opts.id, error: `spawn error: ${e.message}`, exitCode: void 0 });
4630
- resolve2();
4673
+ resolve3();
4631
4674
  });
4632
4675
  child.on("close", (code) => {
4633
4676
  if (chunksSinceProgress > 0)
@@ -4644,7 +4687,7 @@ function runHeadless(opts) {
4644
4687
  const ref = opts.writeResult ? opts.writeResult(opts.id, res.payload ?? "") : "";
4645
4688
  opts.emit({ type: "task.done", id: opts.id, resultRef: ref, parseWarning: res.parseWarning });
4646
4689
  }
4647
- resolve2();
4690
+ resolve3();
4648
4691
  });
4649
4692
  });
4650
4693
  return { result, kill: () => child.kill("SIGTERM") };
@@ -4660,7 +4703,7 @@ var CmuxTimeoutError = class extends Error {
4660
4703
  }
4661
4704
  };
4662
4705
  function cmux(args) {
4663
- return new Promise((resolve2, reject) => {
4706
+ return new Promise((resolve3, reject) => {
4664
4707
  execFile2(
4665
4708
  resolveCmuxBin(),
4666
4709
  args,
@@ -4674,19 +4717,19 @@ function cmux(args) {
4674
4717
  reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
4675
4718
  return;
4676
4719
  }
4677
- resolve2(stdout.trim());
4720
+ resolve3(stdout.trim());
4678
4721
  }
4679
4722
  );
4680
4723
  });
4681
4724
  }
4682
4725
  function cmuxStdin(args, input) {
4683
- return new Promise((resolve2, reject) => {
4726
+ return new Promise((resolve3, reject) => {
4684
4727
  const child = execFile2(resolveCmuxBin(), args, { encoding: "utf-8", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: "1" } }, (err, stdout) => {
4685
4728
  if (err) {
4686
4729
  reject(err.code === "ETIMEDOUT" ? new CmuxTimeoutError(args.join(" ")) : err);
4687
4730
  return;
4688
4731
  }
4689
- resolve2(stdout.trim());
4732
+ resolve3(stdout.trim());
4690
4733
  });
4691
4734
  child.stdin.end(input);
4692
4735
  });
@@ -5049,9 +5092,9 @@ function createCmuxDriver() {
5049
5092
  }
5050
5093
  const draft = parseDraftFromScreen(screen);
5051
5094
  if (draft === null)
5052
- throw new DeferDelivery(null);
5095
+ throw new DeferDelivery(null, "no-box");
5053
5096
  if (hasModalOptionList(screen))
5054
- throw new DeferDelivery(null);
5097
+ throw new DeferDelivery(null, "modal");
5055
5098
  if (draft === "") {
5056
5099
  await deliver();
5057
5100
  return;
@@ -5245,7 +5288,7 @@ var NotifierRegistry = class {
5245
5288
 
5246
5289
  // packages/workspaces/dist/workspaces/obsidian.js
5247
5290
  import fs16 from "fs/promises";
5248
- import { existsSync as existsSync9 } from "fs";
5291
+ import { existsSync as existsSync10 } from "fs";
5249
5292
  import path17 from "path";
5250
5293
 
5251
5294
  // packages/workspaces/dist/cmux-daemon/events-bridge.js
@@ -5314,12 +5357,12 @@ var CmuxEventsBridge = class {
5314
5357
  continue;
5315
5358
  }
5316
5359
  this.child = child;
5317
- await new Promise((resolve2) => {
5360
+ await new Promise((resolve3) => {
5318
5361
  let settled = false;
5319
5362
  const done = () => {
5320
5363
  if (!settled) {
5321
5364
  settled = true;
5322
- resolve2();
5365
+ resolve3();
5323
5366
  }
5324
5367
  };
5325
5368
  child.stdout?.on("data", (b) => this.onData(b));
@@ -5383,9 +5426,9 @@ var CmuxEventsBridge = class {
5383
5426
  };
5384
5427
 
5385
5428
  // packages/workspaces/dist/cmux-daemon/daemon-cmux.js
5386
- import { readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
5387
- import { join as join15 } from "path";
5388
- import { homedir as homedir10 } from "os";
5429
+ import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
5430
+ import { join as join16 } from "path";
5431
+ import { homedir as homedir11 } from "os";
5389
5432
 
5390
5433
  // packages/workspaces/dist/cmux-daemon/store-fingerprint.js
5391
5434
  function roleFromTemplate(args) {
@@ -5504,22 +5547,22 @@ var DaemonCmux = class {
5504
5547
  * file failed to read/parse — see the class doc above.
5505
5548
  */
5506
5549
  async liveness() {
5507
- const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join15(homedir10(), ".cmuxterm");
5550
+ const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join16(homedir11(), ".cmuxterm");
5508
5551
  const projects = loadConfig().projects;
5509
5552
  let files;
5510
5553
  try {
5511
- files = readdirSync3(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
5554
+ files = readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
5512
5555
  } catch (e) {
5513
5556
  throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
5514
5557
  }
5515
- return readLivenessSnapshot(files, (f) => readFileSync10(join15(dir, f), "utf-8"), projects);
5558
+ return readLivenessSnapshot(files, (f) => readFileSync11(join16(dir, f), "utf-8"), projects);
5516
5559
  }
5517
5560
  };
5518
5561
 
5519
5562
  // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
5520
- import { join as join16 } from "path";
5521
- import { homedir as homedir11 } from "os";
5522
- import { watch, readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
5563
+ import { join as join17 } from "path";
5564
+ import { homedir as homedir12 } from "os";
5565
+ import { watch, readdirSync as readdirSync5, readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
5523
5566
  var CmuxStoreSource = class {
5524
5567
  name = "cmux-store";
5525
5568
  stateDir;
@@ -5540,12 +5583,12 @@ var CmuxStoreSource = class {
5540
5583
  active = false;
5541
5584
  lastError = null;
5542
5585
  constructor(opts = {}) {
5543
- this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join16(homedir11(), ".cmuxterm");
5586
+ this.stateDir = opts.stateDir ?? process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join17(homedir12(), ".cmuxterm");
5544
5587
  this.debounceMs = opts.debounceMs ?? 50;
5545
5588
  this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
5546
5589
  this.listFiles = opts.listFiles ?? defaultListFiles;
5547
5590
  this.readFile = opts.readFile ?? defaultReadFile;
5548
- this.fileExists = opts.fileExists ?? existsSync10;
5591
+ this.fileExists = opts.fileExists ?? existsSync11;
5549
5592
  this.watchDir = opts.watchDir ?? defaultWatchDir;
5550
5593
  this.scheduleTimer = opts.scheduleTimer ?? setTimeout;
5551
5594
  this.cancelTimer = opts.cancelTimer ?? clearTimeout;
@@ -5602,7 +5645,7 @@ var CmuxStoreSource = class {
5602
5645
  }
5603
5646
  scanFile(filename) {
5604
5647
  const deps = this.deps;
5605
- const filePath = join16(this.stateDir, filename);
5648
+ const filePath = join17(this.stateDir, filename);
5606
5649
  const lockPath = `${filePath}.lock`;
5607
5650
  if (this.fileExists(lockPath)) {
5608
5651
  this.log(`cmux-store: skipping ${filename} (locked)`);
@@ -5668,14 +5711,14 @@ function defaultIsPidAlive2(pid) {
5668
5711
  }
5669
5712
  function defaultListFiles(dir) {
5670
5713
  try {
5671
- return readdirSync4(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
5714
+ return readdirSync5(dir).filter((f) => f.endsWith("-hook-sessions.json") && !f.endsWith(".lock"));
5672
5715
  } catch {
5673
5716
  return [];
5674
5717
  }
5675
5718
  }
5676
5719
  function defaultReadFile(path19) {
5677
5720
  try {
5678
- return readFileSync11(path19, "utf-8");
5721
+ return readFileSync12(path19, "utf-8");
5679
5722
  } catch {
5680
5723
  return void 0;
5681
5724
  }
@@ -5690,9 +5733,9 @@ function defaultWatchDir(dir, cb) {
5690
5733
  }
5691
5734
 
5692
5735
  // packages/workspaces/dist/native-hooks/native-hook-source.js
5693
- import { join as join17 } from "path";
5694
- import { homedir as homedir12 } from "os";
5695
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
5736
+ import { join as join18 } from "path";
5737
+ import { homedir as homedir13 } from "os";
5738
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
5696
5739
  var CLAUDE_HOOK_EVENTS = [
5697
5740
  ["SessionStart", "session-start"],
5698
5741
  ["UserPromptSubmit", "prompt-submit"],
@@ -5704,7 +5747,7 @@ var CLAUDE_HOOK_EVENTS = [
5704
5747
  ];
5705
5748
  var DEFAULT_HOOK_CMD = "squadrant hooks";
5706
5749
  function installClaudeHooks(opts = {}) {
5707
- const settingsPath = opts.settingsPath ?? join17(homedir12(), ".claude", "settings.json");
5750
+ const settingsPath = opts.settingsPath ?? join18(homedir13(), ".claude", "settings.json");
5708
5751
  const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
5709
5752
  const readFile6 = opts.readFile ?? defaultReadFile2;
5710
5753
  const writeFile5 = opts.writeFile ?? defaultWriteFile;
@@ -5712,6 +5755,7 @@ function installClaudeHooks(opts = {}) {
5712
5755
  });
5713
5756
  let settings = {};
5714
5757
  const raw = readFile6(settingsPath);
5758
+ const hadExistingSettings = raw !== void 0;
5715
5759
  if (raw) {
5716
5760
  try {
5717
5761
  settings = JSON.parse(raw);
@@ -5724,6 +5768,7 @@ function installClaudeHooks(opts = {}) {
5724
5768
  }
5725
5769
  const hooks = settings.hooks;
5726
5770
  let changed = false;
5771
+ const repaired = [];
5727
5772
  for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
5728
5773
  if (!Array.isArray(hooks[eventName])) {
5729
5774
  hooks[eventName] = [];
@@ -5735,6 +5780,26 @@ function installClaudeHooks(opts = {}) {
5735
5780
  if (!alreadyPresent) {
5736
5781
  entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
5737
5782
  changed = true;
5783
+ repaired.push(`${eventName}/${sub}`);
5784
+ }
5785
+ }
5786
+ if (repaired.length > 0 && hadExistingSettings) {
5787
+ log(`native-hook: repaired ${repaired.length} missing squadrant hook(s) in ${settingsPath} [${repaired.join(", ")}] \u2014 WARNING: blocked-signalling or lifecycle tracking may have been broken until this run`);
5788
+ }
5789
+ if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {
5790
+ if (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env)) {
5791
+ settings.env = {};
5792
+ }
5793
+ const env = settings.env;
5794
+ for (const [key, value] of Object.entries(opts.claudeEnv)) {
5795
+ if (key in env) {
5796
+ if (env[key] !== value) {
5797
+ log(`native-hook: claudeEnv key '${key}' already set to '${String(env[key])}' in ${settingsPath} \u2014 not overwriting with '${value}'`);
5798
+ }
5799
+ continue;
5800
+ }
5801
+ env[key] = value;
5802
+ changed = true;
5738
5803
  }
5739
5804
  }
5740
5805
  if (changed) {
@@ -5771,9 +5836,9 @@ var NativeHookSource = class {
5771
5836
  cache = /* @__PURE__ */ new Map();
5772
5837
  active = false;
5773
5838
  constructor(opts = {}) {
5774
- this.hookInstall = opts.hookInstall ?? {};
5775
5839
  this.log = opts.log ?? (() => {
5776
5840
  });
5841
+ this.hookInstall = { log: this.log, ...opts.hookInstall };
5777
5842
  }
5778
5843
  start(deps) {
5779
5844
  this.deps = deps;
@@ -5852,14 +5917,14 @@ function extractDetail(sub, payload) {
5852
5917
  }
5853
5918
  function defaultReadFile2(path19) {
5854
5919
  try {
5855
- return readFileSync12(path19, "utf-8");
5920
+ return readFileSync13(path19, "utf-8");
5856
5921
  } catch {
5857
5922
  return void 0;
5858
5923
  }
5859
5924
  }
5860
5925
  function defaultWriteFile(path19, content) {
5861
- mkdirSync6(path19.replace(/\/[^/]+$/, ""), { recursive: true });
5862
- writeFileSync8(path19, content, "utf-8");
5926
+ mkdirSync7(path19.replace(/\/[^/]+$/, ""), { recursive: true });
5927
+ writeFileSync9(path19, content, "utf-8");
5863
5928
  }
5864
5929
 
5865
5930
  // packages/workspaces/dist/crew-pane.js
@@ -5983,12 +6048,12 @@ async function maybeBroadcastDaemonRestart(opts) {
5983
6048
 
5984
6049
  // packages/cli/src/squadrantd.ts
5985
6050
  var SELF_PATH2 = fileURLToPath3(import.meta.url);
5986
- var CLI_BIN = join18(dirname5(SELF_PATH2), "index.js");
5987
- var DAEMON_SOCK = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
6051
+ var CLI_BIN = join19(dirname5(SELF_PATH2), "index.js");
6052
+ var DAEMON_SOCK = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
5988
6053
  function readPkgVersion() {
5989
6054
  try {
5990
- const pkgPath = join18(dirname5(SELF_PATH2), "..", "package.json");
5991
- return JSON.parse(readFileSync13(pkgPath, "utf-8")).version ?? "unknown";
6055
+ const pkgPath = join19(dirname5(SELF_PATH2), "..", "package.json");
6056
+ return JSON.parse(readFileSync14(pkgPath, "utf-8")).version ?? "unknown";
5992
6057
  } catch {
5993
6058
  return "unknown";
5994
6059
  }
@@ -6081,11 +6146,11 @@ function startSquadrantd(opts = {}) {
6081
6146
  (r) => r.mode === "interactive" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd
6082
6147
  );
6083
6148
  },
6084
- cursorFile: join18(stateRoot, "cmux-events.seq"),
6149
+ cursorFile: join19(stateRoot, "cmux-events.seq"),
6085
6150
  log
6086
6151
  });
6087
6152
  const cmuxStoreSource = new CmuxStoreSource({ log });
6088
- const nativeHookSource = new NativeHookSource({ log });
6153
+ const nativeHookSource = new NativeHookSource({ log, hookInstall: { claudeEnv: loadConfig().defaults.claudeEnv } });
6089
6154
  ctx.codexDriver = codexDriver;
6090
6155
  ctx.opencodeBridge = opencodeBridge;
6091
6156
  ctx.cmuxEventsBridge = cmuxEventsBridge;
@@ -6240,7 +6305,7 @@ ${buildCompletionProtocol(rec.id, rec.project)}`;
6240
6305
  }
6241
6306
  if (!process.env.VITEST) {
6242
6307
  try {
6243
- const buildMtimeMs = statSync3(SELF_PATH2).mtimeMs;
6308
+ const buildMtimeMs = statSync4(SELF_PATH2).mtimeMs;
6244
6309
  const restartConfig = loadConfig();
6245
6310
  const registry = new RuntimeRegistry({ cmux: createCmuxDriver() });
6246
6311
  void maybeBroadcastDaemonRestart({
@@ -6293,7 +6358,7 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
6293
6358
  process.stdout.write("squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\n");
6294
6359
  process.exit(0);
6295
6360
  }
6296
- const sock = join18(homedir13(), ".config", "squadrant", "squadrant.sock");
6361
+ const sock = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
6297
6362
  if (await isDaemonSocketLive(sock)) {
6298
6363
  process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}
6299
6364
  `);