@pikaa-ai/pikaa 0.3.28 → 0.4.1
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/bin/pikaa.js +5 -2
- package/dist/cli.js +972 -179
- package/dist/index.js +956 -168
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1356,6 +1356,7 @@ class DefaultModelClientSession {
|
|
|
1356
1356
|
systemMessage.cache_control = { type: "ephemeral" };
|
|
1357
1357
|
}
|
|
1358
1358
|
const messages = [systemMessage];
|
|
1359
|
+
const declaredToolCallIds = new Set;
|
|
1359
1360
|
for (let i = 0;i < params.history.length; i++) {
|
|
1360
1361
|
const item = params.history[i];
|
|
1361
1362
|
if (item.type === "user_message") {
|
|
@@ -1380,6 +1381,9 @@ class DefaultModelClientSession {
|
|
|
1380
1381
|
j++;
|
|
1381
1382
|
}
|
|
1382
1383
|
if (toolCalls.length > 0) {
|
|
1384
|
+
for (const tc of toolCalls) {
|
|
1385
|
+
declaredToolCallIds.add(tc.id);
|
|
1386
|
+
}
|
|
1383
1387
|
messages.push({
|
|
1384
1388
|
role: "assistant",
|
|
1385
1389
|
content: cleanedContent || null,
|
|
@@ -1416,6 +1420,9 @@ class DefaultModelClientSession {
|
|
|
1416
1420
|
});
|
|
1417
1421
|
j++;
|
|
1418
1422
|
}
|
|
1423
|
+
for (const tc of toolCalls) {
|
|
1424
|
+
declaredToolCallIds.add(tc.id);
|
|
1425
|
+
}
|
|
1419
1426
|
messages.push({
|
|
1420
1427
|
role: "assistant",
|
|
1421
1428
|
content: null,
|
|
@@ -1423,11 +1430,13 @@ class DefaultModelClientSession {
|
|
|
1423
1430
|
});
|
|
1424
1431
|
i = j - 1;
|
|
1425
1432
|
} else if (item.type === "function_call_output") {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1433
|
+
if (declaredToolCallIds.has(item.callId)) {
|
|
1434
|
+
messages.push({
|
|
1435
|
+
role: "tool",
|
|
1436
|
+
tool_call_id: item.callId,
|
|
1437
|
+
content: item.output
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1431
1440
|
}
|
|
1432
1441
|
}
|
|
1433
1442
|
const toolsPayload = typeof params.tools?.toModelToolsSchema === "function" ? params.tools.toModelToolsSchema() : typeof params.tools?.toOpenAISpec === "function" ? params.tools.toOpenAISpec() : Array.isArray(params.tools) ? params.tools : [];
|
|
@@ -1821,6 +1830,7 @@ var applyPatchTool = {
|
|
|
1821
1830
|
try {
|
|
1822
1831
|
mkdirSync3(dirname2(filePath), { recursive: true });
|
|
1823
1832
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1833
|
+
ctx.onFileModified?.(rawPath);
|
|
1824
1834
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1825
1835
|
} catch (err) {
|
|
1826
1836
|
return {
|
|
@@ -1841,7 +1851,31 @@ var applyPatchTool = {
|
|
|
1841
1851
|
isError: true
|
|
1842
1852
|
};
|
|
1843
1853
|
}
|
|
1844
|
-
|
|
1854
|
+
let targetToFind = targetContent;
|
|
1855
|
+
let replacementToUse = replacementContent;
|
|
1856
|
+
const fileHasCrlf = originalFileContent.includes(`\r
|
|
1857
|
+
`);
|
|
1858
|
+
let firstIndex = originalFileContent.indexOf(targetToFind);
|
|
1859
|
+
if (firstIndex === -1 && fileHasCrlf) {
|
|
1860
|
+
const crlfTarget = targetContent.replace(/\r?\n/g, `\r
|
|
1861
|
+
`);
|
|
1862
|
+
firstIndex = originalFileContent.indexOf(crlfTarget);
|
|
1863
|
+
if (firstIndex !== -1) {
|
|
1864
|
+
targetToFind = crlfTarget;
|
|
1865
|
+
replacementToUse = replacementContent.replace(/\r?\n/g, `\r
|
|
1866
|
+
`);
|
|
1867
|
+
}
|
|
1868
|
+
} else if (firstIndex === -1 && !fileHasCrlf && targetContent.includes(`\r
|
|
1869
|
+
`)) {
|
|
1870
|
+
const lfTarget = targetContent.replace(/\r\n/g, `
|
|
1871
|
+
`);
|
|
1872
|
+
firstIndex = originalFileContent.indexOf(lfTarget);
|
|
1873
|
+
if (firstIndex !== -1) {
|
|
1874
|
+
targetToFind = lfTarget;
|
|
1875
|
+
replacementToUse = replacementContent.replace(/\r\n/g, `
|
|
1876
|
+
`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1845
1879
|
if (firstIndex === -1) {
|
|
1846
1880
|
return {
|
|
1847
1881
|
output: `Error: targetContent was not found in '${rawPath}'.
|
|
@@ -1852,7 +1886,7 @@ var applyPatchTool = {
|
|
|
1852
1886
|
isError: true
|
|
1853
1887
|
};
|
|
1854
1888
|
}
|
|
1855
|
-
const secondIndex = originalFileContent.indexOf(
|
|
1889
|
+
const secondIndex = originalFileContent.indexOf(targetToFind, firstIndex + 1);
|
|
1856
1890
|
if (secondIndex !== -1) {
|
|
1857
1891
|
return {
|
|
1858
1892
|
output: `Error: targetContent matched multiple locations in '${rawPath}'.
|
|
@@ -1863,8 +1897,9 @@ var applyPatchTool = {
|
|
|
1863
1897
|
isError: true
|
|
1864
1898
|
};
|
|
1865
1899
|
}
|
|
1866
|
-
const newFileContent = originalFileContent.slice(0, firstIndex) +
|
|
1900
|
+
const newFileContent = originalFileContent.slice(0, firstIndex) + replacementToUse + originalFileContent.slice(firstIndex + targetToFind.length);
|
|
1867
1901
|
writeFileSync2(filePath, newFileContent, "utf8");
|
|
1902
|
+
ctx.onFileModified?.(rawPath);
|
|
1868
1903
|
return {
|
|
1869
1904
|
output: `Successfully applied patch to '${rawPath}'`
|
|
1870
1905
|
};
|
|
@@ -1876,8 +1911,133 @@ var applyPatchTool = {
|
|
|
1876
1911
|
}
|
|
1877
1912
|
}
|
|
1878
1913
|
};
|
|
1914
|
+
// src/security/shell-parser.ts
|
|
1915
|
+
function parseShellCommand(commandLine) {
|
|
1916
|
+
const commands = [];
|
|
1917
|
+
const subshellCommands = [];
|
|
1918
|
+
let hasPipes = false;
|
|
1919
|
+
let currentSegment = "";
|
|
1920
|
+
let inSingleQuote = false;
|
|
1921
|
+
let inDoubleQuote = false;
|
|
1922
|
+
let isEscaped = false;
|
|
1923
|
+
const len = commandLine.length;
|
|
1924
|
+
for (let i = 0;i < len; i++) {
|
|
1925
|
+
const char = commandLine[i];
|
|
1926
|
+
if (isEscaped) {
|
|
1927
|
+
currentSegment += char;
|
|
1928
|
+
isEscaped = false;
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
if (char === "\\") {
|
|
1932
|
+
isEscaped = true;
|
|
1933
|
+
currentSegment += char;
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (char === "'" && !inDoubleQuote) {
|
|
1937
|
+
inSingleQuote = !inSingleQuote;
|
|
1938
|
+
currentSegment += char;
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (char === '"' && !inSingleQuote) {
|
|
1942
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1943
|
+
currentSegment += char;
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
if (inSingleQuote) {
|
|
1947
|
+
currentSegment += char;
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (char === "`") {
|
|
1951
|
+
let endIdx = -1;
|
|
1952
|
+
for (let j = i + 1;j < len; j++) {
|
|
1953
|
+
if (commandLine[j] === "\\" && j + 1 < len) {
|
|
1954
|
+
j++;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
if (commandLine[j] === "`") {
|
|
1958
|
+
endIdx = j;
|
|
1959
|
+
break;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (endIdx !== -1) {
|
|
1963
|
+
const innerCmd = commandLine.slice(i + 1, endIdx);
|
|
1964
|
+
if (innerCmd.trim()) {
|
|
1965
|
+
subshellCommands.push(innerCmd.trim());
|
|
1966
|
+
}
|
|
1967
|
+
currentSegment += commandLine.slice(i, endIdx + 1);
|
|
1968
|
+
i = endIdx;
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
if (char === "$" && i + 1 < len && commandLine[i + 1] === "(") {
|
|
1973
|
+
let depth = 1;
|
|
1974
|
+
let endIdx = -1;
|
|
1975
|
+
for (let j = i + 2;j < len; j++) {
|
|
1976
|
+
if (commandLine[j] === "\\" && j + 1 < len) {
|
|
1977
|
+
j++;
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
if (commandLine[j] === "(")
|
|
1981
|
+
depth++;
|
|
1982
|
+
else if (commandLine[j] === ")") {
|
|
1983
|
+
depth--;
|
|
1984
|
+
if (depth === 0) {
|
|
1985
|
+
endIdx = j;
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
if (endIdx !== -1) {
|
|
1991
|
+
const innerCmd = commandLine.slice(i + 2, endIdx);
|
|
1992
|
+
if (innerCmd.trim()) {
|
|
1993
|
+
subshellCommands.push(innerCmd.trim());
|
|
1994
|
+
}
|
|
1995
|
+
currentSegment += commandLine.slice(i, endIdx + 1);
|
|
1996
|
+
i = endIdx;
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (!inDoubleQuote) {
|
|
2001
|
+
if (char === "&" && commandLine[i + 1] === "&" || char === "|" && commandLine[i + 1] === "|") {
|
|
2002
|
+
if (currentSegment.trim()) {
|
|
2003
|
+
commands.push(currentSegment.trim());
|
|
2004
|
+
}
|
|
2005
|
+
currentSegment = "";
|
|
2006
|
+
i++;
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
if (char === "|") {
|
|
2010
|
+
hasPipes = true;
|
|
2011
|
+
if (currentSegment.trim()) {
|
|
2012
|
+
commands.push(currentSegment.trim());
|
|
2013
|
+
}
|
|
2014
|
+
currentSegment = "";
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
if (char === ";" || char === `
|
|
2018
|
+
` || char === "&") {
|
|
2019
|
+
if (currentSegment.trim()) {
|
|
2020
|
+
commands.push(currentSegment.trim());
|
|
2021
|
+
}
|
|
2022
|
+
currentSegment = "";
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
currentSegment += char;
|
|
2027
|
+
}
|
|
2028
|
+
if (currentSegment.trim()) {
|
|
2029
|
+
commands.push(currentSegment.trim());
|
|
2030
|
+
}
|
|
2031
|
+
return {
|
|
2032
|
+
commands: commands.filter(Boolean),
|
|
2033
|
+
subshellCommands: subshellCommands.filter(Boolean),
|
|
2034
|
+
hasPipes
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1879
2038
|
// src/security/exec-policy.ts
|
|
1880
2039
|
class ExecPolicy {
|
|
2040
|
+
denyRules = [];
|
|
1881
2041
|
rules = [];
|
|
1882
2042
|
mode = "auto";
|
|
1883
2043
|
constructor(initialMode = "auto") {
|
|
@@ -1891,12 +2051,24 @@ class ExecPolicy {
|
|
|
1891
2051
|
this.mode = mode;
|
|
1892
2052
|
}
|
|
1893
2053
|
initDefaultRules() {
|
|
1894
|
-
this.
|
|
1895
|
-
this.
|
|
1896
|
-
this.
|
|
1897
|
-
this.addRule(/^(
|
|
1898
|
-
this.addRule(/^(
|
|
1899
|
-
this.addRule(/^(
|
|
2054
|
+
this.addDenyRule(/(?:^|[/\\])(mkfs|format|fdisk|parted)\b/i, "Destructive filesystem formatting operation");
|
|
2055
|
+
this.addDenyRule(/^dd\s+.*(of=\/dev\/|\/dev\/sd|\/dev\/nvme)/i, "Raw block device write attempt");
|
|
2056
|
+
this.addDenyRule(/(?:^|[/\\])(reboot|shutdown|poweroff|init\s+0)\b/i, "System power manipulation");
|
|
2057
|
+
this.addRule(/^(sudo|su|doas|runas)\b/i, "prompt", "Privilege escalation attempt");
|
|
2058
|
+
this.addRule(/^(rm|del|rmdir|shred|unlink)\b/i, "prompt", "Destructive file removal");
|
|
2059
|
+
this.addRule(/^(chmod|chown|kill|pkill|killall|systemctl|service|crontab)\b/i, "prompt", "System administration & process control");
|
|
2060
|
+
this.addRule(/^(git\s+(push|reset\s+--hard|clean\s+-fd|rebase|branch\s+-D|checkout\s+-f))\b/i, "prompt", "Destructive git operation");
|
|
2061
|
+
this.addRule(/^(curl|wget|fetch|ssh|scp|sftp|ftp|rsync|nc|ncat|netcat|socat|telnet)\b/i, "prompt", "Network & remote transfer");
|
|
2062
|
+
this.addRule(/^(bash|sh|zsh|dash|ksh|cmd(\.exe)?|powershell(\.exe)?|pwsh)\s+(-c|-command|\/c)\b/i, "prompt", "Arbitrary subshell command execution");
|
|
2063
|
+
this.addRule(/^(python|python3|node|bun|perl|ruby)\s+(-c|-e)\b/i, "prompt", "Inline arbitrary code evaluation");
|
|
2064
|
+
this.addRule(/^(eval|exec)\b/i, "prompt", "Dynamic code execution");
|
|
2065
|
+
this.addRule(/^(npm\s+publish|bun\s+publish|cargo\s+publish)\b/i, "prompt", "Package registry publication");
|
|
2066
|
+
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse|tag|remote|describe))\b/i, "allow", "Safe git query");
|
|
2067
|
+
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where|stat|file|du|df)\b/i, "allow", "Safe read-only shell command");
|
|
2068
|
+
this.addRule(/^(bun\s+(test|run|--version|-v)|npm\s+(test|run|--version|-v)|npx\s+(tsc|eslint|oxlint)|tsc|cargo\s+(check|test|build)|go\s+(test|vet|build)|pytest|python\s+-m\s+unittest|node\s+(-v|--version|--test))\b/i, "allow", "Testing, typechecking & build verification");
|
|
2069
|
+
}
|
|
2070
|
+
addDenyRule(pattern, description) {
|
|
2071
|
+
this.denyRules.push({ pattern, decision: "deny", description });
|
|
1900
2072
|
}
|
|
1901
2073
|
addRule(pattern, decision, description) {
|
|
1902
2074
|
this.rules.unshift({ pattern, decision, description });
|
|
@@ -1919,8 +2091,49 @@ class ExecPolicy {
|
|
|
1919
2091
|
}
|
|
1920
2092
|
evaluate(command) {
|
|
1921
2093
|
const trimmed = command.trim();
|
|
2094
|
+
if (!trimmed) {
|
|
2095
|
+
return { decision: "allow" };
|
|
2096
|
+
}
|
|
2097
|
+
const parsed = parseShellCommand(trimmed);
|
|
2098
|
+
const subCommands = [...parsed.commands, ...parsed.subshellCommands].map((c) => c.trim()).filter(Boolean);
|
|
2099
|
+
if (subCommands.length > 1 || parsed.subshellCommands.length > 0) {
|
|
2100
|
+
for (const subCmd of subCommands) {
|
|
2101
|
+
const subResult = this.evaluateSingle(subCmd);
|
|
2102
|
+
if (subResult.decision === "deny") {
|
|
2103
|
+
return {
|
|
2104
|
+
decision: "deny",
|
|
2105
|
+
reason: `Chained command contains denied operation: '${subCmd}' (${subResult.reason || "Forbidden"})`
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
if (subResult.decision === "prompt") {
|
|
2109
|
+
return {
|
|
2110
|
+
decision: "prompt",
|
|
2111
|
+
reason: `Chained command contains operation requiring confirmation: '${subCmd}' (${subResult.reason || "Requires approval"})`
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
if (parsed.hasPipes && /\|\s*(ba|z|k|c)?sh\b/i.test(trimmed)) {
|
|
2116
|
+
return {
|
|
2117
|
+
decision: "prompt",
|
|
2118
|
+
reason: "Pipeline executes piped input directly into shell interpreter (| sh)"
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
return { decision: "allow", reason: "All chained sub-commands are permitted" };
|
|
2122
|
+
}
|
|
2123
|
+
return this.evaluateSingle(trimmed);
|
|
2124
|
+
}
|
|
2125
|
+
evaluateSingle(command) {
|
|
2126
|
+
const trimmed = command.trim();
|
|
2127
|
+
for (const rule of this.denyRules) {
|
|
2128
|
+
if (rule.pattern.test(trimmed)) {
|
|
2129
|
+
return {
|
|
2130
|
+
decision: "deny",
|
|
2131
|
+
reason: rule.description
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
1922
2135
|
if (this.mode === "plan") {
|
|
1923
|
-
const isReadOnly = /^(git\s+(status|log|diff|branch|show|rev-parse)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
2136
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show|rev-parse|tag|remote)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1924
2137
|
if (isReadOnly) {
|
|
1925
2138
|
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1926
2139
|
}
|
|
@@ -1936,7 +2149,7 @@ class ExecPolicy {
|
|
|
1936
2149
|
};
|
|
1937
2150
|
}
|
|
1938
2151
|
if (this.mode === "accept-edits") {
|
|
1939
|
-
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
|
|
2152
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show|rev-parse)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
|
|
1940
2153
|
if (isReadOnly) {
|
|
1941
2154
|
return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
|
|
1942
2155
|
}
|
|
@@ -1953,11 +2166,21 @@ class ExecPolicy {
|
|
|
1953
2166
|
};
|
|
1954
2167
|
}
|
|
1955
2168
|
}
|
|
2169
|
+
if (this.isRecognizedSafeDevCommand(trimmed)) {
|
|
2170
|
+
return {
|
|
2171
|
+
decision: "allow",
|
|
2172
|
+
reason: "Safe development workspace command"
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
const firstWord = trimmed.split(/\s+/)[0] || trimmed;
|
|
1956
2176
|
return {
|
|
1957
|
-
decision: "
|
|
1958
|
-
reason:
|
|
2177
|
+
decision: "prompt",
|
|
2178
|
+
reason: `Command '${firstWord}' is unclassified and requires confirmation in auto mode`
|
|
1959
2179
|
};
|
|
1960
2180
|
}
|
|
2181
|
+
isRecognizedSafeDevCommand(command) {
|
|
2182
|
+
return /^(git\s+(checkout|add|commit|stash|merge|pull|init)|bun\s+(install|add|remove)|npm\s+(install|i|add|remove)|yarn\s+(add|remove)|pnpm\s+(add|remove|install)|mkdir|touch|cp|copy|mv|move|clear|cls|echo|printf|node|bun|python|python3|cargo|go)\b/i.test(command);
|
|
2183
|
+
}
|
|
1961
2184
|
}
|
|
1962
2185
|
|
|
1963
2186
|
// src/security/kernel/windows.ts
|
|
@@ -2060,8 +2283,12 @@ class LinuxSandbox {
|
|
|
2060
2283
|
}
|
|
2061
2284
|
this.hasBwrap = existsSync4("/usr/bin/bwrap") || existsSync4("/bin/bwrap") || existsSync4("/usr/local/bin/bwrap");
|
|
2062
2285
|
}
|
|
2063
|
-
wrapCommand(cmd, profile) {
|
|
2064
|
-
if (
|
|
2286
|
+
wrapCommand(cmd, profile, onWarning) {
|
|
2287
|
+
if (profile.kind === "danger-unrestricted") {
|
|
2288
|
+
return cmd;
|
|
2289
|
+
}
|
|
2290
|
+
if (!this.hasBwrap) {
|
|
2291
|
+
onWarning?.("Linux kernel sandbox (Bubblewrap / bwrap) is not available. Command will execute without OS-level namespace isolation.");
|
|
2065
2292
|
return cmd;
|
|
2066
2293
|
}
|
|
2067
2294
|
const bwrapArgs = [
|
|
@@ -2077,6 +2304,23 @@ class LinuxSandbox {
|
|
|
2077
2304
|
"--tmpfs",
|
|
2078
2305
|
"/tmp"
|
|
2079
2306
|
];
|
|
2307
|
+
const homeDir = process.env.HOME || "/root";
|
|
2308
|
+
const sensitivePaths = [
|
|
2309
|
+
`${homeDir}/.ssh`,
|
|
2310
|
+
`${homeDir}/.aws`,
|
|
2311
|
+
`${homeDir}/.gnupg`,
|
|
2312
|
+
`${homeDir}/.azure`,
|
|
2313
|
+
`${homeDir}/.kube`,
|
|
2314
|
+
`${homeDir}/.docker`,
|
|
2315
|
+
`${homeDir}/.netrc`,
|
|
2316
|
+
"/etc/shadow",
|
|
2317
|
+
"/etc/sudoers"
|
|
2318
|
+
];
|
|
2319
|
+
for (const sensitive of sensitivePaths) {
|
|
2320
|
+
if (existsSync4(sensitive)) {
|
|
2321
|
+
bwrapArgs.push("--tmpfs", sensitive);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2080
2324
|
for (const writableRoot of profile.writableRoots) {
|
|
2081
2325
|
bwrapArgs.push("--bind", writableRoot, writableRoot);
|
|
2082
2326
|
}
|
|
@@ -2126,11 +2370,21 @@ class MacOSSandbox {
|
|
|
2126
2370
|
} else {
|
|
2127
2371
|
rules.push("(deny network*)");
|
|
2128
2372
|
}
|
|
2373
|
+
const home = process.env.HOME || "/Users/Shared";
|
|
2374
|
+
rules.push(`(deny file-read* (subpath "${home}/.ssh"))`);
|
|
2375
|
+
rules.push(`(deny file-read* (subpath "${home}/.aws"))`);
|
|
2376
|
+
rules.push(`(deny file-read* (subpath "${home}/.gnupg"))`);
|
|
2377
|
+
rules.push(`(deny file-read* (subpath "${home}/.kube"))`);
|
|
2378
|
+
rules.push(`(deny file-read* (subpath "${home}/.docker"))`);
|
|
2129
2379
|
return rules.join(`
|
|
2130
2380
|
`);
|
|
2131
2381
|
}
|
|
2132
|
-
wrapCommand(cmd, profile) {
|
|
2133
|
-
if (
|
|
2382
|
+
wrapCommand(cmd, profile, onWarning) {
|
|
2383
|
+
if (profile.kind === "danger-unrestricted") {
|
|
2384
|
+
return cmd;
|
|
2385
|
+
}
|
|
2386
|
+
if (!this.hasSandboxExec) {
|
|
2387
|
+
onWarning?.("macOS Seatbelt (sandbox-exec) is not available. Command will execute without OS-level namespace isolation.");
|
|
2134
2388
|
return cmd;
|
|
2135
2389
|
}
|
|
2136
2390
|
const policy = this.generateProfile(profile);
|
|
@@ -2163,7 +2417,7 @@ class KernelSandboxManager {
|
|
|
2163
2417
|
isSandboxingActive: this.windowsSandbox.isSupported() || this.linuxSandbox.isSupported() || this.macOsSandbox.isSupported()
|
|
2164
2418
|
};
|
|
2165
2419
|
}
|
|
2166
|
-
buildDefaultProfile(cwd, allowNetwork =
|
|
2420
|
+
buildDefaultProfile(cwd, allowNetwork = false) {
|
|
2167
2421
|
const normCwd = normalize(resolve4(cwd));
|
|
2168
2422
|
return {
|
|
2169
2423
|
kind: "workspace-write",
|
|
@@ -2178,11 +2432,15 @@ class KernelSandboxManager {
|
|
|
2178
2432
|
}
|
|
2179
2433
|
};
|
|
2180
2434
|
}
|
|
2181
|
-
wrapCommand(cmd, profile) {
|
|
2435
|
+
wrapCommand(cmd, profile, onWarning) {
|
|
2182
2436
|
if (process.platform === "linux") {
|
|
2183
|
-
return this.linuxSandbox.wrapCommand(cmd, profile);
|
|
2437
|
+
return this.linuxSandbox.wrapCommand(cmd, profile, onWarning);
|
|
2184
2438
|
} else if (process.platform === "darwin") {
|
|
2185
|
-
return this.macOsSandbox.wrapCommand(cmd, profile);
|
|
2439
|
+
return this.macOsSandbox.wrapCommand(cmd, profile, onWarning);
|
|
2440
|
+
} else if (process.platform === "win32") {
|
|
2441
|
+
if (!this.windowsSandbox.isSupported() && profile.kind !== "danger-unrestricted") {
|
|
2442
|
+
onWarning?.("Windows JobObject isolation is not available in this environment. Command will execute without OS-level process limits.");
|
|
2443
|
+
}
|
|
2186
2444
|
}
|
|
2187
2445
|
return cmd;
|
|
2188
2446
|
}
|
|
@@ -2314,6 +2572,12 @@ class PrefixRulesStore {
|
|
|
2314
2572
|
matchesPrefix(cmdTokens, prefixTokens) {
|
|
2315
2573
|
if (prefixTokens.length > cmdTokens.length)
|
|
2316
2574
|
return false;
|
|
2575
|
+
const SHELL_OPERATORS = new Set([";", "&&", "||", "|", "&", ";;", "&|"]);
|
|
2576
|
+
for (const token of cmdTokens) {
|
|
2577
|
+
if (SHELL_OPERATORS.has(token) || token.includes(";") || token.includes("&&") || token.includes("||")) {
|
|
2578
|
+
return false;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2317
2581
|
for (let i = 0;i < prefixTokens.length; i++) {
|
|
2318
2582
|
const cmd = cmdTokens[i];
|
|
2319
2583
|
const prefix = prefixTokens[i];
|
|
@@ -2439,11 +2703,29 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2439
2703
|
}
|
|
2440
2704
|
const args = rawArgs;
|
|
2441
2705
|
const rulesStore = ctx.prefixRulesStore || globalPrefixRulesStore;
|
|
2442
|
-
const
|
|
2706
|
+
const activePolicy = ctx.execPolicy || policy;
|
|
2707
|
+
const policyDecision = activePolicy.evaluate(command);
|
|
2708
|
+
if (policyDecision.decision === "deny") {
|
|
2709
|
+
return {
|
|
2710
|
+
output: `Error: Command execution denied by policy: ${policyDecision.reason}`,
|
|
2711
|
+
isError: true
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
const parsed = parseShellCommand(command);
|
|
2715
|
+
const isCompound = parsed.commands.length > 1 || parsed.subshellCommands.length > 0 || parsed.hasPipes;
|
|
2443
2716
|
let isEscalated = false;
|
|
2444
|
-
if (
|
|
2445
|
-
|
|
2446
|
-
|
|
2717
|
+
if (!isCompound) {
|
|
2718
|
+
const cmdTokens = command.split(/\s+/).filter(Boolean);
|
|
2719
|
+
if (rulesStore.isApproved(ctx.cwd, cmdTokens)) {
|
|
2720
|
+
isEscalated = true;
|
|
2721
|
+
}
|
|
2722
|
+
} else {
|
|
2723
|
+
const allSubCommands = [...parsed.commands, ...parsed.subshellCommands];
|
|
2724
|
+
if (allSubCommands.length > 0 && allSubCommands.every((sub) => rulesStore.isApproved(ctx.cwd, sub.split(/\s+/).filter(Boolean)))) {
|
|
2725
|
+
isEscalated = true;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
if (!isEscalated && args.sandbox_permissions === "require_escalated") {
|
|
2447
2729
|
if (ctx.requestApproval) {
|
|
2448
2730
|
const promptDesc = args.justification || "Executing command with escalated permissions";
|
|
2449
2731
|
const approvalResult = await ctx.requestApproval(promptDesc, command, args.prefix_rule);
|
|
@@ -2461,16 +2743,8 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2461
2743
|
isEscalated = true;
|
|
2462
2744
|
}
|
|
2463
2745
|
}
|
|
2464
|
-
if (!isEscalated) {
|
|
2465
|
-
|
|
2466
|
-
const policyDecision = activePolicy.evaluate(command);
|
|
2467
|
-
if (policyDecision.decision === "deny") {
|
|
2468
|
-
return {
|
|
2469
|
-
output: `Error: Command execution denied by policy: ${policyDecision.reason}`,
|
|
2470
|
-
isError: true
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
if (policyDecision.decision === "prompt" && ctx.requestApproval) {
|
|
2746
|
+
if (policyDecision.decision === "prompt" && !isEscalated) {
|
|
2747
|
+
if (ctx.requestApproval) {
|
|
2474
2748
|
const approvalResult = await ctx.requestApproval(policyDecision.reason || "Executing external command", command);
|
|
2475
2749
|
const isAllowed = typeof approvalResult === "boolean" ? approvalResult : approvalResult?.allowed;
|
|
2476
2750
|
if (!isAllowed) {
|
|
@@ -2479,17 +2753,22 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2479
2753
|
isError: true
|
|
2480
2754
|
};
|
|
2481
2755
|
}
|
|
2756
|
+
} else {
|
|
2757
|
+
return {
|
|
2758
|
+
output: `Error: Command execution requires user confirmation: '${command}' (${policyDecision.reason || "Requires approval"})`,
|
|
2759
|
+
isError: true
|
|
2760
|
+
};
|
|
2482
2761
|
}
|
|
2483
2762
|
}
|
|
2484
2763
|
const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : 30000;
|
|
2485
2764
|
const isWindows = process.platform === "win32";
|
|
2486
2765
|
const baseCmd = isWindows ? ["cmd.exe", "/d", "/s", "/c", command] : ["/bin/sh", "-c", command];
|
|
2487
2766
|
const ephemeralScratchpad = globalEphemeralWorkspace.createScratchpad(ctx.turnId);
|
|
2488
|
-
const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd);
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2767
|
+
const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd, isEscalated);
|
|
2768
|
+
let sandboxNotice = null;
|
|
2769
|
+
const wrappedCmd = globalKernelSandbox.wrapCommand(baseCmd, sandboxProfile, (w) => {
|
|
2770
|
+
sandboxNotice = w;
|
|
2771
|
+
});
|
|
2493
2772
|
try {
|
|
2494
2773
|
const proc = Bun.spawn(wrappedCmd, {
|
|
2495
2774
|
cwd: ctx.cwd,
|
|
@@ -2543,10 +2822,38 @@ ${result.stderr.trim()}`);
|
|
|
2543
2822
|
2. If this is a test failure, trace the failure in source code and fix the root cause before re-running.
|
|
2544
2823
|
3. If this is a missing command/module, install or configure the prerequisite.`);
|
|
2545
2824
|
}
|
|
2546
|
-
|
|
2825
|
+
let rawOutput = outputParts.join(`
|
|
2547
2826
|
`) || "[Command completed with no output]";
|
|
2827
|
+
const lines = rawOutput.split(`
|
|
2828
|
+
`);
|
|
2829
|
+
const MAX_LINES = 250;
|
|
2830
|
+
const MAX_CHARS = 30000;
|
|
2831
|
+
if (lines.length > MAX_LINES) {
|
|
2832
|
+
const headLines = lines.slice(0, 125).join(`
|
|
2833
|
+
`);
|
|
2834
|
+
const tailLines = lines.slice(-100).join(`
|
|
2835
|
+
`);
|
|
2836
|
+
rawOutput = `${headLines}
|
|
2837
|
+
|
|
2838
|
+
... [${lines.length - 225} lines truncated for context efficiency] ...
|
|
2839
|
+
|
|
2840
|
+
${tailLines}`;
|
|
2841
|
+
}
|
|
2842
|
+
if (rawOutput.length > MAX_CHARS) {
|
|
2843
|
+
const headChars = rawOutput.slice(0, 16000);
|
|
2844
|
+
const tailChars = rawOutput.slice(-12000);
|
|
2845
|
+
rawOutput = `${headChars}
|
|
2846
|
+
|
|
2847
|
+
... [${rawOutput.length - 28000} characters truncated for context efficiency] ...
|
|
2848
|
+
|
|
2849
|
+
${tailChars}`;
|
|
2850
|
+
}
|
|
2851
|
+
if (sandboxNotice) {
|
|
2852
|
+
rawOutput = `[Sandbox Notice]: ${sandboxNotice}
|
|
2853
|
+
${rawOutput}`;
|
|
2854
|
+
}
|
|
2548
2855
|
return {
|
|
2549
|
-
output,
|
|
2856
|
+
output: rawOutput,
|
|
2550
2857
|
isError: result.code !== 0
|
|
2551
2858
|
};
|
|
2552
2859
|
} catch (err) {
|
|
@@ -2565,7 +2872,7 @@ ${result.stderr.trim()}`);
|
|
|
2565
2872
|
}
|
|
2566
2873
|
var shellTool = createShellTool();
|
|
2567
2874
|
// src/tools/handlers/file-ops.ts
|
|
2568
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync8, statSync as statSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
2875
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync8, statSync as statSync3, lstatSync, mkdirSync as mkdirSync6 } from "fs";
|
|
2569
2876
|
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
2570
2877
|
var DEFAULT_MAX_UNPAGINATED_LINES = 250;
|
|
2571
2878
|
var readFileTool = {
|
|
@@ -2677,6 +2984,7 @@ var viewFileTool = {
|
|
|
2677
2984
|
name: "view_file",
|
|
2678
2985
|
description: "View file content with surgical line-range support. Alias for read_file matching Antigravity & Claude Code conventions."
|
|
2679
2986
|
};
|
|
2987
|
+
var DEFAULT_MAX_DIR_ENTRIES = 500;
|
|
2680
2988
|
var listDirTool = {
|
|
2681
2989
|
name: "list_dir",
|
|
2682
2990
|
description: "List contents of a directory with file names and types.",
|
|
@@ -2693,13 +3001,31 @@ var listDirTool = {
|
|
|
2693
3001
|
}
|
|
2694
3002
|
try {
|
|
2695
3003
|
const entries = readdirSync3(dirPath);
|
|
2696
|
-
const
|
|
3004
|
+
const totalEntries = entries.length;
|
|
3005
|
+
const displayEntries = entries.slice(0, DEFAULT_MAX_DIR_ENTRIES);
|
|
3006
|
+
const formatted = displayEntries.map((entry) => {
|
|
2697
3007
|
const full = resolve6(dirPath, entry);
|
|
2698
|
-
|
|
3008
|
+
let isDir = false;
|
|
3009
|
+
try {
|
|
3010
|
+
isDir = statSync3(full).isDirectory();
|
|
3011
|
+
} catch {
|
|
3012
|
+
try {
|
|
3013
|
+
const lst = lstatSync(full);
|
|
3014
|
+
if (lst.isSymbolicLink())
|
|
3015
|
+
return `[SYMLINK] ${entry}`;
|
|
3016
|
+
} catch {}
|
|
3017
|
+
return `[FILE] ${entry}`;
|
|
3018
|
+
}
|
|
2699
3019
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2700
3020
|
});
|
|
2701
|
-
|
|
2702
|
-
`) || "[Empty directory]"
|
|
3021
|
+
let output = formatted.join(`
|
|
3022
|
+
`) || "[Empty directory]";
|
|
3023
|
+
if (totalEntries > DEFAULT_MAX_DIR_ENTRIES) {
|
|
3024
|
+
output += `
|
|
3025
|
+
|
|
3026
|
+
... [Truncated: ${totalEntries - DEFAULT_MAX_DIR_ENTRIES} more entries. Showing first ${DEFAULT_MAX_DIR_ENTRIES} of ${totalEntries}]`;
|
|
3027
|
+
}
|
|
3028
|
+
return { output };
|
|
2703
3029
|
} catch (err) {
|
|
2704
3030
|
return { output: `Failed to list directory: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
2705
3031
|
}
|
|
@@ -2740,6 +3066,7 @@ var writeFileTool = {
|
|
|
2740
3066
|
try {
|
|
2741
3067
|
mkdirSync6(dirname4(filePath), { recursive: true });
|
|
2742
3068
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
3069
|
+
ctx.onFileModified?.(rawPath);
|
|
2743
3070
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2744
3071
|
} catch (err) {
|
|
2745
3072
|
return {
|
|
@@ -3233,6 +3560,12 @@ class CodeModeToolsProxy {
|
|
|
3233
3560
|
if (result.isError) {
|
|
3234
3561
|
throw new Error(`Tool '${tool.name}' failed: ${result.output}`);
|
|
3235
3562
|
}
|
|
3563
|
+
if (normalizedName === "write_file" || normalizedName === "apply_patch" || tool.name === "write_file" || tool.name === "apply_patch") {
|
|
3564
|
+
const pathArg = String(args?.path || "");
|
|
3565
|
+
if (pathArg) {
|
|
3566
|
+
this.context.onFileModified?.(pathArg);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3236
3569
|
return result.output;
|
|
3237
3570
|
};
|
|
3238
3571
|
}
|
|
@@ -3306,6 +3639,9 @@ class SandboxedWorkerHost {
|
|
|
3306
3639
|
const fetch = undefined;
|
|
3307
3640
|
const XMLHttpRequest = undefined;
|
|
3308
3641
|
const WebSocket = undefined;
|
|
3642
|
+
const globalThis = Object.freeze(Object.create(null));
|
|
3643
|
+
const global = undefined;
|
|
3644
|
+
const window = undefined;
|
|
3309
3645
|
|
|
3310
3646
|
return (async () => {
|
|
3311
3647
|
${cleanCode}
|
|
@@ -4316,14 +4652,39 @@ function compactHistory(history, retainedRecentItems = 6) {
|
|
|
4316
4652
|
if (history.length <= retainedRecentItems) {
|
|
4317
4653
|
return [...history];
|
|
4318
4654
|
}
|
|
4319
|
-
const
|
|
4320
|
-
|
|
4655
|
+
const targetCutoff = history.length - retainedRecentItems;
|
|
4656
|
+
let bestCutoff = -1;
|
|
4657
|
+
for (let i = targetCutoff;i >= 0; i--) {
|
|
4658
|
+
if (history[i]?.type === "user_message") {
|
|
4659
|
+
bestCutoff = i;
|
|
4660
|
+
break;
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
if (bestCutoff <= 0) {
|
|
4664
|
+
for (let i = targetCutoff + 1;i < history.length; i++) {
|
|
4665
|
+
if (history[i]?.type === "user_message") {
|
|
4666
|
+
bestCutoff = i;
|
|
4667
|
+
break;
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
let cutoffIndex = bestCutoff > 0 ? bestCutoff : targetCutoff;
|
|
4672
|
+
while (cutoffIndex < history.length && history[cutoffIndex]?.type === "function_call_output") {
|
|
4673
|
+
cutoffIndex++;
|
|
4674
|
+
}
|
|
4675
|
+
if (cutoffIndex <= 0 || cutoffIndex >= history.length) {
|
|
4676
|
+
return [...history];
|
|
4677
|
+
}
|
|
4678
|
+
const itemsToCompact = history.slice(0, cutoffIndex);
|
|
4679
|
+
const recentItems = history.slice(cutoffIndex);
|
|
4321
4680
|
const summaryParts = ["### Summary of previous conversation context:"];
|
|
4322
4681
|
for (const item of itemsToCompact) {
|
|
4323
4682
|
if (item.type === "user_message") {
|
|
4324
4683
|
summaryParts.push(`- User: ${item.content.slice(0, 200)}`);
|
|
4325
4684
|
} else if (item.type === "function_call") {
|
|
4326
4685
|
summaryParts.push(`- Executed tool: ${item.name}`);
|
|
4686
|
+
} else if (item.type === "function_call_output") {
|
|
4687
|
+
summaryParts.push(` Tool output: ${item.output.slice(0, 100)}`);
|
|
4327
4688
|
} else if (item.type === "agent_message") {
|
|
4328
4689
|
summaryParts.push(`- Assistant: ${item.content.slice(0, 200)}`);
|
|
4329
4690
|
}
|
|
@@ -4363,7 +4724,7 @@ class TurnContext {
|
|
|
4363
4724
|
}
|
|
4364
4725
|
}
|
|
4365
4726
|
// src/verification/verifier.ts
|
|
4366
|
-
import { spawnSync } from "child_process";
|
|
4727
|
+
import { spawn, spawnSync } from "child_process";
|
|
4367
4728
|
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
|
|
4368
4729
|
import { join as join8 } from "path";
|
|
4369
4730
|
|
|
@@ -4740,36 +5101,120 @@ class AutoVerifier {
|
|
|
4740
5101
|
this.customCommand = options.customCommand;
|
|
4741
5102
|
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
4742
5103
|
}
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
5104
|
+
findTargetedTests(modifiedFiles = []) {
|
|
5105
|
+
const matchedTests = new Set;
|
|
5106
|
+
for (const rawFile of modifiedFiles) {
|
|
5107
|
+
const normalized = rawFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5108
|
+
if (/\.(test|spec)\.[jt]sx?$/i.test(normalized) || /(^|\/)test_[^/]+\.py$/i.test(normalized) || /_test\.py$/i.test(normalized) || /_test\.go$/i.test(normalized)) {
|
|
5109
|
+
if (existsSync14(join8(this.cwd, normalized))) {
|
|
5110
|
+
matchedTests.add(normalized);
|
|
5111
|
+
}
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
const extMatch = normalized.match(/\.[^.]+$/);
|
|
5115
|
+
if (!extMatch)
|
|
5116
|
+
continue;
|
|
5117
|
+
const withoutExt = normalized.slice(0, -extMatch[0].length);
|
|
5118
|
+
const parts = withoutExt.split("/");
|
|
5119
|
+
const baseName = parts[parts.length - 1];
|
|
5120
|
+
const subPath = normalized.startsWith("src/") ? normalized.slice(4, -extMatch[0].length) : normalized.startsWith("lib/") ? normalized.slice(4, -extMatch[0].length) : withoutExt;
|
|
5121
|
+
const candidatePaths = [
|
|
5122
|
+
`tests/${subPath}.test.ts`,
|
|
5123
|
+
`tests/${subPath}.test.js`,
|
|
5124
|
+
`tests/${subPath}.test.tsx`,
|
|
5125
|
+
`tests/${subPath}.spec.ts`,
|
|
5126
|
+
`tests/${subPath}.spec.js`,
|
|
5127
|
+
`tests/${baseName}.test.ts`,
|
|
5128
|
+
`tests/${baseName}.test.js`,
|
|
5129
|
+
`tests/${baseName}.spec.ts`,
|
|
5130
|
+
`test/${subPath}.test.ts`,
|
|
5131
|
+
`test/${subPath}.test.js`,
|
|
5132
|
+
`test/${baseName}.test.ts`,
|
|
5133
|
+
`test/${baseName}.test.js`,
|
|
5134
|
+
`src/${subPath}.test.ts`,
|
|
5135
|
+
`src/${subPath}.spec.ts`,
|
|
5136
|
+
`${withoutExt}.test.ts`,
|
|
5137
|
+
`${withoutExt}.spec.ts`,
|
|
5138
|
+
`tests/test_${baseName}.py`,
|
|
5139
|
+
`test_${baseName}.py`,
|
|
5140
|
+
`tests/${baseName}_test.go`
|
|
5141
|
+
];
|
|
5142
|
+
for (const candidate of candidatePaths) {
|
|
5143
|
+
if (existsSync14(join8(this.cwd, candidate))) {
|
|
5144
|
+
matchedTests.add(candidate);
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
return Array.from(matchedTests);
|
|
5149
|
+
}
|
|
5150
|
+
resolveTargetedTestCommand(testFiles) {
|
|
5151
|
+
if (testFiles.length === 0)
|
|
5152
|
+
return null;
|
|
5153
|
+
const quotedFiles = testFiles.map((f) => f.includes(" ") ? `"${f}"` : f).join(" ");
|
|
5154
|
+
if (existsSync14(join8(this.cwd, "bun.lockb")) || existsSync14(join8(this.cwd, "bun.lock"))) {
|
|
5155
|
+
return `bun test ${quotedFiles}`;
|
|
5156
|
+
}
|
|
5157
|
+
const pkgPath = join8(this.cwd, "package.json");
|
|
5158
|
+
if (existsSync14(pkgPath)) {
|
|
5159
|
+
try {
|
|
5160
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
5161
|
+
const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
|
|
5162
|
+
if (allDeps.vitest) {
|
|
5163
|
+
return `npx vitest run ${quotedFiles}`;
|
|
5164
|
+
}
|
|
5165
|
+
if (allDeps.jest) {
|
|
5166
|
+
return `npx jest ${quotedFiles}`;
|
|
5167
|
+
}
|
|
5168
|
+
const pm = existsSync14(join8(this.cwd, "pnpm-lock.yaml")) ? "pnpm" : existsSync14(join8(this.cwd, "yarn.lock")) ? "yarn" : "npm";
|
|
5169
|
+
if (pkg.scripts?.test) {
|
|
5170
|
+
if (pkg.scripts.test.includes("bun test")) {
|
|
5171
|
+
return `bun test ${quotedFiles}`;
|
|
5172
|
+
}
|
|
5173
|
+
return `${pm} test -- ${quotedFiles}`;
|
|
5174
|
+
}
|
|
5175
|
+
} catch (err) {
|
|
5176
|
+
console.warn(`[AutoVerifier] Failed to parse package.json for test runner:`, err);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
if (existsSync14(join8(this.cwd, "pyproject.toml")) || existsSync14(join8(this.cwd, "requirements.txt"))) {
|
|
5180
|
+
if (existsSync14(join8(this.cwd, "uv.lock"))) {
|
|
5181
|
+
return `uv run pytest ${quotedFiles}`;
|
|
5182
|
+
}
|
|
5183
|
+
return `pytest ${quotedFiles}`;
|
|
5184
|
+
}
|
|
5185
|
+
if (existsSync14(join8(this.cwd, "go.mod"))) {
|
|
5186
|
+
return `go test ${quotedFiles}`;
|
|
4746
5187
|
}
|
|
5188
|
+
if (existsSync14(join8(this.cwd, "Cargo.toml"))) {
|
|
5189
|
+
return `cargo test ${quotedFiles}`;
|
|
5190
|
+
}
|
|
5191
|
+
return null;
|
|
5192
|
+
}
|
|
5193
|
+
resolveStaticCommand() {
|
|
4747
5194
|
try {
|
|
4748
5195
|
const analyzer = new ProjectAnalyzer(this.cwd);
|
|
4749
5196
|
const analysis = analyzer.analyze();
|
|
4750
5197
|
if (analysis.commands.typecheck) {
|
|
4751
5198
|
return analysis.commands.typecheck;
|
|
4752
5199
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
if (analysis.commands.test) {
|
|
4757
|
-
return analysis.commands.test;
|
|
4758
|
-
}
|
|
4759
|
-
} catch {}
|
|
5200
|
+
} catch (err) {
|
|
5201
|
+
console.warn(`[AutoVerifier] ProjectAnalyzer analysis error:`, err);
|
|
5202
|
+
}
|
|
4760
5203
|
const pkgPath = join8(this.cwd, "package.json");
|
|
4761
5204
|
if (existsSync14(pkgPath)) {
|
|
4762
5205
|
try {
|
|
4763
5206
|
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
4764
5207
|
if (pkg.scripts) {
|
|
5208
|
+
const pm = existsSync14(join8(this.cwd, "bun.lockb")) || existsSync14(join8(this.cwd, "bun.lock")) ? "bun" : existsSync14(join8(this.cwd, "pnpm-lock.yaml")) ? "pnpm" : existsSync14(join8(this.cwd, "yarn.lock")) ? "yarn" : "npm";
|
|
5209
|
+
const runCmd = pm === "bun" || pm === "pnpm" || pm === "yarn" ? `${pm} run` : "npm run";
|
|
4765
5210
|
if (pkg.scripts.typecheck)
|
|
4766
|
-
return
|
|
5211
|
+
return `${runCmd} typecheck`;
|
|
4767
5212
|
if (pkg.scripts.check)
|
|
4768
|
-
return
|
|
4769
|
-
if (pkg.scripts.test)
|
|
4770
|
-
return "npm test";
|
|
5213
|
+
return `${runCmd} check`;
|
|
4771
5214
|
}
|
|
4772
|
-
} catch {
|
|
5215
|
+
} catch (err) {
|
|
5216
|
+
console.warn(`[AutoVerifier] Failed to parse package.json for static check:`, err);
|
|
5217
|
+
}
|
|
4773
5218
|
}
|
|
4774
5219
|
if (existsSync14(join8(this.cwd, "tsconfig.json"))) {
|
|
4775
5220
|
return "npx tsc --noEmit";
|
|
@@ -4780,17 +5225,76 @@ class AutoVerifier {
|
|
|
4780
5225
|
if (existsSync14(join8(this.cwd, "go.mod"))) {
|
|
4781
5226
|
return "go vet ./...";
|
|
4782
5227
|
}
|
|
4783
|
-
if (existsSync14(join8(this.cwd, "
|
|
4784
|
-
|
|
4785
|
-
return "mypy .";
|
|
4786
|
-
}
|
|
5228
|
+
if (existsSync14(join8(this.cwd, "mypy.ini")) || existsSync14(join8(this.cwd, ".mypy.ini"))) {
|
|
5229
|
+
return "mypy .";
|
|
4787
5230
|
}
|
|
4788
5231
|
return null;
|
|
4789
5232
|
}
|
|
4790
|
-
|
|
4791
|
-
|
|
5233
|
+
resolveVerificationCommand(modifiedFiles = []) {
|
|
5234
|
+
if (this.customCommand && this.customCommand.trim()) {
|
|
5235
|
+
return this.customCommand.trim();
|
|
5236
|
+
}
|
|
5237
|
+
const targetedTests = this.findTargetedTests(modifiedFiles);
|
|
5238
|
+
const targetedTestCmd = this.resolveTargetedTestCommand(targetedTests);
|
|
5239
|
+
const staticCmd = this.resolveStaticCommand();
|
|
5240
|
+
if (targetedTestCmd && staticCmd) {
|
|
5241
|
+
return `${staticCmd} && ${targetedTestCmd}`;
|
|
5242
|
+
}
|
|
5243
|
+
if (targetedTestCmd) {
|
|
5244
|
+
return targetedTestCmd;
|
|
5245
|
+
}
|
|
5246
|
+
if (staticCmd) {
|
|
5247
|
+
return staticCmd;
|
|
5248
|
+
}
|
|
5249
|
+
try {
|
|
5250
|
+
const analyzer = new ProjectAnalyzer(this.cwd);
|
|
5251
|
+
const analysis = analyzer.analyze();
|
|
5252
|
+
if (analysis.commands.test)
|
|
5253
|
+
return analysis.commands.test;
|
|
5254
|
+
if (analysis.commands.lint)
|
|
5255
|
+
return analysis.commands.lint;
|
|
5256
|
+
} catch {}
|
|
5257
|
+
const pkgPath = join8(this.cwd, "package.json");
|
|
5258
|
+
if (existsSync14(pkgPath)) {
|
|
5259
|
+
try {
|
|
5260
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
5261
|
+
if (pkg.scripts?.test) {
|
|
5262
|
+
const pm = existsSync14(join8(this.cwd, "bun.lockb")) || existsSync14(join8(this.cwd, "bun.lock")) ? "bun" : "npm";
|
|
5263
|
+
return pm === "bun" ? "bun test" : "npm test";
|
|
5264
|
+
}
|
|
5265
|
+
if (pkg.scripts?.lint) {
|
|
5266
|
+
return "npm run lint";
|
|
5267
|
+
}
|
|
5268
|
+
} catch {}
|
|
5269
|
+
}
|
|
5270
|
+
return null;
|
|
5271
|
+
}
|
|
5272
|
+
resolveVerificationStages(modifiedFiles = []) {
|
|
5273
|
+
if (this.customCommand && this.customCommand.trim()) {
|
|
5274
|
+
return [this.customCommand.trim()];
|
|
5275
|
+
}
|
|
5276
|
+
const stages = [];
|
|
5277
|
+
const staticCmd = this.resolveStaticCommand();
|
|
5278
|
+
const targetedTests = this.findTargetedTests(modifiedFiles);
|
|
5279
|
+
const targetedTestCmd = this.resolveTargetedTestCommand(targetedTests);
|
|
5280
|
+
if (staticCmd) {
|
|
5281
|
+
stages.push(staticCmd);
|
|
5282
|
+
}
|
|
5283
|
+
if (targetedTestCmd) {
|
|
5284
|
+
stages.push(targetedTestCmd);
|
|
5285
|
+
}
|
|
5286
|
+
if (stages.length === 0) {
|
|
5287
|
+
const fallback = this.resolveVerificationCommand(modifiedFiles);
|
|
5288
|
+
if (fallback) {
|
|
5289
|
+
stages.push(fallback);
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
return stages;
|
|
5293
|
+
}
|
|
5294
|
+
async verify(modifiedFiles = [], signal) {
|
|
5295
|
+
const stages = this.resolveVerificationStages(modifiedFiles);
|
|
4792
5296
|
const startTime = performance.now();
|
|
4793
|
-
if (
|
|
5297
|
+
if (stages.length === 0) {
|
|
4794
5298
|
return {
|
|
4795
5299
|
command: "none",
|
|
4796
5300
|
success: true,
|
|
@@ -4800,59 +5304,158 @@ class AutoVerifier {
|
|
|
4800
5304
|
reason: "NO_VERIFIER_DETECTED"
|
|
4801
5305
|
};
|
|
4802
5306
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
5307
|
+
const combinedOutputs = [];
|
|
5308
|
+
let executedCommands = [];
|
|
5309
|
+
for (const cmd of stages) {
|
|
5310
|
+
if (signal?.aborted) {
|
|
5311
|
+
return {
|
|
5312
|
+
command: cmd,
|
|
5313
|
+
success: false,
|
|
5314
|
+
exitCode: 1,
|
|
5315
|
+
output: "Verification was aborted.",
|
|
5316
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
5317
|
+
reason: "ABORTED"
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
executedCommands.push(cmd);
|
|
5321
|
+
const stageResult = await this.executeCommandAsync(cmd, signal);
|
|
5322
|
+
combinedOutputs.push(`[${cmd}]
|
|
5323
|
+
${stageResult.output}`);
|
|
5324
|
+
if (!stageResult.success) {
|
|
5325
|
+
return {
|
|
5326
|
+
command: cmd,
|
|
5327
|
+
success: false,
|
|
5328
|
+
exitCode: stageResult.exitCode,
|
|
5329
|
+
output: this.truncateOutput(combinedOutputs.join(`
|
|
5330
|
+
|
|
5331
|
+
`)),
|
|
5332
|
+
durationMs: Math.round(performance.now() - startTime)
|
|
5333
|
+
};
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
return {
|
|
5337
|
+
command: executedCommands.join(" && "),
|
|
5338
|
+
success: true,
|
|
5339
|
+
exitCode: 0,
|
|
5340
|
+
output: this.truncateOutput(combinedOutputs.join(`
|
|
5341
|
+
|
|
5342
|
+
`)),
|
|
5343
|
+
durationMs: Math.round(performance.now() - startTime)
|
|
5344
|
+
};
|
|
5345
|
+
}
|
|
5346
|
+
executeCommandAsync(command, signal) {
|
|
5347
|
+
return new Promise((resolve) => {
|
|
5348
|
+
let proc = null;
|
|
5349
|
+
let stdoutData = "";
|
|
5350
|
+
let stderrData = "";
|
|
5351
|
+
let isSettled = false;
|
|
5352
|
+
const finish = (success, exitCode, output) => {
|
|
5353
|
+
if (isSettled)
|
|
5354
|
+
return;
|
|
5355
|
+
isSettled = true;
|
|
5356
|
+
cleanup();
|
|
5357
|
+
resolve({ success, exitCode, output: output.trim() });
|
|
5358
|
+
};
|
|
5359
|
+
const killProcessTree = () => {
|
|
5360
|
+
if (!proc || !proc.pid)
|
|
5361
|
+
return;
|
|
5362
|
+
try {
|
|
5363
|
+
if (process.platform === "win32") {
|
|
5364
|
+
spawnSync("taskkill", ["/pid", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
|
|
5365
|
+
} else {
|
|
5366
|
+
proc.kill("SIGTERM");
|
|
5367
|
+
setTimeout(() => {
|
|
5368
|
+
try {
|
|
5369
|
+
proc?.kill("SIGKILL");
|
|
5370
|
+
} catch {}
|
|
5371
|
+
}, 500);
|
|
5372
|
+
}
|
|
5373
|
+
} catch {}
|
|
5374
|
+
};
|
|
5375
|
+
const onAbort = () => {
|
|
5376
|
+
killProcessTree();
|
|
5377
|
+
finish(false, 1, "Verification command aborted by user or session signal.");
|
|
5378
|
+
};
|
|
5379
|
+
if (signal?.aborted) {
|
|
5380
|
+
return finish(false, 1, "Verification aborted before launch.");
|
|
5381
|
+
}
|
|
5382
|
+
if (signal) {
|
|
5383
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5384
|
+
}
|
|
5385
|
+
const timer = setTimeout(() => {
|
|
5386
|
+
killProcessTree();
|
|
5387
|
+
const partial = (stdoutData + `
|
|
5388
|
+
` + stderrData).trim();
|
|
5389
|
+
const timeoutMsg = `[Verification Timeout Error]: Verification command '${command}' exceeded timeout limit of ${this.timeoutMs}ms and was terminated.`;
|
|
5390
|
+
finish(false, 1, partial ? `${partial}
|
|
5391
|
+
|
|
5392
|
+
${timeoutMsg}` : timeoutMsg);
|
|
5393
|
+
}, this.timeoutMs);
|
|
5394
|
+
const cleanup = () => {
|
|
5395
|
+
clearTimeout(timer);
|
|
5396
|
+
if (signal) {
|
|
5397
|
+
signal.removeEventListener("abort", onAbort);
|
|
4815
5398
|
}
|
|
4816
|
-
}
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
5399
|
+
};
|
|
5400
|
+
try {
|
|
5401
|
+
proc = spawn(command, {
|
|
5402
|
+
cwd: this.cwd,
|
|
5403
|
+
shell: true,
|
|
5404
|
+
env: {
|
|
5405
|
+
...process.env,
|
|
5406
|
+
CI: "true",
|
|
5407
|
+
FORCE_COLOR: "0"
|
|
5408
|
+
}
|
|
5409
|
+
});
|
|
5410
|
+
proc.stdout?.on("data", (chunk) => {
|
|
5411
|
+
stdoutData += chunk.toString();
|
|
5412
|
+
});
|
|
5413
|
+
proc.stderr?.on("data", (chunk) => {
|
|
5414
|
+
stderrData += chunk.toString();
|
|
5415
|
+
});
|
|
5416
|
+
proc.on("error", (err) => {
|
|
5417
|
+
const partial = (stdoutData + `
|
|
5418
|
+
` + stderrData).trim();
|
|
5419
|
+
const spawnMsg = `[Verification Process Spawn Error]: Failed to spawn command '${command}': ${err.message}`;
|
|
5420
|
+
finish(false, 1, partial ? `${partial}
|
|
5421
|
+
|
|
5422
|
+
${spawnMsg}` : spawnMsg);
|
|
5423
|
+
});
|
|
5424
|
+
proc.on("close", (code) => {
|
|
5425
|
+
const exitCode = code ?? 0;
|
|
5426
|
+
const combined = (stdoutData + `
|
|
5427
|
+
` + stderrData).trim();
|
|
5428
|
+
if (exitCode === 0) {
|
|
5429
|
+
finish(true, 0, combined || "Verification passed cleanly.");
|
|
5430
|
+
} else {
|
|
5431
|
+
const errorFallback = `[Verification Failure]: Command '${command}' exited with code ${exitCode} and produced no output.`;
|
|
5432
|
+
finish(false, exitCode, combined ? `${combined}
|
|
5433
|
+
|
|
5434
|
+
[Process exited with non-zero code ${exitCode}]` : errorFallback);
|
|
5435
|
+
}
|
|
5436
|
+
});
|
|
5437
|
+
} catch (err) {
|
|
5438
|
+
finish(false, 1, `[Verification Execution Exception]: ${err.message || String(err)}`);
|
|
5439
|
+
}
|
|
5440
|
+
});
|
|
5441
|
+
}
|
|
5442
|
+
truncateOutput(output) {
|
|
5443
|
+
if (output.length > 4000) {
|
|
5444
|
+
const lines = output.split(`
|
|
4824
5445
|
`);
|
|
4825
|
-
|
|
4826
|
-
|
|
5446
|
+
if (lines.length > 70) {
|
|
5447
|
+
const head = lines.slice(0, 35).join(`
|
|
4827
5448
|
`);
|
|
4828
|
-
|
|
5449
|
+
const tail = lines.slice(-30).join(`
|
|
4829
5450
|
`);
|
|
4830
|
-
|
|
5451
|
+
return `${head}
|
|
4831
5452
|
|
|
4832
|
-
... [${lines.length -
|
|
5453
|
+
... [${lines.length - 65} lines truncated for context efficiency] ...
|
|
4833
5454
|
|
|
4834
5455
|
${tail}`;
|
|
4835
|
-
}
|
|
4836
5456
|
}
|
|
4837
|
-
const exitCode = proc.status ?? (proc.error ? 1 : 0);
|
|
4838
|
-
const success = exitCode === 0;
|
|
4839
|
-
return {
|
|
4840
|
-
command,
|
|
4841
|
-
success,
|
|
4842
|
-
exitCode,
|
|
4843
|
-
output: combined || (success ? "Verification succeeded cleanly." : "Command failed with empty output."),
|
|
4844
|
-
durationMs
|
|
4845
|
-
};
|
|
4846
|
-
} catch (err) {
|
|
4847
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
4848
|
-
return {
|
|
4849
|
-
command,
|
|
4850
|
-
success: false,
|
|
4851
|
-
exitCode: 1,
|
|
4852
|
-
output: `Verification execution error: ${err.message || String(err)}`,
|
|
4853
|
-
durationMs
|
|
4854
|
-
};
|
|
4855
5457
|
}
|
|
5458
|
+
return output;
|
|
4856
5459
|
}
|
|
4857
5460
|
}
|
|
4858
5461
|
// src/session/turn.ts
|
|
@@ -4976,6 +5579,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
4976
5579
|
});
|
|
4977
5580
|
}
|
|
4978
5581
|
if (toolCallRequests.length > 0) {
|
|
5582
|
+
const validCalls = [];
|
|
4979
5583
|
for (const toolCall of toolCallRequests) {
|
|
4980
5584
|
if (!toolCall.name || !toolCall.name.trim())
|
|
4981
5585
|
continue;
|
|
@@ -4999,6 +5603,14 @@ async function runTurn(session, turnContext, input) {
|
|
|
4999
5603
|
toolName: toolCall.name,
|
|
5000
5604
|
arguments: toolCall.arguments
|
|
5001
5605
|
});
|
|
5606
|
+
validCalls.push({
|
|
5607
|
+
callId: toolCall.callId,
|
|
5608
|
+
name: toolCall.name,
|
|
5609
|
+
arguments: toolCall.arguments,
|
|
5610
|
+
functionCallItem
|
|
5611
|
+
});
|
|
5612
|
+
}
|
|
5613
|
+
for (const toolCall of validCalls) {
|
|
5002
5614
|
const toolResult = await turnContext.tools.execute(toolCall.name, toolCall.arguments, {
|
|
5003
5615
|
cwd: turnContext.environment.cwd,
|
|
5004
5616
|
turnId,
|
|
@@ -5006,6 +5618,12 @@ async function runTurn(session, turnContext, input) {
|
|
|
5006
5618
|
execPolicy: session.execPolicy,
|
|
5007
5619
|
mode: session.collaborationMode,
|
|
5008
5620
|
permissionMode: session.permissionMode,
|
|
5621
|
+
onFileModified: (p) => {
|
|
5622
|
+
if (p) {
|
|
5623
|
+
modifiedFiles.add(p);
|
|
5624
|
+
hasRunVerification = false;
|
|
5625
|
+
}
|
|
5626
|
+
},
|
|
5009
5627
|
onPlanUpdate: (plan, explanation) => {
|
|
5010
5628
|
session.emitEvent({
|
|
5011
5629
|
type: "PlanUpdated",
|
|
@@ -5021,7 +5639,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
5021
5639
|
turnId,
|
|
5022
5640
|
toolName: toolCall.name,
|
|
5023
5641
|
description,
|
|
5024
|
-
command
|
|
5642
|
+
command,
|
|
5643
|
+
prefixRule
|
|
5025
5644
|
});
|
|
5026
5645
|
},
|
|
5027
5646
|
requestInput: async (question, options) => {
|
|
@@ -5072,7 +5691,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
5072
5691
|
cwd,
|
|
5073
5692
|
customCommand: session.autoVerificationCommand
|
|
5074
5693
|
});
|
|
5075
|
-
const command = verifier.resolveVerificationCommand();
|
|
5694
|
+
const command = verifier.resolveVerificationCommand(Array.from(modifiedFiles));
|
|
5076
5695
|
if (command) {
|
|
5077
5696
|
session.emitEvent({
|
|
5078
5697
|
type: "VerificationStarted",
|
|
@@ -5080,7 +5699,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
5080
5699
|
command,
|
|
5081
5700
|
modifiedFiles: Array.from(modifiedFiles)
|
|
5082
5701
|
});
|
|
5083
|
-
const vResult = verifier.verify(Array.from(modifiedFiles));
|
|
5702
|
+
const vResult = await verifier.verify(Array.from(modifiedFiles), signal);
|
|
5084
5703
|
session.emitEvent({
|
|
5085
5704
|
type: "VerificationCompleted",
|
|
5086
5705
|
turnId,
|
|
@@ -5373,7 +5992,8 @@ class Session {
|
|
|
5373
5992
|
turnId: params.turnId,
|
|
5374
5993
|
toolName: params.toolName,
|
|
5375
5994
|
description: params.description,
|
|
5376
|
-
command: params.command
|
|
5995
|
+
command: params.command,
|
|
5996
|
+
prefixRule: params.prefixRule
|
|
5377
5997
|
});
|
|
5378
5998
|
this.emitEvent({
|
|
5379
5999
|
type: "StatusChanged",
|
|
@@ -9008,7 +9628,30 @@ class AgentRoleRegistry {
|
|
|
9008
9628
|
name: "default",
|
|
9009
9629
|
description: "Generalist autonomous developer agent capable of coding, testing, and debugging.",
|
|
9010
9630
|
systemPrompt: "You are Groupy, an expert autonomous software engineer. Think carefully, use tools surgically, and verify every step.",
|
|
9011
|
-
nicknameCandidates: ["Pikaa", "Heca", "Bankli", "Moli"]
|
|
9631
|
+
nicknameCandidates: ["Pikaa", "Heca", "Bankli", "Moli"],
|
|
9632
|
+
allowedToolNames: [
|
|
9633
|
+
"read_file",
|
|
9634
|
+
"view_file",
|
|
9635
|
+
"write_file",
|
|
9636
|
+
"apply_patch",
|
|
9637
|
+
"list_dir",
|
|
9638
|
+
"shell",
|
|
9639
|
+
"update_plan",
|
|
9640
|
+
"ask_question",
|
|
9641
|
+
"request_user_input",
|
|
9642
|
+
"remember",
|
|
9643
|
+
"list_memories",
|
|
9644
|
+
"read_memory",
|
|
9645
|
+
"save_memory",
|
|
9646
|
+
"spawn_agent",
|
|
9647
|
+
"wait_agent",
|
|
9648
|
+
"send_input",
|
|
9649
|
+
"close_agent",
|
|
9650
|
+
"list_agents",
|
|
9651
|
+
"create_worktree",
|
|
9652
|
+
"list_worktrees",
|
|
9653
|
+
"merge_worktree"
|
|
9654
|
+
]
|
|
9012
9655
|
});
|
|
9013
9656
|
this.registerRole({
|
|
9014
9657
|
name: "reviewer",
|
|
@@ -9105,7 +9748,13 @@ class AgentRoleRegistry {
|
|
|
9105
9748
|
if (!role || !role.allowedToolNames)
|
|
9106
9749
|
return sourceRouter;
|
|
9107
9750
|
const filtered = sourceRouter.list().filter((t) => {
|
|
9108
|
-
return role.allowedToolNames.some((allowed) =>
|
|
9751
|
+
return role.allowedToolNames.some((allowed) => {
|
|
9752
|
+
if (t.name === allowed)
|
|
9753
|
+
return true;
|
|
9754
|
+
if (allowed.endsWith("*") && t.name.startsWith(allowed.slice(0, -1)))
|
|
9755
|
+
return true;
|
|
9756
|
+
return false;
|
|
9757
|
+
});
|
|
9109
9758
|
});
|
|
9110
9759
|
const newRouter = new sourceRouter.constructor;
|
|
9111
9760
|
for (const tool of filtered) {
|
|
@@ -9128,7 +9777,11 @@ class AgentGraphStore {
|
|
|
9128
9777
|
if (dbPath !== ":memory:") {
|
|
9129
9778
|
const dir = resolve19(dbPath, "..");
|
|
9130
9779
|
if (!existsSync19(dir)) {
|
|
9131
|
-
|
|
9780
|
+
try {
|
|
9781
|
+
mkdirSync10(dir, { recursive: true });
|
|
9782
|
+
} catch (err) {
|
|
9783
|
+
console.warn(`[AgentGraphStore] Failed to create database directory '${dir}':`, err);
|
|
9784
|
+
}
|
|
9132
9785
|
}
|
|
9133
9786
|
}
|
|
9134
9787
|
this.db = new Database3(dbPath);
|
|
@@ -9210,24 +9863,44 @@ class AgentGraphStore {
|
|
|
9210
9863
|
close() {
|
|
9211
9864
|
try {
|
|
9212
9865
|
this.db.close();
|
|
9213
|
-
} catch {
|
|
9866
|
+
} catch (err) {
|
|
9867
|
+
console.warn("[AgentGraphStore] Failed to close database cleanly:", err);
|
|
9868
|
+
}
|
|
9214
9869
|
}
|
|
9215
9870
|
}
|
|
9216
9871
|
// src/agents/spawner.ts
|
|
9217
9872
|
class AgentSpawner {
|
|
9218
9873
|
parentSession;
|
|
9874
|
+
depth;
|
|
9219
9875
|
subAgents = new Map;
|
|
9220
9876
|
nextAgentId = 1;
|
|
9221
9877
|
roleRegistry;
|
|
9222
9878
|
parentIdentity;
|
|
9223
9879
|
graphStore;
|
|
9224
|
-
|
|
9880
|
+
maxConcurrentAgents;
|
|
9881
|
+
maxDepth;
|
|
9882
|
+
maxRetainedCompleted;
|
|
9883
|
+
defaultTokenBudget;
|
|
9884
|
+
constructor(parentSession, roleRegistry, parentIdentity, graphStore, options, depth = 0) {
|
|
9225
9885
|
this.parentSession = parentSession;
|
|
9886
|
+
this.depth = depth;
|
|
9226
9887
|
this.roleRegistry = roleRegistry || new AgentRoleRegistry;
|
|
9227
9888
|
this.parentIdentity = parentIdentity || createAgentIdentity(undefined, "groupy-main");
|
|
9228
9889
|
this.graphStore = graphStore || new AgentGraphStore;
|
|
9890
|
+
const envMax = process.env.PIKAA_MAX_SUBAGENTS ? parseInt(process.env.PIKAA_MAX_SUBAGENTS, 10) : NaN;
|
|
9891
|
+
this.maxConcurrentAgents = options?.maxConcurrentAgents ?? (!isNaN(envMax) && envMax > 0 ? envMax : 5);
|
|
9892
|
+
this.maxDepth = options?.maxDepth ?? 2;
|
|
9893
|
+
this.maxRetainedCompleted = options?.maxRetainedCompleted ?? 20;
|
|
9894
|
+
this.defaultTokenBudget = options?.defaultTokenBudget ?? 50000;
|
|
9229
9895
|
}
|
|
9230
9896
|
async spawnAgent(params) {
|
|
9897
|
+
if (this.depth >= this.maxDepth) {
|
|
9898
|
+
throw new GroupyError(`Recursion limit exceeded: Maximum sub-agent nesting depth (${this.maxDepth}) reached.`);
|
|
9899
|
+
}
|
|
9900
|
+
const activeRunningCount = Array.from(this.subAgents.values()).filter((h) => h.status === "running").length;
|
|
9901
|
+
if (activeRunningCount >= this.maxConcurrentAgents) {
|
|
9902
|
+
throw new GroupyError(`Resource limit exceeded: Maximum concurrent sub-agents limit (${this.maxConcurrentAgents}) reached. Please wait for running sub-agents to complete or close them.`);
|
|
9903
|
+
}
|
|
9231
9904
|
const roleName = params.role || "default";
|
|
9232
9905
|
const roleConfig = this.roleRegistry.getRole(roleName);
|
|
9233
9906
|
const agentIndex = this.nextAgentId++;
|
|
@@ -9239,7 +9912,18 @@ class AgentSpawner {
|
|
|
9239
9912
|
|
|
9240
9913
|
Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus strictly on this task.` : `You are a specialized sub-agent named ${nickname} tasked with: '${params.taskName}'. Focus strictly on this task.`);
|
|
9241
9914
|
const baseTools = params.tools || this.parentSession.tools;
|
|
9242
|
-
|
|
9915
|
+
let effectiveTools = this.roleRegistry.filterRouterForRole(baseTools, roleName);
|
|
9916
|
+
if (this.depth + 1 >= this.maxDepth) {
|
|
9917
|
+
const sanitizedRouter = new ToolRouter;
|
|
9918
|
+
for (const t of effectiveTools.list()) {
|
|
9919
|
+
if (t.name !== "spawn_agent") {
|
|
9920
|
+
sanitizedRouter.register(t);
|
|
9921
|
+
}
|
|
9922
|
+
}
|
|
9923
|
+
effectiveTools = sanitizedRouter;
|
|
9924
|
+
}
|
|
9925
|
+
const tokenBudget = params.maxTokens ?? this.defaultTokenBudget;
|
|
9926
|
+
let accumulatedTokens = 0;
|
|
9243
9927
|
const childSession = new Session({
|
|
9244
9928
|
threadId: agentId,
|
|
9245
9929
|
model: effectiveModel,
|
|
@@ -9263,45 +9947,87 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
9263
9947
|
createdAt: Date.now(),
|
|
9264
9948
|
identity: childIdentity,
|
|
9265
9949
|
session: childSession,
|
|
9266
|
-
promise: taskPromise
|
|
9950
|
+
promise: taskPromise,
|
|
9951
|
+
depth: this.depth + 1,
|
|
9952
|
+
tokenBudget,
|
|
9953
|
+
totalTokens: 0
|
|
9267
9954
|
};
|
|
9268
9955
|
try {
|
|
9269
9956
|
this.graphStore.upsertEdge(this.parentSession.threadId, agentId, "open");
|
|
9270
|
-
} catch {
|
|
9957
|
+
} catch (err) {
|
|
9958
|
+
console.warn(`[AgentSpawner] Failed to record edge in graph store for agent '${agentId}':`, err);
|
|
9959
|
+
}
|
|
9271
9960
|
let collectedAgentText = "";
|
|
9272
9961
|
childSession.onEvent((event) => {
|
|
9273
9962
|
if (event.msg.type === "AgentMessageDelta") {
|
|
9274
9963
|
collectedAgentText += event.msg.delta;
|
|
9964
|
+
accumulatedTokens += Math.ceil(event.msg.delta.length / 4);
|
|
9965
|
+
handle.totalTokens = accumulatedTokens;
|
|
9966
|
+
if (accumulatedTokens > tokenBudget && handle.status === "running") {
|
|
9967
|
+
handle.status = "error";
|
|
9968
|
+
handle.error = `Token budget limit exceeded (${tokenBudget} tokens).`;
|
|
9969
|
+
childSession.interrupt();
|
|
9970
|
+
try {
|
|
9971
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
9972
|
+
} catch (err) {
|
|
9973
|
+
console.warn(`[AgentSpawner] Failed to update edge status for agent '${agentId}':`, err);
|
|
9974
|
+
}
|
|
9975
|
+
rejectPromise(new Error(handle.error));
|
|
9976
|
+
}
|
|
9275
9977
|
} else if (event.msg.type === "TurnCompleted") {
|
|
9276
|
-
handle.status
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9978
|
+
if (handle.status === "running") {
|
|
9979
|
+
handle.status = "completed";
|
|
9980
|
+
handle.lastOutput = collectedAgentText.trim();
|
|
9981
|
+
handle.totalTokens = accumulatedTokens;
|
|
9982
|
+
try {
|
|
9983
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
9984
|
+
} catch (err) {
|
|
9985
|
+
console.warn(`[AgentSpawner] Failed to update edge status for agent '${agentId}':`, err);
|
|
9986
|
+
}
|
|
9987
|
+
this.pruneCompletedAgents();
|
|
9988
|
+
resolvePromise(handle.lastOutput);
|
|
9989
|
+
}
|
|
9282
9990
|
} else if (event.msg.type === "Error") {
|
|
9283
|
-
handle.status
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9991
|
+
if (handle.status === "running") {
|
|
9992
|
+
handle.status = "error";
|
|
9993
|
+
handle.error = event.msg.message;
|
|
9994
|
+
handle.totalTokens = accumulatedTokens;
|
|
9995
|
+
try {
|
|
9996
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
9997
|
+
} catch (err) {
|
|
9998
|
+
console.warn(`[AgentSpawner] Failed to update edge status for agent '${agentId}':`, err);
|
|
9999
|
+
}
|
|
10000
|
+
this.pruneCompletedAgents();
|
|
10001
|
+
rejectPromise(new Error(event.msg.message));
|
|
10002
|
+
}
|
|
9289
10003
|
} else if (event.msg.type === "StatusChanged" && event.msg.status === "interrupted") {
|
|
9290
|
-
handle.status
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
10004
|
+
if (handle.status === "running") {
|
|
10005
|
+
handle.status = "interrupted";
|
|
10006
|
+
handle.totalTokens = accumulatedTokens;
|
|
10007
|
+
try {
|
|
10008
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
10009
|
+
} catch (err) {
|
|
10010
|
+
console.warn(`[AgentSpawner] Failed to update edge status for agent '${agentId}':`, err);
|
|
10011
|
+
}
|
|
10012
|
+
this.pruneCompletedAgents();
|
|
10013
|
+
resolvePromise(collectedAgentText.trim() || "[Task was interrupted]");
|
|
10014
|
+
}
|
|
9295
10015
|
}
|
|
9296
10016
|
});
|
|
9297
10017
|
this.subAgents.set(agentId, handle);
|
|
9298
10018
|
childSession.prompt(params.message).catch((err) => {
|
|
9299
|
-
handle.status
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
10019
|
+
if (handle.status === "running") {
|
|
10020
|
+
handle.status = "error";
|
|
10021
|
+
handle.error = err instanceof Error ? err.message : String(err);
|
|
10022
|
+
handle.totalTokens = accumulatedTokens;
|
|
10023
|
+
try {
|
|
10024
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
10025
|
+
} catch (storeErr) {
|
|
10026
|
+
console.warn(`[AgentSpawner] Failed to update edge status for agent '${agentId}':`, storeErr);
|
|
10027
|
+
}
|
|
10028
|
+
this.pruneCompletedAgents();
|
|
10029
|
+
rejectPromise(err);
|
|
10030
|
+
}
|
|
9305
10031
|
});
|
|
9306
10032
|
return {
|
|
9307
10033
|
id: handle.id,
|
|
@@ -9310,9 +10036,22 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
9310
10036
|
role: handle.role,
|
|
9311
10037
|
status: handle.status,
|
|
9312
10038
|
createdAt: handle.createdAt,
|
|
9313
|
-
agentRuntimeId: childIdentity.agentRuntimeId
|
|
10039
|
+
agentRuntimeId: childIdentity.agentRuntimeId,
|
|
10040
|
+
depth: handle.depth,
|
|
10041
|
+
tokenBudget: handle.tokenBudget,
|
|
10042
|
+
totalTokens: handle.totalTokens
|
|
9314
10043
|
};
|
|
9315
10044
|
}
|
|
10045
|
+
pruneCompletedAgents() {
|
|
10046
|
+
const finishedHandles = Array.from(this.subAgents.values()).filter((h) => h.status !== "running");
|
|
10047
|
+
if (finishedHandles.length > this.maxRetainedCompleted) {
|
|
10048
|
+
finishedHandles.sort((a, b) => a.createdAt - b.createdAt);
|
|
10049
|
+
const toRemove = finishedHandles.slice(0, finishedHandles.length - this.maxRetainedCompleted);
|
|
10050
|
+
for (const h of toRemove) {
|
|
10051
|
+
this.subAgents.delete(h.id);
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
9316
10055
|
async waitAgent(agentIdOrTaskName, timeoutMs = 60000) {
|
|
9317
10056
|
if (!agentIdOrTaskName) {
|
|
9318
10057
|
const handles = Array.from(this.subAgents.values());
|
|
@@ -9366,9 +10105,36 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
9366
10105
|
handle.status = "interrupted";
|
|
9367
10106
|
try {
|
|
9368
10107
|
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
9369
|
-
} catch {
|
|
10108
|
+
} catch (err) {
|
|
10109
|
+
console.warn(`[AgentSpawner] Failed to close edge for agent '${agentId}':`, err);
|
|
10110
|
+
}
|
|
10111
|
+
this.pruneCompletedAgents();
|
|
9370
10112
|
return `Sub-agent ${handle.nickname} (${agentId}) interrupted and closed.`;
|
|
9371
10113
|
}
|
|
10114
|
+
removeAgent(agentId) {
|
|
10115
|
+
const handle = this.subAgents.get(agentId);
|
|
10116
|
+
if (!handle)
|
|
10117
|
+
return false;
|
|
10118
|
+
if (handle.status === "running") {
|
|
10119
|
+
handle.session.interrupt();
|
|
10120
|
+
}
|
|
10121
|
+
try {
|
|
10122
|
+
this.graphStore.setEdgeStatus(agentId, "closed");
|
|
10123
|
+
} catch (err) {
|
|
10124
|
+
console.warn(`[AgentSpawner] Failed to close edge on removal for '${agentId}':`, err);
|
|
10125
|
+
}
|
|
10126
|
+
return this.subAgents.delete(agentId);
|
|
10127
|
+
}
|
|
10128
|
+
clearCompleted() {
|
|
10129
|
+
let cleared = 0;
|
|
10130
|
+
for (const [id, handle] of this.subAgents.entries()) {
|
|
10131
|
+
if (handle.status !== "running") {
|
|
10132
|
+
this.subAgents.delete(id);
|
|
10133
|
+
cleared++;
|
|
10134
|
+
}
|
|
10135
|
+
}
|
|
10136
|
+
return cleared;
|
|
10137
|
+
}
|
|
9372
10138
|
listAgents() {
|
|
9373
10139
|
const list = [];
|
|
9374
10140
|
for (const handle of this.subAgents.values()) {
|
|
@@ -9380,7 +10146,10 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
9380
10146
|
status: handle.status,
|
|
9381
10147
|
createdAt: handle.createdAt,
|
|
9382
10148
|
agentRuntimeId: handle.identity.agentRuntimeId,
|
|
9383
|
-
lastOutput: handle.lastOutput
|
|
10149
|
+
lastOutput: handle.lastOutput,
|
|
10150
|
+
depth: handle.depth,
|
|
10151
|
+
tokenBudget: handle.tokenBudget,
|
|
10152
|
+
totalTokens: handle.totalTokens
|
|
9384
10153
|
});
|
|
9385
10154
|
}
|
|
9386
10155
|
return list;
|
|
@@ -9413,6 +10182,10 @@ function createMultiAgentTools(spawner) {
|
|
|
9413
10182
|
model: {
|
|
9414
10183
|
type: "string",
|
|
9415
10184
|
description: "Optional model override. Defaults to inheriting parent model."
|
|
10185
|
+
},
|
|
10186
|
+
max_tokens: {
|
|
10187
|
+
type: "number",
|
|
10188
|
+
description: "Optional token budget limit for the sub-agent (default: 50000)."
|
|
9416
10189
|
}
|
|
9417
10190
|
},
|
|
9418
10191
|
required: ["task_name", "message"]
|
|
@@ -9422,8 +10195,9 @@ function createMultiAgentTools(spawner) {
|
|
|
9422
10195
|
const message = String(args.message || "");
|
|
9423
10196
|
const role = args.role ? String(args.role) : undefined;
|
|
9424
10197
|
const model = args.model ? String(args.model) : undefined;
|
|
10198
|
+
const maxTokens = typeof args.max_tokens === "number" ? args.max_tokens : undefined;
|
|
9425
10199
|
try {
|
|
9426
|
-
const handle = await spawner.spawnAgent({ taskName, message, role, model });
|
|
10200
|
+
const handle = await spawner.spawnAgent({ taskName, message, role, model, maxTokens });
|
|
9427
10201
|
return {
|
|
9428
10202
|
output: `Successfully spawned sub-agent '${handle.id}' with role '${handle.role}' for task '${handle.taskName}' (status: ${handle.status}). Use 'wait_agent' to collect results.`
|
|
9429
10203
|
};
|
|
@@ -9770,7 +10544,8 @@ class SessionPersistenceManager {
|
|
|
9770
10544
|
loadSession(threadId) {
|
|
9771
10545
|
try {
|
|
9772
10546
|
return this.store.restoreSession(threadId);
|
|
9773
|
-
} catch {
|
|
10547
|
+
} catch (err) {
|
|
10548
|
+
console.warn(`[SessionPersistenceManager] Failed to restore session '${threadId}':`, err);
|
|
9774
10549
|
return null;
|
|
9775
10550
|
}
|
|
9776
10551
|
}
|
|
@@ -9778,7 +10553,9 @@ class SessionPersistenceManager {
|
|
|
9778
10553
|
for (const unsub of this.unsubscribers) {
|
|
9779
10554
|
try {
|
|
9780
10555
|
unsub();
|
|
9781
|
-
} catch {
|
|
10556
|
+
} catch (err) {
|
|
10557
|
+
console.warn("[SessionPersistenceManager] Error in unbindSession callback:", err);
|
|
10558
|
+
}
|
|
9782
10559
|
}
|
|
9783
10560
|
this.unsubscribers = [];
|
|
9784
10561
|
}
|
|
@@ -9805,7 +10582,9 @@ class SessionPersistenceManager {
|
|
|
9805
10582
|
for (const unsub of this.unsubscribers) {
|
|
9806
10583
|
try {
|
|
9807
10584
|
unsub();
|
|
9808
|
-
} catch {
|
|
10585
|
+
} catch (err) {
|
|
10586
|
+
console.warn("[SessionPersistenceManager] Error closing session persistence listener:", err);
|
|
10587
|
+
}
|
|
9809
10588
|
}
|
|
9810
10589
|
this.unsubscribers = [];
|
|
9811
10590
|
this.store.close();
|
|
@@ -10070,7 +10849,9 @@ class MemoryStore {
|
|
|
10070
10849
|
if (!existsSync22(dir)) {
|
|
10071
10850
|
try {
|
|
10072
10851
|
mkdirSync12(dir, { recursive: true });
|
|
10073
|
-
} catch {
|
|
10852
|
+
} catch (err) {
|
|
10853
|
+
console.warn(`[MemoryStore] Failed to create custom memory directory '${dir}':`, err);
|
|
10854
|
+
}
|
|
10074
10855
|
}
|
|
10075
10856
|
return dir;
|
|
10076
10857
|
}
|
|
@@ -10079,7 +10860,9 @@ class MemoryStore {
|
|
|
10079
10860
|
if (!existsSync22(dir)) {
|
|
10080
10861
|
try {
|
|
10081
10862
|
mkdirSync12(dir, { recursive: true });
|
|
10082
|
-
} catch {
|
|
10863
|
+
} catch (err) {
|
|
10864
|
+
console.warn(`[MemoryStore] Failed to create project memory directory '${dir}':`, err);
|
|
10865
|
+
}
|
|
10083
10866
|
}
|
|
10084
10867
|
return dir;
|
|
10085
10868
|
}
|
|
@@ -10223,7 +11006,9 @@ class MemoryStore {
|
|
|
10223
11006
|
`)[0] || parsed.name,
|
|
10224
11007
|
file: f
|
|
10225
11008
|
});
|
|
10226
|
-
} catch {
|
|
11009
|
+
} catch (err) {
|
|
11010
|
+
console.warn(`[MemoryStore] Failed to parse topic memory file '${f}':`, err);
|
|
11011
|
+
}
|
|
10227
11012
|
}
|
|
10228
11013
|
const indexLines = [
|
|
10229
11014
|
"# Project Auto-Memory Index",
|
|
@@ -10265,7 +11050,9 @@ class MemoryStore {
|
|
|
10265
11050
|
try {
|
|
10266
11051
|
const full = join13(memoryDir, f);
|
|
10267
11052
|
list.push(this.parseTopicFile(readFileSync13(full, "utf8"), full));
|
|
10268
|
-
} catch {
|
|
11053
|
+
} catch (err) {
|
|
11054
|
+
console.warn(`[MemoryStore] Failed to read topic memory file '${f}':`, err);
|
|
11055
|
+
}
|
|
10269
11056
|
}
|
|
10270
11057
|
return list;
|
|
10271
11058
|
}
|
|
@@ -10785,7 +11572,7 @@ function ClaudeLogo({
|
|
|
10785
11572
|
}, undefined, false, undefined, this);
|
|
10786
11573
|
}
|
|
10787
11574
|
function ClaudeHeader({
|
|
10788
|
-
version = "v0.
|
|
11575
|
+
version = "v0.4.0",
|
|
10789
11576
|
user = "Developer",
|
|
10790
11577
|
model = "Claude 3.7 Sonnet (Thinking)",
|
|
10791
11578
|
plan = "Pro",
|
|
@@ -12234,6 +13021,7 @@ export {
|
|
|
12234
13021
|
CredentialsStore,
|
|
12235
13022
|
DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS,
|
|
12236
13023
|
DEFAULT_MAX_CONTEXT_TOKENS,
|
|
13024
|
+
DEFAULT_MAX_DIR_ENTRIES,
|
|
12237
13025
|
DEFAULT_MAX_UNPAGINATED_LINES,
|
|
12238
13026
|
DEFAULT_WORKTREE_KEEP_COUNT,
|
|
12239
13027
|
DefaultModelClientSession,
|