minimal-agent 0.1.9 → 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 +117 -738
- package/package.json +4 -2
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +186 -0
- package/plugins/ralph-wiggum/commands/ralph-loop.md +6 -16
- package/plugins/ralph-wiggum/plugin.ts +275 -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/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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.tsx
|
|
4
4
|
import { render } from "ink";
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync9, mkdirSync } from "fs";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
import { resolve as resolve9 } from "path";
|
|
8
8
|
|
|
@@ -418,16 +418,12 @@ ${toolList}
|
|
|
418
418
|
${skillHint}
|
|
419
419
|
|
|
420
420
|
# \u63D2\u4EF6\u7CFB\u7EDF\uFF08\u88AB\u52A8\u89E6\u53D1\uFF09
|
|
421
|
-
- minimal-agent \u5728 \`plugins/\` \u76EE\u5F55\u4E0B\u52A0\u8F7D\u7528\u6237\u5B89\u88C5\u7684\u63D2\u4EF6\uFF0C\u66B4\u9732\u5F62\u5982
|
|
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
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
423
|
\u4E5F\u4E0D\u8981\u5728\u5DE5\u5177\u8C03\u7528\u91CC\u5047\u88C5\u5728\u8DD1\u63D2\u4EF6\u3002
|
|
424
|
-
- \u5F53\
|
|
425
|
-
\
|
|
426
|
-
|
|
427
|
-
- \u5982\u679C\u5F53\u8F6E\u7528\u6237\u6D88\u606F\u91CC\u51FA\u73B0 \`<promise>DONE</promise>\` / \`<PROMISE>NEED_REPLAN</PROMISE>\` \u54E8\u5175
|
|
428
|
-
\u6216 PLAN/BUILD/VERIFY/HEAL \u9636\u6BB5\u6807\u8BB0\uFF0C\u8BF4\u660E\u4F60\u6B63\u8FD0\u884C\u5728 ralph-loop \u5185\u2014\u2014\u4E25\u683C\u6309\u7167\u5F53\u8F6E\u6CE8\u5165\u7684
|
|
429
|
-
freshContext \u6307\u5F15\u884C\u52A8\uFF0C**\u4EC5\u5728\u4EFB\u52A1\u771F\u6B63\u5B8C\u6210\u65F6**\u8F93\u51FA \`<promise>DONE</promise>\`\u3002
|
|
430
|
-
- \u5728**\u5E38\u89C4\u5BF9\u8BDD**\uFF08\u65E0\u4E0A\u8FF0\u9636\u6BB5\u6807\u8BB0\uFF09\u4E2D**\u7981\u6B62**\u4F7F\u7528\u8FD9\u4E9B\u54E8\u5175\u548C\u9636\u6BB5\u8BCD\u3002
|
|
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
|
|
431
427
|
|
|
432
428
|
# \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
|
|
433
429
|
## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
|
|
@@ -4120,22 +4116,26 @@ function useTokenUsage(messages, provider) {
|
|
|
4120
4116
|
|
|
4121
4117
|
// src/ui/StatusLine.tsx
|
|
4122
4118
|
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
4123
|
-
function StatusLine({
|
|
4119
|
+
function StatusLine({
|
|
4120
|
+
provider,
|
|
4121
|
+
history,
|
|
4122
|
+
pluginProgress
|
|
4123
|
+
}) {
|
|
4124
4124
|
const usage = useTokenUsage(history, provider);
|
|
4125
4125
|
const ratio = usage.tokens / usage.threshold;
|
|
4126
4126
|
const color = ratio >= 1 ? "red" : ratio >= 0.7 ? "yellow" : "green";
|
|
4127
4127
|
const cwdDisplay = shortenPath(getWorkingDir());
|
|
4128
4128
|
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
4129
|
-
|
|
4129
|
+
pluginProgress && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
4130
4130
|
/* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
|
|
4131
4131
|
"\u{1F504} ",
|
|
4132
|
-
|
|
4132
|
+
pluginProgress.pluginId,
|
|
4133
4133
|
" "
|
|
4134
4134
|
] }),
|
|
4135
4135
|
/* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
|
|
4136
|
-
|
|
4136
|
+
pluginProgress.current,
|
|
4137
4137
|
"/",
|
|
4138
|
-
|
|
4138
|
+
pluginProgress.max
|
|
4139
4139
|
] }),
|
|
4140
4140
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " })
|
|
4141
4141
|
] }),
|
|
@@ -4452,24 +4452,8 @@ function resolveCommand(input) {
|
|
|
4452
4452
|
}
|
|
4453
4453
|
return null;
|
|
4454
4454
|
}
|
|
4455
|
-
var COMMAND_DEFAULTS = {
|
|
4456
|
-
"ralph-loop": ["--max-iterations", "50"]
|
|
4457
|
-
};
|
|
4458
|
-
function applyDefaultArgs(cmdName, args) {
|
|
4459
|
-
const defaults = COMMAND_DEFAULTS[cmdName];
|
|
4460
|
-
if (!defaults) return args;
|
|
4461
|
-
const existing = new Set(args.trim().split(/\s+/).filter(Boolean));
|
|
4462
|
-
let result = args;
|
|
4463
|
-
for (let i = 0; i < defaults.length; i += 2) {
|
|
4464
|
-
const flag = defaults[i];
|
|
4465
|
-
if (existing.has(flag)) continue;
|
|
4466
|
-
result = result.trim() ? `${result} ${flag} ${defaults[i + 1]}` : `${flag} ${defaults[i + 1]}`;
|
|
4467
|
-
}
|
|
4468
|
-
return result;
|
|
4469
|
-
}
|
|
4470
4455
|
function buildCommandInput(resolved) {
|
|
4471
|
-
const { cmd, arguments:
|
|
4472
|
-
const args = applyDefaultArgs(cmd.name, rawArgs);
|
|
4456
|
+
const { cmd, arguments: args } = resolved;
|
|
4473
4457
|
let input = cmd.promptBody;
|
|
4474
4458
|
input = input.replaceAll("${CLAUDE_PLUGIN_ROOT}", cmd.pluginRoot);
|
|
4475
4459
|
input = input.replaceAll("$ARGUMENTS", args);
|
|
@@ -4481,537 +4465,47 @@ function buildCommandInput(resolved) {
|
|
|
4481
4465
|
}
|
|
4482
4466
|
return input;
|
|
4483
4467
|
}
|
|
4484
|
-
function getActiveStopHookPlugins() {
|
|
4485
|
-
const result = [];
|
|
4486
|
-
for (const plugin of pluginCache.values()) {
|
|
4487
|
-
if (plugin.hasStopHook) {
|
|
4488
|
-
result.push(plugin.root);
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
return result;
|
|
4492
|
-
}
|
|
4493
4468
|
|
|
4494
|
-
// src/plugins/
|
|
4495
|
-
import {
|
|
4469
|
+
// src/plugins/pluginLoader.ts
|
|
4470
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4496
4471
|
import { join as join8 } from "path";
|
|
4497
|
-
import {
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
}
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
return commands;
|
|
4517
|
-
} catch {
|
|
4518
|
-
return [];
|
|
4519
|
-
}
|
|
4520
|
-
}
|
|
4521
|
-
async function executeStopHooks(pluginRoots, transcriptText) {
|
|
4522
|
-
if (process.platform === "win32") {
|
|
4523
|
-
return { decision: "pass" };
|
|
4524
|
-
}
|
|
4525
|
-
for (const pluginRoot of pluginRoots) {
|
|
4526
|
-
const hookConfigs = await loadStopHookConfig(pluginRoot);
|
|
4527
|
-
for (const hookConfig of hookConfigs) {
|
|
4528
|
-
const result = await runSingleStopHook(hookConfig, pluginRoot, transcriptText);
|
|
4529
|
-
if (result.decision === "block") {
|
|
4530
|
-
return result;
|
|
4531
|
-
}
|
|
4532
|
-
}
|
|
4533
|
-
}
|
|
4534
|
-
return { decision: "pass" };
|
|
4535
|
-
}
|
|
4536
|
-
function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
|
|
4537
|
-
const resolvedCommand = hookConfig.command.replaceAll("${CLAUDE_PLUGIN_ROOT}", pluginRoot);
|
|
4538
|
-
return new Promise((resolve10) => {
|
|
4539
|
-
const child = spawn4("bash", [resolvedCommand], {
|
|
4540
|
-
env: {
|
|
4541
|
-
...process.env,
|
|
4542
|
-
CLAUDE_PLUGIN_ROOT: pluginRoot
|
|
4543
|
-
}
|
|
4544
|
-
});
|
|
4545
|
-
let stdout = "";
|
|
4546
|
-
child.stdout.on("data", (data) => {
|
|
4547
|
-
stdout += data.toString();
|
|
4548
|
-
});
|
|
4549
|
-
child.on("error", () => {
|
|
4550
|
-
resolve10({ decision: "pass" });
|
|
4551
|
-
});
|
|
4552
|
-
child.on("close", (code) => {
|
|
4553
|
-
if (code !== 0) {
|
|
4554
|
-
resolve10({ decision: "pass" });
|
|
4555
|
-
return;
|
|
4556
|
-
}
|
|
4557
|
-
const trimmed = stdout.trim();
|
|
4558
|
-
if (!trimmed) {
|
|
4559
|
-
resolve10({ decision: "pass" });
|
|
4560
|
-
return;
|
|
4561
|
-
}
|
|
4562
|
-
try {
|
|
4563
|
-
const parsed = JSON.parse(trimmed);
|
|
4564
|
-
if (parsed.decision === "block") {
|
|
4565
|
-
resolve10({
|
|
4566
|
-
decision: "block",
|
|
4567
|
-
reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
|
|
4568
|
-
systemMessage: typeof parsed.systemMessage === "string" ? parsed.systemMessage : void 0
|
|
4569
|
-
});
|
|
4570
|
-
return;
|
|
4571
|
-
}
|
|
4572
|
-
resolve10({ decision: "pass" });
|
|
4573
|
-
} catch {
|
|
4574
|
-
resolve10({ decision: "pass" });
|
|
4575
|
-
}
|
|
4576
|
-
});
|
|
4577
|
-
child.stdin.write(transcriptText);
|
|
4578
|
-
child.stdin.end();
|
|
4579
|
-
});
|
|
4580
|
-
}
|
|
4581
|
-
|
|
4582
|
-
// src/plugins/verificationGate.ts
|
|
4583
|
-
import { existsSync as existsSync8, readFileSync } from "fs";
|
|
4584
|
-
import { spawn as spawn5 } from "child_process";
|
|
4585
|
-
function parseVerifyArg(arg) {
|
|
4586
|
-
const colonIdx = arg.indexOf(":");
|
|
4587
|
-
if (colonIdx < 0) return null;
|
|
4588
|
-
const type = arg.slice(0, colonIdx).trim().toLowerCase();
|
|
4589
|
-
const value = arg.slice(colonIdx + 1).trim();
|
|
4590
|
-
switch (type) {
|
|
4591
|
-
case "shell":
|
|
4592
|
-
return { type: "shell", command: value, timeout: 3e4 };
|
|
4593
|
-
case "file_exists":
|
|
4594
|
-
return { type: "file_exists", file: value };
|
|
4595
|
-
case "file_contains": {
|
|
4596
|
-
const sep2 = value.indexOf(":");
|
|
4597
|
-
if (sep2 < 0) return null;
|
|
4598
|
-
return {
|
|
4599
|
-
type: "file_contains",
|
|
4600
|
-
file: value.slice(0, sep2),
|
|
4601
|
-
pattern: value.slice(sep2 + 1)
|
|
4602
|
-
};
|
|
4603
|
-
}
|
|
4604
|
-
case "test_count": {
|
|
4605
|
-
const count = parseInt(value, 10);
|
|
4606
|
-
if (isNaN(count) || count < 0) return null;
|
|
4607
|
-
return { type: "test_count", minCount: count };
|
|
4608
|
-
}
|
|
4609
|
-
default:
|
|
4610
|
-
return null;
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
function parseVerifyArgs(args) {
|
|
4614
|
-
const checks = [];
|
|
4615
|
-
const regex = /--verify\s+("[^"]*"|\S+)/gi;
|
|
4616
|
-
let match;
|
|
4617
|
-
while ((match = regex.exec(args)) !== null) {
|
|
4618
|
-
const raw = match[1].replace(/^"|"$/g, "");
|
|
4619
|
-
const check = parseVerifyArg(raw);
|
|
4620
|
-
if (check) checks.push(check);
|
|
4621
|
-
}
|
|
4622
|
-
return checks;
|
|
4623
|
-
}
|
|
4624
|
-
function runShell(command, timeout) {
|
|
4625
|
-
return new Promise((resolve10) => {
|
|
4626
|
-
const isWin = process.platform === "win32";
|
|
4627
|
-
const child = isWin ? spawn5("cmd", ["/c", command], { timeout, env: process.env }) : spawn5("bash", ["-c", command], { timeout, env: process.env });
|
|
4628
|
-
let stdout = "";
|
|
4629
|
-
let stderr = "";
|
|
4630
|
-
child.stdout.on("data", (d) => {
|
|
4631
|
-
stdout += d.toString();
|
|
4632
|
-
});
|
|
4633
|
-
child.stderr.on("data", (d) => {
|
|
4634
|
-
stderr += d.toString();
|
|
4635
|
-
});
|
|
4636
|
-
child.on("error", () => {
|
|
4637
|
-
resolve10({ exitCode: null, stdout, stderr, errored: true });
|
|
4638
|
-
});
|
|
4639
|
-
child.on("close", (code) => {
|
|
4640
|
-
resolve10({ exitCode: code, stdout, stderr, errored: false });
|
|
4641
|
-
});
|
|
4642
|
-
});
|
|
4643
|
-
}
|
|
4644
|
-
async function verifyShell(command, timeout) {
|
|
4645
|
-
const r = await runShell(command, timeout);
|
|
4646
|
-
if (r.errored) {
|
|
4647
|
-
return {
|
|
4648
|
-
check: { type: "shell", command },
|
|
4649
|
-
passed: false,
|
|
4650
|
-
output: `\u6267\u884C\u5931\u8D25`
|
|
4651
|
-
};
|
|
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;
|
|
4652
4488
|
}
|
|
4653
|
-
return {
|
|
4654
|
-
check: { type: "shell", command },
|
|
4655
|
-
passed: r.exitCode === 0,
|
|
4656
|
-
output: r.stdout.trim() || r.stderr.trim() || `exit code ${r.exitCode}`
|
|
4657
|
-
};
|
|
4658
|
-
}
|
|
4659
|
-
function verifyFileExists(file) {
|
|
4660
|
-
const exists = existsSync8(file);
|
|
4661
|
-
return {
|
|
4662
|
-
check: { type: "file_exists", file },
|
|
4663
|
-
passed: exists,
|
|
4664
|
-
output: exists ? "\u6587\u4EF6\u5B58\u5728" : `\u6587\u4EF6\u4E0D\u5B58\u5728: ${file}`
|
|
4665
|
-
};
|
|
4666
|
-
}
|
|
4667
|
-
function verifyFileContains(file, pattern) {
|
|
4668
4489
|
try {
|
|
4669
|
-
const
|
|
4670
|
-
const
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
passed: matched,
|
|
4675
|
-
output: matched ? `\u6587\u4EF6\u5305\u542B "${pattern}"` : `\u6587\u4EF6\u4E0D\u5305\u542B "${pattern}"`
|
|
4676
|
-
};
|
|
4677
|
-
} catch {
|
|
4678
|
-
return {
|
|
4679
|
-
check: { type: "file_contains", file, pattern },
|
|
4680
|
-
passed: false,
|
|
4681
|
-
output: `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6: ${file}`
|
|
4682
|
-
};
|
|
4683
|
-
}
|
|
4684
|
-
}
|
|
4685
|
-
async function verifyTestCount(minCount) {
|
|
4686
|
-
const r = await runShell(`bun test`, 6e4);
|
|
4687
|
-
const combined = `${r.stdout}
|
|
4688
|
-
${r.stderr}`;
|
|
4689
|
-
if (r.errored) {
|
|
4690
|
-
return {
|
|
4691
|
-
check: { type: "test_count", minCount },
|
|
4692
|
-
passed: false,
|
|
4693
|
-
output: "\u65E0\u6CD5\u6267\u884C bun test"
|
|
4694
|
-
};
|
|
4695
|
-
}
|
|
4696
|
-
const matches = [...combined.matchAll(/(\d+)\s+pass/gi)];
|
|
4697
|
-
const count = matches.length > 0 ? parseInt(matches[matches.length - 1][1], 10) : 0;
|
|
4698
|
-
const passed = count >= minCount;
|
|
4699
|
-
return {
|
|
4700
|
-
check: { type: "test_count", minCount },
|
|
4701
|
-
passed,
|
|
4702
|
-
output: `${count} pass (\u9700\u8981 >=${minCount})`
|
|
4703
|
-
};
|
|
4704
|
-
}
|
|
4705
|
-
async function runVerification(checks) {
|
|
4706
|
-
if (checks.length === 0) {
|
|
4707
|
-
return { passed: true, details: [], summary: "\u65E0\u9A8C\u8BC1\u9879" };
|
|
4708
|
-
}
|
|
4709
|
-
const results = [];
|
|
4710
|
-
for (const check of checks) {
|
|
4711
|
-
let result;
|
|
4712
|
-
switch (check.type) {
|
|
4713
|
-
case "shell":
|
|
4714
|
-
result = await verifyShell(check.command ?? "", check.timeout ?? 3e4);
|
|
4715
|
-
break;
|
|
4716
|
-
case "file_exists":
|
|
4717
|
-
result = verifyFileExists(check.file ?? "");
|
|
4718
|
-
break;
|
|
4719
|
-
case "file_contains":
|
|
4720
|
-
result = verifyFileContains(check.file ?? "", check.pattern ?? "");
|
|
4721
|
-
break;
|
|
4722
|
-
case "test_count":
|
|
4723
|
-
result = await verifyTestCount(check.minCount ?? 0);
|
|
4724
|
-
break;
|
|
4725
|
-
default:
|
|
4726
|
-
result = {
|
|
4727
|
-
check,
|
|
4728
|
-
passed: false,
|
|
4729
|
-
output: `\u672A\u77E5\u9A8C\u8BC1\u7C7B\u578B: ${check.type}`
|
|
4730
|
-
};
|
|
4731
|
-
}
|
|
4732
|
-
results.push(result);
|
|
4733
|
-
}
|
|
4734
|
-
const allPassed = results.every((r) => r.passed);
|
|
4735
|
-
const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));
|
|
4736
|
-
let summary;
|
|
4737
|
-
if (allPassed) {
|
|
4738
|
-
summary = `\u2705 \u5168\u90E8\u901A\u8FC7 (${results.length}/${results.length})`;
|
|
4739
|
-
} else {
|
|
4740
|
-
summary = `\u274C \u9A8C\u8BC1\u672A\u901A\u8FC7: ${failedNames.join(", ")}`;
|
|
4741
|
-
}
|
|
4742
|
-
return { passed: allPassed, details: results, summary };
|
|
4743
|
-
}
|
|
4744
|
-
function formatCheckName(check) {
|
|
4745
|
-
switch (check.type) {
|
|
4746
|
-
case "shell":
|
|
4747
|
-
return `shell(${(check.command ?? "").slice(0, 40)})`;
|
|
4748
|
-
case "file_exists":
|
|
4749
|
-
return `exists(${check.file})`;
|
|
4750
|
-
case "file_contains":
|
|
4751
|
-
return `contains(${check.file}:${check.pattern})`;
|
|
4752
|
-
case "test_count":
|
|
4753
|
-
return `tests(>=${check.minCount})`;
|
|
4754
|
-
default:
|
|
4755
|
-
return check.type;
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
// src/plugins/goalState.ts
|
|
4760
|
-
import { mkdir as mkdir6, appendFile, writeFile as writeFile6, unlink as unlink2, rmdir as rmdir2 } from "fs/promises";
|
|
4761
|
-
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
|
|
4762
|
-
import { join as join9 } from "path";
|
|
4763
|
-
var Phase = /* @__PURE__ */ ((Phase2) => {
|
|
4764
|
-
Phase2["PLAN"] = "plan";
|
|
4765
|
-
Phase2["BUILD"] = "build";
|
|
4766
|
-
Phase2["VERIFY"] = "verify";
|
|
4767
|
-
Phase2["HEAL"] = "heal";
|
|
4768
|
-
Phase2["DONE"] = "done";
|
|
4769
|
-
return Phase2;
|
|
4770
|
-
})(Phase || {});
|
|
4771
|
-
var VALID_PHASES = new Set(Object.values(Phase));
|
|
4772
|
-
var PHASE_TRANSITIONS = {
|
|
4773
|
-
["plan" /* PLAN */]: {
|
|
4774
|
-
plan_complete: "build" /* BUILD */,
|
|
4775
|
-
stuck: "plan" /* PLAN */
|
|
4776
|
-
},
|
|
4777
|
-
["build" /* BUILD */]: {
|
|
4778
|
-
task_complete: "verify" /* VERIFY */,
|
|
4779
|
-
need_replan: "plan" /* PLAN */,
|
|
4780
|
-
tests_failing: "heal" /* HEAL */
|
|
4781
|
-
},
|
|
4782
|
-
["verify" /* VERIFY */]: {
|
|
4783
|
-
all_pass: "build" /* BUILD */,
|
|
4784
|
-
failures: "heal" /* HEAL */,
|
|
4785
|
-
goal_complete: "done" /* DONE */
|
|
4786
|
-
},
|
|
4787
|
-
["heal" /* HEAL */]: {
|
|
4788
|
-
fixed: "verify" /* VERIFY */,
|
|
4789
|
-
cannot_fix_locally: "plan" /* PLAN */
|
|
4790
|
-
},
|
|
4791
|
-
["done" /* DONE */]: {}
|
|
4792
|
-
};
|
|
4793
|
-
function isLegalTransition(from, to) {
|
|
4794
|
-
if (from === to) return true;
|
|
4795
|
-
const allowed = PHASE_TRANSITIONS[from];
|
|
4796
|
-
if (!allowed) return false;
|
|
4797
|
-
return Object.values(allowed).includes(to);
|
|
4798
|
-
}
|
|
4799
|
-
var LEARNINGS_TAIL_LINES = 20;
|
|
4800
|
-
var GoalState = class {
|
|
4801
|
-
dir;
|
|
4802
|
-
constructor(workspaceDir, sessionTag) {
|
|
4803
|
-
const suffix = sessionTag ? `-${sessionTag}` : "";
|
|
4804
|
-
this.dir = join9(workspaceDir, `.minimal-agent${suffix}`);
|
|
4805
|
-
}
|
|
4806
|
-
async reset() {
|
|
4807
|
-
const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
|
|
4808
|
-
for (const f of files) {
|
|
4809
|
-
try {
|
|
4810
|
-
await unlink2(join9(this.dir, f));
|
|
4811
|
-
} catch {
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
}
|
|
4815
|
-
async init(goal, criteria) {
|
|
4816
|
-
await mkdir6(this.dir, { recursive: true });
|
|
4817
|
-
const files = {
|
|
4818
|
-
goal: `# \u4E0D\u53EF\u53D8\u76EE\u6807 (IMMUTABLE GOAL)
|
|
4819
|
-
|
|
4820
|
-
${goal}
|
|
4821
|
-
`,
|
|
4822
|
-
completion: JSON.stringify(criteria, null, 2),
|
|
4823
|
-
phase: "plan" /* PLAN */,
|
|
4824
|
-
progress: "",
|
|
4825
|
-
learnings: "",
|
|
4826
|
-
decisions: ""
|
|
4827
|
-
};
|
|
4828
|
-
for (const [name, content] of Object.entries(files)) {
|
|
4829
|
-
const path2 = join9(this.dir, `${name}.md`);
|
|
4830
|
-
if (!existsSync9(path2)) {
|
|
4831
|
-
await writeFile6(path2, content);
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
get goal() {
|
|
4836
|
-
try {
|
|
4837
|
-
return readFileSync2(join9(this.dir, "goal.md"), "utf8").trim();
|
|
4838
|
-
} catch {
|
|
4839
|
-
return "";
|
|
4840
|
-
}
|
|
4841
|
-
}
|
|
4842
|
-
get completionCriteria() {
|
|
4843
|
-
try {
|
|
4844
|
-
const raw = readFileSync2(join9(this.dir, "completion.md"), "utf8").trim();
|
|
4845
|
-
return JSON.parse(raw);
|
|
4846
|
-
} catch {
|
|
4847
|
-
return [];
|
|
4848
|
-
}
|
|
4849
|
-
}
|
|
4850
|
-
get currentPhase() {
|
|
4851
|
-
try {
|
|
4852
|
-
const raw = readFileSync2(join9(this.dir, "phase.md"), "utf8").trim();
|
|
4853
|
-
if (VALID_PHASES.has(raw)) {
|
|
4854
|
-
return raw;
|
|
4855
|
-
}
|
|
4856
|
-
} catch {
|
|
4857
|
-
}
|
|
4858
|
-
return "plan" /* PLAN */;
|
|
4859
|
-
}
|
|
4860
|
-
/**
|
|
4861
|
-
* 切换阶段。`reason` 是给人看的日志文本,不参与校验。
|
|
4862
|
-
* 校验只看 from→to 是否在 PHASE_TRANSITIONS 表里有路径(任意 event 通向 to 即合法)。
|
|
4863
|
-
* 想绕开 FSM 用 forceSetPhase。
|
|
4864
|
-
*/
|
|
4865
|
-
async setPhase(phase, reason) {
|
|
4866
|
-
if (!VALID_PHASES.has(phase)) {
|
|
4867
|
-
throw new Error(`Invalid phase: ${phase}`);
|
|
4868
|
-
}
|
|
4869
|
-
const current = this.currentPhase;
|
|
4870
|
-
if (!isLegalTransition(current, phase)) {
|
|
4871
|
-
throw new Error(
|
|
4872
|
-
`\u975E\u6CD5\u9636\u6BB5\u5207\u6362: ${current} \u2192 ${phase}\u3002\u8BE5\u76EE\u6807\u9636\u6BB5\u4E0D\u5728 PHASE_TRANSITIONS[${current}] \u7684\u53EF\u8FBE\u96C6\u5408\u5185\uFF0C\u9700\u8981\u8D70 forceSetPhase\u3002`
|
|
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}`
|
|
4873
4495
|
);
|
|
4496
|
+
loaderCache.set(pluginRoot, null);
|
|
4497
|
+
return null;
|
|
4874
4498
|
}
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
|
|
4884
|
-
}
|
|
4885
|
-
async appendProgress(line) {
|
|
4886
|
-
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
4887
|
-
await appendFile(join9(this.dir, "progress.md"), `[${timestamp}] ${line}
|
|
4888
|
-
`);
|
|
4889
|
-
}
|
|
4890
|
-
tailProgress(n) {
|
|
4891
|
-
try {
|
|
4892
|
-
const content = readFileSync2(join9(this.dir, "progress.md"), "utf8");
|
|
4893
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
4894
|
-
return lines.slice(-n).join("\n");
|
|
4895
|
-
} catch {
|
|
4896
|
-
return "";
|
|
4897
|
-
}
|
|
4898
|
-
}
|
|
4899
|
-
async appendLearning(lesson) {
|
|
4900
|
-
await appendFile(join9(this.dir, "learnings.md"), `- ${lesson}
|
|
4901
|
-
`);
|
|
4902
|
-
}
|
|
4903
|
-
get learnings() {
|
|
4904
|
-
try {
|
|
4905
|
-
const raw = readFileSync2(join9(this.dir, "learnings.md"), "utf8").trim();
|
|
4906
|
-
if (!raw) return "";
|
|
4907
|
-
const lines = raw.split("\n").filter(Boolean);
|
|
4908
|
-
return lines.slice(-LEARNINGS_TAIL_LINES).join("\n");
|
|
4909
|
-
} catch {
|
|
4910
|
-
return "";
|
|
4911
|
-
}
|
|
4912
|
-
}
|
|
4913
|
-
async recordDecision(ctx, options, chosen, reasoning) {
|
|
4914
|
-
const entry = {
|
|
4915
|
-
iteration: ctx.iteration,
|
|
4916
|
-
phase: ctx.phase,
|
|
4917
|
-
contextSummary: ctx.summary,
|
|
4918
|
-
options,
|
|
4919
|
-
chosen,
|
|
4920
|
-
reasoning
|
|
4921
|
-
};
|
|
4922
|
-
const line = JSON.stringify(entry);
|
|
4923
|
-
await appendFile(join9(this.dir, "decisions.md"), `${line}
|
|
4924
|
-
`);
|
|
4925
|
-
}
|
|
4926
|
-
findSimilarDecisions(ctx, k = 3) {
|
|
4927
|
-
try {
|
|
4928
|
-
const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
|
|
4929
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
4930
|
-
const entries = [];
|
|
4931
|
-
for (const line of lines) {
|
|
4932
|
-
try {
|
|
4933
|
-
entries.push(JSON.parse(line));
|
|
4934
|
-
} catch {
|
|
4935
|
-
}
|
|
4936
|
-
}
|
|
4937
|
-
const scored = entries.map((entry) => ({
|
|
4938
|
-
entry,
|
|
4939
|
-
score: this._similarity(entry.contextSummary, ctx.summary)
|
|
4940
|
-
})).sort((a, b) => b.score - a.score);
|
|
4941
|
-
return scored.slice(0, k).filter((s) => s.score > 0.3).map((s) => s.entry);
|
|
4942
|
-
} catch {
|
|
4943
|
-
return [];
|
|
4944
|
-
}
|
|
4945
|
-
}
|
|
4946
|
-
summarizeDecisions(maxEntries = 5) {
|
|
4947
|
-
try {
|
|
4948
|
-
const content = readFileSync2(join9(this.dir, "decisions.md"), "utf8");
|
|
4949
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
4950
|
-
const entries = [];
|
|
4951
|
-
for (const line of lines.slice(-maxEntries * 2)) {
|
|
4952
|
-
try {
|
|
4953
|
-
entries.push(JSON.parse(line));
|
|
4954
|
-
} catch {
|
|
4955
|
-
}
|
|
4956
|
-
}
|
|
4957
|
-
return entries.slice(-maxEntries).map(
|
|
4958
|
-
(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)}`
|
|
4959
|
-
).join("\n");
|
|
4960
|
-
} catch {
|
|
4961
|
-
return "(\u65E0\u51B3\u7B56\u8BB0\u5F55)";
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
composeContext(iteration) {
|
|
4965
|
-
const sections = [];
|
|
4966
|
-
sections.push(`# \u4E0D\u53EF\u53D8\u76EE\u6807 (IMMUTABLE GOAL)
|
|
4967
|
-
${this.goal}
|
|
4968
|
-
\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`);
|
|
4969
|
-
sections.push(`# \u5F53\u524D\u9636\u6BB5: ${this.currentPhase.toUpperCase()}`);
|
|
4970
|
-
const lrn = this.learnings;
|
|
4971
|
-
if (lrn) {
|
|
4972
|
-
sections.push(`# \u5173\u952E\u6559\u8BAD\uFF08\u5FC5\u987B\u9075\u5B88\uFF0C\u907F\u514D\u91CD\u590D\u8E29\u5751\uFF09
|
|
4973
|
-
${lrn}`);
|
|
4974
|
-
}
|
|
4975
|
-
const decisions = this.summarizeDecisions(3);
|
|
4976
|
-
if (decisions !== "(\u65E0\u51B3\u7B56\u8BB0\u5F55)") {
|
|
4977
|
-
sections.push(`# \u5386\u53F2\u5173\u952E\u51B3\u7B56\uFF08\u8BF7\u53C2\u8003\uFF0C\u907F\u514D\u91CD\u590D\u8BD5\u9519\uFF09
|
|
4978
|
-
${decisions}`);
|
|
4979
|
-
}
|
|
4980
|
-
const recentProgress = this.tailProgress(10);
|
|
4981
|
-
if (recentProgress) {
|
|
4982
|
-
sections.push(`# \u6700\u8FD1\u8FDB\u5EA6
|
|
4983
|
-
${recentProgress}`);
|
|
4984
|
-
}
|
|
4985
|
-
sections.push(`---
|
|
4986
|
-
|
|
4987
|
-
# \u672C\u8F6E\u4EFB\u52A1 (\u8FED\u4EE3 ${iteration})`);
|
|
4988
|
-
return sections.join("\n\n---\n\n");
|
|
4989
|
-
}
|
|
4990
|
-
async cleanup() {
|
|
4991
|
-
const files = ["goal.md", "completion.md", "phase.md", "progress.md", "learnings.md", "decisions.md"];
|
|
4992
|
-
for (const f of files) {
|
|
4993
|
-
try {
|
|
4994
|
-
await unlink2(join9(this.dir, f));
|
|
4995
|
-
} catch {
|
|
4996
|
-
}
|
|
4997
|
-
}
|
|
4998
|
-
try {
|
|
4999
|
-
await rmdir2(this.dir);
|
|
5000
|
-
} catch {
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
_similarity(a, b) {
|
|
5004
|
-
if (!a || !b) return 0;
|
|
5005
|
-
const wordsA = new Set(a.toLowerCase().split(/\s+/));
|
|
5006
|
-
const wordsB = new Set(b.toLowerCase().split(/\s+/));
|
|
5007
|
-
let intersection = 0;
|
|
5008
|
-
for (const word of wordsA) {
|
|
5009
|
-
if (wordsB.has(word)) intersection++;
|
|
5010
|
-
}
|
|
5011
|
-
const union = (/* @__PURE__ */ new Set([...wordsA, ...wordsB])).size;
|
|
5012
|
-
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;
|
|
5013
4507
|
}
|
|
5014
|
-
}
|
|
4508
|
+
}
|
|
5015
4509
|
|
|
5016
4510
|
// src/context/microCompactLite.ts
|
|
5017
4511
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -5249,28 +4743,23 @@ function previewArgs(rawJson) {
|
|
|
5249
4743
|
}
|
|
5250
4744
|
|
|
5251
4745
|
// src/plugins/pluginRunner.ts
|
|
5252
|
-
var DEFAULT_MAX_ITERATIONS = 50;
|
|
5253
|
-
var SAFETY_CEILING = 200;
|
|
5254
|
-
function extractMaxIterations(args) {
|
|
5255
|
-
const match = args.match(/--max-iterations\s+(\d+)/i);
|
|
5256
|
-
return match ? parseInt(match[1], 10) : void 0;
|
|
5257
|
-
}
|
|
5258
4746
|
async function* runWithPlugins(userInput, options) {
|
|
5259
4747
|
const { provider, history, signal } = options;
|
|
5260
4748
|
const isSlashCommand = userInput.trimStart().startsWith("/");
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
}
|
|
4749
|
+
if (!isSlashCommand) {
|
|
4750
|
+
yield* runQuery(userInput, {
|
|
4751
|
+
provider,
|
|
4752
|
+
history,
|
|
4753
|
+
signal,
|
|
4754
|
+
maxTurns: options.maxTurns,
|
|
4755
|
+
sessionState: options.sessionState
|
|
4756
|
+
});
|
|
4757
|
+
return;
|
|
5269
4758
|
}
|
|
5270
|
-
|
|
5271
|
-
const
|
|
5272
|
-
if (!
|
|
5273
|
-
yield* runQuery(
|
|
4759
|
+
await discoverPlugins();
|
|
4760
|
+
const matched = resolveCommand(userInput);
|
|
4761
|
+
if (!matched) {
|
|
4762
|
+
yield* runQuery(userInput, {
|
|
5274
4763
|
provider,
|
|
5275
4764
|
history,
|
|
5276
4765
|
signal,
|
|
@@ -5279,146 +4768,25 @@ async function* runWithPlugins(userInput, options) {
|
|
|
5279
4768
|
});
|
|
5280
4769
|
return;
|
|
5281
4770
|
}
|
|
5282
|
-
const
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
const checks = parseVerifyArgs(matchedCmd.arguments);
|
|
5293
|
-
const goalState = new GoalState(getWorkingDir(), cmd.pluginName);
|
|
5294
|
-
await goalState.reset();
|
|
5295
|
-
await goalState.init(currentInput, checks);
|
|
5296
|
-
await goalState.appendProgress(`=== Loop \u542F\u52A8 === \u76EE\u6807: ${currentInput.slice(0, 120)}...`);
|
|
5297
|
-
const baseHistory = history.slice();
|
|
5298
|
-
let iterationCount = 0;
|
|
5299
|
-
let consecutiveFailures = 0;
|
|
5300
|
-
let finalAssistantMsg = null;
|
|
5301
|
-
try {
|
|
5302
|
-
do {
|
|
5303
|
-
iterationCount++;
|
|
5304
|
-
if (iterationCount > maxIter) {
|
|
5305
|
-
await goalState.forceSetPhase("done" /* DONE */, `\u8FBE\u5230\u8FED\u4EE3\u4E0A\u9650 ${maxIter}`);
|
|
5306
|
-
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`);
|
|
5307
|
-
yield { type: "error", error: `Loop \u5DF2\u8FBE\u8FED\u4EE3\u4E0A\u9650 ${maxIter}\uFF0C\u81EA\u52A8\u505C\u6B62` };
|
|
5308
|
-
return;
|
|
5309
|
-
}
|
|
5310
|
-
if (signal?.aborted) {
|
|
5311
|
-
yield { type: "interrupted" };
|
|
5312
|
-
return;
|
|
5313
|
-
}
|
|
5314
|
-
yield {
|
|
5315
|
-
type: "plugin_iteration",
|
|
5316
|
-
pluginName: cmd.pluginName,
|
|
5317
|
-
current: iterationCount,
|
|
5318
|
-
max: maxIter
|
|
5319
|
-
};
|
|
5320
|
-
history.length = 0;
|
|
5321
|
-
history.push(...baseHistory);
|
|
5322
|
-
const freshContext = goalState.composeContext(iterationCount);
|
|
5323
|
-
const enhancedInput = `${freshContext}
|
|
5324
|
-
|
|
5325
|
-
${currentInput}`;
|
|
5326
|
-
yield* runQuery(enhancedInput, {
|
|
5327
|
-
provider,
|
|
5328
|
-
history,
|
|
5329
|
-
signal,
|
|
5330
|
-
maxTurns: options.maxTurns,
|
|
5331
|
-
sessionState: options.sessionState
|
|
5332
|
-
});
|
|
5333
|
-
if (signal?.aborted) {
|
|
5334
|
-
yield { type: "interrupted" };
|
|
5335
|
-
return;
|
|
5336
|
-
}
|
|
5337
|
-
const lastAssistantIdx = (() => {
|
|
5338
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
5339
|
-
if (history[i].role === "assistant") return i;
|
|
5340
|
-
}
|
|
5341
|
-
return -1;
|
|
5342
|
-
})();
|
|
5343
|
-
finalAssistantMsg = lastAssistantIdx >= 0 ? history[lastAssistantIdx] : null;
|
|
5344
|
-
const lastAssistantText = finalAssistantMsg ? typeof finalAssistantMsg.content === "string" ? finalAssistantMsg.content : JSON.stringify(finalAssistantMsg.content) : "";
|
|
5345
|
-
const hasCompleteSentinel = /<promise>(?:COMPLETE|DONE|GOAL_COMPLETE)<\/promise>/i.test(lastAssistantText);
|
|
5346
|
-
const hasNeedReplan = /<PROMISE>NEED_REPLAN<\/PROMISE>/i.test(lastAssistantText);
|
|
5347
|
-
if (hasCompleteSentinel) {
|
|
5348
|
-
await goalState.forceSetPhase("verify" /* VERIFY */, "\u68C0\u6D4B\u5230\u5B8C\u6210\u54E8\u5175\uFF0C\u8FDB\u5165\u9A8C\u8BC1");
|
|
5349
|
-
await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: \u68C0\u6D4B\u5230\u5B8C\u6210\u54E8\u5175\uFF0C\u8FD0\u884C\u9A8C\u8BC1\u95E8...`);
|
|
5350
|
-
if (checks.length > 0) {
|
|
5351
|
-
const vResult = await runVerification(checks);
|
|
5352
|
-
if (!vResult.passed) {
|
|
5353
|
-
consecutiveFailures++;
|
|
5354
|
-
await goalState.appendLearning(
|
|
5355
|
-
`[\u8FED\u4EE3 ${iterationCount}] \u58F0\u79F0\u5B8C\u6210\u4F46\u9A8C\u8BC1\u672A\u901A\u8FC7: ${vResult.summary}`
|
|
5356
|
-
);
|
|
5357
|
-
yield {
|
|
5358
|
-
type: "error",
|
|
5359
|
-
error: `\u26A0\uFE0F \u9A8C\u8BC1\u672A\u901A\u8FC7: ${vResult.summary}\u3002\u7EE7\u7EED\u5C1D\u8BD5...`
|
|
5360
|
-
};
|
|
5361
|
-
if (consecutiveFailures >= 3) {
|
|
5362
|
-
await goalState.forceSetPhase(
|
|
5363
|
-
"heal" /* HEAL */,
|
|
5364
|
-
`\u8FDE\u7EED ${consecutiveFailures} \u6B21\u9A8C\u8BC1\u5931\u8D25`
|
|
5365
|
-
);
|
|
5366
|
-
} else {
|
|
5367
|
-
await goalState.setPhase("build" /* BUILD */, "\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF0C\u8FD4\u56DE\u6784\u5EFA");
|
|
5368
|
-
}
|
|
5369
|
-
continue;
|
|
5370
|
-
}
|
|
5371
|
-
await goalState.appendProgress(`\u2705 \u9A8C\u8BC1\u901A\u8FC7: ${vResult.summary}`);
|
|
5372
|
-
}
|
|
5373
|
-
await goalState.setPhase("done" /* DONE */, "goal complete & verified");
|
|
5374
|
-
yield {
|
|
5375
|
-
type: "plugin_iteration",
|
|
5376
|
-
pluginName: cmd.pluginName,
|
|
5377
|
-
current: iterationCount,
|
|
5378
|
-
max: maxIter
|
|
5379
|
-
};
|
|
5380
|
-
return;
|
|
5381
|
-
}
|
|
5382
|
-
if (hasNeedReplan) {
|
|
5383
|
-
await goalState.forceSetPhase("plan" /* PLAN */, "agent \u8BF7\u6C42\u91CD\u65B0\u89C4\u5212");
|
|
5384
|
-
await goalState.appendLearning("[NEED_REPLAN] Agent \u8BA4\u4E3A\u5F53\u524D\u65B9\u6848\u4E0D\u53EF\u884C\uFF0C\u9700\u8981\u91CD\u65B0\u89C4\u5212");
|
|
5385
|
-
await goalState.appendProgress("Agent \u8BF7\u6C42 NEED_REPLAN\uFF0C\u56DE PLAN \u9636\u6BB5");
|
|
5386
|
-
consecutiveFailures = 0;
|
|
5387
|
-
continue;
|
|
5388
|
-
}
|
|
5389
|
-
if (goalState.currentPhase === "plan" /* PLAN */ && iterationCount >= 2) {
|
|
5390
|
-
await goalState.setPhase("build" /* BUILD */, "\u89C4\u5212\u9636\u6BB5\u5DF2\u5B8C\u6210\uFF0C\u8FDB\u5165\u6784\u5EFA");
|
|
5391
|
-
}
|
|
5392
|
-
const hookTranscript = lastAssistantText;
|
|
5393
|
-
const hookResult = await executeStopHooks(activeHookPlugins, hookTranscript);
|
|
5394
|
-
if (hookResult.decision === "block" && hookResult.reason) {
|
|
5395
|
-
currentInput = hookResult.reason;
|
|
5396
|
-
consecutiveFailures = 0;
|
|
5397
|
-
await goalState.recordDecision(
|
|
5398
|
-
{ iteration: iterationCount, phase: goalState.currentPhase, summary: "stop-hook \u53CD\u9988" },
|
|
5399
|
-
["\u7EE7\u7EED\u5FAA\u73AF", "\u7EC8\u6B62"],
|
|
5400
|
-
"\u7EE7\u7EED\u5FAA\u73AF",
|
|
5401
|
-
hookResult.reason.slice(0, 200)
|
|
5402
|
-
);
|
|
5403
|
-
if (hookResult.systemMessage) {
|
|
5404
|
-
baseHistory.push({
|
|
5405
|
-
role: "user",
|
|
5406
|
-
content: `[Plugin Stop Hook] ${hookResult.systemMessage}`
|
|
5407
|
-
});
|
|
5408
|
-
}
|
|
5409
|
-
await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: Stop hook block\uFF0C\u6CE8\u5165\u53CD\u9988\u7EE7\u7EED`);
|
|
5410
|
-
} else {
|
|
5411
|
-
await goalState.appendProgress(`\u8FED\u4EE3 ${iterationCount}: \u65E0\u54E8\u5175 / hook pass\uFF0C\u7EE7\u7EED\u4E0B\u4E00\u8F6E`);
|
|
5412
|
-
}
|
|
5413
|
-
} while (true);
|
|
5414
|
-
} finally {
|
|
5415
|
-
history.length = 0;
|
|
5416
|
-
history.push(...baseHistory);
|
|
5417
|
-
if (finalAssistantMsg) {
|
|
5418
|
-
history.push(finalAssistantMsg);
|
|
5419
|
-
}
|
|
5420
|
-
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;
|
|
5421
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
|
+
});
|
|
5422
4790
|
}
|
|
5423
4791
|
|
|
5424
4792
|
// src/ui/hooks/useChat.ts
|
|
@@ -5432,7 +4800,7 @@ function useChat(args) {
|
|
|
5432
4800
|
const [error, setError] = useState5(null);
|
|
5433
4801
|
const [interrupted, setInterrupted] = useState5(false);
|
|
5434
4802
|
const [compacting, setCompacting] = useState5(false);
|
|
5435
|
-
const [
|
|
4803
|
+
const [pluginProgress, setPluginProgress] = useState5(null);
|
|
5436
4804
|
const abortRef = useRef3(null);
|
|
5437
4805
|
useEffect(() => {
|
|
5438
4806
|
return () => {
|
|
@@ -5448,7 +4816,7 @@ function useChat(args) {
|
|
|
5448
4816
|
setError(null);
|
|
5449
4817
|
setInterrupted(false);
|
|
5450
4818
|
setStreamingText("");
|
|
5451
|
-
|
|
4819
|
+
setPluginProgress(null);
|
|
5452
4820
|
const ac = new AbortController();
|
|
5453
4821
|
abortRef.current = ac;
|
|
5454
4822
|
try {
|
|
@@ -5463,7 +4831,7 @@ function useChat(args) {
|
|
|
5463
4831
|
setCompacting,
|
|
5464
4832
|
setError,
|
|
5465
4833
|
setInterrupted,
|
|
5466
|
-
|
|
4834
|
+
setPluginProgress,
|
|
5467
4835
|
bump
|
|
5468
4836
|
});
|
|
5469
4837
|
}
|
|
@@ -5474,7 +4842,7 @@ function useChat(args) {
|
|
|
5474
4842
|
setStreamingText("");
|
|
5475
4843
|
setToolStatus(null);
|
|
5476
4844
|
setCompacting(false);
|
|
5477
|
-
|
|
4845
|
+
setPluginProgress(null);
|
|
5478
4846
|
abortRef.current = null;
|
|
5479
4847
|
args.onPersist?.(historyRef.current);
|
|
5480
4848
|
}
|
|
@@ -5531,7 +4899,7 @@ function useChat(args) {
|
|
|
5531
4899
|
error,
|
|
5532
4900
|
interrupted,
|
|
5533
4901
|
compacting,
|
|
5534
|
-
|
|
4902
|
+
pluginProgress,
|
|
5535
4903
|
submit,
|
|
5536
4904
|
abort,
|
|
5537
4905
|
clearHistory,
|
|
@@ -5539,9 +4907,10 @@ function useChat(args) {
|
|
|
5539
4907
|
};
|
|
5540
4908
|
}
|
|
5541
4909
|
function handleEvent(ev, setters) {
|
|
5542
|
-
|
|
4910
|
+
const e = ev;
|
|
4911
|
+
switch (e.type) {
|
|
5543
4912
|
case "text":
|
|
5544
|
-
setters.setStreamingText((prev) => prev +
|
|
4913
|
+
setters.setStreamingText((prev) => prev + e.delta);
|
|
5545
4914
|
break;
|
|
5546
4915
|
case "assistant_message":
|
|
5547
4916
|
setters.setStreamingText(() => "");
|
|
@@ -5549,8 +4918,8 @@ function handleEvent(ev, setters) {
|
|
|
5549
4918
|
break;
|
|
5550
4919
|
case "tool_start":
|
|
5551
4920
|
setters.setToolStatus({
|
|
5552
|
-
toolName:
|
|
5553
|
-
argsPreview:
|
|
4921
|
+
toolName: e.toolName,
|
|
4922
|
+
argsPreview: e.argsPreview,
|
|
5554
4923
|
status: "running"
|
|
5555
4924
|
});
|
|
5556
4925
|
setters.bump();
|
|
@@ -5576,16 +4945,15 @@ function handleEvent(ev, setters) {
|
|
|
5576
4945
|
setters.bump();
|
|
5577
4946
|
break;
|
|
5578
4947
|
case "error":
|
|
5579
|
-
setters.setError(
|
|
4948
|
+
setters.setError(e.error);
|
|
5580
4949
|
setters.bump();
|
|
5581
4950
|
break;
|
|
5582
|
-
case "
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
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
|
|
5589
4957
|
});
|
|
5590
4958
|
setters.bump();
|
|
5591
4959
|
break;
|
|
@@ -5637,7 +5005,14 @@ function App({ provider, initialHistory }) {
|
|
|
5637
5005
|
onCompact: chat2.compactNow
|
|
5638
5006
|
}
|
|
5639
5007
|
),
|
|
5640
|
-
/* @__PURE__ */ jsx6(
|
|
5008
|
+
/* @__PURE__ */ jsx6(
|
|
5009
|
+
StatusLine,
|
|
5010
|
+
{
|
|
5011
|
+
provider,
|
|
5012
|
+
history: chat2.history,
|
|
5013
|
+
pluginProgress: chat2.pluginProgress
|
|
5014
|
+
}
|
|
5015
|
+
)
|
|
5641
5016
|
] });
|
|
5642
5017
|
}
|
|
5643
5018
|
|
|
@@ -5772,24 +5147,28 @@ async function runPrintMode(provider, args, initialHistory, options) {
|
|
|
5772
5147
|
await saveContext(history);
|
|
5773
5148
|
}
|
|
5774
5149
|
function handleEvent2(event, output, verbose) {
|
|
5775
|
-
|
|
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) {
|
|
5776
5155
|
case "text":
|
|
5777
|
-
if (verbose) process.stdout.write(
|
|
5778
|
-
output.buffer +=
|
|
5156
|
+
if (verbose) process.stdout.write(e.delta);
|
|
5157
|
+
output.buffer += e.delta;
|
|
5779
5158
|
return {};
|
|
5780
5159
|
case "assistant_message":
|
|
5781
5160
|
return {};
|
|
5782
5161
|
case "tool_start":
|
|
5783
5162
|
if (verbose) {
|
|
5784
5163
|
process.stderr.write(`
|
|
5785
|
-
\u{1F527} \u5DE5\u5177: ${
|
|
5164
|
+
\u{1F527} \u5DE5\u5177: ${e.toolName}(${e.argsPreview})
|
|
5786
5165
|
`);
|
|
5787
5166
|
}
|
|
5788
5167
|
return {};
|
|
5789
5168
|
case "tool_end":
|
|
5790
5169
|
if (verbose) {
|
|
5791
5170
|
process.stderr.write(
|
|
5792
|
-
`${
|
|
5171
|
+
`${e.ok ? "\u2705" : "\u274C"} ${e.toolName}: ${truncateForDisplay(e.content)}
|
|
5793
5172
|
`
|
|
5794
5173
|
);
|
|
5795
5174
|
}
|
|
@@ -5800,7 +5179,7 @@ function handleEvent2(event, output, verbose) {
|
|
|
5800
5179
|
case "compact_done":
|
|
5801
5180
|
if (verbose) {
|
|
5802
5181
|
process.stderr.write(
|
|
5803
|
-
`\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${
|
|
5182
|
+
`\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${e.before} \u2192 ${e.after} tokens
|
|
5804
5183
|
`
|
|
5805
5184
|
);
|
|
5806
5185
|
}
|
|
@@ -5810,7 +5189,7 @@ function handleEvent2(event, output, verbose) {
|
|
|
5810
5189
|
return {};
|
|
5811
5190
|
case "error":
|
|
5812
5191
|
console.error(`
|
|
5813
|
-
\u9519\u8BEF: ${
|
|
5192
|
+
\u9519\u8BEF: ${e.error}`);
|
|
5814
5193
|
return { exitCode: 1 };
|
|
5815
5194
|
default:
|
|
5816
5195
|
return {};
|
|
@@ -5851,7 +5230,7 @@ async function main() {
|
|
|
5851
5230
|
const dirArg = extractCwdArg(args);
|
|
5852
5231
|
if (dirArg) {
|
|
5853
5232
|
const abs = resolve9(dirArg);
|
|
5854
|
-
if (!
|
|
5233
|
+
if (!existsSync9(abs)) {
|
|
5855
5234
|
mkdirSync(abs, { recursive: true });
|
|
5856
5235
|
}
|
|
5857
5236
|
process.chdir(abs);
|