@atlasharness/atlascode 0.1.81 → 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 +877 -151
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -29154,6 +29154,11 @@ var DEFAULT_HOOK_SHELL = "bash";
29154
29154
  var init_getMatchingHooks = () => {};
29155
29155
 
29156
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
+ }
29157
29162
  function isSimpleModeHooksSkipped() {
29158
29163
  const raw = process.env.ATLAS_SIMPLE;
29159
29164
  if (raw === undefined)
@@ -29273,6 +29278,7 @@ ${r.additionalContext}` : r.additionalContext;
29273
29278
  if (!acc.permissionBehavior || PERMISSION_RANK[r.permissionBehavior] > PERMISSION_RANK[acc.permissionBehavior]) {
29274
29279
  acc.permissionBehavior = r.permissionBehavior;
29275
29280
  acc.permissionDecisionReason = r.permissionDecisionReason;
29281
+ acc.permissionDecisionSource = r.source;
29276
29282
  }
29277
29283
  }
29278
29284
  }
@@ -29290,12 +29296,14 @@ function runOneHook(m, port, env, signal, options) {
29290
29296
  if (result.blockingError && cmd.continueOnBlock === true) {
29291
29297
  result.continueOnBlock = true;
29292
29298
  }
29299
+ result.source = m.source;
29293
29300
  return result;
29294
29301
  }, (e) => ({
29295
29302
  command: cmd.command,
29296
29303
  status: -1,
29297
29304
  succeeded: false,
29298
- stderr: e instanceof Error ? e.message : String(e)
29305
+ stderr: e instanceof Error ? e.message : String(e),
29306
+ source: m.source
29299
29307
  }));
29300
29308
  }
29301
29309
  async function runHooks(hookEvent, hookInput, options = {}) {
@@ -29350,7 +29358,15 @@ async function runStopHooks(options = {}) {
29350
29358
  const hookInput = { ...base, hook_event_name: "Stop" };
29351
29359
  return runHooks("Stop", hookInput, options);
29352
29360
  }
29353
- 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;
29354
29370
  var init_runHooks = __esm(() => {
29355
29371
  init_createBaseHookInput();
29356
29372
  init_getMatchingHooks();
@@ -29386,9 +29402,20 @@ async function* runHooksStream(hookEvent, hookInput, options = {}) {
29386
29402
  toolUseID: options.toolUseID
29387
29403
  };
29388
29404
  }
29389
- const started = matched.map((m) => runOneHook(m, port, env, signal, options));
29390
- for (const pending of started) {
29391
- 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();
29392
29419
  results.push(result);
29393
29420
  yield { kind: "hook_result", result };
29394
29421
  mergeAggregated(aggregated, result);
@@ -30543,12 +30570,22 @@ function applyDontAskMode(decision, permissionContext, toolName) {
30543
30570
  }
30544
30571
  return decision;
30545
30572
  }
30546
- 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) => {
30547
30574
  const permissionContext = context.getToolPermissionContext?.();
30548
30575
  if (forceDecision !== undefined) {
30549
30576
  return applyDontAskMode(forceDecision, permissionContext, tool.name);
30550
30577
  }
30551
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
+ }
30552
30589
  const denyRule = permissionContext ? getDenyRuleForTool(permissionContext, tool) : null;
30553
30590
  if (denyRule) {
30554
30591
  return {
@@ -30667,6 +30704,10 @@ var init_permissions2 = __esm(() => {
30667
30704
  init_ruleMatching();
30668
30705
  init_sandboxAccess();
30669
30706
  init_denialMessages();
30707
+ PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES = new Set([
30708
+ "EnterPlanMode",
30709
+ "ExitPlanMode"
30710
+ ]);
30670
30711
  });
30671
30712
 
30672
30713
  // src/permissions/permissionMode.ts
@@ -46612,9 +46653,67 @@ var init_bashCommandHelpers = __esm(() => {
46612
46653
  });
46613
46654
 
46614
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
+ }
46615
46713
  function matchReadOnlyPrefix(command) {
46616
46714
  const trimmed = command.trim();
46617
- 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")) {
46618
46717
  return null;
46619
46718
  }
46620
46719
  let best = null;
@@ -46624,13 +46723,31 @@ function matchReadOnlyPrefix(command) {
46624
46723
  best = p;
46625
46724
  }
46626
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
+ }
46627
46743
  return best;
46628
46744
  }
46629
46745
  function isReadOnlyCommand(command) {
46630
46746
  return matchReadOnlyPrefix(command) !== null;
46631
46747
  }
46632
- var READ_ONLY_PREFIXES;
46748
+ var READ_ONLY_PREFIXES, FIND_WRITE_ACTION_TOKENS;
46633
46749
  var init_bashReadOnly = __esm(() => {
46750
+ init_shellQuote();
46634
46751
  READ_ONLY_PREFIXES = [
46635
46752
  "ls",
46636
46753
  "cat",
@@ -46657,6 +46774,17 @@ var init_bashReadOnly = __esm(() => {
46657
46774
  "printenv",
46658
46775
  "type"
46659
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
+ ]);
46660
46788
  });
46661
46789
 
46662
46790
  // src/engine/tools/bash/abortError.ts
@@ -75335,6 +75463,27 @@ var init_tasks3 = __esm(() => {
75335
75463
  init_taskOutputTool();
75336
75464
  });
75337
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
+
75338
75487
  // src/engine/skill/types.ts
75339
75488
  function getCommandName(cmd) {
75340
75489
  return cmd.userFacingName?.() ?? cmd.name;
@@ -75457,44 +75606,129 @@ function parseArgumentNames(argumentNames) {
75457
75606
  return [];
75458
75607
  }
75459
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);
75460
75610
  if (Array.isArray(argumentNames)) {
75461
- return argumentNames.filter(isValidName);
75611
+ return cap(argumentNames.filter(isValidName));
75462
75612
  }
75463
75613
  if (typeof argumentNames === "string") {
75464
- return argumentNames.split(/\s+/).filter(isValidName);
75614
+ return cap(argumentNames.split(/\s+/).filter(isValidName));
75465
75615
  }
75466
75616
  return [];
75467
75617
  }
75468
- 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) {
75469
75673
  if (args === undefined || args === null) {
75470
75674
  return content;
75471
75675
  }
75472
- const parsedArgs = parseArguments(args);
75473
- const originalContent = content;
75474
- for (let i = 0;i < argumentNames.length; i++) {
75475
- const name = argumentNames[i];
75476
- if (!name)
75477
- continue;
75478
- 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
+ }
75479
75690
  }
75480
- content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, indexStr) => {
75481
- const index = parseInt(indexStr, 10);
75482
- return parsedArgs[index] ?? "";
75483
- });
75484
- content = content.replace(/\$(\d+)(?!\w)/g, (_, indexStr) => {
75485
- const index = parseInt(indexStr, 10);
75486
- return parsedArgs[index] ?? "";
75487
- });
75488
- content = content.replaceAll("$ARGUMENTS", args);
75489
- if (content === originalContent && appendIfNoPlaceholder && args) {
75490
- 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 + `
75491
75719
 
75492
75720
  ARGUMENTS: ${args}`;
75493
75721
  }
75494
- return content;
75722
+ return out;
75495
75723
  }
75724
+ function substituteArguments(content, args, appendIfNoPlaceholder = true, argumentNames = []) {
75725
+ return substituteArgumentsImpl(content, args, appendIfNoPlaceholder, argumentNames, false) ?? content;
75726
+ }
75727
+ var MAX_ARGUMENT_NAMES = 64, MAX_ARGUMENT_NAME_CHARS = 64, MAX_SUBSTITUTION_OUTPUT_CHARS = 1e6, INDEXED_PLACEHOLDER_RE, SHORTHAND_PLACEHOLDER_RE;
75496
75728
  var init_argumentSubstitution = __esm(() => {
75497
75729
  init_bash();
75730
+ INDEXED_PLACEHOLDER_RE = /\$ARGUMENTS\[(\d+)\]/y;
75731
+ SHORTHAND_PLACEHOLDER_RE = /\$(\d+)/y;
75498
75732
  });
75499
75733
 
75500
75734
  // src/engine/skill/skillModel.ts
@@ -76931,7 +77165,7 @@ function parseSkillFrontmatterFields(frontmatter, markdownContent, resolvedName,
76931
77165
  logForDebugging(`Skill ${resolvedName} has invalid effort '${effortRaw}'. Valid options: ${EFFORT_LEVELS.join(", ")} or an integer`);
76932
77166
  }
76933
77167
  return {
76934
- displayName: frontmatter.name != null ? String(frontmatter.name) : undefined,
77168
+ displayName: sanitizeDisplayName(frontmatter.name != null ? String(frontmatter.name) : undefined, resolvedName),
76935
77169
  description,
76936
77170
  hasUserSpecifiedDescription: validatedDescription !== null,
76937
77171
  allowedTools: parseSlashCommandToolsFromFrontmatter(frontmatter["allowed-tools"]),
@@ -77042,6 +77276,7 @@ var init_skillCommand = __esm(() => {
77042
77276
  init_frontmatterFields();
77043
77277
  init_markdownLoader();
77044
77278
  init_skillModel();
77279
+ init_commandNameSafety();
77045
77280
  init_promptShellExecution();
77046
77281
  });
77047
77282
 
@@ -77081,6 +77316,10 @@ async function loadSkillsFromSkillsDir(basePath, source) {
77081
77316
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
77082
77317
  return null;
77083
77318
  }
77319
+ if (isUnsafeCommandName(entry.name)) {
77320
+ logForDebugging(`[skills] skipping unsafe skill directory name: ${JSON.stringify(entry.name)}`);
77321
+ return null;
77322
+ }
77084
77323
  const skillDirPath = join32(basePath, entry.name);
77085
77324
  const skillFilePath = join32(skillDirPath, "SKILL.md");
77086
77325
  let content;
@@ -77189,6 +77428,10 @@ async function loadSkillsFromCommandsDir(cwd) {
77189
77428
  content,
77190
77429
  source
77191
77430
  });
77431
+ if (isUnsafeCommandName(cmdName)) {
77432
+ logForDebugging(`[skills] skipping unsafe command name: ${JSON.stringify(cmdName)}`);
77433
+ continue;
77434
+ }
77192
77435
  const parsed = parseSkillFrontmatterFields(frontmatter, content, cmdName, "Custom command");
77193
77436
  skills.push({
77194
77437
  skill: createSkillCommand({
@@ -77228,6 +77471,7 @@ var init_loadSkillsDir = __esm(() => {
77228
77471
  init_memory();
77229
77472
  init_config();
77230
77473
  init_messaging();
77474
+ init_commandNameSafety();
77231
77475
  init_gitignore();
77232
77476
  init_markdownLoader();
77233
77477
  init_skillCommand();
@@ -77643,6 +77887,7 @@ var init_pluginIdentifier = __esm(() => {
77643
77887
 
77644
77888
  // src/engine/skill/index.ts
77645
77889
  var init_skill = __esm(() => {
77890
+ init_commandNameSafety();
77646
77891
  init_frontmatterFields();
77647
77892
  init_argumentSubstitution();
77648
77893
  init_skillModel();
@@ -77696,6 +77941,14 @@ import { randomUUID as randomUUID14 } from "crypto";
77696
77941
  async function getAllCommands() {
77697
77942
  return getCommands(process.cwd());
77698
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
+ }
77699
77952
  function getToolUseIDFromParentMessage(parentMessage, toolName) {
77700
77953
  if (!parentMessage)
77701
77954
  return;
@@ -77763,7 +78016,7 @@ function skillHasOnlySafeProperties(command) {
77763
78016
  }
77764
78017
  return true;
77765
78018
  }
77766
- var SKILL_TOOL_INPUT_SCHEMA, SAFE_SKILL_PROPERTIES, SkillTool;
78019
+ var SKILL_TOOL_INPUT_SCHEMA, MAX_UNKNOWN_SKILL_SUGGESTIONS = 10, SAFE_SKILL_PROPERTIES, SkillTool;
77767
78020
  var init_skillTool = __esm(() => {
77768
78021
  init_shared();
77769
78022
  init_permissions3();
@@ -77935,7 +78188,7 @@ var init_skillTool = __esm(() => {
77935
78188
  if (!foundCommand) {
77936
78189
  return {
77937
78190
  result: false,
77938
- message: `Unknown skill: ${normalizedCommandName}`,
78191
+ message: buildUnknownSkillMessage(normalizedCommandName, commands),
77939
78192
  errorCode: 2
77940
78193
  };
77941
78194
  }
@@ -79050,6 +79303,24 @@ function classifyToolError(error) {
79050
79303
  return "UnknownError";
79051
79304
  }
79052
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
+
79053
79324
  // src/engine/pipeline/schemaValidation.ts
79054
79325
  function typeName(v) {
79055
79326
  if (v === null)
@@ -79210,7 +79481,7 @@ async function runWithToolHeartbeat(opts, fn) {
79210
79481
  var DEFAULT_TOOL_HEARTBEAT_INTERVAL_MS = 30000, MIN_TOOL_HEARTBEAT_INTERVAL_MS = 100;
79211
79482
 
79212
79483
  // src/engine/pipeline/toolExecution.ts
79213
- function mergeHookPermission(hookBehavior, gate, hookReason) {
79484
+ function mergeHookPermission(hookBehavior, gate, hookReason, hookSource) {
79214
79485
  if (hookBehavior === "defer") {
79215
79486
  return {
79216
79487
  allowed: false,
@@ -79224,7 +79495,8 @@ function mergeHookPermission(hookBehavior, gate, hookReason) {
79224
79495
  if (!gate.allowed)
79225
79496
  return gate;
79226
79497
  if (hookBehavior === "ask") {
79227
- 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 };
79228
79500
  }
79229
79501
  return gate;
79230
79502
  }
@@ -79295,12 +79567,12 @@ async function executeToolUse(tu, assistantMsg, deps) {
79295
79567
  isError: true
79296
79568
  };
79297
79569
  }
79298
- const effectiveInput = preOutcome?.updatedInput ?? tu.input;
79570
+ const effectiveInput = preOutcome?.updatedInput !== undefined ? sanitizeUpdatedInput(tu.input, preOutcome.updatedInput) : tu.input;
79299
79571
  const gateVerdict = deps.checkPermission ? await deps.checkPermission(tool, effectiveInput, {
79300
79572
  toolUseId: tu.id,
79301
79573
  assistantMessage: assistantMsg
79302
79574
  }) : { allowed: true };
79303
- const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict, preOutcome?.hookPermissionDecisionReason);
79575
+ const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict, preOutcome?.hookPermissionDecisionReason, preOutcome?.hookSource);
79304
79576
  if (verdict.deferred) {
79305
79577
  return { deferred: true, deferReason: verdict.reason, isError: false };
79306
79578
  }
@@ -83302,6 +83574,7 @@ function createToolHooks(opts = {}) {
83302
83574
  updatedInput: r.updatedInput,
83303
83575
  hookBehavior: r.permissionBehavior,
83304
83576
  hookPermissionDecisionReason: r.permissionDecisionReason,
83577
+ hookSource: r.permissionDecisionSource,
83305
83578
  preventContinuation: r.preventContinuation,
83306
83579
  stopReason: r.stopReason
83307
83580
  };
@@ -83590,6 +83863,7 @@ var init_state2 = () => {};
83590
83863
  var init_hooks2 = __esm(() => {
83591
83864
  init_toolHooks();
83592
83865
  init_hooks();
83866
+ init_hooks();
83593
83867
  });
83594
83868
 
83595
83869
  // src/engine/goal/goalCheckIn.ts
@@ -83678,6 +83952,7 @@ var init_engine = __esm(() => {
83678
83952
  init_permissions4();
83679
83953
  init_hooks2();
83680
83954
  init_hooks2();
83955
+ init_hooks2();
83681
83956
  init_config();
83682
83957
  init_messaging();
83683
83958
  init_messaging();
@@ -87105,6 +87380,19 @@ var init_hooksWiring = __esm(() => {
87105
87380
  init_hooks();
87106
87381
  });
87107
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
+
87108
87396
  // src/cli/print.ts
87109
87397
  import { randomUUID as randomUUID21 } from "crypto";
87110
87398
  import { readFile as readFile12 } from "node:fs/promises";
@@ -87864,6 +88152,7 @@ async function runHeadless(inputPrompt, options) {
87864
88152
  cronScheduler.stop();
87865
88153
  await drainToOutput();
87866
88154
  await drainPendingBackgroundAgents(abortController.signal);
88155
+ await runHeadlessSessionEndHooks("prompt_input_exit");
87867
88156
  } catch (error) {
87868
88157
  cronScheduler.stop();
87869
88158
  logForDebugging2(headlessErrorLine(error), { level: "error" });
@@ -87882,6 +88171,7 @@ async function runHeadless(inputPrompt, options) {
87882
88171
  });
87883
88172
  } catch {}
87884
88173
  abortController.abort();
88174
+ await runHeadlessSessionEndHooks("other");
87885
88175
  process.exitCode = 1;
87886
88176
  }
87887
88177
  await flushDebugSink();
@@ -87932,6 +88222,7 @@ var init_print = __esm(() => {
87932
88222
  init_headlessPrompt();
87933
88223
  init_debugSink();
87934
88224
  init_hooksWiring();
88225
+ init_headlessSessionEnd();
87935
88226
  });
87936
88227
 
87937
88228
  // src/cli/handlers/update.ts
@@ -89320,7 +89611,7 @@ var init_cli = __esm(() => {
89320
89611
  });
89321
89612
 
89322
89613
  // node_modules/is-plain-obj/index.js
89323
- function isPlainObject3(value) {
89614
+ function isPlainObject4(value) {
89324
89615
  if (typeof value !== "object" || value === null) {
89325
89616
  return false;
89326
89617
  }
@@ -89342,7 +89633,7 @@ var init_file_url = () => {};
89342
89633
  // node_modules/execa/lib/methods/parameters.js
89343
89634
  var normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => {
89344
89635
  const filePath = safeNormalizeFileUrl(rawFile, "First argument");
89345
- const [commandArguments, options] = isPlainObject3(rawArguments) ? [[], rawArguments] : [rawArguments, rawOptions];
89636
+ const [commandArguments, options] = isPlainObject4(rawArguments) ? [[], rawArguments] : [rawArguments, rawOptions];
89346
89637
  if (!Array.isArray(commandArguments)) {
89347
89638
  throw new TypeError(`Second argument must be either an array of arguments or an options object: ${commandArguments}`);
89348
89639
  }
@@ -89354,7 +89645,7 @@ var normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => {
89354
89645
  if (nullByteArgument !== undefined) {
89355
89646
  throw new TypeError(`Arguments cannot contain null bytes ("\\0"): ${nullByteArgument}`);
89356
89647
  }
89357
- if (!isPlainObject3(options)) {
89648
+ if (!isPlainObject4(options)) {
89358
89649
  throw new TypeError(`Last argument must be an options object: ${options}`);
89359
89650
  }
89360
89651
  return [filePath, normalizedArguments, { __proto__: null, ...options }];
@@ -89476,7 +89767,7 @@ var isTemplateString = (templates) => Array.isArray(templates) && Array.isArray(
89476
89767
  if (typeOfExpression === "number") {
89477
89768
  return String(expression);
89478
89769
  }
89479
- if (isPlainObject3(expression) && (("stdout" in expression) || ("isMaxBuffer" in expression))) {
89770
+ if (isPlainObject4(expression) && (("stdout" in expression) || ("isMaxBuffer" in expression))) {
89480
89771
  return getSubprocessResult(expression);
89481
89772
  }
89482
89773
  if (expression instanceof ChildProcess || Object.prototype.toString.call(expression) === "[object Promise]") {
@@ -89523,7 +89814,7 @@ var normalizeFdSpecificOptions = (options) => {
89523
89814
  const optionBaseArray = Array.from({ length: stdioLength + 1 });
89524
89815
  const optionArray = normalizeFdSpecificValue(options[optionName], optionBaseArray, optionName, stdioLength);
89525
89816
  return addDefaultValue(optionArray, optionName);
89526
- }, 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) => {
89527
89818
  for (const fdName of Object.keys(optionValue).sort(compareFdName)) {
89528
89819
  for (const fdNumber of parseFdName(fdName, optionName, stdioLength)) {
89529
89820
  optionArray[fdNumber] = optionValue[fdName];
@@ -92770,7 +93061,7 @@ var getStdioItemType = (value, optionName) => {
92770
93061
  if (value !== undefined && typeof value !== "boolean") {
92771
93062
  throw new TypeError(`The \`${optionName}\` option must use a boolean.`);
