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.
Files changed (43) hide show
  1. package/README.md +405 -122
  2. package/dist/main.js +117 -738
  3. package/package.json +4 -2
  4. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +186 -0
  5. package/plugins/ralph-wiggum/commands/ralph-loop.md +6 -16
  6. package/plugins/ralph-wiggum/plugin.ts +275 -0
  7. package/plugins/ralph-wiggum/src/goalState.ts +310 -0
  8. package/plugins/ralph-wiggum/src/sentinels.ts +24 -0
  9. package/plugins/ralph-wiggum/src/stopHookRunner.ts +136 -0
  10. package/plugins/ralph-wiggum/src/verificationGate.ts +252 -0
  11. package/plugins/ralph-wiggum/test/goalState.test.ts +410 -0
  12. package/plugins/ralph-wiggum/test/verificationGate.test.ts +122 -0
  13. package/plugins/workflow-runner/.claude-plugin/plugin.json +5 -0
  14. package/plugins/workflow-runner/commands/workflow.md +15 -0
  15. package/plugins/workflow-runner/commands/workflows.md +8 -0
  16. package/plugins/workflow-runner/plugin.ts +42 -0
  17. package/plugins/workflow-runner/src/expressions.ts +371 -0
  18. package/plugins/workflow-runner/src/index.ts +194 -0
  19. package/plugins/workflow-runner/src/loader.ts +193 -0
  20. package/plugins/workflow-runner/src/runner.ts +313 -0
  21. package/plugins/workflow-runner/src/stepExecutors/assert.ts +30 -0
  22. package/plugins/workflow-runner/src/stepExecutors/llm.ts +54 -0
  23. package/plugins/workflow-runner/src/stepExecutors/skill.ts +115 -0
  24. package/plugins/workflow-runner/src/stepExecutors/tool.ts +41 -0
  25. package/plugins/workflow-runner/src/types.ts +183 -0
  26. package/plugins/workflow-runner/src/workflowState.ts +65 -0
  27. package/plugins/workflow-runner/test/cli.e2e.test.ts +114 -0
  28. package/plugins/workflow-runner/test/e2e.test.ts +268 -0
  29. package/plugins/workflow-runner/test/expressions.test.ts +140 -0
  30. package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +27 -0
  31. package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +49 -0
  32. package/plugins/workflow-runner/test/graceful.test.ts +139 -0
  33. package/plugins/workflow-runner/test/loader.test.ts +216 -0
  34. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +230 -0
  35. package/plugins/workflow-runner/test/runner.test.ts +511 -0
  36. package/skills/image-gen-openrouter/SKILL.md +121 -0
  37. package/skills/subtitle-srt/SKILL.md +134 -0
  38. package/skills/tts-zh/SKILL.md +137 -0
  39. package/skills/video-compose/SKILL.md +139 -0
  40. package/workflows/book-review-short.yaml +99 -0
  41. package/workflows/e2e-write-greet.yaml +27 -0
  42. package/workflows/schema.json +74 -0
  43. 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 existsSync10, mkdirSync } from "fs";
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 \`/ralph-loop\` \u7684\u547D\u4EE4\u3002
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\u7528\u6237\u7684\u9700\u6C42\u662F **"\u81EA\u52A8\u91CD\u590D\u5C1D\u8BD5\u76F4\u5230\u6210\u529F / \u8FED\u4EE3\u4FEE\u590D\u6D4B\u8BD5\u76F4\u5230\u5168\u7EFF / \u53CD\u590D\u6253\u78E8\u540C\u4E00\u4EFD\u4EA7\u7269"** \u7B49
425
- \u957F\u5FAA\u73AF\u573A\u666F\u65F6\uFF0C\u53EF\u4EE5\u5EFA\u8BAE\u7528\u6237\u6539\u7528\uFF1A\`/ralph-loop "<\u76EE\u6807>" [--max-iterations N] [--verify ...]\`
426
- \uFF08\u5185\u7F6E\u7684\u590D\u5236\u4EFB\u52A1\u5FAA\u73AF\uFF0C\u4E94\u9636\u6BB5 FSM\uFF1APLAN \u2192 BUILD \u2192 VERIFY \u2192 HEAL \u2192 DONE\uFF09\u3002
427
- - \u5982\u679C\u5F53\u8F6E\u7528\u6237\u6D88\u606F\u91CC\u51FA\u73B0 \`<promise>DONE</promise>\` / \`<PROMISE>NEED_REPLAN</PROMISE>\` \u54E8\u5175
428
- \u6216 PLAN/BUILD/VERIFY/HEAL \u9636\u6BB5\u6807\u8BB0\uFF0C\u8BF4\u660E\u4F60\u6B63\u8FD0\u884C\u5728 ralph-loop \u5185\u2014\u2014\u4E25\u683C\u6309\u7167\u5F53\u8F6E\u6CE8\u5165\u7684
429
- freshContext \u6307\u5F15\u884C\u52A8\uFF0C**\u4EC5\u5728\u4EFB\u52A1\u771F\u6B63\u5B8C\u6210\u65F6**\u8F93\u51FA \`<promise>DONE</promise>\`\u3002
430
- - \u5728**\u5E38\u89C4\u5BF9\u8BDD**\uFF08\u65E0\u4E0A\u8FF0\u9636\u6BB5\u6807\u8BB0\uFF09\u4E2D**\u7981\u6B62**\u4F7F\u7528\u8FD9\u4E9B\u54E8\u5175\u548C\u9636\u6BB5\u8BCD\u3002
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({ provider, history, pluginLoop }) {
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
- pluginLoop && /* @__PURE__ */ jsxs4(Fragment, { children: [
4129
+ pluginProgress && /* @__PURE__ */ jsxs4(Fragment, { children: [
4130
4130
  /* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
4131
4131
  "\u{1F504} ",
4132
- pluginLoop.pluginName,
4132
+ pluginProgress.pluginId,
4133
4133
  " "
4134
4134
  ] }),
4135
4135
  /* @__PURE__ */ jsxs4(Text4, { color: "magenta", children: [
4136
- pluginLoop.current,
4136
+ pluginProgress.current,
4137
4137
  "/",
4138
- pluginLoop.max
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: rawArgs } = resolved;
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/stopHook.ts
4495
- import { readFile as readFile10 } from "fs/promises";
4469
+ // src/plugins/pluginLoader.ts
4470
+ import { existsSync as existsSync8 } from "fs";
4496
4471
  import { join as join8 } from "path";
4497
- import { spawn as spawn4 } from "child_process";
4498
- async function loadStopHookConfig(pluginRoot) {
4499
- const hooksJsonPath = join8(pluginRoot, "hooks", "hooks.json");
4500
- try {
4501
- const raw = await readFile10(hooksJsonPath, "utf8");
4502
- const parsed = JSON.parse(raw);
4503
- const stopEntries = parsed?.hooks?.Stop;
4504
- if (!Array.isArray(stopEntries)) return [];
4505
- const commands = [];
4506
- for (const entry of stopEntries) {
4507
- const hooks = entry.hooks;
4508
- if (Array.isArray(hooks)) {
4509
- for (const h of hooks) {
4510
- if (h.type === "command" && h.command) {
4511
- commands.push(h);
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 content = readFileSync(file, "utf8");
4670
- const regex = new RegExp(pattern);
4671
- const matched = regex.test(content);
4672
- return {
4673
- check: { type: "file_contains", file, pattern },
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
- await writeFile6(join9(this.dir, "phase.md"), phase);
4876
- await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
4877
- }
4878
- async forceSetPhase(phase, reason) {
4879
- if (!VALID_PHASES.has(phase)) {
4880
- throw new Error(`Invalid phase: ${phase}`);
4881
- }
4882
- await writeFile6(join9(this.dir, "phase.md"), phase);
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
- let currentInput = userInput;
5262
- let matchedCmd = null;
5263
- if (isSlashCommand) {
5264
- await discoverPlugins();
5265
- matchedCmd = resolveCommand(userInput);
5266
- if (matchedCmd) {
5267
- currentInput = buildCommandInput(matchedCmd);
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
- const activeHookPlugins = matchedCmd ? getActiveStopHookPlugins() : [];
5271
- const enterLoop = matchedCmd !== null && activeHookPlugins.length > 0;
5272
- if (!enterLoop) {
5273
- yield* runQuery(currentInput, {
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 cmd = matchedCmd.cmd;
5283
- const maxIter = Math.min(
5284
- extractMaxIterations(currentInput) ?? DEFAULT_MAX_ITERATIONS,
5285
- SAFETY_CEILING
5286
- );
5287
- yield {
5288
- type: "plugin_start",
5289
- pluginName: cmd.pluginName,
5290
- description: cmd.description
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 [pluginLoop, setPluginLoop] = useState5(null);
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
- setPluginLoop(null);
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
- setPluginLoop,
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
- setPluginLoop(null);
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
- pluginLoop,
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
- switch (ev.type) {
4910
+ const e = ev;
4911
+ switch (e.type) {
5543
4912
  case "text":
5544
- setters.setStreamingText((prev) => prev + ev.delta);
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: ev.toolName,
5553
- argsPreview: ev.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(ev.error);
4948
+ setters.setError(e.error);
5580
4949
  setters.bump();
5581
4950
  break;
5582
- case "plugin_start":
5583
- break;
5584
- case "plugin_iteration":
5585
- setters.setPluginLoop({
5586
- pluginName: ev.pluginName,
5587
- current: ev.current,
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(StatusLine, { provider, history: chat2.history, pluginLoop: chat2.pluginLoop })
5008
+ /* @__PURE__ */ jsx6(
5009
+ StatusLine,
5010
+ {
5011
+ provider,
5012
+ history: chat2.history,
5013
+ pluginProgress: chat2.pluginProgress
5014
+ }
5015
+ )
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
- switch (event.type) {
5150
+ if (event.type === "workflow_start" || event.type === "workflow_step_start" || event.type === "workflow_step_end" || event.type === "workflow_step_skipped" || event.type === "workflow_done" || event.type === "plugin_progress") {
5151
+ return {};
5152
+ }
5153
+ const e = event;
5154
+ switch (e.type) {
5776
5155
  case "text":
5777
- if (verbose) process.stdout.write(event.delta);
5778
- output.buffer += event.delta;
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: ${event.toolName}(${event.argsPreview})
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
- `${event.ok ? "\u2705" : "\u274C"} ${event.toolName}: ${truncateForDisplay(event.content)}
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: ${event.before} \u2192 ${event.after} tokens
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: ${event.error}`);
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 (!existsSync10(abs)) {
5233
+ if (!existsSync9(abs)) {
5855
5234
  mkdirSync(abs, { recursive: true });
5856
5235
  }
5857
5236
  process.chdir(abs);