@yanlinglabs/winter-agent-runtime 0.0.41 → 0.0.44
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 +29 -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-host.js +1 -1
- package/dist/embedded-worker.js +4 -4
- package/dist/embedded.js +3 -3
- package/dist/engine.d.ts +3 -3
- package/dist/{index-97t2rmtf.js → index-dne3dw18.js} +11 -0
- package/dist/{index-z9yngxj4.js → index-hbkdq3xs.js} +1072 -827
- package/dist/{index-hdvvezf8.js → index-r1pter2v.js} +239 -155
- package/dist/{index-ey5c52j3.js → index-x7hdare6.js} +2 -2
- package/dist/index.js +2 -2
- 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/protocol/channel.d.ts +6 -0
- 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 +3 -3
- 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/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
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./index-2wgfv0pa.js";
|
|
27
27
|
import {
|
|
28
28
|
Queue
|
|
29
|
-
} from "./index-
|
|
29
|
+
} from "./index-dne3dw18.js";
|
|
30
30
|
|
|
31
31
|
// src/rpc/bridge.ts
|
|
32
32
|
import { randomUUID } from "node:crypto";
|
|
@@ -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";
|
|
@@ -1705,37 +1649,53 @@ function resolveRealTarget(path) {
|
|
|
1705
1649
|
return join(resolveRealTarget(dir), basename(path));
|
|
1706
1650
|
}
|
|
1707
1651
|
}
|
|
1708
|
-
var MAX_SYMLINK_CHAIN_HOPS = 40;
|
|
1709
1652
|
function resolveSymlinkTargetChain(path) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1653
|
+
return followChainFrom(path, MAX_HAND_FOLLOWED_LINKS);
|
|
1654
|
+
}
|
|
1655
|
+
var MAX_HAND_FOLLOWED_LINKS = 40;
|
|
1656
|
+
function followChainFrom(current, followsLeft) {
|
|
1657
|
+
const real = realpathOrUndefined(current);
|
|
1658
|
+
if (real !== undefined)
|
|
1659
|
+
return real;
|
|
1660
|
+
const split = splitAtDeepestResolvableAncestor(current);
|
|
1661
|
+
if (split === undefined)
|
|
1662
|
+
return;
|
|
1663
|
+
const { realAncestor, nextName, remainingNames } = split;
|
|
1664
|
+
const linkTarget = readlinkOrUndefined(join(realAncestor, nextName));
|
|
1665
|
+
if (linkTarget === undefined)
|
|
1666
|
+
return join(realAncestor, nextName, ...remainingNames);
|
|
1667
|
+
if (followsLeft <= 1)
|
|
1668
|
+
return;
|
|
1669
|
+
return followChainFrom(join(resolve(realAncestor, linkTarget), ...remainingNames), followsLeft - 1);
|
|
1670
|
+
}
|
|
1671
|
+
function splitAtDeepestResolvableAncestor(path) {
|
|
1672
|
+
const lineage = [path];
|
|
1673
|
+
for (let parent = dirname(path);parent !== lineage[lineage.length - 1]; parent = dirname(parent)) {
|
|
1674
|
+
lineage.push(parent);
|
|
1675
|
+
}
|
|
1676
|
+
for (let depth = 1;depth < lineage.length; depth++) {
|
|
1677
|
+
const realAncestor = realpathOrUndefined(lineage[depth]);
|
|
1678
|
+
if (realAncestor === undefined)
|
|
1679
|
+
continue;
|
|
1680
|
+
const leafward = lineage.slice(0, depth).map((member) => basename(member));
|
|
1681
|
+
return { realAncestor, nextName: leafward[depth - 1], remainingNames: leafward.slice(0, depth - 1).reverse() };
|
|
1736
1682
|
}
|
|
1737
1683
|
return;
|
|
1738
1684
|
}
|
|
1685
|
+
function realpathOrUndefined(path) {
|
|
1686
|
+
try {
|
|
1687
|
+
return realpathSync(path);
|
|
1688
|
+
} catch {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
function readlinkOrUndefined(path) {
|
|
1693
|
+
try {
|
|
1694
|
+
return readlinkSync(path);
|
|
1695
|
+
} catch {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1739
1699
|
function resolveSymlinkEnds(path) {
|
|
1740
1700
|
return { target: resolveRealTarget(path), chainTarget: resolveSymlinkTargetChain(path) };
|
|
1741
1701
|
}
|
|
@@ -1752,7 +1712,7 @@ function checkSymlinkBothEnds(path, matcher) {
|
|
|
1752
1712
|
|
|
1753
1713
|
// src/permissions/file-rules.ts
|
|
1754
1714
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1755
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative
|
|
1715
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, normalize as normalize2, relative } from "node:path";
|
|
1756
1716
|
import ignoreFactory from "ignore";
|
|
1757
1717
|
function fileRuleKindFor(toolName) {
|
|
1758
1718
|
switch (toolName) {
|
|
@@ -1773,20 +1733,14 @@ function canonicalFileRuleAuthoringToolName(kind) {
|
|
|
1773
1733
|
}
|
|
1774
1734
|
var INERT_ANCHOR = Symbol("file-rule-inert-anchor");
|
|
1775
1735
|
function resolveFileRuleAnchor(pattern, opts) {
|
|
1776
|
-
if (pattern.startsWith("//"))
|
|
1736
|
+
if (pattern.startsWith("//"))
|
|
1777
1737
|
return { relativePattern: pattern.slice(1), root: "/" };
|
|
1778
|
-
|
|
1779
|
-
if (pattern.startsWith("~/")) {
|
|
1738
|
+
if (pattern.startsWith("~/"))
|
|
1780
1739
|
return { relativePattern: pattern.slice(1), root: opts.home };
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
return { relativePattern: pattern, root: INERT_ANCHOR };
|
|
1785
|
-
return { relativePattern: pattern, root: opts.sourceDir };
|
|
1786
|
-
}
|
|
1787
|
-
if (pattern.startsWith("./")) {
|
|
1740
|
+
if (pattern.startsWith("/"))
|
|
1741
|
+
return { relativePattern: pattern, root: opts.sourceDir ?? INERT_ANCHOR };
|
|
1742
|
+
if (pattern.startsWith("./"))
|
|
1788
1743
|
return { relativePattern: pattern.slice(2), root: null };
|
|
1789
|
-
}
|
|
1790
1744
|
return { relativePattern: pattern, root: null };
|
|
1791
1745
|
}
|
|
1792
1746
|
var RULE_PATH_GLOB_CHARS = /[*?[\]]/;
|
|
@@ -1853,36 +1807,19 @@ function escapeRegexLiteralPath(path) {
|
|
|
1853
1807
|
return path.replace(/[.^$+{}()|\\[\]*?]/g, "\\$&");
|
|
1854
1808
|
}
|
|
1855
1809
|
function isSuspiciousRealpathResolution(original, resolved) {
|
|
1856
|
-
const
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1810
|
+
const written = normalize2(original);
|
|
1811
|
+
const landed = normalize2(resolved);
|
|
1812
|
+
const spellings = acceptableSpellingsOf(written);
|
|
1813
|
+
if (spellings.includes(landed))
|
|
1859
1814
|
return false;
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
if (o === "/")
|
|
1869
|
-
return true;
|
|
1870
|
-
if (o.split("/").filter(Boolean).length <= 1)
|
|
1871
|
-
return true;
|
|
1872
|
-
if (r.startsWith(o + "/"))
|
|
1873
|
-
return true;
|
|
1874
|
-
let p = r;
|
|
1875
|
-
if (r.startsWith("/tmp/"))
|
|
1876
|
-
p = "/private" + r;
|
|
1877
|
-
else if (r.startsWith("/var/"))
|
|
1878
|
-
p = "/private" + r;
|
|
1879
|
-
if (p !== r && p.startsWith(o + "/"))
|
|
1880
|
-
return true;
|
|
1881
|
-
const linkUnderOriginal = o.startsWith(r + "/");
|
|
1882
|
-
const linkUnderPrivateAlias = p !== r && o.startsWith(p + "/");
|
|
1883
|
-
if (o !== r && !(p !== r && o === p) && !linkUnderOriginal && !linkUnderPrivateAlias)
|
|
1884
|
-
return true;
|
|
1885
|
-
return false;
|
|
1815
|
+
const shallow = landed.split("/").filter((segment) => segment !== "").length <= 1;
|
|
1816
|
+
const climbedAbove = spellings.some((spelling) => spelling.startsWith(landed + "/"));
|
|
1817
|
+
const wentBelow = spellings.some((spelling) => landed.startsWith(spelling + "/"));
|
|
1818
|
+
return shallow || climbedAbove || !wentBelow;
|
|
1819
|
+
}
|
|
1820
|
+
function acceptableSpellingsOf(path) {
|
|
1821
|
+
const aliased = path.startsWith("/tmp/") || path.startsWith("/var/");
|
|
1822
|
+
return aliased ? [path, "/private" + path] : [path];
|
|
1886
1823
|
}
|
|
1887
1824
|
function guardedCanonicalize(path) {
|
|
1888
1825
|
try {
|
|
@@ -1901,19 +1838,81 @@ function canonicalizedGlobFixedPrefix(absoluteGlob) {
|
|
|
1901
1838
|
return guardedCanonicalize(scan.literal.slice(0, scan.lastSepLiteralLength));
|
|
1902
1839
|
}
|
|
1903
1840
|
function ancestorDirectoriesOf(path) {
|
|
1904
|
-
const
|
|
1905
|
-
let current =
|
|
1906
|
-
|
|
1907
|
-
out.push(current);
|
|
1841
|
+
const ancestors = [];
|
|
1842
|
+
let current = path;
|
|
1843
|
+
for (;; ) {
|
|
1908
1844
|
const parent = dirname2(current);
|
|
1909
|
-
if (parent === current)
|
|
1910
|
-
|
|
1845
|
+
if (parent === current || parent === "/" || parent === ".")
|
|
1846
|
+
return ancestors;
|
|
1847
|
+
ancestors.push(parent);
|
|
1911
1848
|
current = parent;
|
|
1912
1849
|
}
|
|
1913
|
-
return out;
|
|
1914
1850
|
}
|
|
1915
|
-
function
|
|
1916
|
-
|
|
1851
|
+
function globToAnchoredRegex(glob) {
|
|
1852
|
+
const literalBracketAt = glob.indexOf("[", glob.lastIndexOf("]") + 1);
|
|
1853
|
+
let body = "";
|
|
1854
|
+
let wordRun = "";
|
|
1855
|
+
const emit = (rendered) => {
|
|
1856
|
+
if (wordRun !== "") {
|
|
1857
|
+
body += renderWordRun(wordRun);
|
|
1858
|
+
wordRun = "";
|
|
1859
|
+
}
|
|
1860
|
+
body += rendered;
|
|
1861
|
+
};
|
|
1862
|
+
let i = 0;
|
|
1863
|
+
while (i < glob.length) {
|
|
1864
|
+
const ch = glob.charAt(i);
|
|
1865
|
+
if (ch === "*") {
|
|
1866
|
+
let runEnd = i;
|
|
1867
|
+
while (glob.charAt(runEnd) === "*")
|
|
1868
|
+
runEnd++;
|
|
1869
|
+
let stars = runEnd - i;
|
|
1870
|
+
const intoDirectory = stars >= 2 && glob.charAt(runEnd) === "/";
|
|
1871
|
+
if (intoDirectory)
|
|
1872
|
+
stars -= 2;
|
|
1873
|
+
for (let pair = 0;pair < Math.floor(stars / 2); pair++)
|
|
1874
|
+
wordRun += GLOBSTAR_SPELLING;
|
|
1875
|
+
if (stars % 2 === 1)
|
|
1876
|
+
emit(SINGLE_SEGMENT_REGEX);
|
|
1877
|
+
if (intoDirectory)
|
|
1878
|
+
wordRun += GLOBSTAR_SLASH_SPELLING;
|
|
1879
|
+
i = intoDirectory ? runEnd + 1 : runEnd;
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
if (WORD_RUN_CHARACTERS.has(ch))
|
|
1883
|
+
wordRun += ch;
|
|
1884
|
+
else
|
|
1885
|
+
emit(renderGlobCharacter(ch, i === literalBracketAt));
|
|
1886
|
+
i++;
|
|
1887
|
+
}
|
|
1888
|
+
emit("");
|
|
1889
|
+
return "^" + body + "$";
|
|
1890
|
+
}
|
|
1891
|
+
var SINGLE_SEGMENT_REGEX = "[^/]*";
|
|
1892
|
+
var GLOBSTAR_SPELLING = "__GLOBSTAR__";
|
|
1893
|
+
var GLOBSTAR_SLASH_SPELLING = "__GLOBSTAR_SLASH__";
|
|
1894
|
+
var WORD_RUN_CHARACTERS = new Set(GLOBSTAR_SPELLING + GLOBSTAR_SLASH_SPELLING);
|
|
1895
|
+
var ESCAPED_REGEX_CHARACTERS = new Set(".^$+{}()|\\");
|
|
1896
|
+
function renderGlobCharacter(ch, isLiteralBracket) {
|
|
1897
|
+
if (ESCAPED_REGEX_CHARACTERS.has(ch))
|
|
1898
|
+
return "\\" + ch;
|
|
1899
|
+
if (ch === "?")
|
|
1900
|
+
return "[^/]";
|
|
1901
|
+
if (ch === "[" && isLiteralBracket)
|
|
1902
|
+
return "\\[";
|
|
1903
|
+
return ch;
|
|
1904
|
+
}
|
|
1905
|
+
function renderWordRun(spelling) {
|
|
1906
|
+
return expandOccurrences(spelling, GLOBSTAR_SLASH_SPELLING, "(.*/)?", (stretch) => expandOccurrences(stretch, GLOBSTAR_SPELLING, ".*", (plain) => plain));
|
|
1907
|
+
}
|
|
1908
|
+
function expandOccurrences(text, needle, expansion, between) {
|
|
1909
|
+
let out = "";
|
|
1910
|
+
let from = 0;
|
|
1911
|
+
for (let hit = text.indexOf(needle);hit !== -1; hit = text.indexOf(needle, from)) {
|
|
1912
|
+
out += between(text.slice(from, hit)) + expansion;
|
|
1913
|
+
from = hit + needle.length;
|
|
1914
|
+
}
|
|
1915
|
+
return out + between(text.slice(from));
|
|
1917
1916
|
}
|
|
1918
1917
|
function globToSbplRegexSource(absoluteGlob) {
|
|
1919
1918
|
const scan = scanDenyPathGlob(absoluteGlob);
|
|
@@ -1921,12 +1920,11 @@ function globToSbplRegexSource(absoluteGlob) {
|
|
|
1921
1920
|
return "^" + escapeRegexLiteralPath(guardedCanonicalize(scan.literal)) + "$";
|
|
1922
1921
|
const canonicalPrefix = canonicalizedGlobFixedPrefix(absoluteGlob);
|
|
1923
1922
|
if (canonicalPrefix === undefined)
|
|
1924
|
-
return
|
|
1925
|
-
return "^" + escapeRegexLiteralPath(canonicalPrefix) +
|
|
1923
|
+
return globToAnchoredRegex(absoluteGlob);
|
|
1924
|
+
return "^" + escapeRegexLiteralPath(canonicalPrefix) + globToAnchoredRegex(absoluteGlob.slice(scan.lastSepRaw)).slice(1);
|
|
1926
1925
|
}
|
|
1927
1926
|
function recursiveGlobToSbplRegexSource(absoluteGlob) {
|
|
1928
|
-
|
|
1929
|
-
return base.slice(0, -1) + "(/.*)?$";
|
|
1927
|
+
return globToSbplRegexSource(absoluteGlob).replace(/\$$/, "(/.*)?$");
|
|
1930
1928
|
}
|
|
1931
1929
|
function splitDenyPathsByGlobShape(paths) {
|
|
1932
1930
|
const plain = [];
|
|
@@ -1956,18 +1954,37 @@ function globDenyEntriesOf(paths) {
|
|
|
1956
1954
|
}
|
|
1957
1955
|
function normalizeFileRulePattern(relativePattern) {
|
|
1958
1956
|
const collapsed = relativePattern.replace(/\/{2,}/g, "/");
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1957
|
+
switch (leadingBomTreatment(collapsed)) {
|
|
1958
|
+
case "keep":
|
|
1959
|
+
return collapsed;
|
|
1960
|
+
case "escape-directive":
|
|
1961
|
+
return "\\" + collapsed.slice(1);
|
|
1962
|
+
case "bracket-second-bom":
|
|
1963
|
+
return `[${BYTE_ORDER_MARK}]` + collapsed.slice(2);
|
|
1964
|
+
case "drop":
|
|
1965
|
+
return collapsed.slice(1);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
var BYTE_ORDER_MARK = "\uFEFF";
|
|
1969
|
+
function leadingBomTreatment(pattern) {
|
|
1970
|
+
if (/^\s*(?:\/\*\*)?$/.test(pattern) || !pattern.startsWith(BYTE_ORDER_MARK))
|
|
1971
|
+
return "keep";
|
|
1972
|
+
const next = pattern.charAt(1);
|
|
1973
|
+
if (next === "!" || next === "#")
|
|
1974
|
+
return "escape-directive";
|
|
1975
|
+
return next === BYTE_ORDER_MARK ? "bracket-second-bom" : "drop";
|
|
1962
1976
|
}
|
|
1963
1977
|
function unanchorTrailingDoubleStar(pattern, isAllow) {
|
|
1964
1978
|
if (!pattern.endsWith("/**"))
|
|
1965
1979
|
return pattern;
|
|
1966
|
-
const
|
|
1967
|
-
if (
|
|
1968
|
-
return
|
|
1969
|
-
|
|
1970
|
-
|
|
1980
|
+
const rest = pattern.slice(0, -3);
|
|
1981
|
+
if (/^\/*$/.test(rest))
|
|
1982
|
+
return "/**";
|
|
1983
|
+
if (!isAllow)
|
|
1984
|
+
return rest;
|
|
1985
|
+
const singleSegment = !rest.includes("/");
|
|
1986
|
+
const directive = rest.startsWith("!") || rest.startsWith("#");
|
|
1987
|
+
return singleSegment && !directive ? "/" + rest : rest;
|
|
1971
1988
|
}
|
|
1972
1989
|
|
|
1973
1990
|
class FileRuleCompileError extends Error {
|
|
@@ -1976,90 +1993,94 @@ class FileRuleCompileError extends Error {
|
|
|
1976
1993
|
this.name = "FileRuleCompileError";
|
|
1977
1994
|
}
|
|
1978
1995
|
}
|
|
1979
|
-
var INVALID_RELATIVE_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
1980
|
-
function isPathValidRelative(rel) {
|
|
1981
|
-
return rel !== "" && !INVALID_RELATIVE_PATH.test(rel);
|
|
1982
|
-
}
|
|
1983
1996
|
function matchFileRulesGrouped(candidates, path, opts, behavior) {
|
|
1984
|
-
|
|
1985
|
-
return null;
|
|
1997
|
+
const isAllow = behavior === "allow";
|
|
1986
1998
|
const groups = new Map;
|
|
1987
|
-
const groupOrder = [];
|
|
1988
1999
|
for (const candidate of candidates) {
|
|
1989
2000
|
const anchor = resolveFileRuleAnchor(candidate.pattern, { home: opts.home, sourceDir: candidate.sourceDir });
|
|
1990
2001
|
if (anchor.root === INERT_ANCHOR)
|
|
1991
2002
|
continue;
|
|
1992
|
-
const
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
let group = groups.get(rootKey);
|
|
2003
|
+
const root = anchor.root ?? opts.cwd;
|
|
2004
|
+
const compiled = unanchorTrailingDoubleStar(normalizeFileRulePattern(anchor.relativePattern), isAllow);
|
|
2005
|
+
let group = groups.get(root);
|
|
1996
2006
|
if (group === undefined) {
|
|
1997
|
-
group = {
|
|
1998
|
-
groups.set(
|
|
1999
|
-
groupOrder.push(rootKey);
|
|
2007
|
+
group = { matcher: ignoreFactory({ ignorecase: true }), entryByPattern: new Map };
|
|
2008
|
+
groups.set(root, group);
|
|
2000
2009
|
}
|
|
2001
|
-
group.
|
|
2002
|
-
group.
|
|
2010
|
+
group.matcher.add(compiled);
|
|
2011
|
+
group.entryByPattern.set(compiled, candidate.entry);
|
|
2003
2012
|
}
|
|
2004
|
-
for (const
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
if (!isPathValidRelative(rel))
|
|
2013
|
+
for (const [root, group] of groups) {
|
|
2014
|
+
const rel = relative(root, path);
|
|
2015
|
+
if (isOutsideIgnoreRoot(rel))
|
|
2008
2016
|
continue;
|
|
2009
2017
|
let result;
|
|
2010
2018
|
try {
|
|
2011
|
-
result = group.
|
|
2012
|
-
} catch (
|
|
2013
|
-
throw new FileRuleCompileError(`a file-rule pattern under ${JSON.stringify(
|
|
2014
|
-
}
|
|
2015
|
-
if (result.ignored && result.rule) {
|
|
2016
|
-
const winner = group.byCompiledPattern.get(result.rule.pattern);
|
|
2017
|
-
if (winner !== undefined)
|
|
2018
|
-
return winner;
|
|
2019
|
+
result = group.matcher.test(rel);
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
throw new FileRuleCompileError(`a file-rule pattern under ${JSON.stringify(root)} failed to compile`, { cause: err });
|
|
2019
2022
|
}
|
|
2023
|
+
if (!result.ignored)
|
|
2024
|
+
continue;
|
|
2025
|
+
const matchedPattern = result.rule?.pattern;
|
|
2026
|
+
if (matchedPattern !== undefined && group.entryByPattern.has(matchedPattern))
|
|
2027
|
+
return group.entryByPattern.get(matchedPattern);
|
|
2020
2028
|
}
|
|
2021
2029
|
return null;
|
|
2022
2030
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
["/private/var", "/var"],
|
|
2026
|
-
["/private/etc", "/etc"],
|
|
2027
|
-
["/usr/bin", "/bin"],
|
|
2028
|
-
["/usr/lib", "/lib"],
|
|
2029
|
-
["/usr/sbin", "/sbin"]
|
|
2030
|
-
];
|
|
2031
|
-
var cachedTrustedSymlinkEquivalences;
|
|
2032
|
-
function trustedSymlinkEquivalences() {
|
|
2033
|
-
if (cachedTrustedSymlinkEquivalences !== undefined)
|
|
2034
|
-
return cachedTrustedSymlinkEquivalences;
|
|
2035
|
-
const result = new Map;
|
|
2036
|
-
for (const [real, alias] of TRUSTED_SYMLINK_CANDIDATES) {
|
|
2037
|
-
try {
|
|
2038
|
-
if (realpathSync2(alias) === real)
|
|
2039
|
-
result.set(real, alias);
|
|
2040
|
-
} catch {}
|
|
2041
|
-
}
|
|
2042
|
-
cachedTrustedSymlinkEquivalences = result;
|
|
2043
|
-
return result;
|
|
2031
|
+
function isOutsideIgnoreRoot(rel) {
|
|
2032
|
+
return rel === "" || rel === "." || rel === ".." || rel.startsWith("/") || rel.startsWith("./") || rel.startsWith("../");
|
|
2044
2033
|
}
|
|
2045
2034
|
function canonicalizeTrustedSymlinkPath(path) {
|
|
2046
|
-
for (const
|
|
2047
|
-
if (path === real
|
|
2035
|
+
for (const { real, alias } of verifiedTrustedSymlinkPairs()) {
|
|
2036
|
+
if (path === real)
|
|
2037
|
+
return alias;
|
|
2038
|
+
if (path.startsWith(real + "/"))
|
|
2048
2039
|
return alias + path.slice(real.length);
|
|
2049
2040
|
}
|
|
2050
2041
|
return path;
|
|
2051
2042
|
}
|
|
2043
|
+
var TRUSTED_SYMLINK_PAIRS = [
|
|
2044
|
+
{ real: "/private/tmp", alias: "/tmp" },
|
|
2045
|
+
{ real: "/private/var", alias: "/var" },
|
|
2046
|
+
{ real: "/private/etc", alias: "/etc" },
|
|
2047
|
+
{ real: "/usr/bin", alias: "/bin" },
|
|
2048
|
+
{ real: "/usr/lib", alias: "/lib" },
|
|
2049
|
+
{ real: "/usr/sbin", alias: "/sbin" }
|
|
2050
|
+
];
|
|
2051
|
+
var trustedSymlinkPairsCache;
|
|
2052
|
+
function verifiedTrustedSymlinkPairs() {
|
|
2053
|
+
if (trustedSymlinkPairsCache === undefined) {
|
|
2054
|
+
trustedSymlinkPairsCache = TRUSTED_SYMLINK_PAIRS.filter(({ real, alias }) => {
|
|
2055
|
+
try {
|
|
2056
|
+
return realpathSync2(alias) === real;
|
|
2057
|
+
} catch {
|
|
2058
|
+
return false;
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
return trustedSymlinkPairsCache;
|
|
2063
|
+
}
|
|
2052
2064
|
function isPathWithinRoot(childPath, rootPath, opts = {}) {
|
|
2053
2065
|
const caseFold = opts.caseFold ?? true;
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
|
|
2066
|
+
const prepare = (p) => {
|
|
2067
|
+
const short = shortSystemAlias(p);
|
|
2068
|
+
return caseFold ? short.toLowerCase() : short;
|
|
2069
|
+
};
|
|
2070
|
+
const rel = relative(prepare(rootPath), prepare(childPath));
|
|
2057
2071
|
if (rel === "")
|
|
2058
2072
|
return true;
|
|
2059
|
-
if (rel === ".." || rel.startsWith(
|
|
2073
|
+
if (rel === ".." || rel.startsWith("../"))
|
|
2060
2074
|
return false;
|
|
2061
2075
|
return !isAbsolute2(rel);
|
|
2062
2076
|
}
|
|
2077
|
+
function shortSystemAlias(p) {
|
|
2078
|
+
if (p.startsWith("/private/var/"))
|
|
2079
|
+
return "/var/" + p.slice("/private/var/".length);
|
|
2080
|
+
if (p === "/private/tmp" || p.startsWith("/private/tmp/"))
|
|
2081
|
+
return "/tmp" + p.slice("/private/tmp".length);
|
|
2082
|
+
return p;
|
|
2083
|
+
}
|
|
2063
2084
|
function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
|
|
2064
2085
|
if (candidatePath.includes("\\"))
|
|
2065
2086
|
return false;
|
|
@@ -2079,8 +2100,16 @@ function resolvesWithinPluginRoot(candidatePath, pluginRoot) {
|
|
|
2079
2100
|
return !isAbsolute2(rel);
|
|
2080
2101
|
}
|
|
2081
2102
|
function escapeFileRulePathSegment(path) {
|
|
2082
|
-
const
|
|
2083
|
-
|
|
2103
|
+
const escaped = path.replace(/[[\]*\\]/g, "\\$&");
|
|
2104
|
+
let start = escaped.length;
|
|
2105
|
+
while (start > 0 && /\s/.test(escaped[start - 1]))
|
|
2106
|
+
start--;
|
|
2107
|
+
if (start === escaped.length)
|
|
2108
|
+
return escaped;
|
|
2109
|
+
let tail = "";
|
|
2110
|
+
for (const ch of escaped.slice(start))
|
|
2111
|
+
tail += "\\" + ch;
|
|
2112
|
+
return escaped.slice(0, start) + tail;
|
|
2084
2113
|
}
|
|
2085
2114
|
function matchesSingleFileRulePattern(pattern, path, opts, behavior) {
|
|
2086
2115
|
return matchFileRulesGrouped([{ entry: true, pattern }], path, opts, behavior) !== null;
|
|
@@ -2120,113 +2149,120 @@ function canonicalizePath(p) {
|
|
|
2120
2149
|
}
|
|
2121
2150
|
}
|
|
2122
2151
|
var canon = canonicalizePath;
|
|
2152
|
+
var subpathClause = (p) => `(subpath "${sbplString(p)}")`;
|
|
2153
|
+
var literalClause = (p) => `(literal "${sbplString(p)}")`;
|
|
2154
|
+
var regexClause = (source) => `(regex #"${escapeSbplRegexDelimiter(source)}")`;
|
|
2155
|
+
|
|
2156
|
+
class ClauseBlock {
|
|
2157
|
+
seen = new Set;
|
|
2158
|
+
clauses = [];
|
|
2159
|
+
add(clause) {
|
|
2160
|
+
if (this.seen.has(clause))
|
|
2161
|
+
return;
|
|
2162
|
+
this.seen.add(clause);
|
|
2163
|
+
this.clauses.push(clause);
|
|
2164
|
+
}
|
|
2165
|
+
render(header) {
|
|
2166
|
+
if (this.clauses.length === 0)
|
|
2167
|
+
return "";
|
|
2168
|
+
const lines = this.clauses.map((c) => ` ${c}`);
|
|
2169
|
+
lines[lines.length - 1] += ")";
|
|
2170
|
+
return [header, ...lines].join(`
|
|
2171
|
+
`);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2123
2174
|
function buildAncestorRenameBypassBlock(plainDenyPaths, globFixedPrefixes) {
|
|
2124
|
-
const
|
|
2125
|
-
for (const
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
for (const
|
|
2129
|
-
|
|
2175
|
+
const block = new ClauseBlock;
|
|
2176
|
+
for (const entry of plainDenyPaths) {
|
|
2177
|
+
const path = canon(entry);
|
|
2178
|
+
block.add(subpathClause(path));
|
|
2179
|
+
for (const dir of ancestorDirectoriesOf(path))
|
|
2180
|
+
block.add(literalClause(dir));
|
|
2130
2181
|
}
|
|
2131
2182
|
for (const prefix of globFixedPrefixes) {
|
|
2132
|
-
|
|
2133
|
-
for (const
|
|
2134
|
-
|
|
2183
|
+
block.add(literalClause(prefix));
|
|
2184
|
+
for (const dir of ancestorDirectoriesOf(prefix))
|
|
2185
|
+
block.add(literalClause(dir));
|
|
2135
2186
|
}
|
|
2136
|
-
|
|
2137
|
-
return "";
|
|
2138
|
-
return [`(deny file-write-unlink file-write-create`, ...[...clauses].map((c) => ` ${c}`)].join(`
|
|
2139
|
-
`) + ")";
|
|
2187
|
+
return block.render("(deny file-write-unlink file-write-create");
|
|
2140
2188
|
}
|
|
2141
2189
|
function buildReadDenyWritePermitBlock(writableRoots) {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
return
|
|
2146
|
-
`) + ")";
|
|
2190
|
+
const block = new ClauseBlock;
|
|
2191
|
+
for (const root of writableRoots)
|
|
2192
|
+
block.add(subpathClause(root));
|
|
2193
|
+
return block.render("(allow file-write-unlink file-write-create");
|
|
2147
2194
|
}
|
|
2148
2195
|
var IMAGE_PREP_DIRNAME = "image-prep";
|
|
2149
2196
|
var WRITE_OPS_SURVIVING_READ_DENY_REPERMIT = "file-write* file-write-unlink file-write-create";
|
|
2150
2197
|
var DEFAULT_PROTECTED_FILES = [".gitconfig", ".gitmodules", ".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile", ".ripgreprc", ".mcp.json"];
|
|
2151
2198
|
var DEFAULT_PROTECTED_DIRS = [".vscode", ".idea", ".claude/commands", ".claude/agents"];
|
|
2152
2199
|
var PROJECT_DIR_PROTECTED_KINDS = ["commands", "agents", "skills", "rules", "output-styles"];
|
|
2153
|
-
function cwdAnchoredEntryRegex(cwd, entry, recursive) {
|
|
2154
|
-
const absoluteGlob = join3(cwd, "**", entry) + (recursive ? "/**" : "");
|
|
2155
|
-
return recursiveGlobToSbplRegexSource(absoluteGlob);
|
|
2156
|
-
}
|
|
2157
|
-
function buildDefaultWriteProtectionEntries(cwd, brand, allowGitConfigWrites) {
|
|
2158
|
-
const plainPaths = [];
|
|
2159
|
-
const regexes = [];
|
|
2160
|
-
for (const f of DEFAULT_PROTECTED_FILES) {
|
|
2161
|
-
plainPaths.push(join3(cwd, f));
|
|
2162
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, f, false));
|
|
2163
|
-
}
|
|
2164
|
-
const winterMcpJson = join3(brand.projectDirName, "mcp.json");
|
|
2165
|
-
plainPaths.push(join3(cwd, winterMcpJson));
|
|
2166
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, winterMcpJson, false));
|
|
2167
|
-
for (const d of DEFAULT_PROTECTED_DIRS) {
|
|
2168
|
-
plainPaths.push(join3(cwd, d));
|
|
2169
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
|
|
2170
|
-
}
|
|
2171
|
-
for (const kind of PROJECT_DIR_PROTECTED_KINDS) {
|
|
2172
|
-
const d = join3(brand.projectDirName, kind);
|
|
2173
|
-
plainPaths.push(join3(cwd, d));
|
|
2174
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, d, true));
|
|
2175
|
-
}
|
|
2176
|
-
plainPaths.push(join3(cwd, ".git", "hooks"));
|
|
2177
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, ".git/hooks", true));
|
|
2178
|
-
if (!allowGitConfigWrites) {
|
|
2179
|
-
plainPaths.push(join3(cwd, ".git", "config"));
|
|
2180
|
-
regexes.push(cwdAnchoredEntryRegex(cwd, ".git/config", false));
|
|
2181
|
-
}
|
|
2182
|
-
return { plainPaths, regexes };
|
|
2183
|
-
}
|
|
2184
2200
|
function buildDefaultWriteProtectionBlock(cwd, brand, allowGitConfigWrites) {
|
|
2185
|
-
const
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2201
|
+
const proj = brand.projectDirName;
|
|
2202
|
+
const entries = [
|
|
2203
|
+
...DEFAULT_PROTECTED_FILES.map((rel) => ({ rel, isDir: false })),
|
|
2204
|
+
{ rel: `${proj}/mcp.json`, isDir: false },
|
|
2205
|
+
...DEFAULT_PROTECTED_DIRS.map((rel) => ({ rel, isDir: true })),
|
|
2206
|
+
...PROJECT_DIR_PROTECTED_KINDS.map((kind) => ({ rel: `${proj}/${kind}`, isDir: true })),
|
|
2207
|
+
{ rel: ".git/hooks", isDir: true },
|
|
2208
|
+
...allowGitConfigWrites ? [] : [{ rel: ".git/config", isDir: false }]
|
|
2209
|
+
];
|
|
2210
|
+
const plainPaths = entries.map((e) => join3(cwd, e.rel));
|
|
2211
|
+
const regexSources = entries.map((e) => {
|
|
2212
|
+
const anyDepth = join3(cwd, "**", e.rel);
|
|
2213
|
+
return recursiveGlobToSbplRegexSource(e.isDir ? `${anyDepth}/**` : anyDepth);
|
|
2214
|
+
});
|
|
2215
|
+
const lines = [
|
|
2216
|
+
...plainPaths.map((p) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} ${subpathClause(canon(p))})`),
|
|
2217
|
+
...regexSources.map((r) => `(deny ${WRITE_OPS_SURVIVING_READ_DENY_REPERMIT} ${regexClause(r)})`),
|
|
2218
|
+
buildAncestorRenameBypassBlock(plainPaths, [])
|
|
2219
|
+
];
|
|
2220
|
+
return lines.join(`
|
|
2192
2221
|
`);
|
|
2193
2222
|
}
|
|
2194
2223
|
function isProperDescendantOf(candidate, root) {
|
|
2195
|
-
|
|
2224
|
+
if (root === "/")
|
|
2225
|
+
return candidate !== "/" && candidate.startsWith("/");
|
|
2226
|
+
return candidate !== root && candidate.startsWith(`${root}/`);
|
|
2227
|
+
}
|
|
2228
|
+
function withCarveOuts(base, carveOuts) {
|
|
2229
|
+
if (carveOuts.length === 0)
|
|
2230
|
+
return base;
|
|
2231
|
+
const exclusions = carveOuts.map((x) => `(require-not ${subpathClause(x)})`).join(" ");
|
|
2232
|
+
return `(require-all ${base} ${exclusions})`;
|
|
2196
2233
|
}
|
|
2197
2234
|
function buildReadDenyKeepInPlaceBlock(plainDenyReadPaths, globDenyReadEntries, writableRoots) {
|
|
2198
2235
|
if (writableRoots.length === 0)
|
|
2199
2236
|
return "";
|
|
2200
|
-
const
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
if (isUnderAnyWriteRoot(ancestor))
|
|
2206
|
-
clauses.add(`(literal "${sbplString(ancestor)}")`);
|
|
2207
|
-
};
|
|
2208
|
-
const clauses = new Set;
|
|
2209
|
-
for (const rawPath of plainDenyReadPaths) {
|
|
2210
|
-
const path = canon(rawPath);
|
|
2211
|
-
if (!isUnderAnyWriteRoot(path))
|
|
2237
|
+
const insideSomeRoot = (p) => writableRoots.some((root) => isProperDescendantOf(p, root));
|
|
2238
|
+
const block = new ClauseBlock;
|
|
2239
|
+
for (const entry of plainDenyReadPaths) {
|
|
2240
|
+
const path = canon(entry);
|
|
2241
|
+
if (!insideSomeRoot(path))
|
|
2212
2242
|
continue;
|
|
2213
|
-
|
|
2214
|
-
|
|
2243
|
+
const nestedRoots = writableRoots.filter((root) => isProperDescendantOf(root, path));
|
|
2244
|
+
block.add(withCarveOuts(subpathClause(path), nestedRoots));
|
|
2245
|
+
for (const dir of ancestorDirectoriesOf(path)) {
|
|
2246
|
+
if (insideSomeRoot(dir))
|
|
2247
|
+
block.add(literalClause(dir));
|
|
2248
|
+
}
|
|
2215
2249
|
}
|
|
2216
|
-
for (const
|
|
2217
|
-
const
|
|
2218
|
-
|
|
2250
|
+
for (const { regex, fixedPrefix } of globDenyReadEntries) {
|
|
2251
|
+
const prefix = fixedPrefix;
|
|
2252
|
+
const relates = writableRoots.some((root) => prefix === root || isProperDescendantOf(prefix, root) || isProperDescendantOf(root, prefix));
|
|
2253
|
+
if (!relates)
|
|
2219
2254
|
continue;
|
|
2220
|
-
const
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
if (
|
|
2224
|
-
|
|
2255
|
+
const compiled = new RegExp(regex);
|
|
2256
|
+
const matchedRoots = writableRoots.filter((root) => compiled.test(root));
|
|
2257
|
+
block.add(withCarveOuts(regexClause(regex), matchedRoots));
|
|
2258
|
+
if (prefix !== "/") {
|
|
2259
|
+
for (const dir of [prefix, ...ancestorDirectoriesOf(prefix)]) {
|
|
2260
|
+
if (insideSomeRoot(dir))
|
|
2261
|
+
block.add(literalClause(dir));
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2225
2264
|
}
|
|
2226
|
-
|
|
2227
|
-
return "";
|
|
2228
|
-
return [`(deny file-write-unlink`, ...[...clauses].map((c) => ` ${c}`)].join(`
|
|
2229
|
-
`) + ")";
|
|
2265
|
+
return block.render("(deny file-write-unlink");
|
|
2230
2266
|
}
|
|
2231
2267
|
var CONTROL_PLANE_FILES = ["permissions.local.json", "settings.json", "settings.local.json"];
|
|
2232
2268
|
function caseFoldSegment(segment) {
|
|
@@ -2634,23 +2670,22 @@ registerAttachmentRenderer("agent_listing_delta", (a) => {
|
|
|
2634
2670
|
const addedLines = stringArray(a["addedLines"]);
|
|
2635
2671
|
const addedTypes = stringArray(a["addedTypes"]);
|
|
2636
2672
|
const removedTypes = stringArray(a["removedTypes"]);
|
|
2673
|
+
const initial = a["isInitial"] === true;
|
|
2637
2674
|
const sections = [];
|
|
2638
2675
|
const hasAdded = addedLines.length > 0 && addedTypes.length > 0;
|
|
2639
2676
|
if (hasAdded)
|
|
2640
|
-
sections.push(`${
|
|
2677
|
+
sections.push(`${initial ? AGENT_LISTING_INITIAL_HEADER : AGENT_LISTING_ADDED_HEADER}
|
|
2641
2678
|
${addedLines.join(`
|
|
2642
2679
|
`)}`);
|
|
2643
2680
|
if (removedTypes.length > 0) {
|
|
2644
2681
|
sections.push(`${AGENT_LISTING_REMOVED_HEADER}
|
|
2645
|
-
${removedTypes.map((
|
|
2682
|
+
${removedTypes.map((name) => `- ${name}`).join(`
|
|
2646
2683
|
`)}`);
|
|
2647
2684
|
sections.push(AMBIENT_CONTEXT_SENTENCE);
|
|
2648
2685
|
}
|
|
2649
|
-
if (hasAdded &&
|
|
2686
|
+
if (hasAdded && initial && a["showConcurrencyNote"] === true)
|
|
2650
2687
|
sections.push(AGENT_CONCURRENCY_SENTENCE);
|
|
2651
|
-
|
|
2652
|
-
return;
|
|
2653
|
-
return neutralizeReminderTags(sections.join(`
|
|
2688
|
+
return sections.length === 0 ? undefined : neutralizeReminderTags(sections.join(`
|
|
2654
2689
|
|
|
2655
2690
|
`));
|
|
2656
2691
|
});
|
|
@@ -2718,6 +2753,20 @@ registerAttachmentRenderer("tool_changes", (a) => {
|
|
|
2718
2753
|
const parts = [...names("add").map((n) => `+${n}`), ...names("remove").map((n) => `-${n}`)];
|
|
2719
2754
|
return `[tools changed: ${parts.length > 0 ? parts.join(" ") : "declarations only"}]`;
|
|
2720
2755
|
}, { wrap: false });
|
|
2756
|
+
var QUEUED_PROMPT_HEADER = "The user sent a new message while you were working:";
|
|
2757
|
+
var QUEUED_PROMPT_FOOTER = "IMPORTANT: After completing your current task, you MUST address the user's message above. Do not ignore it.";
|
|
2758
|
+
function queuedPromptAttachment(prompt) {
|
|
2759
|
+
return { type: "queued_command", prompt, commandMode: "prompt" };
|
|
2760
|
+
}
|
|
2761
|
+
registerAttachmentRenderer("queued_command", (a) => {
|
|
2762
|
+
const prompt = a["prompt"];
|
|
2763
|
+
if (a["commandMode"] !== "prompt" || a["origin"] !== undefined || typeof prompt !== "string")
|
|
2764
|
+
return;
|
|
2765
|
+
return `${QUEUED_PROMPT_HEADER}
|
|
2766
|
+
${neutralizeReminderTags(prompt)}
|
|
2767
|
+
|
|
2768
|
+
${QUEUED_PROMPT_FOOTER}`;
|
|
2769
|
+
});
|
|
2721
2770
|
function renderAttachment(attachment) {
|
|
2722
2771
|
const renderer = renderers.get(attachment.type);
|
|
2723
2772
|
if (renderer === undefined)
|
|
@@ -2746,10 +2795,10 @@ function announcedAgentTypes(messages) {
|
|
|
2746
2795
|
if (a.type !== "agent_listing_delta")
|
|
2747
2796
|
continue;
|
|
2748
2797
|
if (Array.isArray(a["addedLines"]))
|
|
2749
|
-
for (const
|
|
2750
|
-
announced.add(
|
|
2751
|
-
for (const
|
|
2752
|
-
announced.delete(
|
|
2798
|
+
for (const type of stringArray(a["addedTypes"]))
|
|
2799
|
+
announced.add(type);
|
|
2800
|
+
for (const type of stringArray(a["removedTypes"]))
|
|
2801
|
+
announced.delete(type);
|
|
2753
2802
|
}
|
|
2754
2803
|
return announced;
|
|
2755
2804
|
}
|
|
@@ -2785,26 +2834,26 @@ function localDateString(now = new Date) {
|
|
|
2785
2834
|
function xmlEscape(value) {
|
|
2786
2835
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2787
2836
|
}
|
|
2788
|
-
var TAG_ROOT = "task-notification";
|
|
2789
|
-
var TAG_ORDER = [
|
|
2790
|
-
["task-id", "taskId"],
|
|
2791
|
-
["tool-use-id", "toolUseId"],
|
|
2792
|
-
["task-type", "taskType"],
|
|
2793
|
-
["output-file", "outputFile"],
|
|
2794
|
-
["status", "status"],
|
|
2795
|
-
["summary", "summary"]
|
|
2796
|
-
];
|
|
2797
2837
|
function renderTaskNotification(fields) {
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2838
|
+
const tagged = [
|
|
2839
|
+
["task-id", fields.taskId],
|
|
2840
|
+
["tool-use-id", fields.toolUseId],
|
|
2841
|
+
["task-type", fields.taskType],
|
|
2842
|
+
["output-file", fields.outputFile],
|
|
2843
|
+
["status", fields.status],
|
|
2844
|
+
["summary", fields.summary]
|
|
2845
|
+
];
|
|
2846
|
+
let out = "<task-notification>";
|
|
2847
|
+
for (const [tag, value] of tagged) {
|
|
2848
|
+
if (typeof value === "string" && value.length > 0)
|
|
2849
|
+
out += `
|
|
2804
2850
|
<${tag}>${value}</${tag}>`;
|
|
2805
2851
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2852
|
+
out += fields.body ?? "";
|
|
2853
|
+
out += `
|
|
2854
|
+
</task-notification>`;
|
|
2855
|
+
out += fields.trailing ?? "";
|
|
2856
|
+
return out;
|
|
2808
2857
|
}
|
|
2809
2858
|
var SYSTEM_NOTIFICATION_MARKER = "[SYSTEM NOTIFICATION - NOT USER INPUT]";
|
|
2810
2859
|
var NOTIFICATION_PREAMBLE = `${SYSTEM_NOTIFICATION_MARKER}
|
|
@@ -2824,34 +2873,60 @@ function withNotificationPreamble(value, opts) {
|
|
|
2824
2873
|
return value;
|
|
2825
2874
|
return `${opts?.inHumanTurn === true ? NOTIFICATION_PREAMBLE_IN_HUMAN_TURN : NOTIFICATION_PREAMBLE}${neutralizeReminderTags(value)}`;
|
|
2826
2875
|
}
|
|
2827
|
-
function
|
|
2876
|
+
function nonEmpty(value) {
|
|
2877
|
+
return typeof value === "string" && value.length > 0;
|
|
2878
|
+
}
|
|
2879
|
+
function statusWord(status) {
|
|
2828
2880
|
return status === "stopped" ? "killed" : status;
|
|
2829
2881
|
}
|
|
2882
|
+
function optionalEscaped(toolUseId, outputFile) {
|
|
2883
|
+
return {
|
|
2884
|
+
...toolUseId !== undefined ? { toolUseId: xmlEscape(toolUseId) } : {},
|
|
2885
|
+
...outputFile !== undefined ? { outputFile: xmlEscape(outputFile) } : {}
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
function usageCounters(usage) {
|
|
2889
|
+
return `<subagent_tokens>${String(usage.totalTokens)}</subagent_tokens><tool_uses>${String(usage.toolUses)}</tool_uses><duration_ms>${String(usage.durationMs)}</duration_ms>`;
|
|
2890
|
+
}
|
|
2891
|
+
function usageBlock(usage) {
|
|
2892
|
+
return `<usage>${usageCounters(usage)}</usage>`;
|
|
2893
|
+
}
|
|
2894
|
+
var AGENT_RESUME_NOTE = "<note>This notification fires each time the agent stops with no background work of its own still running, so the same task-id can notify more than once. Send it another message with SendMessage to resume it.</note>";
|
|
2830
2895
|
function renderAgentNotification(input) {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2896
|
+
let outcome;
|
|
2897
|
+
if (input.status === "completed") {
|
|
2898
|
+
outcome = input.maxTurnsReached !== undefined ? `stopped at its ${input.maxTurnsReached}-turn limit (partial result; SendMessage to task-id to continue)` : "finished";
|
|
2899
|
+
} else if (input.status === "failed") {
|
|
2900
|
+
outcome = `failed: ${nonEmpty(input.error) ? input.error : "Unknown error"}`;
|
|
2901
|
+
} else {
|
|
2902
|
+
outcome = input.stoppedBy === "parent" ? "was stopped by the assistant" : input.stoppedBy === "user" ? "was stopped by user" : "was stopped";
|
|
2903
|
+
}
|
|
2904
|
+
let body = `
|
|
2905
|
+
${AGENT_RESUME_NOTE}`;
|
|
2906
|
+
if (nonEmpty(input.finalMessage))
|
|
2907
|
+
body += `
|
|
2908
|
+
<result>${xmlEscape(input.finalMessage)}</result>`;
|
|
2909
|
+
if (input.usage !== undefined)
|
|
2910
|
+
body += `
|
|
2911
|
+
${usageBlock(input.usage)}`;
|
|
2912
|
+
if (input.worktree !== undefined) {
|
|
2913
|
+
const branch = input.worktree.branch !== undefined ? `<worktreeBranch>${xmlEscape(input.worktree.branch)}</worktreeBranch>` : "";
|
|
2914
|
+
body += `
|
|
2915
|
+
<worktree><worktreePath>${xmlEscape(input.worktree.path)}</worktreePath>${branch}</worktree>`;
|
|
2916
|
+
}
|
|
2839
2917
|
return renderTaskNotification({
|
|
2840
2918
|
taskId: xmlEscape(input.taskId),
|
|
2841
|
-
...input.toolUseId
|
|
2842
|
-
|
|
2843
|
-
status: xmlStatus(input.status),
|
|
2919
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2920
|
+
status: statusWord(input.status),
|
|
2844
2921
|
summary: xmlEscape(`Agent "${input.description}" ${outcome}`),
|
|
2845
|
-
body
|
|
2846
|
-
<note>This notification fires each time the agent stops with no background work of its own still running, so the same task-id can notify more than once. Send it another message with SendMessage to resume it.</note>${result}${usage}${worktree}`
|
|
2922
|
+
body
|
|
2847
2923
|
});
|
|
2848
2924
|
}
|
|
2849
2925
|
function renderShellNotification(input) {
|
|
2850
2926
|
return renderTaskNotification({
|
|
2851
2927
|
taskId: xmlEscape(input.taskId),
|
|
2852
|
-
...input.toolUseId
|
|
2853
|
-
|
|
2854
|
-
status: xmlStatus(input.status),
|
|
2928
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2929
|
+
status: statusWord(input.status),
|
|
2855
2930
|
summary: xmlEscape(input.summary)
|
|
2856
2931
|
});
|
|
2857
2932
|
}
|
|
@@ -2867,28 +2942,40 @@ function renderTaskStopNotification(input) {
|
|
|
2867
2942
|
const who = input.stoppedBy === "parent" ? "the assistant" : "user";
|
|
2868
2943
|
return renderTaskNotification({
|
|
2869
2944
|
taskId: xmlEscape(input.taskId),
|
|
2870
|
-
...input.toolUseId
|
|
2945
|
+
...optionalEscaped(input.toolUseId, undefined),
|
|
2871
2946
|
status: "stopped",
|
|
2872
2947
|
summary: xmlEscape(`Task "${input.description}" was stopped by ${who}`)
|
|
2873
2948
|
});
|
|
2874
2949
|
}
|
|
2875
2950
|
function renderWorkflowNotification(input) {
|
|
2876
|
-
const name = xmlEscape(input.name
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2951
|
+
const name = xmlEscape(nonEmpty(input.name) ? input.name : "Dynamic workflow");
|
|
2952
|
+
let summary;
|
|
2953
|
+
if (input.status === "completed")
|
|
2954
|
+
summary = `Dynamic workflow "${name}" completed`;
|
|
2955
|
+
else if (input.status === "failed")
|
|
2956
|
+
summary = `Dynamic workflow "${name}" failed: ${nonEmpty(input.error) ? xmlEscape(input.error) : "Unknown error"}`;
|
|
2957
|
+
else
|
|
2958
|
+
summary = `Dynamic workflow "${name}" was stopped`;
|
|
2959
|
+
let body = "";
|
|
2960
|
+
if (nonEmpty(input.result))
|
|
2961
|
+
body += `
|
|
2962
|
+
<result>${xmlEscape(input.result)}</result>`;
|
|
2963
|
+
if (input.failures !== undefined && input.failures.length > 0)
|
|
2964
|
+
body += `
|
|
2881
2965
|
<failures>${xmlEscape(input.failures.join(`
|
|
2882
|
-
`))}</failures
|
|
2883
|
-
|
|
2884
|
-
|
|
2966
|
+
`))}</failures>`;
|
|
2967
|
+
if (input.usage !== undefined || input.agentCount !== undefined) {
|
|
2968
|
+
const given = input.usage ?? {};
|
|
2969
|
+
const usage = { totalTokens: given.totalTokens ?? 0, toolUses: given.toolUses ?? 0, durationMs: given.durationMs ?? 0 };
|
|
2970
|
+
body += `
|
|
2971
|
+
<usage><agent_count>${String(input.agentCount ?? 0)}</agent_count>${usageCounters(usage)}</usage>`;
|
|
2972
|
+
}
|
|
2885
2973
|
return renderTaskNotification({
|
|
2886
2974
|
taskId: xmlEscape(input.taskId),
|
|
2887
|
-
...input.toolUseId
|
|
2888
|
-
|
|
2889
|
-
status: xmlStatus(input.status),
|
|
2975
|
+
...optionalEscaped(input.toolUseId, input.outputFile),
|
|
2976
|
+
status: statusWord(input.status),
|
|
2890
2977
|
summary,
|
|
2891
|
-
body
|
|
2978
|
+
body
|
|
2892
2979
|
});
|
|
2893
2980
|
}
|
|
2894
2981
|
var PRIORITY_RANK = { next: 0, later: 1 };
|
|
@@ -7008,8 +7095,17 @@ function isCacheableTransport(config) {
|
|
|
7008
7095
|
}
|
|
7009
7096
|
var FIRST_TURN_MCP_WAIT_DEFAULT_MS = 2000;
|
|
7010
7097
|
function firstTurnMcpWaitDeadlineMs(opts) {
|
|
7011
|
-
|
|
7012
|
-
|
|
7098
|
+
if (opts.strictMcpConfig === true)
|
|
7099
|
+
return opts.envConfig.timeoutMs;
|
|
7100
|
+
const servers = opts.explicitServers;
|
|
7101
|
+
if (servers !== undefined && servers !== null) {
|
|
7102
|
+
for (const config of Object.values(servers)) {
|
|
7103
|
+
const inProcess = typeof config === "object" && config !== null && config.type === "sdk";
|
|
7104
|
+
if (!inProcess)
|
|
7105
|
+
return opts.envConfig.timeoutMs;
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
return FIRST_TURN_MCP_WAIT_DEFAULT_MS;
|
|
7013
7109
|
}
|
|
7014
7110
|
var sessionLifecycles = new Map;
|
|
7015
7111
|
function registerSessionMcpLifecycle(sessionId, lifecycle) {
|
|
@@ -9125,66 +9221,80 @@ function resolveBuiltinAgents(opts) {
|
|
|
9125
9221
|
}
|
|
9126
9222
|
|
|
9127
9223
|
// src/subagents/definitions.ts
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9224
|
+
function parseFrontmatter(raw) {
|
|
9225
|
+
const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
9226
|
+
const split = splitFrontmatter(text);
|
|
9227
|
+
if (split === undefined)
|
|
9228
|
+
return { attrs: {}, body: raw };
|
|
9229
|
+
return { attrs: readFrontmatterBlock(split.block), body: text.slice(split.bodyStart) };
|
|
9131
9230
|
}
|
|
9132
|
-
var
|
|
9133
|
-
function
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
const match = /^([a-zA-Z_-]+):\s+(.+)$/.exec(line);
|
|
9139
|
-
if (match) {
|
|
9140
|
-
const key = match[1];
|
|
9141
|
-
const value = match[2];
|
|
9142
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
9143
|
-
try {
|
|
9144
|
-
if (Array.isArray(Bun.YAML.parse(value))) {
|
|
9145
|
-
result.push(line);
|
|
9146
|
-
continue;
|
|
9147
|
-
}
|
|
9148
|
-
} catch {}
|
|
9149
|
-
}
|
|
9150
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
9151
|
-
result.push(line);
|
|
9152
|
-
continue;
|
|
9153
|
-
}
|
|
9154
|
-
if (YAML_SPECIAL_CHARS.test(value)) {
|
|
9155
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
9156
|
-
result.push(`${key}: "${escaped}"`);
|
|
9157
|
-
continue;
|
|
9158
|
-
}
|
|
9159
|
-
}
|
|
9160
|
-
result.push(line);
|
|
9161
|
-
}
|
|
9162
|
-
return result.join(`
|
|
9163
|
-
`);
|
|
9231
|
+
var WHITESPACE_CHAR = /\s/;
|
|
9232
|
+
function skipWhitespace(text, from) {
|
|
9233
|
+
let i = from;
|
|
9234
|
+
while (i < text.length && WHITESPACE_CHAR.test(text[i]))
|
|
9235
|
+
i++;
|
|
9236
|
+
return i;
|
|
9164
9237
|
}
|
|
9165
|
-
function
|
|
9166
|
-
|
|
9238
|
+
function splitFrontmatter(text) {
|
|
9239
|
+
if (!text.startsWith("---"))
|
|
9240
|
+
return;
|
|
9241
|
+
const runEnd = skipWhitespace(text, 3);
|
|
9242
|
+
const lastLf = text.lastIndexOf(`
|
|
9243
|
+
`, runEnd - 1);
|
|
9244
|
+
if (lastLf < 3)
|
|
9245
|
+
return;
|
|
9246
|
+
const blockStart = lastLf + 1;
|
|
9247
|
+
const close = text.indexOf("---", blockStart);
|
|
9248
|
+
if (close === -1)
|
|
9249
|
+
return;
|
|
9250
|
+
return { block: text.slice(blockStart, close), bodyStart: skipWhitespace(text, close + 3) };
|
|
9167
9251
|
}
|
|
9168
|
-
function
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
const frontmatterText = match[1] ?? "";
|
|
9174
|
-
const body = stripped.slice(match[0].length);
|
|
9175
|
-
let attrs = {};
|
|
9252
|
+
function isPlainObject2(value) {
|
|
9253
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9254
|
+
}
|
|
9255
|
+
function readFrontmatterBlock(block) {
|
|
9256
|
+
let parsed;
|
|
9176
9257
|
try {
|
|
9177
|
-
|
|
9178
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
|
|
9179
|
-
attrs = parsed;
|
|
9258
|
+
parsed = Bun.YAML.parse(block);
|
|
9180
9259
|
} catch {
|
|
9181
9260
|
try {
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
}
|
|
9261
|
+
parsed = Bun.YAML.parse(detabLineStarts(quoteLooseValues(block)));
|
|
9262
|
+
} catch {
|
|
9263
|
+
return {};
|
|
9264
|
+
}
|
|
9186
9265
|
}
|
|
9187
|
-
return
|
|
9266
|
+
return isPlainObject2(parsed) ? parsed : {};
|
|
9267
|
+
}
|
|
9268
|
+
var KEY_LINE_HEAD = /^([A-Za-z_-]+):(\s+)/;
|
|
9269
|
+
var LINE_TERMINATOR = /[\r\u2028\u2029]/;
|
|
9270
|
+
var YAML_SPECIAL_CHAR = /[{}[\]*&#!|>%@`]/;
|
|
9271
|
+
function quoteLooseValues(block) {
|
|
9272
|
+
return block.split(`
|
|
9273
|
+
`).map((line) => {
|
|
9274
|
+
const head = KEY_LINE_HEAD.exec(line);
|
|
9275
|
+
if (head === null)
|
|
9276
|
+
return line;
|
|
9277
|
+
const key = head[1];
|
|
9278
|
+
const value = line.slice(head[0].length);
|
|
9279
|
+
if (value.length === 0 || LINE_TERMINATOR.test(value))
|
|
9280
|
+
return line;
|
|
9281
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
9282
|
+
try {
|
|
9283
|
+
if (Array.isArray(Bun.YAML.parse(value)))
|
|
9284
|
+
return line;
|
|
9285
|
+
} catch {}
|
|
9286
|
+
}
|
|
9287
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))
|
|
9288
|
+
return line;
|
|
9289
|
+
if (YAML_SPECIAL_CHAR.test(value) || value.includes(": ")) {
|
|
9290
|
+
return `${key}: "${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
9291
|
+
}
|
|
9292
|
+
return line;
|
|
9293
|
+
}).join(`
|
|
9294
|
+
`);
|
|
9295
|
+
}
|
|
9296
|
+
function detabLineStarts(text) {
|
|
9297
|
+
return text.replace(/^\t+/gm, (tabs) => " ".repeat(tabs.length));
|
|
9188
9298
|
}
|
|
9189
9299
|
function asString(v) {
|
|
9190
9300
|
return typeof v === "string" ? v : undefined;
|
|
@@ -9495,67 +9605,70 @@ var PREAPPROVED_HOST_ENTRIES = [
|
|
|
9495
9605
|
"nginx.org",
|
|
9496
9606
|
"httpd.apache.org"
|
|
9497
9607
|
];
|
|
9608
|
+
function isPreapprovedHost(hostname, pathname) {
|
|
9609
|
+
if (HOSTNAME_ONLY.has(hostname))
|
|
9610
|
+
return true;
|
|
9611
|
+
const prefixes = PATH_PREFIXES.get(hostname);
|
|
9612
|
+
if (prefixes === undefined)
|
|
9613
|
+
return false;
|
|
9614
|
+
if (isTraversalEncoded(pathname))
|
|
9615
|
+
return false;
|
|
9616
|
+
return prefixes.some((prefix) => underPrefix(pathname, prefix));
|
|
9617
|
+
}
|
|
9498
9618
|
var HOSTNAME_ONLY = new Set;
|
|
9499
9619
|
var PATH_PREFIXES = new Map;
|
|
9500
9620
|
for (const entry of PREAPPROVED_HOST_ENTRIES) {
|
|
9501
9621
|
const slash = entry.indexOf("/");
|
|
9502
9622
|
if (slash === -1) {
|
|
9503
9623
|
HOSTNAME_ONLY.add(entry);
|
|
9504
|
-
|
|
9505
|
-
const host = entry.slice(0, slash);
|
|
9506
|
-
const prefix = entry.slice(slash);
|
|
9507
|
-
const existing = PATH_PREFIXES.get(host);
|
|
9508
|
-
if (existing)
|
|
9509
|
-
existing.push(prefix);
|
|
9510
|
-
else
|
|
9511
|
-
PATH_PREFIXES.set(host, [prefix]);
|
|
9624
|
+
continue;
|
|
9512
9625
|
}
|
|
9626
|
+
const host = entry.slice(0, slash);
|
|
9627
|
+
const prefix = entry.slice(slash);
|
|
9628
|
+
const list = PATH_PREFIXES.get(host);
|
|
9629
|
+
if (list === undefined)
|
|
9630
|
+
PATH_PREFIXES.set(host, [prefix]);
|
|
9631
|
+
else if (!list.includes(prefix))
|
|
9632
|
+
list.push(prefix);
|
|
9513
9633
|
}
|
|
9514
|
-
var ENCODED_TRAVERSAL = /%(25)*(2f|5c|2e)/i;
|
|
9515
|
-
function
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
return
|
|
9634
|
+
var ENCODED_TRAVERSAL = /%(?:25)*(?:2f|5c|2e)/i;
|
|
9635
|
+
function isTraversalEncoded(pathname) {
|
|
9636
|
+
return ENCODED_TRAVERSAL.test(pathname);
|
|
9637
|
+
}
|
|
9638
|
+
function underPrefix(pathname, prefix) {
|
|
9639
|
+
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
9640
|
+
}
|
|
9641
|
+
function wwwVariants(host) {
|
|
9642
|
+
const bare = host.startsWith("www.") ? host.slice(4) : host;
|
|
9643
|
+
return [...new Set([host, bare, `www.${bare}`])];
|
|
9524
9644
|
}
|
|
9525
9645
|
function isPreapprovedUrl(url) {
|
|
9526
9646
|
return isPreapprovedHost(url.hostname, url.pathname);
|
|
9527
9647
|
}
|
|
9528
|
-
function candidateHosts(hostname) {
|
|
9529
|
-
const stripped = hostname.startsWith("www.") ? hostname.slice(4) : hostname;
|
|
9530
|
-
return [...new Set([hostname, stripped, `www.${stripped}`])];
|
|
9531
|
-
}
|
|
9532
9648
|
function preapprovedScopeOf(url) {
|
|
9533
|
-
const
|
|
9534
|
-
for (const
|
|
9535
|
-
if (HOSTNAME_ONLY.has(
|
|
9536
|
-
return { host };
|
|
9537
|
-
const prefixes = PATH_PREFIXES.get(
|
|
9649
|
+
const pathname = url.pathname;
|
|
9650
|
+
for (const candidate of wwwVariants(url.hostname)) {
|
|
9651
|
+
if (HOSTNAME_ONLY.has(candidate))
|
|
9652
|
+
return { host: candidate };
|
|
9653
|
+
const prefixes = PATH_PREFIXES.get(candidate);
|
|
9538
9654
|
if (prefixes === undefined)
|
|
9539
9655
|
continue;
|
|
9540
|
-
if (
|
|
9656
|
+
if (isTraversalEncoded(pathname))
|
|
9541
9657
|
return;
|
|
9542
|
-
const prefix = prefixes.find((p) => pathname
|
|
9658
|
+
const prefix = prefixes.find((p) => underPrefix(pathname, p));
|
|
9543
9659
|
if (prefix !== undefined)
|
|
9544
|
-
return { host, pathPrefix: prefix };
|
|
9660
|
+
return { host: candidate, pathPrefix: prefix };
|
|
9545
9661
|
}
|
|
9546
9662
|
return;
|
|
9547
9663
|
}
|
|
9548
9664
|
function staysWithinScope(from, url) {
|
|
9549
|
-
|
|
9550
|
-
const acceptableHosts = new Set([from.host, stripped, `www.${stripped}`]);
|
|
9551
|
-
if (!acceptableHosts.has(url.hostname))
|
|
9665
|
+
if (!wwwVariants(from.host).includes(url.hostname))
|
|
9552
9666
|
return false;
|
|
9553
9667
|
if (from.pathPrefix === undefined)
|
|
9554
9668
|
return true;
|
|
9555
|
-
|
|
9556
|
-
if (ENCODED_TRAVERSAL.test(pathname))
|
|
9669
|
+
if (isTraversalEncoded(url.pathname))
|
|
9557
9670
|
return false;
|
|
9558
|
-
return pathname
|
|
9671
|
+
return underPrefix(url.pathname, from.pathPrefix);
|
|
9559
9672
|
}
|
|
9560
9673
|
|
|
9561
9674
|
// src/web/private-address.ts
|
|
@@ -9653,29 +9766,22 @@ function extractCandidateWritePaths(call, ctx) {
|
|
|
9653
9766
|
const recognized = recognizeEditOperation(call, { sessionRoot: ctx.sessionRoot, home: ctx.home, ...ctx.brand !== undefined ? { brand: ctx.brand } : {} });
|
|
9654
9767
|
return recognized ? recognized.paths : [];
|
|
9655
9768
|
}
|
|
9656
|
-
var TRAILING_DOT_OR_WHITESPACE_COMPONENT = /[.\s]+$/;
|
|
9657
|
-
var DEVICE_NAME_SUFFIX = /\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
|
|
9658
|
-
var TRIPLE_DOT_COMPONENT = /(^|\/|\\)\.{3,}(\/|\\|$)/;
|
|
9659
|
-
var SHORT_FILENAME_TILDE = /~\d/;
|
|
9660
9769
|
function isSuspiciousPath(path) {
|
|
9661
|
-
if (/^[
|
|
9770
|
+
if (/^[/\\]\?\?[/\\]/.test(path))
|
|
9662
9771
|
return true;
|
|
9663
|
-
if (
|
|
9772
|
+
if (/~[0-9]/.test(path))
|
|
9664
9773
|
return true;
|
|
9665
|
-
if (
|
|
9774
|
+
if (SUSPICIOUS_PATH_PREFIXES.some((prefix) => path.startsWith(prefix)))
|
|
9666
9775
|
return true;
|
|
9667
9776
|
for (const component of path.split(/[/\\]/)) {
|
|
9668
9777
|
if (component === "" || component === "." || component === "..")
|
|
9669
9778
|
continue;
|
|
9670
|
-
if (
|
|
9779
|
+
if (/[.\s]$/.test(component))
|
|
9671
9780
|
return true;
|
|
9672
9781
|
}
|
|
9673
|
-
|
|
9674
|
-
return true;
|
|
9675
|
-
if (TRIPLE_DOT_COMPONENT.test(path))
|
|
9676
|
-
return true;
|
|
9677
|
-
return false;
|
|
9782
|
+
return /\.(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(path);
|
|
9678
9783
|
}
|
|
9784
|
+
var SUSPICIOUS_PATH_PREFIXES = ["\\\\?\\", "\\\\.\\", "//?/", "//./"];
|
|
9679
9785
|
function suspiciousSymlinkTarget(rawCandidate, ctx) {
|
|
9680
9786
|
try {
|
|
9681
9787
|
const absPath = resolveTargetPath(rawCandidate, ctx.cwd);
|
|
@@ -11915,66 +12021,46 @@ ${DIRECTIVE_PREFIX}${input.prompt}`;
|
|
|
11915
12021
|
|
|
11916
12022
|
${worktreeNote(input.worktree.parentRoot, input.worktree.worktreeRoot)}` : directive;
|
|
11917
12023
|
}
|
|
11918
|
-
function
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
return block.type === "tool_result";
|
|
11923
|
-
}
|
|
11924
|
-
function answeredToolUseIds(messages) {
|
|
12024
|
+
function buildForkInitialMessages(inherit, forkToolUseId) {
|
|
12025
|
+
const messages = inherit.messages;
|
|
12026
|
+
if (messages === undefined)
|
|
12027
|
+
return [];
|
|
11925
12028
|
const answered = new Set;
|
|
11926
|
-
for (const
|
|
11927
|
-
if (
|
|
12029
|
+
for (const message of messages) {
|
|
12030
|
+
if (!Array.isArray(message.content))
|
|
11928
12031
|
continue;
|
|
11929
|
-
for (const block of
|
|
11930
|
-
if (
|
|
12032
|
+
for (const block of message.content) {
|
|
12033
|
+
if (block.type === "tool_result")
|
|
11931
12034
|
answered.add(block.tool_use_id);
|
|
12035
|
+
}
|
|
11932
12036
|
}
|
|
11933
|
-
|
|
11934
|
-
}
|
|
11935
|
-
function dropUnansweredAssistantMessages(messages) {
|
|
11936
|
-
const answered = answeredToolUseIds(messages);
|
|
11937
|
-
const filtered = [];
|
|
12037
|
+
const kept = [];
|
|
11938
12038
|
const dropped = [];
|
|
11939
|
-
for (const
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
continue;
|
|
11943
|
-
}
|
|
11944
|
-
const toolUses = m.content.filter(isToolUseBlock);
|
|
11945
|
-
const hasUnanswered = toolUses.some((call) => !answered.has(call.id));
|
|
11946
|
-
(hasUnanswered ? dropped : filtered).push(m);
|
|
12039
|
+
for (const message of messages) {
|
|
12040
|
+
const hasUnanswered = message.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "tool_use" && !answered.has(block.id));
|
|
12041
|
+
(hasUnanswered ? dropped : kept).push(message);
|
|
11947
12042
|
}
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
content: [ownToolUse],
|
|
11954
|
-
...original.origin !== undefined ? { origin: original.origin } : {}
|
|
11955
|
-
};
|
|
11956
|
-
}
|
|
11957
|
-
function buildForkInitialMessages(inherit, forkToolUseId) {
|
|
11958
|
-
if (inherit.messages === undefined)
|
|
11959
|
-
return [];
|
|
11960
|
-
const { filtered, dropped } = dropUnansweredAssistantMessages(inherit.messages);
|
|
11961
|
-
let ownToolUse;
|
|
11962
|
-
let sourceMessage;
|
|
11963
|
-
for (const m of dropped) {
|
|
11964
|
-
if (typeof m.content === "string")
|
|
11965
|
-
continue;
|
|
11966
|
-
const match = m.content.filter(isToolUseBlock).find((call) => call.id === forkToolUseId);
|
|
12043
|
+
let source;
|
|
12044
|
+
let call;
|
|
12045
|
+
for (const message of dropped) {
|
|
12046
|
+
const blocks = message.content;
|
|
12047
|
+
const match = blocks.find((block) => block.type === "tool_use" && block.id === forkToolUseId);
|
|
11967
12048
|
if (match !== undefined) {
|
|
11968
|
-
|
|
11969
|
-
|
|
12049
|
+
source = message;
|
|
12050
|
+
call = match;
|
|
11970
12051
|
break;
|
|
11971
12052
|
}
|
|
11972
12053
|
}
|
|
11973
|
-
if (
|
|
11974
|
-
return
|
|
11975
|
-
const clone =
|
|
11976
|
-
|
|
11977
|
-
|
|
12054
|
+
if (source === undefined || call === undefined)
|
|
12055
|
+
return kept;
|
|
12056
|
+
const clone = { role: "assistant", content: [call] };
|
|
12057
|
+
if (source.origin !== undefined)
|
|
12058
|
+
clone.origin = source.origin;
|
|
12059
|
+
const placeholder = {
|
|
12060
|
+
role: "tool",
|
|
12061
|
+
content: [{ type: "tool_result", tool_use_id: forkToolUseId, content: FORK_PLACEHOLDER_TOOL_RESULT }]
|
|
12062
|
+
};
|
|
12063
|
+
return [...kept, clone, placeholder];
|
|
11978
12064
|
}
|
|
11979
12065
|
class ForkRequestLayoutUnavailableError extends Error {
|
|
11980
12066
|
constructor() {
|
|
@@ -12138,9 +12224,8 @@ function renderAgentToolSpec(entry) {
|
|
|
12138
12224
|
const tools = entry.tools ?? [];
|
|
12139
12225
|
const disallowed = entry.disallowedTools ?? [];
|
|
12140
12226
|
if (tools.length > 0 && disallowed.length > 0) {
|
|
12141
|
-
const
|
|
12142
|
-
|
|
12143
|
-
return effective.length === 0 ? "None" : effective.join(", ");
|
|
12227
|
+
const kept = tools.filter((name) => !disallowed.includes(name));
|
|
12228
|
+
return kept.length === 0 ? "None" : kept.join(", ");
|
|
12144
12229
|
}
|
|
12145
12230
|
if (tools.length > 0)
|
|
12146
12231
|
return tools.join(", ");
|
|
@@ -12149,26 +12234,25 @@ function renderAgentToolSpec(entry) {
|
|
|
12149
12234
|
return "All tools";
|
|
12150
12235
|
}
|
|
12151
12236
|
function renderAgentListingLine(entry, leanModel = false) {
|
|
12152
|
-
const
|
|
12153
|
-
|
|
12237
|
+
const lean = entry.whenToUseLean;
|
|
12238
|
+
const description = leanModel && typeof lean === "string" && lean.length > 0 ? lean : entry.whenToUse;
|
|
12239
|
+
return neutralizeReminderTags(`- ${entry.agentType}: ${description} (Tools: ${renderAgentToolSpec(entry)})`);
|
|
12154
12240
|
}
|
|
12155
12241
|
function computeAgentListingDelta(available, history, opts = {}) {
|
|
12156
12242
|
const announced = announcedAgentTypes(history);
|
|
12157
|
-
const availableTypes = new Set(available.map((
|
|
12158
|
-
const added = available.filter((
|
|
12159
|
-
const removed = [];
|
|
12160
|
-
for (const type of announced)
|
|
12161
|
-
if (!availableTypes.has(type))
|
|
12162
|
-
removed.push(type);
|
|
12243
|
+
const availableTypes = new Set(available.map((entry) => entry.agentType));
|
|
12244
|
+
const added = available.filter((entry) => !announced.has(entry.agentType));
|
|
12245
|
+
const removed = [...announced].filter((type) => !availableTypes.has(type));
|
|
12163
12246
|
if (added.length === 0 && removed.length === 0)
|
|
12164
12247
|
return;
|
|
12165
|
-
added.sort((a, b) => a.agentType.localeCompare(b.agentType));
|
|
12166
|
-
removed.sort();
|
|
12248
|
+
const sortedAdded = [...added].sort((a, b) => a.agentType.localeCompare(b.agentType));
|
|
12249
|
+
const sortedRemoved = [...removed].sort();
|
|
12250
|
+
const lean = opts.leanModel === true;
|
|
12167
12251
|
return {
|
|
12168
12252
|
type: "agent_listing_delta",
|
|
12169
|
-
addedTypes:
|
|
12170
|
-
addedLines:
|
|
12171
|
-
removedTypes:
|
|
12253
|
+
addedTypes: sortedAdded.map((entry) => entry.agentType),
|
|
12254
|
+
addedLines: sortedAdded.map((entry) => renderAgentListingLine(entry, lean)),
|
|
12255
|
+
removedTypes: sortedRemoved,
|
|
12172
12256
|
isInitial: announced.size === 0,
|
|
12173
12257
|
showConcurrencyNote: opts.showConcurrencyNote ?? true
|
|
12174
12258
|
};
|
|
@@ -12433,53 +12517,55 @@ function isUserRole(message) {
|
|
|
12433
12517
|
function isReorderStop(message) {
|
|
12434
12518
|
if (message.role === "assistant")
|
|
12435
12519
|
return true;
|
|
12436
|
-
|
|
12520
|
+
if (typeof message.content === "string")
|
|
12521
|
+
return false;
|
|
12522
|
+
const first = message.content[0];
|
|
12523
|
+
return first !== undefined && first.type === "tool_result";
|
|
12437
12524
|
}
|
|
12438
12525
|
function reorderAttachments(messages, stays = () => false) {
|
|
12439
12526
|
if (!messages.some((m) => m.meta !== undefined))
|
|
12440
12527
|
return [...messages];
|
|
12441
|
-
const
|
|
12442
|
-
const
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12528
|
+
const top = [];
|
|
12529
|
+
const groups = [];
|
|
12530
|
+
let landing = top;
|
|
12531
|
+
for (const message of messages) {
|
|
12532
|
+
const moving = message.meta !== undefined && !stays(message);
|
|
12533
|
+
if (moving) {
|
|
12534
|
+
landing.push(message);
|
|
12447
12535
|
continue;
|
|
12448
12536
|
}
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
pending.length = 0;
|
|
12454
|
-
} else {
|
|
12455
|
-
reversed.push(m);
|
|
12456
|
-
}
|
|
12537
|
+
const group = { fixed: message, moved: [] };
|
|
12538
|
+
groups.push(group);
|
|
12539
|
+
if (isReorderStop(message))
|
|
12540
|
+
landing = group.moved;
|
|
12457
12541
|
}
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12542
|
+
const out = [...top];
|
|
12543
|
+
for (const group of groups)
|
|
12544
|
+
out.push(group.fixed, ...group.moved);
|
|
12545
|
+
return out;
|
|
12461
12546
|
}
|
|
12462
12547
|
function hoistToolResults(blocks) {
|
|
12463
|
-
const results =
|
|
12464
|
-
const rest =
|
|
12465
|
-
for (const b of blocks)
|
|
12466
|
-
(b.type === "tool_result" ? results : rest).push(b);
|
|
12548
|
+
const results = blocks.filter((b) => b.type === "tool_result");
|
|
12549
|
+
const rest = blocks.filter((b) => b.type !== "tool_result");
|
|
12467
12550
|
return [...results, ...rest];
|
|
12468
12551
|
}
|
|
12469
12552
|
function joinUserBlocks(prev, next) {
|
|
12470
12553
|
const last = prev[prev.length - 1];
|
|
12471
12554
|
const first = next[0];
|
|
12472
|
-
if (last
|
|
12555
|
+
if (last !== undefined && last.type === "text" && first !== undefined && first.type === "text") {
|
|
12473
12556
|
return [...prev.slice(0, -1), { ...last, text: `${last.text}
|
|
12474
12557
|
` }, ...next];
|
|
12558
|
+
}
|
|
12475
12559
|
return [...prev, ...next];
|
|
12476
12560
|
}
|
|
12561
|
+
var SMOOSH_EXEMPT_TAG = `<system-reminder>
|
|
12562
|
+
`;
|
|
12563
|
+
var SMOOSH_EXEMPT_BODIES = ["<system>authentic event nonces for this delivery: ", "<event "];
|
|
12477
12564
|
function isSmooshExempt(text) {
|
|
12478
|
-
if (!text.startsWith(
|
|
12479
|
-
`))
|
|
12565
|
+
if (!text.startsWith(SMOOSH_EXEMPT_TAG))
|
|
12480
12566
|
return false;
|
|
12481
|
-
const
|
|
12482
|
-
return
|
|
12567
|
+
const rest = text.slice(SMOOSH_EXEMPT_TAG.length);
|
|
12568
|
+
return SMOOSH_EXEMPT_BODIES.some((prefix) => rest.startsWith(prefix));
|
|
12483
12569
|
}
|
|
12484
12570
|
function foldTextIntoToolResult(result, texts) {
|
|
12485
12571
|
if (texts.length === 0)
|
|
@@ -12487,44 +12573,44 @@ function foldTextIntoToolResult(result, texts) {
|
|
|
12487
12573
|
const content = result.content;
|
|
12488
12574
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_reference"))
|
|
12489
12575
|
return null;
|
|
12490
|
-
if ((result.loadedTools
|
|
12576
|
+
if (Array.isArray(result.loadedTools) && result.loadedTools.length > 0)
|
|
12491
12577
|
return null;
|
|
12492
12578
|
if (typeof content === "string") {
|
|
12493
|
-
const
|
|
12579
|
+
const pieces = [content.trim(), ...texts.map((t) => t.text.trim())].filter((piece) => piece.length > 0);
|
|
12580
|
+
return { ...result, content: pieces.join(`
|
|
12494
12581
|
|
|
12495
|
-
`);
|
|
12496
|
-
return { ...result, content: joined };
|
|
12582
|
+
`) };
|
|
12497
12583
|
}
|
|
12498
|
-
const
|
|
12584
|
+
const folded = [];
|
|
12499
12585
|
for (const block of [...content, ...texts]) {
|
|
12500
12586
|
if (block.type !== "text") {
|
|
12501
|
-
|
|
12587
|
+
folded.push(block);
|
|
12502
12588
|
continue;
|
|
12503
12589
|
}
|
|
12504
12590
|
const trimmed = block.text.trim();
|
|
12505
12591
|
if (trimmed.length === 0)
|
|
12506
12592
|
continue;
|
|
12507
|
-
const tail =
|
|
12508
|
-
if (tail
|
|
12509
|
-
|
|
12593
|
+
const tail = folded[folded.length - 1];
|
|
12594
|
+
if (tail !== undefined && tail.type === "text")
|
|
12595
|
+
folded[folded.length - 1] = { type: "text", text: `${tail.text}
|
|
12510
12596
|
|
|
12511
12597
|
${trimmed}` };
|
|
12512
12598
|
else
|
|
12513
|
-
|
|
12599
|
+
folded.push({ type: "text", text: trimmed });
|
|
12514
12600
|
}
|
|
12515
|
-
return { ...result, content:
|
|
12601
|
+
return { ...result, content: folded };
|
|
12516
12602
|
}
|
|
12517
12603
|
function joinAttachmentBlocks(prev, next) {
|
|
12518
12604
|
const last = prev[prev.length - 1];
|
|
12519
|
-
if (last
|
|
12605
|
+
if (last === undefined || last.type !== "tool_result")
|
|
12520
12606
|
return [...prev, ...next];
|
|
12521
|
-
if ((last.loadedTools
|
|
12607
|
+
if (Array.isArray(last.loadedTools) && last.loadedTools.length > 0)
|
|
12522
12608
|
return [...prev, ...next];
|
|
12523
12609
|
if (next.some((b) => b.type === "text" && isSmooshExempt(b.text)))
|
|
12524
12610
|
return [...prev, ...next];
|
|
12525
12611
|
if (typeof last.content === "string" && next.every((b) => b.type === "text")) {
|
|
12526
|
-
const folded = foldTextIntoToolResult(last, next)
|
|
12527
|
-
return [...prev.slice(0, -1), folded];
|
|
12612
|
+
const folded = foldTextIntoToolResult(last, next);
|
|
12613
|
+
return [...prev.slice(0, -1), folded ?? last];
|
|
12528
12614
|
}
|
|
12529
12615
|
return [...prev, ...next];
|
|
12530
12616
|
}
|
|
@@ -12672,7 +12758,6 @@ import { execFile as execFile2 } from "node:child_process";
|
|
|
12672
12758
|
var GIT_STATUS_CAVEAT = "This git status was captured when the session began; it is a snapshot and does not change as the conversation goes on.";
|
|
12673
12759
|
var BASH_TOOL_NAME = "Bash";
|
|
12674
12760
|
var GIT_STATUS_MAX_CHARS = 2000;
|
|
12675
|
-
var MAIN_BRANCH_CANDIDATES = ["main", "master"];
|
|
12676
12761
|
var GIT_TIMEOUT_MS = 1e4;
|
|
12677
12762
|
function runGit2(cwd, args, env) {
|
|
12678
12763
|
return new Promise((resolve) => {
|
|
@@ -12684,17 +12769,25 @@ function runGit2(cwd, args, env) {
|
|
|
12684
12769
|
}
|
|
12685
12770
|
async function currentBranch(cwd, env) {
|
|
12686
12771
|
const { code, stdout } = await runGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], env);
|
|
12687
|
-
|
|
12772
|
+
const name = stdout.trim();
|
|
12773
|
+
return code === 0 && name.length > 0 ? name : "HEAD";
|
|
12688
12774
|
}
|
|
12775
|
+
async function originRefExists(cwd, name, env) {
|
|
12776
|
+
const { code } = await runGit2(cwd, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${name}`], env);
|
|
12777
|
+
return code === 0;
|
|
12778
|
+
}
|
|
12779
|
+
var FALLBACK_MAIN_BRANCHES = ["main", "master"];
|
|
12689
12780
|
async function mainBranch(cwd, env) {
|
|
12690
|
-
const head = await runGit2(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], env);
|
|
12781
|
+
const head = await runGit2(cwd, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], env);
|
|
12691
12782
|
if (head.code === 0) {
|
|
12692
|
-
|
|
12693
|
-
if (name.
|
|
12783
|
+
let name = head.stdout.trim();
|
|
12784
|
+
if (name.startsWith("origin/"))
|
|
12785
|
+
name = name.slice("origin/".length);
|
|
12786
|
+
if (name.length > 0 && await originRefExists(cwd, name, env))
|
|
12694
12787
|
return name;
|
|
12695
12788
|
}
|
|
12696
|
-
for (const candidate of
|
|
12697
|
-
if (
|
|
12789
|
+
for (const candidate of FALLBACK_MAIN_BRANCHES) {
|
|
12790
|
+
if (await originRefExists(cwd, candidate, env))
|
|
12698
12791
|
return candidate;
|
|
12699
12792
|
}
|
|
12700
12793
|
return "main";
|
|
@@ -12769,11 +12862,17 @@ function isUserInvocable(overrides, skill) {
|
|
|
12769
12862
|
var DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS = 200000;
|
|
12770
12863
|
function skillListingBudgetChars(opts) {
|
|
12771
12864
|
if (opts.budgetChars !== undefined)
|
|
12772
|
-
return
|
|
12773
|
-
const window =
|
|
12774
|
-
const fraction =
|
|
12865
|
+
return isPositiveFinite(opts.budgetChars) ? opts.budgetChars : 0;
|
|
12866
|
+
const window = isPositiveFinite(opts.contextWindowTokens) ? opts.contextWindowTokens : DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS;
|
|
12867
|
+
const fraction = isPositiveFinite(opts.budgetFraction) ? opts.budgetFraction : DEFAULT_SKILL_LISTING_BUDGET_FRACTION;
|
|
12775
12868
|
return Math.max(1, Math.floor(window * SKILL_LISTING_CHARS_PER_TOKEN * fraction));
|
|
12776
12869
|
}
|
|
12870
|
+
function isPositiveFinite(value) {
|
|
12871
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
12872
|
+
}
|
|
12873
|
+
function lineLength(name, description) {
|
|
12874
|
+
return description.length > 0 ? 4 + name.length + description.length : 2 + name.length;
|
|
12875
|
+
}
|
|
12777
12876
|
function truncateSkillDescription(description, maxDescChars) {
|
|
12778
12877
|
if (!Number.isFinite(maxDescChars) || maxDescChars <= 0 || description.length <= maxDescChars)
|
|
12779
12878
|
return description;
|
|
@@ -12787,42 +12886,53 @@ function renderSkillListingContent(entries) {
|
|
|
12787
12886
|
`);
|
|
12788
12887
|
}
|
|
12789
12888
|
function buildSkillListing(skills, opts) {
|
|
12790
|
-
const
|
|
12889
|
+
const cap = opts?.maxDescChars ?? DEFAULT_SKILL_LISTING_MAX_DESC_CHARS;
|
|
12791
12890
|
const budget = skillListingBudgetChars({
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12891
|
+
contextWindowTokens: opts?.contextWindowTokens,
|
|
12892
|
+
budgetFraction: opts?.budgetFraction,
|
|
12893
|
+
budgetChars: opts?.budgetChars
|
|
12795
12894
|
});
|
|
12796
12895
|
const entries = [];
|
|
12797
|
-
const
|
|
12896
|
+
const fixed = [];
|
|
12798
12897
|
for (const skill of skills) {
|
|
12799
12898
|
if (!isModelVisible(opts?.skillOverrides, skill))
|
|
12800
12899
|
continue;
|
|
12801
12900
|
const nameOnly = overrideFor(opts?.skillOverrides, skill) === "name-only";
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12901
|
+
const description = nameOnly ? "" : truncateSkillDescription(skill.description, cap);
|
|
12902
|
+
entries.push({ name: skill.name, description, source: skill.source });
|
|
12903
|
+
fixed.push(nameOnly || skill.source === "builtin");
|
|
12805
12904
|
}
|
|
12806
|
-
if (budget
|
|
12905
|
+
if (!(budget > 0) || entries.length === 0)
|
|
12807
12906
|
return entries;
|
|
12808
|
-
const
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12907
|
+
const separators = entries.length - 1;
|
|
12908
|
+
let fullSize = separators;
|
|
12909
|
+
for (const e of entries)
|
|
12910
|
+
fullSize += lineLength(e.name, e.description);
|
|
12911
|
+
if (fullSize <= budget)
|
|
12812
12912
|
return entries;
|
|
12813
|
-
|
|
12814
|
-
if (candidates.length === 0)
|
|
12913
|
+
if (fixed.every(Boolean))
|
|
12815
12914
|
return entries;
|
|
12816
|
-
let remaining = budget -
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12915
|
+
let remaining = budget - separators;
|
|
12916
|
+
for (let i = 0;i < entries.length; i++) {
|
|
12917
|
+
const e = entries[i];
|
|
12918
|
+
remaining -= fixed[i] ? lineLength(e.name, e.description) : lineLength(e.name, "");
|
|
12919
|
+
}
|
|
12920
|
+
const out = [];
|
|
12921
|
+
for (let i = 0;i < entries.length; i++) {
|
|
12922
|
+
const e = entries[i];
|
|
12923
|
+
if (fixed[i]) {
|
|
12924
|
+
out.push(e);
|
|
12925
|
+
continue;
|
|
12926
|
+
}
|
|
12927
|
+
const cost = lineLength(e.name, e.description) - lineLength(e.name, "");
|
|
12820
12928
|
if (cost <= remaining) {
|
|
12821
|
-
keep.add(i);
|
|
12822
12929
|
remaining -= cost;
|
|
12930
|
+
out.push(e);
|
|
12931
|
+
} else {
|
|
12932
|
+
out.push({ name: e.name, description: "", source: e.source });
|
|
12823
12933
|
}
|
|
12824
12934
|
}
|
|
12825
|
-
return
|
|
12935
|
+
return out;
|
|
12826
12936
|
}
|
|
12827
12937
|
|
|
12828
12938
|
// src/subagents/plugin-agents.ts
|
|
@@ -12887,7 +12997,7 @@ var HOOK_SOURCE_BY_SETTING_SOURCE = {
|
|
|
12887
12997
|
project: "project",
|
|
12888
12998
|
local: "local"
|
|
12889
12999
|
};
|
|
12890
|
-
function
|
|
13000
|
+
function isPlainObject3(v) {
|
|
12891
13001
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12892
13002
|
}
|
|
12893
13003
|
function buildHookEntriesFromSettings(perSource) {
|
|
@@ -12901,7 +13011,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12901
13011
|
if (hooks === undefined)
|
|
12902
13012
|
continue;
|
|
12903
13013
|
const where = { source: tier.source, ...tier.path !== undefined ? { path: tier.path } : {} };
|
|
12904
|
-
if (!
|
|
13014
|
+
if (!isPlainObject3(hooks)) {
|
|
12905
13015
|
rejected.push({ ...where, reason: `expected an object for "hooks", got ${Array.isArray(hooks) ? "an array" : typeof hooks}` });
|
|
12906
13016
|
continue;
|
|
12907
13017
|
}
|
|
@@ -12914,7 +13024,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12914
13024
|
continue;
|
|
12915
13025
|
}
|
|
12916
13026
|
groups.forEach((group, groupIndex) => {
|
|
12917
|
-
if (!
|
|
13027
|
+
if (!isPlainObject3(group)) {
|
|
12918
13028
|
rejected.push({ ...where, event, reason: `matcher group ${groupIndex} is not an object` });
|
|
12919
13029
|
return;
|
|
12920
13030
|
}
|
|
@@ -12925,7 +13035,7 @@ function buildHookEntriesFromSettings(perSource) {
|
|
|
12925
13035
|
}
|
|
12926
13036
|
const matcher = typeof group["matcher"] === "string" ? group["matcher"] : undefined;
|
|
12927
13037
|
groupHooks.forEach((handler, hookIndex) => {
|
|
12928
|
-
if (!
|
|
13038
|
+
if (!isPlainObject3(handler)) {
|
|
12929
13039
|
rejected.push({ ...where, event, reason: `hook ${groupIndex}:${hookIndex} is not an object` });
|
|
12930
13040
|
return;
|
|
12931
13041
|
}
|
|
@@ -15893,7 +16003,7 @@ replaceExecutor(ASK_USER_QUESTION_TOOL_NAME2, askUserQuestionExecutor);
|
|
|
15893
16003
|
// src/tools/impl/bash.ts
|
|
15894
16004
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
15895
16005
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync10, unlinkSync, createWriteStream } from "node:fs";
|
|
15896
|
-
import { join as join17, sep
|
|
16006
|
+
import { join as join17, sep } from "node:path";
|
|
15897
16007
|
|
|
15898
16008
|
// src/tools/sandbox-run-inputs.ts
|
|
15899
16009
|
function computeWritableRoots(ctx) {
|
|
@@ -15985,7 +16095,7 @@ function buildRunCommandOptions(input, ctx) {
|
|
|
15985
16095
|
function isWithinAllowedDirs(candidate, cwd, writableRoots) {
|
|
15986
16096
|
const canonicalCandidate = canonicalizePath(candidate);
|
|
15987
16097
|
const allowed = [canonicalizePath(cwd), ...writableRoots.map(canonicalizePath)];
|
|
15988
|
-
return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root +
|
|
16098
|
+
return allowed.some((root) => canonicalCandidate === root || canonicalCandidate.startsWith(root + sep));
|
|
15989
16099
|
}
|
|
15990
16100
|
function computeCwdCarryAllowedRoots(ctx) {
|
|
15991
16101
|
return [ctx.session.getSessionRoot(), ...ctx.session.getBoundedRoots()];
|
|
@@ -16485,11 +16595,11 @@ function writeDurableJobsAtomic(projectDir, jobs, brand) {
|
|
|
16485
16595
|
}
|
|
16486
16596
|
renameSync2(tmpPath, path);
|
|
16487
16597
|
}
|
|
16488
|
-
function
|
|
16598
|
+
function isPlainObject4(v) {
|
|
16489
16599
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16490
16600
|
}
|
|
16491
16601
|
function parseCreateInput(raw) {
|
|
16492
|
-
if (!
|
|
16602
|
+
if (!isPlainObject4(raw))
|
|
16493
16603
|
throw new Error("input must be an object");
|
|
16494
16604
|
const cron = raw["cron"];
|
|
16495
16605
|
if (typeof cron !== "string" || cron.length === 0)
|
|
@@ -16507,7 +16617,7 @@ function parseCreateInput(raw) {
|
|
|
16507
16617
|
return { cron, prompt, recurring: recurring ?? true, durable: durable ?? false };
|
|
16508
16618
|
}
|
|
16509
16619
|
function parseIdInput(raw) {
|
|
16510
|
-
if (!
|
|
16620
|
+
if (!isPlainObject4(raw))
|
|
16511
16621
|
throw new Error("input must be an object");
|
|
16512
16622
|
const id = raw["id"];
|
|
16513
16623
|
if (typeof id !== "string" || id.length === 0)
|
|
@@ -17026,13 +17136,13 @@ var exitPlanModeExecutor = {
|
|
|
17026
17136
|
replaceExecutor(EXIT_PLAN_MODE_TOOL_NAME, exitPlanModeExecutor);
|
|
17027
17137
|
|
|
17028
17138
|
// src/tools/impl/exit-worktree.ts
|
|
17029
|
-
import { sep as
|
|
17139
|
+
import { sep as sep2 } from "node:path";
|
|
17030
17140
|
var EXIT_WORKTREE_TOOL_NAME = "ExitWorktree";
|
|
17031
17141
|
function hasUncommittedChanges(statusPorcelainOutput) {
|
|
17032
17142
|
return statusPorcelainOutput.trim().length > 0;
|
|
17033
17143
|
}
|
|
17034
17144
|
function findWorktreeContainingCwd(worktrees, cwdRealPath) {
|
|
17035
|
-
const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath +
|
|
17145
|
+
const candidates = worktrees.filter((w) => cwdRealPath === w.realPath || cwdRealPath.startsWith(w.realPath + sep2));
|
|
17036
17146
|
if (candidates.length === 0)
|
|
17037
17147
|
return;
|
|
17038
17148
|
return candidates.reduce((longest, w) => w.realPath.length > longest.realPath.length ? w : longest);
|
|
@@ -17197,7 +17307,7 @@ replaceExecutor("Glob", globExecutor, extractGlobPaths);
|
|
|
17197
17307
|
|
|
17198
17308
|
// src/tools/impl/grep.ts
|
|
17199
17309
|
import { readFileSync as readFileSync13, statSync as statSync7 } from "node:fs";
|
|
17200
|
-
import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as
|
|
17310
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve9, sep as sep3 } from "node:path";
|
|
17201
17311
|
var OUTPUT_MODES = ["content", "files_with_matches", "count"];
|
|
17202
17312
|
var MAX_PATTERN_LENGTH = 1000;
|
|
17203
17313
|
function parseInput4(raw) {
|
|
@@ -17302,7 +17412,7 @@ var TYPE_EXTENSIONS = {
|
|
|
17302
17412
|
};
|
|
17303
17413
|
function extOf(path) {
|
|
17304
17414
|
const i = path.lastIndexOf(".");
|
|
17305
|
-
const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(
|
|
17415
|
+
const slashIdx = Math.max(path.lastIndexOf("/"), path.lastIndexOf(sep3));
|
|
17306
17416
|
if (i <= slashIdx)
|
|
17307
17417
|
return "";
|
|
17308
17418
|
return path.slice(i).toLowerCase();
|
|
@@ -17346,7 +17456,7 @@ async function loadGitignoreRules(searchRoot) {
|
|
|
17346
17456
|
const found = [];
|
|
17347
17457
|
try {
|
|
17348
17458
|
for await (const rel of discovery.scan({ cwd: searchRoot, dot: true, onlyFiles: true, followSymlinks: false })) {
|
|
17349
|
-
const relPosix = rel.split(
|
|
17459
|
+
const relPosix = rel.split(sep3).join("/");
|
|
17350
17460
|
const dir = dirname8(relPosix);
|
|
17351
17461
|
const relDir = dir === "." ? "" : dir;
|
|
17352
17462
|
try {
|
|
@@ -17384,7 +17494,7 @@ async function discoverCandidateFiles(searchRoot, globPattern) {
|
|
|
17384
17494
|
const out = [];
|
|
17385
17495
|
for await (const p of glob.scan({ cwd: searchRoot, onlyFiles: true, dot: false, followSymlinks: false })) {
|
|
17386
17496
|
const abs = isAbsolute4(p) ? resolve9(p) : resolve9(searchRoot, p);
|
|
17387
|
-
const relPosix = relative2(searchRoot, abs).split(
|
|
17497
|
+
const relPosix = relative2(searchRoot, abs).split(sep3).join("/");
|
|
17388
17498
|
if (isIgnored(relPosix, rules))
|
|
17389
17499
|
continue;
|
|
17390
17500
|
out.push(abs);
|
|
@@ -18119,11 +18229,11 @@ replaceExecutor("NotebookEdit", notebookEditExecutor);
|
|
|
18119
18229
|
var MAX_MESSAGE_LENGTH = 199;
|
|
18120
18230
|
var DISABLED_REASON = "push transport unconfigured";
|
|
18121
18231
|
var activeNotifier = () => {};
|
|
18122
|
-
function
|
|
18232
|
+
function isPlainObject5(v) {
|
|
18123
18233
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18124
18234
|
}
|
|
18125
18235
|
function parseInput6(raw) {
|
|
18126
|
-
if (!
|
|
18236
|
+
if (!isPlainObject5(raw))
|
|
18127
18237
|
throw new Error("input must be an object");
|
|
18128
18238
|
const message = raw["message"];
|
|
18129
18239
|
if (typeof message !== "string" || message.length === 0)
|
|
@@ -18432,11 +18542,11 @@ var MAX_FINDINGS = 32;
|
|
|
18432
18542
|
var LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
18433
18543
|
var VERDICTS = ["CONFIRMED", "PLAUSIBLE"];
|
|
18434
18544
|
var OUTCOMES = ["fixed", "skipped", "no_change_needed"];
|
|
18435
|
-
function
|
|
18545
|
+
function isPlainObject6(v) {
|
|
18436
18546
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18437
18547
|
}
|
|
18438
18548
|
function parseFinding(raw, index) {
|
|
18439
|
-
if (!
|
|
18549
|
+
if (!isPlainObject6(raw))
|
|
18440
18550
|
throw new Error(`findings[${index}] must be an object`);
|
|
18441
18551
|
const file = raw["file"];
|
|
18442
18552
|
if (typeof file !== "string" || file.length === 0)
|
|
@@ -18477,7 +18587,7 @@ function parseFinding(raw, index) {
|
|
|
18477
18587
|
};
|
|
18478
18588
|
}
|
|
18479
18589
|
function parseInput8(raw) {
|
|
18480
|
-
if (!
|
|
18590
|
+
if (!isPlainObject6(raw))
|
|
18481
18591
|
throw new Error("input must be an object");
|
|
18482
18592
|
const level = raw["level"];
|
|
18483
18593
|
if (level !== undefined && (typeof level !== "string" || !LEVELS.includes(level))) {
|
|
@@ -18523,11 +18633,11 @@ function sessionList(sessionId) {
|
|
|
18523
18633
|
}
|
|
18524
18634
|
return list;
|
|
18525
18635
|
}
|
|
18526
|
-
function
|
|
18636
|
+
function isPlainObject7(v) {
|
|
18527
18637
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18528
18638
|
}
|
|
18529
18639
|
function parseInput9(raw) {
|
|
18530
|
-
if (!
|
|
18640
|
+
if (!isPlainObject7(raw))
|
|
18531
18641
|
throw new Error("input must be an object");
|
|
18532
18642
|
const stop = raw["stop"];
|
|
18533
18643
|
if (stop !== undefined && typeof stop !== "boolean")
|
|
@@ -18672,7 +18782,7 @@ function updateTask2(sessionId, input) {
|
|
|
18672
18782
|
}
|
|
18673
18783
|
|
|
18674
18784
|
// src/tools/impl/task-graph.ts
|
|
18675
|
-
function
|
|
18785
|
+
function isPlainObject8(v) {
|
|
18676
18786
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18677
18787
|
}
|
|
18678
18788
|
function isNonEmptyString(v) {
|
|
@@ -18686,7 +18796,7 @@ function errorResult5(message) {
|
|
|
18686
18796
|
return { output: `Error: ${message}`, isError: true };
|
|
18687
18797
|
}
|
|
18688
18798
|
function parseCreateInput2(raw) {
|
|
18689
|
-
if (!
|
|
18799
|
+
if (!isPlainObject8(raw))
|
|
18690
18800
|
throw new Error("input must be an object");
|
|
18691
18801
|
const subject = raw["subject"];
|
|
18692
18802
|
const description = raw["description"];
|
|
@@ -18698,7 +18808,7 @@ function parseCreateInput2(raw) {
|
|
|
18698
18808
|
if (activeForm !== undefined && typeof activeForm !== "string")
|
|
18699
18809
|
throw new Error("activeForm must be a string");
|
|
18700
18810
|
const metadata = raw["metadata"];
|
|
18701
|
-
if (metadata !== undefined && !
|
|
18811
|
+
if (metadata !== undefined && !isPlainObject8(metadata))
|
|
18702
18812
|
throw new Error("metadata must be an object");
|
|
18703
18813
|
return {
|
|
18704
18814
|
subject,
|
|
@@ -18718,7 +18828,7 @@ async function executeCreate2(rawInput, ctx) {
|
|
|
18718
18828
|
return { output: JSON.stringify({ task: { id: row.id, subject: row.subject } }) };
|
|
18719
18829
|
}
|
|
18720
18830
|
function parseTaskIdInput(raw) {
|
|
18721
|
-
if (!
|
|
18831
|
+
if (!isPlainObject8(raw))
|
|
18722
18832
|
throw new Error("input must be an object");
|
|
18723
18833
|
const taskId = raw["taskId"];
|
|
18724
18834
|
if (!isNonEmptyString(taskId))
|
|
@@ -18753,7 +18863,7 @@ async function executeList2(_rawInput, ctx) {
|
|
|
18753
18863
|
return { output: JSON.stringify({ tasks: rows }) };
|
|
18754
18864
|
}
|
|
18755
18865
|
function parseUpdateInput(raw) {
|
|
18756
|
-
if (!
|
|
18866
|
+
if (!isPlainObject8(raw))
|
|
18757
18867
|
throw new Error("input must be an object");
|
|
18758
18868
|
const taskId = raw["taskId"];
|
|
18759
18869
|
if (!isNonEmptyString(taskId))
|
|
@@ -18781,7 +18891,7 @@ function parseUpdateInput(raw) {
|
|
|
18781
18891
|
if (addBlockedBy !== undefined && !isStringArray(addBlockedBy))
|
|
18782
18892
|
throw new Error("addBlockedBy must be an array of non-empty strings");
|
|
18783
18893
|
const metadata = raw["metadata"];
|
|
18784
|
-
if (metadata !== undefined && !
|
|
18894
|
+
if (metadata !== undefined && !isPlainObject8(metadata))
|
|
18785
18895
|
throw new Error("metadata must be an object");
|
|
18786
18896
|
return {
|
|
18787
18897
|
taskId,
|
|
@@ -18856,7 +18966,7 @@ replaceExecutor("TaskUpdate", { execute: executeUpdate });
|
|
|
18856
18966
|
|
|
18857
18967
|
// src/tools/impl/task-output.ts
|
|
18858
18968
|
import { existsSync as existsSync7, readFileSync as readFileSync16 } from "node:fs";
|
|
18859
|
-
import { join as join20, sep as
|
|
18969
|
+
import { join as join20, sep as sep4 } from "node:path";
|
|
18860
18970
|
function parseTaskOutputInput(input) {
|
|
18861
18971
|
if (typeof input !== "object" || input === null)
|
|
18862
18972
|
return { error: "TaskOutput: input must be an object" };
|
|
@@ -18886,7 +18996,7 @@ function resolveOutputPath(taskId, ctx) {
|
|
|
18886
18996
|
return;
|
|
18887
18997
|
const realFallback = resolveRealTarget(fallback);
|
|
18888
18998
|
const realTasksDir = resolveRealTarget(tasksDir);
|
|
18889
|
-
return realFallback.startsWith(realTasksDir +
|
|
18999
|
+
return realFallback.startsWith(realTasksDir + sep4) ? fallback : undefined;
|
|
18890
19000
|
}
|
|
18891
19001
|
async function waitForTerminal(taskId, timeoutMs) {
|
|
18892
19002
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -19014,12 +19124,12 @@ replaceExecutor("TaskStop", taskStopExecutor);
|
|
|
19014
19124
|
|
|
19015
19125
|
// src/tools/impl/todo-write.ts
|
|
19016
19126
|
var sessions3 = new Map;
|
|
19017
|
-
function
|
|
19127
|
+
function isPlainObject9(v) {
|
|
19018
19128
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19019
19129
|
}
|
|
19020
19130
|
var TODO_STATUSES = ["pending", "in_progress", "completed"];
|
|
19021
19131
|
function parseTodo(raw, index) {
|
|
19022
|
-
if (!
|
|
19132
|
+
if (!isPlainObject9(raw))
|
|
19023
19133
|
throw new Error(`todos[${index}] must be an object`);
|
|
19024
19134
|
const content = raw["content"];
|
|
19025
19135
|
if (typeof content !== "string" || content.length === 0)
|
|
@@ -19034,7 +19144,7 @@ function parseTodo(raw, index) {
|
|
|
19034
19144
|
return { content, status, activeForm };
|
|
19035
19145
|
}
|
|
19036
19146
|
function parseInput10(raw) {
|
|
19037
|
-
if (!
|
|
19147
|
+
if (!isPlainObject9(raw))
|
|
19038
19148
|
throw new Error("input must be an object");
|
|
19039
19149
|
const todos = raw["todos"];
|
|
19040
19150
|
if (!Array.isArray(todos))
|
|
@@ -22145,17 +22255,24 @@ function flushWebSearchStream(events) {
|
|
|
22145
22255
|
let buffer = "";
|
|
22146
22256
|
const flush = () => {
|
|
22147
22257
|
const text = buffer.trim();
|
|
22148
|
-
if (text
|
|
22258
|
+
if (text !== "")
|
|
22149
22259
|
items.push(text);
|
|
22150
22260
|
buffer = "";
|
|
22151
22261
|
};
|
|
22152
22262
|
for (const event of events) {
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
|
|
22263
|
+
switch (event.type) {
|
|
22264
|
+
case "text":
|
|
22265
|
+
buffer += event.text;
|
|
22266
|
+
break;
|
|
22267
|
+
case "search_result":
|
|
22268
|
+
flush();
|
|
22269
|
+
items.push({ content: event.hits.map((hit) => ({ title: hit.title, url: hit.url })) });
|
|
22270
|
+
break;
|
|
22271
|
+
default:
|
|
22272
|
+
flush();
|
|
22273
|
+
items.push(`Web search error: ${String(event.code)}`);
|
|
22274
|
+
break;
|
|
22156
22275
|
}
|
|
22157
|
-
flush();
|
|
22158
|
-
items.push(event.type === "search_result" ? { content: event.hits.map((h) => ({ title: h.title, url: h.url })) } : `Web search error: ${event.code}`);
|
|
22159
22276
|
}
|
|
22160
22277
|
flush();
|
|
22161
22278
|
return items;
|
|
@@ -22168,14 +22285,20 @@ function headerQuery(query) {
|
|
|
22168
22285
|
return `${query.slice(0, HEADER_QUERY_CAP)}[query truncated at ${HEADER_QUERY_CAP.toLocaleString("en-US")} characters]`;
|
|
22169
22286
|
}
|
|
22170
22287
|
function renderWebSearchToolResult(query, items) {
|
|
22171
|
-
|
|
22172
|
-
const body = items.map((item) => typeof item === "string" ? item : item.content.length > 0 ? `Links: ${JSON.stringify(item.content)}` : "No links found.").reduce((acc, rendered) => acc + rendered + `
|
|
22288
|
+
let out = `Web search results for query: "${headerQuery(query)}"
|
|
22173
22289
|
|
|
22174
|
-
|
|
22175
|
-
|
|
22290
|
+
`;
|
|
22291
|
+
for (const item of items)
|
|
22292
|
+
out += `${renderItem(item)}
|
|
22176
22293
|
|
|
22177
|
-
|
|
22178
|
-
|
|
22294
|
+
`;
|
|
22295
|
+
return `${out}
|
|
22296
|
+
${REMINDER}`;
|
|
22297
|
+
}
|
|
22298
|
+
function renderItem(item) {
|
|
22299
|
+
if (typeof item === "string")
|
|
22300
|
+
return item;
|
|
22301
|
+
return item.content.length > 0 ? `Links: ${JSON.stringify(item.content)}` : "No links found.";
|
|
22179
22302
|
}
|
|
22180
22303
|
function renderWebSearchToolResultCapped(query, items, cap) {
|
|
22181
22304
|
const full = renderWebSearchToolResult(query, items);
|
|
@@ -24125,16 +24248,19 @@ function createSessionReadState(opts) {
|
|
|
24125
24248
|
}
|
|
24126
24249
|
|
|
24127
24250
|
// src/provider/lean-prompt.ts
|
|
24128
|
-
var FULL_PROMPT_OPUS_MINORS = new Set(["0", "1", "5", "6", "7"]);
|
|
24129
24251
|
function claudeModelTakesFullPrompt(modelKey) {
|
|
24130
24252
|
const id = modelKey.slice(modelKey.lastIndexOf("/") + 1).toLowerCase();
|
|
24131
|
-
if (
|
|
24253
|
+
if (FULL_FAMILY_MARKERS.some((marker) => id.includes(marker)))
|
|
24132
24254
|
return true;
|
|
24133
|
-
const
|
|
24134
|
-
if (
|
|
24255
|
+
const match = OPUS_4_ID.exec(id);
|
|
24256
|
+
if (match === null)
|
|
24135
24257
|
return false;
|
|
24136
|
-
|
|
24258
|
+
const minor = match[1] ?? "0";
|
|
24259
|
+
return FULL_OPUS_4_MINORS.has(minor);
|
|
24137
24260
|
}
|
|
24261
|
+
var FULL_FAMILY_MARKERS = ["claude-3-", "haiku", "sonnet"];
|
|
24262
|
+
var OPUS_4_ID = /^claude-opus-4(?:[-.](\d{1,2}))?(?:-\d{8})?$/;
|
|
24263
|
+
var FULL_OPUS_4_MINORS = new Set(["0", "1", "5", "6", "7"]);
|
|
24138
24264
|
|
|
24139
24265
|
// src/provider/first-party.ts
|
|
24140
24266
|
var FIRST_PARTY_ANTHROPIC_PROVIDER_IDS = new Set(["anthropic", "console"]);
|
|
@@ -25262,30 +25388,34 @@ ${escapeAttributionText(msg.body)}
|
|
|
25262
25388
|
function isTruthyEnvValue(v) {
|
|
25263
25389
|
return v === "1" || v === "true";
|
|
25264
25390
|
}
|
|
25265
|
-
function claudeTierMatches(modelKey, tier) {
|
|
25266
|
-
if (resolveSlot === undefined)
|
|
25267
|
-
return false;
|
|
25268
|
-
const resolution = resolveSlot(tier, modelKey);
|
|
25269
|
-
return resolution.ok && resolution.modelKey === modelKey;
|
|
25270
|
-
}
|
|
25271
|
-
function isAtOrBelowOpusTier(modelKey) {
|
|
25272
|
-
return claudeTierMatches(modelKey, "haiku") || claudeTierMatches(modelKey, "sonnet") || claudeTierMatches(modelKey, "opus");
|
|
25273
|
-
}
|
|
25274
25391
|
function sessionLeanModel(modelKey) {
|
|
25275
25392
|
if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
|
|
25276
25393
|
return false;
|
|
25277
25394
|
return !claudeModelTakesFullPrompt(modelKey);
|
|
25278
25395
|
}
|
|
25279
25396
|
function exploreModelCap(parentModel) {
|
|
25280
|
-
|
|
25281
|
-
if (isTruthyEnvValue(env[envName8(sessionBrand, "DISABLE_EXPLORE_INHERIT_CAP")]))
|
|
25397
|
+
if (exploreCapOptedOut())
|
|
25282
25398
|
return;
|
|
25283
25399
|
if (!isFirstPartyAnthropic(currentProviderIdentity?.providerId))
|
|
25284
25400
|
return;
|
|
25285
|
-
if (
|
|
25401
|
+
if (modelSitsOnCappedOrLowerTier(parentModel))
|
|
25286
25402
|
return;
|
|
25287
25403
|
return "opus";
|
|
25288
25404
|
}
|
|
25405
|
+
function exploreCapOptedOut() {
|
|
25406
|
+
const env = engineEnv ?? process.env;
|
|
25407
|
+
return isTruthyEnvValue(env[envName8(sessionBrand, "DISABLE_EXPLORE_INHERIT_CAP")]);
|
|
25408
|
+
}
|
|
25409
|
+
function modelSitsOnCappedOrLowerTier(modelKey) {
|
|
25410
|
+
if (resolveSlot === undefined)
|
|
25411
|
+
return false;
|
|
25412
|
+
for (const tier of ["haiku", "sonnet", "opus"]) {
|
|
25413
|
+
const resolution = resolveSlot(tier, modelKey);
|
|
25414
|
+
if (resolution.ok && resolution.modelKey === modelKey)
|
|
25415
|
+
return true;
|
|
25416
|
+
}
|
|
25417
|
+
return false;
|
|
25418
|
+
}
|
|
25289
25419
|
function resolveChildModel(req) {
|
|
25290
25420
|
if (req.fork === true)
|
|
25291
25421
|
return currentModel;
|
|
@@ -25876,6 +26006,88 @@ ${escapeAttributionText(msg.body)}
|
|
|
25876
26006
|
output.write({ type: "data", ...buildSdkInitMessage() });
|
|
25877
26007
|
};
|
|
25878
26008
|
const userFrames = new Queue;
|
|
26009
|
+
const hostInputFrames = new WeakSet;
|
|
26010
|
+
const endsFold = async (text) => {
|
|
26011
|
+
if (resolveBuiltinCommand(text) !== undefined)
|
|
26012
|
+
return true;
|
|
26013
|
+
if (!looksLikeCommand(text) || commandResolver === undefined)
|
|
26014
|
+
return false;
|
|
26015
|
+
try {
|
|
26016
|
+
return (await commandResolver.resolve(text, config.cwd)).kind !== "none";
|
|
26017
|
+
} catch {
|
|
26018
|
+
return true;
|
|
26019
|
+
}
|
|
26020
|
+
};
|
|
26021
|
+
const recordFoldedInput = async (attachment) => {
|
|
26022
|
+
if (store?.recordAttachmentEntry === undefined)
|
|
26023
|
+
return true;
|
|
26024
|
+
try {
|
|
26025
|
+
await store.recordAttachmentEntry(attachment);
|
|
26026
|
+
return true;
|
|
26027
|
+
} catch {
|
|
26028
|
+
return false;
|
|
26029
|
+
}
|
|
26030
|
+
};
|
|
26031
|
+
const foldPendingHostInput = async (signal) => {
|
|
26032
|
+
const accepted = [];
|
|
26033
|
+
for (const frame of userFrames.pending()) {
|
|
26034
|
+
if (signal.aborted)
|
|
26035
|
+
return;
|
|
26036
|
+
if (!hostInputFrames.has(frame))
|
|
26037
|
+
continue;
|
|
26038
|
+
if (await endsFold(frame.text))
|
|
26039
|
+
break;
|
|
26040
|
+
let composite;
|
|
26041
|
+
try {
|
|
26042
|
+
composite = await fireObservationalHook("UserPromptSubmit", { payload: { prompt: frame.text } });
|
|
26043
|
+
} catch {
|
|
26044
|
+
break;
|
|
26045
|
+
}
|
|
26046
|
+
if (contextStrings(composite.blockReasons).length > 0 || composite.preventContinuation !== undefined)
|
|
26047
|
+
break;
|
|
26048
|
+
accepted.push({ frame, composite });
|
|
26049
|
+
}
|
|
26050
|
+
if (accepted.length === 0 || signal.aborted)
|
|
26051
|
+
return;
|
|
26052
|
+
const stillPending = new Set(userFrames.pending());
|
|
26053
|
+
const claimed = [];
|
|
26054
|
+
for (const entry of accepted) {
|
|
26055
|
+
if (!stillPending.has(entry.frame))
|
|
26056
|
+
break;
|
|
26057
|
+
claimed.push(entry);
|
|
26058
|
+
}
|
|
26059
|
+
if (claimed.length === 0)
|
|
26060
|
+
return;
|
|
26061
|
+
userFrames.remove(new Set(claimed.map((c) => c.frame)));
|
|
26062
|
+
let folded = 0;
|
|
26063
|
+
for (const [index, { frame, composite }] of claimed.entries()) {
|
|
26064
|
+
const attachment = queuedPromptAttachment(frame.text);
|
|
26065
|
+
if (!await recordFoldedInput(attachment)) {
|
|
26066
|
+
userFrames.restore(claimed.slice(index).map((c) => c.frame));
|
|
26067
|
+
break;
|
|
26068
|
+
}
|
|
26069
|
+
const message = attachmentMessage(attachment);
|
|
26070
|
+
if (message !== undefined)
|
|
26071
|
+
messages.push(message);
|
|
26072
|
+
for (const notice of contextStrings(composite.systemMessages))
|
|
26073
|
+
emitHookNotice(notice);
|
|
26074
|
+
const context = hookAdditionalContextAttachment("UserPromptSubmit", contextStrings(composite.extraContext));
|
|
26075
|
+
if (context !== undefined) {
|
|
26076
|
+
const contextMessage = attachmentMessage(context);
|
|
26077
|
+
if (contextMessage !== undefined) {
|
|
26078
|
+
messages.push(contextMessage);
|
|
26079
|
+
await recordAttachment(context);
|
|
26080
|
+
}
|
|
26081
|
+
}
|
|
26082
|
+
folded++;
|
|
26083
|
+
}
|
|
26084
|
+
if (folded === 0)
|
|
26085
|
+
return;
|
|
26086
|
+
try {
|
|
26087
|
+
output.write({ type: "data", message: { type: "system", subtype: "host_input_folded", count: folded, uuid: randomUUID18(), session_id: config.sessionId } });
|
|
26088
|
+
} catch {}
|
|
26089
|
+
};
|
|
26090
|
+
const clearPendingHostInputs = () => userFrames.remove(new Set(userFrames.pending().filter((frame) => hostInputFrames.has(frame))));
|
|
25879
26091
|
const notifications = notificationQueueFor(config.sessionId);
|
|
25880
26092
|
let turnActive = false;
|
|
25881
26093
|
let controlCompaction;
|
|
@@ -26059,6 +26271,7 @@ ${escapeAttributionText(msg.body)}
|
|
|
26059
26271
|
return;
|
|
26060
26272
|
const frame = outcome.result.value;
|
|
26061
26273
|
if (frame.type === "user") {
|
|
26274
|
+
hostInputFrames.add(frame);
|
|
26062
26275
|
userFrames.write(frame);
|
|
26063
26276
|
continue;
|
|
26064
26277
|
}
|
|
@@ -26173,6 +26386,10 @@ ${escapeAttributionText(msg.body)}
|
|
|
26173
26386
|
});
|
|
26174
26387
|
continue;
|
|
26175
26388
|
}
|
|
26389
|
+
if (cf.subtype === "clear_queued_input") {
|
|
26390
|
+
output.write({ type: "control_response", requestId: cf.requestId, ok: true, payload: { cleared: clearPendingHostInputs() } });
|
|
26391
|
+
continue;
|
|
26392
|
+
}
|
|
26176
26393
|
if (cf.subtype === "list_models") {
|
|
26177
26394
|
output.write({ type: "control_response", requestId: cf.requestId, ok: true, payload: supportedModels?.() ?? [] });
|
|
26178
26395
|
continue;
|
|
@@ -27584,6 +27801,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
|
|
|
27584
27801
|
terminal_reason: "prompt_too_long",
|
|
27585
27802
|
api_error_status: status
|
|
27586
27803
|
});
|
|
27804
|
+
let toolRoundEnded = false;
|
|
27587
27805
|
roundLoop:
|
|
27588
27806
|
while (true) {
|
|
27589
27807
|
if (interruptedAwaitingMcp) {
|
|
@@ -27608,6 +27826,8 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
|
|
|
27608
27826
|
finalResult = takeHookStopResult();
|
|
27609
27827
|
break roundLoop;
|
|
27610
27828
|
}
|
|
27829
|
+
const foldAfterToolRound = toolRoundEnded;
|
|
27830
|
+
toolRoundEnded = false;
|
|
27611
27831
|
let turn;
|
|
27612
27832
|
let sentPerMessageBeta = false;
|
|
27613
27833
|
let sentToolChanges = false;
|
|
@@ -27618,6 +27838,8 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
|
|
|
27618
27838
|
try {
|
|
27619
27839
|
const context = await ensureSessionContext(assembled, envelopeInput);
|
|
27620
27840
|
const effortPlan = planEffort();
|
|
27841
|
+
if (foldAfterToolRound && config.agentId === undefined)
|
|
27842
|
+
await foldPendingHostInput(turnAbort.signal);
|
|
27621
27843
|
const toolPlan = await planToolsForRequest();
|
|
27622
27844
|
const toolSpecs = toolPlan.tools;
|
|
27623
27845
|
sentToolChanges = toolPlan.optIn === true || (toolPlan.toolChanges?.render.size ?? 0) > 0;
|
|
@@ -28313,6 +28535,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
|
|
|
28313
28535
|
break roundLoop;
|
|
28314
28536
|
}
|
|
28315
28537
|
await scanAttachments("tool-round");
|
|
28538
|
+
toolRoundEnded = true;
|
|
28316
28539
|
}
|
|
28317
28540
|
if (!stopHookFired)
|
|
28318
28541
|
await fireStopHooks(undefined);
|
|
@@ -29030,100 +29253,122 @@ function ancestryChain(byUuid, leafUuid) {
|
|
|
29030
29253
|
}
|
|
29031
29254
|
return chain.reverse();
|
|
29032
29255
|
}
|
|
29033
|
-
function toolUseIdsOf(e) {
|
|
29034
|
-
if (e.type !== "assistant" || !Array.isArray(e.message?.content))
|
|
29035
|
-
return [];
|
|
29036
|
-
return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_use" && typeof b.id === "string" ? [b.id] : []);
|
|
29037
|
-
}
|
|
29038
|
-
function toolResultIdsOf(e) {
|
|
29039
|
-
if (e.type !== "user" || !Array.isArray(e.message?.content))
|
|
29040
|
-
return [];
|
|
29041
|
-
return e.message.content.flatMap((b) => isRecord2(b) && b.type === "tool_result" && typeof b.tool_use_id === "string" ? [b.tool_use_id] : []);
|
|
29042
|
-
}
|
|
29043
29256
|
function recoverParallelToolResults(chain, pool) {
|
|
29044
29257
|
const onChain = new Set(chain.map((e) => e.uuid));
|
|
29045
29258
|
const resultsByParent = new Map;
|
|
29046
|
-
const
|
|
29047
|
-
for (const
|
|
29048
|
-
if (onChain.has(
|
|
29049
|
-
continue;
|
|
29050
|
-
if (e.type === "assistant" && e.isApiErrorMessage !== true)
|
|
29051
|
-
offChainAssistants.push(e);
|
|
29052
|
-
if (toolResultIdsOf(e).length === 0)
|
|
29259
|
+
const siblingCandidates = [];
|
|
29260
|
+
for (const entry of pool) {
|
|
29261
|
+
if (onChain.has(entry.uuid) || entry.parentUuid === null)
|
|
29053
29262
|
continue;
|
|
29054
|
-
|
|
29055
|
-
|
|
29056
|
-
|
|
29057
|
-
|
|
29058
|
-
|
|
29263
|
+
if (answerIdsOf(entry).length > 0) {
|
|
29264
|
+
const group = resultsByParent.get(entry.parentUuid);
|
|
29265
|
+
if (group === undefined)
|
|
29266
|
+
resultsByParent.set(entry.parentUuid, [entry]);
|
|
29267
|
+
else
|
|
29268
|
+
group.push(entry);
|
|
29269
|
+
}
|
|
29270
|
+
if (entry.type === "assistant" && entry.isApiErrorMessage !== true)
|
|
29271
|
+
siblingCandidates.push(entry);
|
|
29059
29272
|
}
|
|
29060
|
-
if (resultsByParent.size === 0 &&
|
|
29273
|
+
if (resultsByParent.size === 0 && siblingCandidates.length === 0)
|
|
29061
29274
|
return [...chain];
|
|
29062
|
-
const
|
|
29063
|
-
const
|
|
29064
|
-
|
|
29065
|
-
|
|
29066
|
-
|
|
29067
|
-
|
|
29068
|
-
|
|
29275
|
+
const satisfied = new Set;
|
|
29276
|
+
for (const entry of chain)
|
|
29277
|
+
for (const id of answerIdsOf(entry))
|
|
29278
|
+
satisfied.add(id);
|
|
29279
|
+
const returned = new Set;
|
|
29280
|
+
const selectResults = (parentUuid, open) => {
|
|
29281
|
+
const taken = [];
|
|
29282
|
+
const claimed = new Set;
|
|
29283
|
+
for (const result of resultsByParent.get(parentUuid) ?? []) {
|
|
29284
|
+
if (returned.has(result.uuid))
|
|
29069
29285
|
continue;
|
|
29070
|
-
const
|
|
29071
|
-
if (!
|
|
29286
|
+
const ids = answerIdsOf(result);
|
|
29287
|
+
if (!ids.some((id) => open.has(id) && !claimed.has(id)))
|
|
29072
29288
|
continue;
|
|
29073
|
-
|
|
29074
|
-
for (const id of
|
|
29075
|
-
|
|
29076
|
-
}
|
|
29077
|
-
return {
|
|
29078
|
-
};
|
|
29079
|
-
const
|
|
29080
|
-
|
|
29081
|
-
|
|
29082
|
-
|
|
29083
|
-
|
|
29084
|
-
|
|
29085
|
-
|
|
29086
|
-
for (let start = 0;start < chain.length; ) {
|
|
29087
|
-
if (chain[start].type !== "assistant") {
|
|
29088
|
-
start++;
|
|
29289
|
+
taken.push(result);
|
|
29290
|
+
for (const id of ids)
|
|
29291
|
+
claimed.add(id);
|
|
29292
|
+
}
|
|
29293
|
+
return { taken, claimed };
|
|
29294
|
+
};
|
|
29295
|
+
const openCallsOf = (entry) => new Set(callIdsOf(entry).filter((id) => !satisfied.has(id)));
|
|
29296
|
+
const insertAfter = new Map;
|
|
29297
|
+
const lastIndex = chain.length - 1;
|
|
29298
|
+
let index = 0;
|
|
29299
|
+
while (index <= lastIndex) {
|
|
29300
|
+
if (chain[index].type !== "assistant") {
|
|
29301
|
+
index++;
|
|
29089
29302
|
continue;
|
|
29090
29303
|
}
|
|
29091
|
-
let end =
|
|
29092
|
-
while (end + 1
|
|
29304
|
+
let end = index;
|
|
29305
|
+
while (end + 1 <= lastIndex && chain[end + 1].type === "assistant")
|
|
29093
29306
|
end++;
|
|
29094
|
-
|
|
29095
|
-
|
|
29096
|
-
|
|
29097
|
-
|
|
29098
|
-
|
|
29099
|
-
|
|
29100
|
-
|
|
29101
|
-
|
|
29102
|
-
|
|
29103
|
-
const
|
|
29104
|
-
|
|
29105
|
-
|
|
29106
|
-
|
|
29107
|
-
|
|
29108
|
-
|
|
29109
|
-
|
|
29110
|
-
|
|
29111
|
-
|
|
29112
|
-
|
|
29113
|
-
|
|
29114
|
-
|
|
29115
|
-
|
|
29116
|
-
|
|
29117
|
-
|
|
29118
|
-
|
|
29119
|
-
|
|
29120
|
-
|
|
29121
|
-
|
|
29122
|
-
|
|
29307
|
+
const members = chain.slice(index, end + 1);
|
|
29308
|
+
index = end + 1;
|
|
29309
|
+
if (end === lastIndex)
|
|
29310
|
+
continue;
|
|
29311
|
+
const memberResults = [];
|
|
29312
|
+
for (const member of members) {
|
|
29313
|
+
const { taken, claimed } = selectResults(member.uuid, openCallsOf(member));
|
|
29314
|
+
for (const result of taken)
|
|
29315
|
+
returned.add(result.uuid);
|
|
29316
|
+
for (const id of claimed)
|
|
29317
|
+
satisfied.add(id);
|
|
29318
|
+
memberResults.push(...taken);
|
|
29319
|
+
}
|
|
29320
|
+
const attachable = new Set(members.map((m) => m.uuid));
|
|
29321
|
+
const siblings = [];
|
|
29322
|
+
const siblingResults = [];
|
|
29323
|
+
for (const candidate of siblingCandidates) {
|
|
29324
|
+
if (candidate.parentUuid === null || !attachable.has(candidate.parentUuid) || returned.has(candidate.uuid))
|
|
29325
|
+
continue;
|
|
29326
|
+
attachable.add(candidate.uuid);
|
|
29327
|
+
const { taken, claimed } = selectResults(candidate.uuid, openCallsOf(candidate));
|
|
29328
|
+
const complete = callIdsOf(candidate).every((id) => satisfied.has(id) || claimed.has(id));
|
|
29329
|
+
if (!complete)
|
|
29330
|
+
continue;
|
|
29331
|
+
returned.add(candidate.uuid);
|
|
29332
|
+
for (const result of taken)
|
|
29333
|
+
returned.add(result.uuid);
|
|
29334
|
+
for (const id of claimed)
|
|
29335
|
+
satisfied.add(id);
|
|
29336
|
+
siblings.push(candidate);
|
|
29337
|
+
siblingResults.push(...taken);
|
|
29338
|
+
}
|
|
29339
|
+
const spliced = [...siblings, ...memberResults, ...siblingResults];
|
|
29340
|
+
if (spliced.length > 0)
|
|
29341
|
+
insertAfter.set(chain[end].uuid, spliced);
|
|
29123
29342
|
}
|
|
29124
|
-
|
|
29125
|
-
|
|
29126
|
-
|
|
29343
|
+
const out = [];
|
|
29344
|
+
for (const entry of chain) {
|
|
29345
|
+
out.push(entry);
|
|
29346
|
+
const extra = insertAfter.get(entry.uuid);
|
|
29347
|
+
if (extra !== undefined)
|
|
29348
|
+
out.push(...extra);
|
|
29349
|
+
}
|
|
29350
|
+
return out;
|
|
29351
|
+
}
|
|
29352
|
+
function callIdsOf(entry) {
|
|
29353
|
+
if (entry.type !== "assistant")
|
|
29354
|
+
return [];
|
|
29355
|
+
return contentBlockIds(entry, "tool_use", "id");
|
|
29356
|
+
}
|
|
29357
|
+
function answerIdsOf(entry) {
|
|
29358
|
+
if (entry.type !== "user")
|
|
29359
|
+
return [];
|
|
29360
|
+
return contentBlockIds(entry, "tool_result", "tool_use_id");
|
|
29361
|
+
}
|
|
29362
|
+
function contentBlockIds(entry, blockType, idKey) {
|
|
29363
|
+
const content = entry.message?.content;
|
|
29364
|
+
if (!Array.isArray(content))
|
|
29365
|
+
return [];
|
|
29366
|
+
const ids = [];
|
|
29367
|
+
for (const block of content) {
|
|
29368
|
+
if (isRecord2(block) && block.type === blockType && typeof block[idKey] === "string")
|
|
29369
|
+
ids.push(block[idKey]);
|
|
29370
|
+
}
|
|
29371
|
+
return ids;
|
|
29127
29372
|
}
|
|
29128
29373
|
function isDescendant(byUuid, ancestorUuid, candidateUuid) {
|
|
29129
29374
|
if (candidateUuid === ancestorUuid)
|
|
@@ -29527,7 +29772,7 @@ class TranscriptWriter2 {
|
|
|
29527
29772
|
return entries;
|
|
29528
29773
|
}
|
|
29529
29774
|
}
|
|
29530
|
-
var RUNTIME_ENGINE_VERSION2 = "0.0.
|
|
29775
|
+
var RUNTIME_ENGINE_VERSION2 = "0.0.44";
|
|
29531
29776
|
function withPermissionJournal(writer, location) {
|
|
29532
29777
|
return {
|
|
29533
29778
|
transcriptPath: join27(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),
|
|
@@ -30253,4 +30498,4 @@ function selectClassifierRoute2(config, descriptorOf) {
|
|
|
30253
30498
|
return { kind: "worker-eligible" };
|
|
30254
30499
|
}
|
|
30255
30500
|
|
|
30256
|
-
export { ChildResumeModeIncomparableError, resolveChildResumeMode, ensureDefaultMessagingRuntimeRegistered, buildContinuationChain, AGENT_TOOL_CANONICAL_NAME, computeActiveSlotSet, resolveSlotToProvider, createRpcBridge2, shellWords,
|
|
30501
|
+
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 };
|