squadrant 0.22.0 → 0.23.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 +278 -11
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +222 -10
- package/dist/squadrantd.js.map +1 -1
- package/package.json +2 -1
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) {
|
|
@@ -7736,6 +7769,12 @@ function resolveGateMode(env, gate) {
|
|
|
7736
7769
|
return fromEnv;
|
|
7737
7770
|
return gate?.mode && isGateMode(gate.mode) ? gate.mode : "auto";
|
|
7738
7771
|
}
|
|
7772
|
+
function resolveGateEngine(env, gate) {
|
|
7773
|
+
const fromEnv = env.SQUADRANT_GATE_ENGINE;
|
|
7774
|
+
if (fromEnv !== void 0 && isGateEngine(fromEnv))
|
|
7775
|
+
return fromEnv;
|
|
7776
|
+
return gate?.engine && isGateEngine(gate.engine) ? gate.engine : "router";
|
|
7777
|
+
}
|
|
7739
7778
|
function resolveGatePolicy(env, gate) {
|
|
7740
7779
|
const fromEnv = env.SQUADRANT_GATE_POLICY;
|
|
7741
7780
|
if (fromEnv !== void 0 && isGatePolicy(fromEnv))
|
|
@@ -8120,6 +8159,59 @@ var init_permission_gate = __esm({
|
|
|
8120
8159
|
}
|
|
8121
8160
|
});
|
|
8122
8161
|
|
|
8162
|
+
// packages/core/dist/auto-gate.js
|
|
8163
|
+
import { createAutoGate, projectGateConfig } from "@squadrant-ai/auto-gate";
|
|
8164
|
+
function gateForProjection(config) {
|
|
8165
|
+
const gate = config.defaults.gate;
|
|
8166
|
+
if (!gate)
|
|
8167
|
+
return {};
|
|
8168
|
+
const out = {};
|
|
8169
|
+
if (gate.mode)
|
|
8170
|
+
out.mode = gate.mode;
|
|
8171
|
+
if (gate.tools)
|
|
8172
|
+
out.tools = gate.tools;
|
|
8173
|
+
if (gate.deny)
|
|
8174
|
+
out.deny = gate.deny;
|
|
8175
|
+
return out;
|
|
8176
|
+
}
|
|
8177
|
+
function createSquadrantAutoGate(deps) {
|
|
8178
|
+
const env = deps.env ?? process.env;
|
|
8179
|
+
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
8180
|
+
const project = deps.project ?? env.SQUADRANT_CREW_PROJECT;
|
|
8181
|
+
const mode = resolveGateMode(env, deps.config.defaults.gate);
|
|
8182
|
+
const projected = projectGateConfig({ gate: { ...gateForProjection(deps.config), mode } });
|
|
8183
|
+
const enabled = mode === "on";
|
|
8184
|
+
const blockedSignal = async (ctx) => {
|
|
8185
|
+
if (!taskId || !project || !deps.sendBlocked)
|
|
8186
|
+
return;
|
|
8187
|
+
const event = {
|
|
8188
|
+
type: "task.blocked",
|
|
8189
|
+
id: taskId,
|
|
8190
|
+
reason: ctx.reason,
|
|
8191
|
+
question: `crew needs permission to run ${ctx.tool}`
|
|
8192
|
+
};
|
|
8193
|
+
await deps.sendBlocked(project, event);
|
|
8194
|
+
};
|
|
8195
|
+
const host = createAutoGate({
|
|
8196
|
+
config: projected,
|
|
8197
|
+
readIntent: (p) => readUserIntent(p) ?? void 0,
|
|
8198
|
+
blockedSignal,
|
|
8199
|
+
...deps.decide ? { decide: deps.decide } : {},
|
|
8200
|
+
...project ? { project } : {},
|
|
8201
|
+
env,
|
|
8202
|
+
log: deps.log
|
|
8203
|
+
});
|
|
8204
|
+
if (!enabled) {
|
|
8205
|
+
return { config: host.config, decideClaudeHookPayload: async () => void 0 };
|
|
8206
|
+
}
|
|
8207
|
+
return host;
|
|
8208
|
+
}
|
|
8209
|
+
var init_auto_gate = __esm({
|
|
8210
|
+
"packages/core/dist/auto-gate.js"() {
|
|
8211
|
+
init_permission_gate();
|
|
8212
|
+
}
|
|
8213
|
+
});
|
|
8214
|
+
|
|
8123
8215
|
// packages/core/dist/group-dispatch.js
|
|
8124
8216
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8125
8217
|
import { createConnection as createConnection2 } from "net";
|
|
@@ -8526,10 +8618,11 @@ async function runCrewAnswer(project, name, option, runtime, workspaceId, deps,
|
|
|
8526
8618
|
if (!crew) {
|
|
8527
8619
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
8528
8620
|
}
|
|
8529
|
-
const
|
|
8530
|
-
if (!options) {
|
|
8621
|
+
const modal = await deps.readModalOptions(crew);
|
|
8622
|
+
if (!modal || modal.options.length === 0) {
|
|
8531
8623
|
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
8624
|
}
|
|
8625
|
+
const options = modal.options;
|
|
8533
8626
|
const target = resolveOption(options, option);
|
|
8534
8627
|
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
8535
8628
|
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 +8634,9 @@ ${describeOptions(options)}`);
|
|
|
8541
8634
|
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
8542
8635
|
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
8543
8636
|
const steps = target.index - current.index;
|
|
8544
|
-
const
|
|
8637
|
+
const forwardKey = modal.axis === "horizontal" ? "Right" : "Down";
|
|
8638
|
+
const backwardKey = modal.axis === "horizontal" ? "Left" : "Up";
|
|
8639
|
+
const key = steps >= 0 ? forwardKey : backwardKey;
|
|
8545
8640
|
for (let i = 0; i < Math.abs(steps); i++) {
|
|
8546
8641
|
await runtime.sendKeyToPane(crew, key);
|
|
8547
8642
|
}
|
|
@@ -8913,6 +9008,7 @@ __export(dist_exports, {
|
|
|
8913
9008
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
8914
9009
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
8915
9010
|
CaptainDelivery: () => CaptainDelivery,
|
|
9011
|
+
DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
|
|
8916
9012
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
|
|
8917
9013
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
8918
9014
|
DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
|
|
@@ -8995,6 +9091,7 @@ __export(dist_exports, {
|
|
|
8995
9091
|
createRouterService: () => createRouterService,
|
|
8996
9092
|
createRouterShim: () => createRouterShim,
|
|
8997
9093
|
createRunCommand: () => createRunCommand,
|
|
9094
|
+
createSquadrantAutoGate: () => createSquadrantAutoGate,
|
|
8998
9095
|
createStore: () => createStore,
|
|
8999
9096
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
9000
9097
|
createTelegramBridge: () => createTelegramBridge,
|
|
@@ -9136,6 +9233,7 @@ __export(dist_exports, {
|
|
|
9136
9233
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
9137
9234
|
resolveGate: () => resolveGate,
|
|
9138
9235
|
resolveGateDenyRules: () => resolveGateDenyRules,
|
|
9236
|
+
resolveGateEngine: () => resolveGateEngine,
|
|
9139
9237
|
resolveGateMode: () => resolveGateMode,
|
|
9140
9238
|
resolveGatePolicy: () => resolveGatePolicy,
|
|
9141
9239
|
resolveGateTools: () => resolveGateTools,
|
|
@@ -9241,6 +9339,7 @@ var init_dist2 = __esm({
|
|
|
9241
9339
|
init_router_resolution();
|
|
9242
9340
|
init_captain_router();
|
|
9243
9341
|
init_permission_gate();
|
|
9342
|
+
init_auto_gate();
|
|
9244
9343
|
init_restart_daemon();
|
|
9245
9344
|
init_group_dispatch();
|
|
9246
9345
|
init_launch_workspace();
|
|
@@ -12149,6 +12248,48 @@ function removeCommandHandlers(entries, command) {
|
|
|
12149
12248
|
}
|
|
12150
12249
|
return removed;
|
|
12151
12250
|
}
|
|
12251
|
+
var FOREIGN_AUTO_GATE_MARKER = "auto-gate-managed";
|
|
12252
|
+
function isForeignAutoGateCommand(command) {
|
|
12253
|
+
if (typeof command !== "string")
|
|
12254
|
+
return false;
|
|
12255
|
+
if (command.includes(FOREIGN_AUTO_GATE_MARKER))
|
|
12256
|
+
return true;
|
|
12257
|
+
const first = command.trim().split(/\s+/)[0] ?? "";
|
|
12258
|
+
return /(^|[/\\])auto-gate$/.test(first);
|
|
12259
|
+
}
|
|
12260
|
+
function findForeignGateHandlers(entries) {
|
|
12261
|
+
const found = [];
|
|
12262
|
+
for (const entry of entries) {
|
|
12263
|
+
const hooks = entry?.hooks;
|
|
12264
|
+
if (!Array.isArray(hooks))
|
|
12265
|
+
continue;
|
|
12266
|
+
for (const h of hooks) {
|
|
12267
|
+
const cmd = h?.command;
|
|
12268
|
+
if (isForeignAutoGateCommand(cmd))
|
|
12269
|
+
found.push(cmd);
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12272
|
+
return found;
|
|
12273
|
+
}
|
|
12274
|
+
function removeForeignGateHandlers(entries) {
|
|
12275
|
+
const removed = [];
|
|
12276
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
12277
|
+
const entry = entries[i];
|
|
12278
|
+
if (!Array.isArray(entry?.hooks))
|
|
12279
|
+
continue;
|
|
12280
|
+
entry.hooks = entry.hooks.filter((h) => {
|
|
12281
|
+
const cmd = h?.command;
|
|
12282
|
+
if (isForeignAutoGateCommand(cmd)) {
|
|
12283
|
+
removed.push(cmd);
|
|
12284
|
+
return false;
|
|
12285
|
+
}
|
|
12286
|
+
return true;
|
|
12287
|
+
});
|
|
12288
|
+
if (entry.hooks.length === 0)
|
|
12289
|
+
entries.splice(i, 1);
|
|
12290
|
+
}
|
|
12291
|
+
return removed;
|
|
12292
|
+
}
|
|
12152
12293
|
function installClaudeHooks(opts = {}) {
|
|
12153
12294
|
const settingsPath = opts.settingsPath ?? join25(homedir13(), ".claude", "settings.json");
|
|
12154
12295
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
@@ -12186,6 +12327,23 @@ function installClaudeHooks(opts = {}) {
|
|
|
12186
12327
|
changed = true;
|
|
12187
12328
|
migrated.push(`${eventName}/${sub}`);
|
|
12188
12329
|
}
|
|
12330
|
+
const foreign = findForeignGateHandlers(entries);
|
|
12331
|
+
if (foreign.length > 0) {
|
|
12332
|
+
if (opts.gateMode === "on") {
|
|
12333
|
+
const removed = removeForeignGateHandlers(entries);
|
|
12334
|
+
if (removed.length > 0)
|
|
12335
|
+
changed = true;
|
|
12336
|
+
for (const command2 of removed) {
|
|
12337
|
+
log(`native-hook: removed foreign auto-gate PermissionRequest handler in ${settingsPath} [#P6 \xA715#5]: ${command2}`);
|
|
12338
|
+
opts.recordGateAction?.({ action: "removed-foreign-gate", command: command2 });
|
|
12339
|
+
}
|
|
12340
|
+
} else {
|
|
12341
|
+
for (const command2 of foreign) {
|
|
12342
|
+
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}`);
|
|
12343
|
+
opts.recordGateAction?.({ action: "left-foreign-gate", command: command2 });
|
|
12344
|
+
}
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12189
12347
|
}
|
|
12190
12348
|
const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
|
|
12191
12349
|
if (!alreadyPresent) {
|
|
@@ -12216,11 +12374,57 @@ function installClaudeHooks(opts = {}) {
|
|
|
12216
12374
|
changed = true;
|
|
12217
12375
|
}
|
|
12218
12376
|
}
|
|
12377
|
+
if (typeof settings.env === "object" && settings.env !== null && !Array.isArray(settings.env)) {
|
|
12378
|
+
if (reconcileSubagentModels(settings.env, log))
|
|
12379
|
+
changed = true;
|
|
12380
|
+
}
|
|
12219
12381
|
if (changed) {
|
|
12220
12382
|
writeFile6(settingsPath, JSON.stringify(settings, null, 2));
|
|
12221
12383
|
}
|
|
12222
12384
|
return settingsPath;
|
|
12223
12385
|
}
|
|
12386
|
+
var SUBAGENT_MODEL_KEY = "CLAUDE_CODE_SUBAGENT_MODEL";
|
|
12387
|
+
var SMALL_FAST_MODEL_KEY = "ANTHROPIC_SMALL_FAST_MODEL";
|
|
12388
|
+
function isCustomUpstream(baseUrl) {
|
|
12389
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0)
|
|
12390
|
+
return false;
|
|
12391
|
+
try {
|
|
12392
|
+
return new URL(baseUrl).hostname.toLowerCase() !== "api.anthropic.com";
|
|
12393
|
+
} catch {
|
|
12394
|
+
return false;
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
function isAnthropicModelId(value) {
|
|
12398
|
+
if (typeof value !== "string")
|
|
12399
|
+
return false;
|
|
12400
|
+
const v2 = value.trim().toLowerCase();
|
|
12401
|
+
return v2 === "sonnet" || v2 === "opus" || v2 === "haiku" || v2.startsWith("claude-");
|
|
12402
|
+
}
|
|
12403
|
+
function reconcileSubagentModels(env, log) {
|
|
12404
|
+
if (!isCustomUpstream(env.ANTHROPIC_BASE_URL))
|
|
12405
|
+
return false;
|
|
12406
|
+
const model = env.ANTHROPIC_MODEL;
|
|
12407
|
+
if (typeof model !== "string" || model.length === 0)
|
|
12408
|
+
return false;
|
|
12409
|
+
let changed = false;
|
|
12410
|
+
for (const key of [SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY]) {
|
|
12411
|
+
const current = env[key];
|
|
12412
|
+
if (current === void 0) {
|
|
12413
|
+
env[key] = model;
|
|
12414
|
+
changed = true;
|
|
12415
|
+
log(`native-hook: set ${key}='${model}' to match ANTHROPIC_MODEL on a custom upstream`);
|
|
12416
|
+
} else if (isAnthropicModelId(current)) {
|
|
12417
|
+
if (current !== model) {
|
|
12418
|
+
env[key] = model;
|
|
12419
|
+
changed = true;
|
|
12420
|
+
log(`native-hook: aligned ${key} '${String(current)}' \u2192 '${model}' for a custom upstream`);
|
|
12421
|
+
}
|
|
12422
|
+
} else {
|
|
12423
|
+
log(`native-hook: ${key} '${String(current)}' is a custom model id \u2014 left untouched`);
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
return changed;
|
|
12427
|
+
}
|
|
12224
12428
|
function mapSubToLifecycle(sub) {
|
|
12225
12429
|
switch (sub) {
|
|
12226
12430
|
case "session-start":
|
|
@@ -12786,7 +12990,15 @@ function startSquadrantd(opts = {}) {
|
|
|
12786
12990
|
log
|
|
12787
12991
|
});
|
|
12788
12992
|
const cmuxStoreSource = new CmuxStoreSource({ log });
|
|
12789
|
-
const nativeHookSource = new NativeHookSource({
|
|
12993
|
+
const nativeHookSource = new NativeHookSource({
|
|
12994
|
+
log,
|
|
12995
|
+
hookInstall: {
|
|
12996
|
+
claudeEnv: loadConfig().defaults.claudeEnv,
|
|
12997
|
+
// #P6 (§15#5): only an explicit gate mode "on" lets squadrant remove a
|
|
12998
|
+
// foreign auto-gate PermissionRequest handler; auto/absent leaves it alone.
|
|
12999
|
+
gateMode: loadConfig().defaults.gate?.mode
|
|
13000
|
+
}
|
|
13001
|
+
});
|
|
12790
13002
|
ctx.codexDriver = codexDriver;
|
|
12791
13003
|
ctx.opencodeBridge = opencodeBridge;
|
|
12792
13004
|
ctx.cmuxEventsBridge = cmuxEventsBridge;
|