squadrant 0.22.0 → 0.24.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 +427 -152
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +267 -34
- package/dist/squadrantd.js.map +1 -1
- package/package.json +2 -1
- package/plugin/skills/gate/SKILL.md +8 -0
- package/plugin/skills/set-gate/SKILL.md +66 -0
package/dist/squadrantd.js
CHANGED
|
@@ -94,6 +94,9 @@ function isGateMode(v2) {
|
|
|
94
94
|
function isGatePolicy(v2) {
|
|
95
95
|
return GATE_POLICIES.includes(v2);
|
|
96
96
|
}
|
|
97
|
+
function isGateEngine(v2) {
|
|
98
|
+
return GATE_ENGINES.includes(v2);
|
|
99
|
+
}
|
|
97
100
|
function getDefaultConfig() {
|
|
98
101
|
return {
|
|
99
102
|
commandName: "\u{1F3DB}\uFE0F command",
|
|
@@ -177,12 +180,13 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
177
180
|
function resolveHome(p) {
|
|
178
181
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
179
182
|
}
|
|
180
|
-
var GATE_MODES, GATE_POLICIES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
183
|
+
var GATE_MODES, GATE_POLICIES, GATE_ENGINES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
181
184
|
var init_config = __esm({
|
|
182
185
|
"packages/shared/dist/config.js"() {
|
|
183
186
|
init_config_io();
|
|
184
187
|
GATE_MODES = ["on", "off", "auto"];
|
|
185
188
|
GATE_POLICIES = ["deny-dangerous", "ask-on-doubt"];
|
|
189
|
+
GATE_ENGINES = ["router", "auto-gate"];
|
|
186
190
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
187
191
|
CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
|
|
188
192
|
DAEMON_SOCK_PATH = path2.join(CONFIG_DIR, "squadrant.sock");
|
|
@@ -1070,10 +1074,11 @@ function formatDelegationReport(rec, originProject, targetProject) {
|
|
|
1070
1074
|
return null;
|
|
1071
1075
|
}
|
|
1072
1076
|
}
|
|
1073
|
-
function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
|
|
1077
|
+
function firePush(deps, project, prev, next, event, lastCaptainTurnAt, prevRec) {
|
|
1074
1078
|
if (!deps.notify)
|
|
1075
1079
|
return;
|
|
1076
|
-
|
|
1080
|
+
const questionChanged = prev === "blocked" && next.state === "blocked" && prevRec != null && (prevRec.question ?? "").trim() !== (next.question ?? "").trim();
|
|
1081
|
+
if (prev === next.state && !questionChanged)
|
|
1077
1082
|
return;
|
|
1078
1083
|
if (!ATTENTION_STATES.has(next.state))
|
|
1079
1084
|
return;
|
|
@@ -1111,6 +1116,7 @@ function createDaemon(deps) {
|
|
|
1111
1116
|
const { store, now } = deps;
|
|
1112
1117
|
const lastCaptainTurnAt = /* @__PURE__ */ new Map();
|
|
1113
1118
|
const quietNotifiedAt = /* @__PURE__ */ new Map();
|
|
1119
|
+
const blockedRenudgedFor = /* @__PURE__ */ new Map();
|
|
1114
1120
|
const resendAttemptedAt = /* @__PURE__ */ new Map();
|
|
1115
1121
|
const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;
|
|
1116
1122
|
const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;
|
|
@@ -1131,7 +1137,7 @@ function createDaemon(deps) {
|
|
|
1131
1137
|
const next = reduce(cur, event, now());
|
|
1132
1138
|
if (next !== cur) {
|
|
1133
1139
|
store.put(next);
|
|
1134
|
-
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));
|
|
1140
|
+
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id), cur);
|
|
1135
1141
|
}
|
|
1136
1142
|
return next;
|
|
1137
1143
|
}
|
|
@@ -1283,6 +1289,8 @@ function createDaemon(deps) {
|
|
|
1283
1289
|
}
|
|
1284
1290
|
const recoveryPromises = [];
|
|
1285
1291
|
for (const r of store.listAll()) {
|
|
1292
|
+
if (r.state !== "blocked")
|
|
1293
|
+
blockedRenudgedFor.delete(r.id);
|
|
1286
1294
|
if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
|
|
1287
1295
|
store.delete(r.project, r.id);
|
|
1288
1296
|
continue;
|
|
@@ -1345,6 +1353,27 @@ function createDaemon(deps) {
|
|
|
1345
1353
|
continue;
|
|
1346
1354
|
}
|
|
1347
1355
|
}
|
|
1356
|
+
if (r.state === "blocked" && !r.operatorHold) {
|
|
1357
|
+
const threshold = deps.blockedRenudgeMs ?? DEFAULT_BLOCKED_RENUDGE_MS;
|
|
1358
|
+
const blockedFor = t - r.lastHeartbeat;
|
|
1359
|
+
const episodeKey = r.question ?? "";
|
|
1360
|
+
if (blockedFor > threshold && blockedRenudgedFor.get(r.id) !== episodeKey) {
|
|
1361
|
+
blockedRenudgedFor.set(r.id, episodeKey);
|
|
1362
|
+
if (deps.notify) {
|
|
1363
|
+
const tag = crewTag(r);
|
|
1364
|
+
const mins = Math.max(1, Math.round(blockedFor / 6e4));
|
|
1365
|
+
const message = `CREW BLOCKED REMINDER ${tag}: still blocked ~${mins}min (reminder \u2014 same question, not a new one): ${(r.question ?? "(no question)").trim()}`;
|
|
1366
|
+
const synthEvent = { type: "task.blocked", id: r.id, reason: "renudge", question: r.question ?? "" };
|
|
1367
|
+
try {
|
|
1368
|
+
const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
|
|
1369
|
+
if (p && typeof p.catch === "function")
|
|
1370
|
+
p.catch(() => {
|
|
1371
|
+
});
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1348
1377
|
if (r.mode === "interactive" && r.state === "submitted" && !r.firstTurnConfirmedAt) {
|
|
1349
1378
|
const undeliveredMs = t - r.createdAt;
|
|
1350
1379
|
if (undeliveredMs > firstTurnUndeliveredBudgetMs) {
|
|
@@ -1434,7 +1463,7 @@ function createDaemon(deps) {
|
|
|
1434
1463
|
}
|
|
1435
1464
|
};
|
|
1436
1465
|
}
|
|
1437
|
-
var DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS, DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS, DEFAULT_TASK_TIMEOUT_MS, TERMINAL_RECORD_TTL_MS, TERMINAL_RECORD_KEEP_PER_PROJECT, ATTENTION_STATES, REAPABLE_SURFACE_STATES, IDLE_DEBOUNCE_MS, KNOWN_EVENT_TYPES;
|
|
1466
|
+
var DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS, DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS, DEFAULT_BLOCKED_RENUDGE_MS, DEFAULT_TASK_TIMEOUT_MS, TERMINAL_RECORD_TTL_MS, TERMINAL_RECORD_KEEP_PER_PROJECT, ATTENTION_STATES, REAPABLE_SURFACE_STATES, IDLE_DEBOUNCE_MS, KNOWN_EVENT_TYPES;
|
|
1438
1467
|
var init_reduce = __esm({
|
|
1439
1468
|
"packages/core/dist/daemon/reduce.js"() {
|
|
1440
1469
|
init_dist();
|
|
@@ -1442,6 +1471,7 @@ var init_reduce = __esm({
|
|
|
1442
1471
|
init_watchdog();
|
|
1443
1472
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 12e4;
|
|
1444
1473
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 6e4;
|
|
1474
|
+
DEFAULT_BLOCKED_RENUDGE_MS = 30 * 60 * 1e3;
|
|
1445
1475
|
DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
1446
1476
|
TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1447
1477
|
TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
@@ -3572,8 +3602,11 @@ function buildRouterEnv(creds, model) {
|
|
|
3572
3602
|
if (headers)
|
|
3573
3603
|
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
3574
3604
|
}
|
|
3575
|
-
if (model)
|
|
3605
|
+
if (model) {
|
|
3576
3606
|
env.ANTHROPIC_MODEL = model;
|
|
3607
|
+
env.CLAUDE_CODE_SUBAGENT_MODEL = model;
|
|
3608
|
+
env.ANTHROPIC_SMALL_FAST_MODEL = model;
|
|
3609
|
+
}
|
|
3577
3610
|
return env;
|
|
3578
3611
|
}
|
|
3579
3612
|
function mergeClaudeEnvRouterSettings(routerEnv, claudeEnv) {
|
|
@@ -4456,8 +4489,10 @@ async function resolveAndPersistOpencodeCaptain(opts) {
|
|
|
4456
4489
|
sleep: opts.sleep,
|
|
4457
4490
|
fetchImpl: opts.fetchImpl
|
|
4458
4491
|
});
|
|
4459
|
-
if (!sessionId)
|
|
4492
|
+
if (!sessionId) {
|
|
4493
|
+
opts.log?.(`opencode captain session resolution timed out for ${opts.project} (${opts.directory}) \u2014 captain.json has no session id yet; the daemon's own re-resolve fills it in once a session appears`);
|
|
4460
4494
|
return null;
|
|
4495
|
+
}
|
|
4461
4496
|
writeCaptainAddress(opts.stateRoot, opts.project, {
|
|
4462
4497
|
agent: "opencode",
|
|
4463
4498
|
port: opts.port,
|
|
@@ -4477,6 +4512,33 @@ var init_opencode_session = __esm({
|
|
|
4477
4512
|
function discoverCaptainSurface(surfaces, captainTitle) {
|
|
4478
4513
|
return surfaces.find((s) => s.title === captainTitle) ?? null;
|
|
4479
4514
|
}
|
|
4515
|
+
async function reresolveOpencodeAddress(rec) {
|
|
4516
|
+
const live = discoverLiveOpencodeServer({ directory: rec.directory, sessionId: rec.sessionId });
|
|
4517
|
+
const port = live?.port ?? rec.port;
|
|
4518
|
+
if (port === void 0)
|
|
4519
|
+
return null;
|
|
4520
|
+
const newest = newestSessionInDirectory(await listSessions(port), rec.directory, Date.parse(rec.launchedAt));
|
|
4521
|
+
const sessionId = newest ?? live?.sessionId ?? rec.sessionId;
|
|
4522
|
+
if (!sessionId || port === rec.port && sessionId === rec.sessionId)
|
|
4523
|
+
return null;
|
|
4524
|
+
return { port, sessionId };
|
|
4525
|
+
}
|
|
4526
|
+
async function healStaleOpencodeAddress(stateRoot, project, log, logPrefix) {
|
|
4527
|
+
const rec = readCaptainAddress(stateRoot, project);
|
|
4528
|
+
if (!rec)
|
|
4529
|
+
return false;
|
|
4530
|
+
const resolved = await reresolveOpencodeAddress(rec);
|
|
4531
|
+
if (!resolved)
|
|
4532
|
+
return false;
|
|
4533
|
+
const current = readCaptainAddress(stateRoot, project);
|
|
4534
|
+
if (!current || current.launchedAt !== rec.launchedAt) {
|
|
4535
|
+
log(`captain-channel ${project}: skipped stale-address heal \u2014 a newer launch record won the race`);
|
|
4536
|
+
return false;
|
|
4537
|
+
}
|
|
4538
|
+
writeCaptainAddress(stateRoot, project, { ...current, port: resolved.port, sessionId: resolved.sessionId });
|
|
4539
|
+
log(`captain-channel ${project}: ${logPrefix} \u2192 port ${resolved.port}`);
|
|
4540
|
+
return true;
|
|
4541
|
+
}
|
|
4480
4542
|
async function reapOrphanedCrews(store, project, isSurfaceAlive) {
|
|
4481
4543
|
let reaped = 0;
|
|
4482
4544
|
for (const r of store.list(project)) {
|
|
@@ -4701,27 +4763,7 @@ function createDelivery(ctx, daemonCmux, isSurfaceAlive) {
|
|
|
4701
4763
|
log(`captain-channel ${project}: threw, falling back to pane \u2014 ${e.message}`);
|
|
4702
4764
|
}
|
|
4703
4765
|
if (agent === "opencode" && mode !== "off") {
|
|
4704
|
-
const
|
|
4705
|
-
let healed = false;
|
|
4706
|
-
if (rec?.port) {
|
|
4707
|
-
const fresh = newestSessionInDirectory(await listSessions(rec.port), rec.directory, Date.parse(rec.launchedAt));
|
|
4708
|
-
if (fresh && fresh !== rec.sessionId) {
|
|
4709
|
-
writeCaptainAddress(stateRoot, project, { ...rec, sessionId: fresh });
|
|
4710
|
-
healed = true;
|
|
4711
|
-
}
|
|
4712
|
-
}
|
|
4713
|
-
if (!healed && rec) {
|
|
4714
|
-
const live = discoverLiveOpencodeServer({ directory: rec.directory, sessionId: rec.sessionId });
|
|
4715
|
-
if (live && (live.port !== rec.port || live.sessionId && live.sessionId !== rec.sessionId)) {
|
|
4716
|
-
writeCaptainAddress(stateRoot, project, {
|
|
4717
|
-
...rec,
|
|
4718
|
-
port: live.port,
|
|
4719
|
-
sessionId: live.sessionId ?? rec.sessionId
|
|
4720
|
-
});
|
|
4721
|
-
log(`captain-channel ${project}: re-resolved opencode address \u2192 port ${live.port}`);
|
|
4722
|
-
healed = true;
|
|
4723
|
-
}
|
|
4724
|
-
}
|
|
4766
|
+
const healed = await healStaleOpencodeAddress(stateRoot, project, log, "re-resolved opencode address");
|
|
4725
4767
|
if (healed) {
|
|
4726
4768
|
try {
|
|
4727
4769
|
const retry = await deliverToCaptain(project, text, { channel: picked, mode, log });
|
|
@@ -5579,11 +5621,15 @@ function parseCommand(text) {
|
|
|
5579
5621
|
}
|
|
5580
5622
|
return entry.build(args);
|
|
5581
5623
|
}
|
|
5582
|
-
var WRITABLE_CONFIG_KEYS, EFFORT_MODES, REGISTRY;
|
|
5624
|
+
var WRITABLE_CONFIG_KEYS, EFFORT_MODES, GATE_MODES2, CONFIG_VALUE_VALIDATORS, REGISTRY;
|
|
5583
5625
|
var init_commands = __esm({
|
|
5584
5626
|
"packages/core/dist/telegram/commands.js"() {
|
|
5585
|
-
WRITABLE_CONFIG_KEYS = ["defaults.effort"];
|
|
5627
|
+
WRITABLE_CONFIG_KEYS = ["defaults.effort", "defaults.gate.mode"];
|
|
5586
5628
|
EFFORT_MODES = /* @__PURE__ */ new Set(["max", "balance", "low"]);
|
|
5629
|
+
GATE_MODES2 = /* @__PURE__ */ new Set(["on", "off", "auto"]);
|
|
5630
|
+
CONFIG_VALUE_VALIDATORS = {
|
|
5631
|
+
"defaults.gate.mode": { valid: GATE_MODES2, usage: "usage: /config set defaults.gate.mode <on|off|auto>" }
|
|
5632
|
+
};
|
|
5587
5633
|
REGISTRY = {
|
|
5588
5634
|
status: { usage: "/status", build: () => ok("status", ["status"]) },
|
|
5589
5635
|
projects: { usage: "/projects", build: () => ok("projects", ["projects", "list"]) },
|
|
@@ -5630,6 +5676,10 @@ var init_commands = __esm({
|
|
|
5630
5676
|
message: `\u26D4 '${key}' is not writable over Telegram. Allowed: ${WRITABLE_CONFIG_KEYS.join(", ")}`
|
|
5631
5677
|
};
|
|
5632
5678
|
}
|
|
5679
|
+
const validator = CONFIG_VALUE_VALIDATORS[key];
|
|
5680
|
+
if (validator && !validator.valid.has(value)) {
|
|
5681
|
+
return usage("config", validator.usage);
|
|
5682
|
+
}
|
|
5633
5683
|
return ok("config", ["config", "set", key, value]);
|
|
5634
5684
|
}
|
|
5635
5685
|
return usage("config", "usage: /config get <key> | /config set <key> <value>");
|
|
@@ -7736,6 +7786,12 @@ function resolveGateMode(env, gate) {
|
|
|
7736
7786
|
return fromEnv;
|
|
7737
7787
|
return gate?.mode && isGateMode(gate.mode) ? gate.mode : "auto";
|
|
7738
7788
|
}
|
|
7789
|
+
function resolveGateEngine(env, gate) {
|
|
7790
|
+
const fromEnv = env.SQUADRANT_GATE_ENGINE;
|
|
7791
|
+
if (fromEnv !== void 0 && isGateEngine(fromEnv))
|
|
7792
|
+
return fromEnv;
|
|
7793
|
+
return gate?.engine && isGateEngine(gate.engine) ? gate.engine : "router";
|
|
7794
|
+
}
|
|
7739
7795
|
function resolveGatePolicy(env, gate) {
|
|
7740
7796
|
const fromEnv = env.SQUADRANT_GATE_POLICY;
|
|
7741
7797
|
if (fromEnv !== void 0 && isGatePolicy(fromEnv))
|
|
@@ -8120,6 +8176,62 @@ var init_permission_gate = __esm({
|
|
|
8120
8176
|
}
|
|
8121
8177
|
});
|
|
8122
8178
|
|
|
8179
|
+
// packages/core/dist/auto-gate.js
|
|
8180
|
+
import { createAutoGate, projectGateConfig, resolveApiKey } from "@squadrant-ai/auto-gate";
|
|
8181
|
+
function gateForProjection(config) {
|
|
8182
|
+
const gate = config.defaults.gate;
|
|
8183
|
+
if (!gate)
|
|
8184
|
+
return {};
|
|
8185
|
+
const out = {};
|
|
8186
|
+
if (gate.mode)
|
|
8187
|
+
out.mode = gate.mode;
|
|
8188
|
+
if (gate.tools)
|
|
8189
|
+
out.tools = gate.tools;
|
|
8190
|
+
if (gate.deny)
|
|
8191
|
+
out.deny = gate.deny;
|
|
8192
|
+
return out;
|
|
8193
|
+
}
|
|
8194
|
+
function hasAutoGateCredential(env = process.env, home) {
|
|
8195
|
+
return Boolean(resolveApiKey({ env, ...home !== void 0 ? { home } : {} }));
|
|
8196
|
+
}
|
|
8197
|
+
function createSquadrantAutoGate(deps) {
|
|
8198
|
+
const env = deps.env ?? process.env;
|
|
8199
|
+
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
8200
|
+
const project = deps.project ?? env.SQUADRANT_CREW_PROJECT;
|
|
8201
|
+
const mode = resolveGateMode(env, deps.config.defaults.gate);
|
|
8202
|
+
const projected = projectGateConfig({ gate: { ...gateForProjection(deps.config), mode } });
|
|
8203
|
+
const enabled = mode === "on";
|
|
8204
|
+
const blockedSignal = async (ctx) => {
|
|
8205
|
+
if (!taskId || !project || !deps.sendBlocked)
|
|
8206
|
+
return;
|
|
8207
|
+
const event = {
|
|
8208
|
+
type: "task.blocked",
|
|
8209
|
+
id: taskId,
|
|
8210
|
+
reason: ctx.reason,
|
|
8211
|
+
question: `crew needs permission to run ${ctx.tool}`
|
|
8212
|
+
};
|
|
8213
|
+
await deps.sendBlocked(project, event);
|
|
8214
|
+
};
|
|
8215
|
+
const host = createAutoGate({
|
|
8216
|
+
config: projected,
|
|
8217
|
+
readIntent: (p) => readUserIntent(p) ?? void 0,
|
|
8218
|
+
blockedSignal,
|
|
8219
|
+
...deps.decide ? { decide: deps.decide } : {},
|
|
8220
|
+
...project ? { project } : {},
|
|
8221
|
+
env,
|
|
8222
|
+
log: deps.log
|
|
8223
|
+
});
|
|
8224
|
+
if (!enabled) {
|
|
8225
|
+
return { config: host.config, decideClaudeHookPayload: async () => void 0 };
|
|
8226
|
+
}
|
|
8227
|
+
return host;
|
|
8228
|
+
}
|
|
8229
|
+
var init_auto_gate = __esm({
|
|
8230
|
+
"packages/core/dist/auto-gate.js"() {
|
|
8231
|
+
init_permission_gate();
|
|
8232
|
+
}
|
|
8233
|
+
});
|
|
8234
|
+
|
|
8123
8235
|
// packages/core/dist/group-dispatch.js
|
|
8124
8236
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8125
8237
|
import { createConnection as createConnection2 } from "net";
|
|
@@ -8526,10 +8638,11 @@ async function runCrewAnswer(project, name, option, runtime, workspaceId, deps,
|
|
|
8526
8638
|
if (!crew) {
|
|
8527
8639
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
8528
8640
|
}
|
|
8529
|
-
const
|
|
8530
|
-
if (!options) {
|
|
8641
|
+
const modal = await deps.readModalOptions(crew);
|
|
8642
|
+
if (!modal || modal.options.length === 0) {
|
|
8531
8643
|
throw new Error(`Crew '${name}' has no interactive option prompt visible right now \u2014 nothing to answer. Read its screen with 'squadrant crew read ${project} ${name}' to check its state.`);
|
|
8532
8644
|
}
|
|
8645
|
+
const options = modal.options;
|
|
8533
8646
|
const target = resolveOption(options, option);
|
|
8534
8647
|
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
8535
8648
|
throw new Error(`Refusing: option ${target.index} is "${target.label}", which does not contain expected text "${opts.expect}". Option order is model-generated and can shift between renders \u2014 re-check with 'squadrant crew read ${project} ${name}'.
|
|
@@ -8541,7 +8654,9 @@ ${describeOptions(options)}`);
|
|
|
8541
8654
|
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
8542
8655
|
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
8543
8656
|
const steps = target.index - current.index;
|
|
8544
|
-
const
|
|
8657
|
+
const forwardKey = modal.axis === "horizontal" ? "Right" : "Down";
|
|
8658
|
+
const backwardKey = modal.axis === "horizontal" ? "Left" : "Up";
|
|
8659
|
+
const key = steps >= 0 ? forwardKey : backwardKey;
|
|
8545
8660
|
for (let i = 0; i < Math.abs(steps); i++) {
|
|
8546
8661
|
await runtime.sendKeyToPane(crew, key);
|
|
8547
8662
|
}
|
|
@@ -8913,6 +9028,7 @@ __export(dist_exports, {
|
|
|
8913
9028
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
8914
9029
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
8915
9030
|
CaptainDelivery: () => CaptainDelivery,
|
|
9031
|
+
DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
|
|
8916
9032
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
|
|
8917
9033
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
8918
9034
|
DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
|
|
@@ -8995,6 +9111,7 @@ __export(dist_exports, {
|
|
|
8995
9111
|
createRouterService: () => createRouterService,
|
|
8996
9112
|
createRouterShim: () => createRouterShim,
|
|
8997
9113
|
createRunCommand: () => createRunCommand,
|
|
9114
|
+
createSquadrantAutoGate: () => createSquadrantAutoGate,
|
|
8998
9115
|
createStore: () => createStore,
|
|
8999
9116
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
9000
9117
|
createTelegramBridge: () => createTelegramBridge,
|
|
@@ -9051,6 +9168,7 @@ __export(dist_exports, {
|
|
|
9051
9168
|
freshTrace: () => freshTrace,
|
|
9052
9169
|
gateCacheKey: () => gateCacheKey,
|
|
9053
9170
|
getDaemonPid: () => getDaemonPid,
|
|
9171
|
+
hasAutoGateCredential: () => hasAutoGateCredential,
|
|
9054
9172
|
hasModalOptionList: () => hasModalOptionList,
|
|
9055
9173
|
healCmdFor: () => healCmdFor,
|
|
9056
9174
|
inboundBody: () => inboundBody,
|
|
@@ -9136,6 +9254,7 @@ __export(dist_exports, {
|
|
|
9136
9254
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
9137
9255
|
resolveGate: () => resolveGate,
|
|
9138
9256
|
resolveGateDenyRules: () => resolveGateDenyRules,
|
|
9257
|
+
resolveGateEngine: () => resolveGateEngine,
|
|
9139
9258
|
resolveGateMode: () => resolveGateMode,
|
|
9140
9259
|
resolveGatePolicy: () => resolveGatePolicy,
|
|
9141
9260
|
resolveGateTools: () => resolveGateTools,
|
|
@@ -9241,6 +9360,7 @@ var init_dist2 = __esm({
|
|
|
9241
9360
|
init_router_resolution();
|
|
9242
9361
|
init_captain_router();
|
|
9243
9362
|
init_permission_gate();
|
|
9363
|
+
init_auto_gate();
|
|
9244
9364
|
init_restart_daemon();
|
|
9245
9365
|
init_group_dispatch();
|
|
9246
9366
|
init_launch_workspace();
|
|
@@ -12149,6 +12269,48 @@ function removeCommandHandlers(entries, command) {
|
|
|
12149
12269
|
}
|
|
12150
12270
|
return removed;
|
|
12151
12271
|
}
|
|
12272
|
+
var FOREIGN_AUTO_GATE_MARKER = "auto-gate-managed";
|
|
12273
|
+
function isForeignAutoGateCommand(command) {
|
|
12274
|
+
if (typeof command !== "string")
|
|
12275
|
+
return false;
|
|
12276
|
+
if (command.includes(FOREIGN_AUTO_GATE_MARKER))
|
|
12277
|
+
return true;
|
|
12278
|
+
const first = command.trim().split(/\s+/)[0] ?? "";
|
|
12279
|
+
return /(^|[/\\])auto-gate$/.test(first);
|
|
12280
|
+
}
|
|
12281
|
+
function findForeignGateHandlers(entries) {
|
|
12282
|
+
const found = [];
|
|
12283
|
+
for (const entry of entries) {
|
|
12284
|
+
const hooks = entry?.hooks;
|
|
12285
|
+
if (!Array.isArray(hooks))
|
|
12286
|
+
continue;
|
|
12287
|
+
for (const h of hooks) {
|
|
12288
|
+
const cmd = h?.command;
|
|
12289
|
+
if (isForeignAutoGateCommand(cmd))
|
|
12290
|
+
found.push(cmd);
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12293
|
+
return found;
|
|
12294
|
+
}
|
|
12295
|
+
function removeForeignGateHandlers(entries) {
|
|
12296
|
+
const removed = [];
|
|
12297
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
12298
|
+
const entry = entries[i];
|
|
12299
|
+
if (!Array.isArray(entry?.hooks))
|
|
12300
|
+
continue;
|
|
12301
|
+
entry.hooks = entry.hooks.filter((h) => {
|
|
12302
|
+
const cmd = h?.command;
|
|
12303
|
+
if (isForeignAutoGateCommand(cmd)) {
|
|
12304
|
+
removed.push(cmd);
|
|
12305
|
+
return false;
|
|
12306
|
+
}
|
|
12307
|
+
return true;
|
|
12308
|
+
});
|
|
12309
|
+
if (entry.hooks.length === 0)
|
|
12310
|
+
entries.splice(i, 1);
|
|
12311
|
+
}
|
|
12312
|
+
return removed;
|
|
12313
|
+
}
|
|
12152
12314
|
function installClaudeHooks(opts = {}) {
|
|
12153
12315
|
const settingsPath = opts.settingsPath ?? join25(homedir13(), ".claude", "settings.json");
|
|
12154
12316
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
@@ -12186,6 +12348,23 @@ function installClaudeHooks(opts = {}) {
|
|
|
12186
12348
|
changed = true;
|
|
12187
12349
|
migrated.push(`${eventName}/${sub}`);
|
|
12188
12350
|
}
|
|
12351
|
+
const foreign = findForeignGateHandlers(entries);
|
|
12352
|
+
if (foreign.length > 0) {
|
|
12353
|
+
if (opts.gateMode === "on") {
|
|
12354
|
+
const removed = removeForeignGateHandlers(entries);
|
|
12355
|
+
if (removed.length > 0)
|
|
12356
|
+
changed = true;
|
|
12357
|
+
for (const command2 of removed) {
|
|
12358
|
+
log(`native-hook: removed foreign auto-gate PermissionRequest handler in ${settingsPath} [#P6 \xA715#5]: ${command2}`);
|
|
12359
|
+
opts.recordGateAction?.({ action: "removed-foreign-gate", command: command2 });
|
|
12360
|
+
}
|
|
12361
|
+
} else {
|
|
12362
|
+
for (const command2 of foreign) {
|
|
12363
|
+
log(`native-hook: foreign auto-gate PermissionRequest handler present in ${settingsPath} but squadrant's gate is '${opts.gateMode ?? "auto"}' (not 'on') \u2014 foreign entry left untouched [#P6 \xA715#5]: ${command2}`);
|
|
12364
|
+
opts.recordGateAction?.({ action: "left-foreign-gate", command: command2 });
|
|
12365
|
+
}
|
|
12366
|
+
}
|
|
12367
|
+
}
|
|
12189
12368
|
}
|
|
12190
12369
|
const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
|
|
12191
12370
|
if (!alreadyPresent) {
|
|
@@ -12216,11 +12395,57 @@ function installClaudeHooks(opts = {}) {
|
|
|
12216
12395
|
changed = true;
|
|
12217
12396
|
}
|
|
12218
12397
|
}
|
|
12398
|
+
if (typeof settings.env === "object" && settings.env !== null && !Array.isArray(settings.env)) {
|
|
12399
|
+
if (reconcileSubagentModels(settings.env, log))
|
|
12400
|
+
changed = true;
|
|
12401
|
+
}
|
|
12219
12402
|
if (changed) {
|
|
12220
12403
|
writeFile6(settingsPath, JSON.stringify(settings, null, 2));
|
|
12221
12404
|
}
|
|
12222
12405
|
return settingsPath;
|
|
12223
12406
|
}
|
|
12407
|
+
var SUBAGENT_MODEL_KEY = "CLAUDE_CODE_SUBAGENT_MODEL";
|
|
12408
|
+
var SMALL_FAST_MODEL_KEY = "ANTHROPIC_SMALL_FAST_MODEL";
|
|
12409
|
+
function isCustomUpstream(baseUrl) {
|
|
12410
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0)
|
|
12411
|
+
return false;
|
|
12412
|
+
try {
|
|
12413
|
+
return new URL(baseUrl).hostname.toLowerCase() !== "api.anthropic.com";
|
|
12414
|
+
} catch {
|
|
12415
|
+
return false;
|
|
12416
|
+
}
|
|
12417
|
+
}
|
|
12418
|
+
function isAnthropicModelId(value) {
|
|
12419
|
+
if (typeof value !== "string")
|
|
12420
|
+
return false;
|
|
12421
|
+
const v2 = value.trim().toLowerCase();
|
|
12422
|
+
return v2 === "sonnet" || v2 === "opus" || v2 === "haiku" || v2.startsWith("claude-");
|
|
12423
|
+
}
|
|
12424
|
+
function reconcileSubagentModels(env, log) {
|
|
12425
|
+
if (!isCustomUpstream(env.ANTHROPIC_BASE_URL))
|
|
12426
|
+
return false;
|
|
12427
|
+
const model = env.ANTHROPIC_MODEL;
|
|
12428
|
+
if (typeof model !== "string" || model.length === 0)
|
|
12429
|
+
return false;
|
|
12430
|
+
let changed = false;
|
|
12431
|
+
for (const key of [SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY]) {
|
|
12432
|
+
const current = env[key];
|
|
12433
|
+
if (current === void 0) {
|
|
12434
|
+
env[key] = model;
|
|
12435
|
+
changed = true;
|
|
12436
|
+
log(`native-hook: set ${key}='${model}' to match ANTHROPIC_MODEL on a custom upstream`);
|
|
12437
|
+
} else if (isAnthropicModelId(current)) {
|
|
12438
|
+
if (current !== model) {
|
|
12439
|
+
env[key] = model;
|
|
12440
|
+
changed = true;
|
|
12441
|
+
log(`native-hook: aligned ${key} '${String(current)}' \u2192 '${model}' for a custom upstream`);
|
|
12442
|
+
}
|
|
12443
|
+
} else {
|
|
12444
|
+
log(`native-hook: ${key} '${String(current)}' is a custom model id \u2014 left untouched`);
|
|
12445
|
+
}
|
|
12446
|
+
}
|
|
12447
|
+
return changed;
|
|
12448
|
+
}
|
|
12224
12449
|
function mapSubToLifecycle(sub) {
|
|
12225
12450
|
switch (sub) {
|
|
12226
12451
|
case "session-start":
|
|
@@ -12786,7 +13011,15 @@ function startSquadrantd(opts = {}) {
|
|
|
12786
13011
|
log
|
|
12787
13012
|
});
|
|
12788
13013
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
12789
|
-
const nativeHookSource = new NativeHookSource({
|
|
13014
|
+
const nativeHookSource = new NativeHookSource({
|
|
13015
|
+
log,
|
|
13016
|
+
hookInstall: {
|
|
13017
|
+
claudeEnv: loadConfig().defaults.claudeEnv,
|
|
13018
|
+
// #P6 (§15#5): only an explicit gate mode "on" lets squadrant remove a
|
|
13019
|
+
// foreign auto-gate PermissionRequest handler; auto/absent leaves it alone.
|
|
13020
|
+
gateMode: loadConfig().defaults.gate?.mode
|
|
13021
|
+
}
|
|
13022
|
+
});
|
|
12790
13023
|
ctx.codexDriver = codexDriver;
|
|
12791
13024
|
ctx.opencodeBridge = opencodeBridge;
|
|
12792
13025
|
ctx.cmuxEventsBridge = cmuxEventsBridge;
|