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