jinzd-ai-cli 0.4.211 → 0.4.212
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/README.zh-CN.md +23 -0
- package/dist/{batch-DE4RXKZD.js → batch-WIKFEOAZ.js} +2 -2
- package/dist/{chat-index-UBCWHBLR.js → chat-index-R2E27VXN.js} +1 -1
- package/dist/{chunk-E5XCM4A6.js → chunk-GBBVCZ4W.js} +1 -1
- package/dist/{chunk-C3OU2OPF.js → chunk-HW2ALWQJ.js} +2 -2
- package/dist/{chunk-6NS6643Y.js → chunk-HX6N6QEY.js} +1 -1
- package/dist/{chunk-JBWA73GK.js → chunk-IFZX26K7.js} +1 -1
- package/dist/{chunk-ZY2N2N6T.js → chunk-KE4B3NOQ.js} +1 -1
- package/dist/{chunk-BE6ERF7M.js → chunk-LU6FBJQ5.js} +1 -1
- package/dist/{chunk-ZOPYREL5.js → chunk-N5LB3PPL.js} +258 -52
- package/dist/{chunk-UOROWTGG.js → chunk-NYCBOVNF.js} +26 -3
- package/dist/{chunk-W7UKO3PS.js → chunk-OQGVGPEK.js} +5 -0
- package/dist/{ci-XMUEX526.js → ci-X3LNUFZV.js} +2 -2
- package/dist/{constants-AWTIQIWG.js → constants-KDWG4KR4.js} +1 -1
- package/dist/{doctor-cli-SSI6ETFT.js → doctor-cli-TJTWIW7U.js} +4 -4
- package/dist/electron-server.js +468 -174
- package/dist/{hub-INUJND2G.js → hub-JWSV4MKC.js} +1 -1
- package/dist/index.js +142 -17
- package/dist/{run-tests-PACN4UYX.js → run-tests-D7YBY4XB.js} +1 -1
- package/dist/{run-tests-3MHWUF43.js → run-tests-H4Q2PRX6.js} +2 -2
- package/dist/{server-7USZJJAH.js → server-OFKGDRYI.js} +4 -4
- package/dist/{server-BTSKOPQI.js → server-VNCN7AY5.js} +99 -10
- package/dist/{task-orchestrator-OV4O25MX.js → task-orchestrator-AL2E642M.js} +4 -4
- package/dist/{usage-T2P6FTE7.js → usage-LB6REWPO.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
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-KE4B3NOQ.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 join20, dirname as dirname6, resolve as resolve8, relative as relative4, sep as sep3 } from "path";
|
|
65
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, 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 existsSync8, readFileSync as readFileSync7 } 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;
|
|
@@ -6549,6 +6706,7 @@ var ToolExecutor = class {
|
|
|
6549
6706
|
allowedPermissionProfiles = [];
|
|
6550
6707
|
workspaceRoot = process.cwd();
|
|
6551
6708
|
networkPolicy;
|
|
6709
|
+
hookConfigDir = process.cwd();
|
|
6552
6710
|
/** 注入 hooks 和 permission rules 配置 */
|
|
6553
6711
|
setConfig(opts) {
|
|
6554
6712
|
this.hookConfig = opts.hookConfig;
|
|
@@ -6559,6 +6717,7 @@ var ToolExecutor = class {
|
|
|
6559
6717
|
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
6560
6718
|
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
6561
6719
|
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
6720
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
6562
6721
|
}
|
|
6563
6722
|
async execute(call) {
|
|
6564
6723
|
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
@@ -6579,6 +6738,24 @@ var ToolExecutor = class {
|
|
|
6579
6738
|
dangerLevel,
|
|
6580
6739
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
6581
6740
|
});
|
|
6741
|
+
const preToolDecisions = runLifecycleHooks(
|
|
6742
|
+
this.hookConfig,
|
|
6743
|
+
"PreToolUse",
|
|
6744
|
+
{ tool: call.name, dangerLevel, args: call.arguments },
|
|
6745
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(` [hook] ${msg}`)) }
|
|
6746
|
+
);
|
|
6747
|
+
const preDeny = preToolDecisions.find((d) => d.action === "deny");
|
|
6748
|
+
if (preDeny) {
|
|
6749
|
+
return {
|
|
6750
|
+
callId: call.id,
|
|
6751
|
+
content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
|
|
6752
|
+
isError: true
|
|
6753
|
+
};
|
|
6754
|
+
}
|
|
6755
|
+
for (const d of preToolDecisions) {
|
|
6756
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
6757
|
+
for (const w of warnings) console.log(theme.warning(` \u26A0 Hook warning: ${w}`));
|
|
6758
|
+
}
|
|
6582
6759
|
const permission = checkPermissionWithProfile(
|
|
6583
6760
|
call.name,
|
|
6584
6761
|
call.arguments,
|
|
@@ -6594,6 +6771,14 @@ var ToolExecutor = class {
|
|
|
6594
6771
|
}
|
|
6595
6772
|
);
|
|
6596
6773
|
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
6774
|
+
if (networkPermission?.action === "confirm" || permission.action === "confirm") {
|
|
6775
|
+
runLifecycleHooks(
|
|
6776
|
+
this.hookConfig,
|
|
6777
|
+
"PermissionRequest",
|
|
6778
|
+
{ tool: call.name, dangerLevel, args: call.arguments, reason: networkPermission?.reason ?? permission.reason },
|
|
6779
|
+
{ configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(" [hook] " + msg)) }
|
|
6780
|
+
);
|
|
6781
|
+
}
|
|
6597
6782
|
if (networkPermission?.action === "deny") {
|
|
6598
6783
|
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
6599
6784
|
return {
|
|
@@ -6618,11 +6803,13 @@ var ToolExecutor = class {
|
|
|
6618
6803
|
const wasTruncated = content !== rawContent;
|
|
6619
6804
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6620
6805
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
6806
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6621
6807
|
return { callId: call.id, content, isError: false };
|
|
6622
6808
|
} catch (err) {
|
|
6623
6809
|
const message = err instanceof Error ? err.message : String(err);
|
|
6624
6810
|
this.printToolResult(call.name, message, true, false);
|
|
6625
6811
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
6812
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6626
6813
|
return { callId: call.id, content: message, isError: true };
|
|
6627
6814
|
}
|
|
6628
6815
|
}
|
|
@@ -6648,11 +6835,13 @@ var ToolExecutor = class {
|
|
|
6648
6835
|
const wasTruncated = content !== rawContent;
|
|
6649
6836
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6650
6837
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
6838
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6651
6839
|
return { callId: call.id, content, isError: false };
|
|
6652
6840
|
} catch (err) {
|
|
6653
6841
|
const message = err instanceof Error ? err.message : String(err);
|
|
6654
6842
|
this.printToolResult(call.name, message, true, false);
|
|
6655
6843
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
6844
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6656
6845
|
return { callId: call.id, content: message, isError: true };
|
|
6657
6846
|
}
|
|
6658
6847
|
}
|
|
@@ -6686,11 +6875,13 @@ var ToolExecutor = class {
|
|
|
6686
6875
|
const wasTruncated = content !== rawContent;
|
|
6687
6876
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
6688
6877
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
6878
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
6689
6879
|
return { callId: call.id, content, isError: false };
|
|
6690
6880
|
} catch (err) {
|
|
6691
6881
|
const message = err instanceof Error ? err.message : String(err);
|
|
6692
6882
|
this.printToolResult(call.name, message, true, false);
|
|
6693
6883
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
6884
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
6694
6885
|
return { callId: call.id, content: message, isError: true };
|
|
6695
6886
|
}
|
|
6696
6887
|
}
|
|
@@ -6878,10 +7069,10 @@ var ToolExecutor = class {
|
|
|
6878
7069
|
const filePath = String(call.arguments["path"] ?? "");
|
|
6879
7070
|
const newContent = String(call.arguments["content"] ?? "");
|
|
6880
7071
|
if (!filePath) return;
|
|
6881
|
-
if (
|
|
7072
|
+
if (existsSync8(filePath)) {
|
|
6882
7073
|
let oldContent;
|
|
6883
7074
|
try {
|
|
6884
|
-
oldContent =
|
|
7075
|
+
oldContent = readFileSync7(filePath, "utf-8");
|
|
6885
7076
|
} catch {
|
|
6886
7077
|
return;
|
|
6887
7078
|
}
|
|
@@ -6904,7 +7095,7 @@ var ToolExecutor = class {
|
|
|
6904
7095
|
}
|
|
6905
7096
|
} else if (call.name === "edit_file") {
|
|
6906
7097
|
const filePath = String(call.arguments["path"] ?? "");
|
|
6907
|
-
if (!filePath || !
|
|
7098
|
+
if (!filePath || !existsSync8(filePath)) return;
|
|
6908
7099
|
const oldStr = call.arguments["old_str"];
|
|
6909
7100
|
const newStr = call.arguments["new_str"];
|
|
6910
7101
|
if (oldStr !== void 0) {
|
|
@@ -6931,7 +7122,7 @@ var ToolExecutor = class {
|
|
|
6931
7122
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
6932
7123
|
let fileContent;
|
|
6933
7124
|
try {
|
|
6934
|
-
fileContent =
|
|
7125
|
+
fileContent = readFileSync7(filePath, "utf-8");
|
|
6935
7126
|
} catch {
|
|
6936
7127
|
return;
|
|
6937
7128
|
}
|
|
@@ -7166,7 +7357,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7166
7357
|
}
|
|
7167
7358
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
7168
7359
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
7169
|
-
|
|
7360
|
+
mkdirSync5(dirname3(filePath), { recursive: true });
|
|
7170
7361
|
if (appendMode) {
|
|
7171
7362
|
appendFileSync(filePath, content, encoding);
|
|
7172
7363
|
} else {
|
|
@@ -7186,7 +7377,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7186
7377
|
};
|
|
7187
7378
|
|
|
7188
7379
|
// src/tools/builtin/edit-file.ts
|
|
7189
|
-
import { readFileSync as
|
|
7380
|
+
import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
|
|
7190
7381
|
|
|
7191
7382
|
// src/tools/builtin/patch-apply.ts
|
|
7192
7383
|
function parseUnifiedDiff(patch) {
|
|
@@ -7547,7 +7738,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7547
7738
|
const filePath = String(args["path"] ?? "");
|
|
7548
7739
|
const encoding = args["encoding"] ?? "utf-8";
|
|
7549
7740
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
7550
|
-
if (!
|
|
7741
|
+
if (!existsSync9(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
7551
7742
|
const verdict = classifyWritePath(filePath);
|
|
7552
7743
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
7553
7744
|
throw new ToolError(
|
|
@@ -7555,7 +7746,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7555
7746
|
`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
7747
|
);
|
|
7557
7748
|
}
|
|
7558
|
-
const original =
|
|
7749
|
+
const original = readFileSync8(filePath, encoding);
|
|
7559
7750
|
if (args["patch"] !== void 0) {
|
|
7560
7751
|
const patchText = String(args["patch"] ?? "");
|
|
7561
7752
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -7713,8 +7904,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
7713
7904
|
}
|
|
7714
7905
|
|
|
7715
7906
|
// src/tools/builtin/list-dir.ts
|
|
7716
|
-
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as
|
|
7717
|
-
import { join as
|
|
7907
|
+
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync10 } from "fs";
|
|
7908
|
+
import { join as join5, basename as basename3 } from "path";
|
|
7718
7909
|
var listDirTool = {
|
|
7719
7910
|
definition: {
|
|
7720
7911
|
name: "list_dir",
|
|
@@ -7736,7 +7927,7 @@ var listDirTool = {
|
|
|
7736
7927
|
async execute(args) {
|
|
7737
7928
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
7738
7929
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
7739
|
-
if (!
|
|
7930
|
+
if (!existsSync10(dirPath)) {
|
|
7740
7931
|
const targetName = basename3(dirPath).toLowerCase();
|
|
7741
7932
|
const cwd = process.cwd();
|
|
7742
7933
|
const suggestions = [];
|
|
@@ -7794,11 +7985,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
7794
7985
|
if (entry.isDirectory()) {
|
|
7795
7986
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
7796
7987
|
if (recursive) {
|
|
7797
|
-
listRecursive(
|
|
7988
|
+
listRecursive(join5(basePath, entry.name), indent + " ", true, lines);
|
|
7798
7989
|
}
|
|
7799
7990
|
} else {
|
|
7800
7991
|
try {
|
|
7801
|
-
const stat = statSync3(
|
|
7992
|
+
const stat = statSync3(join5(basePath, entry.name));
|
|
7802
7993
|
const size = formatSize(stat.size);
|
|
7803
7994
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
7804
7995
|
} catch {
|
|
@@ -7814,9 +8005,9 @@ function formatSize(bytes) {
|
|
|
7814
8005
|
}
|
|
7815
8006
|
|
|
7816
8007
|
// src/tools/builtin/grep-files.ts
|
|
7817
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8008
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync9, statSync as statSync4, existsSync as existsSync11 } from "fs";
|
|
7818
8009
|
import { readFile } from "fs/promises";
|
|
7819
|
-
import { join as
|
|
8010
|
+
import { join as join6, relative } from "path";
|
|
7820
8011
|
var grepFilesTool = {
|
|
7821
8012
|
definition: {
|
|
7822
8013
|
name: "grep_files",
|
|
@@ -7867,7 +8058,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
7867
8058
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
7868
8059
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
7869
8060
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
7870
|
-
if (!
|
|
8061
|
+
if (!existsSync11(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
7871
8062
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
7872
8063
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
7873
8064
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -7961,11 +8152,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
7961
8152
|
if (paths.length >= maxFiles) return;
|
|
7962
8153
|
if (entry.isDirectory()) {
|
|
7963
8154
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
7964
|
-
walk(
|
|
8155
|
+
walk(join6(dirPath, entry.name));
|
|
7965
8156
|
} else if (entry.isFile()) {
|
|
7966
8157
|
if (isBinary(entry.name)) continue;
|
|
7967
8158
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
7968
|
-
const fullPath =
|
|
8159
|
+
const fullPath = join6(dirPath, entry.name);
|
|
7969
8160
|
try {
|
|
7970
8161
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
7971
8162
|
} catch {
|
|
@@ -8020,7 +8211,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8020
8211
|
}
|
|
8021
8212
|
let content;
|
|
8022
8213
|
try {
|
|
8023
|
-
content =
|
|
8214
|
+
content = readFileSync9(fullPath, "utf-8");
|
|
8024
8215
|
} catch {
|
|
8025
8216
|
return;
|
|
8026
8217
|
}
|
|
@@ -8051,8 +8242,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8051
8242
|
}
|
|
8052
8243
|
|
|
8053
8244
|
// src/tools/builtin/glob-files.ts
|
|
8054
|
-
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as
|
|
8055
|
-
import { join as
|
|
8245
|
+
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync12 } from "fs";
|
|
8246
|
+
import { join as join7, relative as relative2, basename as basename4 } from "path";
|
|
8056
8247
|
var globFilesTool = {
|
|
8057
8248
|
definition: {
|
|
8058
8249
|
name: "glob_files",
|
|
@@ -8085,7 +8276,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8085
8276
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8086
8277
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8087
8278
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8088
|
-
if (!
|
|
8279
|
+
if (!existsSync12(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8089
8280
|
const regex = globToRegex(pattern);
|
|
8090
8281
|
const matches = [];
|
|
8091
8282
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -8162,7 +8353,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
8162
8353
|
}
|
|
8163
8354
|
for (const entry of entries) {
|
|
8164
8355
|
if (results.length >= maxResults) break;
|
|
8165
|
-
const fullPath =
|
|
8356
|
+
const fullPath = join7(dirPath, entry.name);
|
|
8166
8357
|
if (entry.isDirectory()) {
|
|
8167
8358
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8168
8359
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -8902,7 +9093,7 @@ ${preamble}`;
|
|
|
8902
9093
|
}
|
|
8903
9094
|
|
|
8904
9095
|
// src/tools/builtin/save-last-response.ts
|
|
8905
|
-
import { mkdirSync as
|
|
9096
|
+
import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
|
|
8906
9097
|
import { dirname as dirname4 } from "path";
|
|
8907
9098
|
var lastResponseStore = { content: "" };
|
|
8908
9099
|
function cleanupRejectedTeeFile(filePath) {
|
|
@@ -8952,7 +9143,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
8952
9143
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
8953
9144
|
}
|
|
8954
9145
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
8955
|
-
|
|
9146
|
+
mkdirSync6(dirname4(filePath), { recursive: true });
|
|
8956
9147
|
atomicWriteFileSync(filePath, content);
|
|
8957
9148
|
const lines = content.split("\n").length;
|
|
8958
9149
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -8960,11 +9151,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
8960
9151
|
};
|
|
8961
9152
|
|
|
8962
9153
|
// src/tools/builtin/save-memory.ts
|
|
8963
|
-
import { existsSync as
|
|
8964
|
-
import { join as
|
|
9154
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
9155
|
+
import { join as join8 } from "path";
|
|
8965
9156
|
import { homedir as homedir5 } from "os";
|
|
8966
9157
|
function getMemoryFilePath() {
|
|
8967
|
-
return
|
|
9158
|
+
return join8(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
8968
9159
|
}
|
|
8969
9160
|
function formatTimestamp() {
|
|
8970
9161
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -8988,16 +9179,16 @@ var saveMemoryTool = {
|
|
|
8988
9179
|
const content = String(args["content"] ?? "").trim();
|
|
8989
9180
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
8990
9181
|
const memoryPath = getMemoryFilePath();
|
|
8991
|
-
const configDir =
|
|
8992
|
-
if (!
|
|
8993
|
-
|
|
9182
|
+
const configDir = join8(homedir5(), CONFIG_DIR_NAME);
|
|
9183
|
+
if (!existsSync13(configDir)) {
|
|
9184
|
+
mkdirSync7(configDir, { recursive: true });
|
|
8994
9185
|
}
|
|
8995
9186
|
const timestamp = formatTimestamp();
|
|
8996
9187
|
const entry = `
|
|
8997
9188
|
## ${timestamp}
|
|
8998
9189
|
${content}
|
|
8999
9190
|
`;
|
|
9000
|
-
const previous =
|
|
9191
|
+
const previous = existsSync13(memoryPath) ? readFileSync10(memoryPath, "utf-8") : "";
|
|
9001
9192
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
9002
9193
|
const byteSize = statSync6(memoryPath).size;
|
|
9003
9194
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -9534,11 +9725,27 @@ var spawnAgentTool = {
|
|
|
9534
9725
|
if (!ctx.provider) {
|
|
9535
9726
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
9536
9727
|
}
|
|
9728
|
+
const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
|
|
9729
|
+
const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
|
|
9730
|
+
const taskCount = singleTask ? 1 : tasksArr.length;
|
|
9731
|
+
const hookPayload = {
|
|
9732
|
+
task: singleTask || void 0,
|
|
9733
|
+
tasks: tasksArr.length > 0 ? tasksArr : void 0,
|
|
9734
|
+
taskCount,
|
|
9735
|
+
maxRounds
|
|
9736
|
+
};
|
|
9737
|
+
const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
|
|
9738
|
+
const startDeny = startDecisions.find((d) => d.action === "deny");
|
|
9739
|
+
if (startDeny) {
|
|
9740
|
+
throw new ToolError("spawn_agent", "SubagentStart hook denied: " + (startDeny.reason ?? "denied"));
|
|
9741
|
+
}
|
|
9742
|
+
let subagentStatus = "error";
|
|
9537
9743
|
const guardWasActive = subAgentGuard.active;
|
|
9538
9744
|
subAgentGuard.active = true;
|
|
9539
9745
|
try {
|
|
9540
9746
|
if (singleTask) {
|
|
9541
9747
|
const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
|
|
9748
|
+
subagentStatus = "ok";
|
|
9542
9749
|
return [
|
|
9543
9750
|
"## Sub-Agent Result",
|
|
9544
9751
|
"",
|
|
@@ -9567,8 +9774,10 @@ var spawnAgentTool = {
|
|
|
9567
9774
|
});
|
|
9568
9775
|
lines.push("---");
|
|
9569
9776
|
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
9777
|
+
subagentStatus = "ok";
|
|
9570
9778
|
return lines.join("\n");
|
|
9571
9779
|
} finally {
|
|
9780
|
+
runLifecycleHooks(hookConfig, "SubagentStop", { ...hookPayload, status: subagentStatus }, hookConfigDir);
|
|
9572
9781
|
subAgentGuard.active = guardWasActive;
|
|
9573
9782
|
}
|
|
9574
9783
|
}
|
|
@@ -9791,14 +10000,14 @@ var taskStopTool = {
|
|
|
9791
10000
|
|
|
9792
10001
|
// src/tools/builtin/git-tools.ts
|
|
9793
10002
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9794
|
-
import { existsSync as
|
|
9795
|
-
import { join as
|
|
10003
|
+
import { existsSync as existsSync14 } from "fs";
|
|
10004
|
+
import { join as join9 } from "path";
|
|
9796
10005
|
function assertGitRepo(cwd) {
|
|
9797
10006
|
let dir = cwd;
|
|
9798
10007
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
9799
10008
|
while (dir && dir !== root) {
|
|
9800
|
-
if (
|
|
9801
|
-
const parent =
|
|
10009
|
+
if (existsSync14(join9(dir, ".git"))) return;
|
|
10010
|
+
const parent = join9(dir, "..");
|
|
9802
10011
|
if (parent === dir) break;
|
|
9803
10012
|
dir = parent;
|
|
9804
10013
|
}
|
|
@@ -10063,7 +10272,7 @@ ${commitOutput.trim()}`;
|
|
|
10063
10272
|
};
|
|
10064
10273
|
|
|
10065
10274
|
// src/tools/builtin/notebook-edit.ts
|
|
10066
|
-
import { readFileSync as
|
|
10275
|
+
import { readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
|
|
10067
10276
|
import { writeFile } from "fs/promises";
|
|
10068
10277
|
import { resolve as resolve5, extname as extname2 } from "path";
|
|
10069
10278
|
var notebookEditTool = {
|
|
@@ -10118,10 +10327,10 @@ var notebookEditTool = {
|
|
|
10118
10327
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
10119
10328
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
10120
10329
|
}
|
|
10121
|
-
if (!
|
|
10330
|
+
if (!existsSync15(absPath)) {
|
|
10122
10331
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
10123
10332
|
}
|
|
10124
|
-
const raw =
|
|
10333
|
+
const raw = readFileSync11(absPath, "utf-8");
|
|
10125
10334
|
const nb = parseNotebook(raw);
|
|
10126
10335
|
const cellIdx0 = cellIndexRaw - 1;
|
|
10127
10336
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -10538,8 +10747,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
10538
10747
|
|
|
10539
10748
|
// src/tools/registry.ts
|
|
10540
10749
|
import { pathToFileURL } from "url";
|
|
10541
|
-
import { existsSync as
|
|
10542
|
-
import { join as
|
|
10750
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
|
|
10751
|
+
import { join as join10 } from "path";
|
|
10543
10752
|
var ToolRegistry = class {
|
|
10544
10753
|
tools = /* @__PURE__ */ new Map();
|
|
10545
10754
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -10700,9 +10909,9 @@ var ToolRegistry = class {
|
|
|
10700
10909
|
* Returns the number of successfully loaded plugins.
|
|
10701
10910
|
*/
|
|
10702
10911
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
10703
|
-
if (!
|
|
10912
|
+
if (!existsSync16(pluginsDir)) {
|
|
10704
10913
|
try {
|
|
10705
|
-
|
|
10914
|
+
mkdirSync8(pluginsDir, { recursive: true });
|
|
10706
10915
|
} catch {
|
|
10707
10916
|
}
|
|
10708
10917
|
return 0;
|
|
@@ -10726,12 +10935,12 @@ var ToolRegistry = class {
|
|
|
10726
10935
|
process.stderr.write(
|
|
10727
10936
|
`
|
|
10728
10937
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
10729
|
-
` + files.map((f) => ` + ${
|
|
10938
|
+
` + files.map((f) => ` + ${join10(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
10730
10939
|
);
|
|
10731
10940
|
let loaded = 0;
|
|
10732
10941
|
for (const file of files) {
|
|
10733
10942
|
try {
|
|
10734
|
-
const fileUrl = pathToFileURL(
|
|
10943
|
+
const fileUrl = pathToFileURL(join10(pluginsDir, file)).href;
|
|
10735
10944
|
const mod = await import(fileUrl);
|
|
10736
10945
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
10737
10946
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11277,11 +11486,11 @@ var McpManager = class {
|
|
|
11277
11486
|
};
|
|
11278
11487
|
|
|
11279
11488
|
// src/skills/manager.ts
|
|
11280
|
-
import { existsSync as
|
|
11281
|
-
import { join as
|
|
11489
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
|
|
11490
|
+
import { join as join11 } from "path";
|
|
11282
11491
|
|
|
11283
11492
|
// src/skills/types.ts
|
|
11284
|
-
import { readFileSync as
|
|
11493
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
11285
11494
|
import { basename as basename5 } from "path";
|
|
11286
11495
|
function parseSimpleYaml(yaml) {
|
|
11287
11496
|
const result = {};
|
|
@@ -11303,7 +11512,7 @@ function parseYamlArray(value) {
|
|
|
11303
11512
|
function parseSkillFile(filePath) {
|
|
11304
11513
|
let raw;
|
|
11305
11514
|
try {
|
|
11306
|
-
raw =
|
|
11515
|
+
raw = readFileSync12(filePath, "utf-8");
|
|
11307
11516
|
} catch {
|
|
11308
11517
|
return null;
|
|
11309
11518
|
}
|
|
@@ -11346,9 +11555,9 @@ var SkillManager = class {
|
|
|
11346
11555
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
11347
11556
|
loadSkills() {
|
|
11348
11557
|
this.skills.clear();
|
|
11349
|
-
if (!
|
|
11558
|
+
if (!existsSync17(this.skillsDir)) {
|
|
11350
11559
|
try {
|
|
11351
|
-
|
|
11560
|
+
mkdirSync9(this.skillsDir, { recursive: true });
|
|
11352
11561
|
} catch {
|
|
11353
11562
|
}
|
|
11354
11563
|
return 0;
|
|
@@ -11361,14 +11570,14 @@ var SkillManager = class {
|
|
|
11361
11570
|
}
|
|
11362
11571
|
for (const entry of entries) {
|
|
11363
11572
|
let filePath;
|
|
11364
|
-
const fullPath =
|
|
11573
|
+
const fullPath = join11(this.skillsDir, entry);
|
|
11365
11574
|
if (entry.endsWith(".md")) {
|
|
11366
11575
|
filePath = fullPath;
|
|
11367
11576
|
} else {
|
|
11368
11577
|
try {
|
|
11369
11578
|
if (statSync7(fullPath).isDirectory()) {
|
|
11370
|
-
const skillMd =
|
|
11371
|
-
if (
|
|
11579
|
+
const skillMd = join11(fullPath, "SKILL.md");
|
|
11580
|
+
if (existsSync17(skillMd)) {
|
|
11372
11581
|
filePath = skillMd;
|
|
11373
11582
|
} else {
|
|
11374
11583
|
continue;
|
|
@@ -11433,7 +11642,7 @@ var SkillManager = class {
|
|
|
11433
11642
|
// src/web/tool-executor-web.ts
|
|
11434
11643
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11435
11644
|
import { tmpdir as tmpdir2 } from "os";
|
|
11436
|
-
import { existsSync as
|
|
11645
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
|
|
11437
11646
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11438
11647
|
constructor(registry, ws) {
|
|
11439
11648
|
this.registry = registry;
|
|
@@ -11449,6 +11658,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11449
11658
|
allowedPermissionProfiles = [];
|
|
11450
11659
|
workspaceRoot = process.cwd();
|
|
11451
11660
|
networkPolicy;
|
|
11661
|
+
hookConfigDir = process.cwd();
|
|
11452
11662
|
/** Pending confirm promises keyed by requestId */
|
|
11453
11663
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
11454
11664
|
/** Pending batch confirm promises */
|
|
@@ -11488,6 +11698,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11488
11698
|
if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
|
|
11489
11699
|
if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
|
|
11490
11700
|
if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
|
|
11701
|
+
if (opts.hookConfigDir) this.hookConfigDir = opts.hookConfigDir;
|
|
11491
11702
|
}
|
|
11492
11703
|
/** Clear M7 timeout timer for a requestId */
|
|
11493
11704
|
clearPendingTimer(requestId) {
|
|
@@ -11536,7 +11747,6 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11536
11747
|
}
|
|
11537
11748
|
sendToolCallStart(call) {
|
|
11538
11749
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
11539
|
-
let toolCallStarted = false;
|
|
11540
11750
|
const startTime = Date.now();
|
|
11541
11751
|
this.toolStartTimes.set(call.id, startTime);
|
|
11542
11752
|
const msg = {
|
|
@@ -11569,9 +11779,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11569
11779
|
if (call.name === "write_file") {
|
|
11570
11780
|
const filePath = String(call.arguments["path"] ?? "");
|
|
11571
11781
|
const newContent = String(call.arguments["content"] ?? "");
|
|
11572
|
-
if (filePath &&
|
|
11782
|
+
if (filePath && existsSync18(filePath)) {
|
|
11573
11783
|
try {
|
|
11574
|
-
const old =
|
|
11784
|
+
const old = readFileSync13(filePath, "utf-8");
|
|
11575
11785
|
return renderDiff(old, newContent, { filePath });
|
|
11576
11786
|
} catch {
|
|
11577
11787
|
}
|
|
@@ -11651,6 +11861,26 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11651
11861
|
dangerLevel,
|
|
11652
11862
|
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
11653
11863
|
});
|
|
11864
|
+
const preToolDecisions = runLifecycleHooks(
|
|
11865
|
+
this.hookConfig,
|
|
11866
|
+
"PreToolUse",
|
|
11867
|
+
{ tool: call.name, dangerLevel, args: call.arguments },
|
|
11868
|
+
{ configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: `[hook] ${message}` }) }
|
|
11869
|
+
);
|
|
11870
|
+
const preDeny = preToolDecisions.find((d) => d.action === "deny");
|
|
11871
|
+
if (preDeny) {
|
|
11872
|
+
const result = {
|
|
11873
|
+
callId: call.id,
|
|
11874
|
+
content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
|
|
11875
|
+
isError: true
|
|
11876
|
+
};
|
|
11877
|
+
this.sendToolCallResult(call, result.content, true);
|
|
11878
|
+
return result;
|
|
11879
|
+
}
|
|
11880
|
+
for (const d of preToolDecisions) {
|
|
11881
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
11882
|
+
for (const warning of warnings) this.send({ type: "info", message: `[hook] ${warning}` });
|
|
11883
|
+
}
|
|
11654
11884
|
const permission = checkPermissionWithProfile(
|
|
11655
11885
|
call.name,
|
|
11656
11886
|
call.arguments,
|
|
@@ -11666,6 +11896,14 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11666
11896
|
}
|
|
11667
11897
|
);
|
|
11668
11898
|
const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
|
|
11899
|
+
if (networkPermission?.action === "confirm" || permission.action === "confirm") {
|
|
11900
|
+
runLifecycleHooks(
|
|
11901
|
+
this.hookConfig,
|
|
11902
|
+
"PermissionRequest",
|
|
11903
|
+
{ tool: call.name, dangerLevel, args: call.arguments, reason: networkPermission?.reason ?? permission.reason },
|
|
11904
|
+
{ configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
11905
|
+
);
|
|
11906
|
+
}
|
|
11669
11907
|
if (networkPermission?.action === "deny") {
|
|
11670
11908
|
const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
|
|
11671
11909
|
return {
|
|
@@ -11689,11 +11927,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11689
11927
|
const content = truncateOutput(rawContent, call.name);
|
|
11690
11928
|
this.sendToolCallResult(call, rawContent, false);
|
|
11691
11929
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
11930
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11692
11931
|
return { callId: call.id, content, isError: false };
|
|
11693
11932
|
} catch (err) {
|
|
11694
11933
|
const message = err instanceof Error ? err.message : String(err);
|
|
11695
11934
|
this.sendToolCallResult(call, message, true);
|
|
11696
11935
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
11936
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11697
11937
|
return { callId: call.id, content: message, isError: true };
|
|
11698
11938
|
}
|
|
11699
11939
|
}
|
|
@@ -11714,11 +11954,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11714
11954
|
const content = truncateOutput(rawContent, call.name);
|
|
11715
11955
|
this.sendToolCallResult(call, rawContent, false);
|
|
11716
11956
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
11957
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11717
11958
|
return { callId: call.id, content, isError: false };
|
|
11718
11959
|
} catch (err) {
|
|
11719
11960
|
const message = err instanceof Error ? err.message : String(err);
|
|
11720
11961
|
this.sendToolCallResult(call, message, true);
|
|
11721
11962
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
11963
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11722
11964
|
return { callId: call.id, content: message, isError: true };
|
|
11723
11965
|
}
|
|
11724
11966
|
}
|
|
@@ -11736,11 +11978,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11736
11978
|
const content = truncateOutput(rawContent, call.name);
|
|
11737
11979
|
this.sendToolCallResult(call, rawContent, false);
|
|
11738
11980
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
11981
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
11739
11982
|
return { callId: call.id, content, isError: false };
|
|
11740
11983
|
} catch (err) {
|
|
11741
11984
|
const message = err instanceof Error ? err.message : String(err);
|
|
11742
11985
|
this.sendToolCallResult(call, message, true);
|
|
11743
11986
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
11987
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
11744
11988
|
return { callId: call.id, content: message, isError: true };
|
|
11745
11989
|
}
|
|
11746
11990
|
}
|
|
@@ -11811,20 +12055,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11811
12055
|
};
|
|
11812
12056
|
|
|
11813
12057
|
// src/core/system-prompt-builder.ts
|
|
11814
|
-
import { existsSync as
|
|
11815
|
-
import { join as
|
|
12058
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
12059
|
+
import { join as join13 } from "path";
|
|
11816
12060
|
|
|
11817
12061
|
// src/repl/dev-state.ts
|
|
11818
|
-
import { existsSync as
|
|
11819
|
-
import { join as
|
|
12062
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
12063
|
+
import { join as join12 } from "path";
|
|
11820
12064
|
import { homedir as homedir6 } from "os";
|
|
11821
12065
|
function getDevStatePath() {
|
|
11822
|
-
return
|
|
12066
|
+
return join12(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
11823
12067
|
}
|
|
11824
12068
|
function loadDevState() {
|
|
11825
12069
|
const path3 = getDevStatePath();
|
|
11826
|
-
if (!
|
|
11827
|
-
const content =
|
|
12070
|
+
if (!existsSync19(path3)) return null;
|
|
12071
|
+
const content = readFileSync14(path3, "utf-8").trim();
|
|
11828
12072
|
return content || null;
|
|
11829
12073
|
}
|
|
11830
12074
|
|
|
@@ -11882,9 +12126,9 @@ ${ctx.activeSkill.content}`);
|
|
|
11882
12126
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
11883
12127
|
}
|
|
11884
12128
|
function loadMemoryContent(configDir) {
|
|
11885
|
-
const memoryPath =
|
|
11886
|
-
if (!
|
|
11887
|
-
let content =
|
|
12129
|
+
const memoryPath = join13(configDir, MEMORY_FILE_NAME);
|
|
12130
|
+
if (!existsSync20(memoryPath)) return null;
|
|
12131
|
+
let content = readFileSync15(memoryPath, "utf-8").trim();
|
|
11888
12132
|
if (!content) return null;
|
|
11889
12133
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
11890
12134
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -12093,8 +12337,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
12093
12337
|
}
|
|
12094
12338
|
|
|
12095
12339
|
// src/core/context-files.ts
|
|
12096
|
-
import { existsSync as
|
|
12097
|
-
import { join as
|
|
12340
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
|
|
12341
|
+
import { join as join14, relative as relative3, resolve as resolve6 } from "path";
|
|
12098
12342
|
function uniqueNonEmpty(values) {
|
|
12099
12343
|
const seen = /* @__PURE__ */ new Set();
|
|
12100
12344
|
const out = [];
|
|
@@ -12124,7 +12368,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12124
12368
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
12125
12369
|
const shown = displayPath(filePath, cwd);
|
|
12126
12370
|
try {
|
|
12127
|
-
const raw =
|
|
12371
|
+
const raw = readFileSync16(filePath);
|
|
12128
12372
|
const byteCount = raw.byteLength;
|
|
12129
12373
|
if (byteCount === 0) {
|
|
12130
12374
|
return {
|
|
@@ -12171,8 +12415,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12171
12415
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
12172
12416
|
const skipped = [];
|
|
12173
12417
|
for (const candidate of candidates) {
|
|
12174
|
-
const filePath =
|
|
12175
|
-
if (!
|
|
12418
|
+
const filePath = join14(dir, candidate);
|
|
12419
|
+
if (!existsSync21(filePath)) continue;
|
|
12176
12420
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
12177
12421
|
if (result.skipped) skipped.push(result.skipped);
|
|
12178
12422
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -12199,7 +12443,7 @@ function loadContextFiles(options) {
|
|
|
12199
12443
|
});
|
|
12200
12444
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12201
12445
|
}
|
|
12202
|
-
if (!
|
|
12446
|
+
if (!existsSync21(filePath)) {
|
|
12203
12447
|
skipped.push({
|
|
12204
12448
|
level: "single",
|
|
12205
12449
|
filePath,
|
|
@@ -12250,13 +12494,13 @@ function loadContextFiles(options) {
|
|
|
12250
12494
|
}
|
|
12251
12495
|
|
|
12252
12496
|
// src/web/session-handler.ts
|
|
12253
|
-
import { existsSync as
|
|
12254
|
-
import { join as
|
|
12497
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
12498
|
+
import { join as join18, resolve as resolve7, dirname as dirname5 } from "path";
|
|
12255
12499
|
|
|
12256
12500
|
// src/tools/git-context.ts
|
|
12257
12501
|
import { execSync as execSync2 } from "child_process";
|
|
12258
|
-
import { existsSync as
|
|
12259
|
-
import { join as
|
|
12502
|
+
import { existsSync as existsSync22 } from "fs";
|
|
12503
|
+
import { join as join15 } from "path";
|
|
12260
12504
|
function runGit2(cmd, cwd) {
|
|
12261
12505
|
try {
|
|
12262
12506
|
return execSync2(`git ${cmd}`, {
|
|
@@ -12273,7 +12517,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
12273
12517
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
12274
12518
|
}
|
|
12275
12519
|
function getGitContext(cwd = process.cwd()) {
|
|
12276
|
-
if (!
|
|
12520
|
+
if (!existsSync22(join15(cwd, ".git"))) {
|
|
12277
12521
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
12278
12522
|
if (!result) return null;
|
|
12279
12523
|
}
|
|
@@ -12402,8 +12646,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
12402
12646
|
}
|
|
12403
12647
|
|
|
12404
12648
|
// src/repl/commands/project-init.ts
|
|
12405
|
-
import { existsSync as
|
|
12406
|
-
import { join as
|
|
12649
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
12650
|
+
import { join as join16 } from "path";
|
|
12407
12651
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12408
12652
|
"node_modules",
|
|
12409
12653
|
".git",
|
|
@@ -12447,11 +12691,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12447
12691
|
const sorted = filtered.sort((a, b) => {
|
|
12448
12692
|
let aIsDir = false, bIsDir = false;
|
|
12449
12693
|
try {
|
|
12450
|
-
aIsDir = statSync8(
|
|
12694
|
+
aIsDir = statSync8(join16(d, a)).isDirectory();
|
|
12451
12695
|
} catch {
|
|
12452
12696
|
}
|
|
12453
12697
|
try {
|
|
12454
|
-
bIsDir = statSync8(
|
|
12698
|
+
bIsDir = statSync8(join16(d, b)).isDirectory();
|
|
12455
12699
|
} catch {
|
|
12456
12700
|
}
|
|
12457
12701
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -12459,7 +12703,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12459
12703
|
});
|
|
12460
12704
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12461
12705
|
const name = sorted[i];
|
|
12462
|
-
const fullPath =
|
|
12706
|
+
const fullPath = join16(d, name);
|
|
12463
12707
|
const isLast = i === sorted.length - 1;
|
|
12464
12708
|
const connector = isLast ? "+-- " : "|-- ";
|
|
12465
12709
|
let isDir;
|
|
@@ -12486,7 +12730,7 @@ function scanProject(cwd) {
|
|
|
12486
12730
|
configFiles: [],
|
|
12487
12731
|
directoryStructure: ""
|
|
12488
12732
|
};
|
|
12489
|
-
const check = (file) =>
|
|
12733
|
+
const check = (file) => existsSync23(join16(cwd, file));
|
|
12490
12734
|
const configCandidates = [
|
|
12491
12735
|
"package.json",
|
|
12492
12736
|
"tsconfig.json",
|
|
@@ -12513,7 +12757,7 @@ function scanProject(cwd) {
|
|
|
12513
12757
|
info.type = "node";
|
|
12514
12758
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12515
12759
|
try {
|
|
12516
|
-
const pkg = JSON.parse(
|
|
12760
|
+
const pkg = JSON.parse(readFileSync17(join16(cwd, "package.json"), "utf-8"));
|
|
12517
12761
|
const scripts = pkg.scripts ?? {};
|
|
12518
12762
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12519
12763
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13173,7 +13417,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
13173
13417
|
}
|
|
13174
13418
|
|
|
13175
13419
|
// src/hub/persist.ts
|
|
13176
|
-
import { join as
|
|
13420
|
+
import { join as join17 } from "path";
|
|
13177
13421
|
function discussionToMessages(state2) {
|
|
13178
13422
|
const out = [];
|
|
13179
13423
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -13211,7 +13455,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
13211
13455
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
13212
13456
|
session.titleAiGenerated = true;
|
|
13213
13457
|
await sm.save();
|
|
13214
|
-
return { id: session.id, path:
|
|
13458
|
+
return { id: session.id, path: join17(config.getHistoryDir(), `${session.id}.json`) };
|
|
13215
13459
|
}
|
|
13216
13460
|
|
|
13217
13461
|
// src/web/session-handler.ts
|
|
@@ -13314,6 +13558,7 @@ var SessionHandler = class {
|
|
|
13314
13558
|
const permissionProfiles = this.config.get("permissionProfiles") ?? {};
|
|
13315
13559
|
this.toolExecutor.setConfig({
|
|
13316
13560
|
hookConfig: hooks,
|
|
13561
|
+
hookConfigDir: this.config.getConfigDir(),
|
|
13317
13562
|
permissionRules,
|
|
13318
13563
|
defaultPermission,
|
|
13319
13564
|
permissionProfileName,
|
|
@@ -13326,6 +13571,11 @@ var SessionHandler = class {
|
|
|
13326
13571
|
if (permissionProfileWarning) {
|
|
13327
13572
|
this.send({ type: "info", message: permissionProfileWarning });
|
|
13328
13573
|
}
|
|
13574
|
+
const pendingHooks = getPendingHookTrust(hooks, this.config.getConfigDir());
|
|
13575
|
+
if (pendingHooks.length > 0) {
|
|
13576
|
+
this.send({ type: "info", message: `${pendingHooks.length} project hook(s) require trust. Use /hooks list and /hooks trust <id>.` });
|
|
13577
|
+
}
|
|
13578
|
+
runLifecycleHooks(hooks, "SessionStart", { source: "web" }, { configDir: this.config.getConfigDir() });
|
|
13329
13579
|
this.sendStatus();
|
|
13330
13580
|
askUserContext.rl = null;
|
|
13331
13581
|
askUserContext.prompting = false;
|
|
@@ -13434,6 +13684,7 @@ var SessionHandler = class {
|
|
|
13434
13684
|
}
|
|
13435
13685
|
}
|
|
13436
13686
|
onDisconnect() {
|
|
13687
|
+
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
13437
13688
|
this.toolExecutor.cancelAll();
|
|
13438
13689
|
if (this.abortController) this.abortController.abort();
|
|
13439
13690
|
for (const resolve9 of this.pendingAskUser.values()) resolve9(null);
|
|
@@ -13611,6 +13862,26 @@ var SessionHandler = class {
|
|
|
13611
13862
|
try {
|
|
13612
13863
|
this.ensureSession();
|
|
13613
13864
|
const session = this.sessions.current;
|
|
13865
|
+
const promptDecisions = runLifecycleHooks(
|
|
13866
|
+
this.config.get("hooks") ?? void 0,
|
|
13867
|
+
"UserPromptSubmit",
|
|
13868
|
+
{ sessionId: session.id, prompt: content },
|
|
13869
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
13870
|
+
);
|
|
13871
|
+
const promptDeny = promptDecisions.find((d) => d.action === "deny");
|
|
13872
|
+
if (promptDeny) {
|
|
13873
|
+
this.send({ type: "error", message: "Prompt blocked by hook: " + (promptDeny.reason ?? "UserPromptSubmit denied") });
|
|
13874
|
+
return;
|
|
13875
|
+
}
|
|
13876
|
+
for (const d of promptDecisions) {
|
|
13877
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
13878
|
+
for (const warning of warnings) this.send({ type: "info", message: "[hook] " + warning });
|
|
13879
|
+
}
|
|
13880
|
+
const secretHits = scanString(content, { customRegexes: [] });
|
|
13881
|
+
if (secretHits.length > 0) {
|
|
13882
|
+
const kinds = [...new Set(secretHits.map((h) => h.kind))].join(", ");
|
|
13883
|
+
this.send({ type: "info", message: "Possible secret in prompt (" + kinds + ")." });
|
|
13884
|
+
}
|
|
13614
13885
|
const ALLOWED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"]);
|
|
13615
13886
|
const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
|
|
13616
13887
|
let msgContent;
|
|
@@ -13886,7 +14157,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13886
14157
|
this.send({ type: "response_done", content, usage });
|
|
13887
14158
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
13888
14159
|
try {
|
|
13889
|
-
|
|
14160
|
+
mkdirSync11(dirname5(pendingTeeSave), { recursive: true });
|
|
13890
14161
|
const bodyToSave = stripOuterCodeFence(content);
|
|
13891
14162
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
13892
14163
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -14062,7 +14333,7 @@ ${summaryContent}`,
|
|
|
14062
14333
|
let isError = false;
|
|
14063
14334
|
let summary;
|
|
14064
14335
|
try {
|
|
14065
|
-
|
|
14336
|
+
mkdirSync11(dirname5(saveToFile), { recursive: true });
|
|
14066
14337
|
fileStream = createWriteStream(saveToFile);
|
|
14067
14338
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
14068
14339
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -14648,9 +14919,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14648
14919
|
let modifiedFiles = 0;
|
|
14649
14920
|
const diffLines2 = [];
|
|
14650
14921
|
for (const [filePath, { earliest }] of fileMap) {
|
|
14651
|
-
const currentContent =
|
|
14922
|
+
const currentContent = existsSync24(filePath) ? (() => {
|
|
14652
14923
|
try {
|
|
14653
|
-
return
|
|
14924
|
+
return readFileSync18(filePath, "utf-8");
|
|
14654
14925
|
} catch {
|
|
14655
14926
|
return null;
|
|
14656
14927
|
}
|
|
@@ -15166,7 +15437,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15166
15437
|
case "test": {
|
|
15167
15438
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
15168
15439
|
try {
|
|
15169
|
-
const { executeTests } = await import("./run-tests-
|
|
15440
|
+
const { executeTests } = await import("./run-tests-D7YBY4XB.js");
|
|
15170
15441
|
const argStr = args.join(" ").trim();
|
|
15171
15442
|
let testArgs = {};
|
|
15172
15443
|
if (argStr) {
|
|
@@ -15183,9 +15454,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15183
15454
|
// ── /init ───────────────────────────────────────────────────────
|
|
15184
15455
|
case "init": {
|
|
15185
15456
|
const cwd = process.cwd();
|
|
15186
|
-
const targetPath =
|
|
15457
|
+
const targetPath = join18(cwd, "AICLI.md");
|
|
15187
15458
|
const force = args.includes("--force");
|
|
15188
|
-
if (
|
|
15459
|
+
if (existsSync24(targetPath) && !force) {
|
|
15189
15460
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
15190
15461
|
Use /init --force to overwrite.` });
|
|
15191
15462
|
break;
|
|
@@ -15218,7 +15489,7 @@ Use /context reload to load it.` });
|
|
|
15218
15489
|
lines.push("**Config Files:**");
|
|
15219
15490
|
lines.push(` Dir: ${configDir}`);
|
|
15220
15491
|
const checkFile = (label, filePath) => {
|
|
15221
|
-
const exists =
|
|
15492
|
+
const exists = existsSync24(filePath);
|
|
15222
15493
|
let extra = "";
|
|
15223
15494
|
if (exists) {
|
|
15224
15495
|
try {
|
|
@@ -15228,9 +15499,9 @@ Use /context reload to load it.` });
|
|
|
15228
15499
|
}
|
|
15229
15500
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
15230
15501
|
};
|
|
15231
|
-
checkFile("config.json",
|
|
15232
|
-
checkFile("memory.md",
|
|
15233
|
-
checkFile("dev-state.md",
|
|
15502
|
+
checkFile("config.json", join18(configDir, "config.json"));
|
|
15503
|
+
checkFile("memory.md", join18(configDir, MEMORY_FILE_NAME));
|
|
15504
|
+
checkFile("dev-state.md", join18(configDir, "dev-state.md"));
|
|
15234
15505
|
lines.push("");
|
|
15235
15506
|
if (this.mcpManager) {
|
|
15236
15507
|
lines.push("**MCP Servers:**");
|
|
@@ -15418,7 +15689,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
15418
15689
|
break;
|
|
15419
15690
|
}
|
|
15420
15691
|
const dirPath = resolve7(sub);
|
|
15421
|
-
if (!
|
|
15692
|
+
if (!existsSync24(dirPath)) {
|
|
15422
15693
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
15423
15694
|
break;
|
|
15424
15695
|
}
|
|
@@ -15434,8 +15705,8 @@ It will be included in AI context for subsequent messages.` });
|
|
|
15434
15705
|
// ── /commands ───────────────────────────────────────────────────
|
|
15435
15706
|
case "commands": {
|
|
15436
15707
|
const configDir = this.config.getConfigDir();
|
|
15437
|
-
const commandsDir =
|
|
15438
|
-
if (!
|
|
15708
|
+
const commandsDir = join18(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15709
|
+
if (!existsSync24(commandsDir)) {
|
|
15439
15710
|
this.send({ type: "info", message: `No custom commands directory.
|
|
15440
15711
|
Create: ${commandsDir}/ with .md files.` });
|
|
15441
15712
|
break;
|
|
@@ -15461,7 +15732,7 @@ Add .md files to create commands.` });
|
|
|
15461
15732
|
// ── /plugins ────────────────────────────────────────────────────
|
|
15462
15733
|
case "plugins": {
|
|
15463
15734
|
const configDir = this.config.getConfigDir();
|
|
15464
|
-
const pluginsDir =
|
|
15735
|
+
const pluginsDir = join18(configDir, PLUGINS_DIR_NAME);
|
|
15465
15736
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
15466
15737
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
15467
15738
|
if (pluginTools.length === 0) {
|
|
@@ -15516,6 +15787,21 @@ Add .md files to create commands.` });
|
|
|
15516
15787
|
this.send({ type: "info", message: "Not enough messages to compact." });
|
|
15517
15788
|
return;
|
|
15518
15789
|
}
|
|
15790
|
+
const preDecisions = runLifecycleHooks(
|
|
15791
|
+
this.config.get("hooks") ?? void 0,
|
|
15792
|
+
"PreCompact",
|
|
15793
|
+
{ sessionId: session.id, messageCount: session.messages.length, instruction },
|
|
15794
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
15795
|
+
);
|
|
15796
|
+
const preDeny = preDecisions.find((d) => d.action === "deny");
|
|
15797
|
+
if (preDeny) {
|
|
15798
|
+
this.send({ type: "error", message: "Compact blocked by hook: " + (preDeny.reason ?? "PreCompact denied") });
|
|
15799
|
+
return;
|
|
15800
|
+
}
|
|
15801
|
+
for (const d of preDecisions) {
|
|
15802
|
+
const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
|
|
15803
|
+
for (const warning of warnings) this.send({ type: "info", message: "[hook] " + warning });
|
|
15804
|
+
}
|
|
15519
15805
|
const provider = this.providers.get(this.currentProvider);
|
|
15520
15806
|
if (!provider) return;
|
|
15521
15807
|
this.send({ type: "info", message: "Compacting conversation..." });
|
|
@@ -15535,7 +15821,15 @@ Add .md files to create commands.` });
|
|
|
15535
15821
|
});
|
|
15536
15822
|
const summaryMsg = { role: "user", content: "[Conversation summary]", timestamp: /* @__PURE__ */ new Date() };
|
|
15537
15823
|
const ackMsg = { role: "assistant", content: response.content, timestamp: /* @__PURE__ */ new Date() };
|
|
15538
|
-
session.
|
|
15824
|
+
const beforeCount = session.messages.length;
|
|
15825
|
+
const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
|
|
15826
|
+
const afterCount = session.messages.length;
|
|
15827
|
+
runLifecycleHooks(
|
|
15828
|
+
this.config.get("hooks") ?? void 0,
|
|
15829
|
+
"PostCompact",
|
|
15830
|
+
{ sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
|
|
15831
|
+
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
15832
|
+
);
|
|
15539
15833
|
this.send({ type: "info", message: `Compacted: removed ${toSummarize.length} messages, kept last ${keepLast}.` });
|
|
15540
15834
|
this.sendStatus();
|
|
15541
15835
|
} catch (err) {
|
|
@@ -15635,11 +15929,11 @@ Add .md files to create commands.` });
|
|
|
15635
15929
|
}
|
|
15636
15930
|
memoryShow() {
|
|
15637
15931
|
const configDir = this.config.getConfigDir();
|
|
15638
|
-
const memPath =
|
|
15932
|
+
const memPath = join18(configDir, MEMORY_FILE_NAME);
|
|
15639
15933
|
let content = "";
|
|
15640
15934
|
try {
|
|
15641
|
-
if (
|
|
15642
|
-
content =
|
|
15935
|
+
if (existsSync24(memPath)) {
|
|
15936
|
+
content = readFileSync18(memPath, "utf-8");
|
|
15643
15937
|
}
|
|
15644
15938
|
} catch (err) {
|
|
15645
15939
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -15653,11 +15947,11 @@ Add .md files to create commands.` });
|
|
|
15653
15947
|
}
|
|
15654
15948
|
memoryAdd(text) {
|
|
15655
15949
|
const configDir = this.config.getConfigDir();
|
|
15656
|
-
const memPath =
|
|
15950
|
+
const memPath = join18(configDir, MEMORY_FILE_NAME);
|
|
15657
15951
|
try {
|
|
15658
|
-
|
|
15952
|
+
mkdirSync11(configDir, { recursive: true });
|
|
15659
15953
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
15660
|
-
const previous =
|
|
15954
|
+
const previous = existsSync24(memPath) ? readFileSync18(memPath, "utf-8") : "";
|
|
15661
15955
|
const entry = `
|
|
15662
15956
|
- [${timestamp}] ${text}
|
|
15663
15957
|
`;
|
|
@@ -15669,7 +15963,7 @@ Add .md files to create commands.` });
|
|
|
15669
15963
|
}
|
|
15670
15964
|
memoryClear() {
|
|
15671
15965
|
const configDir = this.config.getConfigDir();
|
|
15672
|
-
const memPath =
|
|
15966
|
+
const memPath = join18(configDir, MEMORY_FILE_NAME);
|
|
15673
15967
|
try {
|
|
15674
15968
|
atomicWriteFileSync(memPath, "");
|
|
15675
15969
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
@@ -15687,7 +15981,7 @@ Add .md files to create commands.` });
|
|
|
15687
15981
|
return;
|
|
15688
15982
|
}
|
|
15689
15983
|
try {
|
|
15690
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
15984
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-R2E27VXN.js");
|
|
15691
15985
|
const loaded = loadChatIndex2();
|
|
15692
15986
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
15693
15987
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -15723,7 +16017,7 @@ Add .md files to create commands.` });
|
|
|
15723
16017
|
}
|
|
15724
16018
|
async handleMemoryStatus() {
|
|
15725
16019
|
try {
|
|
15726
|
-
const { getChatIndexStatus } = await import("./chat-index-
|
|
16020
|
+
const { getChatIndexStatus } = await import("./chat-index-R2E27VXN.js");
|
|
15727
16021
|
const s = getChatIndexStatus();
|
|
15728
16022
|
this.send({
|
|
15729
16023
|
type: "memory_status",
|
|
@@ -15748,7 +16042,7 @@ Add .md files to create commands.` });
|
|
|
15748
16042
|
type: "info",
|
|
15749
16043
|
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
16044
|
});
|
|
15751
|
-
const { buildChatIndex } = await import("./chat-index-
|
|
16045
|
+
const { buildChatIndex } = await import("./chat-index-R2E27VXN.js");
|
|
15752
16046
|
const stats = await buildChatIndex({
|
|
15753
16047
|
full,
|
|
15754
16048
|
onProgress: (p) => {
|
|
@@ -15903,8 +16197,8 @@ async function setupProxy(configProxy) {
|
|
|
15903
16197
|
}
|
|
15904
16198
|
|
|
15905
16199
|
// src/web/auth.ts
|
|
15906
|
-
import { existsSync as
|
|
15907
|
-
import { join as
|
|
16200
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
|
|
16201
|
+
import { join as join19 } from "path";
|
|
15908
16202
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
15909
16203
|
var USERS_FILE = "users.json";
|
|
15910
16204
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -15919,7 +16213,7 @@ var AuthManager = class {
|
|
|
15919
16213
|
db;
|
|
15920
16214
|
constructor(baseDir) {
|
|
15921
16215
|
this.baseDir = baseDir;
|
|
15922
|
-
this.usersFile =
|
|
16216
|
+
this.usersFile = join19(baseDir, USERS_FILE);
|
|
15923
16217
|
this.db = this.loadOrCreate();
|
|
15924
16218
|
}
|
|
15925
16219
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -15948,9 +16242,9 @@ var AuthManager = class {
|
|
|
15948
16242
|
}
|
|
15949
16243
|
const salt = randomBytes(16).toString("hex");
|
|
15950
16244
|
const passwordHash = this.hashPassword(password, salt);
|
|
15951
|
-
const dataDir =
|
|
15952
|
-
const fullDataDir =
|
|
15953
|
-
|
|
16245
|
+
const dataDir = join19(USERS_DIR, username);
|
|
16246
|
+
const fullDataDir = join19(this.baseDir, dataDir);
|
|
16247
|
+
mkdirSync12(join19(fullDataDir, "history"), { recursive: true });
|
|
15954
16248
|
const user = {
|
|
15955
16249
|
username,
|
|
15956
16250
|
passwordHash,
|
|
@@ -16065,7 +16359,7 @@ var AuthManager = class {
|
|
|
16065
16359
|
getUserDataDir(username) {
|
|
16066
16360
|
const user = this.db.users.find((u) => u.username === username);
|
|
16067
16361
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
16068
|
-
return
|
|
16362
|
+
return join19(this.baseDir, user.dataDir);
|
|
16069
16363
|
}
|
|
16070
16364
|
/** List all usernames */
|
|
16071
16365
|
listUsers() {
|
|
@@ -16101,30 +16395,30 @@ var AuthManager = class {
|
|
|
16101
16395
|
const err = this.register(username, password);
|
|
16102
16396
|
if (err) return err;
|
|
16103
16397
|
const userDir = this.getUserDataDir(username);
|
|
16104
|
-
const globalConfig =
|
|
16105
|
-
if (
|
|
16398
|
+
const globalConfig = join19(this.baseDir, "config.json");
|
|
16399
|
+
if (existsSync25(globalConfig)) {
|
|
16106
16400
|
try {
|
|
16107
|
-
const content =
|
|
16108
|
-
writeFileSync3(
|
|
16401
|
+
const content = readFileSync19(globalConfig, "utf-8");
|
|
16402
|
+
writeFileSync3(join19(userDir, "config.json"), content, "utf-8");
|
|
16109
16403
|
} catch {
|
|
16110
16404
|
}
|
|
16111
16405
|
}
|
|
16112
|
-
const globalMemory =
|
|
16113
|
-
if (
|
|
16406
|
+
const globalMemory = join19(this.baseDir, "memory.md");
|
|
16407
|
+
if (existsSync25(globalMemory)) {
|
|
16114
16408
|
try {
|
|
16115
|
-
const content =
|
|
16116
|
-
writeFileSync3(
|
|
16409
|
+
const content = readFileSync19(globalMemory, "utf-8");
|
|
16410
|
+
writeFileSync3(join19(userDir, "memory.md"), content, "utf-8");
|
|
16117
16411
|
} catch {
|
|
16118
16412
|
}
|
|
16119
16413
|
}
|
|
16120
|
-
const globalHistory =
|
|
16121
|
-
if (
|
|
16414
|
+
const globalHistory = join19(this.baseDir, "history");
|
|
16415
|
+
if (existsSync25(globalHistory)) {
|
|
16122
16416
|
try {
|
|
16123
16417
|
const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
|
|
16124
|
-
const userHistory =
|
|
16418
|
+
const userHistory = join19(userDir, "history");
|
|
16125
16419
|
for (const f of files) {
|
|
16126
16420
|
try {
|
|
16127
|
-
copyFileSync(
|
|
16421
|
+
copyFileSync(join19(globalHistory, f), join19(userHistory, f));
|
|
16128
16422
|
} catch {
|
|
16129
16423
|
}
|
|
16130
16424
|
}
|
|
@@ -16135,9 +16429,9 @@ var AuthManager = class {
|
|
|
16135
16429
|
}
|
|
16136
16430
|
// ── Private methods ────────────────────────────────────────────
|
|
16137
16431
|
loadOrCreate() {
|
|
16138
|
-
if (
|
|
16432
|
+
if (existsSync25(this.usersFile)) {
|
|
16139
16433
|
try {
|
|
16140
|
-
return JSON.parse(
|
|
16434
|
+
return JSON.parse(readFileSync19(this.usersFile, "utf-8"));
|
|
16141
16435
|
} catch {
|
|
16142
16436
|
}
|
|
16143
16437
|
}
|
|
@@ -16153,7 +16447,7 @@ var AuthManager = class {
|
|
|
16153
16447
|
this.saveDB(this.db);
|
|
16154
16448
|
}
|
|
16155
16449
|
saveDB(db) {
|
|
16156
|
-
|
|
16450
|
+
mkdirSync12(this.baseDir, { recursive: true });
|
|
16157
16451
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
16158
16452
|
}
|
|
16159
16453
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -16254,8 +16548,8 @@ async function startWebServer(options = {}) {
|
|
|
16254
16548
|
}
|
|
16255
16549
|
}
|
|
16256
16550
|
let skillManager = null;
|
|
16257
|
-
const skillsDir =
|
|
16258
|
-
if (
|
|
16551
|
+
const skillsDir = join20(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
16552
|
+
if (existsSync26(skillsDir)) {
|
|
16259
16553
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
16260
16554
|
skillManager.loadSkills();
|
|
16261
16555
|
const count = skillManager.listSkills().length;
|
|
@@ -16326,18 +16620,18 @@ async function startWebServer(options = {}) {
|
|
|
16326
16620
|
next();
|
|
16327
16621
|
};
|
|
16328
16622
|
const moduleDir = getModuleDir();
|
|
16329
|
-
let clientDir =
|
|
16330
|
-
if (!
|
|
16331
|
-
clientDir =
|
|
16623
|
+
let clientDir = join20(moduleDir, "web", "client");
|
|
16624
|
+
if (!existsSync26(clientDir)) {
|
|
16625
|
+
clientDir = join20(moduleDir, "client");
|
|
16332
16626
|
}
|
|
16333
|
-
if (!
|
|
16334
|
-
clientDir =
|
|
16627
|
+
if (!existsSync26(clientDir)) {
|
|
16628
|
+
clientDir = join20(moduleDir, "..", "..", "src", "web", "client");
|
|
16335
16629
|
}
|
|
16336
|
-
if (!
|
|
16337
|
-
clientDir =
|
|
16630
|
+
if (!existsSync26(clientDir)) {
|
|
16631
|
+
clientDir = join20(process.cwd(), "src", "web", "client");
|
|
16338
16632
|
}
|
|
16339
16633
|
console.log(` Static files: ${clientDir}`);
|
|
16340
|
-
app.use("/vendor", express.static(
|
|
16634
|
+
app.use("/vendor", express.static(join20(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
16341
16635
|
app.use(express.static(clientDir));
|
|
16342
16636
|
app.get("/api/status", (_req, res) => {
|
|
16343
16637
|
res.json({
|
|
@@ -16404,7 +16698,7 @@ async function startWebServer(options = {}) {
|
|
|
16404
16698
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
16405
16699
|
const cwd = process.cwd();
|
|
16406
16700
|
const prefix = req.query.prefix || "";
|
|
16407
|
-
const targetDir =
|
|
16701
|
+
const targetDir = join20(cwd, prefix);
|
|
16408
16702
|
try {
|
|
16409
16703
|
const canonicalTarget = realpathSync(resolve8(targetDir));
|
|
16410
16704
|
const canonicalCwd = realpathSync(resolve8(cwd));
|
|
@@ -16421,7 +16715,7 @@ async function startWebServer(options = {}) {
|
|
|
16421
16715
|
const entries = readdirSync12(targetDir, { withFileTypes: true });
|
|
16422
16716
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
16423
16717
|
name: e.name,
|
|
16424
|
-
path: relative4(cwd,
|
|
16718
|
+
path: relative4(cwd, join20(targetDir, e.name)).replace(/\\/g, "/"),
|
|
16425
16719
|
isDir: e.isDirectory()
|
|
16426
16720
|
}));
|
|
16427
16721
|
res.json({ files });
|
|
@@ -16457,8 +16751,8 @@ async function startWebServer(options = {}) {
|
|
|
16457
16751
|
try {
|
|
16458
16752
|
const authUser = req._authUser;
|
|
16459
16753
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
16460
|
-
const filePath =
|
|
16461
|
-
if (!
|
|
16754
|
+
const filePath = join20(histDir, `${id}.json`);
|
|
16755
|
+
if (!existsSync26(filePath)) {
|
|
16462
16756
|
res.status(404).json({ error: "Session not found" });
|
|
16463
16757
|
return;
|
|
16464
16758
|
}
|
|
@@ -16473,7 +16767,7 @@ async function startWebServer(options = {}) {
|
|
|
16473
16767
|
res.status(404).json({ error: "Session not found" });
|
|
16474
16768
|
return;
|
|
16475
16769
|
}
|
|
16476
|
-
const data = JSON.parse(
|
|
16770
|
+
const data = JSON.parse(readFileSync20(filePath, "utf-8"));
|
|
16477
16771
|
res.json({ session: data });
|
|
16478
16772
|
} catch (err) {
|
|
16479
16773
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -16486,7 +16780,7 @@ async function startWebServer(options = {}) {
|
|
|
16486
16780
|
return;
|
|
16487
16781
|
}
|
|
16488
16782
|
const cwd = process.cwd();
|
|
16489
|
-
const fullPath = resolve8(
|
|
16783
|
+
const fullPath = resolve8(join20(cwd, filePath));
|
|
16490
16784
|
try {
|
|
16491
16785
|
const canonicalFull = realpathSync(fullPath);
|
|
16492
16786
|
const canonicalCwd = realpathSync(resolve8(cwd));
|
|
@@ -16504,7 +16798,7 @@ async function startWebServer(options = {}) {
|
|
|
16504
16798
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
16505
16799
|
return;
|
|
16506
16800
|
}
|
|
16507
|
-
const content =
|
|
16801
|
+
const content = readFileSync20(fullPath, "utf-8");
|
|
16508
16802
|
res.json({ content, size: stat.size });
|
|
16509
16803
|
} catch {
|
|
16510
16804
|
res.json({ error: "Cannot read file" });
|
|
@@ -16759,17 +17053,17 @@ function resolveProjectMcpPath() {
|
|
|
16759
17053
|
const cwd = process.cwd();
|
|
16760
17054
|
const gitRoot = getGitRoot(cwd);
|
|
16761
17055
|
const projectRoot = gitRoot ?? cwd;
|
|
16762
|
-
const configPath =
|
|
16763
|
-
return
|
|
17056
|
+
const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17057
|
+
return existsSync26(configPath) ? configPath : null;
|
|
16764
17058
|
}
|
|
16765
17059
|
function loadProjectMcpConfig() {
|
|
16766
17060
|
const cwd = process.cwd();
|
|
16767
17061
|
const gitRoot = getGitRoot(cwd);
|
|
16768
17062
|
const projectRoot = gitRoot ?? cwd;
|
|
16769
|
-
const configPath =
|
|
16770
|
-
if (!
|
|
17063
|
+
const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17064
|
+
if (!existsSync26(configPath)) return null;
|
|
16771
17065
|
try {
|
|
16772
|
-
const raw = JSON.parse(
|
|
17066
|
+
const raw = JSON.parse(readFileSync20(configPath, "utf-8"));
|
|
16773
17067
|
return raw.mcpServers ?? raw;
|
|
16774
17068
|
} catch {
|
|
16775
17069
|
return null;
|