minimal-agent 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +405 -122
  2. package/dist/main.js +423 -941
  3. package/package.json +5 -2
  4. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +186 -0
  5. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +9 -0
  6. package/plugins/ralph-wiggum/README.md +179 -0
  7. package/plugins/ralph-wiggum/commands/cancel-ralph.md +18 -0
  8. package/plugins/ralph-wiggum/commands/help.md +126 -0
  9. package/plugins/ralph-wiggum/commands/ralph-loop.md +59 -0
  10. package/plugins/ralph-wiggum/hooks/hooks.json +15 -0
  11. package/plugins/ralph-wiggum/hooks/stop-hook.sh +191 -0
  12. package/plugins/ralph-wiggum/plugin.ts +275 -0
  13. package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +203 -0
  14. package/plugins/ralph-wiggum/src/goalState.ts +310 -0
  15. package/plugins/ralph-wiggum/src/sentinels.ts +24 -0
  16. package/plugins/ralph-wiggum/src/stopHookRunner.ts +136 -0
  17. package/plugins/ralph-wiggum/src/verificationGate.ts +252 -0
  18. package/plugins/ralph-wiggum/test/goalState.test.ts +410 -0
  19. package/plugins/ralph-wiggum/test/verificationGate.test.ts +122 -0
  20. package/plugins/workflow-runner/.claude-plugin/plugin.json +5 -0
  21. package/plugins/workflow-runner/commands/workflow.md +15 -0
  22. package/plugins/workflow-runner/commands/workflows.md +8 -0
  23. package/plugins/workflow-runner/plugin.ts +42 -0
  24. package/plugins/workflow-runner/src/expressions.ts +371 -0
  25. package/plugins/workflow-runner/src/index.ts +194 -0
  26. package/plugins/workflow-runner/src/loader.ts +193 -0
  27. package/plugins/workflow-runner/src/runner.ts +313 -0
  28. package/plugins/workflow-runner/src/stepExecutors/assert.ts +30 -0
  29. package/plugins/workflow-runner/src/stepExecutors/llm.ts +54 -0
  30. package/plugins/workflow-runner/src/stepExecutors/skill.ts +115 -0
  31. package/plugins/workflow-runner/src/stepExecutors/tool.ts +41 -0
  32. package/plugins/workflow-runner/src/types.ts +183 -0
  33. package/plugins/workflow-runner/src/workflowState.ts +65 -0
  34. package/plugins/workflow-runner/test/cli.e2e.test.ts +114 -0
  35. package/plugins/workflow-runner/test/e2e.test.ts +268 -0
  36. package/plugins/workflow-runner/test/expressions.test.ts +140 -0
  37. package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +27 -0
  38. package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +49 -0
  39. package/plugins/workflow-runner/test/graceful.test.ts +139 -0
  40. package/plugins/workflow-runner/test/loader.test.ts +216 -0
  41. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +230 -0
  42. package/plugins/workflow-runner/test/runner.test.ts +511 -0
  43. package/skills/config/SKILL.md +27 -1
  44. package/skills/image-gen-openrouter/SKILL.md +121 -0
  45. package/skills/subtitle-srt/SKILL.md +134 -0
  46. package/skills/tts-zh/SKILL.md +137 -0
  47. package/skills/video-compose/SKILL.md +139 -0
  48. package/workflows/book-review-short.yaml +99 -0
  49. package/workflows/e2e-write-greet.yaml +27 -0
  50. package/workflows/schema.json +74 -0
  51. package/workflows/youtube-shorts.yaml +171 -0
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
  import { existsSync as existsSync9, 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,14 @@ ${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\u6BCF\u4E2A\u63D2\u4EF6\u53EF\u66B4\u9732\u5F62\u5982 \`/<cmd>\` \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\u67D0\u4E2A\u63D2\u4EF6\u547D\u4EE4\u5728\u6267\u884C\u4E2D\uFF08\u4F60\u4F1A\u5728\u5F53\u8F6E\u7528\u6237\u6D88\u606F\u91CC\u770B\u5230\u8BE5\u63D2\u4EF6\u6CE8\u5165\u7684\u4E0A\u4E0B\u6587 / \u9636\u6BB5\u6807\u8BB0 / \u54E8\u5175\u7EA6\u5B9A\uFF09\uFF0C
425
+ \u8BF7\u4E25\u683C\u6309\u7167\u8BE5\u63D2\u4EF6\u7ED9\u51FA\u7684\u6307\u5F15\u884C\u52A8\uFF0C\u4E0D\u8981\u7ED5\u5F00\u5B83\u7684\u5951\u7EA6\u3002
426
+ - \u5728**\u5E38\u89C4\u5BF9\u8BDD**\uFF08\u65E0\u63D2\u4EF6\u6CE8\u5165\u7684\u9636\u6BB5\u6807\u8BB0 / \u54E8\u5175\u7EA6\u5B9A\uFF09\u4E2D\uFF0C\u4E0D\u8981\u51ED\u7A7A\u4F7F\u7528\u8FD9\u4E9B\u6807\u8BB0\u6216\u54E8\u5175\u8BCD\u3002
427
+
385
428
  # \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
386
429
  ## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
387
430
  ### \u573A\u666F 1\uFF1A\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9
@@ -861,14 +904,14 @@ var bashTool = {
861
904
 
862
905
  // src/tools/edit/edit.ts
863
906
  import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
864
- import { existsSync as existsSync3 } from "fs";
907
+ import { existsSync as existsSync4 } from "fs";
865
908
  import { dirname as dirname5 } from "path";
866
- import { z as z2 } from "zod";
909
+ import { z as z3 } from "zod";
867
910
 
868
911
  // src/tools/shared/fileUtils.ts
869
912
  import { open, readFile as readFile5 } from "fs/promises";
870
913
  import { homedir as homedir4 } from "os";
871
- import { extname, resolve as resolve4 } from "path";
914
+ import { extname, resolve as resolve5 } from "path";
872
915
  var BLOCKED_DEVICE_PATHS = /* @__PURE__ */ new Set([
873
916
  "/dev/zero",
874
917
  "/dev/random",
@@ -904,7 +947,7 @@ function validateAndResolvePath(rawPath, workingDir) {
904
947
  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
948
  }
906
949
  const expanded = expandPath(rawPath);
907
- const resolved = resolve4(workingDir, expanded);
950
+ const resolved = resolve5(workingDir, expanded);
908
951
  if (isBlockedDevicePath(resolved)) {
909
952
  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
953
  }
@@ -915,10 +958,13 @@ function validateAndResolvePath(rawPath, workingDir) {
915
958
  }
916
959
  function expandPath(p) {
917
960
  if (p.startsWith("~/") || p === "~") {
918
- return resolve4(homedir4(), p.slice(2));
961
+ return resolve5(homedir4(), p.slice(2));
919
962
  }
920
963
  return p;
921
964
  }
965
+ function resolveToolPath(rawPath) {
966
+ return validateAndResolvePath(rawPath, getWorkingDir());
967
+ }
922
968
  function detectLineEndingsForString(content) {
923
969
  let crlfCount = 0;
924
970
  let lfCount = 0;
@@ -1108,9 +1154,28 @@ function applyCurlySingleQuotes(str) {
1108
1154
  }
1109
1155
  return result.join("");
1110
1156
  }
1157
+ function countOccurrences(haystack, needle) {
1158
+ if (needle.length === 0) return 0;
1159
+ let count = 0;
1160
+ let pos = 0;
1161
+ while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1162
+ count++;
1163
+ pos += needle.length;
1164
+ }
1165
+ return count;
1166
+ }
1167
+ function splitReplaceAll(haystack, needle, replacement) {
1168
+ return haystack.split(needle).join(replacement);
1169
+ }
1170
+
1171
+ // src/tools/shared/schemas.ts
1172
+ import { z as z2 } from "zod";
1173
+ function filePathField(action) {
1174
+ 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`);
1175
+ }
1111
1176
 
1112
1177
  // src/tools/shared/fileState.ts
1113
- import { existsSync as existsSync2, statSync } from "fs";
1178
+ import { existsSync as existsSync3, statSync } from "fs";
1114
1179
  var fileState = /* @__PURE__ */ new Map();
