jinzd-ai-cli 0.4.212 → 0.4.214
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/README.md +12 -2
- package/README.zh-CN.md +12 -2
- package/dist/{batch-WIKFEOAZ.js → batch-NPUUDETK.js} +2 -2
- package/dist/{chunk-HW2ALWQJ.js → chunk-5FG7PSVM.js} +2 -2
- package/dist/{chunk-GBBVCZ4W.js → chunk-DBYMX2ZC.js} +1 -1
- package/dist/{chunk-KE4B3NOQ.js → chunk-JOJA35RK.js} +1 -1
- package/dist/{chunk-LU6FBJQ5.js → chunk-JQC7IATA.js} +1 -1
- package/dist/{chunk-N5LB3PPL.js → chunk-R5CJD2AB.js} +615 -118
- package/dist/{chunk-HX6N6QEY.js → chunk-TR6MSPSS.js} +1 -1
- package/dist/{chunk-NYCBOVNF.js → chunk-YFHF3PVB.js} +1 -1
- package/dist/{chunk-IFZX26K7.js → chunk-YMFGGCUP.js} +1 -1
- package/dist/{ci-X3LNUFZV.js → ci-3TU7WCY4.js} +2 -2
- package/dist/{constants-KDWG4KR4.js → constants-BKR7T3QH.js} +1 -1
- package/dist/{doctor-cli-TJTWIW7U.js → doctor-cli-TGVSWKDB.js} +4 -4
- package/dist/electron-server.js +913 -280
- package/dist/{hub-JWSV4MKC.js → hub-GURYEW6F.js} +1 -1
- package/dist/index.js +80 -18
- package/dist/{run-tests-H4Q2PRX6.js → run-tests-F44JA2YW.js} +2 -2
- package/dist/{run-tests-D7YBY4XB.js → run-tests-R6FL6CQ3.js} +1 -1
- package/dist/{server-VNCN7AY5.js → server-FXWCSCDZ.js} +164 -8
- package/dist/{server-OFKGDRYI.js → server-GQXC46R5.js} +4 -4
- package/dist/{task-orchestrator-AL2E642M.js → task-orchestrator-6AZD5WSR.js} +4 -4
- package/dist/{usage-LB6REWPO.js → usage-W7J2QA3G.js} +2 -2
- package/dist/web/client/app.js +4 -1
- package/package.json +1 -1
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
} from "./chunk-T2NL5ZIA.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TR6MSPSS.js";
|
|
9
9
|
import {
|
|
10
10
|
runTool
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-YMFGGCUP.js";
|
|
12
12
|
import {
|
|
13
13
|
getDangerLevel,
|
|
14
14
|
isFileWriteTool,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
SUBAGENT_ALLOWED_TOOLS,
|
|
27
27
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
28
28
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-DBYMX2ZC.js";
|
|
30
30
|
import {
|
|
31
31
|
fileCheckpoints
|
|
32
32
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -1055,7 +1055,7 @@ import { dirname as dirname2 } from "path";
|
|
|
1055
1055
|
|
|
1056
1056
|
// src/tools/executor.ts
|
|
1057
1057
|
import chalk3 from "chalk";
|
|
1058
|
-
import { existsSync as
|
|
1058
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1059
1059
|
import { tmpdir } from "os";
|
|
1060
1060
|
|
|
1061
1061
|
// src/core/readline-internal.ts
|
|
@@ -1860,6 +1860,157 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
1860
1860
|
}
|
|
1861
1861
|
});
|
|
1862
1862
|
|
|
1863
|
+
// src/tools/action-classifier.ts
|
|
1864
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1865
|
+
import { isAbsolute as isAbsolute2, join as join2, resolve as resolve4 } from "path";
|
|
1866
|
+
var RECENT_DENIED_LIMIT = 50;
|
|
1867
|
+
var recentlyDenied = [];
|
|
1868
|
+
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
1869
|
+
recentlyDenied.unshift({
|
|
1870
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1871
|
+
tool: call.name,
|
|
1872
|
+
reason: classification.reason,
|
|
1873
|
+
ruleId: classification.ruleId,
|
|
1874
|
+
argsPreview: JSON.stringify(call.arguments).slice(0, 240)
|
|
1875
|
+
});
|
|
1876
|
+
recentlyDenied.splice(RECENT_DENIED_LIMIT);
|
|
1877
|
+
}
|
|
1878
|
+
function getRecentlyDeniedAutoActions() {
|
|
1879
|
+
return [...recentlyDenied];
|
|
1880
|
+
}
|
|
1881
|
+
function clearRecentlyDeniedAutoActions() {
|
|
1882
|
+
recentlyDenied.splice(0, recentlyDenied.length);
|
|
1883
|
+
}
|
|
1884
|
+
function normalizePath(value) {
|
|
1885
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1886
|
+
}
|
|
1887
|
+
function isInside(root, target) {
|
|
1888
|
+
const absoluteRoot = resolve4(root);
|
|
1889
|
+
const absoluteTarget = isAbsolute2(target) ? target : resolve4(root, target);
|
|
1890
|
+
const base = normalizePath(absoluteRoot);
|
|
1891
|
+
const t = normalizePath(absoluteTarget);
|
|
1892
|
+
return t === base || t.startsWith(base + "/");
|
|
1893
|
+
}
|
|
1894
|
+
function getPathArg(call) {
|
|
1895
|
+
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
1896
|
+
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
1897
|
+
}
|
|
1898
|
+
function splitShellWords(command) {
|
|
1899
|
+
const out = [];
|
|
1900
|
+
const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
1901
|
+
let m;
|
|
1902
|
+
while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
1903
|
+
return out.filter(Boolean);
|
|
1904
|
+
}
|
|
1905
|
+
function readPackageManifest(root) {
|
|
1906
|
+
const p = join2(root, "package.json");
|
|
1907
|
+
if (!existsSync5(p)) return null;
|
|
1908
|
+
try {
|
|
1909
|
+
return JSON.parse(readFileSync4(p, "utf-8"));
|
|
1910
|
+
} catch {
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function manifestHasDependency(root, name) {
|
|
1915
|
+
const manifest = readPackageManifest(root);
|
|
1916
|
+
if (!manifest) return false;
|
|
1917
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
1918
|
+
const deps = manifest[key];
|
|
1919
|
+
if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
|
|
1920
|
+
}
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
function lockfileMentions(root, name) {
|
|
1924
|
+
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
1925
|
+
const p = join2(root, file);
|
|
1926
|
+
if (!existsSync5(p)) continue;
|
|
1927
|
+
try {
|
|
1928
|
+
if (readFileSync4(p, "utf-8").includes(name)) return true;
|
|
1929
|
+
} catch {
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return false;
|
|
1933
|
+
}
|
|
1934
|
+
function getInstallPackages(command) {
|
|
1935
|
+
const words = splitShellWords(command);
|
|
1936
|
+
const cmd = words[0]?.toLowerCase();
|
|
1937
|
+
const sub = words[1]?.toLowerCase();
|
|
1938
|
+
if (!cmd || !sub) return [];
|
|
1939
|
+
const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
|
|
1940
|
+
if (!isInstall) return [];
|
|
1941
|
+
return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
|
|
1942
|
+
}
|
|
1943
|
+
function isPlainGitPush(command) {
|
|
1944
|
+
if (!/^\s*git\s+push\b/i.test(command)) return false;
|
|
1945
|
+
if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
|
|
1946
|
+
return !/[;&|`$<>\n\r]/.test(command);
|
|
1947
|
+
}
|
|
1948
|
+
function hasSecretExfiltrationShape(command) {
|
|
1949
|
+
return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
|
|
1950
|
+
}
|
|
1951
|
+
var RuleBasedActionClassifier = class {
|
|
1952
|
+
classify(call, dangerLevel, ctx) {
|
|
1953
|
+
if (call.name === "bash") return this.classifyShell(call, dangerLevel, ctx);
|
|
1954
|
+
if (call.name === "web_fetch" || call.name === "web_search" || call.name === "google_search") {
|
|
1955
|
+
return { decision: "auto-approve", reason: "read-only HTTP tool", ruleId: "auto.readonly-http", confidence: "high" };
|
|
1956
|
+
}
|
|
1957
|
+
if (dangerLevel === "safe") {
|
|
1958
|
+
return { decision: "auto-approve", reason: "safe read-only tool", ruleId: "auto.safe-tool", confidence: "high" };
|
|
1959
|
+
}
|
|
1960
|
+
if (dangerLevel === "write") {
|
|
1961
|
+
const p = getPathArg(call);
|
|
1962
|
+
if (p && (isInside(ctx.workspaceRoot, p) || (ctx.tempDirs ?? []).some((root) => isInside(root, p)))) {
|
|
1963
|
+
return { decision: "auto-approve", reason: "explicit write inside workspace/temp roots", ruleId: "auto.workspace-write", confidence: "high" };
|
|
1964
|
+
}
|
|
1965
|
+
return { decision: "confirm", reason: "write action is not proven low-risk", ruleId: "auto.write-confirm", confidence: "medium" };
|
|
1966
|
+
}
|
|
1967
|
+
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
1968
|
+
}
|
|
1969
|
+
classifyShell(call, dangerLevel, ctx) {
|
|
1970
|
+
const command = String(call.arguments.command ?? "");
|
|
1971
|
+
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
1972
|
+
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
1973
|
+
}
|
|
1974
|
+
if (/\bgit\s+push\b/i.test(command) && /--force|-f\b|\+[^\s]+/i.test(command)) {
|
|
1975
|
+
return { decision: "deny", reason: "force push is blocked in auto mode", ruleId: "deny.force-push", confidence: "high" };
|
|
1976
|
+
}
|
|
1977
|
+
if (hasSecretExfiltrationShape(command)) {
|
|
1978
|
+
return { decision: "deny", reason: "possible secret exfiltration is blocked in auto mode", ruleId: "deny.secret-exfiltration", confidence: "high" };
|
|
1979
|
+
}
|
|
1980
|
+
if (/\b(terraform|tofu)\s+destroy\b|\bkubectl\s+delete\b|\bcdk\s+destroy\b/i.test(command)) {
|
|
1981
|
+
return { decision: "confirm", reason: "infrastructure destroy requires confirmation", ruleId: "confirm.iac-destroy", confidence: "high" };
|
|
1982
|
+
}
|
|
1983
|
+
if (/\b(prisma|sequelize|knex|typeorm)\b[^\n]*\b(migrate|migration)\b|\bdb:migrate\b/i.test(command)) {
|
|
1984
|
+
return { decision: "confirm", reason: "database migration requires confirmation", ruleId: "confirm.database-migration", confidence: "high" };
|
|
1985
|
+
}
|
|
1986
|
+
if (/\b(vercel|netlify|flyctl|railway|wrangler|serverless|firebase)\b[^\n]*\bdeploy\b|\bdeploy\b[^\n]*\b(prod|production)\b/i.test(command)) {
|
|
1987
|
+
return { decision: "confirm", reason: "production deployment requires confirmation", ruleId: "confirm.production-deploy", confidence: "high" };
|
|
1988
|
+
}
|
|
1989
|
+
if (/\b(iam|policy|token|secret|api[-_]?key|credential|permission|role)\b/i.test(command)) {
|
|
1990
|
+
return { decision: "confirm", reason: "credential or permission change requires confirmation", ruleId: "confirm.credentials-permissions", confidence: "medium" };
|
|
1991
|
+
}
|
|
1992
|
+
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
1993
|
+
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
1994
|
+
}
|
|
1995
|
+
if (isPlainGitPush(command)) {
|
|
1996
|
+
return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
|
|
1997
|
+
}
|
|
1998
|
+
const pkgs = getInstallPackages(command);
|
|
1999
|
+
if (pkgs.length > 0) {
|
|
2000
|
+
const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
|
|
2001
|
+
return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
|
|
2002
|
+
}
|
|
2003
|
+
if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
|
|
2004
|
+
return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
|
|
2005
|
+
}
|
|
2006
|
+
if (dangerLevel === "safe") {
|
|
2007
|
+
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
2008
|
+
}
|
|
2009
|
+
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
var defaultActionClassifier = new RuleBasedActionClassifier();
|
|
2013
|
+
|
|
1863
2014
|
// src/tools/executor.ts
|
|
1864
2015
|
function buildErrorPreview(content) {
|
|
1865
2016
|
const meaningful = content.split("\n").filter(
|
|
@@ -1899,6 +2050,8 @@ var ToolExecutor = class {
|
|
|
1899
2050
|
* destructive 操作仍然必须逐次确认。
|
|
1900
2051
|
*/
|
|
1901
2052
|
sessionAutoApprove = false;
|
|
2053
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
2054
|
+
sessionAutoMode = false;
|
|
1902
2055
|
/**
|
|
1903
2056
|
* Logical session key used to scope per-session state in stateful tools
|
|
1904
2057
|
* (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
|
|
@@ -2058,6 +2211,40 @@ var ToolExecutor = class {
|
|
|
2058
2211
|
};
|
|
2059
2212
|
}
|
|
2060
2213
|
}
|
|
2214
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
2215
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
2216
|
+
workspaceRoot: this.workspaceRoot,
|
|
2217
|
+
tempDirs: [tmpdir()]
|
|
2218
|
+
});
|
|
2219
|
+
if (classification.decision === "deny") {
|
|
2220
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
2221
|
+
return {
|
|
2222
|
+
callId: call.id,
|
|
2223
|
+
content: `[Auto denied] ${classification.reason}. Do not retry without asking.`,
|
|
2224
|
+
isError: true
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
if (classification.decision === "auto-approve") {
|
|
2228
|
+
this.printToolCall(call);
|
|
2229
|
+
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
2230
|
+
console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
|
|
2231
|
+
try {
|
|
2232
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
2233
|
+
const content = truncateOutput(rawContent, call.name);
|
|
2234
|
+
const wasTruncated = content !== rawContent;
|
|
2235
|
+
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2236
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2237
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
2238
|
+
return { callId: call.id, content, isError: false };
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2241
|
+
this.printToolResult(call.name, message, true, false);
|
|
2242
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2243
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
2244
|
+
return { callId: call.id, content: message, isError: true };
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2061
2248
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
2062
2249
|
this.printToolCall(call);
|
|
2063
2250
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
@@ -2225,7 +2412,7 @@ var ToolExecutor = class {
|
|
|
2225
2412
|
rl.resume();
|
|
2226
2413
|
process.stdout.write(prompt);
|
|
2227
2414
|
this.confirming = true;
|
|
2228
|
-
return new Promise((
|
|
2415
|
+
return new Promise((resolve7) => {
|
|
2229
2416
|
let completed = false;
|
|
2230
2417
|
const cleanup = (result) => {
|
|
2231
2418
|
if (completed) return;
|
|
@@ -2235,7 +2422,7 @@ var ToolExecutor = class {
|
|
|
2235
2422
|
rl.pause();
|
|
2236
2423
|
rlAny.output = savedOutput;
|
|
2237
2424
|
this.confirming = false;
|
|
2238
|
-
|
|
2425
|
+
resolve7(result);
|
|
2239
2426
|
};
|
|
2240
2427
|
const onLine = (line) => {
|
|
2241
2428
|
const trimmed = line.trim();
|
|
@@ -2302,10 +2489,10 @@ var ToolExecutor = class {
|
|
|
2302
2489
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2303
2490
|
const newContent = String(call.arguments["content"] ?? "");
|
|
2304
2491
|
if (!filePath) return;
|
|
2305
|
-
if (
|
|
2492
|
+
if (existsSync6(filePath)) {
|
|
2306
2493
|
let oldContent;
|
|
2307
2494
|
try {
|
|
2308
|
-
oldContent =
|
|
2495
|
+
oldContent = readFileSync5(filePath, "utf-8");
|
|
2309
2496
|
} catch {
|
|
2310
2497
|
return;
|
|
2311
2498
|
}
|
|
@@ -2328,7 +2515,7 @@ var ToolExecutor = class {
|
|
|
2328
2515
|
}
|
|
2329
2516
|
} else if (call.name === "edit_file") {
|
|
2330
2517
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2331
|
-
if (!filePath || !
|
|
2518
|
+
if (!filePath || !existsSync6(filePath)) return;
|
|
2332
2519
|
const oldStr = call.arguments["old_str"];
|
|
2333
2520
|
const newStr = call.arguments["new_str"];
|
|
2334
2521
|
if (oldStr !== void 0) {
|
|
@@ -2355,7 +2542,7 @@ var ToolExecutor = class {
|
|
|
2355
2542
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
2356
2543
|
let fileContent;
|
|
2357
2544
|
try {
|
|
2358
|
-
fileContent =
|
|
2545
|
+
fileContent = readFileSync5(filePath, "utf-8");
|
|
2359
2546
|
} catch {
|
|
2360
2547
|
return;
|
|
2361
2548
|
}
|
|
@@ -2409,7 +2596,7 @@ var ToolExecutor = class {
|
|
|
2409
2596
|
rl.resume();
|
|
2410
2597
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
2411
2598
|
this.confirming = true;
|
|
2412
|
-
return new Promise((
|
|
2599
|
+
return new Promise((resolve7) => {
|
|
2413
2600
|
let completed = false;
|
|
2414
2601
|
const cleanup = (answer) => {
|
|
2415
2602
|
if (completed) return;
|
|
@@ -2419,7 +2606,7 @@ var ToolExecutor = class {
|
|
|
2419
2606
|
rl.pause();
|
|
2420
2607
|
rlAny.output = savedOutput;
|
|
2421
2608
|
this.confirming = false;
|
|
2422
|
-
|
|
2609
|
+
resolve7(answer === "y");
|
|
2423
2610
|
};
|
|
2424
2611
|
const onLine = (line) => {
|
|
2425
2612
|
const trimmed = line.trim();
|
|
@@ -2447,11 +2634,11 @@ var ToolExecutor = class {
|
|
|
2447
2634
|
};
|
|
2448
2635
|
|
|
2449
2636
|
// src/tools/sensitive-paths.ts
|
|
2450
|
-
import { resolve as
|
|
2637
|
+
import { resolve as resolve5, sep as sep2, basename as basename2 } from "path";
|
|
2451
2638
|
import { homedir as homedir2 } from "os";
|
|
2452
2639
|
var home = homedir2();
|
|
2453
2640
|
function norm(p) {
|
|
2454
|
-
const abs =
|
|
2641
|
+
const abs = resolve5(p);
|
|
2455
2642
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
2456
2643
|
}
|
|
2457
2644
|
function homeRel(p) {
|
|
@@ -2610,7 +2797,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2610
2797
|
};
|
|
2611
2798
|
|
|
2612
2799
|
// src/tools/builtin/edit-file.ts
|
|
2613
|
-
import { readFileSync as
|
|
2800
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
2614
2801
|
|
|
2615
2802
|
// src/tools/builtin/patch-apply.ts
|
|
2616
2803
|
function parseUnifiedDiff(patch) {
|
|
@@ -2971,7 +3158,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2971
3158
|
const filePath = String(args["path"] ?? "");
|
|
2972
3159
|
const encoding = args["encoding"] ?? "utf-8";
|
|
2973
3160
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
2974
|
-
if (!
|
|
3161
|
+
if (!existsSync7(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
2975
3162
|
const verdict = classifyWritePath(filePath);
|
|
2976
3163
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
2977
3164
|
throw new ToolError(
|
|
@@ -2979,7 +3166,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2979
3166
|
`Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
|
|
2980
3167
|
);
|
|
2981
3168
|
}
|
|
2982
|
-
const original =
|
|
3169
|
+
const original = readFileSync6(filePath, encoding);
|
|
2983
3170
|
if (args["patch"] !== void 0) {
|
|
2984
3171
|
const patchText = String(args["patch"] ?? "");
|
|
2985
3172
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -3137,8 +3324,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
3137
3324
|
}
|
|
3138
3325
|
|
|
3139
3326
|
// src/tools/builtin/list-dir.ts
|
|
3140
|
-
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as
|
|
3141
|
-
import { join as
|
|
3327
|
+
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync8 } from "fs";
|
|
3328
|
+
import { join as join3, basename as basename3 } from "path";
|
|
3142
3329
|
var listDirTool = {
|
|
3143
3330
|
definition: {
|
|
3144
3331
|
name: "list_dir",
|
|
@@ -3160,7 +3347,7 @@ var listDirTool = {
|
|
|
3160
3347
|
async execute(args) {
|
|
3161
3348
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
3162
3349
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
3163
|
-
if (!
|
|
3350
|
+
if (!existsSync8(dirPath)) {
|
|
3164
3351
|
const targetName = basename3(dirPath).toLowerCase();
|
|
3165
3352
|
const cwd = process.cwd();
|
|
3166
3353
|
const suggestions = [];
|
|
@@ -3218,11 +3405,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
3218
3405
|
if (entry.isDirectory()) {
|
|
3219
3406
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
3220
3407
|
if (recursive) {
|
|
3221
|
-
listRecursive(
|
|
3408
|
+
listRecursive(join3(basePath, entry.name), indent + " ", true, lines);
|
|
3222
3409
|
}
|
|
3223
3410
|
} else {
|
|
3224
3411
|
try {
|
|
3225
|
-
const stat = statSync3(
|
|
3412
|
+
const stat = statSync3(join3(basePath, entry.name));
|
|
3226
3413
|
const size = formatSize(stat.size);
|
|
3227
3414
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
3228
3415
|
} catch {
|
|
@@ -3238,9 +3425,9 @@ function formatSize(bytes) {
|
|
|
3238
3425
|
}
|
|
3239
3426
|
|
|
3240
3427
|
// src/tools/builtin/grep-files.ts
|
|
3241
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
3428
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync4, existsSync as existsSync9 } from "fs";
|
|
3242
3429
|
import { readFile } from "fs/promises";
|
|
3243
|
-
import { join as
|
|
3430
|
+
import { join as join4, relative } from "path";
|
|
3244
3431
|
var grepFilesTool = {
|
|
3245
3432
|
definition: {
|
|
3246
3433
|
name: "grep_files",
|
|
@@ -3291,7 +3478,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
3291
3478
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
3292
3479
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
3293
3480
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
3294
|
-
if (!
|
|
3481
|
+
if (!existsSync9(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
3295
3482
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
3296
3483
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
3297
3484
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -3385,11 +3572,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
3385
3572
|
if (paths.length >= maxFiles) return;
|
|
3386
3573
|
if (entry.isDirectory()) {
|
|
3387
3574
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3388
|
-
walk(
|
|
3575
|
+
walk(join4(dirPath, entry.name));
|
|
3389
3576
|
} else if (entry.isFile()) {
|
|
3390
3577
|
if (isBinary(entry.name)) continue;
|
|
3391
3578
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
3392
|
-
const fullPath =
|
|
3579
|
+
const fullPath = join4(dirPath, entry.name);
|
|
3393
3580
|
try {
|
|
3394
3581
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
3395
3582
|
} catch {
|
|
@@ -3444,7 +3631,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3444
3631
|
}
|
|
3445
3632
|
let content;
|
|
3446
3633
|
try {
|
|
3447
|
-
content =
|
|
3634
|
+
content = readFileSync7(fullPath, "utf-8");
|
|
3448
3635
|
} catch {
|
|
3449
3636
|
return;
|
|
3450
3637
|
}
|
|
@@ -3475,8 +3662,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3475
3662
|
}
|
|
3476
3663
|
|
|
3477
3664
|
// src/tools/builtin/glob-files.ts
|
|
3478
|
-
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as
|
|
3479
|
-
import { join as
|
|
3665
|
+
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync10 } from "fs";
|
|
3666
|
+
import { join as join5, relative as relative2, basename as basename4 } from "path";
|
|
3480
3667
|
var globFilesTool = {
|
|
3481
3668
|
definition: {
|
|
3482
3669
|
name: "glob_files",
|
|
@@ -3509,7 +3696,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
3509
3696
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
3510
3697
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
3511
3698
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
3512
|
-
if (!
|
|
3699
|
+
if (!existsSync10(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
3513
3700
|
const regex = globToRegex(pattern);
|
|
3514
3701
|
const matches = [];
|
|
3515
3702
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -3586,7 +3773,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
3586
3773
|
}
|
|
3587
3774
|
for (const entry of entries) {
|
|
3588
3775
|
if (results.length >= maxResults) break;
|
|
3589
|
-
const fullPath =
|
|
3776
|
+
const fullPath = join5(dirPath, entry.name);
|
|
3590
3777
|
if (entry.isDirectory()) {
|
|
3591
3778
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3592
3779
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -3668,7 +3855,7 @@ var runInteractiveTool = {
|
|
|
3668
3855
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
3669
3856
|
};
|
|
3670
3857
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
3671
|
-
return new Promise((
|
|
3858
|
+
return new Promise((resolve7) => {
|
|
3672
3859
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
3673
3860
|
cwd: process.cwd(),
|
|
3674
3861
|
env,
|
|
@@ -3701,22 +3888,22 @@ var runInteractiveTool = {
|
|
|
3701
3888
|
setTimeout(writeNextLine, 400);
|
|
3702
3889
|
const timer = setTimeout(() => {
|
|
3703
3890
|
child.kill();
|
|
3704
|
-
|
|
3891
|
+
resolve7(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
3705
3892
|
${buildOutput(stdout, stderr)}`);
|
|
3706
3893
|
}, timeout);
|
|
3707
3894
|
child.on("close", (code) => {
|
|
3708
3895
|
clearTimeout(timer);
|
|
3709
3896
|
const output = buildOutput(stdout, stderr);
|
|
3710
3897
|
if (code !== 0 && code !== null) {
|
|
3711
|
-
|
|
3898
|
+
resolve7(`${prefixWarnings}Exit code ${code}:
|
|
3712
3899
|
${output}`);
|
|
3713
3900
|
} else {
|
|
3714
|
-
|
|
3901
|
+
resolve7(`${prefixWarnings}${output || "(no output)"}`);
|
|
3715
3902
|
}
|
|
3716
3903
|
});
|
|
3717
3904
|
child.on("error", (err) => {
|
|
3718
3905
|
clearTimeout(timer);
|
|
3719
|
-
|
|
3906
|
+
resolve7(
|
|
3720
3907
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
3721
3908
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
3722
3909
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -4384,11 +4571,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4384
4571
|
};
|
|
4385
4572
|
|
|
4386
4573
|
// src/tools/builtin/save-memory.ts
|
|
4387
|
-
import { existsSync as
|
|
4388
|
-
import { join as
|
|
4574
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, statSync as statSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
4575
|
+
import { join as join6 } from "path";
|
|
4389
4576
|
import { homedir as homedir3 } from "os";
|
|
4390
4577
|
function getMemoryFilePath() {
|
|
4391
|
-
return
|
|
4578
|
+
return join6(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
4392
4579
|
}
|
|
4393
4580
|
function formatTimestamp() {
|
|
4394
4581
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -4412,8 +4599,8 @@ var saveMemoryTool = {
|
|
|
4412
4599
|
const content = String(args["content"] ?? "").trim();
|
|
4413
4600
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
4414
4601
|
const memoryPath = getMemoryFilePath();
|
|
4415
|
-
const configDir =
|
|
4416
|
-
if (!
|
|
4602
|
+
const configDir = join6(homedir3(), CONFIG_DIR_NAME);
|
|
4603
|
+
if (!existsSync11(configDir)) {
|
|
4417
4604
|
mkdirSync4(configDir, { recursive: true });
|
|
4418
4605
|
}
|
|
4419
4606
|
const timestamp = formatTimestamp();
|
|
@@ -4421,7 +4608,7 @@ var saveMemoryTool = {
|
|
|
4421
4608
|
## ${timestamp}
|
|
4422
4609
|
${content}
|
|
4423
4610
|
`;
|
|
4424
|
-
const previous =
|
|
4611
|
+
const previous = existsSync11(memoryPath) ? readFileSync8(memoryPath, "utf-8") : "";
|
|
4425
4612
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
4426
4613
|
const byteSize = statSync6(memoryPath).size;
|
|
4427
4614
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -4468,7 +4655,7 @@ function promptUser(rl, question) {
|
|
|
4468
4655
|
console.log();
|
|
4469
4656
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
4470
4657
|
process.stdout.write(chalk4.cyan("> "));
|
|
4471
|
-
return new Promise((
|
|
4658
|
+
return new Promise((resolve7) => {
|
|
4472
4659
|
let completed = false;
|
|
4473
4660
|
const cleanup = (answer) => {
|
|
4474
4661
|
if (completed) return;
|
|
@@ -4478,7 +4665,7 @@ function promptUser(rl, question) {
|
|
|
4478
4665
|
rl.pause();
|
|
4479
4666
|
rlAny.output = savedOutput;
|
|
4480
4667
|
askUserContext.prompting = false;
|
|
4481
|
-
|
|
4668
|
+
resolve7(answer);
|
|
4482
4669
|
};
|
|
4483
4670
|
const onLine = (line) => {
|
|
4484
4671
|
cleanup(line);
|
|
@@ -4709,6 +4896,218 @@ function formatResults2(query, data, _requested) {
|
|
|
4709
4896
|
return header + "\n" + results.join("\n\n");
|
|
4710
4897
|
}
|
|
4711
4898
|
|
|
4899
|
+
// src/agents/agent-config.ts
|
|
4900
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
4901
|
+
import { join as join7 } from "path";
|
|
4902
|
+
import { homedir as homedir4 } from "os";
|
|
4903
|
+
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
4904
|
+
"read_file",
|
|
4905
|
+
"list_dir",
|
|
4906
|
+
"grep_files",
|
|
4907
|
+
"glob_files",
|
|
4908
|
+
"web_fetch",
|
|
4909
|
+
"google_search",
|
|
4910
|
+
"write_todos",
|
|
4911
|
+
"find_symbol",
|
|
4912
|
+
"get_outline",
|
|
4913
|
+
"find_references",
|
|
4914
|
+
"search_code"
|
|
4915
|
+
]);
|
|
4916
|
+
var BUILTIN_AGENTS = [
|
|
4917
|
+
{
|
|
4918
|
+
name: "explorer",
|
|
4919
|
+
description: "Read-only code exploration and evidence gathering.",
|
|
4920
|
+
permissionProfile: "read-only",
|
|
4921
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
4922
|
+
maxToolRounds: 10,
|
|
4923
|
+
contextPolicy: "task-only",
|
|
4924
|
+
system: "Act as a read-only explorer. Inspect code, collect evidence, and do not modify files.",
|
|
4925
|
+
source: "builtin"
|
|
4926
|
+
},
|
|
4927
|
+
{
|
|
4928
|
+
name: "worker",
|
|
4929
|
+
description: "Implementation and focused fixes within sub-agent safety limits.",
|
|
4930
|
+
permissionProfile: "workspace-write",
|
|
4931
|
+
maxToolRounds: 15,
|
|
4932
|
+
contextPolicy: "task-only",
|
|
4933
|
+
system: "Act as a focused implementation worker. Keep edits scoped and report changed files.",
|
|
4934
|
+
source: "builtin"
|
|
4935
|
+
},
|
|
4936
|
+
{
|
|
4937
|
+
name: "reviewer",
|
|
4938
|
+
description: "Code review focused on bugs, regressions, and missing tests.",
|
|
4939
|
+
permissionProfile: "read-only",
|
|
4940
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
4941
|
+
maxToolRounds: 12,
|
|
4942
|
+
contextPolicy: "task-only",
|
|
4943
|
+
system: "Act as a code reviewer. Lead with concrete findings, file evidence, and residual risks.",
|
|
4944
|
+
source: "builtin"
|
|
4945
|
+
},
|
|
4946
|
+
{
|
|
4947
|
+
name: "security",
|
|
4948
|
+
description: "Security audit with read-only evidence collection.",
|
|
4949
|
+
permissionProfile: "read-only",
|
|
4950
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
4951
|
+
maxToolRounds: 12,
|
|
4952
|
+
contextPolicy: "task-only",
|
|
4953
|
+
system: "Act as a security reviewer. Look for trust-boundary, injection, filesystem, network, and secret-handling issues.",
|
|
4954
|
+
source: "builtin"
|
|
4955
|
+
},
|
|
4956
|
+
{
|
|
4957
|
+
name: "tester",
|
|
4958
|
+
description: "Test planning and regression analysis without direct shell execution.",
|
|
4959
|
+
permissionProfile: "read-only",
|
|
4960
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
4961
|
+
maxToolRounds: 12,
|
|
4962
|
+
contextPolicy: "task-only",
|
|
4963
|
+
system: "Act as a tester. Inspect tests, identify coverage gaps, and propose precise regression checks.",
|
|
4964
|
+
source: "builtin"
|
|
4965
|
+
}
|
|
4966
|
+
];
|
|
4967
|
+
function normalizeName(name) {
|
|
4968
|
+
return name.trim().toLowerCase();
|
|
4969
|
+
}
|
|
4970
|
+
function isStringArray(value) {
|
|
4971
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4972
|
+
}
|
|
4973
|
+
function parseAgentConfig(raw, source, path3) {
|
|
4974
|
+
if (!raw || typeof raw !== "object") return null;
|
|
4975
|
+
const obj = raw;
|
|
4976
|
+
if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
|
|
4977
|
+
const cfg = {
|
|
4978
|
+
name: obj.name.trim(),
|
|
4979
|
+
source,
|
|
4980
|
+
path: path3
|
|
4981
|
+
};
|
|
4982
|
+
if (typeof obj.description === "string") cfg.description = obj.description;
|
|
4983
|
+
if (typeof obj.provider === "string") cfg.provider = obj.provider;
|
|
4984
|
+
if (typeof obj.model === "string") cfg.model = obj.model;
|
|
4985
|
+
if (typeof obj.system === "string") cfg.system = obj.system;
|
|
4986
|
+
if (typeof obj.instructions === "string") cfg.instructions = obj.instructions;
|
|
4987
|
+
if (typeof obj.systemInstructions === "string") cfg.systemInstructions = obj.systemInstructions;
|
|
4988
|
+
if (isStringArray(obj.allowedTools)) cfg.allowedTools = obj.allowedTools;
|
|
4989
|
+
if (isStringArray(obj.blockedTools)) cfg.blockedTools = obj.blockedTools;
|
|
4990
|
+
if (typeof obj.permissionProfile === "string") cfg.permissionProfile = obj.permissionProfile;
|
|
4991
|
+
if (typeof obj.maxToolRounds === "number" && Number.isFinite(obj.maxToolRounds)) cfg.maxToolRounds = obj.maxToolRounds;
|
|
4992
|
+
if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
|
|
4993
|
+
return cfg;
|
|
4994
|
+
}
|
|
4995
|
+
function loadAgentDir(dir, source) {
|
|
4996
|
+
if (!existsSync12(dir)) return [];
|
|
4997
|
+
const out = [];
|
|
4998
|
+
for (const file of readdirSync6(dir)) {
|
|
4999
|
+
if (!file.endsWith(".json")) continue;
|
|
5000
|
+
const path3 = join7(dir, file);
|
|
5001
|
+
try {
|
|
5002
|
+
const parsed = JSON.parse(readFileSync9(path3, "utf-8"));
|
|
5003
|
+
const cfg = parseAgentConfig(parsed, source, path3);
|
|
5004
|
+
if (cfg) out.push(cfg);
|
|
5005
|
+
} catch {
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
return out;
|
|
5009
|
+
}
|
|
5010
|
+
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
5011
|
+
return {
|
|
5012
|
+
user: join7(configDir ?? join7(homedir4(), CONFIG_DIR_NAME), "agents"),
|
|
5013
|
+
project: join7(cwd, CONFIG_DIR_NAME, "agents")
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
5017
|
+
const dirs = getAgentDirs(configDir, cwd);
|
|
5018
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5019
|
+
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
5020
|
+
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
5021
|
+
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
5022
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
5023
|
+
}
|
|
5024
|
+
function resolveAgentConfig(name, configDir, cwd = process.cwd()) {
|
|
5025
|
+
const target = normalizeName(name);
|
|
5026
|
+
return listAgentConfigs(configDir, cwd).find((cfg) => normalizeName(cfg.name) === target);
|
|
5027
|
+
}
|
|
5028
|
+
function getAgentInstructions(agent) {
|
|
5029
|
+
return agent.system ?? agent.systemInstructions ?? agent.instructions;
|
|
5030
|
+
}
|
|
5031
|
+
function getEffectiveAgentTools(agent) {
|
|
5032
|
+
let allowed = new Set(SUBAGENT_ALLOWED_TOOLS);
|
|
5033
|
+
if (agent.permissionProfile === "read-only") {
|
|
5034
|
+
allowed = new Set([...allowed].filter((name) => READ_ONLY_TOOLS2.has(name)));
|
|
5035
|
+
}
|
|
5036
|
+
if (agent.allowedTools && agent.allowedTools.length > 0) {
|
|
5037
|
+
const requested = new Set(agent.allowedTools);
|
|
5038
|
+
allowed = new Set([...allowed].filter((name) => requested.has(name)));
|
|
5039
|
+
}
|
|
5040
|
+
if (agent.blockedTools && agent.blockedTools.length > 0) {
|
|
5041
|
+
for (const name of agent.blockedTools) allowed.delete(name);
|
|
5042
|
+
}
|
|
5043
|
+
return allowed;
|
|
5044
|
+
}
|
|
5045
|
+
function getEffectiveMaxRounds(requestedMaxRounds, agent) {
|
|
5046
|
+
const agentMax = agent.maxToolRounds && agent.maxToolRounds > 0 ? Math.round(agent.maxToolRounds) : SUBAGENT_MAX_ROUNDS_LIMIT;
|
|
5047
|
+
return Math.min(requestedMaxRounds, agentMax, SUBAGENT_MAX_ROUNDS_LIMIT);
|
|
5048
|
+
}
|
|
5049
|
+
|
|
5050
|
+
// src/agents/agent-runtime.ts
|
|
5051
|
+
var nextId = 1;
|
|
5052
|
+
var preferredAgentName = "worker";
|
|
5053
|
+
var runs = [];
|
|
5054
|
+
var MAX_RUNS = 50;
|
|
5055
|
+
function nowIso() {
|
|
5056
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5057
|
+
}
|
|
5058
|
+
function startAgentRun(input) {
|
|
5059
|
+
const run = {
|
|
5060
|
+
id: `agent-${nextId++}`,
|
|
5061
|
+
agentName: input.agentName,
|
|
5062
|
+
task: input.task,
|
|
5063
|
+
status: "running",
|
|
5064
|
+
startedAt: nowIso(),
|
|
5065
|
+
toolNames: [],
|
|
5066
|
+
agentIndex: input.agentIndex,
|
|
5067
|
+
stopRequested: false
|
|
5068
|
+
};
|
|
5069
|
+
runs.unshift(run);
|
|
5070
|
+
if (runs.length > MAX_RUNS) runs.splice(MAX_RUNS);
|
|
5071
|
+
return run;
|
|
5072
|
+
}
|
|
5073
|
+
function recordAgentTool(runId, toolName) {
|
|
5074
|
+
const run = runs.find((r) => r.id === runId);
|
|
5075
|
+
if (!run) return;
|
|
5076
|
+
if (!run.toolNames.includes(toolName)) run.toolNames.push(toolName);
|
|
5077
|
+
}
|
|
5078
|
+
function finishAgentRun(runId, status, summary, error) {
|
|
5079
|
+
const run = runs.find((r) => r.id === runId);
|
|
5080
|
+
if (!run) return;
|
|
5081
|
+
run.status = status;
|
|
5082
|
+
run.finishedAt = nowIso();
|
|
5083
|
+
if (summary) run.summary = summary;
|
|
5084
|
+
if (error) run.error = error;
|
|
5085
|
+
}
|
|
5086
|
+
function listAgentRuns() {
|
|
5087
|
+
return runs.map((run) => ({ ...run, toolNames: [...run.toolNames] }));
|
|
5088
|
+
}
|
|
5089
|
+
function requestAgentStop(idOrAll) {
|
|
5090
|
+
const target = idOrAll.trim().toLowerCase();
|
|
5091
|
+
let count = 0;
|
|
5092
|
+
for (const run of runs) {
|
|
5093
|
+
if (run.status !== "running") continue;
|
|
5094
|
+
if (target === "all" || run.id.toLowerCase() === target) {
|
|
5095
|
+
run.stopRequested = true;
|
|
5096
|
+
count++;
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
return count;
|
|
5100
|
+
}
|
|
5101
|
+
function isAgentStopRequested(runId) {
|
|
5102
|
+
return runs.find((r) => r.id === runId)?.stopRequested ?? false;
|
|
5103
|
+
}
|
|
5104
|
+
function setPreferredAgentName(name) {
|
|
5105
|
+
preferredAgentName = name.trim() || "worker";
|
|
5106
|
+
}
|
|
5107
|
+
function getPreferredAgentName() {
|
|
5108
|
+
return preferredAgentName;
|
|
5109
|
+
}
|
|
5110
|
+
|
|
4712
5111
|
// src/tools/builtin/spawn-agent.ts
|
|
4713
5112
|
var spawnAgentContext = {
|
|
4714
5113
|
provider: null,
|
|
@@ -4723,18 +5122,27 @@ function agentPrefix(agentIndex) {
|
|
|
4723
5122
|
return theme.dim(` \u2503${agentIndex} `);
|
|
4724
5123
|
}
|
|
4725
5124
|
var SubAgentExecutor = class {
|
|
4726
|
-
constructor(registry, agentIndex = null) {
|
|
5125
|
+
constructor(registry, agentIndex = null, runId) {
|
|
4727
5126
|
this.registry = registry;
|
|
5127
|
+
this.runId = runId;
|
|
4728
5128
|
this.prefix = agentPrefix(agentIndex);
|
|
4729
5129
|
}
|
|
4730
5130
|
round = 0;
|
|
4731
5131
|
totalRounds = 0;
|
|
4732
5132
|
prefix = PREFIX;
|
|
5133
|
+
toolNames = [];
|
|
4733
5134
|
setRoundInfo(current, total) {
|
|
4734
5135
|
this.round = current;
|
|
4735
5136
|
this.totalRounds = total;
|
|
4736
5137
|
}
|
|
4737
5138
|
async execute(call) {
|
|
5139
|
+
if (this.runId && isAgentStopRequested(this.runId)) {
|
|
5140
|
+
return {
|
|
5141
|
+
callId: call.id,
|
|
5142
|
+
content: "Sub-agent stop requested.",
|
|
5143
|
+
isError: true
|
|
5144
|
+
};
|
|
5145
|
+
}
|
|
4738
5146
|
const tool = this.registry.get(call.name);
|
|
4739
5147
|
if (!tool) {
|
|
4740
5148
|
return {
|
|
@@ -4755,6 +5163,8 @@ var SubAgentExecutor = class {
|
|
|
4755
5163
|
};
|
|
4756
5164
|
}
|
|
4757
5165
|
this.printToolCall(call, dangerLevel);
|
|
5166
|
+
if (!this.toolNames.includes(call.name)) this.toolNames.push(call.name);
|
|
5167
|
+
if (this.runId) recordAgentTool(this.runId, call.name);
|
|
4758
5168
|
try {
|
|
4759
5169
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
4760
5170
|
const content = truncateOutput(rawContent, call.name);
|
|
@@ -4774,7 +5184,6 @@ var SubAgentExecutor = class {
|
|
|
4774
5184
|
}
|
|
4775
5185
|
return results;
|
|
4776
5186
|
}
|
|
4777
|
-
// ── 带前缀的终端输出 ──
|
|
4778
5187
|
printPrefixed(text) {
|
|
4779
5188
|
for (const line of text.split("\n")) {
|
|
4780
5189
|
console.log(this.prefix + line);
|
|
@@ -4815,7 +5224,7 @@ var SubAgentExecutor = class {
|
|
|
4815
5224
|
}
|
|
4816
5225
|
}
|
|
4817
5226
|
};
|
|
4818
|
-
function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
|
|
5227
|
+
function buildSubAgentSystemPrompt(task, agent, parentSystemPrompt) {
|
|
4819
5228
|
const parts = [];
|
|
4820
5229
|
if (parentSystemPrompt) {
|
|
4821
5230
|
parts.push(parentSystemPrompt);
|
|
@@ -4823,6 +5232,8 @@ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
|
|
|
4823
5232
|
parts.push(
|
|
4824
5233
|
`# Sub-Agent Mode
|
|
4825
5234
|
|
|
5235
|
+
**Agent Role**: ${agent.name}${agent.description ? ` \u2014 ${agent.description}` : ""}
|
|
5236
|
+
|
|
4826
5237
|
You are a focused sub-agent delegated by the main agent to complete a specific task.
|
|
4827
5238
|
|
|
4828
5239
|
**Your Task**:
|
|
@@ -4836,13 +5247,19 @@ ${task}
|
|
|
4836
5247
|
5. Destructive operations (rm -rf etc.) will be automatically blocked
|
|
4837
5248
|
6. Be efficient, minimize unnecessary tool call rounds`
|
|
4838
5249
|
);
|
|
5250
|
+
const agentInstructions = getAgentInstructions(agent);
|
|
5251
|
+
if (agentInstructions) {
|
|
5252
|
+
parts.push(`# Agent Instructions
|
|
5253
|
+
|
|
5254
|
+
${agentInstructions}`);
|
|
5255
|
+
}
|
|
4839
5256
|
return parts.join("\n\n---\n\n");
|
|
4840
5257
|
}
|
|
4841
|
-
function printSubAgentHeader(task, maxRounds, agentIndex = null) {
|
|
5258
|
+
function printSubAgentHeader(task, maxRounds, agentName, agentIndex = null) {
|
|
4842
5259
|
const prefix = agentPrefix(agentIndex);
|
|
4843
5260
|
console.log();
|
|
4844
5261
|
console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
4845
|
-
const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned` :
|
|
5262
|
+
const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned (${agentName})` : `\u{1F916} Sub-Agent Spawned (${agentName})`;
|
|
4846
5263
|
console.log(prefix + theme.toolCall(label));
|
|
4847
5264
|
console.log(
|
|
4848
5265
|
prefix + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
|
|
@@ -4865,58 +5282,110 @@ function printSubAgentFooter(usage, agentIndex = null) {
|
|
|
4865
5282
|
console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
4866
5283
|
console.log();
|
|
4867
5284
|
}
|
|
4868
|
-
|
|
5285
|
+
function summarizeContent(content) {
|
|
5286
|
+
const flat = content.replace(/\s+/g, " ").trim();
|
|
5287
|
+
return flat.length > 500 ? flat.slice(0, 497) + "..." : flat;
|
|
5288
|
+
}
|
|
5289
|
+
function extractEvidence(content, toolNames) {
|
|
5290
|
+
const evidence = /* @__PURE__ */ new Set();
|
|
5291
|
+
if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
|
|
5292
|
+
const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
|
|
5293
|
+
for (const match of matches.slice(0, 8)) evidence.add(match);
|
|
5294
|
+
return [...evidence].slice(0, 10);
|
|
5295
|
+
}
|
|
5296
|
+
function formatAgentResult(input) {
|
|
5297
|
+
const lines = [input.title, "", `**Agent**: ${input.agentName}`];
|
|
5298
|
+
if (input.task) lines.push(`**Task**: ${input.task}`);
|
|
5299
|
+
lines.push("", "### Summary", input.summary || "(no summary)", "", "### Key Evidence");
|
|
5300
|
+
if (input.evidence.length > 0) {
|
|
5301
|
+
for (const item of input.evidence) lines.push(`- ${item}`);
|
|
5302
|
+
} else {
|
|
5303
|
+
lines.push("- No structured evidence extracted.");
|
|
5304
|
+
}
|
|
5305
|
+
lines.push("", "### Tools Used");
|
|
5306
|
+
lines.push(input.toolNames.length > 0 ? input.toolNames.join(", ") : "No tools used.");
|
|
5307
|
+
lines.push("", "### Full Result", input.content, "");
|
|
5308
|
+
lines.push(`*Tokens: ${input.usage.inputTokens} in / ${input.usage.outputTokens} out*`);
|
|
5309
|
+
return lines;
|
|
5310
|
+
}
|
|
5311
|
+
async function runSubAgent(task, maxRounds, agentIndex, ctx, agent) {
|
|
4869
5312
|
if (!ctx.provider) {
|
|
4870
5313
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
4871
5314
|
}
|
|
5315
|
+
const providerFromConfig = agent.provider && ctx.providers?.has(agent.provider) ? ctx.providers.get(agent.provider) : void 0;
|
|
5316
|
+
const provider = providerFromConfig ?? ctx.provider;
|
|
5317
|
+
const model = agent.model ?? (providerFromConfig?.info?.defaultModel ?? ctx.model);
|
|
5318
|
+
const run = startAgentRun({
|
|
5319
|
+
agentName: agent.name,
|
|
5320
|
+
task,
|
|
5321
|
+
agentIndex: agentIndex ?? void 0
|
|
5322
|
+
});
|
|
5323
|
+
const effectiveTools = getEffectiveAgentTools(agent);
|
|
4872
5324
|
const subRegistry = new ToolRegistry();
|
|
4873
5325
|
for (const tool of subRegistry.listAll()) {
|
|
4874
|
-
if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
|
|
5326
|
+
if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name) || !effectiveTools.has(tool.definition.name)) {
|
|
4875
5327
|
subRegistry.unregister(tool.definition.name);
|
|
4876
5328
|
}
|
|
4877
5329
|
}
|
|
4878
|
-
const subExecutor = new SubAgentExecutor(subRegistry, agentIndex);
|
|
5330
|
+
const subExecutor = new SubAgentExecutor(subRegistry, agentIndex, run.id);
|
|
4879
5331
|
const subMessages = [
|
|
4880
5332
|
{ role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
|
|
4881
5333
|
];
|
|
4882
|
-
const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
|
|
5334
|
+
const subSystemPrompt = buildSubAgentSystemPrompt(task, agent, ctx.systemPrompt);
|
|
4883
5335
|
const toolDefs = subRegistry.getDefinitions();
|
|
4884
|
-
printSubAgentHeader(task, maxRounds, agentIndex);
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
5336
|
+
printSubAgentHeader(task, maxRounds, agent.name, agentIndex);
|
|
5337
|
+
try {
|
|
5338
|
+
const loop = await runLeanAgentLoop({
|
|
5339
|
+
provider,
|
|
5340
|
+
messages: subMessages,
|
|
5341
|
+
model,
|
|
5342
|
+
maxRounds,
|
|
5343
|
+
chatParams: {
|
|
5344
|
+
temperature: ctx.modelParams.temperature,
|
|
5345
|
+
maxTokens: ctx.modelParams.maxTokens,
|
|
5346
|
+
timeout: ctx.modelParams.timeout,
|
|
5347
|
+
thinking: ctx.modelParams.thinking
|
|
5348
|
+
},
|
|
5349
|
+
executeTools: (calls) => subExecutor.executeAll(calls),
|
|
5350
|
+
systemPromptForRound: () => subSystemPrompt,
|
|
5351
|
+
toolDefsForRound: () => toolDefs,
|
|
5352
|
+
onRoundStart: (round) => {
|
|
5353
|
+
subExecutor.setRoundInfo(round + 1, maxRounds);
|
|
5354
|
+
if (ctx.configManager) {
|
|
5355
|
+
googleSearchContext.configManager = ctx.configManager;
|
|
5356
|
+
}
|
|
5357
|
+
},
|
|
5358
|
+
onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
|
|
5359
|
+
onError: (errMsg) => {
|
|
5360
|
+
process.stderr.write(`
|
|
4908
5361
|
[spawn_agent] Error in sub-agent loop: ${errMsg}
|
|
4909
5362
|
`);
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
5363
|
+
return `(Sub-agent error: ${errMsg})`;
|
|
5364
|
+
}
|
|
5365
|
+
});
|
|
5366
|
+
printSubAgentFooter(loop.usage, agentIndex);
|
|
5367
|
+
const summary = summarizeContent(loop.content);
|
|
5368
|
+
const evidence = extractEvidence(loop.content, subExecutor.toolNames);
|
|
5369
|
+
const stopped = isAgentStopRequested(run.id);
|
|
5370
|
+
finishAgentRun(run.id, stopped ? "stopped" : "ok", summary);
|
|
5371
|
+
return {
|
|
5372
|
+
content: loop.content,
|
|
5373
|
+
usage: loop.usage,
|
|
5374
|
+
toolNames: subExecutor.toolNames,
|
|
5375
|
+
summary,
|
|
5376
|
+
evidence,
|
|
5377
|
+
agentName: agent.name
|
|
5378
|
+
};
|
|
5379
|
+
} catch (err) {
|
|
5380
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5381
|
+
finishAgentRun(run.id, "error", void 0, message);
|
|
5382
|
+
throw err;
|
|
5383
|
+
}
|
|
4915
5384
|
}
|
|
4916
5385
|
var spawnAgentTool = {
|
|
4917
5386
|
definition: {
|
|
4918
5387
|
name: "spawn_agent",
|
|
4919
|
-
description: "Delegate subtask(s) to independent
|
|
5388
|
+
description: "Delegate subtask(s) to independent named agents. Each sub-agent has its own conversation context and agentic tool-call loop, inherits the parent safety boundary, and can only narrow the sub-agent tool whitelist. Pass `agent` to select a built-in or configured role. Built-ins: explorer, worker, reviewer, security, tester. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Returns summary, key evidence, and tools used for each sub-agent result.",
|
|
4920
5389
|
parameters: {
|
|
4921
5390
|
task: {
|
|
4922
5391
|
type: "string",
|
|
@@ -4934,7 +5403,12 @@ var spawnAgentTool = {
|
|
|
4934
5403
|
},
|
|
4935
5404
|
max_rounds: {
|
|
4936
5405
|
type: "number",
|
|
4937
|
-
description: "Max tool-call rounds per sub-agent (1-
|
|
5406
|
+
description: "Max tool-call rounds per sub-agent (1-30, default 15). Agent config can only reduce this value.",
|
|
5407
|
+
required: false
|
|
5408
|
+
},
|
|
5409
|
+
agent: {
|
|
5410
|
+
type: "string",
|
|
5411
|
+
description: "Agent role name from built-ins or ~/.aicli/agents / .aicli/agents JSON configs. Built-ins: explorer, worker, reviewer, security, tester.",
|
|
4938
5412
|
required: false
|
|
4939
5413
|
}
|
|
4940
5414
|
},
|
|
@@ -4954,10 +5428,17 @@ var spawnAgentTool = {
|
|
|
4954
5428
|
Math.max(Math.round(rawMaxRounds), 1),
|
|
4955
5429
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
4956
5430
|
);
|
|
5431
|
+
const requestedAgentName = typeof args["agent"] === "string" && args["agent"].trim() ? args["agent"].trim() : getPreferredAgentName();
|
|
4957
5432
|
const ctx = spawnAgentContext;
|
|
4958
5433
|
if (!ctx.provider) {
|
|
4959
5434
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
4960
5435
|
}
|
|
5436
|
+
const agent = resolveAgentConfig(requestedAgentName, ctx.configManager?.getConfigDir());
|
|
5437
|
+
if (!agent) {
|
|
5438
|
+
const available = listAgentConfigs(ctx.configManager?.getConfigDir()).map((cfg) => cfg.name).join(", ");
|
|
5439
|
+
throw new ToolError("spawn_agent", `unknown agent "${requestedAgentName}". Available agents: ${available}`);
|
|
5440
|
+
}
|
|
5441
|
+
const effectiveMaxRounds = getEffectiveMaxRounds(maxRounds, agent);
|
|
4961
5442
|
const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
|
|
4962
5443
|
const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
|
|
4963
5444
|
const taskCount = singleTask ? 1 : tasksArr.length;
|
|
@@ -4965,7 +5446,8 @@ var spawnAgentTool = {
|
|
|
4965
5446
|
task: singleTask || void 0,
|
|
4966
5447
|
tasks: tasksArr.length > 0 ? tasksArr : void 0,
|
|
4967
5448
|
taskCount,
|
|
4968
|
-
maxRounds
|
|
5449
|
+
maxRounds: effectiveMaxRounds,
|
|
5450
|
+
agent: agent.name
|
|
4969
5451
|
};
|
|
4970
5452
|
const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
|
|
4971
5453
|
const startDeny = startDecisions.find((d) => d.action === "deny");
|
|
@@ -4977,32 +5459,37 @@ var spawnAgentTool = {
|
|
|
4977
5459
|
subAgentGuard.active = true;
|
|
4978
5460
|
try {
|
|
4979
5461
|
if (singleTask) {
|
|
4980
|
-
const
|
|
5462
|
+
const result = await runSubAgent(singleTask, effectiveMaxRounds, null, ctx, agent);
|
|
4981
5463
|
subagentStatus = "ok";
|
|
4982
|
-
return
|
|
4983
|
-
"## Sub-Agent Result",
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5464
|
+
return formatAgentResult({
|
|
5465
|
+
title: "## Sub-Agent Result",
|
|
5466
|
+
content: result.content,
|
|
5467
|
+
usage: result.usage,
|
|
5468
|
+
toolNames: result.toolNames,
|
|
5469
|
+
summary: result.summary,
|
|
5470
|
+
evidence: result.evidence,
|
|
5471
|
+
agentName: result.agentName
|
|
5472
|
+
}).join("\n");
|
|
4990
5473
|
}
|
|
4991
5474
|
console.log();
|
|
4992
|
-
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
|
|
5475
|
+
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel as ${agent.name}...`));
|
|
4993
5476
|
const results = await Promise.all(
|
|
4994
|
-
tasksArr.map((t, i) => runSubAgent(t,
|
|
5477
|
+
tasksArr.map((t, i) => runSubAgent(t, effectiveMaxRounds, i + 1, ctx, agent))
|
|
4995
5478
|
);
|
|
4996
5479
|
const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
|
|
4997
5480
|
const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
|
|
4998
5481
|
const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
|
|
4999
5482
|
results.forEach((r, i) => {
|
|
5000
|
-
lines.push(
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5483
|
+
lines.push(...formatAgentResult({
|
|
5484
|
+
title: `### Sub-Agent #${i + 1}`,
|
|
5485
|
+
task: `${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`,
|
|
5486
|
+
content: r.content,
|
|
5487
|
+
usage: r.usage,
|
|
5488
|
+
toolNames: r.toolNames,
|
|
5489
|
+
summary: r.summary,
|
|
5490
|
+
evidence: r.evidence,
|
|
5491
|
+
agentName: r.agentName
|
|
5492
|
+
}));
|
|
5006
5493
|
lines.push("");
|
|
5007
5494
|
});
|
|
5008
5495
|
lines.push("---");
|
|
@@ -5233,14 +5720,14 @@ var taskStopTool = {
|
|
|
5233
5720
|
|
|
5234
5721
|
// src/tools/builtin/git-tools.ts
|
|
5235
5722
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5236
|
-
import { existsSync as
|
|
5237
|
-
import { join as
|
|
5723
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5724
|
+
import { join as join8 } from "path";
|
|
5238
5725
|
function assertGitRepo(cwd) {
|
|
5239
5726
|
let dir = cwd;
|
|
5240
5727
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
5241
5728
|
while (dir && dir !== root) {
|
|
5242
|
-
if (
|
|
5243
|
-
const parent =
|
|
5729
|
+
if (existsSync13(join8(dir, ".git"))) return;
|
|
5730
|
+
const parent = join8(dir, "..");
|
|
5244
5731
|
if (parent === dir) break;
|
|
5245
5732
|
dir = parent;
|
|
5246
5733
|
}
|
|
@@ -5505,9 +5992,9 @@ ${commitOutput.trim()}`;
|
|
|
5505
5992
|
};
|
|
5506
5993
|
|
|
5507
5994
|
// src/tools/builtin/notebook-edit.ts
|
|
5508
|
-
import { readFileSync as
|
|
5995
|
+
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
5509
5996
|
import { writeFile } from "fs/promises";
|
|
5510
|
-
import { resolve as
|
|
5997
|
+
import { resolve as resolve6, extname as extname2 } from "path";
|
|
5511
5998
|
var notebookEditTool = {
|
|
5512
5999
|
definition: {
|
|
5513
6000
|
name: "notebook_edit",
|
|
@@ -5556,14 +6043,14 @@ var notebookEditTool = {
|
|
|
5556
6043
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
5557
6044
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
5558
6045
|
}
|
|
5559
|
-
const absPath =
|
|
6046
|
+
const absPath = resolve6(filePath);
|
|
5560
6047
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
5561
6048
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
5562
6049
|
}
|
|
5563
|
-
if (!
|
|
6050
|
+
if (!existsSync14(absPath)) {
|
|
5564
6051
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
5565
6052
|
}
|
|
5566
|
-
const raw =
|
|
6053
|
+
const raw = readFileSync10(absPath, "utf-8");
|
|
5567
6054
|
const nb = parseNotebook(raw);
|
|
5568
6055
|
const cellIdx0 = cellIndexRaw - 1;
|
|
5569
6056
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -5980,8 +6467,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
5980
6467
|
|
|
5981
6468
|
// src/tools/registry.ts
|
|
5982
6469
|
import { pathToFileURL } from "url";
|
|
5983
|
-
import { existsSync as
|
|
5984
|
-
import { join as
|
|
6470
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync7 } from "fs";
|
|
6471
|
+
import { join as join9 } from "path";
|
|
5985
6472
|
var ToolRegistry = class {
|
|
5986
6473
|
tools = /* @__PURE__ */ new Map();
|
|
5987
6474
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -6142,7 +6629,7 @@ var ToolRegistry = class {
|
|
|
6142
6629
|
* Returns the number of successfully loaded plugins.
|
|
6143
6630
|
*/
|
|
6144
6631
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
6145
|
-
if (!
|
|
6632
|
+
if (!existsSync15(pluginsDir)) {
|
|
6146
6633
|
try {
|
|
6147
6634
|
mkdirSync5(pluginsDir, { recursive: true });
|
|
6148
6635
|
} catch {
|
|
@@ -6151,7 +6638,7 @@ var ToolRegistry = class {
|
|
|
6151
6638
|
}
|
|
6152
6639
|
let files;
|
|
6153
6640
|
try {
|
|
6154
|
-
files =
|
|
6641
|
+
files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
6155
6642
|
} catch {
|
|
6156
6643
|
return 0;
|
|
6157
6644
|
}
|
|
@@ -6168,12 +6655,12 @@ var ToolRegistry = class {
|
|
|
6168
6655
|
process.stderr.write(
|
|
6169
6656
|
`
|
|
6170
6657
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
6171
|
-
` + files.map((f) => ` + ${
|
|
6658
|
+
` + files.map((f) => ` + ${join9(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
6172
6659
|
);
|
|
6173
6660
|
let loaded = 0;
|
|
6174
6661
|
for (const file of files) {
|
|
6175
6662
|
try {
|
|
6176
|
-
const fileUrl = pathToFileURL(
|
|
6663
|
+
const fileUrl = pathToFileURL(join9(pluginsDir, file)).href;
|
|
6177
6664
|
const mod = await import(fileUrl);
|
|
6178
6665
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
6179
6666
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -6211,6 +6698,10 @@ export {
|
|
|
6211
6698
|
getPendingHookTrust,
|
|
6212
6699
|
runLifecycleHooks,
|
|
6213
6700
|
runHook,
|
|
6701
|
+
recordRecentlyDeniedAutoAction,
|
|
6702
|
+
getRecentlyDeniedAutoActions,
|
|
6703
|
+
clearRecentlyDeniedAutoActions,
|
|
6704
|
+
defaultActionClassifier,
|
|
6214
6705
|
isInterrupted,
|
|
6215
6706
|
requestInterrupt,
|
|
6216
6707
|
resetInterrupt,
|
|
@@ -6230,6 +6721,12 @@ export {
|
|
|
6230
6721
|
cleanupRejectedTeeFile,
|
|
6231
6722
|
askUserContext,
|
|
6232
6723
|
googleSearchContext,
|
|
6724
|
+
listAgentConfigs,
|
|
6725
|
+
resolveAgentConfig,
|
|
6726
|
+
listAgentRuns,
|
|
6727
|
+
requestAgentStop,
|
|
6728
|
+
setPreferredAgentName,
|
|
6729
|
+
getPreferredAgentName,
|
|
6233
6730
|
spawnAgentContext,
|
|
6234
6731
|
estimateTokens,
|
|
6235
6732
|
ToolRegistry
|