minimal-agent 0.1.7 → 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");
@@ -360,6 +395,8 @@ ${toolList}
360
395
  "\u6211\u8BB0\u5F97"\u3001"\u6211\u4E4B\u524D\u770B\u8FC7"\u3001"\u5E94\u8BE5\u5DEE\u4E0D\u591A"\u90FD\u4E0D\u7B97\u8BFB\u8FC7\u2014\u2014\u5FC5\u987B\u5728\u672C\u8F6E\u4EFB\u52A1\u4E2D\u5B9E\u9645\u8C03\u7528 Read\u3002
361
396
  \u5C24\u5176\u5F53\u4F60\u8981\u53C2\u8003\u67D0\u4E2A\u6587\u4EF6\u7684\u5199\u6CD5\u6765\u5B9E\u73B0\u7C7B\u4F3C\u529F\u80FD\u65F6\uFF0C\u5FC5\u987B\u5148\u91CD\u8BFB\u8BE5\u6587\u4EF6\u7684\u5173\u952E\u90E8\u5206\uFF08\u51FD\u6570\u5B9E\u73B0\u3001\u5224\u65AD\u903B\u8F91\u3001\u6570\u636E\u6D41\uFF09\uFF0C\u7406\u89E3\u6E05\u695A\u540E\u518D\u52A8\u624B\u3002
362
397
  - Edit \u5DE5\u5177\u7684 old_string \u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF1B\u4E0D\u552F\u4E00\u65F6\u8BF7\u6269\u5927\u4E0A\u4E0B\u6587\u6216\u663E\u5F0F replace_all=true\u3002
398
+ - \u540C\u4E00\u6587\u4EF6\u9700\u8981\u4FEE\u6539\u591A\u5904\uFF083 \u5904\u53CA\u4EE5\u4E0A\uFF09\u65F6\u4F18\u5148\u7528 MultiEdit\uFF1A\u6240\u6709 edit \u6309\u987A\u5E8F\u5728\u5185\u5B58\u4E2D\u5E94\u7528\uFF0C**\u5168\u90E8\u6210\u529F\u624D\u843D\u76D8**\uFF1B\u4EFB\u4E00\u5931\u8D25\u78C1\u76D8\u4E0D\u52A8\uFF0C\u907F\u514D\u4E2D\u95F4\u72B6\u6001\u6C61\u67D3\u3002\u5355\u70B9\u4FEE\u6539\u7EE7\u7EED\u7528 Edit\u3002
399
+ - Write \u8986\u76D6\u65E2\u6709\u6587\u4EF6\u524D\u540C\u6837\u5FC5\u987B\u5148 Read\uFF08\u4E0E Edit \u5BF9\u79F0\uFF0C\u672A\u5148 Read \u4F1A\u88AB\u62D2\u7EDD\u5E76\u63D0\u793A"\u8BF7\u5148 Read"\uFF09\uFF1B\u5199\u65B0\u6587\u4EF6\u65E0\u6B64\u8981\u6C42\u3002
363
400
  - \u521B\u5EFA\u65B0\u6587\u4EF6\u7528 Write\uFF0C\u6216 Edit \u65F6 old_string \u4F20\u7A7A\u5B57\u7B26\u4E32\u3002
364
401
  - \u627E\u6587\u4EF6\u7528 Glob\uFF08"**/*.ts"\uFF09\uFF0C\u627E\u6587\u4EF6\u5185\u5BB9\u7528 Grep\uFF08\u57FA\u4E8E ripgrep\uFF09\u3002
365
402
  - \u5F53\u7528\u6237\u95EE\u5230\u8BAD\u7EC3\u622A\u6B62\u540E\u624D\u51FA\u73B0\u7684\u4FE1\u606F\uFF08\u6700\u65B0\u7248\u672C\u53F7\u3001\u8FD1\u671F\u65B0\u95FB\u3001\u7B2C\u4E09\u65B9 API \u6587\u6863\uFF09\u65F6\u7528 WebSearch\uFF1B\u4F18\u5148\u7CBE\u786E\u7684\u81EA\u7136\u8BED\u8A00\u67E5\u8BE2\u3002
@@ -369,6 +406,8 @@ ${toolList}
369
406
  Bash \u6709\u5B89\u5168\u9ED1\u540D\u5355\uFF08rm -rf /\u3001mkfs\u3001shutdown \u7B49\u5371\u9669\u547D\u4EE4\u4F1A\u88AB\u62E6\u622A\uFF09\uFF0C\u4F46\u4ECD\u9700\u8C28\u614E\uFF1A
370
407
  \u5148\u786E\u8BA4\u547D\u4EE4\u65E0\u5BB3\u518D\u6267\u884C\uFF0C\u907F\u514D\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u5982 git push --force\u3001git reset --hard\uFF09\u3002
371
408
  \u957F\u65F6\u95F4\u8FD0\u884C\u7684\u547D\u4EE4\uFF08npm install\u3001bun test\uFF09\u6CE8\u610F\u8D85\u65F6\u8BBE\u7F6E\uFF1B\u9700\u8981\u4EA4\u4E92\u8F93\u5165\u7684\u547D\u4EE4\u4E0D\u8981\u7528 Bash\uFF08\u7528 Write \u5199\u811A\u672C\u4EE3\u66FF\uFF09\u3002
409
+ Bash \u5DF2\u8BC6\u522B"\u4FE1\u606F\u6027\u9000\u51FA\u7801"\uFF1Agrep/rg/find/diff/test \u7684 exit=1\uFF08\u65E0\u5339\u914D / \u90E8\u5206\u4E0D\u53EF\u8BBF\u95EE / \u6709\u5DEE\u5F02 / \u6761\u4EF6\u5047\uFF09\u4F1A\u81EA\u52A8\u5224\u4E3A\u6210\u529F\uFF0C\u4E0D\u8981\u56E0 1 \u800C\u91CD\u8BD5\u3002
410
+ \u7834\u574F\u6027\u547D\u4EE4\uFF08git reset --hard / git push -f / rm -rf \u7B49\uFF09\u4F1A\u88AB Bash \u4E3B\u52A8\u5728\u8F93\u51FA\u5934\u90E8\u52A0 \u26A0\uFE0F \u8B66\u544A\uFF08\u4E0D\u62E6\u622A\uFF09\uFF0C\u770B\u5230\u65F6\u5E94\u5411\u7528\u6237\u786E\u8BA4\u610F\u56FE\u3002
372
411
  - \u5F53\u9700\u8981\u83B7\u53D6\u7F51\u9875\u9759\u6001\u6587\u672C\u5185\u5BB9\uFF08\u6293\u53D6\u6587\u6863\u3001\u8BFB\u53D6\u6587\u7AE0\uFF09\u65F6\u7528 WebFetch\u3002
373
412
  WebBrowser \u4F9D\u8D56\u53EF\u9009\u5305\uFF08playwright-core + chromium\uFF09\u2014\u2014 **\u9ED8\u8BA4\u5047\u5B9A\u672A\u5B89\u88C5**\uFF0C
374
413
  \u4EC5\u5728 WebFetch \u660E\u786E\u65E0\u6CD5\u6EE1\u8DB3\uFF08\u5982\u9700\u8981 JS \u6E32\u67D3\u540E\u7684\u5185\u5BB9\u3001\u70B9\u51FB\u6309\u94AE\u3001\u586B\u8868\u5355\u3001\u622A\u56FE\uFF09\u65F6\u518D\u5C1D\u8BD5\u3002
