squadrant 0.25.0 → 0.25.2

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.
@@ -114,13 +114,8 @@ function getDefaultConfig() {
114
114
  captain: "auto",
115
115
  crew: "auto"
116
116
  },
117
- models: {
118
- command: "opus",
119
- captain: "opus",
120
- crew: "sonnet",
121
- exploration: "haiku",
122
- review: "opus"
123
- },
117
+ // #874: no `models` here — it's deprecated (superseded by `roles`), so a
118
+ // fresh init wrote a key the drift check immediately flagged.
124
119
  roles: {
125
120
  command: { agent: "claude", model: "opus" },
126
121
  captain: { agent: "claude", model: "opus" },
@@ -2449,6 +2444,10 @@ function sanitizePathForPlist(path24) {
2449
2444
  continue;
2450
2445
  if (p.includes("/.claude/plugins/"))
2451
2446
  continue;
2447
+ if (p.includes("/.cmuxterm/cmux-cli-shims/"))
2448
+ continue;
2449
+ if (p.includes("/fnm_multishells/"))
2450
+ continue;
2452
2451
  if (seen.has(p))
2453
2452
  continue;
2454
2453
  seen.add(p);
@@ -2476,18 +2475,7 @@ function resolveAgentBinDirs() {
2476
2475
  });
2477
2476
  }
2478
2477
  function buildDaemonPath(shellPath) {
2479
- const agentDirs = resolveAgentBinDirs();
2480
- const sanitized = sanitizePathForPlist(shellPath);
2481
- if (agentDirs.length === 0)
2482
- return sanitized;
2483
- const parts = [...agentDirs, ...sanitized.split(":")];
2484
- const seen = /* @__PURE__ */ new Set();
2485
- return parts.filter((p) => {
2486
- if (!p || seen.has(p))
2487
- return false;
2488
- seen.add(p);
2489
- return true;
2490
- }).join(":");
2478
+ return sanitizePathForPlist([...resolveAgentBinDirs(), shellPath].join(":"));
2491
2479
  }
