jinzd-ai-cli 0.4.216 → 0.4.218
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 +6 -3
- package/dist/{batch-C6HCXZIG.js → batch-7V7OTMUP.js} +2 -2
- package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
- package/dist/{chunk-TJ6GGN4B.js → chunk-5CA2TJ5F.js} +1 -1
- package/dist/{chunk-OLN7VUZA.js → chunk-C2Z42DI5.js} +3 -1
- package/dist/{chunk-TCOC4AUI.js → chunk-GX3HSGJX.js} +690 -169
- package/dist/{chunk-PNTLA3MN.js → chunk-H2UIHGHH.js} +23 -22
- package/dist/{chunk-GY3C3C2Y.js → chunk-L4UREAID.js} +3 -3
- package/dist/{chunk-77HDCGTN.js → chunk-MWKE2TNS.js} +1 -1
- package/dist/{chunk-7YWS3JLU.js → chunk-NTCB7CMT.js} +3 -1
- package/dist/{chunk-6HLKCSB3.js → chunk-OUC75QCF.js} +1 -1
- package/dist/{chunk-WAI3WPV2.js → chunk-P4VBLXKS.js} +1 -1
- package/dist/{chunk-VW7Y27WW.js → chunk-VGFTM3XT.js} +1 -1
- package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
- package/dist/{ci-6ZTFO3LX.js → ci-L6GH2WVC.js} +4 -4
- package/dist/{ci-format-CLZ6QJRL.js → ci-format-WW7454AY.js} +2 -2
- package/dist/{constants-APSORFOH.js → constants-NCTFSHDU.js} +3 -1
- package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-EWMFBP5Q.js} +4 -4
- package/dist/electron-server.js +965 -377
- package/dist/{hub-ZGHQWNWE.js → hub-CDL6T7CP.js} +1 -1
- package/dist/index.js +259 -97
- package/dist/{pr-JOL3IAGV.js → pr-D6PEKEGK.js} +4 -4
- package/dist/{run-tests-4PKSIVK5.js → run-tests-NXVVKAK2.js} +1 -1
- package/dist/{run-tests-ZRK4TQUN.js → run-tests-SWU2XEV7.js} +2 -2
- package/dist/{server-FXUF5P64.js → server-LHYSS6CK.js} +207 -100
- package/dist/{server-O3XHT56X.js → server-WUT7VYTD.js} +5 -4
- package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-C5AA2BI5.js} +5 -4
- package/dist/{usage-X7MJX4YD.js → usage-6ZUUJBI2.js} +2 -2
- package/dist/web/client/app.js +53 -2
- package/dist/web/client/index.html +11 -1
- package/package.json +1 -1
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
} from "./chunk-T2NL5ZIA.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VGFTM3XT.js";
|
|
9
9
|
import {
|
|
10
10
|
runTool
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MWKE2TNS.js";
|
|
12
12
|
import {
|
|
13
13
|
getDangerLevel,
|
|
14
14
|
isFileWriteTool,
|
|
@@ -23,10 +23,16 @@ import {
|
|
|
23
23
|
CONFIG_DIR_NAME,
|
|
24
24
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
25
25
|
MEMORY_FILE_NAME,
|
|
26
|
+
MEMORY_MAX_CHARS,
|
|
27
|
+
MEMORY_STORE_FILE_NAME,
|
|
28
|
+
PLUGINS_DIR_NAME,
|
|
26
29
|
SUBAGENT_ALLOWED_TOOLS,
|
|
27
30
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
28
31
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-NTCB7CMT.js";
|
|
33
|
+
import {
|
|
34
|
+
getGitRoot
|
|
35
|
+
} from "./chunk-HOSJZMQS.js";
|
|
30
36
|
import {
|
|
31
37
|
fileCheckpoints
|
|
32
38
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -34,6 +40,10 @@ import {
|
|
|
34
40
|
loadChatIndex,
|
|
35
41
|
searchChatMemory
|
|
36
42
|
} from "./chunk-TB4W4Y4T.js";
|
|
43
|
+
import {
|
|
44
|
+
DEFAULT_PATTERNS,
|
|
45
|
+
redactString
|
|
46
|
+
} from "./chunk-SLSWPBK3.js";
|
|
37
47
|
import {
|
|
38
48
|
indexProject
|
|
39
49
|
} from "./chunk-K3CF65QH.js";
|
|
@@ -44,14 +54,295 @@ import {
|
|
|
44
54
|
atomicWriteFileSync
|
|
45
55
|
} from "./chunk-IW3Q7AE5.js";
|
|
46
56
|
|
|
57
|
+
// src/plugins/plugin-manager.ts
|
|
58
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, cpSync, rmSync, statSync } from "fs";
|
|
59
|
+
import { basename, dirname, join, resolve } from "path";
|
|
60
|
+
import { createHash } from "crypto";
|
|
61
|
+
import { z } from "zod";
|
|
62
|
+
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
63
|
+
var STATE_FILE = "plugin-state.json";
|
|
64
|
+
var HookCommandSchema = z.union([
|
|
65
|
+
z.string(),
|
|
66
|
+
z.object({
|
|
67
|
+
command: z.string(),
|
|
68
|
+
source: z.enum(["user", "project", "managed"]).optional(),
|
|
69
|
+
description: z.string().optional(),
|
|
70
|
+
required: z.boolean().optional(),
|
|
71
|
+
timeoutMs: z.number().int().min(100).max(3e4).optional(),
|
|
72
|
+
disabled: z.boolean().optional()
|
|
73
|
+
}),
|
|
74
|
+
z.array(z.union([
|
|
75
|
+
z.string(),
|
|
76
|
+
z.object({
|
|
77
|
+
command: z.string(),
|
|
78
|
+
source: z.enum(["user", "project", "managed"]).optional(),
|
|
79
|
+
description: z.string().optional(),
|
|
80
|
+
required: z.boolean().optional(),
|
|
81
|
+
timeoutMs: z.number().int().min(100).max(3e4).optional(),
|
|
82
|
+
disabled: z.boolean().optional()
|
|
83
|
+
})
|
|
84
|
+
]))
|
|
85
|
+
]);
|
|
86
|
+
var HookEventNameSchema = z.enum([
|
|
87
|
+
"SessionStart",
|
|
88
|
+
"UserPromptSubmit",
|
|
89
|
+
"PreToolUse",
|
|
90
|
+
"PermissionRequest",
|
|
91
|
+
"PostToolUse",
|
|
92
|
+
"PreCompact",
|
|
93
|
+
"PostCompact",
|
|
94
|
+
"Stop",
|
|
95
|
+
"SubagentStart",
|
|
96
|
+
"SubagentStop"
|
|
97
|
+
]);
|
|
98
|
+
var McpServerSchema = z.object({
|
|
99
|
+
command: z.string(),
|
|
100
|
+
args: z.array(z.string()).default([]),
|
|
101
|
+
env: z.record(z.string()).optional(),
|
|
102
|
+
timeout: z.number().default(3e4)
|
|
103
|
+
});
|
|
104
|
+
var PluginManifestSchema = z.object({
|
|
105
|
+
name: z.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
|
|
106
|
+
version: z.string().min(1),
|
|
107
|
+
description: z.string().optional(),
|
|
108
|
+
skills: z.array(z.string()).default([]),
|
|
109
|
+
hooks: z.object({
|
|
110
|
+
enabled: z.boolean().optional(),
|
|
111
|
+
events: z.record(HookEventNameSchema, HookCommandSchema).default({})
|
|
112
|
+
}).default({ events: {} }),
|
|
113
|
+
commands: z.array(z.string()).default([]),
|
|
114
|
+
mcpServers: z.record(McpServerSchema).default({}),
|
|
115
|
+
agents: z.array(z.string()).default([]),
|
|
116
|
+
permissionHints: z.array(z.string()).default([])
|
|
117
|
+
}).strict();
|
|
118
|
+
function pluginRoot(configDir) {
|
|
119
|
+
return join(configDir, PLUGINS_DIR_NAME);
|
|
120
|
+
}
|
|
121
|
+
function pluginManifestPath(pluginDir) {
|
|
122
|
+
return join(pluginDir, MANIFEST_RELATIVE);
|
|
123
|
+
}
|
|
124
|
+
function statePath(configDir) {
|
|
125
|
+
return join(pluginRoot(configDir), STATE_FILE);
|
|
126
|
+
}
|
|
127
|
+
function loadState(configDir) {
|
|
128
|
+
const file = statePath(configDir);
|
|
129
|
+
if (!existsSync(file)) return { version: 1, plugins: [] };
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
132
|
+
return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
|
|
133
|
+
} catch {
|
|
134
|
+
return { version: 1, plugins: [] };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function saveState(configDir, state) {
|
|
138
|
+
mkdirSync(pluginRoot(configDir), { recursive: true });
|
|
139
|
+
atomicWriteFileSync(statePath(configDir), JSON.stringify(state, null, 2));
|
|
140
|
+
}
|
|
141
|
+
function hashPluginManifest(manifestPath) {
|
|
142
|
+
return createHash("sha256").update(readFileSync(manifestPath)).digest("hex");
|
|
143
|
+
}
|
|
144
|
+
function readPluginManifest(manifestPath) {
|
|
145
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync(manifestPath, "utf-8")));
|
|
146
|
+
}
|
|
147
|
+
function findPluginSourceDir(inputPath) {
|
|
148
|
+
const abs = resolve(inputPath);
|
|
149
|
+
const direct = statSync(abs).isDirectory() ? abs : dirname(abs);
|
|
150
|
+
if (existsSync(pluginManifestPath(direct))) return direct;
|
|
151
|
+
const nested = join(direct, ".aicli-plugin");
|
|
152
|
+
if (existsSync(join(nested, "plugin.json"))) return direct;
|
|
153
|
+
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
154
|
+
}
|
|
155
|
+
function installPlugin(configDir, inputPath) {
|
|
156
|
+
const sourceDir = findPluginSourceDir(inputPath);
|
|
157
|
+
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
158
|
+
const targetDir = join(pluginRoot(configDir), manifest.name);
|
|
159
|
+
mkdirSync(pluginRoot(configDir), { recursive: true });
|
|
160
|
+
if (existsSync(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
161
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
162
|
+
const manifestPath = pluginManifestPath(targetDir);
|
|
163
|
+
const hash = hashPluginManifest(manifestPath);
|
|
164
|
+
const state = loadState(configDir);
|
|
165
|
+
const previous = state.plugins.find((p) => p.name === manifest.name);
|
|
166
|
+
const next = {
|
|
167
|
+
name: manifest.name,
|
|
168
|
+
enabled: previous?.enabled ?? false,
|
|
169
|
+
trusted: false,
|
|
170
|
+
hash,
|
|
171
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
172
|
+
};
|
|
173
|
+
state.plugins = [...state.plugins.filter((p) => p.name !== manifest.name), next];
|
|
174
|
+
saveState(configDir, state);
|
|
175
|
+
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
|
|
176
|
+
}
|
|
177
|
+
function setPluginEnabled(configDir, name, enabled) {
|
|
178
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
179
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
180
|
+
const state = loadState(configDir);
|
|
181
|
+
const current = state.plugins.find((p) => p.name === plugin.name) ?? {
|
|
182
|
+
name: plugin.name,
|
|
183
|
+
enabled: false,
|
|
184
|
+
trusted: false,
|
|
185
|
+
hash: plugin.hash,
|
|
186
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
187
|
+
};
|
|
188
|
+
const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
|
|
189
|
+
state.plugins = [...state.plugins.filter((p) => p.name !== plugin.name), next];
|
|
190
|
+
saveState(configDir, state);
|
|
191
|
+
return next;
|
|
192
|
+
}
|
|
193
|
+
function trustPlugin(configDir, name) {
|
|
194
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
195
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
196
|
+
const state = loadState(configDir);
|
|
197
|
+
const current = state.plugins.find((p) => p.name === plugin.name) ?? {
|
|
198
|
+
name: plugin.name,
|
|
199
|
+
enabled: false,
|
|
200
|
+
trusted: false,
|
|
201
|
+
hash: plugin.hash,
|
|
202
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
203
|
+
};
|
|
204
|
+
const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
205
|
+
state.plugins = [...state.plugins.filter((p) => p.name !== plugin.name), next];
|
|
206
|
+
saveState(configDir, state);
|
|
207
|
+
return next;
|
|
208
|
+
}
|
|
209
|
+
function getInstalledPlugin(configDir, name) {
|
|
210
|
+
const plugins = listInstalledPlugins(configDir);
|
|
211
|
+
const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
|
|
212
|
+
if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
|
|
213
|
+
if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
|
|
214
|
+
return matches[0];
|
|
215
|
+
}
|
|
216
|
+
function listInstalledPlugins(configDir) {
|
|
217
|
+
const root = pluginRoot(configDir);
|
|
218
|
+
if (!existsSync(root)) return [];
|
|
219
|
+
const state = loadState(configDir);
|
|
220
|
+
const out = [];
|
|
221
|
+
for (const entry of readdirSync(root)) {
|
|
222
|
+
const dir = join(root, entry);
|
|
223
|
+
try {
|
|
224
|
+
if (!statSync(dir).isDirectory()) continue;
|
|
225
|
+
} catch {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const manifestPath = pluginManifestPath(dir);
|
|
229
|
+
if (!existsSync(manifestPath)) continue;
|
|
230
|
+
try {
|
|
231
|
+
const manifest = readPluginManifest(manifestPath);
|
|
232
|
+
const hash = hashPluginManifest(manifestPath);
|
|
233
|
+
const st = state.plugins.find((p) => p.name === manifest.name);
|
|
234
|
+
const trusted = st?.trusted === true && st.hash === hash;
|
|
235
|
+
out.push({
|
|
236
|
+
name: manifest.name,
|
|
237
|
+
dir,
|
|
238
|
+
manifestPath,
|
|
239
|
+
manifest,
|
|
240
|
+
hash,
|
|
241
|
+
enabled: st?.enabled === true,
|
|
242
|
+
trusted,
|
|
243
|
+
valid: true
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
out.push({
|
|
247
|
+
name: entry,
|
|
248
|
+
dir,
|
|
249
|
+
manifestPath,
|
|
250
|
+
manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
|
|
251
|
+
hash: "",
|
|
252
|
+
enabled: false,
|
|
253
|
+
trusted: false,
|
|
254
|
+
valid: false,
|
|
255
|
+
error: err instanceof Error ? err.message : String(err)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
260
|
+
}
|
|
261
|
+
function activePlugins(configDir) {
|
|
262
|
+
return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
|
|
263
|
+
}
|
|
264
|
+
function uniqueDirs(plugin, rels) {
|
|
265
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
266
|
+
for (const rel of rels) {
|
|
267
|
+
const abs = resolve(plugin.dir, rel);
|
|
268
|
+
if (!abs.startsWith(resolve(plugin.dir))) continue;
|
|
269
|
+
if (existsSync(abs)) dirs.add(statSync(abs).isDirectory() ? abs : dirname(abs));
|
|
270
|
+
}
|
|
271
|
+
return [...dirs];
|
|
272
|
+
}
|
|
273
|
+
function prefixCommand(plugin, command) {
|
|
274
|
+
return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
|
|
275
|
+
}
|
|
276
|
+
function pluginHooks(plugin) {
|
|
277
|
+
const events = plugin.manifest.hooks?.events ?? {};
|
|
278
|
+
const out = {};
|
|
279
|
+
for (const [event, raw] of Object.entries(events)) {
|
|
280
|
+
const normalize = (item) => {
|
|
281
|
+
if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
|
|
282
|
+
if (item && typeof item === "object") {
|
|
283
|
+
const obj = item;
|
|
284
|
+
return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
|
|
285
|
+
}
|
|
286
|
+
return item;
|
|
287
|
+
};
|
|
288
|
+
out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
|
|
289
|
+
}
|
|
290
|
+
return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
|
|
291
|
+
}
|
|
292
|
+
function mergePluginHooks(base, configDir) {
|
|
293
|
+
const plugins = activePlugins(configDir);
|
|
294
|
+
if (plugins.length === 0) return base;
|
|
295
|
+
const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
|
|
296
|
+
for (const plugin of plugins) {
|
|
297
|
+
const hooks = pluginHooks(plugin);
|
|
298
|
+
if (!hooks?.events) continue;
|
|
299
|
+
for (const [event, entry] of Object.entries(hooks.events)) {
|
|
300
|
+
const key = event;
|
|
301
|
+
const existing = merged.events?.[key];
|
|
302
|
+
const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
|
|
303
|
+
const newItems = Array.isArray(entry) ? entry : [entry];
|
|
304
|
+
merged.events[key] = [...existingItems, ...newItems];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return merged;
|
|
308
|
+
}
|
|
309
|
+
function getActivePluginAssets(configDir) {
|
|
310
|
+
const plugins = activePlugins(configDir);
|
|
311
|
+
const mcpServers = {};
|
|
312
|
+
const skillDirs = [];
|
|
313
|
+
const commandDirs = [];
|
|
314
|
+
const agentDirs = [];
|
|
315
|
+
for (const plugin of plugins) {
|
|
316
|
+
skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
|
|
317
|
+
commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
|
|
318
|
+
agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
|
|
319
|
+
for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
|
|
320
|
+
mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
|
|
324
|
+
}
|
|
325
|
+
function describePlugin(plugin) {
|
|
326
|
+
const m = plugin.manifest;
|
|
327
|
+
return [
|
|
328
|
+
`${plugin.name}@${m.version}`,
|
|
329
|
+
` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
|
|
330
|
+
` dir: ${plugin.dir}`,
|
|
331
|
+
m.description ? ` description: ${m.description}` : void 0,
|
|
332
|
+
` assets: ${m.skills.length} skill(s), ${m.commands.length} command(s), ${m.agents.length} agent(s), ${Object.keys(m.mcpServers).length} MCP server(s), ${Object.keys(m.hooks?.events ?? {}).length} hook event(s)`,
|
|
333
|
+
m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
|
|
334
|
+
plugin.error ? ` error: ${plugin.error}` : void 0
|
|
335
|
+
].filter((line) => Boolean(line));
|
|
336
|
+
}
|
|
337
|
+
|
|
47
338
|
// src/tools/builtin/bash.ts
|
|
48
339
|
import { spawn } from "child_process";
|
|
49
|
-
import { existsSync as
|
|
340
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
50
341
|
import { platform as platform2 } from "os";
|
|
51
|
-
import { resolve } from "path";
|
|
342
|
+
import { resolve as resolve2 } from "path";
|
|
52
343
|
|
|
53
344
|
// src/tools/undo-stack.ts
|
|
54
|
-
import { readFileSync, writeFileSync, unlinkSync, rmdirSync, existsSync } from "fs";
|
|
345
|
+
import { readFileSync as readFileSync2, writeFileSync, unlinkSync, rmdirSync, existsSync as existsSync2 } from "fs";
|
|
55
346
|
var MAX_UNDO_DEPTH = 20;
|
|
56
347
|
var UndoStack = class {
|
|
57
348
|
stack = [];
|
|
@@ -62,9 +353,9 @@ var UndoStack = class {
|
|
|
62
353
|
*/
|
|
63
354
|
push(filePath, description) {
|
|
64
355
|
let previousContent = null;
|
|
65
|
-
if (
|
|
356
|
+
if (existsSync2(filePath)) {
|
|
66
357
|
try {
|
|
67
|
-
previousContent =
|
|
358
|
+
previousContent = readFileSync2(filePath, "utf-8");
|
|
68
359
|
} catch {
|
|
69
360
|
return;
|
|
70
361
|
}
|
|
@@ -118,7 +409,7 @@ var UndoStack = class {
|
|
|
118
409
|
try {
|
|
119
410
|
if (entry.previousContent === null) {
|
|
120
411
|
if (entry.isDirectory) {
|
|
121
|
-
if (
|
|
412
|
+
if (existsSync2(entry.filePath)) {
|
|
122
413
|
try {
|
|
123
414
|
rmdirSync(entry.filePath);
|
|
124
415
|
return { entry, result: `Removed newly created directory: ${entry.filePath}` };
|
|
@@ -128,7 +419,7 @@ var UndoStack = class {
|
|
|
128
419
|
}
|
|
129
420
|
return { entry, result: `Directory already removed: ${entry.filePath}` };
|
|
130
421
|
} else {
|
|
131
|
-
if (
|
|
422
|
+
if (existsSync2(entry.filePath)) {
|
|
132
423
|
unlinkSync(entry.filePath);
|
|
133
424
|
}
|
|
134
425
|
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
@@ -287,7 +578,7 @@ Important rules:
|
|
|
287
578
|
throw new ToolError("bash", blockingHint);
|
|
288
579
|
}
|
|
289
580
|
let currentCwd = getCwd();
|
|
290
|
-
if (!
|
|
581
|
+
if (!existsSync3(currentCwd)) {
|
|
291
582
|
const fallback = process.cwd();
|
|
292
583
|
process.stderr.write(
|
|
293
584
|
`[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
|
|
@@ -298,8 +589,8 @@ Important rules:
|
|
|
298
589
|
}
|
|
299
590
|
let effectiveCwd = currentCwd;
|
|
300
591
|
if (cwdArg) {
|
|
301
|
-
const resolved =
|
|
302
|
-
if (!
|
|
592
|
+
const resolved = resolve2(currentCwd, cwdArg);
|
|
593
|
+
if (!existsSync3(resolved)) {
|
|
303
594
|
throw new ToolError(
|
|
304
595
|
"bash",
|
|
305
596
|
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
@@ -319,7 +610,7 @@ Important rules:
|
|
|
319
610
|
const parsedTargets = parseCreationTargets(command, effectiveCwd);
|
|
320
611
|
const parsedTargetsBefore = /* @__PURE__ */ new Map();
|
|
321
612
|
for (const t of parsedTargets) {
|
|
322
|
-
parsedTargetsBefore.set(t,
|
|
613
|
+
parsedTargetsBefore.set(t, existsSync3(t));
|
|
323
614
|
}
|
|
324
615
|
try {
|
|
325
616
|
const { stdout, stderr, status, signal, timedOut, aborted } = await runShell(
|
|
@@ -499,7 +790,7 @@ Then poll readiness with SHORT bash calls (these DO exit), e.g.:
|
|
|
499
790
|
}
|
|
500
791
|
function snapshotDir(dir) {
|
|
501
792
|
try {
|
|
502
|
-
return new Set(
|
|
793
|
+
return new Set(readdirSync2(dir).map((name) => resolve2(dir, name)));
|
|
503
794
|
} catch {
|
|
504
795
|
return /* @__PURE__ */ new Set();
|
|
505
796
|
}
|
|
@@ -507,25 +798,25 @@ function snapshotDir(dir) {
|
|
|
507
798
|
function parseCreationTargets(command, cwd) {
|
|
508
799
|
const targets = [];
|
|
509
800
|
for (const m of command.matchAll(/(?:echo|cat|printf)\s+[^>]*>\s*(['"]?)([^\s;&|'"]+)\1/g)) {
|
|
510
|
-
if (m[2]) targets.push(
|
|
801
|
+
if (m[2]) targets.push(resolve2(cwd, m[2]));
|
|
511
802
|
}
|
|
512
803
|
for (const m of command.matchAll(/\btouch\s+((?:['"]?[^\s;&|'"]+['"]?\s*)+)/g)) {
|
|
513
804
|
for (const f of m[1].trim().split(/\s+/)) {
|
|
514
805
|
const clean = f.replace(/^['"]|['"]$/g, "");
|
|
515
|
-
if (clean && !clean.startsWith("-")) targets.push(
|
|
806
|
+
if (clean && !clean.startsWith("-")) targets.push(resolve2(cwd, clean));
|
|
516
807
|
}
|
|
517
808
|
}
|
|
518
809
|
for (const m of command.matchAll(/\bmkdir\s+(?:-\w+\s+)*((?:['"]?[^\s;&|'"]+['"]?\s*)+)/g)) {
|
|
519
810
|
for (const d of m[1].trim().split(/\s+/)) {
|
|
520
811
|
const clean = d.replace(/^['"]|['"]$/g, "");
|
|
521
|
-
if (clean && !clean.startsWith("-")) targets.push(
|
|
812
|
+
if (clean && !clean.startsWith("-")) targets.push(resolve2(cwd, clean));
|
|
522
813
|
}
|
|
523
814
|
}
|
|
524
815
|
for (const m of command.matchAll(/\bcp\s+(?:-\w+\s+)*['"]?[^\s;&|'"]+['"]?\s+(['"]?)([^\s;&|'"]+)\1/g)) {
|
|
525
|
-
if (m[2]) targets.push(
|
|
816
|
+
if (m[2]) targets.push(resolve2(cwd, m[2]));
|
|
526
817
|
}
|
|
527
818
|
for (const m of command.matchAll(/\bNew-Item\s+(?:-(?:Path|ItemType)\s+\w+\s+)*['"]?([^\s;&|'"]+)['"]?/gi)) {
|
|
528
|
-
if (m[1] && !m[1].startsWith("-")) targets.push(
|
|
819
|
+
if (m[1] && !m[1].startsWith("-")) targets.push(resolve2(cwd, m[1]));
|
|
529
820
|
}
|
|
530
821
|
return [...new Set(targets)];
|
|
531
822
|
}
|
|
@@ -535,7 +826,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
535
826
|
for (const absPath of afterSnapshot) {
|
|
536
827
|
if (!beforeSnapshot.has(absPath)) {
|
|
537
828
|
try {
|
|
538
|
-
const st =
|
|
829
|
+
const st = statSync2(absPath);
|
|
539
830
|
if (st.isDirectory()) {
|
|
540
831
|
undoStack.pushNewDir(absPath, `bash (new dir): ${absPath}`);
|
|
541
832
|
} else {
|
|
@@ -547,9 +838,9 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
547
838
|
}
|
|
548
839
|
}
|
|
549
840
|
for (const [target, existedBefore] of parsedTargetsBefore) {
|
|
550
|
-
if (!existedBefore && !tracked.has(target) &&
|
|
841
|
+
if (!existedBefore && !tracked.has(target) && existsSync3(target)) {
|
|
551
842
|
try {
|
|
552
|
-
const st =
|
|
843
|
+
const st = statSync2(target);
|
|
553
844
|
if (st.isDirectory()) {
|
|
554
845
|
undoStack.pushNewDir(target, `bash (new dir): ${target}`);
|
|
555
846
|
} else {
|
|
@@ -659,8 +950,8 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
659
950
|
const target = lastMatch?.[2];
|
|
660
951
|
if (!target || target.startsWith("$") || target === "~") return;
|
|
661
952
|
try {
|
|
662
|
-
const newDir =
|
|
663
|
-
if (
|
|
953
|
+
const newDir = resolve2(baseCwd, target);
|
|
954
|
+
if (existsSync3(newDir)) {
|
|
664
955
|
setCwd(newDir);
|
|
665
956
|
}
|
|
666
957
|
} catch {
|
|
@@ -668,9 +959,9 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
668
959
|
}
|
|
669
960
|
|
|
670
961
|
// src/tools/builtin/read-file.ts
|
|
671
|
-
import { readFileSync as
|
|
962
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
672
963
|
import { execFileSync } from "child_process";
|
|
673
|
-
import { extname, resolve as
|
|
964
|
+
import { extname, resolve as resolve3, basename as basename2, sep, dirname as dirname2 } from "path";
|
|
674
965
|
import { homedir } from "os";
|
|
675
966
|
|
|
676
967
|
// src/tools/builtin/notebook-utils.ts
|
|
@@ -780,7 +1071,7 @@ var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
|
780
1071
|
function getHardRefusal(normalizedPath) {
|
|
781
1072
|
const home2 = homedir();
|
|
782
1073
|
const p = normalizedPath.toLowerCase();
|
|
783
|
-
const base =
|
|
1074
|
+
const base = basename2(normalizedPath).toLowerCase();
|
|
784
1075
|
if (normalizedPath.startsWith(home2) && p.includes(".aicli") && base === "config.json") {
|
|
785
1076
|
return `[Refused] Reading ${normalizedPath} is blocked because it contains all of your provider API keys.
|
|
786
1077
|
If you genuinely need to inspect or edit it, open it manually outside ai-cli (e.g. via your editor).
|
|
@@ -794,7 +1085,7 @@ For programmatic access, use \`aicli config\` or \`aicli config get <key>\` whic
|
|
|
794
1085
|
function getSensitiveWarning(normalizedPath) {
|
|
795
1086
|
const home2 = homedir();
|
|
796
1087
|
const p = normalizedPath.toLowerCase();
|
|
797
|
-
const base =
|
|
1088
|
+
const base = basename2(normalizedPath).toLowerCase();
|
|
798
1089
|
if (normalizedPath.startsWith(home2) && p.includes(".aicli") && base === "config.json") {
|
|
799
1090
|
return "[\u26A0 Security Warning: This file contains API keys. Be careful not to share this content.]\n\n";
|
|
800
1091
|
}
|
|
@@ -857,19 +1148,19 @@ function isBinaryBuffer(buf) {
|
|
|
857
1148
|
return nullCount / sample.length > 0.1;
|
|
858
1149
|
}
|
|
859
1150
|
function findSimilarFiles(filePath) {
|
|
860
|
-
const targetName =
|
|
1151
|
+
const targetName = basename2(filePath).toLowerCase();
|
|
861
1152
|
if (!targetName) return [];
|
|
862
1153
|
const cwd = process.cwd();
|
|
863
1154
|
const candidates = [];
|
|
864
1155
|
try {
|
|
865
|
-
for (const entry of
|
|
1156
|
+
for (const entry of readdirSync3(cwd, { withFileTypes: true })) {
|
|
866
1157
|
if (entry.isFile() && entry.name.toLowerCase() === targetName) {
|
|
867
1158
|
candidates.push(entry.name);
|
|
868
1159
|
}
|
|
869
1160
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
870
1161
|
try {
|
|
871
|
-
const subDir =
|
|
872
|
-
for (const sub of
|
|
1162
|
+
const subDir = resolve3(cwd, entry.name);
|
|
1163
|
+
for (const sub of readdirSync3(subDir, { withFileTypes: true })) {
|
|
873
1164
|
if (sub.isFile() && sub.name.toLowerCase() === targetName) {
|
|
874
1165
|
candidates.push(`${entry.name}/${sub.name}`);
|
|
875
1166
|
}
|
|
@@ -942,10 +1233,10 @@ var readFileTool = {
|
|
|
942
1233
|
const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
|
|
943
1234
|
const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
|
|
944
1235
|
if (!filePath) throw new ToolError("read_file", "path is required");
|
|
945
|
-
const normalizedPath =
|
|
1236
|
+
const normalizedPath = resolve3(filePath);
|
|
946
1237
|
const refusal = getHardRefusal(normalizedPath);
|
|
947
1238
|
if (refusal) return refusal;
|
|
948
|
-
if (!
|
|
1239
|
+
if (!existsSync4(normalizedPath)) {
|
|
949
1240
|
const suggestions = findSimilarFiles(filePath);
|
|
950
1241
|
if (suggestions.length > 0) {
|
|
951
1242
|
throw new ToolError(
|
|
@@ -964,7 +1255,7 @@ Current working directory: ${process.cwd()}
|
|
|
964
1255
|
Please use list_dir to verify the file path and retry.`
|
|
965
1256
|
);
|
|
966
1257
|
}
|
|
967
|
-
const { size } =
|
|
1258
|
+
const { size } = statSync3(normalizedPath);
|
|
968
1259
|
if (size > MAX_FILE_BYTES) {
|
|
969
1260
|
const mb = (size / 1024 / 1024).toFixed(1);
|
|
970
1261
|
return `[File too large: ${filePath} (${mb} MB)]
|
|
@@ -984,13 +1275,13 @@ Use the bash tool to read in segments, e.g.:
|
|
|
984
1275
|
|
|
985
1276
|
${pdfText}`;
|
|
986
1277
|
}
|
|
987
|
-
const dir =
|
|
988
|
-
const nameNoExt =
|
|
989
|
-
const textAlts = [".md", ".txt", ".html"].map((e) =>
|
|
1278
|
+
const dir = dirname2(normalizedPath);
|
|
1279
|
+
const nameNoExt = basename2(normalizedPath, ext);
|
|
1280
|
+
const textAlts = [".md", ".txt", ".html"].map((e) => resolve3(dir, nameNoExt + e)).filter(existsSync4);
|
|
990
1281
|
if (textAlts.length > 0) {
|
|
991
1282
|
return `[PDF file: ${filePath}]
|
|
992
1283
|
Cannot extract text from this PDF, but found alternative text versions:
|
|
993
|
-
` + textAlts.map((p) => ` \u2192 ${
|
|
1284
|
+
` + textAlts.map((p) => ` \u2192 ${basename2(p)}`).join("\n") + `
|
|
994
1285
|
Please use read_file to read the above files.`;
|
|
995
1286
|
}
|
|
996
1287
|
return `[PDF file: ${filePath}]
|
|
@@ -999,7 +1290,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
999
1290
|
}
|
|
1000
1291
|
if (ext === ".ipynb") {
|
|
1001
1292
|
try {
|
|
1002
|
-
const raw =
|
|
1293
|
+
const raw = readFileSync3(normalizedPath, "utf-8");
|
|
1003
1294
|
const nb = parseNotebook(raw);
|
|
1004
1295
|
return renderNotebookAsText(nb, filePath);
|
|
1005
1296
|
} catch (err) {
|
|
@@ -1012,7 +1303,7 @@ ${err.message}`;
|
|
|
1012
1303
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
1013
1304
|
}
|
|
1014
1305
|
const { readFile: readFile2 } = await import("fs/promises");
|
|
1015
|
-
const buf = size > 1048576 ? await readFile2(normalizedPath) :
|
|
1306
|
+
const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync3(normalizedPath);
|
|
1016
1307
|
if (encoding === "base64") {
|
|
1017
1308
|
return `[File: ${filePath} | base64]
|
|
1018
1309
|
|
|
@@ -1050,12 +1341,12 @@ ${content}`;
|
|
|
1050
1341
|
};
|
|
1051
1342
|
|
|
1052
1343
|
// src/tools/builtin/write-file.ts
|
|
1053
|
-
import { appendFileSync, mkdirSync as
|
|
1054
|
-
import { dirname as
|
|
1344
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
1345
|
+
import { dirname as dirname3 } from "path";
|
|
1055
1346
|
|
|
1056
1347
|
// src/tools/executor.ts
|
|
1057
1348
|
import chalk3 from "chalk";
|
|
1058
|
-
import { existsSync as
|
|
1349
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1059
1350
|
import { tmpdir } from "os";
|
|
1060
1351
|
|
|
1061
1352
|
// src/core/readline-internal.ts
|
|
@@ -1263,9 +1554,9 @@ function simpleDiff(oldLines, newLines) {
|
|
|
1263
1554
|
|
|
1264
1555
|
// src/tools/hooks.ts
|
|
1265
1556
|
import { execSync } from "child_process";
|
|
1266
|
-
import { createHash } from "crypto";
|
|
1267
|
-
import { existsSync as
|
|
1268
|
-
import { join } from "path";
|
|
1557
|
+
import { createHash as createHash2 } from "crypto";
|
|
1558
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
1559
|
+
import { join as join2 } from "path";
|
|
1269
1560
|
var TRUST_FILE = "hooks-trust.json";
|
|
1270
1561
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
1271
1562
|
function asArray(entry) {
|
|
@@ -1274,24 +1565,24 @@ function asArray(entry) {
|
|
|
1274
1565
|
return raw.map((item) => typeof item === "string" ? { command: item } : item);
|
|
1275
1566
|
}
|
|
1276
1567
|
function hookTrustPath(configDir) {
|
|
1277
|
-
return
|
|
1568
|
+
return join2(configDir, TRUST_FILE);
|
|
1278
1569
|
}
|
|
1279
1570
|
function loadHookTrustStore(configDir) {
|
|
1280
1571
|
const file = hookTrustPath(configDir);
|
|
1281
|
-
if (!
|
|
1572
|
+
if (!existsSync5(file)) return { records: [] };
|
|
1282
1573
|
try {
|
|
1283
|
-
const parsed = JSON.parse(
|
|
1574
|
+
const parsed = JSON.parse(readFileSync4(file, "utf-8"));
|
|
1284
1575
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
1285
1576
|
} catch {
|
|
1286
1577
|
return { records: [] };
|
|
1287
1578
|
}
|
|
1288
1579
|
}
|
|
1289
1580
|
function saveHookTrustStore(configDir, store) {
|
|
1290
|
-
|
|
1581
|
+
mkdirSync2(configDir, { recursive: true });
|
|
1291
1582
|
atomicWriteFileSync(hookTrustPath(configDir), JSON.stringify(store, null, 2));
|
|
1292
1583
|
}
|
|
1293
1584
|
function hashHook(event, command) {
|
|
1294
|
-
return
|
|
1585
|
+
return createHash2("sha256").update(`${event}
|
|
1295
1586
|
${command}`, "utf-8").digest("hex");
|
|
1296
1587
|
}
|
|
1297
1588
|
function isTrusted(source, event, hash, store) {
|
|
@@ -1440,7 +1731,7 @@ function runHook(template, vars) {
|
|
|
1440
1731
|
}
|
|
1441
1732
|
|
|
1442
1733
|
// src/tools/permissions.ts
|
|
1443
|
-
import { isAbsolute, resolve as
|
|
1734
|
+
import { isAbsolute, resolve as resolve4 } from "path";
|
|
1444
1735
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
1445
1736
|
for (const rule of rules) {
|
|
1446
1737
|
if (rule.tool !== "*" && rule.tool !== toolName) continue;
|
|
@@ -1486,8 +1777,8 @@ function normalizePathForPermission(value) {
|
|
|
1486
1777
|
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1487
1778
|
}
|
|
1488
1779
|
function isInsidePath(root, target) {
|
|
1489
|
-
const absoluteTarget = isAbsolute(target) ? target :
|
|
1490
|
-
const r = normalizePathForPermission(
|
|
1780
|
+
const absoluteTarget = isAbsolute(target) ? target : resolve4(root, target);
|
|
1781
|
+
const r = normalizePathForPermission(resolve4(root));
|
|
1491
1782
|
const t = normalizePathForPermission(absoluteTarget);
|
|
1492
1783
|
return t === r || t.startsWith(`${r}/`);
|
|
1493
1784
|
}
|
|
@@ -1861,8 +2152,8 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
1861
2152
|
});
|
|
1862
2153
|
|
|
1863
2154
|
// src/tools/action-classifier.ts
|
|
1864
|
-
import { existsSync as
|
|
1865
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2155
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2156
|
+
import { isAbsolute as isAbsolute2, join as join3, resolve as resolve5 } from "path";
|
|
1866
2157
|
var RECENT_DENIED_LIMIT = 50;
|
|
1867
2158
|
var recentlyDenied = [];
|
|
1868
2159
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -1885,8 +2176,8 @@ function normalizePath(value) {
|
|
|
1885
2176
|
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1886
2177
|
}
|
|
1887
2178
|
function isInside(root, target) {
|
|
1888
|
-
const absoluteRoot =
|
|
1889
|
-
const absoluteTarget = isAbsolute2(target) ? target :
|
|
2179
|
+
const absoluteRoot = resolve5(root);
|
|
2180
|
+
const absoluteTarget = isAbsolute2(target) ? target : resolve5(root, target);
|
|
1890
2181
|
const base = normalizePath(absoluteRoot);
|
|
1891
2182
|
const t = normalizePath(absoluteTarget);
|
|
1892
2183
|
return t === base || t.startsWith(base + "/");
|
|
@@ -1903,10 +2194,10 @@ function splitShellWords(command) {
|
|
|
1903
2194
|
return out.filter(Boolean);
|
|
1904
2195
|
}
|
|
1905
2196
|
function readPackageManifest(root) {
|
|
1906
|
-
const p =
|
|
1907
|
-
if (!
|
|
2197
|
+
const p = join3(root, "package.json");
|
|
2198
|
+
if (!existsSync6(p)) return null;
|
|
1908
2199
|
try {
|
|
1909
|
-
return JSON.parse(
|
|
2200
|
+
return JSON.parse(readFileSync5(p, "utf-8"));
|
|
1910
2201
|
} catch {
|
|
1911
2202
|
return null;
|
|
1912
2203
|
}
|
|
@@ -1922,10 +2213,10 @@ function manifestHasDependency(root, name) {
|
|
|
1922
2213
|
}
|
|
1923
2214
|
function lockfileMentions(root, name) {
|
|
1924
2215
|
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
1925
|
-
const p =
|
|
1926
|
-
if (!
|
|
2216
|
+
const p = join3(root, file);
|
|
2217
|
+
if (!existsSync6(p)) continue;
|
|
1927
2218
|
try {
|
|
1928
|
-
if (
|
|
2219
|
+
if (readFileSync5(p, "utf-8").includes(name)) return true;
|
|
1929
2220
|
} catch {
|
|
1930
2221
|
}
|
|
1931
2222
|
}
|
|
@@ -2412,7 +2703,7 @@ var ToolExecutor = class {
|
|
|
2412
2703
|
rl.resume();
|
|
2413
2704
|
process.stdout.write(prompt);
|
|
2414
2705
|
this.confirming = true;
|
|
2415
|
-
return new Promise((
|
|
2706
|
+
return new Promise((resolve9) => {
|
|
2416
2707
|
let completed = false;
|
|
2417
2708
|
const cleanup = (result) => {
|
|
2418
2709
|
if (completed) return;
|
|
@@ -2422,7 +2713,7 @@ var ToolExecutor = class {
|
|
|
2422
2713
|
rl.pause();
|
|
2423
2714
|
rlAny.output = savedOutput;
|
|
2424
2715
|
this.confirming = false;
|
|
2425
|
-
|
|
2716
|
+
resolve9(result);
|
|
2426
2717
|
};
|
|
2427
2718
|
const onLine = (line) => {
|
|
2428
2719
|
const trimmed = line.trim();
|
|
@@ -2489,10 +2780,10 @@ var ToolExecutor = class {
|
|
|
2489
2780
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2490
2781
|
const newContent = String(call.arguments["content"] ?? "");
|
|
2491
2782
|
if (!filePath) return;
|
|
2492
|
-
if (
|
|
2783
|
+
if (existsSync7(filePath)) {
|
|
2493
2784
|
let oldContent;
|
|
2494
2785
|
try {
|
|
2495
|
-
oldContent =
|
|
2786
|
+
oldContent = readFileSync6(filePath, "utf-8");
|
|
2496
2787
|
} catch {
|
|
2497
2788
|
return;
|
|
2498
2789
|
}
|
|
@@ -2515,7 +2806,7 @@ var ToolExecutor = class {
|
|
|
2515
2806
|
}
|
|
2516
2807
|
} else if (call.name === "edit_file") {
|
|
2517
2808
|
const filePath = String(call.arguments["path"] ?? "");
|
|
2518
|
-
if (!filePath || !
|
|
2809
|
+
if (!filePath || !existsSync7(filePath)) return;
|
|
2519
2810
|
const oldStr = call.arguments["old_str"];
|
|
2520
2811
|
const newStr = call.arguments["new_str"];
|
|
2521
2812
|
if (oldStr !== void 0) {
|
|
@@ -2542,7 +2833,7 @@ var ToolExecutor = class {
|
|
|
2542
2833
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
2543
2834
|
let fileContent;
|
|
2544
2835
|
try {
|
|
2545
|
-
fileContent =
|
|
2836
|
+
fileContent = readFileSync6(filePath, "utf-8");
|
|
2546
2837
|
} catch {
|
|
2547
2838
|
return;
|
|
2548
2839
|
}
|
|
@@ -2596,7 +2887,7 @@ var ToolExecutor = class {
|
|
|
2596
2887
|
rl.resume();
|
|
2597
2888
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
2598
2889
|
this.confirming = true;
|
|
2599
|
-
return new Promise((
|
|
2890
|
+
return new Promise((resolve9) => {
|
|
2600
2891
|
let completed = false;
|
|
2601
2892
|
const cleanup = (answer) => {
|
|
2602
2893
|
if (completed) return;
|
|
@@ -2606,7 +2897,7 @@ var ToolExecutor = class {
|
|
|
2606
2897
|
rl.pause();
|
|
2607
2898
|
rlAny.output = savedOutput;
|
|
2608
2899
|
this.confirming = false;
|
|
2609
|
-
|
|
2900
|
+
resolve9(answer === "y");
|
|
2610
2901
|
};
|
|
2611
2902
|
const onLine = (line) => {
|
|
2612
2903
|
const trimmed = line.trim();
|
|
@@ -2634,11 +2925,11 @@ var ToolExecutor = class {
|
|
|
2634
2925
|
};
|
|
2635
2926
|
|
|
2636
2927
|
// src/tools/sensitive-paths.ts
|
|
2637
|
-
import { resolve as
|
|
2928
|
+
import { resolve as resolve6, sep as sep2, basename as basename3 } from "path";
|
|
2638
2929
|
import { homedir as homedir2 } from "os";
|
|
2639
2930
|
var home = homedir2();
|
|
2640
2931
|
function norm(p) {
|
|
2641
|
-
const abs =
|
|
2932
|
+
const abs = resolve6(p);
|
|
2642
2933
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
2643
2934
|
}
|
|
2644
2935
|
function homeRel(p) {
|
|
@@ -2653,7 +2944,7 @@ function homeRel(p) {
|
|
|
2653
2944
|
function classifyWritePath(path3) {
|
|
2654
2945
|
if (!path3) return { sensitive: false };
|
|
2655
2946
|
const abs = norm(path3);
|
|
2656
|
-
const base =
|
|
2947
|
+
const base = basename3(abs);
|
|
2657
2948
|
const rel = homeRel(path3);
|
|
2658
2949
|
if (rel) {
|
|
2659
2950
|
const shellRc = /* @__PURE__ */ new Set([
|
|
@@ -2777,7 +3068,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2777
3068
|
}
|
|
2778
3069
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
2779
3070
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2780
|
-
|
|
3071
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
2781
3072
|
if (appendMode) {
|
|
2782
3073
|
appendFileSync(filePath, content, encoding);
|
|
2783
3074
|
} else {
|
|
@@ -2797,7 +3088,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2797
3088
|
};
|
|
2798
3089
|
|
|
2799
3090
|
// src/tools/builtin/edit-file.ts
|
|
2800
|
-
import { readFileSync as
|
|
3091
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
2801
3092
|
|
|
2802
3093
|
// src/tools/builtin/patch-apply.ts
|
|
2803
3094
|
function parseUnifiedDiff(patch) {
|
|
@@ -3158,7 +3449,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
3158
3449
|
const filePath = String(args["path"] ?? "");
|
|
3159
3450
|
const encoding = args["encoding"] ?? "utf-8";
|
|
3160
3451
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
3161
|
-
if (!
|
|
3452
|
+
if (!existsSync8(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
3162
3453
|
const verdict = classifyWritePath(filePath);
|
|
3163
3454
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
3164
3455
|
throw new ToolError(
|
|
@@ -3166,7 +3457,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
3166
3457
|
`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.`
|
|
3167
3458
|
);
|
|
3168
3459
|
}
|
|
3169
|
-
const original =
|
|
3460
|
+
const original = readFileSync7(filePath, encoding);
|
|
3170
3461
|
if (args["patch"] !== void 0) {
|
|
3171
3462
|
const patchText = String(args["patch"] ?? "");
|
|
3172
3463
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -3324,8 +3615,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
3324
3615
|
}
|
|
3325
3616
|
|
|
3326
3617
|
// src/tools/builtin/list-dir.ts
|
|
3327
|
-
import { readdirSync as
|
|
3328
|
-
import { join as
|
|
3618
|
+
import { readdirSync as readdirSync4, statSync as statSync4, existsSync as existsSync9 } from "fs";
|
|
3619
|
+
import { join as join4, basename as basename4 } from "path";
|
|
3329
3620
|
var listDirTool = {
|
|
3330
3621
|
definition: {
|
|
3331
3622
|
name: "list_dir",
|
|
@@ -3347,12 +3638,12 @@ var listDirTool = {
|
|
|
3347
3638
|
async execute(args) {
|
|
3348
3639
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
3349
3640
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
3350
|
-
if (!
|
|
3351
|
-
const targetName =
|
|
3641
|
+
if (!existsSync9(dirPath)) {
|
|
3642
|
+
const targetName = basename4(dirPath).toLowerCase();
|
|
3352
3643
|
const cwd = process.cwd();
|
|
3353
3644
|
const suggestions = [];
|
|
3354
3645
|
try {
|
|
3355
|
-
for (const entry of
|
|
3646
|
+
for (const entry of readdirSync4(cwd, { withFileTypes: true })) {
|
|
3356
3647
|
if (entry.isDirectory() && entry.name.toLowerCase() === targetName) {
|
|
3357
3648
|
suggestions.push(entry.name);
|
|
3358
3649
|
}
|
|
@@ -3385,7 +3676,7 @@ Please use list_dir (without path) to see the current directory structure first.
|
|
|
3385
3676
|
function listRecursive(basePath, indent, recursive, lines) {
|
|
3386
3677
|
let entries;
|
|
3387
3678
|
try {
|
|
3388
|
-
entries =
|
|
3679
|
+
entries = readdirSync4(basePath, { withFileTypes: true });
|
|
3389
3680
|
} catch {
|
|
3390
3681
|
lines.push(`${indent}(permission denied)`);
|
|
3391
3682
|
return;
|
|
@@ -3405,11 +3696,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
3405
3696
|
if (entry.isDirectory()) {
|
|
3406
3697
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
3407
3698
|
if (recursive) {
|
|
3408
|
-
listRecursive(
|
|
3699
|
+
listRecursive(join4(basePath, entry.name), indent + " ", true, lines);
|
|
3409
3700
|
}
|
|
3410
3701
|
} else {
|
|
3411
3702
|
try {
|
|
3412
|
-
const stat =
|
|
3703
|
+
const stat = statSync4(join4(basePath, entry.name));
|
|
3413
3704
|
const size = formatSize(stat.size);
|
|
3414
3705
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
3415
3706
|
} catch {
|
|
@@ -3425,9 +3716,9 @@ function formatSize(bytes) {
|
|
|
3425
3716
|
}
|
|
3426
3717
|
|
|
3427
3718
|
// src/tools/builtin/grep-files.ts
|
|
3428
|
-
import { readdirSync as
|
|
3719
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5, existsSync as existsSync10 } from "fs";
|
|
3429
3720
|
import { readFile } from "fs/promises";
|
|
3430
|
-
import { join as
|
|
3721
|
+
import { join as join5, relative } from "path";
|
|
3431
3722
|
var grepFilesTool = {
|
|
3432
3723
|
definition: {
|
|
3433
3724
|
name: "grep_files",
|
|
@@ -3478,7 +3769,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
3478
3769
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
3479
3770
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
3480
3771
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
3481
|
-
if (!
|
|
3772
|
+
if (!existsSync10(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
3482
3773
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
3483
3774
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
3484
3775
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -3491,7 +3782,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
3491
3782
|
regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
|
|
3492
3783
|
}
|
|
3493
3784
|
const results = [];
|
|
3494
|
-
const stat =
|
|
3785
|
+
const stat = statSync5(rootPath);
|
|
3495
3786
|
if (stat.isFile()) {
|
|
3496
3787
|
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
3497
3788
|
} else {
|
|
@@ -3564,7 +3855,7 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
3564
3855
|
if (paths.length >= maxFiles) return;
|
|
3565
3856
|
let entries;
|
|
3566
3857
|
try {
|
|
3567
|
-
entries =
|
|
3858
|
+
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
3568
3859
|
} catch {
|
|
3569
3860
|
return;
|
|
3570
3861
|
}
|
|
@@ -3572,13 +3863,13 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
3572
3863
|
if (paths.length >= maxFiles) return;
|
|
3573
3864
|
if (entry.isDirectory()) {
|
|
3574
3865
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3575
|
-
walk(
|
|
3866
|
+
walk(join5(dirPath, entry.name));
|
|
3576
3867
|
} else if (entry.isFile()) {
|
|
3577
3868
|
if (isBinary(entry.name)) continue;
|
|
3578
3869
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
3579
|
-
const fullPath =
|
|
3870
|
+
const fullPath = join5(dirPath, entry.name);
|
|
3580
3871
|
try {
|
|
3581
|
-
if (
|
|
3872
|
+
if (statSync5(fullPath).size > 1e6) continue;
|
|
3582
3873
|
} catch {
|
|
3583
3874
|
continue;
|
|
3584
3875
|
}
|
|
@@ -3624,14 +3915,14 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
|
|
|
3624
3915
|
}
|
|
3625
3916
|
function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
|
|
3626
3917
|
try {
|
|
3627
|
-
const stat =
|
|
3918
|
+
const stat = statSync5(fullPath);
|
|
3628
3919
|
if (stat.size > 1e6) return;
|
|
3629
3920
|
} catch {
|
|
3630
3921
|
return;
|
|
3631
3922
|
}
|
|
3632
3923
|
let content;
|
|
3633
3924
|
try {
|
|
3634
|
-
content =
|
|
3925
|
+
content = readFileSync8(fullPath, "utf-8");
|
|
3635
3926
|
} catch {
|
|
3636
3927
|
return;
|
|
3637
3928
|
}
|
|
@@ -3662,8 +3953,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
3662
3953
|
}
|
|
3663
3954
|
|
|
3664
3955
|
// src/tools/builtin/glob-files.ts
|
|
3665
|
-
import { readdirSync as
|
|
3666
|
-
import { join as
|
|
3956
|
+
import { readdirSync as readdirSync6, statSync as statSync6, existsSync as existsSync11 } from "fs";
|
|
3957
|
+
import { join as join6, relative as relative2, basename as basename5 } from "path";
|
|
3667
3958
|
var globFilesTool = {
|
|
3668
3959
|
definition: {
|
|
3669
3960
|
name: "glob_files",
|
|
@@ -3696,7 +3987,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
3696
3987
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
3697
3988
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
3698
3989
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
3699
|
-
if (!
|
|
3990
|
+
if (!existsSync11(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
3700
3991
|
const regex = globToRegex(pattern);
|
|
3701
3992
|
const matches = [];
|
|
3702
3993
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -3767,21 +4058,21 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
3767
4058
|
if (results.length >= maxResults) return;
|
|
3768
4059
|
let entries;
|
|
3769
4060
|
try {
|
|
3770
|
-
entries =
|
|
4061
|
+
entries = readdirSync6(dirPath, { withFileTypes: true });
|
|
3771
4062
|
} catch {
|
|
3772
4063
|
return;
|
|
3773
4064
|
}
|
|
3774
4065
|
for (const entry of entries) {
|
|
3775
4066
|
if (results.length >= maxResults) break;
|
|
3776
|
-
const fullPath =
|
|
4067
|
+
const fullPath = join6(dirPath, entry.name);
|
|
3777
4068
|
if (entry.isDirectory()) {
|
|
3778
4069
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3779
4070
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
3780
4071
|
} else if (entry.isFile()) {
|
|
3781
4072
|
const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
|
|
3782
|
-
if (regex.test(relPath) || regex.test(
|
|
4073
|
+
if (regex.test(relPath) || regex.test(basename5(relPath))) {
|
|
3783
4074
|
try {
|
|
3784
|
-
const stat =
|
|
4075
|
+
const stat = statSync6(fullPath);
|
|
3785
4076
|
results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
|
|
3786
4077
|
} catch {
|
|
3787
4078
|
results.push({ relPath, absPath: fullPath, mtime: 0 });
|
|
@@ -3855,7 +4146,7 @@ var runInteractiveTool = {
|
|
|
3855
4146
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
3856
4147
|
};
|
|
3857
4148
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
3858
|
-
return new Promise((
|
|
4149
|
+
return new Promise((resolve9) => {
|
|
3859
4150
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
3860
4151
|
cwd: process.cwd(),
|
|
3861
4152
|
env,
|
|
@@ -3888,22 +4179,22 @@ var runInteractiveTool = {
|
|
|
3888
4179
|
setTimeout(writeNextLine, 400);
|
|
3889
4180
|
const timer = setTimeout(() => {
|
|
3890
4181
|
child.kill();
|
|
3891
|
-
|
|
4182
|
+
resolve9(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
3892
4183
|
${buildOutput(stdout, stderr)}`);
|
|
3893
4184
|
}, timeout);
|
|
3894
4185
|
child.on("close", (code) => {
|
|
3895
4186
|
clearTimeout(timer);
|
|
3896
4187
|
const output = buildOutput(stdout, stderr);
|
|
3897
4188
|
if (code !== 0 && code !== null) {
|
|
3898
|
-
|
|
4189
|
+
resolve9(`${prefixWarnings}Exit code ${code}:
|
|
3899
4190
|
${output}`);
|
|
3900
4191
|
} else {
|
|
3901
|
-
|
|
4192
|
+
resolve9(`${prefixWarnings}${output || "(no output)"}`);
|
|
3902
4193
|
}
|
|
3903
4194
|
});
|
|
3904
4195
|
child.on("error", (err) => {
|
|
3905
4196
|
clearTimeout(timer);
|
|
3906
|
-
|
|
4197
|
+
resolve9(
|
|
3907
4198
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
3908
4199
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
3909
4200
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -4513,8 +4804,8 @@ ${preamble}`;
|
|
|
4513
4804
|
}
|
|
4514
4805
|
|
|
4515
4806
|
// src/tools/builtin/save-last-response.ts
|
|
4516
|
-
import { mkdirSync as
|
|
4517
|
-
import { dirname as
|
|
4807
|
+
import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
4808
|
+
import { dirname as dirname4 } from "path";
|
|
4518
4809
|
var lastResponseStore = { content: "" };
|
|
4519
4810
|
function cleanupRejectedTeeFile(filePath) {
|
|
4520
4811
|
try {
|
|
@@ -4522,7 +4813,7 @@ function cleanupRejectedTeeFile(filePath) {
|
|
|
4522
4813
|
} catch {
|
|
4523
4814
|
}
|
|
4524
4815
|
try {
|
|
4525
|
-
rmdirSync2(
|
|
4816
|
+
rmdirSync2(dirname4(filePath));
|
|
4526
4817
|
} catch {
|
|
4527
4818
|
}
|
|
4528
4819
|
}
|
|
@@ -4563,7 +4854,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4563
4854
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
4564
4855
|
}
|
|
4565
4856
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
4566
|
-
|
|
4857
|
+
mkdirSync4(dirname4(filePath), { recursive: true });
|
|
4567
4858
|
atomicWriteFileSync(filePath, content);
|
|
4568
4859
|
const lines = content.split("\n").length;
|
|
4569
4860
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -4571,26 +4862,236 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4571
4862
|
};
|
|
4572
4863
|
|
|
4573
4864
|
// src/tools/builtin/save-memory.ts
|
|
4574
|
-
import {
|
|
4575
|
-
import { join as join6 } from "path";
|
|
4865
|
+
import { join as join8 } from "path";
|
|
4576
4866
|
import { homedir as homedir3 } from "os";
|
|
4577
|
-
|
|
4578
|
-
|
|
4867
|
+
|
|
4868
|
+
// src/memory/persistent-memory.ts
|
|
4869
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
4870
|
+
import { randomUUID, createHash as createHash3 } from "crypto";
|
|
4871
|
+
import { dirname as dirname5, join as join7, resolve as resolve7 } from "path";
|
|
4872
|
+
function memoryStorePath(configDir) {
|
|
4873
|
+
return join7(configDir, MEMORY_STORE_FILE_NAME);
|
|
4579
4874
|
}
|
|
4580
|
-
function
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4875
|
+
function memoryMarkdownPath(configDir) {
|
|
4876
|
+
return join7(configDir, MEMORY_FILE_NAME);
|
|
4877
|
+
}
|
|
4878
|
+
function getProjectKey(cwd = process.cwd()) {
|
|
4879
|
+
return resolve7(getGitRoot(cwd) ?? cwd);
|
|
4880
|
+
}
|
|
4881
|
+
function nowIso() {
|
|
4882
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4883
|
+
}
|
|
4884
|
+
function isValidScope(value) {
|
|
4885
|
+
return value === "personal" || value === "project" || value === "session" || value === "team";
|
|
4886
|
+
}
|
|
4887
|
+
function isValidSensitivity(value) {
|
|
4888
|
+
return value === "low" || value === "medium" || value === "high";
|
|
4889
|
+
}
|
|
4890
|
+
function normalizeEntry(value) {
|
|
4891
|
+
if (!value || typeof value !== "object") return null;
|
|
4892
|
+
const raw = value;
|
|
4893
|
+
const content = typeof raw.content === "string" ? raw.content.trim() : "";
|
|
4894
|
+
if (!content) return null;
|
|
4895
|
+
const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
|
|
4896
|
+
return {
|
|
4897
|
+
id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
|
|
4898
|
+
content,
|
|
4899
|
+
scope: isValidScope(raw.scope) ? raw.scope : "personal",
|
|
4900
|
+
sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
|
|
4901
|
+
sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
|
|
4902
|
+
createdAt,
|
|
4903
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
|
|
4904
|
+
expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
|
|
4905
|
+
sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
|
|
4906
|
+
approved: raw.approved === true,
|
|
4907
|
+
redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
|
|
4908
|
+
};
|
|
4909
|
+
}
|
|
4910
|
+
function legacyEntriesFromMarkdown(configDir) {
|
|
4911
|
+
const file = memoryMarkdownPath(configDir);
|
|
4912
|
+
if (!existsSync12(file)) return [];
|
|
4913
|
+
const content = readFileSync9(file, "utf-8").trim();
|
|
4914
|
+
if (!content) return [];
|
|
4915
|
+
const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
|
|
4916
|
+
const chunks = parts.length > 1 ? parts : [content];
|
|
4917
|
+
return chunks.map((chunk, index) => {
|
|
4918
|
+
const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
|
|
4919
|
+
const body = (match ? match[2] : chunk).trim();
|
|
4920
|
+
const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
|
|
4921
|
+
const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
|
|
4922
|
+
const id = "legacy-" + createHash3("sha1").update(`${index}
|
|
4923
|
+
${chunk}`).digest("hex").slice(0, 12);
|
|
4924
|
+
return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
|
|
4925
|
+
}).filter((entry) => Boolean(entry));
|
|
4926
|
+
}
|
|
4927
|
+
function loadMemoryEntries(configDir) {
|
|
4928
|
+
const file = memoryStorePath(configDir);
|
|
4929
|
+
if (!existsSync12(file)) return legacyEntriesFromMarkdown(configDir);
|
|
4930
|
+
const text = readFileSync9(file, "utf-8").trim();
|
|
4931
|
+
if (!text) return [];
|
|
4932
|
+
const entries = [];
|
|
4933
|
+
for (const line of text.split("\n")) {
|
|
4934
|
+
const trimmed = line.trim();
|
|
4935
|
+
if (!trimmed) continue;
|
|
4936
|
+
try {
|
|
4937
|
+
const entry = normalizeEntry(JSON.parse(trimmed));
|
|
4938
|
+
if (entry) entries.push(entry);
|
|
4939
|
+
} catch {
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
return entries;
|
|
4943
|
+
}
|
|
4944
|
+
function saveMemoryEntries(configDir, entries) {
|
|
4945
|
+
mkdirSync5(configDir, { recursive: true });
|
|
4946
|
+
const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
4947
|
+
atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
|
|
4948
|
+
syncLegacyMarkdown(configDir, entries);
|
|
4949
|
+
}
|
|
4950
|
+
function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
|
|
4951
|
+
return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
|
|
4952
|
+
}
|
|
4953
|
+
function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
|
|
4954
|
+
if (entry.scope !== "project") return true;
|
|
4955
|
+
return entry.sourceProject === getProjectKey(cwd);
|
|
4956
|
+
}
|
|
4957
|
+
function listMemoryEntries(configDir, options = {}) {
|
|
4958
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4959
|
+
return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
|
|
4960
|
+
}
|
|
4961
|
+
function addMemoryEntry(configDir, content, options = {}) {
|
|
4962
|
+
const original = content.trim();
|
|
4963
|
+
if (!original) throw new Error("memory content is required");
|
|
4964
|
+
const redact = options.redact ?? true;
|
|
4965
|
+
const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
|
|
4966
|
+
const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
|
|
4967
|
+
const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
|
|
4968
|
+
const scope = options.scope ?? "personal";
|
|
4969
|
+
const timestamp = nowIso();
|
|
4970
|
+
const entry = {
|
|
4971
|
+
id: randomUUID(),
|
|
4972
|
+
content: redacted.redacted.trim(),
|
|
4973
|
+
scope,
|
|
4974
|
+
sourceSession: options.sourceSession,
|
|
4975
|
+
sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
|
|
4976
|
+
createdAt: timestamp,
|
|
4977
|
+
updatedAt: timestamp,
|
|
4978
|
+
expiresAt: options.expiresAt,
|
|
4979
|
+
sensitivity,
|
|
4980
|
+
approved,
|
|
4981
|
+
redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
|
|
4982
|
+
};
|
|
4983
|
+
const entries = loadMemoryEntries(configDir);
|
|
4984
|
+
entries.push(entry);
|
|
4985
|
+
saveMemoryEntries(configDir, entries);
|
|
4986
|
+
return entry;
|
|
4987
|
+
}
|
|
4988
|
+
function updateMemoryApproval(configDir, idPrefix, approved) {
|
|
4989
|
+
const entries = loadMemoryEntries(configDir);
|
|
4990
|
+
const entry = findUnique(entries, idPrefix);
|
|
4991
|
+
entry.approved = approved;
|
|
4992
|
+
entry.updatedAt = nowIso();
|
|
4993
|
+
saveMemoryEntries(configDir, entries);
|
|
4994
|
+
return entry;
|
|
4995
|
+
}
|
|
4996
|
+
function deleteMemoryEntry(configDir, idPrefix) {
|
|
4997
|
+
const entries = loadMemoryEntries(configDir);
|
|
4998
|
+
const entry = findUnique(entries, idPrefix);
|
|
4999
|
+
saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
|
|
5000
|
+
return entry;
|
|
5001
|
+
}
|
|
5002
|
+
function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
|
|
5003
|
+
const entries = loadMemoryEntries(configDir);
|
|
5004
|
+
const entry = findUnique(entries, idPrefix);
|
|
5005
|
+
entry.expiresAt = expiresAt;
|
|
5006
|
+
entry.updatedAt = nowIso();
|
|
5007
|
+
saveMemoryEntries(configDir, entries);
|
|
5008
|
+
return entry;
|
|
5009
|
+
}
|
|
5010
|
+
function exportMemoryEntries(configDir, cwd = process.cwd()) {
|
|
5011
|
+
return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
|
|
5012
|
+
}
|
|
5013
|
+
function findUnique(entries, idPrefix) {
|
|
5014
|
+
const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
|
|
5015
|
+
if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
|
|
5016
|
+
if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
|
|
5017
|
+
return matches[0];
|
|
5018
|
+
}
|
|
5019
|
+
function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
|
|
5020
|
+
mkdirSync5(dirname5(memoryMarkdownPath(configDir)), { recursive: true });
|
|
5021
|
+
const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
5022
|
+
const markdown = active.map((entry) => {
|
|
5023
|
+
const date = entry.createdAt.replace("T", " ").slice(0, 19);
|
|
5024
|
+
const source = [
|
|
5025
|
+
`id:${entry.id.slice(0, 8)}`,
|
|
5026
|
+
`scope:${entry.scope}`,
|
|
5027
|
+
entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
|
|
5028
|
+
entry.sourceProject ? `project:${entry.sourceProject}` : "",
|
|
5029
|
+
`sensitivity:${entry.sensitivity}`
|
|
5030
|
+
].filter(Boolean).join(" \xB7 ");
|
|
5031
|
+
return `## ${date}
|
|
5032
|
+
<!-- ${source} -->
|
|
5033
|
+
${entry.content}
|
|
5034
|
+
`;
|
|
5035
|
+
}).join("\n");
|
|
5036
|
+
atomicWriteFileSync(memoryMarkdownPath(configDir), markdown);
|
|
5037
|
+
}
|
|
5038
|
+
function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
|
|
5039
|
+
const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
5040
|
+
if (entries.length === 0) return null;
|
|
5041
|
+
let content = entries.map((entry) => {
|
|
5042
|
+
const source = [
|
|
5043
|
+
`id ${entry.id.slice(0, 8)}`,
|
|
5044
|
+
`scope ${entry.scope}`,
|
|
5045
|
+
entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
|
|
5046
|
+
entry.sourceProject ? `project ${entry.sourceProject}` : "",
|
|
5047
|
+
`sensitivity ${entry.sensitivity}`
|
|
5048
|
+
].filter(Boolean).join(" \xB7 ");
|
|
5049
|
+
return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
|
|
5050
|
+
Source: ${source}
|
|
5051
|
+
${entry.content}`;
|
|
5052
|
+
}).join("\n\n");
|
|
5053
|
+
if (content.length > MEMORY_MAX_CHARS) {
|
|
5054
|
+
content = content.slice(-MEMORY_MAX_CHARS);
|
|
5055
|
+
const firstEntry = content.indexOf("\n## ");
|
|
5056
|
+
if (firstEntry !== -1) content = content.slice(firstEntry + 1);
|
|
5057
|
+
}
|
|
5058
|
+
return { content, entryCount: entries.length };
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5061
|
+
// src/tools/builtin/save-memory.ts
|
|
5062
|
+
function getConfigDir() {
|
|
5063
|
+
return join8(homedir3(), CONFIG_DIR_NAME);
|
|
5064
|
+
}
|
|
5065
|
+
function parseScope(value) {
|
|
5066
|
+
return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
|
|
5067
|
+
}
|
|
5068
|
+
function parseSensitivity(value) {
|
|
5069
|
+
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
4584
5070
|
}
|
|
4585
5071
|
var saveMemoryTool = {
|
|
4586
5072
|
definition: {
|
|
4587
5073
|
name: "save_memory",
|
|
4588
|
-
description: "Save important information to persistent memory
|
|
5074
|
+
description: "Save important information to governed persistent memory. Low-risk memories are approved automatically; sensitive entries are stored as pending and require /memory approve <id>. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
|
|
4589
5075
|
parameters: {
|
|
4590
5076
|
content: {
|
|
4591
5077
|
type: "string",
|
|
4592
5078
|
description: "The information to save to persistent memory. Keep it concise and actionable.",
|
|
4593
5079
|
required: true
|
|
5080
|
+
},
|
|
5081
|
+
scope: {
|
|
5082
|
+
type: "string",
|
|
5083
|
+
description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
|
|
5084
|
+
required: false
|
|
5085
|
+
},
|
|
5086
|
+
sensitivity: {
|
|
5087
|
+
type: "string",
|
|
5088
|
+
description: "Sensitivity: low, medium, or high. Low-risk entries may be auto-approved; medium/high remain pending unless explicitly approved.",
|
|
5089
|
+
required: false
|
|
5090
|
+
},
|
|
5091
|
+
expiresAt: {
|
|
5092
|
+
type: "string",
|
|
5093
|
+
description: "Optional ISO timestamp after which this memory should no longer be injected.",
|
|
5094
|
+
required: false
|
|
4594
5095
|
}
|
|
4595
5096
|
},
|
|
4596
5097
|
dangerous: false
|
|
@@ -4598,20 +5099,16 @@ var saveMemoryTool = {
|
|
|
4598
5099
|
async execute(args) {
|
|
4599
5100
|
const content = String(args["content"] ?? "").trim();
|
|
4600
5101
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
4601
|
-
const
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
${
|
|
4610
|
-
`;
|
|
4611
|
-
const previous = existsSync11(memoryPath) ? readFileSync8(memoryPath, "utf-8") : "";
|
|
4612
|
-
atomicWriteFileSync(memoryPath, previous + entry);
|
|
4613
|
-
const byteSize = statSync6(memoryPath).size;
|
|
4614
|
-
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
5102
|
+
const entry = addMemoryEntry(getConfigDir(), content, {
|
|
5103
|
+
scope: parseScope(args["scope"]) ?? "personal",
|
|
5104
|
+
sensitivity: parseSensitivity(args["sensitivity"]),
|
|
5105
|
+
expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
|
|
5106
|
+
cwd: process.cwd(),
|
|
5107
|
+
redact: true
|
|
5108
|
+
});
|
|
5109
|
+
const status = entry.approved ? "approved and active" : "pending approval";
|
|
5110
|
+
const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
|
|
5111
|
+
return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
|
|
4615
5112
|
}
|
|
4616
5113
|
};
|
|
4617
5114
|
|
|
@@ -4655,7 +5152,7 @@ function promptUser(rl, question) {
|
|
|
4655
5152
|
console.log();
|
|
4656
5153
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
4657
5154
|
process.stdout.write(chalk4.cyan("> "));
|
|
4658
|
-
return new Promise((
|
|
5155
|
+
return new Promise((resolve9) => {
|
|
4659
5156
|
let completed = false;
|
|
4660
5157
|
const cleanup = (answer) => {
|
|
4661
5158
|
if (completed) return;
|
|
@@ -4665,7 +5162,7 @@ function promptUser(rl, question) {
|
|
|
4665
5162
|
rl.pause();
|
|
4666
5163
|
rlAny.output = savedOutput;
|
|
4667
5164
|
askUserContext.prompting = false;
|
|
4668
|
-
|
|
5165
|
+
resolve9(answer);
|
|
4669
5166
|
};
|
|
4670
5167
|
const onLine = (line) => {
|
|
4671
5168
|
cleanup(line);
|
|
@@ -4897,8 +5394,8 @@ function formatResults2(query, data, _requested) {
|
|
|
4897
5394
|
}
|
|
4898
5395
|
|
|
4899
5396
|
// src/agents/agent-config.ts
|
|
4900
|
-
import { existsSync as
|
|
4901
|
-
import { join as
|
|
5397
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
|
|
5398
|
+
import { join as join9 } from "path";
|
|
4902
5399
|
import { homedir as homedir4 } from "os";
|
|
4903
5400
|
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
4904
5401
|
"read_file",
|
|
@@ -4993,13 +5490,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
4993
5490
|
return cfg;
|
|
4994
5491
|
}
|
|
4995
5492
|
function loadAgentDir(dir, source) {
|
|
4996
|
-
if (!
|
|
5493
|
+
if (!existsSync13(dir)) return [];
|
|
4997
5494
|
const out = [];
|
|
4998
|
-
for (const file of
|
|
5495
|
+
for (const file of readdirSync7(dir)) {
|
|
4999
5496
|
if (!file.endsWith(".json")) continue;
|
|
5000
|
-
const path3 =
|
|
5497
|
+
const path3 = join9(dir, file);
|
|
5001
5498
|
try {
|
|
5002
|
-
const parsed = JSON.parse(
|
|
5499
|
+
const parsed = JSON.parse(readFileSync10(path3, "utf-8"));
|
|
5003
5500
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
5004
5501
|
if (cfg) out.push(cfg);
|
|
5005
5502
|
} catch {
|
|
@@ -5009,8 +5506,8 @@ function loadAgentDir(dir, source) {
|
|
|
5009
5506
|
}
|
|
5010
5507
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
5011
5508
|
return {
|
|
5012
|
-
user:
|
|
5013
|
-
project:
|
|
5509
|
+
user: join9(configDir ?? join9(homedir4(), CONFIG_DIR_NAME), "agents"),
|
|
5510
|
+
project: join9(cwd, CONFIG_DIR_NAME, "agents")
|
|
5014
5511
|
};
|
|
5015
5512
|
}
|
|
5016
5513
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -5018,6 +5515,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
|
5018
5515
|
const byName = /* @__PURE__ */ new Map();
|
|
5019
5516
|
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
5020
5517
|
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
5518
|
+
if (configDir) {
|
|
5519
|
+
for (const dir of getActivePluginAssets(configDir).agentDirs) {
|
|
5520
|
+
for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5021
5523
|
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
5022
5524
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
5023
5525
|
}
|
|
@@ -5052,7 +5554,7 @@ var nextId = 1;
|
|
|
5052
5554
|
var preferredAgentName = "worker";
|
|
5053
5555
|
var runs = [];
|
|
5054
5556
|
var MAX_RUNS = 50;
|
|
5055
|
-
function
|
|
5557
|
+
function nowIso2() {
|
|
5056
5558
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5057
5559
|
}
|
|
5058
5560
|
function startAgentRun(input) {
|
|
@@ -5061,7 +5563,7 @@ function startAgentRun(input) {
|
|
|
5061
5563
|
agentName: input.agentName,
|
|
5062
5564
|
task: input.task,
|
|
5063
5565
|
status: "running",
|
|
5064
|
-
startedAt:
|
|
5566
|
+
startedAt: nowIso2(),
|
|
5065
5567
|
toolNames: [],
|
|
5066
5568
|
agentIndex: input.agentIndex,
|
|
5067
5569
|
stopRequested: false
|
|
@@ -5079,7 +5581,7 @@ function finishAgentRun(runId, status, summary, error) {
|
|
|
5079
5581
|
const run = runs.find((r) => r.id === runId);
|
|
5080
5582
|
if (!run) return;
|
|
5081
5583
|
run.status = status;
|
|
5082
|
-
run.finishedAt =
|
|
5584
|
+
run.finishedAt = nowIso2();
|
|
5083
5585
|
if (summary) run.summary = summary;
|
|
5084
5586
|
if (error) run.error = error;
|
|
5085
5587
|
}
|
|
@@ -5505,7 +6007,7 @@ var spawnAgentTool = {
|
|
|
5505
6007
|
|
|
5506
6008
|
// src/tools/builtin/task-manager.ts
|
|
5507
6009
|
import { spawn as spawn3 } from "child_process";
|
|
5508
|
-
import { randomUUID } from "crypto";
|
|
6010
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
5509
6011
|
import { platform as platform4 } from "os";
|
|
5510
6012
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
5511
6013
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -5538,7 +6040,7 @@ function createTask(command, description) {
|
|
|
5538
6040
|
}
|
|
5539
6041
|
}
|
|
5540
6042
|
}
|
|
5541
|
-
const id =
|
|
6043
|
+
const id = randomUUID2().slice(0, 8);
|
|
5542
6044
|
const { shell, args } = shellInvocation(command);
|
|
5543
6045
|
const proc = spawn3(shell, args, {
|
|
5544
6046
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5720,14 +6222,14 @@ var taskStopTool = {
|
|
|
5720
6222
|
|
|
5721
6223
|
// src/tools/builtin/git-tools.ts
|
|
5722
6224
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5723
|
-
import { existsSync as
|
|
5724
|
-
import { join as
|
|
6225
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6226
|
+
import { join as join10 } from "path";
|
|
5725
6227
|
function assertGitRepo(cwd) {
|
|
5726
6228
|
let dir = cwd;
|
|
5727
6229
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
5728
6230
|
while (dir && dir !== root) {
|
|
5729
|
-
if (
|
|
5730
|
-
const parent =
|
|
6231
|
+
if (existsSync14(join10(dir, ".git"))) return;
|
|
6232
|
+
const parent = join10(dir, "..");
|
|
5731
6233
|
if (parent === dir) break;
|
|
5732
6234
|
dir = parent;
|
|
5733
6235
|
}
|
|
@@ -5992,9 +6494,9 @@ ${commitOutput.trim()}`;
|
|
|
5992
6494
|
};
|
|
5993
6495
|
|
|
5994
6496
|
// src/tools/builtin/notebook-edit.ts
|
|
5995
|
-
import { readFileSync as
|
|
6497
|
+
import { readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
|
|
5996
6498
|
import { writeFile } from "fs/promises";
|
|
5997
|
-
import { resolve as
|
|
6499
|
+
import { resolve as resolve8, extname as extname2 } from "path";
|
|
5998
6500
|
var notebookEditTool = {
|
|
5999
6501
|
definition: {
|
|
6000
6502
|
name: "notebook_edit",
|
|
@@ -6043,14 +6545,14 @@ var notebookEditTool = {
|
|
|
6043
6545
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
6044
6546
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
6045
6547
|
}
|
|
6046
|
-
const absPath =
|
|
6548
|
+
const absPath = resolve8(filePath);
|
|
6047
6549
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
6048
6550
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
6049
6551
|
}
|
|
6050
|
-
if (!
|
|
6552
|
+
if (!existsSync15(absPath)) {
|
|
6051
6553
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
6052
6554
|
}
|
|
6053
|
-
const raw =
|
|
6555
|
+
const raw = readFileSync11(absPath, "utf-8");
|
|
6054
6556
|
const nb = parseNotebook(raw);
|
|
6055
6557
|
const cellIdx0 = cellIndexRaw - 1;
|
|
6056
6558
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -6467,8 +6969,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
6467
6969
|
|
|
6468
6970
|
// src/tools/registry.ts
|
|
6469
6971
|
import { pathToFileURL } from "url";
|
|
6470
|
-
import { existsSync as
|
|
6471
|
-
import { join as
|
|
6972
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readdirSync as readdirSync8 } from "fs";
|
|
6973
|
+
import { join as join11 } from "path";
|
|
6472
6974
|
var ToolRegistry = class {
|
|
6473
6975
|
tools = /* @__PURE__ */ new Map();
|
|
6474
6976
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -6629,16 +7131,16 @@ var ToolRegistry = class {
|
|
|
6629
7131
|
* Returns the number of successfully loaded plugins.
|
|
6630
7132
|
*/
|
|
6631
7133
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
6632
|
-
if (!
|
|
7134
|
+
if (!existsSync16(pluginsDir)) {
|
|
6633
7135
|
try {
|
|
6634
|
-
|
|
7136
|
+
mkdirSync6(pluginsDir, { recursive: true });
|
|
6635
7137
|
} catch {
|
|
6636
7138
|
}
|
|
6637
7139
|
return 0;
|
|
6638
7140
|
}
|
|
6639
7141
|
let files;
|
|
6640
7142
|
try {
|
|
6641
|
-
files =
|
|
7143
|
+
files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
6642
7144
|
} catch {
|
|
6643
7145
|
return 0;
|
|
6644
7146
|
}
|
|
@@ -6655,12 +7157,12 @@ var ToolRegistry = class {
|
|
|
6655
7157
|
process.stderr.write(
|
|
6656
7158
|
`
|
|
6657
7159
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
6658
|
-
` + files.map((f) => ` + ${
|
|
7160
|
+
` + files.map((f) => ` + ${join11(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
6659
7161
|
);
|
|
6660
7162
|
let loaded = 0;
|
|
6661
7163
|
for (const file of files) {
|
|
6662
7164
|
try {
|
|
6663
|
-
const fileUrl = pathToFileURL(
|
|
7165
|
+
const fileUrl = pathToFileURL(join11(pluginsDir, file)).href;
|
|
6664
7166
|
const mod = await import(fileUrl);
|
|
6665
7167
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
6666
7168
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -6702,6 +7204,25 @@ export {
|
|
|
6702
7204
|
getRecentlyDeniedAutoActions,
|
|
6703
7205
|
clearRecentlyDeniedAutoActions,
|
|
6704
7206
|
defaultActionClassifier,
|
|
7207
|
+
pluginRoot,
|
|
7208
|
+
installPlugin,
|
|
7209
|
+
setPluginEnabled,
|
|
7210
|
+
trustPlugin,
|
|
7211
|
+
getInstalledPlugin,
|
|
7212
|
+
listInstalledPlugins,
|
|
7213
|
+
mergePluginHooks,
|
|
7214
|
+
getActivePluginAssets,
|
|
7215
|
+
describePlugin,
|
|
7216
|
+
memoryStorePath,
|
|
7217
|
+
memoryMarkdownPath,
|
|
7218
|
+
isMemoryExpired,
|
|
7219
|
+
listMemoryEntries,
|
|
7220
|
+
addMemoryEntry,
|
|
7221
|
+
updateMemoryApproval,
|
|
7222
|
+
deleteMemoryEntry,
|
|
7223
|
+
expireMemoryEntry,
|
|
7224
|
+
exportMemoryEntries,
|
|
7225
|
+
formatMemoryForPrompt,
|
|
6705
7226
|
isInterrupted,
|
|
6706
7227
|
requestInterrupt,
|
|
6707
7228
|
resetInterrupt,
|