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