@yanlinglabs/winter-agent-runtime 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/commands/resolver.d.ts +8 -13
  2. package/dist/context/agent-listing.d.ts +7 -11
  3. package/dist/context/attachments.d.ts +14 -23
  4. package/dist/context/dynamic-sections.d.ts +5 -5
  5. package/dist/context/git-branch-fixture.d.ts +22 -0
  6. package/dist/context/git-status.d.ts +2 -2
  7. package/dist/context/memory.d.ts +1 -1
  8. package/dist/context/output-styles.d.ts +2 -2
  9. package/dist/context/request-layout.d.ts +16 -17
  10. package/dist/context/seam.d.ts +7 -6
  11. package/dist/embedded-worker.js +3 -3
  12. package/dist/embedded.js +3 -3
  13. package/dist/engine.d.ts +3 -3
  14. package/dist/{index-ey5c52j3.js → index-14xkt4jp.js} +2 -2
  15. package/dist/{index-hdvvezf8.js → index-bh1ve8ws.js} +238 -154
  16. package/dist/{index-z9yngxj4.js → index-kqc3tdph.js} +964 -826
  17. package/dist/index.js +1 -1
  18. package/dist/mcp/lifecycle.d.ts +2 -1
  19. package/dist/permissions/edit-recognition.d.ts +1 -1
  20. package/dist/permissions/evaluator.d.ts +19 -30
  21. package/dist/permissions/file-rules.d.ts +177 -277
  22. package/dist/permissions/grammar.d.ts +58 -0
  23. package/dist/permissions/policy-state.d.ts +1 -1
  24. package/dist/permissions/ruleset.d.ts +1 -1
  25. package/dist/permissions/shell-structure.d.ts +1 -1
  26. package/dist/plugins/bundle.d.ts +4 -6
  27. package/dist/plugins/loader.corpus-fixture.d.ts +26 -0
  28. package/dist/plugins/loader.d.ts +3 -4
  29. package/dist/plugins/manifest.d.ts +23 -46
  30. package/dist/production-wiring.d.ts +1 -1
  31. package/dist/provider/lean-prompt.d.ts +1 -6
  32. package/dist/sandbox/profile.d.ts +39 -79
  33. package/dist/sandbox/spawn.d.ts +5 -6
  34. package/dist/skills/listing.d.ts +8 -26
  35. package/dist/skills/store.d.ts +2 -3
  36. package/dist/store/dialect.d.ts +1 -1
  37. package/dist/subagents/availability.d.ts +10 -8
  38. package/dist/subagents/child-handle.d.ts +5 -5
  39. package/dist/subagents/definitions.d.ts +7 -6
  40. package/dist/subagents/fork.d.ts +4 -8
  41. package/dist/subagents/notification-queue.d.ts +30 -38
  42. package/dist/testing.js +2 -2
  43. package/dist/tools/descriptors/web-fetch.d.ts +1 -1
  44. package/dist/tools/descriptors/web-search.d.ts +1 -1
  45. package/dist/tools/impl/_search-budget.d.ts +1 -1
  46. package/dist/tools/impl/_web-fetch-net.d.ts +6 -6
  47. package/dist/tools/impl/_web-search-assembler.d.ts +7 -20
  48. package/dist/tools/impl/background-task-runtime.d.ts +13 -13
  49. package/dist/tools/impl/bash.d.ts +3 -3
  50. package/dist/tools/impl/monitor.d.ts +2 -2
  51. package/dist/toolsearch/exposure.d.ts +3 -3
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/web/fetchable-url.d.ts +1 -1
  55. package/dist/web/preapproved-hosts.d.ts +9 -31
  56. package/dist/workflows/store.d.ts +23 -31
  57. package/package.json +4 -4
@@ -732,43 +732,59 @@ function isExactWebFetchDomainRule(rule, hostname) {
732
732
  }
733
733
  var SKILL_RULE_TOOL = "Skill";
734
734
  var FIELD_VALUE = /^([A-Za-z_][A-Za-z0-9_]*):(.*)$/s;
