minimal-agent 0.1.8 → 0.1.9

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/dist/main.js CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  // src/main.tsx
4
4
  import { render } from "ink";
5
- import { existsSync as existsSync9, mkdirSync } from "fs";
5
+ import { existsSync as existsSync10, mkdirSync } from "fs";
6
6
  import { createRequire } from "module";
7
- import { resolve as resolve8 } from "path";
7
+ import { resolve as resolve9 } from "path";
8
8
 
9
9
  // src/bootstrap/cwdArg.ts
10
10
  function extractCwdArg(argv) {
@@ -50,6 +50,7 @@ async function readSavedConfig() {
50
50
  model: data.model,
51
51
  provider: typeof data.provider === "string" ? data.provider : void 0,
52
52
  contextWindow: typeof data.contextWindow === "number" && data.contextWindow > 0 ? data.contextWindow : void 0,
53
+ tavilyApiKey: typeof data.tavilyApiKey === "string" && data.tavilyApiKey.length > 0 ? data.tavilyApiKey : void 0,
53
54
  savedAt: typeof data.savedAt === "number" ? data.savedAt : 0
54
55
  };
55
56
  } catch {
@@ -98,6 +99,13 @@ async function loadProviderLayered() {
98
99
  contextWindow
99
100
  };
100
101
  }
102
+ async function applyToolKeysToEnv() {
103
+ const saved = await readSavedConfig();
104
+ if (!saved) return;
105
+ if (!process.env.TAVILY_API_KEY && saved.tavilyApiKey) {
106
+ process.env.TAVILY_API_KEY = saved.tavilyApiKey;
107
+ }
108
+ }
101
109
 
102
110
  // src/context/persistContext.ts
103
111
  import { mkdir as mkdir3, readFile as readFile2, readdir, rmdir, unlink, writeFile as writeFile2 } from "fs/promises";
