jinzd-ai-cli 0.4.211 → 0.4.213
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 +30 -0
- package/README.zh-CN.md +30 -0
- package/dist/{batch-DE4RXKZD.js → batch-PKY63TME.js} +2 -2
- package/dist/{chat-index-UBCWHBLR.js → chat-index-R2E27VXN.js} +1 -1
- package/dist/{chunk-JBWA73GK.js → chunk-7NTFMLJJ.js} +1 -1
- package/dist/{chunk-C3OU2OPF.js → chunk-FGUKBIYO.js} +2 -2
- package/dist/{chunk-BE6ERF7M.js → chunk-G3PW3AOB.js} +1 -1
- package/dist/{chunk-E5XCM4A6.js → chunk-IWLVH32D.js} +1 -1
- package/dist/{chunk-ZY2N2N6T.js → chunk-J35V3IAH.js} +1 -1
- package/dist/{chunk-UOROWTGG.js → chunk-MRGWPCFQ.js} +26 -3
- package/dist/{chunk-W7UKO3PS.js → chunk-OQGVGPEK.js} +5 -0
- package/dist/{chunk-ZOPYREL5.js → chunk-THNECAAY.js} +464 -67
- package/dist/{chunk-6NS6643Y.js → chunk-UDMU4FUF.js} +1 -1
- package/dist/{ci-XMUEX526.js → ci-HBZ6CIW5.js} +2 -2
- package/dist/{constants-AWTIQIWG.js → constants-CAUBTSFW.js} +1 -1
- package/dist/{doctor-cli-SSI6ETFT.js → doctor-cli-5Z5UWYW6.js} +4 -4
- package/dist/electron-server.js +794 -241
- package/dist/{hub-INUJND2G.js → hub-ZRADBR76.js} +1 -1
- package/dist/index.js +205 -19
- package/dist/{run-tests-PACN4UYX.js → run-tests-7J3PNWVS.js} +1 -1
- package/dist/{run-tests-3MHWUF43.js → run-tests-DSOJBJWA.js} +2 -2
- package/dist/{server-7USZJJAH.js → server-PWBWHVYB.js} +4 -4
- package/dist/{server-BTSKOPQI.js → server-U4I7RI42.js} +175 -10
- package/dist/{task-orchestrator-OV4O25MX.js → task-orchestrator-RUXWRMQV.js} +4 -4
- package/dist/{usage-T2P6FTE7.js → usage-2P6TIBTN.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
VERSION,
|
|
38
38
|
buildUserIdentityPrompt,
|
|
39
39
|
runTestsTool
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-J35V3IAH.js";
|
|
41
41
|
import {
|
|
42
42
|
hasSemanticIndex,
|
|
43
43
|
semanticSearch
|
|
@@ -49,8 +49,9 @@ import "./chunk-IEQAE3QG.js";
|
|
|
49
49
|
import {
|
|
50
50
|
loadChatIndex,
|
|
51
51
|
redactJson,
|
|
52
|
+
scanString,
|
|
52
53
|
searchChatMemory
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-OQGVGPEK.js";
|
|
54
55
|
import "./chunk-JV5N65KN.js";
|
|
55
56
|
import {
|
|
56
57
|
atomicWriteFileSync
|
|
@@ -60,8 +61,8 @@ import {
|
|
|
60
61
|
import express from "express";
|
|
61
62
|
import { createServer } from "http";
|
|
62
63
|
import { WebSocketServer } from "ws";
|
|
63
|
-
import { join as
|
|
64
|
-
import { existsSync as
|
|
64
|
+
import { join as join21, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
|
|
65
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
|
|
65
66
|
import { networkInterfaces } from "os";
|
|
66
67
|
|
|
67
68
|
// src/config/config-manager.ts
|
|
@@ -208,10 +209,33 @@ var ConfigSchema = z.object({
|
|
|
208
209
|
env: z.record(z.string()).optional(),
|
|
209
210
|
timeout: z.number().default(3e4)
|
|
210
211
|
})).default({}),
|
|
211
|
-
//
|
|
212
|
+
// Hooks 生命周期(v0.4.212+):兼容旧 pre/post 字符串,同时支持 events.<EventName> 结构化 JSON hook。
|
|
212
213
|
hooks: z.object({
|
|
214
|
+
enabled: z.boolean().default(true),
|
|
213
215
|
preToolExecution: z.string().optional(),
|
|
214
|
-
postToolExecution: z.string().optional()
|
|
216
|
+
postToolExecution: z.string().optional(),
|
|
217
|
+
events: z.record(z.union([
|
|
218
|
+
z.string(),
|
|
219
|
+
z.object({
|
|
220
|
+
command: z.string(),
|
|
221
|
+
source: z.enum(["user", "project", "managed"]).default("user"),
|
|
222
|
+
description: z.string().optional(),
|
|
223
|
+
required: z.boolean().default(false),
|
|
224
|
+
timeoutMs: z.number().int().min(100).max(3e4).default(5e3),
|
|
225
|
+
disabled: z.boolean().default(false)
|
|
226
|
+
}),
|
|
227
|
+
z.array(z.union([
|
|
228
|
+
z.string(),
|
|
229
|
+
z.object({
|
|
230
|
+
command: z.string(),
|
|
231
|
+
source: z.enum(["user", "project", "managed"]).default("user"),
|
|
232
|
+
description: z.string().optional(),
|
|
233
|
+
required: z.boolean().default(false),
|
|
234
|
+
timeoutMs: z.number().int().min(100).max(3e4).default(5e3),
|
|
235
|
+
disabled: z.boolean().default(false)
|
|
236
|
+
})
|
|
237
|
+
]))
|
|
238
|
+
])).default({})
|
|
215
239
|
}).optional(),
|
|
216
240
|
// 网络访问治理(v0.4.211+):默认关闭以保持兼容;开启后统一治理 web/search/MCP/shell 网络出口。
|
|
217
241
|
networkPolicy: z.object({
|
|
@@ -5761,12 +5785,12 @@ ${content}`;
|
|
|
5761
5785
|
};
|
|
5762
5786
|
|
|
5763
5787
|
// src/tools/builtin/write-file.ts
|
|
5764
|
-
import { appendFileSync, mkdirSync as
|
|
5788
|
+
import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
|
|
5765
5789
|
import { dirname as dirname3 } from "path";
|
|
5766
5790
|
|
|
5767
5791
|
// src/tools/executor.ts
|
|
5768
5792
|
import chalk3 from "chalk";
|
|
5769
|
-
import { existsSync as
|
|
5793
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
5770
5794
|
import { tmpdir } from "os";
|
|
5771
5795
|
|
|
5772
5796
|
// src/core/readline-internal.ts
|
|
@@ -5974,6 +5998,139 @@ function simpleDiff(oldLines, newLines) {
|
|
|
5974
5998
|
|
|
5975
5999
|
// src/tools/hooks.ts
|
|
5976
6000
|
import { execSync } from "child_process";
|
|
6001
|
+
import { createHash } from "crypto";
|
|
6002
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "fs";
|
|
6003
|
+
import { join as join3 } from "path";
|
|
6004
|
+
var TRUST_FILE = "hooks-trust.json";
|
|
6005
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
6006
|
+
function asArray(entry) {
|
|
6007
|
+
if (!entry) return [];
|
|
6008
|
+
const raw = Array.isArray(entry) ? entry : [entry];
|
|
6009
|
+
return raw.map((item) => typeof item === "string" ? { command: item } : item);
|
|
6010
|
+
}
|
|
6011
|
+
function hookTrustPath(configDir) {
|
|
6012
|
+
return join3(configDir, TRUST_FILE);
|
|
6013
|
+
}
|
|
6014
|
+
function loadHookTrustStore(configDir) {
|
|
6015
|
+
const file = hookTrustPath(configDir);
|
|
6016
|
+
if (!existsSync6(file)) return { records: [] };
|
|
6017
|
+
try {
|
|
6018
|
+
const parsed = JSON.parse(readFileSync5(file, "utf-8"));
|
|
6019
|
+
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
6020
|
+
} catch {
|
|
6021
|
+
return { records: [] };
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
function hashHook(event, command) {
|
|
6025
|
+
return createHash("sha256").update(`${event}
|
|
6026
|
+
${command}`, "utf-8").digest("hex");
|
|
6027
|
+
}
|
|
6028
|
+
function isTrusted(source, event, hash, store) {
|
|
6029
|
+
if (source === "user" || source === "managed") return true;
|
|
6030
|
+
return store.records.some((r) => r.source === source && r.event === event && r.hash === hash);
|
|
6031
|
+
}
|
|
6032
|
+
function listHooks(config, configDir) {
|
|
6033
|
+
if (!config || config.enabled === false) return [];
|
|
6034
|
+
const store = loadHookTrustStore(configDir);
|
|
6035
|
+
const out = [];
|
|
6036
|
+
const push = (event, hook, index) => {
|
|
6037
|
+
if (!hook.command) return;
|
|
6038
|
+
const source = hook.source ?? "user";
|
|
6039
|
+
const hash = hashHook(event, hook.command);
|
|
6040
|
+
out.push({
|
|
6041
|
+
id: `${event}:${index}`,
|
|
6042
|
+
event,
|
|
6043
|
+
command: hook.command,
|
|
6044
|
+
source,
|
|
6045
|
+
hash,
|
|
6046
|
+
trusted: isTrusted(source, event, hash, store),
|
|
6047
|
+
disabled: hook.disabled === true,
|
|
6048
|
+
description: hook.description
|
|
6049
|
+
});
|
|
6050
|
+
};
|
|
6051
|
+
if (config.preToolExecution) push("PreToolUse", { command: config.preToolExecution }, 0);
|
|
6052
|
+
if (config.postToolExecution) push("PostToolUse", { command: config.postToolExecution }, 0);
|
|
6053
|
+
for (const [event, entry] of Object.entries(config.events ?? {})) {
|
|
6054
|
+
asArray(entry).forEach((hook, i) => push(event, hook, i));
|
|
6055
|
+
}
|
|
6056
|
+
return out;
|
|
6057
|
+
}
|
|
6058
|
+
function getPendingHookTrust(config, configDir) {
|
|
6059
|
+
return listHooks(config, configDir).filter((h) => h.source === "project" && !h.disabled && !h.trusted);
|
|
6060
|
+
}
|
|
6061
|
+
function normalizeDecision(value, hook) {
|
|
6062
|
+
if (!value || typeof value !== "object") return null;
|
|
6063
|
+
const obj = value;
|
|
6064
|
+
const action = obj.action;
|
|
6065
|
+
if (action !== "allow" && action !== "deny" && action !== "ask") return null;
|
|
6066
|
+
return {
|
|
6067
|
+
action,
|
|
6068
|
+
reason: typeof obj.reason === "string" ? obj.reason : void 0,
|
|
6069
|
+
prompt: typeof obj.prompt === "string" ? obj.prompt : void 0,
|
|
6070
|
+
contextAppend: typeof obj.contextAppend === "string" ? obj.contextAppend : void 0,
|
|
6071
|
+
warning: typeof obj.warning === "string" ? obj.warning : void 0,
|
|
6072
|
+
warnings: Array.isArray(obj.warnings) ? obj.warnings.filter((x) => typeof x === "string") : void 0,
|
|
6073
|
+
hook
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
function runStructuredHook(hook, descriptor, payload) {
|
|
6077
|
+
if (hook.disabled === true) return null;
|
|
6078
|
+
const timeout = Math.max(100, Math.min(hook.timeoutMs ?? DEFAULT_TIMEOUT_MS, 3e4));
|
|
6079
|
+
try {
|
|
6080
|
+
const stdout = execSync(hook.command, {
|
|
6081
|
+
timeout,
|
|
6082
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
6083
|
+
encoding: "utf-8",
|
|
6084
|
+
env: {
|
|
6085
|
+
...process.env,
|
|
6086
|
+
AICLI_HOOK_EVENT: payload.event,
|
|
6087
|
+
AICLI_HOOK_EVENT_JSON: JSON.stringify(payload)
|
|
6088
|
+
}
|
|
6089
|
+
}).trim();
|
|
6090
|
+
if (!stdout) return null;
|
|
6091
|
+
return normalizeDecision(JSON.parse(stdout), descriptor);
|
|
6092
|
+
} catch (err) {
|
|
6093
|
+
process.stderr.write(`\u26A0 Hook failed: ${hook.command.slice(0, 100)}
|
|
6094
|
+
`);
|
|
6095
|
+
if (hook.required) {
|
|
6096
|
+
return { action: "deny", reason: err instanceof Error ? err.message : String(err), hook: descriptor };
|
|
6097
|
+
}
|
|
6098
|
+
return null;
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
function runLifecycleHooks(config, event, payload, opts) {
|
|
6102
|
+
if (!config || config.enabled === false) return [];
|
|
6103
|
+
const options = typeof opts === "string" ? { configDir: opts } : opts;
|
|
6104
|
+
const store = loadHookTrustStore(options.configDir);
|
|
6105
|
+
const hooks = asArray(config.events?.[event]);
|
|
6106
|
+
const decisions = [];
|
|
6107
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
6108
|
+
const hook = hooks[i];
|
|
6109
|
+
if (!hook.command || hook.disabled) continue;
|
|
6110
|
+
const source = hook.source ?? "user";
|
|
6111
|
+
const hash = hashHook(event, hook.command);
|
|
6112
|
+
const descriptor = {
|
|
6113
|
+
id: `${event}:${i}`,
|
|
6114
|
+
event,
|
|
6115
|
+
command: hook.command,
|
|
6116
|
+
source,
|
|
6117
|
+
hash,
|
|
6118
|
+
trusted: isTrusted(source, event, hash, store),
|
|
6119
|
+
disabled: false,
|
|
6120
|
+
description: hook.description
|
|
6121
|
+
};
|
|
6122
|
+
if (!descriptor.trusted) {
|
|
6123
|
+
options.onSummary?.(`hook skipped: ${event} project hook requires trust (${hash.slice(0, 12)})`);
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6126
|
+
const decision = runStructuredHook(hook, descriptor, { event, ...payload });
|
|
6127
|
+
if (decision) {
|
|
6128
|
+
decisions.push(decision);
|
|
6129
|
+
options.onSummary?.(`hook ${event}: ${decision.action}${decision.reason ? ` (${decision.reason})` : ""}`);
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
return decisions;
|
|
6133
|
+
}
|
|
5977
6134
|
function rewriteTemplate(template, isWindows) {
|
|
5978
6135
|
const ref = (name) => isWindows ? `%${name}%` : `$${name}`;
|
|
5979
6136
|
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"));
|
|
@@ -5984,7 +6141,7 @@ function runHook(template, vars) {
|
|
|
5984
6141
|
const cmd = rewriteTemplate(template, isWindows);
|
|
5985
6142
|
try {
|
|
5986
6143
|
execSync(cmd, {
|
|
5987
|
-
timeout:
|
|
6144
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
5988
6145
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5989
6146
|
encoding: "utf-8",
|
|
5990
6147
|
env: {
|
|
@@ -6384,8 +6541,8 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
6384
6541
|
});
|
|
6385
6542
|
|
|
6386
6543
|
// src/diagnostics/tool-stats.ts
|
|
6387
|
-
import { existsSync as
|
|
6388
|
-
import { join as
|
|
6544
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
6545
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
6389
6546
|
import { homedir as homedir3 } from "os";
|
|
6390
6547
|
var STATS_FILE_NAME = "tool-stats.json";
|
|
6391
6548
|
var STATS_VERSION = 1;
|
|
@@ -6395,16 +6552,16 @@ var dirty = false;
|
|
|
6395
6552
|
var pendingWrites = 0;
|
|
6396
6553
|
var configDirOverride;
|
|
6397
6554
|
function statsFilePath() {
|
|
6398
|
-
const base = configDirOverride ??
|
|
6399
|
-
return
|
|
6555
|
+
const base = configDirOverride ?? join4(homedir3(), CONFIG_DIR_NAME);
|
|
6556
|
+
return join4(base, STATS_FILE_NAME);
|
|
6400
6557
|
}
|
|
6401
6558
|
function load() {
|
|
6402
6559
|
const path3 = statsFilePath();
|
|
6403
|
-
if (!
|
|
6560
|
+
if (!existsSync7(path3)) {
|
|
6404
6561
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
6405
6562
|
}
|
|
6406
6563
|
try {
|
|
6407
|
-
const raw = JSON.parse(
|
|
6564
|
+
const raw = JSON.parse(readFileSync6(path3, "utf-8"));
|
|
6408
6565
|
if (raw.version !== STATS_VERSION || !raw.entries) {
|
|
6409
6566
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
6410
6567
|
}
|
|
@@ -6462,7 +6619,7 @@ function flush() {
|
|
|
6462
6619
|
if (!dirty || state === null) return;
|
|
6463
6620
|
const path3 = statsFilePath();
|
|
6464
6621
|
try {
|
|
6465
|
-
|
|
6622
|
+
mkdirSync4(dirname2(path3), { recursive: true });
|
|
6466
6623
|
atomicWriteFileSync(path3, JSON.stringify(state, null, 2));
|
|
6467
6624
|
dirty = false;
|
|
6468
6625
|
pendingWrites = 0;
|
|
@@ -6470,6 +6627,157 @@ function flush() {
|
|
|
6470
6627
|
}
|
|
6471
6628
|
}
|
|
6472
6629
|
|
|
6630
|
+
// src/tools/action-classifier.ts
|
|
6631
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
6632
|
+
import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
|
|
6633
|
+
var RECENT_DENIED_LIMIT = 50;
|
|
6634
|
+
var recentlyDenied = [];
|
|
6635
|
+
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
6636
|
+
recentlyDenied.unshift({
|
|
6637
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6638
|
+
tool: call.name,
|
|
6639
|
+
reason: classification.reason,
|
|
6640
|
+
ruleId: classification.ruleId,
|
|
6641
|
+
argsPreview: JSON.stringify(call.arguments).slice(0, 240)
|
|
6642
|
+
});
|
|
6643
|
+
recentlyDenied.splice(RECENT_DENIED_LIMIT);
|
|
6644
|
+
}
|
|
6645
|
+
function getRecentlyDeniedAutoActions() {
|
|
6646
|
+
return [...recentlyDenied];
|
|
6647
|
+
}
|
|
6648
|
+
function clearRecentlyDeniedAutoActions() {
|
|
6649
|
+
recentlyDenied.splice(0, recentlyDenied.length);
|
|
6650
|
+
}
|
|
6651
|
+
function normalizePath(value) {
|
|
6652
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6653
|
+
}
|
|
6654
|
+
function isInside(root, target) {
|
|
6655
|
+
const absoluteRoot = resolve4(root);
|
|
6656
|
+
const absoluteTarget = isAbsolute2(target) ? target : resolve4(root, target);
|
|
6657
|
+
const base = normalizePath(absoluteRoot);
|
|
6658
|
+
const t = normalizePath(absoluteTarget);
|
|
6659
|
+
return t === base || t.startsWith(base + "/");
|
|
6660
|
+
}
|
|
6661
|
+
function getPathArg(call) {
|
|
6662
|
+
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
6663
|
+
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
6664
|
+
}
|
|
6665
|
+
function splitShellWords(command) {
|
|
6666
|
+
const out = [];
|
|
6667
|
+
const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
6668
|
+
let m;
|
|
6669
|
+
while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
6670
|
+
return out.filter(Boolean);
|
|
6671
|
+
}
|
|
6672
|
+
function readPackageManifest(root) {
|
|
6673
|
+
const p = join5(root, "package.json");
|
|
6674
|
+
if (!existsSync8(p)) return null;
|
|
6675
|
+
try {
|
|
6676
|
+
return JSON.parse(readFileSync7(p, "utf-8"));
|
|
6677
|
+
} catch {
|
|
6678
|
+
return null;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
function manifestHasDependency(root, name) {
|
|
6682
|
+
const manifest = readPackageManifest(root);
|
|
6683
|
+
if (!manifest) return false;
|
|
6684
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
6685
|
+
const deps = manifest[key];
|
|
6686
|
+
if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
|
|
6687
|
+
}
|
|
6688
|
+
return false;
|
|
6689
|
+
}
|
|
6690
|
+
function lockfileMentions(root, name) {
|
|
6691
|
+
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
6692
|
+
const p = join5(root, file);
|
|
6693
|
+
if (!existsSync8(p)) continue;
|
|
6694
|
+
try {
|
|
6695
|
+
if (readFileSync7(p, "utf-8").includes(name)) return true;
|
|
6696
|
+
} catch {
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
return false;
|
|
6700
|
+
}
|
|
6701
|
+
function getInstallPackages(command) {
|
|
6702
|
+
const words = splitShellWords(command);
|
|
6703
|
+
const cmd = words[0]?.toLowerCase();
|
|
6704
|
+
const sub = words[1]?.toLowerCase();
|
|
6705
|
+
if (!cmd || !sub) return [];
|
|
6706
|
+
const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
|
|
6707
|
+
if (!isInstall) return [];
|
|
6708
|
+
return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
|
|
6709
|
+
}
|
|
6710
|
+
function isPlainGitPush(command) {
|
|
6711
|
+
if (!/^\s*git\s+push\b/i.test(command)) return false;
|
|
6712
|
+
if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
|
|
6713
|
+
return !/[;&|`$<>\n\r]/.test(command);
|
|
6714
|
+
}
|
|
6715
|
+
function hasSecretExfiltrationShape(command) {
|
|
6716
|
+
return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
|
|
6717
|
+
}
|
|
6718
|
+
var RuleBasedActionClassifier = class {
|
|
6719
|
+
classify(call, dangerLevel, ctx) {
|
|
6720
|
+
if (call.name === "bash") return this.classifyShell(call, dangerLevel, ctx);
|
|
6721
|
+
if (call.name === "web_fetch" || call.name === "web_search" || call.name === "google_search") {
|
|
6722
|
+
return { decision: "auto-approve", reason: "read-only HTTP tool", ruleId: "auto.readonly-http", confidence: "high" };
|
|
6723
|
+
}
|
|
6724
|
+
if (dangerLevel === "safe") {
|
|
6725
|
+
return { decision: "auto-approve", reason: "safe read-only tool", ruleId: "auto.safe-tool", confidence: "high" };
|
|
6726
|
+
}
|
|
6727
|
+
if (dangerLevel === "write") {
|
|
6728
|
+
const p = getPathArg(call);
|
|
6729
|
+
if (p && (isInside(ctx.workspaceRoot, p) || (ctx.tempDirs ?? []).some((root) => isInside(root, p)))) {
|
|
6730
|
+
return { decision: "auto-approve", reason: "explicit write inside workspace/temp roots", ruleId: "auto.workspace-write", confidence: "high" };
|
|
6731
|
+
}
|
|
6732
|
+
return { decision: "confirm", reason: "write action is not proven low-risk", ruleId: "auto.write-confirm", confidence: "medium" };
|
|
6733
|
+
}
|
|
6734
|
+
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
6735
|
+
}
|
|
6736
|
+
classifyShell(call, dangerLevel, ctx) {
|
|
6737
|
+
const command = String(call.arguments.command ?? "");
|
|
6738
|
+
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
6739
|
+
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
6740
|
+
}
|
|
6741
|
+
if (/\bgit\s+push\b/i.test(command) && /--force|-f\b|\+[^\s]+/i.test(command)) {
|
|
6742
|
+
return { decision: "deny", reason: "force push is blocked in auto mode", ruleId: "deny.force-push", confidence: "high" };
|
|
6743
|
+
}
|
|
6744
|
+
if (hasSecretExfiltrationShape(command)) {
|
|
6745
|
+
return { decision: "deny", reason: "possible secret exfiltration is blocked in auto mode", ruleId: "deny.secret-exfiltration", confidence: "high" };
|
|
6746
|
+
}
|
|
6747
|
+
if (/\b(terraform|tofu)\s+destroy\b|\bkubectl\s+delete\b|\bcdk\s+destroy\b/i.test(command)) {
|
|
6748
|
+
return { decision: "confirm", reason: "infrastructure destroy requires confirmation", ruleId: "confirm.iac-destroy", confidence: "high" };
|
|
6749
|
+
}
|
|
6750
|
+
if (/\b(prisma|sequelize|knex|typeorm)\b[^\n]*\b(migrate|migration)\b|\bdb:migrate\b/i.test(command)) {
|
|
6751
|
+
return { decision: "confirm", reason: "database migration requires confirmation", ruleId: "confirm.database-migration", confidence: "high" };
|
|
6752
|
+
}
|
|
6753
|
+
if (/\b(vercel|netlify|flyctl|railway|wrangler|serverless|firebase)\b[^\n]*\bdeploy\b|\bdeploy\b[^\n]*\b(prod|production)\b/i.test(command)) {
|
|
6754
|
+
return { decision: "confirm", reason: "production deployment requires confirmation", ruleId: "confirm.production-deploy", confidence: "high" };
|
|
6755
|
+
}
|
|
6756
|
+
if (/\b(iam|policy|token|secret|api[-_]?key|credential|permission|role)\b/i.test(command)) {
|
|
6757
|
+
return { decision: "confirm", reason: "credential or permission change requires confirmation", ruleId: "confirm.credentials-permissions", confidence: "medium" };
|
|
6758
|
+
}
|
|
6759
|
+
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
6760
|
+
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
6761
|
+
}
|
|
6762
|
+
if (isPlainGitPush(command)) {
|
|
6763
|
+
return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
|
|
6764
|
+
}
|
|
6765
|
+
const pkgs = getInstallPackages(command);
|
|
6766
|
+
if (pkgs.length > 0) {
|
|
6767
|
+
const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
|
|
6768
|
+
return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
|
|
6769
|
+
}
|
|
6770
|
+
if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
|
|
6771
|
+
return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
|
|
6772
|
+
}
|
|
6773
|
+
if (dangerLevel === "safe") {
|
|
6774
|
+
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
6775
|
+
}
|
|
6776
|
+
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
6777
|
+
}
|
|
6778
|
+
};
|
|
6779
|
+
var defaultActionClassifier = new RuleBasedActionClassifier();
|
|
6780
|
+
|
|
6473
6781
|
// src/tools/executor.ts
|
|
6474
6782
|
function buildErrorPreview(content) {
|
|
6475
6783
|
const meaningful = content.split("\n").filter(
|
|
@@ -6509,6 +6817,8 @@ var ToolExecutor = class {
|
|
|
6509
6817
|
* destructive 操作仍然必须逐次确认。
|
|
6510
6818
|
*/
|
|
6511
6819
|
sessionAutoApprove = false;
|
|
6820
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
6821
|
+
sessionAutoMode = false;
|
|
6512
6822
|
/**
|
|
6513
6823
|
* Logical session key used to scope per-session state in stateful tools
|
|
6514
6824
|
* (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
|
|
@@ -6549,6 +6859,7 @@ var ToolExecutor = class {
|
|
|
6549
6859
|
allowedPermissionProfiles = [];
|
|
6550
6860
|
workspaceRoot = process.cwd();
|
|
6551
6861
|
networkPolicy;
|
|
6862
|
+
hookConfigDir = process.cwd();
|
|
6552
6863
|
/** 注入 hooks 和 permission rules 配置 */
|
|
6553
6864
|
setConfig(opts) {
|
|
6554
6865
|
this.hookConfig = opts.hookConfig;
|
|
@@ -6559,6 +6870,7 @@ var ToolExecutor = class {
|
|
|
6559
6870
|
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
6560
6871
|
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
6561
6872
|
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
6873
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
6562
6874
|
}
|
|
6563
6875
|
async execute(call) {
|
|
6564
6876
|
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
@@ -6579,6 +6891,24 @@ var ToolExecutor = class {
|
|
|
6579
6891
|
dangerLevel,
|
|
6580
6892
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
6581
6893
|
});
|
|
6894
|
+
const preToolDecisions = runLifecycleHooks(
|
|
6895
|
+
this.hookConfig,
|
|
6896
|
+
"PreToolUse",
|
|
6897
|
+
{ tool: call.name, dangerLevel, args: call.arguments },
|
|
6898
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(` [hook] ${msg}`)) }
|
|
6899
|
+
);
|
|
6900
|
+
const preDeny = preToolDecisions.find((d) => d.action === "deny");
|
|
6901
|
+
if (preDeny) {
|
|
6902
|
+
return {
|
|
6903
|
+
callId: call.id,
|
|
6904
|
+
content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
|
|
6905
|
+
isError: true
|
|
6906
|
+
};
|
|
6907
|
+
}
|
|
6908
|
+
for (const d of preToolDecisions) {
|
|
6909
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
6910
|
+
for (const w of warnings) console.log(theme.warning(` \u26A0 Hook warning: ${w}`));
|
|
6911
|
+
}
|
|
6582
6912
|
const permission = checkPermissionWithProfile(
|
|
6583
6913
|
call.name,
|
|
6584
6914
|
call.arguments,
|
|
@@ -6594,6 +6924,14 @@ var ToolExecutor = class {
|
|
|
6594
6924
|
}
|
|
6595
6925
|
);
|
|
6596
6926
|
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
6927
|
+
if (networkPermission?.action === "confirm" || permission.action === "confirm") {
|
|
6928
|
+
runLifecycleHooks(
|
|
6929
|
+
this.hookConfig,
|
|
6930
|
+
"PermissionRequest",
|
|
6931
|
+
{ tool: call.name, dangerLevel, args: call.arguments, reason: networkPermission?.reason ?? permission.reason },
|
|
6932
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(" [hook] " + msg)) }
|
|
6933
|
+
);
|
|
6934
|
+
}
|
|
6597
6935
|
if (networkPermission?.action === "deny") {
|
|
6598
6936
|
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
6599
6937
|
return {
|
|
@@ -6618,11 +6956,13 @@ var ToolExecutor = class {
|
|
|
6618
6956
|
const wasTruncated = content !== rawContent;
|
|
6619
6957
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6620
6958
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
6959
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6621
6960
|
return { callId: call.id, content, isError: false };
|
|
6622
6961
|
} catch (err) {
|
|
6623
6962
|
const message = err instanceof Error ? err.message : String(err);
|
|
6624
6963
|
this.printToolResult(call.name, message, true, false);
|
|
6625
6964
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
6965
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6626
6966
|
return { callId: call.id, content: message, isError: true };
|
|
6627
6967
|
}
|
|
6628
6968
|
}
|
|
@@ -6638,6 +6978,40 @@ var ToolExecutor = class {
|
|
|
6638
6978
|
};
|
|
6639
6979
|
}
|
|
6640
6980
|
}
|
|
6981
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
6982
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
6983
|
+
workspaceRoot: this.workspaceRoot,
|
|
6984
|
+
tempDirs: [tmpdir()]
|
|
6985
|
+
});
|
|
6986
|
+
if (classification.decision === "deny") {
|
|
6987
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
6988
|
+
return {
|
|
6989
|
+
callId: call.id,
|
|
6990
|
+
content: `[Auto denied] ${classification.reason}. Do not retry without asking.`,
|
|
6991
|
+
isError: true
|
|
6992
|
+
};
|
|
6993
|
+
}
|
|
6994
|
+
if (classification.decision === "auto-approve") {
|
|
6995
|
+
this.printToolCall(call);
|
|
6996
|
+
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
6997
|
+
console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
|
|
6998
|
+
try {
|
|
6999
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
7000
|
+
const content = truncateOutput(rawContent, call.name);
|
|
7001
|
+
const wasTruncated = content !== rawContent;
|
|
7002
|
+
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
7003
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
7004
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
7005
|
+
return { callId: call.id, content, isError: false };
|
|
7006
|
+
} catch (err) {
|
|
7007
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7008
|
+
this.printToolResult(call.name, message, true, false);
|
|
7009
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
7010
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
7011
|
+
return { callId: call.id, content: message, isError: true };
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
6641
7015
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
6642
7016
|
this.printToolCall(call);
|
|
6643
7017
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
@@ -6648,11 +7022,13 @@ var ToolExecutor = class {
|
|
|
6648
7022
|
const wasTruncated = content !== rawContent;
|
|
6649
7023
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6650
7024
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
7025
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6651
7026
|
return { callId: call.id, content, isError: false };
|
|
6652
7027
|
} catch (err) {
|
|
6653
7028
|
const message = err instanceof Error ? err.message : String(err);
|
|
6654
7029
|
this.printToolResult(call.name, message, true, false);
|
|
6655
7030
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
7031
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6656
7032
|
return { callId: call.id, content: message, isError: true };
|
|
6657
7033
|
}
|
|
6658
7034
|
}
|
|
@@ -6686,11 +7062,13 @@ var ToolExecutor = class {
|
|
|
6686
7062
|
const wasTruncated = content !== rawContent;
|
|
6687
7063
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6688
7064
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
7065
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6689
7066
|
return { callId: call.id, content, isError: false };
|
|
6690
7067
|
} catch (err) {
|
|
6691
7068
|
const message = err instanceof Error ? err.message : String(err);
|
|
6692
7069
|
this.printToolResult(call.name, message, true, false);
|
|
6693
7070
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
7071
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6694
7072
|
return { callId: call.id, content: message, isError: true };
|
|
6695
7073
|
}
|
|
6696
7074
|
}
|
|
@@ -6801,7 +7179,7 @@ var ToolExecutor = class {
|
|
|
6801
7179
|
rl.resume();
|
|
6802
7180
|
process.stdout.write(prompt);
|
|
6803
7181
|
this.confirming = true;
|
|
6804
|
-
return new Promise((
|
|
7182
|
+
return new Promise((resolve10) => {
|
|
6805
7183
|
let completed = false;
|
|
6806
7184
|
const cleanup = (result) => {
|
|
6807
7185
|
if (completed) return;
|
|
@@ -6811,7 +7189,7 @@ var ToolExecutor = class {
|
|
|
6811
7189
|
rl.pause();
|
|
6812
7190
|
rlAny.output = savedOutput;
|
|
6813
7191
|
this.confirming = false;
|
|
6814
|
-
|
|
7192
|
+
resolve10(result);
|
|
6815
7193
|
};
|
|
6816
7194
|
const onLine = (line) => {
|
|
6817
7195
|
const trimmed = line.trim();
|
|
@@ -6878,10 +7256,10 @@ var ToolExecutor = class {
|
|
|
6878
7256
|
const filePath = String(call.arguments["path"] ?? "");
|
|
6879
7257
|
const newContent = String(call.arguments["content"] ?? "");
|
|
6880
7258
|
if (!filePath) return;
|
|
6881
|
-
if (
|
|
7259
|
+
if (existsSync9(filePath)) {
|
|
6882
7260
|
let oldContent;
|
|
6883
7261
|
try {
|
|
6884
|
-
oldContent =
|
|
7262
|
+
oldContent = readFileSync8(filePath, "utf-8");
|
|
6885
7263
|
} catch {
|
|
6886
7264
|
return;
|
|
6887
7265
|
}
|
|
@@ -6904,7 +7282,7 @@ var ToolExecutor = class {
|
|
|
6904
7282
|
}
|
|
6905
7283
|
} else if (call.name === "edit_file") {
|
|
6906
7284
|
const filePath = String(call.arguments["path"] ?? "");
|
|
6907
|
-
if (!filePath || !
|
|
7285
|
+
if (!filePath || !existsSync9(filePath)) return;
|
|
6908
7286
|
const oldStr = call.arguments["old_str"];
|
|
6909
7287
|
const newStr = call.arguments["new_str"];
|
|
6910
7288
|
if (oldStr !== void 0) {
|
|
@@ -6931,7 +7309,7 @@ var ToolExecutor = class {
|
|
|
6931
7309
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
6932
7310
|
let fileContent;
|
|
6933
7311
|
try {
|
|
6934
|
-
fileContent =
|
|
7312
|
+
fileContent = readFileSync8(filePath, "utf-8");
|
|
6935
7313
|
} catch {
|
|
6936
7314
|
return;
|
|
6937
7315
|
}
|
|
@@ -6985,7 +7363,7 @@ var ToolExecutor = class {
|
|
|
6985
7363
|
rl.resume();
|
|
6986
7364
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
6987
7365
|
this.confirming = true;
|
|
6988
|
-
return new Promise((
|
|
7366
|
+
return new Promise((resolve10) => {
|
|
6989
7367
|
let completed = false;
|
|
6990
7368
|
const cleanup = (answer) => {
|
|
6991
7369
|
if (completed) return;
|
|
@@ -6995,7 +7373,7 @@ var ToolExecutor = class {
|
|
|
6995
7373
|
rl.pause();
|
|
6996
7374
|
rlAny.output = savedOutput;
|
|
6997
7375
|
this.confirming = false;
|
|
6998
|
-
|
|
7376
|
+
resolve10(answer === "y");
|
|
6999
7377
|
};
|
|
7000
7378
|
const onLine = (line) => {
|
|
7001
7379
|
const trimmed = line.trim();
|
|
@@ -7023,11 +7401,11 @@ var ToolExecutor = class {
|
|
|
7023
7401
|
};
|
|
7024
7402
|
|
|
7025
7403
|
// src/tools/sensitive-paths.ts
|
|
7026
|
-
import { resolve as
|
|
7404
|
+
import { resolve as resolve5, sep as sep2, basename as basename2 } from "path";
|
|
7027
7405
|
import { homedir as homedir4 } from "os";
|
|
7028
7406
|
var home = homedir4();
|
|
7029
7407
|
function norm(p) {
|
|
7030
|
-
const abs =
|
|
7408
|
+
const abs = resolve5(p);
|
|
7031
7409
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
7032
7410
|
}
|
|
7033
7411
|
function homeRel(p) {
|
|
@@ -7166,7 +7544,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7166
7544
|
}
|
|
7167
7545
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
7168
7546
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
7169
|
-
|
|
7547
|
+
mkdirSync5(dirname3(filePath), { recursive: true });
|
|
7170
7548
|
if (appendMode) {
|
|
7171
7549
|
appendFileSync(filePath, content, encoding);
|
|
7172
7550
|
} else {
|
|
@@ -7186,7 +7564,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7186
7564
|
};
|
|
7187
7565
|
|
|
7188
7566
|
// src/tools/builtin/edit-file.ts
|
|
7189
|
-
import { readFileSync as
|
|
7567
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
7190
7568
|
|
|
7191
7569
|
// src/tools/builtin/patch-apply.ts
|
|
7192
7570
|
function parseUnifiedDiff(patch) {
|
|
@@ -7547,7 +7925,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7547
7925
|
const filePath = String(args["path"] ?? "");
|
|
7548
7926
|
const encoding = args["encoding"] ?? "utf-8";
|
|
7549
7927
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
7550
|
-
if (!
|
|
7928
|
+
if (!existsSync10(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
7551
7929
|
const verdict = classifyWritePath(filePath);
|
|
7552
7930
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
7553
7931
|
throw new ToolError(
|
|
@@ -7555,7 +7933,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7555
7933
|
`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.`
|
|
7556
7934
|
);
|
|
7557
7935
|
}
|
|
7558
|
-
const original =
|
|
7936
|
+
const original = readFileSync9(filePath, encoding);
|
|
7559
7937
|
if (args["patch"] !== void 0) {
|
|
7560
7938
|
const patchText = String(args["patch"] ?? "");
|
|
7561
7939
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -7713,8 +8091,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
7713
8091
|
}
|
|
7714
8092
|
|
|
7715
8093
|
// src/tools/builtin/list-dir.ts
|
|
7716
|
-
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as
|
|
7717
|
-
import { join as
|
|
8094
|
+
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync11 } from "fs";
|
|
8095
|
+
import { join as join6, basename as basename3 } from "path";
|
|
7718
8096
|
var listDirTool = {
|
|
7719
8097
|
definition: {
|
|
7720
8098
|
name: "list_dir",
|
|
@@ -7736,7 +8114,7 @@ var listDirTool = {
|
|
|
7736
8114
|
async execute(args) {
|
|
7737
8115
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
7738
8116
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
7739
|
-
if (!
|
|
8117
|
+
if (!existsSync11(dirPath)) {
|
|
7740
8118
|
const targetName = basename3(dirPath).toLowerCase();
|
|
7741
8119
|
const cwd = process.cwd();
|
|
7742
8120
|
const suggestions = [];
|
|
@@ -7794,11 +8172,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
7794
8172
|
if (entry.isDirectory()) {
|
|
7795
8173
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
7796
8174
|
if (recursive) {
|
|
7797
|
-
listRecursive(
|
|
8175
|
+
listRecursive(join6(basePath, entry.name), indent + " ", true, lines);
|
|
7798
8176
|
}
|
|
7799
8177
|
} else {
|
|
7800
8178
|
try {
|
|
7801
|
-
const stat = statSync3(
|
|
8179
|
+
const stat = statSync3(join6(basePath, entry.name));
|
|
7802
8180
|
const size = formatSize(stat.size);
|
|
7803
8181
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
7804
8182
|
} catch {
|
|
@@ -7814,9 +8192,9 @@ function formatSize(bytes) {
|
|
|
7814
8192
|
}
|
|
7815
8193
|
|
|
7816
8194
|
// src/tools/builtin/grep-files.ts
|
|
7817
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8195
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync4, existsSync as existsSync12 } from "fs";
|
|
7818
8196
|
import { readFile } from "fs/promises";
|
|
7819
|
-
import { join as
|
|
8197
|
+
import { join as join7, relative } from "path";
|
|
7820
8198
|
var grepFilesTool = {
|
|
7821
8199
|
definition: {
|
|
7822
8200
|
name: "grep_files",
|
|
@@ -7867,7 +8245,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
7867
8245
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
7868
8246
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
7869
8247
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
7870
|
-
if (!
|
|
8248
|
+
if (!existsSync12(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
7871
8249
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
7872
8250
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
7873
8251
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -7961,11 +8339,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
7961
8339
|
if (paths.length >= maxFiles) return;
|
|
7962
8340
|
if (entry.isDirectory()) {
|
|
7963
8341
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
7964
|
-
walk(
|
|
8342
|
+
walk(join7(dirPath, entry.name));
|
|
7965
8343
|
} else if (entry.isFile()) {
|
|
7966
8344
|
if (isBinary(entry.name)) continue;
|
|
7967
8345
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
7968
|
-
const fullPath =
|
|
8346
|
+
const fullPath = join7(dirPath, entry.name);
|
|
7969
8347
|
try {
|
|
7970
8348
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
7971
8349
|
} catch {
|
|
@@ -8020,7 +8398,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8020
8398
|
}
|
|
8021
8399
|
let content;
|
|
8022
8400
|
try {
|
|
8023
|
-
content =
|
|
8401
|
+
content = readFileSync10(fullPath, "utf-8");
|
|
8024
8402
|
} catch {
|
|
8025
8403
|
return;
|
|
8026
8404
|
}
|
|
@@ -8051,8 +8429,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8051
8429
|
}
|
|
8052
8430
|
|
|
8053
8431
|
// src/tools/builtin/glob-files.ts
|
|
8054
|
-
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as
|
|
8055
|
-
import { join as
|
|
8432
|
+
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync13 } from "fs";
|
|
8433
|
+
import { join as join8, relative as relative2, basename as basename4 } from "path";
|
|
8056
8434
|
var globFilesTool = {
|
|
8057
8435
|
definition: {
|
|
8058
8436
|
name: "glob_files",
|
|
@@ -8085,7 +8463,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8085
8463
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8086
8464
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8087
8465
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8088
|
-
if (!
|
|
8466
|
+
if (!existsSync13(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8089
8467
|
const regex = globToRegex(pattern);
|
|
8090
8468
|
const matches = [];
|
|
8091
8469
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -8162,7 +8540,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
8162
8540
|
}
|
|
8163
8541
|
for (const entry of entries) {
|
|
8164
8542
|
if (results.length >= maxResults) break;
|
|
8165
|
-
const fullPath =
|
|
8543
|
+
const fullPath = join8(dirPath, entry.name);
|
|
8166
8544
|
if (entry.isDirectory()) {
|
|
8167
8545
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8168
8546
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -8244,7 +8622,7 @@ var runInteractiveTool = {
|
|
|
8244
8622
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8245
8623
|
};
|
|
8246
8624
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8247
|
-
return new Promise((
|
|
8625
|
+
return new Promise((resolve10) => {
|
|
8248
8626
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
8249
8627
|
cwd: process.cwd(),
|
|
8250
8628
|
env,
|
|
@@ -8277,22 +8655,22 @@ var runInteractiveTool = {
|
|
|
8277
8655
|
setTimeout(writeNextLine, 400);
|
|
8278
8656
|
const timer = setTimeout(() => {
|
|
8279
8657
|
child.kill();
|
|
8280
|
-
|
|
8658
|
+
resolve10(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8281
8659
|
${buildOutput(stdout, stderr)}`);
|
|
8282
8660
|
}, timeout);
|
|
8283
8661
|
child.on("close", (code) => {
|
|
8284
8662
|
clearTimeout(timer);
|
|
8285
8663
|
const output = buildOutput(stdout, stderr);
|
|
8286
8664
|
if (code !== 0 && code !== null) {
|
|
8287
|
-
|
|
8665
|
+
resolve10(`${prefixWarnings}Exit code ${code}:
|
|
8288
8666
|
${output}`);
|
|
8289
8667
|
} else {
|
|
8290
|
-
|
|
8668
|
+
resolve10(`${prefixWarnings}${output || "(no output)"}`);
|
|
8291
8669
|
}
|
|
8292
8670
|
});
|
|
8293
8671
|
child.on("error", (err) => {
|
|
8294
8672
|
clearTimeout(timer);
|
|
8295
|
-
|
|
8673
|
+
resolve10(
|
|
8296
8674
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8297
8675
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8298
8676
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -8902,7 +9280,7 @@ ${preamble}`;
|
|
|
8902
9280
|
}
|
|
8903
9281
|
|
|
8904
9282
|
// src/tools/builtin/save-last-response.ts
|
|
8905
|
-
import { mkdirSync as
|
|
9283
|
+
import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
|
|
8906
9284
|
import { dirname as dirname4 } from "path";
|
|
8907
9285
|
var lastResponseStore = { content: "" };
|
|
8908
9286
|
function cleanupRejectedTeeFile(filePath) {
|
|
@@ -8952,7 +9330,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
8952
9330
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
8953
9331
|
}
|
|
8954
9332
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
8955
|
-
|
|
9333
|
+
mkdirSync6(dirname4(filePath), { recursive: true });
|
|
8956
9334
|
atomicWriteFileSync(filePath, content);
|
|
8957
9335
|
const lines = content.split("\n").length;
|
|
8958
9336
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -8960,11 +9338,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
8960
9338
|
};
|
|
8961
9339
|
|
|
8962
9340
|
// src/tools/builtin/save-memory.ts
|
|
8963
|
-
import { existsSync as
|
|
8964
|
-
import { join as
|
|
9341
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
9342
|
+
import { join as join9 } from "path";
|
|
8965
9343
|
import { homedir as homedir5 } from "os";
|
|
8966
9344
|
function getMemoryFilePath() {
|
|
8967
|
-
return
|
|
9345
|
+
return join9(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
8968
9346
|
}
|
|
8969
9347
|
function formatTimestamp() {
|
|
8970
9348
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -8988,16 +9366,16 @@ var saveMemoryTool = {
|
|
|
8988
9366
|
const content = String(args["content"] ?? "").trim();
|
|
8989
9367
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
8990
9368
|
const memoryPath = getMemoryFilePath();
|
|
8991
|
-
const configDir =
|
|
8992
|
-
if (!
|
|
8993
|
-
|
|
9369
|
+
const configDir = join9(homedir5(), CONFIG_DIR_NAME);
|
|
9370
|
+
if (!existsSync14(configDir)) {
|
|
9371
|
+
mkdirSync7(configDir, { recursive: true });
|
|
8994
9372
|
}
|
|
8995
9373
|
const timestamp = formatTimestamp();
|
|
8996
9374
|
const entry = `
|
|
8997
9375
|
## ${timestamp}
|
|
8998
9376
|
${content}
|
|
8999
9377
|
`;
|
|
9000
|
-
const previous =
|
|
9378
|
+
const previous = existsSync14(memoryPath) ? readFileSync11(memoryPath, "utf-8") : "";
|
|
9001
9379
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
9002
9380
|
const byteSize = statSync6(memoryPath).size;
|
|
9003
9381
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -9044,7 +9422,7 @@ function promptUser(rl, question) {
|
|
|
9044
9422
|
console.log();
|
|
9045
9423
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9046
9424
|
process.stdout.write(chalk4.cyan("> "));
|
|
9047
|
-
return new Promise((
|
|
9425
|
+
return new Promise((resolve10) => {
|
|
9048
9426
|
let completed = false;
|
|
9049
9427
|
const cleanup = (answer) => {
|
|
9050
9428
|
if (completed) return;
|
|
@@ -9054,7 +9432,7 @@ function promptUser(rl, question) {
|
|
|
9054
9432
|
rl.pause();
|
|
9055
9433
|
rlAny.output = savedOutput;
|
|
9056
9434
|
askUserContext.prompting = false;
|
|
9057
|
-
|
|
9435
|
+
resolve10(answer);
|
|
9058
9436
|
};
|
|
9059
9437
|
const onLine = (line) => {
|
|
9060
9438
|
cleanup(line);
|
|
@@ -9534,11 +9912,27 @@ var spawnAgentTool = {
|
|
|
9534
9912
|
if (!ctx.provider) {
|
|
9535
9913
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
9536
9914
|
}
|
|
9915
|
+
const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
|
|
9916
|
+
const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
|
|
9917
|
+
const taskCount = singleTask ? 1 : tasksArr.length;
|
|
9918
|
+
const hookPayload = {
|
|
9919
|
+
task: singleTask || void 0,
|
|
9920
|
+
tasks: tasksArr.length > 0 ? tasksArr : void 0,
|
|
9921
|
+
taskCount,
|
|
9922
|
+
maxRounds
|
|
9923
|
+
};
|
|
9924
|
+
const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
|
|
9925
|
+
const startDeny = startDecisions.find((d) => d.action === "deny");
|
|
9926
|
+
if (startDeny) {
|
|
9927
|
+
throw new ToolError("spawn_agent", "SubagentStart hook denied: " + (startDeny.reason ?? "denied"));
|
|
9928
|
+
}
|
|
9929
|
+
let subagentStatus = "error";
|
|
9537
9930
|
const guardWasActive = subAgentGuard.active;
|
|
9538
9931
|
subAgentGuard.active = true;
|
|
9539
9932
|
try {
|
|
9540
9933
|
if (singleTask) {
|
|
9541
9934
|
const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
|
|
9935
|
+
subagentStatus = "ok";
|
|
9542
9936
|
return [
|
|
9543
9937
|
"## Sub-Agent Result",
|
|
9544
9938
|
"",
|
|
@@ -9567,8 +9961,10 @@ var spawnAgentTool = {
|
|
|
9567
9961
|
});
|
|
9568
9962
|
lines.push("---");
|
|
9569
9963
|
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
9964
|
+
subagentStatus = "ok";
|
|
9570
9965
|
return lines.join("\n");
|
|
9571
9966
|
} finally {
|
|
9967
|
+
runLifecycleHooks(hookConfig, "SubagentStop", { ...hookPayload, status: subagentStatus }, hookConfigDir);
|
|
9572
9968
|
subAgentGuard.active = guardWasActive;
|
|
9573
9969
|
}
|
|
9574
9970
|
}
|
|
@@ -9791,14 +10187,14 @@ var taskStopTool = {
|
|
|
9791
10187
|
|
|
9792
10188
|
// src/tools/builtin/git-tools.ts
|
|
9793
10189
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9794
|
-
import { existsSync as
|
|
9795
|
-
import { join as
|
|
10190
|
+
import { existsSync as existsSync15 } from "fs";
|
|
10191
|
+
import { join as join10 } from "path";
|
|
9796
10192
|
function assertGitRepo(cwd) {
|
|
9797
10193
|
let dir = cwd;
|
|
9798
10194
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
9799
10195
|
while (dir && dir !== root) {
|
|
9800
|
-
if (
|
|
9801
|
-
const parent =
|
|
10196
|
+
if (existsSync15(join10(dir, ".git"))) return;
|
|
10197
|
+
const parent = join10(dir, "..");
|
|
9802
10198
|
if (parent === dir) break;
|
|
9803
10199
|
dir = parent;
|
|
9804
10200
|
}
|
|
@@ -10063,9 +10459,9 @@ ${commitOutput.trim()}`;
|
|
|
10063
10459
|
};
|
|
10064
10460
|
|
|
10065
10461
|
// src/tools/builtin/notebook-edit.ts
|
|
10066
|
-
import { readFileSync as
|
|
10462
|
+
import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
|
|
10067
10463
|
import { writeFile } from "fs/promises";
|
|
10068
|
-
import { resolve as
|
|
10464
|
+
import { resolve as resolve6, extname as extname2 } from "path";
|
|
10069
10465
|
var notebookEditTool = {
|
|
10070
10466
|
definition: {
|
|
10071
10467
|
name: "notebook_edit",
|
|
@@ -10114,14 +10510,14 @@ var notebookEditTool = {
|
|
|
10114
10510
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
10115
10511
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
10116
10512
|
}
|
|
10117
|
-
const absPath =
|
|
10513
|
+
const absPath = resolve6(filePath);
|
|
10118
10514
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
10119
10515
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
10120
10516
|
}
|
|
10121
|
-
if (!
|
|
10517
|
+
if (!existsSync16(absPath)) {
|
|
10122
10518
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
10123
10519
|
}
|
|
10124
|
-
const raw =
|
|
10520
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
10125
10521
|
const nb = parseNotebook(raw);
|
|
10126
10522
|
const cellIdx0 = cellIndexRaw - 1;
|
|
10127
10523
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -10538,8 +10934,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
10538
10934
|
|
|
10539
10935
|
// src/tools/registry.ts
|
|
10540
10936
|
import { pathToFileURL } from "url";
|
|
10541
|
-
import { existsSync as
|
|
10542
|
-
import { join as
|
|
10937
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
|
|
10938
|
+
import { join as join11 } from "path";
|
|
10543
10939
|
var ToolRegistry = class {
|
|
10544
10940
|
tools = /* @__PURE__ */ new Map();
|
|
10545
10941
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -10700,9 +11096,9 @@ var ToolRegistry = class {
|
|
|
10700
11096
|
* Returns the number of successfully loaded plugins.
|
|
10701
11097
|
*/
|
|
10702
11098
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
10703
|
-
if (!
|
|
11099
|
+
if (!existsSync17(pluginsDir)) {
|
|
10704
11100
|
try {
|
|
10705
|
-
|
|
11101
|
+
mkdirSync8(pluginsDir, { recursive: true });
|
|
10706
11102
|
} catch {
|
|
10707
11103
|
}
|
|
10708
11104
|
return 0;
|
|
@@ -10726,12 +11122,12 @@ var ToolRegistry = class {
|
|
|
10726
11122
|
process.stderr.write(
|
|
10727
11123
|
`
|
|
10728
11124
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
10729
|
-
` + files.map((f) => ` + ${
|
|
11125
|
+
` + files.map((f) => ` + ${join11(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
10730
11126
|
);
|
|
10731
11127
|
let loaded = 0;
|
|
10732
11128
|
for (const file of files) {
|
|
10733
11129
|
try {
|
|
10734
|
-
const fileUrl = pathToFileURL(
|
|
11130
|
+
const fileUrl = pathToFileURL(join11(pluginsDir, file)).href;
|
|
10735
11131
|
const mod = await import(fileUrl);
|
|
10736
11132
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
10737
11133
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -10893,7 +11289,7 @@ var McpClient = class {
|
|
|
10893
11289
|
// 内部方法:JSON-RPC 通信
|
|
10894
11290
|
// ══════════════════════════════════════════════════════════════════
|
|
10895
11291
|
sendRequest(method, params) {
|
|
10896
|
-
return new Promise((
|
|
11292
|
+
return new Promise((resolve10, reject) => {
|
|
10897
11293
|
if (!this.process?.stdin?.writable) {
|
|
10898
11294
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
10899
11295
|
}
|
|
@@ -10917,7 +11313,7 @@ var McpClient = class {
|
|
|
10917
11313
|
this.pendingRequests.set(id, {
|
|
10918
11314
|
resolve: (result) => {
|
|
10919
11315
|
cleanup();
|
|
10920
|
-
|
|
11316
|
+
resolve10(result);
|
|
10921
11317
|
},
|
|
10922
11318
|
reject: (error) => {
|
|
10923
11319
|
cleanup();
|
|
@@ -10994,13 +11390,13 @@ var McpClient = class {
|
|
|
10994
11390
|
}
|
|
10995
11391
|
/** Promise 超时包装 */
|
|
10996
11392
|
withTimeout(promise, ms, label) {
|
|
10997
|
-
return new Promise((
|
|
11393
|
+
return new Promise((resolve10, reject) => {
|
|
10998
11394
|
const timer = setTimeout(() => {
|
|
10999
11395
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11000
11396
|
}, ms);
|
|
11001
11397
|
promise.then((val) => {
|
|
11002
11398
|
clearTimeout(timer);
|
|
11003
|
-
|
|
11399
|
+
resolve10(val);
|
|
11004
11400
|
}).catch((err) => {
|
|
11005
11401
|
clearTimeout(timer);
|
|
11006
11402
|
reject(err);
|
|
@@ -11277,11 +11673,11 @@ var McpManager = class {
|
|
|
11277
11673
|
};
|
|
11278
11674
|
|
|
11279
11675
|
// src/skills/manager.ts
|
|
11280
|
-
import { existsSync as
|
|
11281
|
-
import { join as
|
|
11676
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
|
|
11677
|
+
import { join as join12 } from "path";
|
|
11282
11678
|
|
|
11283
11679
|
// src/skills/types.ts
|
|
11284
|
-
import { readFileSync as
|
|
11680
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
11285
11681
|
import { basename as basename5 } from "path";
|
|
11286
11682
|
function parseSimpleYaml(yaml) {
|
|
11287
11683
|
const result = {};
|
|
@@ -11303,7 +11699,7 @@ function parseYamlArray(value) {
|
|
|
11303
11699
|
function parseSkillFile(filePath) {
|
|
11304
11700
|
let raw;
|
|
11305
11701
|
try {
|
|
11306
|
-
raw =
|
|
11702
|
+
raw = readFileSync13(filePath, "utf-8");
|
|
11307
11703
|
} catch {
|
|
11308
11704
|
return null;
|
|
11309
11705
|
}
|
|
@@ -11346,9 +11742,9 @@ var SkillManager = class {
|
|
|
11346
11742
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
11347
11743
|
loadSkills() {
|
|
11348
11744
|
this.skills.clear();
|
|
11349
|
-
if (!
|
|
11745
|
+
if (!existsSync18(this.skillsDir)) {
|
|
11350
11746
|
try {
|
|
11351
|
-
|
|
11747
|
+
mkdirSync9(this.skillsDir, { recursive: true });
|
|
11352
11748
|
} catch {
|
|
11353
11749
|
}
|
|
11354
11750
|
return 0;
|
|
@@ -11361,14 +11757,14 @@ var SkillManager = class {
|
|
|
11361
11757
|
}
|
|
11362
11758
|
for (const entry of entries) {
|
|
11363
11759
|
let filePath;
|
|
11364
|
-
const fullPath =
|
|
11760
|
+
const fullPath = join12(this.skillsDir, entry);
|
|
11365
11761
|
if (entry.endsWith(".md")) {
|
|
11366
11762
|
filePath = fullPath;
|
|
11367
11763
|
} else {
|
|
11368
11764
|
try {
|
|
11369
11765
|
if (statSync7(fullPath).isDirectory()) {
|
|
11370
|
-
const skillMd =
|
|
11371
|
-
if (
|
|
11766
|
+
const skillMd = join12(fullPath, "SKILL.md");
|
|
11767
|
+
if (existsSync18(skillMd)) {
|
|
11372
11768
|
filePath = skillMd;
|
|
11373
11769
|
} else {
|
|
11374
11770
|
continue;
|
|
@@ -11433,7 +11829,7 @@ var SkillManager = class {
|
|
|
11433
11829
|
// src/web/tool-executor-web.ts
|
|
11434
11830
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11435
11831
|
import { tmpdir as tmpdir2 } from "os";
|
|
11436
|
-
import { existsSync as
|
|
11832
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
|
|
11437
11833
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11438
11834
|
constructor(registry, ws) {
|
|
11439
11835
|
this.registry = registry;
|
|
@@ -11449,6 +11845,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11449
11845
|
allowedPermissionProfiles = [];
|
|
11450
11846
|
workspaceRoot = process.cwd();
|
|
11451
11847
|
networkPolicy;
|
|
11848
|
+
hookConfigDir = process.cwd();
|
|
11452
11849
|
/** Pending confirm promises keyed by requestId */
|
|
11453
11850
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
11454
11851
|
/** Pending batch confirm promises */
|
|
@@ -11461,6 +11858,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11461
11858
|
confirming = false;
|
|
11462
11859
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
11463
11860
|
sessionAutoApprove = false;
|
|
11861
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
11862
|
+
sessionAutoMode = false;
|
|
11464
11863
|
/** Track tool start times for duration calculation */
|
|
11465
11864
|
toolStartTimes = /* @__PURE__ */ new Map();
|
|
11466
11865
|
/**
|
|
@@ -11488,6 +11887,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11488
11887
|
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
11489
11888
|
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
11490
11889
|
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
11890
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
11491
11891
|
}
|
|
11492
11892
|
/** Clear M7 timeout timer for a requestId */
|
|
11493
11893
|
clearPendingTimer(requestId) {
|
|
@@ -11499,33 +11899,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11499
11899
|
}
|
|
11500
11900
|
/** Resolve a pending confirm from client response */
|
|
11501
11901
|
resolveConfirm(requestId, approved) {
|
|
11502
|
-
const
|
|
11503
|
-
if (
|
|
11902
|
+
const resolve10 = this.pendingConfirms.get(requestId);
|
|
11903
|
+
if (resolve10) {
|
|
11504
11904
|
this.clearPendingTimer(requestId);
|
|
11505
11905
|
this.pendingConfirms.delete(requestId);
|
|
11506
11906
|
this.confirming = false;
|
|
11507
|
-
|
|
11907
|
+
resolve10(approved);
|
|
11508
11908
|
}
|
|
11509
11909
|
}
|
|
11510
11910
|
/** Resolve a pending batch confirm from client response */
|
|
11511
11911
|
resolveBatchConfirm(requestId, decision) {
|
|
11512
|
-
const
|
|
11513
|
-
if (
|
|
11912
|
+
const resolve10 = this.pendingBatchConfirms.get(requestId);
|
|
11913
|
+
if (resolve10) {
|
|
11514
11914
|
this.clearPendingTimer(requestId);
|
|
11515
11915
|
this.pendingBatchConfirms.delete(requestId);
|
|
11516
11916
|
this.confirming = false;
|
|
11517
11917
|
if (decision === "all" || decision === "none") {
|
|
11518
|
-
|
|
11918
|
+
resolve10(decision);
|
|
11519
11919
|
} else {
|
|
11520
|
-
|
|
11920
|
+
resolve10(new Set(decision));
|
|
11521
11921
|
}
|
|
11522
11922
|
}
|
|
11523
11923
|
}
|
|
11524
11924
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11525
11925
|
cancelAll() {
|
|
11526
|
-
for (const
|
|
11926
|
+
for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
|
|
11527
11927
|
this.pendingConfirms.clear();
|
|
11528
|
-
for (const
|
|
11928
|
+
for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
|
|
11529
11929
|
this.pendingBatchConfirms.clear();
|
|
11530
11930
|
this.confirming = false;
|
|
11531
11931
|
}
|
|
@@ -11536,7 +11936,6 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11536
11936
|
}
|
|
11537
11937
|
sendToolCallStart(call) {
|
|
11538
11938
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
11539
|
-
let toolCallStarted = false;
|
|
11540
11939
|
const startTime = Date.now();
|
|
11541
11940
|
this.toolStartTimes.set(call.id, startTime);
|
|
11542
11941
|
const msg = {
|
|
@@ -11569,9 +11968,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11569
11968
|
if (call.name === "write_file") {
|
|
11570
11969
|
const filePath = String(call.arguments["path"] ?? "");
|
|
11571
11970
|
const newContent = String(call.arguments["content"] ?? "");
|
|
11572
|
-
if (filePath &&
|
|
11971
|
+
if (filePath && existsSync19(filePath)) {
|
|
11573
11972
|
try {
|
|
11574
|
-
const old =
|
|
11973
|
+
const old = readFileSync14(filePath, "utf-8");
|
|
11575
11974
|
return renderDiff(old, newContent, { filePath });
|
|
11576
11975
|
} catch {
|
|
11577
11976
|
}
|
|
@@ -11602,8 +12001,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11602
12001
|
diff: this.getDiffPreview(call)
|
|
11603
12002
|
};
|
|
11604
12003
|
this.send(msg);
|
|
11605
|
-
return new Promise((
|
|
11606
|
-
this.pendingConfirms.set(requestId,
|
|
12004
|
+
return new Promise((resolve10) => {
|
|
12005
|
+
this.pendingConfirms.set(requestId, resolve10);
|
|
11607
12006
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11608
12007
|
if (this.pendingConfirms.has(requestId)) {
|
|
11609
12008
|
this.resolveConfirm(requestId, false);
|
|
@@ -11627,8 +12026,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11627
12026
|
files
|
|
11628
12027
|
};
|
|
11629
12028
|
this.send(msg);
|
|
11630
|
-
return new Promise((
|
|
11631
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12029
|
+
return new Promise((resolve10) => {
|
|
12030
|
+
this.pendingBatchConfirms.set(requestId, resolve10);
|
|
11632
12031
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11633
12032
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11634
12033
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11651,6 +12050,26 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11651
12050
|
dangerLevel,
|
|
11652
12051
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
11653
12052
|
});
|
|
12053
|
+
const preToolDecisions = runLifecycleHooks(
|
|
12054
|
+
this.hookConfig,
|
|
12055
|
+
"PreToolUse",
|
|
12056
|
+
{ tool: call.name, dangerLevel, args: call.arguments },
|
|
12057
|
+
{ configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: `[hook] ${message}` }) }
|
|
12058
|
+
);
|
|
12059
|
+
const preDeny = preToolDecisions.find((d) => d.action === "deny");
|
|
12060
|
+
if (preDeny) {
|
|
12061
|
+
const result = {
|
|
12062
|
+
callId: call.id,
|
|
12063
|
+
content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
|
|
12064
|
+
isError: true
|
|
12065
|
+
};
|
|
12066
|
+
this.sendToolCallResult(call, result.content, true);
|
|
12067
|
+
return result;
|
|
12068
|
+
}
|
|
12069
|
+
for (const d of preToolDecisions) {
|
|
12070
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
12071
|
+
for (const warning of warnings) this.send({ type: "info", message: `[hook] ${warning}` });
|
|
12072
|
+
}
|
|
11654
12073
|
const permission = checkPermissionWithProfile(
|
|
11655
12074
|
call.name,
|
|
11656
12075
|
call.arguments,
|
|
@@ -11666,6 +12085,14 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11666
12085
|
}
|
|
11667
12086
|
);
|
|
11668
12087
|
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
12088
|
+
if (networkPermission?.action === "confirm" || permission.action === "confirm") {
|
|
12089
|
+
runLifecycleHooks(
|
|
12090
|
+
this.hookConfig,
|
|
12091
|
+
"PermissionRequest",
|
|
12092
|
+
{ tool: call.name, dangerLevel, args: call.arguments, reason: networkPermission?.reason ?? permission.reason },
|
|
12093
|
+
{ configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
12094
|
+
);
|
|
12095
|
+
}
|
|
11669
12096
|
if (networkPermission?.action === "deny") {
|
|
11670
12097
|
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
11671
12098
|
return {
|
|
@@ -11689,11 +12116,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11689
12116
|
const content = truncateOutput(rawContent, call.name);
|
|
11690
12117
|
this.sendToolCallResult(call, rawContent, false);
|
|
11691
12118
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12119
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11692
12120
|
return { callId: call.id, content, isError: false };
|
|
11693
12121
|
} catch (err) {
|
|
11694
12122
|
const message = err instanceof Error ? err.message : String(err);
|
|
11695
12123
|
this.sendToolCallResult(call, message, true);
|
|
11696
12124
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12125
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11697
12126
|
return { callId: call.id, content: message, isError: true };
|
|
11698
12127
|
}
|
|
11699
12128
|
}
|
|
@@ -11708,17 +12137,48 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11708
12137
|
}
|
|
11709
12138
|
}
|
|
11710
12139
|
this.sendToolCallStart(call);
|
|
12140
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
12141
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
12142
|
+
workspaceRoot: this.workspaceRoot,
|
|
12143
|
+
tempDirs: [tmpdir2()]
|
|
12144
|
+
});
|
|
12145
|
+
if (classification.decision === "deny") {
|
|
12146
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
12147
|
+
const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
|
|
12148
|
+
this.sendToolCallResult(call, rejectionMsg, true);
|
|
12149
|
+
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
12150
|
+
}
|
|
12151
|
+
if (classification.decision === "auto-approve") {
|
|
12152
|
+
this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
|
|
12153
|
+
try {
|
|
12154
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
12155
|
+
const content = truncateOutput(rawContent, call.name);
|
|
12156
|
+
this.sendToolCallResult(call, rawContent, false);
|
|
12157
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12158
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
12159
|
+
return { callId: call.id, content, isError: false };
|
|
12160
|
+
} catch (err) {
|
|
12161
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12162
|
+
this.sendToolCallResult(call, message, true);
|
|
12163
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12164
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
12165
|
+
return { callId: call.id, content: message, isError: true };
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
11711
12169
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
11712
12170
|
try {
|
|
11713
12171
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
11714
12172
|
const content = truncateOutput(rawContent, call.name);
|
|
11715
12173
|
this.sendToolCallResult(call, rawContent, false);
|
|
11716
12174
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12175
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11717
12176
|
return { callId: call.id, content, isError: false };
|
|
11718
12177
|
} catch (err) {
|
|
11719
12178
|
const message = err instanceof Error ? err.message : String(err);
|
|
11720
12179
|
this.sendToolCallResult(call, message, true);
|
|
11721
12180
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12181
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11722
12182
|
return { callId: call.id, content: message, isError: true };
|
|
11723
12183
|
}
|
|
11724
12184
|
}
|
|
@@ -11736,11 +12196,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11736
12196
|
const content = truncateOutput(rawContent, call.name);
|
|
11737
12197
|
this.sendToolCallResult(call, rawContent, false);
|
|
11738
12198
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12199
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11739
12200
|
return { callId: call.id, content, isError: false };
|
|
11740
12201
|
} catch (err) {
|
|
11741
12202
|
const message = err instanceof Error ? err.message : String(err);
|
|
11742
12203
|
this.sendToolCallResult(call, message, true);
|
|
11743
12204
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12205
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11744
12206
|
return { callId: call.id, content: message, isError: true };
|
|
11745
12207
|
}
|
|
11746
12208
|
}
|
|
@@ -11811,20 +12273,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11811
12273
|
};
|
|
11812
12274
|
|
|
11813
12275
|
// src/core/system-prompt-builder.ts
|
|
11814
|
-
import { existsSync as
|
|
11815
|
-
import { join as
|
|
12276
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
|
|
12277
|
+
import { join as join14 } from "path";
|
|
11816
12278
|
|
|
11817
12279
|
// src/repl/dev-state.ts
|
|
11818
|
-
import { existsSync as
|
|
11819
|
-
import { join as
|
|
12280
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
12281
|
+
import { join as join13 } from "path";
|
|
11820
12282
|
import { homedir as homedir6 } from "os";
|
|
11821
12283
|
function getDevStatePath() {
|
|
11822
|
-
return
|
|
12284
|
+
return join13(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
11823
12285
|
}
|
|
11824
12286
|
function loadDevState() {
|
|
11825
12287
|
const path3 = getDevStatePath();
|
|
11826
|
-
if (!
|
|
11827
|
-
const content =
|
|
12288
|
+
if (!existsSync20(path3)) return null;
|
|
12289
|
+
const content = readFileSync15(path3, "utf-8").trim();
|
|
11828
12290
|
return content || null;
|
|
11829
12291
|
}
|
|
11830
12292
|
|
|
@@ -11882,9 +12344,9 @@ ${ctx.activeSkill.content}`);
|
|
|
11882
12344
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
11883
12345
|
}
|
|
11884
12346
|
function loadMemoryContent(configDir) {
|
|
11885
|
-
const memoryPath =
|
|
11886
|
-
if (!
|
|
11887
|
-
let content =
|
|
12347
|
+
const memoryPath = join14(configDir, MEMORY_FILE_NAME);
|
|
12348
|
+
if (!existsSync21(memoryPath)) return null;
|
|
12349
|
+
let content = readFileSync16(memoryPath, "utf-8").trim();
|
|
11888
12350
|
if (!content) return null;
|
|
11889
12351
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
11890
12352
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -12093,8 +12555,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
12093
12555
|
}
|
|
12094
12556
|
|
|
12095
12557
|
// src/core/context-files.ts
|
|
12096
|
-
import { existsSync as
|
|
12097
|
-
import { join as
|
|
12558
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
|
|
12559
|
+
import { join as join15, relative as relative3, resolve as resolve7 } from "path";
|
|
12098
12560
|
function uniqueNonEmpty(values) {
|
|
12099
12561
|
const seen = /* @__PURE__ */ new Set();
|
|
12100
12562
|
const out = [];
|
|
@@ -12117,14 +12579,14 @@ function displayPath(filePath, cwd) {
|
|
|
12117
12579
|
return filePath;
|
|
12118
12580
|
}
|
|
12119
12581
|
function isInsideOrEqual(parent, child) {
|
|
12120
|
-
const rel = relative3(
|
|
12582
|
+
const rel = relative3(resolve7(parent), resolve7(child));
|
|
12121
12583
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
12122
12584
|
}
|
|
12123
12585
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
12124
12586
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
12125
12587
|
const shown = displayPath(filePath, cwd);
|
|
12126
12588
|
try {
|
|
12127
|
-
const raw =
|
|
12589
|
+
const raw = readFileSync17(filePath);
|
|
12128
12590
|
const byteCount = raw.byteLength;
|
|
12129
12591
|
if (byteCount === 0) {
|
|
12130
12592
|
return {
|
|
@@ -12171,8 +12633,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12171
12633
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
12172
12634
|
const skipped = [];
|
|
12173
12635
|
for (const candidate of candidates) {
|
|
12174
|
-
const filePath =
|
|
12175
|
-
if (!
|
|
12636
|
+
const filePath = join15(dir, candidate);
|
|
12637
|
+
if (!existsSync22(filePath)) continue;
|
|
12176
12638
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
12177
12639
|
if (result.skipped) skipped.push(result.skipped);
|
|
12178
12640
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -12180,7 +12642,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
12180
12642
|
return { layer: null, skipped };
|
|
12181
12643
|
}
|
|
12182
12644
|
function loadContextFiles(options) {
|
|
12183
|
-
const cwd =
|
|
12645
|
+
const cwd = resolve7(options.cwd);
|
|
12184
12646
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
12185
12647
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
12186
12648
|
const skipped = [];
|
|
@@ -12188,7 +12650,7 @@ function loadContextFiles(options) {
|
|
|
12188
12650
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12189
12651
|
}
|
|
12190
12652
|
if (options.setting && options.setting !== "auto") {
|
|
12191
|
-
const filePath =
|
|
12653
|
+
const filePath = resolve7(cwd, String(options.setting));
|
|
12192
12654
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
12193
12655
|
skipped.push({
|
|
12194
12656
|
level: "single",
|
|
@@ -12199,7 +12661,7 @@ function loadContextFiles(options) {
|
|
|
12199
12661
|
});
|
|
12200
12662
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12201
12663
|
}
|
|
12202
|
-
if (!
|
|
12664
|
+
if (!existsSync22(filePath)) {
|
|
12203
12665
|
skipped.push({
|
|
12204
12666
|
level: "single",
|
|
12205
12667
|
filePath,
|
|
@@ -12232,7 +12694,7 @@ function loadContextFiles(options) {
|
|
|
12232
12694
|
skipped.push(...result.skipped);
|
|
12233
12695
|
if (result.layer) layers.push(result.layer);
|
|
12234
12696
|
}
|
|
12235
|
-
if (
|
|
12697
|
+
if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
|
|
12236
12698
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
12237
12699
|
skipped.push(...result.skipped);
|
|
12238
12700
|
if (result.layer) layers.push(result.layer);
|
|
@@ -12250,13 +12712,13 @@ function loadContextFiles(options) {
|
|
|
12250
12712
|
}
|
|
12251
12713
|
|
|
12252
12714
|
// src/web/session-handler.ts
|
|
12253
|
-
import { existsSync as
|
|
12254
|
-
import { join as
|
|
12715
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
12716
|
+
import { join as join19, resolve as resolve8, dirname as dirname5 } from "path";
|
|
12255
12717
|
|
|
12256
12718
|
// src/tools/git-context.ts
|
|
12257
12719
|
import { execSync as execSync2 } from "child_process";
|
|
12258
|
-
import { existsSync as
|
|
12259
|
-
import { join as
|
|
12720
|
+
import { existsSync as existsSync23 } from "fs";
|
|
12721
|
+
import { join as join16 } from "path";
|
|
12260
12722
|
function runGit2(cmd, cwd) {
|
|
12261
12723
|
try {
|
|
12262
12724
|
return execSync2(`git ${cmd}`, {
|
|
@@ -12273,7 +12735,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
12273
12735
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
12274
12736
|
}
|
|
12275
12737
|
function getGitContext(cwd = process.cwd()) {
|
|
12276
|
-
if (!
|
|
12738
|
+
if (!existsSync23(join16(cwd, ".git"))) {
|
|
12277
12739
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
12278
12740
|
if (!result) return null;
|
|
12279
12741
|
}
|
|
@@ -12402,8 +12864,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
12402
12864
|
}
|
|
12403
12865
|
|
|
12404
12866
|
// src/repl/commands/project-init.ts
|
|
12405
|
-
import { existsSync as
|
|
12406
|
-
import { join as
|
|
12867
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
12868
|
+
import { join as join17 } from "path";
|
|
12407
12869
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12408
12870
|
"node_modules",
|
|
12409
12871
|
".git",
|
|
@@ -12447,11 +12909,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12447
12909
|
const sorted = filtered.sort((a, b) => {
|
|
12448
12910
|
let aIsDir = false, bIsDir = false;
|
|
12449
12911
|
try {
|
|
12450
|
-
aIsDir = statSync8(
|
|
12912
|
+
aIsDir = statSync8(join17(d, a)).isDirectory();
|
|
12451
12913
|
} catch {
|
|
12452
12914
|
}
|
|
12453
12915
|
try {
|
|
12454
|
-
bIsDir = statSync8(
|
|
12916
|
+
bIsDir = statSync8(join17(d, b)).isDirectory();
|
|
12455
12917
|
} catch {
|
|
12456
12918
|
}
|
|
12457
12919
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -12459,7 +12921,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12459
12921
|
});
|
|
12460
12922
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12461
12923
|
const name = sorted[i];
|
|
12462
|
-
const fullPath =
|
|
12924
|
+
const fullPath = join17(d, name);
|
|
12463
12925
|
const isLast = i === sorted.length - 1;
|
|
12464
12926
|
const connector = isLast ? "+-- " : "|-- ";
|
|
12465
12927
|
let isDir;
|
|
@@ -12486,7 +12948,7 @@ function scanProject(cwd) {
|
|
|
12486
12948
|
configFiles: [],
|
|
12487
12949
|
directoryStructure: ""
|
|
12488
12950
|
};
|
|
12489
|
-
const check = (file) =>
|
|
12951
|
+
const check = (file) => existsSync24(join17(cwd, file));
|
|
12490
12952
|
const configCandidates = [
|
|
12491
12953
|
"package.json",
|
|
12492
12954
|
"tsconfig.json",
|
|
@@ -12513,7 +12975,7 @@ function scanProject(cwd) {
|
|
|
12513
12975
|
info.type = "node";
|
|
12514
12976
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12515
12977
|
try {
|
|
12516
|
-
const pkg = JSON.parse(
|
|
12978
|
+
const pkg = JSON.parse(readFileSync18(join17(cwd, "package.json"), "utf-8"));
|
|
12517
12979
|
const scripts = pkg.scripts ?? {};
|
|
12518
12980
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12519
12981
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13173,7 +13635,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
13173
13635
|
}
|
|
13174
13636
|
|
|
13175
13637
|
// src/hub/persist.ts
|
|
13176
|
-
import { join as
|
|
13638
|
+
import { join as join18 } from "path";
|
|
13177
13639
|
function discussionToMessages(state2) {
|
|
13178
13640
|
const out = [];
|
|
13179
13641
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -13211,7 +13673,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
13211
13673
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
13212
13674
|
session.titleAiGenerated = true;
|
|
13213
13675
|
await sm.save();
|
|
13214
|
-
return { id: session.id, path:
|
|
13676
|
+
return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
|
|
13215
13677
|
}
|
|
13216
13678
|
|
|
13217
13679
|
// src/web/session-handler.ts
|
|
@@ -13314,6 +13776,7 @@ var SessionHandler = class {
|
|
|
13314
13776
|
const permissionProfiles = this.config.get("permissionProfiles") ?? {};
|
|
13315
13777
|
this.toolExecutor.setConfig({
|
|
13316
13778
|
hookConfig: hooks,
|
|
13779
|
+
hookConfigDir: this.config.getConfigDir(),
|
|
13317
13780
|
permissionRules,
|
|
13318
13781
|
defaultPermission,
|
|
13319
13782
|
permissionProfileName,
|
|
@@ -13326,6 +13789,11 @@ var SessionHandler = class {
|
|
|
13326
13789
|
if (permissionProfileWarning) {
|
|
13327
13790
|
this.send({ type: "info", message: permissionProfileWarning });
|
|
13328
13791
|
}
|
|
13792
|
+
const pendingHooks = getPendingHookTrust(hooks, this.config.getConfigDir());
|
|
13793
|
+
if (pendingHooks.length > 0) {
|
|
13794
|
+
this.send({ type: "info", message: `${pendingHooks.length} project hook(s) require trust. Use /hooks list and /hooks trust <id>.` });
|
|
13795
|
+
}
|
|
13796
|
+
runLifecycleHooks(hooks, "SessionStart", { source: "web" }, { configDir: this.config.getConfigDir() });
|
|
13329
13797
|
this.sendStatus();
|
|
13330
13798
|
askUserContext.rl = null;
|
|
13331
13799
|
askUserContext.prompting = false;
|
|
@@ -13389,10 +13857,10 @@ var SessionHandler = class {
|
|
|
13389
13857
|
return;
|
|
13390
13858
|
}
|
|
13391
13859
|
case "ask_user_response": {
|
|
13392
|
-
const
|
|
13393
|
-
if (
|
|
13860
|
+
const resolve10 = this.pendingAskUser.get(msg.requestId);
|
|
13861
|
+
if (resolve10) {
|
|
13394
13862
|
this.pendingAskUser.delete(msg.requestId);
|
|
13395
|
-
|
|
13863
|
+
resolve10(msg.answer);
|
|
13396
13864
|
}
|
|
13397
13865
|
return;
|
|
13398
13866
|
}
|
|
@@ -13403,10 +13871,10 @@ var SessionHandler = class {
|
|
|
13403
13871
|
case "memory_rebuild":
|
|
13404
13872
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
13405
13873
|
case "auto_pause_response": {
|
|
13406
|
-
const
|
|
13407
|
-
if (
|
|
13874
|
+
const resolve10 = this.pendingAutoPause.get(msg.requestId);
|
|
13875
|
+
if (resolve10) {
|
|
13408
13876
|
this.pendingAutoPause.delete(msg.requestId);
|
|
13409
|
-
|
|
13877
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13410
13878
|
}
|
|
13411
13879
|
return;
|
|
13412
13880
|
}
|
|
@@ -13421,10 +13889,10 @@ var SessionHandler = class {
|
|
|
13421
13889
|
this.hubOrchestrator?.abort();
|
|
13422
13890
|
return;
|
|
13423
13891
|
case "hub_steer": {
|
|
13424
|
-
const
|
|
13425
|
-
if (
|
|
13892
|
+
const resolve10 = this.pendingHubReview.get(msg.requestId);
|
|
13893
|
+
if (resolve10) {
|
|
13426
13894
|
this.pendingHubReview.delete(msg.requestId);
|
|
13427
|
-
|
|
13895
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13428
13896
|
}
|
|
13429
13897
|
return;
|
|
13430
13898
|
}
|
|
@@ -13434,11 +13902,12 @@ var SessionHandler = class {
|
|
|
13434
13902
|
}
|
|
13435
13903
|
}
|
|
13436
13904
|
onDisconnect() {
|
|
13905
|
+
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
13437
13906
|
this.toolExecutor.cancelAll();
|
|
13438
13907
|
if (this.abortController) this.abortController.abort();
|
|
13439
|
-
for (const
|
|
13908
|
+
for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
|
|
13440
13909
|
this.pendingAskUser.clear();
|
|
13441
|
-
for (const
|
|
13910
|
+
for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
|
|
13442
13911
|
this.pendingAutoPause.clear();
|
|
13443
13912
|
this.saveIfNeeded();
|
|
13444
13913
|
}
|
|
@@ -13572,9 +14041,9 @@ var SessionHandler = class {
|
|
|
13572
14041
|
this.hubOrchestrator = orchestrator;
|
|
13573
14042
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
13574
14043
|
if (config.humanSteer) {
|
|
13575
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
14044
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
|
|
13576
14045
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13577
|
-
this.pendingHubReview.set(requestId,
|
|
14046
|
+
this.pendingHubReview.set(requestId, resolve10);
|
|
13578
14047
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
13579
14048
|
});
|
|
13580
14049
|
}
|
|
@@ -13611,6 +14080,26 @@ var SessionHandler = class {
|
|
|
13611
14080
|
try {
|
|
13612
14081
|
this.ensureSession();
|
|
13613
14082
|
const session = this.sessions.current;
|
|
14083
|
+
const promptDecisions = runLifecycleHooks(
|
|
14084
|
+
this.config.get("hooks") ?? void 0,
|
|
14085
|
+
"UserPromptSubmit",
|
|
14086
|
+
{ sessionId: session.id, prompt: content },
|
|
14087
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
14088
|
+
);
|
|
14089
|
+
const promptDeny = promptDecisions.find((d) => d.action === "deny");
|
|
14090
|
+
if (promptDeny) {
|
|
14091
|
+
this.send({ type: "error", message: "Prompt blocked by hook: " + (promptDeny.reason ?? "UserPromptSubmit denied") });
|
|
14092
|
+
return;
|
|
14093
|
+
}
|
|
14094
|
+
for (const d of promptDecisions) {
|
|
14095
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
14096
|
+
for (const warning of warnings) this.send({ type: "info", message: "[hook] " + warning });
|
|
14097
|
+
}
|
|
14098
|
+
const secretHits = scanString(content, { customRegexes: [] });
|
|
14099
|
+
if (secretHits.length > 0) {
|
|
14100
|
+
const kinds = [...new Set(secretHits.map((h) => h.kind))].join(", ");
|
|
14101
|
+
this.send({ type: "info", message: "Possible secret in prompt (" + kinds + ")." });
|
|
14102
|
+
}
|
|
13614
14103
|
const ALLOWED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"]);
|
|
13615
14104
|
const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
|
13616
14105
|
let msgContent;
|
|
@@ -13886,7 +14375,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13886
14375
|
this.send({ type: "response_done", content, usage });
|
|
13887
14376
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
13888
14377
|
try {
|
|
13889
|
-
|
|
14378
|
+
mkdirSync11(dirname5(pendingTeeSave), { recursive: true });
|
|
13890
14379
|
const bodyToSave = stripOuterCodeFence(content);
|
|
13891
14380
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
13892
14381
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -13911,8 +14400,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13911
14400
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
13912
14401
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
13913
14402
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13914
|
-
const pauseResp = await new Promise((
|
|
13915
|
-
this.pendingAutoPause.set(requestId,
|
|
14403
|
+
const pauseResp = await new Promise((resolve10) => {
|
|
14404
|
+
this.pendingAutoPause.set(requestId, resolve10);
|
|
13916
14405
|
this.send({
|
|
13917
14406
|
type: "auto_pause_request",
|
|
13918
14407
|
requestId,
|
|
@@ -14062,7 +14551,7 @@ ${summaryContent}`,
|
|
|
14062
14551
|
let isError = false;
|
|
14063
14552
|
let summary;
|
|
14064
14553
|
try {
|
|
14065
|
-
|
|
14554
|
+
mkdirSync11(dirname5(saveToFile), { recursive: true });
|
|
14066
14555
|
fileStream = createWriteStream(saveToFile);
|
|
14067
14556
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
14068
14557
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -14091,8 +14580,8 @@ ${summaryContent}`,
|
|
|
14091
14580
|
}
|
|
14092
14581
|
if (chunk.done) break;
|
|
14093
14582
|
}
|
|
14094
|
-
await new Promise((
|
|
14095
|
-
fileStream.end((err) => err ? reject(err) :
|
|
14583
|
+
await new Promise((resolve10, reject) => {
|
|
14584
|
+
fileStream.end((err) => err ? reject(err) : resolve10());
|
|
14096
14585
|
});
|
|
14097
14586
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
14098
14587
|
if (verdict.kind === "reject") {
|
|
@@ -14118,7 +14607,7 @@ ${summaryContent}`,
|
|
|
14118
14607
|
} catch (err) {
|
|
14119
14608
|
if (fileStream) {
|
|
14120
14609
|
try {
|
|
14121
|
-
await new Promise((
|
|
14610
|
+
await new Promise((resolve10) => fileStream.end(() => resolve10()));
|
|
14122
14611
|
} catch {
|
|
14123
14612
|
}
|
|
14124
14613
|
}
|
|
@@ -14548,6 +15037,47 @@ ${activated.meta.description || ""}` });
|
|
|
14548
15037
|
break;
|
|
14549
15038
|
}
|
|
14550
15039
|
// ── /yolo ──────────────────────────────────────────────────────
|
|
15040
|
+
case "auto": {
|
|
15041
|
+
const sub = args[0]?.toLowerCase() ?? "status";
|
|
15042
|
+
if (sub === "on") {
|
|
15043
|
+
this.toolExecutor.sessionAutoMode = true;
|
|
15044
|
+
this.send({ type: "info", message: "\u26A1 Auto Mode ON \u2014 low-risk actions may auto-run; high-risk actions still confirm or deny. Auto Mode is session-scoped." });
|
|
15045
|
+
} else if (sub === "off") {
|
|
15046
|
+
this.toolExecutor.sessionAutoMode = false;
|
|
15047
|
+
this.send({ type: "info", message: "Auto Mode disabled." });
|
|
15048
|
+
} else if (sub === "status") {
|
|
15049
|
+
this.send({ type: "info", message: `Auto Mode: ${this.toolExecutor.sessionAutoMode ? "on" : "off"}
|
|
15050
|
+
Scope: current session only
|
|
15051
|
+
Denied: /permissions recently-denied` });
|
|
15052
|
+
} else {
|
|
15053
|
+
this.send({ type: "error", message: "Usage: /auto [on|off|status]" });
|
|
15054
|
+
}
|
|
15055
|
+
this.sendStatus();
|
|
15056
|
+
break;
|
|
15057
|
+
}
|
|
15058
|
+
case "permissions": {
|
|
15059
|
+
const sub = args[0]?.toLowerCase() ?? "recently-denied";
|
|
15060
|
+
if (sub === "recently-denied") {
|
|
15061
|
+
const rows = getRecentlyDeniedAutoActions();
|
|
15062
|
+
if (rows.length === 0) {
|
|
15063
|
+
this.send({ type: "info", message: "No recently denied Auto Mode actions." });
|
|
15064
|
+
} else {
|
|
15065
|
+
const lines = [`Recently denied Auto Mode actions (${rows.length})`, ""];
|
|
15066
|
+
for (const r of rows.slice(0, 20)) {
|
|
15067
|
+
lines.push(`- ${r.timestamp} ${r.tool} ${r.ruleId}`);
|
|
15068
|
+
lines.push(` ${r.reason}`);
|
|
15069
|
+
lines.push(` ${r.argsPreview}`);
|
|
15070
|
+
}
|
|
15071
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
15072
|
+
}
|
|
15073
|
+
} else if (sub === "clear-denied") {
|
|
15074
|
+
clearRecentlyDeniedAutoActions();
|
|
15075
|
+
this.send({ type: "info", message: "Cleared recently denied Auto Mode actions." });
|
|
15076
|
+
} else {
|
|
15077
|
+
this.send({ type: "error", message: "Usage: /permissions recently-denied|clear-denied" });
|
|
15078
|
+
}
|
|
15079
|
+
break;
|
|
15080
|
+
}
|
|
14551
15081
|
case "yolo": {
|
|
14552
15082
|
const sub = args[0]?.toLowerCase();
|
|
14553
15083
|
if (sub === "off") {
|
|
@@ -14648,9 +15178,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14648
15178
|
let modifiedFiles = 0;
|
|
14649
15179
|
const diffLines2 = [];
|
|
14650
15180
|
for (const [filePath, { earliest }] of fileMap) {
|
|
14651
|
-
const currentContent =
|
|
15181
|
+
const currentContent = existsSync25(filePath) ? (() => {
|
|
14652
15182
|
try {
|
|
14653
|
-
return
|
|
15183
|
+
return readFileSync19(filePath, "utf-8");
|
|
14654
15184
|
} catch {
|
|
14655
15185
|
return null;
|
|
14656
15186
|
}
|
|
@@ -14751,7 +15281,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14751
15281
|
break;
|
|
14752
15282
|
}
|
|
14753
15283
|
const sub = args[0]?.toLowerCase();
|
|
14754
|
-
const
|
|
15284
|
+
const resolve10 = (ref) => {
|
|
14755
15285
|
const r = session.resolveBranchRef(ref);
|
|
14756
15286
|
if (r.ok) return r.id;
|
|
14757
15287
|
if (r.reason === "ambiguous") {
|
|
@@ -14805,7 +15335,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14805
15335
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
14806
15336
|
break;
|
|
14807
15337
|
}
|
|
14808
|
-
const id =
|
|
15338
|
+
const id = resolve10(ref);
|
|
14809
15339
|
if (!id) break;
|
|
14810
15340
|
const ok = session.switchBranch(id);
|
|
14811
15341
|
if (ok) {
|
|
@@ -14825,7 +15355,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14825
15355
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
14826
15356
|
break;
|
|
14827
15357
|
}
|
|
14828
|
-
const id =
|
|
15358
|
+
const id = resolve10(ref);
|
|
14829
15359
|
if (!id) break;
|
|
14830
15360
|
const ok = session.deleteBranch(id);
|
|
14831
15361
|
if (ok) {
|
|
@@ -14844,7 +15374,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14844
15374
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
14845
15375
|
break;
|
|
14846
15376
|
}
|
|
14847
|
-
const id =
|
|
15377
|
+
const id = resolve10(ref);
|
|
14848
15378
|
if (!id) break;
|
|
14849
15379
|
const ok = session.renameBranch(id, title);
|
|
14850
15380
|
if (ok) {
|
|
@@ -14862,7 +15392,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14862
15392
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
14863
15393
|
break;
|
|
14864
15394
|
}
|
|
14865
|
-
const id =
|
|
15395
|
+
const id = resolve10(ref);
|
|
14866
15396
|
if (!id) break;
|
|
14867
15397
|
const d = session.diffBranches(id);
|
|
14868
15398
|
if (!d) {
|
|
@@ -14904,7 +15434,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14904
15434
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
14905
15435
|
break;
|
|
14906
15436
|
}
|
|
14907
|
-
const id =
|
|
15437
|
+
const id = resolve10(ref);
|
|
14908
15438
|
if (!id) break;
|
|
14909
15439
|
const idx = parseInt(idxStr, 10);
|
|
14910
15440
|
if (Number.isNaN(idx)) {
|
|
@@ -15166,7 +15696,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15166
15696
|
case "test": {
|
|
15167
15697
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
15168
15698
|
try {
|
|
15169
|
-
const { executeTests } = await import("./run-tests-
|
|
15699
|
+
const { executeTests } = await import("./run-tests-7J3PNWVS.js");
|
|
15170
15700
|
const argStr = args.join(" ").trim();
|
|
15171
15701
|
let testArgs = {};
|
|
15172
15702
|
if (argStr) {
|
|
@@ -15183,9 +15713,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15183
15713
|
// ── /init ───────────────────────────────────────────────────────
|
|
15184
15714
|
case "init": {
|
|
15185
15715
|
const cwd = process.cwd();
|
|
15186
|
-
const targetPath =
|
|
15716
|
+
const targetPath = join19(cwd, "AICLI.md");
|
|
15187
15717
|
const force = args.includes("--force");
|
|
15188
|
-
if (
|
|
15718
|
+
if (existsSync25(targetPath) && !force) {
|
|
15189
15719
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
15190
15720
|
Use /init --force to overwrite.` });
|
|
15191
15721
|
break;
|
|
@@ -15218,7 +15748,7 @@ Use /context reload to load it.` });
|
|
|
15218
15748
|
lines.push("**Config Files:**");
|
|
15219
15749
|
lines.push(` Dir: ${configDir}`);
|
|
15220
15750
|
const checkFile = (label, filePath) => {
|
|
15221
|
-
const exists =
|
|
15751
|
+
const exists = existsSync25(filePath);
|
|
15222
15752
|
let extra = "";
|
|
15223
15753
|
if (exists) {
|
|
15224
15754
|
try {
|
|
@@ -15228,9 +15758,9 @@ Use /context reload to load it.` });
|
|
|
15228
15758
|
}
|
|
15229
15759
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
15230
15760
|
};
|
|
15231
|
-
checkFile("config.json",
|
|
15232
|
-
checkFile("memory.md",
|
|
15233
|
-
checkFile("dev-state.md",
|
|
15761
|
+
checkFile("config.json", join19(configDir, "config.json"));
|
|
15762
|
+
checkFile("memory.md", join19(configDir, MEMORY_FILE_NAME));
|
|
15763
|
+
checkFile("dev-state.md", join19(configDir, "dev-state.md"));
|
|
15234
15764
|
lines.push("");
|
|
15235
15765
|
if (this.mcpManager) {
|
|
15236
15766
|
lines.push("**MCP Servers:**");
|
|
@@ -15417,8 +15947,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
15417
15947
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
15418
15948
|
break;
|
|
15419
15949
|
}
|
|
15420
|
-
const dirPath =
|
|
15421
|
-
if (!
|
|
15950
|
+
const dirPath = resolve8(sub);
|
|
15951
|
+
if (!existsSync25(dirPath)) {
|
|
15422
15952
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
15423
15953
|
break;
|
|
15424
15954
|
}
|
|
@@ -15434,8 +15964,8 @@ It will be included in AI context for subsequent messages.` });
|
|
|
15434
15964
|
// ── /commands ───────────────────────────────────────────────────
|
|
15435
15965
|
case "commands": {
|
|
15436
15966
|
const configDir = this.config.getConfigDir();
|
|
15437
|
-
const commandsDir =
|
|
15438
|
-
if (!
|
|
15967
|
+
const commandsDir = join19(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15968
|
+
if (!existsSync25(commandsDir)) {
|
|
15439
15969
|
this.send({ type: "info", message: `No custom commands directory.
|
|
15440
15970
|
Create: ${commandsDir}/ with .md files.` });
|
|
15441
15971
|
break;
|
|
@@ -15461,7 +15991,7 @@ Add .md files to create commands.` });
|
|
|
15461
15991
|
// ── /plugins ────────────────────────────────────────────────────
|
|
15462
15992
|
case "plugins": {
|
|
15463
15993
|
const configDir = this.config.getConfigDir();
|
|
15464
|
-
const pluginsDir =
|
|
15994
|
+
const pluginsDir = join19(configDir, PLUGINS_DIR_NAME);
|
|
15465
15995
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
15466
15996
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
15467
15997
|
if (pluginTools.length === 0) {
|
|
@@ -15516,6 +16046,21 @@ Add .md files to create commands.` });
|
|
|
15516
16046
|
this.send({ type: "info", message: "Not enough messages to compact." });
|
|
15517
16047
|
return;
|
|
15518
16048
|
}
|
|
16049
|
+
const preDecisions = runLifecycleHooks(
|
|
16050
|
+
this.config.get("hooks") ?? void 0,
|
|
16051
|
+
"PreCompact",
|
|
16052
|
+
{ sessionId: session.id, messageCount: session.messages.length, instruction },
|
|
16053
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
16054
|
+
);
|
|
16055
|
+
const preDeny = preDecisions.find((d) => d.action === "deny");
|
|
16056
|
+
if (preDeny) {
|
|
16057
|
+
this.send({ type: "error", message: "Compact blocked by hook: " + (preDeny.reason ?? "PreCompact denied") });
|
|
16058
|
+
return;
|
|
16059
|
+
}
|
|
16060
|
+
for (const d of preDecisions) {
|
|
16061
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
16062
|
+
for (const warning of warnings) this.send({ type: "info", message: "[hook] " + warning });
|
|
16063
|
+
}
|
|
15519
16064
|
const provider = this.providers.get(this.currentProvider);
|
|
15520
16065
|
if (!provider) return;
|
|
15521
16066
|
this.send({ type: "info", message: "Compacting conversation..." });
|
|
@@ -15535,7 +16080,15 @@ Add .md files to create commands.` });
|
|
|
15535
16080
|
});
|
|
15536
16081
|
const summaryMsg = { role: "user", content: "[Conversation summary]", timestamp: /* @__PURE__ */ new Date() };
|
|
15537
16082
|
const ackMsg = { role: "assistant", content: response.content, timestamp: /* @__PURE__ */ new Date() };
|
|
15538
|
-
session.
|
|
16083
|
+
const beforeCount = session.messages.length;
|
|
16084
|
+
const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
|
|
16085
|
+
const afterCount = session.messages.length;
|
|
16086
|
+
runLifecycleHooks(
|
|
16087
|
+
this.config.get("hooks") ?? void 0,
|
|
16088
|
+
"PostCompact",
|
|
16089
|
+
{ sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
|
|
16090
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
16091
|
+
);
|
|
15539
16092
|
this.send({ type: "info", message: `Compacted: removed ${toSummarize.length} messages, kept last ${keepLast}.` });
|
|
15540
16093
|
this.sendStatus();
|
|
15541
16094
|
} catch (err) {
|
|
@@ -15635,11 +16188,11 @@ Add .md files to create commands.` });
|
|
|
15635
16188
|
}
|
|
15636
16189
|
memoryShow() {
|
|
15637
16190
|
const configDir = this.config.getConfigDir();
|
|
15638
|
-
const memPath =
|
|
16191
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15639
16192
|
let content = "";
|
|
15640
16193
|
try {
|
|
15641
|
-
if (
|
|
15642
|
-
content =
|
|
16194
|
+
if (existsSync25(memPath)) {
|
|
16195
|
+
content = readFileSync19(memPath, "utf-8");
|
|
15643
16196
|
}
|
|
15644
16197
|
} catch (err) {
|
|
15645
16198
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -15653,11 +16206,11 @@ Add .md files to create commands.` });
|
|
|
15653
16206
|
}
|
|
15654
16207
|
memoryAdd(text) {
|
|
15655
16208
|
const configDir = this.config.getConfigDir();
|
|
15656
|
-
const memPath =
|
|
16209
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15657
16210
|
try {
|
|
15658
|
-
|
|
16211
|
+
mkdirSync11(configDir, { recursive: true });
|
|
15659
16212
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
15660
|
-
const previous =
|
|
16213
|
+
const previous = existsSync25(memPath) ? readFileSync19(memPath, "utf-8") : "";
|
|
15661
16214
|
const entry = `
|
|
15662
16215
|
- [${timestamp}] ${text}
|
|
15663
16216
|
`;
|
|
@@ -15669,7 +16222,7 @@ Add .md files to create commands.` });
|
|
|
15669
16222
|
}
|
|
15670
16223
|
memoryClear() {
|
|
15671
16224
|
const configDir = this.config.getConfigDir();
|
|
15672
|
-
const memPath =
|
|
16225
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15673
16226
|
try {
|
|
15674
16227
|
atomicWriteFileSync(memPath, "");
|
|
15675
16228
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
@@ -15687,7 +16240,7 @@ Add .md files to create commands.` });
|
|
|
15687
16240
|
return;
|
|
15688
16241
|
}
|
|
15689
16242
|
try {
|
|
15690
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
16243
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-R2E27VXN.js");
|
|
15691
16244
|
const loaded = loadChatIndex2();
|
|
15692
16245
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
15693
16246
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -15723,7 +16276,7 @@ Add .md files to create commands.` });
|
|
|
15723
16276
|
}
|
|
15724
16277
|
async handleMemoryStatus() {
|
|
15725
16278
|
try {
|
|
15726
|
-
const { getChatIndexStatus } = await import("./chat-index-
|
|
16279
|
+
const { getChatIndexStatus } = await import("./chat-index-R2E27VXN.js");
|
|
15727
16280
|
const s = getChatIndexStatus();
|
|
15728
16281
|
this.send({
|
|
15729
16282
|
type: "memory_status",
|
|
@@ -15748,7 +16301,7 @@ Add .md files to create commands.` });
|
|
|
15748
16301
|
type: "info",
|
|
15749
16302
|
message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
|
|
15750
16303
|
});
|
|
15751
|
-
const { buildChatIndex } = await import("./chat-index-
|
|
16304
|
+
const { buildChatIndex } = await import("./chat-index-R2E27VXN.js");
|
|
15752
16305
|
const stats = await buildChatIndex({
|
|
15753
16306
|
full,
|
|
15754
16307
|
onProgress: (p) => {
|
|
@@ -15903,8 +16456,8 @@ async function setupProxy(configProxy) {
|
|
|
15903
16456
|
}
|
|
15904
16457
|
|
|
15905
16458
|
// src/web/auth.ts
|
|
15906
|
-
import { existsSync as
|
|
15907
|
-
import { join as
|
|
16459
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
|
|
16460
|
+
import { join as join20 } from "path";
|
|
15908
16461
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
15909
16462
|
var USERS_FILE = "users.json";
|
|
15910
16463
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -15919,7 +16472,7 @@ var AuthManager = class {
|
|
|
15919
16472
|
db;
|
|
15920
16473
|
constructor(baseDir) {
|
|
15921
16474
|
this.baseDir = baseDir;
|
|
15922
|
-
this.usersFile =
|
|
16475
|
+
this.usersFile = join20(baseDir, USERS_FILE);
|
|
15923
16476
|
this.db = this.loadOrCreate();
|
|
15924
16477
|
}
|
|
15925
16478
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -15948,9 +16501,9 @@ var AuthManager = class {
|
|
|
15948
16501
|
}
|
|
15949
16502
|
const salt = randomBytes(16).toString("hex");
|
|
15950
16503
|
const passwordHash = this.hashPassword(password, salt);
|
|
15951
|
-
const dataDir =
|
|
15952
|
-
const fullDataDir =
|
|
15953
|
-
|
|
16504
|
+
const dataDir = join20(USERS_DIR, username);
|
|
16505
|
+
const fullDataDir = join20(this.baseDir, dataDir);
|
|
16506
|
+
mkdirSync12(join20(fullDataDir, "history"), { recursive: true });
|
|
15954
16507
|
const user = {
|
|
15955
16508
|
username,
|
|
15956
16509
|
passwordHash,
|
|
@@ -16065,7 +16618,7 @@ var AuthManager = class {
|
|
|
16065
16618
|
getUserDataDir(username) {
|
|
16066
16619
|
const user = this.db.users.find((u) => u.username === username);
|
|
16067
16620
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
16068
|
-
return
|
|
16621
|
+
return join20(this.baseDir, user.dataDir);
|
|
16069
16622
|
}
|
|
16070
16623
|
/** List all usernames */
|
|
16071
16624
|
listUsers() {
|
|
@@ -16101,30 +16654,30 @@ var AuthManager = class {
|
|
|
16101
16654
|
const err = this.register(username, password);
|
|
16102
16655
|
if (err) return err;
|
|
16103
16656
|
const userDir = this.getUserDataDir(username);
|
|
16104
|
-
const globalConfig =
|
|
16105
|
-
if (
|
|
16657
|
+
const globalConfig = join20(this.baseDir, "config.json");
|
|
16658
|
+
if (existsSync26(globalConfig)) {
|
|
16106
16659
|
try {
|
|
16107
|
-
const content =
|
|
16108
|
-
writeFileSync3(
|
|
16660
|
+
const content = readFileSync20(globalConfig, "utf-8");
|
|
16661
|
+
writeFileSync3(join20(userDir, "config.json"), content, "utf-8");
|
|
16109
16662
|
} catch {
|
|
16110
16663
|
}
|
|
16111
16664
|
}
|
|
16112
|
-
const globalMemory =
|
|
16113
|
-
if (
|
|
16665
|
+
const globalMemory = join20(this.baseDir, "memory.md");
|
|
16666
|
+
if (existsSync26(globalMemory)) {
|
|
16114
16667
|
try {
|
|
16115
|
-
const content =
|
|
16116
|
-
writeFileSync3(
|
|
16668
|
+
const content = readFileSync20(globalMemory, "utf-8");
|
|
16669
|
+
writeFileSync3(join20(userDir, "memory.md"), content, "utf-8");
|
|
16117
16670
|
} catch {
|
|
16118
16671
|
}
|
|
16119
16672
|
}
|
|
16120
|
-
const globalHistory =
|
|
16121
|
-
if (
|
|
16673
|
+
const globalHistory = join20(this.baseDir, "history");
|
|
16674
|
+
if (existsSync26(globalHistory)) {
|
|
16122
16675
|
try {
|
|
16123
16676
|
const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
|
|
16124
|
-
const userHistory =
|
|
16677
|
+
const userHistory = join20(userDir, "history");
|
|
16125
16678
|
for (const f of files) {
|
|
16126
16679
|
try {
|
|
16127
|
-
copyFileSync(
|
|
16680
|
+
copyFileSync(join20(globalHistory, f), join20(userHistory, f));
|
|
16128
16681
|
} catch {
|
|
16129
16682
|
}
|
|
16130
16683
|
}
|
|
@@ -16135,9 +16688,9 @@ var AuthManager = class {
|
|
|
16135
16688
|
}
|
|
16136
16689
|
// ── Private methods ────────────────────────────────────────────
|
|
16137
16690
|
loadOrCreate() {
|
|
16138
|
-
if (
|
|
16691
|
+
if (existsSync26(this.usersFile)) {
|
|
16139
16692
|
try {
|
|
16140
|
-
return JSON.parse(
|
|
16693
|
+
return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
|
|
16141
16694
|
} catch {
|
|
16142
16695
|
}
|
|
16143
16696
|
}
|
|
@@ -16153,7 +16706,7 @@ var AuthManager = class {
|
|
|
16153
16706
|
this.saveDB(this.db);
|
|
16154
16707
|
}
|
|
16155
16708
|
saveDB(db) {
|
|
16156
|
-
|
|
16709
|
+
mkdirSync12(this.baseDir, { recursive: true });
|
|
16157
16710
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
16158
16711
|
}
|
|
16159
16712
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -16189,7 +16742,7 @@ function getModuleDir() {
|
|
|
16189
16742
|
}
|
|
16190
16743
|
} catch {
|
|
16191
16744
|
}
|
|
16192
|
-
return
|
|
16745
|
+
return resolve9(".");
|
|
16193
16746
|
}
|
|
16194
16747
|
async function startWebServer(options = {}) {
|
|
16195
16748
|
const port = options.port ?? 3e3;
|
|
@@ -16254,8 +16807,8 @@ async function startWebServer(options = {}) {
|
|
|
16254
16807
|
}
|
|
16255
16808
|
}
|
|
16256
16809
|
let skillManager = null;
|
|
16257
|
-
const skillsDir =
|
|
16258
|
-
if (
|
|
16810
|
+
const skillsDir = join21(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
16811
|
+
if (existsSync27(skillsDir)) {
|
|
16259
16812
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
16260
16813
|
skillManager.loadSkills();
|
|
16261
16814
|
const count = skillManager.listSkills().length;
|
|
@@ -16326,18 +16879,18 @@ async function startWebServer(options = {}) {
|
|
|
16326
16879
|
next();
|
|
16327
16880
|
};
|
|
16328
16881
|
const moduleDir = getModuleDir();
|
|
16329
|
-
let clientDir =
|
|
16330
|
-
if (!
|
|
16331
|
-
clientDir =
|
|
16882
|
+
let clientDir = join21(moduleDir, "web", "client");
|
|
16883
|
+
if (!existsSync27(clientDir)) {
|
|
16884
|
+
clientDir = join21(moduleDir, "client");
|
|
16332
16885
|
}
|
|
16333
|
-
if (!
|
|
16334
|
-
clientDir =
|
|
16886
|
+
if (!existsSync27(clientDir)) {
|
|
16887
|
+
clientDir = join21(moduleDir, "..", "..", "src", "web", "client");
|
|
16335
16888
|
}
|
|
16336
|
-
if (!
|
|
16337
|
-
clientDir =
|
|
16889
|
+
if (!existsSync27(clientDir)) {
|
|
16890
|
+
clientDir = join21(process.cwd(), "src", "web", "client");
|
|
16338
16891
|
}
|
|
16339
16892
|
console.log(` Static files: ${clientDir}`);
|
|
16340
|
-
app.use("/vendor", express.static(
|
|
16893
|
+
app.use("/vendor", express.static(join21(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
16341
16894
|
app.use(express.static(clientDir));
|
|
16342
16895
|
app.get("/api/status", (_req, res) => {
|
|
16343
16896
|
res.json({
|
|
@@ -16404,10 +16957,10 @@ async function startWebServer(options = {}) {
|
|
|
16404
16957
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
16405
16958
|
const cwd = process.cwd();
|
|
16406
16959
|
const prefix = req.query.prefix || "";
|
|
16407
|
-
const targetDir =
|
|
16960
|
+
const targetDir = join21(cwd, prefix);
|
|
16408
16961
|
try {
|
|
16409
|
-
const canonicalTarget = realpathSync(
|
|
16410
|
-
const canonicalCwd = realpathSync(
|
|
16962
|
+
const canonicalTarget = realpathSync(resolve9(targetDir));
|
|
16963
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16411
16964
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
16412
16965
|
res.json({ files: [] });
|
|
16413
16966
|
return;
|
|
@@ -16421,7 +16974,7 @@ async function startWebServer(options = {}) {
|
|
|
16421
16974
|
const entries = readdirSync12(targetDir, { withFileTypes: true });
|
|
16422
16975
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
16423
16976
|
name: e.name,
|
|
16424
|
-
path: relative4(cwd,
|
|
16977
|
+
path: relative4(cwd, join21(targetDir, e.name)).replace(/\\/g, "/"),
|
|
16425
16978
|
isDir: e.isDirectory()
|
|
16426
16979
|
}));
|
|
16427
16980
|
res.json({ files });
|
|
@@ -16457,8 +17010,8 @@ async function startWebServer(options = {}) {
|
|
|
16457
17010
|
try {
|
|
16458
17011
|
const authUser = req._authUser;
|
|
16459
17012
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
16460
|
-
const filePath =
|
|
16461
|
-
if (!
|
|
17013
|
+
const filePath = join21(histDir, `${id}.json`);
|
|
17014
|
+
if (!existsSync27(filePath)) {
|
|
16462
17015
|
res.status(404).json({ error: "Session not found" });
|
|
16463
17016
|
return;
|
|
16464
17017
|
}
|
|
@@ -16473,7 +17026,7 @@ async function startWebServer(options = {}) {
|
|
|
16473
17026
|
res.status(404).json({ error: "Session not found" });
|
|
16474
17027
|
return;
|
|
16475
17028
|
}
|
|
16476
|
-
const data = JSON.parse(
|
|
17029
|
+
const data = JSON.parse(readFileSync21(filePath, "utf-8"));
|
|
16477
17030
|
res.json({ session: data });
|
|
16478
17031
|
} catch (err) {
|
|
16479
17032
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -16486,10 +17039,10 @@ async function startWebServer(options = {}) {
|
|
|
16486
17039
|
return;
|
|
16487
17040
|
}
|
|
16488
17041
|
const cwd = process.cwd();
|
|
16489
|
-
const fullPath =
|
|
17042
|
+
const fullPath = resolve9(join21(cwd, filePath));
|
|
16490
17043
|
try {
|
|
16491
17044
|
const canonicalFull = realpathSync(fullPath);
|
|
16492
|
-
const canonicalCwd = realpathSync(
|
|
17045
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16493
17046
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
16494
17047
|
res.json({ error: "Access denied" });
|
|
16495
17048
|
return;
|
|
@@ -16504,7 +17057,7 @@ async function startWebServer(options = {}) {
|
|
|
16504
17057
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
16505
17058
|
return;
|
|
16506
17059
|
}
|
|
16507
|
-
const content =
|
|
17060
|
+
const content = readFileSync21(fullPath, "utf-8");
|
|
16508
17061
|
res.json({ content, size: stat.size });
|
|
16509
17062
|
} catch {
|
|
16510
17063
|
res.json({ error: "Cannot read file" });
|
|
@@ -16708,7 +17261,7 @@ async function startWebServer(options = {}) {
|
|
|
16708
17261
|
});
|
|
16709
17262
|
const MAX_PORT_ATTEMPTS = 10;
|
|
16710
17263
|
let actualPort = port;
|
|
16711
|
-
const result = await new Promise((
|
|
17264
|
+
const result = await new Promise((resolve10, reject) => {
|
|
16712
17265
|
const tryListen = (attempt) => {
|
|
16713
17266
|
server.once("error", (err) => {
|
|
16714
17267
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -16739,7 +17292,7 @@ async function startWebServer(options = {}) {
|
|
|
16739
17292
|
}
|
|
16740
17293
|
console.log(` Press Ctrl+C to stop
|
|
16741
17294
|
`);
|
|
16742
|
-
|
|
17295
|
+
resolve10({ port: actualPort, host, url });
|
|
16743
17296
|
});
|
|
16744
17297
|
};
|
|
16745
17298
|
tryListen(1);
|
|
@@ -16759,17 +17312,17 @@ function resolveProjectMcpPath() {
|
|
|
16759
17312
|
const cwd = process.cwd();
|
|
16760
17313
|
const gitRoot = getGitRoot(cwd);
|
|
16761
17314
|
const projectRoot = gitRoot ?? cwd;
|
|
16762
|
-
const configPath =
|
|
16763
|
-
return
|
|
17315
|
+
const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17316
|
+
return existsSync27(configPath) ? configPath : null;
|
|
16764
17317
|
}
|
|
16765
17318
|
function loadProjectMcpConfig() {
|
|
16766
17319
|
const cwd = process.cwd();
|
|
16767
17320
|
const gitRoot = getGitRoot(cwd);
|
|
16768
17321
|
const projectRoot = gitRoot ?? cwd;
|
|
16769
|
-
const configPath =
|
|
16770
|
-
if (!
|
|
17322
|
+
const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17323
|
+
if (!existsSync27(configPath)) return null;
|
|
16771
17324
|
try {
|
|
16772
|
-
const raw = JSON.parse(
|
|
17325
|
+
const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
16773
17326
|
return raw.mcpServers ?? raw;
|
|
16774
17327
|
} catch {
|
|
16775
17328
|
return null;
|