@yanlinglabs/winter-agent-runtime 0.0.40 → 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.
- package/dist/commands/resolver.d.ts +8 -13
- package/dist/context/agent-listing.d.ts +7 -11
- package/dist/context/attachments.d.ts +14 -23
- package/dist/context/dynamic-sections.d.ts +5 -5
- package/dist/context/git-branch-fixture.d.ts +22 -0
- package/dist/context/git-status.d.ts +2 -2
- package/dist/context/memory.d.ts +1 -1
- package/dist/context/output-styles.d.ts +2 -2
- package/dist/context/request-layout.d.ts +16 -17
- package/dist/context/seam.d.ts +7 -6
- package/dist/embedded-worker.js +3 -3
- package/dist/embedded.js +3 -3
- package/dist/engine.d.ts +3 -3
- package/dist/{index-r7yjcppm.js → index-14xkt4jp.js} +2 -2
- package/dist/{index-rhxkckyn.js → index-bh1ve8ws.js} +238 -154
- package/dist/{index-a8j104mg.js → index-kqc3tdph.js} +980 -828
- package/dist/index.js +1 -1
- package/dist/mcp/lifecycle.d.ts +2 -1
- package/dist/permissions/edit-recognition.d.ts +1 -1
- package/dist/permissions/evaluator.d.ts +19 -30
- package/dist/permissions/file-rules.d.ts +177 -277
- package/dist/permissions/grammar.d.ts +58 -0
- package/dist/permissions/policy-state.d.ts +1 -1
- package/dist/permissions/ruleset.d.ts +1 -1
- package/dist/permissions/shell-structure.d.ts +1 -1
- package/dist/plugins/bundle.d.ts +4 -6
- package/dist/plugins/loader.corpus-fixture.d.ts +26 -0
- package/dist/plugins/loader.d.ts +3 -4
- package/dist/plugins/manifest.d.ts +23 -46
- package/dist/production-wiring.d.ts +1 -1
- package/dist/provider/lean-prompt.d.ts +1 -6
- package/dist/sandbox/profile.d.ts +39 -79
- package/dist/sandbox/spawn.d.ts +5 -6
- package/dist/skills/listing.d.ts +8 -26
- package/dist/skills/store.d.ts +2 -3
- package/dist/store/dialect.d.ts +1 -1
- package/dist/subagents/availability.d.ts +10 -8
- package/dist/subagents/child-handle.d.ts +5 -5
- package/dist/subagents/definitions.d.ts +7 -6
- package/dist/subagents/fork.d.ts +4 -8
- package/dist/subagents/notification-queue.d.ts +30 -38
- package/dist/testing.js +2 -2
- package/dist/tools/descriptors/web-fetch.d.ts +1 -1
- package/dist/tools/descriptors/web-search.d.ts +1 -1
- package/dist/tools/impl/_search-budget.d.ts +1 -1
- package/dist/tools/impl/_web-fetch-net.d.ts +6 -6
- package/dist/tools/impl/_web-search-assembler.d.ts +7 -20
- package/dist/tools/impl/background-task-runtime.d.ts +13 -13
- package/dist/tools/impl/bash.d.ts +3 -3
- package/dist/tools/impl/monitor.d.ts +2 -2
- package/dist/tools/registry.d.ts +12 -0
- package/dist/toolsearch/exposure.d.ts +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web/fetchable-url.d.ts +1 -1
- package/dist/web/preapproved-hosts.d.ts +9 -31
- package/dist/workflows/store.d.ts +23 -31
- 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
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
-
|
|
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
|
|
763
|
-
if (
|
|
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
|
|
769
|
-
if (
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
const
|
|
960
|
-
if (mcp
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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 (
|
|
976
|
-
return
|
|
977
|
-
if (direction === "allow")
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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 (
|
|
992
|
-
if (
|
|
993
|
-
return
|
|
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 (
|
|
996
|
-
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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";
|
|
@@ -1187,6 +1131,11 @@ var CONCURRENCY_LANE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
|
1187
1131
|
function isValidConcurrencyLane(lane) {
|
|
1188
1132
|
return typeof lane === "string" && CONCURRENCY_LANE.test(lane);
|
|
1189
1133
|
}
|
|
1134
|
+
function concurrentToolSet(raw) {
|
|
1135
|
+
if (!Array.isArray(raw))
|
|
1136
|
+
return new Set;
|
|
1137
|
+
return new Set(raw.filter((name) => typeof name === "string"));
|
|
1138
|
+
}
|
|
1190
1139
|
function registerMcpServerTools(server, incomingTools, opts) {
|
|
1191
1140
|
if (RESERVED_MCP_SERVER_NAMES.has(server)) {
|
|
1192
1141
|
throw new Error(`registerMcpServerTools: "${server}" is a RESERVED server name -- the standing Winter server ` + `(its name is defined in mcp/winter-server.ts) owns this identity and is registry-native; it is never installed through ` + `this live-mutation function, and nothing else may register live tools under it either.`);
|
|
@@ -1238,12 +1187,13 @@ function registerMcpServerTools(server, incomingTools, opts) {
|
|
|
1238
1187
|
}
|
|
1239
1188
|
}
|
|
1240
1189
|
const nowOwned = new Set;
|
|
1190
|
+
const concurrent = concurrentToolSet(opts.concurrentTools);
|
|
1241
1191
|
for (const tool of tools) {
|
|
1242
1192
|
const canonicalName = registeredName(tool);
|
|
1243
1193
|
const existingEntry = registry.get(canonicalName);
|
|
1244
1194
|
const built = buildMcpToolDescriptor(server, tool, opts, plainNameFor(tool));
|
|
1245
1195
|
const lane = opts.toolLanes !== undefined && Object.hasOwn(opts.toolLanes, tool.name) ? opts.toolLanes[tool.name] : undefined;
|
|
1246
|
-
const descriptor = isValidConcurrencyLane(lane) ? { ...built, concurrencyLane: lane } : built;
|
|
1196
|
+
const descriptor = isValidConcurrencyLane(lane) ? { ...built, concurrencyLane: lane } : concurrent.has(tool.name) ? { ...built, concurrencySafe: true } : built;
|
|
1247
1197
|
registry.set(canonicalName, existingEntry ? { ...existingEntry, descriptor } : { descriptor });
|
|
1248
1198
|
mcpToolOwner.set(canonicalName, server);
|
|
1249
1199
|
nowOwned.add(canonicalName);
|
|
@@ -1699,37 +1649,53 @@ function resolveRealTarget(path) {
|
|
|
1699
1649
|
return join(resolveRealTarget(dir), basename(path));
|
|
1700
1650
|
}
|
|
1701
1651
|
}
|
|
1702
|
-
var MAX_SYMLINK_CHAIN_HOPS = 40;
|
|
1703
1652
|
function resolveSymlinkTargetChain(path) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
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() };
|
|
1730
1682
|
}
|
|
1731
1683
|
return;
|
|
1732
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
|
+
}
|
|
1733
1699
|
function resolveSymlinkEnds(path) {
|
|
1734
1700
|
return { target: resolveRealTarget(path), chainTarget: resolveSymlinkTargetChain(path) };
|
|
1735
1701
|
}
|
|
@@ -1746,7 +1712,7 @@ function checkSymlinkBothEnds(path, matcher) {
|
|
|
1746
1712
|
|
|
1747
1713
|
// src/permissions/file-rules.ts
|
|
1748
1714
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1749
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative
|
|
1715
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative } from "node:path";
|
|
1750
1716
|
import ignoreFactory from "ignore";
|
|
1751
1717
|
function fileRuleKindFor(toolName) {
|
|
1752
1718
|
switch (toolName) {
|
|
@@ -1767,20 +1733,14 @@ function canonicalFileRuleAuthoringToolName(kind) {
|
|
|
1767
1733
|
}
|
|
1768
1734
|
var INERT_ANCHOR = Symbol("file-rule-inert-anchor");
|
|
1769
1735
|
function resolveFileRuleAnchor(pattern, opts) {
|
|
1770
|
-
if (pattern.startsWith("//"))
|
|
1736
|
+
if (pattern.startsWith("//"))
|
|
1771
1737
|
return { relativePattern: pattern.slice(1), root: "/" };
|
|
1772
|
-
|
|
1773
|
-
if (pattern.startsWith("~/")) {
|
|
1738
|
+
if (pattern.startsWith("~/"))
|
|
1774
1739
|
return { relativePattern: pattern.slice(1), root: opts.home };
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
return { relativePattern: pattern, root: INERT_ANCHOR };
|
|
1779
|
-
return { relativePattern: pattern, root: opts.sourceDir };
|
|
1780
|
-
}
|
|
1781
|
-
if (pattern.startsWith("./")) {
|
|
1740
|
+
if (pattern.startsWith("/"))
|
|
1741
|
+
return { relativePattern: pattern, root: opts.sourceDir ?? INERT_ANCHOR };
|
|
1742
|
+
if (pattern.startsWith("./"))
|
|
1782
1743
|
return { relativePattern: pattern.slice(2), root: null };
|
|
1783
|
-
}
|
|
1784
1744
|
return { relativePattern: pattern, root: null };
|
|
1785
1745
|
}
|
|
1786
1746
|
var RULE_PATH_GLOB_CHARS = /[*?[\]]/;
|
|
@@ -1847,36 +1807,19 @@ function escapeRegexLiteralPath(path) {
|
|
|
1847
1807
|
return path.replace(/[.^$+{}()|\\[\]*?]/g, "\\$&");
|
|
1848
1808
|
}
|
|
1849
1809
|
function isSuspiciousRealpathResolution(original, resolved) {
|
|
1850
|
-
const
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
if (r.startsWith("/tmp/") && o === "/private" + r)
|
|
1855
|
-
return false;
|
|
1856
|
-
if (r.startsWith("/var/") && o === "/private" + r)
|
|
1810
|
+
const written = normalize2(original);
|
|
1811
|
+
const landed = normalize2(resolved);
|
|
1812
|
+
const spellings = acceptableSpellingsOf(written);
|
|
1813
|
+
if (spellings.includes(landed))
|
|
1857
1814
|
return false;
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (r.startsWith(o + "/"))
|
|
1867
|
-
return true;
|
|
1868
|
-
let p = r;
|
|
1869
|
-
if (r.startsWith("/tmp/"))
|
|
1870
|
-
p = "/private" + r;
|
|
1871
|
-
else if (r.startsWith("/var/"))
|
|
1872
|
-
p = "/private" + r;
|
|
1873
|
-
if (p !== r && p.startsWith(o + "/"))
|
|
1874
|
-
return true;
|
|
1875
|
-
const linkUnderOriginal = o.startsWith(r + "/");
|
|
1876
|
-
const linkUnderPrivateAlias = p !== r && o.startsWith(p + "/");
|
|
1877
|
-
if (o !== r && !(p !== r && o === p) && !linkUnderOriginal && !linkUnderPrivateAlias)
|
|
1878
|
-
return true;
|
|
1879
|
-
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];
|
|
1880
1823
|
}
|
|
1881
1824
|
function guardedCanonicalize(path) {
|
|
1882
1825
|
try {
|
|
@@ -1895,19 +1838,81 @@ function canonicalizedGlobFixedPrefix(absoluteGlob) {
|
|
|
1895
1838
|
return guardedCanonicalize(scan.literal.slice(0, scan.lastSepLiteralLength));
|
|
1896
1839
|
}
|
|
1897
1840
|
function ancestorDirectoriesOf(path) {
|
|
1898
|
-
const
|
|
1899
|
-
let current =
|
|
1900
|
-
|
|
1901
|
-
out.push(current);
|
|
1841
|
+
const ancestors = [];
|
|
1842
|
+
let current = path;
|
|
1843
|
+
for (;; ) {
|
|
1902
1844
|
const parent = dirname2(current);
|
|
1903
|
-
if (parent === current)
|
|
1904
|
-
|
|
1845
|
+
if (parent === current || parent === "/" || parent === ".")
|
|
1846
|
+
return ancestors;
|
|
1847
|
+
ancestors.push(parent);
|
|
1905
1848
|
current = parent;
|
|
1906
1849
|
}
|
|
1907
|
-
return out;
|
|
1908
1850
|
}
|
|
1909
|
-
function
|
|
1910
|
-
|
|
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));
|
|
1911
1916
|
}
|
|
1912
1917
|
function globToSbplRegexSource(absoluteGlob) {
|
|
1913
1918
|
const scan = scanDenyPathGlob(absoluteGlob);
|
|
@@ -1915,12 +1920,11 @@ function globToSbplRegexSource(absoluteGlob) {
|
|
|
1915
1920
|
return "^" + escapeRegexLiteralPath(guardedCanonicalize(scan.literal)) + "$";
|
|
1916
1921
|
const canonicalPrefix = canonicalizedGlobFixedPrefix(absoluteGlob);
|
|
1917
1922
|
if (canonicalPrefix === undefined)
|
|
1918
|
-
return
|
|
1919
|
-
return "^" + escapeRegexLiteralPath(canonicalPrefix) +
|
|
1923
|
+
return globToAnchoredRegex(absoluteGlob);
|
|
1924
|
+
return "^" + escapeRegexLiteralPath(canonicalPrefix) + globToAnchoredRegex(absoluteGlob.slice(scan.lastSepRaw)).slice(1);
|
|
1920
1925
|
}
|
|
1921
1926
|
function recursiveGlobToSbplRegexSource(absoluteGlob) {
|
|
1922
|
-
|
|
1923
|
-
return base.slice(0, -1) + "(/.*)?$";
|
|
1927
|
+
return globToSbplRegexSource(absoluteGlob).replace(/\$$/, "(/.*)?$");
|
|
1924
1928
|
}
|
|
1925
1929
|
function splitDenyPathsByGlobShape(paths) {
|
|
1926
1930
|
const plain = [];
|
|
@@ -1950,18 +1954,37 @@ function globDenyEntriesOf(paths) {
|
|
|
1950
1954
|
}
|
|
1951
1955
|
function normalizeFileRulePattern(relativePattern) {
|
|
1952
1956
|
const collapsed = relativePattern.replace(/\/{2,}/g, "/");
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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";
|
|
1956
1976
|
}
|
|
1957
1977
|
function unanchorTrailingDoubleStar(pattern, isAllow) {
|
|
1958
1978
|
if (!pattern.endsWith("/**"))
|
|
1959
1979
|
return pattern;
|
|
1960
|
-
const
|
|
1961
|
-
if (
|
|
1962
|
-
return
|
|
1963
|
-
|
|
1964
|
-
|
|
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;
|
|
1965
1988
|
}
|
|
1966
1989
|
|
|
1967
1990
|
class FileRuleCompileError extends Error {
|
|
@@ -1970,90 +1993,94 @@ class FileRuleCompileError extends Error {
|
|
|
1970
1993
|
this.name = "FileRuleCompileError";
|
|
1971
1994
|
}
|
|
1972
1995
|
}
|
|
1973
|
-
var INVALID_RELATIVE_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
1974
|
-
function isPathValidRelative(rel) {
|
|
1975
|
-
return rel !== "" && !INVALID_RELATIVE_PATH.test(rel);
|
|
1976
|
-
}
|
|
1977
1996
|
function matchFileRulesGrouped(candidates, path, opts, behavior) {
|
|
1978
|
-
|
|
1979
|
-
return null;
|
|
1997
|
+
const isAllow = behavior === "allow";
|
|
1980
1998
|
const groups = new Map;
|
|
1981
|
-
const groupOrder = [];
|
|
1982
1999
|
for (const candidate of candidates) {
|
|
1983
2000
|
const anchor = resolveFileRuleAnchor(candidate.pattern, { home: opts.home, sourceDir: candidate.sourceDir });
|
|
1984
2001
|
if (anchor.root === INERT_ANCHOR)
|
|
1985
2002
|
continue;
|
|
1986
|
-
const
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
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);
|
|
1990
2006
|
if (group === undefined) {
|
|
1991
|
-
group = {
|
|
1992
|
-
groups.set(
|
|
1993
|
-
groupOrder.push(rootKey);
|
|
2007
|
+
group = { matcher: ignoreFactory({ ignorecase: true }), entryByPattern: new Map };
|
|
2008
|
+
groups.set(root, group);
|
|
1994
2009
|
}
|
|
1995
|
-
group.
|
|
1996
|
-
group.
|
|
2010
|
+
group.matcher.add(compiled);
|
|
2011
|
+
group.entryByPattern.set(compiled, candidate.entry);
|
|
1997
2012
|
}
|
|
1998
|
-
for (const
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2001
|
-
if (!isPathValidRelative(rel))
|
|
2013
|
+
for (const [root, group] of groups) {
|
|
2014
|
+
const rel = relative(root, path);
|
|
2015
|
+
if (isOutsideIgnoreRoot(rel))
|
|
2002
2016
|
continue;
|
|
2003
2017
|
let result;
|
|
2004
2018
|
try {
|
|
2005
|
-
result = group.
|
|
2006
|
-
} catch (
|
|
2007
|
-
throw new FileRuleCompileError(`a file-rule pattern under ${JSON.stringify(
|
|
2008
|
-
}
|
|
2009
|
-
if (result.ignored && result.rule) {
|
|
2010
|
-
const winner = group.byCompiledPattern.get(result.rule.pattern);
|
|
2011
|
-
if (winner !== undefined)
|
|
2012
|
-
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 });
|
|
2013
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);
|
|
2014
2028
|
}
|
|
2015
2029
|
return null;
|
|
2016
2030
|
}
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
["/private/var", "/var"],
|
|
2020
|
-
["/private/etc", "/etc"],
|
|
2021
|
-
["/usr/bin", "/bin"],
|
|
2022
|
-
["/usr/lib", "/lib"],
|
|
2023
|
-
["/usr/sbin", "/sbin"]
|
|
2024
|
-
];
|
|
2025
|
-
var cachedTrustedSymlinkEquivalences;
|
|
2026
|
-
function trustedSymlinkEquivalences() {
|
|
2027
|
-
if (cachedTrustedSymlinkEquivalences !== undefined)
|
|
2028
|
-
return cachedTrustedSymlinkEquivalences;
|
|
2029
|
-
const result = new Map;
|
|
2030
|
-
for (const [real, alias] of TRUSTED_SYMLINK_CANDIDATES) {
|
|
2031
|
-
try {
|
|
2032
|
-
if (realpathSync2(alias) === real)
|
|
2033
|
-
result.set(real, alias);
|
|
2034
|
-
} catch {}
|
|
2035
|
-
}
|
|
2036
|
-
cachedTrustedSymlinkEquivalences = result;
|
|
2037
|
-
return result;
|
|
2031
|
+
function isOutsideIgnoreRoot(rel) {
|
|
2032
|
+
return rel === "" || rel === "." || rel === ".." || rel.startsWith("/") || rel.startsWith("./") || rel.startsWith("../");
|
|
2038
2033
|
}
|
|
2039
2034
|
function canonicalizeTrustedSymlinkPath(path) {
|
|
2040
|
-
for (const
|
|
2041
|
-
if (path === real
|
|
2035
|
+
for (const { real, alias } of verifiedTrustedSymlinkPairs()) {
|
|
2036
|
+
if (path === real)
|
|
2037
|
+
return alias;
|
|
2038
|
+
if (path.startsWith(real + "/"))
|
|
2042
2039
|
return alias + path.slice(real.length);
|
|
2043
2040
|
}
|
|
2044
2041
|
return path;
|
|
2045
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
|
+
}
|
|
2046
2064
|
function isPathWithinRoot(childPath, rootPath, opts = {}) {
|
|
2047
2065
|
const caseFold = opts.caseFold ?? true;
|
|
2048
|
-
const
|
|
2049
|
-
|
|
2050
|
-
|
|
2066
|
+
const prepare = (p) => {
|
|
2067
|
+
const short = shortSystemAlias(p);
|
|
2068
|
+
return caseFold ? short.toLowerCase() : short;
|
|
2069
|
+
};
|
|
2070
|
+
const rel = relative(prepare(rootPath), prepare(childPath));
|
|
2051
2071
|
if (rel === "")
|
|
2052
2072
|
return true;
|
|
2053
|
-
if (rel === ".." || rel.startsWith(
|
|
2073
|
+
if (rel === ".." || rel.startsWith("../"))
|
|
2054
2074
|
return false;
|
|
2055
2075
|
return !isAbsolute2(rel);
|
|
2056
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
|
+
}
|
|
2057
2084
|
function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
|
|
2058
2085
|
if (candidatePath.includes("\\"))
|
|
2059
2086
|
return false;
|
|
@@ -2073,8 +2100,16 @@ function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
|
|
|
2073
2100
|
return !isAbsolute2(rel);
|
|
2074
2101
|
}
|
|
2075
2102
|
function escapeFileRulePathSegment(path) {
|
|
2076
|
-
const
|
|
2077
|
-
|
|
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;
|
|
2078
2113
|
}
|
|
2079
2114
|
function matchesSingleFileRulePattern(pattern, path, opts, behavior) {
|
|
2080
2115
|
return matchFileRulesGrouped([{ entry: true, pattern }], path, opts, behavior) !== null;
|
|
@@ -2114,113 +2149,120 @@ function canonicalizePath(p) {
|
|
|
2114
2149
|
}
|
|
2115
2150
|
}
|
|
2116
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
|
+
}
|
|
2117
2174
|
function buildAncestorRenameBypassBlock(plainDenyPaths, globFixedPrefixes) {
|
|
2118
|
-
const
|
|
2119
|
-
for (const
|
|
2120
|
-
const
|
|
2121
|
-
|
|
2122
|
-
for (const
|
|
2123
|
-
|
|
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));
|
|
2124
2181
|
}
|
|
2125
2182
|
for (const prefix of globFixedPrefixes) {
|
|
2126
|
-
|
|
2127
|
-
for (const
|
|
2128
|
-
|
|
2183
|
+
block.add(literalClause(prefix));
|
|
2184
|
+
for (const dir of ancestorDirectoriesOf(prefix))
|
|
2185
|
+
block.add(literalClause(dir));
|
|
2129
2186
|
}
|
|
2130
|
-
|
|
2131
|
-
return "";
|
|
2132
|
-
return [`(deny file-write-unlink file-write-create`, ...[...clauses].map((c) => ` ${c}`)].join(`
|
|
2133
|
-
`) + ")";
|
|
2187
|
+
return block.render("(deny file-write-unlink file-write-create");
|
|
2134
2188
|
}
|
|
2135
2189
|
function buildReadDenyWritePermitBlock(writableRoots) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
return
|
|
2140
|
-
`) + ")";
|
|
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");
|
|
2141
2194
|
}
|
|
2142
2195
|
var IMAGE_PREP_DIRNAME = "image-prep";
|
|
2143
2196
|
var WRITE_OPS_SURVIVING_READ_DENY_REPERMIT = "file-write* file-write-unlink file-write-create";
|
|
2144
2197
|
var DEFAULT_PROTECTED_FILES = [".gitconfig", ".gitmodules", ".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile", ".ripgreprc", ".mcp.json"];
|
|
2145
2198
|
var DEFAULT_PROTECTED_DIRS = [".vscode", ".idea", ".claude/commands", ".claude/agents"];
|
|
2146
2199
|
var PROJECT_DIR_PROTECTED_KINDS = ["commands", "agents", "skills", "rules", "output-styles"];
|
|
2147
|
-
function cwdAnchoredEntryRegex(cwd, entry, recursive) {
|
|
2148
|
-
const absoluteGlob = join3(cwd, "**", entry) + (recursive ? "/**" : "");
|
|
2149
|
-
return recursiveGlobToSbplRegexSource(absoluteGlob);
|
|
2150
|
-
}
|
|
2151
|
-
function buildDefaultWriteProtectionEntries(cwd, brand, allowGitConfigWrites) {
|
|
2152
|
-
const plainPaths = [];
|
|
2153
|
-
const regexes = [];
|
|
2154
|
-
for (const f of DEFAULT_PROTECTED_FILES) {
|
|
2155
|
-
plainPaths.push(join3(cwd, f));
|
|
2156
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, f, false));
|
|
2157
|
-
}
|
|
2158
|
-
const winterMcpJson = join3(brand.projectDirName, "mcp.json");
|
|
2159
|
-
plainPaths.push(join3(cwd, winterMcpJson));
|
|
2160
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, winterMcpJson, false));
|
|
2161
|
-
for (const d of DEFAULT_PROTECTED_DIRS) {
|
|
2162
|
-
plainPaths.push(join3(cwd, d));
|
|
2163
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
|
|
2164
|
-
}
|
|
2165
|
-
for (const kind of PROJECT_DIR_PROTECTED_KINDS) {
|
|
2166
|
-
const d = join3(brand.projectDirName, kind);
|
|
2167
|
-
plainPaths.push(join3(cwd, d));
|
|
2168
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
|
|
2169
|
-
}
|
|
2170
|
-
plainPaths.push(join3(cwd, ".git", "hooks"));
|
|
2171
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, ".git/hooks", true));
|
|
2172
|
-
if (!allowGitConfigWrites) {
|
|
2173
|
-
plainPaths.push(join3(cwd, ".git", "config"));
|
|
2174
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, ".git/config", false));
|
|
2175
|
-
}
|
|
2176
|
-
return { plainPaths, regexes };
|
|
2177
|
-
}
|
|
2178
2200
|
function buildDefaultWriteProtectionBlock(cwd, brand, allowGitConfigWrites) {
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
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(`
|
|
2186
2221
|
`);
|
|
2187
2222
|
}
|
|
2188
2223
|
function isProperDescendantOf(candidate, root) {
|
|
2189
|
-
|
|
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})`;
|
|
2190
2233
|
}
|
|
2191
2234
|
function buildReadDenyKeepInPlaceBlock(plainDenyReadPaths, globDenyReadEntries, writableRoots) {
|
|
2192
2235
|
if (writableRoots.length === 0)
|
|
2193
2236
|
return "";
|
|
2194
|
-
const
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (isUnderAnyWriteRoot(ancestor))
|
|
2200
|
-
clauses.add(`(literal "${sbplString(ancestor)}")`);
|
|
2201
|
-
};
|
|
2202
|
-
const clauses = new Set;
|
|
2203
|
-
for (const rawPath of plainDenyReadPaths) {
|
|
2204
|
-
const path = canon(rawPath);
|
|
2205
|
-
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))
|
|
2206
2242
|
continue;
|
|
2207
|
-
|
|
2208
|
-
|
|
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
|
+
}
|
|
2209
2249
|
}
|
|
2210
|
-
for (const
|
|
2211
|
-
const
|
|
2212
|
-
|
|
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)
|
|
2213
2254
|
continue;
|
|
2214
|
-
const
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
if (
|
|
2218
|
-
|
|
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
|
+
}
|
|
2219
2264
|
}
|
|
2220
|
-
|
|
2221
|
-
return "";
|
|
2222
|
-
return [`(deny file-write-unlink`, ...[...clauses].map((c) => ` ${c}`)].join(`
|
|
2223
|
-
`) + ")";
|
|
2265
|
+
return block.render("(deny file-write-unlink");
|
|
2224
2266
|
}
|
|
2225
2267
|
var CONTROL_PLANE_FILES = ["permissions.local.json", "settings.json", "settings.local.json"];
|
|
2226
2268
|
function caseFoldSegment(segment) {
|
|
@@ -2628,23 +2670,22 @@ registerAttachmentRenderer("agent_listing_delta", (a) => {
|
|
|
2628
2670
|
const addedLines = stringArray(a["addedLines"]);
|
|
2629
2671
|
const addedTypes = stringArray(a["addedTypes"]);
|
|
2630
2672
|
const removedTypes = stringArray(a["removedTypes"]);
|
|
2673
|
+
const initial = a["isInitial"] === true;
|
|
2631
2674
|
const sections = [];
|
|
2632
2675
|
const hasAdded = addedLines.length > 0 && addedTypes.length > 0;
|
|
2633
2676
|
if (hasAdded)
|
|
2634
|
-
sections.push(`${
|
|
2677
|
+
sections.push(`${initial ? AGENT_LISTING_INITIAL_HEADER : AGENT_LISTING_ADDED_HEADER}
|
|
2635
2678
|
${addedLines.join(`
|
|
2636
2679
|
`)}`);
|
|
2637
2680
|
if (removedTypes.length > 0) {
|
|
2638
2681
|
sections.push(`${AGENT_LISTING_REMOVED_HEADER}
|
|
2639
|
-
${removedTypes.map((
|
|
2682
|
+
${removedTypes.map((name) => `- ${name}`).join(`
|
|
2640
2683
|
`)}`);
|
|
2641
2684
|
sections.push(AMBIENT_CONTEXT_SENTENCE);
|
|
2642
2685
|
}
|
|
2643
|
-
if (hasAdded &&
|
|
2686
|
+
if (hasAdded && initial && a["showConcurrencyNote"] === true)
|
|
2644
2687
|
sections.push(AGENT_CONCURRENCY_SENTENCE);
|
|
2645
|
-
|
|
2646
|
-
return;
|
|
2647
|
-
return neutralizeReminderTags(sections.join(`
|
|
2688
|
+
return sections.length === 0 ? undefined : neutralizeReminderTags(sections.join(`
|
|
2648
2689
|
|
|
2649
2690
|
`));
|
|
2650
2691
|
});
|
|
@@ -2740,10 +2781,10 @@ function announcedAgentTypes(messages) {
|
|
|
2740
2781
|
if (a.type !== "agent_listing_delta")
|
|
2741
2782
|
continue;
|
|
2742
2783
|
if (Array.isArray(a["addedLines"]))
|
|
2743
|
-
for (const
|
|
2744
|
-
announced.add(
|
|
2745
|
-
for (const
|
|
2746
|
-
announced.delete(
|
|
2784
|
+
for (const type of stringArray(a["addedTypes"]))
|
|
2785
|
+
announced.add(type);
|
|
2786
|
+
for (const type of stringArray(a["removedTypes"]))
|
|
2787
|
+
announced.delete(type);
|
|
2747
2788
|
}
|
|
2748
2789
|
return announced;
|
|
2749
2790
|
}
|
|
@@ -2779,26 +2820,26 @@ function localDateString(now = new Date) {
|
|
|
2779
2820
|
function xmlEscape(value) {
|
|
2780
2821
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2781
2822
|
}
|
|
2782
|
-
var TAG_ROOT = "task-notification";
|
|
2783
|
-
var TAG_ORDER = [
|
|
2784
|
-
["task-id", "taskId"],
|
|
2785
|
-
["tool-use-id", "toolUseId"],
|
|
2786
|
-
["task-type", "taskType"],
|
|
2787
|
-
["output-file", "outputFile"],
|
|
2788
|
-
["status", "status"],
|
|
2789
|
-
["summary", "summary"]
|
|
2790
|
-
];
|
|
2791
2823
|
function renderTaskNotification(fields) {
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
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 += `
|
|
2798
2836
|
<${tag}>${value}</${tag}>`;
|
|
2799
2837
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
2838
|
+
out += fields.body ?? "";
|
|
2839
|
+
out += `
|
|
2840
|
+
</task-notification>`;
|
|
2841
|
+
out += fields.trailing ?? "";
|
|
2842
|
+
return out;
|
|
2802
2843
|
}
|
|
2803
2844
|
var SYSTEM_NOTIFICATION_MARKER = "[SYSTEM NOTIFICATION - NOT USER INPUT]";
|
|
2804
2845
|
var NOTIFICATION_PREAMBLE = `${SYSTEM_NOTIFICATION_MARKER}
|
|
@@ -2818,34 +2859,60 @@ function withNotificationPreamble(value, opts) {
|
|
|
2818
2859
|
return value;
|
|
2819
2860
|
return `${opts?.inHumanTurn === true ? NOTIFICATION_PREAMBLE_IN_HUMAN_TURN : NOTIFICATION_PREAMBLE}${neutralizeReminderTags(value)}`;
|
|
2820
2861
|
}
|
|
2821
|
-
function
|
|
2862
|
+
function nonEmpty(value) {
|
|
2863
|
+
return typeof value === "string" && value.length > 0;
|
|
2864
|
+
}
|
|
2865
|
+
function statusWord(status) {
|
|
2822
2866
|
return status === "stopped" ? "killed" : status;
|
|
2823
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>";
|
|
2824
2881
|
function renderAgentNotification(input) {
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
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
|
+
}
|
|
2833
2903
|
return renderTaskNotification({
|
|
2834
2904
|
taskId: xmlEscape(input.taskId),
|
|
2835
|
-
...input.toolUseId
|
|
2836
|
-
|
|
2837
|
-
status: xmlStatus(input.status),
|
|
2905
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2906
|
+
status: statusWord(input.status),
|
|
2838
2907
|
summary: xmlEscape(`Agent "${input.description}" ${outcome}`),
|
|
2839
|
-
body
|
|
2840
|
-
<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
|
|
2841
2909
|
});
|
|
2842
2910
|
}
|
|
2843
2911
|
function renderShellNotification(input) {
|
|
2844
2912
|
return renderTaskNotification({
|
|
2845
2913
|
taskId: xmlEscape(input.taskId),
|
|
2846
|
-
...input.toolUseId
|
|
2847
|
-
|
|
2848
|
-
status: xmlStatus(input.status),
|
|
2914
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2915
|
+
status: statusWord(input.status),
|
|
2849
2916
|
summary: xmlEscape(input.summary)
|
|
2850
2917
|
});
|
|
2851
2918
|
}
|
|
@@ -2861,28 +2928,40 @@ function renderTaskStopNotification(input) {
|
|
|
2861
2928
|
const who = input.stoppedBy === "parent" ? "the assistant" : "user";
|
|
2862
2929
|
return renderTaskNotification({
|
|
2863
2930
|
taskId: xmlEscape(input.taskId),
|
|
2864
|
-
...input.toolUseId
|
|
2931
|
+
...optionalEscaped(input.toolUseId, undefined),
|
|
2865
2932
|
status: "stopped",
|
|
2866
2933
|
summary: xmlEscape(`Task "${input.description}" was stopped by ${who}`)
|
|
2867
2934
|
});
|
|
2868
2935
|
}
|
|
2869
2936
|
function renderWorkflowNotification(input) {
|
|
2870
|
-
const name = xmlEscape(input.name
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
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 += `
|
|
2875
2951
|
<failures>${xmlEscape(input.failures.join(`
|
|
2876
|
-
`))}</failures
|
|
2877
|
-
|
|
2878
|
-
|
|
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
|
+
}
|
|
2879
2959
|
return renderTaskNotification({
|
|
2880
2960
|
taskId: xmlEscape(input.taskId),
|
|
2881
|
-
...input.toolUseId
|
|
2882
|
-
|
|
2883
|
-
status: xmlStatus(input.status),
|
|
2961
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2962
|
+
status: statusWord(input.status),
|
|
2884
2963
|
summary,
|
|
2885
|
-
body
|
|
2964
|
+
body
|
|
2886
2965
|
});
|
|
2887
2966
|
}
|
|
2888
2967
|
var PRIORITY_RANK = { next: 0, later: 1 };
|
|
@@ -7002,8 +7081,17 @@ function isCacheableTransport(config) {
|
|
|
7002
7081
|
}
|
|
7003
7082
|
var FIRST_TURN_MCP_WAIT_DEFAULT_MS = 2000;
|
|
7004
7083
|
function firstTurnMcpWaitDeadlineMs(opts) {
|
|
7005
|
-
|
|
7006
|
-
|
|
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;
|
|
7007
7095
|
}
|
|
7008
7096
|
var sessionLifecycles = new Map;
|
|
7009
7097
|
function registerSessionMcpLifecycle(sessionId, lifecycle) {
|
|
@@ -9119,66 +9207,80 @@ function resolveBuiltinAgents(opts) {
|
|
|
9119
9207
|
}
|
|
9120
9208
|
|
|
9121
9209
|
// src/subagents/definitions.ts
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
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) };
|
|
9125
9216
|
}
|
|
9126
|
-
var
|
|
9127
|
-
function
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
const match = /^([a-zA-Z_-]+):\s+(.+)$/.exec(line);
|
|
9133
|
-
if (match) {
|
|
9134
|
-
const key = match[1];
|
|
9135
|
-
const value = match[2];
|
|
9136
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
9137
|
-
try {
|
|
9138
|
-
if (Array.isArray(Bun.YAML.parse(value))) {
|
|
9139
|
-
result.push(line);
|
|
9140
|
-
continue;
|
|
9141
|
-
}
|
|
9142
|
-
} catch {}
|
|
9143
|
-
}
|
|
9144
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
9145
|
-
result.push(line);
|
|
9146
|
-
continue;
|
|
9147
|
-
}
|
|
9148
|
-
if (YAML_SPECIAL_CHARS.test(value)) {
|
|
9149
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
9150
|
-
result.push(`${key}: "${escaped}"`);
|
|
9151
|
-
continue;
|
|
9152
|
-
}
|
|
9153
|
-
}
|
|
9154
|
-
result.push(line);
|
|
9155
|
-
}
|
|
9156
|
-
return result.join(`
|
|
9157
|
-
`);
|
|
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;
|
|
9158
9223
|
}
|
|
9159
|
-
function
|
|
9160
|
-
|
|
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) };
|
|
9161
9237
|
}
|
|
9162
|
-
function
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
const frontmatterText = match[1] ?? "";
|
|
9168
|
-
const body = stripped.slice(match[0].length);
|
|
9169
|
-
let attrs = {};
|
|
9238
|
+
function isPlainObject2(value) {
|
|
9239
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9240
|
+
}
|
|
9241
|
+
function readFrontmatterBlock(block) {
|
|
9242
|
+
let parsed;
|
|
9170
9243
|
try {
|
|
9171
|
-
|
|
9172
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
|
|
9173
|
-
attrs = parsed;
|
|
9244
|
+
parsed = Bun.YAML.parse(block);
|
|
9174
9245
|
} catch {
|
|
9175
9246
|
try {
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
}
|
|
9247
|
+
parsed = Bun.YAML.parse(detabLineStarts(quoteLooseValues(block)));
|
|
9248
|
+
} catch {
|
|
9249
|
+
return {};
|
|
9250
|
+
}
|
|
9180
9251
|
}
|
|
9181
|
-
return
|
|
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));
|
|
9182
9284
|
}
|
|
9183
9285
|
function asString(v) {
|
|
9184
9286
|
return typeof v === "string" ? v : undefined;
|
|
@@ -9489,67 +9591,70 @@ var PREAPPROVED_HOST_ENTRIES = [
|
|
|
9489
9591
|
"nginx.org",
|
|
9490
9592
|
"httpd.apache.org"
|
|
9491
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
|
+
}
|
|
9492
9604
|
var HOSTNAME_ONLY = new Set;
|
|
9493
9605
|
var PATH_PREFIXES = new Map;
|
|
9494
9606
|
for (const entry of PREAPPROVED_HOST_ENTRIES) {
|
|
9495
9607
|
const slash = entry.indexOf("/");
|
|
9496
9608
|
if (slash === -1) {
|
|
9497
9609
|
HOSTNAME_ONLY.add(entry);
|
|
9498
|
-
|
|
9499
|
-
const host = entry.slice(0, slash);
|
|
9500
|
-
const prefix = entry.slice(slash);
|
|
9501
|
-
const existing = PATH_PREFIXES.get(host);
|
|
9502
|
-
if (existing)
|
|
9503
|
-
existing.push(prefix);
|
|
9504
|
-
else
|
|
9505
|
-
PATH_PREFIXES.set(host, [prefix]);
|
|
9610
|
+
continue;
|
|
9506
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);
|
|
9507
9619
|
}
|
|
9508
|
-
var ENCODED_TRAVERSAL = /%(25)*(2f|5c|2e)/i;
|
|
9509
|
-
function
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
return
|
|
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}`])];
|
|
9518
9630
|
}
|
|
9519
9631
|
function isPreapprovedUrl(url) {
|
|
9520
9632
|
return isPreapprovedHost(url.hostname, url.pathname);
|
|
9521
9633
|
}
|
|
9522
|
-
function candidateHosts(hostname) {
|
|
9523
|
-
const stripped = hostname.startsWith("www.") ? hostname.slice(4) : hostname;
|
|
9524
|
-
return [...new Set([hostname, stripped, `www.${stripped}`])];
|
|
9525
|
-
}
|
|
9526
9634
|
function preapprovedScopeOf(url) {
|
|
9527
|
-
const
|
|
9528
|
-
for (const
|
|
9529
|
-
if (HOSTNAME_ONLY.has(
|
|
9530
|
-
return { host };
|
|
9531
|
-
const prefixes = PATH_PREFIXES.get(
|
|
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);
|
|
9532
9640
|
if (prefixes === undefined)
|
|
9533
9641
|
continue;
|
|
9534
|
-
if (
|
|
9642
|
+
if (isTraversalEncoded(pathname))
|
|
9535
9643
|
return;
|
|
9536
|
-
const prefix = prefixes.find((p) => pathname
|
|
9644
|
+
const prefix = prefixes.find((p) => underPrefix(pathname, p));
|
|
9537
9645
|
if (prefix !== undefined)
|
|
9538
|
-
return { host, pathPrefix: prefix };
|
|
9646
|
+
return { host: candidate, pathPrefix: prefix };
|
|
9539
9647
|
}
|
|
9540
9648
|
return;
|
|
9541
9649
|
}
|
|
9542
9650
|
function staysWithinScope(from, url) {
|
|
9543
|
-
|
|
9544
|
-
const acceptableHosts = new Set([from.host, stripped, `www.${stripped}`]);
|
|
9545
|
-
if (!acceptableHosts.has(url.hostname))
|
|
9651
|
+
if (!wwwVariants(from.host).includes(url.hostname))
|
|
9546
9652
|
return false;
|
|
9547
9653
|
if (from.pathPrefix === undefined)
|
|
9548
9654
|
return true;
|
|
9549
|
-
|
|
9550
|
-
if (ENCODED_TRAVERSAL.test(pathname))
|
|
9655
|
+
if (isTraversalEncoded(url.pathname))
|
|
9551
9656
|
return false;
|
|
9552
|
-
return pathname
|
|
9657
|
+
return underPrefix(url.pathname, from.pathPrefix);
|
|
9553
9658
|
}
|
|
9554
9659
|
|
|
9555
9660
|
// src/web/private-address.ts
|
|
@@ -9647,29 +9752,22 @@ function extractCandidateWritePaths(call, ctx) {
|
|
|
9647
9752
|
const recognized = recognizeEditOperation(call, { sessionRoot: ctx.sessionRoot, home: ctx.home, ...ctx.brand !== undefined ? { brand: ctx.brand } : {} });
|
|
9648
9753
|
return recognized ? recognized.paths : [];
|
|
9649
9754
|
}
|
|
9650
|
-
var TRAILING_DOT_OR_WHITESPACE_COMPONENT = /[.\s]+$/;
|
|
9651
|
-
var DEVICE_NAME_SUFFIX = /\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
|
|
9652
|
-
var TRIPLE_DOT_COMPONENT = /(^|\/|\\)\.{3,}(\/|\\|$)/;
|
|
9653
|
-
var SHORT_FILENAME_TILDE = /~\d/;
|
|
9654
9755
|
function isSuspiciousPath(path) {
|
|
9655
|
-
if (/^[
|
|
9756
|
+
if (/^[/\\]\?\?[/\\]/.test(path))
|
|
9656
9757
|
return true;
|
|
9657
|
-
if (
|
|
9758
|
+
if (/~[0-9]/.test(path))
|
|
9658
9759
|
return true;
|
|
9659
|
-
if (
|
|
9760
|
+
if (SUSPICIOUS_PATH_PREFIXES.some((prefix) => path.startsWith(prefix)))
|
|
9660
9761
|
return true;
|
|
9661
9762
|
for (const component of path.split(/[/\\]/)) {
|
|
9662
9763
|
if (component === "" || component === "." || component === "..")
|
|
9663
9764
|
continue;
|
|
9664
|
-
if (
|
|
9765
|
+
if (/[.\s]$/.test(component))
|
|
9665
9766
|
return true;
|
|
9666
9767
|
}
|
|
9667
|
-
|
|
9668
|
-
return true;
|
|
9669
|
-
if (TRIPLE_DOT_COMPONENT.test(path))
|
|
9670
|
-
return true;
|
|
9671
|
-
return false;
|
|
9768
|
+
return /\.(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(path);
|
|
9672
9769
|
}
|
|
9770
|
+
var SUSPICIOUS_PATH_PREFIXES = ["\\\\?\\", "\\\\.\\", "//?/", "//./"];
|
|
9673
9771
|
function suspiciousSymlinkTarget(rawCandidate, ctx) {
|
|
9674
9772
|
try {
|
|
9675
9773
|
const absPath = resolveTargetPath(rawCandidate, ctx.cwd);
|
|
@@ -11909,66 +12007,46 @@ ${DIRECTIVE_PREFIX}${input.prompt}`;
|
|
|
11909
12007
|
|
|
11910
12008
|
${worktreeNote(input.worktree.parentRoot, input.worktree.worktreeRoot)}` : directive;
|
|
11911
12009
|
}
|
|
11912
|
-
function
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
return block.type === "tool_result";
|
|
11917
|
-
}
|
|
11918
|
-
function answeredToolUseIds(messages) {
|
|
12010
|
+
function buildForkInitialMessages(inherit, forkToolUseId) {
|
|
12011
|
+
const messages = inherit.messages;
|
|
12012
|
+
if (messages === undefined)
|
|
12013
|
+
return [];
|
|
11919
12014
|
const answered = new Set;
|
|
11920
|
-
for (const
|
|
11921
|
-
if (
|
|
12015
|
+
for (const message of messages) {
|
|
12016
|
+
if (!Array.isArray(message.content))
|
|
11922
12017
|
continue;
|
|
11923
|
-
for (const block of
|
|
11924
|
-
if (
|
|
12018
|
+
for (const block of message.content) {
|
|
12019
|
+
if (block.type === "tool_result")
|
|
11925
12020
|
answered.add(block.tool_use_id);
|
|
12021
|
+
}
|
|
11926
12022
|
}
|
|
11927
|
-
|
|
11928
|
-
}
|
|
11929
|
-
function dropUnansweredAssistantMessages(messages) {
|
|
11930
|
-
const answered = answeredToolUseIds(messages);
|
|
11931
|
-
const filtered = [];
|
|
12023
|
+
const kept = [];
|
|
11932
12024
|
const dropped = [];
|
|
11933
|
-
for (const
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
continue;
|
|
11937
|
-
}
|
|
11938
|
-
const toolUses = m.content.filter(isToolUseBlock);
|
|
11939
|
-
const hasUnanswered = toolUses.some((call) => !answered.has(call.id));
|
|
11940
|
-
(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);
|
|
11941
12028
|
}
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
content: [ownToolUse],
|
|
11948
|
-
...original.origin !== undefined ? { origin: original.origin } : {}
|
|
11949
|
-
};
|
|
11950
|
-
}
|
|
11951
|
-
function buildForkInitialMessages(inherit, forkToolUseId) {
|
|
11952
|
-
if (inherit.messages === undefined)
|
|
11953
|
-
return [];
|
|
11954
|
-
const { filtered, dropped } = dropUnansweredAssistantMessages(inherit.messages);
|
|
11955
|
-
let ownToolUse;
|
|
11956
|
-
let sourceMessage;
|
|
11957
|
-
for (const m of dropped) {
|
|
11958
|
-
if (typeof m.content === "string")
|
|
11959
|
-
continue;
|
|
11960
|
-
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);
|
|
11961
12034
|
if (match !== undefined) {
|
|
11962
|
-
|
|
11963
|
-
|
|
12035
|
+
source = message;
|
|
12036
|
+
call = match;
|
|
11964
12037
|
break;
|
|
11965
12038
|
}
|
|
11966
12039
|
}
|
|
11967
|
-
if (
|
|
11968
|
-
return
|
|
11969
|
-
const clone =
|
|
11970
|
-
|
|
11971
|
-
|
|
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];
|
|
11972
12050
|
}
|
|
11973
12051
|
class ForkRequestLayoutUnavailableError extends Error {
|
|
11974
12052
|
constructor() {
|
|
@@ -12132,9 +12210,8 @@ function renderAgentToolSpec(entry) {
|
|
|
12132
12210
|
const tools = entry.tools ?? [];
|
|
12133
12211
|
const disallowed = entry.disallowedTools ?? [];
|
|
12134
12212
|
if (tools.length > 0 && disallowed.length > 0) {
|
|
12135
|
-
const
|
|
12136
|
-
|
|
12137
|
-
return effective.length === 0 ? "None" : effective.join(", ");
|
|
12213
|
+
const kept = tools.filter((name) => !disallowed.includes(name));
|
|
12214
|
+
return kept.length === 0 ? "None" : kept.join(", ");
|
|
12138
12215
|
}
|
|
12139
12216
|
if (tools.length > 0)
|
|
12140
12217
|
return tools.join(", ");
|
|
@@ -12143,26 +12220,25 @@ function renderAgentToolSpec(entry) {
|
|
|
12143
12220
|
return "All tools";
|
|
12144
12221
|
}
|
|
12145
12222
|
function renderAgentListingLine(entry, leanModel = false) {
|
|
12146
|
-
const
|
|
12147
|
-
|
|
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)})`);
|
|
12148
12226
|
}
|
|
12149
12227
|
function computeAgentListingDelta(available, history, opts = {}) {
|
|
12150
12228
|
const announced = announcedAgentTypes(history);
|
|
12151
|
-
const availableTypes = new Set(available.map((
|
|
12152
|
-
const added = available.filter((
|
|
12153
|
-
const removed = [];
|
|
12154
|
-
for (const type of announced)
|
|
12155
|
-
if (!availableTypes.has(type))
|
|
12156
|
-
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));
|
|
12157
12232
|
if (added.length === 0 && removed.length === 0)
|
|
12158
12233
|
return;
|
|
12159
|
-
added.sort((a, b) => a.agentType.localeCompare(b.agentType));
|
|
12160
|
-
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;
|
|
12161
12237
|
return {
|
|
12162
12238
|
type: "agent_listing_delta",
|
|
12163
|
-
addedTypes:
|
|
12164
|
-
addedLines:
|
|
12165
|
-
removedTypes:
|
|
12239
|
+
addedTypes: sortedAdded.map((entry) => entry.agentType),
|
|
12240
|
+
addedLines: sortedAdded.map((entry) => renderAgentListingLine(entry, lean)),
|
|
12241
|
+
removedTypes: sortedRemoved,
|
|
12166
12242
|
isInitial: announced.size === 0,
|
|
12167
12243
|
showConcurrencyNote: opts.showConcurrencyNote ?? true
|
|
12168
12244
|
};
|
|
@@ -12427,53 +12503,55 @@ function isUserRole(message) {
|
|
|
12427
12503
|
function isReorderStop(message) {
|
|
12428
12504
|
if (message.role === "assistant")
|
|
12429
12505
|
return true;
|
|
12430
|
-
|
|
12506
|
+
if (typeof message.content === "string")
|
|
12507
|
+
return false;
|
|
12508
|
+
const first = message.content[0];
|
|
12509
|
+
return first !== undefined && first.type === "tool_result";
|
|
12431
12510
|
}
|
|
12432
12511
|
function reorderAttachments(messages, stays = () => false) {
|
|
12433
12512
|
if (!messages.some((m) => m.meta !== undefined))
|
|
12434
12513
|
return [...messages];
|
|
12435
|
-
const
|
|
12436
|
-
const
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
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);
|
|
12441
12521
|
continue;
|
|
12442
12522
|
}
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
pending.length = 0;
|
|
12448
|
-
} else {
|
|
12449
|
-
reversed.push(m);
|
|
12450
|
-
}
|
|
12523
|
+
const group = { fixed: message, moved: [] };
|
|
12524
|
+
groups.push(group);
|
|
12525
|
+
if (isReorderStop(message))
|
|
12526
|
+
landing = group.moved;
|
|
12451
12527
|
}
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12528
|
+
const out = [...top];
|
|
12529
|
+
for (const group of groups)
|
|
12530
|
+
out.push(group.fixed, ...group.moved);
|
|
12531
|
+
return out;
|
|
12455
12532
|
}
|
|
12456
12533
|
function hoistToolResults(blocks) {
|
|
12457
|
-
const results =
|
|
12458
|
-
const rest =
|
|
12459
|
-
for (const b of blocks)
|
|
12460
|
-
(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");
|
|
12461
12536
|
return [...results, ...rest];
|
|
12462
12537
|
}
|
|
12463
12538
|
function joinUserBlocks(prev, next) {
|
|
12464
12539
|
const last = prev[prev.length - 1];
|
|
12465
12540
|
const first = next[0];
|
|
12466
|
-
if (last
|
|
12541
|
+
if (last !== undefined && last.type === "text" && first !== undefined && first.type === "text") {
|
|
12467
12542
|
return [...prev.slice(0, -1), { ...last, text: `${last.text}
|
|
12468
12543
|
` }, ...next];
|
|
12544
|
+
}
|
|
12469
12545
|
return [...prev, ...next];
|
|
12470
12546
|
}
|
|
12547
|
+
var SMOOSH_EXEMPT_TAG = `<system-reminder>
|
|
12548
|
+
`;
|
|
12549
|
+
var SMOOSH_EXEMPT_BODIES = ["<system>authentic event nonces for this delivery: ", "<event "];
|
|
12471
12550
|
function isSmooshExempt(text) {
|
|
12472
|
-
if (!text.startsWith(
|
|
12473
|
-
`))
|
|
12551
|
+
if (!text.startsWith(SMOOSH_EXEMPT_TAG))
|
|
12474
12552
|
return false;
|
|
12475
|
-
const
|
|
12476
|
-
return
|
|
12553
|
+
const rest = text.slice(SMOOSH_EXEMPT_TAG.length);
|
|
12554
|
+
return SMOOSH_EXEMPT_BODIES.some((prefix) => rest.startsWith(prefix));
|
|
12477
12555
|
}
|
|
12478
12556
|
function foldTextIntoToolResult(result, texts) {
|
|
12479
12557
|
if (texts.length === 0)
|
|
@@ -12481,44 +12559,44 @@ function foldTextIntoToolResult(result, texts) {
|
|
|
12481
12559
|
const content = result.content;
|
|
12482
12560
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_reference"))
|
|
12483
12561
|
return null;
|
|
12484
|
-
if ((result.loadedTools
|
|
12562
|
+
if (Array.isArray(result.loadedTools) && result.loadedTools.length > 0)
|
|
12485
12563
|
return null;
|
|
12486
12564
|
if (typeof content === "string") {
|
|
12487
|
-
const
|
|
12565
|
+
const pieces = [content.trim(), ...texts.map((t) => t.text.trim())].filter((piece) => piece.length > 0);
|
|
12566
|
+
return { ...result, content: pieces.join(`
|
|
12488
12567
|
|
|
12489
|
-
`);
|
|
12490
|
-
return { ...result, content: joined };
|
|
12568
|
+
`) };
|
|
12491
12569
|
}
|
|
12492
|
-
const
|
|
12570
|
+
const folded = [];
|
|
12493
12571
|
for (const block of [...content, ...texts]) {
|
|
12494
12572
|
if (block.type !== "text") {
|
|
12495
|
-
|
|
12573
|
+
folded.push(block);
|
|
12496
12574
|
continue;
|
|
12497
12575
|
}
|
|
12498
12576
|
const trimmed = block.text.trim();
|
|
12499
12577
|
if (trimmed.length === 0)
|
|
12500
12578
|
continue;
|
|
12501
|
-
const tail =
|
|
12502
|
-
if (tail
|
|
12503
|
-
|
|
12579
|
+
const tail = folded[folded.length - 1];
|
|
12580
|
+
if (tail !== undefined && tail.type === "text")
|
|
12581
|
+
folded[folded.length - 1] = { type: "text", text: `${tail.text}
|
|
12504
12582
|
|
|
12505
12583
|
${trimmed}` };
|
|
12506
12584
|
else
|
|
12507
|
-
|
|
12585
|
+
folded.push({ type: "text", text: trimmed });
|
|
12508
12586
|
}
|
|
12509
|
-
return { ...result, content:
|
|
12587
|
+
return { ...result, content: folded };
|
|
12510
12588
|
}
|
|
12511
12589
|
function joinAttachmentBlocks(prev, next) {
|
|
12512
12590
|
const last = prev[prev.length - 1];
|
|
12513
|
-
if (last
|
|
12591
|
+
if (last === undefined || last.type !== "tool_result")
|
|
12514
12592
|
return [...prev, ...next];
|
|
12515
|
-
if ((last.loadedTools
|
|
12593
|
+
if (Array.isArray(last.loadedTools) && last.loadedTools.length > 0)
|
|
12516
12594
|
return [...prev, ...next];
|
|
12517
12595
|
if (next.some((b) => b.type === "text" && isSmooshExempt(b.text)))
|
|
12518
12596
|
return [...prev, ...next];
|
|
12519
12597
|
if (typeof last.content === "string" && next.every((b) => b.type === "text")) {
|
|
12520
|
-
const folded = foldTextIntoToolResult(last, next)
|
|
12521
|
-
return [...prev.slice(0, -1), folded];
|
|
12598
|
+
const folded = foldTextIntoToolResult(last, next);
|
|
12599
|
+
return [...prev.slice(0, -1), folded ?? last];
|
|
12522
12600
|
}
|
|
12523
12601
|
return [...prev, ...next];
|
|
12524
12602
|
}
|
|
@@ -12666,7 +12744,6 @@ import { execFile as execFile2 } from "node:child_process";
|
|
|
12666
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.";
|
|
12667
12745
|
var BASH_TOOL_NAME = "Bash";
|
|
12668
12746
|
var GIT_STATUS_MAX_CHARS = 2000;
|
|
12669
|
-
var MAIN_BRANCH_CANDIDATES = ["main", "master"];
|
|
12670
12747
|
var GIT_TIMEOUT_MS = 1e4;
|
|
12671
12748
|
function runGit2(cwd, args, env) {
|
|
12672
12749
|
return new Promise((resolve) => {
|
|
@@ -12678,17 +12755,25 @@ function runGit2(cwd, args, env) {
|
|
|
12678
12755
|
}
|
|
12679
12756
|
async function currentBranch(cwd, env) {
|
|
12680
12757
|
const { code, stdout } = await runGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], env);
|
|
12681
|
-
|
|
12758
|
+
const name = stdout.trim();
|
|
12759
|
+
return code === 0 && name.length > 0 ? name : "HEAD";
|
|
12682
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"];
|
|
12683
12766
|
async function mainBranch(cwd, env) {
|
|
12684
|
-
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);
|
|
12685
12768
|
if (head.code === 0) {
|
|
12686
|
-
|
|
12687
|
-
if (name.
|
|
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))
|
|
12688
12773
|
return name;
|
|
12689
12774
|
}
|
|
12690
|
-
for (const candidate of
|
|
12691
|
-
if (
|
|
12775
|
+
for (const candidate of FALLBACK_MAIN_BRANCHES) {
|
|
12776
|
+
if (await originRefExists(cwd, candidate, env))
|
|
12692
12777
|
return candidate;
|
|
12693
12778
|
}
|
|
12694
12779
|
return "main";
|
|
@@ -12763,11 +12848,17 @@ function isUserInvocable(overrides, skill) {
|
|
|
12763
12848
|
var DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS = 200000;
|
|
12764
12849
|
function skillListingBudgetChars(opts) {
|
|
12765
12850
|
if (opts.budgetChars !== undefined)
|
|
12766
|
-
return
|
|
12767
|
-
const window =
|
|
12768
|
-
const 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;
|
|
12769
12854
|
return Math.max(1, Math.floor(window * SKILL_LISTING_CHARS_PER_TOKEN * fraction));
|
|
12770
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
|
+
}
|
|
12771
12862
|
function truncateSkillDescription(description, maxDescChars) {
|
|
12772
12863
|
if (!Number.isFinite(maxDescChars) || maxDescChars <= 0 || description.length <= maxDescChars)
|
|
12773
12864
|
return description;
|
|
@@ -12781,42 +12872,53 @@ function renderSkillListingContent(entries) {
|
|
|
12781
12872
|
`);
|
|
12782
12873
|
}
|
|
12783
12874
|
function buildSkillListing(skills, opts) {
|
|
12784
|
-
const
|
|
12875
|
+
const cap = opts?.maxDescChars ?? DEFAULT_SKILL_LISTING_MAX_DESC_CHARS;
|
|
12785
12876
|
const budget = skillListingBudgetChars({
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12877
|
+
contextWindowTokens: opts?.contextWindowTokens,
|
|
12878
|
+
budgetFraction: opts?.budgetFraction,
|
|
12879
|
+
budgetChars: opts?.budgetChars
|
|
12789
12880
|
});
|
|
12790
12881
|
const entries = [];
|
|
12791
|
-
const
|
|
12882
|
+
const fixed = [];
|
|
12792
12883
|
for (const skill of skills) {
|
|
12793
12884
|
if (!isModelVisible(opts?.skillOverrides, skill))
|
|
12794
12885
|
continue;
|
|
12795
12886
|
const nameOnly = overrideFor(opts?.skillOverrides, skill) === "name-only";
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
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");
|
|
12799
12890
|
}
|
|
12800
|
-
if (budget
|
|
12891
|
+
if (!(budget > 0) || entries.length === 0)
|
|
12801
12892
|
return entries;
|
|
12802
|
-
const
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
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)
|
|
12806
12898
|
return entries;
|
|
12807
|
-
|
|
12808
|
-
if (candidates.length === 0)
|
|
12899
|
+
if (fixed.every(Boolean))
|
|
12809
12900
|
return entries;
|
|
12810
|
-
let remaining = budget -
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
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, "");
|
|
12814
12914
|
if (cost <= remaining) {
|
|
12815
|
-
keep.add(i);
|
|
12816
12915
|
remaining -= cost;
|
|
12916
|
+
out.push(e);
|
|
12917
|
+
} else {
|
|
12918
|
+
out.push({ name: e.name, description: "", source: e.source });
|
|
12817
12919
|
}
|
|
12818
12920
|
}
|
|
12819
|
-
return
|
|
12921
|
+
return out;
|
|
12820
12922
|
}
|
|
12821
12923
|
|
|
12822
12924
|
// src/subagents/plugin-agents.ts
|
|
@@ -12881,7 +12983,7 @@ var HOOK_SOURCE_BY_SETTING_SOURCE = {
|
|
|
12881
12983
|
project: "project",
|
|
12882
12984
|
local: "local"
|
|
12883
12985
|
};
|
|
12884
|
-
function
|
|
12986
|
+
function isPlainObject3(v) {
|
|
12885
12987
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12886
12988
|
}
|
|
12887
12989
|
function buildHookEntriesFromSettings(perSource) {
|
|
@@ -12895,7 +12997,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12895
12997
|
if (hooks === undefined)
|
|
12896
12998
|
continue;
|
|
12897
12999
|
const where = { source: tier.source, ...tier.path !== undefined ? { path: tier.path } : {} };
|
|
12898
|
-
if (!
|
|
13000
|
+
if (!isPlainObject3(hooks)) {
|
|
12899
13001
|
rejected.push({ ...where, reason: `expected an object for "hooks", got ${Array.isArray(hooks) ? "an array" : typeof hooks}` });
|
|
12900
13002
|
continue;
|
|
12901
13003
|
}
|
|
@@ -12908,7 +13010,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12908
13010
|
continue;
|
|
12909
13011
|
}
|
|
12910
13012
|
groups.forEach((group, groupIndex) => {
|
|
12911
|
-
if (!
|
|
13013
|
+
if (!isPlainObject3(group)) {
|
|
12912
13014
|
rejected.push({ ...where, event, reason: `matcher group ${groupIndex} is not an object` });
|
|
12913
13015
|
return;
|
|
12914
13016
|
}
|
|
@@ -12919,7 +13021,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12919
13021
|
}
|
|
12920
13022
|
const matcher = typeof group["matcher"] === "string" ? group["matcher"] : undefined;
|
|
12921
13023
|
groupHooks.forEach((handler, hookIndex) => {
|
|
12922
|
-
if (!
|
|
13024
|
+
if (!isPlainObject3(handler)) {
|
|
12923
13025
|
rejected.push({ ...where, event, reason: `hook ${groupIndex}:${hookIndex} is not an object` });
|
|
12924
13026
|
return;
|
|
12925
13027
|
}
|
|
@@ -15887,7 +15989,7 @@ replaceExecutor(ASK_USER_QUESTION_TOOL_NAME2, askUserQuestionExecutor);
|
|
|
15887
15989
|
// src/tools/impl/bash.ts
|
|
15888
15990
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
15889
15991
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync10, unlinkSync, createWriteStream } from "node:fs";
|
|
15890
|
-
import { join as join17, sep
|
|
15992
|
+
import { join as join17, sep } from "node:path";
|
|
15891
15993
|
|
|
15892
15994
|
// src/tools/sandbox-run-inputs.ts
|
|
15893
15995
|
function computeWritableRoots(ctx) {
|
|
@@ -15979,7 +16081,7 @@ function buildRunCommandOptions(input, ctx) {
|
|
|
15979
16081
|
function isWithinAllowedDirs(candidate, cwd, writableRoots) {
|
|
15980
16082
|
const canonicalCandidate = canonicalizePath(candidate);
|
|
15981
16083
|
const allowed = [canonicalizePath(cwd), ...writableRoots.map(canonicalizePath)];
|
|
15982
|
-
return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root +
|
|
16084
|
+
return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root + sep));
|
|
15983
16085
|
}
|
|
15984
16086
|
function computeCwdCarryAllowedRoots(ctx) {
|
|
15985
16087
|
return [ctx.session.getSessionRoot(), ...ctx.session.getBoundedRoots()];
|
|
@@ -16479,11 +16581,11 @@ function writeDurableJobsAtomic(projectDir, jobs, brand) {
|
|
|
16479
16581
|
}
|
|
16480
16582
|
renameSync2(tmpPath, path);
|
|
16481
16583
|
}
|
|
16482
|
-
function
|
|
16584
|
+
function isPlainObject4(v) {
|
|
16483
16585
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16484
16586
|
}
|
|
16485
16587
|
function parseCreateInput(raw) {
|
|
16486
|
-
if (!
|
|
16588
|
+
if (!isPlainObject4(raw))
|
|
16487
16589
|
throw new Error("input must be an object");
|
|
16488
16590
|
const cron = raw["cron"];
|
|
16489
16591
|
if (typeof cron !== "string" || cron.length === 0)
|
|
@@ -16501,7 +16603,7 @@ function parseCreateInput(raw) {
|
|
|
16501
16603
|
return { cron, prompt, recurring: recurring ?? true, durable: durable ?? false };
|
|
16502
16604
|
}
|
|
16503
16605
|
function parseIdInput(raw) {
|
|
16504
|
-
if (!
|
|
16606
|
+
if (!isPlainObject4(raw))
|
|
16505
16607
|
throw new Error("input must be an object");
|
|
16506
16608
|
const id = raw["id"];
|
|
16507
16609
|
if (typeof id !== "string" || id.length === 0)
|
|
@@ -17020,13 +17122,13 @@ var exitPlanModeExecutor = {
|
|
|
17020
17122
|
replaceExecutor(EXIT_PLAN_MODE_TOOL_NAME, exitPlanModeExecutor);
|
|
17021
17123
|
|
|
17022
17124
|
// src/tools/impl/exit-worktree.ts
|
|
17023
|
-
import { sep as
|
|
17125
|
+
import { sep as sep2 } from "node:path";
|
|
17024
17126
|
var EXIT_WORKTREE_TOOL_NAME = "ExitWorktree";
|
|
17025
17127
|
function hasUncommittedChanges(statusPorcelainOutput) {
|
|
17026
17128
|
return statusPorcelainOutput.trim().length > 0;
|
|
17027
17129
|
}
|
|
17028
17130
|
function findWorktreeContainingCwd(worktrees, cwdRealPath) {
|
|
17029
|
-
const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath +
|
|
17131
|
+
const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath + sep2));
|
|
17030
17132
|
if (candidates.length === 0)
|
|
17031
17133
|
return;
|
|
17032
17134
|
return candidates.reduce((longest, w) => w.realPath.length > longest.realPath.length ? w : longest);
|
|
@@ -17191,7 +17293,7 @@ replaceExecutor("Glob", globExecutor, extractGlobPaths);
|
|
|
17191
17293
|
|
|
17192
17294
|
// src/tools/impl/grep.ts
|
|
17193
17295
|
import { readFileSync as readFileSync13, statSync as statSync7 } from "node:fs";
|
|
17194
|
-
import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as
|
|
17296
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as sep3 } from "node:path";
|
|
17195
17297
|
var OUTPUT_MODES = ["content", "files_with_matches", "count"];
|
|
17196
17298
|
var MAX_PATTERN_LENGTH = 1000;
|
|
17197
17299
|
function parseInput4(raw) {
|
|
@@ -17296,7 +17398,7 @@ var TYPE_EXTENSIONS = {
|
|
|
17296
17398
|
};
|
|
17297
17399
|
function extOf(path) {
|
|
17298
17400
|
const i = path.lastIndexOf(".");
|
|
17299
|
-
const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(
|
|
17401
|
+
const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(sep3));
|
|
17300
17402
|
if (i <= slashIdx)
|
|
17301
17403
|
return "";
|
|
17302
17404
|
return path.slice(i).toLowerCase();
|
|
@@ -17340,7 +17442,7 @@ async function loadGitignoreRules(searchRoot) {
|
|
|
17340
17442
|
const found = [];
|
|
17341
17443
|
try {
|
|
17342
17444
|
for await (const rel of discovery.scan({ cwd: searchRoot, dot: true, onlyFiles: true, followSymlinks: false })) {
|
|
17343
|
-
const relPosix = rel.split(
|
|
17445
|
+
const relPosix = rel.split(sep3).join("/");
|
|
17344
17446
|
const dir = dirname8(relPosix);
|
|
17345
17447
|
const relDir = dir === "." ? "" : dir;
|
|
17346
17448
|
try {
|
|
@@ -17378,7 +17480,7 @@ async function discoverCandidateFiles(searchRoot, globPattern) {
|
|
|
17378
17480
|
const out = [];
|
|
17379
17481
|
for await (const p of glob.scan({ cwd: searchRoot, onlyFiles: true, dot: false, followSymlinks: false })) {
|
|
17380
17482
|
const abs = isAbsolute4(p) ? resolve9(p) : resolve9(searchRoot, p);
|
|
17381
|
-
const relPosix = relative2(searchRoot, abs).split(
|
|
17483
|
+
const relPosix = relative2(searchRoot, abs).split(sep3).join("/");
|
|
17382
17484
|
if (isIgnored(relPosix, rules))
|
|
17383
17485
|
continue;
|
|
17384
17486
|
out.push(abs);
|
|
@@ -18113,11 +18215,11 @@ replaceExecutor("NotebookEdit", notebookEditExecutor);
|
|
|
18113
18215
|
var MAX_MESSAGE_LENGTH = 199;
|
|
18114
18216
|
var DISABLED_REASON = "push transport unconfigured";
|
|
18115
18217
|
var activeNotifier = () => {};
|
|
18116
|
-
function
|
|
18218
|
+
function isPlainObject5(v) {
|
|
18117
18219
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18118
18220
|
}
|
|
18119
18221
|
function parseInput6(raw) {
|
|
18120
|
-
if (!
|
|
18222
|
+
if (!isPlainObject5(raw))
|
|
18121
18223
|
throw new Error("input must be an object");
|
|
18122
18224
|
const message = raw["message"];
|
|
18123
18225
|
if (typeof message !== "string" || message.length === 0)
|
|
@@ -18426,11 +18528,11 @@ var MAX_FINDINGS = 32;
|
|
|
18426
18528
|
var LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
18427
18529
|
var VERDICTS = ["CONFIRMED", "PLAUSIBLE"];
|
|
18428
18530
|
var OUTCOMES = ["fixed", "skipped", "no_change_needed"];
|
|
18429
|
-
function
|
|
18531
|
+
function isPlainObject6(v) {
|
|
18430
18532
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18431
18533
|
}
|
|
18432
18534
|
function parseFinding(raw, index) {
|
|
18433
|
-
if (!
|
|
18535
|
+
if (!isPlainObject6(raw))
|
|
18434
18536
|
throw new Error(`findings[${index}] must be an object`);
|
|
18435
18537
|
const file = raw["file"];
|
|
18436
18538
|
if (typeof file !== "string" || file.length === 0)
|
|
@@ -18471,7 +18573,7 @@ function parseFinding(raw, index) {
|
|
|
18471
18573
|
};
|
|
18472
18574
|
}
|
|
18473
18575
|
function parseInput8(raw) {
|
|
18474
|
-
if (!
|
|
18576
|
+
if (!isPlainObject6(raw))
|
|
18475
18577
|
throw new Error("input must be an object");
|
|
18476
18578
|
const level = raw["level"];
|
|
18477
18579
|
if (level !== undefined && (typeof level !== "string" || !LEVELS.includes(level))) {
|
|
@@ -18517,11 +18619,11 @@ function sessionList(sessionId) {
|
|
|
18517
18619
|
}
|
|
18518
18620
|
return list;
|
|
18519
18621
|
}
|
|
18520
|
-
function
|
|
18622
|
+
function isPlainObject7(v) {
|
|
18521
18623
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18522
18624
|
}
|
|
18523
18625
|
function parseInput9(raw) {
|
|
18524
|
-
if (!
|
|
18626
|
+
if (!isPlainObject7(raw))
|
|
18525
18627
|
throw new Error("input must be an object");
|
|
18526
18628
|
const stop = raw["stop"];
|
|
18527
18629
|
if (stop !== undefined && typeof stop !== "boolean")
|
|
@@ -18666,7 +18768,7 @@ function updateTask2(sessionId, input) {
|
|
|
18666
18768
|
}
|
|
18667
18769
|
|
|
18668
18770
|
// src/tools/impl/task-graph.ts
|
|
18669
|
-
function
|
|
18771
|
+
function isPlainObject8(v) {
|
|
18670
18772
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18671
18773
|
}
|
|
18672
18774
|
function isNonEmptyString(v) {
|
|
@@ -18680,7 +18782,7 @@ function errorResult5(message) {
|
|
|
18680
18782
|
return { output: `Error: ${message}`, isError: true };
|
|
18681
18783
|
}
|
|
18682
18784
|
function parseCreateInput2(raw) {
|
|
18683
|
-
if (!
|
|
18785
|
+
if (!isPlainObject8(raw))
|
|
18684
18786
|
throw new Error("input must be an object");
|
|
18685
18787
|
const subject = raw["subject"];
|
|
18686
18788
|
const description = raw["description"];
|
|
@@ -18692,7 +18794,7 @@ function parseCreateInput2(raw) {
|
|
|
18692
18794
|
if (activeForm !== undefined && typeof activeForm !== "string")
|
|
18693
18795
|
throw new Error("activeForm must be a string");
|
|
18694
18796
|
const metadata = raw["metadata"];
|
|
18695
|
-
if (metadata !== undefined && !
|
|
18797
|
+
if (metadata !== undefined && !isPlainObject8(metadata))
|
|
18696
18798
|
throw new Error("metadata must be an object");
|
|
18697
18799
|
return {
|
|
18698
18800
|
subject,
|
|
@@ -18712,7 +18814,7 @@ async function executeCreate2(rawInput, ctx) {
|
|
|
18712
18814
|
return { output: JSON.stringify({ task: { id: row.id, subject: row.subject } }) };
|
|
18713
18815
|
}
|
|
18714
18816
|
function parseTaskIdInput(raw) {
|
|
18715
|
-
if (!
|
|
18817
|
+
if (!isPlainObject8(raw))
|
|
18716
18818
|
throw new Error("input must be an object");
|
|
18717
18819
|
const taskId = raw["taskId"];
|
|
18718
18820
|
if (!isNonEmptyString(taskId))
|
|
@@ -18747,7 +18849,7 @@ async function executeList2(_rawInput, ctx) {
|
|
|
18747
18849
|
return { output: JSON.stringify({ tasks: rows }) };
|
|
18748
18850
|
}
|
|
18749
18851
|
function parseUpdateInput(raw) {
|
|
18750
|
-
if (!
|
|
18852
|
+
if (!isPlainObject8(raw))
|
|
18751
18853
|
throw new Error("input must be an object");
|
|
18752
18854
|
const taskId = raw["taskId"];
|
|
18753
18855
|
if (!isNonEmptyString(taskId))
|
|
@@ -18775,7 +18877,7 @@ function parseUpdateInput(raw) {
|
|
|
18775
18877
|
if (addBlockedBy !== undefined && !isStringArray(addBlockedBy))
|
|
18776
18878
|
throw new Error("addBlockedBy must be an array of non-empty strings");
|
|
18777
18879
|
const metadata = raw["metadata"];
|
|
18778
|
-
if (metadata !== undefined && !
|
|
18880
|
+
if (metadata !== undefined && !isPlainObject8(metadata))
|
|
18779
18881
|
throw new Error("metadata must be an object");
|
|
18780
18882
|
return {
|
|
18781
18883
|
taskId,
|
|
@@ -18850,7 +18952,7 @@ replaceExecutor("TaskUpdate", { execute: executeUpdate });
|
|
|
18850
18952
|
|
|
18851
18953
|
// src/tools/impl/task-output.ts
|
|
18852
18954
|
import { existsSync as existsSync7, readFileSync as readFileSync16 } from "node:fs";
|
|
18853
|
-
import { join as join20, sep as
|
|
18955
|
+
import { join as join20, sep as sep4 } from "node:path";
|
|
18854
18956
|
function parseTaskOutputInput(input) {
|
|
18855
18957
|
if (typeof input !== "object" || input === null)
|
|
18856
18958
|
return { error: "TaskOutput: input must be an object" };
|
|
@@ -18880,7 +18982,7 @@ function resolveOutputPath(taskId, ctx) {
|
|
|
18880
18982
|
return;
|
|
18881
18983
|
const realFallback = resolveRealTarget(fallback);
|
|
18882
18984
|
const realTasksDir = resolveRealTarget(tasksDir);
|
|
18883
|
-
return realFallback.startsWith(realTasksDir +
|
|
18985
|
+
return realFallback.startsWith(realTasksDir + sep4) ? fallback : undefined;
|
|
18884
18986
|
}
|
|
18885
18987
|
async function waitForTerminal(taskId, timeoutMs) {
|
|
18886
18988
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -19008,12 +19110,12 @@ replaceExecutor("TaskStop", taskStopExecutor);
|
|
|
19008
19110
|
|
|
19009
19111
|
// src/tools/impl/todo-write.ts
|
|
19010
19112
|
var sessions3 = new Map;
|
|
19011
|
-
function
|
|
19113
|
+
function isPlainObject9(v) {
|
|
19012
19114
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19013
19115
|
}
|
|
19014
19116
|
var TODO_STATUSES = ["pending", "in_progress", "completed"];
|
|
19015
19117
|
function parseTodo(raw, index) {
|
|
19016
|
-
if (!
|
|
19118
|
+
if (!isPlainObject9(raw))
|
|
19017
19119
|
throw new Error(`todos[${index}] must be an object`);
|
|
19018
19120
|
const content = raw["content"];
|
|
19019
19121
|
if (typeof content !== "string" || content.length === 0)
|
|
@@ -19028,7 +19130,7 @@ function parseTodo(raw, index) {
|
|
|
19028
19130
|
return { content, status, activeForm };
|
|
19029
19131
|
}
|
|
19030
19132
|
function parseInput10(raw) {
|
|
19031
|
-
if (!
|
|
19133
|
+
if (!isPlainObject9(raw))
|
|
19032
19134
|
throw new Error("input must be an object");
|
|
19033
19135
|
const todos = raw["todos"];
|
|
19034
19136
|
if (!Array.isArray(todos))
|
|
@@ -22139,17 +22241,24 @@ function flushWebSearchStream(events) {
|
|
|
22139
22241
|
let buffer = "";
|
|
22140
22242
|
const flush = () => {
|
|
22141
22243
|
const text = buffer.trim();
|
|
22142
|
-
if (text
|
|
22244
|
+
if (text !== "")
|
|
22143
22245
|
items.push(text);
|
|
22144
22246
|
buffer = "";
|
|
22145
22247
|
};
|
|
22146
22248
|
for (const event of events) {
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
|
|
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;
|
|
22150
22261
|
}
|
|
22151
|
-
flush();
|
|
22152
|
-
items.push(event.type === "search_result" ? { content: event.hits.map((h) => ({ title: h.title, url: h.url })) } : `Web search error: ${event.code}`);
|
|
22153
22262
|
}
|
|
22154
22263
|
flush();
|
|
22155
22264
|
return items;
|
|
@@ -22162,14 +22271,20 @@ function headerQuery(query) {
|
|
|
22162
22271
|
return `${query.slice(0, HEADER_QUERY_CAP)}[query truncated at ${HEADER_QUERY_CAP.toLocaleString("en-US")} characters]`;
|
|
22163
22272
|
}
|
|
22164
22273
|
function renderWebSearchToolResult(query, items) {
|
|
22165
|
-
|
|
22166
|
-
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)}"
|
|
22167
22275
|
|
|
22168
|
-
|
|
22169
|
-
|
|
22276
|
+
`;
|
|
22277
|
+
for (const item of items)
|
|
22278
|
+
out += `${renderItem(item)}
|
|
22170
22279
|
|
|
22171
|
-
|
|
22172
|
-
|
|
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.";
|
|
22173
22288
|
}
|
|
22174
22289
|
function renderWebSearchToolResultCapped(query, items, cap) {
|
|
22175
22290
|
const full = renderWebSearchToolResult(query, items);
|
|
@@ -24077,6 +24192,8 @@ function schedulingForCall(name, input, bash) {
|
|
|
24077
24192
|
return { kind: "concurrent" };
|
|
24078
24193
|
if (descriptor.concurrencyLane !== undefined)
|
|
24079
24194
|
return { kind: "lane", lane: descriptor.concurrencyLane };
|
|
24195
|
+
if (descriptor.concurrencySafe === true)
|
|
24196
|
+
return { kind: "concurrent" };
|
|
24080
24197
|
}
|
|
24081
24198
|
return { kind: "serial" };
|
|
24082
24199
|
}
|
|
@@ -24117,16 +24234,19 @@ function createSessionReadState(opts) {
|
|
|
24117
24234
|
}
|
|
24118
24235
|
|
|
24119
24236
|
// src/provider/lean-prompt.ts
|
|
24120
|
-
var FULL_PROMPT_OPUS_MINORS = new Set(["0", "1", "5", "6", "7"]);
|
|
24121
24237
|
function claudeModelTakesFullPrompt(modelKey) {
|
|
24122
24238
|
const id = modelKey.slice(modelKey.lastIndexOf("/") + 1).toLowerCase();
|
|
24123
|
-
if (
|
|
24239
|
+
if (FULL_FAMILY_MARKERS.some((marker) => id.includes(marker)))
|
|
24124
24240
|
return true;
|
|
24125
|
-
const
|
|
24126
|
-
if (
|
|
24241
|
+
const match = OPUS_4_ID.exec(id);
|
|
24242
|
+
if (match === null)
|
|
24127
24243
|
return false;
|
|
24128
|
-
|
|
24244
|
+
const minor = match[1] ?? "0";
|
|
24245
|
+
return FULL_OPUS_4_MINORS.has(minor);
|
|
24129
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"]);
|
|
24130
24250
|
|
|
24131
24251
|
// src/provider/first-party.ts
|
|
24132
24252
|
var FIRST_PARTY_ANTHROPIC_PROVIDER_IDS = new Set(["anthropic", "console"]);
|
|
@@ -25254,30 +25374,34 @@ ${escapeAttributionText(msg.body)}
|
|
|
25254
25374
|
function isTruthyEnvValue(v) {
|
|
25255
25375
|
return v === "1" || v === "true";
|
|
25256
25376
|
}
|
|
25257
|
-
function claudeTierMatches(modelKey, tier) {
|
|
25258
|
-
if (resolveSlot === undefined)
|
|
25259
|
-
return false;
|
|
25260
|
-
const resolution = resolveSlot(tier, modelKey);
|
|
25261
|
-
return resolution.ok && resolution.modelKey === modelKey;
|
|
25262
|
-
}
|
|
25263
|
-
function isAtOrBelowOpusTier(modelKey) {
|
|
25264
|
-
return claudeTierMatches(modelKey, "haiku") || claudeTierMatches(modelKey, "sonnet") || claudeTierMatches(modelKey, "opus");
|
|
25265
|
-
}
|
|
25266
25377
|
function sessionLeanModel(modelKey) {
|
|
25267
25378
|
if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
|
|
25268
25379
|
return false;
|
|
25269
25380
|
return !claudeModelTakesFullPrompt(modelKey);
|
|
25270
25381
|
}
|
|
25271
25382
|
function exploreModelCap(parentModel) {
|
|
25272
|
-
|
|
25273
|
-
if (isTruthyEnvValue(env[envName8(sessionBrand, "DISABLE_EXPLORE_INHERIT_CAP")]))
|
|
25383
|
+
if (exploreCapOptedOut())
|
|
25274
25384
|
return;
|
|
25275
25385
|
if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
|
|
25276
25386
|
return;
|
|
25277
|
-
if (
|
|
25387
|
+
if (modelSitsOnCappedOrLowerTier(parentModel))
|
|
25278
25388
|
return;
|
|
25279
25389
|
return "opus";
|
|
25280
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
|
+
}
|
|
25281
25405
|
function resolveChildModel(req) {
|
|
25282
25406
|
if (req.fork === true)
|
|
25283
25407
|
return currentModel;
|
|
@@ -25519,7 +25643,13 @@ ${escapeAttributionText(msg.body)}
|
|
|
25519
25643
|
const toolDefs = serverCfg.tools;
|
|
25520
25644
|
const toolNames = serverCfg.toolNames;
|
|
25521
25645
|
const toolLanes = serverCfg.toolLanes;
|
|
25522
|
-
|
|
25646
|
+
const concurrentTools = serverCfg.concurrentTools;
|
|
25647
|
+
registerMcpServerTools(serverName, toolDefs, {
|
|
25648
|
+
deferredDefault: true,
|
|
25649
|
+
...toolNames !== undefined ? { toolNames } : {},
|
|
25650
|
+
...toolLanes !== undefined ? { toolLanes } : {},
|
|
25651
|
+
...concurrentTools !== undefined ? { concurrentTools } : {}
|
|
25652
|
+
});
|
|
25523
25653
|
sdkMcpServerNames.push(serverName);
|
|
25524
25654
|
const perServerTimeoutMs = serverCfg.timeout;
|
|
25525
25655
|
for (const tool of toolDefs) {
|
|
@@ -29016,100 +29146,122 @@ function ancestryChain(byUuid, leafUuid) {
|
|
|
29016
29146
|
}
|
|
29017
29147
|
return chain.reverse();
|
|
29018
29148
|
}
|
|
29019
|
-
function toolUseIdsOf(e) {
|
|
29020
|
-
if (e.type !== "assistant" || !Array.isArray(e.message?.content))
|
|
29021
|
-
return [];
|
|
29022
|
-
return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_use" && typeof b.id === "string" ? [b.id] : []);
|
|
29023
|
-
}
|
|
29024
|
-
function toolResultIdsOf(e) {
|
|
29025
|
-
if (e.type !== "user" || !Array.isArray(e.message?.content))
|
|
29026
|
-
return [];
|
|
29027
|
-
return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_result" && typeof b.tool_use_id === "string" ? [b.tool_use_id] : []);
|
|
29028
|
-
}
|
|
29029
29149
|
function recoverParallelToolResults(chain, pool) {
|
|
29030
29150
|
const onChain = new Set(chain.map((e) => e.uuid));
|
|
29031
29151
|
const resultsByParent = new Map;
|
|
29032
|
-
const
|
|
29033
|
-
for (const
|
|
29034
|
-
if (onChain.has(
|
|
29035
|
-
continue;
|
|
29036
|
-
if (e.type === "assistant" && e.isApiErrorMessage !== true)
|
|
29037
|
-
offChainAssistants.push(e);
|
|
29038
|
-
if (toolResultIdsOf(e).length === 0)
|
|
29152
|
+
const siblingCandidates = [];
|
|
29153
|
+
for (const entry of pool) {
|
|
29154
|
+
if (onChain.has(entry.uuid) || entry.parentUuid === null)
|
|
29039
29155
|
continue;
|
|
29040
|
-
|
|
29041
|
-
|
|
29042
|
-
|
|
29043
|
-
|
|
29044
|
-
|
|
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);
|
|
29045
29165
|
}
|
|
29046
|
-
if (resultsByParent.size === 0 &&
|
|
29166
|
+
if (resultsByParent.size === 0 && siblingCandidates.length === 0)
|
|
29047
29167
|
return [...chain];
|
|
29048
|
-
const
|
|
29049
|
-
const
|
|
29050
|
-
|
|
29051
|
-
|
|
29052
|
-
|
|
29053
|
-
|
|
29054
|
-
|
|
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))
|
|
29055
29178
|
continue;
|
|
29056
|
-
const
|
|
29057
|
-
if (!
|
|
29179
|
+
const ids = answerIdsOf(result);
|
|
29180
|
+
if (!ids.some((id) => open.has(id) && !claimed.has(id)))
|
|
29058
29181
|
continue;
|
|
29059
|
-
|
|
29060
|
-
for (const id of
|
|
29061
|
-
|
|
29062
|
-
}
|
|
29063
|
-
return {
|
|
29064
|
-
};
|
|
29065
|
-
const
|
|
29066
|
-
|
|
29067
|
-
|
|
29068
|
-
|
|
29069
|
-
|
|
29070
|
-
|
|
29071
|
-
|
|
29072
|
-
for (let start = 0;start < chain.length; ) {
|
|
29073
|
-
if (chain[start].type !== "assistant") {
|
|
29074
|
-
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++;
|
|
29075
29195
|
continue;
|
|
29076
29196
|
}
|
|
29077
|
-
let end =
|
|
29078
|
-
while (end + 1
|
|
29197
|
+
let end = index;
|
|
29198
|
+
while (end + 1 <= lastIndex && chain[end + 1].type === "assistant")
|
|
29079
29199
|
end++;
|
|
29080
|
-
|
|
29081
|
-
|
|
29082
|
-
|
|
29083
|
-
|
|
29084
|
-
|
|
29085
|
-
|
|
29086
|
-
|
|
29087
|
-
|
|
29088
|
-
|
|
29089
|
-
const
|
|
29090
|
-
|
|
29091
|
-
|
|
29092
|
-
|
|
29093
|
-
|
|
29094
|
-
|
|
29095
|
-
|
|
29096
|
-
|
|
29097
|
-
|
|
29098
|
-
|
|
29099
|
-
|
|
29100
|
-
|
|
29101
|
-
|
|
29102
|
-
|
|
29103
|
-
|
|
29104
|
-
|
|
29105
|
-
|
|
29106
|
-
|
|
29107
|
-
|
|
29108
|
-
|
|
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);
|
|
29109
29235
|
}
|
|
29110
|
-
|
|
29111
|
-
|
|
29112
|
-
|
|
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;
|
|
29113
29265
|
}
|
|
29114
29266
|
function isDescendant(byUuid, ancestorUuid, candidateUuid) {
|
|
29115
29267
|
if (candidateUuid === ancestorUuid)
|
|
@@ -29513,7 +29665,7 @@ class TranscriptWriter2 {
|
|
|
29513
29665
|
return entries;
|
|
29514
29666
|
}
|
|
29515
29667
|
}
|
|
29516
|
-
var RUNTIME_ENGINE_VERSION2 = "0.0.
|
|
29668
|
+
var RUNTIME_ENGINE_VERSION2 = "0.0.43";
|
|
29517
29669
|
function withPermissionJournal(writer, location) {
|
|
29518
29670
|
return {
|
|
29519
29671
|
transcriptPath: join27(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),
|
|
@@ -30239,4 +30391,4 @@ function selectClassifierRoute2(config, descriptorOf) {
|
|
|
30239
30391
|
return { kind: "worker-eligible" };
|
|
30240
30392
|
}
|
|
30241
30393
|
|
|
30242
|
-
export { ChildResumeModeIncomparableError, resolveChildResumeMode, ensureDefaultMessagingRuntimeRegistered, buildContinuationChain, AGENT_TOOL_CANONICAL_NAME, computeActiveSlotSet, resolveSlotToProvider, createRpcBridge2, shellWords,
|
|
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 };
|