2492
2480
  function renderPlist(nodeBin, daemonEntry, pathEnv = "") {
2493
2481
  const logPath2 = join8(homedir5(), ".config", "squadrant", "squadrantd.log");
@@ -9448,6 +9436,7 @@ init_dist2();
9448
9436
  init_dist2();
9449
9437
  init_dist2();
9450
9438
  init_dist2();
9439
+ init_dist2();
9451
9440
  import { join as join27, dirname as dirname6, resolve as resolve3 } from "path";
9452
9441
  import { homedir as homedir14 } from "os";
9453
9442
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -11739,7 +11728,8 @@ var RuntimeRegistry = class {
11739
11728
  };
11740
11729
 
11741
11730
  // packages/workspaces/dist/notifiers/cmux.js
11742
- import { execFile as execFileCb, execSync as execSync7 } from "child_process";
11731
+ init_dist();
11732
+ import { execFile as execFileCb, execFileSync as execFileSync7 } from "child_process";
11743
11733
  import { promisify as promisify2 } from "util";
11744
11734
  var execFile3 = promisify2(execFileCb);
11745
11735
  function createCmuxNotifier(_scope) {
@@ -11747,7 +11737,7 @@ function createCmuxNotifier(_scope) {
11747
11737
  name: "cmux",
11748
11738
  async probe() {
11749
11739
  try {
11750
- execSync7("squadrant runtime status --command", { encoding: "utf-8", stdio: "pipe" });
11740
+ execFileSync7(resolveCmuxBin(), ["capabilities"], { encoding: "utf-8", stdio: "pipe", timeout: CMUX_TIMEOUT });
11751
11741
  return { installed: true, reachable: true };
11752
11742
  } catch (err) {
11753
11743
  const code = err.code;
@@ -11957,9 +11947,9 @@ import { homedir as homedir11 } from "os";
11957
11947
  // packages/workspaces/dist/cmux-daemon/store-fingerprint.js
11958
11948
  init_dist();
11959
11949
  init_dist2();
11960
- import { execFileSync as execFileSync7 } from "child_process";
11950
+ import { execFileSync as execFileSync8 } from "child_process";
11961
11951
  function defaultExecPs(pid) {
11962
- return execFileSync7("ps", ["-o", "command=", "-p", String(pid)], {
11952
+ return execFileSync8("ps", ["-o", "command=", "-p", String(pid)], {
11963
11953
  encoding: "utf-8",
11964
11954
  stdio: ["ignore", "pipe", "ignore"]
11965
11955
  });
@@ -12129,6 +12119,7 @@ var CmuxStoreSource = class {
12129
12119
  debounceMs;
12130
12120
  lockRetryMs;
12131
12121
  maxLockRetries;
12122
+ rewatchMs;
12132
12123
  isPidAlive;
12133
12124
  listFiles;
12134
12125
  readFile;
@@ -12140,6 +12131,7 @@ var CmuxStoreSource = class {
12140
12131
  deps;
12141
12132
  stopWatcher;
12142
12133
  debounceTimer;
12134
+ rewatchTimer;
12143
12135
  /** filename → pending lock-retry timer (one per locked file). */
12144
12136
  lockRetryTimers = /* @__PURE__ */ new Map();
12145
12137
  /** filename → re-scan attempts used in the current lock episode. */
@@ -12153,6 +12145,7 @@ var CmuxStoreSource = class {
12153
12145
  this.debounceMs = opts.debounceMs ?? 50;
12154
12146
  this.lockRetryMs = opts.lockRetryMs ?? 50;
12155
12147
  this.maxLockRetries = opts.maxLockRetries ?? 3;
12148
+ this.rewatchMs = opts.rewatchMs ?? 5e3;
12156
12149
  this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive2;
12157
12150
  this.listFiles = opts.listFiles ?? defaultListFiles;
12158
12151
  this.readFile = opts.readFile ?? defaultReadFile;
@@ -12168,18 +12161,17 @@ var CmuxStoreSource = class {
12168
12161
  this.active = true;
12169
12162
  this.lastError = null;
12170
12163
  this.scan();
12171
- try {
12172
- this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced());
12173
- } catch (e) {
12174
- this.lastError = e.message;
12175
- this.log(`cmux-store: failed to watch ${this.stateDir}: ${e.message}`);
12176
- }
12164
+ this.armWatcher();
12177
12165
  }
12178
12166
  stop() {
12179
12167
  if (this.debounceTimer !== void 0) {
12180
12168
  this.cancelTimer(this.debounceTimer);
12181
12169
  this.debounceTimer = void 0;
12182
12170
  }
12171
+ if (this.rewatchTimer !== void 0) {
12172
+ this.cancelTimer(this.rewatchTimer);
12173
+ this.rewatchTimer = void 0;
12174
+ }
12183
12175
  for (const timer of this.lockRetryTimers.values()) {
12184
12176
  this.cancelTimer(timer);
12185
12177
  }
@@ -12200,6 +12192,36 @@ var CmuxStoreSource = class {
12200
12192
  return { active: this.active, error: this.lastError };
12201
12193
  }
12202
12194
  // ── private ─────────────────────────────────────────────────────────────────
12195
+ /** Watch for subsequent changes, debounced. */
12196
+ armWatcher() {
12197
+ try {
12198
+ this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced(), (err) => this.onWatchError(err));
12199
+ } catch (e) {
12200
+ this.lastError = e.message;
12201
+ this.log(`cmux-store: failed to watch ${this.stateDir}: ${e.message}`);
12202
+ }
12203
+ }
12204
+ /**
12205
+ * The watcher failed after starting (macOS: a refused FSEventStreamStart()
12206
+ * surfaces as EMFILE). Drop it and re-arm later instead of letting the error
12207
+ * escape as an uncaughtException that kills the daemon.
12208
+ */
12209
+ onWatchError(err) {
12210
+ this.lastError = err.message;
12211
+ this.log(`cmux-store: watcher error on ${this.stateDir}: ${err.message} \u2014 re-arming in ${this.rewatchMs}ms`);
12212
+ this.stopWatcher?.();
12213
+ this.stopWatcher = void 0;
12214
+ if (!this.active || this.rewatchTimer !== void 0)
12215
+ return;
12216
+ this.rewatchTimer = this.scheduleTimer(() => {
12217
+ this.rewatchTimer = void 0;
12218
+ if (!this.active)
12219
+ return;
12220
+ this.lastError = null;
12221
+ this.scan();
12222
+ this.armWatcher();
12223
+ }, this.rewatchMs);
12224
+ }
12203
12225
  scheduleDebounced() {
12204
12226
  if (this.debounceTimer !== void 0) {
12205
12227
  this.cancelTimer(this.debounceTimer);
@@ -12322,12 +12344,13 @@ function defaultReadFile(path24) {
12322
12344
  return void 0;
12323
12345
  }
12324
12346
  }
12325
- function defaultWatchDir(dir, cb) {
12347
+ function defaultWatchDir(dir, cb, onError) {
12326
12348
  const w = watch(dir, (_event, filename) => {
12327
12349
  if (typeof filename === "string" && filename.endsWith("-hook-sessions.json")) {
12328
12350
  cb();
12329
12351
  }
12330
12352
  });
12353
+ w.on("error", onError);
12331
12354
  return () => w.close();
12332
12355
  }
12333
12356
 
@@ -12350,10 +12373,36 @@ var CLAUDE_HOOK_EVENTS = [
12350
12373
  // this at all; without it the watchdog reports a plain stall (wrong story).
12351
12374
  ["StopFailure", "stop-failure"]
12352
12375
  ];
12353
- var DEFAULT_HOOK_CMD = "squadrant hooks";
12354
- var GATE_HOOK_CMD = "squadrant gate";
12355
- function hookCommandFor(eventName, sub, hookCmd) {
12356
- return eventName === "PermissionRequest" ? `${GATE_HOOK_CMD} claude ${sub}` : `${hookCmd} claude ${sub}`;
12376
+ var DEFAULT_CLI_CMD = "squadrant";
12377
+ function hookCommandFor(eventName, sub, hookCmd, gateCmd) {
12378
+ return eventName === "PermissionRequest" ? `${gateCmd} claude ${sub}` : `${hookCmd} claude ${sub}`;
12379
+ }
12380
+ function isSquadrantOwnedCommand(command, sub) {
12381
+ if (typeof command !== "string")
12382
+ return false;
12383
+ const m = command.match(/^(.*) (?:hooks|gate) claude (\S+)$/);
12384
+ if (!m || m[2] !== sub)
12385
+ return false;
12386
+ const prefix = m[1];
12387
+ return prefix === DEFAULT_CLI_CMD || /squadrant\/dist\/index\.js'?$/.test(prefix);
12388
+ }
12389
+ function removeStaleOwnedHandlers(entries, sub, keep) {
12390
+ let removed = false;
12391
+ for (let i = entries.length - 1; i >= 0; i--) {
12392
+ const entry = entries[i];
12393
+ if (!Array.isArray(entry?.hooks))
12394
+ continue;
12395
+ const before = entry.hooks.length;
12396
+ entry.hooks = entry.hooks.filter((h) => {
12397
+ const cmd = h?.command;
12398
+ return cmd === keep || !isSquadrantOwnedCommand(cmd, sub);
12399
+ });
12400
+ if (entry.hooks.length !== before)
12401
+ removed = true;
12402
+ if (entry.hooks.length === 0)
12403
+ entries.splice(i, 1);
12404
+ }
12405
+ return removed;
12357
12406
  }
12358
12407
  function removeCommandHandlers(entries, command) {
12359
12408
  let removed = false;
@@ -12414,7 +12463,9 @@ function removeForeignGateHandlers(entries) {
12414
12463
  }
12415
12464
  function installClaudeHooks(opts = {}) {
12416
12465
  const settingsPath = opts.settingsPath ?? join25(homedir13(), ".claude", "settings.json");
12417
- const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
12466
+ const cliCmd = opts.cliCmd ?? DEFAULT_CLI_CMD;
12467
+ const hookCmd = opts.hookCmd ?? `${cliCmd} hooks`;
12468
+ const gateCmd = `${cliCmd} gate`;
12418
12469
  const readFile7 = opts.readFile ?? defaultReadFile2;
12419
12470
  const writeFile6 = opts.writeFile ?? defaultWriteFile;
12420
12471
  const log = opts.log ?? (() => {
@@ -12436,12 +12487,13 @@ function installClaudeHooks(opts = {}) {
12436
12487
  let changed = false;
12437
12488
  const repaired = [];
12438
12489
  const migrated = [];
12490
+ const repointed = [];
12439
12491
  for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {
12440
12492
  if (!Array.isArray(hooks[eventName])) {
12441
12493
  hooks[eventName] = [];
12442
12494
  }
12443
12495
  const entries = hooks[eventName];
12444
- const command = hookCommandFor(eventName, sub, hookCmd);
12496
+ const command = hookCommandFor(eventName, sub, hookCmd, gateCmd);
12445
12497
  const hookMatcher = matcher ?? "";
12446
12498
  if (eventName === "PermissionRequest") {
12447
12499
  const legacy = `${hookCmd} claude ${sub}`;
@@ -12467,11 +12519,14 @@ function installClaudeHooks(opts = {}) {
12467
12519
  }
12468
12520
  }
12469
12521
  }
12522
+ const wasRepointed = removeStaleOwnedHandlers(entries, sub, command);
12523
+ if (wasRepointed)
12524
+ changed = true;
12470
12525
  const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
12471
12526
  if (!alreadyPresent) {
12472
12527
  entries.push({ matcher: hookMatcher, hooks: [{ type: "command", command, timeout: 10 }] });
12473
12528
  changed = true;
12474
- repaired.push(`${eventName}/${sub}`);
12529
+ (wasRepointed ? repointed : repaired).push(`${eventName}/${sub}`);
12475
12530
  }
12476
12531
  }
12477
12532
  if (repaired.length > 0 && hadExistingSettings) {
@@ -12480,6 +12535,9 @@ function installClaudeHooks(opts = {}) {
12480
12535
  if (migrated.length > 0) {
12481
12536
  log(`native-hook: migrated ${migrated.length} PermissionRequest hook(s) to the U7 permission gate in ${settingsPath} [${migrated.join(", ")}]`);
12482
12537
  }
12538
+ if (repointed.length > 0) {
12539
+ log(`native-hook: repointed ${repointed.length} squadrant hook(s) to ${cliCmd} in ${settingsPath} [${repointed.join(", ")}]`);
12540
+ }
12483
12541
  if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {
12484
12542
  if (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env)) {
12485
12543
  settings.env = {};
@@ -13129,6 +13187,9 @@ function startSquadrantd(opts = {}) {
13129
13187
  const nativeHookSource = new NativeHookSource({
13130
13188
  log,
13131
13189
  hookInstall: {
13190
+ // #872: absolute node + CLI paths — Claude runs hooks via /bin/sh with its
13191
+ // own PATH, which often lacks a version-managed Node's global bin.
13192
+ cliCmd: `${shellQuote(process.execPath)} ${shellQuote(CLI_BIN)}`,
13132
13193
  claudeEnv: loadConfig().defaults.claudeEnv,
13133
13194
  // #P6 (§15#5): only an explicit gate mode "on" lets squadrant remove a
13134
13195
  // foreign auto-gate PermissionRequest handler; auto/absent leaves it alone.