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 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
- if (prev === next.state)
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 options = await deps.readModalOptions(crew);
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 key = steps >= 0 ? "Down" : "Up";
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 parseModalOptions(screen);
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) => {