jinzd-ai-cli 0.4.210 → 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 +122 -1
- package/README.zh-CN.md +63 -0
- package/dist/{batch-MFPYHOIL.js → batch-WIKFEOAZ.js} +2 -2
- package/dist/{chat-index-UBCWHBLR.js → chat-index-R2E27VXN.js} +1 -1
- package/dist/{chunk-GRJNQJA5.js → chunk-GBBVCZ4W.js} +1 -1
- package/dist/{chunk-7JWT3KXO.js → chunk-HW2ALWQJ.js} +2 -2
- package/dist/{chunk-E2ZWWE6Q.js → chunk-HX6N6QEY.js} +1 -1
- package/dist/{chunk-DJ35G5VO.js → chunk-IFZX26K7.js} +1 -1
- package/dist/{chunk-HZQVX7VF.js → chunk-KE4B3NOQ.js} +1 -1
- package/dist/{chunk-NWP7C6CC.js → chunk-LU6FBJQ5.js} +1 -1
- package/dist/{chunk-NEPFADHX.js → chunk-N5LB3PPL.js} +567 -87
- package/dist/{chunk-P6MTFCXB.js → chunk-NYCBOVNF.js} +62 -5
- package/dist/{chunk-W7UKO3PS.js → chunk-OQGVGPEK.js} +5 -0
- package/dist/{ci-QLJUDLMY.js → ci-X3LNUFZV.js} +2 -2
- package/dist/{constants-47OR72MV.js → constants-KDWG4KR4.js} +1 -1
- package/dist/{doctor-cli-32COMA5K.js → doctor-cli-TJTWIW7U.js} +4 -4
- package/dist/electron-server.js +947 -280
- package/dist/{hub-3NWJUCLK.js → hub-JWSV4MKC.js} +1 -1
- package/dist/index.js +160 -18
- package/dist/{run-tests-NT2UIUVB.js → run-tests-D7YBY4XB.js} +1 -1
- package/dist/{run-tests-UT7RZ7Y3.js → run-tests-H4Q2PRX6.js} +2 -2
- package/dist/{server-AIHVMLNU.js → server-OFKGDRYI.js} +4 -4
- package/dist/{server-E5D54DIZ.js → server-VNCN7AY5.js} +187 -29
- package/dist/{task-orchestrator-BFDSTSOH.js → task-orchestrator-AL2E642M.js} +4 -4
- package/dist/{usage-P4B2RZKL.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,13 @@ ${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
|
+
import { tmpdir } from "os";
|
|
1059
1060
|
|
|
1060
1061
|
// src/core/readline-internal.ts
|
|
1061
1062
|
function rlInternal(rl) {
|
|
@@ -1262,6 +1263,155 @@ function simpleDiff(oldLines, newLines) {
|
|
|
1262
1263
|
|
|
1263
1264
|
// src/tools/hooks.ts
|
|
1264
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
|
+
}
|
|
1265
1415
|
function rewriteTemplate(template, isWindows) {
|
|
1266
1416
|
const ref = (name) => isWindows ? `%${name}%` : `$${name}`;
|
|
1267
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"));
|
|
@@ -1272,7 +1422,7 @@ function runHook(template, vars) {
|
|
|
1272
1422
|
const cmd = rewriteTemplate(template, isWindows);
|
|
1273
1423
|
try {
|
|
1274
1424
|
execSync(cmd, {
|
|
1275
|
-
timeout:
|
|
1425
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
1276
1426
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1277
1427
|
encoding: "utf-8",
|
|
1278
1428
|
env: {
|
|
@@ -1290,6 +1440,7 @@ function runHook(template, vars) {
|
|
|
1290
1440
|
}
|
|
1291
1441
|
|
|
1292
1442
|
// src/tools/permissions.ts
|
|
1443
|
+
import { isAbsolute, resolve as resolve3 } from "path";
|
|
1293
1444
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
1294
1445
|
for (const rule of rules) {
|
|
1295
1446
|
if (rule.tool !== "*" && rule.tool !== toolName) continue;
|
|
@@ -1310,6 +1461,225 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
1310
1461
|
}
|
|
1311
1462
|
return defaultAction;
|
|
1312
1463
|
}
|
|
1464
|
+
var BUILTIN_PROFILE_NAMES = /* @__PURE__ */ new Set(["legacy", "read-only", "workspace-write", "danger-full-access"]);
|
|
1465
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
1466
|
+
"read_file",
|
|
1467
|
+
"list_dir",
|
|
1468
|
+
"grep_files",
|
|
1469
|
+
"glob_files",
|
|
1470
|
+
"find_symbol",
|
|
1471
|
+
"get_outline",
|
|
1472
|
+
"find_references",
|
|
1473
|
+
"search_code",
|
|
1474
|
+
"git_status",
|
|
1475
|
+
"git_diff",
|
|
1476
|
+
"git_log",
|
|
1477
|
+
"task_list",
|
|
1478
|
+
"web_fetch",
|
|
1479
|
+
"web_search",
|
|
1480
|
+
"google_search",
|
|
1481
|
+
"ask_user",
|
|
1482
|
+
"write_todos",
|
|
1483
|
+
"recall_memory"
|
|
1484
|
+
]);
|
|
1485
|
+
function normalizePathForPermission(value) {
|
|
1486
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1487
|
+
}
|
|
1488
|
+
function isInsidePath(root, target) {
|
|
1489
|
+
const absoluteTarget = isAbsolute(target) ? target : resolve3(root, target);
|
|
1490
|
+
const r = normalizePathForPermission(resolve3(root));
|
|
1491
|
+
const t = normalizePathForPermission(absoluteTarget);
|
|
1492
|
+
return t === r || t.startsWith(`${r}/`);
|
|
1493
|
+
}
|
|
1494
|
+
function extractPathArg(toolName, args) {
|
|
1495
|
+
const raw = args["path"] ?? args["filePath"] ?? args["notebook_path"];
|
|
1496
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
1497
|
+
if (toolName === "save_last_response") {
|
|
1498
|
+
const output = args["outputPath"] ?? args["file"] ?? args["path"];
|
|
1499
|
+
if (typeof output === "string" && output.trim()) return output.trim();
|
|
1500
|
+
}
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
function isExplicitFileWriteTool(toolName) {
|
|
1504
|
+
return toolName === "write_file" || toolName === "edit_file" || toolName === "notebook_edit" || toolName === "save_last_response";
|
|
1505
|
+
}
|
|
1506
|
+
function resolveProfileName(ctx) {
|
|
1507
|
+
const requested = ctx?.profileName ?? "legacy";
|
|
1508
|
+
if (ctx?.allowedProfiles && ctx.allowedProfiles.length > 0 && !ctx.allowedProfiles.includes(requested)) {
|
|
1509
|
+
return "legacy";
|
|
1510
|
+
}
|
|
1511
|
+
if (BUILTIN_PROFILE_NAMES.has(requested)) return requested;
|
|
1512
|
+
const ext = ctx?.profile?.extends;
|
|
1513
|
+
return ext && BUILTIN_PROFILE_NAMES.has(ext) ? ext : "legacy";
|
|
1514
|
+
}
|
|
1515
|
+
function getProfileRules(ctx) {
|
|
1516
|
+
return ctx?.profile?.rules ?? [];
|
|
1517
|
+
}
|
|
1518
|
+
function getProfileDefaultAction(name, ctx) {
|
|
1519
|
+
if (ctx?.profile?.defaultAction) return ctx.profile.defaultAction;
|
|
1520
|
+
if (name === "danger-full-access") return "auto-approve";
|
|
1521
|
+
return "confirm";
|
|
1522
|
+
}
|
|
1523
|
+
function getWorkspaceRoots(ctx) {
|
|
1524
|
+
const roots = [ctx?.workspaceRoot, ...ctx?.profile?.workspaceRoots ?? []].filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
1525
|
+
return roots;
|
|
1526
|
+
}
|
|
1527
|
+
function getAllowedWriteRoots(ctx) {
|
|
1528
|
+
const roots = [...getWorkspaceRoots(ctx)];
|
|
1529
|
+
const allowTemp = ctx?.profile?.allowTemp !== false;
|
|
1530
|
+
if (allowTemp) roots.push(...ctx?.tempDirs ?? []);
|
|
1531
|
+
return roots.filter((v) => v.trim().length > 0);
|
|
1532
|
+
}
|
|
1533
|
+
function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
1534
|
+
if (profileName === "legacy") return null;
|
|
1535
|
+
if (profileName === "read-only") {
|
|
1536
|
+
const mcpReadOnly = toolName.startsWith("mcp__") && dangerLevel === "safe";
|
|
1537
|
+
if (dangerLevel !== "safe" || !READ_ONLY_TOOLS.has(toolName) && !mcpReadOnly) {
|
|
1538
|
+
return { action: "deny", reason: "blocked by read-only permission profile", profileName };
|
|
1539
|
+
}
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
if (profileName === "workspace-write") {
|
|
1543
|
+
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools always require confirmation", profileName };
|
|
1544
|
+
if (dangerLevel === "write" && isExplicitFileWriteTool(toolName)) {
|
|
1545
|
+
const target = extractPathArg(toolName, args);
|
|
1546
|
+
if (!target) return { action: "confirm", reason: "file write target is not explicit", profileName };
|
|
1547
|
+
const allowedRoots = getAllowedWriteRoots(ctx);
|
|
1548
|
+
if (allowedRoots.length === 0 || !allowedRoots.some((root) => isInsidePath(root, target))) {
|
|
1549
|
+
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName };
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
if (profileName === "danger-full-access") {
|
|
1555
|
+
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName };
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
function checkPermissionWithProfile(toolName, args, dangerLevel, rules, defaultAction = "confirm", profileContext) {
|
|
1561
|
+
const profileName = resolveProfileName(profileContext);
|
|
1562
|
+
const hard = profileHardDecision(profileName, toolName, args, dangerLevel, profileContext);
|
|
1563
|
+
if (hard?.action === "deny") return hard;
|
|
1564
|
+
const combinedRules = [...getProfileRules(profileContext), ...rules];
|
|
1565
|
+
const profileDefault = profileName === "legacy" ? defaultAction : getProfileDefaultAction(profileName, profileContext);
|
|
1566
|
+
const action = checkPermission(toolName, args, dangerLevel, combinedRules, profileDefault);
|
|
1567
|
+
if (hard?.action === "confirm" && action === "auto-approve") return hard;
|
|
1568
|
+
return { action, profileName };
|
|
1569
|
+
}
|
|
1570
|
+
function formatPermissionProfileWarning(profileName) {
|
|
1571
|
+
if (profileName === "danger-full-access") {
|
|
1572
|
+
return "\u26A0 danger-full-access permission profile is active. Write tools may auto-run; destructive tools still require confirmation.";
|
|
1573
|
+
}
|
|
1574
|
+
if (profileName === "read-only") return "Read-only permission profile is active. Write and destructive tools are blocked.";
|
|
1575
|
+
if (profileName === "workspace-write") return "Workspace-write permission profile is active. Writes outside workspace/temp roots are blocked.";
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
var NETWORK_TOOL_HOSTS = {
|
|
1579
|
+
web_search: ["cn.bing.com", "www.google.com"],
|
|
1580
|
+
google_search: ["www.googleapis.com"]
|
|
1581
|
+
};
|
|
1582
|
+
var NETWORK_TOOL_PORTS = {
|
|
1583
|
+
web_search: [443],
|
|
1584
|
+
google_search: [443]
|
|
1585
|
+
};
|
|
1586
|
+
function networkActionToPermission(action) {
|
|
1587
|
+
if (action === "allow") return null;
|
|
1588
|
+
return action;
|
|
1589
|
+
}
|
|
1590
|
+
function domainMatches(pattern, host) {
|
|
1591
|
+
const p = pattern.trim().toLowerCase().replace(/^\*\./, "");
|
|
1592
|
+
const h = host.trim().toLowerCase().replace(/:\d+$/, "");
|
|
1593
|
+
return h === p || h.endsWith(`.${p}`);
|
|
1594
|
+
}
|
|
1595
|
+
function isPrivateNetworkHost(host) {
|
|
1596
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1597
|
+
if (h === "" || h === "localhost") return true;
|
|
1598
|
+
if (/^(127|10)\./.test(h)) return true;
|
|
1599
|
+
if (/^192\.168\./.test(h)) return true;
|
|
1600
|
+
if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(h)) return true;
|
|
1601
|
+
if (/^169\.254\./.test(h)) return true;
|
|
1602
|
+
if (h === "::1" || h === "::") return true;
|
|
1603
|
+
if (/^f[cd][0-9a-f]*:/i.test(h) || /^fe[89ab][0-9a-f]*:/i.test(h)) return true;
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
function extractNetworkHosts(toolName, args) {
|
|
1607
|
+
if (toolName === "web_fetch") {
|
|
1608
|
+
const raw = String(args["url"] ?? "").trim();
|
|
1609
|
+
try {
|
|
1610
|
+
return raw ? [new URL(raw).hostname] : [];
|
|
1611
|
+
} catch {
|
|
1612
|
+
return [];
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
return NETWORK_TOOL_HOSTS[toolName] ?? [];
|
|
1616
|
+
}
|
|
1617
|
+
function extractNetworkPorts(toolName, args) {
|
|
1618
|
+
if (toolName === "web_fetch") {
|
|
1619
|
+
const raw = String(args["url"] ?? "").trim();
|
|
1620
|
+
try {
|
|
1621
|
+
if (!raw) return [];
|
|
1622
|
+
const url = new URL(raw);
|
|
1623
|
+
if (url.port) return [Number(url.port)];
|
|
1624
|
+
return [url.protocol === "http:" ? 80 : 443];
|
|
1625
|
+
} catch {
|
|
1626
|
+
return [];
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return NETWORK_TOOL_PORTS[toolName] ?? [];
|
|
1630
|
+
}
|
|
1631
|
+
function isShellNetworkCommand(command) {
|
|
1632
|
+
return /\b(curl|wget|aria2c|Invoke-WebRequest|Invoke-RestMethod|iwr|irm|ssh|scp|sftp|ftp|telnet|nc|ncat|netcat)\b/i.test(command) || /\b(git\s+(clone|fetch|pull|push)|npm\s+(install|publish|view)|pnpm\s+(add|install)|yarn\s+(add|install)|pip\s+install|python\s+-m\s+pip\s+install)\b/i.test(command);
|
|
1633
|
+
}
|
|
1634
|
+
function networkToolAction(toolName, policy) {
|
|
1635
|
+
if (toolName === "web_fetch") return policy.tools?.web_fetch ?? policy.defaultAction ?? "confirm";
|
|
1636
|
+
if (toolName === "web_search") return policy.tools?.web_search ?? policy.defaultAction ?? "confirm";
|
|
1637
|
+
if (toolName === "google_search") return policy.tools?.google_search ?? policy.defaultAction ?? "confirm";
|
|
1638
|
+
if (toolName.startsWith("mcp__")) return policy.tools?.mcp ?? policy.defaultAction ?? "confirm";
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1641
|
+
function checkNetworkPolicy(toolName, args, policy) {
|
|
1642
|
+
if (!policy?.enabled) return null;
|
|
1643
|
+
let action = networkToolAction(toolName, policy);
|
|
1644
|
+
let reason = "network access requires confirmation";
|
|
1645
|
+
if (toolName === "bash") {
|
|
1646
|
+
const command = String(args["command"] ?? "");
|
|
1647
|
+
if (!isShellNetworkCommand(command)) return null;
|
|
1648
|
+
action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
|
|
1649
|
+
reason = "shell command may access the network";
|
|
1650
|
+
}
|
|
1651
|
+
if (!action) return null;
|
|
1652
|
+
const hosts = extractNetworkHosts(toolName, args);
|
|
1653
|
+
for (const host of hosts) {
|
|
1654
|
+
if (policy.denyDomains?.some((pattern) => domainMatches(pattern, host))) {
|
|
1655
|
+
return { action: "deny", reason: `network host "${host}" is denied by networkPolicy`, profileName: "networkPolicy" };
|
|
1656
|
+
}
|
|
1657
|
+
if (!policy.allowPrivateNetwork && isPrivateNetworkHost(host)) {
|
|
1658
|
+
return { action: "deny", reason: `network host "${host}" is private/internal`, profileName: "networkPolicy" };
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (hosts.length > 0 && policy.allowDomains && policy.allowDomains.length > 0) {
|
|
1662
|
+
const allAllowed = hosts.every((host) => policy.allowDomains.some((pattern) => domainMatches(pattern, host)));
|
|
1663
|
+
if (!allAllowed) {
|
|
1664
|
+
return { action: "deny", reason: `network host is not in networkPolicy.allowDomains`, profileName: "networkPolicy" };
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const ports = extractNetworkPorts(toolName, args);
|
|
1668
|
+
for (const port of ports) {
|
|
1669
|
+
if (policy.denyPorts?.includes(port)) {
|
|
1670
|
+
return { action: "deny", reason: `network port ${port} is denied by networkPolicy`, profileName: "networkPolicy" };
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (ports.length > 0 && policy.allowPorts && policy.allowPorts.length > 0) {
|
|
1674
|
+
const allPortsAllowed = ports.every((port) => policy.allowPorts.includes(port));
|
|
1675
|
+
if (!allPortsAllowed) {
|
|
1676
|
+
return { action: "deny", reason: `network port is not in networkPolicy.allowPorts`, profileName: "networkPolicy" };
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
const permissionAction = networkActionToPermission(action);
|
|
1680
|
+
if (!permissionAction) return null;
|
|
1681
|
+
return { action: permissionAction, reason, profileName: "networkPolicy" };
|
|
1682
|
+
}
|
|
1313
1683
|
|
|
1314
1684
|
// src/tools/truncate.ts
|
|
1315
1685
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
@@ -1564,11 +1934,23 @@ var ToolExecutor = class {
|
|
|
1564
1934
|
/** 权限规则(可选) */
|
|
1565
1935
|
permissionRules = [];
|
|
1566
1936
|
defaultPermission = "confirm";
|
|
1937
|
+
permissionProfileName = "legacy";
|
|
1938
|
+
permissionProfile;
|
|
1939
|
+
allowedPermissionProfiles = [];
|
|
1940
|
+
workspaceRoot = process.cwd();
|
|
1941
|
+
networkPolicy;
|
|
1942
|
+
hookConfigDir = process.cwd();
|
|
1567
1943
|
/** 注入 hooks 和 permission rules 配置 */
|
|
1568
1944
|
setConfig(opts) {
|
|
1569
1945
|
this.hookConfig = opts.hookConfig;
|
|
1570
1946
|
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
1571
1947
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
1948
|
+
if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
|
|
1949
|
+
if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
|
|
1950
|
+
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
1951
|
+
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
1952
|
+
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
1953
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
1572
1954
|
}
|
|
1573
1955
|
async execute(call) {
|
|
1574
1956
|
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
@@ -1583,31 +1965,97 @@ var ToolExecutor = class {
|
|
|
1583
1965
|
};
|
|
1584
1966
|
}
|
|
1585
1967
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
1968
|
+
let toolCallAlreadyPrinted = false;
|
|
1586
1969
|
runHook(this.hookConfig?.preToolExecution, {
|
|
1587
1970
|
tool: call.name,
|
|
1588
1971
|
dangerLevel,
|
|
1589
1972
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
1590
1973
|
});
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
+
}
|
|
1992
|
+
const permission = checkPermissionWithProfile(
|
|
1993
|
+
call.name,
|
|
1994
|
+
call.arguments,
|
|
1995
|
+
dangerLevel,
|
|
1996
|
+
this.permissionRules,
|
|
1997
|
+
this.defaultPermission,
|
|
1998
|
+
{
|
|
1999
|
+
profileName: this.permissionProfileName,
|
|
2000
|
+
profile: this.permissionProfile,
|
|
2001
|
+
allowedProfiles: this.allowedPermissionProfiles,
|
|
2002
|
+
workspaceRoot: this.workspaceRoot,
|
|
2003
|
+
tempDirs: [tmpdir()]
|
|
1595
2004
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
}
|
|
2005
|
+
);
|
|
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
|
+
}
|
|
2015
|
+
if (networkPermission?.action === "deny") {
|
|
2016
|
+
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
2017
|
+
return {
|
|
2018
|
+
callId: call.id,
|
|
2019
|
+
content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
|
|
2020
|
+
isError: true
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
if (permission.action === "deny") {
|
|
2024
|
+
const reason = permission.reason ? ` (${permission.reason})` : "";
|
|
2025
|
+
return {
|
|
2026
|
+
callId: call.id,
|
|
2027
|
+
content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
|
|
2028
|
+
isError: true
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
|
|
2032
|
+
this.printToolCall(call);
|
|
2033
|
+
try {
|
|
2034
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
2035
|
+
const content = truncateOutput(rawContent, call.name);
|
|
2036
|
+
const wasTruncated = content !== rawContent;
|
|
2037
|
+
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2038
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2039
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
2040
|
+
return { callId: call.id, content, isError: false };
|
|
2041
|
+
} catch (err) {
|
|
2042
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2043
|
+
this.printToolResult(call.name, message, true, false);
|
|
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 });
|
|
2046
|
+
return { callId: call.id, content: message, isError: true };
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
|
|
2050
|
+
this.printToolCall(call);
|
|
2051
|
+
toolCallAlreadyPrinted = true;
|
|
2052
|
+
const confirmed = await this.confirm(call, "write");
|
|
2053
|
+
if (!confirmed) {
|
|
2054
|
+
return {
|
|
2055
|
+
callId: call.id,
|
|
2056
|
+
content: `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`,
|
|
2057
|
+
isError: true
|
|
2058
|
+
};
|
|
1611
2059
|
}
|
|
1612
2060
|
}
|
|
1613
2061
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
@@ -1620,11 +2068,13 @@ var ToolExecutor = class {
|
|
|
1620
2068
|
const wasTruncated = content !== rawContent;
|
|
1621
2069
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1622
2070
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2071
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
1623
2072
|
return { callId: call.id, content, isError: false };
|
|
1624
2073
|
} catch (err) {
|
|
1625
2074
|
const message = err instanceof Error ? err.message : String(err);
|
|
1626
2075
|
this.printToolResult(call.name, message, true, false);
|
|
1627
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 });
|
|
1628
2078
|
return { callId: call.id, content: message, isError: true };
|
|
1629
2079
|
}
|
|
1630
2080
|
}
|
|
@@ -1658,11 +2108,13 @@ var ToolExecutor = class {
|
|
|
1658
2108
|
const wasTruncated = content !== rawContent;
|
|
1659
2109
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1660
2110
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2111
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
1661
2112
|
return { callId: call.id, content, isError: false };
|
|
1662
2113
|
} catch (err) {
|
|
1663
2114
|
const message = err instanceof Error ? err.message : String(err);
|
|
1664
2115
|
this.printToolResult(call.name, message, true, false);
|
|
1665
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 });
|
|
1666
2118
|
return { callId: call.id, content: message, isError: true };
|
|
1667
2119
|
}
|
|
1668
2120
|
}
|
|
@@ -1697,6 +2149,9 @@ var ToolExecutor = class {
|
|
|
1697
2149
|
* 然后让用户 approve all / reject all / 选择性 approve。
|
|
1698
2150
|
*/
|
|
1699
2151
|
async executeBatchFileWrites(calls) {
|
|
2152
|
+
if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
|
|
2153
|
+
return Promise.all(calls.map((call) => this.execute(call)));
|
|
2154
|
+
}
|
|
1700
2155
|
console.log();
|
|
1701
2156
|
console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`));
|
|
1702
2157
|
console.log(theme.dim("\u2500".repeat(50)));
|
|
@@ -1770,7 +2225,7 @@ var ToolExecutor = class {
|
|
|
1770
2225
|
rl.resume();
|
|
1771
2226
|
process.stdout.write(prompt);
|
|
1772
2227
|
this.confirming = true;
|
|
1773
|
-
return new Promise((
|
|
2228
|
+
return new Promise((resolve6) => {
|
|
1774
2229
|
let completed = false;
|
|
1775
2230
|
const cleanup = (result) => {
|
|
1776
2231
|
if (completed) return;
|
|
@@ -1780,7 +2235,7 @@ var ToolExecutor = class {
|
|
|
1780
2235
|
rl.pause();
|
|
1781
2236
|
rlAny.output = savedOutput;
|
|
1782
2237
|
this.confirming = false;
|
|
1783
|
-
|
|
2238
|
+
resolve6(result);
|
|
1784
2239
|
};
|
|
1785
2240
|
const onLine = (line) => {
|
|
1786
2241
|
const trimmed = line.trim();
|
|
@@ -1847,10 +2302,10 @@ var ToolExecutor = class {
|
|
|
1847
2302
|
const filePath = String(call.arguments["path"] ?? "");
|
|
1848
2303
|
const newContent = String(call.arguments["content"] ?? "");
|
|
1849
2304
|
if (!filePath) return;
|
|
1850
|
-
if (
|
|
2305
|
+
if (existsSync5(filePath)) {
|
|
1851
2306
|
let oldContent;
|
|
1852
2307
|
try {
|
|
1853
|
-
oldContent =
|
|
2308
|
+
oldContent = readFileSync4(filePath, "utf-8");
|
|
1854
2309
|
} catch {
|
|
1855
2310
|
return;
|
|
1856
2311
|
}
|
|
@@ -1873,7 +2328,7 @@ var ToolExecutor = class {
|
|
|
1873
2328
|
}
|
|
1874
2329
|
} else if (call.name === "edit_file") {
|
|
1875
2330
|
const filePath = String(call.arguments["path"] ?? "");
|
|
1876
|
-
if (!filePath || !
|
|
2331
|
+
if (!filePath || !existsSync5(filePath)) return;
|
|
1877
2332
|
const oldStr = call.arguments["old_str"];
|
|
1878
2333
|
const newStr = call.arguments["new_str"];
|
|
1879
2334
|
if (oldStr !== void 0) {
|
|
@@ -1900,7 +2355,7 @@ var ToolExecutor = class {
|
|
|
1900
2355
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
1901
2356
|
let fileContent;
|
|
1902
2357
|
try {
|
|
1903
|
-
fileContent =
|
|
2358
|
+
fileContent = readFileSync4(filePath, "utf-8");
|
|
1904
2359
|
} catch {
|
|
1905
2360
|
return;
|
|
1906
2361
|
}
|
|
@@ -1954,7 +2409,7 @@ var ToolExecutor = class {
|
|
|
1954
2409
|
rl.resume();
|
|
1955
2410
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
1956
2411
|
this.confirming = true;
|
|
1957
|
-
return new Promise((
|
|
2412
|
+
return new Promise((resolve6) => {
|
|
1958
2413
|
let completed = false;
|
|
1959
2414
|
const cleanup = (answer) => {
|
|
1960
2415
|
if (completed) return;
|
|
@@ -1964,7 +2419,7 @@ var ToolExecutor = class {
|
|
|
1964
2419
|
rl.pause();
|
|
1965
2420
|
rlAny.output = savedOutput;
|
|
1966
2421
|
this.confirming = false;
|
|
1967
|
-
|
|
2422
|
+
resolve6(answer === "y");
|
|
1968
2423
|
};
|
|
1969
2424
|
const onLine = (line) => {
|
|
1970
2425
|
const trimmed = line.trim();
|
|
@@ -1992,11 +2447,11 @@ var ToolExecutor = class {
|
|
|
1992
2447
|
};
|
|
1993
2448
|
|
|
1994
2449
|
// src/tools/sensitive-paths.ts
|
|
1995
|
-
import { resolve as
|
|
2450
|
+
import { resolve as resolve4, sep as sep2, basename as basename2 } from "path";
|
|
1996
2451
|
import { homedir as homedir2 } from "os";
|
|
1997
2452
|
var home = homedir2();
|
|
1998
2453
|
function norm(p) {
|
|
1999
|
-
const abs =
|
|
2454
|
+
const abs = resolve4(p);
|
|
2000
2455
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
2001
2456
|
}
|
|
2002
2457
|
function homeRel(p) {
|
|
@@ -2135,7 +2590,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2135
2590
|
}
|
|
2136
2591
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
2137
2592
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2138
|
-
|
|
2593
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
2139
2594
|
if (appendMode) {
|
|
2140
2595
|
appendFileSync(filePath, content, encoding);
|
|
2141
2596
|
} else {
|
|
@@ -2155,7 +2610,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2155
2610
|
};
|
|
2156
2611
|
|
|
2157
2612
|
// src/tools/builtin/edit-file.ts
|
|
2158
|
-
import { readFileSync as
|
|
2613
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
2159
2614
|
|
|
2160
2615
|
// src/tools/builtin/patch-apply.ts
|
|
2161
2616
|
function parseUnifiedDiff(patch) {
|
|
@@ -2516,7 +2971,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2516
2971
|
const filePath = String(args["path"] ?? "");
|
|
2517
2972
|
const encoding = args["encoding"] ?? "utf-8";
|
|
2518
2973
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
2519
|
-
if (!
|
|
2974
|
+
if (!existsSync6(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
2520
2975
|
const verdict = classifyWritePath(filePath);
|
|
2521
2976
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
2522
2977
|
throw new ToolError(
|
|
@@ -2524,7 +2979,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2524
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.`
|
|
2525
2980
|
);
|
|
2526
2981
|
}
|
|
2527
|
-
const original =
|
|
2982
|
+
const original = readFileSync5(filePath, encoding);
|
|
2528
2983
|
if (args["patch"] !== void 0) {
|
|
2529
2984
|
const patchText = String(args["patch"] ?? "");
|
|
2530
2985
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -2682,8 +3137,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
2682
3137
|
}
|
|
2683
3138
|
|
|
2684
3139
|
// src/tools/builtin/list-dir.ts
|
|
2685
|
-
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as
|
|
2686
|
-
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";
|
|
2687
3142
|
var listDirTool = {
|
|
2688
3143
|
definition: {
|
|
2689
3144
|
name: "list_dir",
|
|
@@ -2705,7 +3160,7 @@ var listDirTool = {
|
|
|
2705
3160
|
async execute(args) {
|
|
2706
3161
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
2707
3162
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
2708
|
-
if (!
|
|
3163
|
+
if (!existsSync7(dirPath)) {
|
|
2709
3164
|
const targetName = basename3(dirPath).toLowerCase();
|
|
2710
3165
|
const cwd = process.cwd();
|
|
2711
3166
|
const suggestions = [];
|
|
@@ -2763,11 +3218,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
2763
3218
|
if (entry.isDirectory()) {
|
|
2764
3219
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
2765
3220
|
if (recursive) {
|
|
2766
|
-
listRecursive(
|
|
3221
|
+
listRecursive(join2(basePath, entry.name), indent + " ", true, lines);
|
|
2767
3222
|
}
|
|
2768
3223
|
} else {
|
|
2769
3224
|
try {
|
|
2770
|
-
const stat = statSync3(
|
|
3225
|
+
const stat = statSync3(join2(basePath, entry.name));
|
|
2771
3226
|
const size = formatSize(stat.size);
|
|
2772
3227
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
2773
3228
|
} catch {
|
|
@@ -2783,9 +3238,9 @@ function formatSize(bytes) {
|
|
|
2783
3238
|
}
|
|
2784
3239
|
|
|
2785
3240
|
// src/tools/builtin/grep-files.ts
|
|
2786
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
3241
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync8 } from "fs";
|
|
2787
3242
|
import { readFile } from "fs/promises";
|
|
2788
|
-
import { join as
|
|
3243
|
+
import { join as join3, relative } from "path";
|
|
2789
3244
|
var grepFilesTool = {
|
|
2790
3245
|
definition: {
|
|
2791
3246
|
name: "grep_files",
|
|
@@ -2836,7 +3291,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
2836
3291
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
2837
3292
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
2838
3293
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
2839
|
-
if (!
|
|
3294
|
+
if (!existsSync8(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
2840
3295
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
2841
3296
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
2842
3297
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -2930,11 +3385,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
2930
3385
|
if (paths.length >= maxFiles) return;
|
|
2931
3386
|
if (entry.isDirectory()) {
|
|
2932
3387
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
2933
|
-
walk(
|
|
3388
|
+
walk(join3(dirPath, entry.name));
|
|
2934
3389
|
} else if (entry.isFile()) {
|
|
2935
3390
|
if (isBinary(entry.name)) continue;
|
|
2936
3391
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
2937
|
-
const fullPath =
|
|
3392
|
+
const fullPath = join3(dirPath, entry.name);
|
|
2938
3393
|
try {
|
|
2939
3394
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
2940
3395
|
} catch {
|
|
@@ -2989,7 +3444,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
2989
3444
|
}
|
|
2990
3445
|
let content;
|
|
2991
3446
|
try {
|
|
2992
|
-
content =
|
|
3447
|
+
content = readFileSync6(fullPath, "utf-8");
|
|
2993
3448
|
} catch {
|
|
2994
3449
|
return;
|
|
2995
3450
|
}
|
|
@@ -3020,8 +3475,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3020
3475
|
}
|
|
3021
3476
|
|
|
3022
3477
|
// src/tools/builtin/glob-files.ts
|
|
3023
|
-
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as
|
|
3024
|
-
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";
|
|
3025
3480
|
var globFilesTool = {
|
|
3026
3481
|
definition: {
|
|
3027
3482
|
name: "glob_files",
|
|
@@ -3054,7 +3509,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
3054
3509
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
3055
3510
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
3056
3511
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
3057
|
-
if (!
|
|
3512
|
+
if (!existsSync9(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
3058
3513
|
const regex = globToRegex(pattern);
|
|
3059
3514
|
const matches = [];
|
|
3060
3515
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -3131,7 +3586,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
3131
3586
|
}
|
|
3132
3587
|
for (const entry of entries) {
|
|
3133
3588
|
if (results.length >= maxResults) break;
|
|
3134
|
-
const fullPath =
|
|
3589
|
+
const fullPath = join4(dirPath, entry.name);
|
|
3135
3590
|
if (entry.isDirectory()) {
|
|
3136
3591
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3137
3592
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -3213,7 +3668,7 @@ var runInteractiveTool = {
|
|
|
3213
3668
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
3214
3669
|
};
|
|
3215
3670
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
3216
|
-
return new Promise((
|
|
3671
|
+
return new Promise((resolve6) => {
|
|
3217
3672
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
3218
3673
|
cwd: process.cwd(),
|
|
3219
3674
|
env,
|
|
@@ -3246,22 +3701,22 @@ var runInteractiveTool = {
|
|
|
3246
3701
|
setTimeout(writeNextLine, 400);
|
|
3247
3702
|
const timer = setTimeout(() => {
|
|
3248
3703
|
child.kill();
|
|
3249
|
-
|
|
3704
|
+
resolve6(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
3250
3705
|
${buildOutput(stdout, stderr)}`);
|
|
3251
3706
|
}, timeout);
|
|
3252
3707
|
child.on("close", (code) => {
|
|
3253
3708
|
clearTimeout(timer);
|
|
3254
3709
|
const output = buildOutput(stdout, stderr);
|
|
3255
3710
|
if (code !== 0 && code !== null) {
|
|
3256
|
-
|
|
3711
|
+
resolve6(`${prefixWarnings}Exit code ${code}:
|
|
3257
3712
|
${output}`);
|
|
3258
3713
|
} else {
|
|
3259
|
-
|
|
3714
|
+
resolve6(`${prefixWarnings}${output || "(no output)"}`);
|
|
3260
3715
|
}
|
|
3261
3716
|
});
|
|
3262
3717
|
child.on("error", (err) => {
|
|
3263
3718
|
clearTimeout(timer);
|
|
3264
|
-
|
|
3719
|
+
resolve6(
|
|
3265
3720
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
3266
3721
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
3267
3722
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -3871,7 +4326,7 @@ ${preamble}`;
|
|
|
3871
4326
|
}
|
|
3872
4327
|
|
|
3873
4328
|
// src/tools/builtin/save-last-response.ts
|
|
3874
|
-
import { mkdirSync as
|
|
4329
|
+
import { mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
3875
4330
|
import { dirname as dirname3 } from "path";
|
|
3876
4331
|
var lastResponseStore = { content: "" };
|
|
3877
4332
|
function cleanupRejectedTeeFile(filePath) {
|
|
@@ -3921,7 +4376,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
3921
4376
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
3922
4377
|
}
|
|
3923
4378
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
3924
|
-
|
|
4379
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
3925
4380
|
atomicWriteFileSync(filePath, content);
|
|
3926
4381
|
const lines = content.split("\n").length;
|
|
3927
4382
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -3929,11 +4384,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
3929
4384
|
};
|
|
3930
4385
|
|
|
3931
4386
|
// src/tools/builtin/save-memory.ts
|
|
3932
|
-
import { existsSync as
|
|
3933
|
-
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";
|
|
3934
4389
|
import { homedir as homedir3 } from "os";
|
|
3935
4390
|
function getMemoryFilePath() {
|
|
3936
|
-
return
|
|
4391
|
+
return join5(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
3937
4392
|
}
|
|
3938
4393
|
function formatTimestamp() {
|
|
3939
4394
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3957,16 +4412,16 @@ var saveMemoryTool = {
|
|
|
3957
4412
|
const content = String(args["content"] ?? "").trim();
|
|
3958
4413
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
3959
4414
|
const memoryPath = getMemoryFilePath();
|
|
3960
|
-
const configDir =
|
|
3961
|
-
if (!
|
|
3962
|
-
|
|
4415
|
+
const configDir = join5(homedir3(), CONFIG_DIR_NAME);
|
|
4416
|
+
if (!existsSync10(configDir)) {
|
|
4417
|
+
mkdirSync4(configDir, { recursive: true });
|
|
3963
4418
|
}
|
|
3964
4419
|
const timestamp = formatTimestamp();
|
|
3965
4420
|
const entry = `
|
|
3966
4421
|
## ${timestamp}
|
|
3967
4422
|
${content}
|
|
3968
4423
|
`;
|
|
3969
|
-
const previous =
|
|
4424
|
+
const previous = existsSync10(memoryPath) ? readFileSync7(memoryPath, "utf-8") : "";
|
|
3970
4425
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
3971
4426
|
const byteSize = statSync6(memoryPath).size;
|
|
3972
4427
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -4013,7 +4468,7 @@ function promptUser(rl, question) {
|
|
|
4013
4468
|
console.log();
|
|
4014
4469
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
4015
4470
|
process.stdout.write(chalk4.cyan("> "));
|
|
4016
|
-
return new Promise((
|
|
4471
|
+
return new Promise((resolve6) => {
|
|
4017
4472
|
let completed = false;
|
|
4018
4473
|
const cleanup = (answer) => {
|
|
4019
4474
|
if (completed) return;
|
|
@@ -4023,7 +4478,7 @@ function promptUser(rl, question) {
|
|
|
4023
4478
|
rl.pause();
|
|
4024
4479
|
rlAny.output = savedOutput;
|
|
4025
4480
|
askUserContext.prompting = false;
|
|
4026
|
-
|
|
4481
|
+
resolve6(answer);
|
|
4027
4482
|
};
|
|
4028
4483
|
const onLine = (line) => {
|
|
4029
4484
|
cleanup(line);
|
|
@@ -4503,11 +4958,27 @@ var spawnAgentTool = {
|
|
|
4503
4958
|
if (!ctx.provider) {
|
|
4504
4959
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
4505
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";
|
|
4506
4976
|
const guardWasActive = subAgentGuard.active;
|
|
4507
4977
|
subAgentGuard.active = true;
|
|
4508
4978
|
try {
|
|
4509
4979
|
if (singleTask) {
|
|
4510
4980
|
const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
|
|
4981
|
+
subagentStatus = "ok";
|
|
4511
4982
|
return [
|
|
4512
4983
|
"## Sub-Agent Result",
|
|
4513
4984
|
"",
|
|
@@ -4536,8 +5007,10 @@ var spawnAgentTool = {
|
|
|
4536
5007
|
});
|
|
4537
5008
|
lines.push("---");
|
|
4538
5009
|
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
5010
|
+
subagentStatus = "ok";
|
|
4539
5011
|
return lines.join("\n");
|
|
4540
5012
|
} finally {
|
|
5013
|
+
runLifecycleHooks(hookConfig, "SubagentStop", { ...hookPayload, status: subagentStatus }, hookConfigDir);
|
|
4541
5014
|
subAgentGuard.active = guardWasActive;
|
|
4542
5015
|
}
|
|
4543
5016
|
}
|
|
@@ -4760,14 +5233,14 @@ var taskStopTool = {
|
|
|
4760
5233
|
|
|
4761
5234
|
// src/tools/builtin/git-tools.ts
|
|
4762
5235
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4763
|
-
import { existsSync as
|
|
4764
|
-
import { join as
|
|
5236
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5237
|
+
import { join as join6 } from "path";
|
|
4765
5238
|
function assertGitRepo(cwd) {
|
|
4766
5239
|
let dir = cwd;
|
|
4767
5240
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
4768
5241
|
while (dir && dir !== root) {
|
|
4769
|
-
if (
|
|
4770
|
-
const parent =
|
|
5242
|
+
if (existsSync11(join6(dir, ".git"))) return;
|
|
5243
|
+
const parent = join6(dir, "..");
|
|
4771
5244
|
if (parent === dir) break;
|
|
4772
5245
|
dir = parent;
|
|
4773
5246
|
}
|
|
@@ -5032,9 +5505,9 @@ ${commitOutput.trim()}`;
|
|
|
5032
5505
|
};
|
|
5033
5506
|
|
|
5034
5507
|
// src/tools/builtin/notebook-edit.ts
|
|
5035
|
-
import { readFileSync as
|
|
5508
|
+
import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
|
|
5036
5509
|
import { writeFile } from "fs/promises";
|
|
5037
|
-
import { resolve as
|
|
5510
|
+
import { resolve as resolve5, extname as extname2 } from "path";
|
|
5038
5511
|
var notebookEditTool = {
|
|
5039
5512
|
definition: {
|
|
5040
5513
|
name: "notebook_edit",
|
|
@@ -5083,14 +5556,14 @@ var notebookEditTool = {
|
|
|
5083
5556
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
5084
5557
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
5085
5558
|
}
|
|
5086
|
-
const absPath =
|
|
5559
|
+
const absPath = resolve5(filePath);
|
|
5087
5560
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
5088
5561
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
5089
5562
|
}
|
|
5090
|
-
if (!
|
|
5563
|
+
if (!existsSync12(absPath)) {
|
|
5091
5564
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
5092
5565
|
}
|
|
5093
|
-
const raw =
|
|
5566
|
+
const raw = readFileSync8(absPath, "utf-8");
|
|
5094
5567
|
const nb = parseNotebook(raw);
|
|
5095
5568
|
const cellIdx0 = cellIndexRaw - 1;
|
|
5096
5569
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -5507,8 +5980,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
5507
5980
|
|
|
5508
5981
|
// src/tools/registry.ts
|
|
5509
5982
|
import { pathToFileURL } from "url";
|
|
5510
|
-
import { existsSync as
|
|
5511
|
-
import { join as
|
|
5983
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync6 } from "fs";
|
|
5984
|
+
import { join as join7 } from "path";
|
|
5512
5985
|
var ToolRegistry = class {
|
|
5513
5986
|
tools = /* @__PURE__ */ new Map();
|
|
5514
5987
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -5669,9 +6142,9 @@ var ToolRegistry = class {
|
|
|
5669
6142
|
* Returns the number of successfully loaded plugins.
|
|
5670
6143
|
*/
|
|
5671
6144
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
5672
|
-
if (!
|
|
6145
|
+
if (!existsSync13(pluginsDir)) {
|
|
5673
6146
|
try {
|
|
5674
|
-
|
|
6147
|
+
mkdirSync5(pluginsDir, { recursive: true });
|
|
5675
6148
|
} catch {
|
|
5676
6149
|
}
|
|
5677
6150
|
return 0;
|
|
@@ -5695,12 +6168,12 @@ var ToolRegistry = class {
|
|
|
5695
6168
|
process.stderr.write(
|
|
5696
6169
|
`
|
|
5697
6170
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
5698
|
-
` + files.map((f) => ` + ${
|
|
6171
|
+
` + files.map((f) => ` + ${join7(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
5699
6172
|
);
|
|
5700
6173
|
let loaded = 0;
|
|
5701
6174
|
for (const file of files) {
|
|
5702
6175
|
try {
|
|
5703
|
-
const fileUrl = pathToFileURL(
|
|
6176
|
+
const fileUrl = pathToFileURL(join7(pluginsDir, file)).href;
|
|
5704
6177
|
const mod = await import(fileUrl);
|
|
5705
6178
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
5706
6179
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -5732,6 +6205,12 @@ export {
|
|
|
5732
6205
|
theme,
|
|
5733
6206
|
undoStack,
|
|
5734
6207
|
renderDiff,
|
|
6208
|
+
listHooks,
|
|
6209
|
+
trustHook,
|
|
6210
|
+
untrustHook,
|
|
6211
|
+
getPendingHookTrust,
|
|
6212
|
+
runLifecycleHooks,
|
|
6213
|
+
runHook,
|
|
5735
6214
|
isInterrupted,
|
|
5736
6215
|
requestInterrupt,
|
|
5737
6216
|
resetInterrupt,
|
|
@@ -5739,8 +6218,9 @@ export {
|
|
|
5739
6218
|
rlInternal,
|
|
5740
6219
|
groupCallsByPhase,
|
|
5741
6220
|
runSafePhases,
|
|
5742
|
-
|
|
5743
|
-
|
|
6221
|
+
checkPermissionWithProfile,
|
|
6222
|
+
formatPermissionProfileWarning,
|
|
6223
|
+
checkNetworkPolicy,
|
|
5744
6224
|
setMaxOutputCap,
|
|
5745
6225
|
setContextWindow,
|
|
5746
6226
|
truncateForPersist,
|