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.
Files changed (31) hide show
  1. package/README.md +6 -3
  2. package/dist/{batch-C6HCXZIG.js → batch-7V7OTMUP.js} +2 -2
  3. package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
  4. package/dist/{chunk-TJ6GGN4B.js → chunk-5CA2TJ5F.js} +1 -1
  5. package/dist/{chunk-OLN7VUZA.js → chunk-C2Z42DI5.js} +3 -1
  6. package/dist/{chunk-TCOC4AUI.js → chunk-GX3HSGJX.js} +690 -169
  7. package/dist/{chunk-PNTLA3MN.js → chunk-H2UIHGHH.js} +23 -22
  8. package/dist/{chunk-GY3C3C2Y.js → chunk-L4UREAID.js} +3 -3
  9. package/dist/{chunk-77HDCGTN.js → chunk-MWKE2TNS.js} +1 -1
  10. package/dist/{chunk-7YWS3JLU.js → chunk-NTCB7CMT.js} +3 -1
  11. package/dist/{chunk-6HLKCSB3.js → chunk-OUC75QCF.js} +1 -1
  12. package/dist/{chunk-WAI3WPV2.js → chunk-P4VBLXKS.js} +1 -1
  13. package/dist/{chunk-VW7Y27WW.js → chunk-VGFTM3XT.js} +1 -1
  14. package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
  15. package/dist/{ci-6ZTFO3LX.js → ci-L6GH2WVC.js} +4 -4
  16. package/dist/{ci-format-CLZ6QJRL.js → ci-format-WW7454AY.js} +2 -2
  17. package/dist/{constants-APSORFOH.js → constants-NCTFSHDU.js} +3 -1
  18. package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-EWMFBP5Q.js} +4 -4
  19. package/dist/electron-server.js +965 -377
  20. package/dist/{hub-ZGHQWNWE.js → hub-CDL6T7CP.js} +1 -1
  21. package/dist/index.js +259 -97
  22. package/dist/{pr-JOL3IAGV.js → pr-D6PEKEGK.js} +4 -4
  23. package/dist/{run-tests-4PKSIVK5.js → run-tests-NXVVKAK2.js} +1 -1
  24. package/dist/{run-tests-ZRK4TQUN.js → run-tests-SWU2XEV7.js} +2 -2
  25. package/dist/{server-FXUF5P64.js → server-LHYSS6CK.js} +207 -100
  26. package/dist/{server-O3XHT56X.js → server-WUT7VYTD.js} +5 -4
  27. package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-C5AA2BI5.js} +5 -4
  28. package/dist/{usage-X7MJX4YD.js → usage-6ZUUJBI2.js} +2 -2
  29. package/dist/web/client/app.js +53 -2
  30. package/dist/web/client/index.html +11 -1
  31. 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-VW7Y27WW.js";
