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.
- package/dist/index.js +127 -70
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +98 -37
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
package/dist/squadrantd.js
CHANGED
|
@@ -114,13 +114,8 @@ function getDefaultConfig() {
|
|
|
114
114
|
captain: "auto",
|
|
115
115
|
crew: "auto"
|
|
116
116
|
},
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
11950
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
11961
11951
|
function defaultExecPs(pid) {
|
|
11962
|
-
return
|
|
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
|
-
|
|
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
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
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
|
|
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.
|