@atlasharness/atlascode 0.1.80 → 0.1.82

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.
Files changed (2) hide show
  1. package/dist/cli.js +1494 -333
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -19862,7 +19862,7 @@ var init_openai = __esm(() => {
19862
19862
  });
19863
19863
 
19864
19864
  // src/modelprovider/constants.ts
19865
- var API_IMAGE_MAX_BASE64_SIZE, IMAGE_TARGET_RAW_SIZE, PDF_TARGET_RAW_SIZE, API_PDF_MAX_PAGES = 100, LLM_TIMEOUT_DEFAULT_MS = 900000, LLM_TIMEOUT_CAP_MS = 1800000, LLM_TIMEOUT_UNIT_WARN_THRESHOLD_MS = 60000, STREAM_IDLE_TIMEOUT_MS = 300000, SUMMARIZE_CONNECTOR_TEXT_BETA_HEADER, AFK_MODE_BETA_HEADER;
19865
+ var API_IMAGE_MAX_BASE64_SIZE, IMAGE_TARGET_RAW_SIZE, PDF_TARGET_RAW_SIZE, API_PDF_MAX_PAGES = 100, LLM_TIMEOUT_DEFAULT_MS = 900000, LLM_TIMEOUT_CAP_MS = 1800000, LLM_TIMEOUT_UNIT_WARN_THRESHOLD_MS = 60000, STREAM_IDLE_TIMEOUT_MS = 300000, STREAM_IDLE_RETURN_GRACE_MS = 1000, SUMMARIZE_CONNECTOR_TEXT_BETA_HEADER, AFK_MODE_BETA_HEADER;
19866
19866
  var init_constants2 = __esm(() => {
19867
19867
  init_shared();
19868
19868
  API_IMAGE_MAX_BASE64_SIZE = 5 * 1024 * 1024;
@@ -19901,7 +19901,7 @@ var init_clients = __esm(() => {
19901
19901
  });
19902
19902
 
19903
19903
  // src/modelprovider/streamIdleWatchdog.ts
19904
- async function* withStreamIdleWatchdog(stream, timeoutMs = STREAM_IDLE_TIMEOUT_MS) {
19904
+ async function* withStreamIdleWatchdog(stream, timeoutMs = STREAM_IDLE_TIMEOUT_MS, returnGraceMs = STREAM_IDLE_RETURN_GRACE_MS) {
19905
19905
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
19906
19906
  yield* stream;
19907
19907
  return;
@@ -19933,7 +19933,19 @@ async function* withStreamIdleWatchdog(stream, timeoutMs = STREAM_IDLE_TIMEOUT_M
19933
19933
  }
19934
19934
  } finally {
19935
19935
  try {
19936
- await iterator.return?.();
19936
+ const ret = iterator.return?.();
19937
+ if (ret) {
19938
+ let graceTimer;
19939
+ const grace = new Promise((resolve) => {
19940
+ graceTimer = setTimeout(resolve, returnGraceMs);
19941
+ if (typeof graceTimer === "object" && graceTimer !== null && "unref" in graceTimer) {
19942
+ graceTimer.unref();
19943
+ }
19944
+ });
19945
+ await Promise.race([ret.then(() => {
19946
+ return;
19947
+ }, () => {}), grace]);
19948
+ }
19937
19949
  } catch {}
19938
19950
  }
19939
19951
  }
@@ -27201,6 +27213,12 @@ function isQuotaExhaustedMessage(message) {
27201
27213
  }
27202
27214
  return false;
27203
27215
  }
27216
+ function parseToolSchemaRejectionError(err) {
27217
+ if (!(err instanceof APIError2) || err.status !== 400)
27218
+ return null;
27219
+ const m = /invalid schema for (?:tool|function) [`'"]?([A-Za-z0-9_.\-]+)[`'"]?/i.exec(err.message);
27220
+ return m ? m[1] : null;
27221
+ }
27204
27222
  function classifyAPIError(error) {
27205
27223
  if (error instanceof Error && error.message === "Request was aborted.") {
27206
27224
  return "aborted";
@@ -27341,6 +27359,8 @@ function shouldRetryModelError(err, signal) {
27341
27359
  return false;
27342
27360
  if (isClientRequestTimeout(err))
27343
27361
  return false;
27362
+ if (parseToolSchemaRejectionError(err))
27363
+ return false;
27344
27364
  return isRetryableError(err);
27345
27365
  }
27346
27366
  function llmTimeoutUnitHint(ms) {
@@ -29134,6 +29154,11 @@ var DEFAULT_HOOK_SHELL = "bash";
29134
29154
  var init_getMatchingHooks = () => {};
29135
29155
 
29136
29156
  // src/hooks/runHooks.ts
29157
+ function sessionEndHookTimeoutMs() {
29158
+ const raw = process.env.ATLAS_SESSIONEND_HOOKS_TIMEOUT_MS;
29159
+ const parsed = raw ? Number(raw) : NaN;
29160
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : SESSION_END_HOOK_TIMEOUT_MS_DEFAULT;
29161
+ }
29137
29162
  function isSimpleModeHooksSkipped() {
29138
29163
  const raw = process.env.ATLAS_SIMPLE;
29139
29164
  if (raw === undefined)
@@ -29253,6 +29278,7 @@ ${r.additionalContext}` : r.additionalContext;
29253
29278
  if (!acc.permissionBehavior || PERMISSION_RANK[r.permissionBehavior] > PERMISSION_RANK[acc.permissionBehavior]) {
29254
29279
  acc.permissionBehavior = r.permissionBehavior;
29255
29280
  acc.permissionDecisionReason = r.permissionDecisionReason;
29281
+ acc.permissionDecisionSource = r.source;
29256
29282
  }
29257
29283
  }
29258
29284
  }
@@ -29270,12 +29296,14 @@ function runOneHook(m, port, env, signal, options) {
29270
29296
  if (result.blockingError && cmd.continueOnBlock === true) {
29271
29297
  result.continueOnBlock = true;
29272
29298
  }
29299
+ result.source = m.source;
29273
29300
  return result;
29274
29301
  }, (e) => ({
29275
29302
  command: cmd.command,
29276
29303
  status: -1,
29277
29304
  succeeded: false,
29278
- stderr: e instanceof Error ? e.message : String(e)
29305
+ stderr: e instanceof Error ? e.message : String(e),
29306
+ source: m.source
29279
29307
  }));
29280
29308
  }
29281
29309
  async function runHooks(hookEvent, hookInput, options = {}) {
@@ -29330,7 +29358,15 @@ async function runStopHooks(options = {}) {
29330
29358
  const hookInput = { ...base, hook_event_name: "Stop" };
29331
29359
  return runHooks("Stop", hookInput, options);
29332
29360
  }
29333
- var HOOK_EXECUTION_TIMEOUT_MS, PERMISSION_RANK;
29361
+ async function runSessionEndHooks(reason, options = {}) {
29362
+ const base = createBaseHookInput(options.permissionMode, options.sessionId, options.agentInfo);
29363
+ const hookInput = { ...base, hook_event_name: "SessionEnd", reason };
29364
+ return runHooks("SessionEnd", hookInput, {
29365
+ ...options,
29366
+ timeoutMs: options.timeoutMs ?? sessionEndHookTimeoutMs()
29367
+ });
29368
+ }
29369
+ var MAX_HOOK_CONCURRENCY = 12, HOOK_EXECUTION_TIMEOUT_MS, SESSION_END_HOOK_TIMEOUT_MS_DEFAULT = 1500, PERMISSION_RANK;
29334
29370
  var init_runHooks = __esm(() => {
29335
29371
  init_createBaseHookInput();
29336
29372
  init_getMatchingHooks();
@@ -29366,9 +29402,20 @@ async function* runHooksStream(hookEvent, hookInput, options = {}) {
29366
29402
  toolUseID: options.toolUseID
29367
29403
  };
29368
29404
  }
29369
- const started = matched.map((m) => runOneHook(m, port, env, signal, options));
29370
- for (const pending of started) {
29371
- const result = await pending;
29405
+ const slots = [];
29406
+ let nextStart = 0;
29407
+ const startNext = () => {
29408
+ if (nextStart < matched.length) {
29409
+ slots[nextStart] = runOneHook(matched[nextStart], port, env, signal, options);
29410
+ nextStart++;
29411
+ }
29412
+ };
29413
+ for (let i = 0;i < Math.min(MAX_HOOK_CONCURRENCY, matched.length); i++) {
29414
+ startNext();
29415
+ }
29416
+ for (let i = 0;i < matched.length; i++) {
29417
+ const result = await slots[i];
29418
+ startNext();
29372
29419
  results.push(result);
29373
29420
  yield { kind: "hook_result", result };
29374
29421
  mergeAggregated(aggregated, result);
@@ -29509,8 +29556,8 @@ var SettingsSchema = () => object({
29509
29556
  mcpServers: record(string2(), any()).optional(),
29510
29557
  allowedMcpServers: any().optional(),
29511
29558
  deniedMcpServers: any().optional(),
29512
- enabledMcpjsonServers: array(string2()).optional(),
29513
- disabledMcpjsonServers: array(string2()).optional(),
29559
+ enabledMcpjsonServers: union([string2(), array(string2())]).transform((v) => Array.isArray(v) ? v : [v]).optional(),
29560
+ disabledMcpjsonServers: union([string2(), array(string2())]).transform((v) => Array.isArray(v) ? v : [v]).optional(),
29514
29561
  sandbox: any().optional(),
29515
29562
  permissions: any().optional(),
29516
29563
  hooks: lazy(() => HooksSchema).optional(),
@@ -30144,7 +30191,7 @@ var init_filesystem = __esm(() => {
30144
30191
 
30145
30192
  // src/permissions/permissionRuleParser.ts
30146
30193
  function setLegacyToolNameAliases(map) {
30147
- legacyToolNameAliases = map;
30194
+ legacyToolNameAliases = Object.assign(Object.create(null), map);
30148
30195
  }
30149
30196
  function normalizeLegacyToolName(name) {
30150
30197
  return legacyToolNameAliases[name] ?? name;
@@ -30219,7 +30266,7 @@ function findLastUnescapedChar(str, char) {
30219
30266
  }
30220
30267
  var legacyToolNameAliases;
30221
30268
  var init_permissionRuleParser = __esm(() => {
30222
- legacyToolNameAliases = {};
30269
+ legacyToolNameAliases = Object.create(null);
30223
30270
  });
30224
30271
 
30225
30272
  // src/permissions/mcpRuleNames.ts
@@ -30228,6 +30275,9 @@ function normalizeNameForMCP(name) {
30228
30275
  const normalized = folded.replace(/_+/g, "_").replace(/^_|_$/g, "");
30229
30276
  return normalized === "" ? folded : normalized;
30230
30277
  }
30278
+ function foldMcpNameForCompare(name) {
30279
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_");
30280
+ }
30231
30281
  function mcpInfoFromString(toolString) {
30232
30282
  const parts = toolString.split("__");
30233
30283
  const [mcpPart, serverName, ...toolNameParts] = parts;
@@ -30297,9 +30347,17 @@ function toolMatchesRule(tool, rule) {
30297
30347
  if (rule.ruleValue.toolName === nameForRuleMatch) {
30298
30348
  return true;
30299
30349
  }
30300
- const ruleInfo = mcpInfoFromString(rule.ruleValue.toolName);
30301
- const toolInfo = mcpInfoFromString(nameForRuleMatch);
30302
- return ruleInfo !== null && toolInfo !== null && (ruleInfo.toolName === undefined || ruleInfo.toolName === "*") && ruleInfo.serverName === toolInfo.serverName;
30350
+ const toolInfo = tool.mcpInfo ?? mcpInfoFromString(nameForRuleMatch);
30351
+ const ruleName = rule.ruleValue.toolName;
30352
+ if (!toolInfo || !ruleName.startsWith("mcp__")) {
30353
+ return false;
30354
+ }
30355
+ if (foldMcpNameForCompare(ruleName) === foldMcpNameForCompare(nameForRuleMatch)) {
30356
+ return true;
30357
+ }
30358
+ const rest = ruleName.slice("mcp__".length);
30359
+ const ruleServerRaw = rest.endsWith("__*") ? rest.slice(0, -3) : rest;
30360
+ return foldMcpNameForCompare(ruleServerRaw) === foldMcpNameForCompare(toolInfo.serverName);
30303
30361
  }
30304
30362
  function toolAlwaysAllowedRule(context, tool) {
30305
30363
  return getAllowRules(context).find((rule) => toolMatchesRule(tool, rule)) || null;
@@ -30512,12 +30570,22 @@ function applyDontAskMode(decision, permissionContext, toolName) {
30512
30570
  }
30513
30571
  return decision;
30514
30572
  }
30515
- var BASH_TOOL_NAME = "Bash", hasPermissionsToUseTool = async (tool, input, context, _assistantMessage, _toolUseID, forceDecision) => {
30573
+ var BASH_TOOL_NAME = "Bash", PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES, PLAN_MODE_WRITE_DENIED_MESSAGE = (toolName) => `Plan mode is active — ${toolName} is not allowed to make changes. ` + `Present your plan and use ExitPlanMode, or ask the user to change the permission mode.`, hasPermissionsToUseTool = async (tool, input, context, _assistantMessage, _toolUseID, forceDecision) => {
30516
30574
  const permissionContext = context.getToolPermissionContext?.();
30517
30575
  if (forceDecision !== undefined) {
30518
30576
  return applyDontAskMode(forceDecision, permissionContext, tool.name);
30519
30577
  }
30520
30578
  const sandbox = getSandboxAccess();
30579
+ if (permissionContext?.mode === "plan" && !permissionContext.isBypassPermissionsModeAvailable && !PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES.has(tool.name) && !(tool.isReadOnly?.(input) ?? false)) {
30580
+ return {
30581
+ behavior: "deny",
30582
+ message: PLAN_MODE_WRITE_DENIED_MESSAGE(tool.name),
30583
+ decisionReason: {
30584
+ type: "mode",
30585
+ mode: "plan"
30586
+ }
30587
+ };
30588
+ }
30521
30589
  const denyRule = permissionContext ? getDenyRuleForTool(permissionContext, tool) : null;
30522
30590
  if (denyRule) {
30523
30591
  return {
@@ -30636,6 +30704,10 @@ var init_permissions2 = __esm(() => {
30636
30704
  init_ruleMatching();
30637
30705
  init_sandboxAccess();
30638
30706
  init_denialMessages();
30707
+ PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES = new Set([
30708
+ "EnterPlanMode",
30709
+ "ExitPlanMode"
30710
+ ]);
30639
30711
  });
30640
30712
 
30641
30713
  // src/permissions/permissionMode.ts
@@ -36851,6 +36923,15 @@ function filterToolsForAgent({
36851
36923
  function computeChildSpawnDepth(parentOptions) {
36852
36924
  return (parentOptions?.spawnDepth ?? 0) + 1;
36853
36925
  }
36926
+ function resolveMcpServerDomainTools(toolName, available) {
36927
+ if (typeof toolName !== "string")
36928
+ return null;
36929
+ const info = mcpInfoFromString(toolName);
36930
+ if (!info || info.toolName !== undefined)
36931
+ return null;
36932
+ const prefix = getMcpPrefix(info.serverName);
36933
+ return available.filter((t) => t.name.startsWith(prefix));
36934
+ }
36854
36935
  function resolveAgentTools(agentDefinition, availableTools, allowFanOut = false) {
36855
36936
  const { tools: agentTools, disallowedTools, source } = agentDefinition;
36856
36937
  const filtered = filterToolsForAgent({
@@ -36881,7 +36962,18 @@ function resolveAgentTools(agentDefinition, availableTools, allowFanOut = false)
36881
36962
  seen.add(tool);
36882
36963
  }
36883
36964
  } else {
36884
- invalidTools.push(spec);
36965
+ const domain = resolveMcpServerDomainTools(toolName, allowed);
36966
+ if (domain && domain.length > 0) {
36967
+ validTools.push(spec);
36968
+ for (const t of domain) {
36969
+ if (!seen.has(t)) {
36970
+ resolved.push(t);
36971
+ seen.add(t);
36972
+ }
36973
+ }
36974
+ } else {
36975
+ invalidTools.push(spec);
36976
+ }
36885
36977
  }
36886
36978
  }
36887
36979
  return { hasWildcard: false, validTools, invalidTools, resolvedTools: resolved };
@@ -46561,9 +46653,67 @@ var init_bashCommandHelpers = __esm(() => {
46561
46653
  });
46562
46654
 
46563
46655
  // src/engine/tools/bash/bashReadOnly.ts
46656
+ function globPatternCouldMatchWriteAction(pattern) {
46657
+ let regex = "^";
46658
+ let i = 0;
46659
+ while (i < pattern.length) {
46660
+ const c = pattern[i];
46661
+ if (c === "*") {
46662
+ regex += ".*";
46663
+ i++;
46664
+ continue;
46665
+ }
46666
+ if (c === "?") {
46667
+ regex += ".";
46668
+ i++;
46669
+ continue;
46670
+ }
46671
+ if (c === "[") {
46672
+ const close = pattern.indexOf("]", i + 1);
46673
+ if (close > i) {
46674
+ let cls = pattern.slice(i + 1, close);
46675
+ if (cls.startsWith("!"))
46676
+ cls = `^${cls.slice(1)}`;
46677
+ regex += `[${cls}]`;
46678
+ i = close + 1;
46679
+ continue;
46680
+ }
46681
+ }
46682
+ regex += c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46683
+ i++;
46684
+ }
46685
+ regex += "$";
46686
+ let re;
46687
+ try {
46688
+ re = new RegExp(regex);
46689
+ } catch {
46690
+ return true;
46691
+ }
46692
+ for (const action of FIND_WRITE_ACTION_TOKENS) {
46693
+ if (re.test(action))
46694
+ return true;
46695
+ }
46696
+ return false;
46697
+ }
46698
+ function isAllowedNonStringToken(token) {
46699
+ if (typeof token !== "object" || token === null)
46700
+ return false;
46701
+ const t = token;
46702
+ if (t.comment !== undefined)
46703
+ return true;
46704
+ if (t.op === "<")
46705
+ return true;
46706
+ if (t.op === "glob") {
46707
+ if (typeof t.pattern !== "string")
46708
+ return false;
46709
+ return !globPatternCouldMatchWriteAction(t.pattern);
46710
+ }
46711
+ return false;
46712
+ }
46564
46713
  function matchReadOnlyPrefix(command) {
46565
46714
  const trimmed = command.trim();
46566
- if (trimmed.includes("&&") || trimmed.includes("||") || trimmed.includes(";") || trimmed.includes("|") || trimmed.includes("`") || trimmed.includes("$(") || trimmed.includes(">")) {
46715
+ if (trimmed.includes("&&") || trimmed.includes("||") || trimmed.includes(";") || trimmed.includes("|") || trimmed.includes("`") || trimmed.includes("$(") || trimmed.includes(">") || trimmed.includes(`
46716
+ `) || trimmed.includes("\r") || trimmed.includes("-delete") || trimmed.includes("-exec") || trimmed.includes("-ok") || trimmed.includes("-fprint") || trimmed.includes("-fls")) {
46567
46717
  return null;
46568
46718
  }
46569
46719
  let best = null;
@@ -46573,13 +46723,31 @@ function matchReadOnlyPrefix(command) {
46573
46723
  best = p;
46574
46724
  }
46575
46725
  }
46726
+ if (best === null)
46727
+ return null;
46728
+ const parsed = tryParseShellCommand(trimmed, (key) => `$${key}`);
46729
+ if (!parsed.success) {
46730
+ return null;
46731
+ }
46732
+ for (const token of parsed.tokens) {
46733
+ if (typeof token === "string") {
46734
+ if (FIND_WRITE_ACTION_TOKENS.has(token))
46735
+ return null;
46736
+ if (token.includes("[") && globPatternCouldMatchWriteAction(token)) {
46737
+ return null;
46738
+ }
46739
+ } else if (!isAllowedNonStringToken(token)) {
46740
+ return null;
46741
+ }
46742
+ }
46576
46743
  return best;
46577
46744
  }
46578
46745
  function isReadOnlyCommand(command) {
46579
46746
  return matchReadOnlyPrefix(command) !== null;
46580
46747
  }
46581
- var READ_ONLY_PREFIXES;
46748
+ var READ_ONLY_PREFIXES, FIND_WRITE_ACTION_TOKENS;
46582
46749
  var init_bashReadOnly = __esm(() => {
46750
+ init_shellQuote();
46583
46751
  READ_ONLY_PREFIXES = [
46584
46752
  "ls",
46585
46753
  "cat",
@@ -46606,6 +46774,17 @@ var init_bashReadOnly = __esm(() => {
46606
46774
  "printenv",
46607
46775
  "type"
46608
46776
  ];
46777
+ FIND_WRITE_ACTION_TOKENS = new Set([
46778
+ "-delete",
46779
+ "-exec",
46780
+ "-execdir",
46781
+ "-ok",
46782
+ "-okdir",
46783
+ "-fprint",
46784
+ "-fprint0",
46785
+ "-fprintf",
46786
+ "-fls"
46787
+ ]);
46609
46788
  });
46610
46789
 
46611
46790
  // src/engine/tools/bash/abortError.ts
@@ -75284,6 +75463,27 @@ var init_tasks3 = __esm(() => {
75284
75463
  init_taskOutputTool();
75285
75464
  });
75286
75465
 
75466
+ // src/engine/skill/commandNameSafety.ts
75467
+ function isUnsafeCommandName(name) {
75468
+ if (name === "")
75469
+ return true;
75470
+ if (name.includes("<") || name.includes(">"))
75471
+ return true;
75472
+ return UNSAFE_NAME_CHARS_RE.test(name);
75473
+ }
75474
+ function sanitizeDisplayName(displayName, canonicalName) {
75475
+ if (displayName === undefined || !isUnsafeCommandName(displayName)) {
75476
+ return displayName;
75477
+ }
75478
+ logForDebugging(`Discarding unsafe frontmatter display name ${JSON.stringify(displayName)} for ${canonicalName}; falling back to canonical name`);
75479
+ return;
75480
+ }
75481
+ var UNSAFE_NAME_CHARS_RE;
75482
+ var init_commandNameSafety = __esm(() => {
75483
+ init_shared();
75484
+ UNSAFE_NAME_CHARS_RE = /[\u0000-\u001F\u007F-\u00A0\u00AD\u034F\u061C\u070F\u1680\u180E\u2000-\u200F\u2028\u2029\u202A-\u202E\u202F\u205F\u2060-\u2069\u3000\uFE00-\uFE0F\uFEFF\u{E0100}-\u{E01EF}]/u;
75485
+ });
75486
+
75287
75487
  // src/engine/skill/types.ts
75288
75488
  function getCommandName(cmd) {
75289
75489
  return cmd.userFacingName?.() ?? cmd.name;
@@ -75406,44 +75606,129 @@ function parseArgumentNames(argumentNames) {
75406
75606
  return [];
75407
75607
  }
75408
75608
  const isValidName = (name) => typeof name === "string" && name.trim() !== "" && !/^\d+$/.test(name);
75609
+ const cap = (names) => names.map((name) => name.slice(0, MAX_ARGUMENT_NAME_CHARS)).slice(0, MAX_ARGUMENT_NAMES);
75409
75610
  if (Array.isArray(argumentNames)) {
75410
- return argumentNames.filter(isValidName);
75611
+ return cap(argumentNames.filter(isValidName));
75411
75612
  }
75412
75613
  if (typeof argumentNames === "string") {
75413
- return argumentNames.split(/\s+/).filter(isValidName);
75614
+ return cap(argumentNames.split(/\s+/).filter(isValidName));
75414
75615
  }
75415
75616
  return [];
75416
75617
  }
75417
- function substituteArguments(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
75618
+ function isWordChar2(c) {
75619
+ return c !== undefined && /[A-Za-z0-9_]/.test(c);
75620
+ }
75621
+ function matchPlaceholderAt(content, i, argumentNames) {
75622
+ if (content[i] !== "$")
75623
+ return null;
75624
+ for (let n = 0;n < argumentNames.length; n++) {
75625
+ const name = argumentNames[n];
75626
+ if (!name)
75627
+ continue;
75628
+ if (content.startsWith(`$${name}`, i)) {
75629
+ const after = content[i + 1 + name.length];
75630
+ if (after !== "[" && !isWordChar2(after)) {
75631
+ return { length: 1 + name.length, kind: "named", nameIndex: n };
75632
+ }
75633
+ }
75634
+ }
75635
+ INDEXED_PLACEHOLDER_RE.lastIndex = i;
75636
+ const indexed = INDEXED_PLACEHOLDER_RE.exec(content);
75637
+ if (indexed) {
75638
+ return {
75639
+ length: indexed[0].length,
75640
+ kind: "indexed",
75641
+ argIndex: parseInt(indexed[1], 10)
75642
+ };
75643
+ }
75644
+ if (content.startsWith("$ARGUMENTS", i)) {
75645
+ return { length: "$ARGUMENTS".length, kind: "bare" };
75646
+ }
75647
+ SHORTHAND_PLACEHOLDER_RE.lastIndex = i;
75648
+ const shorthand = SHORTHAND_PLACEHOLDER_RE.exec(content);
75649
+ if (shorthand && !isWordChar2(content[i + shorthand[0].length])) {
75650
+ return {
75651
+ length: shorthand[0].length,
75652
+ kind: "shorthand",
75653
+ argIndex: parseInt(shorthand[1], 10)
75654
+ };
75655
+ }
75656
+ return null;
75657
+ }
75658
+ function countPlaceholderOccurrences(content, argumentNames) {
75659
+ let count = 0;
75660
+ let i = 0;
75661
+ while (i < content.length) {
75662
+ const m = matchPlaceholderAt(content, i, argumentNames);
75663
+ if (m) {
75664
+ count++;
75665
+ i += m.length;
75666
+ } else {
75667
+ i++;
75668
+ }
75669
+ }
75670
+ return count;
75671
+ }
75672
+ function substituteArgumentsImpl(content, args, appendIfNoPlaceholder, argumentNames, strict) {
75418
75673
  if (args === undefined || args === null) {
75419
75674
  return content;
75420
75675
  }
75421
- const parsedArgs = parseArguments(args);
75422
- const originalContent = content;
75423
- for (let i = 0;i < argumentNames.length; i++) {
75424
- const name = argumentNames[i];
75425
- if (!name)
75426
- continue;
75427
- content = content.replace(new RegExp(`\\$${name}(?![\\[\\w])`, "g"), parsedArgs[i] ?? "");
75676
+ const placeholderCount = countPlaceholderOccurrences(content, argumentNames);
75677
+ let effectiveArgs = args;
75678
+ if (placeholderCount > 0 && content.length + placeholderCount * args.length > MAX_SUBSTITUTION_OUTPUT_CHARS) {
75679
+ if (strict)
75680
+ return null;
75681
+ const budgetPerReplacement = Math.max(0, Math.floor((MAX_SUBSTITUTION_OUTPUT_CHARS - content.length) / placeholderCount));
75682
+ effectiveArgs = args.slice(0, budgetPerReplacement);
75683
+ } else if (strict) {
75684
+ const appendLen = appendIfNoPlaceholder && args ? `
75685
+
75686
+ ARGUMENTS: `.length + args.length : 0;
75687
+ if (content.length + appendLen > MAX_SUBSTITUTION_OUTPUT_CHARS) {
75688
+ return null;
75689
+ }
75428
75690
  }
75429
- content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, indexStr) => {
75430
- const index = parseInt(indexStr, 10);
75431
- return parsedArgs[index] ?? "";
75432
- });
75433
- content = content.replace(/\$(\d+)(?!\w)/g, (_, indexStr) => {
75434
- const index = parseInt(indexStr, 10);
75435
- return parsedArgs[index] ?? "";
75436
- });
75437
- content = content.replaceAll("$ARGUMENTS", args);
75438
- if (content === originalContent && appendIfNoPlaceholder && args) {
75439
- content = content + `
75691
+ const parsedArgs = parseArguments(effectiveArgs);
75692
+ let out = "";
75693
+ const appendValue = (value) => {
75694
+ if (out.length + value.length > MAX_SUBSTITUTION_OUTPUT_CHARS) {
75695
+ value = value.slice(0, Math.max(0, MAX_SUBSTITUTION_OUTPUT_CHARS - out.length));
75696
+ }
75697
+ out += value;
75698
+ };
75699
+ let matched = false;
75700
+ let i = 0;
75701
+ while (i < content.length) {
75702
+ const m = matchPlaceholderAt(content, i, argumentNames);
75703
+ if (m) {
75704
+ matched = true;
75705
+ if (m.kind === "bare")
75706
+ appendValue(effectiveArgs);
75707
+ else if (m.kind === "named")
75708
+ appendValue(parsedArgs[m.nameIndex] ?? "");
75709
+ else
75710
+ appendValue(parsedArgs[m.argIndex] ?? "");
75711
+ i += m.length;
75712
+ } else {
75713
+ out += content[i];
75714
+ i++;
75715
+ }
75716
+ }
75717
+ if (!matched && appendIfNoPlaceholder && args) {
75718
+ out = out + `
75440
75719
 
75441
75720
  ARGUMENTS: ${args}`;
75442
75721
  }
75443
- return content;
75722
+ return out;
75723
+ }
75724
+ function substituteArguments(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
75725
+ return substituteArgumentsImpl(content, args, appendIfNoPlaceholder, argumentNames, false) ?? content;
75444
75726
  }
75727
+ var MAX_ARGUMENT_NAMES = 64, MAX_ARGUMENT_NAME_CHARS = 64, MAX_SUBSTITUTION_OUTPUT_CHARS = 1e6, INDEXED_PLACEHOLDER_RE, SHORTHAND_PLACEHOLDER_RE;
75445
75728
  var init_argumentSubstitution = __esm(() => {
75446
75729
  init_bash();
75730
+ INDEXED_PLACEHOLDER_RE = /\$ARGUMENTS\[(\d+)\]/y;
75731
+ SHORTHAND_PLACEHOLDER_RE = /\$(\d+)/y;
75447
75732
  });
75448
75733
 
75449
75734
  // src/engine/skill/skillModel.ts
@@ -76880,7 +77165,7 @@ function parseSkillFrontmatterFields(frontmatter, markdownContent, resolvedName,
76880
77165
  logForDebugging(`Skill ${resolvedName} has invalid effort '${effortRaw}'. Valid options: ${EFFORT_LEVELS.join(", ")} or an integer`);
76881
77166
  }
76882
77167
  return {
76883
- displayName: frontmatter.name != null ? String(frontmatter.name) : undefined,
77168
+ displayName: sanitizeDisplayName(frontmatter.name != null ? String(frontmatter.name) : undefined, resolvedName),
76884
77169
  description,
76885
77170
  hasUserSpecifiedDescription: validatedDescription !== null,
76886
77171
  allowedTools: parseSlashCommandToolsFromFrontmatter(frontmatter["allowed-tools"]),
@@ -76991,6 +77276,7 @@ var init_skillCommand = __esm(() => {
76991
77276
  init_frontmatterFields();
76992
77277
  init_markdownLoader();
76993
77278
  init_skillModel();
77279
+ init_commandNameSafety();
76994
77280
  init_promptShellExecution();
76995
77281
  });
76996
77282
 
@@ -77030,6 +77316,10 @@ async function loadSkillsFromSkillsDir(basePath, source) {
77030
77316
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
77031
77317
  return null;
77032
77318
  }
77319
+ if (isUnsafeCommandName(entry.name)) {
77320
+ logForDebugging(`[skills] skipping unsafe skill directory name: ${JSON.stringify(entry.name)}`);
77321
+ return null;
77322
+ }
77033
77323
  const skillDirPath = join32(basePath, entry.name);
77034
77324
  const skillFilePath = join32(skillDirPath, "SKILL.md");
77035
77325
  let content;
@@ -77138,6 +77428,10 @@ async function loadSkillsFromCommandsDir(cwd) {
77138
77428
  content,
77139
77429
  source
77140
77430
  });
77431
+ if (isUnsafeCommandName(cmdName)) {
77432
+ logForDebugging(`[skills] skipping unsafe command name: ${JSON.stringify(cmdName)}`);
77433
+ continue;
77434
+ }
77141
77435
  const parsed = parseSkillFrontmatterFields(frontmatter, content, cmdName, "Custom command");
77142
77436
  skills.push({
77143
77437
  skill: createSkillCommand({
@@ -77177,6 +77471,7 @@ var init_loadSkillsDir = __esm(() => {
77177
77471
  init_memory();
77178
77472
  init_config();
77179
77473
  init_messaging();
77474
+ init_commandNameSafety();
77180
77475
  init_gitignore();
77181
77476
  init_markdownLoader();
77182
77477
  init_skillCommand();
@@ -77592,6 +77887,7 @@ var init_pluginIdentifier = __esm(() => {
77592
77887
 
77593
77888
  // src/engine/skill/index.ts
77594
77889
  var init_skill = __esm(() => {
77890
+ init_commandNameSafety();
77595
77891
  init_frontmatterFields();
77596
77892
  init_argumentSubstitution();
77597
77893
  init_skillModel();
@@ -77645,6 +77941,14 @@ import { randomUUID as randomUUID14 } from "crypto";
77645
77941
  async function getAllCommands() {
77646
77942
  return getCommands(process.cwd());
77647
77943
  }
77944
+ function buildUnknownSkillMessage(skillName, commands) {
77945
+ const available = commands.filter((c) => !c.isHidden && !c.disableModelInvocation).map((c) => c.userFacingName?.() ?? c.name);
77946
+ if (available.length === 0)
77947
+ return `Unknown skill: ${skillName}`;
77948
+ const listed = available.slice(0, MAX_UNKNOWN_SKILL_SUGGESTIONS);
77949
+ const more = available.length - listed.length;
77950
+ return `Unknown skill: ${skillName}. Available skills: ${listed.join(", ")}${more > 0 ? ` (and ${more} more)` : ""}`;
77951
+ }
77648
77952
  function getToolUseIDFromParentMessage(parentMessage, toolName) {
77649
77953
  if (!parentMessage)
77650
77954
  return;
@@ -77712,7 +78016,7 @@ function skillHasOnlySafeProperties(command) {
77712
78016
  }
77713
78017
  return true;
77714
78018
  }
77715
- var SKILL_TOOL_INPUT_SCHEMA, SAFE_SKILL_PROPERTIES, SkillTool;
78019
+ var SKILL_TOOL_INPUT_SCHEMA, MAX_UNKNOWN_SKILL_SUGGESTIONS = 10, SAFE_SKILL_PROPERTIES, SkillTool;
77716
78020
  var init_skillTool = __esm(() => {
77717
78021
  init_shared();
77718
78022
  init_permissions3();
@@ -77884,7 +78188,7 @@ var init_skillTool = __esm(() => {
77884
78188
  if (!foundCommand) {
77885
78189
  return {
77886
78190
  result: false,
77887
- message: `Unknown skill: ${normalizedCommandName}`,
78191
+ message: buildUnknownSkillMessage(normalizedCommandName, commands),
77888
78192
  errorCode: 2
77889
78193
  };
77890
78194
  }
@@ -78400,7 +78704,7 @@ function getMcpDiscoveryInput() {
78400
78704
  return discoveryInput;
78401
78705
  }
78402
78706
  async function buildMcpServerConfigs(input) {
78403
- const servers = {};
78707
+ const servers = Object.create(null);
78404
78708
  for (const [name, raw] of Object.entries(input.settingsServers ?? {})) {
78405
78709
  const parsed = parseMcpServerConfig(raw);
78406
78710
  if (parsed) {
@@ -78631,6 +78935,7 @@ async function terminateChild(name, child) {
78631
78935
  function createMcpConnectionManager() {
78632
78936
  const connections = new Map;
78633
78937
  const pending = new Set;
78938
+ const pendingConnects = new Map;
78634
78939
  const connectFailures = new Map;
78635
78940
  function fail(name, config, error, authFailure = false) {
78636
78941
  const conn = {
@@ -78643,6 +78948,17 @@ function createMcpConnectionManager() {
78643
78948
  connections.set(name, conn);
78644
78949
  return conn;
78645
78950
  }
78951
+ function markBlocked(name, config, reason) {
78952
+ const conn = {
78953
+ name,
78954
+ type: "failed",
78955
+ config,
78956
+ error: `MCP server "${name}" blocked by enterprise policy: ${reason}`,
78957
+ blockedByPolicy: true
78958
+ };
78959
+ connections.set(name, conn);
78960
+ return conn;
78961
+ }
78646
78962
  async function connectStdio(name, config, opts) {
78647
78963
  const finalCommand = process.env.ATLAS_SHELL_PREFIX || config.command;
78648
78964
  const finalArgs = process.env.ATLAS_SHELL_PREFIX ? [[config.command, ...config.args ?? []].join(" ")] : config.args ?? [];
@@ -78700,7 +79016,7 @@ function createMcpConnectionManager() {
78700
79016
  };
78701
79017
  client.onClose(closeOnDrop);
78702
79018
  const initPromise = client.request("initialize", {
78703
- protocolVersion: "2025-03-26",
79019
+ protocolVersion: "2026-07-28",
78704
79020
  capabilities: {
78705
79021
  elicitation: {},
78706
79022
  roots: {}
@@ -78727,6 +79043,7 @@ function createMcpConnectionManager() {
78727
79043
  version: init.serverInfo.version
78728
79044
  }
78729
79045
  } : {},
79046
+ ...init.protocolVersion ? { negotiatedProtocolVersion: init.protocolVersion } : {},
78730
79047
  ...init.instructions ? { instructions: init.instructions } : {},
78731
79048
  config,
78732
79049
  cleanup: async () => {
@@ -78753,6 +79070,21 @@ function createMcpConnectionManager() {
78753
79070
  }
78754
79071
  }
78755
79072
  async function connect(name, config, opts) {
79073
+ const existing = connections.get(name);
79074
+ if (existing?.type === "connected")
79075
+ return existing;
79076
+ const inflight = pendingConnects.get(name);
79077
+ if (inflight)
79078
+ return inflight;
79079
+ const attempt = connectOnce(name, config, opts);
79080
+ pendingConnects.set(name, attempt);
79081
+ try {
79082
+ return await attempt;
79083
+ } finally {
79084
+ pendingConnects.delete(name);
79085
+ }
79086
+ }
79087
+ async function connectOnce(name, config, opts) {
78756
79088
  const existing = connections.get(name);
78757
79089
  if (existing?.type === "connected")
78758
79090
  return existing;
@@ -78794,7 +79126,8 @@ function createMcpConnectionManager() {
78794
79126
  await manager.close(name);
78795
79127
  }
78796
79128
  },
78797
- getPendingServerNames: () => [...pending]
79129
+ getPendingServerNames: () => [...pending],
79130
+ markBlocked
78798
79131
  };
78799
79132
  return manager;
78800
79133
  }
@@ -78809,6 +79142,143 @@ var init_mcpConnectionManager = __esm(() => {
78809
79142
  init_mcpFetch();
78810
79143
  });
78811
79144
 
79145
+ // src/mcp/mcpPolicy.ts
79146
+ function getMcpPolicyFromSettings(settings) {
79147
+ if (typeof settings !== "object" || settings === null)
79148
+ return {};
79149
+ const rec = settings;
79150
+ return {
79151
+ ...rec.allowedMcpServers !== undefined ? { allowedMcpServers: rec.allowedMcpServers } : {},
79152
+ ...rec.deniedMcpServers !== undefined ? { deniedMcpServers: rec.deniedMcpServers } : {}
79153
+ };
79154
+ }
79155
+ function getMcpPolicyInput() {
79156
+ return policyInput;
79157
+ }
79158
+ function normalizePolicyEntries(raw) {
79159
+ if (!Array.isArray(raw))
79160
+ return [];
79161
+ const out = [];
79162
+ for (const entry of raw) {
79163
+ if (typeof entry === "string") {
79164
+ out.push({ kind: "name", value: entry });
79165
+ continue;
79166
+ }
79167
+ if (typeof entry !== "object" || entry === null)
79168
+ continue;
79169
+ const rec = entry;
79170
+ if (typeof rec.serverName === "string") {
79171
+ out.push({ kind: "name", value: rec.serverName });
79172
+ } else if (Array.isArray(rec.serverCommand) && rec.serverCommand.every((c) => typeof c === "string")) {
79173
+ out.push({ kind: "command", value: rec.serverCommand });
79174
+ } else if (typeof rec.serverUrl === "string") {
79175
+ out.push({ kind: "url", value: rec.serverUrl });
79176
+ }
79177
+ }
79178
+ return out;
79179
+ }
79180
+ function getServerCommandArray(config) {
79181
+ const t = config.type;
79182
+ if (t !== undefined && t !== "stdio") {
79183
+ return null;
79184
+ }
79185
+ const stdioConfig = config;
79186
+ return [stdioConfig.command, ...stdioConfig.args ?? []];
79187
+ }
79188
+ function commandArraysMatch(a, b) {
79189
+ if (a.length !== b.length) {
79190
+ return false;
79191
+ }
79192
+ return a.every((val, idx) => val === b[idx]);
79193
+ }
79194
+ function getServerUrl(config) {
79195
+ return "url" in config ? config.url : null;
79196
+ }
79197
+ function urlPatternToRegex(pattern) {
79198
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
79199
+ const regexStr = escaped.replace(/\*/g, ".*");
79200
+ return new RegExp(`^${regexStr}$`);
79201
+ }
79202
+ function urlMatchesPattern(url, pattern) {
79203
+ return urlPatternToRegex(pattern).test(url);
79204
+ }
79205
+ function isDeniedByEntries(serverName, config, entries) {
79206
+ for (const entry of entries) {
79207
+ if (entry.kind === "name" && entry.value === serverName)
79208
+ return true;
79209
+ }
79210
+ const serverCommand = getServerCommandArray(config);
79211
+ if (serverCommand) {
79212
+ for (const entry of entries) {
79213
+ if (entry.kind === "command" && commandArraysMatch(entry.value, serverCommand)) {
79214
+ return true;
79215
+ }
79216
+ }
79217
+ }
79218
+ const serverUrl = getServerUrl(config);
79219
+ if (serverUrl) {
79220
+ for (const entry of entries) {
79221
+ if (entry.kind === "url" && urlMatchesPattern(serverUrl, entry.value)) {
79222
+ return true;
79223
+ }
79224
+ }
79225
+ }
79226
+ return false;
79227
+ }
79228
+ function isAllowedByEntries(serverName, config, entries) {
79229
+ const hasCommandEntries = entries.some((e) => e.kind === "command");
79230
+ const hasUrlEntries = entries.some((e) => e.kind === "url");
79231
+ const serverCommand = getServerCommandArray(config);
79232
+ const serverUrl = getServerUrl(config);
79233
+ if (serverCommand) {
79234
+ if (hasCommandEntries) {
79235
+ return entries.some((e) => e.kind === "command" && commandArraysMatch(e.value, serverCommand));
79236
+ }
79237
+ return entries.some((e) => e.kind === "name" && e.value === serverName);
79238
+ }
79239
+ if (serverUrl) {
79240
+ if (hasUrlEntries) {
79241
+ return entries.some((e) => e.kind === "url" && urlMatchesPattern(serverUrl, e.value));
79242
+ }
79243
+ return entries.some((e) => e.kind === "name" && e.value === serverName);
79244
+ }
79245
+ return entries.some((e) => e.kind === "name" && e.value === serverName);
79246
+ }
79247
+ function checkMcpServerPolicy(serverName, config, policy) {
79248
+ const p = policy ?? getMcpPolicyInput() ?? {};
79249
+ const denied = normalizePolicyEntries(p.deniedMcpServers);
79250
+ if (isDeniedByEntries(serverName, config, denied)) {
79251
+ return { blocked: true, reason: "denied by policy (deniedMcpServers)" };
79252
+ }
79253
+ if (!Array.isArray(p.allowedMcpServers))
79254
+ return { blocked: false };
79255
+ if (p.allowedMcpServers.length === 0) {
79256
+ return {
79257
+ blocked: true,
79258
+ reason: "not in allowedMcpServers allowlist (empty allowlist blocks all servers)"
79259
+ };
79260
+ }
79261
+ const allowed = normalizePolicyEntries(p.allowedMcpServers);
79262
+ if (isAllowedByEntries(serverName, config, allowed)) {
79263
+ return { blocked: false };
79264
+ }
79265
+ return { blocked: true, reason: "not in allowedMcpServers allowlist" };
79266
+ }
79267
+ function filterMcpServersByPolicy(configs, policy) {
79268
+ const allowed = {};
79269
+ const blocked = [];
79270
+ for (const [name, config] of Object.entries(configs)) {
79271
+ const verdict = checkMcpServerPolicy(name, config, policy);
79272
+ if (verdict.blocked) {
79273
+ blocked.push({ name, reason: verdict.reason ?? "blocked by policy" });
79274
+ } else {
79275
+ allowed[name] = config;
79276
+ }
79277
+ }
79278
+ return { allowed, blocked };
79279
+ }
79280
+ var policyInput = null;
79281
+
78812
79282
  // src/mcp/index.ts
78813
79283
  var init_mcp3 = __esm(() => {
78814
79284
  init_types6();
@@ -78833,6 +79303,24 @@ function classifyToolError(error) {
78833
79303
  return "UnknownError";
78834
79304
  }
78835
79305
 
79306
+ // src/engine/pipeline/sanitizeUpdatedInput.ts
79307
+ function isPlainObject3(value) {
79308
+ if (typeof value !== "object" || value === null)
79309
+ return false;
79310
+ const proto = Object.getPrototypeOf(value);
79311
+ return proto === Object.prototype || proto === null;
79312
+ }
79313
+ function sanitizeUpdatedInput(originalInput, updatedInput) {
79314
+ if (!isPlainObject3(originalInput) || !isPlainObject3(updatedInput)) {
79315
+ return originalInput;
79316
+ }
79317
+ const result = {};
79318
+ for (const key of Object.keys(originalInput)) {
79319
+ result[key] = key in updatedInput ? updatedInput[key] : originalInput[key];
79320
+ }
79321
+ return result;
79322
+ }
79323
+
78836
79324
  // src/engine/pipeline/schemaValidation.ts
78837
79325
  function typeName(v) {
78838
79326
  if (v === null)
@@ -78993,7 +79481,7 @@ async function runWithToolHeartbeat(opts, fn) {
78993
79481
  var DEFAULT_TOOL_HEARTBEAT_INTERVAL_MS = 30000, MIN_TOOL_HEARTBEAT_INTERVAL_MS = 100;
78994
79482
 
78995
79483
  // src/engine/pipeline/toolExecution.ts
78996
- function mergeHookPermission(hookBehavior, gate, hookReason) {
79484
+ function mergeHookPermission(hookBehavior, gate, hookReason, hookSource) {
78997
79485
  if (hookBehavior === "defer") {
78998
79486
  return {
78999
79487
  allowed: false,
@@ -79007,7 +79495,8 @@ function mergeHookPermission(hookBehavior, gate, hookReason) {
79007
79495
  if (!gate.allowed)
79008
79496
  return gate;
79009
79497
  if (hookBehavior === "ask") {
79010
- return { allowed: false, ask: true, reason: "hook requested confirmation" };
79498
+ const reason = hookSource && hookSource !== "settings" ? `hook requested confirmation (from ${hookSource})` : "hook requested confirmation";
79499
+ return { allowed: false, ask: true, reason };
79011
79500
  }
79012
79501
  return gate;
79013
79502
  }
@@ -79078,12 +79567,12 @@ async function executeToolUse(tu, assistantMsg, deps) {
79078
79567
  isError: true
79079
79568
  };
79080
79569
  }
79081
- const effectiveInput = preOutcome?.updatedInput ?? tu.input;
79570
+ const effectiveInput = preOutcome?.updatedInput !== undefined ? sanitizeUpdatedInput(tu.input, preOutcome.updatedInput) : tu.input;
79082
79571
  const gateVerdict = deps.checkPermission ? await deps.checkPermission(tool, effectiveInput, {
79083
79572
  toolUseId: tu.id,
79084
79573
  assistantMessage: assistantMsg
79085
79574
  }) : { allowed: true };
79086
- const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict, preOutcome?.hookPermissionDecisionReason);
79575
+ const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict, preOutcome?.hookPermissionDecisionReason, preOutcome?.hookSource);
79087
79576
  if (verdict.deferred) {
79088
79577
  return { deferred: true, deferReason: verdict.reason, isError: false };
79089
79578
  }
@@ -79909,12 +80398,12 @@ var LEGACY_TOOL_NAME_ALIASES;
79909
80398
  var init_legacyToolNameAliases = __esm(() => {
79910
80399
  init_permissions3();
79911
80400
  init_toolNames();
79912
- LEGACY_TOOL_NAME_ALIASES = {
80401
+ LEGACY_TOOL_NAME_ALIASES = Object.assign(Object.create(null), {
79913
80402
  Task: AGENT_TOOL_NAME,
79914
80403
  KillShell: TASK_STOP_TOOL_NAME,
79915
80404
  AgentOutputTool: TASK_OUTPUT_TOOL_NAME,
79916
80405
  BashOutputTool: TASK_OUTPUT_TOOL_NAME
79917
- };
80406
+ });
79918
80407
  setLegacyToolNameAliases(LEGACY_TOOL_NAME_ALIASES);
79919
80408
  });
79920
80409
 
@@ -82800,6 +83289,8 @@ async function queryAgentLoop(deps, args) {
82800
83289
  let turns = 0;
82801
83290
  let terminated = false;
82802
83291
  let lastRound;
83292
+ let activeTools = tools;
83293
+ const degradedTools = new Set;
82803
83294
  const maxGoalTurns = args.context?.maxGoalTurns ?? DEFAULT_MAX_GOAL_TURNS;
82804
83295
  let goalTurns = 0;
82805
83296
  let deferredToolUse;
@@ -82842,6 +83333,24 @@ async function queryAgentLoop(deps, args) {
82842
83333
  deps.emit?.({ type: "compacted", turn: turns, messages });
82843
83334
  }
82844
83335
  }
83336
+ if (deps.refreshRoundContext) {
83337
+ try {
83338
+ const refresh = deps.refreshRoundContext(messages);
83339
+ if (refresh) {
83340
+ if (refresh.tools) {
83341
+ activeTools = degradedTools.size > 0 ? refresh.tools.filter((t) => !degradedTools.has(t.name)) : refresh.tools;
83342
+ }
83343
+ if (refresh.appendedMessages && refresh.appendedMessages.length > 0) {
83344
+ messages = [...messages, ...refresh.appendedMessages];
83345
+ if (deps.transcript) {
83346
+ await deps.transcript.record(messages);
83347
+ }
83348
+ }
83349
+ }
83350
+ } catch (refreshErr) {
83351
+ logForDebugging(`[round-context-refresh] failed: ${errorMessage(refreshErr)}`);
83352
+ }
83353
+ }
82845
83354
  deps.emit?.({ type: "round_start", turn: turns });
82846
83355
  let reactiveRetried = false;
82847
83356
  let effectiveDeps = deps;
@@ -82849,7 +83358,7 @@ async function queryAgentLoop(deps, args) {
82849
83358
  for (;; ) {
82850
83359
  const roundInputLen = messages.length;
82851
83360
  try {
82852
- lastRound = await queryOneRound(effectiveDeps, tools, messages);
83361
+ lastRound = await queryOneRound(effectiveDeps, activeTools, messages);
82853
83362
  } catch (err) {
82854
83363
  if (!reactiveRetried && deps.reactiveCompact !== undefined && isReactiveCompactEnabled() && isReactiveCompactRecoverableError(err)) {
82855
83364
  reactiveRetried = true;
@@ -82872,6 +83381,13 @@ async function queryAgentLoop(deps, args) {
82872
83381
  continue;
82873
83382
  }
82874
83383
  }
83384
+ const rejectedTool = parseToolSchemaRejectionError(err);
83385
+ if (rejectedTool && !degradedTools.has(rejectedTool) && activeTools.some((t) => t.name === rejectedTool)) {
83386
+ degradedTools.add(rejectedTool);
83387
+ activeTools = activeTools.filter((t) => t.name !== rejectedTool);
83388
+ logForDebugging(`[tool-schema-strip] 网关拒绝工具 '${rejectedTool}' 的 input schema,本会话剥除其参数后续跑(tools ${activeTools.length} 个)`);
83389
+ continue;
83390
+ }
82875
83391
  const nextRef = nextFallbackRef(deps.fallbackModels, triedFallbackRefs);
82876
83392
  if (nextRef && shouldRetryModelError(err, deps.signal)) {
82877
83393
  triedFallbackRefs.add(nextRef);
@@ -83058,6 +83574,7 @@ function createToolHooks(opts = {}) {
83058
83574
  updatedInput: r.updatedInput,
83059
83575
  hookBehavior: r.permissionBehavior,
83060
83576
  hookPermissionDecisionReason: r.permissionDecisionReason,
83577
+ hookSource: r.permissionDecisionSource,
83061
83578
  preventContinuation: r.preventContinuation,
83062
83579
  stopReason: r.stopReason
83063
83580
  };
@@ -83346,6 +83863,7 @@ var init_state2 = () => {};
83346
83863
  var init_hooks2 = __esm(() => {
83347
83864
  init_toolHooks();
83348
83865
  init_hooks();
83866
+ init_hooks();
83349
83867
  });
83350
83868
 
83351
83869
  // src/engine/goal/goalCheckIn.ts
@@ -83434,6 +83952,7 @@ var init_engine = __esm(() => {
83434
83952
  init_permissions4();
83435
83953
  init_hooks2();
83436
83954
  init_hooks2();
83955
+ init_hooks2();
83437
83956
  init_config();
83438
83957
  init_messaging();
83439
83958
  init_messaging();
@@ -86861,6 +87380,19 @@ var init_hooksWiring = __esm(() => {
86861
87380
  init_hooks();
86862
87381
  });
86863
87382
 
87383
+ // src/cli/headlessSessionEnd.ts
87384
+ async function runHeadlessSessionEndHooks(reason) {
87385
+ try {
87386
+ await runSessionEndHooks(reason);
87387
+ } catch (error) {
87388
+ logForDebugging2(`SessionEnd hooks (headless exit, reason=${reason}) failed: ${String(error)}`, { level: "error" });
87389
+ }
87390
+ }
87391
+ var init_headlessSessionEnd = __esm(() => {
87392
+ init_hooks();
87393
+ init_debugSink();
87394
+ });
87395
+
86864
87396
  // src/cli/print.ts
86865
87397
  import { randomUUID as randomUUID21 } from "crypto";
86866
87398
  import { readFile as readFile12 } from "node:fs/promises";
@@ -86960,7 +87492,12 @@ async function connectMcpStartup(args) {
86960
87492
  }) : {};
86961
87493
  const dynamicNames = new Set(Object.keys(dynamicConfigs));
86962
87494
  const allConfigs = strictMcpConfig ? dynamicConfigs : { ...baseConfigs, ...dynamicConfigs };
86963
- const connectAll = () => Promise.allSettled(Object.entries(allConfigs).map(([name, config]) => manager.connect(name, config, dynamicNames.has(name) ? { connectionTimeoutMs: 5000 } : undefined)));
87495
+ const policy = getMcpPolicyInput() ?? getMcpPolicyFromSettings(getSettingsWithErrors().settings);
87496
+ const { allowed, blocked } = filterMcpServersByPolicy(allConfigs, policy);
87497
+ for (const b of blocked) {
87498
+ manager.markBlocked(b.name, allConfigs[b.name], b.reason);
87499
+ }
87500
+ const connectAll = () => Promise.allSettled(Object.entries(allowed).map(([name, config]) => manager.connect(name, config, dynamicNames.has(name) ? { connectionTimeoutMs: 5000 } : undefined)));
86964
87501
  const nonBlocking = isMcpConnectionNonBlocking();
86965
87502
  if (nonBlocking) {
86966
87503
  connectAll();
@@ -87615,6 +88152,7 @@ async function runHeadless(inputPrompt, options) {
87615
88152
  cronScheduler.stop();
87616
88153
  await drainToOutput();
87617
88154
  await drainPendingBackgroundAgents(abortController.signal);
88155
+ await runHeadlessSessionEndHooks("prompt_input_exit");
87618
88156
  } catch (error) {
87619
88157
  cronScheduler.stop();
87620
88158
  logForDebugging2(headlessErrorLine(error), { level: "error" });
@@ -87633,6 +88171,7 @@ async function runHeadless(inputPrompt, options) {
87633
88171
  });
87634
88172
  } catch {}
87635
88173
  abortController.abort();
88174
+ await runHeadlessSessionEndHooks("other");
87636
88175
  process.exitCode = 1;
87637
88176
  }
87638
88177
  await flushDebugSink();
@@ -87683,6 +88222,7 @@ var init_print = __esm(() => {
87683
88222
  init_headlessPrompt();
87684
88223
  init_debugSink();
87685
88224
  init_hooksWiring();
88225
+ init_headlessSessionEnd();
87686
88226
  });
87687
88227
 
87688
88228
  // src/cli/handlers/update.ts
@@ -88219,6 +88759,10 @@ async function mapConcurrent(items, fn, concurrency) {
88219
88759
  return results;
88220
88760
  }
88221
88761
  async function checkMcpServerHealth(manager, name, server) {
88762
+ const policy = getMcpPolicyInput() ?? getMcpPolicyFromSettings(getSettingsWithErrors().settings);
88763
+ if (checkMcpServerPolicy(name, server, policy).blocked) {
88764
+ return "✗ Blocked by enterprise policy";
88765
+ }
88222
88766
  try {
88223
88767
  const result = await manager.connect(name, server);
88224
88768
  if (result.type === "connected") {
@@ -88514,6 +89058,7 @@ Warning: The command "${actualCommand}" looks like a URL, but is being interpret
88514
89058
  }
88515
89059
  var init_mcp4 = __esm(() => {
88516
89060
  init_mcp3();
89061
+ init_engine();
88517
89062
  init_entryInit();
88518
89063
  init_mcpConfigWrite();
88519
89064
  init_setup();
@@ -89066,7 +89611,7 @@ var init_cli = __esm(() => {
89066
89611
  });
89067
89612
 
89068
89613
  // node_modules/is-plain-obj/index.js
89069
- function isPlainObject3(value) {
89614
+ function isPlainObject4(value) {
89070
89615
  if (typeof value !== "object" || value === null) {
89071
89616
  return false;
89072
89617
  }
@@ -89088,7 +89633,7 @@ var init_file_url = () => {};
89088
89633
  // node_modules/execa/lib/methods/parameters.js
89089
89634
  var normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => {
89090
89635
  const filePath = safeNormalizeFileUrl(rawFile, "First argument");
89091
- const [commandArguments, options] = isPlainObject3(rawArguments) ? [[], rawArguments] : [rawArguments, rawOptions];
89636
+ const [commandArguments, options] = isPlainObject4(rawArguments) ? [[], rawArguments] : [rawArguments, rawOptions];
89092
89637
  if (!Array.isArray(commandArguments)) {
89093
89638
  throw new TypeError(`Second argument must be either an array of arguments or an options object: ${commandArguments}`);
89094
89639
  }
@@ -89100,7 +89645,7 @@ var normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => {
89100
89645
  if (nullByteArgument !== undefined) {
89101
89646
  throw new TypeError(`Arguments cannot contain null bytes ("\\0"): ${nullByteArgument}`);
89102
89647
  }
89103
- if (!isPlainObject3(options)) {
89648
+ if (!isPlainObject4(options)) {
89104
89649
  throw new TypeError(`Last argument must be an options object: ${options}`);
89105
89650
  }
89106
89651
  return [filePath, normalizedArguments, { __proto__: null, ...options }];
@@ -89222,7 +89767,7 @@ var isTemplateString = (templates) => Array.isArray(templates) && Array.isArray(
89222
89767
  if (typeOfExpression === "number") {
89223
89768
  return String(expression);
89224
89769
  }
89225
- if (isPlainObject3(expression) && (("stdout" in expression) || ("isMaxBuffer" in expression))) {
89770
+ if (isPlainObject4(expression) && (("stdout" in expression) || ("isMaxBuffer" in expression))) {
89226
89771
  return getSubprocessResult(expression);
89227
89772
  }
89228
89773
  if (expression instanceof ChildProcess || Object.prototype.toString.call(expression) === "[object Promise]") {
@@ -89269,7 +89814,7 @@ var normalizeFdSpecificOptions = (options) => {
89269
89814
  const optionBaseArray = Array.from({ length: stdioLength + 1 });
89270
89815
  const optionArray = normalizeFdSpecificValue(options[optionName], optionBaseArray, optionName, stdioLength);
89271
89816
  return addDefaultValue(optionArray, optionName);
89272
- }, getStdioLength = ({ stdio }) => Array.isArray(stdio) ? Math.max(stdio.length, STANDARD_STREAMS_ALIASES.length) : STANDARD_STREAMS_ALIASES.length, normalizeFdSpecificValue = (optionValue, optionArray, optionName, stdioLength) => isPlainObject3(optionValue) ? normalizeOptionObject(optionValue, optionArray, optionName, stdioLength) : optionArray.fill(optionValue), normalizeOptionObject = (optionValue, optionArray, optionName, stdioLength) => {
89817
+ }, getStdioLength = ({ stdio }) => Array.isArray(stdio) ? Math.max(stdio.length, STANDARD_STREAMS_ALIASES.length) : STANDARD_STREAMS_ALIASES.length, normalizeFdSpecificValue = (optionValue, optionArray, optionName, stdioLength) => isPlainObject4(optionValue) ? normalizeOptionObject(optionValue, optionArray, optionName, stdioLength) : optionArray.fill(optionValue), normalizeOptionObject = (optionValue, optionArray, optionName, stdioLength) => {
89273
89818
  for (const fdName of Object.keys(optionValue).sort(compareFdName)) {
89274
89819
  for (const fdNumber of parseFdName(fdName, optionName, stdioLength)) {
89275
89820
  optionArray[fdNumber] = optionValue[fdName];
@@ -92516,7 +93061,7 @@ var getStdioItemType = (value, optionName) => {
92516
93061
  if (value !== undefined && typeof value !== "boolean") {
92517
93062
  throw new TypeError(`The \`${optionName}\` option must use a boolean.`);
92518
93063
  }
92519
- }, isGenerator = (value) => isAsyncGenerator(value) || isSyncGenerator(value), isAsyncGenerator = (value) => Object.prototype.toString.call(value) === "[object AsyncGeneratorFunction]", isSyncGenerator = (value) => Object.prototype.toString.call(value) === "[object GeneratorFunction]", isTransformOptions = (value) => isPlainObject3(value) && (value.transform !== undefined || value.final !== undefined), isUrl = (value) => Object.prototype.toString.call(value) === "[object URL]", isRegularUrl = (value) => isUrl(value) && value.protocol !== "file:", isFilePathObject = (value) => isPlainObject3(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => FILE_PATH_KEYS.has(key)) && isFilePathString(value.file), FILE_PATH_KEYS, isFilePathString = (file) => typeof file === "string", isStdioValueObject = (value) => isPlainObject3(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => STDIO_VALUE_KEYS.has(key)) && ("value" in value), STDIO_VALUE_KEYS, isUnknownStdioString = (type, value) => type === "native" && typeof value === "string" && !KNOWN_STDIO_STRINGS.has(value), KNOWN_STDIO_STRINGS, isReadableStream3 = (value) => Object.prototype.toString.call(value) === "[object ReadableStream]", isWritableStream2 = (value) => Object.prototype.toString.call(value) === "[object WritableStream]", isWebStream = (value) => isReadableStream3(value) || isWritableStream2(value), isTransformStream = (value) => isReadableStream3(value?.readable) && isWritableStream2(value?.writable), isAsyncIterableObject = (value) => isObject2(value) && typeof value[Symbol.asyncIterator] === "function", isIterableObject = (value) => isObject2(value) && typeof value[Symbol.iterator] === "function", isObject2 = (value) => typeof value === "object" && value !== null, TRANSFORM_TYPES, FILE_TYPES, SPECIAL_DUPLICATE_TYPES_SYNC, SPECIAL_DUPLICATE_TYPES, FORBID_DUPLICATE_TYPES, TYPE_TO_MESSAGE;
93064
+ }, isGenerator = (value) => isAsyncGenerator(value) || isSyncGenerator(value), isAsyncGenerator = (value) => Object.prototype.toString.call(value) === "[object AsyncGeneratorFunction]", isSyncGenerator = (value) => Object.prototype.toString.call(value) === "[object GeneratorFunction]", isTransformOptions = (value) => isPlainObject4(value) && (value.transform !== undefined || value.final !== undefined), isUrl = (value) => Object.prototype.toString.call(value) === "[object URL]", isRegularUrl = (value) => isUrl(value) && value.protocol !== "file:", isFilePathObject = (value) => isPlainObject4(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => FILE_PATH_KEYS.has(key)) && isFilePathString(value.file), FILE_PATH_KEYS, isFilePathString = (file) => typeof file === "string", isStdioValueObject = (value) => isPlainObject4(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => STDIO_VALUE_KEYS.has(key)) && ("value" in value), STDIO_VALUE_KEYS, isUnknownStdioString = (type, value) => type === "native" && typeof value === "string" && !KNOWN_STDIO_STRINGS.has(value), KNOWN_STDIO_STRINGS, isReadableStream3 = (value) => Object.prototype.toString.call(value) === "[object ReadableStream]", isWritableStream2 = (value) => Object.prototype.toString.call(value) === "[object WritableStream]", isWebStream = (value) => isReadableStream3(value) || isWritableStream2(value), isTransformStream = (value) => isReadableStream3(value?.readable) && isWritableStream2(value?.writable), isAsyncIterableObject = (value) => isObject2(value) && typeof value[Symbol.asyncIterator] === "function", isIterableObject = (value) => isObject2(value) && typeof value[Symbol.iterator] === "function", isObject2 = (value) => typeof value === "object" && value !== null, TRANSFORM_TYPES, FILE_TYPES, SPECIAL_DUPLICATE_TYPES_SYNC, SPECIAL_DUPLICATE_TYPES, FORBID_DUPLICATE_TYPES, TYPE_TO_MESSAGE;
92520
93065
  var init_type = __esm(() => {
92521
93066
  init_uint_array();
92522
93067
  FILE_PATH_KEYS = new Set(["file", "append"]);
@@ -92618,7 +93163,7 @@ var normalizeTransforms = (stdioItems, optionName, direction, options) => [
92618
93163
  value: { transform, writableObjectMode, readableObjectMode }
92619
93164
  };
92620
93165
  }, normalizeTransformStream = ({ stdioItem, stdioItem: { value }, index, newTransforms, direction }) => {
92621
- const { transform, objectMode } = isPlainObject3(value) ? value : { transform: value };
93166
+ const { transform, objectMode } = isPlainObject4(value) ? value : { transform: value };
92622
93167
  const { writableObjectMode, readableObjectMode } = getTransformObjectModes(objectMode, index, newTransforms, direction);
92623
93168
  return {
92624
93169
  ...stdioItem,
@@ -92631,7 +93176,7 @@ var normalizeTransforms = (stdioItems, optionName, direction, options) => [
92631
93176
  binary: binaryOption = false,
92632
93177
  preserveNewlines = false,
92633
93178
  objectMode
92634
- } = isPlainObject3(value) ? value : { transform: value };
93179
+ } = isPlainObject4(value) ? value : { transform: value };
92635
93180
  const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
92636
93181
  const { writableObjectMode, readableObjectMode } = getTransformObjectModes(objectMode, index, newTransforms, direction);
92637
93182
  return {
@@ -95326,7 +95871,7 @@ var init_abort2 = __esm(() => {
95326
95871
 
95327
95872
  // node_modules/execa/lib/pipe/setup.js
95328
95873
  var pipeToSubprocess = (sourceInfo, ...pipeArguments) => {
95329
- if (isPlainObject3(pipeArguments[0])) {
95874
+ if (isPlainObject4(pipeArguments[0])) {
95330
95875
  return pipeToSubprocess.bind(undefined, {
95331
95876
  ...sourceInfo,
95332
95877
  boundOptions: { ...sourceInfo.boundOptions, ...pipeArguments[0] }
@@ -96235,7 +96780,7 @@ var mergeOptions = (boundOptions, options) => {
96235
96780
  ]));
96236
96781
  return { ...safeBoundOptions, ...mergedOptions };
96237
96782
  }, mergeOption = (optionName, boundOptionValue, optionValue) => {
96238
- if (DEEP_OPTIONS.has(optionName) && isPlainObject3(boundOptionValue) && isPlainObject3(optionValue)) {
96783
+ if (DEEP_OPTIONS.has(optionName) && isPlainObject4(boundOptionValue) && isPlainObject4(optionValue)) {
96239
96784
  return { ...boundOptionValue, ...optionValue };
96240
96785
  }
96241
96786
  return optionValue;
@@ -96260,7 +96805,7 @@ var createExeca = (mapArguments, boundOptions, deepOptions, setBoundExeca) => {
96260
96805
  }
96261
96806
  return boundExeca;
96262
96807
  }, callBoundExeca = ({ mapArguments, deepOptions = {}, boundOptions = {}, setBoundExeca, createNested }, firstArgument, ...nextArguments) => {
96263
- if (isPlainObject3(firstArgument)) {
96808
+ if (isPlainObject4(firstArgument)) {
96264
96809
  return createNested(mapArguments, mergeOptions(boundOptions, firstArgument), setBoundExeca);
96265
96810
  }
96266
96811
  const { file, commandArguments, options, isSync } = parseArguments2({
@@ -98324,7 +98869,7 @@ var init_isArrayLikeObject = __esm(() => {
98324
98869
  });
98325
98870
 
98326
98871
  // node_modules/lodash-es/isPlainObject.js
98327
- function isPlainObject4(value) {
98872
+ function isPlainObject5(value) {
98328
98873
  if (!isObjectLike_default(value) || _baseGetTag_default(value) != objectTag4) {
98329
98874
  return false;
98330
98875
  }
@@ -98345,7 +98890,7 @@ var init_isPlainObject = __esm(() => {
98345
98890
  funcToString3 = funcProto3.toString;
98346
98891
  hasOwnProperty11 = objectProto14.hasOwnProperty;
98347
98892
  objectCtorString = funcToString3.call(Object);
98348
- isPlainObject_default = isPlainObject4;
98893
+ isPlainObject_default = isPlainObject5;
98349
98894
  });
98350
98895
 
98351
98896
  // node_modules/lodash-es/_safeGet.js
@@ -102476,9 +103021,9 @@ function merge2(...objs) {
102476
103021
  }
102477
103022
  const targetKey = caseless && typeof key === "string" && findKey(result, key) || key;
102478
103023
  const existing = hasOwnProperty14(result, targetKey) ? result[targetKey] : undefined;
102479
- if (isPlainObject5(existing) && isPlainObject5(val)) {
103024
+ if (isPlainObject6(existing) && isPlainObject6(val)) {
102480
103025
  result[targetKey] = merge2(existing, val);
102481
- } else if (isPlainObject5(val)) {
103026
+ } else if (isPlainObject6(val)) {
102482
103027
  result[targetKey] = merge2({}, val);
102483
103028
  } else if (isArray3(val)) {
102484
103029
  result[targetKey] = val.slice();
@@ -102581,7 +103126,7 @@ var toString3, getPrototypeOf, iterator, toStringTag, hasOwnProperty14, isUnsafe
102581
103126
  }, kindOf, kindOfTest = (type) => {
102582
103127
  type = type.toLowerCase();
102583
103128
  return (thing) => kindOf(thing) === type;
102584
- }, typeOfTest = (type) => (thing) => typeof thing === type, isArray3, isUndefined, isArrayBuffer3, isString, isFunction2, isNumber, isObject4 = (thing) => thing !== null && typeof thing === "object", isBoolean = (thing) => thing === true || thing === false, isPlainObject5 = (val) => {
103129
+ }, typeOfTest = (type) => (thing) => typeof thing === type, isArray3, isUndefined, isArrayBuffer3, isString, isFunction2, isNumber, isObject4 = (thing) => thing !== null && typeof thing === "object", isBoolean = (thing) => thing === true || thing === false, isPlainObject6 = (val) => {
102585
103130
  if (!isObject4(val)) {
102586
103131
  return false;
102587
103132
  }
@@ -102857,7 +103402,7 @@ var init_utils3 = __esm(() => {
102857
103402
  isNumber,
102858
103403
  isBoolean,
102859
103404
  isObject: isObject4,
102860
- isPlainObject: isPlainObject5,
103405
+ isPlainObject: isPlainObject6,
102861
103406
  isEmptyObject,
102862
103407
  isReadableStream: isReadableStream4,
102863
103408
  isRequest,
@@ -124455,8 +125000,8 @@ var isMcpServerCommandEntry = () => ({}), isMcpServerNameEntry = () => ({}), isM
124455
125000
  mcpServers: record(string2(), any()).optional(),
124456
125001
  allowedMcpServers: any().optional(),
124457
125002
  deniedMcpServers: any().optional(),
124458
- enabledMcpjsonServers: array(string2()).optional(),
124459
- disabledMcpjsonServers: array(string2()).optional(),
125003
+ enabledMcpjsonServers: union([string2(), array(string2())]).transform((v) => Array.isArray(v) ? v : [v]).optional(),
125004
+ disabledMcpjsonServers: union([string2(), array(string2())]).transform((v) => Array.isArray(v) ? v : [v]).optional(),
124460
125005
  enabledPlugins: record(string2(), any()).optional(),
124461
125006
  pluginConfigs: any().optional(),
124462
125007
  extraKnownMarketplaces: any().optional(),
@@ -124797,12 +125342,12 @@ function findLastUnescapedChar2(str, char) {
124797
125342
  var LEGACY_TOOL_NAME_ALIASES2;
124798
125343
  var init_permissionRuleParser2 = __esm(() => {
124799
125344
  init_constants6();
124800
- LEGACY_TOOL_NAME_ALIASES2 = {
125345
+ LEGACY_TOOL_NAME_ALIASES2 = Object.assign(Object.create(null), {
124801
125346
  Task: AGENT_TOOL_NAME2,
124802
125347
  KillShell: TASK_STOP_TOOL_NAME2,
124803
125348
  AgentOutputTool: TASK_OUTPUT_TOOL_NAME2,
124804
125349
  BashOutputTool: TASK_OUTPUT_TOOL_NAME2
124805
- };
125350
+ });
124806
125351
  });
124807
125352
 
124808
125353
  // src/tui/utils/settings/toolValidationConfig.ts
@@ -180895,6 +181440,7 @@ __export(exports_engineCompat, {
180895
181440
  MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES: () => MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES,
180896
181441
  MAX_GOAL_CHECKINS: () => MAX_GOAL_CHECKINS,
180897
181442
  MAX_GOAL_CONDITION_CHARS: () => MAX_GOAL_CONDITION_CHARS,
181443
+ MAX_HOOK_CONCURRENCY: () => MAX_HOOK_CONCURRENCY,
180898
181444
  MAX_PTL_RETRIES: () => MAX_PTL_RETRIES,
180899
181445
  MAX_TRANSCRIPT_READ_BYTES: () => MAX_TRANSCRIPT_READ_BYTES,
180900
181446
  MAX_WORKER_SPAWN_DEPTH: () => MAX_WORKER_SPAWN_DEPTH,
@@ -182181,11 +182727,12 @@ function parseArgumentNames2(argumentNames) {
182181
182727
  return [];
182182
182728
  }
182183
182729
  const isValidName = (name) => typeof name === "string" && name.trim() !== "" && !/^\d+$/.test(name);
182730
+ const cap = (names) => names.map((name) => name.slice(0, MAX_ARGUMENT_NAME_CHARS2)).slice(0, MAX_ARGUMENT_NAMES2);
182184
182731
  if (Array.isArray(argumentNames)) {
182185
- return argumentNames.filter(isValidName);
182732
+ return cap(argumentNames.filter(isValidName));
182186
182733
  }
182187
182734
  if (typeof argumentNames === "string") {
182188
- return argumentNames.split(/\s+/).filter(isValidName);
182735
+ return cap(argumentNames.split(/\s+/).filter(isValidName));
182189
182736
  }
182190
182737
  return [];
182191
182738
  }
@@ -182195,36 +182742,144 @@ function generateProgressiveArgumentHint2(argNames, typedArgs) {
182195
182742
  return;
182196
182743
  return remaining.map((name) => `[${name}]`).join(" ");
182197
182744
  }
182198
- function substituteArguments2(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
182745
+ function isWordChar3(c) {
182746
+ return c !== undefined && /[A-Za-z0-9_]/.test(c);
182747
+ }
182748
+ function matchPlaceholderAt2(content, i, argumentNames) {
182749
+ if (content[i] !== "$")
182750
+ return null;
182751
+ for (let n = 0;n < argumentNames.length; n++) {
182752
+ const name = argumentNames[n];
182753
+ if (!name)
182754
+ continue;
182755
+ if (content.startsWith(`$${name}`, i)) {
182756
+ const after = content[i + 1 + name.length];
182757
+ if (after !== "[" && !isWordChar3(after)) {
182758
+ return { length: 1 + name.length, kind: "named", nameIndex: n };
182759
+ }
182760
+ }
182761
+ }
182762
+ INDEXED_PLACEHOLDER_RE2.lastIndex = i;
182763
+ const indexed = INDEXED_PLACEHOLDER_RE2.exec(content);
182764
+ if (indexed) {
182765
+ return {
182766
+ length: indexed[0].length,
182767
+ kind: "indexed",
182768
+ argIndex: parseInt(indexed[1], 10)
182769
+ };
182770
+ }
182771
+ if (content.startsWith("$ARGUMENTS", i)) {
182772
+ return { length: "$ARGUMENTS".length, kind: "bare" };
182773
+ }
182774
+ SHORTHAND_PLACEHOLDER_RE2.lastIndex = i;
182775
+ const shorthand = SHORTHAND_PLACEHOLDER_RE2.exec(content);
182776
+ if (shorthand && !isWordChar3(content[i + shorthand[0].length])) {
182777
+ return {
182778
+ length: shorthand[0].length,
182779
+ kind: "shorthand",
182780
+ argIndex: parseInt(shorthand[1], 10)
182781
+ };
182782
+ }
182783
+ return null;
182784
+ }
182785
+ function countPlaceholderOccurrences2(content, argumentNames) {
182786
+ let count = 0;
182787
+ let i = 0;
182788
+ while (i < content.length) {
182789
+ const m = matchPlaceholderAt2(content, i, argumentNames);
182790
+ if (m) {
182791
+ count++;
182792
+ i += m.length;
182793
+ } else {
182794
+ i++;
182795
+ }
182796
+ }
182797
+ return count;
182798
+ }
182799
+ function substituteArgumentsImpl2(content, args, appendIfNoPlaceholder, argumentNames, strict) {
182199
182800
  if (args === undefined || args === null) {
182200
182801
  return content;
182201
182802
  }
182202
- const parsedArgs = parseArguments3(args);
182203
- const originalContent = content;
182204
- for (let i = 0;i < argumentNames.length; i++) {
182205
- const name = argumentNames[i];
182206
- if (!name)
182207
- continue;
182208
- content = content.replace(new RegExp(`\\$${name}(?![\\[\\w])`, "g"), parsedArgs[i] ?? "");
182803
+ const placeholderCount = countPlaceholderOccurrences2(content, argumentNames);
182804
+ let effectiveArgs = args;
182805
+ if (placeholderCount > 0 && content.length + placeholderCount * args.length > MAX_SUBSTITUTION_OUTPUT_CHARS2) {
182806
+ if (strict)
182807
+ return null;
182808
+ const budgetPerReplacement = Math.max(0, Math.floor((MAX_SUBSTITUTION_OUTPUT_CHARS2 - content.length) / placeholderCount));
182809
+ effectiveArgs = args.slice(0, budgetPerReplacement);
182810
+ } else if (strict) {
182811
+ const appendLen = appendIfNoPlaceholder && args ? `
182812
+
182813
+ ARGUMENTS: `.length + args.length : 0;
182814
+ if (content.length + appendLen > MAX_SUBSTITUTION_OUTPUT_CHARS2) {
182815
+ return null;
182816
+ }
182209
182817
  }
182210
- content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, indexStr) => {
182211
- const index = parseInt(indexStr, 10);
182212
- return parsedArgs[index] ?? "";
182213
- });
182214
- content = content.replace(/\$(\d+)(?!\w)/g, (_, indexStr) => {
182215
- const index = parseInt(indexStr, 10);
182216
- return parsedArgs[index] ?? "";
182217
- });
182218
- content = content.replaceAll("$ARGUMENTS", args);
182219
- if (content === originalContent && appendIfNoPlaceholder && args) {
182220
- content = content + `
182818
+ const parsedArgs = parseArguments3(effectiveArgs);
182819
+ let out = "";
182820
+ const appendValue = (value) => {
182821
+ if (out.length + value.length > MAX_SUBSTITUTION_OUTPUT_CHARS2) {
182822
+ value = value.slice(0, Math.max(0, MAX_SUBSTITUTION_OUTPUT_CHARS2 - out.length));
182823
+ }
182824
+ out += value;
182825
+ };
182826
+ let matched = false;
182827
+ let i = 0;
182828
+ while (i < content.length) {
182829
+ const m = matchPlaceholderAt2(content, i, argumentNames);
182830
+ if (m) {
182831
+ matched = true;
182832
+ if (m.kind === "bare")
182833
+ appendValue(effectiveArgs);
182834
+ else if (m.kind === "named")
182835
+ appendValue(parsedArgs[m.nameIndex] ?? "");
182836
+ else
182837
+ appendValue(parsedArgs[m.argIndex] ?? "");
182838
+ i += m.length;
182839
+ } else {
182840
+ out += content[i];
182841
+ i++;
182842
+ }
182843
+ }
182844
+ if (!matched && appendIfNoPlaceholder && args) {
182845
+ out = out + `
182221
182846
 
182222
182847
  ARGUMENTS: ${args}`;
182223
182848
  }
182224
- return content;
182849
+ return out;
182225
182850
  }
182851
+ function substituteArguments2(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
182852
+ return substituteArgumentsImpl2(content, args, appendIfNoPlaceholder, argumentNames, false) ?? content;
182853
+ }
182854
+ function substituteArgumentsStrict2(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
182855
+ return substituteArgumentsImpl2(content, args, appendIfNoPlaceholder, argumentNames, true);
182856
+ }
182857
+ var MAX_ARGUMENT_NAMES2 = 64, MAX_ARGUMENT_NAME_CHARS2 = 64, MAX_SUBSTITUTION_OUTPUT_CHARS2 = 1e6, INDEXED_PLACEHOLDER_RE2, SHORTHAND_PLACEHOLDER_RE2;
182226
182858
  var init_argumentSubstitution2 = __esm(() => {
182227
182859
  init_shellQuote2();
182860
+ INDEXED_PLACEHOLDER_RE2 = /\$ARGUMENTS\[(\d+)\]/y;
182861
+ SHORTHAND_PLACEHOLDER_RE2 = /\$(\d+)/y;
182862
+ });
182863
+
182864
+ // src/tui/utils/commandNameSafety.ts
182865
+ function isUnsafeCommandName2(name) {
182866
+ if (name === "")
182867
+ return true;
182868
+ if (name.includes("<") || name.includes(">"))
182869
+ return true;
182870
+ return UNSAFE_NAME_CHARS_RE2.test(name);
182871
+ }
182872
+ function sanitizeDisplayName2(displayName, canonicalName) {
182873
+ if (displayName === undefined || !isUnsafeCommandName2(displayName)) {
182874
+ return displayName;
182875
+ }
182876
+ logForDebugging3(`Discarding unsafe frontmatter display name ${JSON.stringify(displayName)} for ${canonicalName}; falling back to canonical name`);
182877
+ return;
182878
+ }
182879
+ var UNSAFE_NAME_CHARS_RE2;
182880
+ var init_commandNameSafety2 = __esm(() => {
182881
+ init_debug();
182882
+ UNSAFE_NAME_CHARS_RE2 = /[\u0000-\u001F\u007F-\u00A0\u00AD\u034F\u061C\u070F\u1680\u180E\u2000-\u200F\u2028\u2029\u202A-\u202E\u202F\u205F\u2060-\u2069\u3000\uFE00-\uFE0F\uFEFF\u{E0100}-\u{E01EF}]/u;
182228
182883
  });
182229
182884
 
182230
182885
  // src/tui/utils/generatedFiles.ts
@@ -184657,7 +185312,7 @@ class Protocol {
184657
185312
  };
184658
185313
  }
184659
185314
  }
184660
- function isPlainObject6(value) {
185315
+ function isPlainObject7(value) {
184661
185316
  return value !== null && typeof value === "object" && !Array.isArray(value);
184662
185317
  }
184663
185318
  function mergeCapabilities(base, additional) {
@@ -184668,7 +185323,7 @@ function mergeCapabilities(base, additional) {
184668
185323
  if (addValue === undefined)
184669
185324
  continue;
184670
185325
  const baseValue = result[k];
184671
- if (isPlainObject6(baseValue) && isPlainObject6(addValue)) {
185326
+ if (isPlainObject7(baseValue) && isPlainObject7(addValue)) {
184672
185327
  result[k] = { ...baseValue, ...addValue };
184673
185328
  } else {
184674
185329
  result[k] = addValue;
@@ -188182,6 +188837,97 @@ var init_streamableHttp = __esm(() => {
188182
188837
  };
188183
188838
  });
188184
188839
 
188840
+ // src/tui/services/mcp/fetchTimeout.ts
188841
+ function getMcpConnectionTimeoutMs() {
188842
+ return parseInt(process.env.MCP_TIMEOUT || "", 10) || 30000;
188843
+ }
188844
+ function bindBodyReadDeadline(response) {
188845
+ const contentType = response.headers?.get("content-type") ?? "";
188846
+ if (contentType.includes("text/event-stream")) {
188847
+ return response;
188848
+ }
188849
+ const rawBody = response.body;
188850
+ if (!rawBody) {
188851
+ return response;
188852
+ }
188853
+ const reader = rawBody.getReader();
188854
+ let deadlineTimer;
188855
+ const deadline = new Promise((_, reject) => {
188856
+ deadlineTimer = setTimeout(() => reject(new DOMException("The operation timed out.", "TimeoutError")), getMcpConnectionTimeoutMs());
188857
+ deadlineTimer.unref?.();
188858
+ });
188859
+ deadline.catch(() => {});
188860
+ const clearDeadline = () => {
188861
+ if (deadlineTimer) {
188862
+ clearTimeout(deadlineTimer);
188863
+ }
188864
+ };
188865
+ const stream = new ReadableStream({
188866
+ async pull(ctrl) {
188867
+ try {
188868
+ const { done, value } = await Promise.race([reader.read(), deadline]);
188869
+ if (done) {
188870
+ clearDeadline();
188871
+ ctrl.close();
188872
+ } else {
188873
+ ctrl.enqueue(value);
188874
+ }
188875
+ } catch (error) {
188876
+ clearDeadline();
188877
+ reader.cancel().catch(() => {});
188878
+ ctrl.error(error);
188879
+ }
188880
+ },
188881
+ cancel(reason) {
188882
+ clearDeadline();
188883
+ return reader.cancel(reason);
188884
+ }
188885
+ });
188886
+ return new Response(stream, {
188887
+ status: response.status,
188888
+ statusText: response.statusText,
188889
+ headers: response.headers
188890
+ });
188891
+ }
188892
+ function wrapFetchWithTimeout(baseFetch) {
188893
+ return async (url, init) => {
188894
+ const method = (init?.method ?? "GET").toUpperCase();
188895
+ if (method === "GET") {
188896
+ return baseFetch(url, init);
188897
+ }
188898
+ const headers = new Headers(init?.headers);
188899
+ if (!headers.has("accept")) {
188900
+ headers.set("accept", MCP_STREAMABLE_HTTP_ACCEPT);
188901
+ }
188902
+ const controller = new AbortController;
188903
+ const timer = setTimeout((c) => c.abort(new DOMException("The operation timed out.", "TimeoutError")), MCP_REQUEST_TIMEOUT_MS, controller);
188904
+ timer.unref?.();
188905
+ const parentSignal = init?.signal;
188906
+ const abort = () => controller.abort(parentSignal?.reason);
188907
+ parentSignal?.addEventListener("abort", abort);
188908
+ if (parentSignal?.aborted) {
188909
+ controller.abort(parentSignal.reason);
188910
+ }
188911
+ const cleanup = () => {
188912
+ clearTimeout(timer);
188913
+ parentSignal?.removeEventListener("abort", abort);
188914
+ };
188915
+ try {
188916
+ const response = await baseFetch(url, {
188917
+ ...init,
188918
+ headers,
188919
+ signal: controller.signal
188920
+ });
188921
+ cleanup();
188922
+ return bindBodyReadDeadline(response);
188923
+ } catch (error) {
188924
+ cleanup();
188925
+ throw error;
188926
+ }
188927
+ };
188928
+ }
188929
+ var MCP_REQUEST_TIMEOUT_MS = 60000, MCP_STREAMABLE_HTTP_ACCEPT = "application/json, text/event-stream";
188930
+
188185
188931
  // node_modules/lodash-es/_baseZipObject.js
188186
188932
  function baseZipObject(props, values, assignFunc) {
188187
188933
  var index = -1, length = props.length, valsLength = values.length, result = {};
@@ -193521,6 +194267,73 @@ var init_browser = __esm(() => {
193521
194267
  init_execFileNoThrow2();
193522
194268
  });
193523
194269
 
194270
+ // src/tui/services/mcp/headersHelper.ts
194271
+ function isMcpServerFromProjectOrLocalSettings(config) {
194272
+ return config.scope === "project" || config.scope === "local";
194273
+ }
194274
+ async function getMcpHeadersFromHelper(serverName, config) {
194275
+ if (!config.headersHelper) {
194276
+ return null;
194277
+ }
194278
+ if ("scope" in config && isMcpServerFromProjectOrLocalSettings(config)) {
194279
+ const isNonInteractive = getIsNonInteractiveSession();
194280
+ const explicitTrust = process.env.ATLAS_TRUST_WORKSPACE === "true";
194281
+ const hasTrust = checkHasTrustDialogAccepted() || isNonInteractive && explicitTrust;
194282
+ if (!hasTrust) {
194283
+ const error = new Error(`Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`);
194284
+ logAntError("MCP headersHelper invoked before trust check", error);
194285
+ return null;
194286
+ }
194287
+ }
194288
+ try {
194289
+ logMCPDebug(serverName, "Executing headersHelper to get dynamic headers");
194290
+ const execResult = await execFileNoThrowWithCwd2("sh", ["-c", config.headersHelper], {
194291
+ timeout: 1e4,
194292
+ env: {
194293
+ ...process.env,
194294
+ ATLAS_MCP_SERVER_NAME: serverName,
194295
+ ATLAS_MCP_SERVER_URL: config.url
194296
+ }
194297
+ });
194298
+ if (execResult.code !== 0 || !execResult.stdout) {
194299
+ throw new Error(`headersHelper for MCP server '${serverName}' did not return a valid value`);
194300
+ }
194301
+ const result = execResult.stdout.trim();
194302
+ const headers = jsonParse2(result);
194303
+ if (typeof headers !== "object" || headers === null || Array.isArray(headers)) {
194304
+ throw new Error(`headersHelper for MCP server '${serverName}' must return a JSON object with string key-value pairs`);
194305
+ }
194306
+ for (const [key, value] of Object.entries(headers)) {
194307
+ if (typeof value !== "string") {
194308
+ throw new Error(`headersHelper for MCP server '${serverName}' returned non-string value for key "${key}": ${typeof value}`);
194309
+ }
194310
+ }
194311
+ logMCPDebug(serverName, `Successfully retrieved ${Object.keys(headers).length} headers from headersHelper`);
194312
+ return headers;
194313
+ } catch (error) {
194314
+ logMCPError(serverName, `Error getting headers from headersHelper: ${errorMessage2(error)}`);
194315
+ logError3(new Error(`Error getting MCP headers from headersHelper for server '${serverName}': ${errorMessage2(error)}`));
194316
+ return null;
194317
+ }
194318
+ }
194319
+ async function getMcpServerHeaders(serverName, config) {
194320
+ const staticHeaders = config.headers || {};
194321
+ const dynamicHeaders = await getMcpHeadersFromHelper(serverName, config) || {};
194322
+ return {
194323
+ ...staticHeaders,
194324
+ ...dynamicHeaders
194325
+ };
194326
+ }
194327
+ var init_headersHelper = __esm(() => {
194328
+ init_bootstrap();
194329
+ init_config6();
194330
+ init_debug();
194331
+ init_errors4();
194332
+ init_execFileNoThrow2();
194333
+ init_log5();
194334
+ init_slowOperations();
194335
+ });
194336
+
193524
194337
  // src/tui/services/mcp/oauthPort.ts
193525
194338
  import { createServer } from "http";
193526
194339
  function buildRedirectUri(port = REDIRECT_PORT_FALLBACK) {
@@ -194028,20 +194841,20 @@ async function writeMcpjsonFile2(config) {
194028
194841
  throw e;
194029
194842
  }
194030
194843
  }
194031
- function getServerCommandArray(config) {
194844
+ function getServerCommandArray2(config) {
194032
194845
  if (config.type !== undefined && config.type !== "stdio") {
194033
194846
  return null;
194034
194847
  }
194035
194848
  const stdioConfig = config;
194036
194849
  return [stdioConfig.command, ...stdioConfig.args ?? []];
194037
194850
  }
194038
- function commandArraysMatch(a, b) {
194851
+ function commandArraysMatch2(a, b) {
194039
194852
  if (a.length !== b.length) {
194040
194853
  return false;
194041
194854
  }
194042
194855
  return a.every((val, idx) => val === b[idx]);
194043
194856
  }
194044
- function getServerUrl(config) {
194857
+ function getServerUrl2(config) {
194045
194858
  return "url" in config ? config.url : null;
194046
194859
  }
194047
194860
  function unwrapCcrProxyUrl(url) {
@@ -194057,11 +194870,11 @@ function unwrapCcrProxyUrl(url) {
194057
194870
  }
194058
194871
  }
194059
194872
  function getMcpServerSignature(config) {
194060
- const cmd = getServerCommandArray(config);
194873
+ const cmd = getServerCommandArray2(config);
194061
194874
  if (cmd) {
194062
194875
  return `stdio:${jsonStringify5(cmd)}`;
194063
194876
  }
194064
- const url = getServerUrl(config);
194877
+ const url = getServerUrl2(config);
194065
194878
  if (url) {
194066
194879
  return `url:${unwrapCcrProxyUrl(url)}`;
194067
194880
  }
@@ -194123,13 +194936,13 @@ function dedupManagedMcpServers(claudeAiServers, manualServers) {
194123
194936
  }
194124
194937
  return { servers, suppressed };
194125
194938
  }
194126
- function urlPatternToRegex(pattern) {
194939
+ function urlPatternToRegex2(pattern) {
194127
194940
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
194128
194941
  const regexStr = escaped.replace(/\*/g, ".*");
194129
194942
  return new RegExp(`^${regexStr}$`);
194130
194943
  }
194131
- function urlMatchesPattern(url, pattern) {
194132
- const regex = urlPatternToRegex(pattern);
194944
+ function urlMatchesPattern2(url, pattern) {
194945
+ const regex = urlPatternToRegex2(pattern);
194133
194946
  return regex.test(url);
194134
194947
  }
194135
194948
  function getMcpAllowlistSettings() {
@@ -194152,18 +194965,18 @@ function isMcpServerDenied(serverName, config) {
194152
194965
  }
194153
194966
  }
194154
194967
  if (config) {
194155
- const serverCommand = getServerCommandArray(config);
194968
+ const serverCommand = getServerCommandArray2(config);
194156
194969
  if (serverCommand) {
194157
194970
  for (const entry of settings.deniedMcpServers) {
194158
- if (isMcpServerCommandEntry(entry) && commandArraysMatch(entry.serverCommand, serverCommand)) {
194971
+ if (isMcpServerCommandEntry(entry) && commandArraysMatch2(entry.serverCommand, serverCommand)) {
194159
194972
  return true;
194160
194973
  }
194161
194974
  }
194162
194975
  }
194163
- const serverUrl = getServerUrl(config);
194976
+ const serverUrl = getServerUrl2(config);
194164
194977
  if (serverUrl) {
194165
194978
  for (const entry of settings.deniedMcpServers) {
194166
- if (isMcpServerUrlEntry(entry) && urlMatchesPattern(serverUrl, entry.serverUrl)) {
194979
+ if (isMcpServerUrlEntry(entry) && urlMatchesPattern2(serverUrl, entry.serverUrl)) {
194167
194980
  return true;
194168
194981
  }
194169
194982
  }
@@ -194185,12 +194998,12 @@ function isMcpServerAllowedByPolicy(serverName, config) {
194185
194998
  const hasCommandEntries = settings.allowedMcpServers.some(isMcpServerCommandEntry);
194186
194999
  const hasUrlEntries = settings.allowedMcpServers.some(isMcpServerUrlEntry);
194187
195000
  if (config) {
194188
- const serverCommand = getServerCommandArray(config);
194189
- const serverUrl = getServerUrl(config);
195001
+ const serverCommand = getServerCommandArray2(config);
195002
+ const serverUrl = getServerUrl2(config);
194190
195003
  if (serverCommand) {
194191
195004
  if (hasCommandEntries) {
194192
195005
  for (const entry of settings.allowedMcpServers) {
194193
- if (isMcpServerCommandEntry(entry) && commandArraysMatch(entry.serverCommand, serverCommand)) {
195006
+ if (isMcpServerCommandEntry(entry) && commandArraysMatch2(entry.serverCommand, serverCommand)) {
194194
195007
  return true;
194195
195008
  }
194196
195009
  }
@@ -194206,7 +195019,7 @@ function isMcpServerAllowedByPolicy(serverName, config) {
194206
195019
  } else if (serverUrl) {
194207
195020
  if (hasUrlEntries) {
194208
195021
  for (const entry of settings.allowedMcpServers) {
194209
- if (isMcpServerUrlEntry(entry) && urlMatchesPattern(serverUrl, entry.serverUrl)) {
195022
+ if (isMcpServerUrlEntry(entry) && urlMatchesPattern2(serverUrl, entry.serverUrl)) {
194210
195023
  return true;
194211
195024
  }
194212
195025
  }
@@ -194235,7 +195048,7 @@ function isMcpServerAllowedByPolicy(serverName, config) {
194235
195048
  }
194236
195049
  return false;
194237
195050
  }
194238
- function filterMcpServersByPolicy(configs) {
195051
+ function filterMcpServersByPolicy2(configs) {
194239
195052
  const allowed = {};
194240
195053
  const blocked = [];
194241
195054
  for (const [name, config] of Object.entries(configs)) {
@@ -194687,7 +195500,7 @@ async function getAllMcpConfigs2() {
194687
195500
  }
194688
195501
  const claudeaiPromise = Promise.resolve({});
194689
195502
  const { servers: claudeCodeServers, errors } = await getMcpConfigs({}, claudeaiPromise);
194690
- const { allowed: claudeaiMcpServers } = filterMcpServersByPolicy(await claudeaiPromise);
195503
+ const { allowed: claudeaiMcpServers } = filterMcpServersByPolicy2(await claudeaiPromise);
194691
195504
  const { servers: dedupedClaudeAi } = dedupManagedMcpServers(claudeaiMcpServers, claudeCodeServers);
194692
195505
  const servers = Object.assign({}, dedupedClaudeAi, claudeCodeServers);
194693
195506
  return { servers, errors };
@@ -194792,6 +195605,9 @@ function parseMcpConfigFromFilePath(params) {
194792
195605
  ]
194793
195606
  };
194794
195607
  }
195608
+ if (!configContent.trim()) {
195609
+ return { config: { mcpServers: {} }, errors: [] };
195610
+ }
194795
195611
  const parsedJson = safeParseJSON3(configContent);
194796
195612
  if (!parsedJson) {
194797
195613
  logForDebugging3(`MCP config is not valid JSON: ${filePath} (scope=${scope}, length=${configContent.length}, first100=${jsonStringify5(configContent.slice(0, 100))})`, { level: "error" });
@@ -194811,6 +195627,9 @@ function parseMcpConfigFromFilePath(params) {
194811
195627
  ]
194812
195628
  };
194813
195629
  }
195630
+ if (typeof parsedJson === "object" && parsedJson !== null && !Array.isArray(parsedJson) && Object.keys(parsedJson).length === 0) {
195631
+ return { config: { mcpServers: {} }, errors: [] };
195632
+ }
194814
195633
  return parseMcpConfig({
194815
195634
  configObject: parsedJson,
194816
195635
  expandVars,
@@ -194941,11 +195760,11 @@ function hashMcpConfig(config) {
194941
195760
  });
194942
195761
  return createHash4("sha256").update(stable).digest("hex").slice(0, 16);
194943
195762
  }
194944
- function excludeStalePluginClients(mcp, configs) {
195763
+ function excludeStalePluginClients(mcp, configs, opts) {
194945
195764
  const stale = mcp.clients.filter((c) => {
194946
195765
  const fresh = configs[c.name];
194947
195766
  if (!fresh)
194948
- return c.config.scope === "dynamic";
195767
+ return opts?.absentIsStale || c.config.scope === "dynamic";
194949
195768
  return hashMcpConfig(c.config) !== hashMcpConfig(fresh);
194950
195769
  });
194951
195770
  if (stale.length === 0) {
@@ -195741,6 +196560,53 @@ function createAuthFetch() {
195741
196560
  }
195742
196561
  };
195743
196562
  }
196563
+ async function resolveConfiguredMcpHeaders(serverName, serverConfig) {
196564
+ try {
196565
+ return await getMcpServerHeaders(serverName, serverConfig);
196566
+ } catch (error) {
196567
+ logMCPDebug(serverName, `Failed to resolve configured headers for OAuth: ${errorMessage2(error)}`);
196568
+ return {};
196569
+ }
196570
+ }
196571
+ function headersToRecord2(headers) {
196572
+ const out = {};
196573
+ if (!headers)
196574
+ return out;
196575
+ if (headers instanceof Headers) {
196576
+ headers.forEach((v, k) => {
196577
+ out[k] = v;
196578
+ });
196579
+ return out;
196580
+ }
196581
+ if (Array.isArray(headers)) {
196582
+ for (const [k, v] of headers)
196583
+ out[k] = v;
196584
+ return out;
196585
+ }
196586
+ if (typeof headers === "object") {
196587
+ for (const [k, v] of Object.entries(headers)) {
196588
+ out[k] = String(v);
196589
+ }
196590
+ }
196591
+ return out;
196592
+ }
196593
+ function mergeConfiguredHeaders(fetchLike, configured) {
196594
+ if (Object.keys(configured).length === 0)
196595
+ return fetchLike;
196596
+ return (url, init) => {
196597
+ const requestHeaders = headersToRecord2(init?.headers);
196598
+ const requestKeys = new Set(Object.keys(requestHeaders).map((k) => k.toLowerCase()));
196599
+ const configuredNotOverridden = Object.fromEntries(Object.entries(configured).filter(([k]) => !requestKeys.has(k.toLowerCase())));
196600
+ return fetchLike(url, {
196601
+ ...init,
196602
+ headers: { ...configuredNotOverridden, ...requestHeaders }
196603
+ });
196604
+ };
196605
+ }
196606
+ async function withConfiguredHeaders(fetchLike, serverName, serverConfig) {
196607
+ const configured = await resolveConfiguredMcpHeaders(serverName, serverConfig);
196608
+ return mergeConfiguredHeaders(fetchLike, configured);
196609
+ }
195744
196610
  async function fetchAuthServerMetadata(serverName, serverUrl, configuredMetadataUrl, fetchFn, resourceMetadataUrl) {
195745
196611
  if (configuredMetadataUrl) {
195746
196612
  if (!configuredMetadataUrl.startsWith("https://")) {
@@ -195799,12 +196665,14 @@ async function revokeToken({
195799
196665
  clientId,
195800
196666
  clientSecret,
195801
196667
  accessToken,
195802
- authMethod = "client_secret_basic"
196668
+ authMethod = "client_secret_basic",
196669
+ extraHeaders
195803
196670
  }) {
195804
196671
  const params = new URLSearchParams;
195805
196672
  params.set("token", token);
195806
196673
  params.set("token_type_hint", tokenTypeHint);
195807
196674
  const headers = {
196675
+ ...extraHeaders ?? {},
195808
196676
  "Content-Type": "application/x-www-form-urlencoded"
195809
196677
  };
195810
196678
  if (clientId && clientSecret) {
@@ -195847,7 +196715,8 @@ async function revokeServerTokens(serverName, serverConfig, { preserveStepUpStat
195847
196715
  if (tokenData?.accessToken || tokenData?.refreshToken) {
195848
196716
  try {
195849
196717
  const asUrl = tokenData.discoveryState?.authorizationServerUrl ?? serverConfig.url;
195850
- const metadata = await fetchAuthServerMetadata(serverName, asUrl, serverConfig.oauth?.authServerMetadataUrl);
196718
+ const configuredHeaders = await resolveConfiguredMcpHeaders(serverName, serverConfig);
196719
+ const metadata = await fetchAuthServerMetadata(serverName, asUrl, serverConfig.oauth?.authServerMetadataUrl, mergeConfiguredHeaders(createAuthFetch(), configuredHeaders));
195851
196720
  if (!metadata) {
195852
196721
  logMCPDebug(serverName, "No OAuth metadata found");
195853
196722
  } else {
@@ -195869,7 +196738,8 @@ async function revokeServerTokens(serverName, serverConfig, { preserveStepUpStat
195869
196738
  clientId: tokenData.clientId,
195870
196739
  clientSecret: tokenData.clientSecret,
195871
196740
  accessToken: tokenData.accessToken,
195872
- authMethod
196741
+ authMethod,
196742
+ extraHeaders: configuredHeaders
195873
196743
  });
195874
196744
  } catch (error) {
195875
196745
  logMCPDebug(serverName, `Failed to revoke refresh token: ${errorMessage2(error)}`);
@@ -195885,7 +196755,8 @@ async function revokeServerTokens(serverName, serverConfig, { preserveStepUpStat
195885
196755
  clientId: tokenData.clientId,
195886
196756
  clientSecret: tokenData.clientSecret,
195887
196757
  accessToken: tokenData.accessToken,
195888
- authMethod
196758
+ authMethod,
196759
+ extraHeaders: configuredHeaders
195889
196760
  });
195890
196761
  } catch (error) {
195891
196762
  logMCPDebug(serverName, `Failed to revoke access token: ${errorMessage2(error)}`);
@@ -196078,8 +196949,9 @@ async function performMCPOAuthFlow(serverName, serverConfig, onAuthorizationUrl,
196078
196949
  const redirectUri = buildRedirectUri(port);
196079
196950
  logMCPDebug(serverName, `Using redirect port: ${port}${configuredCallbackPort ? " (from config)" : ""}`);
196080
196951
  const provider = new ClaudeAuthProvider(serverName, serverConfig, redirectUri, true, onAuthorizationUrl, options?.skipBrowserOpen);
196952
+ const oauthFetch = await withConfiguredHeaders(createAuthFetch(), serverName, serverConfig);
196081
196953
  try {
196082
- const metadata = await fetchAuthServerMetadata(serverName, serverConfig.url, serverConfig.oauth?.authServerMetadataUrl, undefined, wwwAuthParams.resourceMetadataUrl);
196954
+ const metadata = await fetchAuthServerMetadata(serverName, serverConfig.url, serverConfig.oauth?.authServerMetadataUrl, oauthFetch, wwwAuthParams.resourceMetadataUrl);
196083
196955
  if (metadata) {
196084
196956
  provider.setMetadata(metadata);
196085
196957
  logMCPDebug(serverName, `Fetched OAuth metadata with scope: ${getScopeFromMetadata(metadata) || "NONE"}`);
@@ -196215,7 +197087,8 @@ async function performMCPOAuthFlow(serverName, serverConfig, onAuthorizationUrl,
196215
197087
  const result = await auth(provider, {
196216
197088
  serverUrl: serverConfig.url,
196217
197089
  scope: wwwAuthParams.scope,
196218
- resourceMetadataUrl: wwwAuthParams.resourceMetadataUrl
197090
+ resourceMetadataUrl: wwwAuthParams.resourceMetadataUrl,
197091
+ fetchFn: oauthFetch
196219
197092
  });
196220
197093
  logMCPDebug(serverName, `Initial auth result: ${result}`);
196221
197094
  if (result !== "REDIRECT") {
@@ -196239,7 +197112,8 @@ async function performMCPOAuthFlow(serverName, serverConfig, onAuthorizationUrl,
196239
197112
  const result = await auth(provider, {
196240
197113
  serverUrl: serverConfig.url,
196241
197114
  authorizationCode,
196242
- resourceMetadataUrl: wwwAuthParams.resourceMetadataUrl
197115
+ resourceMetadataUrl: wwwAuthParams.resourceMetadataUrl,
197116
+ fetchFn: oauthFetch
196243
197117
  });
196244
197118
  logMCPDebug(serverName, `Auth result: ${result}`);
196245
197119
  if (result === "AUTHORIZED") {
@@ -196746,7 +197620,7 @@ class ClaudeAuthProvider {
196746
197620
  if (metadataUrl) {
196747
197621
  logMCPDebug(this.serverName, `Fetching metadata from configured URL: ${metadataUrl}`);
196748
197622
  try {
196749
- const metadata = await fetchAuthServerMetadata(this.serverName, this.serverConfig.url, metadataUrl);
197623
+ const metadata = await fetchAuthServerMetadata(this.serverName, this.serverConfig.url, metadataUrl, await withConfiguredHeaders(createAuthFetch(), this.serverName, this.serverConfig));
196750
197624
  if (metadata) {
196751
197625
  return {
196752
197626
  authorizationServerUrl: metadata.issuer,
@@ -196831,7 +197705,7 @@ class ClaudeAuthProvider {
196831
197705
  for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
196832
197706
  try {
196833
197707
  logMCPDebug(this.serverName, `Starting token refresh`);
196834
- const authFetch = createAuthFetch();
197708
+ const authFetch = await withConfiguredHeaders(createAuthFetch(), this.serverName, this.serverConfig);
196835
197709
  let metadata = this._metadata;
196836
197710
  if (!metadata) {
196837
197711
  const cached = await this.discoveryState();
@@ -197007,6 +197881,7 @@ var init_auth3 = __esm(() => {
197007
197881
  init_secureStorage();
197008
197882
  init_macOsKeychainHelpers();
197009
197883
  init_slowOperations();
197884
+ init_headersHelper();
197010
197885
  init_oauthPort();
197011
197886
  init_utils6();
197012
197887
  init_xaa();
@@ -201002,10 +201877,10 @@ function parseProxyResponse(socket) {
201002
201877
  read();
201003
201878
  });
201004
201879
  }
201005
- var import_debug52, debug;
201880
+ var import_debug54, debug;
201006
201881
  var init_parse_proxy_response = __esm(() => {
201007
- import_debug52 = __toESM(require_src(), 1);
201008
- debug = import_debug52.default("https-proxy-agent:parse-proxy-response");
201882
+ import_debug54 = __toESM(require_src(), 1);
201883
+ debug = import_debug54.default("https-proxy-agent:parse-proxy-response");
201009
201884
  });
201010
201885
 
201011
201886
  // node_modules/proxy-agent-negotiate/dist/index.js
@@ -201058,7 +201933,7 @@ function omit2(obj, ...keys) {
201058
201933
  }
201059
201934
  return ret;
201060
201935
  }
201061
- var import_debug53, debug2, setServernameFromNonIpHost = (options) => {
201936
+ var import_debug55, debug2, setServernameFromNonIpHost = (options) => {
201062
201937
  if (options.servername === undefined && options.host && !net2.isIP(options.host)) {
201063
201938
  return {
201064
201939
  ...options,
@@ -201070,8 +201945,8 @@ var import_debug53, debug2, setServernameFromNonIpHost = (options) => {
201070
201945
  var init_dist7 = __esm(() => {
201071
201946
  init_dist6();
201072
201947
  init_parse_proxy_response();
201073
- import_debug53 = __toESM(require_src(), 1);
201074
- debug2 = import_debug53.default("https-proxy-agent");
201948
+ import_debug55 = __toESM(require_src(), 1);
201949
+ debug2 = import_debug55.default("https-proxy-agent");
201075
201950
  HttpsProxyAgent2 = class HttpsProxyAgent2 extends Agent2 {
201076
201951
  constructor(proxy, opts) {
201077
201952
  super(opts);
@@ -202132,73 +203007,6 @@ var init_classifyForCollapse = __esm(() => {
202132
203007
  "resources_list"
202133
203008
  ]);
202134
203009
  });
202135
-
202136
- // src/tui/services/mcp/headersHelper.ts
202137
- function isMcpServerFromProjectOrLocalSettings(config) {
202138
- return config.scope === "project" || config.scope === "local";
202139
- }
202140
- async function getMcpHeadersFromHelper(serverName, config) {
202141
- if (!config.headersHelper) {
202142
- return null;
202143
- }
202144
- if ("scope" in config && isMcpServerFromProjectOrLocalSettings(config)) {
202145
- const isNonInteractive = getIsNonInteractiveSession();
202146
- const explicitTrust = process.env.ATLAS_TRUST_WORKSPACE === "true";
202147
- const hasTrust = checkHasTrustDialogAccepted() || isNonInteractive && explicitTrust;
202148
- if (!hasTrust) {
202149
- const error = new Error(`Security: headersHelper for MCP server '${serverName}' executed before workspace trust is confirmed. If you see this message, post in ${MACRO.FEEDBACK_CHANNEL}.`);
202150
- logAntError("MCP headersHelper invoked before trust check", error);
202151
- return null;
202152
- }
202153
- }
202154
- try {
202155
- logMCPDebug(serverName, "Executing headersHelper to get dynamic headers");
202156
- const execResult = await execFileNoThrowWithCwd2("sh", ["-c", config.headersHelper], {
202157
- timeout: 1e4,
202158
- env: {
202159
- ...process.env,
202160
- ATLAS_MCP_SERVER_NAME: serverName,
202161
- ATLAS_MCP_SERVER_URL: config.url
202162
- }
202163
- });
202164
- if (execResult.code !== 0 || !execResult.stdout) {
202165
- throw new Error(`headersHelper for MCP server '${serverName}' did not return a valid value`);
202166
- }
202167
- const result = execResult.stdout.trim();
202168
- const headers = jsonParse2(result);
202169
- if (typeof headers !== "object" || headers === null || Array.isArray(headers)) {
202170
- throw new Error(`headersHelper for MCP server '${serverName}' must return a JSON object with string key-value pairs`);
202171
- }
202172
- for (const [key, value] of Object.entries(headers)) {
202173
- if (typeof value !== "string") {
202174
- throw new Error(`headersHelper for MCP server '${serverName}' returned non-string value for key "${key}": ${typeof value}`);
202175
- }
202176
- }
202177
- logMCPDebug(serverName, `Successfully retrieved ${Object.keys(headers).length} headers from headersHelper`);
202178
- return headers;
202179
- } catch (error) {
202180
- logMCPError(serverName, `Error getting headers from headersHelper: ${errorMessage2(error)}`);
202181
- logError3(new Error(`Error getting MCP headers from headersHelper for server '${serverName}': ${errorMessage2(error)}`));
202182
- return null;
202183
- }
202184
- }
202185
- async function getMcpServerHeaders(serverName, config) {
202186
- const staticHeaders = config.headers || {};
202187
- const dynamicHeaders = await getMcpHeadersFromHelper(serverName, config) || {};
202188
- return {
202189
- ...staticHeaders,
202190
- ...dynamicHeaders
202191
- };
202192
- }
202193
- var init_headersHelper = __esm(() => {
202194
- init_bootstrap();
202195
- init_config6();
202196
- init_debug();
202197
- init_errors4();
202198
- init_execFileNoThrow2();
202199
- init_log5();
202200
- init_slowOperations();
202201
- });
202202
203010
  // src/tui/skills/mcpSkills.ts
202203
203011
  var exports_mcpSkills = {};
202204
203012
  __export(exports_mcpSkills, {
@@ -209645,6 +210453,11 @@ function isMcpSessionExpiredError(error) {
209645
210453
  }
209646
210454
  return error.message.includes('"code":-32001') || error.message.includes('"code": -32001');
209647
210455
  }
210456
+ function truncateMcpDescriptionText(text) {
210457
+ if (!text)
210458
+ return text;
210459
+ return text.length > MAX_MCP_DESCRIPTION_LENGTH ? text.slice(0, MAX_MCP_DESCRIPTION_LENGTH) + "… [truncated]" : text;
210460
+ }
209648
210461
  function getMcpToolTimeoutMs() {
209649
210462
  return parseInt(process.env.MCP_TOOL_TIMEOUT || "", 10) || DEFAULT_MCP_TOOL_TIMEOUT_MS;
209650
210463
  }
@@ -209693,46 +210506,6 @@ async function createNodeWsClient(url, options) {
209693
210506
  const WS = wrapper_default;
209694
210507
  return new WS(url, ["mcp"], options);
209695
210508
  }
209696
- function getConnectionTimeoutMs2() {
209697
- return parseInt(process.env.MCP_TIMEOUT || "", 10) || 30000;
209698
- }
209699
- function wrapFetchWithTimeout(baseFetch) {
209700
- return async (url, init) => {
209701
- const method = (init?.method ?? "GET").toUpperCase();
209702
- if (method === "GET") {
209703
- return baseFetch(url, init);
209704
- }
209705
- const headers = new Headers(init?.headers);
209706
- if (!headers.has("accept")) {
209707
- headers.set("accept", MCP_STREAMABLE_HTTP_ACCEPT);
209708
- }
209709
- const controller = new AbortController;
209710
- const timer = setTimeout((c) => c.abort(new DOMException("The operation timed out.", "TimeoutError")), MCP_REQUEST_TIMEOUT_MS, controller);
209711
- timer.unref?.();
209712
- const parentSignal = init?.signal;
209713
- const abort = () => controller.abort(parentSignal?.reason);
209714
- parentSignal?.addEventListener("abort", abort);
209715
- if (parentSignal?.aborted) {
209716
- controller.abort(parentSignal.reason);
209717
- }
209718
- const cleanup = () => {
209719
- clearTimeout(timer);
209720
- parentSignal?.removeEventListener("abort", abort);
209721
- };
209722
- try {
209723
- const response = await baseFetch(url, {
209724
- ...init,
209725
- headers,
209726
- signal: controller.signal
209727
- });
209728
- cleanup();
209729
- return response;
209730
- } catch (error) {
209731
- cleanup();
209732
- throw error;
209733
- }
209734
- };
209735
- }
209736
210509
  function getMcpServerConnectionBatchSize() {
209737
210510
  return parseInt(process.env.MCP_SERVER_CONNECTION_BATCH_SIZE || "", 10) || 3;
209738
210511
  }
@@ -210382,7 +211155,7 @@ function extractToolUseId(message) {
210382
211155
  }
210383
211156
  return message.message.content[0].id;
210384
211157
  }
210385
- var fetchMcpSkillsForClient2, McpAuthError, McpSessionExpiredError, McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, DEFAULT_MCP_TOOL_TIMEOUT_MS = 1e8, MAX_MCP_DESCRIPTION_LENGTH = 2048, computerUseWrapper, isComputerUseMCPServer2, MCP_AUTH_CACHE_TTL_MS, authCachePromise = null, writeChain, IMAGE_MIME_TYPES, MCP_REQUEST_TIMEOUT_MS = 60000, MCP_STREAMABLE_HTTP_ACCEPT = "application/json, text/event-stream", ALLOWED_IDE_TOOLS, connectToServer, MCP_FETCH_CACHE_SIZE2 = 20, fetchToolsForClient2, fetchResourcesForClient2, fetchCommandsForClient2;
211158
+ var fetchMcpSkillsForClient2, McpAuthError, McpSessionExpiredError, McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, DEFAULT_MCP_TOOL_TIMEOUT_MS = 1e8, MAX_MCP_DESCRIPTION_LENGTH = 4096, computerUseWrapper, isComputerUseMCPServer2, MCP_AUTH_CACHE_TTL_MS, authCachePromise = null, writeChain, IMAGE_MIME_TYPES, ALLOWED_IDE_TOOLS, connectToServer, MCP_FETCH_CACHE_SIZE2 = 20, fetchToolsForClient2, fetchResourcesForClient2, fetchCommandsForClient2;
210386
211159
  var init_client4 = __esm(() => {
210387
211160
  init_shared();
210388
211161
  init_client3();
@@ -210674,7 +211447,7 @@ var init_client4 = __esm(() => {
210674
211447
  ]
210675
211448
  };
210676
211449
  });
210677
- logMCPDebug(name, `Starting connection with timeout of ${getConnectionTimeoutMs2()}ms`);
211450
+ logMCPDebug(name, `Starting connection with timeout of ${getMcpConnectionTimeoutMs()}ms`);
210678
211451
  if (serverRef.type === "http") {
210679
211452
  logMCPDebug(name, `Testing basic HTTP connectivity to ${serverRef.url}`);
210680
211453
  try {
@@ -210691,13 +211464,13 @@ var init_client4 = __esm(() => {
210691
211464
  const timeoutPromise = new Promise((_, reject) => {
210692
211465
  const timeoutId = setTimeout(() => {
210693
211466
  const elapsed = Date.now() - connectStartTime;
210694
- logMCPDebug(name, `Connection timeout triggered after ${elapsed}ms (limit: ${getConnectionTimeoutMs2()}ms)`);
211467
+ logMCPDebug(name, `Connection timeout triggered after ${elapsed}ms (limit: ${getMcpConnectionTimeoutMs()}ms)`);
210695
211468
  if (inProcessServer) {
210696
211469
  inProcessServer.close().catch(() => {});
210697
211470
  }
210698
211471
  transport.close().catch(() => {});
210699
- reject(new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS(`MCP server "${name}" connection timed out after ${getConnectionTimeoutMs2()}ms`, "MCP connection timeout"));
210700
- }, getConnectionTimeoutMs2());
211472
+ reject(new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS(`MCP server "${name}" connection timed out after ${getMcpConnectionTimeoutMs()}ms`, "MCP connection timeout"));
211473
+ }, getMcpConnectionTimeoutMs());
210701
211474
  connectPromise.then(() => {
210702
211475
  clearTimeout(timeoutId);
210703
211476
  }, (_error) => {
@@ -210745,9 +211518,8 @@ var init_client4 = __esm(() => {
210745
211518
  const capabilities = client.getServerCapabilities();
210746
211519
  const serverVersion = client.getServerVersion();
210747
211520
  const rawInstructions = client.getInstructions();
210748
- let instructions = rawInstructions;
210749
- if (rawInstructions && rawInstructions.length > MAX_MCP_DESCRIPTION_LENGTH) {
210750
- instructions = rawInstructions.slice(0, MAX_MCP_DESCRIPTION_LENGTH) + "… [truncated]";
211521
+ const instructions = truncateMcpDescriptionText(rawInstructions);
211522
+ if (instructions !== rawInstructions) {
210751
211523
  logMCPDebug(name, `Server instructions truncated from ${rawInstructions.length} to ${MAX_MCP_DESCRIPTION_LENGTH} chars`);
210752
211524
  }
210753
211525
  logMCPDebug(name, `Connection established with capabilities: ${jsonStringify5({
@@ -211035,8 +211807,7 @@ var init_client4 = __esm(() => {
211035
211807
  return tool.description ?? "";
211036
211808
  },
211037
211809
  async prompt() {
211038
- const desc = tool.description ?? "";
211039
- return desc.length > MAX_MCP_DESCRIPTION_LENGTH ? desc.slice(0, MAX_MCP_DESCRIPTION_LENGTH) + "… [truncated]" : desc;
211810
+ return truncateMcpDescriptionText(tool.description ?? "");
211040
211811
  },
211041
211812
  isConcurrencySafe() {
211042
211813
  return tool.annotations?.readOnlyHint ?? false;
@@ -262650,6 +263421,9 @@ function convertToHookMatchers(sessionMatchers) {
262650
263421
  return sessionMatchers.map((sm) => ({
262651
263422
  matcher: sm.matcher,
262652
263423
  skillRoot: sm.skillRoot,
263424
+ pluginRoot: sm.pluginRoot,
263425
+ pluginName: sm.pluginName,
263426
+ pluginId: sm.pluginId,
262653
263427
  hooks: sm.hooks.map((h) => h.hook).filter((h) => h.type !== "function")
262654
263428
  }));
262655
263429
  }
@@ -273429,6 +274203,19 @@ async function toArray2(generator) {
273429
274203
  }
273430
274204
  return result;
273431
274205
  }
274206
+ async function runWithConcurrencyLimit(tasks, limit) {
274207
+ const results = new Array(tasks.length);
274208
+ let next = 0;
274209
+ const worker = async () => {
274210
+ while (next < tasks.length) {
274211
+ const index = next++;
274212
+ results[index] = await tasks[index]();
274213
+ }
274214
+ };
274215
+ const workers = Math.max(1, Math.min(limit, tasks.length));
274216
+ await Promise.all(Array.from({ length: workers }, worker));
274217
+ return results;
274218
+ }
273432
274219
  var NO_VALUE;
273433
274220
  var init_generators = __esm(() => {
273434
274221
  NO_VALUE = Symbol("NO_VALUE");
@@ -276339,6 +277126,15 @@ function filterToolsForAgent2({
276339
277126
  function computeChildSpawnDepth2(parentOptions) {
276340
277127
  return (parentOptions?.spawnDepth ?? 0) + 1;
276341
277128
  }
277129
+ function resolveMcpServerDomainTools2(toolName, available) {
277130
+ if (typeof toolName !== "string")
277131
+ return null;
277132
+ const info = mcpInfoFromString3(toolName);
277133
+ if (!info || info.toolName !== undefined)
277134
+ return null;
277135
+ const prefix = getMcpPrefix3(info.serverName);
277136
+ return available.filter((t) => t.name.startsWith(prefix));
277137
+ }
276342
277138
  function resolveAgentTools2(agentDefinition, availableTools, isAsync = false, isMainThread = false, allowFanOut = false) {
276343
277139
  const {
276344
277140
  tools: agentTools,
@@ -276395,7 +277191,18 @@ function resolveAgentTools2(agentDefinition, availableTools, isAsync = false, is
276395
277191
  resolvedToolsSet.add(tool);
276396
277192
  }
276397
277193
  } else {
276398
- invalidTools.push(toolSpec);
277194
+ const domain = resolveMcpServerDomainTools2(toolName, allowedAvailableTools);
277195
+ if (domain && domain.length > 0) {
277196
+ validTools.push(toolSpec);
277197
+ for (const t of domain) {
277198
+ if (!resolvedToolsSet.has(t)) {
277199
+ resolved.push(t);
277200
+ resolvedToolsSet.add(t);
277201
+ }
277202
+ }
277203
+ } else {
277204
+ invalidTools.push(toolSpec);
277205
+ }
276399
277206
  }
276400
277207
  }
276401
277208
  return {
@@ -276652,6 +277459,7 @@ var init_agentToolUtils2 = __esm(() => {
276652
277459
  init_errors4();
276653
277460
  init_messages3();
276654
277461
  init_permissionRuleParser2();
277462
+ init_mcpStringUtils();
276655
277463
  init_yoloClassifier();
276656
277464
  init_sdkProgress();
276657
277465
  init_teammateContext3();
@@ -303311,11 +304119,11 @@ function byteAt2(L, charIdx) {
303311
304119
  L.byteTable = t;
303312
304120
  return t[charIdx];
303313
304121
  }
303314
- function isWordChar2(c) {
304122
+ function isWordChar4(c) {
303315
304123
  return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === "/" || c === "." || c === "-" || c === "+" || c === ":" || c === "@" || c === "%" || c === "," || c === "~" || c === "^" || c === "?" || c === "*" || c === "!" || c === "=" || c === "[" || c === "]";
303316
304124
  }
303317
304125
  function isWordStart2(c) {
303318
- return isWordChar2(c) || c === "\\";
304126
+ return isWordChar4(c) || c === "\\";
303319
304127
  }
303320
304128
  function isIdentStart2(c) {
303321
304129
  return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
@@ -303625,7 +304433,7 @@ function nextToken2(L, ctx = "arg") {
303625
304433
  advance2(L);
303626
304434
  continue;
303627
304435
  }
303628
- if (!isWordChar2(ch) && ch !== "{" && ch !== "}") {
304436
+ if (!isWordChar4(ch) && ch !== "{" && ch !== "}") {
303629
304437
  break;
303630
304438
  }
303631
304439
  advance2(L);
@@ -321769,7 +322577,8 @@ function buildAgentLoopParams(m) {
321769
322577
  }
321770
322578
  },
321771
322579
  systemPrompt: asSystemPrompt(appendSystemContext(m.systemPrompt, systemContext)),
321772
- effortValue: m.effort !== undefined ? String(m.effort) : undefined
322580
+ effortValue: m.effort !== undefined ? String(m.effort) : undefined,
322581
+ refreshRoundContext: m.refreshRoundContext
321773
322582
  };
321774
322583
  const unboundedTurns = m.unboundedTurns ?? m.maxTurns === undefined;
321775
322584
  const args = {
@@ -322338,7 +323147,8 @@ function getDefaultAppState() {
322338
323147
  tools: [],
322339
323148
  commands: [],
322340
323149
  resources: {},
322341
- pluginReconnectKey: 0
323150
+ pluginReconnectKey: 0,
323151
+ cwdReconnectKey: 0
322342
323152
  },
322343
323153
  plugins: {
322344
323154
  enabled: [],
@@ -325228,7 +326038,7 @@ function parseSkillFrontmatterFields2(frontmatter, markdownContent, resolvedName
325228
326038
  logForDebugging3(`Skill ${resolvedName} has invalid effort '${effortRaw}'. Valid options: ${EFFORT_LEVELS2.join(", ")} or an integer`);
325229
326039
  }
325230
326040
  return {
325231
- displayName: frontmatter.name != null ? String(frontmatter.name) : undefined,
326041
+ displayName: sanitizeDisplayName2(frontmatter.name != null ? String(frontmatter.name) : undefined, resolvedName),
325232
326042
  description,
325233
326043
  hasUserSpecifiedDescription: validatedDescription !== null,
325234
326044
  allowedTools: parseSlashCommandToolsFromFrontmatter2(frontmatter["allowed-tools"]),
@@ -325344,6 +326154,10 @@ async function loadSkillsFromSkillsDir2(basePath, source) {
325344
326154
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
325345
326155
  return null;
325346
326156
  }
326157
+ if (isUnsafeCommandName2(entry.name)) {
326158
+ logForDebugging3(`[skills] skipping unsafe skill directory name: ${JSON.stringify(entry.name)}`);
326159
+ return null;
326160
+ }
325347
326161
  const skillDirPath = join108(basePath, entry.name);
325348
326162
  const skillFilePath = join108(skillDirPath, "SKILL.md");
325349
326163
  let content;
@@ -325454,6 +326268,10 @@ async function loadSkillsFromCommandsDir2(cwd) {
325454
326268
  content,
325455
326269
  source
325456
326270
  });
326271
+ if (isUnsafeCommandName2(cmdName)) {
326272
+ logForDebugging3(`[skills] skipping unsafe command name: ${JSON.stringify(cmdName)}`);
326273
+ continue;
326274
+ }
325457
326275
  const parsed = parseSkillFrontmatterFields2(frontmatter, content, cmdName, "Custom command");
325458
326276
  skills.push({
325459
326277
  skill: createSkillCommand2({
@@ -325592,6 +326410,7 @@ var init_loadSkillsDir2 = __esm(() => {
325592
326410
  init_bootstrapState();
325593
326411
  init_tokenEstimation();
325594
326412
  init_argumentSubstitution2();
326413
+ init_commandNameSafety2();
325595
326414
  init_debug();
325596
326415
  init_effort2();
325597
326416
  init_envUtils2();
@@ -330013,7 +330832,7 @@ function createPluginCommand(commandName, file, sourceName, pluginManifest, plug
330013
330832
  const argumentNames = parseArgumentNames2(frontmatter.arguments);
330014
330833
  const whenToUse = frontmatter["when-to-use"] ?? frontmatter.when_to_use;
330015
330834
  const version = frontmatter.version;
330016
- const displayName = frontmatter.name;
330835
+ const displayName = sanitizeDisplayName2(frontmatter.name, commandName);
330017
330836
  const model = frontmatter.model === "inherit" ? undefined : frontmatter.model ? parseUserSpecifiedModel2(frontmatter.model) : undefined;
330018
330837
  const effortRaw = frontmatter["effort"];
330019
330838
  const effort = effortRaw !== undefined ? parseEffortValue2(effortRaw) : undefined;
@@ -330198,6 +331017,7 @@ var init_loadPluginCommands = __esm(() => {
330198
331017
  init_bootstrapState();
330199
331018
  init_argumentSubstitution2();
330200
331019
  init_debug();
331020
+ init_commandNameSafety2();
330201
331021
  init_effort2();
330202
331022
  init_envUtils2();
330203
331023
  init_errors4();
@@ -330349,8 +331169,15 @@ var init_loadPluginCommands = __esm(() => {
330349
331169
  return pluginCommands;
330350
331170
  }));
330351
331171
  const allCommands = perPluginCommands.flat();
330352
- logForDebugging3(`Total plugin commands loaded: ${allCommands.length}`);
330353
- return allCommands;
331172
+ const safeCommands = allCommands.filter((cmd) => {
331173
+ if (isUnsafeCommandName2(cmd.name)) {
331174
+ logForDebugging3(`Skipping unsafe plugin command name: ${JSON.stringify(cmd.name)}`);
331175
+ return false;
331176
+ }
331177
+ return true;
331178
+ });
331179
+ logForDebugging3(`Total plugin commands loaded: ${safeCommands.length}`);
331180
+ return safeCommands;
330354
331181
  });
330355
331182
  getPluginSkills = memoize_default(async () => {
330356
331183
  if (isBareMode() && getInlinePlugins().length === 0) {
@@ -330395,8 +331222,15 @@ var init_loadPluginCommands = __esm(() => {
330395
331222
  return pluginSkills;
330396
331223
  }));
330397
331224
  const allSkills = perPluginSkills.flat();
330398
- logForDebugging3(`Total plugin skills loaded: ${allSkills.length}`);
330399
- return allSkills;
331225
+ const safeSkills = allSkills.filter((cmd) => {
331226
+ if (isUnsafeCommandName2(cmd.name)) {
331227
+ logForDebugging3(`Skipping unsafe plugin skill name: ${JSON.stringify(cmd.name)}`);
331228
+ return false;
331229
+ }
331230
+ return true;
331231
+ });
331232
+ logForDebugging3(`Total plugin skills loaded: ${safeSkills.length}`);
331233
+ return safeSkills;
330400
331234
  });
330401
331235
  });
330402
331236
 
@@ -335419,6 +336253,12 @@ function mergePluginSources(sources) {
335419
336253
  const marketplacePlugins = sources.marketplace.filter((p) => {
335420
336254
  if (sessionNames.has(p.name)) {
335421
336255
  logForDebugging3(`Plugin "${p.name}" from --plugin-dir overrides installed version`);
336256
+ errors.push({
336257
+ type: "generic-error",
336258
+ source: p.source,
336259
+ plugin: p.name,
336260
+ error: `Installed plugin "${p.name}" excluded (hooks and commands inactive) — overridden by --plugin-dir copy of the same name`
336261
+ });
335422
336262
  return false;
335423
336263
  }
335424
336264
  return true;
@@ -337425,8 +338265,9 @@ function handleMessageFromStream(message, onMessage, onUpdateLength, onSetStream
337425
338265
  }
337426
338266
  }
337427
338267
  function wrapInSystemReminder(content) {
338268
+ const neutralized = content.replaceAll("</system-reminder>", "&lt;/system-reminder&gt;").replaceAll("<system-reminder>", "&lt;system-reminder&gt;");
337428
338269
  return `<system-reminder>
337429
- ${content}
338270
+ ${neutralized}
337430
338271
  </system-reminder>`;
337431
338272
  }
337432
338273
  function wrapMessagesInSystemReminder(messages) {
@@ -340035,6 +340876,16 @@ async function hasPermissionsToUseToolInner(tool, input, context) {
340035
340876
  throw new AbortError2;
340036
340877
  }
340037
340878
  let appState = context.getAppState();
340879
+ if (appState.toolPermissionContext.mode === "plan" && !appState.toolPermissionContext.isBypassPermissionsModeAvailable && !PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES2.has(tool.name) && !(tool.isReadOnly?.(input) ?? false)) {
340880
+ return {
340881
+ behavior: "deny",
340882
+ message: PLAN_MODE_WRITE_DENIED_MESSAGE2(tool.name),
340883
+ decisionReason: {
340884
+ type: "mode",
340885
+ mode: "plan"
340886
+ }
340887
+ };
340888
+ }
340038
340889
  const denyRule = getDenyRuleForTool2(appState.toolPermissionContext, tool);
340039
340890
  if (denyRule) {
340040
340891
  return {
@@ -340215,7 +341066,7 @@ function syncPermissionRulesFromDisk2(toolPermissionContext, rules) {
340215
341066
  function getUpdatedInputOrFallback2(permissionResult, fallback) {
340216
341067
  return ("updatedInput" in permissionResult ? permissionResult.updatedInput : undefined) ?? fallback;
340217
341068
  }
340218
- var classifierDecisionModule, autoModeStateModule3, CLASSIFIER_FAIL_CLOSED_REFRESH_MS, PERMISSION_RULE_SOURCES2, hasPermissionsToUseTool2 = async (tool, input, context, assistantMessage, toolUseID) => {
341069
+ var PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES2, PLAN_MODE_WRITE_DENIED_MESSAGE2 = (toolName) => `Plan mode is active — ${toolName} is not allowed to make changes. ` + `Present your plan and use ExitPlanMode, or ask the user to change the permission mode.`, classifierDecisionModule, autoModeStateModule3, CLASSIFIER_FAIL_CLOSED_REFRESH_MS, PERMISSION_RULE_SOURCES2, hasPermissionsToUseTool2 = async (tool, input, context, assistantMessage, toolUseID) => {
340219
341070
  const result = await hasPermissionsToUseToolInner(tool, input, context);
340220
341071
  if (result.behavior === "allow") {
340221
341072
  const appState = context.getAppState();
@@ -340448,6 +341299,10 @@ var init_permissions6 = __esm(() => {
340448
341299
  init_slowOperations();
340449
341300
  init_denialTracking();
340450
341301
  init_yoloClassifier();
341302
+ PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES2 = new Set([
341303
+ "EnterPlanMode",
341304
+ "ExitPlanMode"
341305
+ ]);
340451
341306
  classifierDecisionModule = feature("TRANSCRIPT_CLASSIFIER") ? (init_classifierDecision(), __toCommonJS(exports_classifierDecision)) : null;
340452
341307
  autoModeStateModule3 = feature("TRANSCRIPT_CLASSIFIER") ? __toCommonJS(exports_autoModeState) : null;
340453
341308
  CLASSIFIER_FAIL_CLOSED_REFRESH_MS = 30 * 60 * 1000;
@@ -348079,7 +348934,8 @@ async function clearConversation({
348079
348934
  tools: [],
348080
348935
  commands: [],
348081
348936
  resources: {},
348082
- pluginReconnectKey: prev.mcp.pluginReconnectKey
348937
+ pluginReconnectKey: prev.mcp.pluginReconnectKey,
348938
+ cwdReconnectKey: prev.mcp.cwdReconnectKey
348083
348939
  }
348084
348940
  };
348085
348941
  });
@@ -369468,7 +370324,9 @@ function useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig = false) {
369468
370324
  const store = useAppStateStore();
369469
370325
  const _authVersion = useAppState((s) => s.authVersion);
369470
370326
  const _pluginReconnectKey = useAppState((s) => s.mcp.pluginReconnectKey);
370327
+ const _cwdReconnectKey = useAppState((s) => s.mcp.cwdReconnectKey);
369471
370328
  const setAppState = useSetAppState();
370329
+ const lastCwdRef = import_react110.useRef(null);
369472
370330
  const reconnectTimersRef = import_react110.useRef(new Map);
369473
370331
  const mcpFailureTrackerRef = import_react110.useRef(null);
369474
370332
  const notifyMcpConnectionFailure = import_react110.useCallback((serverName, error) => {
@@ -369695,9 +370553,12 @@ function useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig = false) {
369695
370553
  async function initializeServersAsPending() {
369696
370554
  const { servers: existingConfigs, errors: mcpErrors } = isStrictMcpConfig ? { servers: {}, errors: [] } : await getMcpConfigs(dynamicMcpConfig);
369697
370555
  const configs = { ...existingConfigs, ...dynamicMcpConfig };
370556
+ const currentCwd = getCwd();
370557
+ const cwdChanged = lastCwdRef.current !== null && lastCwdRef.current !== currentCwd;
370558
+ lastCwdRef.current = currentCwd;
369698
370559
  addErrorsToAppState(setAppState, mcpErrors);
369699
370560
  setAppState((prevState) => {
369700
- const { stale, ...mcpWithoutStale } = excludeStalePluginClients(prevState.mcp, configs);
370561
+ const { stale, ...mcpWithoutStale } = excludeStalePluginClients(prevState.mcp, configs, { absentIsStale: cwdChanged });
369701
370562
  for (const s of stale) {
369702
370563
  const timer = reconnectTimersRef.current.get(s.name);
369703
370564
  if (timer) {
@@ -369736,7 +370597,8 @@ function useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig = false) {
369736
370597
  dynamicMcpConfig,
369737
370598
  setAppState,
369738
370599
  sessionId,
369739
- _pluginReconnectKey
370600
+ _pluginReconnectKey,
370601
+ _cwdReconnectKey
369740
370602
  ]);
369741
370603
  import_react110.useEffect(() => {
369742
370604
  let cancelled = false;
@@ -369753,7 +370615,7 @@ function useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig = false) {
369753
370615
  });
369754
370616
  let claudeaiConfigs = {};
369755
370617
  if (!isStrictMcpConfig) {
369756
- claudeaiConfigs = filterMcpServersByPolicy(await claudeaiPromise).allowed;
370618
+ claudeaiConfigs = filterMcpServersByPolicy2(await claudeaiPromise).allowed;
369757
370619
  if (cancelled)
369758
370620
  return;
369759
370621
  if (Object.keys(claudeaiConfigs).length > 0) {
@@ -369819,7 +370681,8 @@ function useManageMCPConnections(dynamicMcpConfig, isStrictMcpConfig = false) {
369819
370681
  setAppState,
369820
370682
  _authVersion,
369821
370683
  sessionId,
369822
- _pluginReconnectKey
370684
+ _pluginReconnectKey,
370685
+ _cwdReconnectKey
369823
370686
  ]);
369824
370687
  import_react110.useEffect(() => {
369825
370688
  const timers = reconnectTimersRef.current;
@@ -404670,6 +405533,8 @@ async function searchSkillsAcrossMarketplaces(query) {
404670
405533
  for (const d of dirEntries) {
404671
405534
  if (!d.isDirectory())
404672
405535
  continue;
405536
+ if (isUnsafeCommandName2(d.name))
405537
+ continue;
404673
405538
  const skillFile = join146(skillDir, d.name, "SKILL.md");
404674
405539
  if (seen.has(skillFile))
404675
405540
  continue;
@@ -404681,7 +405546,7 @@ async function searchSkillsAcrossMarketplaces(query) {
404681
405546
  continue;
404682
405547
  }
404683
405548
  const { frontmatter, content: markdown } = parseFrontmatter2(content);
404684
- const resolvedName = typeof frontmatter.name === "string" ? frontmatter.name : d.name;
405549
+ const resolvedName = d.name;
404685
405550
  const parsed = parseSkillFrontmatterFields2(frontmatter, markdown, resolvedName);
404686
405551
  const installId = createPluginId(entry.name, marketplaceName);
404687
405552
  const skill = {
@@ -404706,6 +405571,7 @@ async function searchSkillsAcrossMarketplaces(query) {
404706
405571
  return results;
404707
405572
  }
404708
405573
  var init_skillSearch = __esm(() => {
405574
+ init_commandNameSafety2();
404709
405575
  init_frontmatterParser2();
404710
405576
  init_loadSkillsDir2();
404711
405577
  init_marketplaceHelpers();
@@ -412070,7 +412936,7 @@ var call45 = async (_args, _context) => {
412070
412936
  reloadSkillsNow();
412071
412937
  return {
412072
412938
  type: "text",
412073
- value: "Re-scanned skill directories; in-session skill registry refreshed (new/removed/changed skills now active without restart)."
412939
+ value: "Re-scanned skill directories; in-session skill registry refreshed (new/removed/changed skills now active without restart). The complete skill list will be re-sent to the model with your next message."
412074
412940
  };
412075
412941
  };
412076
412942
  var init_reload_skills = __esm(() => {
@@ -412095,7 +412961,7 @@ var exports_cd = {};
412095
412961
  __export(exports_cd, {
412096
412962
  call: () => call46
412097
412963
  });
412098
- var USAGE2 = "Usage: /cd [path] (no argument = show current session cwd)", call46 = async (_args, _context) => {
412964
+ var USAGE2 = "Usage: /cd [path] (no argument = show current session cwd)", call46 = async (_args, context) => {
412099
412965
  const arg = (_args ?? "").trim();
412100
412966
  if (arg === "") {
412101
412967
  return {
@@ -412106,6 +412972,10 @@ ${USAGE2}`
412106
412972
  }
412107
412973
  try {
412108
412974
  setCwd2(arg, pwd());
412975
+ context.setAppState((prev) => ({
412976
+ ...prev,
412977
+ mcp: { ...prev.mcp, cwdReconnectKey: prev.mcp.cwdReconnectKey + 1 }
412978
+ }));
412109
412979
  return {
412110
412980
  type: "text",
412111
412981
  value: `Session working directory changed to: ${pwd()}`
@@ -428100,9 +428970,144 @@ var init_combinedAbortSignal = __esm(() => {
428100
428970
  init_abortController2();
428101
428971
  });
428102
428972
 
428973
+ // src/tui/utils/hooks/hookInputDepth.ts
428974
+ function exceedsMaxNestingDepth(value) {
428975
+ const stack = [[value, 0]];
428976
+ const visited = new WeakSet;
428977
+ while (stack.length > 0) {
428978
+ const [v, depth] = stack.pop();
428979
+ if (depth > MAX_HOOK_INPUT_NESTING_DEPTH)
428980
+ return true;
428981
+ if (v === null || typeof v !== "object")
428982
+ continue;
428983
+ if (visited.has(v))
428984
+ continue;
428985
+ visited.add(v);
428986
+ let children;
428987
+ try {
428988
+ children = Array.isArray(v) ? v : Object.values(v);
428989
+ } catch {
428990
+ continue;
428991
+ }
428992
+ for (const child of children) {
428993
+ stack.push([child, depth + 1]);
428994
+ }
428995
+ }
428996
+ return false;
428997
+ }
428998
+ var MAX_HOOK_INPUT_NESTING_DEPTH = 28000;
428999
+
429000
+ // src/tui/utils/hooks/hookInputMaterialize.ts
429001
+ function materializeHookInput(value) {
429002
+ if (value === null || typeof value !== "object") {
429003
+ return { ok: true, value, droppedKeys: [] };
429004
+ }
429005
+ const droppedKeys = [];
429006
+ const visited = new WeakMap;
429007
+ const root = Array.isArray(value) ? [] : {};
429008
+ visited.set(value, root);
429009
+ const stack = [{ src: value, target: root }];
429010
+ while (stack.length > 0) {
429011
+ const { src, target } = stack.pop();
429012
+ let keys;
429013
+ try {
429014
+ keys = Object.keys(src);
429015
+ } catch {
429016
+ droppedKeys.push("<enumeration-failed>");
429017
+ continue;
429018
+ }
429019
+ for (const key of keys) {
429020
+ let child;
429021
+ try {
429022
+ child = src[key];
429023
+ } catch {
429024
+ droppedKeys.push(key);
429025
+ continue;
429026
+ }
429027
+ if (child === null || typeof child !== "object") {
429028
+ target[key] = child;
429029
+ } else if (visited.has(child)) {
429030
+ target[key] = "[Circular]";
429031
+ } else {
429032
+ const childClone = Array.isArray(child) ? [] : {};
429033
+ visited.set(child, childClone);
429034
+ target[key] = childClone;
429035
+ stack.push({ src: child, target: childClone });
429036
+ }
429037
+ }
429038
+ }
429039
+ return { ok: true, value: root, droppedKeys };
429040
+ }
429041
+ function stringifyWithGetterFallback(value) {
429042
+ try {
429043
+ return { ok: true, value: jsonStringify5(value), droppedKeys: [] };
429044
+ } catch (error) {
429045
+ const materialized = materializeHookInput(value);
429046
+ if (materialized.ok === false)
429047
+ return { ok: false, error };
429048
+ try {
429049
+ return {
429050
+ ok: true,
429051
+ value: jsonStringify5(materialized.value),
429052
+ droppedKeys: materialized.droppedKeys
429053
+ };
429054
+ } catch {
429055
+ return { ok: false, error };
429056
+ }
429057
+ }
429058
+ }
429059
+ var init_hookInputMaterialize = __esm(() => {
429060
+ init_slowOperations();
429061
+ });
429062
+
429063
+ // src/tui/utils/hooks/hookReentrancy.ts
429064
+ import { AsyncLocalStorage as AsyncLocalStorage8 } from "async_hooks";
429065
+ function getCallbackId(fn) {
429066
+ let id = callbackIds.get(fn);
429067
+ if (id === undefined) {
429068
+ id = nextCallbackId++;
429069
+ callbackIds.set(fn, id);
429070
+ }
429071
+ return id;
429072
+ }
429073
+ function getHookReentrancyKey(hookEvent, hook, pluginRoot) {
429074
+ const h = hook;
429075
+ const body = typeof h.callback === "function" ? `fn:${getCallbackId(h.callback)}` : String(h.command ?? h.prompt ?? h.url ?? "");
429076
+ return `${hookEvent}:${String(h.type)}:${pluginRoot ?? ""}:${body}`;
429077
+ }
429078
+ function isReentrantHook(hookEvent, hook, pluginRoot) {
429079
+ const inFlight = hookReentrancyStorage.getStore();
429080
+ if (inFlight === undefined)
429081
+ return false;
429082
+ return inFlight.has(getHookReentrancyKey(hookEvent, hook, pluginRoot));
429083
+ }
429084
+ function runWithHookReentrancy(hookEvent, hook, pluginRoot, fn) {
429085
+ const store = new Set(hookReentrancyStorage.getStore() ?? []);
429086
+ store.add(getHookReentrancyKey(hookEvent, hook, pluginRoot));
429087
+ return hookReentrancyStorage.run(store, fn);
429088
+ }
429089
+ var hookReentrancyStorage, callbackIds, nextCallbackId = 1;
429090
+ var init_hookReentrancy = __esm(() => {
429091
+ hookReentrancyStorage = new AsyncLocalStorage8;
429092
+ callbackIds = new WeakMap;
429093
+ });
429094
+
429095
+ // src/tui/utils/hooks/hookFailClosed.ts
429096
+ function shouldFailClosePluginHook(hookEvent, pluginRoot) {
429097
+ return pluginRoot !== undefined && GATE_EVENTS.has(hookEvent);
429098
+ }
429099
+ var GATE_EVENTS;
429100
+ var init_hookFailClosed = __esm(() => {
429101
+ GATE_EVENTS = new Set([
429102
+ "PreToolUse",
429103
+ "UserPromptSubmit",
429104
+ "PermissionRequest"
429105
+ ]);
429106
+ });
429107
+
428103
429108
  // src/tui/utils/hooks/hookHelpers.ts
428104
429109
  function addArgumentsToPrompt(prompt, jsonInput) {
428105
- return substituteArguments2(prompt, jsonInput);
429110
+ return substituteArgumentsStrict2(prompt, jsonInput);
428106
429111
  }
428107
429112
  function createStructuredOutputTool() {
428108
429113
  return {
@@ -428150,6 +429155,19 @@ async function execPromptHook(hook, hookName, hookEvent, jsonInput, signal, tool
428150
429155
  const effectiveToolUseID = toolUseID || `hook-${randomUUID41()}`;
428151
429156
  try {
428152
429157
  const processedPrompt = addArgumentsToPrompt(hook.prompt, jsonInput);
429158
+ if (processedPrompt === null) {
429159
+ logForDebugging3(`Hooks: hook input exceeds substitution budget — blocking (fail-closed)`);
429160
+ return {
429161
+ hook,
429162
+ outcome: "blocking",
429163
+ blockingError: {
429164
+ blockingError: "Hook input exceeds substitution budget; refusing to evaluate on truncated input",
429165
+ command: hook.prompt
429166
+ },
429167
+ preventContinuation: true,
429168
+ stopReason: "hook input exceeds substitution budget"
429169
+ };
429170
+ }
428153
429171
  logForDebugging3(`Hooks: Processing prompt hook with prompt: ${processedPrompt}`);
428154
429172
  const userMessage = createUserMessage2({ content: processedPrompt });
428155
429173
  const messagesToQuery = messages && messages.length > 0 ? [...messages, userMessage] : [userMessage];
@@ -428296,6 +429314,17 @@ async function execAgentHook(hook, hookName, hookEvent, jsonInput, signal, toolU
428296
429314
  const hookStartTime = Date.now();
428297
429315
  try {
428298
429316
  const processedPrompt = addArgumentsToPrompt(hook.prompt, jsonInput);
429317
+ if (processedPrompt === null) {
429318
+ logForDebugging3(`Hooks: hook input exceeds substitution budget — blocking (fail-closed)`);
429319
+ return {
429320
+ hook,
429321
+ outcome: "blocking",
429322
+ blockingError: {
429323
+ blockingError: "Hook input exceeds substitution budget; refusing to verify on truncated input",
429324
+ command: hook.prompt
429325
+ }
429326
+ };
429327
+ }
428299
429328
  logForDebugging3(`Hooks: Processing agent hook with prompt: ${processedPrompt}`);
428300
429329
  const userMessage = createUserMessage2({ content: processedPrompt });
428301
429330
  const agentMessages = [userMessage];
@@ -428686,7 +429715,7 @@ function getHttpHookPolicy() {
428686
429715
  allowedEnvVars: settings.httpHookAllowedEnvVars
428687
429716
  };
428688
429717
  }
428689
- function urlMatchesPattern2(url, pattern) {
429718
+ function urlMatchesPattern3(url, pattern) {
428690
429719
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
428691
429720
  const regexStr = escaped.replace(/\*/g, ".*");
428692
429721
  return new RegExp(`^${regexStr}$`).test(url);
@@ -428708,7 +429737,7 @@ function interpolateEnvVars(value, allowedEnvVars) {
428708
429737
  async function execHttpHook(hook, _hookEvent, jsonInput, signal) {
428709
429738
  const policy = getHttpHookPolicy();
428710
429739
  if (policy.allowedUrls !== undefined) {
428711
- const matched = policy.allowedUrls.some((p) => urlMatchesPattern2(hook.url, p));
429740
+ const matched = policy.allowedUrls.some((p) => urlMatchesPattern3(hook.url, p));
428712
429741
  if (!matched) {
428713
429742
  const msg = `HTTP hook blocked: ${hook.url} does not match any pattern in allowedHttpHookUrls`;
428714
429743
  logForDebugging3(msg, { level: "warn" });
@@ -428784,7 +429813,7 @@ import { randomUUID as randomUUID43 } from "crypto";
428784
429813
  function getSessionEndHookTimeoutMs() {
428785
429814
  const raw = process.env.ATLAS_SESSIONEND_HOOKS_TIMEOUT_MS;
428786
429815
  const parsed = raw ? parseInt(raw, 10) : NaN;
428787
- return Number.isFinite(parsed) && parsed > 0 ? parsed : SESSION_END_HOOK_TIMEOUT_MS_DEFAULT;
429816
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : SESSION_END_HOOK_TIMEOUT_MS_DEFAULT2;
428788
429817
  }
428789
429818
  function executeInBackground({
428790
429819
  processId,
@@ -429820,18 +430849,33 @@ async function* executeHooks({
429820
430849
  if (jsonInputResult !== undefined) {
429821
430850
  return jsonInputResult;
429822
430851
  }
429823
- try {
429824
- return jsonInputResult = { ok: true, value: jsonStringify5(hookInput) };
429825
- } catch (error) {
430852
+ if (exceedsMaxNestingDepth(hookInput)) {
430853
+ const error = Error(`Hook input nesting depth exceeds MAX_HOOK_INPUT_NESTING_DEPTH (${MAX_HOOK_INPUT_NESTING_DEPTH}); refusing to serialize`);
429826
430854
  logError3(Error(`Failed to stringify hook ${hookName} input`, { cause: error }));
429827
430855
  return jsonInputResult = { ok: false, error };
429828
430856
  }
430857
+ const serialized = stringifyWithGetterFallback(hookInput);
430858
+ if (serialized.ok !== true) {
430859
+ logError3(Error(`Failed to stringify hook ${hookName} input`, {
430860
+ cause: serialized.error
430861
+ }));
430862
+ return jsonInputResult = { ok: false, error: serialized.error };
430863
+ }
430864
+ if (serialized.droppedKeys.length > 0) {
430865
+ logForDebugging3(`Hooks: ${hookName}: dropped input keys with throwing getters: ${serialized.droppedKeys.join(", ")}`);
430866
+ }
430867
+ return jsonInputResult = { ok: true, value: serialized.value };
429829
430868
  }
429830
430869
  const hookPromises = matchingHooks.map(async function* ({ hook, pluginRoot, pluginId, skillRoot }, hookIndex) {
430870
+ if (isReentrantHook(hookEvent, hook, pluginRoot)) {
430871
+ logForDebugging3(`Hooks: ${hookName}: skipping re-entrant execution of ${getHookDisplayText(hook)}`);
430872
+ yield { outcome: "success", hook };
430873
+ return;
430874
+ }
429831
430875
  if (hook.type === "callback") {
429832
430876
  const callbackTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
429833
430877
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: callbackTimeoutMs });
429834
- yield executeHookCallback({
430878
+ yield runWithHookReentrancy(hookEvent, hook, pluginRoot, () => executeHookCallback({
429835
430879
  toolUseID,
429836
430880
  hook,
429837
430881
  hookEvent,
@@ -429839,7 +430883,23 @@ async function* executeHooks({
429839
430883
  signal: abortSignal,
429840
430884
  hookIndex,
429841
430885
  toolUseContext
429842
- }).finally(cleanup);
430886
+ }).catch((error) => {
430887
+ const errorMessage = error instanceof Error ? error.message : String(error);
430888
+ return {
430889
+ message: createAttachmentMessage({
430890
+ type: "hook_non_blocking_error",
430891
+ hookName: `${hookEvent}:Callback`,
430892
+ toolUseID,
430893
+ hookEvent,
430894
+ stderr: `Failed to run: ${errorMessage}`,
430895
+ stdout: "",
430896
+ exitCode: 1,
430897
+ command: "callback"
430898
+ }),
430899
+ outcome: "non_blocking_error",
430900
+ hook
430901
+ };
430902
+ }).finally(cleanup));
429843
430903
  return;
429844
430904
  }
429845
430905
  if (hook.type === "function") {
@@ -429857,7 +430917,7 @@ async function* executeHooks({
429857
430917
  };
429858
430918
  return;
429859
430919
  }
429860
- yield executeFunctionHook({
430920
+ yield runWithHookReentrancy(hookEvent, hook, pluginRoot, () => executeFunctionHook({
429861
430921
  hook,
429862
430922
  messages,
429863
430923
  hookName,
@@ -429865,7 +430925,7 @@ async function* executeHooks({
429865
430925
  hookEvent,
429866
430926
  timeoutMs,
429867
430927
  signal
429868
- });
430928
+ }));
429869
430929
  return;
429870
430930
  }
429871
430931
  const commandTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
@@ -429878,6 +430938,7 @@ async function* executeHooks({
429878
430938
  try {
429879
430939
  const jsonInputRes = getJsonInput();
429880
430940
  if (!jsonInputRes.ok) {
430941
+ const failClosed = shouldFailClosePluginHook(hookEvent, pluginRoot);
429881
430942
  yield {
429882
430943
  message: createAttachmentMessage({
429883
430944
  type: "hook_error_during_execution",
@@ -429888,7 +430949,13 @@ async function* executeHooks({
429888
430949
  command: hookCommand,
429889
430950
  durationMs: Date.now() - hookStartMs
429890
430951
  }),
429891
- outcome: "non_blocking_error",
430952
+ ...failClosed ? {
430953
+ blockingError: {
430954
+ blockingError: `Plugin hook input could not be prepared; blocking gated action (fail-closed): ${errorMessage2(jsonInputRes.error)}`,
430955
+ command: hookCommand
430956
+ }
430957
+ } : {},
430958
+ outcome: failClosed ? "blocking" : "non_blocking_error",
429892
430959
  hook
429893
430960
  };
429894
430961
  cleanup();
@@ -429899,7 +430966,7 @@ async function* executeHooks({
429899
430966
  if (!toolUseContext) {
429900
430967
  throw new Error("ToolUseContext is required for prompt hooks. This is a bug.");
429901
430968
  }
429902
- const promptResult = await execPromptHook(hook, hookName, hookEvent, jsonInput, abortSignal, toolUseContext, messages, toolUseID);
430969
+ const promptResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execPromptHook(hook, hookName, hookEvent, jsonInput, abortSignal, toolUseContext, messages, toolUseID));
429903
430970
  if (promptResult.message?.type === "attachment") {
429904
430971
  const att = promptResult.message.attachment;
429905
430972
  if (att.type === "hook_success" || att.type === "hook_non_blocking_error") {
@@ -429918,7 +430985,7 @@ async function* executeHooks({
429918
430985
  if (!messages) {
429919
430986
  throw new Error("Messages are required for agent hooks. This is a bug.");
429920
430987
  }
429921
- const agentResult = await execAgentHook(hook, hookName, hookEvent, jsonInput, abortSignal, toolUseContext, toolUseID, messages, "agent_type" in hookInput ? hookInput.agent_type : undefined);
430988
+ const agentResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execAgentHook(hook, hookName, hookEvent, jsonInput, abortSignal, toolUseContext, toolUseID, messages, "agent_type" in hookInput ? hookInput.agent_type : undefined));
429922
430989
  if (agentResult.message?.type === "attachment") {
429923
430990
  const att = agentResult.message.attachment;
429924
430991
  if (att.type === "hook_success" || att.type === "hook_non_blocking_error") {
@@ -429932,7 +430999,7 @@ async function* executeHooks({
429932
430999
  }
429933
431000
  if (hook.type === "http") {
429934
431001
  emitHookStarted(hookId, hookName, hookEvent);
429935
- const httpResult = await execHttpHook(hook, hookEvent, jsonInput, signal);
431002
+ const httpResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execHttpHook(hook, hookEvent, jsonInput, signal));
429936
431003
  cleanup?.();
429937
431004
  if (httpResult.aborted) {
429938
431005
  emitHookResponse({
@@ -430060,7 +431127,7 @@ async function* executeHooks({
430060
431127
  return;
430061
431128
  }
430062
431129
  emitHookStarted(hookId, hookName, hookEvent);
430063
- const result = await execCommandHook(hook, hookEvent, hookName, jsonInput, abortSignal, hookId, hookIndex, pluginRoot, pluginId, skillRoot, forceSyncExecution, boundRequestPrompt);
431130
+ const result = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execCommandHook(hook, hookEvent, hookName, jsonInput, abortSignal, hookId, hookIndex, pluginRoot, pluginId, skillRoot, forceSyncExecution, boundRequestPrompt));
430064
431131
  cleanup?.();
430065
431132
  const durationMs = Date.now() - hookStartMs;
430066
431133
  if (result.backgrounded) {
@@ -430306,7 +431373,7 @@ async function* executeHooks({
430306
431373
  cancelled: 0
430307
431374
  };
430308
431375
  let permissionBehavior;
430309
- for await (const result of all2(hookPromises)) {
431376
+ for await (const result of all2(hookPromises, MAX_HOOK_CONCURRENCY)) {
430310
431377
  outcomes[result.outcome]++;
430311
431378
  if (result.terminalSequence) {
430312
431379
  try {
@@ -430496,20 +431563,46 @@ async function executeHooksOutsideREPL({
430496
431563
  const pluginHookCounts = getPluginHookCounts(userHooks);
430497
431564
  const hookTypeCounts = getHookTypeCounts(userHooks);
430498
431565
  }
430499
- let jsonInput;
430500
- try {
430501
- jsonInput = jsonStringify5(hookInput);
430502
- } catch (error) {
430503
- logError3(error);
430504
- return [];
431566
+ if (exceedsMaxNestingDepth(hookInput)) {
431567
+ const message = `Hook input nesting depth exceeds MAX_HOOK_INPUT_NESTING_DEPTH (${MAX_HOOK_INPUT_NESTING_DEPTH}); refusing to run hooks`;
431568
+ logForDebugging3(`Hooks: ${hookName}: ${message}`);
431569
+ return matchingHooks.map(({ hook, pluginRoot }) => ({
431570
+ command: getHookDisplayText(hook),
431571
+ succeeded: false,
431572
+ output: message,
431573
+ blocked: shouldFailClosePluginHook(hookEvent, pluginRoot)
431574
+ }));
431575
+ }
431576
+ const serialized = stringifyWithGetterFallback(hookInput);
431577
+ if (serialized.ok !== true) {
431578
+ logError3(serialized.error);
431579
+ return matchingHooks.map(({ hook, pluginRoot }) => ({
431580
+ command: getHookDisplayText(hook),
431581
+ succeeded: false,
431582
+ output: `Failed to serialize hook input: ${errorMessage2(serialized.error)}`,
431583
+ blocked: shouldFailClosePluginHook(hookEvent, pluginRoot)
431584
+ }));
431585
+ }
431586
+ if (serialized.droppedKeys.length > 0) {
431587
+ logForDebugging3(`Hooks: ${hookName}: dropped input keys with throwing getters: ${serialized.droppedKeys.join(", ")}`);
430505
431588
  }
430506
- const hookPromises = matchingHooks.map(async ({ hook, pluginRoot, pluginId }, hookIndex) => {
431589
+ const jsonInput = serialized.value;
431590
+ const hookTasks = matchingHooks.map(({ hook, pluginRoot, pluginId }, hookIndex) => async () => {
431591
+ if (isReentrantHook(hookEvent, hook, pluginRoot)) {
431592
+ logForDebugging3(`Hooks: ${hookName}: skipping re-entrant execution of ${getHookDisplayText(hook)}`);
431593
+ return {
431594
+ command: getHookDisplayText(hook),
431595
+ succeeded: true,
431596
+ output: "Skipped: re-entrant hook execution (already running in this call chain)",
431597
+ blocked: false
431598
+ };
431599
+ }
430507
431600
  if (hook.type === "callback") {
430508
431601
  const callbackTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
430509
431602
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: callbackTimeoutMs });
430510
431603
  try {
430511
431604
  const toolUseID = randomUUID43();
430512
- const json = await hook.callback(hookInput, toolUseID, abortSignal, hookIndex);
431605
+ const json = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => hook.callback(hookInput, toolUseID, abortSignal, hookIndex));
430513
431606
  cleanup?.();
430514
431607
  if (isAsyncHookJSONOutput(json)) {
430515
431608
  logForDebugging3(`${hookName} [callback] returned async response, returning empty output`);
@@ -430568,7 +431661,7 @@ async function executeHooksOutsideREPL({
430568
431661
  }
430569
431662
  if (hook.type === "http") {
430570
431663
  try {
430571
- const httpResult = await execHttpHook(hook, hookEvent, jsonInput, signal);
431664
+ const httpResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execHttpHook(hook, hookEvent, jsonInput, signal));
430572
431665
  if (httpResult.aborted) {
430573
431666
  logForDebugging3(`${hookName} [${hook.url}] cancelled`);
430574
431667
  return {
@@ -430619,7 +431712,7 @@ async function executeHooksOutsideREPL({
430619
431712
  const commandTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
430620
431713
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: commandTimeoutMs });
430621
431714
  try {
430622
- const result = await execCommandHook(hook, hookEvent, hookName, jsonInput, abortSignal, randomUUID43(), hookIndex, pluginRoot, pluginId);
431715
+ const result = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execCommandHook(hook, hookEvent, hookName, jsonInput, abortSignal, randomUUID43(), hookIndex, pluginRoot, pluginId));
430623
431716
  cleanup?.();
430624
431717
  if (result.aborted) {
430625
431718
  logForDebugging3(`${hookName} [${hook.command}] cancelled`);
@@ -430663,7 +431756,7 @@ async function executeHooksOutsideREPL({
430663
431756
  };
430664
431757
  }
430665
431758
  });
430666
- return await Promise.all(hookPromises);
431759
+ return await runWithConcurrencyLimit(hookTasks, MAX_HOOK_CONCURRENCY);
430667
431760
  }
430668
431761
  async function* executePermissionDeniedHooks(toolName, toolUseID, toolInput, reason, toolUseContext, permissionMode, signal, timeoutMs = TOOL_HOOK_EXECUTION_TIMEOUT_MS) {
430669
431762
  const appState = toolUseContext.getAppState();
@@ -431463,7 +432556,7 @@ function getHookDefinitionsForTelemetry(matchedHooks) {
431463
432556
  return { type: "unknown" };
431464
432557
  });
431465
432558
  }
431466
- var TOOL_HOOK_EXECUTION_TIMEOUT_MS, SESSION_END_HOOK_TIMEOUT_MS_DEFAULT = 1500;
432559
+ var TOOL_HOOK_EXECUTION_TIMEOUT_MS, SESSION_END_HOOK_TIMEOUT_MS_DEFAULT2 = 1500;
431467
432560
  var init_hooks7 = __esm(() => {
431468
432561
  init_file();
431469
432562
  init_ShellCommand2();
@@ -431496,12 +432589,16 @@ var init_hooks7 = __esm(() => {
431496
432589
  init_permissionRuleParser2();
431497
432590
  init_log5();
431498
432591
  init_combinedAbortSignal();
432592
+ init_hookInputMaterialize();
432593
+ init_hookReentrancy();
432594
+ init_hookFailClosed();
431499
432595
  init_AsyncHookRegistry();
431500
432596
  init_messageQueueManager();
431501
432597
  init_messages3();
431502
432598
  init_hookEvents2();
431503
432599
  init_attachments();
431504
432600
  init_generators();
432601
+ init_engine();
431505
432602
  init_Tool();
431506
432603
  init_execPromptHook();
431507
432604
  init_execAgentHook();
@@ -440582,7 +441679,21 @@ function getDestructiveCommandWarning2(command) {
440582
441679
  }
440583
441680
  return null;
440584
441681
  }
440585
- var DESTRUCTIVE_PATTERNS;
441682
+ function isStrictInlineShellRmPromptEnabled(env = process.env) {
441683
+ return env[STRICT_INLINE_SHELL_RM_PROMPT_ENV] === "1";
441684
+ }
441685
+ function getStrictInlineShellRmWarning(command) {
441686
+ if (STRICT_CONCAT_RM_RE.test(command)) {
441687
+ return STRICT_CONCAT_RM_WARNING;
441688
+ }
441689
+ for (const re of STRICT_SCRIPT_INVOCATION_RES) {
441690
+ if (re.test(command)) {
441691
+ return STRICT_SCRIPT_WARNING;
441692
+ }
441693
+ }
441694
+ return null;
441695
+ }
441696
+ var DESTRUCTIVE_PATTERNS, STRICT_INLINE_SHELL_RM_PROMPT_ENV = "ATLAS_STRICT_INLINE_SHELL_RM_PROMPT", STRICT_CONCAT_RM_RE, STRICT_SCRIPT_INVOCATION_RES, STRICT_CONCAT_RM_WARNING = "Note: may remove files via a non-literal rm invocation", STRICT_SCRIPT_WARNING = "Note: runs a shell script whose contents are not inspected";
440586
441697
  var init_destructiveCommandWarning2 = __esm(() => {
440587
441698
  DESTRUCTIVE_PATTERNS = [
440588
441699
  {
@@ -440650,6 +441761,13 @@ var init_destructiveCommandWarning2 = __esm(() => {
440650
441761
  warning: "Note: may destroy Terraform infrastructure"
440651
441762
  }
440652
441763
  ];
441764
+ STRICT_CONCAT_RM_RE = /\brm\s+(?:-[a-zA-Z]*[rRf]|--recursive|--force)/;
441765
+ STRICT_SCRIPT_INVOCATION_RES = [
441766
+ /(^|[;&|\n]\s*)(?:[^\s;&|]*\/)?(?:bash|zsh|dash|ksh|sh)\b/,
441767
+ /(^|[;&|\n]\s*)source\s+\S/,
441768
+ /(^|[;&|\n]\s*)\.[ \t]*\S/,
441769
+ /(^|[;&|\n]\s*)\S+\.sh\b/
441770
+ ];
440653
441771
  });
440654
441772
 
440655
441773
  // src/tui/utils/shell/specPrefix.ts
@@ -444710,7 +445828,7 @@ function BashPermissionRequestInner({
444710
445828
  sandboxingEnabled: sandboxingEnabled_0,
444711
445829
  isSandboxed: isSandboxed_0
444712
445830
  } = import_react193.useMemo(() => {
444713
- const destructiveWarning = getFeatureValue_CACHED_MAY_BE_STALE("atlas_destructive_command_warning", false) ? getDestructiveCommandWarning2(command) : null;
445831
+ const destructiveWarning = getFeatureValue_CACHED_MAY_BE_STALE("atlas_destructive_command_warning", false) ? getDestructiveCommandWarning2(command) ?? (isStrictInlineShellRmPromptEnabled() ? getStrictInlineShellRmWarning(command) : null) : null;
444714
445832
  const sandboxingEnabled = getSandboxManager().isSandboxingEnabled();
444715
445833
  const isSandboxed = sandboxingEnabled && shouldUseSandbox2(toolUseConfirm.input);
444716
445834
  return {
@@ -469670,41 +470788,28 @@ async function processUserInput({
469670
470788
  if (mode === "prompt" && inputString !== null && !isMeta) {
469671
470789
  setUserInputOnProcessing?.(inputString);
469672
470790
  }
469673
- queryCheckpoint("query_process_user_input_base_start");
469674
470791
  const appState = context.getAppState();
469675
- const result = await processUserInputBase(input, mode, setToolJSX, context, pastedContents, ideSelection, messages, uuid, isAlreadyProcessing, querySource, canUseTool, appState.toolPermissionContext.mode, skipSlashCommands, bridgeOrigin, isMeta, skipAttachments, preExpansionInput);
469676
- queryCheckpoint("query_process_user_input_base_end");
469677
- if (!result.shouldQuery) {
469678
- return result;
469679
- }
469680
470792
  queryCheckpoint("query_hooks_start");
469681
470793
  const inputMessage = getContentText(input) || "";
470794
+ let hookBlockingMessage = null;
470795
+ let hookStopped = false;
470796
+ let hookStopReason;
470797
+ const hookMessages = [];
469682
470798
  for await (const hookResult of executeUserPromptSubmitHooks(inputMessage, appState.toolPermissionContext.mode, context, context.requestPrompt)) {
469683
470799
  if (hookResult.message?.type === "progress") {
469684
470800
  continue;
469685
470801
  }
469686
470802
  if (hookResult.blockingError) {
469687
- const blockingMessage = getUserPromptSubmitHookBlockingMessage(hookResult.blockingError);
469688
- return {
469689
- messages: [
469690
- createSystemMessage(`${blockingMessage}
469691
-
469692
- Original prompt: ${input}`, "warning")
469693
- ],
469694
- shouldQuery: false,
469695
- allowedTools: result.allowedTools
469696
- };
470803
+ hookBlockingMessage = getUserPromptSubmitHookBlockingMessage(hookResult.blockingError);
470804
+ break;
469697
470805
  }
469698
470806
  if (hookResult.preventContinuation) {
469699
- const message = hookResult.stopReason ? `Operation stopped by hook: ${hookResult.stopReason}` : "Operation stopped by hook";
469700
- result.messages.push(createUserMessage2({
469701
- content: message
469702
- }));
469703
- result.shouldQuery = false;
469704
- return result;
470807
+ hookStopped = true;
470808
+ hookStopReason = hookResult.stopReason;
470809
+ continue;
469705
470810
  }
469706
470811
  if (hookResult.additionalContexts && hookResult.additionalContexts.length > 0) {
469707
- result.messages.push(createAttachmentMessage({
470812
+ hookMessages.push(createAttachmentMessage({
469708
470813
  type: "hook_additional_context",
469709
470814
  content: hookResult.additionalContexts.map(applyTruncation),
469710
470815
  hookName: "UserPromptSubmit",
@@ -469718,7 +470823,7 @@ Original prompt: ${input}`, "warning")
469718
470823
  if (!hookResult.message.attachment.content) {
469719
470824
  break;
469720
470825
  }
469721
- result.messages.push({
470826
+ hookMessages.push({
469722
470827
  ...hookResult.message,
469723
470828
  attachment: {
469724
470829
  ...hookResult.message.attachment,
@@ -469727,12 +470832,38 @@ Original prompt: ${input}`, "warning")
469727
470832
  });
469728
470833
  break;
469729
470834
  default:
469730
- result.messages.push(hookResult.message);
470835
+ hookMessages.push(hookResult.message);
469731
470836
  break;
469732
470837
  }
469733
470838
  }
469734
470839
  }
469735
470840
  queryCheckpoint("query_hooks_end");
470841
+ if (hookBlockingMessage !== null) {
470842
+ return {
470843
+ messages: [
470844
+ createSystemMessage(`${hookBlockingMessage}
470845
+
470846
+ Original prompt: ${input}`, "warning")
470847
+ ],
470848
+ shouldQuery: false,
470849
+ allowedTools: []
470850
+ };
470851
+ }
470852
+ queryCheckpoint("query_process_user_input_base_start");
470853
+ const result = await processUserInputBase(input, mode, setToolJSX, context, pastedContents, ideSelection, messages, uuid, isAlreadyProcessing, querySource, canUseTool, appState.toolPermissionContext.mode, skipSlashCommands, bridgeOrigin, isMeta, skipAttachments, preExpansionInput);
470854
+ queryCheckpoint("query_process_user_input_base_end");
470855
+ if (hookStopped && result.shouldQuery) {
470856
+ const message = hookStopReason ? `Operation stopped by hook: ${hookStopReason}` : "Operation stopped by hook";
470857
+ result.messages.push(createUserMessage2({
470858
+ content: message
470859
+ }));
470860
+ result.shouldQuery = false;
470861
+ return result;
470862
+ }
470863
+ if (!result.shouldQuery) {
470864
+ return result;
470865
+ }
470866
+ result.messages.push(...hookMessages);
469736
470867
  return result;
469737
470868
  }
469738
470869
  function applyTruncation(content) {
@@ -470300,6 +471431,32 @@ var init_useMailboxBridge = __esm(() => {
470300
471431
 
470301
471432
  // src/tui/replLoopDeps.ts
470302
471433
  function buildReplLoopParams(m) {
471434
+ let prevPoolSig = (m.tools ?? []).map((t) => t.name).join(",");
471435
+ const refreshRoundContext = (messages) => {
471436
+ const appState = m.toolUseContext?.getAppState?.();
471437
+ const liveMcpTools = appState?.mcp?.tools;
471438
+ if (!liveMcpTools)
471439
+ return null;
471440
+ const pool = assembleToolPool(m.toolPermissionContext, liveMcpTools);
471441
+ const sig = pool.map((t) => t.name).join(",");
471442
+ const tuiMessages = messages;
471443
+ const atts = [
471444
+ ...getDeferredToolsDeltaAttachment(pool, m.mainLoopModel, tuiMessages, {
471445
+ callSite: "round_refresh",
471446
+ querySource: m.querySource
471447
+ }),
471448
+ ...getMcpInstructionsDeltaAttachment(m.toolUseContext.options.mcpClients ?? [], pool, m.mainLoopModel, tuiMessages)
471449
+ ];
471450
+ if (sig === prevPoolSig && atts.length === 0)
471451
+ return null;
471452
+ prevPoolSig = sig;
471453
+ return {
471454
+ tools: pool,
471455
+ ...atts.length > 0 ? {
471456
+ appendedMessages: atts.map((att) => createAttachmentMessage(att))
471457
+ } : {}
471458
+ };
471459
+ };
470303
471460
  return buildAgentLoopParams({
470304
471461
  messages: m.messages,
470305
471462
  userContext: m.userContext,
@@ -470315,11 +471472,14 @@ function buildReplLoopParams(m) {
470315
471472
  toolUseContext: m.toolUseContext,
470316
471473
  querySource: m.querySource,
470317
471474
  unboundedTurns: true,
470318
- maxGoalTurns: m.maxGoalTurns
471475
+ maxGoalTurns: m.maxGoalTurns,
471476
+ refreshRoundContext
470319
471477
  });
470320
471478
  }
470321
471479
  var init_replLoopDeps = __esm(() => {
470322
471480
  init_agentLoopDeps();
471481
+ init_tools3();
471482
+ init_attachments();
470323
471483
  });
470324
471484
 
470325
471485
  // src/tui/hooks/useMergedClients.ts
@@ -493526,7 +494686,7 @@ ${formattedErrors}
493526
494686
  const {
493527
494687
  allowed,
493528
494688
  blocked
493529
- } = filterMcpServersByPolicy(scopedConfigs);
494689
+ } = filterMcpServersByPolicy2(scopedConfigs);
493530
494690
  if (blocked.length > 0) {
493531
494691
  process.stderr.write(`Warning: MCP ${plural2(blocked.length, "server")} blocked by enterprise policy: ${blocked.join(", ")}
493532
494692
  `);
@@ -494207,7 +495367,8 @@ ${inputPrompt}` : mergePrompt;
494207
495367
  tools: [],
494208
495368
  commands: [],
494209
495369
  resources: {},
494210
- pluginReconnectKey: 0
495370
+ pluginReconnectKey: 0,
495371
+ cwdReconnectKey: 0
494211
495372
  },
494212
495373
  plugins: {
494213
495374
  enabled: [],