8
+ } from "./chunk-VGFTM3XT.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-77HDCGTN.js";
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-7YWS3JLU.js";
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 existsSync2, readdirSync, statSync } from "fs";
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 (existsSync(filePath)) {
356
+ if (existsSync2(filePath)) {
66
357
  try {
67
- previousContent = readFileSync(filePath, "utf-8");
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 (existsSync(entry.filePath)) {
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 (existsSync(entry.filePath)) {
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 (!existsSync2(currentCwd)) {
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 = resolve(currentCwd, cwdArg);
302
- if (!existsSync2(resolved)) {
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, existsSync2(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(readdirSync(dir).map((name) => resolve(dir, name)));
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(resolve(cwd, m[2]));
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(resolve(cwd, clean));
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(resolve(cwd, clean));
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(resolve(cwd, m[2]));
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(resolve(cwd, m[1]));
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 = statSync(absPath);
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) && existsSync2(target)) {
841
+ if (!existedBefore && !tracked.has(target) && existsSync3(target)) {
551
842
  try {
552
- const st = statSync(target);
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 = resolve(baseCwd, target);
663
- if (existsSync2(newDir)) {
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 readFileSync2, existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
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 resolve2, basename, sep, dirname } from "path";
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 = basename(normalizedPath).toLowerCase();
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 = basename(normalizedPath).toLowerCase();
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 = basename(filePath).toLowerCase();
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 readdirSync2(cwd, { withFileTypes: true })) {
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 = resolve2(cwd, entry.name);
872
- for (const sub of readdirSync2(subDir, { withFileTypes: true })) {
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 = resolve2(filePath);
1236
+ const normalizedPath = resolve3(filePath);
946
1237
  const refusal = getHardRefusal(normalizedPath);
947
1238
  if (refusal) return refusal;
948
- if (!existsSync3(normalizedPath)) {
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 } = statSync2(normalizedPath);
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 = dirname(normalizedPath);
988
- const nameNoExt = basename(normalizedPath, ext);
989
- const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync3);
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 ${basename(p)}`).join("\n") + `
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 = readFileSync2(normalizedPath, "utf-8");
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) : readFileSync2(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 mkdirSync2 } from "fs";
1054
- import { dirname as dirname2 } from "path";
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 existsSync6, readFileSync as readFileSync5 } from "fs";
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 existsSync4, mkdirSync, readFileSync as readFileSync3 } from "fs";
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 join(configDir, TRUST_FILE);
1568
+ return join2(configDir, TRUST_FILE);
1278
1569
  }
1279
1570
  function loadHookTrustStore(configDir) {
1280
1571
  const file = hookTrustPath(configDir);
1281
- if (!existsSync4(file)) return { records: [] };
1572
+ if (!existsSync5(file)) return { records: [] };
1282
1573
  try {
1283
- const parsed = JSON.parse(readFileSync3(file, "utf-8"));
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
- mkdirSync(configDir, { recursive: true });
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 createHash("sha256").update(`${event}
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 resolve3 } from "path";
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 : resolve3(root, target);
1490
- const r = normalizePathForPermission(resolve3(root));
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 existsSync5, readFileSync as readFileSync4 } from "fs";
1865
- import { isAbsolute as isAbsolute2, join as join2, resolve as resolve4 } from "path";
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 = resolve4(root);
1889
- const absoluteTarget = isAbsolute2(target) ? target : resolve4(root, 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 = join2(root, "package.json");
1907
- if (!existsSync5(p)) return null;
2197
+ const p = join3(root, "package.json");
2198
+ if (!existsSync6(p)) return null;
1908
2199
  try {
1909
- return JSON.parse(readFileSync4(p, "utf-8"));
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 = join2(root, file);
1926
- if (!existsSync5(p)) continue;
2216
+ const p = join3(root, file);
2217
+ if (!existsSync6(p)) continue;
1927
2218
  try {
1928
- if (readFileSync4(p, "utf-8").includes(name)) return true;
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((resolve7) => {
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
- resolve7(result);
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 (existsSync6(filePath)) {
2783
+ if (existsSync7(filePath)) {
2493
2784
  let oldContent;
2494
2785
  try {
2495
- oldContent = readFileSync5(filePath, "utf-8");
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 || !existsSync6(filePath)) return;
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 = readFileSync5(filePath, "utf-8");
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((resolve7) => {
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
- resolve7(answer === "y");
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 resolve5, sep as sep2, basename as basename2 } from "path";
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 = resolve5(p);
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 = basename2(abs);
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
- mkdirSync2(dirname2(filePath), { recursive: true });
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 readFileSync6, existsSync as existsSync7 } from "fs";
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 (!existsSync7(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
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 = readFileSync6(filePath, encoding);
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 readdirSync3, statSync as statSync3, existsSync as existsSync8 } from "fs";
3328
- import { join as join3, basename as basename3 } from "path";
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 (!existsSync8(dirPath)) {
3351
- const targetName = basename3(dirPath).toLowerCase();
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 readdirSync3(cwd, { withFileTypes: true })) {
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 = readdirSync3(basePath, { withFileTypes: true });
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(join3(basePath, entry.name), indent + " ", true, lines);
3699
+ listRecursive(join4(basePath, entry.name), indent + " ", true, lines);
3409
3700
  }
3410
3701
  } else {
3411
3702
  try {
3412
- const stat = statSync3(join3(basePath, entry.name));
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 readdirSync4, readFileSync as readFileSync7, statSync as statSync4, existsSync as existsSync9 } from "fs";
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 join4, relative } from "path";
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 (!existsSync9(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
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 = statSync4(rootPath);
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 = readdirSync4(dirPath, { withFileTypes: true });
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(join4(dirPath, entry.name));
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 = join4(dirPath, entry.name);
3870
+ const fullPath = join5(dirPath, entry.name);
3580
3871
  try {
3581
- if (statSync4(fullPath).size > 1e6) continue;
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 = statSync4(fullPath);
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 = readFileSync7(fullPath, "utf-8");
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 readdirSync5, statSync as statSync5, existsSync as existsSync10 } from "fs";
3666
- import { join as join5, relative as relative2, basename as basename4 } from "path";
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 (!existsSync10(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
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 = readdirSync5(dirPath, { withFileTypes: true });
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 = join5(dirPath, entry.name);
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(basename4(relPath))) {
4073
+ if (regex.test(relPath) || regex.test(basename5(relPath))) {
3783
4074
  try {
3784
- const stat = statSync5(fullPath);
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((resolve7) => {
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
- resolve7(`${prefixWarnings}[Timeout after ${timeout}ms]
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
- resolve7(`${prefixWarnings}Exit code ${code}:
4189
+ resolve9(`${prefixWarnings}Exit code ${code}:
3899
4190
  ${output}`);
3900
4191
  } else {
3901
- resolve7(`${prefixWarnings}${output || "(no output)"}`);
4192
+ resolve9(`${prefixWarnings}${output || "(no output)"}`);
3902
4193
  }
3903
4194
  });
3904
4195
  child.on("error", (err) => {
3905
4196
  clearTimeout(timer);
3906
- resolve7(
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 mkdirSync3, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
4517
- import { dirname as dirname3 } from "path";
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(dirname3(filePath));
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
- mkdirSync3(dirname3(filePath), { recursive: true });
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 { existsSync as existsSync11, readFileSync as readFileSync8, statSync as statSync6, mkdirSync as mkdirSync4 } from "fs";
4575
- import { join as join6 } from "path";
4865
+ import { join as join8 } from "path";
4576
4866
  import { homedir as homedir3 } from "os";
4577
- function getMemoryFilePath() {
4578
- return join6(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
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 formatTimestamp() {
4581
- const now = /* @__PURE__ */ new Date();
4582
- const pad = (n) => String(n).padStart(2, "0");
4583
- return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
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 that survives across sessions. Use this to remember: user preferences, coding style, project architecture decisions, recurring patterns, key findings, or any knowledge worth preserving. Keep each memory entry concise (1-3 sentences). The content will be automatically timestamped.",
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 memoryPath = getMemoryFilePath();
4602
- const configDir = join6(homedir3(), CONFIG_DIR_NAME);
4603
- if (!existsSync11(configDir)) {
4604
- mkdirSync4(configDir, { recursive: true });
4605
- }
4606
- const timestamp = formatTimestamp();
4607
- const entry = `
4608
- ## ${timestamp}
4609
- ${content}
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((resolve7) => {
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
- resolve7(answer);
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 existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
4901
- import { join as join7 } from "path";
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 (!existsSync12(dir)) return [];
5493
+ if (!existsSync13(dir)) return [];
4997
5494
  const out = [];
4998
- for (const file of readdirSync6(dir)) {
5495
+ for (const file of readdirSync7(dir)) {
4999
5496
  if (!file.endsWith(".json")) continue;
5000
- const path3 = join7(dir, file);
5497
+ const path3 = join9(dir, file);
5001
5498
  try {
5002
- const parsed = JSON.parse(readFileSync9(path3, "utf-8"));
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: join7(configDir ?? join7(homedir4(), CONFIG_DIR_NAME), "agents"),
5013
- project: join7(cwd, CONFIG_DIR_NAME, "agents")
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 nowIso() {
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: nowIso(),
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 = nowIso();
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 = randomUUID().slice(0, 8);
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 existsSync13 } from "fs";
5724
- import { join as join8 } from "path";
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 (existsSync13(join8(dir, ".git"))) return;
5730
- const parent = join8(dir, "..");
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 readFileSync10, existsSync as existsSync14 } from "fs";
6497
+ import { readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
5996
6498
  import { writeFile } from "fs/promises";
5997
- import { resolve as resolve6, extname as extname2 } from "path";
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 = resolve6(filePath);
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 (!existsSync14(absPath)) {
6552
+ if (!existsSync15(absPath)) {
6051
6553
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
6052
6554
  }
6053
- const raw = readFileSync10(absPath, "utf-8");
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 existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync7 } from "fs";
6471
- import { join as join9 } from "path";
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 (!existsSync15(pluginsDir)) {
7134
+ if (!existsSync16(pluginsDir)) {
6633
7135
  try {
6634
- mkdirSync5(pluginsDir, { recursive: true });
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 = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
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) => ` + ${join9(pluginsDir, f)}`).join("\n") + "\n\n"
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(join9(pluginsDir, file)).href;
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,