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.
- package/README.md +405 -122
- package/dist/main.js +423 -941
- package/package.json +5 -2
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +186 -0
- 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 +59 -0
- package/plugins/ralph-wiggum/hooks/hooks.json +15 -0
- package/plugins/ralph-wiggum/hooks/stop-hook.sh +191 -0
- package/plugins/ralph-wiggum/plugin.ts +275 -0
- package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +203 -0
- package/plugins/ralph-wiggum/src/goalState.ts +310 -0
- package/plugins/ralph-wiggum/src/sentinels.ts +24 -0
- package/plugins/ralph-wiggum/src/stopHookRunner.ts +136 -0
- package/plugins/ralph-wiggum/src/verificationGate.ts +252 -0
- package/plugins/ralph-wiggum/test/goalState.test.ts +410 -0
- package/plugins/ralph-wiggum/test/verificationGate.test.ts +122 -0
- package/plugins/workflow-runner/.claude-plugin/plugin.json +5 -0
- package/plugins/workflow-runner/commands/workflow.md +15 -0
- package/plugins/workflow-runner/commands/workflows.md +8 -0
- package/plugins/workflow-runner/plugin.ts +42 -0
- package/plugins/workflow-runner/src/expressions.ts +371 -0
- package/plugins/workflow-runner/src/index.ts +194 -0
- package/plugins/workflow-runner/src/loader.ts +193 -0
- package/plugins/workflow-runner/src/runner.ts +313 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +30 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +54 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +115 -0
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +41 -0
- package/plugins/workflow-runner/src/types.ts +183 -0
- package/plugins/workflow-runner/src/workflowState.ts +65 -0
- package/plugins/workflow-runner/test/cli.e2e.test.ts +114 -0
- package/plugins/workflow-runner/test/e2e.test.ts +268 -0
- package/plugins/workflow-runner/test/expressions.test.ts +140 -0
- package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +27 -0
- package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +49 -0
- package/plugins/workflow-runner/test/graceful.test.ts +139 -0
- package/plugins/workflow-runner/test/loader.test.ts +216 -0
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +230 -0
- package/plugins/workflow-runner/test/runner.test.ts +511 -0
- package/skills/config/SKILL.md +27 -1
- package/skills/image-gen-openrouter/SKILL.md +121 -0
- package/skills/subtitle-srt/SKILL.md +134 -0
- package/skills/tts-zh/SKILL.md +137 -0
- package/skills/video-compose/SKILL.md +139 -0
- package/workflows/book-review-short.yaml +99 -0
- package/workflows/e2e-write-greet.yaml +27 -0
- package/workflows/schema.json +74 -0
- 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
|
|
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");
|
|
@@ -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
|
|
907
|
+
import { existsSync as existsSync4 } from "fs";
|
|
865
908
|
import { dirname as dirname5 } from "path";
|
|
866
|
-
import { z as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 (
|
|
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 =
|
|
1153
|
-
file_path:
|
|
1154
|
-
old_string:
|
|
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:
|
|
1158
|
-
replace_all:
|
|
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 =
|
|
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 === "" && !
|
|
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 (!
|
|
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
|
|
1353
|
-
import { z as
|
|
1354
|
-
var editItemSchema =
|
|
1355
|
-
old_string:
|
|
1356
|
-
new_string:
|
|
1357
|
-
replace_all:
|
|
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 =
|
|
1360
|
-
file_path:
|
|
1361
|
-
edits:
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 ?
|
|
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
|
|
1519
|
+
import { isAbsolute, resolve as resolve6 } from "path";
|
|
1477
1520
|
import fg from "fast-glob";
|
|
1478
|
-
import { z as
|
|
1479
|
-
var inputSchema4 =
|
|
1480
|
-
pattern:
|
|
1481
|
-
path:
|
|
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 ?
|
|
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 :
|
|
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
|
|
1548
|
-
import { z as
|
|
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
|
|
1553
|
-
import { resolve as
|
|
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 &&
|
|
1605
|
+
if (fromEnv && existsSync6(fromEnv)) return fromEnv;
|
|
1563
1606
|
const vendored = vendoredRgPath();
|
|
1564
|
-
if (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 (
|
|
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
|
|
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(
|
|
1682
|
+
npmRoots.push(resolve7(process.env.APPDATA, "npm", "node_modules"));
|
|
1640
1683
|
}
|
|
1641
1684
|
if (process.env.USERPROFILE) {
|
|
1642
1685
|
npmRoots.push(
|
|
1643
|
-
|
|
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(
|
|
1650
|
-
npmRoots.push(
|
|
1651
|
-
npmRoots.push(
|
|
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) =>
|
|
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 =
|
|
1664
|
-
pattern:
|
|
1665
|
-
path:
|
|
1666
|
-
glob:
|
|
1667
|
-
type:
|
|
1668
|
-
output_mode:
|
|
1669
|
-
"-i":
|
|
1670
|
-
"-n":
|
|
1671
|
-
"-A":
|
|
1672
|
-
"-B":
|
|
1673
|
-
"-C":
|
|
1674
|
-
head_limit:
|
|
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 ?
|
|
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
|
|
1776
|
-
var inputSchema6 =
|
|
1777
|
-
file_path:
|
|
1778
|
-
offset:
|
|
1779
|
-
limit:
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
2218
|
-
url:
|
|
2219
|
-
prompt:
|
|
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
|
|
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 =
|
|
2476
|
-
action:
|
|
2477
|
-
url:
|
|
2478
|
-
selector:
|
|
2479
|
-
value:
|
|
2480
|
-
timeout:
|
|
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
|
|
2629
|
-
var inputSchema9 =
|
|
2630
|
-
query:
|
|
2631
|
-
max_results:
|
|
2632
|
-
search_depth:
|
|
2633
|
-
topic:
|
|
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
|
|
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
|
|
2777
|
+
import { z as z11 } from "zod";
|
|
2737
2778
|
var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
|
|
2738
|
-
var inputSchema10 =
|
|
2739
|
-
file_path:
|
|
2740
|
-
content:
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
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
|
|
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
|
|
3052
|
+
model: finalModel,
|
|
3053
|
+
contextWindow
|
|
2979
3054
|
});
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
|
|
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
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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({
|
|
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
|
-
|
|
4129
|
+
pluginProgress && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
4030
4130
|
/* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
|
|
4031
4131
|
"\u{1F504} ",
|
|
4032
|
-
|
|
4132
|
+
pluginProgress.pluginId,
|
|
4033
4133
|
" "
|
|
4034
4134
|
] }),
|
|
4035
4135
|
/* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
|
|
4036
|
-
|
|
4136
|
+
pluginProgress.current,
|
|
4037
4137
|
"/",
|
|
4038
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
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:
|
|
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/
|
|
4658
|
-
import {
|
|
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
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
return
|
|
4668
|
-
}
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
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
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
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
|
-
|
|
4774
|
-
|
|
4775
|
-
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
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
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
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
|
-
|
|
5169
|
-
const
|
|
5170
|
-
if (!
|
|
5171
|
-
yield* runQuery(
|
|
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
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4910
|
+
const e = ev;
|
|
4911
|
+
switch (e.type) {
|
|
5441
4912
|
case "text":
|
|
5442
|
-
setters.setStreamingText((prev) => prev +
|
|
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:
|
|
5451
|
-
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(
|
|
4948
|
+
setters.setError(e.error);
|
|
5478
4949
|
setters.bump();
|
|
5479
4950
|
break;
|
|
5480
|
-
case "
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
5676
|
-
output.buffer +=
|
|
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: ${
|
|
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
|
-
`${
|
|
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: ${
|
|
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: ${
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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;
|