1115
1180
  function recordRead(absPath) {
1116
1181
  try {
@@ -1122,7 +1187,7 @@ function recordRead(absPath) {
1122
1187
  function assertFresh(absPath) {
1123
1188
  const entry = fileState.get(absPath);
1124
1189
  if (!entry) {
1125
- if (existsSync2(absPath)) {
1190
+ if (existsSync3(absPath)) {
1126
1191
  return {
1127
1192
  ok: false,
1128
1193
  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 +1214,13 @@ function clearFileState() {
1149
1214
 
1150
1215
  // src/tools/edit/edit.ts
1151
1216
  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(
1217
+ var inputSchema2 = z3.object({
1218
+ file_path: filePathField("\u7F16\u8F91"),
1219
+ old_string: z3.string().describe(
1155
1220
  "\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
1221
  ),
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")
1222
+ new_string: z3.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
1223
+ replace_all: z3.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF09")
1159
1224
  });
1160
1225
  var parameters2 = toToolParameters(inputSchema2);
1161
1226
  var description2 = `Performs exact string replacements in files.
@@ -1164,7 +1229,8 @@ Usage:
1164
1229
  - You MUST use your \`Read\` tool to read the current content of the file BEFORE calling Edit.
1165
1230
  Memory is unreliable \u2014 if you think you "remember" how the code looks, you are probably wrong.
1166
1231
  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\`.
1232
+ - 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.
1233
+ - 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
1234
  - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
1169
1235
  - 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
1236
  - 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 +1238,7 @@ Usage:
1172
1238
  async function call2(input) {
1173
1239
  const { old_string, new_string } = input;
1174
1240
  const replaceAll = input.replace_all ?? false;
1175
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1241
+ const pathResult = resolveToolPath(input.file_path);
1176
1242
  if (!pathResult.ok) return pathResult;
1177
1243
  const filePath = pathResult.resolvedPath;
1178
1244
  const freshness = assertFresh(filePath);
@@ -1182,10 +1248,11 @@ async function call2(input) {
1182
1248
  if (old_string === new_string) {
1183
1249
  return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
1184
1250
  }
1185
- if (old_string === "" && !existsSync3(filePath)) {
1251
+ if (old_string === "" && !existsSync4(filePath)) {
1186
1252
  try {
1187
1253
  await mkdir4(dirname5(filePath), { recursive: true });
1188
1254
  await writeFile3(filePath, new_string, "utf8");
1255
+ recordRead(filePath);
1189
1256
  return {
1190
1257
  ok: true,
1191
1258
  content: `\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6\uFF1A${filePath}\uFF08${new_string.length} \u5B57\u7B26\uFF09`
@@ -1194,7 +1261,7 @@ async function call2(input) {
1194
1261
  return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
1195
1262
  }
1196
1263
  }
1197
- if (!existsSync3(filePath)) {
1264
+ if (!existsSync4(filePath)) {
1198
1265
  return {
1199
1266
  ok: false,
1200
1267
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1250,6 +1317,7 @@ async function call2(input) {
1250
1317
  const normalizedReplaced = applyLineEnding(replaced, originalLineEnding);
1251
1318
  try {
1252
1319
  await writeFile3(filePath, normalizedReplaced, "utf8");
1320
+ recordRead(filePath);
1253
1321
  } catch (e) {
1254
1322
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1255
1323
  }
@@ -1262,19 +1330,6 @@ async function call2(input) {
1262
1330
  \u884C\u6570\uFF1A${linesBefore} \u2192 ${linesAfter}`
1263
1331
  };
1264
1332
  }
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
1333
  function findFuzzyMatchHint(fileContent, target) {
1279
1334
  const MIN_OVERLAP_RATIO = 0.5;
1280
1335
  const MAX_HINTS = 3;
@@ -1349,16 +1404,16 @@ var editTool = {
1349
1404
 
1350
1405
  // src/tools/edit/multi-edit.ts
1351
1406
  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")
1407
+ import { existsSync as existsSync5 } from "fs";
1408
+ import { z as z4 } from "zod";
1409
+ var editItemSchema = z4.object({
1410
+ 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"),
1411
+ new_string: z4.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C"),
1412
+ 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
1413
  });
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")
1414
+ var inputSchema3 = z4.object({
1415
+ file_path: filePathField("\u7F16\u8F91"),
1416
+ 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
1417
  });
1363
1418
  var parameters3 = toToolParameters(inputSchema3);
1364
1419
  var description3 = `Performs multiple exact string replacements in a single file, applied atomically (all-or-nothing).
@@ -1371,28 +1426,15 @@ Usage:
1371
1426
  - Each \`old_string\` must be unique in the current content (after prior edits) unless \`replace_all=true\`.
1372
1427
  - 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
1428
  - 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
1429
  async function call3(input) {
1388
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
1430
+ const pathResult = resolveToolPath(input.file_path);
1389
1431
  if (!pathResult.ok) return pathResult;
1390
1432
  const filePath = pathResult.resolvedPath;
1391
1433
  const freshness = assertFresh(filePath);
1392
1434
  if (!freshness.ok) {
1393
1435
  return { ok: false, error: freshness.error };
1394
1436
  }
1395
- if (!existsSync4(filePath)) {
1437
+ if (!existsSync5(filePath)) {
1396
1438
  return {
1397
1439
  ok: false,
1398
1440
  error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
@@ -1433,7 +1475,7 @@ async function call3(input) {
1433
1475
  searchTarget = actualOld;
1434
1476
  processedNewString = preserveQuoteStyle(edit.old_string, actualOld, edit.new_string);
1435
1477
  }
1436
- const occurrences = countOccurrences2(currentContent, searchTarget);
1478
+ const occurrences = countOccurrences(currentContent, searchTarget);
1437
1479
  if (occurrences === 0) {
1438
1480
  return {
1439
1481
  ok: false,
@@ -1446,12 +1488,13 @@ async function call3(input) {
1446
1488
  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
1489
  };
1448
1490
  }
1449
- currentContent = replaceAll ? splitReplaceAll2(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1491
+ currentContent = replaceAll ? splitReplaceAll(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
1450
1492
  }
1451
1493
  const lineEnding = await detectFileLineEndings(filePath);
1452
1494
  const finalContent = applyLineEnding(currentContent, lineEnding);
1453
1495
  try {
1454
1496
  await writeFile4(filePath, finalContent, "utf8");
1497
+ recordRead(filePath);
1455
1498
  } catch (e) {
1456
1499
  return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
1457
1500
  }
@@ -1473,12 +1516,12 @@ var multiEditTool = {
1473
1516
 
1474
1517
  // src/tools/glob/glob.ts
1475
1518
  import { stat as stat2 } from "fs/promises";
1476
- import { isAbsolute, resolve as resolve5 } from "path";
1519
+ import { isAbsolute, resolve as resolve6 } from "path";
1477
1520
  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')
1521
+ import { z as z5 } from "zod";
1522
+ var inputSchema4 = z5.object({
1523
+ pattern: z5.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
1524
+ 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
1525
  });
1483
1526
  var parameters4 = toToolParameters(inputSchema4);
1484
1527
  var description4 = `- Fast file pattern matching tool that works with any codebase size
@@ -1487,7 +1530,7 @@ var description4 = `- Fast file pattern matching tool that works with any codeba
1487
1530
  - Use this tool when you need to find files by name patterns
1488
1531
  - When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
1489
1532
  async function call4(input) {
1490
- const cwd = input.path ? resolve5(input.path) : getWorkingDir();
1533
+ const cwd = input.path ? resolve6(input.path) : getWorkingDir();
1491
1534
  const pattern = input.pattern.replace(/\\/g, "/");
1492
1535
  let matches;
1493
1536
  try {
@@ -1508,7 +1551,7 @@ async function call4(input) {
1508
1551
  }
1509
1552
  const withMtime = await Promise.all(
1510
1553
  matches.map(async (rel) => {
1511
- const abs = isAbsolute(rel) ? rel : resolve5(cwd, rel);
1554
+ const abs = isAbsolute(rel) ? rel : resolve6(cwd, rel);
1512
1555
  try {
1513
1556
  const st = await stat2(abs);
1514
1557
  return { path: rel, mtime: st.mtimeMs };
@@ -1544,13 +1587,13 @@ var globTool = {
1544
1587
 
1545
1588
  // src/tools/grep/grep.ts
1546
1589
  import { spawn as spawn3 } from "child_process";
1547
- import { resolve as resolve7 } from "path";
1548
- import { z as z5 } from "zod";
1590
+ import { resolve as resolve8 } from "path";
1591
+ import { z as z6 } from "zod";
1549
1592
 
1550
1593
  // src/tools/grep/rgPath.ts
1551
1594
  import { spawn as spawn2 } from "child_process";
1552
- import { chmodSync, existsSync as existsSync5 } from "fs";
1553
- import { resolve as resolve6 } from "path";
1595
+ import { chmodSync, existsSync as existsSync6 } from "fs";
1596
+ import { resolve as resolve7 } from "path";
1554
1597
  var cached;
1555
1598
  async function resolveRgPath() {
1556
1599
  if (cached !== void 0) return cached;
@@ -1559,15 +1602,15 @@ async function resolveRgPath() {
1559
1602
  }
1560
1603
  async function detect() {
1561
1604
  const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
1562
- if (fromEnv && existsSync5(fromEnv)) return fromEnv;
1605
+ if (fromEnv && existsSync6(fromEnv)) return fromEnv;
1563
1606
  const vendored = vendoredRgPath();
1564
- if (vendored && existsSync5(vendored)) {
1607
+ if (vendored && existsSync6(vendored)) {
1565
1608
  ensureExecutable(vendored);
1566
1609
  return vendored;
1567
1610
  }
1568
1611
  if (await trySpawn("rg")) return "rg";
1569
1612
  for (const candidate of claudeCodeCandidates()) {
1570
- if (existsSync5(candidate)) {
1613
+ if (existsSync6(candidate)) {
1571
1614
  ensureExecutable(candidate);
1572
1615
  return candidate;
1573
1616
  }
@@ -1577,7 +1620,7 @@ async function detect() {
1577
1620
  function vendoredRgPath() {
1578
1621
  try {
1579
1622
  const projectRoot = findPackageRoot(import.meta.url);
1580
- return resolve6(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1623
+ return resolve7(projectRoot, "vendor", "ripgrep", subdir(), exeName());
1581
1624
  } catch {
1582
1625
  return null;
1583
1626
  }
@@ -1636,42 +1679,42 @@ function claudeCodeCandidates() {
1636
1679
  const npmRoots = [];
1637
1680
  if (platform === "win32") {
1638
1681
  if (process.env.APPDATA) {
1639
- npmRoots.push(resolve6(process.env.APPDATA, "npm", "node_modules"));
1682
+ npmRoots.push(resolve7(process.env.APPDATA, "npm", "node_modules"));
1640
1683
  }
1641
1684
  if (process.env.USERPROFILE) {
1642
1685
  npmRoots.push(
1643
- resolve6(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1686
+ resolve7(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
1644
1687
  );
1645
1688
  }
1646
1689
  } else {
1647
1690
  const home = process.env.HOME ?? "";
1648
1691
  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"));
1692
+ npmRoots.push(resolve7(home, ".npm-global", "lib", "node_modules"));
1693
+ npmRoots.push(resolve7(home, ".npm", "lib", "node_modules"));
1694
+ npmRoots.push(resolve7(home, "node_modules"));
1652
1695
  }
1653
1696
  npmRoots.push("/usr/local/lib/node_modules");
1654
1697
  npmRoots.push("/usr/lib/node_modules");
1655
1698
  npmRoots.push("/opt/homebrew/lib/node_modules");
1656
1699
  }
1657
1700
  return npmRoots.map(
1658
- (root) => resolve6(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1701
+ (root) => resolve7(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1659
1702
  );
1660
1703
  }
1661
1704
 
1662
1705
  // 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")
1706
+ var inputSchema5 = z6.object({
1707
+ pattern: z6.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
1708
+ path: z6.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
1709
+ glob: z6.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
1710
+ type: z6.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
1711
+ 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"),
1712
+ "-i": z6.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
1713
+ "-n": z6.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
1714
+ "-A": z6.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1715
+ "-B": z6.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1716
+ "-C": z6.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
1717
+ 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
1718
  });
1676
1719
  var parameters5 = toToolParameters(inputSchema5);
1677
1720
  var description5 = `A powerful search tool built on ripgrep.
@@ -1701,7 +1744,7 @@ async function call5(input, signal) {
1701
1744
  args.push("--max-columns-preview");
1702
1745
  args.push("--sort", "modified");
1703
1746
  args.push("-e", input.pattern);
1704
- args.push(input.path ? resolve7(input.path) : ".");
1747
+ args.push(input.path ? resolve8(input.path) : ".");
1705
1748
  const rgPath = await resolveRgPath();
1706
1749
  if (!rgPath) {
1707
1750
  return {
@@ -1772,18 +1815,18 @@ var grepTool = {
1772
1815
  import { createReadStream } from "fs";
1773
1816
  import { readFile as readFile8, stat as stat3 } from "fs/promises";
1774
1817
  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}`)
1818
+ import { z as z7 } from "zod";
1819
+ var inputSchema6 = z7.object({
1820
+ file_path: filePathField("\u8BFB\u53D6"),
1821
+ offset: z7.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
1822
+ 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
1823
  });
1781
1824
  var parameters6 = toToolParameters(inputSchema6);
1782
1825
  var description6 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1783
1826
  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
1827
 
1785
1828
  Usage:
1786
- - The file_path parameter must be an absolute path, not a relative path
1829
+ - 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
1830
  - By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file
1788
1831
  - You can optionally specify a line offset and limit (especially handy for long files)
1789
1832
  - Results are returned using cat -n format, with line numbers starting at 1
@@ -1794,10 +1837,8 @@ var STREAM_THRESHOLD = 1024 * 1024;
1794
1837
  async function call6(input) {
1795
1838
  const offset = input.offset ?? 1;
1796
1839
  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
- }
1840
+ const pathResult = resolveToolPath(input.file_path);
1841
+ if (!pathResult.ok) return pathResult;
1801
1842
  const filePath = pathResult.resolvedPath;
1802
1843
  if (isBlockedDevicePath(filePath)) {
1803
1844
  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 +1970,7 @@ var readTool = {
1929
1970
  };
1930
1971
 
1931
1972
  // src/tools/webfetch/webfetch.ts
1932
- import { z as z7 } from "zod";
1973
+ import { z as z8 } from "zod";
1933
1974
 
1934
1975
  // src/tools/webfetch/preapproved.ts
1935
1976
  var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
@@ -2214,9 +2255,9 @@ function cleanCache() {
2214
2255
  }
2215
2256
  }
2216
2257
  }
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")
2258
+ var inputSchema7 = z8.object({
2259
+ url: z8.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
2260
+ 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
2261
  });
2221
2262
  var parameters7 = toToolParameters(inputSchema7);
2222
2263
  var description7 = `- Fetches content from a specified URL and processes it using an AI model.
@@ -2436,7 +2477,7 @@ var webfetchTool = {
2436
2477
  };
2437
2478
 
2438
2479
  // src/tools/webbrowser/webbrowser.ts
2439
- import { z as z8 } from "zod";
2480
+ import { z as z9 } from "zod";
2440
2481
 
2441
2482
  // src/tools/webbrowser/browser.ts
2442
2483
  import os from "os";
@@ -2472,12 +2513,12 @@ function screenshotPath(prefix = "browser") {
2472
2513
  }
2473
2514
 
2474
2515
  // 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)")
2516
+ var inputSchema8 = z9.object({
2517
+ action: z9.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
2518
+ url: z9.string().url().optional().describe("URL to navigate to (required for navigate action)"),
2519
+ selector: z9.string().optional().describe("CSS selector for click/fill/submit actions"),
2520
+ value: z9.string().optional().describe("Value to fill in input fields"),
2521
+ timeout: z9.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
2481
2522
  });
2482
2523
  var parameters8 = toToolParameters(inputSchema8);
2483
2524
  var description8 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
@@ -2625,12 +2666,12 @@ var webbrowserTool = {
2625
2666
  };
2626
2667
 
2627
2668
  // 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")
2669
+ import { z as z10 } from "zod";
2670
+ var inputSchema9 = z10.object({
2671
+ 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"),
2672
+ max_results: z10.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
2673
+ 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"),
2674
+ topic: z10.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
2634
2675
  });
2635
2676
  var parameters9 = toToolParameters(inputSchema9);
2636
2677
  var description9 = `- Searches the public web via the Tavily Search API and returns structured results.
@@ -2730,14 +2771,14 @@ var webSearchTool = {
2730
2771
  };
2731
2772
 
2732
2773
  // src/tools/write/write.ts
2733
- import { existsSync as existsSync6 } from "fs";
2774
+ import { existsSync as existsSync7 } from "fs";
2734
2775
  import { mkdir as mkdir5, stat as stat4, writeFile as writeFile5 } from "fs/promises";
2735
2776
  import { dirname as dirname6 } from "path";
2736
- import { z as z10 } from "zod";
2777
+ import { z as z11 } from "zod";
2737
2778
  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")
2779
+ var inputSchema10 = z11.object({
2780
+ file_path: filePathField("\u5199\u5165"),
2781
+ content: z11.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
2741
2782
  });
2742
2783
  var parameters10 = toToolParameters(inputSchema10);
2743
2784
  var description10 = `Writes a file to the local filesystem.
@@ -2749,7 +2790,7 @@ Usage:
2749
2790
  - ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
2750
2791
  - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
2751
2792
  async function call10(input) {
2752
- const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
2793
+ const pathResult = resolveToolPath(input.file_path);
2753
2794
  if (!pathResult.ok) return pathResult;
2754
2795
  const filePath = pathResult.resolvedPath;
2755
2796
  const freshness = assertFresh(filePath);
@@ -2766,7 +2807,7 @@ async function call10(input) {
2766
2807
  try {
2767
2808
  await mkdir5(dirname6(filePath), { recursive: true });
2768
2809
  let originalSize = 0;
2769
- const fileExisted = existsSync6(filePath);
2810
+ const fileExisted = existsSync7(filePath);
2770
2811
  if (fileExisted) {
2771
2812
  try {
2772
2813
  const st = await stat4(filePath);
@@ -2795,8 +2836,10 @@ async function call10(input) {
2795
2836
  } else {
2796
2837
  await writeFile5(filePath, contentToWrite, "utf8");
2797
2838
  }
2839
+ recordRead(filePath);
2798
2840
  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`;
2841
+ const newSize = Buffer.byteLength(contentToWrite, "utf8");
2842
+ const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u8282 \u2192 \u65B0\u5185\u5BB9 ${newSize} \u5B57\u8282\uFF09` : `\uFF08${newSize} \u5B57\u8282\uFF09`;
2800
2843
  const bomInfo = bomEncoding === "utf8-bom" ? "\uFF0C\u5DF2\u4FDD\u7559 UTF-8 BOM" : "";
2801
2844
  return {
2802
2845
  ok: true,
@@ -2876,39 +2919,52 @@ import { Box as Box7, Text as Text7 } from "ink";
2876
2919
  import { useCallback, useMemo, useState } from "react";
2877
2920
  import { Box, Text, useApp, useInput } from "ink";
2878
2921
  import { jsx, jsxs } from "react/jsx-runtime";
2879
- var DEFAULT_CONTEXT_WINDOW2 = 128e3;
2922
+ var FALLBACK_CONTEXT_WINDOW = 128e3;
2880
2923
  var PRESETS = [
2881
2924
  {
2882
2925
  name: "minimax",
2883
2926
  label: "MiniMax (\u6D77\u87BA)",
2884
2927
  baseURL: "https://api.minimax.chat/v1",
2885
- models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"]
2928
+ models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"],
2929
+ contextWindow: 204800
2886
2930
  },
2887
2931
  {
2888
2932
  name: "deepseek",
2889
2933
  label: "DeepSeek",
2890
2934
  baseURL: "https://api.deepseek.com/v1",
2891
- models: ["deepseek-chat", "deepseek-reasoner"]
2935
+ models: ["deepseek-chat", "deepseek-reasoner"],
2936
+ contextWindow: 128e3
2892
2937
  },
2893
2938
  {
2894
2939
  name: "openai",
2895
2940
  label: "OpenAI",
2896
2941
  baseURL: "https://api.openai.com/v1",
2897
- models: ["gpt-4o", "gpt-4o-mini"]
2942
+ models: ["gpt-4o", "gpt-4o-mini"],
2943
+ contextWindow: 128e3
2898
2944
  },
2899
2945
  {
2900
2946
  name: "moonshot",
2901
2947
  label: "Moonshot (Kimi)",
2902
2948
  baseURL: "https://api.moonshot.cn/v1",
2903
- models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]
2949
+ models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"],
2950
+ contextWindow: 128e3,
2951
+ modelContextWindows: {
2952
+ "moonshot-v1-8k": 8e3,
2953
+ "moonshot-v1-32k": 32e3,
2954
+ "moonshot-v1-128k": 128e3
2955
+ }
2904
2956
  },
2905
2957
  {
2906
2958
  name: "custom",
2907
2959
  label: "\u81EA\u5B9A\u4E49\uFF08\u624B\u52A8\u586B baseURL\uFF09",
2908
2960
  baseURL: "",
2909
- models: []
2961
+ models: [],
2962
+ contextWindow: FALLBACK_CONTEXT_WINDOW
2910
2963
  }
2911
2964
  ];
2965
+ function resolveContextWindow(preset, model) {
2966
+ return preset.modelContextWindows?.[model] ?? preset.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
2967
+ }
2912
2968
  function ConfigWizard({ onSuccess }) {
2913
2969
  const { exit } = useApp();
2914
2970
  const [step, setStep] = useState("preset");
@@ -2937,51 +2993,68 @@ function ConfigWizard({ onSuccess }) {
2937
2993
  },
2938
2994
  [baseURL, model, preset]
2939
2995
  );
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");
2996
+ const handleTest = useCallback(
2997
+ async (finalModel) => {
2998
+ setStep("testing");
2999
+ setErrorMsg(null);
3000
+ try {
3001
+ const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
3002
+ const resp = await fetch(url, {
3003
+ method: "POST",
3004
+ headers: {
3005
+ "Content-Type": "application/json",
3006
+ Authorization: `Bearer ${apiKey}`
3007
+ },
3008
+ body: JSON.stringify({
3009
+ model: finalModel,
3010
+ messages: [{ role: "user", content: "ping" }],
3011
+ max_tokens: 5,
3012
+ stream: false
3013
+ })
3014
+ });
3015
+ if (!resp.ok) {
3016
+ const body = await resp.text().catch(() => "");
3017
+ throw new Error(
3018
+ `HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`
3019
+ );
3020
+ }
3021
+ const data = await resp.json();
3022
+ if (!Array.isArray(data.choices)) {
3023
+ throw new Error("\u54CD\u5E94\u4E2D\u6CA1\u6709 choices \u5B57\u6BB5\uFF0C\u53EF\u80FD\u4E0D\u662F OpenAI \u517C\u5BB9\u534F\u8BAE");
3024
+ }
3025
+ setDraft("");
3026
+ setStep("tavily");
3027
+ } catch (e) {
3028
+ setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
3029
+ setStep("error");
2965
3030
  }
3031
+ },
3032
+ [apiKey, baseURL]
3033
+ );
3034
+ const finalize = useCallback(
3035
+ async (finalModel, tavilyApiKey) => {
3036
+ const contextWindow = resolveContextWindow(preset, finalModel);
2966
3037
  await saveConfig({
2967
3038
  baseURL,
2968
3039
  apiKey,
2969
- model,
3040
+ model: finalModel,
2970
3041
  provider: preset.name,
2971
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3042
+ contextWindow,
3043
+ tavilyApiKey
2972
3044
  });
3045
+ if (tavilyApiKey) {
3046
+ process.env.TAVILY_API_KEY = tavilyApiKey;
3047
+ }
2973
3048
  onSuccess({
2974
3049
  name: preset.name,
2975
3050
  baseURL,
2976
3051
  apiKey,
2977
- model,
2978
- contextWindow: DEFAULT_CONTEXT_WINDOW2
3052
+ model: finalModel,
3053
+ contextWindow
2979
3054
  });
2980
- } catch (e) {
2981
- setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
2982
- setStep("error");
2983
- }
2984
- }, [apiKey, baseURL, model, onSuccess, preset.name]);
3055
+ },
3056
+ [apiKey, baseURL, onSuccess, preset]
3057
+ );
2985
3058
  useInput((input, key) => {
2986
3059
  if (key.escape || key.ctrl && input === "c") {
2987
3060
  exit();
@@ -3036,9 +3109,10 @@ function ConfigWizard({ onSuccess }) {
3036
3109
  }
3037
3110
  }
3038
3111
  if (key.return) {
3039
- if (draft.trim().length === 0) return;
3040
- setModel(draft.trim());
3041
- void handleTest();
3112
+ const finalModel = draft.trim();
3113
+ if (finalModel.length === 0) return;
3114
+ setModel(finalModel);
3115
+ void handleTest(finalModel);
3042
3116
  } else if (key.backspace || key.delete) {
3043
3117
  setDraft((s) => s.slice(0, -1));
3044
3118
  } else if (input && !key.meta && !key.ctrl && !key.upArrow && !key.downArrow) {
@@ -3046,6 +3120,17 @@ function ConfigWizard({ onSuccess }) {
3046
3120
  }
3047
3121
  return;
3048
3122
  }
3123
+ if (step === "tavily") {
3124
+ if (key.return) {
3125
+ const tavilyApiKey = draft.trim().length > 0 ? draft.trim() : void 0;
3126
+ void finalize(model, tavilyApiKey);
3127
+ } else if (key.backspace || key.delete) {
3128
+ setDraft((s) => s.slice(0, -1));
3129
+ } else if (input && !key.meta && !key.ctrl) {
3130
+ setDraft((s) => s + input);
3131
+ }
3132
+ return;
3133
+ }
3049
3134
  if (step === "error") {
3050
3135
  if (key.return) {
3051
3136
  enterStep("preset");
@@ -3058,7 +3143,7 @@ function ConfigWizard({ onSuccess }) {
3058
3143
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "minimal-agent \xB7 \u9996\u6B21\u914D\u7F6E\u5411\u5BFC" }) }),
3059
3144
  /* @__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
3145
  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" }),
3146
+ /* @__PURE__ */ jsx(Text, { children: "Step 1/5 \xB7 \u9009\u62E9 provider \u9884\u8BBE\uFF08\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF09" }),
3062
3147
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === presetIndex ? "cyan" : void 0, children: [
3063
3148
  i === presetIndex ? "> " : " ",
3064
3149
  p.label,
@@ -3066,12 +3151,12 @@ function ConfigWizard({ onSuccess }) {
3066
3151
  ] }, p.name)) })
3067
3152
  ] }),
3068
3153
  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" }),
3154
+ /* @__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
3155
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
3071
3156
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664" })
3072
3157
  ] }),
3073
3158
  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" }),
3159
+ /* @__PURE__ */ jsx(Text, { children: "Step 3/5 \xB7 \u8F93\u5165 API key\uFF08\u8F93\u5165\u5185\u5BB9\u4E0D\u4F1A\u663E\u793A\uFF09" }),
3075
3160
  /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
3076
3161
  /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3077
3162
  "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664\uFF08\u5DF2\u8F93\u5165 ",
@@ -3081,7 +3166,7 @@ function ConfigWizard({ onSuccess }) {
3081
3166
  ] }),
3082
3167
  step === "model" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3083
3168
  /* @__PURE__ */ jsxs(Text, { children: [
3084
- "Step 4/4 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3169
+ "Step 4/5 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
3085
3170
  preset.models.length > 0 ? "\uFF08\u2191\u2193 \u5207\u6362\u9884\u8BBE\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\uFF09" : ""
3086
3171
  ] }),
3087
3172
  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 +3187,17 @@ function ConfigWizard({ onSuccess }) {
3102
3187
  model
3103
3188
  ] })
3104
3189
  ] }),
3190
+ step === "tavily" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3191
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 LLM \u8FDE\u63A5\u6D4B\u8BD5\u901A\u8FC7" }),
3192
+ /* @__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" }) }),
3193
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
3194
+ /* @__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" }),
3195
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3196
+ "\u514D\u8D39 key \u7533\u8BF7\uFF1Ahttps://tavily.com/ \xB7 \u5DF2\u8F93\u5165 ",
3197
+ draft.length,
3198
+ " \u5B57\u7B26"
3199
+ ] })
3200
+ ] }),
3105
3201
  step === "error" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3106
3202
  /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 \u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25" }),
3107
3203
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMsg ?? "\u672A\u77E5\u9519\u8BEF" }) }),
@@ -4020,22 +4116,26 @@ function useTokenUsage(messages, provider) {
4020
4116
 
4021
4117
  // src/ui/StatusLine.tsx
4022
4118
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
4023
- function StatusLine({ provider, history, pluginLoop }) {
4119
+ function StatusLine({
4120
+ provider,
4121
+ history,
4122
+ pluginProgress
4123
+ }) {
4024
4124
  const usage = useTokenUsage(history, provider);
4025
4125
  const ratio = usage.tokens / usage.threshold;
4026
4126
  const color = ratio >= 1 ? "red" : ratio >= 0.7 ? "yellow" : "green";
4027
4127
  const cwdDisplay = shortenPath(getWorkingDir());
4028
4128
  return /* @__PURE__ */ jsxs4(Box4, { children: [
4029
- pluginLoop && /* @__PURE__ */ jsxs4(Fragment, { children: [
4129
+ pluginProgress && /* @__PURE__ */ jsxs4(Fragment, { children: [
4030
4130
  /* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
4031
4131
  "\u{1F504} ",
4032
- pluginLoop.pluginName,
4132
+ pluginProgress.pluginId,
4033
4133
  " "
4034
4134
  ] }),
4035
4135
  /* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
4036
- pluginLoop.current,
4136
+ pluginProgress.current,
4037
4137
  "/",
4038
- pluginLoop.max
4138
+ pluginProgress.max
4039
4139
  ] }),
4040
4140
  /* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " })
4041
4141
  ] }),
@@ -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
  }
@@ -4350,24 +4452,8 @@ function resolveCommand(input) {
4350
4452
  }
4351
4453
  return null;
4352
4454
  }
4353
- var COMMAND_DEFAULTS = {
4354
- "ralph-loop": ["--max-iterations", "50"]
4355
- };
4356
- function applyDefaultArgs(cmdName, args) {
4357
- const defaults = COMMAND_DEFAULTS[cmdName];
4358
- if (!defaults) return args;
4359
- const existing = new Set(args.trim().split(/\s+/).filter(Boolean));
4360
- let result = args;
4361
- for (let i = 0; i < defaults.length; i += 2) {
4362
- const flag = defaults[i];
4363
- if (existing.has(flag)) continue;
4364
- result = result.trim() ? `${result} ${flag} ${defaults[i + 1]}` : `${flag} ${defaults[i + 1]}`;
4365
- }
4366
- return result;
4367
- }
4368
4455
  function buildCommandInput(resolved) {
4369
- const { cmd, arguments: rawArgs } = resolved;
4370
- const args = applyDefaultArgs(cmd.name, rawArgs);
4456
+ const { cmd, arguments: args } = resolved;
4371
4457
  let input = cmd.promptBody;
4372
4458
  input = input.replaceAll("${CLAUDE_PLUGIN_ROOT}", cmd.pluginRoot);
4373
4459
  input = input.replaceAll("$ARGUMENTS", args);
@@ -4379,537 +4465,47 @@ function buildCommandInput(resolved) {
4379
4465
  }
4380
4466
  return input;
4381
4467
  }
4382
- function getActiveStopHookPlugins() {
4383
- const result = [];
4384
- for (const plugin of pluginCache.values()) {
4385
- if (plugin.hasStopHook) {
4386
- result.push(plugin.root);
4387
- }
4388
- }
4389
- return result;
4390
- }
4391
-
4392
- // src/plugins/stopHook.ts
4393
- import { readFile as readFile10 } from "fs/promises";
4394
- import { join as join7 } from "path";
4395
- import { spawn as spawn4 } from "child_process";
4396
- async function loadStopHookConfig(pluginRoot) {
4397
- const hooksJsonPath = join7(pluginRoot, "hooks", "hooks.json");
4398
- try {
4399
- const raw = await readFile10(hooksJsonPath, "utf8");
4400
- const parsed = JSON.parse(raw);
4401
- const stopEntries = parsed?.hooks?.Stop;
4402
- if (!Array.isArray(stopEntries)) return [];
4403
- const commands = [];
4404
- for (const entry of stopEntries) {
4405
- const hooks = entry.hooks;
4406
- if (Array.isArray(hooks)) {
4407
- for (const h of hooks) {
4408
- if (h.type === "command" && h.command) {
4409
- commands.push(h);
4410
- }
4411
- }
4412
- }
4413
- }
4414
- return commands;
4415
- } catch {
4416
- return [];
4417
- }
4418
- }
4419
- async function executeStopHooks(pluginRoots, transcriptText) {
4420
- if (process.platform === "win32") {
4421
- return { decision: "pass" };
4422
- }
4423
- for (const pluginRoot of pluginRoots) {
4424
- const hookConfigs = await loadStopHookConfig(pluginRoot);
4425
- for (const hookConfig of hookConfigs) {
4426
- const result = await runSingleStopHook(hookConfig, pluginRoot, transcriptText);
4427
- if (result.decision === "block") {
4428
- return result;
4429
- }
4430
- }
4431
- }
4432
- return { decision: "pass" };
4433
- }
4434
- function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
4435
- const resolvedCommand = hookConfig.command.replaceAll("${CLAUDE_PLUGIN_ROOT}", pluginRoot);
4436
- return new Promise((resolve9) => {
4437
- const child = spawn4("bash", [resolvedCommand], {
4438
- env: {
4439
- ...process.env,
4440
- CLAUDE_PLUGIN_ROOT: pluginRoot
4441
- }
4442
- });
4443
- let stdout = "";
4444
- child.stdout.on("data", (data) => {
4445
- stdout += data.toString();
4446
- });
4447
- child.on("error", () => {
4448
- resolve9({ decision: "pass" });
4449
- });
4450
- child.on("close", (code) => {
4451
- if (code !== 0) {
4452
- resolve9({ decision: "pass" });
4453
- return;
4454
- }
4455
- const trimmed = stdout.trim();
4456
- if (!trimmed) {
4457
- resolve9({ decision: "pass" });
4458
- return;
4459
- }
4460
- try {
4461
- const parsed = JSON.parse(trimmed);
4462
- if (parsed.decision === "block") {
4463
- resolve9({
4464
- decision: "block",
4465
- reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
4466
- systemMessage: typeof parsed.systemMessage === "string" ? parsed.systemMessage : void 0
4467
- });
4468
- return;
4469
- }
4470
- resolve9({ decision: "pass" });
4471
- } catch {
4472
- resolve9({ decision: "pass" });
4473
- }
4474
- });
4475
- child.stdin.write(transcriptText);
4476
- child.stdin.end();
4477
- });
4478
- }
4479
-
4480
- // src/plugins/verificationGate.ts
4481
- import { existsSync as existsSync7, readFileSync } from "fs";
4482
- import { spawn as spawn5 } from "child_process";
4483
- function parseVerifyArg(arg) {
4484
- const colonIdx = arg.indexOf(":");
4485
- if (colonIdx < 0) return null;
4486
- const type = arg.slice(0, colonIdx).trim().toLowerCase();
4487
- const value = arg.slice(colonIdx + 1).trim();
4488
- switch (type) {
4489
- case "shell":
4490
- return { type: "shell", command: value, timeout: 3e4 };
4491
- case "file_exists":
4492
- return { type: "file_exists", file: value };
4493
- case "file_contains": {
4494
- const sep2 = value.indexOf(":");
4495
- if (sep2 < 0) return null;
4496
- return {
4497
- type: "file_contains",
4498
- file: value.slice(0, sep2),
4499
- pattern: value.slice(sep2 + 1)
4500
- };
4501
- }
4502
- case "test_count": {
4503
- const count = parseInt(value, 10);
4504
- if (isNaN(count) || count < 0) return null;
4505
- return { type: "test_count", minCount: count };
4506
- }
4507
- default:
4508
- return null;
4509
- }
4510
- }
4511
- function parseVerifyArgs(args) {
4512
- const checks = [];
4513
- const regex = /--verify\s+("[^"]*"|\S+)/gi;
4514
- let match;
4515
- while ((match = regex.exec(args)) !== null) {
4516
- const raw = match[1].replace(/^"|"$/g, "");
4517
- const check = parseVerifyArg(raw);
4518
- if (check) checks.push(check);
4519
- }
4520
- return checks;
4521
- }
4522
- function runShell(command, timeout) {
4523
- return new Promise((resolve9) => {
4524
- const isWin = process.platform === "win32";
4525
- const child = isWin ? spawn5("cmd", ["/c", command], { timeout, env: process.env }) : spawn5("bash", ["-c", command], { timeout, env: process.env });
4526
- let stdout = "";
4527
- let stderr = "";
4528
- child.stdout.on("data", (d) => {
4529
- stdout += d.toString();
4530
- });
4531
- child.stderr.on("data", (d) => {
4532
- stderr += d.toString();
4533
- });
4534
- child.on("error", () => {
4535
- resolve9({ exitCode: null, stdout, stderr, errored: true });
4536
- });
4537
- child.on("close", (code) => {
4538
- resolve9({ exitCode: code, stdout, stderr, errored: false });
4539
- });
4540
- });
4541
- }
4542
- async function verifyShell(command, timeout) {
4543
- const r = await runShell(command, timeout);
4544
- if (r.errored) {
4545
- return {
4546
- check: { type: "shell", command },
4547
- passed: false,
4548
- output: `\u6267\u884C\u5931\u8D25`
4549
- };
4550
- }
4551
- return {
4552
- check: { type: "shell", command },
4553
- passed: r.exitCode === 0,
4554
- output: r.stdout.trim() || r.stderr.trim() || `exit code ${r.exitCode}`
4555
- };
4556
- }
4557
- function verifyFileExists(file) {
4558
- const exists = existsSync7(file);
4559
- return {
4560
- check: { type: "file_exists", file },
4561
- passed: exists,
4562
- output: exists ? "\u6587\u4EF6\u5B58\u5728" : `\u6587\u4EF6\u4E0D\u5B58\u5728: ${file}`
4563
- };
4564
- }
4565
- function verifyFileContains(file, pattern) {
4566
- try {
4567
- const content = readFileSync(file, "utf8");
4568
- const regex = new RegExp(pattern);
4569
- const matched = regex.test(content);
4570
- return {
4571
- check: { type: "file_contains", file, pattern },
4572
- passed: matched,
4573
- output: matched ? `\u6587\u4EF6\u5305\u542B "${pattern}"` : `\u6587\u4EF6\u4E0D\u5305\u542B "${pattern}"`
4574
- };
4575
- } catch {
4576
- return {
4577
- check: { type: "file_contains", file, pattern },
4578
- passed: false,
4579
- output: `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6: ${file}`
4580
- };
4581
- }
4582
- }
4583
- async function verifyTestCount(minCount) {
4584
- const r = await runShell(`bun test`, 6e4);
4585
- const combined = `${r.stdout}
4586
- ${r.stderr}`;
4587
- if (r.errored) {
4588
- return {
4589
- check: { type: "test_count", minCount },
4590
- passed: false,
4591
- output: "\u65E0\u6CD5\u6267\u884C bun test"
4592
- };
4593
- }
4594
- const matches = [...combined.matchAll(/(\d+)\s+pass/gi)];
4595
- const count = matches.length > 0 ? parseInt(matches[matches.length - 1][1], 10) : 0;
4596
- const passed = count >= minCount;
4597
- return {
4598
- check: { type: "test_count", minCount },
4599
- passed,
4600
- output: `${count} pass (\u9700\u8981 >=${minCount})`
4601
- };
4602
- }
4603
- async function runVerification(checks) {
4604
- if (checks.length === 0) {
4605
- return { passed: true, details: [], summary: "\u65E0\u9A8C\u8BC1\u9879" };
4606
- }
4607
- const results = [];
4608
- for (const check of checks) {
4609
- let result;
4610
- switch (check.type) {
4611
- case "shell":
4612
- result = await verifyShell(check.command ?? "", check.timeout ?? 3e4);
4613
- break;
4614
- case "file_exists":
4615
- result = verifyFileExists(check.file ?? "");
4616
- break;
4617
- case "file_contains":
4618
- result = verifyFileContains(check.file ?? "", check.pattern ?? "");
4619
- break;
4620
- case "test_count":
4621
- result = await verifyTestCount(check.minCount ?? 0);
4622
- break;
4623
- default:
4624
- result = {
4625
- check,
4626
- passed: false,
4627
- output: `\u672A\u77E5\u9A8C\u8BC1\u7C7B\u578B: ${check.type}`
4628
- };
4629
- }
4630
- results.push(result);
4631
- }
4632
- const allPassed = results.every((r) => r.passed);
4633
- const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));
4634
- let summary;
4635
- if (allPassed) {
4636
- summary = `\u2705 \u5168\u90E8\u901A\u8FC7 (${results.length}/${results.length})`;
4637
- } else {
4638
- summary = `\u274C \u9A8C\u8BC1\u672A\u901A\u8FC7: ${failedNames.join(", ")}`;
4639
- }
4640
- return { passed: allPassed, details: results, summary };
4641
- }
4642
- function formatCheckName(check) {
4643
- switch (check.type) {
4644
- case "shell":
4645
- return `shell(${(check.command ?? "").slice(0, 40)})`;
4646
- case "file_exists":
4647
- return `exists(${check.file})`;
4648
- case "file_contains":
4649
- return `contains(${check.file}:${check.pattern})`;
4650
- case "test_count":
4651
- return `tests(>=${check.minCount})`;
4652
- default:
4653
- return check.type;
4654
- }
4655
- }
4656
4468
 
4657
- // src/plugins/goalState.ts
4658
- 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";
4469
+ // src/plugins/pluginLoader.ts
4470
+ import { existsSync as existsSync8 } from "fs";
4660
4471
  import { join as join8 } from "path";
4661
- var Phase = /* @__PURE__ */ ((Phase2) => {
4662
- Phase2["PLAN"] = "plan";
4663
- Phase2["BUILD"] = "build";
4664
- Phase2["VERIFY"] = "verify";
4665
- Phase2["HEAL"] = "heal";
4666
- Phase2["DONE"] = "done";
4667
- return Phase2;
4668
- })(Phase || {});
4669
- var VALID_PHASES = new Set(Object.values(Phase));
4670
- var PHASE_TRANSITIONS = {
4671
- ["plan" /* PLAN */]: {
4672
- plan_complete: "build" /* BUILD */,
4673
- stuck: "plan" /* PLAN */
4674
- },
4675
- ["build" /* BUILD */]: {
4676
- task_complete: "verify" /* VERIFY */,
4677
- need_replan: "plan" /* PLAN */,
4678
- tests_failing: "heal" /* HEAL */
4679
- },
4680
- ["verify" /* VERIFY */]: {
4681
- all_pass: "build" /* BUILD */,
4682
- failures: "heal" /* HEAL */,
4683
- goal_complete: "done" /* DONE */
4684
- },
4685
- ["heal" /* HEAL */]: {
4686
- fixed: "verify" /* VERIFY */,
4687
- cannot_fix_locally: "plan" /* PLAN */
4688
- },
4689
- ["done" /* DONE */]: {}
4690
- };
4691
- function isLegalTransition(from, to) {
4692
- if (from === to) return true;
4693
- const allowed = PHASE_TRANSITIONS[from];
4694
- if (!allowed) return false;
4695
- return Object.values(allowed).includes(to);
4696
- }
4697
- var LEARNINGS_TAIL_LINES = 20;
4698
- var GoalState = class {
4699
- dir;
4700
- constructor(workspaceDir, sessionTag) {
4701
- const suffix = sessionTag ? `-${sessionTag}` : "";
4702
- this.dir = join8(workspaceDir, `.minimal-agent${suffix}`);
4703
- }
4704
- async reset() {
4705
- const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4706
- for (const f of files) {
4707
- try {
4708
- await unlink2(join8(this.dir, f));
4709
- } catch {
4710
- }
4711
- }
4712
- }
4713
- async init(goal, criteria) {
4714
- await mkdir6(this.dir, { recursive: true });
4715
- const files = {
4716
- goal: `# \u4E0D\u53EF\u53D8\u76EE\u6807 (IMMUTABLE GOAL)
4717
-
4718
- ${goal}
4719
- `,
4720
- completion: JSON.stringify(criteria, null, 2),
4721
- phase: "plan" /* PLAN */,
4722
- progress: "",
4723
- learnings: "",
4724
- decisions: ""
4725
- };
4726
- for (const [name, content] of Object.entries(files)) {
4727
- const path2 = join8(this.dir, `${name}.md`);
4728
- if (!existsSync8(path2)) {
4729
- await writeFile6(path2, content);
4730
- }
4731
- }
4732
- }
4733
- get goal() {
4734
- try {
4735
- return readFileSync2(join8(this.dir, "goal.md"), "utf8").trim();
4736
- } catch {
4737
- return "";
4738
- }
4739
- }
4740
- get completionCriteria() {
4741
- try {
4742
- const raw = readFileSync2(join8(this.dir, "completion.md"), "utf8").trim();
4743
- return JSON.parse(raw);
4744
- } catch {
4745
- return [];
4746
- }
4472
+ import { pathToFileURL } from "url";
4473
+ var loaderCache = /* @__PURE__ */ new Map();
4474
+ function isPluginApi(value) {
4475
+ if (!value || typeof value !== "object") return false;
4476
+ const { runCommand } = value;
4477
+ if (runCommand !== void 0 && typeof runCommand !== "function") return false;
4478
+ return true;
4479
+ }
4480
+ async function loadPluginApi(pluginRoot) {
4481
+ if (loaderCache.has(pluginRoot)) {
4482
+ return loaderCache.get(pluginRoot) ?? null;
4483
+ }
4484
+ const pluginTsPath = join8(pluginRoot, "plugin.ts");
4485
+ if (!existsSync8(pluginTsPath)) {
4486
+ loaderCache.set(pluginRoot, null);
4487
+ return null;
4747
4488
  }
4748
- get currentPhase() {
4749
- try {
4750
- const raw = readFileSync2(join8(this.dir, "phase.md"), "utf8").trim();
4751
- if (VALID_PHASES.has(raw)) {
4752
- return raw;
4753
- }
4754
- } catch {
4755
- }
4756
- return "plan" /* PLAN */;
4757
- }
4758
- /**
4759
- * 切换阶段。`reason` 是给人看的日志文本,不参与校验。
4760
- * 校验只看 from→to 是否在 PHASE_TRANSITIONS 表里有路径(任意 event 通向 to 即合法)。
4761
- * 想绕开 FSM 用 forceSetPhase。
4762
- */
4763
- async setPhase(phase, reason) {
4764
- if (!VALID_PHASES.has(phase)) {
4765
- throw new Error(`Invalid phase: ${phase}`);
4766
- }
4767
- const current = this.currentPhase;
4768
- if (!isLegalTransition(current, phase)) {
4769
- throw new Error(
4770
- `\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`
4489
+ try {
4490
+ const mod = await import(pathToFileURL(pluginTsPath).href);
4491
+ const candidate = mod.default;
4492
+ if (!isPluginApi(candidate)) {
4493
+ console.warn(
4494
+ `[minimal-agent] plugin.ts default export is not a valid PluginApi: ${pluginRoot}`
4771
4495
  );
4496
+ loaderCache.set(pluginRoot, null);
4497
+ return null;
4772
4498
  }
4773
- await writeFile6(join8(this.dir, "phase.md"), phase);
4774
- await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4775
- }
4776
- async forceSetPhase(phase, reason) {
4777
- if (!VALID_PHASES.has(phase)) {
4778
- throw new Error(`Invalid phase: ${phase}`);
4779
- }
4780
- await writeFile6(join8(this.dir, "phase.md"), phase);
4781
- await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4782
- }
4783
- async appendProgress(line) {
4784
- const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
4785
- await appendFile(join8(this.dir, "progress.md"), `[${timestamp}] ${line}
4786
- `);
4787
- }
4788
- tailProgress(n) {
4789
- try {
4790
- const content = readFileSync2(join8(this.dir, "progress.md"), "utf8");
4791
- const lines = content.trim().split("\n").filter(Boolean);
4792
- return lines.slice(-n).join("\n");
4793
- } catch {
4794
- return "";
4795
- }
4796
- }
4797
- async appendLearning(lesson) {
4798
- await appendFile(join8(this.dir, "learnings.md"), `- ${lesson}
4799
- `);
4800
- }
4801
- get learnings() {
4802
- try {
4803
- const raw = readFileSync2(join8(this.dir, "learnings.md"), "utf8").trim();
4804
- if (!raw) return "";
4805
- const lines = raw.split("\n").filter(Boolean);
4806
- return lines.slice(-LEARNINGS_TAIL_LINES).join("\n");
4807
- } catch {
4808
- return "";
4809
- }
4810
- }
4811
- async recordDecision(ctx, options, chosen, reasoning) {
4812
- const entry = {
4813
- iteration: ctx.iteration,
4814
- phase: ctx.phase,
4815
- contextSummary: ctx.summary,
4816
- options,
4817
- chosen,
4818
- reasoning
4819
- };
4820
- const line = JSON.stringify(entry);
4821
- await appendFile(join8(this.dir, "decisions.md"), `${line}
4822
- `);
4823
- }
4824
- findSimilarDecisions(ctx, k = 3) {
4825
- try {
4826
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4827
- const lines = content.trim().split("\n").filter(Boolean);
4828
- const entries = [];
4829
- for (const line of lines) {
4830
- try {
4831
- entries.push(JSON.parse(line));
4832
- } catch {
4833
- }
4834
- }
4835
- const scored = entries.map((entry) => ({
4836
- entry,
4837
- score: this._similarity(entry.contextSummary, ctx.summary)
4838
- })).sort((a, b) => b.score - a.score);
4839
- return scored.slice(0, k).filter((s) => s.score > 0.3).map((s) => s.entry);
4840
- } catch {
4841
- return [];
4842
- }
4843
- }
4844
- summarizeDecisions(maxEntries = 5) {
4845
- try {
4846
- const content = readFileSync2(join8(this.dir, "decisions.md"), "utf8");
4847
- const lines = content.trim().split("\n").filter(Boolean);
4848
- const entries = [];
4849
- for (const line of lines.slice(-maxEntries * 2)) {
4850
- try {
4851
- entries.push(JSON.parse(line));
4852
- } catch {
4853
- }
4854
- }
4855
- return entries.slice(-maxEntries).map(
4856
- (e) => `\u8FED\u4EE3 ${e.iteration}\uFF08${e.phase}\uFF09: \u5728 [${e.options.join(", ")}] \u4E2D\u9009\u62E9\u4E86 **${e.chosen}**\uFF0C\u539F\u56E0\uFF1A${e.reasoning.slice(0, 80)}`
4857
- ).join("\n");
4858
- } catch {
4859
- return "(\u65E0\u51B3\u7B56\u8BB0\u5F55)";
4860
- }
4861
- }
4862
- composeContext(iteration) {
4863
- const sections = [];
4864
- sections.push(`# \u4E0D\u53EF\u53D8\u76EE\u6807 (IMMUTABLE GOAL)
4865
- ${this.goal}
4866
- \u26A0\uFE0F \u6CE8\u610F\uFF1A\u4F60\u4E0D\u80FD\u4FEE\u6539\u6216\u6269\u5927\u4E0A\u8FF0\u76EE\u6807\u3002\u5982\u679C\u4F60\u8BA4\u4E3A\u76EE\u6807\u672C\u8EAB\u6709\u95EE\u9898\uFF0C\u8BF7\u8F93\u51FA <PROMISE>NEED_GOAL_REVISION</PROMISE> \u5E76\u505C\u6B62\u3002`);
4867
- sections.push(`# \u5F53\u524D\u9636\u6BB5: ${this.currentPhase.toUpperCase()}`);
4868
- const lrn = this.learnings;
4869
- if (lrn) {
4870
- sections.push(`# \u5173\u952E\u6559\u8BAD\uFF08\u5FC5\u987B\u9075\u5B88\uFF0C\u907F\u514D\u91CD\u590D\u8E29\u5751\uFF09
4871
- ${lrn}`);
4872
- }
4873
- const decisions = this.summarizeDecisions(3);
4874
- if (decisions !== "(\u65E0\u51B3\u7B56\u8BB0\u5F55)") {
4875
- sections.push(`# \u5386\u53F2\u5173\u952E\u51B3\u7B56\uFF08\u8BF7\u53C2\u8003\uFF0C\u907F\u514D\u91CD\u590D\u8BD5\u9519\uFF09
4876
- ${decisions}`);
4877
- }
4878
- const recentProgress = this.tailProgress(10);
4879
- if (recentProgress) {
4880
- sections.push(`# \u6700\u8FD1\u8FDB\u5EA6
4881
- ${recentProgress}`);
4882
- }
4883
- sections.push(`---
4884
-
4885
- # \u672C\u8F6E\u4EFB\u52A1 (\u8FED\u4EE3 ${iteration})`);
4886
- return sections.join("\n\n---\n\n");
4887
- }
4888
- async cleanup() {
4889
- const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
4890
- for (const f of files) {
4891
- try {
4892
- await unlink2(join8(this.dir, f));
4893
- } catch {
4894
- }
4895
- }
4896
- try {
4897
- await rmdir2(this.dir);
4898
- } catch {
4899
- }
4900
- }
4901
- _similarity(a, b) {
4902
- if (!a || !b) return 0;
4903
- const wordsA = new Set(a.toLowerCase().split(/\s+/));
4904
- const wordsB = new Set(b.toLowerCase().split(/\s+/));
4905
- let intersection = 0;
4906
- for (const word of wordsA) {
4907
- if (wordsB.has(word)) intersection++;
4908
- }
4909
- const union = (/* @__PURE__ */ new Set([...wordsA, ...wordsB])).size;
4910
- return union > 0 ? intersection / union : 0;
4499
+ loaderCache.set(pluginRoot, candidate);
4500
+ return candidate;
4501
+ } catch (err) {
4502
+ console.warn(
4503
+ `[minimal-agent] failed to load plugin.ts at ${pluginRoot}: ${err instanceof Error ? err.message : String(err)}`
4504
+ );
4505
+ loaderCache.set(pluginRoot, null);
4506
+ return null;
4911
4507
  }
4912
- };
4508
+ }
4913
4509
 
4914
4510
  // src/context/microCompactLite.ts
4915
4511
  import { createHash as createHash2 } from "crypto";
@@ -5147,28 +4743,23 @@ function previewArgs(rawJson) {
5147
4743
  }
5148
4744
 
5149
4745
  // src/plugins/pluginRunner.ts
5150
- var DEFAULT_MAX_ITERATIONS = 50;
5151
- var SAFETY_CEILING = 200;
5152
- function extractMaxIterations(args) {
5153
- const match = args.match(/--max-iterations\s+(\d+)/i);
5154
- return match ? parseInt(match[1], 10) : void 0;
5155
- }
5156
4746
  async function* runWithPlugins(userInput, options) {
5157
4747
  const { provider, history, signal } = options;
5158
4748
  const isSlashCommand = userInput.trimStart().startsWith("/");
5159
- let currentInput = userInput;
5160
- let matchedCmd = null;
5161
- if (isSlashCommand) {
5162
- await discoverPlugins();
5163
- matchedCmd = resolveCommand(userInput);
5164
- if (matchedCmd) {
5165
- currentInput = buildCommandInput(matchedCmd);
5166
- }
4749
+ if (!isSlashCommand) {
4750
+ yield* runQuery(userInput, {
4751
+ provider,
4752
+ history,
4753
+ signal,
4754
+ maxTurns: options.maxTurns,
4755
+ sessionState: options.sessionState
4756
+ });
4757
+ return;
5167
4758
  }
5168
- const activeHookPlugins = matchedCmd ? getActiveStopHookPlugins() : [];
5169
- const enterLoop = matchedCmd !== null && activeHookPlugins.length > 0;
5170
- if (!enterLoop) {
5171
- yield* runQuery(currentInput, {
4759
+ await discoverPlugins();
4760
+ const matched = resolveCommand(userInput);
4761
+ if (!matched) {
4762
+ yield* runQuery(userInput, {
5172
4763
  provider,
5173
4764
  history,
5174
4765
  signal,
@@ -5177,146 +4768,25 @@ async function* runWithPlugins(userInput, options) {
5177
4768
  });
5178
4769
  return;
5179
4770
  }
5180
- const cmd = matchedCmd.cmd;
5181
- const maxIter = Math.min(
5182
- extractMaxIterations(currentInput) ?? DEFAULT_MAX_ITERATIONS,
5183
- SAFETY_CEILING
5184
- );
5185
- yield {
5186
- type: "plugin_start",
5187
- pluginName: cmd.pluginName,
5188
- description: cmd.description
5189
- };
5190
- const checks = parseVerifyArgs(matchedCmd.arguments);
5191
- const goalState = new GoalState(getWorkingDir(), cmd.pluginName);
5192
- await goalState.reset();
5193
- await goalState.init(currentInput, checks);
5194
- await goalState.appendProgress(`=== Loop \u542F\u52A8 === \u76EE\u6807: ${currentInput.slice(0, 120)}...`);
5195
- const baseHistory = history.slice();
5196
- let iterationCount = 0;
5197
- let consecutiveFailures = 0;
5198
- let finalAssistantMsg = null;
5199
- try {
5200
- do {
5201
- iterationCount++;
5202
- if (iterationCount > maxIter) {
5203
- await goalState.forceSetPhase("done" /* DONE */, `\u8FBE\u5230\u8FED\u4EE3\u4E0A\u9650 ${maxIter}`);
5204
- await goalState.appendLearning(`[\u8FED\u4EE3\u4E0A\u9650] \u5FAA\u73AF\u5728 ${iterationCount - 1} \u8F6E\u540E\u5F3A\u5236\u7EC8\u6B62\uFF0C\u53EF\u80FD\u76EE\u6807\u8FC7\u5927\u6216\u9677\u5165\u6B7B\u5FAA\u73AF`);
5205
- yield { type: "error", error: `Loop \u5DF2\u8FBE\u8FED\u4EE3\u4E0A\u9650 ${maxIter}\uFF0C\u81EA\u52A8\u505C\u6B62` };
5206
- return;
5207
- }
5208
- if (signal?.aborted) {
5209
- yield { type: "interrupted" };
5210
- return;
5211
- }
5212
- yield {
5213
- type: "plugin_iteration",
5214
- pluginName: cmd.pluginName,
5215
- current: iterationCount,
5216
- max: maxIter
5217
- };
5218
- history.length = 0;
5219
- history.push(...baseHistory);
5220
- const freshContext = goalState.composeContext(iterationCount);
5221
- const enhancedInput = `${freshContext}
5222
-
5223
- ${currentInput}`;
5224
- yield* runQuery(enhancedInput, {
5225
- provider,
5226
- history,
5227
- signal,
5228
- maxTurns: options.maxTurns,
5229
- sessionState: options.sessionState
5230
- });
5231
- if (signal?.aborted) {
5232
- yield { type: "interrupted" };
5233
- return;
5234
- }
5235
- const lastAssistantIdx = (() => {
5236
- for (let i = history.length - 1; i >= 0; i--) {
5237
- if (history[i].role === "assistant") return i;
5238
- }
5239
- return -1;
5240
- })();
5241
- finalAssistantMsg = lastAssistantIdx >= 0 ? history[lastAssistantIdx] : null;
5242
- const lastAssistantText = finalAssistantMsg ? typeof finalAssistantMsg.content === "string" ? finalAssistantMsg.content : JSON.stringify(finalAssistantMsg.content) : "";
5243
- const hasCompleteSentinel = /<promise>(?:COMPLETE|DONE|GOAL_COMPLETE)<\/promise>/i.test(lastAssistantText);
5244
- const hasNeedReplan = /<PROMISE>NEED_REPLAN<\/PROMISE>/i.test(lastAssistantText);
5245
- if (hasCompleteSentinel) {
5246
- await goalState.forceSetPhase("verify" /* VERIFY */, "\u68C0\u6D4B\u5230\u5B8C\u6210\u54E8\u5175\uFF0C\u8FDB\u5165\u9A8C\u8BC1");
5247
- await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: \u68C0\u6D4B\u5230\u5B8C\u6210\u54E8\u5175\uFF0C\u8FD0\u884C\u9A8C\u8BC1\u95E8...`);
5248
- if (checks.length > 0) {
5249
- const vResult = await runVerification(checks);
5250
- if (!vResult.passed) {
5251
- consecutiveFailures++;
5252
- await goalState.appendLearning(
5253
- `[\u8FED\u4EE3 ${iterationCount}] \u58F0\u79F0\u5B8C\u6210\u4F46\u9A8C\u8BC1\u672A\u901A\u8FC7: ${vResult.summary}`
5254
- );
5255
- yield {
5256
- type: "error",
5257
- error: `\u26A0\uFE0F \u9A8C\u8BC1\u672A\u901A\u8FC7: ${vResult.summary}\u3002\u7EE7\u7EED\u5C1D\u8BD5...`
5258
- };
5259
- if (consecutiveFailures >= 3) {
5260
- await goalState.forceSetPhase(
5261
- "heal" /* HEAL */,
5262
- `\u8FDE\u7EED ${consecutiveFailures} \u6B21\u9A8C\u8BC1\u5931\u8D25`
5263
- );
5264
- } else {
5265
- await goalState.setPhase("build" /* BUILD */, "\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF0C\u8FD4\u56DE\u6784\u5EFA");
5266
- }
5267
- continue;
5268
- }
5269
- await goalState.appendProgress(`\u2705 \u9A8C\u8BC1\u901A\u8FC7: ${vResult.summary}`);
5270
- }
5271
- await goalState.setPhase("done" /* DONE */, "goal complete & verified");
5272
- yield {
5273
- type: "plugin_iteration",
5274
- pluginName: cmd.pluginName,
5275
- current: iterationCount,
5276
- max: maxIter
5277
- };
5278
- return;
5279
- }
5280
- if (hasNeedReplan) {
5281
- await goalState.forceSetPhase("plan" /* PLAN */, "agent \u8BF7\u6C42\u91CD\u65B0\u89C4\u5212");
5282
- await goalState.appendLearning("[NEED_REPLAN] Agent \u8BA4\u4E3A\u5F53\u524D\u65B9\u6848\u4E0D\u53EF\u884C\uFF0C\u9700\u8981\u91CD\u65B0\u89C4\u5212");
5283
- await goalState.appendProgress("Agent \u8BF7\u6C42 NEED_REPLAN\uFF0C\u56DE PLAN \u9636\u6BB5");
5284
- consecutiveFailures = 0;
5285
- continue;
5286
- }
5287
- if (goalState.currentPhase === "plan" /* PLAN */ && iterationCount >= 2) {
5288
- await goalState.setPhase("build" /* BUILD */, "\u89C4\u5212\u9636\u6BB5\u5DF2\u5B8C\u6210\uFF0C\u8FDB\u5165\u6784\u5EFA");
5289
- }
5290
- const hookTranscript = lastAssistantText;
5291
- const hookResult = await executeStopHooks(activeHookPlugins, hookTranscript);
5292
- if (hookResult.decision === "block" && hookResult.reason) {
5293
- currentInput = hookResult.reason;
5294
- consecutiveFailures = 0;
5295
- await goalState.recordDecision(
5296
- { iteration: iterationCount, phase: goalState.currentPhase, summary: "stop-hook \u53CD\u9988" },
5297
- ["\u7EE7\u7EED\u5FAA\u73AF", "\u7EC8\u6B62"],
5298
- "\u7EE7\u7EED\u5FAA\u73AF",
5299
- hookResult.reason.slice(0, 200)
5300
- );
5301
- if (hookResult.systemMessage) {
5302
- baseHistory.push({
5303
- role: "user",
5304
- content: `[Plugin Stop Hook] ${hookResult.systemMessage}`
5305
- });
5306
- }
5307
- await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: Stop hook block\uFF0C\u6CE8\u5165\u53CD\u9988\u7EE7\u7EED`);
5308
- } else {
5309
- await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: \u65E0\u54E8\u5175 / hook pass\uFF0C\u7EE7\u7EED\u4E0B\u4E00\u8F6E`);
5310
- }
5311
- } while (true);
5312
- } finally {
5313
- history.length = 0;
5314
- history.push(...baseHistory);
5315
- if (finalAssistantMsg) {
5316
- history.push(finalAssistantMsg);
5317
- }
5318
- await goalState.cleanup();
4771
+ const api = await loadPluginApi(matched.cmd.pluginRoot);
4772
+ if (api?.runCommand) {
4773
+ yield* api.runCommand(matched.cmd.name, matched.arguments, {
4774
+ provider,
4775
+ history,
4776
+ signal,
4777
+ sessionState: options.sessionState,
4778
+ maxTurns: options.maxTurns
4779
+ });
4780
+ return;
5319
4781
  }
4782
+ const promptInput = buildCommandInput(matched);
4783
+ yield* runQuery(promptInput, {
4784
+ provider,
4785
+ history,
4786
+ signal,
4787
+ maxTurns: options.maxTurns,
4788
+ sessionState: options.sessionState
4789
+ });
5320
4790
  }
5321
4791
 
5322
4792
  // src/ui/hooks/useChat.ts
@@ -5330,7 +4800,7 @@ function useChat(args) {
5330
4800
  const [error, setError] = useState5(null);
5331
4801
  const [interrupted, setInterrupted] = useState5(false);
5332
4802
  const [compacting, setCompacting] = useState5(false);
5333
- const [pluginLoop, setPluginLoop] = useState5(null);
4803
+ const [pluginProgress, setPluginProgress] = useState5(null);
5334
4804
  const abortRef = useRef3(null);
5335
4805
  useEffect(() => {
5336
4806
  return () => {
@@ -5346,7 +4816,7 @@ function useChat(args) {
5346
4816
  setError(null);
5347
4817
  setInterrupted(false);
5348
4818
  setStreamingText("");
5349
- setPluginLoop(null);
4819
+ setPluginProgress(null);
5350
4820
  const ac = new AbortController();
5351
4821
  abortRef.current = ac;
5352
4822
  try {
@@ -5361,7 +4831,7 @@ function useChat(args) {
5361
4831
  setCompacting,
5362
4832
  setError,
5363
4833
  setInterrupted,
5364
- setPluginLoop,
4834
+ setPluginProgress,
5365
4835
  bump
5366
4836
  });
5367
4837
  }
@@ -5372,7 +4842,7 @@ function useChat(args) {
5372
4842
  setStreamingText("");
5373
4843
  setToolStatus(null);
5374
4844
  setCompacting(false);
5375
- setPluginLoop(null);
4845
+ setPluginProgress(null);
5376
4846
  abortRef.current = null;
5377
4847
  args.onPersist?.(historyRef.current);
5378
4848
  }
@@ -5429,7 +4899,7 @@ function useChat(args) {
5429
4899
  error,
5430
4900
  interrupted,
5431
4901
  compacting,
5432
- pluginLoop,
4902
+ pluginProgress,
5433
4903
  submit,
5434
4904
  abort,
5435
4905
  clearHistory,
@@ -5437,9 +4907,10 @@ function useChat(args) {
5437
4907
  };
5438
4908
  }
5439
4909
  function handleEvent(ev, setters) {
5440
- switch (ev.type) {
4910
+ const e = ev;
4911
+ switch (e.type) {
5441
4912
  case "text":
5442
- setters.setStreamingText((prev) => prev + ev.delta);
4913
+ setters.setStreamingText((prev) => prev + e.delta);
5443
4914
  break;
5444
4915
  case "assistant_message":
5445
4916
  setters.setStreamingText(() => "");
@@ -5447,8 +4918,8 @@ function handleEvent(ev, setters) {
5447
4918
  break;
5448
4919
  case "tool_start":
5449
4920
  setters.setToolStatus({
5450
- toolName: ev.toolName,
5451
- argsPreview: ev.argsPreview,
4921
+ toolName: e.toolName,
4922
+ argsPreview: e.argsPreview,
5452
4923
  status: "running"
5453
4924
  });
5454
4925
  setters.bump();
@@ -5474,16 +4945,15 @@ function handleEvent(ev, setters) {
5474
4945
  setters.bump();
5475
4946
  break;
5476
4947
  case "error":
5477
- setters.setError(ev.error);
4948
+ setters.setError(e.error);
5478
4949
  setters.bump();
5479
4950
  break;
5480
- case "plugin_start":
5481
- break;
5482
- case "plugin_iteration":
5483
- setters.setPluginLoop({
5484
- pluginName: ev.pluginName,
5485
- current: ev.current,
5486
- max: ev.max ?? 0
4951
+ case "plugin_progress":
4952
+ setters.setPluginProgress({
4953
+ pluginId: e.pluginId,
4954
+ current: e.current,
4955
+ max: e.max ?? 0,
4956
+ message: e.message
5487
4957
  });
5488
4958
  setters.bump();
5489
4959
  break;
@@ -5535,7 +5005,14 @@ function App({ provider, initialHistory }) {
5535
5005
  onCompact: chat2.compactNow
5536
5006
  }
5537
5007
  ),
5538
- /* @__PURE__ */ jsx6(StatusLine, { provider, history: chat2.history, pluginLoop: chat2.pluginLoop })
5008
+ /* @__PURE__ */ jsx6(
5009
+ StatusLine,
5010
+ {
5011
+ provider,
5012
+ history: chat2.history,
5013
+ pluginProgress: chat2.pluginProgress
5014
+ }
5015
+ )
5539
5016
  ] });
5540
5017
  }
5541
5018
 
@@ -5670,24 +5147,28 @@ async function runPrintMode(provider, args, initialHistory, options) {
5670
5147
  await saveContext(history);
5671
5148
  }
5672
5149
  function handleEvent2(event, output, verbose) {
5673
- switch (event.type) {
5150
+ if (event.type === "workflow_start" || event.type === "workflow_step_start" || event.type === "workflow_step_end" || event.type === "workflow_step_skipped" || event.type === "workflow_done" || event.type === "plugin_progress") {
5151
+ return {};
5152
+ }
5153
+ const e = event;
5154
+ switch (e.type) {
5674
5155
  case "text":
5675
- if (verbose) process.stdout.write(event.delta);
5676
- output.buffer += event.delta;
5156
+ if (verbose) process.stdout.write(e.delta);
5157
+ output.buffer += e.delta;
5677
5158
  return {};
5678
5159
  case "assistant_message":
5679
5160
  return {};
5680
5161
  case "tool_start":
5681
5162
  if (verbose) {
5682
5163
  process.stderr.write(`
5683
- \u{1F527} \u5DE5\u5177: ${event.toolName}(${event.argsPreview})
5164
+ \u{1F527} \u5DE5\u5177: ${e.toolName}(${e.argsPreview})
5684
5165
  `);
5685
5166
  }
5686
5167
  return {};
5687
5168
  case "tool_end":
5688
5169
  if (verbose) {
5689
5170
  process.stderr.write(
5690
- `${event.ok ? "\u2705" : "\u274C"} ${event.toolName}: ${truncateForDisplay(event.content)}
5171
+ `${e.ok ? "\u2705" : "\u274C"} ${e.toolName}: ${truncateForDisplay(e.content)}
5691
5172
  `
5692
5173
  );
5693
5174
  }
@@ -5698,7 +5179,7 @@ function handleEvent2(event, output, verbose) {
5698
5179
  case "compact_done":
5699
5180
  if (verbose) {
5700
5181
  process.stderr.write(
5701
- `\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${event.before} \u2192 ${event.after} tokens
5182
+ `\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${e.before} \u2192 ${e.after} tokens
5702
5183
  `
5703
5184
  );
5704
5185
  }
@@ -5708,20 +5189,20 @@ function handleEvent2(event, output, verbose) {
5708
5189
  return {};
5709
5190
  case "error":
5710
5191
  console.error(`
5711
- \u9519\u8BEF: ${event.error}`);
5192
+ \u9519\u8BEF: ${e.error}`);
5712
5193
  return { exitCode: 1 };
5713
5194
  default:
5714
5195
  return {};
5715
5196
  }
5716
5197
  }
5717
5198
  function readFromStdin() {
5718
- return new Promise((resolve9) => {
5199
+ return new Promise((resolve10) => {
5719
5200
  let data = "";
5720
5201
  let settled = false;
5721
5202
  const timer = setTimeout(() => {
5722
5203
  if (!settled) {
5723
5204
  settled = true;
5724
- resolve9("");
5205
+ resolve10("");
5725
5206
  }
5726
5207
  }, STDIN_TIMEOUT_MS);
5727
5208
  process.stdin.setEncoding("utf8");
@@ -5732,7 +5213,7 @@ function readFromStdin() {
5732
5213
  if (!settled) {
5733
5214
  clearTimeout(timer);
5734
5215
  settled = true;
5735
- resolve9(data.trim());
5216
+ resolve10(data.trim());
5736
5217
  }
5737
5218
  }
5738
5219
  process.stdin.on("data", onData);
@@ -5748,7 +5229,7 @@ async function main() {
5748
5229
  const args = process.argv.slice(2);
5749
5230
  const dirArg = extractCwdArg(args);
5750
5231
  if (dirArg) {
5751
- const abs = resolve8(dirArg);
5232
+ const abs = resolve9(dirArg);
5752
5233
  if (!existsSync9(abs)) {
5753
5234
  mkdirSync(abs, { recursive: true });
5754
5235
  }
@@ -5756,6 +5237,7 @@ async function main() {
5756
5237
  }
5757
5238
  initWorkingDir();
5758
5239
  await migrateLegacyContext(getWorkingDir());
5240
+ await applyToolKeysToEnv();
5759
5241
  if (args.includes("-h") || args.includes("--help")) {
5760
5242
  printHelp();
5761
5243
  return;