@yanlinglabs/winter-agent-runtime 0.0.39 → 0.0.40

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.
@@ -61,12 +61,24 @@ function createRpcBridge2(output, bridgeOpts = {}) {
61
61
  const onAbort = () => {
62
62
  if (pending.get(requestId) !== entry)
63
63
  return;
64
- pending.delete(requestId);
65
64
  if (entry.timer !== undefined)
66
65
  clearTimeout(entry.timer);
67
- if (held?.delete(requestId) !== true)
66
+ const wasHeld = held?.delete(requestId) === true;
67
+ if (!wasHeld)
68
68
  output.write({ type: "control_cancel_request", requestId });
69
- reject(new WinterRpcError("cancelled", `the '${subtype}' request was cancelled: the runtime stopped waiting for it`));
69
+ const cancelled = () => {
70
+ if (pending.get(requestId) !== entry)
71
+ return;
72
+ pending.delete(requestId);
73
+ reject(new WinterRpcError("cancelled", `the '${subtype}' request was cancelled: the runtime stopped waiting for it`));
74
+ };
75
+ const grace = opts?.cancelGraceMs;
76
+ if (grace === undefined || wasHeld) {
77
+ cancelled();
78
+ return;
79
+ }
80
+ entry.timer = setTimeout(cancelled, grace);
81
+ entry.timer.unref?.();
70
82
  };
71
83
  signal.addEventListener("abort", onAbort, { once: true });
72
84
  entry.detach = () => signal.removeEventListener("abort", onAbort);
@@ -265,10 +277,28 @@ function leadingWord(s) {
265
277
  function joinLineContinuations(command) {
266
278
  if (!command.includes("\\\n"))
267
279
  return command;
268
- return command.replace(/\\+\n/g, (run) => {
269
- const backslashes = run.length - 1;
270
- return backslashes % 2 === 1 ? "\\".repeat(backslashes - 1) : run;
271
- });
280
+ let out = "";
281
+ let i = 0;
282
+ while (i < command.length) {
283
+ if (command[i] !== "\\") {
284
+ out += command[i];
285
+ i++;
286
+ continue;
287
+ }
288
+ let end = i;
289
+ while (end < command.length && command[end] === "\\")
290
+ end++;
291
+ const run = end - i;
292
+ if (command[end] === `
293
+ ` && run % 2 === 1) {
294
+ out += "\\".repeat(run - 1);
295
+ i = end + 1;
296
+ } else {
297
+ out += "\\".repeat(run);
298
+ i = end;
299
+ }
300
+ }
301
+ return out;
272
302
  }
273
303
  function splitCompound(rawCommand) {
274
304
  const command = joinLineContinuations(rawCommand);
@@ -350,25 +380,34 @@ function stripLeadingAssignmentsAt(s, info, start, direction) {
350
380
  }
351
381
  return pos;
352
382
  }
353
- function stripWrappers(rawCmd, direction) {
383
+ function stripWrappersDetailed(rawCmd, direction) {
354
384
  const cmd = joinLineContinuations(rawCmd);
355
385
  const info = scanShellLike(cmd);
386
+ const finish = (pos, ambiguousForAllow = false) => {
387
+ const carried = info.ok ? redirectWriteSpans(cmd, info).filter((span) => span.start < pos).map((span) => cmd.slice(span.start, span.end)) : [];
388
+ return { inner: cmd.slice(pos), carried, ambiguousForAllow };
389
+ };
356
390
  let pos = 0;
357
391
  for (;; ) {
358
392
  pos = stripLeadingAssignmentsAt(cmd, info, pos, direction);
359
393
  const { word: rawWord, end: afterWordEnd } = leadingWordAt(cmd, info, pos);
360
394
  if (rawWord === undefined)
361
- return cmd.slice(pos);
362
- const word = dequoteShellWord(rawWord);
395
+ return finish(pos);
396
+ const isWrapperName = (r) => r === XARGS || FIXED_WRAPPERS.has(r);
397
+ const readings = shellWordReadings(rawWord);
398
+ const exact = readings[0];
399
+ if (direction === "allow" && !isWrapperName(exact) && readings.some(isWrapperName))
400
+ return finish(pos, true);
401
+ const word = direction === "allow" ? exact : readings.find(isWrapperName) ?? exact;
363
402
  if (word === XARGS) {
364
403
  const { word: next } = leadingWordAt(cmd, info, afterWordEnd);
365
404
  if (next !== undefined && next.startsWith("-"))
366
- return cmd.slice(pos);
405
+ return finish(pos);
367
406
  pos = afterWordEnd;
368
407
  continue;
369
408
  }
370
409
  if (!FIXED_WRAPPERS.has(word))
371
- return cmd.slice(pos);
410
+ return finish(pos);
372
411
  let remainderPos = afterWordEnd;
373
412
  for (;; ) {
374
413
  const { word: flag, end: afterFlagEnd } = leadingWordAt(cmd, info, remainderPos);
@@ -384,6 +423,17 @@ function stripWrappers(rawCmd, direction) {
384
423
  pos = remainderPos;
385
424
  }
386
425
  }
426
+ var joinedStrip = (strip) => [strip.inner, ...strip.carried].filter((part) => part !== "").join(" ");
427
+ function stripWrappers(rawCmd, direction) {
428
+ const strip = stripWrappersDetailed(rawCmd, direction);
429
+ return strip.ambiguousForAllow ? joinLineContinuations(rawCmd).trimStart() : joinedStrip(strip);
430
+ }
431
+ function ruleMatchTexts(command, direction) {
432
+ const strip = stripWrappersDetailed(command, direction);
433
+ if (strip.ambiguousForAllow)
434
+ return [];
435
+ return direction === "allow" ? [strip.inner] : [joinedStrip(strip), strip.inner];
436
+ }
387
437
  var ANSI_C_SIMPLE_ESCAPES = { n: `
388
438
  `, t: "\t", r: "\r", a: "\x07", b: "\b", e: "\x1B", E: "\x1B", f: "\f", v: "\v", "\\": "\\", "'": "'", '"': '"', "?": "?" };
389
439
  function decodeAnsiCEscape(s, i) {
@@ -413,7 +463,9 @@ function decodeAnsiCEscape(s, i) {
413
463
  return { text: String.fromCharCode(s.charCodeAt(i + 1) & 31), next: i + 2 };
414
464
  return { text: `\\${ch}`, next: i + 1 };
415
465
  }
416
- function shellWords(s, split = true) {
466
+ var DOUBLE_QUOTE_ESCAPABLE = new Set(["$", "`", '"', "\\", `
467
+ `]);
468
+ function shellWords(s, split = true, reading = "broad") {
417
469
  const words = [];
418
470
  let cur = "";
419
471
  let start = -1;
@@ -455,9 +507,12 @@ function shellWords(s, split = true) {
455
507
  if (ch === '"')
456
508
  quote = null;
457
509
  else if (ch === "\\" && i + 1 < s.length) {
458
- if (s[i + 1] !== `
510
+ const next = s[i + 1];
511
+ if (reading === "bash" && !DOUBLE_QUOTE_ESCAPABLE.has(next))
512
+ cur += ch + next;
513
+ else if (next !== `
459
514
  `)
460
- cur += s[i + 1];
515
+ cur += next;
461
516
  i++;
462
517
  } else
463
518
  cur += ch;
@@ -498,7 +553,12 @@ function shellWords(s, split = true) {
498
553
  return words;
499
554
  }
500
555
  function dequoteShellWord(word) {
501
- return shellWords(word, false)[0]?.word ?? "";
556
+ return shellWords(word, false, "bash")[0]?.word ?? "";
557
+ }
558
+ function shellWordReadings(word) {
559
+ const exact = dequoteShellWord(word);
560
+ const broad = shellWords(word, false, "broad")[0]?.word ?? "";
561
+ return broad === exact ? [exact] : [exact, broad];
502
562
  }
503
563
  var REDIRECT_WORD_STOP = /[\s;&|<>()]/;
504
564
  function redirectWordAt(s, info, start) {
@@ -515,12 +575,23 @@ function extractRedirectWrites(rawCommand) {
515
575
  const info = scanShellLike(command);
516
576
  if (!info.ok)
517
577
  return [];
518
- const { topLevel } = info;
519
578
  const writes = [];
520
- const push = (raw) => {
521
- writes.push({ raw, target: dequoteShellWord(raw) });
579
+ const pushReadings = (raw) => {
580
+ for (const target of shellWordReadings(raw))
581
+ writes.push({ raw, target });
582
+ };
583
+ for (const { target: raw } of redirectWriteSpans(command, info)) {
584
+ pushReadings(raw);
522
585
  if (raw.length > 1 && raw.startsWith("!"))
523
- writes.push({ raw: raw.slice(1), target: dequoteShellWord(raw.slice(1)) });
586
+ pushReadings(raw.slice(1));
587
+ }
588
+ return writes;
589
+ }
590
+ function redirectWriteSpans(command, info) {
591
+ const { topLevel } = info;
592
+ const spans = [];
593
+ const push = (start, target, end) => {
594
+ spans.push({ start, end, target });
524
595
  };
525
596
  let i = 0;
526
597
  while (i < command.length) {
@@ -566,10 +637,10 @@ function extractRedirectWrites(rawCommand) {
566
637
  continue;
567
638
  }
568
639
  if (!(descriptorCopy && /^(?:[0-9]+|-)$/.test(dequoteShellWord(word))))
569
- push(word);
640
+ push(i, word, end);
570
641
  i = end;
571
642
  }
572
- return writes;
643
+ return spans;
573
644
  }
574
645
  function extractRedirectTargets(command) {
575
646
  return extractRedirectWrites(command).map((w) => w.target);
@@ -583,8 +654,8 @@ function isRecognizedReadOnly(command) {
583
654
  const { word: first, afterWord } = leadingWord(stripped);
584
655
  if (first === undefined)
585
656
  return false;
586
- const dequotedArgs = shellWords(afterWord).map((w) => w.word).join(" ");
587
- const hasFlag = (pattern) => pattern.test(afterWord) || pattern.test(dequotedArgs);
657
+ const views = [afterWord, ...["broad", "bash"].map((reading) => shellWords(afterWord, true, reading).map((w) => w.word).join(" "))];
658
+ const hasFlag = (pattern) => views.some((view) => pattern.test(view));
588
659
  if (first === "git") {
589
660
  const { word: sub } = leadingWord(afterWord);
590
661
  return sub !== undefined && GIT_READ_ONLY_SUBCOMMANDS.has(sub) && !hasFlag(GIT_WRITE_CAPABLE_FLAGS);
@@ -794,8 +865,8 @@ function matchesRule(rule, call, opts) {
794
865
  case "pattern": {
795
866
  const raw = call.input["command"];
796
867
  const cmd = typeof raw === "string" ? raw : "";
797
- const stripped = stripWrappers(cmd, opts.direction);
798
- return compilePattern(rule.specifier.source).test(stripped);
868
+ const pattern = compilePattern(rule.specifier.source);
869
+ return ruleMatchTexts(cmd, opts.direction).some((text) => pattern.test(text));
799
870
  }
800
871
  }
801
872
  }
@@ -1112,6 +1183,10 @@ function dedupeSameBatch(server, tools) {
1112
1183
  }
1113
1184
  return dropped ? out : tools;
1114
1185
  }
1186
+ var CONCURRENCY_LANE = /^[A-Za-z0-9_.:-]{1,64}$/;
1187
+ function isValidConcurrencyLane(lane) {
1188
+ return typeof lane === "string" && CONCURRENCY_LANE.test(lane);
1189
+ }
1115
1190
  function registerMcpServerTools(server, incomingTools, opts) {
1116
1191
  if (RESERVED_MCP_SERVER_NAMES.has(server)) {
1117
1192
  throw new Error(`registerMcpServerTools: "${server}" is a RESERVED server name -- the standing Winter server ` + `(its name is defined in mcp/winter-server.ts) owns this identity and is registry-native; it is never installed through ` + `this live-mutation function, and nothing else may register live tools under it either.`);
@@ -1166,7 +1241,9 @@ function registerMcpServerTools(server, incomingTools, opts) {
1166
1241
  for (const tool of tools) {
1167
1242
  const canonicalName = registeredName(tool);
1168
1243
  const existingEntry = registry.get(canonicalName);
1169
- const descriptor = buildMcpToolDescriptor(server, tool, opts, plainNameFor(tool));
1244
+ const built = buildMcpToolDescriptor(server, tool, opts, plainNameFor(tool));
1245
+ const lane = opts.toolLanes !== undefined && Object.hasOwn(opts.toolLanes, tool.name) ? opts.toolLanes[tool.name] : undefined;
1246
+ const descriptor = isValidConcurrencyLane(lane) ? { ...built, concurrencyLane: lane } : built;
1170
1247
  registry.set(canonicalName, existingEntry ? { ...existingEntry, descriptor } : { descriptor });
1171
1248
  mcpToolOwner.set(canonicalName, server);
1172
1249
  nowOwned.add(canonicalName);
@@ -4554,7 +4631,7 @@ function parentOf(dir) {
4554
4631
  // src/engine.ts
4555
4632
  import { randomUUID as randomUUID18 } from "node:crypto";
4556
4633
  import { homedir, release as osRelease, type as osType } from "node:os";
4557
- import { join as join25, resolve as resolve12 } from "node:path";
4634
+ import { join as join26, resolve as resolve12 } from "node:path";
4558
4635
  import {
4559
4636
  PROTOCOL_VERSION,
4560
4637
  resolveWebToolsConfig,
@@ -8390,9 +8467,10 @@ function naiveWriteWords(command) {
8390
8467
  const out = [];
8391
8468
  for (const m of command.matchAll(/(?:&>>?|[0-9]*>>?\|?|[0-9]*>&|<>)[ \t]*([^\s;&|()<>]+)/g)) {
8392
8469
  const raw = m[1];
8393
- const word = dequoteShellWord(raw);
8394
- if (!/^(?:[0-9]+|-)$/.test(word))
8395
- out.push({ word, raw, quoted: word !== raw });
8470
+ for (const word of shellWordReadings(raw)) {
8471
+ if (!/^(?:[0-9]+|-)$/.test(word))
8472
+ out.push({ word, raw, quoted: word !== raw });
8473
+ }
8396
8474
  }
8397
8475
  for (const piece of naiveCommandPieces(command)) {
8398
8476
  const stripped = stripWrappers(piece, "denyAsk");
@@ -22441,13 +22519,1591 @@ Sources:
22441
22519
  }
22442
22520
  replaceExecutor(SEARCH_CANONICAL_NAME, createSearchExecutor());
22443
22521
 
22522
+ // src/permissions/bash-read-only.ts
22523
+ import { statSync as statSync10 } from "node:fs";
22524
+ import { join as join24 } from "node:path";
22525
+ var READ_ONLY_LENGTH_LIMIT = 4096;
22526
+ var WHOLE_TEXT_REFUSALS = [
22527
+ (text) => text.length > Math.min(PARSE_LIMIT, READ_ONLY_LENGTH_LIMIT),
22528
+ (text) => text.trim() === "",
22529
+ (text) => splitCompound(text) === null,
22530
+ (text) => !passesSecurityValidators(text),
22531
+ (text, platform) => containsVulnerableUncPath(text, platform)
22532
+ ];
22533
+ function isBashCommandReadOnly(command, ctx) {
22534
+ const platform = process.platform;
22535
+ if (typeof command !== "string" || WHOLE_TEXT_REFUSALS.some((refuses) => refuses(command, platform)))
22536
+ return false;
22537
+ const joined = joinLineContinuations(command);
22538
+ if ((substitutionBodies(joined) ?? [""]).length > 0)
22539
+ return false;
22540
+ const commands = readSimpleCommands(joined) ?? [];
22541
+ if (commands.length === 0 || gitRunsUnsafely(joined, commands, ctx))
22542
+ return false;
22543
+ return commands.every((simple) => passesSecurityValidators(simple.text) && isSimpleCommandReadOnly(simple, platform));
22544
+ }
22545
+ var SHELL_OPERATORS = ["<<<", "&&", "||", "|&", ";;", ";&", "&>", ">>", ">&", ">|", "<<", "<&", "<>", ">", "<", "&", "|", ";"];
22546
+ var COMMAND_SEPARATORS = new Set([";", "&&", "||", "|"]);
22547
+ function throwsOutputAway(operator, descriptor, target) {
22548
+ if (operator === ">")
22549
+ return target === "/dev/null" && (descriptor === undefined || ["0", "1", "2"].includes(descriptor));
22550
+ return operator === ">&" && descriptor === "2" && target === "1";
22551
+ }
22552
+ function readSimpleCommands(text) {
22553
+ const lx = lex(text);
22554
+ if (lx.unterminated || text.includes("\r"))
22555
+ return null;
22556
+ let lastNonBlank = text.length - 1;
22557
+ while (lastNonBlank >= 0 && isBlank(text[lastNonBlank]))
22558
+ lastNonBlank--;
22559
+ const commands = [];
22560
+ let words = [];
22561
+ let wordStart = -1;
22562
+ let awaiting;
22563
+ let endedBySemicolon = false;
22564
+ const closeWord = (end) => {
22565
+ if (wordStart === -1)
22566
+ return true;
22567
+ const raw = text.slice(wordStart, end);
22568
+ wordStart = -1;
22569
+ if (awaiting !== undefined) {
22570
+ const harmless = throwsOutputAway(awaiting.operator, awaiting.descriptor, raw);
22571
+ awaiting = undefined;
22572
+ return harmless;
22573
+ }
22574
+ words.push({ raw, value: dequoteShellWord(raw) });
22575
+ return true;
22576
+ };
22577
+ const opensSubstitution = (i, quoteForms) => {
22578
+ const next = text[i + 1];
22579
+ return text[i] === "$" && next !== undefined && (next === "(" || next === "{" || next === "[" || quoteForms && (next === "'" || next === '"'));
22580
+ };
22581
+ for (let i = 0;i < text.length; i++) {
22582
+ const ch = text[i];
22583
+ if (!isBare(lx, i)) {
22584
+ if (lx.role[i] === 0 /* Text */ && lx.quoting[i] === 2 /* Double */ && (ch === "`" || opensSubstitution(i, false)))
22585
+ return null;
22586
+ if (wordStart === -1)
22587
+ wordStart = i;
22588
+ continue;
22589
+ }
22590
+ if (ch === " " || ch === "\t" || ch === `
22591
+ `) {
22592
+ if (!closeWord(i))
22593
+ return null;
22594
+ if (ch === `
22595
+ ` && i < lastNonBlank)
22596
+ return null;
22597
+ continue;
22598
+ }
22599
+ if (ch === "`" || ch === "(" || ch === ")" || ch === "\\")
22600
+ return null;
22601
+ if (opensSubstitution(i, true))
22602
+ return null;
22603
+ if (ch === "#" && wordStart === -1)
22604
+ return null;
22605
+ if (!";&|<>".includes(ch)) {
22606
+ if (wordStart === -1)
22607
+ wordStart = i;
22608
+ continue;
22609
+ }
22610
+ const operator = SHELL_OPERATORS.find((op) => text.startsWith(op, i));
22611
+ const glued = wordStart === -1 ? undefined : text.slice(wordStart, i);
22612
+ let descriptor;
22613
+ if (glued !== undefined && /^\d+$/.test(glued) && (operator[0] === ">" || operator[0] === "<")) {
22614
+ descriptor = glued;
22615
+ wordStart = -1;
22616
+ } else if (!closeWord(i)) {
22617
+ return null;
22618
+ }
22619
+ i += operator.length - 1;
22620
+ if (awaiting !== undefined)
22621
+ return null;
22622
+ if (descriptor === undefined && COMMAND_SEPARATORS.has(operator)) {
22623
+ if (words.length === 0)
22624
+ return null;
22625
+ commands.push({ words, text: words.map((w) => w.raw).join(" ") });
22626
+ words = [];
22627
+ endedBySemicolon = operator === ";";
22628
+ continue;
22629
+ }
22630
+ awaiting = descriptor === undefined ? { operator } : { operator, descriptor };
22631
+ }
22632
+ if (!closeWord(text.length) || awaiting !== undefined)
22633
+ return null;
22634
+ if (words.length > 0)
22635
+ commands.push({ words, text: words.map((w) => w.raw).join(" ") });
22636
+ else if (!(commands.length > 0 && endedBySemicolon))
22637
+ return null;
22638
+ return commands;
22639
+ }
22640
+ var DIRECTORY_CHANGERS = new Set(["cd", "pushd", "popd"]);
22641
+ function programsRunBy(simple) {
22642
+ const programs = new Set([simple.words[0].value]);
22643
+ const inner = leadingWord(stripWrappers(simple.text, "denyAsk")).word;
22644
+ if (inner !== undefined)
22645
+ programs.add(dequoteShellWord(inner));
22646
+ if (simple.words[0].value === "xargs")
22647
+ for (const word of simple.words)
22648
+ programs.add(word.value);
22649
+ return programs;
22650
+ }
22651
+ function entryKind(path) {
22652
+ try {
22653
+ const entry = statSync10(path);
22654
+ return entry.isFile() ? "file" : entry.isDirectory() ? "directory" : "other";
22655
+ } catch {
22656
+ return "missing";
22657
+ }
22658
+ }
22659
+ var BARE_REPOSITORY_MARKERS = [
22660
+ ["HEAD", "file"],
22661
+ ["objects", "directory"],
22662
+ ["refs", "directory"]
22663
+ ];
22664
+ function isCurrentDirectoryBareGitRepo(cwd) {
22665
+ const dotGit = entryKind(join24(cwd, ".git"));
22666
+ const workingRepository = dotGit === "file" || dotGit === "directory" && entryKind(join24(cwd, ".git", "HEAD")) === "file";
22667
+ if (workingRepository)
22668
+ return false;
22669
+ return BARE_REPOSITORY_MARKERS.some(([name, kind]) => entryKind(join24(cwd, name)) === kind);
22670
+ }
22671
+ var GIT_INTERNAL_TOP_LEVEL = new Set(["objects", "refs", "hooks"]);
22672
+ function namesGitInternals(path) {
22673
+ const segments = path.split("/").filter((segment) => segment !== "" && segment !== ".");
22674
+ const top = segments[0];
22675
+ if (top === undefined)
22676
+ return false;
22677
+ return top === "HEAD" && segments.length === 1 || GIT_INTERNAL_TOP_LEVEL.has(top);
22678
+ }
22679
+ var CREATING_COMMANDS = new Set(["mkdir", "touch", "cp", "mv"]);
22680
+ function gitRunsUnsafely(joined, commands, ctx) {
22681
+ const programs = commands.map(programsRunBy);
22682
+ if (!programs.some((set) => set.has("git")))
22683
+ return false;
22684
+ if (programs.some((set) => [...DIRECTORY_CHANGERS].some((changer) => set.has(changer))))
22685
+ return true;
22686
+ if (isCurrentDirectoryBareGitRepo(ctx.cwd))
22687
+ return true;
22688
+ if (extractRedirectTargets(joined).some(namesGitInternals))
22689
+ return true;
22690
+ const createsInternals = commands.some((simple) => CREATING_COMMANDS.has(simple.words[0].value) && simple.words.slice(1).some((w) => !w.value.startsWith("-") && namesGitInternals(w.value)));
22691
+ return createsInternals || ctx.sandboxEnabled && ctx.cwd !== ctx.originalCwd;
22692
+ }
22693
+ var WEBDAV_MARKERS = [/@SSL@\d+/i, /@\d+@SSL/i, /DavWWWRoot/i];
22694
+ var isPathSeparator = (ch) => ch === "/" || ch === "\\";
22695
+ function containsVulnerableUncPath(text, platform = process.platform) {
22696
+ if (platform !== "win32")
22697
+ return false;
22698
+ if (WEBDAV_MARKERS.some((marker) => marker.test(text)))
22699
+ return true;
22700
+ let i = 0;
22701
+ while (i < text.length) {
22702
+ if (!isPathSeparator(text[i])) {
22703
+ i++;
22704
+ continue;
22705
+ }
22706
+ const runStart = i;
22707
+ while (i < text.length && isPathSeparator(text[i]))
22708
+ i++;
22709
+ const host = text[i];
22710
+ if (i - runStart < 2 || host === undefined || /\s/.test(host))
22711
+ continue;
22712
+ const urlScheme = i - runStart === 2 && text[runStart] === "/" && text[runStart + 1] === "/" && text[runStart - 1] === ":";
22713
+ if (!urlScheme)
22714
+ return true;
22715
+ }
22716
+ return false;
22717
+ }
22718
+ function lex(text) {
22719
+ const quoting = new Uint8Array(text.length);
22720
+ const role = new Uint8Array(text.length);
22721
+ let open = 0 /* None */;
22722
+ for (let i = 0;i < text.length; i++) {
22723
+ const ch = text[i];
22724
+ if (open === 1 /* Single */) {
22725
+ quoting[i] = 1 /* Single */;
22726
+ if (ch === "'") {
22727
+ role[i] = 1 /* QuoteMark */;
22728
+ open = 0 /* None */;
22729
+ }
22730
+ continue;
22731
+ }
22732
+ if (ch === "\\" && i + 1 < text.length) {
22733
+ quoting[i] = quoting[i + 1] = open;
22734
+ role[i] = 2 /* Escape */;
22735
+ role[i + 1] = 3 /* Escaped */;
22736
+ i++;
22737
+ continue;
22738
+ }
22739
+ if (open === 2 /* Double */) {
22740
+ quoting[i] = 2 /* Double */;
22741
+ if (ch === '"') {
22742
+ role[i] = 1 /* QuoteMark */;
22743
+ open = 0 /* None */;
22744
+ }
22745
+ continue;
22746
+ }
22747
+ if (ch === "'" || ch === '"') {
22748
+ open = ch === "'" ? 1 /* Single */ : 2 /* Double */;
22749
+ quoting[i] = open;
22750
+ role[i] = 1 /* QuoteMark */;
22751
+ }
22752
+ }
22753
+ return { text, quoting, role, unterminated: open !== 0 /* None */ };
22754
+ }
22755
+ var isBare = (lx, i) => lx.quoting[i] === 0 /* None */ && lx.role[i] === 0 /* Text */;
22756
+ var isBlank = (ch) => ch !== undefined && /\s/.test(ch);
22757
+ function viewsOf(lx, keepDoubleMarks) {
22758
+ let outsideSingle = "";
22759
+ let bare = "";
22760
+ let bareWithMarks = "";
22761
+ for (let i = 0;i < lx.text.length; i++) {
22762
+ const ch = lx.text[i];
22763
+ if (lx.role[i] === 1 /* QuoteMark */) {
22764
+ bareWithMarks += ch;
22765
+ if (ch === '"' && keepDoubleMarks)
22766
+ outsideSingle += ch;
22767
+ continue;
22768
+ }
22769
+ if (lx.quoting[i] === 1 /* Single */)
22770
+ continue;
22771
+ outsideSingle += ch;
22772
+ if (lx.quoting[i] === 0 /* None */) {
22773
+ bare += ch;
22774
+ bareWithMarks += ch;
22775
+ }
22776
+ }
22777
+ return { outsideSingle, bare, bareWithMarks };
22778
+ }
22779
+ function wordSpans(lx) {
22780
+ const spans = [];
22781
+ let start = -1;
22782
+ for (let i = 0;i <= lx.text.length; i++) {
22783
+ const cut = i === lx.text.length || isBare(lx, i) && isBlank(lx.text[i]);
22784
+ if (cut) {
22785
+ if (start !== -1)
22786
+ spans.push({ start, end: i });
22787
+ start = -1;
22788
+ } else if (start === -1)
22789
+ start = i;
22790
+ }
22791
+ return spans;
22792
+ }
22793
+ function withoutHarmlessRedirections(view) {
22794
+ const drop = new Uint8Array(view.length);
22795
+ const markFrom = (from, to) => {
22796
+ for (let k = from;k < to; k++)
22797
+ drop[k] = 1;
22798
+ };
22799
+ const skipBlanks = (k) => {
22800
+ while (isBlank(view[k]))
22801
+ k++;
22802
+ return k;
22803
+ };
22804
+ const endsWord = (k) => k === view.length || isBlank(view[k]);
22805
+ const devNullAt = (k) => view.startsWith("/dev/null", k) && endsWord(k + 9) ? k + 9 : -1;
22806
+ for (let i = 0;i < view.length; i++) {
22807
+ const ch = view[i];
22808
+ if (ch === "<") {
22809
+ const end = devNullAt(skipBlanks(i + 1));
22810
+ if (end !== -1)
22811
+ markFrom(i, end);
22812
+ } else if (ch === ">" && view[i + 1] === "&") {
22813
+ const one = skipBlanks(i + 2);
22814
+ let two = i - 1;
22815
+ while (two >= 0 && isBlank(view[two]))
22816
+ two--;
22817
+ if (view[one] === "1" && endsWord(one + 1) && view[two] === "2" && isBlank(view[two - 1]))
22818
+ markFrom(two, one + 1);
22819
+ } else if (ch === ">") {
22820
+ const end = devNullAt(skipBlanks(i + 1));
22821
+ if (end !== -1)
22822
+ markFrom(i, end);
22823
+ }
22824
+ }
22825
+ let out = "";
22826
+ for (let i = 0;i < view.length; i++)
22827
+ if (drop[i] === 0)
22828
+ out += view[i];
22829
+ return out;
22830
+ }
22831
+ var CONTROL_CHARACTERS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/;
22832
+ var UNICODE_WHITESPACE = /[\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
22833
+ function singleQuoteEndsInBackslash(lx) {
22834
+ let lastSingleQuote = -1;
22835
+ for (let i = lx.text.length - 1;i >= 0; i--) {
22836
+ if (lx.text[i] === "'") {
22837
+ lastSingleQuote = i;
22838
+ break;
22839
+ }
22840
+ }
22841
+ let inside = false;
22842
+ let trailing = 0;
22843
+ for (let i = 0;i < lx.text.length; i++) {
22844
+ if (lx.quoting[i] !== 1 /* Single */)
22845
+ continue;
22846
+ if (lx.role[i] === 1 /* QuoteMark */) {
22847
+ if (inside && (trailing % 2 === 1 || trailing > 0 && lastSingleQuote > i))
22848
+ return true;
22849
+ inside = !inside;
22850
+ trailing = 0;
22851
+ continue;
22852
+ }
22853
+ trailing = lx.text[i] === "\\" ? trailing + 1 : 0;
22854
+ }
22855
+ return false;
22856
+ }
22857
+ var MIDWAY_OPENINGS = ["-", ";", ">", "<", "&&", "||"];
22858
+ function startsMidway(text) {
22859
+ const body = text.trimStart();
22860
+ const indentation = text.slice(0, text.length - body.length);
22861
+ return indentation.includes("\t") || MIDWAY_OPENINGS.some((opening) => body.startsWith(opening));
22862
+ }
22863
+ function jqReachesBeyondItsFilter(text, base) {
22864
+ if (base !== "jq")
22865
+ return false;
22866
+ for (let at = text.indexOf("system");at !== -1; at = text.indexOf("system", at + 1)) {
22867
+ if (at > 0 && /\w/.test(text[at - 1]))
22868
+ continue;
22869
+ let k = at + 6;
22870
+ while (isBlank(text[k]))
22871
+ k++;
22872
+ if (text[k] === "(")
22873
+ return true;
22874
+ }
22875
+ const words = text.split(/\s+/).slice(1);
22876
+ return words.some((word) => {
22877
+ if (["--from-file", "--rawfile", "--slurpfile", "--library-path"].some((long) => word.startsWith(long)))
22878
+ return true;
22879
+ if (!word.startsWith("-") || word.startsWith("--"))
22880
+ return false;
22881
+ const letters = /^-([A-Za-z]*)/.exec(word)[1];
22882
+ return letters.includes("f") || letters.includes("L");
22883
+ });
22884
+ }
22885
+ var QUOTE_MARK = /['"]/;
22886
+ var GLUED_QUOTED_VALUE_OPTION = { cut: "-d" };
22887
+ function quoteInsideFlagName(text, start, base) {
22888
+ let end = start;
22889
+ while (end < text.length && !isBlank(text[end]) && text[end] !== "=")
22890
+ end++;
22891
+ const head = text.slice(start, end);
22892
+ const mark = head.search(/['"]/);
22893
+ if (mark === -1)
22894
+ return false;
22895
+ if (Object.hasOwn(GLUED_QUOTED_VALUE_OPTION, base) && GLUED_QUOTED_VALUE_OPTION[base] === head.slice(0, mark))
22896
+ return false;
22897
+ const follower = text[start + mark + 1];
22898
+ return follower === undefined || /[\w'"-]/.test(follower);
22899
+ }
22900
+ function quoteRunNearDash(text) {
22901
+ for (const run of text.matchAll(/['"]+/g)) {
22902
+ const before = text[run.index - 1];
22903
+ const after = run.index + run[0].length;
22904
+ const opensWord = before === undefined || isBlank(before);
22905
+ let solid = after;
22906
+ while (isBlank(text[solid]))
22907
+ solid++;
22908
+ if (run[0].length >= 2 && (opensWord || before === "$") && text[solid] === "-")
22909
+ return true;
22910
+ if (run[0].length >= 3 && (opensWord || text[after] === "-"))
22911
+ return true;
22912
+ }
22913
+ return false;
22914
+ }
22915
+ function flagSpelledWithQuotes(lx, base) {
22916
+ const text = lx.text;
22917
+ if (base === "echo" && !/[|&;]/.test(text))
22918
+ return false;
22919
+ if (text.includes("$'") || text.includes('$"') || quoteRunNearDash(text))
22920
+ return true;
22921
+ return wordSpans(lx).some(({ start, end }) => {
22922
+ if (lx.role[start] === 1 /* QuoteMark */)
22923
+ return dequoteShellWord(text.slice(start, end)).startsWith("-");
22924
+ return text[start] === "-" && quoteInsideFlagName(text, start, base);
22925
+ });
22926
+ }
22927
+ function nextQuoteMark(s, from) {
22928
+ for (let k = from;k < s.length; k++)
22929
+ if (s[k] === "'" || s[k] === '"')
22930
+ return k;
22931
+ return -1;
22932
+ }
22933
+ function separatorInsideQuoteMarks(outsideSingle) {
22934
+ const s = outsideSingle;
22935
+ const spanHas = (open, chars, mustEndWord) => {
22936
+ const close = nextQuoteMark(s, open + 1);
22937
+ if (close === -1)
22938
+ return false;
22939
+ if (mustEndWord && !(close + 1 === s.length || isBlank(s[close + 1])))
22940
+ return false;
22941
+ for (let k = open + 1;k < close; k++)
22942
+ if (chars.includes(s[k]))
22943
+ return true;
22944
+ return false;
22945
+ };
22946
+ for (let i = 0;i < s.length; i++) {
22947
+ if ((s[i] === "'" || s[i] === '"') && (i === 0 || isBlank(s[i - 1])) && spanHas(i, ";&", true))
22948
+ return true;
22949
+ }
22950
+ for (const [predicate, chars] of [["-name", ";|&"], ["-path", ";|&"], ["-iname", ";|&"], ["-regex", ";&"]]) {
22951
+ for (let at = s.indexOf(predicate);at !== -1; at = s.indexOf(predicate, at + 1)) {
22952
+ let k = at + predicate.length;
22953
+ if (!isBlank(s[k]))
22954
+ continue;
22955
+ while (isBlank(s[k]))
22956
+ k++;
22957
+ if ((s[k] === "'" || s[k] === '"') && spanHas(k, chars, false))
22958
+ return true;
22959
+ }
22960
+ }
22961
+ return false;
22962
+ }
22963
+ function variableBesideRedirectionOrPipe(view) {
22964
+ const solidBefore = [];
22965
+ let last;
22966
+ for (let i = 0;i < view.length; i++) {
22967
+ solidBefore.push(last);
22968
+ if (!isBlank(view[i]))
22969
+ last = view[i];
22970
+ }
22971
+ const solidFrom = new Array(view.length + 1);
22972
+ for (let i = view.length - 1;i >= 0; i--)
22973
+ solidFrom[i] = isBlank(view[i]) ? solidFrom[i + 1] : view[i];
22974
+ const isRouting = (ch) => ch === "<" || ch === ">" || ch === "|";
22975
+ for (const match of view.matchAll(/\$[A-Za-z_]\w*/g)) {
22976
+ if (isRouting(solidBefore[match.index]) || isRouting(solidFrom[match.index + match[0].length]))
22977
+ return true;
22978
+ }
22979
+ return false;
22980
+ }
22981
+ function commentHoldsQuoteMark(lx) {
22982
+ let inComment = false;
22983
+ for (let i = 0;i < lx.text.length; i++) {
22984
+ const ch = lx.text[i];
22985
+ if (inComment) {
22986
+ if (ch === `
22987
+ `)
22988
+ inComment = false;
22989
+ else if (ch === "'" || ch === '"')
22990
+ return true;
22991
+ continue;
22992
+ }
22993
+ if (ch === "#" && isBare(lx, i))
22994
+ inComment = true;
22995
+ }
22996
+ return false;
22997
+ }
22998
+ function quotedNewlineBeforeCommentLine(lx) {
22999
+ if (!lx.text.includes(`
23000
+ `) || !lx.text.includes("#"))
23001
+ return false;
23002
+ for (let i = 0;i < lx.text.length; i++) {
23003
+ if (lx.text[i] !== `
23004
+ ` || lx.quoting[i] === 0 /* None */ || lx.role[i] !== 0 /* Text */)
23005
+ continue;
23006
+ let k = i + 1;
23007
+ while (k < lx.text.length && lx.text[k] !== `
23008
+ ` && isBlank(lx.text[k]))
23009
+ k++;
23010
+ if (lx.text[k] === "#")
23011
+ return true;
23012
+ }
23013
+ return false;
23014
+ }
23015
+ function lineBreakStartsCommand(bare) {
23016
+ let lastNonBlank = -1;
23017
+ for (let i = bare.length - 1;i >= 0; i--) {
23018
+ if (!isBlank(bare[i])) {
23019
+ lastNonBlank = i;
23020
+ break;
23021
+ }
23022
+ }
23023
+ for (let i = 0;i < lastNonBlank; i++) {
23024
+ if (bare[i] !== `
23025
+ ` && bare[i] !== "\r")
23026
+ continue;
23027
+ const continuation = i >= 2 && bare[i - 1] === "\\" && isBlank(bare[i - 2]);
23028
+ if (!continuation)
23029
+ return true;
23030
+ }
23031
+ return false;
23032
+ }
23033
+ function mentionsIfs(text) {
23034
+ if (text.includes("$IFS"))
23035
+ return true;
23036
+ return text.split("}").some((stretch) => {
23037
+ const open = stretch.indexOf("${");
23038
+ return open !== -1 && stretch.includes("IFS", open + 2);
23039
+ });
23040
+ }
23041
+ function readsProcessEnvironment(text) {
23042
+ return text.split(/[\n\r\u2028\u2029]/).some((line) => {
23043
+ const proc = line.indexOf("/proc/");
23044
+ return proc !== -1 && line.includes("/environ", proc + "/proc/".length);
23045
+ });
23046
+ }
23047
+ function hasLiveBacktick(lx) {
23048
+ for (let i = 0;i < lx.text.length; i++) {
23049
+ if (lx.text[i] === "`" && lx.quoting[i] !== 1 /* Single */ && lx.role[i] !== 3 /* Escaped */)
23050
+ return true;
23051
+ }
23052
+ return false;
23053
+ }
23054
+ function escapesBlankOrOperator(lx) {
23055
+ for (let i = 0;i < lx.text.length; i++) {
23056
+ if (lx.role[i] !== 2 /* Escape */ || lx.quoting[i] !== 0 /* None */)
23057
+ continue;
23058
+ const next = lx.text[i + 1];
23059
+ if (next === " " || next === "\t" || ";|&<>".includes(next))
23060
+ return true;
23061
+ }
23062
+ return false;
23063
+ }
23064
+ function hashInsideWord(bareWithMarks) {
23065
+ const glued = (s) => s.split(/\s+/).some((word) => {
23066
+ for (let at = word.indexOf("#", 1);at !== -1; at = word.indexOf("#", at + 1)) {
23067
+ if (!word.slice(0, at).endsWith("${"))
23068
+ return true;
23069
+ }
23070
+ return false;
23071
+ });
23072
+ return glued(bareWithMarks) || glued(joinLineContinuations(bareWithMarks));
23073
+ }
23074
+ function hasBraceExpansion(bare, text) {
23075
+ const events = [];
23076
+ let run = 0;
23077
+ for (let i = 0;i < bare.length; i++) {
23078
+ const ch = bare[i];
23079
+ const escaped = run % 2 === 1;
23080
+ run = ch === "\\" ? run + 1 : 0;
23081
+ if ((ch === "{" || ch === "}") && !escaped)
23082
+ events.push(ch === "{" ? "open" : "close");
23083
+ else if (ch === "," || ch === "." && bare[i + 1] === ".")
23084
+ events.push("separator");
23085
+ }
23086
+ const opens = events.filter((e) => e === "open").length;
23087
+ const closes = events.filter((e) => e === "close").length;
23088
+ if (opens > 0 && closes > opens)
23089
+ return true;
23090
+ const owner = [];
23091
+ const ownsSeparator = new Set;
23092
+ for (const [id, event] of events.entries()) {
23093
+ if (event === "open")
23094
+ owner.push(id);
23095
+ else if (event === "separator") {
23096
+ if (owner.length > 0)
23097
+ ownsSeparator.add(owner[owner.length - 1]);
23098
+ } else if (owner.length > 0 && ownsSeparator.has(owner.pop()))
23099
+ return true;
23100
+ }
23101
+ if (opens === 0)
23102
+ return false;
23103
+ for (let i = 0;i + 2 < text.length; i++) {
23104
+ if ((text[i + 1] === "{" || text[i + 1] === "}") && QUOTE_MARK.test(text[i]) && QUOTE_MARK.test(text[i + 2]))
23105
+ return true;
23106
+ }
23107
+ return false;
23108
+ }
23109
+ var ZSH_DANGEROUS_COMMANDS = new Set([
23110
+ "zmodload",
23111
+ "emulate",
23112
+ "sysopen",
23113
+ "sysread",
23114
+ "syswrite",
23115
+ "sysseek",
23116
+ "zpty",
23117
+ "ztcp",
23118
+ "zsocket",
23119
+ "mapfile",
23120
+ "zf_rm",
23121
+ "zf_mv",
23122
+ "zf_ln",
23123
+ "zf_chmod",
23124
+ "zf_chown",
23125
+ "zf_mkdir",
23126
+ "zf_rmdir",
23127
+ "zf_chgrp"
23128
+ ]);
23129
+ var ZSH_PRECOMMAND_MODIFIERS = new Set(["command", "builtin", "noglob", "nocorrect"]);
23130
+ var PROGRAM_AFTER_PREFIXES = new RegExp(`^\\s*(?:(?:[A-Za-z_]\\w*=\\S*|${[...ZSH_PRECOMMAND_MODIFIERS].join("|")})\\s+)*(\\S+)([\\s\\S]*)$`);
23131
+ function runsZshBuiltin(text) {
23132
+ const named = PROGRAM_AFTER_PREFIXES.exec(text);
23133
+ if (named === null)
23134
+ return false;
23135
+ const [, program, rest] = named;
23136
+ if (ZSH_DANGEROUS_COMMANDS.has(program))
23137
+ return true;
23138
+ return program === "fc" && rest.split(/\s+/).some((option) => option[0] === "-" && option.indexOf("e", 1) !== -1);
23139
+ }
23140
+ var occurrences = (s, ch) => {
23141
+ let n = 0;
23142
+ for (const c of s)
23143
+ if (c === ch)
23144
+ n++;
23145
+ return n;
23146
+ };
23147
+ var unescapedOccurrences = (s, ch) => {
23148
+ let n = 0;
23149
+ for (let i = 0;i < s.length; i++)
23150
+ if (s[i] === ch && s[i - 1] !== "\\")
23151
+ n++;
23152
+ return n;
23153
+ };
23154
+ function unbalancedWordNearSeparator(lx, bare) {
23155
+ if (!(bare.includes(";") || bare.includes("&&") || bare.includes("||")))
23156
+ return false;
23157
+ if (lx.unterminated)
23158
+ return true;
23159
+ let start = -1;
23160
+ for (let i = 0;i <= lx.text.length; i++) {
23161
+ const cut = i === lx.text.length || isBare(lx, i) && (isBlank(lx.text[i]) || ";&|<>()".includes(lx.text[i]));
23162
+ if (!cut) {
23163
+ if (start === -1)
23164
+ start = i;
23165
+ continue;
23166
+ }
23167
+ if (start === -1)
23168
+ continue;
23169
+ const word = shellWords(lx.text.slice(start, i), false, "broad")[0]?.word ?? "";
23170
+ start = -1;
23171
+ if (occurrences(word, "{") !== occurrences(word, "}") || occurrences(word, "(") !== occurrences(word, ")") || occurrences(word, "[") !== occurrences(word, "]") || unescapedOccurrences(word, '"') % 2 !== 0 || unescapedOccurrences(word, "'") % 2 !== 0) {
23172
+ return true;
23173
+ }
23174
+ }
23175
+ return false;
23176
+ }
23177
+ var SUBSTITUTION_PATTERNS = [
23178
+ /<\(/,
23179
+ />\(/,
23180
+ /=\(/,
23181
+ /(?:^|[\s;&|])=[a-zA-Z_]/,
23182
+ /\$\(/,
23183
+ /\$\{/,
23184
+ /\$\[/,
23185
+ /~\[/,
23186
+ /\(e:/,
23187
+ /\(\+/,
23188
+ /\}\s*always\s*\{/,
23189
+ /<#/
23190
+ ];
23191
+ var PRE_CHECKS = {
23192
+ unreadable: [
23193
+ (s) => s.text.includes("\r"),
23194
+ (s) => CONTROL_CHARACTERS.test(s.text) || UNICODE_WHITESPACE.test(s.text),
23195
+ (s) => startsMidway(s.text)
23196
+ ],
23197
+ hidesAWord: [
23198
+ (s) => flagSpelledWithQuotes(s.lx, s.base),
23199
+ (s) => hashInsideWord(s.bareWithMarks),
23200
+ (s) => commentHoldsQuoteMark(s.lx),
23201
+ (s) => quotedNewlineBeforeCommentLine(s.lx),
23202
+ (s) => singleQuoteEndsInBackslash(s.lx),
23203
+ (s) => unbalancedWordNearSeparator(s.lx, s.bare)
23204
+ ],
23205
+ smugglesAnOperator: [
23206
+ (s) => lineBreakStartsCommand(s.bare),
23207
+ (s) => s.cleaned.includes("<") || s.cleaned.includes(">"),
23208
+ (s) => variableBesideRedirectionOrPipe(s.cleaned),
23209
+ (s) => escapesBlankOrOperator(s.lx),
23210
+ (s) => separatorInsideQuoteMarks(s.outsideSingle)
23211
+ ],
23212
+ expandsAtRunTime: [
23213
+ (s) => hasLiveBacktick(s.lx),
23214
+ (s) => SUBSTITUTION_PATTERNS.some((pattern) => pattern.test(s.outsideSingle)),
23215
+ (s) => hasBraceExpansion(s.bare, s.text),
23216
+ (s) => mentionsIfs(s.text)
23217
+ ],
23218
+ reachesBeyondItsArguments: [
23219
+ (s) => jqReachesBeyondItsFilter(s.text, s.base),
23220
+ (s) => runsZshBuiltin(s.text),
23221
+ (s) => readsProcessEnvironment(s.text)
23222
+ ]
23223
+ };
23224
+ function passesSecurityValidators(command) {
23225
+ const program = /^[^ ]*/.exec(command)[0];
23226
+ const lx = lex(command);
23227
+ const views = viewsOf(lx, /^jq$/.test(program));
23228
+ const scanned = { ...views, text: command, lx, base: program, cleaned: withoutHarmlessRedirections(views.bare) };
23229
+ return Object.values(PRE_CHECKS).every((group) => group.every((fires) => !fires(scanned)));
23230
+ }
23231
+ var PARAMETER_START = /[A-Za-z_@*#?!$0-9-]/;
23232
+ function expandsWhenRun(text) {
23233
+ const lx = lex(text);
23234
+ for (let i = 0;i < text.length; i++) {
23235
+ if (lx.role[i] !== 0 /* Text */)
23236
+ continue;
23237
+ const ch = text[i];
23238
+ if (lx.quoting[i] === 0 /* None */ && (ch === "*" || ch === "?" || ch === "[" || ch === "]"))
23239
+ return true;
23240
+ const next = text[i + 1];
23241
+ if (ch === "$" && lx.quoting[i] !== 1 /* Single */ && next !== undefined && PARAMETER_START.test(next))
23242
+ return true;
23243
+ }
23244
+ return false;
23245
+ }
23246
+ function takesPatterns(simple) {
23247
+ const [first, second] = simple.words.map((w) => w.value);
23248
+ return ["grep", "rg", "fd", "fdfind", "find", "sed"].includes(first ?? "") || first === "git" && second === "grep";
23249
+ }
23250
+ function keptBackslashMisleads(simple) {
23251
+ const patterns = takesPatterns(simple);
23252
+ return simple.words.some((word) => {
23253
+ if (!word.value.startsWith("\\"))
23254
+ return false;
23255
+ const broad = shellWords(word.raw, false, "broad")[0]?.word ?? "";
23256
+ if (broad.startsWith("\\"))
23257
+ return false;
23258
+ const after = word.value[1];
23259
+ return !patterns || after !== undefined && "-+{}".includes(after);
23260
+ });
23261
+ }
23262
+ var GIT_CONFIGURING_OPTION = /\s(?:-c|--exec-path|--config-env)[\s=]/;
23263
+ function matchesReadOnlyPattern(simple) {
23264
+ if (simple.words.some((w) => w.value.startsWith("-") && w.raw.includes("\\")))
23265
+ return false;
23266
+ const matched = READ_ONLY_REGEXES.some((regex) => regex.test(simple.text)) || findOnlyLists(simple);
23267
+ return matched && !(simple.text.includes("git") && GIT_CONFIGURING_OPTION.test(simple.text));
23268
+ }
23269
+ function isSimpleCommandReadOnly(simple, platform) {
23270
+ const refused = containsVulnerableUncPath(simple.text, platform) || expandsWhenRun(simple.text) || keptBackslashMisleads(simple);
23271
+ return !refused && (isSafeViaFlagAllowlist(simple, platform) || matchesReadOnlyPattern(simple));
23272
+ }
23273
+ var takes = (kind, ...names) => Object.fromEntries(names.map((name) => [name, kind]));
23274
+ var GIT_REF_SELECTION = takes("none", "--all", "--branches", "--tags", "--remotes");
23275
+ var GIT_DATE_FILTERS = takes("string", "--since", "--after", "--until", "--before");
23276
+ var GIT_LOG_DISPLAY = { ...takes("none", "--oneline", "--graph", "--decorate", "--no-decorate", "--relative-date"), ...takes("string", "--date") };
23277
+ var GIT_COUNT = takes("number", "--max-count", "-n");
23278
+ var GIT_STAT = takes("none", "--stat", "--numstat", "--shortstat", "--name-only", "--name-status");
23279
+ var GIT_COLOR = takes("none", "--color", "--no-color");
23280
+ var GIT_PATCH = takes("none", "--patch", "-p", "--no-patch", "--no-ext-diff", "-s");
23281
+ var GIT_AUTHOR_FILTERS = takes("string", "--author", "--committer", "--grep");
23282
+ var REFLOG_WRITING_SUBCOMMANDS = new Set(["expire", "delete", "exists"]);
23283
+ var reflogWrites = (_text, args) => args.some((arg) => REFLOG_WRITING_SUBCOMMANDS.has(arg));
23284
+ function namesNewRef(args, flags, optionalValue) {
23285
+ let listing = false;
23286
+ let optionsOver = false;
23287
+ let lastOption = "";
23288
+ for (let i = 0;i < args.length; i++) {
23289
+ const token = args[i];
23290
+ if (token === "")
23291
+ continue;
23292
+ const word = optionsOver ? { form: "operand" } : readOptionWord(token);
23293
+ if (word.form === "end-of-options") {
23294
+ optionsOver = true;
23295
+ lastOption = "";
23296
+ } else if (word.form === "operand") {
23297
+ if (!listing && !optionalValue.has(lastOption))
23298
+ return true;
23299
+ } else {
23300
+ lastOption = word.form === "long" ? word.name : `-${word.letters}`;
23301
+ if (word.inline !== undefined)
23302
+ continue;
23303
+ if (lastOption === "--list" || word.form === "short" && word.letters.includes("l"))
23304
+ listing = true;
23305
+ const kind = kindOf(flags, lastOption);
23306
+ if (kind === "number" || kind === "string" || kind === "char")
23307
+ i++;
23308
+ }
23309
+ }
23310
+ return false;
23311
+ }
23312
+ var BRANCH_OPTIONAL_COMMIT = new Set(["--merged", "--no-merged"]);
23313
+ var GIT_TAG_FLAGS = {
23314
+ ...takes("none", "-l", "--list", "--column", "--no-column", "-i", "--ignore-case"),
23315
+ ...takes("number", "-n"),
23316
+ ...takes("string", "--contains", "--no-contains", "--merged", "--no-merged", "--sort", "--format", "--points-at")
23317
+ };
23318
+ var GIT_BRANCH_FLAGS = {
23319
+ ...takes("none", "-l", "--list", "-a", "--all", "-r", "--remotes", "-v", "-vv", "--verbose", "--color", "--no-color", "--column", "--no-column", "--no-abbrev", "--merged", "--no-merged", "--show-current", "-i", "--ignore-case"),
23320
+ ...takes("attached", "--abbrev"),
23321
+ ...takes("string", "--contains", "--no-contains", "--points-at", "--sort")
23322
+ };
23323
+ var BASE64_FLAGS = {
23324
+ ...takes("none", "-d", "-D", "--decode", "--ignore-garbage", "-h", "--help", "--version"),
23325
+ ...takes("number", "-b", "--break", "-w", "--wrap"),
23326
+ ...takes("string", "-i", "--input")
23327
+ };
23328
+ var DATE_FLAGS = {
23329
+ ...takes("string", "-d", "--date", "-r", "--reference"),
23330
+ ...takes("attached", "--iso-8601", "--rfc-3339"),
23331
+ ...takes("none", "-u", "--utc", "--universal", "-I", "-R", "--rfc-email", "--debug", "--help", "--version")
23332
+ };
23333
+ function dateSetsClock(_text, args) {
23334
+ return operandsOf(args, DATE_FLAGS).some((operand) => !operand.startsWith("+"));
23335
+ }
23336
+ var TPUT_DANGEROUS_CAPABILITIES = new Set([
23337
+ "init",
23338
+ "reset",
23339
+ "rs1",
23340
+ "rs2",
23341
+ "rs3",
23342
+ "is1",
23343
+ "is2",
23344
+ "is3",
23345
+ "iprog",
23346
+ "if",
23347
+ "rf",
23348
+ "clear",
23349
+ "flash",
23350
+ "mc0",
23351
+ "mc4",
23352
+ "mc5",
23353
+ "mc5i",
23354
+ "mc5p",
23355
+ "pfkey",
23356
+ "pfloc",
23357
+ "pfx",
23358
+ "pfxl",
23359
+ "smcup",
23360
+ "rmcup"
23361
+ ]);
23362
+ var TPUT_FLAGS = { ...takes("string", "-T"), ...takes("none", "-V", "-x") };
23363
+ function tputChangesTerminal(_text, args) {
23364
+ const { options, operands } = partitionArguments(args, TPUT_FLAGS);
23365
+ const readsStdin = options.some((option) => !option.startsWith("--") && option.split("=")[0].includes("S"));
23366
+ return readsStdin || operands.some((operand) => TPUT_DANGEROUS_CAPABILITIES.has(operand));
23367
+ }
23368
+ var REMOTE_NAME = /^[a-zA-Z0-9_-]+$/;
23369
+ function remoteShowReachesBeyondOneRemote(_text, args) {
23370
+ let names = 0;
23371
+ for (const arg of args) {
23372
+ if (arg === "-n")
23373
+ continue;
23374
+ if (!REMOTE_NAME.test(arg))
23375
+ return true;
23376
+ names++;
23377
+ }
23378
+ return names !== 1;
23379
+ }
23380
+ var REMOTE_LISTING_OPTIONS = new Set(["-v", "--verbose"]);
23381
+ var remoteDoesMoreThanList = (_text, args) => !args.every((arg) => REMOTE_LISTING_OPTIONS.has(arg));
23382
+ var psPrintsEnvironments = (_text, args) => args.some((arg) => /^[a-zA-Z]+$/.test(arg) && arg.includes("e"));
23383
+ var lsofWritesMountSupplement = (_text, args) => args.some((arg) => arg.slice(0, 2) === "+m");
23384
+ var pyrightWatches = (_text, args) => args.includes("--watch") || args.includes("-w");
23385
+ var FD_FLAGS = {
23386
+ ...takes("none", "-h", "--help", "-V", "--version", "-H", "--hidden", "-I", "--no-ignore", "--no-ignore-vcs", "--no-ignore-parent", "-s", "--case-sensitive", "-i", "--ignore-case", "-g", "--glob", "--regex", "-F", "--fixed-strings", "-a", "--absolute-path", "-L", "--follow", "-p", "--full-path", "-0", "--print0", "-1", "-q", "--quiet", "--show-errors", "--strip-cwd-prefix", "--one-file-system", "--prune", "--no-require-git"),
23387
+ ...takes("number", "-d", "--max-depth", "--min-depth", "--exact-depth", "-j", "--threads", "--max-results", "--batch-size"),
23388
+ ...takes("string", "-t", "--type", "-e", "--extension", "-S", "--size", "--changed-within", "--changed-before", "-o", "--owner", "-E", "--exclude", "--ignore-file", "-c", "--color", "--max-buffer-time", "--search-path", "--base-directory", "--path-separator", "--hyperlink", "--and", "--format")
23389
+ };
23390
+ var CHECKSUM_FLAGS = takes("none", "-b", "--binary", "-t", "--text", "-c", "--check", "--ignore-missing", "--quiet", "--status", "--strict", "-w", "--warn", "--tag", "-z", "--zero", "--help", "--version");
23391
+ var COMMAND_SPECS = [
23392
+ [
23393
+ "xargs",
23394
+ {
23395
+ flags: { ...takes("{}", "-I"), ...takes("number", "-n", "-P", "-L", "-s"), ...takes("EOF", "-E"), ...takes("none", "-0", "-t", "-r", "-x"), ...takes("char", "-d") }
23396
+ }
23397
+ ],
23398
+ [
23399
+ "git diff",
23400
+ {
23401
+ flags: {
23402
+ ...GIT_STAT,
23403
+ ...GIT_COLOR,
23404
+ ...takes("none", "--dirstat", "--summary", "--patch-with-stat", "--word-diff", "--color-words", "--no-renames", "--no-ext-diff", "--check", "--full-index", "--binary", "--break-rewrites", "--find-renames", "--find-copies", "--find-copies-harder", "--irreversible-delete", "--histogram", "--patience", "--minimal", "--ignore-space-at-eol", "--ignore-space-change", "--ignore-all-space", "--ignore-blank-lines", "--function-context", "--exit-code", "--quiet", "--cached", "--staged", "--pickaxe-regex", "--pickaxe-all", "--no-index", "-p", "-u", "-s", "-M", "-C", "-B", "-D", "-l", "-R"),
23405
+ ...takes("string", "--word-diff-regex", "--ws-error-highlight", "--diff-algorithm", "--relative", "--diff-filter", "-S", "-G", "-O"),
23406
+ ...takes("number", "--abbrev", "--inter-hunk-context")
23407
+ }
23408
+ }
23409
+ ],
23410
+ [
23411
+ "git log",
23412
+ {
23413
+ flags: {
23414
+ ...GIT_LOG_DISPLAY,
23415
+ ...GIT_REF_SELECTION,
23416
+ ...GIT_DATE_FILTERS,
23417
+ ...GIT_COUNT,
23418
+ ...GIT_STAT,
23419
+ ...GIT_COLOR,
23420
+ ...GIT_PATCH,
23421
+ ...GIT_AUTHOR_FILTERS,
23422
+ ...takes("none", "--abbrev-commit", "--full-history", "--dense", "--sparse", "--simplify-merges", "--ancestry-path", "--source", "--first-parent", "--merges", "--no-merges", "--reverse", "--walk-reflogs", "--no-min-parents", "--no-max-parents", "--follow", "--no-walk", "--left-right", "--cherry-mark", "--cherry-pick", "--boundary", "--topo-order", "--date-order", "--author-date-order", "--pickaxe-regex", "--pickaxe-all"),
23423
+ ...takes("number", "--skip", "--max-age", "--min-age"),
23424
+ ...takes("string", "--pretty", "--format", "--diff-filter", "-S", "-G")
23425
+ }
23426
+ }
23427
+ ],
23428
+ [
23429
+ "git show",
23430
+ {
23431
+ flags: {
23432
+ ...GIT_LOG_DISPLAY,
23433
+ ...GIT_STAT,
23434
+ ...GIT_COLOR,
23435
+ ...GIT_PATCH,
23436
+ ...takes("none", "--abbrev-commit", "--word-diff", "--color-words", "--first-parent", "--raw", "-m", "--quiet"),
23437
+ ...takes("string", "--word-diff-regex", "--pretty", "--format", "--diff-filter")
23438
+ }
23439
+ }
23440
+ ],
23441
+ [
23442
+ "git shortlog",
23443
+ {
23444
+ flags: {
23445
+ ...GIT_REF_SELECTION,
23446
+ ...GIT_DATE_FILTERS,
23447
+ ...takes("none", "-s", "--summary", "-n", "--numbered", "-e", "--email", "-c", "--committer", "--no-merges"),
23448
+ ...takes("string", "--group", "--format", "--author")
23449
+ }
23450
+ }
23451
+ ],
23452
+ ["git reflog", { respectsDoubleDash: false, flags: { ...GIT_LOG_DISPLAY, ...GIT_REF_SELECTION, ...GIT_DATE_FILTERS, ...GIT_COUNT, ...GIT_AUTHOR_FILTERS }, dangerous: reflogWrites }],
23453
+ ["git stash list", { respectsDoubleDash: false, flags: { ...GIT_LOG_DISPLAY, ...GIT_REF_SELECTION, ...GIT_COUNT } }],
23454
+ [
23455
+ "git ls-remote",
23456
+ {
23457
+ respectsDoubleDash: false,
23458
+ flags: {
23459
+ ...takes("none", "--branches", "-b", "--tags", "-t", "--heads", "-h", "--refs", "--quiet", "-q", "--exit-code", "--get-url", "--symref"),
23460
+ ...takes("string", "--sort")
23461
+ }
23462
+ }
23463
+ ],
23464
+ [
23465
+ "git status",
23466
+ {
23467
+ flags: {
23468
+ ...takes("none", "--short", "-s", "--branch", "-b", "--porcelain", "--long", "--verbose", "-v", "--ignored", "--column", "--no-column", "--ahead-behind", "--no-ahead-behind", "--renames", "--no-renames"),
23469
+ ...takes("string", "--untracked-files", "-u", "--ignore-submodules", "--find-renames", "-M")
23470
+ }
23471
+ }
23472
+ ],
23473
+ [
23474
+ "git blame",
23475
+ {
23476
+ flags: {
23477
+ ...GIT_COLOR,
23478
+ ...takes("string", "-L", "--date", "--ignore-rev", "--ignore-revs-file"),
23479
+ ...takes("none", "--porcelain", "-p", "--line-porcelain", "--incremental", "--root", "--show-stats", "--show-name", "--show-number", "-n", "--show-email", "-e", "-f", "-w", "-M", "-C", "--score-debug", "-s", "-l", "-t"),
23480
+ ...takes("number", "--abbrev")
23481
+ }
23482
+ }
23483
+ ],
23484
+ [
23485
+ "git ls-files",
23486
+ {
23487
+ flags: {
23488
+ ...takes("none", "--cached", "-c", "--deleted", "-d", "--modified", "-m", "--others", "-o", "--ignored", "-i", "--stage", "-s", "--killed", "-k", "--unmerged", "-u", "--directory", "--no-empty-directory", "--eol", "--full-name", "--debug", "-z", "-t", "-v", "-f", "--exclude-standard", "--error-unmatch", "--recurse-submodules"),
23489
+ ...takes("number", "--abbrev"),
23490
+ ...takes("string", "--exclude", "-x", "--exclude-from", "-X", "--exclude-per-directory")
23491
+ }
23492
+ }
23493
+ ],
23494
+ [
23495
+ "git config --get",
23496
+ {
23497
+ flags: {
23498
+ ...takes("none", "--local", "--global", "--system", "--worktree", "--bool", "--int", "--bool-or-int", "--path", "--expiry-date", "-z", "--null", "--name-only", "--show-origin", "--show-scope"),
23499
+ ...takes("string", "--default", "--type")
23500
+ }
23501
+ }
23502
+ ],
23503
+ [
23504
+ "git remote show",
23505
+ {
23506
+ flags: takes("none", "-n"),
23507
+ dangerous: remoteShowReachesBeyondOneRemote
23508
+ }
23509
+ ],
23510
+ ["git remote", { flags: takes("none", "-v", "--verbose"), dangerous: remoteDoesMoreThanList }],
23511
+ ["git merge-base", { flags: takes("none", "--is-ancestor", "--fork-point", "--octopus", "--independent", "--all") }],
23512
+ [
23513
+ "git rev-parse",
23514
+ {
23515
+ flags: {
23516
+ ...takes("none", "--verify", "--abbrev-ref", "--symbolic", "--symbolic-full-name", "--show-toplevel", "--show-cdup", "--show-prefix", "--git-dir", "--git-common-dir", "--absolute-git-dir", "--show-superproject-working-tree", "--is-inside-work-tree", "--is-inside-git-dir", "--is-bare-repository", "--is-shallow-repository", "--is-shallow-update", "--path-prefix"),
23517
+ ...takes("string", "--short")
23518
+ }
23519
+ }
23520
+ ],
23521
+ [
23522
+ "git rev-list",
23523
+ {
23524
+ flags: {
23525
+ ...GIT_REF_SELECTION,
23526
+ ...GIT_DATE_FILTERS,
23527
+ ...GIT_COUNT,
23528
+ ...GIT_AUTHOR_FILTERS,
23529
+ ...takes("none", "--count", "--reverse", "--first-parent", "--ancestry-path", "--merges", "--no-merges", "--no-min-parents", "--no-max-parents", "--walk-reflogs", "--oneline", "--abbrev-commit", "--full-history", "--dense", "--sparse", "--source", "--graph"),
23530
+ ...takes("number", "--min-parents", "--max-parents", "--skip", "--max-age", "--min-age", "--abbrev"),
23531
+ ...takes("string", "--pretty", "--format")
23532
+ }
23533
+ }
23534
+ ],
23535
+ [
23536
+ "git describe",
23537
+ {
23538
+ flags: {
23539
+ ...takes("none", "--tags", "--long", "--always", "--contains", "--first-match", "--exact-match", "--dirty", "--broken"),
23540
+ ...takes("string", "--match", "--exclude"),
23541
+ ...takes("number", "--abbrev", "--candidates")
23542
+ }
23543
+ }
23544
+ ],
23545
+ ["git cat-file", { flags: takes("none", "-t", "-s", "-p", "-e", "--batch-check", "--allow-undetermined-type") }],
23546
+ [
23547
+ "git for-each-ref",
23548
+ {
23549
+ flags: {
23550
+ ...takes("string", "--format", "--sort", "--contains", "--no-contains", "--merged", "--no-merged", "--points-at"),
23551
+ ...takes("number", "--count")
23552
+ }
23553
+ }
23554
+ ],
23555
+ [
23556
+ "git grep",
23557
+ {
23558
+ flags: {
23559
+ ...takes("string", "-e"),
23560
+ ...takes("none", "-E", "--extended-regexp", "-G", "--basic-regexp", "-F", "--fixed-strings", "-P", "--perl-regexp", "-i", "--ignore-case", "-v", "--invert-match", "-w", "--word-regexp", "-n", "--line-number", "-c", "--count", "-l", "--files-with-matches", "-L", "--files-without-match", "-h", "-H", "--heading", "--break", "--full-name", "--color", "--no-color", "-o", "--only-matching", "--and", "--or", "--not", "--untracked", "--no-index", "--recurse-submodules", "--cached", "-q", "--quiet"),
23561
+ ...takes("number", "-A", "--after-context", "-B", "--before-context", "-C", "--context", "--max-depth", "--threads")
23562
+ }
23563
+ }
23564
+ ],
23565
+ [
23566
+ "git stash show",
23567
+ {
23568
+ respectsDoubleDash: false,
23569
+ flags: {
23570
+ ...GIT_STAT,
23571
+ ...GIT_COLOR,
23572
+ ...GIT_PATCH,
23573
+ ...takes("none", "--word-diff"),
23574
+ ...takes("string", "--word-diff-regex", "--diff-filter"),
23575
+ ...takes("number", "--abbrev")
23576
+ }
23577
+ }
23578
+ ],
23579
+ ["git worktree list", { flags: { ...takes("none", "--porcelain", "-v", "--verbose"), ...takes("string", "--expire") } }],
23580
+ ["git tag", { flags: GIT_TAG_FLAGS, dangerous: (_text, args) => namesNewRef(args, GIT_TAG_FLAGS, new Set) }],
23581
+ ["git branch", { flags: GIT_BRANCH_FLAGS, dangerous: (_text, args) => namesNewRef(args, GIT_BRANCH_FLAGS, BRANCH_OPTIONAL_COMMIT) }],
23582
+ [
23583
+ "file",
23584
+ {
23585
+ flags: {
23586
+ ...takes("none", "--brief", "-b", "--mime", "-i", "--mime-type", "--mime-encoding", "--apple", "--check-encoding", "-c", "--print0", "-0", "--help", "--version", "-v", "--no-dereference", "-h", "--dereference", "-L", "--keep-going", "-k", "--list", "-l", "--no-buffer", "-n", "--preserve-date", "-p", "--raw", "-r", "-s", "--special-files", "--uncompress", "-z"),
23587
+ ...takes("string", "--exclude", "--exclude-quiet", "-f", "-F", "--separator", "--magic-file", "-m")
23588
+ }
23589
+ }
23590
+ ],
23591
+ [
23592
+ "sed",
23593
+ {
23594
+ flags: {
23595
+ ...takes("string", "--expression", "-e"),
23596
+ ...takes("none", "--quiet", "--silent", "-n", "--regexp-extended", "-r", "--posix", "-E", "--zero-terminated", "-z", "--separate", "-s", "--unbuffered", "-u", "--debug", "--help", "--version"),
23597
+ ...takes("number", "--line-length", "-l")
23598
+ },
23599
+ dangerous: (text, args) => !sedIsAllowed(text, args)
23600
+ }
23601
+ ],
23602
+ [
23603
+ "sort",
23604
+ {
23605
+ flags: {
23606
+ ...takes("none", "--ignore-leading-blanks", "-b", "--dictionary-order", "-d", "--ignore-case", "-f", "--general-numeric-sort", "-g", "--human-numeric-sort", "-h", "--ignore-nonprinting", "-i", "--month-sort", "-M", "--numeric-sort", "-n", "--random-sort", "-R", "--reverse", "-r", "--stable", "-s", "--unique", "-u", "--version-sort", "-V", "--zero-terminated", "-z", "--check", "-c", "--check-char-order", "-C", "--merge", "-m", "--help", "--version"),
23607
+ ...takes("string", "--sort", "--key", "-k", "--field-separator", "-t", "--buffer-size", "-S"),
23608
+ ...takes("number", "--parallel", "--batch-size")
23609
+ }
23610
+ }
23611
+ ],
23612
+ ["man", { flags: { ...takes("none", "-a", "--all", "-d", "-f", "--whatis", "-h", "-k", "--apropos", "-w"), ...takes("string", "-l", "-S", "-s") } }],
23613
+ ["help", { flags: takes("none", "-d", "-m", "-s") }],
23614
+ ["netstat", { flags: { ...takes("none", "-a", "-L", "-l", "-n", "-g", "-i", "-s", "-r", "-m", "-v"), ...takes("string", "-f", "-I") } }],
23615
+ [
23616
+ "ps",
23617
+ {
23618
+ flags: {
23619
+ ...takes("none", "-e", "-A", "-a", "-d", "-N", "--deselect", "-f", "-F", "-l", "-j", "-y", "-w", "-ww", "-c", "-H", "--forest", "--headers", "--no-headers", "-L", "-T", "-m", "--help", "--info", "-V", "--version"),
23620
+ ...takes("number", "--width"),
23621
+ ...takes("string", "-n", "--sort", "-C", "-G", "-g", "-p", "--pid", "-q", "--quick-pid", "-s", "--sid", "-t", "--tty", "-U", "-u", "--user")
23622
+ },
23623
+ dangerous: psPrintsEnvironments
23624
+ }
23625
+ ],
23626
+ [
23627
+ "base64",
23628
+ {
23629
+ respectsDoubleDash: false,
23630
+ flags: BASE64_FLAGS,
23631
+ dangerous: (_text, args) => operandsOf(args, BASE64_FLAGS).length > 1
23632
+ }
23633
+ ],
23634
+ [
23635
+ "grep",
23636
+ {
23637
+ flags: {
23638
+ ...takes("string", "-e", "--regexp", "-f", "--file", "--color", "--colour", "--label", "--group-separator", "--binary-files", "-D", "--devices", "-d", "--directories", "--exclude", "--exclude-from", "--exclude-dir", "--include"),
23639
+ ...takes("none", "-F", "--fixed-strings", "-G", "--basic-regexp", "-E", "--extended-regexp", "-P", "--perl-regexp", "-i", "--ignore-case", "--no-ignore-case", "-v", "--invert-match", "-w", "--word-regexp", "-x", "--line-regexp", "-c", "--count", "-L", "--files-without-match", "-l", "--files-with-matches", "-o", "--only-matching", "-q", "--quiet", "--silent", "-s", "--no-messages", "-b", "--byte-offset", "-H", "--with-filename", "-h", "--no-filename", "-n", "--line-number", "-T", "--initial-tab", "-u", "--unix-byte-offsets", "-Z", "--null", "-z", "--null-data", "--no-group-separator", "-a", "--text", "-r", "--recursive", "-R", "--dereference-recursive", "--line-buffered", "-U", "--binary", "--help", "-V", "--version"),
23640
+ ...takes("number", "-m", "--max-count", "-A", "--after-context", "-B", "--before-context", "-C", "--context")
23641
+ }
23642
+ }
23643
+ ],
23644
+ [
23645
+ "rg",
23646
+ {
23647
+ flags: {
23648
+ ...takes("string", "-e", "--regexp", "-f", "-g", "--glob", "-t", "--type", "-T", "--type-not", "--color"),
23649
+ ...takes("none", "-i", "--ignore-case", "-S", "--smart-case", "-F", "--fixed-strings", "-w", "--word-regexp", "-v", "--invert-match", "-c", "--count", "-l", "--files-with-matches", "--files-without-match", "-n", "--line-number", "-o", "--only-matching", "-H", "-h", "--heading", "--no-heading", "-q", "--quiet", "--column", "--type-list", "--hidden", "--no-ignore", "-u", "-a", "--text", "-z", "-L", "--follow", "--json", "--stats", "--help", "--version", "--debug", "--"),
23650
+ ...takes("number", "-A", "--after-context", "-B", "--before-context", "-C", "--context", "-m", "--max-count", "-d", "--max-depth")
23651
+ }
23652
+ }
23653
+ ],
23654
+ ["sha256sum", { flags: CHECKSUM_FLAGS }],
23655
+ ["sha1sum", { flags: CHECKSUM_FLAGS }],
23656
+ ["md5sum", { flags: CHECKSUM_FLAGS }],
23657
+ [
23658
+ "tree",
23659
+ {
23660
+ respectsDoubleDash: false,
23661
+ flags: {
23662
+ ...takes("none", "-a", "-d", "-l", "-f", "-x", "--gitignore", "--ignore-case", "--matchdirs", "--metafirst", "--prune", "--info", "--noreport", "-q", "-N", "-Q", "-p", "-u", "-g", "-s", "-h", "--si", "--du", "-D", "-F", "--inodes", "--device", "-v", "-t", "-c", "-U", "-r", "--dirsfirst", "--filesfirst", "-i", "-A", "-S", "-n", "-C", "-X", "-J", "--nolinks", "--hyperlink", "--fromfile", "--fromtabfile", "--fflinks", "--help", "--version"),
23663
+ ...takes("number", "-L", "--filelimit"),
23664
+ ...takes("string", "-P", "-I", "--gitfile", "--infofile", "--charset", "--timefmt", "--sort", "-H", "--hintro", "--houtro", "-T", "--scheme", "--authority")
23665
+ }
23666
+ }
23667
+ ],
23668
+ [
23669
+ "date",
23670
+ {
23671
+ flags: DATE_FLAGS,
23672
+ dangerous: dateSetsClock
23673
+ }
23674
+ ],
23675
+ [
23676
+ "hostname",
23677
+ {
23678
+ flags: takes("none", "-f", "--fqdn", "--long", "-s", "--short", "-i", "--ip-address", "-I", "--all-ip-addresses", "-a", "--alias", "-d", "--domain", "-A", "--all-fqdns", "-v", "--verbose", "-h", "--help", "-V", "--version"),
23679
+ pattern: /^hostname(?:\s+(?:-[a-zA-Z]|--[a-zA-Z-]+))*\s*$/
23680
+ }
23681
+ ],
23682
+ [
23683
+ "info",
23684
+ {
23685
+ flags: {
23686
+ ...takes("string", "-f", "--file", "-d", "--directory", "-n", "--node", "-k", "--apropos"),
23687
+ ...takes("none", "-a", "--all", "-w", "--where", "--location", "--show-options", "--vi-keys", "--subnodes", "-h", "--help", "--usage", "--version")
23688
+ }
23689
+ }
23690
+ ],
23691
+ [
23692
+ "lsof",
23693
+ {
23694
+ respectsDoubleDash: false,
23695
+ flags: {
23696
+ ...takes("none", "-?", "-h", "-v", "-a", "-b", "-C", "-l", "-n", "-N", "-O", "-P", "-Q", "-R", "-t", "-U", "-V", "-X", "-H", "-E", "-F", "-g", "-i", "-K", "-L", "-o", "-r", "-s", "-S", "-T", "-x"),
23697
+ ...takes("string", "-A", "-c", "-d", "-e", "-k", "-p", "-u")
23698
+ },
23699
+ dangerous: lsofWritesMountSupplement
23700
+ }
23701
+ ],
23702
+ [
23703
+ "pgrep",
23704
+ {
23705
+ flags: {
23706
+ ...takes("string", "-d", "--delimiter", "-g", "--pgroup", "-G", "--group", "-O", "--older", "-P", "--parent", "-s", "--session", "-t", "--terminal", "-u", "--euid", "-U", "--uid", "-F", "--pidfile", "-r", "--runstates", "--ns", "--nslist"),
23707
+ ...takes("none", "-l", "--list-name", "-a", "--list-full", "-v", "--inverse", "-w", "--lightweight", "-c", "--count", "-f", "--full", "-i", "--ignore-case", "-n", "--newest", "-o", "--oldest", "-x", "--exact", "-L", "--logpidfile", "--help", "-V", "--version")
23708
+ }
23709
+ }
23710
+ ],
23711
+ ["tput", { flags: TPUT_FLAGS, dangerous: tputChangesTerminal }],
23712
+ [
23713
+ "ss",
23714
+ {
23715
+ flags: {
23716
+ ...takes("none", "-h", "--help", "-V", "--version", "-n", "--numeric", "-r", "--resolve", "-a", "--all", "-l", "--listening", "-o", "--options", "-e", "--extended", "-m", "--memory", "-p", "--processes", "-i", "--info", "-s", "--summary", "-4", "--ipv4", "-6", "--ipv6", "-0", "--packet", "-t", "--tcp", "-M", "--mptcp", "-S", "--sctp", "-u", "--udp", "-d", "--dccp", "-w", "--raw", "-x", "--unix", "--tipc", "--vsock", "-Z", "--context", "-z", "--contexts", "-b", "--bpf", "-E", "--events", "-H", "--no-header", "-O", "--oneline", "--tipcinfo", "--tos", "--cgroup", "--inet-sockopt"),
23717
+ ...takes("string", "-f", "--family", "-A", "--query", "--socket")
23718
+ }
23719
+ }
23720
+ ],
23721
+ ["fd", { flags: FD_FLAGS }],
23722
+ ["fdfind", { flags: FD_FLAGS }],
23723
+ [
23724
+ "pyright",
23725
+ {
23726
+ respectsDoubleDash: false,
23727
+ flags: {
23728
+ ...takes("none", "--outputjson", "--stats", "--verbose", "--version", "--dependencies", "--warnings"),
23729
+ ...takes("string", "--project", "-p", "--pythonversion", "--pythonplatform", "--typeshedpath", "--venvpath", "--level")
23730
+ },
23731
+ dangerous: pyrightWatches
23732
+ }
23733
+ ],
23734
+ ["docker logs", { flags: { ...takes("none", "--follow", "-f", "--timestamps", "-t", "--details"), ...takes("string", "--tail", "-n", "--since", "--until") } }],
23735
+ ["docker inspect", { flags: { ...takes("string", "--format", "-f", "--type"), ...takes("none", "--size", "-s") } }]
23736
+ ];
23737
+ var XARGS_SAFE_TARGETS = new Set(["echo", "printf", "wc", "grep", "head", "tail"]);
23738
+ function readOptionWord(token) {
23739
+ if (token === "--")
23740
+ return { form: "end-of-options" };
23741
+ if (token.length < 2 || !token.startsWith("-"))
23742
+ return { form: "operand" };
23743
+ const eq = token.indexOf("=");
23744
+ const head = eq === -1 ? token : token.slice(0, eq);
23745
+ const inline = eq === -1 ? undefined : token.slice(eq + 1);
23746
+ if (head.startsWith("--"))
23747
+ return inline === undefined ? { form: "long", name: head } : { form: "long", name: head, inline };
23748
+ const letters = head.slice(1);
23749
+ return inline === undefined ? { form: "short", letters } : { form: "short", letters, inline };
23750
+ }
23751
+ var looksLikeOption = (token) => token.length > 1 && token.startsWith("-");
23752
+ function kindOf(flags, name) {
23753
+ return Object.hasOwn(flags, name) ? flags[name] : undefined;
23754
+ }
23755
+ function valueFits(kind, value, flag, command) {
23756
+ switch (kind) {
23757
+ case "number":
23758
+ return /^\d+$/.test(value);
23759
+ case "string":
23760
+ case "attached":
23761
+ return !value.startsWith("-") || command === "git" && flag === "--sort" && /^-[a-zA-Z]/.test(value);
23762
+ case "char":
23763
+ return value.length === 1;
23764
+ case "{}":
23765
+ case "EOF":
23766
+ return value === kind;
23767
+ default:
23768
+ return false;
23769
+ }
23770
+ }
23771
+ function acceptOption(tokens, i, flags, command) {
23772
+ const word = readOptionWord(tokens[i]);
23773
+ if (word.form !== "long" && word.form !== "short")
23774
+ return null;
23775
+ const name = word.form === "long" ? word.name : `-${word.letters}`;
23776
+ const kind = kindOf(flags, name);
23777
+ if (kind !== undefined) {
23778
+ if (kind === "none")
23779
+ return word.inline === undefined ? 1 : null;
23780
+ if (kind === "attached")
23781
+ return word.inline !== undefined && valueFits(kind, word.inline, name, command) ? 1 : null;
23782
+ if (word.inline !== undefined)
23783
+ return valueFits(kind, word.inline, name, command) ? 1 : null;
23784
+ const value = tokens[i + 1];
23785
+ if (value === undefined || looksLikeOption(value))
23786
+ return null;
23787
+ return valueFits(kind, value, name, command) ? 2 : null;
23788
+ }
23789
+ if (word.form === "long" || word.inline !== undefined)
23790
+ return null;
23791
+ if (command === "git" && /^\d+$/.test(word.letters))
23792
+ return 1;
23793
+ if (command === "grep" || command === "rg") {
23794
+ const leading = kindOf(flags, `-${word.letters[0]}`);
23795
+ if ((leading === "number" || leading === "string") && /^\d+$/.test(word.letters.slice(1)))
23796
+ return 1;
23797
+ }
23798
+ for (const letter of word.letters) {
23799
+ if (kindOf(flags, `-${letter}`) !== "none")
23800
+ return null;
23801
+ }
23802
+ return 1;
23803
+ }
23804
+ function optionsAllowed(tokens, start, spec, command) {
23805
+ let i = start;
23806
+ while (i < tokens.length) {
23807
+ const word = readOptionWord(tokens[i]);
23808
+ if (word.form === "operand") {
23809
+ i++;
23810
+ continue;
23811
+ }
23812
+ if (word.form === "end-of-options") {
23813
+ if (spec.respectsDoubleDash !== false)
23814
+ return true;
23815
+ i++;
23816
+ continue;
23817
+ }
23818
+ const used = acceptOption(tokens, i, spec.flags, command);
23819
+ if (used === null)
23820
+ return false;
23821
+ i += used;
23822
+ }
23823
+ return true;
23824
+ }
23825
+ function xargsAllowed(tokens, start, spec) {
23826
+ let i = start;
23827
+ while (i < tokens.length) {
23828
+ const word = readOptionWord(tokens[i]);
23829
+ if (word.form === "end-of-options") {
23830
+ const target = tokens[i + 1];
23831
+ return target !== undefined && XARGS_SAFE_TARGETS.has(target);
23832
+ }
23833
+ if (word.form === "operand")
23834
+ return XARGS_SAFE_TARGETS.has(tokens[i]);
23835
+ const used = acceptOption(tokens, i, spec.flags, "xargs");
23836
+ if (used === null)
23837
+ return false;
23838
+ i += used;
23839
+ }
23840
+ return true;
23841
+ }
23842
+ function operandsOf(args, flags) {
23843
+ return partitionArguments(args, flags).operands;
23844
+ }
23845
+ function partitionArguments(args, flags) {
23846
+ const options = [];
23847
+ const operands = [];
23848
+ let optionsOver = false;
23849
+ for (let i = 0;i < args.length; i++) {
23850
+ const token = args[i];
23851
+ const word = optionsOver ? { form: "operand" } : readOptionWord(token);
23852
+ if (word.form === "end-of-options")
23853
+ optionsOver = true;
23854
+ else if (word.form === "operand")
23855
+ operands.push(token);
23856
+ else {
23857
+ options.push(token);
23858
+ const kind = word.inline === undefined ? kindOf(flags, word.form === "long" ? word.name : `-${word.letters}`) : undefined;
23859
+ if (kind === "number" || kind === "string" || kind === "char" || kind === "{}" || kind === "EOF")
23860
+ i++;
23861
+ }
23862
+ }
23863
+ return { options, operands };
23864
+ }
23865
+ var SPEC_LEADING_WORDS = COMMAND_SPECS.map(([name, spec]) => ({ leading: name.split(" "), spec }));
23866
+ function commandSpecFor(tokens, platform) {
23867
+ const entry = SPEC_LEADING_WORDS.find(({ leading }) => leading.every((word, k) => tokens[k] === word) && !(leading[0] === "xargs" && platform === "win32"));
23868
+ return entry && { spec: entry.spec, words: entry.leading.length };
23869
+ }
23870
+ var namesRemoteLocation = (token) => token.length > 0 && !token.startsWith("-") && (token.includes("://") || token.includes("@") || token.includes(":") || token.includes("$"));
23871
+ var expandsAtRunTime = (token) => token.includes("$") || token.includes("{") && (token.includes(",") || token.includes(".."));
23872
+ function isSafeViaFlagAllowlist(sub, platform) {
23873
+ const tokens = sub.words.map((w) => w.value);
23874
+ const match = commandSpecFor(tokens, platform);
23875
+ if (match === undefined)
23876
+ return false;
23877
+ const { spec, words } = match;
23878
+ const args = tokens.slice(words);
23879
+ if (args.some(expandsAtRunTime))
23880
+ return false;
23881
+ if (tokens[0] === "git" && tokens[1] === "ls-remote" && args.some(namesRemoteLocation))
23882
+ return false;
23883
+ const optionsOk = tokens[0] === "xargs" ? xargsAllowed(tokens, words, spec) : optionsAllowed(tokens, words, spec, tokens[0]);
23884
+ if (!optionsOk)
23885
+ return false;
23886
+ if (spec.pattern !== undefined) {
23887
+ if (!spec.pattern.test(sub.text))
23888
+ return false;
23889
+ } else if (sub.text.includes("`") || (tokens[0] === "rg" || tokens[0] === "grep") && /[\n\r]/.test(sub.text)) {
23890
+ return false;
23891
+ }
23892
+ return !(spec.dangerous?.(sub.text, args) ?? false);
23893
+ }
23894
+ var SED_QUIET = ["-n", "--quiet", "--silent"];
23895
+ var SED_PRINT_OPTIONS = { long: ["--quiet", "--silent", "--regexp-extended", "--zero-terminated", "--posix"], letters: "nErz" };
23896
+ var SED_SUBSTITUTE_OPTIONS = { long: ["--regexp-extended", "--posix"], letters: "Er" };
23897
+ function sedOptionsWithin(options, allowed) {
23898
+ return options.every((option) => option.startsWith("--") ? allowed.long.includes(option) : option.length > 1 && [...option.slice(1)].every((letter) => allowed.letters.includes(letter)));
23899
+ }
23900
+ var SED_PRINT_SCRIPT = /^(?:\d+(?:,\d+)?)?p\s*(?:;\s*(?:\d+(?:,\d+)?)?p\s*)*$/;
23901
+ function sedPrintScript(script) {
23902
+ const trimmed = script.trim();
23903
+ return !trimmed.includes(`
23904
+ `) && SED_PRINT_SCRIPT.test(trimmed);
23905
+ }
23906
+ function sedSubstituteScript(script) {
23907
+ const s = script.trim();
23908
+ if (!s.startsWith("s/") || occurrences(s, "/") !== 3)
23909
+ return false;
23910
+ const delimiters = [];
23911
+ for (let i = 0;i < s.length; i++) {
23912
+ if (s[i] === "\\") {
23913
+ if (s[i + 1] === "/")
23914
+ return false;
23915
+ i++;
23916
+ continue;
23917
+ }
23918
+ if (s[i] === "/")
23919
+ delimiters.push(i);
23920
+ }
23921
+ if (delimiters.length !== 3)
23922
+ return false;
23923
+ if (!/^[gpimIM]*[1-9]?[gpimIM]*$/.test(s.slice(delimiters[2] + 1)))
23924
+ return false;
23925
+ for (let i = 0;i < s.length; i++) {
23926
+ const ch = s[i];
23927
+ const code = ch.charCodeAt(0);
23928
+ if (code === 0 || code > 127)
23929
+ return false;
23930
+ if (`
23931
+ {}#;!~`.includes(ch))
23932
+ return false;
23933
+ if (ch === "\\" && s[i - 1] === "s")
23934
+ return false;
23935
+ if (ch === "\\" && "|#%@".includes(s[i + 1] ?? ""))
23936
+ return false;
23937
+ if (isBlank(ch) && "wWeE".includes(s[i + 1] ?? " "))
23938
+ return false;
23939
+ if (ch === ",") {
23940
+ let k = i + 1;
23941
+ while (isBlank(s[k]))
23942
+ k++;
23943
+ if (s[k] === "+" || s[k] === "-")
23944
+ return false;
23945
+ }
23946
+ }
23947
+ const transliterates = /y[^\\\n]/.test(s);
23948
+ return !(transliterates && /[wWeE]/.test(s));
23949
+ }
23950
+ function sedIsAllowed(text, args) {
23951
+ if (!text.startsWith("sed "))
23952
+ return false;
23953
+ const argumentText = text.slice(4);
23954
+ if (/-e[wWe]|-w[eE]/.test(argumentText))
23955
+ return false;
23956
+ const options = args.filter((a) => a.startsWith("-") && a !== "--");
23957
+ const operands = args.filter((a) => !a.startsWith("-"));
23958
+ const script = operands[0];
23959
+ if (script === undefined)
23960
+ return false;
23961
+ const quiet = options.some((o) => SED_QUIET.includes(o) || !o.startsWith("--") && o.includes("n"));
23962
+ if (quiet && sedOptionsWithin(options, SED_PRINT_OPTIONS))
23963
+ return sedPrintScript(script);
23964
+ return operands.length === 1 && sedOptionsWithin(options, SED_SUBSTITUTE_OPTIONS) && sedSubstituteScript(script);
23965
+ }
23966
+ var PLAIN_READ_ONLY_COMMANDS = [
23967
+ "docker ps",
23968
+ "docker images",
23969
+ "cal",
23970
+ "uptime",
23971
+ "cat",
23972
+ "head",
23973
+ "tail",
23974
+ "wc",
23975
+ "stat",
23976
+ "strings",
23977
+ "hexdump",
23978
+ "od",
23979
+ "nl",
23980
+ "id",
23981
+ "uname",
23982
+ "free",
23983
+ "df",
23984
+ "du",
23985
+ "locale",
23986
+ "groups",
23987
+ "nproc",
23988
+ "basename",
23989
+ "dirname",
23990
+ "realpath",
23991
+ "cut",
23992
+ "paste",
23993
+ "tr",
23994
+ "column",
23995
+ "tac",
23996
+ "rev",
23997
+ "fold",
23998
+ "expand",
23999
+ "unexpand",
24000
+ "fmt",
24001
+ "comm",
24002
+ "cmp",
24003
+ "numfmt",
24004
+ "readlink",
24005
+ "diff",
24006
+ "true",
24007
+ "false",
24008
+ "sleep",
24009
+ "which",
24010
+ "type",
24011
+ "expr",
24012
+ "test",
24013
+ "getconf",
24014
+ "seq",
24015
+ "tsort",
24016
+ "pr"
24017
+ ];
24018
+ var PLAIN_ARGUMENTS = "(?:\\s|$)[^<>()$`|{}&;\\n\\r]*$";
24019
+ var FIND_ACTIONS = /-(?:delete|exec|execdir|ok|okdir|fprint0?|fls|fprintf)\b/;
24020
+ var FIND_FORBIDDEN_SYNTAX = "<>()$`|{}&;\n\r";
24021
+ function findOnlyLists(simple) {
24022
+ if (simple.words[0].raw !== "find")
24023
+ return false;
24024
+ const args = simple.text.slice("find".length);
24025
+ for (let i = 0;i < args.length; i++) {
24026
+ if (args[i] === "\\" && (args[i + 1] === "(" || args[i + 1] === ")")) {
24027
+ i++;
24028
+ continue;
24029
+ }
24030
+ if (FIND_FORBIDDEN_SYNTAX.includes(args[i]))
24031
+ return false;
24032
+ }
24033
+ return !FIND_ACTIONS.test(args) && simple.words.every((w) => !FIND_ACTIONS.test(w.value));
24034
+ }
24035
+ var READ_ONLY_REGEXES = [
24036
+ ...PLAIN_READ_ONLY_COMMANDS.map((name) => new RegExp("^" + name + PLAIN_ARGUMENTS)),
24037
+ /^echo(?:\s+(?:'[^']*'|"[^"$<>\n\r]*"|[^|;&`$(){}><#\\!"'\s]+))*(?:\s+2>&1)?\s*$/,
24038
+ /^claude -h$/,
24039
+ /^claude --help$/,
24040
+ /^uniq(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+(?:=\S+)?|-[fsw]\s+\d+))*(?:\s|$)\s*$/,
24041
+ /^pwd$/,
24042
+ /^whoami$/,
24043
+ /^node -v$/,
24044
+ /^node --version$/,
24045
+ /^python --version$/,
24046
+ /^python3 --version$/,
24047
+ /^history(?:\s+\d+)?\s*$/,
24048
+ /^alias$/,
24049
+ /^arch(?:\s+(?:--help|-h))?\s*$/,
24050
+ /^ip addr$/,
24051
+ /^ifconfig(?:\s+[a-zA-Z][a-zA-Z0-9_-]*)?\s*$/,
24052
+ /^jq(?!\s+.*(?:-f\b|--from-file|--rawfile|--slurpfile|--run-tests|-L\b|--library-path|\benv\b|\$ENV\b))(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+(?:=\S+)?))*(?:\s+'[^'`]*'|\s+"[^"`]*"|\s+[^-\s'"][^\s]*)+\s*$/,
24053
+ /^cd(?:\s+(?:'[^']*'|"[^"]*"|[^\s;|&`$(){}><#\\]+))?$/,
24054
+ /^ls(?:\s+[^<>()$`|{}&;\n\r]*)?$/
24055
+ ];
24056
+
24057
+ // src/tools/concurrency.ts
24058
+ var MAX_TOOL_CONCURRENCY = 10;
24059
+ var CONCURRENCY_SAFE_BUILTINS = new Set(["Read", "Glob", "Grep", "LSP", "WebFetch", "WebSearch", "Search", "Agent"]);
24060
+ function schedulingForCall(name, input, bash) {
24061
+ const descriptor = getRegisteredTool(name)?.descriptor;
24062
+ if (descriptor === undefined)
24063
+ return { kind: "serial" };
24064
+ if (descriptor.source === "builtin") {
24065
+ if (CONCURRENCY_SAFE_BUILTINS.has(descriptor.canonicalName))
24066
+ return { kind: "concurrent" };
24067
+ if (descriptor.canonicalName === "Bash" && bash !== undefined) {
24068
+ const command = typeof input === "object" && input !== null ? input.command : undefined;
24069
+ const escapes = typeof input === "object" && input !== null && input.dangerouslyDisableSandbox === true;
24070
+ if (typeof command === "string" && !escapes && isBashCommandReadOnly(command, bash))
24071
+ return { kind: "concurrent" };
24072
+ }
24073
+ return { kind: "serial" };
24074
+ }
24075
+ if (descriptor.source === "mcp" || descriptor.source === "sdk" || descriptor.source === "plugin") {
24076
+ if (descriptor.annotations?.readOnlyHint === true)
24077
+ return { kind: "concurrent" };
24078
+ if (descriptor.concurrencyLane !== undefined)
24079
+ return { kind: "lane", lane: descriptor.concurrencyLane };
24080
+ }
24081
+ return { kind: "serial" };
24082
+ }
24083
+ var SDK_MCP_CANCEL_GRACE_MS = 5000;
24084
+ function createToolLaneTails() {
24085
+ return new Map;
24086
+ }
24087
+ function enterToolLane(lanes, lane, tail) {
24088
+ const previous = lanes.get(lane);
24089
+ const chained = previous === undefined ? tail : Promise.all([previous, tail]).then(() => {
24090
+ return;
24091
+ });
24092
+ lanes.set(lane, chained);
24093
+ chained.finally(() => {
24094
+ if (lanes.get(lane) === chained)
24095
+ lanes.delete(lane);
24096
+ });
24097
+ return previous;
24098
+ }
24099
+
22444
24100
  // src/tools/read-state.ts
22445
- import { isAbsolute as isAbsolute6, join as join24, normalize as normalize3 } from "node:path";
24101
+ import { isAbsolute as isAbsolute6, join as join25, normalize as normalize3 } from "node:path";
22446
24102
  function createSessionReadState(opts) {
22447
24103
  const cwd = opts.cwd;
22448
24104
  const reads = new Map;
22449
24105
  function canonicalKey(p) {
22450
- const abs = isAbsolute6(p) ? p : join24(cwd, p);
24106
+ const abs = isAbsolute6(p) ? p : join25(cwd, p);
22451
24107
  return resolveRealTarget(normalize3(abs));
22452
24108
  }
22453
24109
  return {
@@ -22554,6 +24210,9 @@ function withoutLoadedTools(block) {
22554
24210
  const { loadedTools: _loaded, loadedToolDefinitions: _definitions, ...rest } = block;
22555
24211
  return rest;
22556
24212
  }
24213
+ function hostToolResultContent(blocks, icons) {
24214
+ return withHostSiteIcons(toolResultsForHost(blocks.map((b) => b.type === "tool_result" && b.loadedTools !== undefined ? withoutLoadedTools(b) : b)), icons);
24215
+ }
22557
24216
  function mcpNamespaceFor(advertisedName, server) {
22558
24217
  if (server === undefined)
22559
24218
  return;
@@ -22722,7 +24381,7 @@ function raceInterrupt(p, interrupted) {
22722
24381
  }
22723
24382
  function buildBaselineDenyRules(resolvedWinterHome, brand, resolvedStoreHome) {
22724
24383
  const absolute = [];
22725
- const defaultHome = resolve12(join25(homedir(), (brand ?? WINTER_BRAND24).homeDirName));
24384
+ const defaultHome = resolve12(join26(homedir(), (brand ?? WINTER_BRAND24).homeDirName));
22726
24385
  if (resolvedWinterHome !== undefined && resolve12(resolvedWinterHome) !== defaultHome) {
22727
24386
  const root = `/${escapeFileRulePathSegment(resolve12(resolvedWinterHome))}`;
22728
24387
  for (const dir of ["run"]) {
@@ -22871,6 +24530,7 @@ async function runEngine2(opts) {
22871
24530
  }
22872
24531
  }
22873
24532
  async function runEngineBody(opts, facetDisposers, registerBackgroundShellSweep) {
24533
+ const sessionLaneTails = opts.toolLaneTails ?? createToolLaneTails();
22874
24534
  const {
22875
24535
  config,
22876
24536
  input,
@@ -23858,16 +25518,17 @@ ${escapeAttributionText(msg.body)}
23858
25518
  continue;
23859
25519
  const toolDefs = serverCfg.tools;
23860
25520
  const toolNames = serverCfg.toolNames;
23861
- registerMcpServerTools(serverName, toolDefs, { deferredDefault: true, ...toolNames !== undefined ? { toolNames } : {} });
25521
+ const toolLanes = serverCfg.toolLanes;
25522
+ registerMcpServerTools(serverName, toolDefs, { deferredDefault: true, ...toolNames !== undefined ? { toolNames } : {}, ...toolLanes !== undefined ? { toolLanes } : {} });
23862
25523
  sdkMcpServerNames.push(serverName);
23863
25524
  const perServerTimeoutMs = serverCfg.timeout;
23864
25525
  for (const tool of toolDefs) {
23865
25526
  const canonicalName = toolNames !== undefined && Object.hasOwn(toolNames, tool.name) ? toolNames[tool.name] : `mcp__${serverName}__${tool.name}`;
23866
25527
  replaceExecutor(canonicalName, {
23867
- async execute(input) {
25528
+ async execute(input, ctx) {
23868
25529
  const timeoutMs = perServerTimeoutMs ?? mcpEnvConfig.toolTimeoutMs ?? 120000;
23869
25530
  try {
23870
- const result = await bridge.request("sdk_mcp_call", { server: serverName, tool: tool.name, arguments: input && typeof input === "object" ? input : {} }, { timeoutMs });
25531
+ const result = await bridge.request("sdk_mcp_call", { server: serverName, tool: tool.name, arguments: input && typeof input === "object" ? input : {} }, { timeoutMs, ...ctx?.signal !== undefined ? { signal: ctx.signal, cancelGraceMs: SDK_MCP_CANCEL_GRACE_MS } : {} });
23871
25532
  const text = (result.content ?? []).map((block) => typeof block.text === "string" ? block.text : JSON.stringify(block)).join(`
23872
25533
  `);
23873
25534
  return { output: text, ...result.isError === true ? { isError: true } : {} };
@@ -26153,34 +27814,77 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26153
27814
  ...callRecord.moved !== undefined ? { nativeState: callRecord.moved.nativeState } : {},
26154
27815
  ...generationEffort ?? {}
26155
27816
  });
26156
- const resultBlocks = [];
27817
+ const slots = turn.calls.map(() => {
27818
+ return;
27819
+ });
26157
27820
  const hostSiteIcons = new Map;
26158
27821
  let toolThrowText = null;
27822
+ const throwTextByIndex = new Map;
27823
+ let lateFailure;
26159
27824
  let structuredTerminated = false;
26160
27825
  const truncatedByOutputLimit = turn.stopReason === "max_tokens";
26161
- for (const emitted of turn.calls) {
27826
+ const sentToHost = new Set;
27827
+ const sendResultToHost = (index) => {
27828
+ const block = slots[index];
27829
+ if (block === undefined || sentToHost.has(index))
27830
+ return;
27831
+ sentToHost.add(index);
27832
+ output.write({ type: "data", message: { type: "user", message: { content: hostToolResultContent([block], hostSiteIcons) } } });
27833
+ };
27834
+ const inFlight = new Set;
27835
+ const deferredPostToolEffects = new Map;
27836
+ const applyFinishedStops = () => {
27837
+ if (turnStop.request !== undefined)
27838
+ return;
27839
+ for (const index of [...deferredPostToolEffects.keys()].sort((a, b) => a - b)) {
27840
+ const stop = deferredPostToolEffects.get(index).stop;
27841
+ if (stop !== undefined) {
27842
+ turnStop.request = stop;
27843
+ return;
27844
+ }
27845
+ }
27846
+ };
27847
+ const settleConcurrent = async () => {
27848
+ while (inFlight.size > 0)
27849
+ await Promise.all([...inFlight]);
27850
+ for (const index of [...deferredPostToolEffects.keys()].sort((a, b) => a - b))
27851
+ deferredPostToolEffects.get(index).apply();
27852
+ deferredPostToolEffects.clear();
27853
+ };
27854
+ const deferPostToolEffects = (index, composite, hookName, apply) => {
27855
+ const prevent = composite.preventContinuation;
27856
+ deferredPostToolEffects.set(index, {
27857
+ apply,
27858
+ ...prevent !== undefined ? { stop: { hookName, ...prevent.reason !== undefined ? { reason: prevent.reason } : {} } } : {}
27859
+ });
27860
+ };
27861
+ const processCall = async (emitted, index, mode) => {
27862
+ const record = (block) => {
27863
+ slots[index] = block;
27864
+ };
26162
27865
  const resolvedName = currentToolName(emitted.name);
26163
27866
  const call = resolvedName !== emitted.name ? { ...emitted, name: resolvedName } : emitted;
27867
+ applyFinishedStops();
26164
27868
  const stoppedBy = currentTurnStop();
26165
27869
  if (stoppedBy !== undefined) {
26166
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: `[not executed: the ${stoppedBy.hookName} hook stopped the turn]`, error: true });
26167
- continue;
27870
+ record({ type: "tool_result", tool_use_id: call.id, content: `[not executed: the ${stoppedBy.hookName} hook stopped the turn]`, error: true });
27871
+ return "next";
26168
27872
  }
26169
27873
  if (truncatedByOutputLimit) {
26170
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: OUTPUT_LIMIT_TRUNCATED_CALL_TEXT, is_error: true });
26171
- continue;
27874
+ record({ type: "tool_result", tool_use_id: call.id, content: OUTPUT_LIMIT_TRUNCATED_CALL_TEXT, is_error: true });
27875
+ return "next";
26172
27876
  }
26173
27877
  if (structuredOutputActive && structuredOutput !== undefined && call.name === STRUCTURED_OUTPUT_TOOL_NAME) {
26174
27878
  structuredOutputAttempts++;
26175
27879
  const validation = structuredOutput.validate(outputFormatSchema, call.input);
26176
27880
  if (validation.ok) {
26177
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: "Structured output accepted." });
27881
+ record({ type: "tool_result", tool_use_id: call.id, content: "Structured output accepted." });
26178
27882
  finalResult = { type: "result", subtype: "success", is_error: false, structured_output: validation.value };
26179
27883
  structuredTerminated = true;
26180
- break;
27884
+ return "stop";
26181
27885
  }
26182
27886
  if (structuredOutputAttempts >= maxStructuredOutputAttempts) {
26183
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: `Structured output validation failed: ${validation.errors.join("; ")}`, error: true });
27887
+ record({ type: "tool_result", tool_use_id: call.id, content: `Structured output validation failed: ${validation.errors.join("; ")}`, error: true });
26184
27888
  finalResult = {
26185
27889
  type: "result",
26186
27890
  subtype: "error_max_structured_output_retries",
@@ -26189,26 +27893,26 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26189
27893
  terminal_reason: "structured_output_retry_exhausted"
26190
27894
  };
26191
27895
  structuredTerminated = true;
26192
- break;
27896
+ return "stop";
26193
27897
  }
26194
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: `Structured output validation failed: ${validation.errors.join("; ")}. Call ${STRUCTURED_OUTPUT_TOOL_NAME} again with a corrected value.`, error: true });
26195
- continue;
27898
+ record({ type: "tool_result", tool_use_id: call.id, content: `Structured output validation failed: ${validation.errors.join("; ")}. Call ${STRUCTURED_OUTPUT_TOOL_NAME} again with a corrected value.`, error: true });
27899
+ return "next";
26196
27900
  }
26197
27901
  const callMcpServer = mcpServerIdentityForCall(call.name);
26198
27902
  try {
26199
27903
  const availabilityDescriptor = getRegisteredTool(call.name)?.descriptor;
26200
27904
  if (availabilityDescriptor !== undefined && !isToolAvailable(availabilityDescriptor, { ...advertisedCfg, mode: policyStateStore.getState().mode })) {
26201
- resultBlocks.push({
27905
+ record({
26202
27906
  type: "tool_result",
26203
27907
  tool_use_id: call.id,
26204
27908
  content: `'${call.name}' is not available in this session's current configuration (WS-06 §1.5 availability) -- it is registered but excluded here, so it was not executed`,
26205
27909
  error: true
26206
27910
  });
26207
- continue;
27911
+ return "next";
26208
27912
  }
26209
27913
  if (isDeferredAndUnloaded(call.name)) {
26210
27914
  const excludedReason = aliasExclusionLive(call.name);
26211
- resultBlocks.push(excludedReason !== undefined ? {
27915
+ record(excludedReason !== undefined ? {
26212
27916
  type: "tool_result",
26213
27917
  tool_use_id: call.id,
26214
27918
  content: `'${call.name}' is not available in this session: ${excludedReason}. It cannot be loaded with ToolSearch either -- the same exclusion removes it from the searchable pool.`,
@@ -26219,7 +27923,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26219
27923
  content: `'${call.name}' is a deferred tool that has not been loaded this session yet -- use ToolSearch to select it before calling it (WS-09 §8.5)`,
26220
27924
  loadFirst: true
26221
27925
  });
26222
- continue;
27926
+ return "next";
26223
27927
  }
26224
27928
  const permissionInput = typeof call.input === "object" && call.input !== null ? call.input : {};
26225
27929
  const evalCtxForIdentity = makeEvalCtx();
@@ -26235,8 +27939,8 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26235
27939
  const declaredIdentity = declaredMcpIdentity(call.name);
26236
27940
  const notOffered = [...aliasPermissionIdentities(call.name, config.toolAliases, sessionBrand), ...declaredIdentity !== undefined ? [declaredIdentity] : []].some(probeRule("deny")) ? undefined : toolNotOfferedRefusal(call.name);
26237
27941
  if (notOffered !== undefined) {
26238
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: notOffered, is_error: true });
26239
- continue;
27942
+ record({ type: "tool_result", tool_use_id: call.id, content: notOffered, is_error: true });
27943
+ return "next";
26240
27944
  }
26241
27945
  const identityProbes = {
26242
27946
  deniedByRule: probeRule("deny"),
@@ -26255,12 +27959,12 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26255
27959
  const decisionRaced = await raceInterrupt(evaluateWithFreshPolicy(permissionCall), interruptSignal);
26256
27960
  if (decisionRaced.kind === "interrupted") {
26257
27961
  interrupted = true;
26258
- break;
27962
+ return "stop";
26259
27963
  }
26260
27964
  const decision = decisionRaced.value;
26261
27965
  takeGatingHookComposites(call.id, call.name);
26262
27966
  const denyCall = async (message, mechanism, ruleRef) => {
26263
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: message, denied: true });
27967
+ record({ type: "tool_result", tool_use_id: call.id, content: message, denied: true });
26264
27968
  turnPermissionDenials.push({ tool_name: call.name, tool_use_id: call.id, tool_input: permissionCall.input });
26265
27969
  output.write({
26266
27970
  type: "data",
@@ -26288,7 +27992,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26288
27992
  const deferMessage = decision.message ?? "a PreToolUse hook deferred this call for durable approval";
26289
27993
  if (!approvalStore) {
26290
27994
  await denyCall(`Denied: cannot defer -- no durable approval store is available for this session (${deferMessage})`, "hook");
26291
- continue;
27995
+ return "next";
26292
27996
  }
26293
27997
  const requestId = randomUUID18();
26294
27998
  const executedInput = decision.transformedInput ?? permissionCall.input;
@@ -26315,9 +28019,9 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26315
28019
  } catch (err) {
26316
28020
  const text = err instanceof Error ? err.message : String(err);
26317
28021
  await denyCall(`Denied: failed to persist the durable approval record (${text})`, "hook");
26318
- continue;
28022
+ return "next";
26319
28023
  }
26320
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: "[deferred]", deferred: true });
28024
+ record({ type: "tool_result", tool_use_id: call.id, content: "[deferred]", deferred: true });
26321
28025
  output.write({
26322
28026
  type: "data",
26323
28027
  message: {
@@ -26332,7 +28036,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26332
28036
  uuid: randomUUID18()
26333
28037
  }
26334
28038
  });
26335
- continue;
28039
+ return "next";
26336
28040
  }
26337
28041
  if (decision.decision !== "allow") {
26338
28042
  const deniedMessage = decision.message ?? "Permission denied";
@@ -26340,9 +28044,9 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26340
28044
  if (decision.interrupt === true) {
26341
28045
  interrupted = true;
26342
28046
  interruptCurrentTurn.current?.();
26343
- break;
28047
+ return "stop";
26344
28048
  }
26345
- continue;
28049
+ return "next";
26346
28050
  }
26347
28051
  if (decision.updatedPermissions) {
26348
28052
  for (const update of decision.updatedPermissions) {
@@ -26373,18 +28077,47 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26373
28077
  }
26374
28078
  }
26375
28079
  }
26376
- toolReferenceCollector = [];
26377
- const raced = await raceInterrupt(tools.execute(executedCall, { signal: turnAbort.signal, ...decision.explicitApproval !== undefined ? { explicitApproval: decision.explicitApproval } : {} }), interruptSignal);
26378
- const loadedTools = advertisedNamesFor(toolReferenceCollector);
26379
- toolReferenceCollector = undefined;
28080
+ if (interrupted || toolThrowText !== null || lateFailure !== undefined)
28081
+ return "stop";
28082
+ applyFinishedStops();
28083
+ const stoppedBeforeStart = currentTurnStop();
28084
+ if (stoppedBeforeStart !== undefined) {
28085
+ record({ type: "tool_result", tool_use_id: call.id, content: `[not executed: the ${stoppedBeforeStart.hookName} hook stopped the turn]`, error: true });
28086
+ return "next";
28087
+ }
28088
+ const laneTurn = mode.enterLane?.();
28089
+ mode.readyToExecute?.();
28090
+ if (laneTurn !== undefined) {
28091
+ const turnOfLane = await raceInterrupt(laneTurn, interruptSignal);
28092
+ if (turnOfLane.kind === "interrupted") {
28093
+ interrupted = true;
28094
+ return "stop";
28095
+ }
28096
+ if (interrupted || toolThrowText !== null || lateFailure !== undefined)
28097
+ return "stop";
28098
+ applyFinishedStops();
28099
+ const stoppedInLane = currentTurnStop();
28100
+ if (stoppedInLane !== undefined) {
28101
+ record({ type: "tool_result", tool_use_id: call.id, content: `[not executed: the ${stoppedInLane.hookName} hook stopped the turn]`, error: true });
28102
+ return "next";
28103
+ }
28104
+ }
28105
+ if (!mode.concurrent)
28106
+ toolReferenceCollector = [];
28107
+ const execution = tools.execute(executedCall, { signal: turnAbort.signal, ...decision.explicitApproval !== undefined ? { explicitApproval: decision.explicitApproval } : {} });
28108
+ mode.onExecution?.(execution);
28109
+ const raced = await raceInterrupt(execution, interruptSignal);
28110
+ const loadedTools = mode.concurrent ? [] : advertisedNamesFor(toolReferenceCollector);
28111
+ if (!mode.concurrent)
28112
+ toolReferenceCollector = undefined;
26380
28113
  const loadedToolDefinitions = loadedDefinitionsFor(loadedTools);
26381
28114
  const forkDefinitionsText = exactRequestLayout !== undefined ? forkLoadedDefinitionsText(loadedToolDefinitions) : "";
26382
28115
  if (raced.kind === "interrupted") {
26383
28116
  interrupted = true;
26384
- break;
28117
+ return "stop";
26385
28118
  }
26386
28119
  const resultBlocksOfCall = raced.value.blocks;
26387
- resultBlocks.push({
28120
+ record({
26388
28121
  type: "tool_result",
26389
28122
  tool_use_id: call.id,
26390
28123
  content: resultBlocksOfCall !== undefined ? forkDefinitionsText.length > 0 ? [...resultBlocksOfCall, { type: "text", text: forkDefinitionsText.trimStart() }] : [...resultBlocksOfCall] : forkDefinitionsText.length > 0 ? `${raced.value.output}${forkDefinitionsText}` : raced.value.output,
@@ -26407,57 +28140,154 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26407
28140
  input: executedCall.input,
26408
28141
  payload: { tool_response: raced.value.output }
26409
28142
  });
26410
- if (postToolUseHookOutcome.classifierContext !== undefined) {
26411
- accumulatedClassifierContext.push(...postToolUseHookOutcome.classifierContext);
26412
- }
26413
- absorbHookComposite(postToolUseHookOutcome, { hookName: `${raced.value.isError === true ? "PostToolUseFailure" : "PostToolUse"}:${call.name}`, context: true, toolUseID: call.id });
28143
+ const postHookName = `${raced.value.isError === true ? "PostToolUseFailure" : "PostToolUse"}:${call.name}`;
28144
+ const applyPostToolEffects = () => {
28145
+ if (postToolUseHookOutcome.classifierContext !== undefined) {
28146
+ accumulatedClassifierContext.push(...postToolUseHookOutcome.classifierContext);
28147
+ }
28148
+ absorbHookComposite(postToolUseHookOutcome, { hookName: postHookName, context: true, toolUseID: call.id });
28149
+ };
28150
+ if (mode.concurrent)
28151
+ deferPostToolEffects(index, postToolUseHookOutcome, postHookName, applyPostToolEffects);
28152
+ else
28153
+ applyPostToolEffects();
26414
28154
  if (postToolUseHookOutcome.transformedOutput !== undefined) {
26415
- const last = resultBlocks[resultBlocks.length - 1];
26416
- if (last !== undefined && last.type === "tool_result" && last.tool_use_id === call.id) {
26417
- resultBlocks[resultBlocks.length - 1] = { ...last, content: toolOutputReplacement(postToolUseHookOutcome.transformedOutput) };
28155
+ const own = slots[index];
28156
+ if (own !== undefined && own.type === "tool_result" && own.tool_use_id === call.id) {
28157
+ slots[index] = { ...own, content: toolOutputReplacement(postToolUseHookOutcome.transformedOutput) };
26418
28158
  }
26419
28159
  }
26420
28160
  } catch (err) {
26421
28161
  const text = err instanceof Error ? err.message : String(err);
26422
- finalResult = { type: "result", subtype: "error_during_execution", is_error: true, result: text };
26423
- toolThrowText = text;
26424
- absorbHookComposite(await fireObservationalHook("PostToolUseFailure", {
28162
+ throwTextByIndex.set(index, text);
28163
+ if (toolThrowText === null) {
28164
+ finalResult = { type: "result", subtype: "error_during_execution", is_error: true, result: text };
28165
+ toolThrowText = text;
28166
+ }
28167
+ const failureOutcome = await fireObservationalHook("PostToolUseFailure", {
26425
28168
  toolUseID: call.id,
26426
28169
  toolName: postToolHookSubject("PostToolUseFailure", call.name),
26427
28170
  ...callMcpServer !== undefined ? { mcpServer: callMcpServer } : {},
26428
28171
  input: typeof call.input === "object" && call.input !== null ? call.input : {},
26429
28172
  payload: { error: text }
26430
- }), { hookName: `PostToolUseFailure:${call.name}`, context: true, toolUseID: call.id });
28173
+ });
28174
+ const failureHookName = `PostToolUseFailure:${call.name}`;
28175
+ const applyFailureEffects = () => absorbHookComposite(failureOutcome, { hookName: failureHookName, context: true, toolUseID: call.id });
28176
+ if (mode.concurrent)
28177
+ deferPostToolEffects(index, failureOutcome, failureHookName, applyFailureEffects);
28178
+ else
28179
+ applyFailureEffects();
28180
+ return "stop";
28181
+ }
28182
+ return "next";
28183
+ };
28184
+ const bashReadOnlyContext = () => ({
28185
+ cwd: currentCwd,
28186
+ originalCwd: config.cwd,
28187
+ sandboxEnabled: sandboxSettingsForSession.enabled !== false && isSandboxAvailable()
28188
+ });
28189
+ for (let index = 0;index < turn.calls.length; index++) {
28190
+ const emitted = turn.calls[index];
28191
+ const scheduling = schedulingForCall(currentToolName(emitted.name), emitted.input, bashReadOnlyContext());
28192
+ if (scheduling.kind === "serial") {
28193
+ await settleConcurrent();
28194
+ } else {
28195
+ while (inFlight.size >= MAX_TOOL_CONCURRENCY)
28196
+ await Promise.race(inFlight);
28197
+ }
28198
+ if (interrupted || toolThrowText !== null || lateFailure !== undefined)
26431
28199
  break;
28200
+ if (scheduling.kind === "serial") {
28201
+ try {
28202
+ const outcome = await processCall(emitted, index, { concurrent: false });
28203
+ sendResultToHost(index);
28204
+ if (outcome === "stop")
28205
+ break;
28206
+ } catch (error) {
28207
+ lateFailure = { error };
28208
+ break;
28209
+ }
28210
+ continue;
26432
28211
  }
28212
+ let releaseLane;
28213
+ let laneExecution;
28214
+ const lane = scheduling.kind === "lane" ? scheduling.lane : undefined;
28215
+ const enterLane = lane !== undefined ? () => enterToolLane(sessionLaneTails, lane, new Promise((resolve) => releaseLane = resolve)) : undefined;
28216
+ let markReady;
28217
+ const ready = new Promise((resolve) => markReady = () => resolve("ready"));
28218
+ const settled = processCall(emitted, index, {
28219
+ concurrent: true,
28220
+ readyToExecute: markReady,
28221
+ ...enterLane !== undefined ? { enterLane, onExecution: (execution) => {
28222
+ laneExecution = execution;
28223
+ } } : {}
28224
+ }).then((outcome) => {
28225
+ sendResultToHost(index);
28226
+ return outcome;
28227
+ }).catch((error) => {
28228
+ if (lateFailure === undefined)
28229
+ lateFailure = { error };
28230
+ return "stop";
28231
+ });
28232
+ const tracked = settled.finally(() => {
28233
+ inFlight.delete(tracked);
28234
+ const release = () => releaseLane?.();
28235
+ if (laneExecution !== undefined)
28236
+ laneExecution.then(release, release);
28237
+ else
28238
+ release();
28239
+ });
28240
+ inFlight.add(tracked);
28241
+ const first = await Promise.race([ready, settled]);
28242
+ if (first === "stop")
28243
+ break;
26433
28244
  }
28245
+ await settleConcurrent();
26434
28246
  if (toolThrowText !== null) {
26435
- const resultedIds = new Set(resultBlocks.map((b) => b.tool_use_id));
26436
- for (const call of turn.calls) {
26437
- if (!resultedIds.has(call.id)) {
26438
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: `[error: ${toolThrowText}]`, error: true });
26439
- }
26440
- }
28247
+ turn.calls.forEach((call, index) => {
28248
+ if (slots[index] === undefined)
28249
+ slots[index] = { type: "tool_result", tool_use_id: call.id, content: `[error: ${throwTextByIndex.get(index) ?? toolThrowText}]`, error: true };
28250
+ });
28251
+ }
28252
+ if (lateFailure !== undefined) {
28253
+ const failure = lateFailure.error;
28254
+ const text = failure instanceof Error ? failure.message : String(failure);
28255
+ turn.calls.forEach((call, index) => {
28256
+ if (slots[index] === undefined)
28257
+ slots[index] = { type: "tool_result", tool_use_id: call.id, content: `[error: ${text}]`, error: true };
28258
+ });
26441
28259
  }
26442
28260
  if (structuredTerminated) {
26443
- const resultedIds = new Set(resultBlocks.map((b) => b.tool_use_id));
26444
- for (const call of turn.calls) {
26445
- if (!resultedIds.has(call.id)) {
26446
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: "[not executed: the turn ended on a structured-output result]", error: true });
26447
- }
26448
- }
28261
+ turn.calls.forEach((call, index) => {
28262
+ if (slots[index] === undefined)
28263
+ slots[index] = { type: "tool_result", tool_use_id: call.id, content: "[not executed: the turn ended on a structured-output result]", error: true };
28264
+ });
26449
28265
  }
26450
28266
  if (interrupted) {
26451
- const resultedIds = new Set(resultBlocks.map((b) => b.tool_use_id));
26452
- for (const call of turn.calls) {
26453
- if (!resultedIds.has(call.id)) {
26454
- resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: "[interrupted]", interrupted: true });
26455
- }
28267
+ turn.calls.forEach((call, index) => {
28268
+ if (slots[index] === undefined)
28269
+ slots[index] = { type: "tool_result", tool_use_id: call.id, content: "[interrupted]", interrupted: true };
28270
+ });
28271
+ }
28272
+ turn.calls.forEach((call, index) => {
28273
+ if (slots[index] === undefined)
28274
+ slots[index] = { type: "tool_result", tool_use_id: call.id, content: "[not executed]", error: true };
28275
+ });
28276
+ const resultBlocks = slots;
28277
+ const unsent = resultBlocks.filter((_, index) => !sentToHost.has(index));
28278
+ if (unsent.length > 0) {
28279
+ try {
28280
+ output.write({ type: "data", message: { type: "user", message: { content: hostToolResultContent(unsent, hostSiteIcons) } } });
28281
+ } catch (error) {
28282
+ if (lateFailure === undefined)
28283
+ lateFailure = { error };
26456
28284
  }
28285
+ resultBlocks.forEach((_, index) => sentToHost.add(index));
26457
28286
  }
26458
- output.write({ type: "data", message: { type: "user", message: { content: withHostSiteIcons(toolResultsForHost(resultBlocks.map((b) => b.type === "tool_result" && b.loadedTools !== undefined ? withoutLoadedTools(b) : b)), hostSiteIcons) } } });
26459
28287
  messages.push({ role: "tool", content: resultBlocks });
26460
28288
  await recordUser(resultBlocks);
28289
+ if (lateFailure !== undefined)
28290
+ throw lateFailure.error;
26461
28291
  await flushPendingHookAttachments();
26462
28292
  gatingHookComposites.clear();
26463
28293
  if (finalResult)
@@ -26534,7 +28364,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
26534
28364
  }
26535
28365
 
26536
28366
  // src/provider/mock.ts
26537
- import { randomUUID as randomUUID19 } from "node:crypto";
28367
+ import { createHash as createHash2, randomUUID as randomUUID19 } from "node:crypto";
26538
28368
 
26539
28369
  // src/compaction/summarizer.ts
26540
28370
  var WINTER_SUMMARY_INSTRUCTION = "You are compacting a conversation so that it can continue with less context. " + "Summarize the conversation above as clear declarative statements of what is true and what was decided -- " + "not as a paraphrase of the most recent message and not as an acknowledgement. " + "Preserve every specific verbatim: numbers, names, file paths, identifiers, exact values, and any decision that was made or reversed. " + "Record work still outstanding as well as work completed. " + "Be concise but complete; never drop a fact to save words. " + "Output the summary text and nothing else.";
@@ -26719,7 +28549,7 @@ var TEST_PROVIDER_NAMES = new Set([
26719
28549
  "readimage",
26720
28550
  "calls"
26721
28551
  ]);
26722
- var CALL_LINE = /^CALL\s+(\S+)(?:\s+(.*))?$/;
28552
+ var CALL_LINE = /^(\+?)CALL\s+(\S+)(?:\s+(.*))?$/;
26723
28553
  function promptedCallsProvider() {
26724
28554
  return {
26725
28555
  async generate({ messages }) {
@@ -26734,13 +28564,21 @@ function promptedCallsProvider() {
26734
28564
  }
26735
28565
  if (at < 0)
26736
28566
  return { kind: "text", text: "[]" };
26737
- const script = userMessageText(messages[at]).split(`
26738
- `).map((line) => CALL_LINE.exec(line.trim())).filter((m) => m !== null).map((m) => ({ name: m[1], input: m[2] !== undefined && m[2].trim() !== "" ? JSON.parse(m[2]) : {} }));
28567
+ const rounds = [];
28568
+ for (const m of userMessageText(messages[at]).split(`
28569
+ `).map((line) => CALL_LINE.exec(line.trim())).filter((m) => m !== null)) {
28570
+ const call = { name: m[2], input: m[3] !== undefined && m[3].trim() !== "" ? JSON.parse(m[3]) : {} };
28571
+ if (m[1] === "+" && rounds.length > 0)
28572
+ rounds[rounds.length - 1].push(call);
28573
+ else
28574
+ rounds.push([call]);
28575
+ }
26739
28576
  const after = messages.slice(at + 1);
26740
28577
  const made = after.filter((m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some((b) => b.type === "tool_use")).length;
26741
- if (made < script.length) {
26742
- const next = script[made];
26743
- return { kind: "tool_use", calls: [{ id: `calls-${at + 1}-${made + 1}`, name: next.name, input: next.input }] };
28578
+ if (made < rounds.length) {
28579
+ const script = createHash2("sha256").update(userMessageText(messages[at])).digest("hex").slice(0, 6);
28580
+ const round = rounds[made];
28581
+ return { kind: "tool_use", calls: round.map((next, i) => ({ id: `calls-${script}-${at + 1}-${made + 1}${i === 0 ? "" : `-${i + 1}`}`, name: next.name, input: next.input })) };
26744
28582
  }
26745
28583
  const results = after.flatMap((m) => Array.isArray(m.content) ? m.content : []).filter((b) => b.type === "tool_result");
26746
28584
  const named = new Map(after.flatMap((m) => m.role === "assistant" && Array.isArray(m.content) ? m.content : []).flatMap((b) => b.type === "tool_use" ? [[b.id, b.name]] : []));
@@ -27369,7 +29207,7 @@ function rebuildProviderMessages2(entries) {
27369
29207
 
27370
29208
  // src/store/dialect.ts
27371
29209
  import { randomUUID as randomUUID20 } from "node:crypto";
27372
- import { join as join26 } from "node:path";
29210
+ import { join as join27 } from "node:path";
27373
29211
  import {
27374
29212
  WinterCompatibilitySessionStore,
27375
29213
  DIALECT_RECORD_ENTRY_TYPE,
@@ -27675,10 +29513,10 @@ class TranscriptWriter2 {
27675
29513
  return entries;
27676
29514
  }
27677
29515
  }
27678
- var RUNTIME_ENGINE_VERSION2 = "0.0.39";
29516
+ var RUNTIME_ENGINE_VERSION2 = "0.0.40";
27679
29517
  function withPermissionJournal(writer, location) {
27680
29518
  return {
27681
- transcriptPath: join26(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),
29519
+ transcriptPath: join27(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),
27682
29520
  recordUserEntry: (content, opts) => writer.recordUserEntry(content, opts),
27683
29521
  recordAssistantEntry: (content, opts) => writer.recordAssistantEntry(content, opts),
27684
29522
  recordAttachmentEntry: (attachment) => writer.recordAttachmentEntry(attachment),
@@ -27706,7 +29544,7 @@ function buildWriter(opts) {
27706
29544
  ctx: { sessionId: opts.sessionId, cwd: opts.cwd, version: RUNTIME_ENGINE_VERSION2, projectDirName: opts.projectKey },
27707
29545
  initialParentUuid: opts.initialParentUuid,
27708
29546
  ...opts.initialConversationalUuids !== undefined ? { initialConversationalUuids: opts.initialConversationalUuids } : {},
27709
- providerStateSink: createFileProviderStateSink(providerStateSidecarPath(join26(opts.winterHome, "projects", opts.projectKey, `${opts.sessionId}.jsonl`)))
29547
+ providerStateSink: createFileProviderStateSink(providerStateSidecarPath(join27(opts.winterHome, "projects", opts.projectKey, `${opts.sessionId}.jsonl`)))
27710
29548
  });
27711
29549
  return withPermissionJournal(writer, { winterHome: opts.winterHome, projectKey: opts.projectKey, sessionId: opts.sessionId });
27712
29550
  }
@@ -27719,7 +29557,7 @@ function buildChildTranscriptWriter(opts) {
27719
29557
  key: { projectKey: opts.projectKey, sessionId: opts.parentSessionId, subpath: childTranscriptSubpath(opts.agentId) },
27720
29558
  ctx: { sessionId: opts.parentSessionId, cwd: opts.cwd, version: RUNTIME_ENGINE_VERSION2, projectDirName: opts.projectKey },
27721
29559
  sidechain: { agentId: opts.agentId, parentToolUseId: opts.parentToolUseId },
27722
- ...opts.winterHome !== undefined ? { providerStateSink: createFileProviderStateSink(providerStateSidecarPath(join26(opts.winterHome, "projects", opts.projectKey, opts.parentSessionId, `${childTranscriptSubpath(opts.agentId)}.jsonl`))) } : {}
29560
+ ...opts.winterHome !== undefined ? { providerStateSink: createFileProviderStateSink(providerStateSidecarPath(join27(opts.winterHome, "projects", opts.projectKey, opts.parentSessionId, `${childTranscriptSubpath(opts.agentId)}.jsonl`))) } : {}
27723
29561
  });
27724
29562
  }
27725
29563
  var CHILD_SUBKEY_PATTERN = /^subagents\/agent-(.+)$/;
@@ -28401,4 +30239,4 @@ function selectClassifierRoute2(config, descriptorOf) {
28401
30239
  return { kind: "worker-eligible" };
28402
30240
  }
28403
30241
 
28404
- export { ChildResumeModeIncomparableError, resolveChildResumeMode, ensureDefaultMessagingRuntimeRegistered, buildContinuationChain, AGENT_TOOL_CANONICAL_NAME, computeActiveSlotSet, resolveSlotToProvider, createRpcBridge2, shellWords, escapeRegExpLiteral, validatePermissionRuleString, registerTool2, getRegisteredTool, listRegisteredTools, rebrandStandingServerTools, mcpServerHasRegistrations, onCompaction2, STRUCTURED_OUTPUT_TOOL_NAME, looksLikeCommand, matchFileRule, resolveFileRuleAbsolutePath, resolveFileRuleAbsoluteGlobText, resolvesWithinPluginRoot, buildWorkflowWorkerSeatbeltProfile2, capBytes, neutralizeReminderTags, readWhole, readCappedLinesAndBytes, registerAttachmentRenderer, attachmentsIn, registerChildEngineFactory, rawToRuleValue, sourceRule, parseFrontmatter, parseAgentDefinitionFile, allowedAgentTypesFromTools, validateAgentDefinition, WinterPathsError2, sessionTempDir2, ensureTasksDir2, resolveProjectDirName2, buildForkDirectiveText, buildForkInitialMessages, gitInstructionsEnabled, isUserInvocable, buildSkillListing, registerPluginAgents, clearPluginAgents, registerSkillSessionRuntime, clearSkillSessionRuntime, buildHookEntriesFromSettings, defaultTrustSource, formatValidationErrors, createSchemaValidatorCache, killAllTaskProcessGroups, runGit, listWorktrees, canonicalAliases, WINTER_CANONICAL_ALIASES, DEFAULT_SKILL_BODY_BYTES, skillNameError, pluginNameError, capBytes2, parseSkillFile, validateSkillsOption, isSkillEnabled, autoSkillPermissionEntries, setHostWorkflowWorkerCommand, listWorkflowsForListing, buildWorkflowSkillPrompt, WORKFLOW_SANDBOX_REFUSED_EXIT_CODE2, DEFAULT_CONTEXT_WINDOW_TOKENS2, ProviderTurnError, isProviderTurnError, createContextAccountant2, runEngine2, WINTER_SUMMARY_INSTRUCTION, WINTER_PREFIX_SUMMARY_INSTRUCTION, CARRIED_SUMMARY_NOTE, retainedExchangesNote, buildSummaryInstruction, redactForSummary, summarize, summarizeOverPrefix, recordedProviderSystems2, resetRecordedProviderSystems2, echoProvider2, scriptedProvider2, stubExecutor2, P5_FIXTURE_SKILL_NAME2, P5_WORKFLOW_SCRIPT2, isTestProviderName2, testProviderForNamespace, testProviderByName2, SUBAGENT_CHILD_PROBE_TEXT2, MCP_SDK_TEST_SERVER_NAME2, MCP_SDK_TEST_TOOL_NAME2, BGTASK_TEST_TOOL_NAME2, registerBgTaskTestTool2, ResumeTargetError2, ResumeTruncationError2, findContinueTarget2, findResumeTarget2, toDialectEntries2, truncateAt2, rebuildProviderMessages2, userEntry2, winterMessageIdFor2, assistantEntry2, CLAUDE_COMPACT_SUMMARY_PREAMBLE2, claudeCompactBoundaryEntry2, claudeCompactSummaryEntry2, TranscriptWriterError2, TranscriptWriter2, RUNTIME_ENGINE_VERSION2, childTranscriptSubpath, buildChildTranscriptWriter, listChildAgentIds, resolveEngineSession2, resolveProductionWinterHome2, resolveProductionStoreHome, resolveSessionProvider, redactCredentialRef, createProviderContext, providerCredentialRef2, storeProviderCredential2, deleteProviderCredential2, validateProviderCredential2, CLASSIFIER_TOOL_NAME2, CLASSIFIER_VERDICT_SCHEMA2, CLASSIFIER_NO_VERDICT_REASONS2, DEFAULT_MAX_CONTEXT_CHARS2, CLASSIFIER_SYSTEM_PROMPT2, DEFAULT_CLASSIFIER_TIMEOUT_MS2, createModelClassifier2, selectClassifierRoute2 };
30242
+ export { ChildResumeModeIncomparableError, resolveChildResumeMode, ensureDefaultMessagingRuntimeRegistered, buildContinuationChain, AGENT_TOOL_CANONICAL_NAME, computeActiveSlotSet, resolveSlotToProvider, createRpcBridge2, shellWords, escapeRegExpLiteral, validatePermissionRuleString, registerTool2, getRegisteredTool, listRegisteredTools, rebrandStandingServerTools, mcpServerHasRegistrations, onCompaction2, STRUCTURED_OUTPUT_TOOL_NAME, looksLikeCommand, matchFileRule, resolveFileRuleAbsolutePath, resolveFileRuleAbsoluteGlobText, resolvesWithinPluginRoot, buildWorkflowWorkerSeatbeltProfile2, capBytes, neutralizeReminderTags, readWhole, readCappedLinesAndBytes, registerAttachmentRenderer, attachmentsIn, registerChildEngineFactory, rawToRuleValue, sourceRule, parseFrontmatter, parseAgentDefinitionFile, allowedAgentTypesFromTools, validateAgentDefinition, WinterPathsError2, sessionTempDir2, ensureTasksDir2, resolveProjectDirName2, buildForkDirectiveText, buildForkInitialMessages, gitInstructionsEnabled, isUserInvocable, buildSkillListing, registerPluginAgents, clearPluginAgents, registerSkillSessionRuntime, clearSkillSessionRuntime, buildHookEntriesFromSettings, defaultTrustSource, formatValidationErrors, createSchemaValidatorCache, killAllTaskProcessGroups, runGit, listWorktrees, canonicalAliases, WINTER_CANONICAL_ALIASES, DEFAULT_SKILL_BODY_BYTES, skillNameError, pluginNameError, capBytes2, parseSkillFile, validateSkillsOption, isSkillEnabled, autoSkillPermissionEntries, setHostWorkflowWorkerCommand, listWorkflowsForListing, buildWorkflowSkillPrompt, WORKFLOW_SANDBOX_REFUSED_EXIT_CODE2, createToolLaneTails, DEFAULT_CONTEXT_WINDOW_TOKENS2, ProviderTurnError, isProviderTurnError, createContextAccountant2, runEngine2, WINTER_SUMMARY_INSTRUCTION, WINTER_PREFIX_SUMMARY_INSTRUCTION, CARRIED_SUMMARY_NOTE, retainedExchangesNote, buildSummaryInstruction, redactForSummary, summarize, summarizeOverPrefix, recordedProviderSystems2, resetRecordedProviderSystems2, echoProvider2, scriptedProvider2, stubExecutor2, P5_FIXTURE_SKILL_NAME2, P5_WORKFLOW_SCRIPT2, isTestProviderName2, testProviderForNamespace, testProviderByName2, SUBAGENT_CHILD_PROBE_TEXT2, MCP_SDK_TEST_SERVER_NAME2, MCP_SDK_TEST_TOOL_NAME2, BGTASK_TEST_TOOL_NAME2, registerBgTaskTestTool2, ResumeTargetError2, ResumeTruncationError2, findContinueTarget2, findResumeTarget2, toDialectEntries2, truncateAt2, rebuildProviderMessages2, userEntry2, winterMessageIdFor2, assistantEntry2, CLAUDE_COMPACT_SUMMARY_PREAMBLE2, claudeCompactBoundaryEntry2, claudeCompactSummaryEntry2, TranscriptWriterError2, TranscriptWriter2, RUNTIME_ENGINE_VERSION2, childTranscriptSubpath, buildChildTranscriptWriter, listChildAgentIds, resolveEngineSession2, resolveProductionWinterHome2, resolveProductionStoreHome, resolveSessionProvider, redactCredentialRef, createProviderContext, providerCredentialRef2, storeProviderCredential2, deleteProviderCredential2, validateProviderCredential2, CLASSIFIER_TOOL_NAME2, CLASSIFIER_VERDICT_SCHEMA2, CLASSIFIER_NO_VERDICT_REASONS2, DEFAULT_MAX_CONTEXT_CHARS2, CLASSIFIER_SYSTEM_PROMPT2, DEFAULT_CLASSIFIER_TIMEOUT_MS2, createModelClassifier2, selectClassifierRoute2 };