@@ -378,6 +417,18 @@ ${toolList}
378
417
  # \u6280\u80FD\u7CFB\u7EDF\uFF08\u79EF\u6781\u4F7F\u7528\uFF09
379
418
  ${skillHint}
380
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
+
381
432
  # \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
382
433
  ## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
383
434
  ### \u573A\u666F 1\uFF1A\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9
@@ -713,6 +764,14 @@ While the Bash tool can do similar things, it's better to use the built-in tools
713
764
  - Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
714
765
  - Avoid unnecessary \`sleep\` commands; do not retry failing commands in a sleep loop \u2014 diagnose the root cause.
715
766
 
767
+ # Exit code semantics
768
+ Some commands return non-zero exit codes for informational (non-error) reasons. Bash recognizes these and reports them as success (ok=true) \u2014 do NOT retry just because exit code is 1:
769
+ - \`grep\` / \`rg\` exit 1 \u2192 no match found (not an error)
770
+ - \`find\` exit 1 \u2192 some directories inaccessible (non-fatal, partial results still returned)
771
+ - \`diff\` / \`cmp\` exit 1 \u2192 files differ (informational, not an error)
772
+ - \`test\` / \`[\` exit 1 \u2192 condition is false (the answer to a question, not a failure)
773
+ Only exit codes \u2265 2 from these commands indicate a real failure. For all other commands, non-zero exit codes are treated as failures normally.
774
+
716
775
  # Safety
717
776
  The following command patterns are blocked at the tool level and will fail before execution (no need to try them):
718
777
  - \`rm -rf /\` and variants targeting root, $HOME, ~, or system directories (/etc, /usr, /bin, /Windows, /Users, /home, ...)
@@ -722,7 +781,9 @@ The following command patterns are blocked at the tool level and will fail befor
722
781
  - Pipe-to-shell from network: \`curl ... | sh\`, \`wget ... | bash\`, etc.
723
782
  - \`chmod 777 /\`, Windows full-disk \`del /s\` / \`rmdir /s\`, \`diskpart\`
724
783
 
725
- If you have a legitimate use case that requires one of the above patterns, ask the user to run the command themselves in their terminal \u2014 do not try to bypass the check.`;
784
+ If you have a legitimate use case that requires one of the above patterns, ask the user to run the command themselves in their terminal \u2014 do not try to bypass the check.
785
+
786
+ Separately, Bash scans for common destructive-but-recoverable patterns (\`git reset --hard\`, \`git push -f\` / \`--force\` / \`--force-with-lease\`, \`git checkout .\`, \`git restore .\`, \`git clean -f\`, \`git stash drop/clear\`, \`git branch -D\`, \`git commit --amend\` / \`--no-verify\`, \`rm -rf <path>\`, \`DROP TABLE\`, \`TRUNCATE\`, \`DELETE FROM\`, \`kubectl delete\`, \`terraform destroy\`, etc.) and prepends a \`\u26A0\uFE0F \u8B66\u544A:\` line to the output. These commands are NOT blocked \u2014 the warning is informational. Treat it as a signal to double-check intent and surface the warning to the user when relevant.`;
726
787
  async function call(input, signal) {
727
788
  const command = input.command;
728
789
  const timeoutMs = Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
@@ -847,14 +908,14 @@ var bashTool = {
847
908
 
848
909
  // src/tools/edit/edit.ts
849
910
  import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
850
- import { existsSync as existsSync3 } from "fs";
911
+ import { existsSync as existsSync4 } from "fs";
851
912
  import { dirname as dirname5 } from "path";
852
- import { z as z2 } from "zod";
913
+ import { z as z3 } from "zod";
853
914
 
854
915
  // src/tools/shared/fileUtils.ts
855
916
  import { open, readFile as readFile5 } from "fs/promises";
856
917
  import { homedir as homedir4 } from "os";
857
- import { extname, resolve as resolve4 } from "path";
918
+ import { extname, resolve as resolve5 } from "path";
858
919
  var BLOCKED_DEVICE_PATHS = /* @__PURE__ */ new Set([
859
920
  "/dev/zero",
860
921
  "/dev/random",
@@ -890,7 +951,7 @@ function validateAndResolvePath(rawPath, workingDir) {
890
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` };
891
952
  }
892
953
  const expanded = expandPath(rawPath);
893
- const resolved = resolve4(workingDir, expanded);
954
+ const resolved = resolve5(workingDir, expanded);
894
955
  if (isBlockedDevicePath(resolved)) {
895
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` };
896
957
  }
@@ -901,10 +962,13 @@ function validateAndResolvePath(rawPath, workingDir) {
901
962
  }
902
963
  function expandPath(p) {
903
964
  if (p.startsWith("~/") || p === "~") {
904
- return resolve4(homedir4(), p.slice(2));
965
+ return resolve5(homedir4(), p.slice(2));
905
966
  }
906
967
  return p;
907
968
  }
969
+ function resolveToolPath(rawPath) {
970
+ return validateAndResolvePath(rawPath, getWorkingDir());
971
+ }
908
972
  function detectLineEndingsForString(content) {
909
973
  let crlfCount = 0;
910
974
  let lfCount = 0;
@@ -1094,9 +1158,28 @@ function applyCurlySingleQuotes(str) {
1094
1158
  }
1095
1159
  return result.join("");
1096
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
+ }
1097
1180
 
1098
1181
  // src/tools/shared/fileState.ts
1099
- import { existsSync as existsSync2, statSync } from "fs";
1182
+ import { existsSync as existsSync3, statSync } from "fs";
1100
1183
  var fileState = /* @__PURE__ */ new Map();
1101
1184
  function recordRead(absPath) {
1102
1185
  try {
@@ -1108,7 +1191,7 @@ function recordRead(absPath) {
1108
1191
  function assertFresh(absPath) {
1109
1192
  const entry = fileState.get(absPath);
1110
1193
  if (!entry) {
1111
- if (existsSync2(absPath)) {
1194
+ if (existsSync3(absPath)) {
1112
1195
  return {
1113
1196
  ok: false,
1114
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`
@@ -1135,13 +1218,13 @@ function clearFileState() {
1135
1218
 
1136
1219
  // src/tools/edit/edit.ts
1137
1220
  var MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
1138
- var inputSchema2 = z2.object({
1139
- file_path: z2.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
1140
- old_string: z2.string().describe(
1221
+ var inputSchema2 = z3.object({
1222
+ file_path: filePathField("\u7F16\u8F91"),
1223
+ old_string: z3.string().describe(
1141
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"
1142
1225
  ),
1143
- new_string: z2.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
1144
- 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")
1145
1228
  });
1146
1229
  var parameters2 = toToolParameters(inputSchema2);
1147
1230
  var description2 = `Performs exact string replacements in files.
@@ -1150,7 +1233,8 @@ Usage:
1150
1233
  - You MUST use your \`Read\` tool to read the current content of the file BEFORE calling Edit.
1151
1234
  Memory is unreliable \u2014 if you think you "remember" how the code looks, you are probably wrong.
1152
1235
  Re-read the relevant sections (function, module, or area you plan to change) every time.
1153
- - 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.
1154
1238
  - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
1155
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\`.
1156
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.
@@ -1158,7 +1242,7 @@ Usage:
1158
1242
  async function call2(input) {
1159
1243
  const { old_string, new_string } = input;
1160
1244
  const replaceAll = input.replace_all ?? false;
1161
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1245
+ const pathResult = resolveToolPath(input.file_path);
1162
1246
  if (!pathResult.ok) return pathResult;
1163
1247
  const filePath = pathResult.resolvedPath;
1164
1248
  const freshness = assertFresh(filePath);
@@ -1168,10 +1252,11 @@ async function call2(input) {
1168
1252
  if (old_string === new_string) {
1169
1253
  return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
1170
1254
  }
1171
- if (old_string === "" && !existsSync3(filePath)) {
1255
+ if (old_string === "" && !existsSync4(filePath)) {
1172
1256
  try {
1173
1257
  await mkdir4(dirname5(filePath), { recursive: true });
1174
1258
  await writeFile3(filePath, new_string, "utf8");
1259
+ recordRead(filePath);
1175
1260
  return {
1176
1261
  ok: true,
1177
1262
  content: `\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6\uFF1A${filePath}\uFF08${new_string.length} \u5B57\u7B26\uFF09`
@@ -1180,7 +1265,7 @@ async function call2(input) {
1180
1265
  return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
1181
1266
  }
1182
1267
  }
1183
- if (!existsSync3(filePath)) {
1268
+ if (!existsSync4(filePath)) {
1184
1269
  return {
1185
1270
  ok: false,
1186
1271
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1236,6 +1321,7 @@ async function call2(input) {
1236
1321
  const normalizedReplaced = applyLineEnding(replaced, originalLineEnding);
1237
1322
  try {
1238
1323
  await writeFile3(filePath, normalizedReplaced, "utf8");
1324
+ recordRead(filePath);
1239
1325
  } catch (e) {
1240
1326
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1241
1327
  }
@@ -1248,19 +1334,6 @@ async function call2(input) {
1248
1334
  \u884C\u6570\uFF1A${linesBefore} \u2192 ${linesAfter}`
1249
1335
  };
1250
1336
  }
1251
- function countOccurrences(haystack, needle) {
1252
- if (needle.length === 0) return 0;
1253
- let count = 0;
1254
- let pos = 0;
1255
- while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1256
- count++;
1257
- pos += needle.length;
1258
- }
1259
- return count;
1260
- }
1261
- function splitReplaceAll(haystack, needle, replacement) {
1262
- return haystack.split(needle).join(replacement);
1263
- }
1264
1337
  function findFuzzyMatchHint(fileContent, target) {
1265
1338
  const MIN_OVERLAP_RATIO = 0.5;
1266
1339
  const MAX_HINTS = 3;
@@ -1335,16 +1408,16 @@ var editTool = {
1335
1408
 
1336
1409
  // src/tools/edit/multi-edit.ts
1337
1410
  import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
1338
- import { existsSync as existsSync4 } from "fs";
1339
- import { z as z3 } from "zod";
1340
- var editItemSchema = z3.object({
1341
- 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"),
1342
- new_string: z3.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C"),
1343
- 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")
1344
1417
  });
1345
- var inputSchema3 = z3.object({
1346
- file_path: z3.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
1347
- 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")
1348
1421
  });
1349
1422
  var parameters3 = toToolParameters(inputSchema3);
1350
1423
  var description3 = `Performs multiple exact string replacements in a single file, applied atomically (all-or-nothing).
@@ -1357,28 +1430,15 @@ Usage:
1357
1430
  - Each \`old_string\` must be unique in the current content (after prior edits) unless \`replace_all=true\`.
1358
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.
1359
1432
  - Preserve exact indentation (tabs/spaces).`;
1360
- function countOccurrences2(haystack, needle) {
1361
- if (needle.length === 0) return 0;
1362
- let count = 0;
1363
- let pos = 0;
1364
- while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1365
- count++;
1366
- pos += needle.length;
1367
- }
1368
- return count;
1369
- }
1370
- function splitReplaceAll2(haystack, needle, replacement) {
1371
- return haystack.split(needle).join(replacement);
1372
- }
1373
1433
  async function call3(input) {
1374
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1434
+ const pathResult = resolveToolPath(input.file_path);
1375
1435
  if (!pathResult.ok) return pathResult;
1376
1436
  const filePath = pathResult.resolvedPath;
1377
1437
  const freshness = assertFresh(filePath);
1378
1438
  if (!freshness.ok) {
1379
1439
  return { ok: false, error: freshness.error };
1380
1440
  }
1381
- if (!existsSync4(filePath)) {
1441
+ if (!existsSync5(filePath)) {
1382
1442
  return {
1383
1443
  ok: false,
1384
1444
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1419,7 +1479,7 @@ async function call3(input) {
1419
1479
  searchTarget = actualOld;
1420
1480
  processedNewString = preserveQuoteStyle(edit.old_string, actualOld, edit.new_string);
1421
1481
  }
1422
- const occurrences = countOccurrences2(currentContent, searchTarget);
1482
+ const occurrences = countOccurrences(currentContent, searchTarget);
1423
1483
  if (occurrences === 0) {
1424
1484
  return {
1425
1485
  ok: false,
@@ -1432,12 +1492,13 @@ async function call3(input) {
1432
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`
1433
1493
  };
1434
1494
  }
1435
- currentContent = replaceAll ? splitReplaceAll2(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1495
+ currentContent = replaceAll ? splitReplaceAll(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1436
1496
  }
1437
1497
  const lineEnding = await detectFileLineEndings(filePath);
1438
1498
  const finalContent = applyLineEnding(currentContent, lineEnding);
1439
1499
  try {
1440
1500
  await writeFile4(filePath, finalContent, "utf8");
1501
+ recordRead(filePath);
1441
1502
  } catch (e) {
1442
1503
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1443
1504
  }
@@ -1459,12 +1520,12 @@ var multiEditTool = {
1459
1520
 
1460
1521
  // src/tools/glob/glob.ts
1461
1522
  import { stat as stat2 } from "fs/promises";
1462
- import { isAbsolute, resolve as resolve5 } from "path";
1523
+ import { isAbsolute, resolve as resolve6 } from "path";
1463
1524
  import fg from "fast-glob";
1464
- import { z as z4 } from "zod";
1465
- var inputSchema4 = z4.object({
1466
- pattern: z4.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
1467
- 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')
1468
1529
  });
1469
1530
  var parameters4 = toToolParameters(inputSchema4);
1470
1531
  var description4 = `- Fast file pattern matching tool that works with any codebase size
@@ -1473,7 +1534,7 @@ var description4 = `- Fast file pattern matching tool that works with any codeba
1473
1534
  - Use this tool when you need to find files by name patterns
1474
1535
  - When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
1475
1536
  async function call4(input) {
1476
- const cwd = input.path ? resolve5(input.path) : getWorkingDir();
1537
+ const cwd = input.path ? resolve6(input.path) : getWorkingDir();
1477
1538
  const pattern = input.pattern.replace(/\\/g, "/");
1478
1539
  let matches;
1479
1540
  try {
@@ -1494,7 +1555,7 @@ async function call4(input) {
1494
1555
  }
1495
1556
  const withMtime = await Promise.all(
1496
1557
  matches.map(async (rel) => {
1497
- const abs = isAbsolute(rel) ? rel : resolve5(cwd, rel);
1558
+ const abs = isAbsolute(rel) ? rel : resolve6(cwd, rel);
1498
1559
  try {
1499
1560
  const st = await stat2(abs);
1500
1561
  return { path: rel, mtime: st.mtimeMs };
@@ -1530,13 +1591,13 @@ var globTool = {
1530
1591
 
1531
1592
  // src/tools/grep/grep.ts
1532
1593
  import { spawn as spawn3 } from "child_process";
1533
- import { resolve as resolve7 } from "path";
1534
- import { z as z5 } from "zod";
1594
+ import { resolve as resolve8 } from "path";
1595
+ import { z as z6 } from "zod";
1535
1596
 
1536
1597
  // src/tools/grep/rgPath.ts
1537
1598
  import { spawn as spawn2 } from "child_process";
1538
- import { chmodSync, existsSync as existsSync5 } from "fs";
1539
- import { resolve as resolve6 } from "path";
1599
+ import { chmodSync, existsSync as existsSync6 } from "fs";
1600
+ import { resolve as resolve7 } from "path";
1540
1601
  var cached;
1541
1602
  async function resolveRgPath() {
1542
1603
  if (cached !== void 0) return cached;
@@ -1545,15 +1606,15 @@ async function resolveRgPath() {
1545
1606
  }
1546
1607
  async function detect() {
1547
1608
  const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
1548
- if (fromEnv && existsSync5(fromEnv)) return fromEnv;
1609
+ if (fromEnv && existsSync6(fromEnv)) return fromEnv;
1549
1610
  const vendored = vendoredRgPath();
1550
- if (vendored && existsSync5(vendored)) {
1611
+ if (vendored && existsSync6(vendored)) {
1551
1612
  ensureExecutable(vendored);
1552
1613
  return vendored;
1553
1614
  }
1554
1615
  if (await trySpawn("rg")) return "rg";
1555
1616
  for (const candidate of claudeCodeCandidates()) {
1556
- if (existsSync5(candidate)) {
1617
+ if (existsSync6(candidate)) {
1557
1618
  ensureExecutable(candidate);
1558
1619
  return candidate;
1559
1620
  }
@@ -1563,7 +1624,7 @@ async function detect() {
1563
1624
  function vendoredRgPath() {
1564
1625
  try {
1565
1626
  const projectRoot = findPackageRoot(import.meta.url);
1566
- return resolve6(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1627
+ return resolve7(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1567
1628
  } catch {
1568
1629
  return null;
1569
1630
  }
@@ -1622,42 +1683,42 @@ function claudeCodeCandidates() {
1622
1683
  const npmRoots = [];
1623
1684
  if (platform === "win32") {
1624
1685
  if (process.env.APPDATA) {
1625
- npmRoots.push(resolve6(process.env.APPDATA, "npm", "node_modules"));
1686
+ npmRoots.push(resolve7(process.env.APPDATA, "npm", "node_modules"));
1626
1687
  }
1627
1688
  if (process.env.USERPROFILE) {
1628
1689
  npmRoots.push(
1629
- resolve6(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1690
+ resolve7(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1630
1691
  );
1631
1692
  }
1632
1693
  } else {
1633
1694
  const home = process.env.HOME ?? "";
1634
1695
  if (home) {
1635
- npmRoots.push(resolve6(home, ".npm-global", "lib", "node_modules"));
1636
- npmRoots.push(resolve6(home, ".npm", "lib", "node_modules"));
1637
- 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"));
1638
1699
  }
1639
1700
  npmRoots.push("/usr/local/lib/node_modules");
1640
1701
  npmRoots.push("/usr/lib/node_modules");
1641
1702
  npmRoots.push("/opt/homebrew/lib/node_modules");
1642
1703
  }
1643
1704
  return npmRoots.map(
1644
- (root) => resolve6(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1705
+ (root) => resolve7(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1645
1706
  );
1646
1707
  }
1647
1708
 
1648
1709
  // src/tools/grep/grep.ts
1649
- var inputSchema5 = z5.object({
1650
- pattern: z5.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
1651
- path: z5.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
1652
- glob: z5.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
1653
- type: z5.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
1654
- 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"),
1655
- "-i": z5.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
1656
- "-n": z5.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
1657
- "-A": z5.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1658
- "-B": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1659
- "-C": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
1660
- 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")
1661
1722
  });
1662
1723
  var parameters5 = toToolParameters(inputSchema5);
1663
1724
  var description5 = `A powerful search tool built on ripgrep.
@@ -1687,7 +1748,7 @@ async function call5(input, signal) {
1687
1748
  args.push("--max-columns-preview");
1688
1749
  args.push("--sort", "modified");
1689
1750
  args.push("-e", input.pattern);
1690
- args.push(input.path ? resolve7(input.path) : ".");
1751
+ args.push(input.path ? resolve8(input.path) : ".");
1691
1752
  const rgPath = await resolveRgPath();
1692
1753
  if (!rgPath) {
1693
1754
  return {
@@ -1758,31 +1819,30 @@ var grepTool = {
1758
1819
  import { createReadStream } from "fs";
1759
1820
  import { readFile as readFile8, stat as stat3 } from "fs/promises";
1760
1821
  import { createInterface } from "readline";
1761
- import { z as z6 } from "zod";
1762
- var inputSchema6 = z6.object({
1763
- 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"),
1764
- offset: z6.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
1765
- 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}`)
1766
1827
  });
1767
1828
  var parameters6 = toToolParameters(inputSchema6);
1768
1829
  var description6 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1769
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.
1770
1831
 
1771
1832
  Usage:
1772
- - 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).
1773
1834
  - By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file
1774
1835
  - You can optionally specify a line offset and limit (especially handy for long files)
1775
1836
  - Results are returned using cat -n format, with line numbers starting at 1
1776
1837
  - This tool can only read text files, not directories. To read a directory, use the Glob tool.
1777
- - If you read a file that exists but has empty contents you will receive a warning in place of file contents.`;
1838
+ - If you read a file that exists but has empty contents you will receive a warning in place of file contents.
1839
+ - This tool cannot read binary files. Files whose extensions are on the binary blocklist (images: .png/.jpg/.gif/.webp/..., documents: .pdf/.docx/.xlsx/..., executables: .exe/.dll/.so/..., archives: .zip/.tar/.gz/..., and others) will be rejected with a clear error. If the file is actually text despite the extension (e.g., a misnamed log), rename it or use Bash \`cat\` to read it directly \u2014 do not retry Read with the same path.`;
1778
1840
  var STREAM_THRESHOLD = 1024 * 1024;
1779
1841
  async function call6(input) {
1780
1842
  const offset = input.offset ?? 1;
1781
1843
  const limit = input.limit ?? MAX_LINES_TO_READ;
1782
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1783
- if (!pathResult.ok) {
1784
- return { ok: false, error: pathResult.error };
1785
- }
1844
+ const pathResult = resolveToolPath(input.file_path);
1845
+ if (!pathResult.ok) return pathResult;
1786
1846
  const filePath = pathResult.resolvedPath;
1787
1847
  if (isBlockedDevicePath(filePath)) {
1788
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` };
@@ -1914,7 +1974,7 @@ var readTool = {
1914
1974
  };
1915
1975
 
1916
1976
  // src/tools/webfetch/webfetch.ts
1917
- import { z as z7 } from "zod";
1977
+ import { z as z8 } from "zod";
1918
1978
 
1919
1979
  // src/tools/webfetch/preapproved.ts
1920
1980
  var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
@@ -2199,9 +2259,9 @@ function cleanCache() {
2199
2259
  }
2200
2260
  }
2201
2261
  }
2202
- var inputSchema7 = z7.object({
2203
- url: z7.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
2204
- 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")
2205
2265
  });
2206
2266
  var parameters7 = toToolParameters(inputSchema7);
2207
2267
  var description7 = `- Fetches content from a specified URL and processes it using an AI model.
@@ -2421,7 +2481,7 @@ var webfetchTool = {
2421
2481
  };
2422
2482
 
2423
2483
  // src/tools/webbrowser/webbrowser.ts
2424
- import { z as z8 } from "zod";
2484
+ import { z as z9 } from "zod";
2425
2485
 
2426
2486
  // src/tools/webbrowser/browser.ts
2427
2487
  import os from "os";
@@ -2457,12 +2517,12 @@ function screenshotPath(prefix = "browser") {
2457
2517
  }
2458
2518
 
2459
2519
  // src/tools/webbrowser/webbrowser.ts
2460
- var inputSchema8 = z8.object({
2461
- action: z8.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
2462
- url: z8.string().url().optional().describe("URL to navigate to (required for navigate action)"),
2463
- selector: z8.string().optional().describe("CSS selector for click/fill/submit actions"),
2464
- value: z8.string().optional().describe("Value to fill in input fields"),
2465
- 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)")
2466
2526
  });
2467
2527
  var parameters8 = toToolParameters(inputSchema8);
2468
2528
  var description8 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
@@ -2610,12 +2670,12 @@ var webbrowserTool = {
2610
2670
  };
2611
2671
 
2612
2672
  // src/tools/websearch/websearch.ts
2613
- import { z as z9 } from "zod";
2614
- var inputSchema9 = z9.object({
2615
- 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"),
2616
- max_results: z9.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
2617
- 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"),
2618
- 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")
2619
2679
  });
2620
2680
  var parameters9 = toToolParameters(inputSchema9);
2621
2681
  var description9 = `- Searches the public web via the Tavily Search API and returns structured results.
@@ -2715,25 +2775,26 @@ var webSearchTool = {
2715
2775
  };
2716
2776
 
2717
2777
  // src/tools/write/write.ts
2718
- import { existsSync as existsSync6 } from "fs";
2778
+ import { existsSync as existsSync7 } from "fs";
2719
2779
  import { mkdir as mkdir5, stat as stat4, writeFile as writeFile5 } from "fs/promises";
2720
2780
  import { dirname as dirname6 } from "path";
2721
- import { z as z10 } from "zod";
2781
+ import { z as z11 } from "zod";
2722
2782
  var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
2723
- var inputSchema10 = z10.object({
2724
- file_path: z10.string().min(1).describe("\u8981\u5199\u5165\u7684\u6587\u4EF6\u8DEF\u5F84"),
2725
- 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")
2726
2786
  });
2727
2787
  var parameters10 = toToolParameters(inputSchema10);
2728
2788
  var description10 = `Writes a file to the local filesystem.
2729
2789
 
2730
2790
  Usage:
2731
2791
  - This tool will overwrite the existing file if there is one at the provided path.
2792
+ - If you intend to overwrite an existing file, you MUST use your \`Read\` tool to read its current content at least once in the current session BEFORE calling Write. This tool will error with a "\u8BF7\u5148 Read" message if you attempt to overwrite a file that has not been read. To create a new file (path does not yet exist), you can call Write directly without a prior Read.
2732
2793
  - If the parent directory does not exist, it will be created recursively.
2733
2794
  - ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
2734
2795
  - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
2735
2796
  async function call10(input) {
2736
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
2797
+ const pathResult = resolveToolPath(input.file_path);
2737
2798
  if (!pathResult.ok) return pathResult;
2738
2799
  const filePath = pathResult.resolvedPath;
2739
2800
  const freshness = assertFresh(filePath);
@@ -2750,7 +2811,7 @@ async function call10(input) {
2750
2811
  try {
2751
2812
  await mkdir5(dirname6(filePath), { recursive: true });
2752
2813
  let originalSize = 0;
2753
- const fileExisted = existsSync6(filePath);
2814
+ const fileExisted = existsSync7(filePath);
2754
2815
  if (fileExisted) {
2755
2816
  try {
2756
2817
  const st = await stat4(filePath);
@@ -2779,8 +2840,10 @@ async function call10(input) {
2779
2840
  } else {
2780
2841
  await writeFile5(filePath, contentToWrite, "utf8");
2781
2842
  }
2843
+ recordRead(filePath);
2782
2844
  const action = fileExisted ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6";
2783
- 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`;
2784
2847
  const bomInfo = bomEncoding === "utf8-bom" ? "\uFF0C\u5DF2\u4FDD\u7559 UTF-8 BOM" : "";
2785
2848
  return {
2786
2849
  ok: true,
@@ -2860,39 +2923,52 @@ import { Box as Box7, Text as Text7 } from "ink";
2860
2923
  import { useCallback, useMemo, useState } from "react";
2861
2924
  import { Box, Text, useApp, useInput } from "ink";
2862
2925
  import { jsx, jsxs } from "react/jsx-runtime";
2863
- var DEFAULT_CONTEXT_WINDOW2 = 128e3;
2926
+ var FALLBACK_CONTEXT_WINDOW = 128e3;
2864
2927
  var PRESETS = [
2865
2928
  {
2866
2929
  name: "minimax",
2867
2930
  label: "MiniMax (\u6D77\u87BA)",
2868
2931
  baseURL: "https://api.minimax.chat/v1",
2869
- models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"]
2932
+ models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"],
2933
+ contextWindow: 204800
2870
2934
  },
2871
2935
  {
2872
2936
  name: "deepseek",
2873
2937
  label: "DeepSeek",
2874
2938
  baseURL: "https://api.deepseek.com/v1",
2875
- models: ["deepseek-chat", "deepseek-reasoner"]
2939
+ models: ["deepseek-chat", "deepseek-reasoner"],
2940
+ contextWindow: 128e3
2876
2941
  },
2877
2942
  {
2878
2943
  name: "openai",
2879
2944
  label: "OpenAI",
2880
2945
  baseURL: "https://api.openai.com/v1",
2881
- models: ["gpt-4o", "gpt-4o-mini"]
2946
+ models: ["gpt-4o", "gpt-4o-mini"],
2947
+ contextWindow: 128e3
2882
2948
  },
2883
2949
  {
2884
2950
  name: "moonshot",
2885
2951
  label: "Moonshot (Kimi)",
2886
2952
  baseURL: "https://api.moonshot.cn/v1",
2887
- 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
+ }
2888
2960
  },
2889
2961
  {
2890
2962
  name: "custom",
2891
2963
  label: "\u81EA\u5B9A\u4E49\uFF08\u624B\u52A8\u586B baseURL\uFF09",
2892
2964
  baseURL: "",
2893
- models: []
2965
+ models: [],
2966
+ contextWindow: FALLBACK_CONTEXT_WINDOW
2894
2967
  }
2895
2968
  ];
2969
+ function resolveContextWindow(preset, model) {
2970
+ return preset.modelContextWindows?.[model] ?? preset.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
2971
+ }
2896
2972
  function ConfigWizard({ onSuccess }) {
2897
2973
  const { exit } = useApp();
2898
2974
  const [step, setStep] = useState("preset");
@@ -2921,51 +2997,68 @@ function ConfigWizard({ onSuccess }) {
2921
2997
  },
2922
2998
  [baseURL, model, preset]
2923
2999
  );
2924
- const handleTest = useCallback(async () => {
2925
- setStep("testing");
2926
- setErrorMsg(null);
2927
- try {
2928
- const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
2929
- const resp = await fetch(url, {
2930
- method: "POST",
2931
- headers: {
2932
- "Content-Type": "application/json",
2933
- Authorization: `Bearer ${apiKey}`
2934
- },
2935
- body: JSON.stringify({
2936
- model,
2937
- messages: [{ role: "user", content: "ping" }],
2938
- max_tokens: 5,
2939
- stream: false
2940
- })
2941
- });
2942
- if (!resp.ok) {
2943
- const body = await resp.text().catch(() => "");
2944
- throw new Error(`HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`);
2945
- }
2946
- const data = await resp.json();
2947
- if (!Array.isArray(data.choices)) {
2948
- 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");
2949
3034
  }
3035
+ },
3036
+ [apiKey, baseURL]
3037
+ );
3038
+ const finalize = useCallback(
3039
+ async (finalModel, tavilyApiKey) => {
3040
+ const contextWindow = resolveContextWindow(preset, finalModel);
2950
3041
  await saveConfig({
2951
3042
  baseURL,
2952
3043
  apiKey,
2953
- model,
3044
+ model: finalModel,
2954
3045
  provider: preset.name,
2955
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3046
+ contextWindow,
3047
+ tavilyApiKey
2956
3048
  });
3049
+ if (tavilyApiKey) {
3050
+ process.env.TAVILY_API_KEY = tavilyApiKey;
3051
+ }
2957
3052
  onSuccess({
2958
3053
  name: preset.name,
2959
3054
  baseURL,
2960
3055
  apiKey,
2961
- model,
2962
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3056
+ model: finalModel,
3057
+ contextWindow
2963
3058
  });
2964
- } catch (e) {
2965
- setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
2966
- setStep("error");
2967
- }
2968
- }, [apiKey, baseURL, model, onSuccess, preset.name]);
3059
+ },
3060
+ [apiKey, baseURL, onSuccess, preset]
3061
+ );
2969
3062
  useInput((input, key) => {
2970
3063
  if (key.escape || key.ctrl && input === "c") {
2971
3064
  exit();
@@ -3020,9 +3113,10 @@ function ConfigWizard({ onSuccess }) {
3020
3113
  }
3021
3114
  }
3022
3115
  if (key.return) {
3023
- if (draft.trim().length === 0) return;
3024
- setModel(draft.trim());
3025
- void handleTest();
3116
+ const finalModel = draft.trim();
3117
+ if (finalModel.length === 0) return;
3118
+ setModel(finalModel);
3119
+ void handleTest(finalModel);
3026
3120
  } else if (key.backspace || key.delete) {
3027
3121
  setDraft((s) => s.slice(0, -1));
3028
3122
  } else if (input && !key.meta && !key.ctrl && !key.upArrow && !key.downArrow) {
@@ -3030,6 +3124,17 @@ function ConfigWizard({ onSuccess }) {
3030
3124
  }
3031
3125
  return;
3032
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
+ }
3033
3138
  if (step === "error") {
3034
3139
  if (key.return) {
3035
3140
  enterStep("preset");
@@ -3042,7 +3147,7 @@ function ConfigWizard({ onSuccess }) {
3042
3147
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "minimal-agent \xB7 \u9996\u6B21\u914D\u7F6E\u5411\u5BFC" }) }),
3043
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" }) }),
3044
3149
  step === "preset" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3045
- /* @__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" }),
3046
3151
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === presetIndex ? "cyan" : void 0, children: [
3047
3152
  i === presetIndex ? "> " : " ",
3048
3153
  p.label,
@@ -3050,12 +3155,12 @@ function ConfigWizard({ onSuccess }) {
3050
3155
  ] }, p.name)) })
3051
3156
  ] }),
3052
3157
  step === "baseURL" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3053
- /* @__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" }),
3054
3159
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
3055
3160
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664" })
3056
3161
  ] }),
3057
3162
  step === "apiKey" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3058
- /* @__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" }),
3059
3164
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
3060
3165
  /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3061
3166
  "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664\uFF08\u5DF2\u8F93\u5165 ",
@@ -3065,7 +3170,7 @@ function ConfigWizard({ onSuccess }) {
3065
3170
  ] }),
3066
3171
  step === "model" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3067
3172
  /* @__PURE__ */ jsxs(Text, { children: [
3068
- "Step 4/4 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3173
+ "Step 4/5 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3069
3174
  preset.models.length > 0 ? "\uFF08\u2191\u2193 \u5207\u6362\u9884\u8BBE\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\uFF09" : ""
3070
3175
  ] }),
3071
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: [
@@ -3086,6 +3191,17 @@ function ConfigWizard({ onSuccess }) {
3086
3191
  model
3087
3192
  ] })
3088
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
+ ] }),
3089
3205
  step === "error" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3090
3206
  /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 \u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25" }),
3091
3207
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMsg ?? "\u672A\u77E5\u9519\u8BEF" }) }),
@@ -4215,8 +4331,7 @@ async function reactiveCompactIfApplicable(messages, provider, error, state = de
4215
4331
 
4216
4332
  // src/plugins/commandRouter.ts
4217
4333
  import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
4218
- import { join as join6 } from "path";
4219
- var PLUGINS_DIR = join6(findPackageRoot(import.meta.url), "plugins");
4334
+ import { join as join7 } from "path";
4220
4335
  var pluginCache = /* @__PURE__ */ new Map();
4221
4336
  var discoveryDone = false;
4222
4337
  function stripQuotes2(s) {
@@ -4241,7 +4356,7 @@ function parseMarkdownFrontmatter(content) {
4241
4356
  }
4242
4357
  async function loadPlugin(pluginDirPath) {
4243
4358
  const dirName = pluginDirPath.split("/").pop() ?? pluginDirPath;
4244
- const manifestPath = join6(pluginDirPath, ".claude-plugin", "plugin.json");
4359
+ const manifestPath = join7(pluginDirPath, ".claude-plugin", "plugin.json");
4245
4360
  let manifestName = dirName;
4246
4361
  let manifestVersion;
4247
4362
  let manifestDesc;
@@ -4253,13 +4368,13 @@ async function loadPlugin(pluginDirPath) {
4253
4368
  manifestDesc = parsed.description;
4254
4369
  } catch {
4255
4370
  }
4256
- const commandsDir = join6(pluginDirPath, "commands");
4371
+ const commandsDir = join7(pluginDirPath, "commands");
4257
4372
  const commands = [];
4258
4373
  try {
4259
4374
  const entries = await readdir3(commandsDir, { withFileTypes: true });
4260
4375
  for (const entry of entries) {
4261
4376
  if (!entry.name.endsWith(".md")) continue;
4262
- const cmdPath = join6(commandsDir, entry.name);
4377
+ const cmdPath = join7(commandsDir, entry.name);
4263
4378
  try {
4264
4379
  const content = await readFile9(cmdPath, "utf8");
4265
4380
  const fm = parseMarkdownFrontmatter(content);
@@ -4278,7 +4393,7 @@ async function loadPlugin(pluginDirPath) {
4278
4393
  }
4279
4394
  } catch {
4280
4395
  }
4281
- const hooksJsonPath = join6(pluginDirPath, "hooks", "hooks.json");
4396
+ const hooksJsonPath = join7(pluginDirPath, "hooks", "hooks.json");
4282
4397
  let hasStopHook = false;
4283
4398
  try {
4284
4399
  const hooksRaw = await readFile9(hooksJsonPath, "utf8");
@@ -4305,17 +4420,20 @@ async function discoverPlugins() {
4305
4420
  }
4306
4421
  pluginCache.clear();
4307
4422
  discoveryDone = true;
4308
- try {
4309
- const entries = await readdir3(PLUGINS_DIR, { withFileTypes: true });
4310
- for (const entry of entries) {
4311
- if (!entry.isDirectory()) continue;
4312
- if (entry.name.startsWith(".")) continue;
4313
- const plugin = await loadPlugin(join6(PLUGINS_DIR, entry.name));
4314
- 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;
4315
4433
  pluginCache.set(plugin.name, plugin);
4316
4434
  }
4435
+ } catch {
4317
4436
  }
4318
- } catch {
4319
4437
  }
4320
4438
  return Array.from(pluginCache.values());
4321
4439
  }
@@ -4375,10 +4493,10 @@ function getActiveStopHookPlugins() {
4375
4493
 
4376
4494
  // src/plugins/stopHook.ts
4377
4495
  import { readFile as readFile10 } from "fs/promises";
4378
- import { join as join7 } from "path";
4496
+ import { join as join8 } from "path";
4379
4497
  import { spawn as spawn4 } from "child_process";
4380
4498
  async function loadStopHookConfig(pluginRoot) {
4381
- const hooksJsonPath = join7(pluginRoot, "hooks", "hooks.json");
4499
+ const hooksJsonPath = join8(pluginRoot, "hooks", "hooks.json");
4382
4500
  try {
4383
4501
  const raw = await readFile10(hooksJsonPath, "utf8");
4384
4502
  const parsed = JSON.parse(raw);
@@ -4417,7 +4535,7 @@ async function executeStopHooks(pluginRoots, transcriptText) {
4417
4535
  }
4418
4536
  function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4419
4537
  const resolvedCommand = hookConfig.command.replaceAll("${CLAUDE_PLUGIN_ROOT}", pluginRoot);
4420
- return new Promise((resolve9) => {
4538
+ return new Promise((resolve10) => {
4421
4539
  const child = spawn4("bash", [resolvedCommand], {
4422
4540
  env: {
4423
4541
  ...process.env,
@@ -4429,31 +4547,31 @@ function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4429
4547
  stdout += data.toString();
4430
4548
  });
4431
4549
  child.on("error", () => {
4432
- resolve9({ decision: "pass" });
4550
+ resolve10({ decision: "pass" });
4433
4551
  });
4434
4552
  child.on("close", (code) => {
4435
4553
  if (code !== 0) {
4436
- resolve9({ decision: "pass" });
4554
+ resolve10({ decision: "pass" });
4437
4555
  return;
4438
4556
  }
4439
4557
  const trimmed = stdout.trim();
4440
4558
  if (!trimmed) {
4441
- resolve9({ decision: "pass" });
4559
+ resolve10({ decision: "pass" });
4442
4560
  return;
4443
4561
  }
4444
4562
  try {
4445
4563
  const parsed = JSON.parse(trimmed);
4446
4564
  if (parsed.decision === "block") {
4447
- resolve9({
4565
+ resolve10({
4448
4566
  decision: "block",
4449
4567
  reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
4450
4568
  systemMessage: typeof parsed.systemMessage === "string" ? parsed.systemMessage : void 0
4451
4569
  });
4452
4570
  return;
4453
4571
  }
4454
- resolve9({ decision: "pass" });
4572
+ resolve10({ decision: "pass" });
4455
4573
  } catch {
4456
- resolve9({ decision: "pass" });
4574
+ resolve10({ decision: "pass" });
4457
4575
  }
4458
4576
  });
4459
4577
  child.stdin.write(transcriptText);
@@ -4462,7 +4580,7 @@ function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4462
4580
  }
4463
4581
 
4464
4582
  // src/plugins/verificationGate.ts
4465
- import { existsSync as existsSync7, readFileSync } from "fs";
4583
+ import { existsSync as existsSync8, readFileSync } from "fs";
4466
4584
  import { spawn as spawn5 } from "child_process";
4467
4585
  function parseVerifyArg(arg) {
4468
4586
  const colonIdx = arg.indexOf(":");
@@ -4504,7 +4622,7 @@ function parseVerifyArgs(args) {
4504
4622
  return checks;
4505
4623
  }
4506
4624
  function runShell(command, timeout) {
4507
- return new Promise((resolve9) => {
4625
+ return new Promise((resolve10) => {
4508
4626
  const isWin = process.platform === "win32";
4509
4627
  const child = isWin ? spawn5("cmd", ["/c", command], { timeout, env: process.env }) : spawn5("bash", ["-c", command], { timeout, env: process.env });
4510
4628
  let stdout = "";
@@ -4516,10 +4634,10 @@ function runShell(command, timeout) {
4516
4634
  stderr += d.toString();
4517
4635
  });
4518
4636
  child.on("error", () => {
4519
- resolve9({ exitCode: null, stdout, stderr, errored: true });
4637
+ resolve10({ exitCode: null, stdout, stderr, errored: true });
4520
4638
  });
4521
4639
  child.on("close", (code) => {
4522
- resolve9({ exitCode: code, stdout, stderr, errored: false });
4640
+ resolve10({ exitCode: code, stdout, stderr, errored: false });
4523
4641
  });
4524
4642
  });
4525
4643
  }
@@ -4539,7 +4657,7 @@ async function verifyShell(command, timeout) {
4539
4657
  };
4540
4658
  }
4541
4659
  function verifyFileExists(file) {
4542
- const exists = existsSync7(file);
4660
+ const exists = existsSync8(file);
4543
4661
  return {
4544
4662
  check: { type: "file_exists", file },
4545
4663
  passed: exists,
@@ -4640,8 +4758,8 @@ function formatCheckName(check) {
4640
4758
 
4641
4759
  // src/plugins/goalState.ts
4642
4760
  import { mkdir as mkdir6, appendFile, writeFile as writeFile6, unlink as unlink2, rmdir as rmdir2 } from "fs/promises";
4643
- import { existsSync as existsSync8, readFileSync as readFileSync2 } from "fs";
4644
- import { join as join8 } from "path";
4761
+ import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
4762
+ import { join as join9 } from "path";
4645
4763
  var Phase = /* @__PURE__ */ ((Phase2) => {
4646
4764
  Phase2["PLAN"] = "plan";
4647
4765
  Phase2["BUILD"] = "build";
@@ -4683,13 +4801,13 @@ var GoalState = class {
4683
4801
  dir;
4684
4802
  constructor(workspaceDir, sessionTag) {
4685
4803
  const suffix = sessionTag ? `-${sessionTag}` : "";
4686
- this.dir = join8(workspaceDir, `.minimal-agent${suffix}`);
4804
+ this.dir = join9(workspaceDir, `.minimal-agent${suffix}`);
4687
4805
  }
4688
4806
  async reset() {
4689
4807
  const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4690
4808
  for (const f of files) {
4691
4809
  try {
4692
- await unlink2(join8(this.dir, f));
4810
+ await unlink2(join9(this.dir, f));
4693
4811
  } catch {
4694
4812
  }
4695
4813
  }
@@ -4708,22 +4826,22 @@ ${goal}
4708
4826
  decisions: ""
4709
4827
  };
4710
4828
  for (const [name, content] of Object.entries(files)) {
4711
- const path2 = join8(this.dir, `${name}.md`);
4712
- if (!existsSync8(path2)) {
4829
+ const path2 = join9(this.dir, `${name}.md`);
4830
+ if (!existsSync9(path2)) {
4713
4831
  await writeFile6(path2, content);
4714
4832
  }
4715
4833
  }
4716
4834
  }
4717
4835
  get goal() {
4718
4836
  try {
4719
- return readFileSync2(join8(this.dir, "goal.md"), "utf8").trim();
4837
+ return readFileSync2(join9(this.dir, "goal.md"), "utf8").trim();
4720
4838
  } catch {
4721
4839
  return "";
4722
4840
  }
4723
4841
  }
4724
4842
  get completionCriteria() {
4725
4843
  try {
4726
- const raw = readFileSync2(join8(this.dir, "completion.md"), "utf8").trim();
4844
+ const raw = readFileSync2(join9(this.dir, "completion.md"), "utf8").trim();
4727
4845
  return JSON.parse(raw);
4728
4846
  } catch {
4729
4847
  return [];
@@ -4731,7 +4849,7 @@ ${goal}
4731
4849
  }
4732
4850
  get currentPhase() {
4733
4851
  try {
4734
- const raw = readFileSync2(join8(this.dir, "phase.md"), "utf8").trim();
4852
+ const raw = readFileSync2(join9(this.dir, "phase.md"), "utf8").trim();
4735
4853
  if (VALID_PHASES.has(raw)) {
4736
4854
  return raw;
4737
4855
  }
@@ -4754,24 +4872,24 @@ ${goal}
4754
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`
4755
4873
  );
4756
4874
  }
4757
- await writeFile6(join8(this.dir, "phase.md"), phase);
4875
+ await writeFile6(join9(this.dir, "phase.md"), phase);
4758
4876
  await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4759
4877
  }
4760
4878
  async forceSetPhase(phase, reason) {
4761
4879
  if (!VALID_PHASES.has(phase)) {
4762
4880
  throw new Error(`Invalid phase: ${phase}`);
4763
4881
  }
4764
- await writeFile6(join8(this.dir, "phase.md"), phase);
4882
+ await writeFile6(join9(this.dir, "phase.md"), phase);
4765
4883
  await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4766
4884
  }
4767
4885
  async appendProgress(line) {
4768
4886
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
4769
- await appendFile(join8(this.dir, "progress.md"), `[${timestamp}] ${line}
4887
+ await appendFile(join9(this.dir, "progress.md"), `[${timestamp}] ${line}
4770
4888
  `);
4771
4889
  }
4772
4890
  tailProgress(n) {
4773
4891
  try {
4774
- const content = readFileSync2(join8(this.dir, "progress.md"), "utf8");
4892
+ const content = readFileSync2(join9(this.dir, "progress.md"), "utf8");
4775
4893
  const lines = content.trim().split("\n").filter(Boolean);
4776
4894
  return lines.slice(-n).join("\n");
4777
4895
  } catch {
@@ -4779,12 +4897,12 @@ ${goal}
4779
4897
  }
4780
4898
  }
4781
4899
  async appendLearning(lesson) {
4782
- await appendFile(join8(this.dir, "learnings.md"), `- ${lesson}
4900
+ await appendFile(join9(this.dir, "learnings.md"), `- ${lesson}
4783
4901
  `);
4784
4902
  }
4785
4903
  get learnings() {
4786
4904
  try {
4787
- const raw = readFileSync2(join8(this.dir, "learnings.md"), "utf8").trim();
4905
+ const raw = readFileSync2(join9(this.dir, "learnings.md"), "utf8").trim();
4788
4906
  if (!raw) return "";
4789
4907
  const lines = raw.split("\n").filter(Boolean);
4790
4908
  return lines.slice(-LEARNINGS_TAIL_LINES).join("\n");
@@ -4802,12 +4920,12 @@ ${goal}
4802
4920
  reasoning
4803
4921
  };
4804
4922
  const line = JSON.stringify(entry);
4805
- await appendFile(join8(this.dir, "decisions.md"), `${line}
4923
+ await appendFile(join9(this.dir, "decisions.md"), `${line}
4806
4924
  `);
4807
4925
  }
4808
4926
  findSimilarDecisions(ctx, k = 3) {
4809
4927
  try {
4810
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4928
+ const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
4811
4929
  const lines = content.trim().split("\n").filter(Boolean);
4812
4930
  const entries = [];
4813
4931
  for (const line of lines) {
@@ -4827,7 +4945,7 @@ ${goal}
4827
4945
  }
4828
4946
  summarizeDecisions(maxEntries = 5) {
4829
4947
  try {
4830
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4948
+ const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
4831
4949
  const lines = content.trim().split("\n").filter(Boolean);
4832
4950
  const entries = [];
4833
4951
  for (const line of lines.slice(-maxEntries * 2)) {
@@ -4873,7 +4991,7 @@ ${recentProgress}`);
4873
4991
  const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4874
4992
  for (const f of files) {
4875
4993
  try {
4876
- await unlink2(join8(this.dir, f));
4994
+ await unlink2(join9(this.dir, f));
4877
4995
  } catch {
4878
4996
  }
4879
4997
  }
@@ -5699,13 +5817,13 @@ function handleEvent2(event, output, verbose) {
5699
5817
  }
5700
5818
  }
5701
5819
  function readFromStdin() {
5702
- return new Promise((resolve9) => {
5820
+ return new Promise((resolve10) => {
5703
5821
  let data = "";
5704
5822
  let settled = false;
5705
5823
  const timer = setTimeout(() => {
5706
5824
  if (!settled) {
5707
5825
  settled = true;
5708
- resolve9("");
5826
+ resolve10("");
5709
5827
  }
5710
5828
  }, STDIN_TIMEOUT_MS);
5711
5829
  process.stdin.setEncoding("utf8");
@@ -5716,7 +5834,7 @@ function readFromStdin() {
5716
5834
  if (!settled) {
5717
5835
  clearTimeout(timer);
5718
5836
  settled = true;
5719
- resolve9(data.trim());
5837
+ resolve10(data.trim());
5720
5838
  }
5721
5839
  }
5722
5840
  process.stdin.on("data", onData);
@@ -5732,14 +5850,15 @@ async function main() {
5732
5850
  const args = process.argv.slice(2);
5733
5851
  const dirArg = extractCwdArg(args);
5734
5852
  if (dirArg) {
5735
- const abs = resolve8(dirArg);
5736
- if (!existsSync9(abs)) {
5853
+ const abs = resolve9(dirArg);
5854
+ if (!existsSync10(abs)) {
5737
5855
  mkdirSync(abs, { recursive: true });
5738
5856
  }
5739
5857
  process.chdir(abs);
5740
5858
  }
5741
5859
  initWorkingDir();
5742
5860
  await migrateLegacyContext(getWorkingDir());
5861
+ await applyToolKeysToEnv();
5743
5862
  if (args.includes("-h") || args.includes("--help")) {
5744
5863
  printHelp();
5745
5864
  return;