jinzd-ai-cli 0.4.211 → 0.4.212
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 +23 -0
- package/README.zh-CN.md +23 -0
- package/dist/{batch-DE4RXKZD.js → batch-WIKFEOAZ.js} +2 -2
- package/dist/{chat-index-UBCWHBLR.js → chat-index-R2E27VXN.js} +1 -1
- package/dist/{chunk-E5XCM4A6.js → chunk-GBBVCZ4W.js} +1 -1
- package/dist/{chunk-C3OU2OPF.js → chunk-HW2ALWQJ.js} +2 -2
- package/dist/{chunk-6NS6643Y.js → chunk-HX6N6QEY.js} +1 -1
- package/dist/{chunk-JBWA73GK.js → chunk-IFZX26K7.js} +1 -1
- package/dist/{chunk-ZY2N2N6T.js → chunk-KE4B3NOQ.js} +1 -1
- package/dist/{chunk-BE6ERF7M.js → chunk-LU6FBJQ5.js} +1 -1
- package/dist/{chunk-ZOPYREL5.js → chunk-N5LB3PPL.js} +258 -52
- package/dist/{chunk-UOROWTGG.js → chunk-NYCBOVNF.js} +26 -3
- package/dist/{chunk-W7UKO3PS.js → chunk-OQGVGPEK.js} +5 -0
- package/dist/{ci-XMUEX526.js → ci-X3LNUFZV.js} +2 -2
- package/dist/{constants-AWTIQIWG.js → constants-KDWG4KR4.js} +1 -1
- package/dist/{doctor-cli-SSI6ETFT.js → doctor-cli-TJTWIW7U.js} +4 -4
- package/dist/electron-server.js +468 -174
- package/dist/{hub-INUJND2G.js → hub-JWSV4MKC.js} +1 -1
- package/dist/index.js +142 -17
- package/dist/{run-tests-PACN4UYX.js → run-tests-D7YBY4XB.js} +1 -1
- package/dist/{run-tests-3MHWUF43.js → run-tests-H4Q2PRX6.js} +2 -2
- package/dist/{server-7USZJJAH.js → server-OFKGDRYI.js} +4 -4
- package/dist/{server-BTSKOPQI.js → server-VNCN7AY5.js} +99 -10
- package/dist/{task-orchestrator-OV4O25MX.js → task-orchestrator-AL2E642M.js} +4 -4
- package/dist/{usage-T2P6FTE7.js → usage-LB6REWPO.js} +2 -2
- package/dist/web/client/app.js +1 -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-HX6N6QEY.js";
|
|
9
9
|
import {
|
|
10
10
|
runTool
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IFZX26K7.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-GBBVCZ4W.js";
|
|
30
30
|
import {
|
|
31
31
|
fileCheckpoints
|
|
32
32
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -1050,12 +1050,12 @@ ${content}`;
|
|
|
1050
1050
|
};
|
|
1051
1051
|
|
|
1052
1052
|
// src/tools/builtin/write-file.ts
|
|
1053
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
1053
|
+
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1054
1054
|
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 existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1059
1059
|
import { tmpdir } from "os";
|
|
1060
1060
|
|
|
1061
1061
|
// src/core/readline-internal.ts
|
|
@@ -1263,6 +1263,155 @@ function simpleDiff(oldLines, newLines) {
|
|
|
1263
1263
|
|
|
1264
1264
|
// src/tools/hooks.ts
|
|
1265
1265
|
import { execSync } from "child_process";
|
|
1266
|
+
import { createHash } from "crypto";
|
|
1267
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "fs";
|
|
1268
|
+
import { join } from "path";
|
|
1269
|
+
var TRUST_FILE = "hooks-trust.json";
|
|
1270
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
1271
|
+
function asArray(entry) {
|
|
1272
|
+
if (!entry) return [];
|
|
1273
|
+
const raw = Array.isArray(entry) ? entry : [entry];
|
|
1274
|
+
return raw.map((item) => typeof item === "string" ? { command: item } : item);
|
|
1275
|
+
}
|
|
1276
|
+
function hookTrustPath(configDir) {
|
|
1277
|
+
return join(configDir, TRUST_FILE);
|
|
1278
|
+
}
|
|
1279
|
+
function loadHookTrustStore(configDir) {
|
|
1280
|
+
const file = hookTrustPath(configDir);
|
|
1281
|
+
if (!existsSync4(file)) return { records: [] };
|
|
1282
|
+
try {
|
|
1283
|
+
const parsed = JSON.parse(readFileSync3(file, "utf-8"));
|
|
1284
|
+
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
1285
|
+
} catch {
|
|
1286
|
+
return { records: [] };
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function saveHookTrustStore(configDir, store) {
|
|
1290
|
+
mkdirSync(configDir, { recursive: true });
|
|
1291
|
+
atomicWriteFileSync(hookTrustPath(configDir), JSON.stringify(store, null, 2));
|
|
1292
|
+
}
|
|
1293
|
+
function hashHook(event, command) {
|
|
1294
|
+
return createHash("sha256").update(`${event}
|
|
1295
|
+
${command}`, "utf-8").digest("hex");
|
|
1296
|
+
}
|
|
1297
|
+
function isTrusted(source, event, hash, store) {
|
|
1298
|
+
if (source === "user" || source === "managed") return true;
|
|
1299
|
+
return store.records.some((r) => r.source === source && r.event === event && r.hash === hash);
|
|
1300
|
+
}
|
|
1301
|
+
function listHooks(config, configDir) {
|
|
1302
|
+
if (!config || config.enabled === false) return [];
|
|
1303
|
+
const store = loadHookTrustStore(configDir);
|
|
1304
|
+
const out = [];
|
|
1305
|
+
const push = (event, hook, index) => {
|
|
1306
|
+
if (!hook.command) return;
|
|
1307
|
+
const source = hook.source ?? "user";
|
|
1308
|
+
const hash = hashHook(event, hook.command);
|
|
1309
|
+
out.push({
|
|
1310
|
+
id: `${event}:${index}`,
|
|
1311
|
+
event,
|
|
1312
|
+
command: hook.command,
|
|
1313
|
+
source,
|
|
1314
|
+
hash,
|
|
1315
|
+
trusted: isTrusted(source, event, hash, store),
|
|
1316
|
+
disabled: hook.disabled === true,
|
|
1317
|
+
description: hook.description
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
if (config.preToolExecution) push("PreToolUse", { command: config.preToolExecution }, 0);
|
|
1321
|
+
if (config.postToolExecution) push("PostToolUse", { command: config.postToolExecution }, 0);
|
|
1322
|
+
for (const [event, entry] of Object.entries(config.events ?? {})) {
|
|
1323
|
+
asArray(entry).forEach((hook, i) => push(event, hook, i));
|
|
1324
|
+
}
|
|
1325
|
+
return out;
|
|
1326
|
+
}
|
|
1327
|
+
function trustHook(configDir, hook) {
|
|
1328
|
+
const store = loadHookTrustStore(configDir);
|
|
1329
|
+
const next = store.records.filter((r) => !(r.source === hook.source && r.event === hook.event && r.id === hook.id));
|
|
1330
|
+
next.push({ id: hook.id, event: hook.event, source: hook.source, hash: hook.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1331
|
+
saveHookTrustStore(configDir, { records: next });
|
|
1332
|
+
}
|
|
1333
|
+
function untrustHook(configDir, hook) {
|
|
1334
|
+
const store = loadHookTrustStore(configDir);
|
|
1335
|
+
saveHookTrustStore(configDir, {
|
|
1336
|
+
records: store.records.filter((r) => !(r.source === hook.source && r.event === hook.event && r.hash === hook.hash))
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
function getPendingHookTrust(config, configDir) {
|
|
1340
|
+
return listHooks(config, configDir).filter((h) => h.source === "project" && !h.disabled && !h.trusted);
|
|
1341
|
+
}
|
|
1342
|
+
function normalizeDecision(value, hook) {
|
|
1343
|
+
if (!value || typeof value !== "object") return null;
|
|
1344
|
+
const obj = value;
|
|
1345
|
+
const action = obj.action;
|
|
1346
|
+
if (action !== "allow" && action !== "deny" && action !== "ask") return null;
|
|
1347
|
+
return {
|
|
1348
|
+
action,
|
|
1349
|
+
reason: typeof obj.reason === "string" ? obj.reason : void 0,
|
|
1350
|
+
prompt: typeof obj.prompt === "string" ? obj.prompt : void 0,
|
|
1351
|
+
contextAppend: typeof obj.contextAppend === "string" ? obj.contextAppend : void 0,
|
|
1352
|
+
warning: typeof obj.warning === "string" ? obj.warning : void 0,
|
|
1353
|
+
warnings: Array.isArray(obj.warnings) ? obj.warnings.filter((x) => typeof x === "string") : void 0,
|
|
1354
|
+
hook
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function runStructuredHook(hook, descriptor, payload) {
|
|
1358
|
+
if (hook.disabled === true) return null;
|
|
1359
|
+
const timeout = Math.max(100, Math.min(hook.timeoutMs ?? DEFAULT_TIMEOUT_MS, 3e4));
|
|
1360
|
+
try {
|
|
1361
|
+
const stdout = execSync(hook.command, {
|
|
1362
|
+
timeout,
|
|
1363
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1364
|
+
encoding: "utf-8",
|
|
1365
|
+
env: {
|
|
1366
|
+
...process.env,
|
|
1367
|
+
AICLI_HOOK_EVENT: payload.event,
|
|
1368
|
+
AICLI_HOOK_EVENT_JSON: JSON.stringify(payload)
|
|
1369
|
+
}
|
|
1370
|
+
}).trim();
|
|
1371
|
+
if (!stdout) return null;
|
|
1372
|
+
return normalizeDecision(JSON.parse(stdout), descriptor);
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
process.stderr.write(`\u26A0 Hook failed: ${hook.command.slice(0, 100)}
|
|
1375
|
+
`);
|
|
1376
|
+
if (hook.required) {
|
|
1377
|
+
return { action: "deny", reason: err instanceof Error ? err.message : String(err), hook: descriptor };
|
|
1378
|
+
}
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function runLifecycleHooks(config, event, payload, opts) {
|
|
1383
|
+
if (!config || config.enabled === false) return [];
|
|
1384
|
+
const options = typeof opts === "string" ? { configDir: opts } : opts;
|
|
1385
|
+
const store = loadHookTrustStore(options.configDir);
|
|
1386
|
+
const hooks = asArray(config.events?.[event]);
|
|
1387
|
+
const decisions = [];
|
|
1388
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
1389
|
+
const hook = hooks[i];
|
|
1390
|
+
if (!hook.command || hook.disabled) continue;
|
|
1391
|
+
const source = hook.source ?? "user";
|
|
1392
|
+
const hash = hashHook(event, hook.command);
|
|
1393
|
+
const descriptor = {
|
|
1394
|
+
id: `${event}:${i}`,
|
|
1395
|
+
event,
|
|
1396
|
+
command: hook.command,
|
|
1397
|
+
source,
|
|
1398
|
+
hash,
|
|
1399
|
+
trusted: isTrusted(source, event, hash, store),
|
|
1400
|
+
disabled: false,
|
|
1401
|
+
description: hook.description
|
|
1402
|
+
};
|
|
1403
|
+
if (!descriptor.trusted) {
|
|
1404
|
+
options.onSummary?.(`hook skipped: ${event} project hook requires trust (${hash.slice(0, 12)})`);
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
const decision = runStructuredHook(hook, descriptor, { event, ...payload });
|
|
1408
|
+
if (decision) {
|
|
1409
|
+
decisions.push(decision);
|
|
1410
|
+
options.onSummary?.(`hook ${event}: ${decision.action}${decision.reason ? ` (${decision.reason})` : ""}`);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return decisions;
|
|
1414
|
+
}
|
|
1266
1415
|
function rewriteTemplate(template, isWindows) {
|
|
1267
1416
|
const ref = (name) => isWindows ? `%${name}%` : `$${name}`;
|
|
1268
1417
|
return template.replace(/\{tool\}/g, ref("AICLI_HOOK_TOOL")).replace(/\{dangerLevel\}/g, ref("AICLI_HOOK_DANGER_LEVEL")).replace(/\{args\}/g, ref("AICLI_HOOK_ARGS")).replace(/\{status\}/g, ref("AICLI_HOOK_STATUS"));
|
|
@@ -1273,7 +1422,7 @@ function runHook(template, vars) {
|
|
|
1273
1422
|
const cmd = rewriteTemplate(template, isWindows);
|
|
1274
1423
|
try {
|
|
1275
1424
|
execSync(cmd, {
|
|
1276
|
-
timeout:
|
|
1425
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
1277
1426
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1278
1427
|
encoding: "utf-8",
|
|
1279
1428
|
env: {
|
|
@@ -1790,6 +1939,7 @@ var ToolExecutor = class {
|
|
|
1790
1939
|
allowedPermissionProfiles = [];
|
|
1791
1940
|
workspaceRoot = process.cwd();
|
|
1792
1941
|
networkPolicy;
|
|
1942
|
+
hookConfigDir = process.cwd();
|
|
1793
1943
|
/** 注入 hooks 和 permission rules 配置 */
|
|
1794
1944
|
setConfig(opts) {
|
|
1795
1945
|
this.hookConfig = opts.hookConfig;
|
|
@@ -1800,6 +1950,7 @@ var ToolExecutor = class {
|
|
|
1800
1950
|
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
1801
1951
|
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
1802
1952
|
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
1953
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
1803
1954
|
}
|
|
1804
1955
|
async execute(call) {
|
|
1805
1956
|
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
@@ -1820,6 +1971,24 @@ var ToolExecutor = class {
|
|
|
1820
1971
|
dangerLevel,
|
|
1821
1972
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
1822
1973
|
});
|
|
1974
|
+
const preToolDecisions = runLifecycleHooks(
|
|
1975
|
+
this.hookConfig,
|
|
1976
|
+
"PreToolUse",
|
|
1977
|
+
{ tool: call.name, dangerLevel, args: call.arguments },
|
|
1978
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(` [hook] ${msg}`)) }
|
|
1979
|
+
);
|
|
1980
|
+
const preDeny = preToolDecisions.find((d) => d.action === "deny");
|
|
1981
|
+
if (preDeny) {
|
|
1982
|
+
return {
|
|
1983
|
+
callId: call.id,
|
|
1984
|
+
content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
|
|
1985
|
+
isError: true
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
for (const d of preToolDecisions) {
|
|
1989
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
1990
|
+
for (const w of warnings) console.log(theme.warning(` \u26A0 Hook warning: ${w}`));
|
|
1991
|
+
}
|
|
1823
1992
|
const permission = checkPermissionWithProfile(
|
|
1824
1993
|
call.name,
|
|
1825
1994
|
call.arguments,
|
|
@@ -1835,6 +2004,14 @@ var ToolExecutor = class {
|
|
|
1835
2004
|
}
|
|
1836
2005
|
);
|
|
1837
2006
|
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
2007
|
+
if (networkPermission?.action === "confirm" || permission.action === "confirm") {
|
|
2008
|
+
runLifecycleHooks(
|
|
2009
|
+
this.hookConfig,
|
|
2010
|
+
"PermissionRequest",
|
|
2011
|
+
{ tool: call.name, dangerLevel, args: call.arguments, reason: networkPermission?.reason ?? permission.reason },
|
|
2012
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(" [hook] " + msg)) }
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
1838
2015
|
if (networkPermission?.action === "deny") {
|
|
1839
2016
|
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
1840
2017
|
return {
|
|
@@ -1859,11 +2036,13 @@ var ToolExecutor = class {
|
|
|
1859
2036
|
const wasTruncated = content !== rawContent;
|
|
1860
2037
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1861
2038
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2039
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
1862
2040
|
return { callId: call.id, content, isError: false };
|
|
1863
2041
|
} catch (err) {
|
|
1864
2042
|
const message = err instanceof Error ? err.message : String(err);
|
|
1865
2043
|
this.printToolResult(call.name, message, true, false);
|
|
1866
2044
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2045
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
1867
2046
|
return { callId: call.id, content: message, isError: true };
|
|
1868
2047
|
}
|
|
1869
2048
|
}
|
|
@@ -1889,11 +2068,13 @@ var ToolExecutor = class {
|
|
|
1889
2068
|
const wasTruncated = content !== rawContent;
|
|
1890
2069
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1891
2070
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2071
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
1892
2072
|
return { callId: call.id, content, isError: false };
|
|
1893
2073
|
} catch (err) {
|
|
1894
2074
|
const message = err instanceof Error ? err.message : String(err);
|
|
1895
2075
|
this.printToolResult(call.name, message, true, false);
|
|
1896
2076
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2077
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
1897
2078
|
return { callId: call.id, content: message, isError: true };
|
|
1898
2079
|
}
|
|
1899
2080
|
}
|
|
@@ -1927,11 +2108,13 @@ var ToolExecutor = class {
|
|
|
1927
2108
|
const wasTruncated = content !== rawContent;
|
|
1928
2109
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1929
2110
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2111
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
1930
2112
|
return { callId: call.id, content, isError: false };
|
|
1931
2113
|
} catch (err) {
|
|
1932
2114
|
const message = err instanceof Error ? err.message : String(err);
|
|
1933
2115
|
this.printToolResult(call.name, message, true, false);
|
|
1934
2116
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2117
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
1935
2118
|
return { callId: call.id, content: message, isError: true };
|
|
1936
2119
|
}
|
|
1937
2120
|
}
|
|
@@ -2119,10 +2302,10 @@ var ToolExecutor = class {
|
|
|
2119
2302
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2120
2303
|
const newContent = String(call.arguments["content"] ?? "");
|
|
2121
2304
|
if (!filePath) return;
|
|
2122
|
-
if (
|
|
2305
|
+
if (existsSync5(filePath)) {
|
|
2123
2306
|
let oldContent;
|
|
2124
2307
|
try {
|
|
2125
|
-
oldContent =
|
|
2308
|
+
oldContent = readFileSync4(filePath, "utf-8");
|
|
2126
2309
|
} catch {
|
|
2127
2310
|
return;
|
|
2128
2311
|
}
|
|
@@ -2145,7 +2328,7 @@ var ToolExecutor = class {
|
|
|
2145
2328
|
}
|
|
2146
2329
|
} else if (call.name === "edit_file") {
|
|
2147
2330
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2148
|
-
if (!filePath || !
|
|
2331
|
+
if (!filePath || !existsSync5(filePath)) return;
|
|
2149
2332
|
const oldStr = call.arguments["old_str"];
|
|
2150
2333
|
const newStr = call.arguments["new_str"];
|
|
2151
2334
|
if (oldStr !== void 0) {
|
|
@@ -2172,7 +2355,7 @@ var ToolExecutor = class {
|
|
|
2172
2355
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
2173
2356
|
let fileContent;
|
|
2174
2357
|
try {
|
|
2175
|
-
fileContent =
|
|
2358
|
+
fileContent = readFileSync4(filePath, "utf-8");
|
|
2176
2359
|
} catch {
|
|
2177
2360
|
return;
|
|
2178
2361
|
}
|
|
@@ -2407,7 +2590,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2407
2590
|
}
|
|
2408
2591
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
2409
2592
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2410
|
-
|
|
2593
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
2411
2594
|
if (appendMode) {
|
|
2412
2595
|
appendFileSync(filePath, content, encoding);
|
|
2413
2596
|
} else {
|
|
@@ -2427,7 +2610,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2427
2610
|
};
|
|
2428
2611
|
|
|
2429
2612
|
// src/tools/builtin/edit-file.ts
|
|
2430
|
-
import { readFileSync as
|
|
2613
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
2431
2614
|
|
|
2432
2615
|
// src/tools/builtin/patch-apply.ts
|
|
2433
2616
|
function parseUnifiedDiff(patch) {
|
|
@@ -2788,7 +2971,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2788
2971
|
const filePath = String(args["path"] ?? "");
|
|
2789
2972
|
const encoding = args["encoding"] ?? "utf-8";
|
|
2790
2973
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
2791
|
-
if (!
|
|
2974
|
+
if (!existsSync6(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
2792
2975
|
const verdict = classifyWritePath(filePath);
|
|
2793
2976
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
2794
2977
|
throw new ToolError(
|
|
@@ -2796,7 +2979,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2796
2979
|
`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.`
|
|
2797
2980
|
);
|
|
2798
2981
|
}
|
|
2799
|
-
const original =
|
|
2982
|
+
const original = readFileSync5(filePath, encoding);
|
|
2800
2983
|
if (args["patch"] !== void 0) {
|
|
2801
2984
|
const patchText = String(args["patch"] ?? "");
|
|
2802
2985
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -2954,8 +3137,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
2954
3137
|
}
|
|
2955
3138
|
|
|
2956
3139
|
// src/tools/builtin/list-dir.ts
|
|
2957
|
-
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as
|
|
2958
|
-
import { join, basename as basename3 } from "path";
|
|
3140
|
+
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync7 } from "fs";
|
|
3141
|
+
import { join as join2, basename as basename3 } from "path";
|
|
2959
3142
|
var listDirTool = {
|
|
2960
3143
|
definition: {
|
|
2961
3144
|
name: "list_dir",
|
|
@@ -2977,7 +3160,7 @@ var listDirTool = {
|
|
|
2977
3160
|
async execute(args) {
|
|
2978
3161
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
2979
3162
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
2980
|
-
if (!
|
|
3163
|
+
if (!existsSync7(dirPath)) {
|
|
2981
3164
|
const targetName = basename3(dirPath).toLowerCase();
|
|
2982
3165
|
const cwd = process.cwd();
|
|
2983
3166
|
const suggestions = [];
|
|
@@ -3035,11 +3218,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
3035
3218
|
if (entry.isDirectory()) {
|
|
3036
3219
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
3037
3220
|
if (recursive) {
|
|
3038
|
-
listRecursive(
|
|
3221
|
+
listRecursive(join2(basePath, entry.name), indent + " ", true, lines);
|
|
3039
3222
|
}
|
|
3040
3223
|
} else {
|
|
3041
3224
|
try {
|
|
3042
|
-
const stat = statSync3(
|
|
3225
|
+
const stat = statSync3(join2(basePath, entry.name));
|
|
3043
3226
|
const size = formatSize(stat.size);
|
|
3044
3227
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
3045
3228
|
} catch {
|
|
@@ -3055,9 +3238,9 @@ function formatSize(bytes) {
|
|
|
3055
3238
|
}
|
|
3056
3239
|
|
|
3057
3240
|
// src/tools/builtin/grep-files.ts
|
|
3058
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
3241
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync8 } from "fs";
|
|
3059
3242
|
import { readFile } from "fs/promises";
|
|
3060
|
-
import { join as
|
|
3243
|
+
import { join as join3, relative } from "path";
|
|
3061
3244
|
var grepFilesTool = {
|
|
3062
3245
|
definition: {
|
|
3063
3246
|
name: "grep_files",
|
|
@@ -3108,7 +3291,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
3108
3291
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
3109
3292
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
3110
3293
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
3111
|
-
if (!
|
|
3294
|
+
if (!existsSync8(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
3112
3295
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
3113
3296
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
3114
3297
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -3202,11 +3385,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
3202
3385
|
if (paths.length >= maxFiles) return;
|
|
3203
3386
|
if (entry.isDirectory()) {
|
|
3204
3387
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3205
|
-
walk(
|
|
3388
|
+
walk(join3(dirPath, entry.name));
|
|
3206
3389
|
} else if (entry.isFile()) {
|
|
3207
3390
|
if (isBinary(entry.name)) continue;
|
|
3208
3391
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
3209
|
-
const fullPath =
|
|
3392
|
+
const fullPath = join3(dirPath, entry.name);
|
|
3210
3393
|
try {
|
|
3211
3394
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
3212
3395
|
} catch {
|
|
@@ -3261,7 +3444,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3261
3444
|
}
|
|
3262
3445
|
let content;
|
|
3263
3446
|
try {
|
|
3264
|
-
content =
|
|
3447
|
+
content = readFileSync6(fullPath, "utf-8");
|
|
3265
3448
|
} catch {
|
|
3266
3449
|
return;
|
|
3267
3450
|
}
|
|
@@ -3292,8 +3475,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3292
3475
|
}
|
|
3293
3476
|
|
|
3294
3477
|
// src/tools/builtin/glob-files.ts
|
|
3295
|
-
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as
|
|
3296
|
-
import { join as
|
|
3478
|
+
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync9 } from "fs";
|
|
3479
|
+
import { join as join4, relative as relative2, basename as basename4 } from "path";
|
|
3297
3480
|
var globFilesTool = {
|
|
3298
3481
|
definition: {
|
|
3299
3482
|
name: "glob_files",
|
|
@@ -3326,7 +3509,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
3326
3509
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
3327
3510
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
3328
3511
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
3329
|
-
if (!
|
|
3512
|
+
if (!existsSync9(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
3330
3513
|
const regex = globToRegex(pattern);
|
|
3331
3514
|
const matches = [];
|
|
3332
3515
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -3403,7 +3586,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
3403
3586
|
}
|
|
3404
3587
|
for (const entry of entries) {
|
|
3405
3588
|
if (results.length >= maxResults) break;
|
|
3406
|
-
const fullPath =
|
|
3589
|
+
const fullPath = join4(dirPath, entry.name);
|
|
3407
3590
|
if (entry.isDirectory()) {
|
|
3408
3591
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3409
3592
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -4143,7 +4326,7 @@ ${preamble}`;
|
|
|
4143
4326
|
}
|
|
4144
4327
|
|
|
4145
4328
|
// src/tools/builtin/save-last-response.ts
|
|
4146
|
-
import { mkdirSync as
|
|
4329
|
+
import { mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
4147
4330
|
import { dirname as dirname3 } from "path";
|
|
4148
4331
|
var lastResponseStore = { content: "" };
|
|
4149
4332
|
function cleanupRejectedTeeFile(filePath) {
|
|
@@ -4193,7 +4376,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4193
4376
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
4194
4377
|
}
|
|
4195
4378
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
4196
|
-
|
|
4379
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
4197
4380
|
atomicWriteFileSync(filePath, content);
|
|
4198
4381
|
const lines = content.split("\n").length;
|
|
4199
4382
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -4201,11 +4384,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4201
4384
|
};
|
|
4202
4385
|
|
|
4203
4386
|
// src/tools/builtin/save-memory.ts
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import { join as
|
|
4387
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
4388
|
+
import { join as join5 } from "path";
|
|
4206
4389
|
import { homedir as homedir3 } from "os";
|
|
4207
4390
|
function getMemoryFilePath() {
|
|
4208
|
-
return
|
|
4391
|
+
return join5(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
4209
4392
|
}
|
|
4210
4393
|
function formatTimestamp() {
|
|
4211
4394
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -4229,16 +4412,16 @@ var saveMemoryTool = {
|
|
|
4229
4412
|
const content = String(args["content"] ?? "").trim();
|
|
4230
4413
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
4231
4414
|
const memoryPath = getMemoryFilePath();
|
|
4232
|
-
const configDir =
|
|
4233
|
-
if (!
|
|
4234
|
-
|
|
4415
|
+
const configDir = join5(homedir3(), CONFIG_DIR_NAME);
|
|
4416
|
+
if (!existsSync10(configDir)) {
|
|
4417
|
+
mkdirSync4(configDir, { recursive: true });
|
|
4235
4418
|
}
|
|
4236
4419
|
const timestamp = formatTimestamp();
|
|
4237
4420
|
const entry = `
|
|
4238
4421
|
## ${timestamp}
|
|
4239
4422
|
${content}
|
|
4240
4423
|
`;
|
|
4241
|
-
const previous =
|
|
4424
|
+
const previous = existsSync10(memoryPath) ? readFileSync7(memoryPath, "utf-8") : "";
|
|
4242
4425
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
4243
4426
|
const byteSize = statSync6(memoryPath).size;
|
|
4244
4427
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -4775,11 +4958,27 @@ var spawnAgentTool = {
|
|
|
4775
4958
|
if (!ctx.provider) {
|
|
4776
4959
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
4777
4960
|
}
|
|
4961
|
+
const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
|
|
4962
|
+
const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
|
|
4963
|
+
const taskCount = singleTask ? 1 : tasksArr.length;
|
|
4964
|
+
const hookPayload = {
|
|
4965
|
+
task: singleTask || void 0,
|
|
4966
|
+
tasks: tasksArr.length > 0 ? tasksArr : void 0,
|
|
4967
|
+
taskCount,
|
|
4968
|
+
maxRounds
|
|
4969
|
+
};
|
|
4970
|
+
const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
|
|
4971
|
+
const startDeny = startDecisions.find((d) => d.action === "deny");
|
|
4972
|
+
if (startDeny) {
|
|
4973
|
+
throw new ToolError("spawn_agent", "SubagentStart hook denied: " + (startDeny.reason ?? "denied"));
|
|
4974
|
+
}
|
|
4975
|
+
let subagentStatus = "error";
|
|
4778
4976
|
const guardWasActive = subAgentGuard.active;
|
|
4779
4977
|
subAgentGuard.active = true;
|
|
4780
4978
|
try {
|
|
4781
4979
|
if (singleTask) {
|
|
4782
4980
|
const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
|
|
4981
|
+
subagentStatus = "ok";
|
|
4783
4982
|
return [
|
|
4784
4983
|
"## Sub-Agent Result",
|
|
4785
4984
|
"",
|
|
@@ -4808,8 +5007,10 @@ var spawnAgentTool = {
|
|
|
4808
5007
|
});
|
|
4809
5008
|
lines.push("---");
|
|
4810
5009
|
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
5010
|
+
subagentStatus = "ok";
|
|
4811
5011
|
return lines.join("\n");
|
|
4812
5012
|
} finally {
|
|
5013
|
+
runLifecycleHooks(hookConfig, "SubagentStop", { ...hookPayload, status: subagentStatus }, hookConfigDir);
|
|
4813
5014
|
subAgentGuard.active = guardWasActive;
|
|
4814
5015
|
}
|
|
4815
5016
|
}
|
|
@@ -5032,14 +5233,14 @@ var taskStopTool = {
|
|
|
5032
5233
|
|
|
5033
5234
|
// src/tools/builtin/git-tools.ts
|
|
5034
5235
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5035
|
-
import { existsSync as
|
|
5036
|
-
import { join as
|
|
5236
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5237
|
+
import { join as join6 } from "path";
|
|
5037
5238
|
function assertGitRepo(cwd) {
|
|
5038
5239
|
let dir = cwd;
|
|
5039
5240
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
5040
5241
|
while (dir && dir !== root) {
|
|
5041
|
-
if (
|
|
5042
|
-
const parent =
|
|
5242
|
+
if (existsSync11(join6(dir, ".git"))) return;
|
|
5243
|
+
const parent = join6(dir, "..");
|
|
5043
5244
|
if (parent === dir) break;
|
|
5044
5245
|
dir = parent;
|
|
5045
5246
|
}
|
|
@@ -5304,7 +5505,7 @@ ${commitOutput.trim()}`;
|
|
|
5304
5505
|
};
|
|
5305
5506
|
|
|
5306
5507
|
// src/tools/builtin/notebook-edit.ts
|
|
5307
|
-
import { readFileSync as
|
|
5508
|
+
import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
|
|
5308
5509
|
import { writeFile } from "fs/promises";
|
|
5309
5510
|
import { resolve as resolve5, extname as extname2 } from "path";
|
|
5310
5511
|
var notebookEditTool = {
|
|
@@ -5359,10 +5560,10 @@ var notebookEditTool = {
|
|
|
5359
5560
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
5360
5561
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
5361
5562
|
}
|
|
5362
|
-
if (!
|
|
5563
|
+
if (!existsSync12(absPath)) {
|
|
5363
5564
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
5364
5565
|
}
|
|
5365
|
-
const raw =
|
|
5566
|
+
const raw = readFileSync8(absPath, "utf-8");
|
|
5366
5567
|
const nb = parseNotebook(raw);
|
|
5367
5568
|
const cellIdx0 = cellIndexRaw - 1;
|
|
5368
5569
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -5779,8 +5980,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
5779
5980
|
|
|
5780
5981
|
// src/tools/registry.ts
|
|
5781
5982
|
import { pathToFileURL } from "url";
|
|
5782
|
-
import { existsSync as
|
|
5783
|
-
import { join as
|
|
5983
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync6 } from "fs";
|
|
5984
|
+
import { join as join7 } from "path";
|
|
5784
5985
|
var ToolRegistry = class {
|
|
5785
5986
|
tools = /* @__PURE__ */ new Map();
|
|
5786
5987
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -5941,9 +6142,9 @@ var ToolRegistry = class {
|
|
|
5941
6142
|
* Returns the number of successfully loaded plugins.
|
|
5942
6143
|
*/
|
|
5943
6144
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
5944
|
-
if (!
|
|
6145
|
+
if (!existsSync13(pluginsDir)) {
|
|
5945
6146
|
try {
|
|
5946
|
-
|
|
6147
|
+
mkdirSync5(pluginsDir, { recursive: true });
|
|
5947
6148
|
} catch {
|
|
5948
6149
|
}
|
|
5949
6150
|
return 0;
|
|
@@ -5967,12 +6168,12 @@ var ToolRegistry = class {
|
|
|
5967
6168
|
process.stderr.write(
|
|
5968
6169
|
`
|
|
5969
6170
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
5970
|
-
` + files.map((f) => ` + ${
|
|
6171
|
+
` + files.map((f) => ` + ${join7(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
5971
6172
|
);
|
|
5972
6173
|
let loaded = 0;
|
|
5973
6174
|
for (const file of files) {
|
|
5974
6175
|
try {
|
|
5975
|
-
const fileUrl = pathToFileURL(
|
|
6176
|
+
const fileUrl = pathToFileURL(join7(pluginsDir, file)).href;
|
|
5976
6177
|
const mod = await import(fileUrl);
|
|
5977
6178
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
5978
6179
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -6004,6 +6205,12 @@ export {
|
|
|
6004
6205
|
theme,
|
|
6005
6206
|
undoStack,
|
|
6006
6207
|
renderDiff,
|
|
6208
|
+
listHooks,
|
|
6209
|
+
trustHook,
|
|
6210
|
+
untrustHook,
|
|
6211
|
+
getPendingHookTrust,
|
|
6212
|
+
runLifecycleHooks,
|
|
6213
|
+
runHook,
|
|
6007
6214
|
isInterrupted,
|
|
6008
6215
|
requestInterrupt,
|
|
6009
6216
|
resetInterrupt,
|
|
@@ -6011,7 +6218,6 @@ export {
|
|
|
6011
6218
|
rlInternal,
|
|
6012
6219
|
groupCallsByPhase,
|
|
6013
6220
|
runSafePhases,
|
|
6014
|
-
runHook,
|
|
6015
6221
|
checkPermissionWithProfile,
|
|
6016
6222
|
formatPermissionProfileWarning,
|
|
6017
6223
|
checkNetworkPolicy,
|