@@ -221,7 +229,11 @@ async function loadProjectInstructions(cwd) {
221
229
 
222
230
  // src/prompts/skillList.ts
223
231
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
224
- import { join as join5 } from "path";
232
+ import { join as join6 } from "path";
233
+
234
+ // src/utils/resourcePaths.ts
235
+ import { existsSync as existsSync2 } from "fs";
236
+ import { join as join5, resolve as resolve4 } from "path";
225
237
 
226
238
  // src/utils/packageRoot.ts
227
239
  import { existsSync } from "fs";
@@ -245,8 +257,26 @@ function findPackageRoot(metaUrl) {
245
257
  throw new Error(`packageRoot: \u627E\u4E0D\u5230 package.json\uFF08\u8D77\u70B9 ${metaUrl}\uFF09`);
246
258
  }
247
259
 
260
+ // src/utils/resourcePaths.ts
261
+ function getResourceSearchPaths(name, metaUrl) {
262
+ const paths = [];
263
+ const cwdPath = resolve4(join5(getWorkingDir(), name));
264
+ if (existsSync2(cwdPath)) {
265
+ paths.push(cwdPath);
266
+ }
267
+ let pkgPath;
268
+ try {
269
+ pkgPath = resolve4(join5(findPackageRoot(metaUrl), name));
270
+ } catch {
271
+ return paths;
272
+ }
273
+ if (pkgPath !== cwdPath && existsSync2(pkgPath)) {
274
+ paths.push(pkgPath);
275
+ }
276
+ return paths;
277
+ }
278
+
248
279
  // src/prompts/skillList.ts
249
- var SKILLS_DIR = join5(findPackageRoot(import.meta.url), "skills");
250
280
  function stripQuotes(s) {
251
281
  const trimmed = s.trim();
252
282
  if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
@@ -254,7 +284,7 @@ function stripQuotes(s) {
254
284
  }
255
285
  return trimmed;
256
286
  }
257
- function parseFrontmatter(content) {
287
+ function parseFrontmatter(content, sourceDir) {
258
288
  const match = content.match(/^---\n([\s\S]*?)\n---/);
259
289
  if (!match) return null;
260
290
  const frontmatter = match[1];
@@ -267,26 +297,33 @@ function parseFrontmatter(content) {
267
297
  name: stripQuotes(nameMatch[1]),
268
298
  description: stripQuotes(descMatch[1]),
269
299
  type: typeMatch ? stripQuotes(typeMatch[1]) : void 0,
270
- triggers: triggersMatch ? stripQuotes(triggersMatch[1]) : void 0
300
+ triggers: triggersMatch ? stripQuotes(triggersMatch[1]) : void 0,
301
+ sourceDir
271
302
  };
272
303
  }
273
304
  async function getSkillList() {
274
305
  const skills = [];
275
- try {
276
- const entries = await readdir2(SKILLS_DIR, { withFileTypes: true });
277
- for (const entry of entries) {
278
- if (!entry.isDirectory()) continue;
279
- const skillPath = join5(SKILLS_DIR, entry.name, "SKILL.md");
280
- try {
281
- const content = await readFile4(skillPath, "utf8");
282
- const meta = parseFrontmatter(content);
283
- if (meta) {
284
- skills.push(meta);
306
+ const seenNames = /* @__PURE__ */ new Set();
307
+ const searchPaths = getResourceSearchPaths("skills", import.meta.url);
308
+ for (const root of searchPaths) {
309
+ try {
310
+ const entries = await readdir2(root, { withFileTypes: true });
311
+ for (const entry of entries) {
312
+ if (!entry.isDirectory()) continue;
313
+ const sourceDir = join6(root, entry.name);
314
+ const skillPath = join6(sourceDir, "SKILL.md");
315
+ try {
316
+ const content = await readFile4(skillPath, "utf8");
317
+ const meta = parseFrontmatter(content, sourceDir);
318
+ if (meta && !seenNames.has(meta.name)) {
319
+ seenNames.add(meta.name);
320
+ skills.push(meta);
321
+ }
322
+ } catch {
285
323
  }
286
- } catch {
287
324
  }
325
+ } catch {
288
326
  }
289
- } catch {
290
327
  }
291
328
  return skills;
292
329
  }
@@ -297,13 +334,11 @@ function formatSkillHint(skills) {
297
334
  const lines = [];
298
335
  lines.push("# \u53EF\u7528\u6280\u80FD\uFF08/\u547D\u4EE4\uFF09");
299
336
  lines.push("");
300
- lines.push(`> Skills \u6839\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84\uFF1A\`${SKILLS_DIR}\``);
301
- lines.push("");
302
337
  lines.push(
303
- "\u4EE5\u4E0B\u662F\u5F53\u524D\u53EF\u7528\u7684\u6280\u80FD\u3002**\u5F53\u7528\u6237\u610F\u56FE\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A\u65F6\uFF0C\u4F60\u5E94\u8BE5\u4E3B\u52A8\u4F7F\u7528\u5B83**\u3002"
338
+ "\u4EE5\u4E0B\u662F\u5F53\u524D\u53EF\u7528\u7684\u6280\u80FD\uFF08**\u53CC\u6E90**\uFF1A\u7528\u6237\u9879\u76EE\u76EE\u5F55 + minimal-agent \u5305\u76EE\u5F55\uFF0C\u540C\u540D\u65F6\u9879\u76EE\u4F18\u5148\uFF09\u3002**\u5F53\u7528\u6237\u610F\u56FE\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A\u65F6\uFF0C\u4F60\u5E94\u8BE5\u4E3B\u52A8\u4F7F\u7528\u5B83**\u3002"
304
339
  );
305
340
  lines.push(
306
- `\u5728\u4F7F\u7528\u524D\uFF0C\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6 \`${SKILLS_DIR}/{name}/SKILL.md\` \u4E86\u89E3\u5B8C\u6574\u6D41\u7A0B\u3002`
341
+ "\u5728\u4F7F\u7528\u524D\uFF0C\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6\u4E0B\u65B9\u6BCF\u4E2A\u6280\u80FD\u5361\u7247\u91CC\u7684 `**\u6280\u80FD\u76EE\u5F55**` \u8DEF\u5F84 + `/SKILL.md` \u4E86\u89E3\u5B8C\u6574\u6D41\u7A0B\u3002"
307
342
  );
308
343
  lines.push("");
309
344
  for (const skill of skills) {
@@ -313,14 +348,14 @@ function formatSkillHint(skills) {
313
348
  lines.push(`- **\u89E6\u53D1\u65F6\u673A**: ${triggers}`);
314
349
  lines.push(`- **\u529F\u80FD**: ${skill.description}`);
315
350
  lines.push(`- **\u7C7B\u578B**: ${type}`);
316
- lines.push(`- **\u6280\u80FD\u76EE\u5F55**: \`${SKILLS_DIR}/${skill.name}/\``);
351
+ lines.push(`- **\u6280\u80FD\u76EE\u5F55**: \`${skill.sourceDir}\``);
317
352
  lines.push("");
318
353
  }
319
354
  lines.push("## \u6280\u80FD\u8C03\u7528\u6D41\u7A0B");
320
355
  lines.push("");
321
356
  lines.push("```");
322
357
  lines.push("1. \u5728 T \u9636\u6BB5\u5224\u65AD\u7528\u6237\u610F\u56FE\u662F\u5426\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A");
323
- lines.push(`2. \u5339\u914D \u2192 \u5728 A \u9636\u6BB5\u7528 Read \u5DE5\u5177\u8BFB\u53D6 ${SKILLS_DIR}/{name}/SKILL.md`);
358
+ lines.push('2. \u5339\u914D \u2192 \u5728 A \u9636\u6BB5\u7528 Read \u5DE5\u5177\u8BFB\u53D6\u8BE5\u6280\u80FD\u5361\u7247\u7684"\u6280\u80FD\u76EE\u5F55"/SKILL.md');
324
359
  lines.push("3. \u6309\u7167 SKILL.md \u7684 Quick Reference \u548C\u6D41\u7A0B\u6267\u884C");
325
360
  lines.push("4. \u6280\u80FD\u811A\u672C\uFF08scripts/\uFF09\u4E0E SKILL.md \u4F4D\u4E8E\u540C\u4E00\u76EE\u5F55\u4E0B\uFF0C\u7528\u7EDD\u5BF9\u8DEF\u5F84\u6267\u884C");
326
361
  lines.push("5. \u5982\u679C\u6280\u80FD\u7C7B\u578B\u662F prompt\uFF0C\u5C06\u5176\u5185\u5BB9\u4F5C\u4E3A\u989D\u5916\u4E0A\u4E0B\u6587\u6307\u5BFC\u540E\u7EED\u64CD\u4F5C");
@@ -382,6 +417,18 @@ ${toolList}
382
417
  # \u6280\u80FD\u7CFB\u7EDF\uFF08\u79EF\u6781\u4F7F\u7528\uFF09
383
418
  ${skillHint}
384
419
 
420
+ # \u63D2\u4EF6\u7CFB\u7EDF\uFF08\u88AB\u52A8\u89E6\u53D1\uFF09
421
+ - minimal-agent \u5728 \`plugins/\` \u76EE\u5F55\u4E0B\u52A0\u8F7D\u7528\u6237\u5B89\u88C5\u7684\u63D2\u4EF6\uFF0C\u66B4\u9732\u5F62\u5982 \`/ralph-loop\` \u7684\u547D\u4EE4\u3002
422
+ \u4E0E skill \u4E0D\u540C\uFF0C**\u63D2\u4EF6\u547D\u4EE4\u5FC5\u987B\u7531\u7528\u6237\u4E3B\u52A8\u8F93\u5165\u624D\u89E6\u53D1**\u2014\u2014\u4F60**\u4E0D\u8981**\u4E3B\u52A8\u8C03\u7528\u6216\u6A21\u62DF\u8FD9\u4E9B\u547D\u4EE4\uFF0C
423
+ \u4E5F\u4E0D\u8981\u5728\u5DE5\u5177\u8C03\u7528\u91CC\u5047\u88C5\u5728\u8DD1\u63D2\u4EF6\u3002
424
+ - \u5F53\u7528\u6237\u7684\u9700\u6C42\u662F **"\u81EA\u52A8\u91CD\u590D\u5C1D\u8BD5\u76F4\u5230\u6210\u529F / \u8FED\u4EE3\u4FEE\u590D\u6D4B\u8BD5\u76F4\u5230\u5168\u7EFF / \u53CD\u590D\u6253\u78E8\u540C\u4E00\u4EFD\u4EA7\u7269"** \u7B49
425
+ \u957F\u5FAA\u73AF\u573A\u666F\u65F6\uFF0C\u53EF\u4EE5\u5EFA\u8BAE\u7528\u6237\u6539\u7528\uFF1A\`/ralph-loop "<\u76EE\u6807>" [--max-iterations N] [--verify ...]\`
426
+ \uFF08\u5185\u7F6E\u7684\u590D\u5236\u4EFB\u52A1\u5FAA\u73AF\uFF0C\u4E94\u9636\u6BB5 FSM\uFF1APLAN \u2192 BUILD \u2192 VERIFY \u2192 HEAL \u2192 DONE\uFF09\u3002
427
+ - \u5982\u679C\u5F53\u8F6E\u7528\u6237\u6D88\u606F\u91CC\u51FA\u73B0 \`<promise>DONE</promise>\` / \`<PROMISE>NEED_REPLAN</PROMISE>\` \u54E8\u5175
428
+ \u6216 PLAN/BUILD/VERIFY/HEAL \u9636\u6BB5\u6807\u8BB0\uFF0C\u8BF4\u660E\u4F60\u6B63\u8FD0\u884C\u5728 ralph-loop \u5185\u2014\u2014\u4E25\u683C\u6309\u7167\u5F53\u8F6E\u6CE8\u5165\u7684
429
+ freshContext \u6307\u5F15\u884C\u52A8\uFF0C**\u4EC5\u5728\u4EFB\u52A1\u771F\u6B63\u5B8C\u6210\u65F6**\u8F93\u51FA \`<promise>DONE</promise>\`\u3002
430
+ - \u5728**\u5E38\u89C4\u5BF9\u8BDD**\uFF08\u65E0\u4E0A\u8FF0\u9636\u6BB5\u6807\u8BB0\uFF09\u4E2D**\u7981\u6B62**\u4F7F\u7528\u8FD9\u4E9B\u54E8\u5175\u548C\u9636\u6BB5\u8BCD\u3002
431
+
385
432
  # \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
386
433
  ## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
387
434
  ### \u573A\u666F 1\uFF1A\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9
@@ -861,14 +908,14 @@ var bashTool = {
861
908
 
862
909
  // src/tools/edit/edit.ts
863
910
  import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
864
- import { existsSync as existsSync3 } from "fs";
911
+ import { existsSync as existsSync4 } from "fs";
865
912
  import { dirname as dirname5 } from "path";
866
- import { z as z2 } from "zod";
913
+ import { z as z3 } from "zod";
867
914
 
868
915
  // src/tools/shared/fileUtils.ts
869
916
  import { open, readFile as readFile5 } from "fs/promises";
870
917
  import { homedir as homedir4 } from "os";
871
- import { extname, resolve as resolve4 } from "path";
918
+ import { extname, resolve as resolve5 } from "path";
872
919
  var BLOCKED_DEVICE_PATHS = /* @__PURE__ */ new Set([
873
920
  "/dev/zero",
874
921
  "/dev/random",
@@ -904,7 +951,7 @@ function validateAndResolvePath(rawPath, workingDir) {
904
951
  return { ok: false, error: `\u4E0D\u5141\u8BB8\u8BFB\u53D6\u8BBE\u5907\u6587\u4EF6\uFF1A${rawPath}\u3002\u8BE5\u8DEF\u5F84\u53EF\u80FD\u4EA7\u751F\u65E0\u9650\u8F93\u51FA\u6216\u963B\u585E\u8FDB\u7A0B\u3002` };
905
952
  }
906
953
  const expanded = expandPath(rawPath);
907
- const resolved = resolve4(workingDir, expanded);
954
+ const resolved = resolve5(workingDir, expanded);
908
955
  if (isBlockedDevicePath(resolved)) {
909
956
  return { ok: false, error: `\u4E0D\u5141\u8BB8\u8BFB\u53D6\u8BBE\u5907\u6587\u4EF6\uFF1A${resolved}\u3002\u8BE5\u8DEF\u5F84\u53EF\u80FD\u4EA7\u751F\u65E0\u9650\u8F93\u51FA\u6216\u963B\u585E\u8FDB\u7A0B\u3002` };
910
957
  }
@@ -915,10 +962,13 @@ function validateAndResolvePath(rawPath, workingDir) {
915
962
  }
916
963
  function expandPath(p) {
917
964
  if (p.startsWith("~/") || p === "~") {
918
- return resolve4(homedir4(), p.slice(2));
965
+ return resolve5(homedir4(), p.slice(2));
919
966
  }
920
967
  return p;
921
968
  }
969
+ function resolveToolPath(rawPath) {
970
+ return validateAndResolvePath(rawPath, getWorkingDir());
971
+ }
922
972
  function detectLineEndingsForString(content) {
923
973
  let crlfCount = 0;
924
974
  let lfCount = 0;
@@ -1108,9 +1158,28 @@ function applyCurlySingleQuotes(str) {
1108
1158
  }
1109
1159
  return result.join("");
1110
1160
  }
1161
+ function countOccurrences(haystack, needle) {
1162
+ if (needle.length === 0) return 0;
1163
+ let count = 0;
1164
+ let pos = 0;
1165
+ while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1166
+ count++;
1167
+ pos += needle.length;
1168
+ }
1169
+ return count;
1170
+ }
1171
+ function splitReplaceAll(haystack, needle, replacement) {
1172
+ return haystack.split(needle).join(replacement);
1173
+ }
1174
+
1175
+ // src/tools/shared/schemas.ts
1176
+ import { z as z2 } from "zod";
1177
+ function filePathField(action) {
1178
+ return z2.string().min(1, "\u5FC5\u987B\u63D0\u4F9B file_path").describe(`\u8981${action}\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u8DEF\u5F84\u4F18\u5148\uFF0C\u76F8\u5BF9\u8DEF\u5F84\u57FA\u4E8E working dir \u89E3\u6790\uFF09`);
1179
+ }
1111
1180
 
1112
1181
  // src/tools/shared/fileState.ts
1113
- import { existsSync as existsSync2, statSync } from "fs";
1182
+ import { existsSync as existsSync3, statSync } from "fs";
1114
1183
  var fileState = /* @__PURE__ */ new Map();
1115
1184
  function recordRead(absPath) {
1116
1185
  try {
@@ -1122,7 +1191,7 @@ function recordRead(absPath) {
1122
1191
  function assertFresh(absPath) {
1123
1192
  const entry = fileState.get(absPath);
1124
1193
  if (!entry) {
1125
- if (existsSync2(absPath)) {
1194
+ if (existsSync3(absPath)) {
1126
1195
  return {
1127
1196
  ok: false,
1128
1197
  error: `\u6587\u4EF6 ${absPath} \u5DF2\u5B58\u5728\u4F46\u672A\u5728\u672C\u4F1A\u8BDD Read \u8FC7\u3002\u8BF7\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6\uFF0C\u786E\u8BA4\u5185\u5BB9\u540E\u518D\u4FEE\u6539\u3002`
@@ -1149,13 +1218,13 @@ function clearFileState() {
1149
1218
 
1150
1219
  // src/tools/edit/edit.ts
1151
1220
  var MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
1152
- var inputSchema2 = z2.object({
1153
- file_path: z2.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
1154
- old_string: z2.string().describe(
1221
+ var inputSchema2 = z3.object({
1222
+ file_path: filePathField("\u7F16\u8F91"),
1223
+ old_string: z3.string().describe(
1155
1224
  "\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF0C\u9664\u975E replace_all=true\uFF09\uFF1B\u4E3A\u7A7A\u5B57\u7B26\u4E32\u4E14\u6587\u4EF6\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA\u65B0\u6587\u4EF6"
1156
1225
  ),
1157
- new_string: z2.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
1158
- replace_all: z2.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF09")
1226
+ new_string: z3.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
1227
+ replace_all: z3.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF09")
1159
1228
  });
1160
1229
  var parameters2 = toToolParameters(inputSchema2);
1161
1230
  var description2 = `Performs exact string replacements in files.
@@ -1164,7 +1233,8 @@ Usage:
1164
1233
  - You MUST use your \`Read\` tool to read the current content of the file BEFORE calling Edit.
1165
1234
  Memory is unreliable \u2014 if you think you "remember" how the code looks, you are probably wrong.
1166
1235
  Re-read the relevant sections (function, module, or area you plan to change) every time.
1167
- - To create a new file, pass an empty \`old_string\` and the desired contents as \`new_string\`.
1236
+ - To create a new file, pass an empty \`old_string\` and the desired contents as \`new_string\`. When creating a new file (path does not yet exist), you may call Edit directly \u2014 no prior Read is needed.
1237
+ - If the file already exists and you pass an empty \`old_string\`, the edit is rejected with a clear error. To replace the entire content of an existing file, use the Write tool instead.
1168
1238
  - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
1169
1239
  - The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
1170
1240
  - Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
@@ -1172,7 +1242,7 @@ Usage:
1172
1242
  async function call2(input) {
1173
1243
  const { old_string, new_string } = input;
1174
1244
  const replaceAll = input.replace_all ?? false;
1175
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1245
+ const pathResult = resolveToolPath(input.file_path);
1176
1246
  if (!pathResult.ok) return pathResult;
1177
1247
  const filePath = pathResult.resolvedPath;
1178
1248
  const freshness = assertFresh(filePath);
@@ -1182,10 +1252,11 @@ async function call2(input) {
1182
1252
  if (old_string === new_string) {
1183
1253
  return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
1184
1254
  }
1185
- if (old_string === "" && !existsSync3(filePath)) {
1255
+ if (old_string === "" && !existsSync4(filePath)) {
1186
1256
  try {
1187
1257
  await mkdir4(dirname5(filePath), { recursive: true });
1188
1258
  await writeFile3(filePath, new_string, "utf8");
1259
+ recordRead(filePath);
1189
1260
  return {
1190
1261
  ok: true,
1191
1262
  content: `\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6\uFF1A${filePath}\uFF08${new_string.length} \u5B57\u7B26\uFF09`
@@ -1194,7 +1265,7 @@ async function call2(input) {
1194
1265
  return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
1195
1266
  }
1196
1267
  }
1197
- if (!existsSync3(filePath)) {
1268
+ if (!existsSync4(filePath)) {
1198
1269
  return {
1199
1270
  ok: false,
1200
1271
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1250,6 +1321,7 @@ async function call2(input) {
1250
1321
  const normalizedReplaced = applyLineEnding(replaced, originalLineEnding);
1251
1322
  try {
1252
1323
  await writeFile3(filePath, normalizedReplaced, "utf8");
1324
+ recordRead(filePath);
1253
1325
  } catch (e) {
1254
1326
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1255
1327
  }
@@ -1262,19 +1334,6 @@ async function call2(input) {
1262
1334
  \u884C\u6570\uFF1A${linesBefore} \u2192 ${linesAfter}`
1263
1335
  };
1264
1336
  }
1265
- function countOccurrences(haystack, needle) {
1266
- if (needle.length === 0) return 0;
1267
- let count = 0;
1268
- let pos = 0;
1269
- while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1270
- count++;
1271
- pos += needle.length;
1272
- }
1273
- return count;
1274
- }
1275
- function splitReplaceAll(haystack, needle, replacement) {
1276
- return haystack.split(needle).join(replacement);
1277
- }
1278
1337
  function findFuzzyMatchHint(fileContent, target) {
1279
1338
  const MIN_OVERLAP_RATIO = 0.5;
1280
1339
  const MAX_HINTS = 3;
@@ -1349,16 +1408,16 @@ var editTool = {
1349
1408
 
1350
1409
  // src/tools/edit/multi-edit.ts
1351
1410
  import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
1352
- import { existsSync as existsSync4 } from "fs";
1353
- import { z as z3 } from "zod";
1354
- var editItemSchema = z3.object({
1355
- old_string: z3.string().min(1).describe("\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u4E0D\u5141\u8BB8\u4E3A\u7A7A \u2014\u2014 \u521B\u5EFA\u65B0\u6587\u4EF6\u8BF7\u7528 Edit \u5DE5\u5177\uFF09"),
1356
- new_string: z3.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C"),
1357
- replace_all: z3.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF0C\u8981\u6C42 old_string \u5728\u5F53\u524D\u5185\u5BB9\u4E2D\u552F\u4E00\uFF09")
1411
+ import { existsSync as existsSync5 } from "fs";
1412
+ import { z as z4 } from "zod";
1413
+ var editItemSchema = z4.object({
1414
+ old_string: z4.string().min(1).describe("\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u4E0D\u5141\u8BB8\u4E3A\u7A7A \u2014\u2014 \u521B\u5EFA\u65B0\u6587\u4EF6\u8BF7\u7528 Edit \u5DE5\u5177\uFF09"),
1415
+ new_string: z4.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C"),
1416
+ replace_all: z4.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF0C\u8981\u6C42 old_string \u5728\u5F53\u524D\u5185\u5BB9\u4E2D\u552F\u4E00\uFF09")
1358
1417
  });
1359
- var inputSchema3 = z3.object({
1360
- file_path: z3.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
1361
- edits: z3.array(editItemSchema).min(1).max(50).describe("\u6309\u987A\u5E8F\u5E94\u7528\u7684 edit \u5217\u8868\uFF081-50 \u6761\uFF09\uFF0C\u539F\u5B50\u5316\u6267\u884C\uFF1A\u5168\u90E8\u6210\u529F\u624D\u843D\u76D8")
1418
+ var inputSchema3 = z4.object({
1419
+ file_path: filePathField("\u7F16\u8F91"),
1420
+ edits: z4.array(editItemSchema).min(1).max(50).describe("\u6309\u987A\u5E8F\u5E94\u7528\u7684 edit \u5217\u8868\uFF081-50 \u6761\uFF09\uFF0C\u539F\u5B50\u5316\u6267\u884C\uFF1A\u5168\u90E8\u6210\u529F\u624D\u843D\u76D8")
1362
1421
  });
1363
1422
  var parameters3 = toToolParameters(inputSchema3);
1364
1423
  var description3 = `Performs multiple exact string replacements in a single file, applied atomically (all-or-nothing).
@@ -1371,28 +1430,15 @@ Usage:
1371
1430
  - Each \`old_string\` must be unique in the current content (after prior edits) unless \`replace_all=true\`.
1372
1431
  - Empty \`old_string\` is NOT allowed in MultiEdit. To create a new file, use the \`Edit\` tool with a single empty-old_string call.
1373
1432
  - Preserve exact indentation (tabs/spaces).`;
1374
- function countOccurrences2(haystack, needle) {
1375
- if (needle.length === 0) return 0;
1376
- let count = 0;
1377
- let pos = 0;
1378
- while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1379
- count++;
1380
- pos += needle.length;
1381
- }
1382
- return count;
1383
- }
1384
- function splitReplaceAll2(haystack, needle, replacement) {
1385
- return haystack.split(needle).join(replacement);
1386
- }
1387
1433
  async function call3(input) {
1388
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1434
+ const pathResult = resolveToolPath(input.file_path);
1389
1435
  if (!pathResult.ok) return pathResult;
1390
1436
  const filePath = pathResult.resolvedPath;
1391
1437
  const freshness = assertFresh(filePath);
1392
1438
  if (!freshness.ok) {
1393
1439
  return { ok: false, error: freshness.error };
1394
1440
  }
1395
- if (!existsSync4(filePath)) {
1441
+ if (!existsSync5(filePath)) {
1396
1442
  return {
1397
1443
  ok: false,
1398
1444
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1433,7 +1479,7 @@ async function call3(input) {
1433
1479
  searchTarget = actualOld;
1434
1480
  processedNewString = preserveQuoteStyle(edit.old_string, actualOld, edit.new_string);
1435
1481
  }
1436
- const occurrences = countOccurrences2(currentContent, searchTarget);
1482
+ const occurrences = countOccurrences(currentContent, searchTarget);
1437
1483
  if (occurrences === 0) {
1438
1484
  return {
1439
1485
  ok: false,
@@ -1446,12 +1492,13 @@ async function call3(input) {
1446
1492
  error: `edits[${i}].old_string \u5728\u5F53\u524D\u5185\u5BB9\u4E2D\u51FA\u73B0 ${occurrences} \u6B21\uFF0C\u4E0D\u552F\u4E00\u3002\u8BF7\u6269\u5927 old_string \u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u4F20 replace_all=true\u3002`
1447
1493
  };
1448
1494
  }
1449
- currentContent = replaceAll ? splitReplaceAll2(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1495
+ currentContent = replaceAll ? splitReplaceAll(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1450
1496
  }
1451
1497
  const lineEnding = await detectFileLineEndings(filePath);
1452
1498
  const finalContent = applyLineEnding(currentContent, lineEnding);
1453
1499
  try {
1454
1500
  await writeFile4(filePath, finalContent, "utf8");
1501
+ recordRead(filePath);
1455
1502
  } catch (e) {
1456
1503
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1457
1504
  }
@@ -1473,12 +1520,12 @@ var multiEditTool = {
1473
1520
 
1474
1521
  // src/tools/glob/glob.ts
1475
1522
  import { stat as stat2 } from "fs/promises";
1476
- import { isAbsolute, resolve as resolve5 } from "path";
1523
+ import { isAbsolute, resolve as resolve6 } from "path";
1477
1524
  import fg from "fast-glob";
1478
- import { z as z4 } from "zod";
1479
- var inputSchema4 = z4.object({
1480
- pattern: z4.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
1481
- path: z4.string().optional().describe('\u641C\u7D22\u7684\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1B\u7701\u7565\u65F6\u4E0D\u8981\u4F20 "undefined" \u5B57\u7B26\u4E32')
1525
+ import { z as z5 } from "zod";
1526
+ var inputSchema4 = z5.object({
1527
+ pattern: z5.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
1528
+ path: z5.string().optional().describe('\u641C\u7D22\u7684\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1B\u7701\u7565\u65F6\u4E0D\u8981\u4F20 "undefined" \u5B57\u7B26\u4E32')
1482
1529
  });
1483
1530
  var parameters4 = toToolParameters(inputSchema4);
1484
1531
  var description4 = `- Fast file pattern matching tool that works with any codebase size
@@ -1487,7 +1534,7 @@ var description4 = `- Fast file pattern matching tool that works with any codeba
1487
1534
  - Use this tool when you need to find files by name patterns
1488
1535
  - When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
1489
1536
  async function call4(input) {
1490
- const cwd = input.path ? resolve5(input.path) : getWorkingDir();
1537
+ const cwd = input.path ? resolve6(input.path) : getWorkingDir();
1491
1538
  const pattern = input.pattern.replace(/\\/g, "/");
1492
1539
  let matches;
1493
1540
  try {
@@ -1508,7 +1555,7 @@ async function call4(input) {
1508
1555
  }
1509
1556
  const withMtime = await Promise.all(
1510
1557
  matches.map(async (rel) => {
1511
- const abs = isAbsolute(rel) ? rel : resolve5(cwd, rel);
1558
+ const abs = isAbsolute(rel) ? rel : resolve6(cwd, rel);
1512
1559
  try {
1513
1560
  const st = await stat2(abs);
1514
1561
  return { path: rel, mtime: st.mtimeMs };
@@ -1544,13 +1591,13 @@ var globTool = {
1544
1591
 
1545
1592
  // src/tools/grep/grep.ts
1546
1593
  import { spawn as spawn3 } from "child_process";
1547
- import { resolve as resolve7 } from "path";
1548
- import { z as z5 } from "zod";
1594
+ import { resolve as resolve8 } from "path";
1595
+ import { z as z6 } from "zod";
1549
1596
 
1550
1597
  // src/tools/grep/rgPath.ts
1551
1598
  import { spawn as spawn2 } from "child_process";
1552
- import { chmodSync, existsSync as existsSync5 } from "fs";
1553
- import { resolve as resolve6 } from "path";
1599
+ import { chmodSync, existsSync as existsSync6 } from "fs";
1600
+ import { resolve as resolve7 } from "path";
1554
1601
  var cached;
1555
1602
  async function resolveRgPath() {
1556
1603
  if (cached !== void 0) return cached;
@@ -1559,15 +1606,15 @@ async function resolveRgPath() {
1559
1606
  }
1560
1607
  async function detect() {
1561
1608
  const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
1562
- if (fromEnv && existsSync5(fromEnv)) return fromEnv;
1609
+ if (fromEnv && existsSync6(fromEnv)) return fromEnv;
1563
1610
  const vendored = vendoredRgPath();
1564
- if (vendored && existsSync5(vendored)) {
1611
+ if (vendored && existsSync6(vendored)) {
1565
1612
  ensureExecutable(vendored);
1566
1613
  return vendored;
1567
1614
  }
1568
1615
  if (await trySpawn("rg")) return "rg";
1569
1616
  for (const candidate of claudeCodeCandidates()) {
1570
- if (existsSync5(candidate)) {
1617
+ if (existsSync6(candidate)) {
1571
1618
  ensureExecutable(candidate);
1572
1619
  return candidate;
1573
1620
  }
@@ -1577,7 +1624,7 @@ async function detect() {
1577
1624
  function vendoredRgPath() {
1578
1625
  try {
1579
1626
  const projectRoot = findPackageRoot(import.meta.url);
1580
- return resolve6(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1627
+ return resolve7(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1581
1628
  } catch {
1582
1629
  return null;
1583
1630
  }
@@ -1636,42 +1683,42 @@ function claudeCodeCandidates() {
1636
1683
  const npmRoots = [];
1637
1684
  if (platform === "win32") {
1638
1685
  if (process.env.APPDATA) {
1639
- npmRoots.push(resolve6(process.env.APPDATA, "npm", "node_modules"));
1686
+ npmRoots.push(resolve7(process.env.APPDATA, "npm", "node_modules"));
1640
1687
  }
1641
1688
  if (process.env.USERPROFILE) {
1642
1689
  npmRoots.push(
1643
- resolve6(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1690
+ resolve7(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1644
1691
  );
1645
1692
  }
1646
1693
  } else {
1647
1694
  const home = process.env.HOME ?? "";
1648
1695
  if (home) {
1649
- npmRoots.push(resolve6(home, ".npm-global", "lib", "node_modules"));
1650
- npmRoots.push(resolve6(home, ".npm", "lib", "node_modules"));
1651
- npmRoots.push(resolve6(home, "node_modules"));
1696
+ npmRoots.push(resolve7(home, ".npm-global", "lib", "node_modules"));
1697
+ npmRoots.push(resolve7(home, ".npm", "lib", "node_modules"));
1698
+ npmRoots.push(resolve7(home, "node_modules"));
1652
1699
  }
1653
1700
  npmRoots.push("/usr/local/lib/node_modules");
1654
1701
  npmRoots.push("/usr/lib/node_modules");
1655
1702
  npmRoots.push("/opt/homebrew/lib/node_modules");
1656
1703
  }
1657
1704
  return npmRoots.map(
1658
- (root) => resolve6(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1705
+ (root) => resolve7(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1659
1706
  );
1660
1707
  }
1661
1708
 
1662
1709
  // src/tools/grep/grep.ts
1663
- var inputSchema5 = z5.object({
1664
- pattern: z5.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
1665
- path: z5.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
1666
- glob: z5.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
1667
- type: z5.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
1668
- output_mode: z5.enum(["content", "files_with_matches", "count"]).optional().describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Acontent=\u5339\u914D\u884C\uFF1Bfiles_with_matches=\u53EA\u5217\u6587\u4EF6\uFF1Bcount=\u6BCF\u6587\u4EF6\u8BA1\u6570"),
1669
- "-i": z5.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
1670
- "-n": z5.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
1671
- "-A": z5.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1672
- "-B": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1673
- "-C": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
1674
- head_limit: z5.number().int().positive().optional().describe("\u8F93\u51FA\u6700\u591A\u4FDD\u7559\u524D N \u884C\uFF08\u9632\u6B62\u7ED3\u679C\u8FC7\u5927\uFF09")
1710
+ var inputSchema5 = z6.object({
1711
+ pattern: z6.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
1712
+ path: z6.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
1713
+ glob: z6.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
1714
+ type: z6.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
1715
+ output_mode: z6.enum(["content", "files_with_matches", "count"]).optional().describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Acontent=\u5339\u914D\u884C\uFF1Bfiles_with_matches=\u53EA\u5217\u6587\u4EF6\uFF1Bcount=\u6BCF\u6587\u4EF6\u8BA1\u6570"),
1716
+ "-i": z6.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
1717
+ "-n": z6.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
1718
+ "-A": z6.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1719
+ "-B": z6.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1720
+ "-C": z6.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
1721
+ head_limit: z6.number().int().positive().optional().describe("\u8F93\u51FA\u6700\u591A\u4FDD\u7559\u524D N \u884C\uFF08\u9632\u6B62\u7ED3\u679C\u8FC7\u5927\uFF09")
1675
1722
  });
1676
1723
  var parameters5 = toToolParameters(inputSchema5);
1677
1724
  var description5 = `A powerful search tool built on ripgrep.
@@ -1701,7 +1748,7 @@ async function call5(input, signal) {
1701
1748
  args.push("--max-columns-preview");
1702
1749
  args.push("--sort", "modified");
1703
1750
  args.push("-e", input.pattern);
1704
- args.push(input.path ? resolve7(input.path) : ".");
1751
+ args.push(input.path ? resolve8(input.path) : ".");
1705
1752
  const rgPath = await resolveRgPath();
1706
1753
  if (!rgPath) {
1707
1754
  return {
@@ -1772,18 +1819,18 @@ var grepTool = {
1772
1819
  import { createReadStream } from "fs";
1773
1820
  import { readFile as readFile8, stat as stat3 } from "fs/promises";
1774
1821
  import { createInterface } from "readline";
1775
- import { z as z6 } from "zod";
1776
- var inputSchema6 = z6.object({
1777
- file_path: z6.string().min(1, "\u5FC5\u987B\u63D0\u4F9B file_path").describe("\u8981\u8BFB\u53D6\u7684\u6587\u4EF6\u8DEF\u5F84\uFF0C\u7EDD\u5BF9\u8DEF\u5F84\u4F18\u5148"),
1778
- offset: z6.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
1779
- limit: z6.number().int().positive().optional().describe(`\u6700\u591A\u8BFB\u591A\u5C11\u884C\uFF1B\u4E0D\u586B\u5219\u7528\u9ED8\u8BA4\u503C ${MAX_LINES_TO_READ}`)
1822
+ import { z as z7 } from "zod";
1823
+ var inputSchema6 = z7.object({
1824
+ file_path: filePathField("\u8BFB\u53D6"),
1825
+ offset: z7.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
1826
+ limit: z7.number().int().positive().optional().describe(`\u6700\u591A\u8BFB\u591A\u5C11\u884C\uFF1B\u4E0D\u586B\u5219\u7528\u9ED8\u8BA4\u503C ${MAX_LINES_TO_READ}`)
1780
1827
  });
1781
1828
  var parameters6 = toToolParameters(inputSchema6);
1782
1829
  var description6 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1783
1830
  Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
1784
1831
 
1785
1832
  Usage:
1786
- - The file_path parameter must be an absolute path, not a relative path
1833
+ - The file_path parameter is preferably an absolute path. Relative paths are accepted and will be resolved against the agent's working directory (locked at startup via \`-d <dir>\` or process cwd).
1787
1834
  - By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file
1788
1835
  - You can optionally specify a line offset and limit (especially handy for long files)
1789
1836
  - Results are returned using cat -n format, with line numbers starting at 1
@@ -1794,10 +1841,8 @@ var STREAM_THRESHOLD = 1024 * 1024;
1794
1841
  async function call6(input) {
1795
1842
  const offset = input.offset ?? 1;
1796
1843
  const limit = input.limit ?? MAX_LINES_TO_READ;
1797
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1798
- if (!pathResult.ok) {
1799
- return { ok: false, error: pathResult.error };
1800
- }
1844
+ const pathResult = resolveToolPath(input.file_path);
1845
+ if (!pathResult.ok) return pathResult;
1801
1846
  const filePath = pathResult.resolvedPath;
1802
1847
  if (isBlockedDevicePath(filePath)) {
1803
1848
  return { ok: false, error: `\u4E0D\u5141\u8BB8\u8BFB\u53D6\u8BBE\u5907\u6587\u4EF6\uFF1A${filePath}\u3002\u8BE5\u8DEF\u5F84\u53EF\u80FD\u4EA7\u751F\u65E0\u9650\u8F93\u51FA\u6216\u963B\u585E\u8FDB\u7A0B\u3002` };
@@ -1929,7 +1974,7 @@ var readTool = {
1929
1974
  };
1930
1975
 
1931
1976
  // src/tools/webfetch/webfetch.ts
1932
- import { z as z7 } from "zod";
1977
+ import { z as z8 } from "zod";
1933
1978
 
1934
1979
  // src/tools/webfetch/preapproved.ts
1935
1980
  var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
@@ -2214,9 +2259,9 @@ function cleanCache() {
2214
2259
  }
2215
2260
  }
2216
2261
  }
2217
- var inputSchema7 = z7.object({
2218
- url: z7.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
2219
- prompt: z7.string().describe("\u5BF9\u5185\u5BB9\u8FDB\u884C\u5904\u7406\u7684\u6307\u4EE4\uFF0C\u63CF\u8FF0\u4F60\u60F3\u4ECE\u9875\u9762\u63D0\u53D6\u4EC0\u4E48\u4FE1\u606F")
2262
+ var inputSchema7 = z8.object({
2263
+ url: z8.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
2264
+ prompt: z8.string().describe("\u5BF9\u5185\u5BB9\u8FDB\u884C\u5904\u7406\u7684\u6307\u4EE4\uFF0C\u63CF\u8FF0\u4F60\u60F3\u4ECE\u9875\u9762\u63D0\u53D6\u4EC0\u4E48\u4FE1\u606F")
2220
2265
  });
2221
2266
  var parameters7 = toToolParameters(inputSchema7);
2222
2267
  var description7 = `- Fetches content from a specified URL and processes it using an AI model.
@@ -2436,7 +2481,7 @@ var webfetchTool = {
2436
2481
  };
2437
2482
 
2438
2483
  // src/tools/webbrowser/webbrowser.ts
2439
- import { z as z8 } from "zod";
2484
+ import { z as z9 } from "zod";
2440
2485
 
2441
2486
  // src/tools/webbrowser/browser.ts
2442
2487
  import os from "os";
@@ -2472,12 +2517,12 @@ function screenshotPath(prefix = "browser") {
2472
2517
  }
2473
2518
 
2474
2519
  // src/tools/webbrowser/webbrowser.ts
2475
- var inputSchema8 = z8.object({
2476
- action: z8.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
2477
- url: z8.string().url().optional().describe("URL to navigate to (required for navigate action)"),
2478
- selector: z8.string().optional().describe("CSS selector for click/fill/submit actions"),
2479
- value: z8.string().optional().describe("Value to fill in input fields"),
2480
- timeout: z8.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
2520
+ var inputSchema8 = z9.object({
2521
+ action: z9.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
2522
+ url: z9.string().url().optional().describe("URL to navigate to (required for navigate action)"),
2523
+ selector: z9.string().optional().describe("CSS selector for click/fill/submit actions"),
2524
+ value: z9.string().optional().describe("Value to fill in input fields"),
2525
+ timeout: z9.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
2481
2526
  });
2482
2527
  var parameters8 = toToolParameters(inputSchema8);
2483
2528
  var description8 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
@@ -2625,12 +2670,12 @@ var webbrowserTool = {
2625
2670
  };
2626
2671
 
2627
2672
  // src/tools/websearch/websearch.ts
2628
- import { z as z9 } from "zod";
2629
- var inputSchema9 = z9.object({
2630
- query: z9.string().min(1, "\u5FC5\u987B\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD").max(400, "\u641C\u7D22\u5173\u952E\u8BCD\u592A\u957F\uFF08>400 \u5B57\uFF09").describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5EFA\u8BAE\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u9700\u8981\u67E5\u7684\u4FE1\u606F"),
2631
- max_results: z9.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
2632
- search_depth: z9.enum(["basic", "advanced"]).optional().describe("basic \u5FEB\u4F46\u6D45\uFF1Badvanced \u6162\u4F46\u6DF1\uFF08\u542B answer \u6458\u8981\uFF09\uFF0C\u9ED8\u8BA4 basic"),
2633
- topic: z9.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
2673
+ import { z as z10 } from "zod";
2674
+ var inputSchema9 = z10.object({
2675
+ query: z10.string().min(1, "\u5FC5\u987B\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD").max(400, "\u641C\u7D22\u5173\u952E\u8BCD\u592A\u957F\uFF08>400 \u5B57\uFF09").describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5EFA\u8BAE\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u9700\u8981\u67E5\u7684\u4FE1\u606F"),
2676
+ max_results: z10.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
2677
+ search_depth: z10.enum(["basic", "advanced"]).optional().describe("basic \u5FEB\u4F46\u6D45\uFF1Badvanced \u6162\u4F46\u6DF1\uFF08\u542B answer \u6458\u8981\uFF09\uFF0C\u9ED8\u8BA4 basic"),
2678
+ topic: z10.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
2634
2679
  });
2635
2680
  var parameters9 = toToolParameters(inputSchema9);
2636
2681
  var description9 = `- Searches the public web via the Tavily Search API and returns structured results.
@@ -2730,14 +2775,14 @@ var webSearchTool = {
2730
2775
  };
2731
2776
 
2732
2777
  // src/tools/write/write.ts
2733
- import { existsSync as existsSync6 } from "fs";
2778
+ import { existsSync as existsSync7 } from "fs";
2734
2779
  import { mkdir as mkdir5, stat as stat4, writeFile as writeFile5 } from "fs/promises";
2735
2780
  import { dirname as dirname6 } from "path";
2736
- import { z as z10 } from "zod";
2781
+ import { z as z11 } from "zod";
2737
2782
  var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
2738
- var inputSchema10 = z10.object({
2739
- file_path: z10.string().min(1).describe("\u8981\u5199\u5165\u7684\u6587\u4EF6\u8DEF\u5F84"),
2740
- content: z10.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
2783
+ var inputSchema10 = z11.object({
2784
+ file_path: filePathField("\u5199\u5165"),
2785
+ content: z11.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
2741
2786
  });
2742
2787
  var parameters10 = toToolParameters(inputSchema10);
2743
2788
  var description10 = `Writes a file to the local filesystem.
@@ -2749,7 +2794,7 @@ Usage:
2749
2794
  - ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
2750
2795
  - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
2751
2796
  async function call10(input) {
2752
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
2797
+ const pathResult = resolveToolPath(input.file_path);
2753
2798
  if (!pathResult.ok) return pathResult;
2754
2799
  const filePath = pathResult.resolvedPath;
2755
2800
  const freshness = assertFresh(filePath);
@@ -2766,7 +2811,7 @@ async function call10(input) {
2766
2811
  try {
2767
2812
  await mkdir5(dirname6(filePath), { recursive: true });
2768
2813
  let originalSize = 0;
2769
- const fileExisted = existsSync6(filePath);
2814
+ const fileExisted = existsSync7(filePath);
2770
2815
  if (fileExisted) {
2771
2816
  try {
2772
2817
  const st = await stat4(filePath);
@@ -2795,8 +2840,10 @@ async function call10(input) {
2795
2840
  } else {
2796
2841
  await writeFile5(filePath, contentToWrite, "utf8");
2797
2842
  }
2843
+ recordRead(filePath);
2798
2844
  const action = fileExisted ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6";
2799
- const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u7B26 \u2192 \u65B0\u5185\u5BB9 ${contentToWrite.length} \u5B57\u7B26\uFF09` : `\uFF08${contentToWrite.length} \u5B57\u7B26\uFF09`;
2845
+ const newSize = Buffer.byteLength(contentToWrite, "utf8");
2846
+ const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u8282 \u2192 \u65B0\u5185\u5BB9 ${newSize} \u5B57\u8282\uFF09` : `\uFF08${newSize} \u5B57\u8282\uFF09`;
2800
2847
  const bomInfo = bomEncoding === "utf8-bom" ? "\uFF0C\u5DF2\u4FDD\u7559 UTF-8 BOM" : "";
2801
2848
  return {
2802
2849
  ok: true,
@@ -2876,39 +2923,52 @@ import { Box as Box7, Text as Text7 } from "ink";
2876
2923
  import { useCallback, useMemo, useState } from "react";
2877
2924
  import { Box, Text, useApp, useInput } from "ink";
2878
2925
  import { jsx, jsxs } from "react/jsx-runtime";
2879
- var DEFAULT_CONTEXT_WINDOW2 = 128e3;
2926
+ var FALLBACK_CONTEXT_WINDOW = 128e3;
2880
2927
  var PRESETS = [
2881
2928
  {
2882
2929
  name: "minimax",
2883
2930
  label: "MiniMax (\u6D77\u87BA)",
2884
2931
  baseURL: "https://api.minimax.chat/v1",
2885
- models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"]
2932
+ models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"],
2933
+ contextWindow: 204800
2886
2934
  },
2887
2935
  {
2888
2936
  name: "deepseek",
2889
2937
  label: "DeepSeek",
2890
2938
  baseURL: "https://api.deepseek.com/v1",
2891
- models: ["deepseek-chat", "deepseek-reasoner"]
2939
+ models: ["deepseek-chat", "deepseek-reasoner"],
2940
+ contextWindow: 128e3
2892
2941
  },
2893
2942
  {
2894
2943
  name: "openai",
2895
2944
  label: "OpenAI",
2896
2945
  baseURL: "https://api.openai.com/v1",
2897
- models: ["gpt-4o", "gpt-4o-mini"]
2946
+ models: ["gpt-4o", "gpt-4o-mini"],
2947
+ contextWindow: 128e3
2898
2948
  },
2899
2949
  {
2900
2950
  name: "moonshot",
2901
2951
  label: "Moonshot (Kimi)",
2902
2952
  baseURL: "https://api.moonshot.cn/v1",
2903
- models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]
2953
+ models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"],
2954
+ contextWindow: 128e3,
2955
+ modelContextWindows: {
2956
+ "moonshot-v1-8k": 8e3,
2957
+ "moonshot-v1-32k": 32e3,
2958
+ "moonshot-v1-128k": 128e3
2959
+ }
2904
2960
  },
2905
2961
  {
2906
2962
  name: "custom",
2907
2963
  label: "\u81EA\u5B9A\u4E49\uFF08\u624B\u52A8\u586B baseURL\uFF09",
2908
2964
  baseURL: "",
2909
- models: []
2965
+ models: [],
2966
+ contextWindow: FALLBACK_CONTEXT_WINDOW
2910
2967
  }
2911
2968
  ];
2969
+ function resolveContextWindow(preset, model) {
2970
+ return preset.modelContextWindows?.[model] ?? preset.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
2971
+ }
2912
2972
  function ConfigWizard({ onSuccess }) {
2913
2973
  const { exit } = useApp();
2914
2974
  const [step, setStep] = useState("preset");
@@ -2937,51 +2997,68 @@ function ConfigWizard({ onSuccess }) {
2937
2997
  },
2938
2998
  [baseURL, model, preset]
2939
2999
  );
2940
- const handleTest = useCallback(async () => {
2941
- setStep("testing");
2942
- setErrorMsg(null);
2943
- try {
2944
- const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
2945
- const resp = await fetch(url, {
2946
- method: "POST",
2947
- headers: {
2948
- "Content-Type": "application/json",
2949
- Authorization: `Bearer ${apiKey}`
2950
- },
2951
- body: JSON.stringify({
2952
- model,
2953
- messages: [{ role: "user", content: "ping" }],
2954
- max_tokens: 5,
2955
- stream: false
2956
- })
2957
- });
2958
- if (!resp.ok) {
2959
- const body = await resp.text().catch(() => "");
2960
- throw new Error(`HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`);
2961
- }
2962
- const data = await resp.json();
2963
- if (!Array.isArray(data.choices)) {
2964
- throw new Error("\u54CD\u5E94\u4E2D\u6CA1\u6709 choices \u5B57\u6BB5\uFF0C\u53EF\u80FD\u4E0D\u662F OpenAI \u517C\u5BB9\u534F\u8BAE");
3000
+ const handleTest = useCallback(
3001
+ async (finalModel) => {
3002
+ setStep("testing");
3003
+ setErrorMsg(null);
3004
+ try {
3005
+ const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
3006
+ const resp = await fetch(url, {
3007
+ method: "POST",
3008
+ headers: {
3009
+ "Content-Type": "application/json",
3010
+ Authorization: `Bearer ${apiKey}`
3011
+ },
3012
+ body: JSON.stringify({
3013
+ model: finalModel,
3014
+ messages: [{ role: "user", content: "ping" }],
3015
+ max_tokens: 5,
3016
+ stream: false
3017
+ })
3018
+ });
3019
+ if (!resp.ok) {
3020
+ const body = await resp.text().catch(() => "");
3021
+ throw new Error(
3022
+ `HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`
3023
+ );
3024
+ }
3025
+ const data = await resp.json();
3026
+ if (!Array.isArray(data.choices)) {
3027
+ throw new Error("\u54CD\u5E94\u4E2D\u6CA1\u6709 choices \u5B57\u6BB5\uFF0C\u53EF\u80FD\u4E0D\u662F OpenAI \u517C\u5BB9\u534F\u8BAE");
3028
+ }
3029
+ setDraft("");
3030
+ setStep("tavily");
3031
+ } catch (e) {
3032
+ setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
3033
+ setStep("error");
2965
3034
  }
3035
+ },
3036
+ [apiKey, baseURL]
3037
+ );
3038
+ const finalize = useCallback(
3039
+ async (finalModel, tavilyApiKey) => {
3040
+ const contextWindow = resolveContextWindow(preset, finalModel);
2966
3041
  await saveConfig({
2967
3042
  baseURL,
2968
3043
  apiKey,
2969
- model,
3044
+ model: finalModel,
2970
3045
  provider: preset.name,
2971
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3046
+ contextWindow,
3047
+ tavilyApiKey
2972
3048
  });
3049
+ if (tavilyApiKey) {
3050
+ process.env.TAVILY_API_KEY = tavilyApiKey;
3051
+ }
2973
3052
  onSuccess({
2974
3053
  name: preset.name,
2975
3054
  baseURL,
2976
3055
  apiKey,
2977
- model,
2978
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3056
+ model: finalModel,
3057
+ contextWindow
2979
3058
  });
2980
- } catch (e) {
2981
- setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
2982
- setStep("error");
2983
- }
2984
- }, [apiKey, baseURL, model, onSuccess, preset.name]);
3059
+ },
3060
+ [apiKey, baseURL, onSuccess, preset]
3061
+ );
2985
3062
  useInput((input, key) => {
2986
3063
  if (key.escape || key.ctrl && input === "c") {
2987
3064
  exit();
@@ -3036,9 +3113,10 @@ function ConfigWizard({ onSuccess }) {
3036
3113
  }
3037
3114
  }
3038
3115
  if (key.return) {
3039
- if (draft.trim().length === 0) return;
3040
- setModel(draft.trim());
3041
- void handleTest();
3116
+ const finalModel = draft.trim();
3117
+ if (finalModel.length === 0) return;
3118
+ setModel(finalModel);
3119
+ void handleTest(finalModel);
3042
3120
  } else if (key.backspace || key.delete) {
3043
3121
  setDraft((s) => s.slice(0, -1));
3044
3122
  } else if (input && !key.meta && !key.ctrl && !key.upArrow && !key.downArrow) {
@@ -3046,6 +3124,17 @@ function ConfigWizard({ onSuccess }) {
3046
3124
  }
3047
3125
  return;
3048
3126
  }
3127
+ if (step === "tavily") {
3128
+ if (key.return) {
3129
+ const tavilyApiKey = draft.trim().length > 0 ? draft.trim() : void 0;
3130
+ void finalize(model, tavilyApiKey);
3131
+ } else if (key.backspace || key.delete) {
3132
+ setDraft((s) => s.slice(0, -1));
3133
+ } else if (input && !key.meta && !key.ctrl) {
3134
+ setDraft((s) => s + input);
3135
+ }
3136
+ return;
3137
+ }
3049
3138
  if (step === "error") {
3050
3139
  if (key.return) {
3051
3140
  enterStep("preset");
@@ -3058,7 +3147,7 @@ function ConfigWizard({ onSuccess }) {
3058
3147
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "minimal-agent \xB7 \u9996\u6B21\u914D\u7F6E\u5411\u5BFC" }) }),
3059
3148
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u6309 ESC \u6216 Ctrl+C \u9000\u51FA\uFF08\u4E0D\u5199\u914D\u7F6E\uFF0C\u4E0B\u6B21\u542F\u52A8\u7EE7\u7EED\u5411\u5BFC\uFF09" }) }),
3060
3149
  step === "preset" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3061
- /* @__PURE__ */ jsx(Text, { children: "Step 1/4 \xB7 \u9009\u62E9 provider \u9884\u8BBE\uFF08\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF09" }),
3150
+ /* @__PURE__ */ jsx(Text, { children: "Step 1/5 \xB7 \u9009\u62E9 provider \u9884\u8BBE\uFF08\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF09" }),
3062
3151
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === presetIndex ? "cyan" : void 0, children: [
3063
3152
  i === presetIndex ? "> " : " ",
3064
3153
  p.label,
@@ -3066,12 +3155,12 @@ function ConfigWizard({ onSuccess }) {
3066
3155
  ] }, p.name)) })
3067
3156
  ] }),
3068
3157
  step === "baseURL" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3069
- /* @__PURE__ */ jsx(Text, { children: "Step 2/4 \xB7 \u8F93\u5165 API base URL\uFF08\u9ED8\u8BA4\u4E3A\u9884\u8BBE\u503C\uFF0C\u53EF\u6539\uFF09" }),
3158
+ /* @__PURE__ */ jsx(Text, { children: "Step 2/5 \xB7 \u8F93\u5165 API base URL\uFF08\u9ED8\u8BA4\u4E3A\u9884\u8BBE\u503C\uFF0C\u53EF\u6539\uFF09" }),
3070
3159
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
3071
3160
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664" })
3072
3161
  ] }),
3073
3162
  step === "apiKey" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3074
- /* @__PURE__ */ jsx(Text, { children: "Step 3/4 \xB7 \u8F93\u5165 API key\uFF08\u8F93\u5165\u5185\u5BB9\u4E0D\u4F1A\u663E\u793A\uFF09" }),
3163
+ /* @__PURE__ */ jsx(Text, { children: "Step 3/5 \xB7 \u8F93\u5165 API key\uFF08\u8F93\u5165\u5185\u5BB9\u4E0D\u4F1A\u663E\u793A\uFF09" }),
3075
3164
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
3076
3165
  /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3077
3166
  "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664\uFF08\u5DF2\u8F93\u5165 ",
@@ -3081,7 +3170,7 @@ function ConfigWizard({ onSuccess }) {
3081
3170
  ] }),
3082
3171
  step === "model" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3083
3172
  /* @__PURE__ */ jsxs(Text, { children: [
3084
- "Step 4/4 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3173
+ "Step 4/5 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3085
3174
  preset.models.length > 0 ? "\uFF08\u2191\u2193 \u5207\u6362\u9884\u8BBE\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\uFF09" : ""
3086
3175
  ] }),
3087
3176
  preset.models.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: preset.models.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === modelIndex ? "cyan" : "gray", children: [
@@ -3102,6 +3191,17 @@ function ConfigWizard({ onSuccess }) {
3102
3191
  model
3103
3192
  ] })
3104
3193
  ] }),
3194
+ step === "tavily" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3195
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 LLM \u8FDE\u63A5\u6D4B\u8BD5\u901A\u8FC7" }),
3196
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: "Step 5/5 \xB7 \u53EF\u9009 - Tavily API key\uFF08\u7528\u4E8E WebSearch \u5DE5\u5177\uFF09" }) }),
3197
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
3198
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u76F4\u63A5 Enter \u8DF3\u8FC7\uFF08\u4E4B\u540E\u53EF\u5728\u5BF9\u8BDD\u91CC\u8F93\u5165 /config \u6DFB\u52A0\uFF0C\u6216\u8BBE env TAVILY_API_KEY\uFF09" }),
3199
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3200
+ "\u514D\u8D39 key \u7533\u8BF7\uFF1Ahttps://tavily.com/ \xB7 \u5DF2\u8F93\u5165 ",
3201
+ draft.length,
3202
+ " \u5B57\u7B26"
3203
+ ] })
3204
+ ] }),
3105
3205
  step === "error" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3106
3206
  /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 \u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25" }),
3107
3207
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMsg ?? "\u672A\u77E5\u9519\u8BEF" }) }),
@@ -4231,8 +4331,7 @@ async function reactiveCompactIfApplicable(messages, provider, error, state = de
4231
4331
 
4232
4332
  // src/plugins/commandRouter.ts
4233
4333
  import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
4234
- import { join as join6 } from "path";
4235
- var PLUGINS_DIR = join6(findPackageRoot(import.meta.url), "plugins");
4334
+ import { join as join7 } from "path";
4236
4335
  var pluginCache = /* @__PURE__ */ new Map();
4237
4336
  var discoveryDone = false;
4238
4337
  function stripQuotes2(s) {
@@ -4257,7 +4356,7 @@ function parseMarkdownFrontmatter(content) {
4257
4356
  }
4258
4357
  async function loadPlugin(pluginDirPath) {
4259
4358
  const dirName = pluginDirPath.split("/").pop() ?? pluginDirPath;
4260
- const manifestPath = join6(pluginDirPath, ".claude-plugin", "plugin.json");
4359
+ const manifestPath = join7(pluginDirPath, ".claude-plugin", "plugin.json");
4261
4360
  let manifestName = dirName;
4262
4361
  let manifestVersion;
4263
4362
  let manifestDesc;
@@ -4269,13 +4368,13 @@ async function loadPlugin(pluginDirPath) {
4269
4368
  manifestDesc = parsed.description;
4270
4369
  } catch {
4271
4370
  }
4272
- const commandsDir = join6(pluginDirPath, "commands");
4371
+ const commandsDir = join7(pluginDirPath, "commands");
4273
4372
  const commands = [];
4274
4373
  try {
4275
4374
  const entries = await readdir3(commandsDir, { withFileTypes: true });
4276
4375
  for (const entry of entries) {
4277
4376
  if (!entry.name.endsWith(".md")) continue;
4278
- const cmdPath = join6(commandsDir, entry.name);
4377
+ const cmdPath = join7(commandsDir, entry.name);
4279
4378
  try {
4280
4379
  const content = await readFile9(cmdPath, "utf8");
4281
4380
  const fm = parseMarkdownFrontmatter(content);
@@ -4294,7 +4393,7 @@ async function loadPlugin(pluginDirPath) {
4294
4393
  }
4295
4394
  } catch {
4296
4395
  }
4297
- const hooksJsonPath = join6(pluginDirPath, "hooks", "hooks.json");
4396
+ const hooksJsonPath = join7(pluginDirPath, "hooks", "hooks.json");
4298
4397
  let hasStopHook = false;
4299
4398
  try {
4300
4399
  const hooksRaw = await readFile9(hooksJsonPath, "utf8");
@@ -4321,17 +4420,20 @@ async function discoverPlugins() {
4321
4420
  }
4322
4421
  pluginCache.clear();
4323
4422
  discoveryDone = true;
4324
- try {
4325
- const entries = await readdir3(PLUGINS_DIR, { withFileTypes: true });
4326
- for (const entry of entries) {
4327
- if (!entry.isDirectory()) continue;
4328
- if (entry.name.startsWith(".")) continue;
4329
- const plugin = await loadPlugin(join6(PLUGINS_DIR, entry.name));
4330
- if (plugin) {
4423
+ const searchPaths = getResourceSearchPaths("plugins", import.meta.url);
4424
+ for (const root of searchPaths) {
4425
+ try {
4426
+ const entries = await readdir3(root, { withFileTypes: true });
4427
+ for (const entry of entries) {
4428
+ if (!entry.isDirectory()) continue;
4429
+ if (entry.name.startsWith(".")) continue;
4430
+ const plugin = await loadPlugin(join7(root, entry.name));
4431
+ if (!plugin) continue;
4432
+ if (pluginCache.has(plugin.name)) continue;
4331
4433
  pluginCache.set(plugin.name, plugin);
4332
4434
  }
4435
+ } catch {
4333
4436
  }
4334
- } catch {
4335
4437
  }
4336
4438
  return Array.from(pluginCache.values());
4337
4439
  }
@@ -4391,10 +4493,10 @@ function getActiveStopHookPlugins() {
4391
4493
 
4392
4494
  // src/plugins/stopHook.ts
4393
4495
  import { readFile as readFile10 } from "fs/promises";
4394
- import { join as join7 } from "path";
4496
+ import { join as join8 } from "path";
4395
4497
  import { spawn as spawn4 } from "child_process";
4396
4498
  async function loadStopHookConfig(pluginRoot) {
4397
- const hooksJsonPath = join7(pluginRoot, "hooks", "hooks.json");
4499
+ const hooksJsonPath = join8(pluginRoot, "hooks", "hooks.json");
4398
4500
  try {
4399
4501
  const raw = await readFile10(hooksJsonPath, "utf8");
4400
4502
  const parsed = JSON.parse(raw);
@@ -4433,7 +4535,7 @@ async function executeStopHooks(pluginRoots, transcriptText) {
4433
4535
  }
4434
4536
  function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4435
4537
  const resolvedCommand = hookConfig.command.replaceAll("${CLAUDE_PLUGIN_ROOT}", pluginRoot);
4436
- return new Promise((resolve9) => {
4538
+ return new Promise((resolve10) => {
4437
4539
  const child = spawn4("bash", [resolvedCommand], {
4438
4540
  env: {
4439
4541
  ...process.env,
@@ -4445,31 +4547,31 @@ function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4445
4547
  stdout += data.toString();
4446
4548
  });
4447
4549
  child.on("error", () => {
4448
- resolve9({ decision: "pass" });
4550
+ resolve10({ decision: "pass" });
4449
4551
  });
4450
4552
  child.on("close", (code) => {
4451
4553
  if (code !== 0) {
4452
- resolve9({ decision: "pass" });
4554
+ resolve10({ decision: "pass" });
4453
4555
  return;
4454
4556
  }
4455
4557
  const trimmed = stdout.trim();
4456
4558
  if (!trimmed) {
4457
- resolve9({ decision: "pass" });
4559
+ resolve10({ decision: "pass" });
4458
4560
  return;
4459
4561
  }
4460
4562
  try {
4461
4563
  const parsed = JSON.parse(trimmed);
4462
4564
  if (parsed.decision === "block") {
4463
- resolve9({
4565
+ resolve10({
4464
4566
  decision: "block",
4465
4567
  reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
4466
4568
  systemMessage: typeof parsed.systemMessage === "string" ? parsed.systemMessage : void 0
4467
4569
  });
4468
4570
  return;
4469
4571
  }
4470
- resolve9({ decision: "pass" });
4572
+ resolve10({ decision: "pass" });
4471
4573
  } catch {
4472
- resolve9({ decision: "pass" });
4574
+ resolve10({ decision: "pass" });
4473
4575
  }
4474
4576
  });
4475
4577
  child.stdin.write(transcriptText);
@@ -4478,7 +4580,7 @@ function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4478
4580
  }
4479
4581
 
4480
4582
  // src/plugins/verificationGate.ts
4481
- import { existsSync as existsSync7, readFileSync } from "fs";
4583
+ import { existsSync as existsSync8, readFileSync } from "fs";
4482
4584
  import { spawn as spawn5 } from "child_process";
4483
4585
  function parseVerifyArg(arg) {
4484
4586
  const colonIdx = arg.indexOf(":");
@@ -4520,7 +4622,7 @@ function parseVerifyArgs(args) {
4520
4622
  return checks;
4521
4623
  }
4522
4624
  function runShell(command, timeout) {
4523
- return new Promise((resolve9) => {
4625
+ return new Promise((resolve10) => {
4524
4626
  const isWin = process.platform === "win32";
4525
4627
  const child = isWin ? spawn5("cmd", ["/c", command], { timeout, env: process.env }) : spawn5("bash", ["-c", command], { timeout, env: process.env });
4526
4628
  let stdout = "";
@@ -4532,10 +4634,10 @@ function runShell(command, timeout) {
4532
4634
  stderr += d.toString();
4533
4635
  });
4534
4636
  child.on("error", () => {
4535
- resolve9({ exitCode: null, stdout, stderr, errored: true });
4637
+ resolve10({ exitCode: null, stdout, stderr, errored: true });
4536
4638
  });
4537
4639
  child.on("close", (code) => {
4538
- resolve9({ exitCode: code, stdout, stderr, errored: false });
4640
+ resolve10({ exitCode: code, stdout, stderr, errored: false });
4539
4641
  });
4540
4642
  });
4541
4643
  }
@@ -4555,7 +4657,7 @@ async function verifyShell(command, timeout) {
4555
4657
  };
4556
4658
  }
4557
4659
  function verifyFileExists(file) {
4558
- const exists = existsSync7(file);
4660
+ const exists = existsSync8(file);
4559
4661
  return {
4560
4662
  check: { type: "file_exists", file },
4561
4663
  passed: exists,
@@ -4656,8 +4758,8 @@ function formatCheckName(check) {
4656
4758
 
4657
4759
  // src/plugins/goalState.ts
4658
4760
  import { mkdir as mkdir6, appendFile, writeFile as writeFile6, unlink as unlink2, rmdir as rmdir2 } from "fs/promises";
4659
- import { existsSync as existsSync8, readFileSync as readFileSync2 } from "fs";
4660
- import { join as join8 } from "path";
4761
+ import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
4762
+ import { join as join9 } from "path";
4661
4763
  var Phase = /* @__PURE__ */ ((Phase2) => {
4662
4764
  Phase2["PLAN"] = "plan";
4663
4765
  Phase2["BUILD"] = "build";
@@ -4699,13 +4801,13 @@ var GoalState = class {
4699
4801
  dir;
4700
4802
  constructor(workspaceDir, sessionTag) {
4701
4803
  const suffix = sessionTag ? `-${sessionTag}` : "";
4702
- this.dir = join8(workspaceDir, `.minimal-agent${suffix}`);
4804
+ this.dir = join9(workspaceDir, `.minimal-agent${suffix}`);
4703
4805
  }
4704
4806
  async reset() {
4705
4807
  const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4706
4808
  for (const f of files) {
4707
4809
  try {
4708
- await unlink2(join8(this.dir, f));
4810
+ await unlink2(join9(this.dir, f));
4709
4811
  } catch {
4710
4812
  }
4711
4813
  }
@@ -4724,22 +4826,22 @@ ${goal}
4724
4826
  decisions: ""
4725
4827
  };
4726
4828
  for (const [name, content] of Object.entries(files)) {
4727
- const path2 = join8(this.dir, `${name}.md`);
4728
- if (!existsSync8(path2)) {
4829
+ const path2 = join9(this.dir, `${name}.md`);
4830
+ if (!existsSync9(path2)) {
4729
4831
  await writeFile6(path2, content);
4730
4832
  }
4731
4833
  }
4732
4834
  }
4733
4835
  get goal() {
4734
4836
  try {
4735
- return readFileSync2(join8(this.dir, "goal.md"), "utf8").trim();
4837
+ return readFileSync2(join9(this.dir, "goal.md"), "utf8").trim();
4736
4838
  } catch {
4737
4839
  return "";
4738
4840
  }
4739
4841
  }
4740
4842
  get completionCriteria() {
4741
4843
  try {
4742
- const raw = readFileSync2(join8(this.dir, "completion.md"), "utf8").trim();
4844
+ const raw = readFileSync2(join9(this.dir, "completion.md"), "utf8").trim();
4743
4845
  return JSON.parse(raw);
4744
4846
  } catch {
4745
4847
  return [];
@@ -4747,7 +4849,7 @@ ${goal}
4747
4849
  }
4748
4850
  get currentPhase() {
4749
4851
  try {
4750
- const raw = readFileSync2(join8(this.dir, "phase.md"), "utf8").trim();
4852
+ const raw = readFileSync2(join9(this.dir, "phase.md"), "utf8").trim();
4751
4853
  if (VALID_PHASES.has(raw)) {
4752
4854
  return raw;
4753
4855
  }
@@ -4770,24 +4872,24 @@ ${goal}
4770
4872
  `\u975E\u6CD5\u9636\u6BB5\u5207\u6362: ${current} \u2192 ${phase}\u3002\u8BE5\u76EE\u6807\u9636\u6BB5\u4E0D\u5728 PHASE_TRANSITIONS[${current}] \u7684\u53EF\u8FBE\u96C6\u5408\u5185\uFF0C\u9700\u8981\u8D70 forceSetPhase\u3002`
4771
4873
  );
4772
4874
  }
4773
- await writeFile6(join8(this.dir, "phase.md"), phase);
4875
+ await writeFile6(join9(this.dir, "phase.md"), phase);
4774
4876
  await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4775
4877
  }
4776
4878
  async forceSetPhase(phase, reason) {
4777
4879
  if (!VALID_PHASES.has(phase)) {
4778
4880
  throw new Error(`Invalid phase: ${phase}`);
4779
4881
  }
4780
- await writeFile6(join8(this.dir, "phase.md"), phase);
4882
+ await writeFile6(join9(this.dir, "phase.md"), phase);
4781
4883
  await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4782
4884
  }
4783
4885
  async appendProgress(line) {
4784
4886
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
4785
- await appendFile(join8(this.dir, "progress.md"), `[${timestamp}] ${line}
4887
+ await appendFile(join9(this.dir, "progress.md"), `[${timestamp}] ${line}
4786
4888
  `);
4787
4889
  }
4788
4890
  tailProgress(n) {
4789
4891
  try {
4790
- const content = readFileSync2(join8(this.dir, "progress.md"), "utf8");
4892
+ const content = readFileSync2(join9(this.dir, "progress.md"), "utf8");
4791
4893
  const lines = content.trim().split("\n").filter(Boolean);
4792
4894
  return lines.slice(-n).join("\n");
4793
4895
  } catch {
@@ -4795,12 +4897,12 @@ ${goal}
4795
4897
  }
4796
4898
  }
4797
4899
  async appendLearning(lesson) {
4798
- await appendFile(join8(this.dir, "learnings.md"), `- ${lesson}
4900
+ await appendFile(join9(this.dir, "learnings.md"), `- ${lesson}
4799
4901
  `);
4800
4902
  }
4801
4903
  get learnings() {
4802
4904
  try {
4803
- const raw = readFileSync2(join8(this.dir, "learnings.md"), "utf8").trim();
4905
+ const raw = readFileSync2(join9(this.dir, "learnings.md"), "utf8").trim();
4804
4906
  if (!raw) return "";
4805
4907
  const lines = raw.split("\n").filter(Boolean);
4806
4908
  return lines.slice(-LEARNINGS_TAIL_LINES).join("\n");
@@ -4818,12 +4920,12 @@ ${goal}
4818
4920
  reasoning
4819
4921
  };
4820
4922
  const line = JSON.stringify(entry);
4821
- await appendFile(join8(this.dir, "decisions.md"), `${line}
4923
+ await appendFile(join9(this.dir, "decisions.md"), `${line}
4822
4924
  `);
4823
4925
  }
4824
4926
  findSimilarDecisions(ctx, k = 3) {
4825
4927
  try {
4826
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4928
+ const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
4827
4929
  const lines = content.trim().split("\n").filter(Boolean);
4828
4930
  const entries = [];
4829
4931
  for (const line of lines) {
@@ -4843,7 +4945,7 @@ ${goal}
4843
4945
  }
4844
4946
  summarizeDecisions(maxEntries = 5) {
4845
4947
  try {
4846
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4948
+ const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
4847
4949
  const lines = content.trim().split("\n").filter(Boolean);
4848
4950
  const entries = [];
4849
4951
  for (const line of lines.slice(-maxEntries * 2)) {
@@ -4889,7 +4991,7 @@ ${recentProgress}`);
4889
4991
  const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4890
4992
  for (const f of files) {
4891
4993
  try {
4892
- await unlink2(join8(this.dir, f));
4994
+ await unlink2(join9(this.dir, f));
4893
4995
  } catch {
4894
4996
  }
4895
4997
  }
@@ -5715,13 +5817,13 @@ function handleEvent2(event, output, verbose) {
5715
5817
  }
5716
5818
  }
5717
5819
  function readFromStdin() {
5718
- return new Promise((resolve9) => {
5820
+ return new Promise((resolve10) => {
5719
5821
  let data = "";
5720
5822
  let settled = false;
5721
5823
  const timer = setTimeout(() => {
5722
5824
  if (!settled) {
5723
5825
  settled = true;
5724
- resolve9("");
5826
+ resolve10("");
5725
5827
  }
5726
5828
  }, STDIN_TIMEOUT_MS);
5727
5829
  process.stdin.setEncoding("utf8");
@@ -5732,7 +5834,7 @@ function readFromStdin() {
5732
5834
  if (!settled) {
5733
5835
  clearTimeout(timer);
5734
5836
  settled = true;
5735
- resolve9(data.trim());
5837
+ resolve10(data.trim());
5736
5838
  }
5737
5839
  }
5738
5840
  process.stdin.on("data", onData);
@@ -5748,14 +5850,15 @@ async function main() {
5748
5850
  const args = process.argv.slice(2);
5749
5851
  const dirArg = extractCwdArg(args);
5750
5852
  if (dirArg) {
5751
- const abs = resolve8(dirArg);
5752
- if (!existsSync9(abs)) {
5853
+ const abs = resolve9(dirArg);
5854
+ if (!existsSync10(abs)) {
5753
5855
  mkdirSync(abs, { recursive: true });
5754
5856
  }
5755
5857
  process.chdir(abs);
5756
5858
  }
5757
5859
  initWorkingDir();
5758
5860
  await migrateLegacyContext(getWorkingDir());
5861
+ await applyToolKeysToEnv();
5759
5862
  if (args.includes("-h") || args.includes("--help")) {
5760
5863
  printHelp();
5761
5864
  return;