92772
93063
  }
92773
- }, 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;
92774
93065
  var init_type = __esm(() => {
92775
93066
  init_uint_array();
92776
93067
  FILE_PATH_KEYS = new Set(["file", "append"]);
@@ -92872,7 +93163,7 @@ var normalizeTransforms = (stdioItems, optionName, direction, options) => [
92872
93163
  value: { transform, writableObjectMode, readableObjectMode }
92873
93164
  };
92874
93165
  }, normalizeTransformStream = ({ stdioItem, stdioItem: { value }, index, newTransforms, direction }) => {
92875
- const { transform, objectMode } = isPlainObject3(value) ? value : { transform: value };
93166
+ const { transform, objectMode } = isPlainObject4(value) ? value : { transform: value };
92876
93167
  const { writableObjectMode, readableObjectMode } = getTransformObjectModes(objectMode, index, newTransforms, direction);
92877
93168
  return {
92878
93169
  ...stdioItem,
@@ -92885,7 +93176,7 @@ var normalizeTransforms = (stdioItems, optionName, direction, options) => [
92885
93176
  binary: binaryOption = false,
92886
93177
  preserveNewlines = false,
92887
93178
  objectMode
92888
- } = isPlainObject3(value) ? value : { transform: value };
93179
+ } = isPlainObject4(value) ? value : { transform: value };
92889
93180
  const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
92890
93181
  const { writableObjectMode, readableObjectMode } = getTransformObjectModes(objectMode, index, newTransforms, direction);
92891
93182
  return {
@@ -95580,7 +95871,7 @@ var init_abort2 = __esm(() => {
95580
95871
 
95581
95872
  // node_modules/execa/lib/pipe/setup.js
95582
95873
  var pipeToSubprocess = (sourceInfo, ...pipeArguments) => {
95583
- if (isPlainObject3(pipeArguments[0])) {
95874
+ if (isPlainObject4(pipeArguments[0])) {
95584
95875
  return pipeToSubprocess.bind(undefined, {
95585
95876
  ...sourceInfo,
95586
95877
  boundOptions: { ...sourceInfo.boundOptions, ...pipeArguments[0] }
@@ -96489,7 +96780,7 @@ var mergeOptions = (boundOptions, options) => {
96489
96780
  ]));
96490
96781
  return { ...safeBoundOptions, ...mergedOptions };
96491
96782
  }, mergeOption = (optionName, boundOptionValue, optionValue) => {
96492
- if (DEEP_OPTIONS.has(optionName) && isPlainObject3(boundOptionValue) && isPlainObject3(optionValue)) {
96783
+ if (DEEP_OPTIONS.has(optionName) && isPlainObject4(boundOptionValue) && isPlainObject4(optionValue)) {
96493
96784
  return { ...boundOptionValue, ...optionValue };
96494
96785
  }
96495
96786
  return optionValue;
@@ -96514,7 +96805,7 @@ var createExeca = (mapArguments, boundOptions, deepOptions, setBoundExeca) => {
96514
96805
  }
96515
96806
  return boundExeca;
96516
96807
  }, callBoundExeca = ({ mapArguments, deepOptions = {}, boundOptions = {}, setBoundExeca, createNested }, firstArgument, ...nextArguments) => {
96517
- if (isPlainObject3(firstArgument)) {
96808
+ if (isPlainObject4(firstArgument)) {
96518
96809
  return createNested(mapArguments, mergeOptions(boundOptions, firstArgument), setBoundExeca);
96519
96810
  }
96520
96811
  const { file, commandArguments, options, isSync } = parseArguments2({
@@ -98578,7 +98869,7 @@ var init_isArrayLikeObject = __esm(() => {
98578
98869
  });
98579
98870
 
98580
98871
  // node_modules/lodash-es/isPlainObject.js
98581
- function isPlainObject4(value) {
98872
+ function isPlainObject5(value) {
98582
98873
  if (!isObjectLike_default(value) || _baseGetTag_default(value) != objectTag4) {
98583
98874
  return false;
98584
98875
  }
@@ -98599,7 +98890,7 @@ var init_isPlainObject = __esm(() => {
98599
98890
  funcToString3 = funcProto3.toString;
98600
98891
  hasOwnProperty11 = objectProto14.hasOwnProperty;
98601
98892
  objectCtorString = funcToString3.call(Object);
98602
- isPlainObject_default = isPlainObject4;
98893
+ isPlainObject_default = isPlainObject5;
98603
98894
  });
98604
98895
 
98605
98896
  // node_modules/lodash-es/_safeGet.js
@@ -102730,9 +103021,9 @@ function merge2(...objs) {
102730
103021
  }
102731
103022
  const targetKey = caseless && typeof key === "string" && findKey(result, key) || key;
102732
103023
  const existing = hasOwnProperty14(result, targetKey) ? result[targetKey] : undefined;
102733
- if (isPlainObject5(existing) && isPlainObject5(val)) {
103024
+ if (isPlainObject6(existing) && isPlainObject6(val)) {
102734
103025
  result[targetKey] = merge2(existing, val);
102735
- } else if (isPlainObject5(val)) {
103026
+ } else if (isPlainObject6(val)) {
102736
103027
  result[targetKey] = merge2({}, val);
102737
103028
  } else if (isArray3(val)) {
102738
103029
  result[targetKey] = val.slice();
@@ -102835,7 +103126,7 @@ var toString3, getPrototypeOf, iterator, toStringTag, hasOwnProperty14, isUnsafe
102835
103126
  }, kindOf, kindOfTest = (type) => {
102836
103127
  type = type.toLowerCase();
102837
103128
  return (thing) => kindOf(thing) === type;
102838
- }, 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) => {
102839
103130
  if (!isObject4(val)) {
102840
103131
  return false;
102841
103132
  }
@@ -103111,7 +103402,7 @@ var init_utils3 = __esm(() => {
103111
103402
  isNumber,
103112
103403
  isBoolean,
103113
103404
  isObject: isObject4,
103114
- isPlainObject: isPlainObject5,
103405
+ isPlainObject: isPlainObject6,
103115
103406
  isEmptyObject,
103116
103407
  isReadableStream: isReadableStream4,
103117
103408
  isRequest,
@@ -181149,6 +181440,7 @@ __export(exports_engineCompat, {
181149
181440
  MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES: () => MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES,
181150
181441
  MAX_GOAL_CHECKINS: () => MAX_GOAL_CHECKINS,
181151
181442
  MAX_GOAL_CONDITION_CHARS: () => MAX_GOAL_CONDITION_CHARS,
181443
+ MAX_HOOK_CONCURRENCY: () => MAX_HOOK_CONCURRENCY,
181152
181444
  MAX_PTL_RETRIES: () => MAX_PTL_RETRIES,
181153
181445
  MAX_TRANSCRIPT_READ_BYTES: () => MAX_TRANSCRIPT_READ_BYTES,
181154
181446
  MAX_WORKER_SPAWN_DEPTH: () => MAX_WORKER_SPAWN_DEPTH,
@@ -182435,11 +182727,12 @@ function parseArgumentNames2(argumentNames) {
182435
182727
  return [];
182436
182728
  }
182437
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);
182438
182731
  if (Array.isArray(argumentNames)) {
182439
- return argumentNames.filter(isValidName);
182732
+ return cap(argumentNames.filter(isValidName));
182440
182733
  }
182441
182734
  if (typeof argumentNames === "string") {
182442
- return argumentNames.split(/\s+/).filter(isValidName);
182735
+ return cap(argumentNames.split(/\s+/).filter(isValidName));
182443
182736
  }
182444
182737
  return [];
182445
182738
  }
@@ -182449,36 +182742,144 @@ function generateProgressiveArgumentHint2(argNames, typedArgs) {
182449
182742
  return;
182450
182743
  return remaining.map((name) => `[${name}]`).join(" ");
182451
182744
  }
182452
- 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) {
182453
182800
  if (args === undefined || args === null) {
182454
182801
  return content;
182455
182802
  }
182456
- const parsedArgs = parseArguments3(args);
182457
- const originalContent = content;
182458
- for (let i = 0;i < argumentNames.length; i++) {
182459
- const name = argumentNames[i];
182460
- if (!name)
182461
- continue;
182462
- 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
+ }
182463
182817
  }
182464
- content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, indexStr) => {
182465
- const index = parseInt(indexStr, 10);
182466
- return parsedArgs[index] ?? "";
182467
- });
182468
- content = content.replace(/\$(\d+)(?!\w)/g, (_, indexStr) => {
182469
- const index = parseInt(indexStr, 10);
182470
- return parsedArgs[index] ?? "";
182471
- });
182472
- content = content.replaceAll("$ARGUMENTS", args);
182473
- if (content === originalContent && appendIfNoPlaceholder && args) {
182474
- 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 + `
182475
182846
 
182476
182847
  ARGUMENTS: ${args}`;
182477
182848
  }
182478
- return content;
182849
+ return out;
182479
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;
182480
182858
  var init_argumentSubstitution2 = __esm(() => {
182481
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;
182482
182883
  });
182483
182884
 
182484
182885
  // src/tui/utils/generatedFiles.ts
@@ -184911,7 +185312,7 @@ class Protocol {
184911
185312
  };
184912
185313
  }
184913
185314
  }
184914
- function isPlainObject6(value) {
185315
+ function isPlainObject7(value) {
184915
185316
  return value !== null && typeof value === "object" && !Array.isArray(value);
184916
185317
  }
184917
185318
  function mergeCapabilities(base, additional) {
@@ -184922,7 +185323,7 @@ function mergeCapabilities(base, additional) {
184922
185323
  if (addValue === undefined)
184923
185324
  continue;
184924
185325
  const baseValue = result[k];
184925
- if (isPlainObject6(baseValue) && isPlainObject6(addValue)) {
185326
+ if (isPlainObject7(baseValue) && isPlainObject7(addValue)) {
184926
185327
  result[k] = { ...baseValue, ...addValue };
184927
185328
  } else {
184928
185329
  result[k] = addValue;
@@ -201476,10 +201877,10 @@ function parseProxyResponse(socket) {
201476
201877
  read();
201477
201878
  });
201478
201879
  }
201479
- var import_debug53, debug;
201880
+ var import_debug54, debug;
201480
201881
  var init_parse_proxy_response = __esm(() => {
201481
- import_debug53 = __toESM(require_src(), 1);
201482
- debug = import_debug53.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");
201483
201884
  });
201484
201885
 
201485
201886
  // node_modules/proxy-agent-negotiate/dist/index.js
@@ -201532,7 +201933,7 @@ function omit2(obj, ...keys) {
201532
201933
  }
201533
201934
  return ret;
201534
201935
  }
201535
- var import_debug54, debug2, setServernameFromNonIpHost = (options) => {
201936
+ var import_debug55, debug2, setServernameFromNonIpHost = (options) => {
201536
201937
  if (options.servername === undefined && options.host && !net2.isIP(options.host)) {
201537
201938
  return {
201538
201939
  ...options,
@@ -201544,8 +201945,8 @@ var import_debug54, debug2, setServernameFromNonIpHost = (options) => {
201544
201945
  var init_dist7 = __esm(() => {
201545
201946
  init_dist6();
201546
201947
  init_parse_proxy_response();
201547
- import_debug54 = __toESM(require_src(), 1);
201548
- debug2 = import_debug54.default("https-proxy-agent");
201948
+ import_debug55 = __toESM(require_src(), 1);
201949
+ debug2 = import_debug55.default("https-proxy-agent");
201549
201950
  HttpsProxyAgent2 = class HttpsProxyAgent2 extends Agent2 {
201550
201951
  constructor(proxy, opts) {
201551
201952
  super(opts);
@@ -263020,6 +263421,9 @@ function convertToHookMatchers(sessionMatchers) {
263020
263421
  return sessionMatchers.map((sm) => ({
263021
263422
  matcher: sm.matcher,
263022
263423
  skillRoot: sm.skillRoot,
263424
+ pluginRoot: sm.pluginRoot,
263425
+ pluginName: sm.pluginName,
263426
+ pluginId: sm.pluginId,
263023
263427
  hooks: sm.hooks.map((h) => h.hook).filter((h) => h.type !== "function")
263024
263428
  }));
263025
263429
  }
@@ -273799,6 +274203,19 @@ async function toArray2(generator) {
273799
274203
  }
273800
274204
  return result;
273801
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
+ }
273802
274219
  var NO_VALUE;
273803
274220
  var init_generators = __esm(() => {
273804
274221
  NO_VALUE = Symbol("NO_VALUE");
@@ -303702,11 +304119,11 @@ function byteAt2(L, charIdx) {
303702
304119
  L.byteTable = t;
303703
304120
  return t[charIdx];
303704
304121
  }
303705
- function isWordChar2(c) {
304122
+ function isWordChar4(c) {
303706
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 === "]";
303707
304124
  }
303708
304125
  function isWordStart2(c) {
303709
- return isWordChar2(c) || c === "\\";
304126
+ return isWordChar4(c) || c === "\\";
303710
304127
  }
303711
304128
  function isIdentStart2(c) {
303712
304129
  return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
@@ -304016,7 +304433,7 @@ function nextToken2(L, ctx = "arg") {
304016
304433
  advance2(L);
304017
304434
  continue;
304018
304435
  }
304019
- if (!isWordChar2(ch) && ch !== "{" && ch !== "}") {
304436
+ if (!isWordChar4(ch) && ch !== "{" && ch !== "}") {
304020
304437
  break;
304021
304438
  }
304022
304439
  advance2(L);
@@ -325621,7 +326038,7 @@ function parseSkillFrontmatterFields2(frontmatter, markdownContent, resolvedName
325621
326038
  logForDebugging3(`Skill ${resolvedName} has invalid effort '${effortRaw}'. Valid options: ${EFFORT_LEVELS2.join(", ")} or an integer`);
325622
326039
  }
325623
326040
  return {
325624
- displayName: frontmatter.name != null ? String(frontmatter.name) : undefined,
326041
+ displayName: sanitizeDisplayName2(frontmatter.name != null ? String(frontmatter.name) : undefined, resolvedName),
325625
326042
  description,
325626
326043
  hasUserSpecifiedDescription: validatedDescription !== null,
325627
326044
  allowedTools: parseSlashCommandToolsFromFrontmatter2(frontmatter["allowed-tools"]),
@@ -325737,6 +326154,10 @@ async function loadSkillsFromSkillsDir2(basePath, source) {
325737
326154
  if (!entry.isDirectory() && !entry.isSymbolicLink()) {
325738
326155
  return null;
325739
326156
  }
326157
+ if (isUnsafeCommandName2(entry.name)) {
326158
+ logForDebugging3(`[skills] skipping unsafe skill directory name: ${JSON.stringify(entry.name)}`);
326159
+ return null;
326160
+ }
325740
326161
  const skillDirPath = join108(basePath, entry.name);
325741
326162
  const skillFilePath = join108(skillDirPath, "SKILL.md");
325742
326163
  let content;
@@ -325847,6 +326268,10 @@ async function loadSkillsFromCommandsDir2(cwd) {
325847
326268
  content,
325848
326269
  source
325849
326270
  });
326271
+ if (isUnsafeCommandName2(cmdName)) {
326272
+ logForDebugging3(`[skills] skipping unsafe command name: ${JSON.stringify(cmdName)}`);
326273
+ continue;
326274
+ }
325850
326275
  const parsed = parseSkillFrontmatterFields2(frontmatter, content, cmdName, "Custom command");
325851
326276
  skills.push({
325852
326277
  skill: createSkillCommand2({
@@ -325985,6 +326410,7 @@ var init_loadSkillsDir2 = __esm(() => {
325985
326410
  init_bootstrapState();
325986
326411
  init_tokenEstimation();
325987
326412
  init_argumentSubstitution2();
326413
+ init_commandNameSafety2();
325988
326414
  init_debug();
325989
326415
  init_effort2();
325990
326416
  init_envUtils2();
@@ -330406,7 +330832,7 @@ function createPluginCommand(commandName, file, sourceName, pluginManifest, plug
330406
330832
  const argumentNames = parseArgumentNames2(frontmatter.arguments);
330407
330833
  const whenToUse = frontmatter["when-to-use"] ?? frontmatter.when_to_use;
330408
330834
  const version = frontmatter.version;
330409
- const displayName = frontmatter.name;
330835
+ const displayName = sanitizeDisplayName2(frontmatter.name, commandName);
330410
330836
  const model = frontmatter.model === "inherit" ? undefined : frontmatter.model ? parseUserSpecifiedModel2(frontmatter.model) : undefined;
330411
330837
  const effortRaw = frontmatter["effort"];
330412
330838
  const effort = effortRaw !== undefined ? parseEffortValue2(effortRaw) : undefined;
@@ -330591,6 +331017,7 @@ var init_loadPluginCommands = __esm(() => {
330591
331017
  init_bootstrapState();
330592
331018
  init_argumentSubstitution2();
330593
331019
  init_debug();
331020
+ init_commandNameSafety2();
330594
331021
  init_effort2();
330595
331022
  init_envUtils2();
330596
331023
  init_errors4();
@@ -330742,8 +331169,15 @@ var init_loadPluginCommands = __esm(() => {
330742
331169
  return pluginCommands;
330743
331170
  }));
330744
331171
  const allCommands = perPluginCommands.flat();
330745
- logForDebugging3(`Total plugin commands loaded: ${allCommands.length}`);
330746
- 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;
330747
331181
  });
330748
331182
  getPluginSkills = memoize_default(async () => {
330749
331183
  if (isBareMode() && getInlinePlugins().length === 0) {
@@ -330788,8 +331222,15 @@ var init_loadPluginCommands = __esm(() => {
330788
331222
  return pluginSkills;
330789
331223
  }));
330790
331224
  const allSkills = perPluginSkills.flat();
330791
- logForDebugging3(`Total plugin skills loaded: ${allSkills.length}`);
330792
- 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;
330793
331234
  });
330794
331235
  });
330795
331236
 
@@ -335812,6 +336253,12 @@ function mergePluginSources(sources) {
335812
336253
  const marketplacePlugins = sources.marketplace.filter((p) => {
335813
336254
  if (sessionNames.has(p.name)) {
335814
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
+ });
335815
336262
  return false;
335816
336263
  }
335817
336264
  return true;
@@ -337818,8 +338265,9 @@ function handleMessageFromStream(message, onMessage, onUpdateLength, onSetStream
337818
338265
  }
337819
338266
  }
337820
338267
  function wrapInSystemReminder(content) {
338268
+ const neutralized = content.replaceAll("</system-reminder>", "&lt;/system-reminder&gt;").replaceAll("<system-reminder>", "&lt;system-reminder&gt;");
337821
338269
  return `<system-reminder>
337822
- ${content}
338270
+ ${neutralized}
337823
338271
  </system-reminder>`;
337824
338272
  }
337825
338273
  function wrapMessagesInSystemReminder(messages) {
@@ -340428,6 +340876,16 @@ async function hasPermissionsToUseToolInner(tool, input, context) {
340428
340876
  throw new AbortError2;
340429
340877
  }
340430
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
+ }
340431
340889
  const denyRule = getDenyRuleForTool2(appState.toolPermissionContext, tool);
340432
340890
  if (denyRule) {
340433
340891
  return {
@@ -340608,7 +341066,7 @@ function syncPermissionRulesFromDisk2(toolPermissionContext, rules) {
340608
341066
  function getUpdatedInputOrFallback2(permissionResult, fallback) {
340609
341067
  return ("updatedInput" in permissionResult ? permissionResult.updatedInput : undefined) ?? fallback;
340610
341068
  }
340611
- 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) => {
340612
341070
  const result = await hasPermissionsToUseToolInner(tool, input, context);
340613
341071
  if (result.behavior === "allow") {
340614
341072
  const appState = context.getAppState();
@@ -340841,6 +341299,10 @@ var init_permissions6 = __esm(() => {
340841
341299
  init_slowOperations();
340842
341300
  init_denialTracking();
340843
341301
  init_yoloClassifier();
341302
+ PLAN_MODE_WRITE_EXEMPT_TOOL_NAMES2 = new Set([
341303
+ "EnterPlanMode",
341304
+ "ExitPlanMode"
341305
+ ]);
340844
341306
  classifierDecisionModule = feature("TRANSCRIPT_CLASSIFIER") ? (init_classifierDecision(), __toCommonJS(exports_classifierDecision)) : null;
340845
341307
  autoModeStateModule3 = feature("TRANSCRIPT_CLASSIFIER") ? __toCommonJS(exports_autoModeState) : null;
340846
341308
  CLASSIFIER_FAIL_CLOSED_REFRESH_MS = 30 * 60 * 1000;
@@ -405071,6 +405533,8 @@ async function searchSkillsAcrossMarketplaces(query) {
405071
405533
  for (const d of dirEntries) {
405072
405534
  if (!d.isDirectory())
405073
405535
  continue;
405536
+ if (isUnsafeCommandName2(d.name))
405537
+ continue;
405074
405538
  const skillFile = join146(skillDir, d.name, "SKILL.md");
405075
405539
  if (seen.has(skillFile))
405076
405540
  continue;
@@ -405082,7 +405546,7 @@ async function searchSkillsAcrossMarketplaces(query) {
405082
405546
  continue;
405083
405547
  }
405084
405548
  const { frontmatter, content: markdown } = parseFrontmatter2(content);
405085
- const resolvedName = typeof frontmatter.name === "string" ? frontmatter.name : d.name;
405549
+ const resolvedName = d.name;
405086
405550
  const parsed = parseSkillFrontmatterFields2(frontmatter, markdown, resolvedName);
405087
405551
  const installId = createPluginId(entry.name, marketplaceName);
405088
405552
  const skill = {
@@ -405107,6 +405571,7 @@ async function searchSkillsAcrossMarketplaces(query) {
405107
405571
  return results;
405108
405572
  }
405109
405573
  var init_skillSearch = __esm(() => {
405574
+ init_commandNameSafety2();
405110
405575
  init_frontmatterParser2();
405111
405576
  init_loadSkillsDir2();
405112
405577
  init_marketplaceHelpers();
@@ -412471,7 +412936,7 @@ var call45 = async (_args, _context) => {
412471
412936
  reloadSkillsNow();
412472
412937
  return {
412473
412938
  type: "text",
412474
- 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."
412475
412940
  };
412476
412941
  };
412477
412942
  var init_reload_skills = __esm(() => {
@@ -428505,9 +428970,144 @@ var init_combinedAbortSignal = __esm(() => {
428505
428970
  init_abortController2();
428506
428971
  });
428507
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
+
428508
429108
  // src/tui/utils/hooks/hookHelpers.ts
428509
429109
  function addArgumentsToPrompt(prompt, jsonInput) {
428510
- return substituteArguments2(prompt, jsonInput);
429110
+ return substituteArgumentsStrict2(prompt, jsonInput);
428511
429111
  }
428512
429112
  function createStructuredOutputTool() {
428513
429113
  return {
@@ -428555,6 +429155,19 @@ async function execPromptHook(hook, hookName, hookEvent, jsonInput, signal, tool
428555
429155
  const effectiveToolUseID = toolUseID || `hook-${randomUUID41()}`;
428556
429156
  try {
428557
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
+ }
428558
429171
  logForDebugging3(`Hooks: Processing prompt hook with prompt: ${processedPrompt}`);
428559
429172
  const userMessage = createUserMessage2({ content: processedPrompt });
428560
429173
  const messagesToQuery = messages && messages.length > 0 ? [...messages, userMessage] : [userMessage];
@@ -428701,6 +429314,17 @@ async function execAgentHook(hook, hookName, hookEvent, jsonInput, signal, toolU
428701
429314
  const hookStartTime = Date.now();
428702
429315
  try {
428703
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
+ }
428704
429328
  logForDebugging3(`Hooks: Processing agent hook with prompt: ${processedPrompt}`);
428705
429329
  const userMessage = createUserMessage2({ content: processedPrompt });
428706
429330
  const agentMessages = [userMessage];
@@ -429189,7 +429813,7 @@ import { randomUUID as randomUUID43 } from "crypto";
429189
429813
  function getSessionEndHookTimeoutMs() {
429190
429814
  const raw = process.env.ATLAS_SESSIONEND_HOOKS_TIMEOUT_MS;
429191
429815
  const parsed = raw ? parseInt(raw, 10) : NaN;
429192
- 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;
429193
429817
  }
429194
429818
  function executeInBackground({
429195
429819
  processId,
@@ -430225,18 +430849,33 @@ async function* executeHooks({
430225
430849
  if (jsonInputResult !== undefined) {
430226
430850
  return jsonInputResult;
430227
430851
  }
430228
- try {
430229
- return jsonInputResult = { ok: true, value: jsonStringify5(hookInput) };
430230
- } 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`);
430231
430854
  logError3(Error(`Failed to stringify hook ${hookName} input`, { cause: error }));
430232
430855
  return jsonInputResult = { ok: false, error };
430233
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 };
430234
430868
  }
430235
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
+ }
430236
430875
  if (hook.type === "callback") {
430237
430876
  const callbackTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
430238
430877
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: callbackTimeoutMs });
430239
- yield executeHookCallback({
430878
+ yield runWithHookReentrancy(hookEvent, hook, pluginRoot, () => executeHookCallback({
430240
430879
  toolUseID,
430241
430880
  hook,
430242
430881
  hookEvent,
@@ -430244,7 +430883,23 @@ async function* executeHooks({
430244
430883
  signal: abortSignal,
430245
430884
  hookIndex,
430246
430885
  toolUseContext
430247
- }).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));
430248
430903
  return;
430249
430904
  }
430250
430905
  if (hook.type === "function") {
@@ -430262,7 +430917,7 @@ async function* executeHooks({
430262
430917
  };
430263
430918
  return;
430264
430919
  }
430265
- yield executeFunctionHook({
430920
+ yield runWithHookReentrancy(hookEvent, hook, pluginRoot, () => executeFunctionHook({
430266
430921
  hook,
430267
430922
  messages,
430268
430923
  hookName,
@@ -430270,7 +430925,7 @@ async function* executeHooks({
430270
430925
  hookEvent,
430271
430926
  timeoutMs,
430272
430927
  signal
430273
- });
430928
+ }));
430274
430929
  return;
430275
430930
  }
430276
430931
  const commandTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
@@ -430283,6 +430938,7 @@ async function* executeHooks({
430283
430938
  try {
430284
430939
  const jsonInputRes = getJsonInput();
430285
430940
  if (!jsonInputRes.ok) {
430941
+ const failClosed = shouldFailClosePluginHook(hookEvent, pluginRoot);
430286
430942
  yield {
430287
430943
  message: createAttachmentMessage({
430288
430944
  type: "hook_error_during_execution",
@@ -430293,7 +430949,13 @@ async function* executeHooks({
430293
430949
  command: hookCommand,
430294
430950
  durationMs: Date.now() - hookStartMs
430295
430951
  }),
430296
- 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",
430297
430959
  hook
430298
430960
  };
430299
430961
  cleanup();
@@ -430304,7 +430966,7 @@ async function* executeHooks({
430304
430966
  if (!toolUseContext) {
430305
430967
  throw new Error("ToolUseContext is required for prompt hooks. This is a bug.");
430306
430968
  }
430307
- 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));
430308
430970
  if (promptResult.message?.type === "attachment") {
430309
430971
  const att = promptResult.message.attachment;
430310
430972
  if (att.type === "hook_success" || att.type === "hook_non_blocking_error") {
@@ -430323,7 +430985,7 @@ async function* executeHooks({
430323
430985
  if (!messages) {
430324
430986
  throw new Error("Messages are required for agent hooks. This is a bug.");
430325
430987
  }
430326
- 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));
430327
430989
  if (agentResult.message?.type === "attachment") {
430328
430990
  const att = agentResult.message.attachment;
430329
430991
  if (att.type === "hook_success" || att.type === "hook_non_blocking_error") {
@@ -430337,7 +430999,7 @@ async function* executeHooks({
430337
430999
  }
430338
431000
  if (hook.type === "http") {
430339
431001
  emitHookStarted(hookId, hookName, hookEvent);
430340
- const httpResult = await execHttpHook(hook, hookEvent, jsonInput, signal);
431002
+ const httpResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execHttpHook(hook, hookEvent, jsonInput, signal));
430341
431003
  cleanup?.();
430342
431004
  if (httpResult.aborted) {
430343
431005
  emitHookResponse({
@@ -430465,7 +431127,7 @@ async function* executeHooks({
430465
431127
  return;
430466
431128
  }
430467
431129
  emitHookStarted(hookId, hookName, hookEvent);
430468
- 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));
430469
431131
  cleanup?.();
430470
431132
  const durationMs = Date.now() - hookStartMs;
430471
431133
  if (result.backgrounded) {
@@ -430711,7 +431373,7 @@ async function* executeHooks({
430711
431373
  cancelled: 0
430712
431374
  };
430713
431375
  let permissionBehavior;
430714
- for await (const result of all2(hookPromises)) {
431376
+ for await (const result of all2(hookPromises, MAX_HOOK_CONCURRENCY)) {
430715
431377
  outcomes[result.outcome]++;
430716
431378
  if (result.terminalSequence) {
430717
431379
  try {
@@ -430901,20 +431563,46 @@ async function executeHooksOutsideREPL({
430901
431563
  const pluginHookCounts = getPluginHookCounts(userHooks);
430902
431564
  const hookTypeCounts = getHookTypeCounts(userHooks);
430903
431565
  }
430904
- let jsonInput;
430905
- try {
430906
- jsonInput = jsonStringify5(hookInput);
430907
- } catch (error) {
430908
- logError3(error);
430909
- 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
+ }));
430910
431585
  }
430911
- const hookPromises = matchingHooks.map(async ({ hook, pluginRoot, pluginId }, hookIndex) => {
431586
+ if (serialized.droppedKeys.length > 0) {
431587
+ logForDebugging3(`Hooks: ${hookName}: dropped input keys with throwing getters: ${serialized.droppedKeys.join(", ")}`);
431588
+ }
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
+ }
430912
431600
  if (hook.type === "callback") {
430913
431601
  const callbackTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
430914
431602
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: callbackTimeoutMs });
430915
431603
  try {
430916
431604
  const toolUseID = randomUUID43();
430917
- const json = await hook.callback(hookInput, toolUseID, abortSignal, hookIndex);
431605
+ const json = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => hook.callback(hookInput, toolUseID, abortSignal, hookIndex));
430918
431606
  cleanup?.();
430919
431607
  if (isAsyncHookJSONOutput(json)) {
430920
431608
  logForDebugging3(`${hookName} [callback] returned async response, returning empty output`);
@@ -430973,7 +431661,7 @@ async function executeHooksOutsideREPL({
430973
431661
  }
430974
431662
  if (hook.type === "http") {
430975
431663
  try {
430976
- const httpResult = await execHttpHook(hook, hookEvent, jsonInput, signal);
431664
+ const httpResult = await runWithHookReentrancy(hookEvent, hook, pluginRoot, () => execHttpHook(hook, hookEvent, jsonInput, signal));
430977
431665
  if (httpResult.aborted) {
430978
431666
  logForDebugging3(`${hookName} [${hook.url}] cancelled`);
430979
431667
  return {
@@ -431024,7 +431712,7 @@ async function executeHooksOutsideREPL({
431024
431712
  const commandTimeoutMs = hook.timeout ? hook.timeout * 1000 : timeoutMs;
431025
431713
  const { signal: abortSignal, cleanup } = createCombinedAbortSignal(signal, { timeoutMs: commandTimeoutMs });
431026
431714
  try {
431027
- 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));
431028
431716
  cleanup?.();
431029
431717
  if (result.aborted) {
431030
431718
  logForDebugging3(`${hookName} [${hook.command}] cancelled`);
@@ -431068,7 +431756,7 @@ async function executeHooksOutsideREPL({
431068
431756
  };
431069
431757
  }
431070
431758
  });
431071
- return await Promise.all(hookPromises);
431759
+ return await runWithConcurrencyLimit(hookTasks, MAX_HOOK_CONCURRENCY);
431072
431760
  }
431073
431761
  async function* executePermissionDeniedHooks(toolName, toolUseID, toolInput, reason, toolUseContext, permissionMode, signal, timeoutMs = TOOL_HOOK_EXECUTION_TIMEOUT_MS) {
431074
431762
  const appState = toolUseContext.getAppState();
@@ -431868,7 +432556,7 @@ function getHookDefinitionsForTelemetry(matchedHooks) {
431868
432556
  return { type: "unknown" };
431869
432557
  });
431870
432558
  }
431871
- 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;
431872
432560
  var init_hooks7 = __esm(() => {
431873
432561
  init_file();
431874
432562
  init_ShellCommand2();
@@ -431901,12 +432589,16 @@ var init_hooks7 = __esm(() => {
431901
432589
  init_permissionRuleParser2();
431902
432590
  init_log5();
431903
432591
  init_combinedAbortSignal();
432592
+ init_hookInputMaterialize();
432593
+ init_hookReentrancy();
432594
+ init_hookFailClosed();
431904
432595
  init_AsyncHookRegistry();
431905
432596
  init_messageQueueManager();
431906
432597
  init_messages3();
431907
432598
  init_hookEvents2();
431908
432599
  init_attachments();
431909
432600
  init_generators();
432601
+ init_engine();
431910
432602
  init_Tool();
431911
432603
  init_execPromptHook();
431912
432604
  init_execAgentHook();
@@ -440987,7 +441679,21 @@ function getDestructiveCommandWarning2(command) {
440987
441679
  }
440988
441680
  return null;
440989
441681
  }
440990
- 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";
440991
441697
  var init_destructiveCommandWarning2 = __esm(() => {
440992
441698
  DESTRUCTIVE_PATTERNS = [
440993
441699
  {
@@ -441055,6 +441761,13 @@ var init_destructiveCommandWarning2 = __esm(() => {
441055
441761
  warning: "Note: may destroy Terraform infrastructure"
441056
441762
  }
441057
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
+ ];
441058
441771
  });
441059
441772
 
441060
441773
  // src/tui/utils/shell/specPrefix.ts
@@ -445115,7 +445828,7 @@ function BashPermissionRequestInner({
445115
445828
  sandboxingEnabled: sandboxingEnabled_0,
445116
445829
  isSandboxed: isSandboxed_0
445117
445830
  } = import_react193.useMemo(() => {
445118
- 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;
445119
445832
  const sandboxingEnabled = getSandboxManager().isSandboxingEnabled();
445120
445833
  const isSandboxed = sandboxingEnabled && shouldUseSandbox2(toolUseConfirm.input);
445121
445834
  return {
@@ -470075,41 +470788,28 @@ async function processUserInput({
470075
470788
  if (mode === "prompt" && inputString !== null && !isMeta) {
470076
470789
  setUserInputOnProcessing?.(inputString);
470077
470790
  }
470078
- queryCheckpoint("query_process_user_input_base_start");
470079
470791
  const appState = context.getAppState();
470080
- const result = await processUserInputBase(input, mode, setToolJSX, context, pastedContents, ideSelection, messages, uuid, isAlreadyProcessing, querySource, canUseTool, appState.toolPermissionContext.mode, skipSlashCommands, bridgeOrigin, isMeta, skipAttachments, preExpansionInput);
470081
- queryCheckpoint("query_process_user_input_base_end");
470082
- if (!result.shouldQuery) {
470083
- return result;
470084
- }
470085
470792
  queryCheckpoint("query_hooks_start");
470086
470793
  const inputMessage = getContentText(input) || "";
470794
+ let hookBlockingMessage = null;
470795
+ let hookStopped = false;
470796
+ let hookStopReason;
470797
+ const hookMessages = [];
470087
470798
  for await (const hookResult of executeUserPromptSubmitHooks(inputMessage, appState.toolPermissionContext.mode, context, context.requestPrompt)) {
470088
470799
  if (hookResult.message?.type === "progress") {
470089
470800
  continue;
470090
470801
  }
470091
470802
  if (hookResult.blockingError) {
470092
- const blockingMessage = getUserPromptSubmitHookBlockingMessage(hookResult.blockingError);
470093
- return {
470094
- messages: [
470095
- createSystemMessage(`${blockingMessage}
470096
-
470097
- Original prompt: ${input}`, "warning")
470098
- ],
470099
- shouldQuery: false,
470100
- allowedTools: result.allowedTools
470101
- };
470803
+ hookBlockingMessage = getUserPromptSubmitHookBlockingMessage(hookResult.blockingError);
470804
+ break;
470102
470805
  }
470103
470806
  if (hookResult.preventContinuation) {
470104
- const message = hookResult.stopReason ? `Operation stopped by hook: ${hookResult.stopReason}` : "Operation stopped by hook";
470105
- result.messages.push(createUserMessage2({
470106
- content: message
470107
- }));
470108
- result.shouldQuery = false;
470109
- return result;
470807
+ hookStopped = true;
470808
+ hookStopReason = hookResult.stopReason;
470809
+ continue;
470110
470810
  }
470111
470811
  if (hookResult.additionalContexts && hookResult.additionalContexts.length > 0) {
470112
- result.messages.push(createAttachmentMessage({
470812
+ hookMessages.push(createAttachmentMessage({
470113
470813
  type: "hook_additional_context",
470114
470814
  content: hookResult.additionalContexts.map(applyTruncation),
470115
470815
  hookName: "UserPromptSubmit",
@@ -470123,7 +470823,7 @@ Original prompt: ${input}`, "warning")
470123
470823
  if (!hookResult.message.attachment.content) {
470124
470824
  break;
470125
470825
  }
470126
- result.messages.push({
470826
+ hookMessages.push({
470127
470827
  ...hookResult.message,
470128
470828
  attachment: {
470129
470829
  ...hookResult.message.attachment,
@@ -470132,12 +470832,38 @@ Original prompt: ${input}`, "warning")
470132
470832
  });
470133
470833
  break;
470134
470834
  default:
470135
- result.messages.push(hookResult.message);
470835
+ hookMessages.push(hookResult.message);
470136
470836
  break;
470137
470837
  }
470138
470838
  }
470139
470839
  }
470140
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);
470141
470867
  return result;
470142
470868
  }
470143
470869
  function applyTruncation(content) {