735
- function isEscapedAt(s, t) {
736
- let backslashes = 0;
737
- let i = t - 1;
738
- while (i >= 0 && s[i] === "\\") {
739
- backslashes++;
740
- i--;
741
- }
742
- return backslashes % 2 !== 0;
743
- }
744
- function firstUnescaped(s, ch) {
745
- for (let t = 0;t < s.length; t++)
746
- if (s[t] === ch && !isEscapedAt(s, t))
747
- return t;
748
- return -1;
735
+ function splitRuleText(text) {
736
+ const { opens, closes } = unescapedParens(text);
737
+ const open = opens[0];
738
+ if (open === undefined || open === 0)
739
+ return;
740
+ const close = closes[closes.length - 1];
741
+ if (close === undefined || close !== text.length - 1 || close <= open)
742
+ return;
743
+ return { toolName: text.slice(0, open), rawContent: text.slice(open + 1, close) };
749
744
  }
750
- function lastUnescaped(s, ch) {
751
- for (let t = s.length - 1;t >= 0; t--)
752
- if (s[t] === ch && !isEscapedAt(s, t))
753
- return t;
754
- return -1;
745
+ function unescapedParens(text) {
746
+ const opens = [];
747
+ const closes = [];
748
+ let backslashRun = 0;
749
+ for (let i = 0;i < text.length; i++) {
750
+ const ch = text[i];
751
+ if (ch === "\\") {
752
+ backslashRun++;
753
+ continue;
754
+ }
755
+ if (backslashRun % 2 === 0) {
756
+ if (ch === "(")
757
+ opens.push(i);
758
+ else if (ch === ")")
759
+ closes.push(i);
760
+ }
761
+ backslashRun = 0;
762
+ }
763
+ return { opens, closes };
755
764
  }
756
765
  function unescapeRuleContent(raw) {
757
- return raw.replaceAll("\\(", "(").replaceAll("\\)", ")").replaceAll("\\\\", "\\");
766
+ let out = "";
767
+ for (let i = 0;i < raw.length; i++) {
768
+ const ch = raw[i];
769
+ const next = raw[i + 1];
770
+ if (ch === "\\" && (next === "(" || next === ")" || next === "\\")) {
771
+ out += next;
772
+ i++;
773
+ } else {
774
+ out += ch;
775
+ }
776
+ }
777
+ return out;
758
778
  }
759
779
  function parseRule(raw) {
760
780
  const trimmed = raw.trim();
761
781
  const bareFallback = () => ({ toolName: trimmed, isBareEquivalent: true });
762
- const openIdx = firstUnescaped(trimmed, "(");
763
- if (openIdx === -1)
764
- return bareFallback();
765
- const toolName = trimmed.slice(0, openIdx);
766
- if (toolName === "" || /\s/.test(toolName))
782
+ const parts = splitRuleText(trimmed);
783
+ if (parts === undefined)
767
784
  return bareFallback();
768
- const closeIdx = lastUnescaped(trimmed, ")");
769
- if (closeIdx === -1 || closeIdx <= openIdx || closeIdx !== trimmed.length - 1)
785
+ const { toolName, rawContent } = parts;
786
+ if (/\s/.test(toolName))
770
787
  return bareFallback();
771
- const rawContent = trimmed.slice(openIdx + 1, closeIdx);
772
788
  if (rawContent === "" || rawContent === "*") {
773
789
  return { toolName, specifier: { kind: "wildcardAll" }, isBareEquivalent: true };
774
790
  }
@@ -870,139 +886,67 @@ function matchesRule(rule, call, opts) {
870
886
  }
871
887
  }
872
888
  }
873
- var SUE_FILE_PATTERN_TOOLS = new Set(["Read", "Write", "Edit", "Glob", "NotebookRead", "NotebookEdit", "Cd"]);
874
- var SUE_BASH_PREFIX_TOOLS = new Set(["Bash"]);
875
- function sueCustomValidator(toolName) {
876
- if (toolName === "WebSearch") {
877
- return (content) => content.includes("*") || content.includes("?") ? { valid: false, error: "WebSearch does not support wildcards", suggestion: "Use exact search terms without * or ?" } : { valid: true };
878
- }
879
- if (toolName === "WebFetch") {
880
- return (content) => {
881
- if (content.includes("://") || content.startsWith("http")) {
882
- return { valid: false, error: "WebFetch permissions use domain format, not URLs", suggestion: 'Use "domain:hostname" format' };
883
- }
884
- if (!content.startsWith("domain:")) {
885
- return { valid: false, error: 'WebFetch permissions must use "domain:" prefix', suggestion: 'Use "domain:hostname" format' };
886
- }
887
- return { valid: true };
888
- };
889
- }
890
- return;
891
- }
892
- function sueCountUnescaped(s, ch) {
893
- let count = 0;
894
- for (let i = 0;i < s.length; i++)
895
- if (s[i] === ch && !isEscapedAt(s, i))
896
- count++;
897
- return count;
898
- }
899
- function sueHasUnescapedEmptyParens(s) {
900
- for (let i = 0;i < s.length - 1; i++) {
901
- if (s[i] === "(" && s[i + 1] === ")" && !isEscapedAt(s, i))
902
- return true;
903
- }
904
- return false;
905
- }
906
- function sueParseMcpName(toolName) {
907
- const parts = toolName.split("__");
908
- const first = parts[0];
909
- const serverName = parts[1];
910
- if (first !== "mcp" || !serverName)
911
- return null;
912
- const rest = parts.slice(2);
913
- return { serverName, ...rest.length > 0 ? { toolName: rest.join("__") } : {} };
914
- }
915
- function sueAllowWildcardScopeError(toolName) {
916
- if (!toolName.includes("*"))
917
- return null;
918
- const mcp = sueParseMcpName(toolName);
919
- if (mcp !== null && !mcp.serverName.includes("*"))
920
- return null;
921
- return {
922
- valid: false,
923
- error: `Wildcard tool name "${toolName}" is not supported in allow rules`,
924
- suggestion: "An allow pattern must name the scope it widens — globs are permitted only in the tool position after a literal mcp__<server>__ prefix. Deny and ask rules accept wildcards anywhere"
925
- };
926
- }
927
- function sueExtractToolAndContent(raw) {
928
- const trimmed = raw.trim();
929
- const openIdx = firstUnescaped(trimmed, "(");
930
- if (openIdx === -1)
931
- return { toolName: trimmed };
932
- const toolName = trimmed.slice(0, openIdx);
933
- if (toolName === "")
934
- return { toolName: trimmed };
935
- const closeIdx = lastUnescaped(trimmed, ")");
936
- if (closeIdx === -1 || closeIdx <= openIdx || closeIdx !== trimmed.length - 1)
937
- return { toolName: trimmed };
938
- const rawContent = trimmed.slice(openIdx + 1, closeIdx);
939
- if (rawContent === "" || rawContent === "*")
940
- return { toolName };
941
- return { toolName, ruleContent: unescapeRuleContent(rawContent) };
942
- }
943
889
  function validatePermissionRuleString(raw, direction) {
944
- if (!raw || raw.trim() === "")
945
- return { valid: false, error: "Permission rule cannot be empty" };
946
- const openCount = sueCountUnescaped(raw, "(");
947
- const closeCount = sueCountUnescaped(raw, ")");
948
- if (openCount !== closeCount) {
949
- return { valid: false, error: "Mismatched parentheses", suggestion: "Ensure all opening parentheses have matching closing parentheses" };
950
- }
951
- if (sueHasUnescapedEmptyParens(raw)) {
952
- const parenIdx = raw.indexOf("(");
953
- const prefix = parenIdx === -1 ? "" : raw.substring(0, parenIdx);
954
- if (!prefix)
955
- return { valid: false, error: "Empty parentheses with no tool name", suggestion: "Specify a tool name before the parentheses" };
956
- return { valid: false, error: "Empty parentheses", suggestion: `Either specify a pattern or use just "${prefix}" without parentheses` };
957
- }
958
- const { toolName, ruleContent } = sueExtractToolAndContent(raw);
959
- const mcp = sueParseMcpName(toolName);
960
- if (mcp !== null) {
961
- if (ruleContent !== undefined || sueCountUnescaped(raw, "(") > 0) {
962
- return {
963
- valid: false,
964
- error: "MCP rules do not support patterns in parentheses",
965
- suggestion: `Use "${toolName}" without parentheses, or use "mcp__${mcp.serverName}__*" for all tools`
966
- };
967
- }
968
- if (direction === "allow") {
969
- const wildcardError = sueAllowWildcardScopeError(toolName);
970
- if (wildcardError !== null)
971
- return wildcardError;
972
- }
890
+ const invalid = (error, suggestion) => suggestion === undefined ? { valid: false, error } : { valid: false, error, suggestion };
891
+ const trimmed = raw.trim();
892
+ if (trimmed === "")
893
+ return invalid("Permission rule cannot be empty");
894
+ const { opens, closes } = unescapedParens(raw);
895
+ if (opens.length !== closes.length)
896
+ return invalid("Mismatched parentheses", "Ensure all opening parentheses have matching closing parentheses");
897
+ if (opens.some((i) => raw[i + 1] === ")")) {
898
+ const prefix = raw.slice(0, raw.indexOf("("));
899
+ return prefix === "" ? invalid("Empty parentheses with no tool name", "Specify a tool name before the parentheses") : invalid("Empty parentheses", `Either specify a pattern or use just "${prefix}" without parentheses`);
900
+ }
901
+ const parts = splitRuleText(trimmed);
902
+ const toolName = parts === undefined ? trimmed : parts.toolName;
903
+ const content = parts === undefined || parts.rawContent === "" || parts.rawContent === "*" ? undefined : unescapeRuleContent(parts.rawContent);
904
+ const wildcardRefusal = () => invalid(`Wildcard tool name "${toolName}" is not supported in allow rules`, ALLOW_WILDCARD_SUGGESTION);
905
+ const nameParts = toolName.split("__");
906
+ if (nameParts[0] === "mcp" && nameParts[1]) {
907
+ const server = nameParts[1];
908
+ if (content !== undefined || opens.length > 0) {
909
+ return invalid("MCP rules do not support patterns in parentheses", `Use "${toolName}" without parentheses, or use "mcp__${server}__*" for all tools`);
910
+ }
911
+ if (direction === "allow" && server.includes("*"))
912
+ return wildcardRefusal();
973
913
  return { valid: true };
974
914
  }
975
- if (!toolName || toolName.length === 0)
976
- return { valid: false, error: "Tool name cannot be empty" };
977
- if (direction === "allow") {
978
- const wildcardError = sueAllowWildcardScopeError(toolName);
979
- if (wildcardError !== null)
980
- return wildcardError;
981
- }
982
- if (!toolName.includes("_") && toolName[0] !== toolName[0]?.toUpperCase()) {
983
- return { valid: false, error: "Tool names must start with uppercase", suggestion: `Use "${toolName.charAt(0).toUpperCase()}${toolName.slice(1)}"` };
984
- }
985
- const customValidator = sueCustomValidator(toolName);
986
- if (customValidator !== undefined && ruleContent !== undefined) {
987
- const result = customValidator(ruleContent);
988
- if (!result.valid)
989
- return result;
915
+ if (toolName === "")
916
+ return invalid("Tool name cannot be empty");
917
+ if (direction === "allow" && toolName.includes("*"))
918
+ return wildcardRefusal();
919
+ if (!toolName.includes("_")) {
920
+ const first = toolName[0];
921
+ const upper = first.toUpperCase();
922
+ if (first !== upper)
923
+ return invalid("Tool names must start with uppercase", `Use "${upper}${toolName.slice(1)}"`);
990
924
  }
991
- if (SUE_BASH_PREFIX_TOOLS.has(toolName) && ruleContent !== undefined) {
992
- if (ruleContent.includes(":*") && !ruleContent.endsWith(":*")) {
993
- return { valid: false, error: "The :* pattern must be at the end", suggestion: "Move :* to the end for prefix matching, or use * for wildcard matching" };
925
+ if (content !== undefined) {
926
+ if (toolName === "WebSearch" && (content.includes("*") || content.includes("?"))) {
927
+ return invalid("WebSearch does not support wildcards", "Use exact search terms without * or ?");
994
928
  }
995
- if (ruleContent === ":*") {
996
- return { valid: false, error: "Prefix cannot be empty before :*", suggestion: "Specify a command prefix before :*" };
929
+ if (toolName === "WebFetch") {
930
+ if (content.includes("://") || content.startsWith("http"))
931
+ return invalid("WebFetch permissions use domain format, not URLs", 'Use "domain:hostname" format');
932
+ if (!content.startsWith("domain:"))
933
+ return invalid('WebFetch permissions must use "domain:" prefix', 'Use "domain:hostname" format');
997
934
  }
998
- }
999
- if (SUE_FILE_PATTERN_TOOLS.has(toolName) && ruleContent !== undefined) {
1000
- if (ruleContent.includes(":*")) {
1001
- return { valid: false, error: 'The ":*" syntax is only for Bash prefix rules', suggestion: 'Use glob patterns like "*" or "**" for file matching' };
935
+ if (toolName === "Bash") {
936
+ if (content.includes(":*") && !content.endsWith(":*")) {
937
+ return invalid("The :* pattern must be at the end", "Move :* to the end for prefix matching, or use * for wildcard matching");
938
+ }
939
+ if (content === ":*")
940
+ return invalid("Prefix cannot be empty before :*", "Specify a command prefix before :*");
941
+ }
942
+ if (LOAD_TIME_FILE_PATTERN_TOOLS.has(toolName) && content.includes(":*")) {
943
+ return invalid('The ":*" syntax is only for Bash prefix rules', 'Use glob patterns like "*" or "**" for file matching');
1002
944
  }
1003
945
  }
1004
946
  return { valid: true };
1005
947
  }
948
+ var ALLOW_WILDCARD_SUGGESTION = "An allow pattern must name the scope it widens — globs are permitted only in the tool position after a literal mcp__<server>__ prefix. Deny and ask rules accept wildcards anywhere";
949
+ var LOAD_TIME_FILE_PATTERN_TOOLS = new Set(["Read", "Write", "Edit", "Glob", "NotebookRead", "NotebookEdit", "Cd"]);
1006
950
 
1007
951
  // src/tools/registry.ts
1008
952
  var DEFERRED_BUILTINS_CAPABILITY = "winter.deferred-builtins";
@@ -1705,37 +1649,53 @@ function resolveRealTarget(path) {
1705
1649
  return join(resolveRealTarget(dir), basename(path));
1706
1650
  }
1707
1651
  }
1708
- var MAX_SYMLINK_CHAIN_HOPS = 40;
1709
1652
  function resolveSymlinkTargetChain(path) {
1710
- let current = path;
1711
- for (let hop = 0;hop < MAX_SYMLINK_CHAIN_HOPS; hop++) {
1712
- try {
1713
- return realpathSync(current);
1714
- } catch {}
1715
- let probe = current;
1716
- const remainder = [];
1717
- let deepestReal = null;
1718
- while (deepestReal === null) {
1719
- const parent = dirname(probe);
1720
- if (parent === probe)
1721
- return;
1722
- remainder.unshift(basename(probe));
1723
- probe = parent;
1724
- try {
1725
- deepestReal = realpathSync(probe);
1726
- } catch {}
1727
- }
1728
- const firstUnresolved = join(deepestReal, remainder[0]);
1729
- let linkValue = null;
1730
- try {
1731
- linkValue = readlinkSync(firstUnresolved);
1732
- } catch {}
1733
- if (linkValue === null)
1734
- return join(deepestReal, ...remainder);
1735
- current = join(resolve(dirname(firstUnresolved), linkValue), ...remainder.slice(1));
1653
+ return followChainFrom(path, MAX_HAND_FOLLOWED_LINKS);
1654
+ }
1655
+ var MAX_HAND_FOLLOWED_LINKS = 40;
1656
+ function followChainFrom(current, followsLeft) {
1657
+ const real = realpathOrUndefined(current);
1658
+ if (real !== undefined)
1659
+ return real;
1660
+ const split = splitAtDeepestResolvableAncestor(current);
1661
+ if (split === undefined)
1662
+ return;
1663
+ const { realAncestor, nextName, remainingNames } = split;
1664
+ const linkTarget = readlinkOrUndefined(join(realAncestor, nextName));
1665
+ if (linkTarget === undefined)
1666
+ return join(realAncestor, nextName, ...remainingNames);
1667
+ if (followsLeft <= 1)
1668
+ return;
1669
+ return followChainFrom(join(resolve(realAncestor, linkTarget), ...remainingNames), followsLeft - 1);
1670
+ }
1671
+ function splitAtDeepestResolvableAncestor(path) {
1672
+ const lineage = [path];
1673
+ for (let parent = dirname(path);parent !== lineage[lineage.length - 1]; parent = dirname(parent)) {
1674
+ lineage.push(parent);
1675
+ }
1676
+ for (let depth = 1;depth < lineage.length; depth++) {
1677
+ const realAncestor = realpathOrUndefined(lineage[depth]);
1678
+ if (realAncestor === undefined)
1679
+ continue;
1680
+ const leafward = lineage.slice(0, depth).map((member) => basename(member));
1681
+ return { realAncestor, nextName: leafward[depth - 1], remainingNames: leafward.slice(0, depth - 1).reverse() };
1736
1682
  }
1737
1683
  return;
1738
1684
  }
1685
+ function realpathOrUndefined(path) {
1686
+ try {
1687
+ return realpathSync(path);
1688
+ } catch {
1689
+ return;
1690
+ }
1691
+ }
1692
+ function readlinkOrUndefined(path) {
1693
+ try {
1694
+ return readlinkSync(path);
1695
+ } catch {
1696
+ return;
1697
+ }
1698
+ }
1739
1699
  function resolveSymlinkEnds(path) {
1740
1700
  return { target: resolveRealTarget(path), chainTarget: resolveSymlinkTargetChain(path) };
1741
1701
  }
@@ -1752,7 +1712,7 @@ function checkSymlinkBothEnds(path, matcher) {
1752
1712
 
1753
1713
  // src/permissions/file-rules.ts
1754
1714
  import { realpathSync as realpathSync2 } from "node:fs";
1755
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative, sep } from "node:path";
1715
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative } from "node:path";
1756
1716
  import ignoreFactory from "ignore";
1757
1717
  function fileRuleKindFor(toolName) {
1758
1718
  switch (toolName) {
@@ -1773,20 +1733,14 @@ function canonicalFileRuleAuthoringToolName(kind) {
1773
1733
  }
1774
1734
  var INERT_ANCHOR = Symbol("file-rule-inert-anchor");
1775
1735
  function resolveFileRuleAnchor(pattern, opts) {
1776
- if (pattern.startsWith("//")) {
1736
+ if (pattern.startsWith("//"))
1777
1737
  return { relativePattern: pattern.slice(1), root: "/" };
1778
- }
1779
- if (pattern.startsWith("~/")) {
1738
+ if (pattern.startsWith("~/"))
1780
1739
  return { relativePattern: pattern.slice(1), root: opts.home };
1781
- }
1782
- if (pattern.startsWith("/")) {
1783
- if (opts.sourceDir === undefined)
1784
- return { relativePattern: pattern, root: INERT_ANCHOR };
1785
- return { relativePattern: pattern, root: opts.sourceDir };
1786
- }
1787
- if (pattern.startsWith("./")) {
1740
+ if (pattern.startsWith("/"))
1741
+ return { relativePattern: pattern, root: opts.sourceDir ?? INERT_ANCHOR };
1742
+ if (pattern.startsWith("./"))
1788
1743
  return { relativePattern: pattern.slice(2), root: null };
1789
- }
1790
1744
  return { relativePattern: pattern, root: null };
1791
1745
  }
1792
1746
  var RULE_PATH_GLOB_CHARS = /[*?[\]]/;
@@ -1853,36 +1807,19 @@ function escapeRegexLiteralPath(path) {
1853
1807
  return path.replace(/[.^$+{}()|\\[\]*?]/g, "\\$&");
1854
1808
  }
1855
1809
  function isSuspiciousRealpathResolution(original, resolved) {
1856
- const r = normalize2(original);
1857
- const o = normalize2(resolved);
1858
- if (o === r)
1859
- return false;
1860
- if (r.startsWith("/tmp/") && o === "/private" + r)
1810
+ const written = normalize2(original);
1811
+ const landed = normalize2(resolved);
1812
+ const spellings = acceptableSpellingsOf(written);
1813
+ if (spellings.includes(landed))
1861
1814
  return false;
1862
- if (r.startsWith("/var/") && o === "/private" + r)
1863
- return false;
1864
- if (r.startsWith("/private/tmp/") && o === r)
1865
- return false;
1866
- if (r.startsWith("/private/var/") && o === r)
1867
- return false;
1868
- if (o === "/")
1869
- return true;
1870
- if (o.split("/").filter(Boolean).length <= 1)
1871
- return true;
1872
- if (r.startsWith(o + "/"))
1873
- return true;
1874
- let p = r;
1875
- if (r.startsWith("/tmp/"))
1876
- p = "/private" + r;
1877
- else if (r.startsWith("/var/"))
1878
- p = "/private" + r;
1879
- if (p !== r && p.startsWith(o + "/"))
1880
- return true;
1881
- const linkUnderOriginal = o.startsWith(r + "/");
1882
- const linkUnderPrivateAlias = p !== r && o.startsWith(p + "/");
1883
- if (o !== r && !(p !== r && o === p) && !linkUnderOriginal && !linkUnderPrivateAlias)
1884
- return true;
1885
- return false;
1815
+ const shallow = landed.split("/").filter((segment) => segment !== "").length <= 1;
1816
+ const climbedAbove = spellings.some((spelling) => spelling.startsWith(landed + "/"));
1817
+ const wentBelow = spellings.some((spelling) => landed.startsWith(spelling + "/"));
1818
+ return shallow || climbedAbove || !wentBelow;
1819
+ }
1820
+ function acceptableSpellingsOf(path) {
1821
+ const aliased = path.startsWith("/tmp/") || path.startsWith("/var/");
1822
+ return aliased ? [path, "/private" + path] : [path];
1886
1823
  }
1887
1824
  function guardedCanonicalize(path) {
1888
1825
  try {
@@ -1901,19 +1838,81 @@ function canonicalizedGlobFixedPrefix(absoluteGlob) {
1901
1838
  return guardedCanonicalize(scan.literal.slice(0, scan.lastSepLiteralLength));
1902
1839
  }
1903
1840
  function ancestorDirectoriesOf(path) {
1904
- const out = [];
1905
- let current = dirname2(path);
1906
- while (current !== "/" && current !== ".") {
1907
- out.push(current);
1841
+ const ancestors = [];
1842
+ let current = path;
1843
+ for (;; ) {
1908
1844
  const parent = dirname2(current);
1909
- if (parent === current)
1910
- break;
1845
+ if (parent === current || parent === "/" || parent === ".")
1846
+ return ancestors;
1847
+ ancestors.push(parent);
1911
1848
  current = parent;
1912
1849
  }
1913
- return out;
1914
1850
  }
1915
- function poVerbatim(glob) {
1916
- return "^" + glob.replace(/[.^$+{}()|\\]/g, "\\$&").replace(/\[([^\]]*?)$/g, "\\[$1").replace(/\*\*\//g, "__GLOBSTAR_SLASH__").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/__GLOBSTAR_SLASH__/g, "(.*/)?").replace(/__GLOBSTAR__/g, ".*") + "$";
1851
+ function globToAnchoredRegex(glob) {
1852
+ const literalBracketAt = glob.indexOf("[", glob.lastIndexOf("]") + 1);
1853
+ let body = "";
1854
+ let wordRun = "";
1855
+ const emit = (rendered) => {
1856
+ if (wordRun !== "") {
1857
+ body += renderWordRun(wordRun);
1858
+ wordRun = "";
1859
+ }
1860
+ body += rendered;
1861
+ };
1862
+ let i = 0;
1863
+ while (i < glob.length) {
1864
+ const ch = glob.charAt(i);
1865
+ if (ch === "*") {
1866
+ let runEnd = i;
1867
+ while (glob.charAt(runEnd) === "*")
1868
+ runEnd++;
1869
+ let stars = runEnd - i;
1870
+ const intoDirectory = stars >= 2 && glob.charAt(runEnd) === "/";
1871
+ if (intoDirectory)
1872
+ stars -= 2;
1873
+ for (let pair = 0;pair < Math.floor(stars / 2); pair++)
1874
+ wordRun += GLOBSTAR_SPELLING;
1875
+ if (stars % 2 === 1)
1876
+ emit(SINGLE_SEGMENT_REGEX);
1877
+ if (intoDirectory)
1878
+ wordRun += GLOBSTAR_SLASH_SPELLING;
1879
+ i = intoDirectory ? runEnd + 1 : runEnd;
1880
+ continue;
1881
+ }
1882
+ if (WORD_RUN_CHARACTERS.has(ch))
1883
+ wordRun += ch;
1884
+ else
1885
+ emit(renderGlobCharacter(ch, i === literalBracketAt));
1886
+ i++;
1887
+ }
1888
+ emit("");
1889
+ return "^" + body + "$";
1890
+ }
1891
+ var SINGLE_SEGMENT_REGEX = "[^/]*";
1892
+ var GLOBSTAR_SPELLING = "__GLOBSTAR__";
1893
+ var GLOBSTAR_SLASH_SPELLING = "__GLOBSTAR_SLASH__";
1894
+ var WORD_RUN_CHARACTERS = new Set(GLOBSTAR_SPELLING + GLOBSTAR_SLASH_SPELLING);
1895
+ var ESCAPED_REGEX_CHARACTERS = new Set(".^$+{}()|\\");
1896
+ function renderGlobCharacter(ch, isLiteralBracket) {
1897
+ if (ESCAPED_REGEX_CHARACTERS.has(ch))
1898
+ return "\\" + ch;
1899
+ if (ch === "?")
1900
+ return "[^/]";
1901
+ if (ch === "[" && isLiteralBracket)
1902
+ return "\\[";
1903
+ return ch;
1904
+ }
1905
+ function renderWordRun(spelling) {
1906
+ return expandOccurrences(spelling, GLOBSTAR_SLASH_SPELLING, "(.*/)?", (stretch) => expandOccurrences(stretch, GLOBSTAR_SPELLING, ".*", (plain) => plain));
1907
+ }
1908
+ function expandOccurrences(text, needle, expansion, between) {
1909
+ let out = "";
1910
+ let from = 0;
1911
+ for (let hit = text.indexOf(needle);hit !== -1; hit = text.indexOf(needle, from)) {
1912
+ out += between(text.slice(from, hit)) + expansion;
1913
+ from = hit + needle.length;
1914
+ }
1915
+ return out + between(text.slice(from));
1917
1916
  }
1918
1917
  function globToSbplRegexSource(absoluteGlob) {
1919
1918
  const scan = scanDenyPathGlob(absoluteGlob);
@@ -1921,12 +1920,11 @@ function globToSbplRegexSource(absoluteGlob) {
1921
1920
  return "^" + escapeRegexLiteralPath(guardedCanonicalize(scan.literal)) + "$";
1922
1921
  const canonicalPrefix = canonicalizedGlobFixedPrefix(absoluteGlob);
1923
1922
  if (canonicalPrefix === undefined)
1924
- return poVerbatim(absoluteGlob);
1925
- return "^" + escapeRegexLiteralPath(canonicalPrefix) + poVerbatim(absoluteGlob.slice(scan.lastSepRaw)).slice(1);
1923
+ return globToAnchoredRegex(absoluteGlob);
1924
+ return "^" + escapeRegexLiteralPath(canonicalPrefix) + globToAnchoredRegex(absoluteGlob.slice(scan.lastSepRaw)).slice(1);
1926
1925
  }
1927
1926
  function recursiveGlobToSbplRegexSource(absoluteGlob) {
1928
- const base = globToSbplRegexSource(absoluteGlob);
1929
- return base.slice(0, -1) + "(/.*)?$";
1927
+ return globToSbplRegexSource(absoluteGlob).replace(/\$$/, "(/.*)?$");
1930
1928
  }
1931
1929
  function splitDenyPathsByGlobShape(paths) {
1932
1930
  const plain = [];
@@ -1956,18 +1954,37 @@ function globDenyEntriesOf(paths) {
1956
1954
  }
1957
1955
  function normalizeFileRulePattern(relativePattern) {
1958
1956
  const collapsed = relativePattern.replace(/\/{2,}/g, "/");
1959
- if (/^\s*(?:\/\*\*)?$/.test(collapsed))
1960
- return collapsed;
1961
- return collapsed.replace(/^([!#]?)/, (_m, directive) => directive ? "\\" + directive : "").replace(/^/, "[\uFEFF]");
1957
+ switch (leadingBomTreatment(collapsed)) {
1958
+ case "keep":
1959
+ return collapsed;
1960
+ case "escape-directive":
1961
+ return "\\" + collapsed.slice(1);
1962
+ case "bracket-second-bom":
1963
+ return `[${BYTE_ORDER_MARK}]` + collapsed.slice(2);
1964
+ case "drop":
1965
+ return collapsed.slice(1);
1966
+ }
1967
+ }
1968
+ var BYTE_ORDER_MARK = "\uFEFF";
1969
+ function leadingBomTreatment(pattern) {
1970
+ if (/^\s*(?:\/\*\*)?$/.test(pattern) || !pattern.startsWith(BYTE_ORDER_MARK))
1971
+ return "keep";
1972
+ const next = pattern.charAt(1);
1973
+ if (next === "!" || next === "#")
1974
+ return "escape-directive";
1975
+ return next === BYTE_ORDER_MARK ? "bracket-second-bom" : "drop";
1962
1976
  }
1963
1977
  function unanchorTrailingDoubleStar(pattern, isAllow) {
1964
1978
  if (!pattern.endsWith("/**"))
1965
1979
  return pattern;
1966
- const stripped = pattern.slice(0, -3);
1967
- if (/[^/]/.test(stripped)) {
1968
- return stripped.includes("/") || !isAllow || /^[!#]/.test(stripped) ? stripped : "/" + stripped;
1969
- }
1970
- return "/**";
1980
+ const rest = pattern.slice(0, -3);
1981
+ if (/^\/*$/.test(rest))
1982
+ return "/**";
1983
+ if (!isAllow)
1984
+ return rest;
1985
+ const singleSegment = !rest.includes("/");
1986
+ const directive = rest.startsWith("!") || rest.startsWith("#");
1987
+ return singleSegment && !directive ? "/" + rest : rest;
1971
1988
  }
1972
1989
 
1973
1990
  class FileRuleCompileError extends Error {
@@ -1976,90 +1993,94 @@ class FileRuleCompileError extends Error {
1976
1993
  this.name = "FileRuleCompileError";
1977
1994
  }
1978
1995
  }
1979
- var INVALID_RELATIVE_PATH = /^\.{0,2}\/|^\.{1,2}$/;
1980
- function isPathValidRelative(rel) {
1981
- return rel !== "" && !INVALID_RELATIVE_PATH.test(rel);
1982
- }
1983
1996
  function matchFileRulesGrouped(candidates, path, opts, behavior) {
1984
- if (candidates.length === 0)
1985
- return null;
1997
+ const isAllow = behavior === "allow";
1986
1998
  const groups = new Map;
1987
- const groupOrder = [];
1988
1999
  for (const candidate of candidates) {
1989
2000
  const anchor = resolveFileRuleAnchor(candidate.pattern, { home: opts.home, sourceDir: candidate.sourceDir });
1990
2001
  if (anchor.root === INERT_ANCHOR)
1991
2002
  continue;
1992
- const rootKey = anchor.root ?? opts.cwd;
1993
- const normalized = normalizeFileRulePattern(anchor.relativePattern);
1994
- const compiled = unanchorTrailingDoubleStar(normalized, behavior === "allow");
1995
- let group = groups.get(rootKey);
2003
+ const root = anchor.root ?? opts.cwd;
2004
+ const compiled = unanchorTrailingDoubleStar(normalizeFileRulePattern(anchor.relativePattern), isAllow);
2005
+ let group = groups.get(root);
1996
2006
  if (group === undefined) {
1997
- group = { byCompiledPattern: new Map, ig: ignoreFactory({ ignorecase: true }) };
1998
- groups.set(rootKey, group);
1999
- groupOrder.push(rootKey);
2007
+ group = { matcher: ignoreFactory({ ignorecase: true }), entryByPattern: new Map };
2008
+ groups.set(root, group);
2000
2009
  }
2001
- group.byCompiledPattern.set(compiled, candidate.entry);
2002
- group.ig.add(compiled);
2010
+ group.matcher.add(compiled);
2011
+ group.entryByPattern.set(compiled, candidate.entry);
2003
2012
  }
2004
- for (const rootKey of groupOrder) {
2005
- const group = groups.get(rootKey);
2006
- const rel = relative(rootKey, path);
2007
- if (!isPathValidRelative(rel))
2013
+ for (const [root, group] of groups) {
2014
+ const rel = relative(root, path);
2015
+ if (isOutsideIgnoreRoot(rel))
2008
2016
  continue;
2009
2017
  let result;
2010
2018
  try {
2011
- result = group.ig.test(rel);
2012
- } catch (cause) {
2013
- throw new FileRuleCompileError(`a file-rule pattern under ${JSON.stringify(rootKey)} failed to compile`, { cause });
2014
- }
2015
- if (result.ignored && result.rule) {
2016
- const winner = group.byCompiledPattern.get(result.rule.pattern);
2017
- if (winner !== undefined)
2018
- return winner;
2019
+ result = group.matcher.test(rel);
2020
+ } catch (err) {
2021
+ throw new FileRuleCompileError(`a file-rule pattern under ${JSON.stringify(root)} failed to compile`, { cause: err });
2019
2022
  }
2023
+ if (!result.ignored)
2024
+ continue;
2025
+ const matchedPattern = result.rule?.pattern;
2026
+ if (matchedPattern !== undefined && group.entryByPattern.has(matchedPattern))
2027
+ return group.entryByPattern.get(matchedPattern);
2020
2028
  }
2021
2029
  return null;
2022
2030
  }
2023
- var TRUSTED_SYMLINK_CANDIDATES = [
2024
- ["/private/tmp", "/tmp"],
2025
- ["/private/var", "/var"],
2026
- ["/private/etc", "/etc"],
2027
- ["/usr/bin", "/bin"],
2028
- ["/usr/lib", "/lib"],
2029
- ["/usr/sbin", "/sbin"]
2030
- ];
2031
- var cachedTrustedSymlinkEquivalences;
2032
- function trustedSymlinkEquivalences() {
2033
- if (cachedTrustedSymlinkEquivalences !== undefined)
2034
- return cachedTrustedSymlinkEquivalences;
2035
- const result = new Map;
2036
- for (const [real, alias] of TRUSTED_SYMLINK_CANDIDATES) {
2037
- try {
2038
- if (realpathSync2(alias) === real)
2039
- result.set(real, alias);
2040
- } catch {}
2041
- }
2042
- cachedTrustedSymlinkEquivalences = result;
2043
- return result;
2031
+ function isOutsideIgnoreRoot(rel) {
2032
+ return rel === "" || rel === "." || rel === ".." || rel.startsWith("/") || rel.startsWith("./") || rel.startsWith("../");
2044
2033
  }
2045
2034
  function canonicalizeTrustedSymlinkPath(path) {
2046
- for (const [real, alias] of trustedSymlinkEquivalences()) {
2047
- if (path === real || path.startsWith(real + sep))
2035
+ for (const { real, alias } of verifiedTrustedSymlinkPairs()) {
2036
+ if (path === real)
2037
+ return alias;
2038
+ if (path.startsWith(real + "/"))
2048
2039
  return alias + path.slice(real.length);
2049
2040
  }
2050
2041
  return path;
2051
2042
  }
2043
+ var TRUSTED_SYMLINK_PAIRS = [
2044
+ { real: "/private/tmp", alias: "/tmp" },
2045
+ { real: "/private/var", alias: "/var" },
2046
+ { real: "/private/etc", alias: "/etc" },
2047
+ { real: "/usr/bin", alias: "/bin" },
2048
+ { real: "/usr/lib", alias: "/lib" },
2049
+ { real: "/usr/sbin", alias: "/sbin" }
2050
+ ];
2051
+ var trustedSymlinkPairsCache;
2052
+ function verifiedTrustedSymlinkPairs() {
2053
+ if (trustedSymlinkPairsCache === undefined) {
2054
+ trustedSymlinkPairsCache = TRUSTED_SYMLINK_PAIRS.filter(({ real, alias }) => {
2055
+ try {
2056
+ return realpathSync2(alias) === real;
2057
+ } catch {
2058
+ return false;
2059
+ }
2060
+ });
2061
+ }
2062
+ return trustedSymlinkPairsCache;
2063
+ }
2052
2064
  function isPathWithinRoot(childPath, rootPath, opts = {}) {
2053
2065
  const caseFold = opts.caseFold ?? true;
2054
- const alias = (p) => p.replace(/^\/private\/var\//, "/var/").replace(/^\/private\/tmp(\/|$)/, "/tmp$1");
2055
- const fold = (p) => caseFold ? p.toLowerCase() : p;
2056
- const rel = relative(fold(alias(rootPath)), fold(alias(childPath)));
2066
+ const prepare = (p) => {
2067
+ const short = shortSystemAlias(p);
2068
+ return caseFold ? short.toLowerCase() : short;
2069
+ };
2070
+ const rel = relative(prepare(rootPath), prepare(childPath));
2057
2071
  if (rel === "")
2058
2072
  return true;
2059
- if (rel === ".." || rel.startsWith(`..${sep}`))
2073
+ if (rel === ".." || rel.startsWith("../"))
2060
2074
  return false;
2061
2075
  return !isAbsolute2(rel);
2062
2076
  }
2077
+ function shortSystemAlias(p) {
2078
+ if (p.startsWith("/private/var/"))
2079
+ return "/var/" + p.slice("/private/var/".length);
2080
+ if (p === "/private/tmp" || p.startsWith("/private/tmp/"))
2081
+ return "/tmp" + p.slice("/private/tmp".length);
2082
+ return p;
2083
+ }
2063
2084
  function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
2064
2085
  if (candidatePath.includes("\\"))
2065
2086
  return false;
@@ -2079,8 +2100,16 @@ function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
2079
2100
  return !isAbsolute2(rel);
2080
2101
  }
2081
2102
  function escapeFileRulePathSegment(path) {
2082
- const bracketsAndStar = path.replace(/[[\]*\\]/g, (ch) => "\\" + ch);
2083
- return bracketsAndStar.replace(/\s+$/, (run) => Array.from(run, (ch) => "\\" + ch).join(""));
2103
+ const escaped = path.replace(/[[\]*\\]/g, "\\$&");
2104
+ let start = escaped.length;
2105
+ while (start > 0 && /\s/.test(escaped[start - 1]))
2106
+ start--;
2107
+ if (start === escaped.length)
2108
+ return escaped;
2109
+ let tail = "";
2110
+ for (const ch of escaped.slice(start))
2111
+ tail += "\\" + ch;
2112
+ return escaped.slice(0, start) + tail;
2084
2113
  }
2085
2114
  function matchesSingleFileRulePattern(pattern, path, opts, behavior) {
2086
2115
  return matchFileRulesGrouped([{ entry: true, pattern }], path, opts, behavior) !== null;
@@ -2120,113 +2149,120 @@ function canonicalizePath(p) {
2120
2149
  }
2121
2150
  }
2122
2151
  var canon = canonicalizePath;
2152
+ var subpathClause = (p) => `(subpath "${sbplString(p)}")`;
2153
+ var literalClause = (p) => `(literal "${sbplString(p)}")`;
2154
+ var regexClause = (source) => `(regex #"${escapeSbplRegexDelimiter(source)}")`;
2155
+
2156
+ class ClauseBlock {
2157
+ seen = new Set;
2158
+ clauses = [];
2159
+ add(clause) {
2160
+ if (this.seen.has(clause))
2161
+ return;
2162
+ this.seen.add(clause);
2163
+ this.clauses.push(clause);
2164
+ }
2165
+ render(header) {
2166
+ if (this.clauses.length === 0)
2167
+ return "";
2168
+ const lines = this.clauses.map((c) => ` ${c}`);
2169
+ lines[lines.length - 1] += ")";
2170
+ return [header, ...lines].join(`
2171
+ `);
2172
+ }
2173
+ }
2123
2174
  function buildAncestorRenameBypassBlock(plainDenyPaths, globFixedPrefixes) {
2124
- const clauses = new Set;
2125
- for (const path of plainDenyPaths) {
2126
- const canonical = canon(path);
2127
- clauses.add(`(subpath "${sbplString(canonical)}")`);
2128
- for (const ancestor of ancestorDirectoriesOf(canonical))
2129
- clauses.add(`(literal "${sbplString(ancestor)}")`);
2175
+ const block = new ClauseBlock;
2176
+ for (const entry of plainDenyPaths) {
2177
+ const path = canon(entry);
2178
+ block.add(subpathClause(path));
2179
+ for (const dir of ancestorDirectoriesOf(path))
2180
+ block.add(literalClause(dir));
2130
2181
  }
2131
2182
  for (const prefix of globFixedPrefixes) {
2132
- clauses.add(`(literal "${sbplString(prefix)}")`);
2133
- for (const ancestor of ancestorDirectoriesOf(prefix))
2134
- clauses.add(`(literal "${sbplString(ancestor)}")`);
2183
+ block.add(literalClause(prefix));
2184
+ for (const dir of ancestorDirectoriesOf(prefix))
2185
+ block.add(literalClause(dir));
2135
2186
  }
2136
- if (clauses.size === 0)
2137
- return "";
2138
- return [`(deny file-write-unlink file-write-create`, ...[...clauses].map((c) => ` ${c}`)].join(`
2139
- `) + ")";
2187
+ return block.render("(deny file-write-unlink file-write-create");
2140
2188
  }
2141
2189
  function buildReadDenyWritePermitBlock(writableRoots) {
2142
- if (writableRoots.length === 0)
2143
- return "";
2144
- const clauses = new Set(writableRoots.map((r) => `(subpath "${sbplString(r)}")`));
2145
- return [`(allow file-write-unlink file-write-create`, ...[...clauses].map((c) => ` ${c}`)].join(`
2146
- `) + ")";
2190
+ const block = new ClauseBlock;
2191
+ for (const root of writableRoots)
2192
+ block.add(subpathClause(root));
2193
+ return block.render("(allow file-write-unlink file-write-create");
2147
2194
  }
2148
2195
  var IMAGE_PREP_DIRNAME = "image-prep";
2149
2196
  var WRITE_OPS_SURVIVING_READ_DENY_REPERMIT = "file-write* file-write-unlink file-write-create";
2150
2197
  var DEFAULT_PROTECTED_FILES = [".gitconfig", ".gitmodules", ".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile", ".ripgreprc", ".mcp.json"];
2151
2198
  var DEFAULT_PROTECTED_DIRS = [".vscode", ".idea", ".claude/commands", ".claude/agents"];
2152
2199
  var PROJECT_DIR_PROTECTED_KINDS = ["commands", "agents", "skills", "rules", "output-styles"];
2153
- function cwdAnchoredEntryRegex(cwd, entry, recursive) {
2154
- const absoluteGlob = join3(cwd, "**", entry) + (recursive ? "/**" : "");
2155
- return recursiveGlobToSbplRegexSource(absoluteGlob);
2156
- }
2157
- function buildDefaultWriteProtectionEntries(cwd, brand, allowGitConfigWrites) {
2158
- const plainPaths = [];
2159
- const regexes = [];
2160
- for (const f of DEFAULT_PROTECTED_FILES) {
2161
- plainPaths.push(join3(cwd, f));
2162
- regexes.push(cwdAnchoredEntryRegex(cwd, f, false));
2163
- }
2164
- const winterMcpJson = join3(brand.projectDirName, "mcp.json");
2165
- plainPaths.push(join3(cwd, winterMcpJson));
2166
- regexes.push(cwdAnchoredEntryRegex(cwd, winterMcpJson, false));
2167
- for (const d of DEFAULT_PROTECTED_DIRS) {
2168
- plainPaths.push(join3(cwd, d));
2169
- regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
2170
- }
2171
- for (const kind of PROJECT_DIR_PROTECTED_KINDS) {
2172
- const d = join3(brand.projectDirName, kind);
2173
- plainPaths.push(join3(cwd, d));
2174
- regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
2175
- }
2176
- plainPaths.push(join3(cwd, ".git", "hooks"));
2177
- regexes.push(cwdAnchoredEntryRegex(cwd, ".git/hooks", true));
2178
- if (!allowGitConfigWrites) {
2179
- plainPaths.push(join3(cwd, ".git", "config"));
2180
- regexes.push(cwdAnchoredEntryRegex(cwd, ".git/config", false));
2181
- }
2182
- return { plainPaths, regexes };
2183
- }
2184
2200
  function buildDefaultWriteProtectionBlock(cwd, brand, allowGitConfigWrites) {
2185
- const { plainPaths, regexes } = buildDefaultWriteProtectionEntries(cwd, brand, allowGitConfigWrites);
2186
- const plainDenyClauses = plainPaths.map((p) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} (subpath "${sbplString(canon(p))}"))`).join(`
2187
- `);
2188
- const regexDenyClauses = regexes.map((r) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} (regex #"${escapeSbplRegexDelimiter(r)}"))`).join(`
2189
- `);
2190
- const ancestorFence = buildAncestorRenameBypassBlock(plainPaths, []);
2191
- return [plainDenyClauses, regexDenyClauses, ancestorFence].filter((s) => s.length > 0).join(`
2201
+ const proj = brand.projectDirName;
2202
+ const entries = [
2203
+ ...DEFAULT_PROTECTED_FILES.map((rel) => ({ rel, isDir: false })),
2204
+ { rel: `${proj}/mcp.json`, isDir: false },
2205
+ ...DEFAULT_PROTECTED_DIRS.map((rel) => ({ rel, isDir: true })),
2206
+ ...PROJECT_DIR_PROTECTED_KINDS.map((kind) => ({ rel: `${proj}/${kind}`, isDir: true })),
2207
+ { rel: ".git/hooks", isDir: true },
2208
+ ...allowGitConfigWrites ? [] : [{ rel: ".git/config", isDir: false }]
2209
+ ];
2210
+ const plainPaths = entries.map((e) => join3(cwd, e.rel));
2211
+ const regexSources = entries.map((e) => {
2212
+ const anyDepth = join3(cwd, "**", e.rel);
2213
+ return recursiveGlobToSbplRegexSource(e.isDir ? `${anyDepth}/**` : anyDepth);
2214
+ });
2215
+ const lines = [
2216
+ ...plainPaths.map((p) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} ${subpathClause(canon(p))})`),
2217
+ ...regexSources.map((r) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} ${regexClause(r)})`),
2218
+ buildAncestorRenameBypassBlock(plainPaths, [])
2219
+ ];
2220
+ return lines.join(`
2192
2221
  `);
2193
2222
  }
2194
2223
  function isProperDescendantOf(candidate, root) {
2195
- return root === "/" ? candidate !== "/" && candidate.startsWith("/") : candidate !== root && candidate.startsWith(root + "/");
2224
+ if (root === "/")
2225
+ return candidate !== "/" && candidate.startsWith("/");
2226
+ return candidate !== root && candidate.startsWith(`${root}/`);
2227
+ }
2228
+ function withCarveOuts(base, carveOuts) {
2229
+ if (carveOuts.length === 0)
2230
+ return base;
2231
+ const exclusions = carveOuts.map((x) => `(require-not ${subpathClause(x)})`).join(" ");
2232
+ return `(require-all ${base} ${exclusions})`;
2196
2233
  }
2197
2234
  function buildReadDenyKeepInPlaceBlock(plainDenyReadPaths, globDenyReadEntries, writableRoots) {
2198
2235
  if (writableRoots.length === 0)
2199
2236
  return "";
2200
- const isUnderAnyWriteRoot = (path) => writableRoots.some((root) => isProperDescendantOf(path, root));
2201
- const nestedWriteRootCarveOuts = (deniedPath) => writableRoots.filter((root) => isProperDescendantOf(root, deniedPath)).map((root) => `(subpath "${sbplString(root)}")`);
2202
- const withCarveOuts = (base, carveOuts) => carveOuts.length === 0 ? base : `(require-all ${base} ${carveOuts.map((c) => `(require-not ${c})`).join(" ")})`;
2203
- const addAncestorLiterals = (clauses, ancestors) => {
2204
- for (const ancestor of ancestors)
2205
- if (isUnderAnyWriteRoot(ancestor))
2206
- clauses.add(`(literal "${sbplString(ancestor)}")`);
2207
- };
2208
- const clauses = new Set;
2209
- for (const rawPath of plainDenyReadPaths) {
2210
- const path = canon(rawPath);
2211
- if (!isUnderAnyWriteRoot(path))
2237
+ const insideSomeRoot = (p) => writableRoots.some((root) => isProperDescendantOf(p, root));
2238
+ const block = new ClauseBlock;
2239
+ for (const entry of plainDenyReadPaths) {
2240
+ const path = canon(entry);
2241
+ if (!insideSomeRoot(path))
2212
2242
  continue;
2213
- clauses.add(withCarveOuts(`(subpath "${sbplString(path)}")`, nestedWriteRootCarveOuts(path)));
2214
- addAncestorLiterals(clauses, ancestorDirectoriesOf(path));
2243
+ const nestedRoots = writableRoots.filter((root) => isProperDescendantOf(root, path));
2244
+ block.add(withCarveOuts(subpathClause(path), nestedRoots));
2245
+ for (const dir of ancestorDirectoriesOf(path)) {
2246
+ if (insideSomeRoot(dir))
2247
+ block.add(literalClause(dir));
2248
+ }
2215
2249
  }
2216
- for (const entry of globDenyReadEntries) {
2217
- const relatesToAWriteRoot = writableRoots.some((root) => entry.fixedPrefix === root || isProperDescendantOf(entry.fixedPrefix, root) || isProperDescendantOf(root, entry.fixedPrefix));
2218
- if (!relatesToAWriteRoot)
2250
+ for (const { regex, fixedPrefix } of globDenyReadEntries) {
2251
+ const prefix = fixedPrefix;
2252
+ const relates = writableRoots.some((root) => prefix === root || isProperDescendantOf(prefix, root) || isProperDescendantOf(root, prefix));
2253
+ if (!relates)
2219
2254
  continue;
2220
- const regex = new RegExp(entry.regex);
2221
- const carveOuts = writableRoots.filter((root) => regex.test(root)).map((root) => `(subpath "${sbplString(root)}")`);
2222
- clauses.add(withCarveOuts(`(regex #"${escapeSbplRegexDelimiter(entry.regex)}")`, carveOuts));
2223
- if (entry.fixedPrefix !== "/")
2224
- addAncestorLiterals(clauses, [entry.fixedPrefix, ...ancestorDirectoriesOf(entry.fixedPrefix)]);
2255
+ const compiled = new RegExp(regex);
2256
+ const matchedRoots = writableRoots.filter((root) => compiled.test(root));
2257
+ block.add(withCarveOuts(regexClause(regex), matchedRoots));
2258
+ if (prefix !== "/") {
2259
+ for (const dir of [prefix, ...ancestorDirectoriesOf(prefix)]) {
2260
+ if (insideSomeRoot(dir))
2261
+ block.add(literalClause(dir));
2262
+ }
2263
+ }
2225
2264
  }
2226
- if (clauses.size === 0)
2227
- return "";
2228
- return [`(deny file-write-unlink`, ...[...clauses].map((c) => ` ${c}`)].join(`
2229
- `) + ")";
2265
+ return block.render("(deny file-write-unlink");
2230
2266
  }
2231
2267
  var CONTROL_PLANE_FILES = ["permissions.local.json", "settings.json", "settings.local.json"];
2232
2268
  function caseFoldSegment(segment) {
@@ -2634,23 +2670,22 @@ registerAttachmentRenderer("agent_listing_delta", (a) => {
2634
2670
  const addedLines = stringArray(a["addedLines"]);
2635
2671
  const addedTypes = stringArray(a["addedTypes"]);
2636
2672
  const removedTypes = stringArray(a["removedTypes"]);
2673
+ const initial = a["isInitial"] === true;
2637
2674
  const sections = [];
2638
2675
  const hasAdded = addedLines.length > 0 && addedTypes.length > 0;
2639
2676
  if (hasAdded)
2640
- sections.push(`${a["isInitial"] === true ? AGENT_LISTING_INITIAL_HEADER : AGENT_LISTING_ADDED_HEADER}
2677
+ sections.push(`${initial ? AGENT_LISTING_INITIAL_HEADER : AGENT_LISTING_ADDED_HEADER}
2641
2678
  ${addedLines.join(`
2642
2679
  `)}`);
2643
2680
  if (removedTypes.length > 0) {
2644
2681
  sections.push(`${AGENT_LISTING_REMOVED_HEADER}
2645
- ${removedTypes.map((t) => `- ${t}`).join(`
2682
+ ${removedTypes.map((name) => `- ${name}`).join(`
2646
2683
  `)}`);
2647
2684
  sections.push(AMBIENT_CONTEXT_SENTENCE);
2648
2685
  }
2649
- if (hasAdded && a["isInitial"] === true && a["showConcurrencyNote"] === true)
2686
+ if (hasAdded && initial && a["showConcurrencyNote"] === true)
2650
2687
  sections.push(AGENT_CONCURRENCY_SENTENCE);
2651
- if (sections.length === 0)
2652
- return;
2653
- return neutralizeReminderTags(sections.join(`
2688
+ return sections.length === 0 ? undefined : neutralizeReminderTags(sections.join(`
2654
2689
 
2655
2690
  `));
2656
2691
  });
@@ -2746,10 +2781,10 @@ function announcedAgentTypes(messages) {
2746
2781
  if (a.type !== "agent_listing_delta")
2747
2782
  continue;
2748
2783
  if (Array.isArray(a["addedLines"]))
2749
- for (const t of stringArray(a["addedTypes"]))
2750
- announced.add(t);
2751
- for (const t of stringArray(a["removedTypes"]))
2752
- announced.delete(t);
2784
+ for (const type of stringArray(a["addedTypes"]))
2785
+ announced.add(type);
2786
+ for (const type of stringArray(a["removedTypes"]))
2787
+ announced.delete(type);
2753
2788
  }
2754
2789
  return announced;
2755
2790
  }
@@ -2785,26 +2820,26 @@ function localDateString(now = new Date) {
2785
2820
  function xmlEscape(value) {
2786
2821
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2787
2822
  }
2788
- var TAG_ROOT = "task-notification";
2789
- var TAG_ORDER = [
2790
- ["task-id", "taskId"],
2791
- ["tool-use-id", "toolUseId"],
2792
- ["task-type", "taskType"],
2793
- ["output-file", "outputFile"],
2794
- ["status", "status"],
2795
- ["summary", "summary"]
2796
- ];
2797
2823
  function renderTaskNotification(fields) {
2798
- let out = `<${TAG_ROOT}>`;
2799
- for (const [tag, key] of TAG_ORDER) {
2800
- const value = fields[key];
2801
- if (typeof value !== "string" || value.length === 0)
2802
- continue;
2803
- out += `
2824
+ const tagged = [
2825
+ ["task-id", fields.taskId],
2826
+ ["tool-use-id", fields.toolUseId],
2827
+ ["task-type", fields.taskType],
2828
+ ["output-file", fields.outputFile],
2829
+ ["status", fields.status],
2830
+ ["summary", fields.summary]
2831
+ ];
2832
+ let out = "<task-notification>";
2833
+ for (const [tag, value] of tagged) {
2834
+ if (typeof value === "string" && value.length > 0)
2835
+ out += `
2804
2836
  <${tag}>${value}</${tag}>`;
2805
2837
  }
2806
- return `${out}${fields.body ?? ""}
2807
- </${TAG_ROOT}>${fields.trailing ?? ""}`;
2838
+ out += fields.body ?? "";
2839
+ out += `
2840
+ </task-notification>`;
2841
+ out += fields.trailing ?? "";
2842
+ return out;
2808
2843
  }
2809
2844
  var SYSTEM_NOTIFICATION_MARKER = "[SYSTEM NOTIFICATION - NOT USER INPUT]";
2810
2845
  var NOTIFICATION_PREAMBLE = `${SYSTEM_NOTIFICATION_MARKER}
@@ -2824,34 +2859,60 @@ function withNotificationPreamble(value, opts) {
2824
2859
  return value;
2825
2860
  return `${opts?.inHumanTurn === true ? NOTIFICATION_PREAMBLE_IN_HUMAN_TURN : NOTIFICATION_PREAMBLE}${neutralizeReminderTags(value)}`;
2826
2861
  }
2827
- function xmlStatus(status) {
2862
+ function nonEmpty(value) {
2863
+ return typeof value === "string" && value.length > 0;
2864
+ }
2865
+ function statusWord(status) {
2828
2866
  return status === "stopped" ? "killed" : status;
2829
2867
  }
2868
+ function optionalEscaped(toolUseId, outputFile) {
2869
+ return {
2870
+ ...toolUseId !== undefined ? { toolUseId: xmlEscape(toolUseId) } : {},
2871
+ ...outputFile !== undefined ? { outputFile: xmlEscape(outputFile) } : {}
2872
+ };
2873
+ }
2874
+ function usageCounters(usage) {
2875
+ return `<subagent_tokens>${String(usage.totalTokens)}</subagent_tokens><tool_uses>${String(usage.toolUses)}</tool_uses><duration_ms>${String(usage.durationMs)}</duration_ms>`;
2876
+ }
2877
+ function usageBlock(usage) {
2878
+ return `<usage>${usageCounters(usage)}</usage>`;
2879
+ }
2880
+ var AGENT_RESUME_NOTE = "<note>This notification fires each time the agent stops with no background work of its own still running, so the same task-id can notify more than once. Send it another message with SendMessage to resume it.</note>";
2830
2881
  function renderAgentNotification(input) {
2831
- const finished = input.maxTurnsReached !== undefined ? `stopped at its ${input.maxTurnsReached}-turn limit (partial result; SendMessage to task-id to continue)` : "finished";
2832
- const outcome = input.status === "completed" ? finished : input.status === "failed" ? `failed: ${input.error !== undefined && input.error.length > 0 ? input.error : "Unknown error"}` : input.stoppedBy === "parent" ? "was stopped by the assistant" : input.stoppedBy === "user" ? "was stopped by user" : "was stopped";
2833
- const result = input.finalMessage !== undefined && input.finalMessage.length > 0 ? `
2834
- <result>${xmlEscape(input.finalMessage)}</result>` : "";
2835
- const usage = input.usage !== undefined ? `
2836
- <usage><subagent_tokens>${input.usage.totalTokens}</subagent_tokens><tool_uses>${input.usage.toolUses}</tool_uses><duration_ms>${input.usage.durationMs}</duration_ms></usage>` : "";
2837
- const worktree = input.worktree !== undefined ? `
2838
- <worktree><worktreePath>${xmlEscape(input.worktree.path)}</worktreePath>${input.worktree.branch !== undefined ? `<worktreeBranch>${xmlEscape(input.worktree.branch)}</worktreeBranch>` : ""}</worktree>` : "";
2882
+ let outcome;
2883
+ if (input.status === "completed") {
2884
+ outcome = input.maxTurnsReached !== undefined ? `stopped at its ${input.maxTurnsReached}-turn limit (partial result; SendMessage to task-id to continue)` : "finished";
2885
+ } else if (input.status === "failed") {
2886
+ outcome = `failed: ${nonEmpty(input.error) ? input.error : "Unknown error"}`;
2887
+ } else {
2888
+ outcome = input.stoppedBy === "parent" ? "was stopped by the assistant" : input.stoppedBy === "user" ? "was stopped by user" : "was stopped";
2889
+ }
2890
+ let body = `
2891
+ ${AGENT_RESUME_NOTE}`;
2892
+ if (nonEmpty(input.finalMessage))
2893
+ body += `
2894
+ <result>${xmlEscape(input.finalMessage)}</result>`;
2895
+ if (input.usage !== undefined)
2896
+ body += `
2897
+ ${usageBlock(input.usage)}`;
2898
+ if (input.worktree !== undefined) {
2899
+ const branch = input.worktree.branch !== undefined ? `<worktreeBranch>${xmlEscape(input.worktree.branch)}</worktreeBranch>` : "";
2900
+ body += `
2901
+ <worktree><worktreePath>${xmlEscape(input.worktree.path)}</worktreePath>${branch}</worktree>`;
2902
+ }
2839
2903
  return renderTaskNotification({
2840
2904
  taskId: xmlEscape(input.taskId),
2841
- ...input.toolUseId !== undefined ? { toolUseId: xmlEscape(input.toolUseId) } : {},
2842
- ...input.outputFile !== undefined ? { outputFile: xmlEscape(input.outputFile) } : {},
2843
- status: xmlStatus(input.status),
2905
+ ...optionalEscaped(input.toolUseId, input.outputFile),
2906
+ status: statusWord(input.status),
2844
2907
  summary: xmlEscape(`Agent "${input.description}" ${outcome}`),
2845
- body: `
2846
- <note>This notification fires each time the agent stops with no background work of its own still running, so the same task-id can notify more than once. Send it another message with SendMessage to resume it.</note>${result}${usage}${worktree}`
2908
+ body
2847
2909
  });
2848
2910
  }
2849
2911
  function renderShellNotification(input) {
2850
2912
  return renderTaskNotification({
2851
2913
  taskId: xmlEscape(input.taskId),
2852
- ...input.toolUseId !== undefined ? { toolUseId: xmlEscape(input.toolUseId) } : {},
2853
- ...input.outputFile !== undefined ? { outputFile: xmlEscape(input.outputFile) } : {},
2854
- status: xmlStatus(input.status),
2914
+ ...optionalEscaped(input.toolUseId, input.outputFile),
2915
+ status: statusWord(input.status),
2855
2916
  summary: xmlEscape(input.summary)
2856
2917
  });
2857
2918
  }
@@ -2867,28 +2928,40 @@ function renderTaskStopNotification(input) {
2867
2928
  const who = input.stoppedBy === "parent" ? "the assistant" : "user";
2868
2929
  return renderTaskNotification({
2869
2930
  taskId: xmlEscape(input.taskId),
2870
- ...input.toolUseId !== undefined ? { toolUseId: xmlEscape(input.toolUseId) } : {},
2931
+ ...optionalEscaped(input.toolUseId, undefined),
2871
2932
  status: "stopped",
2872
2933
  summary: xmlEscape(`Task "${input.description}" was stopped by ${who}`)
2873
2934
  });
2874
2935
  }
2875
2936
  function renderWorkflowNotification(input) {
2876
- const name = xmlEscape(input.name !== undefined && input.name.length > 0 ? input.name : "Dynamic workflow");
2877
- const summary = input.status === "completed" ? `Dynamic workflow "${name}" completed` : input.status === "failed" ? `Dynamic workflow "${name}" failed: ${input.error !== undefined && input.error.length > 0 ? xmlEscape(input.error) : "Unknown error"}` : `Dynamic workflow "${name}" was stopped`;
2878
- const result = input.result !== undefined && input.result.length > 0 ? `
2879
- <result>${xmlEscape(input.result)}</result>` : "";
2880
- const failures = input.failures !== undefined && input.failures.length > 0 ? `
2937
+ const name = xmlEscape(nonEmpty(input.name) ? input.name : "Dynamic workflow");
2938
+ let summary;
2939
+ if (input.status === "completed")
2940
+ summary = `Dynamic workflow "${name}" completed`;
2941
+ else if (input.status === "failed")
2942
+ summary = `Dynamic workflow "${name}" failed: ${nonEmpty(input.error) ? xmlEscape(input.error) : "Unknown error"}`;
2943
+ else
2944
+ summary = `Dynamic workflow "${name}" was stopped`;
2945
+ let body = "";
2946
+ if (nonEmpty(input.result))
2947
+ body += `
2948
+ <result>${xmlEscape(input.result)}</result>`;
2949
+ if (input.failures !== undefined && input.failures.length > 0)
2950
+ body += `
2881
2951
  <failures>${xmlEscape(input.failures.join(`
2882
- `))}</failures>` : "";
2883
- const usage = input.usage !== undefined || input.agentCount !== undefined ? `
2884
- <usage><agent_count>${input.agentCount ?? 0}</agent_count><subagent_tokens>${input.usage?.totalTokens ?? 0}</subagent_tokens><tool_uses>${input.usage?.toolUses ?? 0}</tool_uses><duration_ms>${input.usage?.durationMs ?? 0}</duration_ms></usage>` : "";
2952
+ `))}</failures>`;
2953
+ if (input.usage !== undefined || input.agentCount !== undefined) {
2954
+ const given = input.usage ?? {};
2955
+ const usage = { totalTokens: given.totalTokens ?? 0, toolUses: given.toolUses ?? 0, durationMs: given.durationMs ?? 0 };
2956
+ body += `
2957
+ <usage><agent_count>${String(input.agentCount ?? 0)}</agent_count>${usageCounters(usage)}</usage>`;
2958
+ }
2885
2959
  return renderTaskNotification({
2886
2960
  taskId: xmlEscape(input.taskId),
2887
- ...input.toolUseId !== undefined ? { toolUseId: xmlEscape(input.toolUseId) } : {},
2888
- ...input.outputFile !== undefined ? { outputFile: xmlEscape(input.outputFile) } : {},
2889
- status: xmlStatus(input.status),
2961
+ ...optionalEscaped(input.toolUseId, input.outputFile),
2962
+ status: statusWord(input.status),
2890
2963
  summary,
2891
- body: `${result}${failures}${usage}`
2964
+ body
2892
2965
  });
2893
2966
  }
2894
2967
  var PRIORITY_RANK = { next: 0, later: 1 };
@@ -7008,8 +7081,17 @@ function isCacheableTransport(config) {
7008
7081
  }
7009
7082
  var FIRST_TURN_MCP_WAIT_DEFAULT_MS = 2000;
7010
7083
  function firstTurnMcpWaitDeadlineMs(opts) {
7011
- const explicitAsksForWait = opts.strictMcpConfig === true || Object.values(opts.explicitServers ?? {}).some((server) => server?.type !== "sdk");
7012
- return explicitAsksForWait ? opts.envConfig.timeoutMs : FIRST_TURN_MCP_WAIT_DEFAULT_MS;
7084
+ if (opts.strictMcpConfig === true)
7085
+ return opts.envConfig.timeoutMs;
7086
+ const servers = opts.explicitServers;
7087
+ if (servers !== undefined && servers !== null) {
7088
+ for (const config of Object.values(servers)) {
7089
+ const inProcess = typeof config === "object" && config !== null && config.type === "sdk";
7090
+ if (!inProcess)
7091
+ return opts.envConfig.timeoutMs;
7092
+ }
7093
+ }
7094
+ return FIRST_TURN_MCP_WAIT_DEFAULT_MS;
7013
7095
  }
7014
7096
  var sessionLifecycles = new Map;
7015
7097
  function registerSessionMcpLifecycle(sessionId, lifecycle) {
@@ -9125,66 +9207,80 @@ function resolveBuiltinAgents(opts) {
9125
9207
  }
9126
9208
 
9127
9209
  // src/subagents/definitions.ts
9128
- var FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)---\s*\n?/;
9129
- function stripLeadingBom(raw) {
9130
- return raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
9210
+ function parseFrontmatter(raw) {
9211
+ const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
9212
+ const split = splitFrontmatter(text);
9213
+ if (split === undefined)
9214
+ return { attrs: {}, body: raw };
9215
+ return { attrs: readFrontmatterBlock(split.block), body: text.slice(split.bodyStart) };
9131
9216
  }
9132
- var YAML_SPECIAL_CHARS = /[{}[\]*&#!|>%@`]|: /;
9133
- function quoteProblematicValues(frontmatterText) {
9134
- const lines = frontmatterText.split(`
9135
- `);
9136
- const result = [];
9137
- for (const line of lines) {
9138
- const match = /^([a-zA-Z_-]+):\s+(.+)$/.exec(line);
9139
- if (match) {
9140
- const key = match[1];
9141
- const value = match[2];
9142
- if (value.startsWith("[") && value.endsWith("]")) {
9143
- try {
9144
- if (Array.isArray(Bun.YAML.parse(value))) {
9145
- result.push(line);
9146
- continue;
9147
- }
9148
- } catch {}
9149
- }
9150
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
9151
- result.push(line);
9152
- continue;
9153
- }
9154
- if (YAML_SPECIAL_CHARS.test(value)) {
9155
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
9156
- result.push(`${key}: "${escaped}"`);
9157
- continue;
9158
- }
9159
- }
9160
- result.push(line);
9161
- }
9162
- return result.join(`
9163
- `);
9217
+ var WHITESPACE_CHAR = /\s/;
9218
+ function skipWhitespace(text, from) {
9219
+ let i = from;
9220
+ while (i < text.length && WHITESPACE_CHAR.test(text[i]))
9221
+ i++;
9222
+ return i;
9164
9223
  }
9165
- function quoteAndDetab(frontmatterText) {
9166
- return quoteProblematicValues(frontmatterText).replace(/^\t+/gm, (m) => " ".repeat(m.length));
9224
+ function splitFrontmatter(text) {
9225
+ if (!text.startsWith("---"))
9226
+ return;
9227
+ const runEnd = skipWhitespace(text, 3);
9228
+ const lastLf = text.lastIndexOf(`
9229
+ `, runEnd - 1);
9230
+ if (lastLf < 3)
9231
+ return;
9232
+ const blockStart = lastLf + 1;
9233
+ const close = text.indexOf("---", blockStart);
9234
+ if (close === -1)
9235
+ return;
9236
+ return { block: text.slice(blockStart, close), bodyStart: skipWhitespace(text, close + 3) };
9167
9237
  }
9168
- function parseFrontmatter(raw) {
9169
- const stripped = stripLeadingBom(raw);
9170
- const match = FRONTMATTER_REGEX.exec(stripped);
9171
- if (!match)
9172
- return { attrs: {}, body: raw };
9173
- const frontmatterText = match[1] ?? "";
9174
- const body = stripped.slice(match[0].length);
9175
- let attrs = {};
9238
+ function isPlainObject2(value) {
9239
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9240
+ }
9241
+ function readFrontmatterBlock(block) {
9242
+ let parsed;
9176
9243
  try {
9177
- const parsed = Bun.YAML.parse(frontmatterText);
9178
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
9179
- attrs = parsed;
9244
+ parsed = Bun.YAML.parse(block);
9180
9245
  } catch {
9181
9246
  try {
9182
- const parsed = Bun.YAML.parse(quoteAndDetab(frontmatterText));
9183
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
9184
- attrs = parsed;
9185
- } catch {}
9247
+ parsed = Bun.YAML.parse(detabLineStarts(quoteLooseValues(block)));
9248
+ } catch {
9249
+ return {};
9250
+ }
9186
9251
  }
9187
- return { attrs, body };
9252
+ return isPlainObject2(parsed) ? parsed : {};
9253
+ }
9254
+ var KEY_LINE_HEAD = /^([A-Za-z_-]+):(\s+)/;
9255
+ var LINE_TERMINATOR = /[\r\u2028\u2029]/;
9256
+ var YAML_SPECIAL_CHAR = /[{}[\]*&#!|>%@`]/;
9257
+ function quoteLooseValues(block) {
9258
+ return block.split(`
9259
+ `).map((line) => {
9260
+ const head = KEY_LINE_HEAD.exec(line);
9261
+ if (head === null)
9262
+ return line;
9263
+ const key = head[1];
9264
+ const value = line.slice(head[0].length);
9265
+ if (value.length === 0 || LINE_TERMINATOR.test(value))
9266
+ return line;
9267
+ if (value.startsWith("[") && value.endsWith("]")) {
9268
+ try {
9269
+ if (Array.isArray(Bun.YAML.parse(value)))
9270
+ return line;
9271
+ } catch {}
9272
+ }
9273
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
9274
+ return line;
9275
+ if (YAML_SPECIAL_CHAR.test(value) || value.includes(": ")) {
9276
+ return `${key}: "${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
9277
+ }
9278
+ return line;
9279
+ }).join(`
9280
+ `);
9281
+ }
9282
+ function detabLineStarts(text) {
9283
+ return text.replace(/^\t+/gm, (tabs) => " ".repeat(tabs.length));
9188
9284
  }
9189
9285
  function asString(v) {
9190
9286
  return typeof v === "string" ? v : undefined;
@@ -9495,67 +9591,70 @@ var PREAPPROVED_HOST_ENTRIES = [
9495
9591
  "nginx.org",
9496
9592
  "httpd.apache.org"
9497
9593
  ];
9594
+ function isPreapprovedHost(hostname, pathname) {
9595
+ if (HOSTNAME_ONLY.has(hostname))
9596
+ return true;
9597
+ const prefixes = PATH_PREFIXES.get(hostname);
9598
+ if (prefixes === undefined)
9599
+ return false;
9600
+ if (isTraversalEncoded(pathname))
9601
+ return false;
9602
+ return prefixes.some((prefix) => underPrefix(pathname, prefix));
9603
+ }
9498
9604
  var HOSTNAME_ONLY = new Set;
9499
9605
  var PATH_PREFIXES = new Map;
9500
9606
  for (const entry of PREAPPROVED_HOST_ENTRIES) {
9501
9607
  const slash = entry.indexOf("/");
9502
9608
  if (slash === -1) {
9503
9609
  HOSTNAME_ONLY.add(entry);
9504
- } else {
9505
- const host = entry.slice(0, slash);
9506
- const prefix = entry.slice(slash);
9507
- const existing = PATH_PREFIXES.get(host);
9508
- if (existing)
9509
- existing.push(prefix);
9510
- else
9511
- PATH_PREFIXES.set(host, [prefix]);
9610
+ continue;
9512
9611
  }
9612
+ const host = entry.slice(0, slash);
9613
+ const prefix = entry.slice(slash);
9614
+ const list = PATH_PREFIXES.get(host);
9615
+ if (list === undefined)
9616
+ PATH_PREFIXES.set(host, [prefix]);
9617
+ else if (!list.includes(prefix))
9618
+ list.push(prefix);
9513
9619
  }
9514
- var ENCODED_TRAVERSAL = /%(25)*(2f|5c|2e)/i;
9515
- function isPreapprovedHost(hostname, pathname) {
9516
- if (HOSTNAME_ONLY.has(hostname))
9517
- return true;
9518
- const prefixes = PATH_PREFIXES.get(hostname);
9519
- if (prefixes === undefined)
9520
- return false;
9521
- if (ENCODED_TRAVERSAL.test(pathname))
9522
- return false;
9523
- return prefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
9620
+ var ENCODED_TRAVERSAL = /%(?:25)*(?:2f|5c|2e)/i;
9621
+ function isTraversalEncoded(pathname) {
9622
+ return ENCODED_TRAVERSAL.test(pathname);
9623
+ }
9624
+ function underPrefix(pathname, prefix) {
9625
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
9626
+ }
9627
+ function wwwVariants(host) {
9628
+ const bare = host.startsWith("www.") ? host.slice(4) : host;
9629
+ return [...new Set([host, bare, `www.${bare}`])];
9524
9630
  }
9525
9631
  function isPreapprovedUrl(url) {
9526
9632
  return isPreapprovedHost(url.hostname, url.pathname);
9527
9633
  }
9528
- function candidateHosts(hostname) {
9529
- const stripped = hostname.startsWith("www.") ? hostname.slice(4) : hostname;
9530
- return [...new Set([hostname, stripped, `www.${stripped}`])];
9531
- }
9532
9634
  function preapprovedScopeOf(url) {
9533
- const { pathname } = url;
9534
- for (const host of candidateHosts(url.hostname)) {
9535
- if (HOSTNAME_ONLY.has(host))
9536
- return { host };
9537
- const prefixes = PATH_PREFIXES.get(host);
9635
+ const pathname = url.pathname;
9636
+ for (const candidate of wwwVariants(url.hostname)) {
9637
+ if (HOSTNAME_ONLY.has(candidate))
9638
+ return { host: candidate };
9639
+ const prefixes = PATH_PREFIXES.get(candidate);
9538
9640
  if (prefixes === undefined)
9539
9641
  continue;
9540
- if (ENCODED_TRAVERSAL.test(pathname))
9642
+ if (isTraversalEncoded(pathname))
9541
9643
  return;
9542
- const prefix = prefixes.find((p) => pathname === p || pathname.startsWith(`${p}/`));
9644
+ const prefix = prefixes.find((p) => underPrefix(pathname, p));
9543
9645
  if (prefix !== undefined)
9544
- return { host, pathPrefix: prefix };
9646
+ return { host: candidate, pathPrefix: prefix };
9545
9647
  }
9546
9648
  return;
9547
9649
  }
9548
9650
  function staysWithinScope(from, url) {
9549
- const stripped = from.host.startsWith("www.") ? from.host.slice(4) : from.host;
9550
- const acceptableHosts = new Set([from.host, stripped, `www.${stripped}`]);
9551
- if (!acceptableHosts.has(url.hostname))
9651
+ if (!wwwVariants(from.host).includes(url.hostname))
9552
9652
  return false;
9553
9653
  if (from.pathPrefix === undefined)
9554
9654
  return true;
9555
- const { pathname } = url;
9556
- if (ENCODED_TRAVERSAL.test(pathname))
9655
+ if (isTraversalEncoded(url.pathname))
9557
9656
  return false;
9558
- return pathname === from.pathPrefix || pathname.startsWith(`${from.pathPrefix}/`);
9657
+ return underPrefix(url.pathname, from.pathPrefix);
9559
9658
  }
9560
9659
 
9561
9660
  // src/web/private-address.ts
@@ -9653,29 +9752,22 @@ function extractCandidateWritePaths(call, ctx) {
9653
9752
  const recognized = recognizeEditOperation(call, { sessionRoot: ctx.sessionRoot, home: ctx.home, ...ctx.brand !== undefined ? { brand: ctx.brand } : {} });
9654
9753
  return recognized ? recognized.paths : [];
9655
9754
  }
9656
- var TRAILING_DOT_OR_WHITESPACE_COMPONENT = /[.\s]+$/;
9657
- var DEVICE_NAME_SUFFIX = /\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
9658
- var TRIPLE_DOT_COMPONENT = /(^|\/|\\)\.{3,}(\/|\\|$)/;
9659
- var SHORT_FILENAME_TILDE = /~\d/;
9660
9755
  function isSuspiciousPath(path) {
9661
- if (/^[\\/]\?\?[\\/]/.test(path))
9756
+ if (/^[/\\]\?\?[/\\]/.test(path))
9662
9757
  return true;
9663
- if (SHORT_FILENAME_TILDE.test(path))
9758
+ if (/~[0-9]/.test(path))
9664
9759
  return true;
9665
- if (path.startsWith("\\\\?\\") || path.startsWith("\\\\.\\") || path.startsWith("//?/") || path.startsWith("//./"))
9760
+ if (SUSPICIOUS_PATH_PREFIXES.some((prefix) => path.startsWith(prefix)))
9666
9761
  return true;
9667
9762
  for (const component of path.split(/[/\\]/)) {
9668
9763
  if (component === "" || component === "." || component === "..")
9669
9764
  continue;
9670
- if (TRAILING_DOT_OR_WHITESPACE_COMPONENT.test(component))
9765
+ if (/[.\s]$/.test(component))
9671
9766
  return true;
9672
9767
  }
9673
- if (DEVICE_NAME_SUFFIX.test(path))
9674
- return true;
9675
- if (TRIPLE_DOT_COMPONENT.test(path))
9676
- return true;
9677
- return false;
9768
+ return /\.(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(path);
9678
9769
  }
9770
+ var SUSPICIOUS_PATH_PREFIXES = ["\\\\?\\", "\\\\.\\", "//?/", "//./"];
9679
9771
  function suspiciousSymlinkTarget(rawCandidate, ctx) {
9680
9772
  try {
9681
9773
  const absPath = resolveTargetPath(rawCandidate, ctx.cwd);
@@ -11915,66 +12007,46 @@ ${DIRECTIVE_PREFIX}${input.prompt}`;
11915
12007
 
11916
12008
  ${worktreeNote(input.worktree.parentRoot, input.worktree.worktreeRoot)}` : directive;
11917
12009
  }
11918
- function isToolUseBlock(block) {
11919
- return block.type === "tool_use";
11920
- }
11921
- function isToolResultBlock(block) {
11922
- return block.type === "tool_result";
11923
- }
11924
- function answeredToolUseIds(messages) {
12010
+ function buildForkInitialMessages(inherit, forkToolUseId) {
12011
+ const messages = inherit.messages;
12012
+ if (messages === undefined)
12013
+ return [];
11925
12014
  const answered = new Set;
11926
- for (const m of messages) {
11927
- if (typeof m.content === "string")
12015
+ for (const message of messages) {
12016
+ if (!Array.isArray(message.content))
11928
12017
  continue;
11929
- for (const block of m.content)
11930
- if (isToolResultBlock(block))
12018
+ for (const block of message.content) {
12019
+ if (block.type === "tool_result")
11931
12020
  answered.add(block.tool_use_id);
12021
+ }
11932
12022
  }
11933
- return answered;
11934
- }
11935
- function dropUnansweredAssistantMessages(messages) {
11936
- const answered = answeredToolUseIds(messages);
11937
- const filtered = [];
12023
+ const kept = [];
11938
12024
  const dropped = [];
11939
- for (const m of messages) {
11940
- if (m.role !== "assistant" || typeof m.content === "string") {
11941
- filtered.push(m);
11942
- continue;
11943
- }
11944
- const toolUses = m.content.filter(isToolUseBlock);
11945
- const hasUnanswered = toolUses.some((call) => !answered.has(call.id));
11946
- (hasUnanswered ? dropped : filtered).push(m);
12025
+ for (const message of messages) {
12026
+ const hasUnanswered = message.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "tool_use" && !answered.has(block.id));
12027
+ (hasUnanswered ? dropped : kept).push(message);
11947
12028
  }
11948
- return { filtered, dropped };
11949
- }
11950
- function cloneWithOwnToolUse(original, ownToolUse) {
11951
- return {
11952
- role: "assistant",
11953
- content: [ownToolUse],
11954
- ...original.origin !== undefined ? { origin: original.origin } : {}
11955
- };
11956
- }
11957
- function buildForkInitialMessages(inherit, forkToolUseId) {
11958
- if (inherit.messages === undefined)
11959
- return [];
11960
- const { filtered, dropped } = dropUnansweredAssistantMessages(inherit.messages);
11961
- let ownToolUse;
11962
- let sourceMessage;
11963
- for (const m of dropped) {
11964
- if (typeof m.content === "string")
11965
- continue;
11966
- const match = m.content.filter(isToolUseBlock).find((call) => call.id === forkToolUseId);
12029
+ let source;
12030
+ let call;
12031
+ for (const message of dropped) {
12032
+ const blocks = message.content;
12033
+ const match = blocks.find((block) => block.type === "tool_use" && block.id === forkToolUseId);
11967
12034
  if (match !== undefined) {
11968
- ownToolUse = match;
11969
- sourceMessage = m;
12035
+ source = message;
12036
+ call = match;
11970
12037
  break;
11971
12038
  }
11972
12039
  }
11973
- if (ownToolUse === undefined || sourceMessage === undefined)
11974
- return filtered;
11975
- const clone = cloneWithOwnToolUse(sourceMessage, ownToolUse);
11976
- const toolResult = { role: "tool", content: [{ type: "tool_result", tool_use_id: forkToolUseId, content: FORK_PLACEHOLDER_TOOL_RESULT }] };
11977
- return [...filtered, clone, toolResult];
12040
+ if (source === undefined || call === undefined)
12041
+ return kept;
12042
+ const clone = { role: "assistant", content: [call] };
12043
+ if (source.origin !== undefined)
12044
+ clone.origin = source.origin;
12045
+ const placeholder = {
12046
+ role: "tool",
12047
+ content: [{ type: "tool_result", tool_use_id: forkToolUseId, content: FORK_PLACEHOLDER_TOOL_RESULT }]
12048
+ };
12049
+ return [...kept, clone, placeholder];
11978
12050
  }
11979
12051
  class ForkRequestLayoutUnavailableError extends Error {
11980
12052
  constructor() {
@@ -12138,9 +12210,8 @@ function renderAgentToolSpec(entry) {
12138
12210
  const tools = entry.tools ?? [];
12139
12211
  const disallowed = entry.disallowedTools ?? [];
12140
12212
  if (tools.length > 0 && disallowed.length > 0) {
12141
- const denied = new Set(disallowed);
12142
- const effective = tools.filter((t) => !denied.has(t));
12143
- return effective.length === 0 ? "None" : effective.join(", ");
12213
+ const kept = tools.filter((name) => !disallowed.includes(name));
12214
+ return kept.length === 0 ? "None" : kept.join(", ");
12144
12215
  }
12145
12216
  if (tools.length > 0)
12146
12217
  return tools.join(", ");
@@ -12149,26 +12220,25 @@ function renderAgentToolSpec(entry) {
12149
12220
  return "All tools";
12150
12221
  }
12151
12222
  function renderAgentListingLine(entry, leanModel = false) {
12152
- const whenToUse = leanModel && entry.whenToUseLean || entry.whenToUse;
12153
- return neutralizeReminderTags(`- ${entry.agentType}: ${whenToUse} (Tools: ${renderAgentToolSpec(entry)})`);
12223
+ const lean = entry.whenToUseLean;
12224
+ const description = leanModel && typeof lean === "string" && lean.length > 0 ? lean : entry.whenToUse;
12225
+ return neutralizeReminderTags(`- ${entry.agentType}: ${description} (Tools: ${renderAgentToolSpec(entry)})`);
12154
12226
  }
12155
12227
  function computeAgentListingDelta(available, history, opts = {}) {
12156
12228
  const announced = announcedAgentTypes(history);
12157
- const availableTypes = new Set(available.map((e) => e.agentType));
12158
- const added = available.filter((e) => !announced.has(e.agentType));
12159
- const removed = [];
12160
- for (const type of announced)
12161
- if (!availableTypes.has(type))
12162
- removed.push(type);
12229
+ const availableTypes = new Set(available.map((entry) => entry.agentType));
12230
+ const added = available.filter((entry) => !announced.has(entry.agentType));
12231
+ const removed = [...announced].filter((type) => !availableTypes.has(type));
12163
12232
  if (added.length === 0 && removed.length === 0)
12164
12233
  return;
12165
- added.sort((a, b) => a.agentType.localeCompare(b.agentType));
12166
- removed.sort();
12234
+ const sortedAdded = [...added].sort((a, b) => a.agentType.localeCompare(b.agentType));
12235
+ const sortedRemoved = [...removed].sort();
12236
+ const lean = opts.leanModel === true;
12167
12237
  return {
12168
12238
  type: "agent_listing_delta",
12169
- addedTypes: added.map((e) => e.agentType),
12170
- addedLines: added.map((e) => renderAgentListingLine(e, opts.leanModel === true)),
12171
- removedTypes: removed,
12239
+ addedTypes: sortedAdded.map((entry) => entry.agentType),
12240
+ addedLines: sortedAdded.map((entry) => renderAgentListingLine(entry, lean)),
12241
+ removedTypes: sortedRemoved,
12172
12242
  isInitial: announced.size === 0,
12173
12243
  showConcurrencyNote: opts.showConcurrencyNote ?? true
12174
12244
  };
@@ -12433,53 +12503,55 @@ function isUserRole(message) {
12433
12503
  function isReorderStop(message) {
12434
12504
  if (message.role === "assistant")
12435
12505
  return true;
12436
- return Array.isArray(message.content) && message.content[0]?.type === "tool_result";
12506
+ if (typeof message.content === "string")
12507
+ return false;
12508
+ const first = message.content[0];
12509
+ return first !== undefined && first.type === "tool_result";
12437
12510
  }
12438
12511
  function reorderAttachments(messages, stays = () => false) {
12439
12512
  if (!messages.some((m) => m.meta !== undefined))
12440
12513
  return [...messages];
12441
- const reversed = [];
12442
- const pending = [];
12443
- for (let i = messages.length - 1;i >= 0; i--) {
12444
- const m = messages[i];
12445
- if (m.meta !== undefined && !stays(m)) {
12446
- pending.push(m);
12514
+ const top = [];
12515
+ const groups = [];
12516
+ let landing = top;
12517
+ for (const message of messages) {
12518
+ const moving = message.meta !== undefined && !stays(message);
12519
+ if (moving) {
12520
+ landing.push(message);
12447
12521
  continue;
12448
12522
  }
12449
- if (isReorderStop(m) && pending.length > 0) {
12450
- for (const a of pending)
12451
- reversed.push(a);
12452
- reversed.push(m);
12453
- pending.length = 0;
12454
- } else {
12455
- reversed.push(m);
12456
- }
12523
+ const group = { fixed: message, moved: [] };
12524
+ groups.push(group);
12525
+ if (isReorderStop(message))
12526
+ landing = group.moved;
12457
12527
  }
12458
- for (const a of pending)
12459
- reversed.push(a);
12460
- return reversed.reverse();
12528
+ const out = [...top];
12529
+ for (const group of groups)
12530
+ out.push(group.fixed, ...group.moved);
12531
+ return out;
12461
12532
  }
12462
12533
  function hoistToolResults(blocks) {
12463
- const results = [];
12464
- const rest = [];
12465
- for (const b of blocks)
12466
- (b.type === "tool_result" ? results : rest).push(b);
12534
+ const results = blocks.filter((b) => b.type === "tool_result");
12535
+ const rest = blocks.filter((b) => b.type !== "tool_result");
12467
12536
  return [...results, ...rest];
12468
12537
  }
12469
12538
  function joinUserBlocks(prev, next) {
12470
12539
  const last = prev[prev.length - 1];
12471
12540
  const first = next[0];
12472
- if (last?.type === "text" && first?.type === "text")
12541
+ if (last !== undefined && last.type === "text" && first !== undefined && first.type === "text") {
12473
12542
  return [...prev.slice(0, -1), { ...last, text: `${last.text}
12474
12543
  ` }, ...next];
12544
+ }
12475
12545
  return [...prev, ...next];
12476
12546
  }
12547
+ var SMOOSH_EXEMPT_TAG = `<system-reminder>
12548
+ `;
12549
+ var SMOOSH_EXEMPT_BODIES = ["<system>authentic event nonces for this delivery: ", "<event "];
12477
12550
  function isSmooshExempt(text) {
12478
- if (!text.startsWith(`<system-reminder>
12479
- `))
12551
+ if (!text.startsWith(SMOOSH_EXEMPT_TAG))
12480
12552
  return false;
12481
- const inner = text.slice(18);
12482
- return inner.startsWith("<system>authentic event nonces for this delivery: ") || inner.startsWith("<event ");
12553
+ const rest = text.slice(SMOOSH_EXEMPT_TAG.length);
12554
+ return SMOOSH_EXEMPT_BODIES.some((prefix) => rest.startsWith(prefix));
12483
12555
  }
12484
12556
  function foldTextIntoToolResult(result, texts) {
12485
12557
  if (texts.length === 0)
@@ -12487,44 +12559,44 @@ function foldTextIntoToolResult(result, texts) {
12487
12559
  const content = result.content;
12488
12560
  if (Array.isArray(content) && content.some((b) => b.type === "tool_reference"))
12489
12561
  return null;
12490
- if ((result.loadedTools?.length ?? 0) > 0)
12562
+ if (Array.isArray(result.loadedTools) && result.loadedTools.length > 0)
12491
12563
  return null;
12492
12564
  if (typeof content === "string") {
12493
- const joined = [content.trim(), ...texts.map((t) => t.text.trim())].filter((s) => s.length > 0).join(`
12565
+ const pieces = [content.trim(), ...texts.map((t) => t.text.trim())].filter((piece) => piece.length > 0);
12566
+ return { ...result, content: pieces.join(`
12494
12567
 
12495
- `);
12496
- return { ...result, content: joined };
12568
+ `) };
12497
12569
  }
12498
- const merged = [];
12570
+ const folded = [];
12499
12571
  for (const block of [...content, ...texts]) {
12500
12572
  if (block.type !== "text") {
12501
- merged.push(block);
12573
+ folded.push(block);
12502
12574
  continue;
12503
12575
  }
12504
12576
  const trimmed = block.text.trim();
12505
12577
  if (trimmed.length === 0)
12506
12578
  continue;
12507
- const tail = merged[merged.length - 1];
12508
- if (tail?.type === "text")
12509
- merged[merged.length - 1] = { ...tail, text: `${tail.text}
12579
+ const tail = folded[folded.length - 1];
12580
+ if (tail !== undefined && tail.type === "text")
12581
+ folded[folded.length - 1] = { type: "text", text: `${tail.text}
12510
12582
 
12511
12583
  ${trimmed}` };
12512
12584
  else
12513
- merged.push({ type: "text", text: trimmed });
12585
+ folded.push({ type: "text", text: trimmed });
12514
12586
  }
12515
- return { ...result, content: merged };
12587
+ return { ...result, content: folded };
12516
12588
  }
12517
12589
  function joinAttachmentBlocks(prev, next) {
12518
12590
  const last = prev[prev.length - 1];
12519
- if (last?.type !== "tool_result")
12591
+ if (last === undefined || last.type !== "tool_result")
12520
12592
  return [...prev, ...next];
12521
- if ((last.loadedTools?.length ?? 0) > 0)
12593
+ if (Array.isArray(last.loadedTools) && last.loadedTools.length > 0)
12522
12594
  return [...prev, ...next];
12523
12595
  if (next.some((b) => b.type === "text" && isSmooshExempt(b.text)))
12524
12596
  return [...prev, ...next];
12525
12597
  if (typeof last.content === "string" && next.every((b) => b.type === "text")) {
12526
- const folded = foldTextIntoToolResult(last, next) ?? last;
12527
- return [...prev.slice(0, -1), folded];
12598
+ const folded = foldTextIntoToolResult(last, next);
12599
+ return [...prev.slice(0, -1), folded ?? last];
12528
12600
  }
12529
12601
  return [...prev, ...next];
12530
12602
  }
@@ -12672,7 +12744,6 @@ import { execFile as execFile2 } from "node:child_process";
12672
12744
  var GIT_STATUS_CAVEAT = "This git status was captured when the session began; it is a snapshot and does not change as the conversation goes on.";
12673
12745
  var BASH_TOOL_NAME = "Bash";
12674
12746
  var GIT_STATUS_MAX_CHARS = 2000;
12675
- var MAIN_BRANCH_CANDIDATES = ["main", "master"];
12676
12747
  var GIT_TIMEOUT_MS = 1e4;
12677
12748
  function runGit2(cwd, args, env) {
12678
12749
  return new Promise((resolve) => {
@@ -12684,17 +12755,25 @@ function runGit2(cwd, args, env) {
12684
12755
  }
12685
12756
  async function currentBranch(cwd, env) {
12686
12757
  const { code, stdout } = await runGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], env);
12687
- return code === 0 ? stdout.trim() || "HEAD" : "HEAD";
12758
+ const name = stdout.trim();
12759
+ return code === 0 && name.length > 0 ? name : "HEAD";
12688
12760
  }
12761
+ async function originRefExists(cwd, name, env) {
12762
+ const { code } = await runGit2(cwd, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${name}`], env);
12763
+ return code === 0;
12764
+ }
12765
+ var FALLBACK_MAIN_BRANCHES = ["main", "master"];
12689
12766
  async function mainBranch(cwd, env) {
12690
- const head = await runGit2(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], env);
12767
+ const head = await runGit2(cwd, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], env);
12691
12768
  if (head.code === 0) {
12692
- const name = head.stdout.trim().replace(/^origin\//, "");
12693
- if (name.length > 0 && (await runGit2(cwd, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${name}`], env)).code === 0)
12769
+ let name = head.stdout.trim();
12770
+ if (name.startsWith("origin/"))
12771
+ name = name.slice("origin/".length);
12772
+ if (name.length > 0 && await originRefExists(cwd, name, env))
12694
12773
  return name;
12695
12774
  }
12696
- for (const candidate of MAIN_BRANCH_CANDIDATES) {
12697
- if ((await runGit2(cwd, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${candidate}`], env)).code === 0)
12775
+ for (const candidate of FALLBACK_MAIN_BRANCHES) {
12776
+ if (await originRefExists(cwd, candidate, env))
12698
12777
  return candidate;
12699
12778
  }
12700
12779
  return "main";
@@ -12769,11 +12848,17 @@ function isUserInvocable(overrides, skill) {
12769
12848
  var DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS = 200000;
12770
12849
  function skillListingBudgetChars(opts) {
12771
12850
  if (opts.budgetChars !== undefined)
12772
- return Number.isFinite(opts.budgetChars) && opts.budgetChars > 0 ? opts.budgetChars : 0;
12773
- const window = opts.contextWindowTokens !== undefined && Number.isFinite(opts.contextWindowTokens) && opts.contextWindowTokens > 0 ? opts.contextWindowTokens : DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS;
12774
- const fraction = opts.budgetFraction !== undefined && Number.isFinite(opts.budgetFraction) && opts.budgetFraction > 0 ? opts.budgetFraction : DEFAULT_SKILL_LISTING_BUDGET_FRACTION;
12851
+ return isPositiveFinite(opts.budgetChars) ? opts.budgetChars : 0;
12852
+ const window = isPositiveFinite(opts.contextWindowTokens) ? opts.contextWindowTokens : DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS;
12853
+ const fraction = isPositiveFinite(opts.budgetFraction) ? opts.budgetFraction : DEFAULT_SKILL_LISTING_BUDGET_FRACTION;
12775
12854
  return Math.max(1, Math.floor(window * SKILL_LISTING_CHARS_PER_TOKEN * fraction));
12776
12855
  }
12856
+ function isPositiveFinite(value) {
12857
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
12858
+ }
12859
+ function lineLength(name, description) {
12860
+ return description.length > 0 ? 4 + name.length + description.length : 2 + name.length;
12861
+ }
12777
12862
  function truncateSkillDescription(description, maxDescChars) {
12778
12863
  if (!Number.isFinite(maxDescChars) || maxDescChars <= 0 || description.length <= maxDescChars)
12779
12864
  return description;
@@ -12787,42 +12872,53 @@ function renderSkillListingContent(entries) {
12787
12872
  `);
12788
12873
  }
12789
12874
  function buildSkillListing(skills, opts) {
12790
- const maxDescChars = opts?.maxDescChars ?? DEFAULT_SKILL_LISTING_MAX_DESC_CHARS;
12875
+ const cap = opts?.maxDescChars ?? DEFAULT_SKILL_LISTING_MAX_DESC_CHARS;
12791
12876
  const budget = skillListingBudgetChars({
12792
- ...opts?.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {},
12793
- ...opts?.budgetFraction !== undefined ? { budgetFraction: opts.budgetFraction } : {},
12794
- ...opts?.budgetChars !== undefined ? { budgetChars: opts.budgetChars } : {}
12877
+ contextWindowTokens: opts?.contextWindowTokens,
12878
+ budgetFraction: opts?.budgetFraction,
12879
+ budgetChars: opts?.budgetChars
12795
12880
  });
12796
12881
  const entries = [];
12797
- const alwaysFull = new Set;
12882
+ const fixed = [];
12798
12883
  for (const skill of skills) {
12799
12884
  if (!isModelVisible(opts?.skillOverrides, skill))
12800
12885
  continue;
12801
12886
  const nameOnly = overrideFor(opts?.skillOverrides, skill) === "name-only";
12802
- if (nameOnly || skill.source === "builtin")
12803
- alwaysFull.add(entries.length);
12804
- entries.push({ name: skill.name, description: nameOnly ? "" : truncateSkillDescription(skill.description, maxDescChars), source: skill.source });
12887
+ const description = nameOnly ? "" : truncateSkillDescription(skill.description, cap);
12888
+ entries.push({ name: skill.name, description, source: skill.source });
12889
+ fixed.push(nameOnly || skill.source === "builtin");
12805
12890
  }
12806
- if (budget <= 0 || entries.length === 0)
12891
+ if (!(budget > 0) || entries.length === 0)
12807
12892
  return entries;
12808
- const fullLength = (i) => renderSkillListingLine(entries[i]).length;
12809
- const nameLength = (i) => entries[i].name.length + 2;
12810
- const total = entries.reduce((sum, _e, i) => sum + fullLength(i), 0) + (entries.length - 1);
12811
- if (total <= budget)
12893
+ const separators = entries.length - 1;
12894
+ let fullSize = separators;
12895
+ for (const e of entries)
12896
+ fullSize += lineLength(e.name, e.description);
12897
+ if (fullSize <= budget)
12812
12898
  return entries;
12813
- const candidates = entries.map((_e, i) => i).filter((i) => !alwaysFull.has(i));
12814
- if (candidates.length === 0)
12899
+ if (fixed.every(Boolean))
12815
12900
  return entries;
12816
- let remaining = budget - (entries.reduce((sum, _e, i) => sum + (alwaysFull.has(i) ? fullLength(i) : nameLength(i)), 0) + (entries.length - 1));
12817
- const keep = new Set;
12818
- for (const i of candidates) {
12819
- const cost = fullLength(i) - nameLength(i);
12901
+ let remaining = budget - separators;
12902
+ for (let i = 0;i < entries.length; i++) {
12903
+ const e = entries[i];
12904
+ remaining -= fixed[i] ? lineLength(e.name, e.description) : lineLength(e.name, "");
12905
+ }
12906
+ const out = [];
12907
+ for (let i = 0;i < entries.length; i++) {
12908
+ const e = entries[i];
12909
+ if (fixed[i]) {
12910
+ out.push(e);
12911
+ continue;
12912
+ }
12913
+ const cost = lineLength(e.name, e.description) - lineLength(e.name, "");
12820
12914
  if (cost <= remaining) {
12821
- keep.add(i);
12822
12915
  remaining -= cost;
12916
+ out.push(e);
12917
+ } else {
12918
+ out.push({ name: e.name, description: "", source: e.source });
12823
12919
  }
12824
12920
  }
12825
- return entries.map((e, i) => alwaysFull.has(i) || keep.has(i) ? e : { ...e, description: "" });
12921
+ return out;
12826
12922
  }
12827
12923
 
12828
12924
  // src/subagents/plugin-agents.ts
@@ -12887,7 +12983,7 @@ var HOOK_SOURCE_BY_SETTING_SOURCE = {
12887
12983
  project: "project",
12888
12984
  local: "local"
12889
12985
  };
12890
- function isPlainObject2(v) {
12986
+ function isPlainObject3(v) {
12891
12987
  return typeof v === "object" && v !== null && !Array.isArray(v);
12892
12988
  }
12893
12989
  function buildHookEntriesFromSettings(perSource) {
@@ -12901,7 +12997,7 @@ function buildHookEntriesFromSettings(perSource) {
12901
12997
  if (hooks === undefined)
12902
12998
  continue;
12903
12999
  const where = { source: tier.source, ...tier.path !== undefined ? { path: tier.path } : {} };
12904
- if (!isPlainObject2(hooks)) {
13000
+ if (!isPlainObject3(hooks)) {
12905
13001
  rejected.push({ ...where, reason: `expected an object for "hooks", got ${Array.isArray(hooks) ? "an array" : typeof hooks}` });
12906
13002
  continue;
12907
13003
  }
@@ -12914,7 +13010,7 @@ function buildHookEntriesFromSettings(perSource) {
12914
13010
  continue;
12915
13011
  }
12916
13012
  groups.forEach((group, groupIndex) => {
12917
- if (!isPlainObject2(group)) {
13013
+ if (!isPlainObject3(group)) {
12918
13014
  rejected.push({ ...where, event, reason: `matcher group ${groupIndex} is not an object` });
12919
13015
  return;
12920
13016
  }
@@ -12925,7 +13021,7 @@ function buildHookEntriesFromSettings(perSource) {
12925
13021
  }
12926
13022
  const matcher = typeof group["matcher"] === "string" ? group["matcher"] : undefined;
12927
13023
  groupHooks.forEach((handler, hookIndex) => {
12928
- if (!isPlainObject2(handler)) {
13024
+ if (!isPlainObject3(handler)) {
12929
13025
  rejected.push({ ...where, event, reason: `hook ${groupIndex}:${hookIndex} is not an object` });
12930
13026
  return;
12931
13027
  }
@@ -15893,7 +15989,7 @@ replaceExecutor(ASK_USER_QUESTION_TOOL_NAME2, askUserQuestionExecutor);
15893
15989
  // src/tools/impl/bash.ts
15894
15990
  import { randomUUID as randomUUID9 } from "node:crypto";
15895
15991
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync10, unlinkSync, createWriteStream } from "node:fs";
15896
- import { join as join17, sep as sep2 } from "node:path";
15992
+ import { join as join17, sep } from "node:path";
15897
15993
 
15898
15994
  // src/tools/sandbox-run-inputs.ts
15899
15995
  function computeWritableRoots(ctx) {
@@ -15985,7 +16081,7 @@ function buildRunCommandOptions(input, ctx) {
15985
16081
  function isWithinAllowedDirs(candidate, cwd, writableRoots) {
15986
16082
  const canonicalCandidate = canonicalizePath(candidate);
15987
16083
  const allowed = [canonicalizePath(cwd), ...writableRoots.map(canonicalizePath)];
15988
- return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root + sep2));
16084
+ return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root + sep));
15989
16085
  }
15990
16086
  function computeCwdCarryAllowedRoots(ctx) {
15991
16087
  return [ctx.session.getSessionRoot(), ...ctx.session.getBoundedRoots()];
@@ -16485,11 +16581,11 @@ function writeDurableJobsAtomic(projectDir, jobs, brand) {
16485
16581
  }
16486
16582
  renameSync2(tmpPath, path);
16487
16583
  }
16488
- function isPlainObject3(v) {
16584
+ function isPlainObject4(v) {
16489
16585
  return typeof v === "object" && v !== null && !Array.isArray(v);
16490
16586
  }
16491
16587
  function parseCreateInput(raw) {
16492
- if (!isPlainObject3(raw))
16588
+ if (!isPlainObject4(raw))
16493
16589
  throw new Error("input must be an object");
16494
16590
  const cron = raw["cron"];
16495
16591
  if (typeof cron !== "string" || cron.length === 0)
@@ -16507,7 +16603,7 @@ function parseCreateInput(raw) {
16507
16603
  return { cron, prompt, recurring: recurring ?? true, durable: durable ?? false };
16508
16604
  }
16509
16605
  function parseIdInput(raw) {
16510
- if (!isPlainObject3(raw))
16606
+ if (!isPlainObject4(raw))
16511
16607
  throw new Error("input must be an object");
16512
16608
  const id = raw["id"];
16513
16609
  if (typeof id !== "string" || id.length === 0)
@@ -17026,13 +17122,13 @@ var exitPlanModeExecutor = {
17026
17122
  replaceExecutor(EXIT_PLAN_MODE_TOOL_NAME, exitPlanModeExecutor);
17027
17123
 
17028
17124
  // src/tools/impl/exit-worktree.ts
17029
- import { sep as sep3 } from "node:path";
17125
+ import { sep as sep2 } from "node:path";
17030
17126
  var EXIT_WORKTREE_TOOL_NAME = "ExitWorktree";
17031
17127
  function hasUncommittedChanges(statusPorcelainOutput) {
17032
17128
  return statusPorcelainOutput.trim().length > 0;
17033
17129
  }
17034
17130
  function findWorktreeContainingCwd(worktrees, cwdRealPath) {
17035
- const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath + sep3));
17131
+ const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath + sep2));
17036
17132
  if (candidates.length === 0)
17037
17133
  return;
17038
17134
  return candidates.reduce((longest, w) => w.realPath.length > longest.realPath.length ? w : longest);
@@ -17197,7 +17293,7 @@ replaceExecutor("Glob", globExecutor, extractGlobPaths);
17197
17293
 
17198
17294
  // src/tools/impl/grep.ts
17199
17295
  import { readFileSync as readFileSync13, statSync as statSync7 } from "node:fs";
17200
- import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as sep4 } from "node:path";
17296
+ import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as sep3 } from "node:path";
17201
17297
  var OUTPUT_MODES = ["content", "files_with_matches", "count"];
17202
17298
  var MAX_PATTERN_LENGTH = 1000;
17203
17299
  function parseInput4(raw) {
@@ -17302,7 +17398,7 @@ var TYPE_EXTENSIONS = {
17302
17398
  };
17303
17399
  function extOf(path) {
17304
17400
  const i = path.lastIndexOf(".");
17305
- const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(sep4));
17401
+ const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(sep3));
17306
17402
  if (i <= slashIdx)
17307
17403
  return "";
17308
17404
  return path.slice(i).toLowerCase();
@@ -17346,7 +17442,7 @@ async function loadGitignoreRules(searchRoot) {
17346
17442
  const found = [];
17347
17443
  try {
17348
17444
  for await (const rel of discovery.scan({ cwd: searchRoot, dot: true, onlyFiles: true, followSymlinks: false })) {
17349
- const relPosix = rel.split(sep4).join("/");
17445
+ const relPosix = rel.split(sep3).join("/");
17350
17446
  const dir = dirname8(relPosix);
17351
17447
  const relDir = dir === "." ? "" : dir;
17352
17448
  try {
@@ -17384,7 +17480,7 @@ async function discoverCandidateFiles(searchRoot, globPattern) {
17384
17480
  const out = [];
17385
17481
  for await (const p of glob.scan({ cwd: searchRoot, onlyFiles: true, dot: false, followSymlinks: false })) {
17386
17482
  const abs = isAbsolute4(p) ? resolve9(p) : resolve9(searchRoot, p);
17387
- const relPosix = relative2(searchRoot, abs).split(sep4).join("/");
17483
+ const relPosix = relative2(searchRoot, abs).split(sep3).join("/");
17388
17484
  if (isIgnored(relPosix, rules))
17389
17485
  continue;
17390
17486
  out.push(abs);
@@ -18119,11 +18215,11 @@ replaceExecutor("NotebookEdit", notebookEditExecutor);
18119
18215
  var MAX_MESSAGE_LENGTH = 199;
18120
18216
  var DISABLED_REASON = "push transport unconfigured";
18121
18217
  var activeNotifier = () => {};
18122
- function isPlainObject4(v) {
18218
+ function isPlainObject5(v) {
18123
18219
  return typeof v === "object" && v !== null && !Array.isArray(v);
18124
18220
  }
18125
18221
  function parseInput6(raw) {
18126
- if (!isPlainObject4(raw))
18222
+ if (!isPlainObject5(raw))
18127
18223
  throw new Error("input must be an object");
18128
18224
  const message = raw["message"];
18129
18225
  if (typeof message !== "string" || message.length === 0)
@@ -18432,11 +18528,11 @@ var MAX_FINDINGS = 32;
18432
18528
  var LEVELS = ["low", "medium", "high", "xhigh", "max"];
18433
18529
  var VERDICTS = ["CONFIRMED", "PLAUSIBLE"];
18434
18530
  var OUTCOMES = ["fixed", "skipped", "no_change_needed"];
18435
- function isPlainObject5(v) {
18531
+ function isPlainObject6(v) {
18436
18532
  return typeof v === "object" && v !== null && !Array.isArray(v);
18437
18533
  }
18438
18534
  function parseFinding(raw, index) {
18439
- if (!isPlainObject5(raw))
18535
+ if (!isPlainObject6(raw))
18440
18536
  throw new Error(`findings[${index}] must be an object`);
18441
18537
  const file = raw["file"];
18442
18538
  if (typeof file !== "string" || file.length === 0)
@@ -18477,7 +18573,7 @@ function parseFinding(raw, index) {
18477
18573
  };
18478
18574
  }
18479
18575
  function parseInput8(raw) {
18480
- if (!isPlainObject5(raw))
18576
+ if (!isPlainObject6(raw))
18481
18577
  throw new Error("input must be an object");
18482
18578
  const level = raw["level"];
18483
18579
  if (level !== undefined && (typeof level !== "string" || !LEVELS.includes(level))) {
@@ -18523,11 +18619,11 @@ function sessionList(sessionId) {
18523
18619
  }
18524
18620
  return list;
18525
18621
  }
18526
- function isPlainObject6(v) {
18622
+ function isPlainObject7(v) {
18527
18623
  return typeof v === "object" && v !== null && !Array.isArray(v);
18528
18624
  }
18529
18625
  function parseInput9(raw) {
18530
- if (!isPlainObject6(raw))
18626
+ if (!isPlainObject7(raw))
18531
18627
  throw new Error("input must be an object");
18532
18628
  const stop = raw["stop"];
18533
18629
  if (stop !== undefined && typeof stop !== "boolean")
@@ -18672,7 +18768,7 @@ function updateTask2(sessionId, input) {
18672
18768
  }
18673
18769
 
18674
18770
  // src/tools/impl/task-graph.ts
18675
- function isPlainObject7(v) {
18771
+ function isPlainObject8(v) {
18676
18772
  return typeof v === "object" && v !== null && !Array.isArray(v);
18677
18773
  }
18678
18774
  function isNonEmptyString(v) {
@@ -18686,7 +18782,7 @@ function errorResult5(message) {
18686
18782
  return { output: `Error: ${message}`, isError: true };
18687
18783
  }
18688
18784
  function parseCreateInput2(raw) {
18689
- if (!isPlainObject7(raw))
18785
+ if (!isPlainObject8(raw))
18690
18786
  throw new Error("input must be an object");
18691
18787
  const subject = raw["subject"];
18692
18788
  const description = raw["description"];
@@ -18698,7 +18794,7 @@ function parseCreateInput2(raw) {
18698
18794
  if (activeForm !== undefined && typeof activeForm !== "string")
18699
18795
  throw new Error("activeForm must be a string");
18700
18796
  const metadata = raw["metadata"];
18701
- if (metadata !== undefined && !isPlainObject7(metadata))
18797
+ if (metadata !== undefined && !isPlainObject8(metadata))
18702
18798
  throw new Error("metadata must be an object");
18703
18799
  return {
18704
18800
  subject,
@@ -18718,7 +18814,7 @@ async function executeCreate2(rawInput, ctx) {
18718
18814
  return { output: JSON.stringify({ task: { id: row.id, subject: row.subject } }) };
18719
18815
  }
18720
18816
  function parseTaskIdInput(raw) {
18721
- if (!isPlainObject7(raw))
18817
+ if (!isPlainObject8(raw))
18722
18818
  throw new Error("input must be an object");
18723
18819
  const taskId = raw["taskId"];
18724
18820
  if (!isNonEmptyString(taskId))
@@ -18753,7 +18849,7 @@ async function executeList2(_rawInput, ctx) {
18753
18849
  return { output: JSON.stringify({ tasks: rows }) };
18754
18850
  }
18755
18851
  function parseUpdateInput(raw) {
18756
- if (!isPlainObject7(raw))
18852
+ if (!isPlainObject8(raw))
18757
18853
  throw new Error("input must be an object");
18758
18854
  const taskId = raw["taskId"];
18759
18855
  if (!isNonEmptyString(taskId))
@@ -18781,7 +18877,7 @@ function parseUpdateInput(raw) {
18781
18877
  if (addBlockedBy !== undefined && !isStringArray(addBlockedBy))
18782
18878
  throw new Error("addBlockedBy must be an array of non-empty strings");
18783
18879
  const metadata = raw["metadata"];
18784
- if (metadata !== undefined && !isPlainObject7(metadata))
18880
+ if (metadata !== undefined && !isPlainObject8(metadata))
18785
18881
  throw new Error("metadata must be an object");
18786
18882
  return {
18787
18883
  taskId,
@@ -18856,7 +18952,7 @@ replaceExecutor("TaskUpdate", { execute: executeUpdate });
18856
18952
 
18857
18953
  // src/tools/impl/task-output.ts
18858
18954
  import { existsSync as existsSync7, readFileSync as readFileSync16 } from "node:fs";
18859
- import { join as join20, sep as sep5 } from "node:path";
18955
+ import { join as join20, sep as sep4 } from "node:path";
18860
18956
  function parseTaskOutputInput(input) {
18861
18957
  if (typeof input !== "object" || input === null)
18862
18958
  return { error: "TaskOutput: input must be an object" };
@@ -18886,7 +18982,7 @@ function resolveOutputPath(taskId, ctx) {
18886
18982
  return;
18887
18983
  const realFallback = resolveRealTarget(fallback);
18888
18984
  const realTasksDir = resolveRealTarget(tasksDir);
18889
- return realFallback.startsWith(realTasksDir + sep5) ? fallback : undefined;
18985
+ return realFallback.startsWith(realTasksDir + sep4) ? fallback : undefined;
18890
18986
  }
18891
18987
  async function waitForTerminal(taskId, timeoutMs) {
18892
18988
  const deadline = Date.now() + timeoutMs;
@@ -19014,12 +19110,12 @@ replaceExecutor("TaskStop", taskStopExecutor);
19014
19110
 
19015
19111
  // src/tools/impl/todo-write.ts
19016
19112
  var sessions3 = new Map;
19017
- function isPlainObject8(v) {
19113
+ function isPlainObject9(v) {
19018
19114
  return typeof v === "object" && v !== null && !Array.isArray(v);
19019
19115
  }
19020
19116
  var TODO_STATUSES = ["pending", "in_progress", "completed"];
19021
19117
  function parseTodo(raw, index) {
19022
- if (!isPlainObject8(raw))
19118
+ if (!isPlainObject9(raw))
19023
19119
  throw new Error(`todos[${index}] must be an object`);
19024
19120
  const content = raw["content"];
19025
19121
  if (typeof content !== "string" || content.length === 0)
@@ -19034,7 +19130,7 @@ function parseTodo(raw, index) {
19034
19130
  return { content, status, activeForm };
19035
19131
  }
19036
19132
  function parseInput10(raw) {
19037
- if (!isPlainObject8(raw))
19133
+ if (!isPlainObject9(raw))
19038
19134
  throw new Error("input must be an object");
19039
19135
  const todos = raw["todos"];
19040
19136
  if (!Array.isArray(todos))
@@ -22145,17 +22241,24 @@ function flushWebSearchStream(events) {
22145
22241
  let buffer = "";
22146
22242
  const flush = () => {
22147
22243
  const text = buffer.trim();
22148
- if (text.length > 0)
22244
+ if (text !== "")
22149
22245
  items.push(text);
22150
22246
  buffer = "";
22151
22247
  };
22152
22248
  for (const event of events) {
22153
- if (event.type === "text") {
22154
- buffer += event.text;
22155
- continue;
22249
+ switch (event.type) {
22250
+ case "text":
22251
+ buffer += event.text;
22252
+ break;
22253
+ case "search_result":
22254
+ flush();
22255
+ items.push({ content: event.hits.map((hit) => ({ title: hit.title, url: hit.url })) });
22256
+ break;
22257
+ default:
22258
+ flush();
22259
+ items.push(`Web search error: ${String(event.code)}`);
22260
+ break;
22156
22261
  }
22157
- flush();
22158
- items.push(event.type === "search_result" ? { content: event.hits.map((h) => ({ title: h.title, url: h.url })) } : `Web search error: ${event.code}`);
22159
22262
  }
22160
22263
  flush();
22161
22264
  return items;
@@ -22168,14 +22271,20 @@ function headerQuery(query) {
22168
22271
  return `${query.slice(0, HEADER_QUERY_CAP)}[query truncated at ${HEADER_QUERY_CAP.toLocaleString("en-US")} characters]`;
22169
22272
  }
22170
22273
  function renderWebSearchToolResult(query, items) {
22171
- const header = `Web search results for query: "${headerQuery(query)}"`;
22172
- const body = items.map((item) => typeof item === "string" ? item : item.content.length > 0 ? `Links: ${JSON.stringify(item.content)}` : "No links found.").reduce((acc, rendered) => acc + rendered + `
22274
+ let out = `Web search results for query: "${headerQuery(query)}"
22173
22275
 
22174
- `, "");
22175
- return (header + `
22276
+ `;
22277
+ for (const item of items)
22278
+ out += `${renderItem(item)}
22176
22279
 
22177
- ` + body + `
22178
- ` + REMINDER).trim();
22280
+ `;
22281
+ return `${out}
22282
+ ${REMINDER}`;
22283
+ }
22284
+ function renderItem(item) {
22285
+ if (typeof item === "string")
22286
+ return item;
22287
+ return item.content.length > 0 ? `Links: ${JSON.stringify(item.content)}` : "No links found.";
22179
22288
  }
22180
22289
  function renderWebSearchToolResultCapped(query, items, cap) {
22181
22290
  const full = renderWebSearchToolResult(query, items);
@@ -24125,16 +24234,19 @@ function createSessionReadState(opts) {
24125
24234
  }
24126
24235
 
24127
24236
  // src/provider/lean-prompt.ts
24128
- var FULL_PROMPT_OPUS_MINORS = new Set(["0", "1", "5", "6", "7"]);
24129
24237
  function claudeModelTakesFullPrompt(modelKey) {
24130
24238
  const id = modelKey.slice(modelKey.lastIndexOf("/") + 1).toLowerCase();
24131
- if (id.includes("claude-3-") || id.includes("haiku") || id.includes("sonnet"))
24239
+ if (FULL_FAMILY_MARKERS.some((marker) => id.includes(marker)))
24132
24240
  return true;
24133
- const opus = /^claude-opus-4(?:[-.](\d{1,2}))?(?:-\d{8})?$/.exec(id);
24134
- if (opus === null)
24241
+ const match = OPUS_4_ID.exec(id);
24242
+ if (match === null)
24135
24243
  return false;
24136
- return FULL_PROMPT_OPUS_MINORS.has(opus[1] ?? "0");
24244
+ const minor = match[1] ?? "0";
24245
+ return FULL_OPUS_4_MINORS.has(minor);
24137
24246
  }
24247
+ var FULL_FAMILY_MARKERS = ["claude-3-", "haiku", "sonnet"];
24248
+ var OPUS_4_ID = /^claude-opus-4(?:[-.](\d{1,2}))?(?:-\d{8})?$/;
24249
+ var FULL_OPUS_4_MINORS = new Set(["0", "1", "5", "6", "7"]);
24138
24250
 
24139
24251
  // src/provider/first-party.ts
24140
24252
  var FIRST_PARTY_ANTHROPIC_PROVIDER_IDS = new Set(["anthropic", "console"]);
@@ -25262,30 +25374,34 @@ ${escapeAttributionText(msg.body)}
25262
25374
  function isTruthyEnvValue(v) {
25263
25375
  return v === "1" || v === "true";
25264
25376
  }
25265
- function claudeTierMatches(modelKey, tier) {
25266
- if (resolveSlot === undefined)
25267
- return false;
25268
- const resolution = resolveSlot(tier, modelKey);
25269
- return resolution.ok && resolution.modelKey === modelKey;
25270
- }
25271
- function isAtOrBelowOpusTier(modelKey) {
25272
- return claudeTierMatches(modelKey, "haiku") || claudeTierMatches(modelKey, "sonnet") || claudeTierMatches(modelKey, "opus");
25273
- }
25274
25377
  function sessionLeanModel(modelKey) {
25275
25378
  if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
25276
25379
  return false;
25277
25380
  return !claudeModelTakesFullPrompt(modelKey);
25278
25381
  }
25279
25382
  function exploreModelCap(parentModel) {
25280
- const env = engineEnv ?? process.env;
25281
- if (isTruthyEnvValue(env[envName8(sessionBrand, "DISABLE_EXPLORE_INHERIT_CAP")]))
25383
+ if (exploreCapOptedOut())
25282
25384
  return;
25283
25385
  if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
25284
25386
  return;
25285
- if (isAtOrBelowOpusTier(parentModel))
25387
+ if (modelSitsOnCappedOrLowerTier(parentModel))
25286
25388
  return;
25287
25389
  return "opus";
25288
25390
  }
25391
+ function exploreCapOptedOut() {
25392
+ const env = engineEnv ?? process.env;
25393
+ return isTruthyEnvValue(env[envName8(sessionBrand, "DISABLE_EXPLORE_INHERIT_CAP")]);
25394
+ }
25395
+ function modelSitsOnCappedOrLowerTier(modelKey) {
25396
+ if (resolveSlot === undefined)
25397
+ return false;
25398
+ for (const tier of ["haiku", "sonnet", "opus"]) {
25399
+ const resolution = resolveSlot(tier, modelKey);
25400
+ if (resolution.ok && resolution.modelKey === modelKey)
25401
+ return true;
25402
+ }
25403
+ return false;
25404
+ }
25289
25405
  function resolveChildModel(req) {
25290
25406
  if (req.fork === true)
25291
25407
  return currentModel;
@@ -29030,100 +29146,122 @@ function ancestryChain(byUuid, leafUuid) {
29030
29146
  }
29031
29147
  return chain.reverse();
29032
29148
  }
29033
- function toolUseIdsOf(e) {
29034
- if (e.type !== "assistant" || !Array.isArray(e.message?.content))
29035
- return [];
29036
- return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_use" && typeof b.id === "string" ? [b.id] : []);
29037
- }
29038
- function toolResultIdsOf(e) {
29039
- if (e.type !== "user" || !Array.isArray(e.message?.content))
29040
- return [];
29041
- return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_result" && typeof b.tool_use_id === "string" ? [b.tool_use_id] : []);
29042
- }
29043
29149
  function recoverParallelToolResults(chain, pool) {
29044
29150
  const onChain = new Set(chain.map((e) => e.uuid));
29045
29151
  const resultsByParent = new Map;
29046
- const offChainAssistants = [];
29047
- for (const e of pool) {
29048
- if (onChain.has(e.uuid) || e.parentUuid === null)
29049
- continue;
29050
- if (e.type === "assistant" && e.isApiErrorMessage !== true)
29051
- offChainAssistants.push(e);
29052
- if (toolResultIdsOf(e).length === 0)
29152
+ const siblingCandidates = [];
29153
+ for (const entry of pool) {
29154
+ if (onChain.has(entry.uuid) || entry.parentUuid === null)
29053
29155
  continue;
29054
- const siblings = resultsByParent.get(e.parentUuid);
29055
- if (siblings !== undefined)
29056
- siblings.push(e);
29057
- else
29058
- resultsByParent.set(e.parentUuid, [e]);
29156
+ if (answerIdsOf(entry).length > 0) {
29157
+ const group = resultsByParent.get(entry.parentUuid);
29158
+ if (group === undefined)
29159
+ resultsByParent.set(entry.parentUuid, [entry]);
29160
+ else
29161
+ group.push(entry);
29162
+ }
29163
+ if (entry.type === "assistant" && entry.isApiErrorMessage !== true)
29164
+ siblingCandidates.push(entry);
29059
29165
  }
29060
- if (resultsByParent.size === 0 && offChainAssistants.length === 0)
29166
+ if (resultsByParent.size === 0 && siblingCandidates.length === 0)
29061
29167
  return [...chain];
29062
- const answered = new Set(chain.flatMap(toolResultIdsOf));
29063
- const recovered = new Set;
29064
- const resultsFor = (parentUuid, open) => {
29065
- const results = [];
29066
- const ids = new Set;
29067
- for (const candidate of resultsByParent.get(parentUuid) ?? []) {
29068
- if (recovered.has(candidate.uuid))
29168
+ const satisfied = new Set;
29169
+ for (const entry of chain)
29170
+ for (const id of answerIdsOf(entry))
29171
+ satisfied.add(id);
29172
+ const returned = new Set;
29173
+ const selectResults = (parentUuid, open) => {
29174
+ const taken = [];
29175
+ const claimed = new Set;
29176
+ for (const result of resultsByParent.get(parentUuid) ?? []) {
29177
+ if (returned.has(result.uuid))
29069
29178
  continue;
29070
- const answers = toolResultIdsOf(candidate);
29071
- if (!answers.some((id) => open.includes(id) && !ids.has(id)))
29179
+ const ids = answerIdsOf(result);
29180
+ if (!ids.some((id) => open.has(id) && !claimed.has(id)))
29072
29181
  continue;
29073
- results.push(candidate);
29074
- for (const id of answers)
29075
- ids.add(id);
29076
- }
29077
- return { results, ids };
29078
- };
29079
- const take = (results, ids) => {
29080
- for (const r of results)
29081
- recovered.add(r.uuid);
29082
- for (const id of ids)
29083
- answered.add(id);
29084
- };
29085
- const inserts = new Map;
29086
- for (let start = 0;start < chain.length; ) {
29087
- if (chain[start].type !== "assistant") {
29088
- start++;
29182
+ taken.push(result);
29183
+ for (const id of ids)
29184
+ claimed.add(id);
29185
+ }
29186
+ return { taken, claimed };
29187
+ };
29188
+ const openCallsOf = (entry) => new Set(callIdsOf(entry).filter((id) => !satisfied.has(id)));
29189
+ const insertAfter = new Map;
29190
+ const lastIndex = chain.length - 1;
29191
+ let index = 0;
29192
+ while (index <= lastIndex) {
29193
+ if (chain[index].type !== "assistant") {
29194
+ index++;
29089
29195
  continue;
29090
29196
  }
29091
- let end = start;
29092
- while (end + 1 < chain.length && chain[end + 1].type === "assistant")
29197
+ let end = index;
29198
+ while (end + 1 <= lastIndex && chain[end + 1].type === "assistant")
29093
29199
  end++;
29094
- if (end < chain.length - 1) {
29095
- const memberResults = [];
29096
- for (let k = start;k <= end; k++) {
29097
- const member = chain[k];
29098
- const { results, ids } = resultsFor(member.uuid, toolUseIdsOf(member).filter((id) => !answered.has(id)));
29099
- take(results, ids);
29100
- memberResults.push(...results);
29101
- }
29102
- const reachable = new Set(chain.slice(start, end + 1).map((e) => e.uuid));
29103
- const siblings = [];
29104
- const siblingResults = [];
29105
- for (const candidate of offChainAssistants) {
29106
- if (candidate.parentUuid === null || !reachable.has(candidate.parentUuid) || recovered.has(candidate.uuid))
29107
- continue;
29108
- reachable.add(candidate.uuid);
29109
- const calls = toolUseIdsOf(candidate);
29110
- const { results, ids } = resultsFor(candidate.uuid, calls.filter((id) => !answered.has(id)));
29111
- if (!calls.every((id) => answered.has(id) || ids.has(id)))
29112
- continue;
29113
- recovered.add(candidate.uuid);
29114
- take(results, ids);
29115
- siblings.push(candidate);
29116
- siblingResults.push(...results);
29117
- }
29118
- const found = [...siblings, ...memberResults, ...siblingResults];
29119
- if (found.length > 0)
29120
- inserts.set(chain[end].uuid, found);
29121
- }
29122
- start = end + 1;
29200
+ const members = chain.slice(index, end + 1);
29201
+ index = end + 1;
29202
+ if (end === lastIndex)
29203
+ continue;
29204
+ const memberResults = [];
29205
+ for (const member of members) {
29206
+ const { taken, claimed } = selectResults(member.uuid, openCallsOf(member));
29207
+ for (const result of taken)
29208
+ returned.add(result.uuid);
29209
+ for (const id of claimed)
29210
+ satisfied.add(id);
29211
+ memberResults.push(...taken);
29212
+ }
29213
+ const attachable = new Set(members.map((m) => m.uuid));
29214
+ const siblings = [];
29215
+ const siblingResults = [];
29216
+ for (const candidate of siblingCandidates) {
29217
+ if (candidate.parentUuid === null || !attachable.has(candidate.parentUuid) || returned.has(candidate.uuid))
29218
+ continue;
29219
+ attachable.add(candidate.uuid);
29220
+ const { taken, claimed } = selectResults(candidate.uuid, openCallsOf(candidate));
29221
+ const complete = callIdsOf(candidate).every((id) => satisfied.has(id) || claimed.has(id));
29222
+ if (!complete)
29223
+ continue;
29224
+ returned.add(candidate.uuid);
29225
+ for (const result of taken)
29226
+ returned.add(result.uuid);
29227
+ for (const id of claimed)
29228
+ satisfied.add(id);
29229
+ siblings.push(candidate);
29230
+ siblingResults.push(...taken);
29231
+ }
29232
+ const spliced = [...siblings, ...memberResults, ...siblingResults];
29233
+ if (spliced.length > 0)
29234
+ insertAfter.set(chain[end].uuid, spliced);
29123
29235
  }
29124
- if (inserts.size === 0)
29125
- return [...chain];
29126
- return chain.flatMap((e) => [e, ...inserts.get(e.uuid) ?? []]);
29236
+ const out = [];
29237
+ for (const entry of chain) {
29238
+ out.push(entry);
29239
+ const extra = insertAfter.get(entry.uuid);
29240
+ if (extra !== undefined)
29241
+ out.push(...extra);
29242
+ }
29243
+ return out;
29244
+ }
29245
+ function callIdsOf(entry) {
29246
+ if (entry.type !== "assistant")
29247
+ return [];
29248
+ return contentBlockIds(entry, "tool_use", "id");
29249
+ }
29250
+ function answerIdsOf(entry) {
29251
+ if (entry.type !== "user")
29252
+ return [];
29253
+ return contentBlockIds(entry, "tool_result", "tool_use_id");
29254
+ }
29255
+ function contentBlockIds(entry, blockType, idKey) {
29256
+ const content = entry.message?.content;
29257
+ if (!Array.isArray(content))
29258
+ return [];
29259
+ const ids = [];
29260
+ for (const block of content) {
29261
+ if (isRecord2(block) && block.type === blockType && typeof block[idKey] === "string")
29262
+ ids.push(block[idKey]);
29263
+ }
29264
+ return ids;
29127
29265
  }
29128
29266
  function isDescendant(byUuid, ancestorUuid, candidateUuid) {
29129
29267
  if (candidateUuid === ancestorUuid)
@@ -29527,7 +29665,7 @@ class TranscriptWriter2 {
29527
29665
  return entries;
29528
29666
  }
29529
29667
  }
29530
- var RUNTIME_ENGINE_VERSION2 = "0.0.41";
29668
+ var RUNTIME_ENGINE_VERSION2 = "0.0.43";
29531
29669
  function withPermissionJournal(writer, location) {
29532
29670
  return {
29533
29671
  transcriptPath: join27(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),
@@ -30253,4 +30391,4 @@ function selectClassifierRoute2(config, descriptorOf) {
30253
30391
  return { kind: "worker-eligible" };
30254
30392
  }
30255
30393
 
30256
- 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 };
30394
+ export { ChildResumeModeIncomparableError, resolveChildResumeMode, ensureDefaultMessagingRuntimeRegistered, buildContinuationChain, AGENT_TOOL_CANONICAL_NAME, computeActiveSlotSet, resolveSlotToProvider, createRpcBridge2, shellWords, 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 };