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/index.js
CHANGED
|
@@ -98,6 +98,9 @@ function isGateMode(v2) {
|
|
|
98
98
|
function isGatePolicy(v2) {
|
|
99
99
|
return GATE_POLICIES.includes(v2);
|
|
100
100
|
}
|
|
101
|
+
function isGateEngine(v2) {
|
|
102
|
+
return GATE_ENGINES.includes(v2);
|
|
103
|
+
}
|
|
101
104
|
function isThinkingLevel(v2) {
|
|
102
105
|
return THINKING_LEVELS.includes(v2);
|
|
103
106
|
}
|
|
@@ -198,13 +201,14 @@ function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
|
|
|
198
201
|
function resolveHome(p) {
|
|
199
202
|
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
200
203
|
}
|
|
201
|
-
var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
204
|
+
var ROUTER_KINDS, GATE_MODES, GATE_POLICIES, GATE_ENGINES, THINKING_LEVELS, CONTROL_CHANNEL_MODES, DEFAULT_CONFIG_PATH, CONFIG_DIR, DAEMON_SOCK_PATH, DEFAULT_CREW_ROUTING_RULES;
|
|
202
205
|
var init_config = __esm({
|
|
203
206
|
"packages/shared/dist/config.js"() {
|
|
204
207
|
init_config_io();
|
|
205
208
|
ROUTER_KINDS = ["opencode-go", "openrouter", "ccr", "litellm", "custom"];
|
|
206
209
|
GATE_MODES = ["on", "off", "auto"];
|
|
207
210
|
GATE_POLICIES = ["deny-dangerous", "ask-on-doubt"];
|
|
211
|
+
GATE_ENGINES = ["router", "auto-gate"];
|
|
208
212
|
THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
209
213
|
CONTROL_CHANNEL_MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
|
|
210
214
|
DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
|
|
@@ -1659,6 +1663,7 @@ __export(dist_exports, {
|
|
|
1659
1663
|
DEFAULT_NOTIFY: () => DEFAULT_NOTIFY,
|
|
1660
1664
|
DEFAULT_ROUTER_BASE_URL: () => DEFAULT_ROUTER_BASE_URL,
|
|
1661
1665
|
DEFAULT_ROUTER_KIND: () => DEFAULT_ROUTER_KIND,
|
|
1666
|
+
GATE_ENGINES: () => GATE_ENGINES,
|
|
1662
1667
|
GATE_MODES: () => GATE_MODES,
|
|
1663
1668
|
GATE_POLICIES: () => GATE_POLICIES,
|
|
1664
1669
|
MANAGED_TARGETS: () => MANAGED_TARGETS,
|
|
@@ -1705,6 +1710,7 @@ __export(dist_exports, {
|
|
|
1705
1710
|
isBackendMode: () => isBackendMode,
|
|
1706
1711
|
isCacheStale: () => isCacheStale,
|
|
1707
1712
|
isDaemonCachedKey: () => isDaemonCachedKey,
|
|
1713
|
+
isGateEngine: () => isGateEngine,
|
|
1708
1714
|
isGateMode: () => isGateMode,
|
|
1709
1715
|
isGatePolicy: () => isGatePolicy,
|
|
1710
1716
|
isNewerVersion: () => isNewerVersion,
|
|
@@ -2027,10 +2033,11 @@ function formatDelegationReport(rec, originProject, targetProject) {
|
|
|
2027
2033
|
return null;
|
|
2028
2034
|
}
|
|
2029
2035
|
}
|
|
2030
|
-
function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
|
|
2036
|
+
function firePush(deps, project, prev, next, event, lastCaptainTurnAt, prevRec) {
|
|
2031
2037
|
if (!deps.notify)
|
|
2032
2038
|
return;
|
|
2033
|
-
|
|
2039
|
+
const questionChanged = prev === "blocked" && next.state === "blocked" && prevRec != null && (prevRec.question ?? "").trim() !== (next.question ?? "").trim();
|
|
2040
|
+
if (prev === next.state && !questionChanged)
|
|
2034
2041
|
return;
|
|
2035
2042
|
if (!ATTENTION_STATES.has(next.state))
|
|
2036
2043
|
return;
|
|
@@ -2068,6 +2075,7 @@ function createDaemon(deps) {
|
|
|
2068
2075
|
const { store, now } = deps;
|
|
2069
2076
|
const lastCaptainTurnAt = /* @__PURE__ */ new Map();
|
|
2070
2077
|
const quietNotifiedAt = /* @__PURE__ */ new Map();
|
|
2078
|
+
const blockedRenudgedFor = /* @__PURE__ */ new Map();
|
|
2071
2079
|
const resendAttemptedAt = /* @__PURE__ */ new Map();
|
|
2072
2080
|
const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;
|
|
2073
2081
|
const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;
|
|
@@ -2088,7 +2096,7 @@ function createDaemon(deps) {
|
|
|
2088
2096
|
const next = reduce(cur, event, now());
|
|
2089
2097
|
if (next !== cur) {
|
|
2090
2098
|
store.put(next);
|
|
2091
|
-
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));
|
|
2099
|
+
firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id), cur);
|
|
2092
2100
|
}
|
|
2093
2101
|
return next;
|
|
2094
2102
|
}
|
|
@@ -2240,6 +2248,8 @@ function createDaemon(deps) {
|
|
|
2240
2248
|
}
|
|
2241
2249
|
const recoveryPromises = [];
|
|
2242
2250
|
for (const r of store.listAll()) {
|
|
2251
|
+
if (r.state !== "blocked")
|
|
2252
|
+
blockedRenudgedFor.delete(r.id);
|
|
2243
2253
|
if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {
|
|
2244
2254
|
store.delete(r.project, r.id);
|
|
2245
2255
|
continue;
|
|
@@ -2302,6 +2312,27 @@ function createDaemon(deps) {
|
|
|
2302
2312
|
continue;
|
|
2303
2313
|
}
|
|
2304
2314
|
}
|
|
2315
|
+
if (r.state === "blocked" && !r.operatorHold) {
|
|
2316
|
+
const threshold = deps.blockedRenudgeMs ?? DEFAULT_BLOCKED_RENUDGE_MS;
|
|
2317
|
+
const blockedFor = t - r.lastHeartbeat;
|
|
2318
|
+
const episodeKey = r.question ?? "";
|
|
2319
|
+
if (blockedFor > threshold && blockedRenudgedFor.get(r.id) !== episodeKey) {
|
|
2320
|
+
blockedRenudgedFor.set(r.id, episodeKey);
|
|
2321
|
+
if (deps.notify) {
|
|
2322
|
+
const tag = crewTag(r);
|
|
2323
|
+
const mins = Math.max(1, Math.round(blockedFor / 6e4));
|
|
2324
|
+
const message = `CREW BLOCKED REMINDER ${tag}: still blocked ~${mins}min (reminder \u2014 same question, not a new one): ${(r.question ?? "(no question)").trim()}`;
|
|
2325
|
+
const synthEvent = { type: "task.blocked", id: r.id, reason: "renudge", question: r.question ?? "" };
|
|
2326
|
+
try {
|
|
2327
|
+
const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });
|
|
2328
|
+
if (p && typeof p.catch === "function")
|
|
2329
|
+
p.catch(() => {
|
|
2330
|
+
});
|
|
2331
|
+
} catch {
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2305
2336
|
if (r.mode === "interactive" && r.state === "submitted" && !r.firstTurnConfirmedAt) {
|
|
2306
2337
|
const undeliveredMs = t - r.createdAt;
|
|
2307
2338
|
if (undeliveredMs > firstTurnUndeliveredBudgetMs) {
|
|
@@ -2391,7 +2422,7 @@ function createDaemon(deps) {
|
|
|
2391
2422
|
}
|
|
2392
2423
|
};
|
|
2393
2424
|
}
|
|
2394
|
-
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;
|
|
2425
|
+
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;
|
|
2395
2426
|
var init_reduce = __esm({
|
|
2396
2427
|
"packages/core/dist/daemon/reduce.js"() {
|
|
2397
2428
|
init_dist();
|
|
@@ -2399,6 +2430,7 @@ var init_reduce = __esm({
|
|
|
2399
2430
|
init_watchdog();
|
|
2400
2431
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 12e4;
|
|
2401
2432
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 6e4;
|
|
2433
|
+
DEFAULT_BLOCKED_RENUDGE_MS = 30 * 60 * 1e3;
|
|
2402
2434
|
DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
|
|
2403
2435
|
TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2404
2436
|
TERMINAL_RECORD_KEEP_PER_PROJECT = 20;
|
|
@@ -4529,8 +4561,11 @@ function buildRouterEnv(creds, model) {
|
|
|
4529
4561
|
if (headers)
|
|
4530
4562
|
env.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
4531
4563
|
}
|
|
4532
|
-
if (model)
|
|
4564
|
+
if (model) {
|
|
4533
4565
|
env.ANTHROPIC_MODEL = model;
|
|
4566
|
+
env.CLAUDE_CODE_SUBAGENT_MODEL = model;
|
|
4567
|
+
env.ANTHROPIC_SMALL_FAST_MODEL = model;
|
|
4568
|
+
}
|
|
4534
4569
|
return env;
|
|
4535
4570
|
}
|
|
4536
4571
|
function mergeClaudeEnvRouterSettings(routerEnv, claudeEnv) {
|
|
@@ -8693,6 +8728,12 @@ function resolveGateMode(env, gate) {
|
|
|
8693
8728
|
return fromEnv;
|
|
8694
8729
|
return gate?.mode && isGateMode(gate.mode) ? gate.mode : "auto";
|
|
8695
8730
|
}
|
|
8731
|
+
function resolveGateEngine(env, gate) {
|
|
8732
|
+
const fromEnv = env.SQUADRANT_GATE_ENGINE;
|
|
8733
|
+
if (fromEnv !== void 0 && isGateEngine(fromEnv))
|
|
8734
|
+
return fromEnv;
|
|
8735
|
+
return gate?.engine && isGateEngine(gate.engine) ? gate.engine : "router";
|
|
8736
|
+
}
|
|
8696
8737
|
function resolveGatePolicy(env, gate) {
|
|
8697
8738
|
const fromEnv = env.SQUADRANT_GATE_POLICY;
|
|
8698
8739
|
if (fromEnv !== void 0 && isGatePolicy(fromEnv))
|
|
@@ -9077,6 +9118,59 @@ var init_permission_gate = __esm({
|
|
|
9077
9118
|
}
|
|
9078
9119
|
});
|
|
9079
9120
|
|
|
9121
|
+
// packages/core/dist/auto-gate.js
|
|
9122
|
+
import { createAutoGate, projectGateConfig } from "@squadrant-ai/auto-gate";
|
|
9123
|
+
function gateForProjection(config) {
|
|
9124
|
+
const gate = config.defaults.gate;
|
|
9125
|
+
if (!gate)
|
|
9126
|
+
return {};
|
|
9127
|
+
const out = {};
|
|
9128
|
+
if (gate.mode)
|
|
9129
|
+
out.mode = gate.mode;
|
|
9130
|
+
if (gate.tools)
|
|
9131
|
+
out.tools = gate.tools;
|
|
9132
|
+
if (gate.deny)
|
|
9133
|
+
out.deny = gate.deny;
|
|
9134
|
+
return out;
|
|
9135
|
+
}
|
|
9136
|
+
function createSquadrantAutoGate(deps) {
|
|
9137
|
+
const env = deps.env ?? process.env;
|
|
9138
|
+
const taskId = env.SQUADRANT_CREW_TASK_ID;
|
|
9139
|
+
const project = deps.project ?? env.SQUADRANT_CREW_PROJECT;
|
|
9140
|
+
const mode = resolveGateMode(env, deps.config.defaults.gate);
|
|
9141
|
+
const projected = projectGateConfig({ gate: { ...gateForProjection(deps.config), mode } });
|
|
9142
|
+
const enabled = mode === "on";
|
|
9143
|
+
const blockedSignal = async (ctx) => {
|
|
9144
|
+
if (!taskId || !project || !deps.sendBlocked)
|
|
9145
|
+
return;
|
|
9146
|
+
const event = {
|
|
9147
|
+
type: "task.blocked",
|
|
9148
|
+
id: taskId,
|
|
9149
|
+
reason: ctx.reason,
|
|
9150
|
+
question: `crew needs permission to run ${ctx.tool}`
|
|
9151
|
+
};
|
|
9152
|
+
await deps.sendBlocked(project, event);
|
|
9153
|
+
};
|
|
9154
|
+
const host = createAutoGate({
|
|
9155
|
+
config: projected,
|
|
9156
|
+
readIntent: (p) => readUserIntent(p) ?? void 0,
|
|
9157
|
+
blockedSignal,
|
|
9158
|
+
...deps.decide ? { decide: deps.decide } : {},
|
|
9159
|
+
...project ? { project } : {},
|
|
9160
|
+
env,
|
|
9161
|
+
log: deps.log
|
|
9162
|
+
});
|
|
9163
|
+
if (!enabled) {
|
|
9164
|
+
return { config: host.config, decideClaudeHookPayload: async () => void 0 };
|
|
9165
|
+
}
|
|
9166
|
+
return host;
|
|
9167
|
+
}
|
|
9168
|
+
var init_auto_gate = __esm({
|
|
9169
|
+
"packages/core/dist/auto-gate.js"() {
|
|
9170
|
+
init_permission_gate();
|
|
9171
|
+
}
|
|
9172
|
+
});
|
|
9173
|
+
|
|
9080
9174
|
// packages/core/dist/group-dispatch.js
|
|
9081
9175
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
9082
9176
|
import { createConnection as createConnection2 } from "net";
|
|
@@ -9483,10 +9577,11 @@ async function runCrewAnswer(project, name, option, runtime, workspaceId, deps,
|
|
|
9483
9577
|
if (!crew) {
|
|
9484
9578
|
throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);
|
|
9485
9579
|
}
|
|
9486
|
-
const
|
|
9487
|
-
if (!options) {
|
|
9580
|
+
const modal = await deps.readModalOptions(crew);
|
|
9581
|
+
if (!modal || modal.options.length === 0) {
|
|
9488
9582
|
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.`);
|
|
9489
9583
|
}
|
|
9584
|
+
const options = modal.options;
|
|
9490
9585
|
const target = resolveOption(options, option);
|
|
9491
9586
|
if (opts?.expect && !target.label.toLowerCase().includes(opts.expect.toLowerCase())) {
|
|
9492
9587
|
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}'.
|
|
@@ -9498,7 +9593,9 @@ ${describeOptions(options)}`);
|
|
|
9498
9593
|
log(`\u2192 selecting ${target.index}. "${target.label}"`);
|
|
9499
9594
|
const current = options.find((o) => o.highlighted) ?? options[0];
|
|
9500
9595
|
const steps = target.index - current.index;
|
|
9501
|
-
const
|
|
9596
|
+
const forwardKey = modal.axis === "horizontal" ? "Right" : "Down";
|
|
9597
|
+
const backwardKey = modal.axis === "horizontal" ? "Left" : "Up";
|
|
9598
|
+
const key = steps >= 0 ? forwardKey : backwardKey;
|
|
9502
9599
|
for (let i = 0; i < Math.abs(steps); i++) {
|
|
9503
9600
|
await runtime.sendKeyToPane(crew, key);
|
|
9504
9601
|
}
|
|
@@ -9870,6 +9967,7 @@ __export(dist_exports2, {
|
|
|
9870
9967
|
CREW_GONE_MS: () => CREW_GONE_MS,
|
|
9871
9968
|
CREW_STALE_MS: () => CREW_STALE_MS,
|
|
9872
9969
|
CaptainDelivery: () => CaptainDelivery,
|
|
9970
|
+
DEFAULT_BLOCKED_RENUDGE_MS: () => DEFAULT_BLOCKED_RENUDGE_MS,
|
|
9873
9971
|
DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS: () => DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS,
|
|
9874
9972
|
DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS: () => DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS,
|
|
9875
9973
|
DEFAULT_GATE_DENY_RULES: () => DEFAULT_GATE_DENY_RULES,
|
|
@@ -9952,6 +10050,7 @@ __export(dist_exports2, {
|
|
|
9952
10050
|
createRouterService: () => createRouterService,
|
|
9953
10051
|
createRouterShim: () => createRouterShim,
|
|
9954
10052
|
createRunCommand: () => createRunCommand,
|
|
10053
|
+
createSquadrantAutoGate: () => createSquadrantAutoGate,
|
|
9955
10054
|
createStore: () => createStore,
|
|
9956
10055
|
createSurfaceLivenessProbe: () => createSurfaceLivenessProbe,
|
|
9957
10056
|
createTelegramBridge: () => createTelegramBridge,
|
|
@@ -10093,6 +10192,7 @@ __export(dist_exports2, {
|
|
|
10093
10192
|
resolveCurrentProject: () => resolveCurrentProject,
|
|
10094
10193
|
resolveGate: () => resolveGate,
|
|
10095
10194
|
resolveGateDenyRules: () => resolveGateDenyRules,
|
|
10195
|
+
resolveGateEngine: () => resolveGateEngine,
|
|
10096
10196
|
resolveGateMode: () => resolveGateMode,
|
|
10097
10197
|
resolveGatePolicy: () => resolveGatePolicy,
|
|
10098
10198
|
resolveGateTools: () => resolveGateTools,
|
|
@@ -10198,6 +10298,7 @@ var init_dist2 = __esm({
|
|
|
10198
10298
|
init_router_resolution();
|
|
10199
10299
|
init_captain_router();
|
|
10200
10300
|
init_permission_gate();
|
|
10301
|
+
init_auto_gate();
|
|
10201
10302
|
init_restart_daemon();
|
|
10202
10303
|
init_group_dispatch();
|
|
10203
10304
|
init_launch_workspace();
|
|
@@ -13683,6 +13784,37 @@ function parseModalOptions(screen) {
|
|
|
13683
13784
|
}
|
|
13684
13785
|
return options.length > 0 ? options : null;
|
|
13685
13786
|
}
|
|
13787
|
+
function parseOpencodePermissionOptions(screen) {
|
|
13788
|
+
if (!screen)
|
|
13789
|
+
return null;
|
|
13790
|
+
if (!/△\s*Permission required/.test(screen))
|
|
13791
|
+
return null;
|
|
13792
|
+
const lines = screen.split(/\r?\n/);
|
|
13793
|
+
const bar = lines.find((l) => /⇆\s*select/.test(l));
|
|
13794
|
+
if (!bar)
|
|
13795
|
+
return null;
|
|
13796
|
+
let region = bar.split("\u21C6")[0];
|
|
13797
|
+
const fullscreenIdx = region.toLowerCase().lastIndexOf("fullscreen");
|
|
13798
|
+
if (fullscreenIdx !== -1) {
|
|
13799
|
+
region = region.slice(0, fullscreenIdx).replace(/\s+\S+\s*$/, "");
|
|
13800
|
+
}
|
|
13801
|
+
region = region.replace(/^[\s┃│╭╮╰╯|]+/, "").trim();
|
|
13802
|
+
if (!region)
|
|
13803
|
+
return null;
|
|
13804
|
+
const labels = region.split(/\s{2,}/).map((s) => s.trim()).filter(Boolean);
|
|
13805
|
+
if (labels.length === 0)
|
|
13806
|
+
return null;
|
|
13807
|
+
return labels.map((label, i) => ({ index: i + 1, label, highlighted: i === 0 }));
|
|
13808
|
+
}
|
|
13809
|
+
function parseModal(screen) {
|
|
13810
|
+
const claude = parseModalOptions(screen);
|
|
13811
|
+
if (claude)
|
|
13812
|
+
return { options: claude, axis: "vertical" };
|
|
13813
|
+
const opencode = parseOpencodePermissionOptions(screen);
|
|
13814
|
+
if (opencode)
|
|
13815
|
+
return { options: opencode, axis: "horizontal" };
|
|
13816
|
+
return null;
|
|
13817
|
+
}
|
|
13686
13818
|
function readInputBoxRaw(screen, opts) {
|
|
13687
13819
|
if (!screen)
|
|
13688
13820
|
return null;
|
|
@@ -14892,6 +15024,47 @@ function removeCommandHandlers(entries, command) {
|
|
|
14892
15024
|
}
|
|
14893
15025
|
return removed;
|
|
14894
15026
|
}
|
|
15027
|
+
function isForeignAutoGateCommand(command) {
|
|
15028
|
+
if (typeof command !== "string")
|
|
15029
|
+
return false;
|
|
15030
|
+
if (command.includes(FOREIGN_AUTO_GATE_MARKER))
|
|
15031
|
+
return true;
|
|
15032
|
+
const first = command.trim().split(/\s+/)[0] ?? "";
|
|
15033
|
+
return /(^|[/\\])auto-gate$/.test(first);
|
|
15034
|
+
}
|
|
15035
|
+
function findForeignGateHandlers(entries) {
|
|
15036
|
+
const found = [];
|
|
15037
|
+
for (const entry of entries) {
|
|
15038
|
+
const hooks = entry?.hooks;
|
|
15039
|
+
if (!Array.isArray(hooks))
|
|
15040
|
+
continue;
|
|
15041
|
+
for (const h of hooks) {
|
|
15042
|
+
const cmd = h?.command;
|
|
15043
|
+
if (isForeignAutoGateCommand(cmd))
|
|
15044
|
+
found.push(cmd);
|
|
15045
|
+
}
|
|
15046
|
+
}
|
|
15047
|
+
return found;
|
|
15048
|
+
}
|
|
15049
|
+
function removeForeignGateHandlers(entries) {
|
|
15050
|
+
const removed = [];
|
|
15051
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
15052
|
+
const entry = entries[i];
|
|
15053
|
+
if (!Array.isArray(entry?.hooks))
|
|
15054
|
+
continue;
|
|
15055
|
+
entry.hooks = entry.hooks.filter((h) => {
|
|
15056
|
+
const cmd = h?.command;
|
|
15057
|
+
if (isForeignAutoGateCommand(cmd)) {
|
|
15058
|
+
removed.push(cmd);
|
|
15059
|
+
return false;
|
|
15060
|
+
}
|
|
15061
|
+
return true;
|
|
15062
|
+
});
|
|
15063
|
+
if (entry.hooks.length === 0)
|
|
15064
|
+
entries.splice(i, 1);
|
|
15065
|
+
}
|
|
15066
|
+
return removed;
|
|
15067
|
+
}
|
|
14895
15068
|
function installClaudeHooks(opts = {}) {
|
|
14896
15069
|
const settingsPath = opts.settingsPath ?? join25(homedir13(), ".claude", "settings.json");
|
|
14897
15070
|
const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;
|
|
@@ -14929,6 +15102,23 @@ function installClaudeHooks(opts = {}) {
|
|
|
14929
15102
|
changed = true;
|
|
14930
15103
|
migrated.push(`${eventName}/${sub}`);
|
|
14931
15104
|
}
|
|
15105
|
+
const foreign = findForeignGateHandlers(entries);
|
|
15106
|
+
if (foreign.length > 0) {
|
|
15107
|
+
if (opts.gateMode === "on") {
|
|
15108
|
+
const removed = removeForeignGateHandlers(entries);
|
|
15109
|
+
if (removed.length > 0)
|
|
15110
|
+
changed = true;
|
|
15111
|
+
for (const command2 of removed) {
|
|
15112
|
+
log(`native-hook: removed foreign auto-gate PermissionRequest handler in ${settingsPath} [#P6 \xA715#5]: ${command2}`);
|
|
15113
|
+
opts.recordGateAction?.({ action: "removed-foreign-gate", command: command2 });
|
|
15114
|
+
}
|
|
15115
|
+
} else {
|
|
15116
|
+
for (const command2 of foreign) {
|
|
15117
|
+
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}`);
|
|
15118
|
+
opts.recordGateAction?.({ action: "left-foreign-gate", command: command2 });
|
|
15119
|
+
}
|
|
15120
|
+
}
|
|
15121
|
+
}
|
|
14932
15122
|
}
|
|
14933
15123
|
const alreadyPresent = entries.some((m) => Array.isArray(m.hooks) && m.hooks.some((h) => typeof h.command === "string" && h.command === command));
|
|
14934
15124
|
if (!alreadyPresent) {
|
|
@@ -14959,11 +15149,55 @@ function installClaudeHooks(opts = {}) {
|
|
|
14959
15149
|
changed = true;
|
|
14960
15150
|
}
|
|
14961
15151
|
}
|
|
15152
|
+
if (typeof settings.env === "object" && settings.env !== null && !Array.isArray(settings.env)) {
|
|
15153
|
+
if (reconcileSubagentModels(settings.env, log))
|
|
15154
|
+
changed = true;
|
|
15155
|
+
}
|
|
14962
15156
|
if (changed) {
|
|
14963
15157
|
writeFile6(settingsPath, JSON.stringify(settings, null, 2));
|
|
14964
15158
|
}
|
|
14965
15159
|
return settingsPath;
|
|
14966
15160
|
}
|
|
15161
|
+
function isCustomUpstream(baseUrl) {
|
|
15162
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0)
|
|
15163
|
+
return false;
|
|
15164
|
+
try {
|
|
15165
|
+
return new URL(baseUrl).hostname.toLowerCase() !== "api.anthropic.com";
|
|
15166
|
+
} catch {
|
|
15167
|
+
return false;
|
|
15168
|
+
}
|
|
15169
|
+
}
|
|
15170
|
+
function isAnthropicModelId(value) {
|
|
15171
|
+
if (typeof value !== "string")
|
|
15172
|
+
return false;
|
|
15173
|
+
const v2 = value.trim().toLowerCase();
|
|
15174
|
+
return v2 === "sonnet" || v2 === "opus" || v2 === "haiku" || v2.startsWith("claude-");
|
|
15175
|
+
}
|
|
15176
|
+
function reconcileSubagentModels(env, log) {
|
|
15177
|
+
if (!isCustomUpstream(env.ANTHROPIC_BASE_URL))
|
|
15178
|
+
return false;
|
|
15179
|
+
const model = env.ANTHROPIC_MODEL;
|
|
15180
|
+
if (typeof model !== "string" || model.length === 0)
|
|
15181
|
+
return false;
|
|
15182
|
+
let changed = false;
|
|
15183
|
+
for (const key of [SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY]) {
|
|
15184
|
+
const current = env[key];
|
|
15185
|
+
if (current === void 0) {
|
|
15186
|
+
env[key] = model;
|
|
15187
|
+
changed = true;
|
|
15188
|
+
log(`native-hook: set ${key}='${model}' to match ANTHROPIC_MODEL on a custom upstream`);
|
|
15189
|
+
} else if (isAnthropicModelId(current)) {
|
|
15190
|
+
if (current !== model) {
|
|
15191
|
+
env[key] = model;
|
|
15192
|
+
changed = true;
|
|
15193
|
+
log(`native-hook: aligned ${key} '${String(current)}' \u2192 '${model}' for a custom upstream`);
|
|
15194
|
+
}
|
|
15195
|
+
} else {
|
|
15196
|
+
log(`native-hook: ${key} '${String(current)}' is a custom model id \u2014 left untouched`);
|
|
15197
|
+
}
|
|
15198
|
+
}
|
|
15199
|
+
return changed;
|
|
15200
|
+
}
|
|
14967
15201
|
function mapSubToLifecycle(sub) {
|
|
14968
15202
|
switch (sub) {
|
|
14969
15203
|
case "session-start":
|
|
@@ -15013,7 +15247,7 @@ function defaultWriteFile(path39, content) {
|
|
|
15013
15247
|
mkdirSync8(path39.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
15014
15248
|
writeFileSync11(path39, content, "utf-8");
|
|
15015
15249
|
}
|
|
15016
|
-
var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, GATE_HOOK_CMD, NativeHookSource;
|
|
15250
|
+
var CLAUDE_HOOK_EVENTS, DEFAULT_HOOK_CMD, GATE_HOOK_CMD, FOREIGN_AUTO_GATE_MARKER, SUBAGENT_MODEL_KEY, SMALL_FAST_MODEL_KEY, NativeHookSource;
|
|
15017
15251
|
var init_native_hook_source = __esm({
|
|
15018
15252
|
"packages/workspaces/dist/native-hooks/native-hook-source.js"() {
|
|
15019
15253
|
CLAUDE_HOOK_EVENTS = [
|
|
@@ -15033,6 +15267,9 @@ var init_native_hook_source = __esm({
|
|
|
15033
15267
|
];
|
|
15034
15268
|
DEFAULT_HOOK_CMD = "squadrant hooks";
|
|
15035
15269
|
GATE_HOOK_CMD = "squadrant gate";
|
|
15270
|
+
FOREIGN_AUTO_GATE_MARKER = "auto-gate-managed";
|
|
15271
|
+
SUBAGENT_MODEL_KEY = "CLAUDE_CODE_SUBAGENT_MODEL";
|
|
15272
|
+
SMALL_FAST_MODEL_KEY = "ANTHROPIC_SMALL_FAST_MODEL";
|
|
15036
15273
|
NativeHookSource = class {
|
|
15037
15274
|
name = "native-hook";
|
|
15038
15275
|
hookInstall;
|
|
@@ -15166,7 +15403,7 @@ async function paneHasOpenModal(runtime, pane) {
|
|
|
15166
15403
|
}
|
|
15167
15404
|
async function readModalOptions(runtime, pane) {
|
|
15168
15405
|
const screen = await runtime.readPaneScreen(pane) ?? "";
|
|
15169
|
-
return
|
|
15406
|
+
return parseModal(screen);
|
|
15170
15407
|
}
|
|
15171
15408
|
async function confirmedSendToPane(runtime, pane, message) {
|
|
15172
15409
|
const preSendScreen = await runtime.readPaneScreen(pane) ?? "";
|
|
@@ -22349,6 +22586,30 @@ init_dist();
|
|
|
22349
22586
|
init_dist3();
|
|
22350
22587
|
import { Command as Command32 } from "commander";
|
|
22351
22588
|
var SOCK4 = DAEMON_SOCK_PATH;
|
|
22589
|
+
async function runAutoGateClaudeHook(o) {
|
|
22590
|
+
const make = o.deps.createAutoGate ?? createSquadrantAutoGate;
|
|
22591
|
+
const autoGate = make({
|
|
22592
|
+
config: o.config,
|
|
22593
|
+
env: o.env,
|
|
22594
|
+
...o.project ? { project: o.project } : {},
|
|
22595
|
+
...o.deps.sendEvent ? { sendBlocked: o.deps.sendEvent } : {},
|
|
22596
|
+
log: o.log
|
|
22597
|
+
});
|
|
22598
|
+
const out = await autoGate.decideClaudeHookPayload(o.deps.rawPayload ?? "");
|
|
22599
|
+
if (out === void 0) {
|
|
22600
|
+
o.log("gate auto-gate: ask/yield \u2014 normal dialog");
|
|
22601
|
+
return { decision: "ask", tier: 2, reason: "auto-gate: ask/yield" };
|
|
22602
|
+
}
|
|
22603
|
+
o.stdout(out);
|
|
22604
|
+
let decision = "allow";
|
|
22605
|
+
try {
|
|
22606
|
+
const parsed = JSON.parse(out);
|
|
22607
|
+
if (parsed?.hookSpecificOutput?.decision?.behavior === "deny") decision = "deny";
|
|
22608
|
+
} catch {
|
|
22609
|
+
}
|
|
22610
|
+
o.log(`gate auto-gate ${decision}`);
|
|
22611
|
+
return { decision, tier: 2, reason: "auto-gate" };
|
|
22612
|
+
}
|
|
22352
22613
|
async function runGatePermissionRequest(deps) {
|
|
22353
22614
|
const env = deps.env ?? process.env;
|
|
22354
22615
|
const payload = deps.payload ?? {};
|
|
@@ -22363,6 +22624,10 @@ async function runGatePermissionRequest(deps) {
|
|
|
22363
22624
|
const log = deps.log ?? ((m) => process.stderr.write(`[squadrant] ${m}
|
|
22364
22625
|
`));
|
|
22365
22626
|
const config = deps.config ?? (deps.loadConfigFn ?? loadConfig)();
|
|
22627
|
+
const gate = config.defaults.gate;
|
|
22628
|
+
if (resolveGateMode(env, gate) === "on" && resolveGateEngine(env, gate) === "auto-gate") {
|
|
22629
|
+
return runAutoGateClaudeHook({ deps, env, config, project, stdout, log });
|
|
22630
|
+
}
|
|
22366
22631
|
const cwd = deps.cwd ?? (typeof p.cwd === "string" && p.cwd ? p.cwd : env.SQUADRANT_CREW_CWD ?? process.cwd());
|
|
22367
22632
|
const transcriptPath = typeof p.transcript_path === "string" && p.transcript_path ? p.transcript_path : deriveTranscriptPath(
|
|
22368
22633
|
typeof p.session_id === "string" ? p.session_id : "",
|
|
@@ -22426,6 +22691,7 @@ function gateCommand() {
|
|
|
22426
22691
|
}
|
|
22427
22692
|
await runGatePermissionRequest({
|
|
22428
22693
|
payload,
|
|
22694
|
+
rawPayload: stdin,
|
|
22429
22695
|
env: process.env,
|
|
22430
22696
|
stdout: (s) => process.stdout.write(s),
|
|
22431
22697
|
sendEvent: async (project, ev) => {
|
|
@@ -22508,6 +22774,7 @@ function hooksCommand() {
|
|
|
22508
22774
|
if (sub === "permission-request") {
|
|
22509
22775
|
await runGatePermissionRequest({
|
|
22510
22776
|
payload,
|
|
22777
|
+
rawPayload: stdin,
|
|
22511
22778
|
env: process.env,
|
|
22512
22779
|
stdout: (s) => process.stdout.write(s),
|
|
22513
22780
|
sendEvent: async (project2, ev2) => {
|