@fieldwangai/agentflow 0.1.153 → 0.1.156

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 (193) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +277 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1092 -18123
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-preview.mjs +12 -2
  57. package/bin/lib/workspace-routes.mjs +3088 -0
  58. package/bin/lib/workspace-server.mjs +6199 -0
  59. package/bin/lib/workspace-state.mjs +331 -0
  60. package/bin/lib/workspace.mjs +2 -1
  61. package/builtin/nodes/agent_subAgent.md +1 -0
  62. package/builtin/nodes/control_agent_toBool.md +2 -0
  63. package/builtin/nodes/control_anyOne.md +2 -0
  64. package/builtin/nodes/control_cancelled.md +2 -0
  65. package/builtin/nodes/control_cd_workspace.md +5 -11
  66. package/builtin/nodes/control_delay.md +2 -0
  67. package/builtin/nodes/control_end.md +2 -0
  68. package/builtin/nodes/control_if.md +1 -0
  69. package/builtin/nodes/control_interval_loop.md +2 -0
  70. package/builtin/nodes/control_load_mcp.md +24 -0
  71. package/builtin/nodes/control_load_skills.md +8 -23
  72. package/builtin/nodes/control_start.md +2 -0
  73. package/builtin/nodes/control_toBool.md +2 -0
  74. package/builtin/nodes/control_user_workspace.md +2 -0
  75. package/builtin/nodes/control_wait_until.md +2 -0
  76. package/builtin/nodes/display_ascii.md +1 -0
  77. package/builtin/nodes/display_chart.md +1 -0
  78. package/builtin/nodes/display_html.md +1 -0
  79. package/builtin/nodes/display_image.md +1 -0
  80. package/builtin/nodes/display_markdown.md +1 -0
  81. package/builtin/nodes/display_mermaid.md +1 -0
  82. package/builtin/nodes/display_react_app.md +1 -0
  83. package/builtin/nodes/display_table.md +1 -0
  84. package/builtin/nodes/provide_bool.md +1 -0
  85. package/builtin/nodes/provide_file.md +1 -0
  86. package/builtin/nodes/provide_password.md +1 -0
  87. package/builtin/nodes/provide_str.md +1 -0
  88. package/builtin/nodes/tool_display_share_link.md +1 -0
  89. package/builtin/nodes/tool_get_env.md +2 -0
  90. package/builtin/nodes/tool_git_checkout.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  92. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  93. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  94. package/builtin/nodes/tool_jenkins_build.md +2 -0
  95. package/builtin/nodes/tool_load_key.md +2 -0
  96. package/builtin/nodes/tool_nodejs.md +17 -19
  97. package/builtin/nodes/tool_print.md +2 -0
  98. package/builtin/nodes/tool_save_key.md +2 -0
  99. package/builtin/nodes/tool_set_run_env.md +1 -0
  100. package/builtin/nodes/tool_user_ask.md +2 -0
  101. package/builtin/nodes/tool_user_check.md +2 -0
  102. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  103. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  104. package/builtin/nodes/workspace_one_click_task.md +44 -0
  105. package/builtin/nodes/workspace_run.md +16 -0
  106. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  107. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  108. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  109. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  110. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  111. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  112. package/builtin/pipelines/new/workspace.flow.js +91 -0
  113. package/builtin/pipelines/new/workspace.layout.json +70 -0
  114. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
  116. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  117. package/builtin/web-ui/dist/index.html +2 -2
  118. package/package.json +3 -1
  119. package/reference/flow-control-capabilities.md +77 -158
  120. package/reference/flow-layout.md +1 -1
  121. package/reference/flow-prompt-handler-check.md +2 -2
  122. package/skills/agentflow-author-flow/SKILL.md +1 -1
  123. package/skills/agentflow-cli/SKILL.md +139 -24
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  128. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  129. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  130. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  131. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  135. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  136. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  137. package/skills/agentflow-node-reference/SKILL.md +2 -2
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  140. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  141. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  142. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  143. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  144. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  145. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  146. package/bin/lib/api-runner.mjs +0 -387
  147. package/bin/lib/apply.mjs +0 -903
  148. package/bin/lib/composer-flow-instances.mjs +0 -68
  149. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  150. package/bin/lib/composer-flow-validate.mjs +0 -47
  151. package/bin/lib/composer-model-router.mjs +0 -185
  152. package/bin/lib/composer-node-schema.mjs +0 -303
  153. package/bin/lib/composer-planner.mjs +0 -751
  154. package/bin/lib/composer-script-ops.mjs +0 -233
  155. package/bin/lib/flow-static-preview.mjs +0 -104
  156. package/bin/lib/hub-login.mjs +0 -54
  157. package/bin/lib/hub-publish.mjs +0 -159
  158. package/bin/lib/hub-remote.mjs +0 -189
  159. package/bin/lib/hub.mjs +0 -299
  160. package/bin/lib/jenkins.mjs +0 -380
  161. package/bin/lib/node-execute.mjs +0 -539
  162. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  163. package/bin/lib/runtime-context.mjs +0 -243
  164. package/bin/lib/scheduler.mjs +0 -601
  165. package/bin/lib/ui-print.mjs +0 -94
  166. package/bin/pipeline/build-node-prompt.mjs +0 -271
  167. package/bin/pipeline/check-cache.mjs +0 -191
  168. package/bin/pipeline/check-flow.mjs +0 -543
  169. package/bin/pipeline/collect-nodes.mjs +0 -212
  170. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  171. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  172. package/bin/pipeline/gc.mjs +0 -129
  173. package/bin/pipeline/get-env.mjs +0 -59
  174. package/bin/pipeline/get-resolved-values.mjs +0 -344
  175. package/bin/pipeline/load-key.mjs +0 -62
  176. package/bin/pipeline/parse-flow.mjs +0 -708
  177. package/bin/pipeline/post-process-control-if.mjs +0 -23
  178. package/bin/pipeline/post-process-node.mjs +0 -490
  179. package/bin/pipeline/pre-process-node.mjs +0 -1430
  180. package/bin/pipeline/resolve-inputs.mjs +0 -201
  181. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  182. package/bin/pipeline/save-key.mjs +0 -93
  183. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  184. package/bin/pipeline/validate-for-ui.mjs +0 -234
  185. package/bin/pipeline/validate-script-output.mjs +0 -130
  186. package/bin/pipeline/write-result.mjs +0 -182
  187. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  188. package/builtin/pipelines/new/flow.yaml +0 -545
  189. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  190. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  191. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  192. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  193. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Composer 执行器:支持单步执行(兼容旧逻辑)和多步编排执行。
2
+ * Composer 执行器:以给定 prompt 拉起一次 CLI agent(Cursor / OpenCode / Claude Code / Codex),
3
+ * 并按用户私有 MCP 配置临时物化 .cursor/mcp.json 或 codex 覆盖参数。
3
4
  *
4
- * 多步模式流程:
5
- * 用户 prompt → planner 分解 → [script 直执 | agent 子调用(按复杂度选模型)] → sync UI
5
+ * Workspace 的节点执行、生成、node-chat、实现优化都走这里。
6
6
  */
7
7
  import fs from "fs";
8
8
  import os from "os";
@@ -11,19 +11,8 @@ import { getAgentflowDataRoot, getAgentflowUserDataRoot, sanitizeAgentflowUserId
11
11
  import { readMergedEnvObject } from "./user-env.mjs";
12
12
  import { resolveCliAndModel } from "./model-config.mjs";
13
13
  import { runCodexAgentWithPrompt, runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
14
- import { planComposerTasks, hasPlannerApiAvailable, shouldUsePhased, classifyComplexity, classifyTaskComplexity, PHASED_DEFINITIONS } from "./composer-planner.mjs";
15
- import { executeScriptOp, isSupportedScriptOp } from "./composer-script-ops.mjs";
16
- import { routeModel } from "./composer-model-router.mjs";
17
- import { validateComposerFlowYaml, formatValidationErrorsBlock } from "./composer-flow-validate.mjs";
18
- import { parseInstanceRoleModelMap } from "./composer-flow-instances.mjs";
19
- import { buildNodeSchemaPromptSection, buildNodeSchemaCompactSection, getBuiltinNodeSchemas, EXTENSIBLE_DEFINITIONS } from "./composer-node-schema.mjs";
20
- import { ensurePhase1Skeletons, applyPlannedSlotsFromSpec } from "./composer-flow-skeleton.mjs";
21
- import yaml from "js-yaml";
22
- import { t } from "./i18n.mjs";
23
14
 
24
15
  const MAX_PROMPT_CHARS = 500_000;
25
- const MAX_COMPOSER_VALIDATION_REPAIR = 5;
26
- const MAX_SCRIPT_INJECT_BYTES = 30_000;
27
16
 
28
17
  function readJsonObject(filePath) {
29
18
  try {
@@ -321,72 +310,6 @@ function runCursorAgentWithPrivateMcp(cliWorkspace, prompt, options, userId) {
321
310
 
322
311
  // ─── script 内容注入辅助 ─────────────────────────────────────────────────
323
312
 
324
- /**
325
- * 从文本中提取 .mjs 脚本文件名(去重)。
326
- */
327
- function extractScriptFilenames(text) {
328
- if (!text) return [];
329
- const matches = text.match(/[\w\-.]+\.mjs/g);
330
- return matches ? [...new Set(matches)] : [];
331
- }
332
-
333
- /**
334
- * 读取 scripts/ 目录下指定 .mjs 文件内容,拼为 prompt 注入块。
335
- */
336
- function readPipelineScriptContents(scriptsDirAbs, filenames, nodeHint) {
337
- if (!scriptsDirAbs || !filenames.length) return "";
338
- const blocks = [];
339
- let totalBytes = 0;
340
- for (const fn of filenames) {
341
- try {
342
- const content = fs.readFileSync(path.join(scriptsDirAbs, fn), "utf-8");
343
- if (totalBytes + content.length > MAX_SCRIPT_INJECT_BYTES) break;
344
- totalBytes += content.length;
345
- const header = nodeHint ? `### ${fn}(节点 \`${nodeHint}\`)` : `### ${fn}`;
346
- blocks.push(`${header}\n\`\`\`javascript\n${content.trimEnd()}\n\`\`\``);
347
- } catch { /* file not found — skip */ }
348
- }
349
- return blocks.length
350
- ? `## 关联脚本文件内容(scripts/ 目录)\n\n${blocks.join("\n\n")}`
351
- : "";
352
- }
353
-
354
- /**
355
- * 为选中的 tool_nodejs 实例构建脚本内容注入块(供单步 Composer 使用)。
356
- */
357
- export function buildScriptContentBlockForInstances(flowYamlAbs, instanceIds) {
358
- if (!flowYamlAbs || !instanceIds?.length) return "";
359
- try {
360
- const flowDir = path.dirname(flowYamlAbs);
361
- const scriptsDirAbs = path.join(flowDir, "scripts");
362
- if (!fs.existsSync(scriptsDirAbs)) return "";
363
- const flowRaw = fs.readFileSync(flowYamlAbs, "utf-8");
364
- const flowDoc = yaml.load(flowRaw);
365
- const instances = flowDoc?.instances || {};
366
- const blocks = [];
367
- let totalBytes = 0;
368
- for (const id of instanceIds) {
369
- const inst = instances[id];
370
- if (!inst || inst.definitionId !== "tool_nodejs" || !inst.script) continue;
371
- const filenames = extractScriptFilenames(String(inst.script));
372
- for (const fn of filenames) {
373
- try {
374
- const content = fs.readFileSync(path.join(scriptsDirAbs, fn), "utf-8");
375
- if (totalBytes + content.length > MAX_SCRIPT_INJECT_BYTES) break;
376
- totalBytes += content.length;
377
- blocks.push(`### ${fn}(节点 \`${id}\`)\n\`\`\`javascript\n${content.trimEnd()}\n\`\`\``);
378
- } catch { /* skip */ }
379
- }
380
- }
381
- if (!blocks.length) return "";
382
- return `## 关联脚本文件内容(scripts/ 目录)\n\n已选中节点引用的脚本实际代码:\n\n${blocks.join("\n\n")}`;
383
- } catch {
384
- return "";
385
- }
386
- }
387
-
388
- // ─── 单步模式(向后兼容) ──────────────────────────────────────────────────
389
-
390
313
  /**
391
314
  * 旧版单步执行:将整个 prompt 一次性发给 Cursor / OpenCode。
392
315
  * @param {object} opts
@@ -452,716 +375,3 @@ export function startComposerAgent(opts) {
452
375
  model: model || undefined,
453
376
  }, opts.agentflowUserId);
454
377
  }
455
-
456
- // ─── 为单个 agent 步骤构建 prompt ──────────────────────────────────────────
457
-
458
- /**
459
- * 从 flow.yaml 中提取指定 instance 的 YAML 片段,供子 agent 上下文使用,
460
- * 避免子 agent 重新 Read 整份 flow.yaml 仅为定位一个节点。
461
- * @param {string} flowYamlAbs
462
- * @param {string} instanceId
463
- * @returns {string} 该 instance 的 YAML 文本(缩进保留),找不到返回空串
464
- */
465
- function extractInstanceYamlExcerpt(flowYamlAbs, instanceId) {
466
- if (!flowYamlAbs || !instanceId) return "";
467
- try {
468
- const raw = fs.readFileSync(flowYamlAbs, "utf-8");
469
- const data = yaml.load(raw);
470
- const inst = data?.instances?.[instanceId];
471
- if (!inst || typeof inst !== "object") return "";
472
- return yaml.dump({ [instanceId]: inst }, { lineWidth: 120, noRefs: true });
473
- } catch {
474
- return "";
475
- }
476
- }
477
-
478
- function buildAgentStepPrompt(step, flowContext) {
479
- const parts = [];
480
-
481
- const nodeRole = step.nodeRole != null ? String(step.nodeRole).trim() : "";
482
- if (nodeRole) {
483
- parts.push(`## ${t("composer.task_title").replace("## ", "")}\n${nodeRole}`);
484
- parts.push("");
485
- }
486
-
487
- if (flowContext) {
488
- parts.push(t("composer.edit_context"));
489
- if (flowContext.flowYamlAbs) {
490
- parts.push(`- 图定义文件:${flowContext.flowYamlAbs}`);
491
- }
492
- if (flowContext.composerSpecAbs) {
493
- parts.push(`- 节点规格书:${flowContext.composerSpecAbs}`);
494
- }
495
- if (flowContext.pipelineScriptsDirAbs) {
496
- parts.push(`- 流水线 scripts 目录(tool_nodejs 可执行脚本放此):${flowContext.pipelineScriptsDirAbs}`);
497
- }
498
- if (flowContext.flowId) {
499
- parts.push(`- flowId:${flowContext.flowId}`);
500
- parts.push(`- flowSource:${flowContext.flowSource || "user"}`);
501
- }
502
- if (flowContext.userId) {
503
- parts.push(`- agentflow 用户:${flowContext.userId}`);
504
- parts.push(`- 执行 agentflow 命令时保留当前环境中的 AGENTFLOW_USER_ID,必要时显式前置 AGENTFLOW_USER_ID='${flowContext.userId}'。`);
505
- }
506
- if (flowContext.skillsHint) {
507
- parts.push(flowContext.skillsHint);
508
- }
509
- if (flowContext.syncCurlHint) {
510
- parts.push(`- 保存后执行:${flowContext.syncCurlHint}`);
511
- }
512
- parts.push("");
513
-
514
- if (flowContext.skillInjectionBlock) {
515
- parts.push(flowContext.skillInjectionBlock);
516
- parts.push("");
517
- }
518
- }
519
-
520
- const sid = step.instanceId != null ? String(step.instanceId).trim() : "";
521
- const instMap = flowContext?._instanceMap;
522
- const targetInst = sid && instMap && instMap[sid];
523
- if (targetInst && targetInst.definitionId === "tool_nodejs") {
524
- parts.push(t("composer.tool_nodejs_rules_title"));
525
- parts.push(t("composer.tool_nodejs_rules_body"));
526
- parts.push("");
527
- }
528
-
529
- parts.push(t("composer.task_title"));
530
- parts.push(step.prompt || step.description || "");
531
- parts.push("");
532
-
533
- // 节点 schema 与目标 instance 上下文
534
- try {
535
- const targetIsExtensible = targetInst && EXTENSIBLE_DEFINITIONS.has(targetInst.definitionId);
536
- const promptText = String(step.prompt || step.description || "");
537
- const promptMentionsSlots = /input\s*:|output\s*:|追加|扩展槽|business\s*slot|业务槽/i.test(promptText);
538
- const useFullSchema = Boolean(targetIsExtensible || promptMentionsSlots);
539
- const schemaSection = useFullSchema
540
- ? buildNodeSchemaPromptSection()
541
- : buildNodeSchemaCompactSection();
542
- if (schemaSection) {
543
- parts.push(schemaSection);
544
- parts.push("");
545
- }
546
-
547
- // Inject YAML excerpt + script content for target instance (or canvas fallback)
548
- const idsToInject = sid ? [sid] : (flowContext?.canvasInstanceIds || []);
549
- if (idsToInject.length > 0 && flowContext?.flowYamlAbs) {
550
- for (const iid of idsToInject) {
551
- const excerpt = extractInstanceYamlExcerpt(flowContext.flowYamlAbs, iid);
552
- if (!excerpt) continue;
553
- const iInst = iid === sid ? targetInst : (instMap && instMap[iid]);
554
- const defId = (iInst && iInst.definitionId) || "";
555
- const headerLabel = sid ? "目标 instance" : "关联 instance(画布选中)";
556
- parts.push(`## ${headerLabel}(${iid}${defId ? ` · ${defId}` : ""})当前 YAML`);
557
- parts.push("```yaml");
558
- parts.push(excerpt.trimEnd());
559
- parts.push("```");
560
- parts.push("");
561
-
562
- // tool_nodejs: inject referenced .mjs script file contents
563
- if (defId === "tool_nodejs" && flowContext?.pipelineScriptsDirAbs) {
564
- const scriptFns = extractScriptFilenames(excerpt);
565
- const scriptBlock = readPipelineScriptContents(flowContext.pipelineScriptsDirAbs, scriptFns, iid);
566
- if (scriptBlock) {
567
- parts.push(scriptBlock);
568
- parts.push("");
569
- }
570
- }
571
- }
572
- }
573
- parts.push(
574
- "## 上下文已就绪(禁止 forage)\n" +
575
- "- 节点定义见上方 schema 表,**禁止** Glob/Read `builtin/nodes/`、`.workspace/agentflow/nodes/`、历史 `runBuild/` 来推断节点结构。\n" +
576
- "- 默认不要读取、搜索或 Glob 历史运行产物;除非用户明确要求分析历史 run/log,否则检索时必须排除 `**/runBuild/**`、`**/logs/**`、`.workspace/agentflow/**/runBuild/**`、`~/agentflow/runBuild/**`、`node_modules/**`、`dist/**`。\n" +
577
- "- 不要从历史 runBuild/logs 中推断业务事实、指标资产或 skill 文档;优先读取当前 workspace 的源文件、analytics-docs、skills、配置与用户给出的上下文。\n" +
578
- "- 目标 instance 的当前 YAML 已附上(若 instanceId 已知);tool_nodejs 节点引用的 .mjs 脚本内容也已附上(若存在)。\n" +
579
- "- 如需查看整份 flow,仅在确实需要时读取一次。"
580
- );
581
- parts.push("");
582
- } catch {
583
- /* schema 注入失败不影响主流程 */
584
- }
585
-
586
- parts.push(t("composer.task_instruction"));
587
- return parts.join("\n");
588
- }
589
-
590
- /**
591
- * @param {object} step
592
- * @param {Record<string, { role: string, model?: string, label: string }>} instMap
593
- * @param {string} [globalModelKey]
594
- * @returns {{ nodeRole: string, preferredModel: string }}
595
- */
596
- function resolveAgentStepRoleAndModel(step, instMap, globalModelKey) {
597
- const sid = step.instanceId != null ? String(step.instanceId).trim() : "";
598
- const fromFlow = sid && instMap[sid];
599
- const plannerRole = step.nodeRole != null ? String(step.nodeRole).trim() : "";
600
- const nodeRole = plannerRole || (fromFlow && fromFlow.role) || "";
601
- let preferredModel = "";
602
- const execM = step.executorModel != null ? String(step.executorModel).trim() : "";
603
- if (execM && execM !== "default") preferredModel = execM;
604
- else if (fromFlow && fromFlow.model && String(fromFlow.model).trim() && String(fromFlow.model).trim() !== "default") {
605
- preferredModel = String(fromFlow.model).trim();
606
- } else if (globalModelKey && String(globalModelKey).trim()) {
607
- preferredModel = String(globalModelKey).trim();
608
- }
609
- return { nodeRole, preferredModel };
610
- }
611
-
612
- /**
613
- * @param {object} step
614
- * @param {number} index
615
- * @param {Record<string, { role: string, model?: string, label: string }>} instMap
616
- */
617
- function summarizePlanStepForUi(step, index, instMap) {
618
- const sid = step.instanceId != null ? String(step.instanceId).trim() : "";
619
- const fromFlow = sid && instMap[sid];
620
- const plannerRole = step.nodeRole != null ? String(step.nodeRole).trim() : "";
621
- const nodeRole = plannerRole || (fromFlow && fromFlow.role) || "";
622
- let modelHint = "";
623
- const execM = step.executorModel != null ? String(step.executorModel).trim() : "";
624
- if (execM && execM !== "default") modelHint = execM;
625
- else if (fromFlow && fromFlow.model && String(fromFlow.model).trim() && String(fromFlow.model).trim() !== "default") {
626
- modelHint = String(fromFlow.model).trim();
627
- }
628
- return {
629
- index,
630
- type: step.type,
631
- description: step.description,
632
- op: step.op,
633
- instanceId: sid || undefined,
634
- nodeRole: nodeRole || undefined,
635
- executorModel: modelHint || undefined,
636
- complexity: step.complexity,
637
- };
638
- }
639
-
640
- // ─── 编辑后校验与自动修复 ─────────────────────────────────────────────────
641
-
642
- /**
643
- * Composer 改动 flow.yaml 之后:运行与 CLI 一致的 validate-flow;若有 errors 则循环调用 agent 修复直至通过或达到上限。
644
- *
645
- * @param {object} opts
646
- * @param {string} opts.uiWorkspaceRoot
647
- * @param {string} [opts.cliWorkspace]
648
- * @param {string} opts.flowYamlAbs
649
- * @param {object} [opts.flowContext] 与多步相同的上下文(含 syncCurlHint、skillsHint 等)
650
- * @param {string} [opts.modelKey]
651
- * @param {boolean} [opts.force]
652
- * @param {(ev: object) => void} [opts.onStreamEvent]
653
- * @param {() => boolean} [opts.getAborted]
654
- * @param {(c: import('child_process').ChildProcess | null) => void} [opts.setCurrentChild] 便于外部 abort 杀子进程
655
- * @param {number} [opts.maxRepairAttempts] 默认 5,最大 10
656
- * @returns {Promise<{ ok: boolean, result?: object, repairAttempts?: number, aborted?: boolean, repairError?: string }>}
657
- */
658
- export async function runComposerPostFlowValidationAndRepair(opts) {
659
- const emit = typeof opts.onStreamEvent === "function" ? opts.onStreamEvent : () => {};
660
- const getAborted = typeof opts.getAborted === "function" ? opts.getAborted : () => false;
661
- const setChild = typeof opts.setCurrentChild === "function" ? opts.setCurrentChild : () => {};
662
-
663
- const uiRoot = String(opts.uiWorkspaceRoot || "").trim();
664
- const flowYamlAbs = String(opts.flowYamlAbs || "").trim();
665
- const cliWs = opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot();
666
- const maxRepair = Math.max(1, Math.min(10, Number(opts.maxRepairAttempts) || MAX_COMPOSER_VALIDATION_REPAIR));
667
- const env = agentflowUserEnv(opts.agentflowUserId || opts.flowContext?.userId);
668
-
669
- if (!uiRoot || !flowYamlAbs) {
670
- return { ok: true, result: { skipped: true } };
671
- }
672
-
673
- let last = validateComposerFlowYaml(flowYamlAbs, uiRoot);
674
- if (last.ok) {
675
- emit({ type: "status", line: t("composer.validation_passed") });
676
- emit({ type: "natural", kind: "assistant", text: t("composer.validation_passed_detail") });
677
- return { ok: true, result: last };
678
- }
679
-
680
- emit({ type: "status", line: t("composer.validation_failed") });
681
- emit({
682
- type: "natural",
683
- kind: "assistant",
684
- text: `⚠ flow 校验未通过(${(last.errors && last.errors.length) || 0} 条错误),将调用 agent 修复…\n${formatValidationErrorsBlock(last)}`,
685
- });
686
-
687
- for (let attempt = 1; attempt <= maxRepair; attempt++) {
688
- if (getAborted()) {
689
- setChild(null);
690
- return { ok: false, result: last, aborted: true };
691
- }
692
-
693
- const repairStep = {
694
- type: "agent",
695
- complexity: "complex",
696
- description: `自动修复校验错误(第 ${attempt}/${maxRepair} 次)`,
697
- prompt: [
698
- t("composer.fix_task_title"),
699
- "",
700
- t("composer.fix_errors_intro"),
701
- "",
702
- formatValidationErrorsBlock(last),
703
- "",
704
- t("composer.fix_constraints_title"),
705
- t("composer.fix_constraints_body"),
706
- "- 完成后**保存文件**,并执行上下文中的同步 Web 画布命令(curl)。",
707
- ].join("\n"),
708
- };
709
-
710
- const agentPrompt = buildAgentStepPrompt(repairStep, opts.flowContext);
711
- const modelKey = opts.modelKey != null ? String(opts.modelKey).trim() : "";
712
- const routed = routeModel("complex", { userPreferredModel: modelKey || null });
713
- const { cli, model } = resolveCliAndModel(uiRoot, (routed.model || modelKey) || null, null);
714
-
715
- emit({ type: "ai-log", tag: "repair-prompt", text: agentPrompt, meta: { attempt, max: maxRepair, cli, model: model || null, errorCount: (last?.errors || []).length } });
716
- emit({ type: "status", line: t("composer.validation_repair", { attempt, max: maxRepair }) });
717
- emit({ type: "natural", kind: "assistant", text: t("composer.validation_repair_start", { attempt, max: maxRepair }) });
718
-
719
- const stepEmit = (ev) => {
720
- emit({ ...ev, stepIndex: -1, stepTotal: 0, phase: "validation-repair" });
721
- };
722
-
723
- try {
724
- if (cli === "opencode") {
725
- const handle = runOpenCodeAgentWithPrompt(cliWs, agentPrompt, {
726
- onStreamEvent: stepEmit,
727
- model: model || undefined,
728
- force: Boolean(opts.force),
729
- env,
730
- });
731
- setChild(handle.child);
732
- await handle.finished;
733
- } else if (cli === "codex") {
734
- const handle = runCodexAgentWithPrivateMcp(cliWs, agentPrompt, {
735
- onStreamEvent: stepEmit,
736
- model: model || undefined,
737
- force: Boolean(opts.force),
738
- env,
739
- }, opts.agentflowUserId || opts.flowContext?.userId);
740
- setChild(handle.child);
741
- await handle.finished;
742
- } else if (cli === "claude-code") {
743
- const handle = runClaudeCodeAgentWithPrompt(cliWs, agentPrompt, {
744
- onStreamEvent: stepEmit,
745
- model: model || undefined,
746
- force: Boolean(opts.force),
747
- env,
748
- });
749
- setChild(handle.child);
750
- await handle.finished;
751
- } else {
752
- const handle = runCursorAgentWithPrivateMcp(cliWs, agentPrompt, {
753
- onStreamEvent: stepEmit,
754
- model: model || undefined,
755
- force: Boolean(opts.force),
756
- env,
757
- }, opts.agentflowUserId || opts.flowContext?.userId);
758
- setChild(handle.child);
759
- await handle.finished;
760
- }
761
- } catch (e) {
762
- setChild(null);
763
- emit({ type: "natural", kind: "error", text: `校验修复 agent 失败: ${e.message}` });
764
- return { ok: false, result: last, repairError: e.message, repairAttempts: attempt };
765
- }
766
- setChild(null);
767
-
768
- if (getAborted()) {
769
- return { ok: false, result: last, aborted: true };
770
- }
771
-
772
- last = validateComposerFlowYaml(flowYamlAbs, uiRoot);
773
- if (last.ok) {
774
- emit({ type: "status", line: t("composer.validation_passed") + t("composer.validation_repair_auto_success") });
775
- emit({ type: "natural", kind: "assistant", text: "✓ 校验修复后 flow.yaml 已通过 validate-flow" });
776
- return { ok: true, result: last, repairAttempts: attempt };
777
- }
778
-
779
- emit({
780
- type: "natural",
781
- kind: "assistant",
782
- text: `⚠ 第 ${attempt} 次修复后仍未通过:\n${formatValidationErrorsBlock(last)}`,
783
- });
784
- }
785
-
786
- emit({
787
- type: "natural",
788
- kind: "error",
789
- text: `flow 校验在 ${maxRepair} 次自动修复后仍未通过,请根据上方错误列表手动修改 flow.yaml。`,
790
- });
791
- return { ok: false, result: last, repairAttempts: maxRepair };
792
- }
793
-
794
- // ─── 多步编排执行 ──────────────────────────────────────────────────────────
795
-
796
- /**
797
- * 多步 Composer:规划 → 分步执行 → 流式推送进度。
798
- * 支持分阶段模式:大任务按「流转规划 → 节点补充 → 流程完善」三阶段逐轮生成,
799
- * 每阶段完成后 emit phase-complete,由前端决定是否继续下一阶段。
800
- *
801
- * @param {object} opts
802
- * @param {string} opts.uiWorkspaceRoot
803
- * @param {string} [opts.cliWorkspace]
804
- * @param {string} opts.userPrompt 用户原始输入
805
- * @param {string} [opts.fullPrompt] 含上下文的完整 prompt(兼容旧逻辑,若多步不使用此字段)
806
- * @param {string} [opts.modelKey] 用户选择的模型
807
- * @param {string} [opts.flowYamlAbs] flow.yaml 绝对路径
808
- * @param {string} [opts.flowId]
809
- * @param {string} [opts.flowSource]
810
- * @param {string[]} [opts.instanceIds]
811
- * @param {object} [opts.flowContext] { skillsHint, syncCurlHint } 等上下文
812
- * @param {Array<{ role: string, text: string }>} [opts.thread] 对话历史
813
- * @param {object} [opts.phaseContext] 分阶段上下文 { phaseIndex, phases, userPromptOriginal }
814
- * @param {string} [opts.phaseRole] 用户为本阶段指定的默认节点角色
815
- * @param {boolean} [opts.force]
816
- * @param {(ev: object) => void} [opts.onStreamEvent]
817
- * @returns {{ finished: Promise<void>, abort: () => void }}
818
- */
819
- export function startComposerMultiStep(opts) {
820
- const uiRoot = opts.uiWorkspaceRoot && String(opts.uiWorkspaceRoot).trim();
821
- if (!uiRoot) throw new Error("Missing uiWorkspaceRoot");
822
-
823
- const emit = typeof opts.onStreamEvent === "function" ? opts.onStreamEvent : () => {};
824
- let aborted = false;
825
- let currentChild = null;
826
- const env = agentflowUserEnv(opts.agentflowUserId || opts.flowContext?.userId);
827
-
828
- const abort = () => {
829
- aborted = true;
830
- if (currentChild && !currentChild.killed) {
831
- try { currentChild.kill("SIGTERM"); } catch { /* ignore */ }
832
- }
833
- };
834
-
835
- const finished = (async () => {
836
- try {
837
- // ── 1. 规划 ──────────────────────────────────────────────────────
838
- emit({ type: "status", line: t("composer.analyzing_task") });
839
-
840
- let flowYaml = "";
841
- if (opts.flowYamlAbs) {
842
- try { flowYaml = fs.readFileSync(opts.flowYamlAbs, "utf-8"); } catch { /* ignore */ }
843
- }
844
- const instMap = parseInstanceRoleModelMap(flowYaml);
845
-
846
- if (!opts.flowContext) opts.flowContext = {};
847
- opts.flowContext._instanceMap = instMap;
848
-
849
- const planResult = await planComposerTasks({
850
- userPrompt: opts.userPrompt,
851
- flowYaml,
852
- flowYamlAbs: opts.flowYamlAbs,
853
- instanceIds: opts.instanceIds,
854
- thread: opts.thread,
855
- intents: opts.flowContext?.intents,
856
- phaseContext: opts.phaseContext,
857
- phaseRole: opts.phaseRole,
858
- onEvent: emit,
859
- });
860
-
861
- if (aborted) return;
862
-
863
- const steps = planResult.steps;
864
- const isPhased = Boolean(planResult.phased);
865
- const phases = planResult.phases || PHASED_DEFINITIONS;
866
- const currentPhase = planResult.currentPhase ?? 0;
867
-
868
- if (isPhased) {
869
- const phaseDef = phases[currentPhase];
870
- emit({
871
- type: "phase-plan",
872
- phases: phases.map((p, i) => ({
873
- ...p,
874
- status: i < currentPhase ? "done" : i === currentPhase ? "running" : "pending",
875
- })),
876
- currentPhase,
877
- phaseTotal: phases.length,
878
- phaseName: phaseDef?.label || `阶段 ${currentPhase + 1}`,
879
- });
880
- }
881
-
882
- const totalSteps = steps.length;
883
- emit({
884
- type: "plan",
885
- steps: steps.map((s, i) => summarizePlanStepForUi(s, i, instMap)),
886
- total: totalSteps,
887
- });
888
-
889
- // ── 1.5 多步前置:脚本预生成 flow.yaml 与 spec.md skeleton ──────
890
- // 让 AI 只做"插入节点 + 填充 section",省下重复 YAML 与样板模板字符串。
891
- // 触发条件(任一):
892
- // - 分阶段模式的第 0 阶段(流转规划),始终尝试
893
- // - 非分阶段多步:只要 flow.yaml 还空,也尝试(用户 prompt 没命中 phased
894
- // 正则,但实质是「新建」场景)
895
- // skeleton 内部幂等:已有 instances 或 spec.md 已存在则自动跳过。
896
- const shouldTrySkeleton = opts.flowYamlAbs && (
897
- (isPhased && currentPhase === 0) ||
898
- (!isPhased)
899
- );
900
- if (shouldTrySkeleton) {
901
- // composerSpecAbs 兜底:flowContext 没传时按 flow.yaml 同目录推
902
- let specAbs = opts.flowContext?.composerSpecAbs || "";
903
- if (!specAbs && opts.flowYamlAbs) {
904
- specAbs = path.join(path.dirname(opts.flowYamlAbs), "composer-node-spec.md");
905
- }
906
- try {
907
- const skel = ensurePhase1Skeletons({
908
- flowYamlAbs: opts.flowYamlAbs,
909
- composerSpecAbs: specAbs,
910
- flowId: opts.flowId,
911
- userRequest: opts.phaseContext?.userPromptOriginal || opts.userPrompt,
912
- });
913
- if (skel.flow.created) {
914
- emit({ type: "natural", kind: "assistant", text: `✓ 预生成 flow.yaml skeleton (start + end + 主链 edge)` });
915
- }
916
- if (skel.spec.created) {
917
- emit({ type: "natural", kind: "assistant", text: `✓ 预生成 composer-node-spec.md 模板:${specAbs}` });
918
- }
919
- } catch (e) {
920
- emit({ type: "natural", kind: "error", text: `skeleton 预生成失败: ${e.message}` });
921
- }
922
- }
923
-
924
- // ── 2. 执行步骤 ────────────────────────────────────────────────
925
- let stepIndex = 0;
926
- while (stepIndex < steps.length && !aborted) {
927
- const step = steps[stepIndex];
928
-
929
- if (step.type === "script" && isSupportedScriptOp(step.op)) {
930
- const scriptBatch = [step];
931
- let nextIdx = stepIndex + 1;
932
- while (nextIdx < steps.length && steps[nextIdx].type === "script" && isSupportedScriptOp(steps[nextIdx].op)) {
933
- scriptBatch.push(steps[nextIdx]);
934
- nextIdx++;
935
- }
936
-
937
- const scriptFirstId = scriptBatch[0]?.params?.instanceId;
938
- emit({
939
- type: "step-start",
940
- index: stepIndex,
941
- total: totalSteps,
942
- stepType: "script",
943
- description: `执行 ${scriptBatch.length} 个脚本操作`,
944
- count: scriptBatch.length,
945
- instanceId: scriptFirstId != null ? String(scriptFirstId) : undefined,
946
- });
947
-
948
- for (let i = 0; i < scriptBatch.length; i++) {
949
- const s = scriptBatch[i];
950
- if (aborted) break;
951
- const globalIdx = stepIndex + i;
952
- emit({ type: "step-progress", index: globalIdx, total: totalSteps, description: s.description || s.op });
953
-
954
- if (!opts.flowYamlAbs) {
955
- emit({ type: "natural", kind: "error", text: `脚本操作需要 flow.yaml 路径,但未提供` });
956
- continue;
957
- }
958
-
959
- const result = executeScriptOp(opts.flowYamlAbs, s);
960
- if (result.success) {
961
- emit({ type: "natural", kind: "assistant", text: `✓ ${result.message}` });
962
- } else {
963
- emit({ type: "natural", kind: "error", text: `✗ ${s.op}: ${result.message}` });
964
- }
965
- emit({ type: "step-done", index: globalIdx, total: totalSteps, success: result.success });
966
- }
967
-
968
- stepIndex = nextIdx;
969
- continue;
970
- }
971
-
972
- if (step.type === "agent") {
973
- const complexity = step.complexity || "medium";
974
- const { nodeRole, preferredModel } = resolveAgentStepRoleAndModel(step, instMap, opts.modelKey);
975
- const routed = routeModel(complexity, { userPreferredModel: preferredModel || null });
976
- const sid = step.instanceId != null ? String(step.instanceId).trim() : "";
977
- const fromFlow = sid && instMap[sid];
978
-
979
- emit({
980
- type: "step-start",
981
- index: stepIndex,
982
- total: totalSteps,
983
- stepType: "agent",
984
- description: step.description,
985
- model: routed.model,
986
- tier: routed.tier,
987
- complexity,
988
- nodeRole: nodeRole || undefined,
989
- instanceId: sid || undefined,
990
- instanceLabel: fromFlow?.label,
991
- });
992
-
993
- const stepForPrompt = nodeRole ? { ...step, nodeRole } : { ...step };
994
- const agentPrompt = buildAgentStepPrompt(stepForPrompt, opts.flowContext);
995
- const cliWs = opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot();
996
- const modelKey = routed.model || preferredModel || opts.modelKey || "";
997
- const { cli, model } = resolveCliAndModel(uiRoot, modelKey || null, null);
998
-
999
- emit({ type: "ai-log", tag: "agent-step-prompt", text: agentPrompt, meta: { stepIndex, total: totalSteps, instanceId: sid || null, nodeRole: nodeRole || null, complexity, cli, model: model || null, description: step.description } });
1000
-
1001
- const stepEmit = (ev) => {
1002
- emit({ ...ev, stepIndex, stepTotal: totalSteps });
1003
- };
1004
-
1005
- try {
1006
- if (cli === "opencode") {
1007
- const handle = runOpenCodeAgentWithPrompt(cliWs, agentPrompt, {
1008
- onStreamEvent: stepEmit,
1009
- model: model || undefined,
1010
- force: Boolean(opts.force),
1011
- env,
1012
- });
1013
- currentChild = handle.child;
1014
- await handle.finished;
1015
- } else if (cli === "codex") {
1016
- const handle = runCodexAgentWithPrivateMcp(cliWs, agentPrompt, {
1017
- onStreamEvent: stepEmit,
1018
- model: model || undefined,
1019
- force: Boolean(opts.force),
1020
- env,
1021
- }, opts.agentflowUserId || opts.flowContext?.userId);
1022
- currentChild = handle.child;
1023
- await handle.finished;
1024
- } else if (cli === "claude-code") {
1025
- const handle = runClaudeCodeAgentWithPrompt(cliWs, agentPrompt, {
1026
- onStreamEvent: stepEmit,
1027
- model: model || undefined,
1028
- force: Boolean(opts.force),
1029
- env,
1030
- });
1031
- currentChild = handle.child;
1032
- await handle.finished;
1033
- } else {
1034
- const handle = runCursorAgentWithPrivateMcp(cliWs, agentPrompt, {
1035
- onStreamEvent: stepEmit,
1036
- model: model || undefined,
1037
- force: Boolean(opts.force),
1038
- env,
1039
- }, opts.agentflowUserId || opts.flowContext?.userId);
1040
- currentChild = handle.child;
1041
- await handle.finished;
1042
- }
1043
- currentChild = null;
1044
- emit({ type: "step-done", index: stepIndex, total: totalSteps, success: true });
1045
- } catch (e) {
1046
- currentChild = null;
1047
- emit({ type: "step-done", index: stepIndex, total: totalSteps, success: false, error: e.message });
1048
- emit({ type: "natural", kind: "error", text: `步骤 ${stepIndex + 1} 失败: ${e.message}` });
1049
- }
1050
-
1051
- stepIndex++;
1052
- continue;
1053
- }
1054
-
1055
- emit({ type: "step-done", index: stepIndex, total: totalSteps, success: false, error: `未知步骤类型: ${step.type}` });
1056
- stepIndex++;
1057
- }
1058
-
1059
- // ── 2.5 阶段一/非分阶段后置:把 spec.md 计划数据槽合并到 flow.yaml ──
1060
- // 幂等:同 name 槽位跳过;非 ★ 节点跳过。AI 已在 yaml 写过的不动。
1061
- const shouldApplyPlannedSlots = !aborted && opts.flowYamlAbs && (
1062
- (isPhased && currentPhase === 0) ||
1063
- (!isPhased)
1064
- );
1065
- if (shouldApplyPlannedSlots) {
1066
- let specAbs = opts.flowContext?.composerSpecAbs || "";
1067
- if (!specAbs && opts.flowYamlAbs) {
1068
- specAbs = path.join(path.dirname(opts.flowYamlAbs), "composer-node-spec.md");
1069
- }
1070
- if (specAbs && fs.existsSync(specAbs)) {
1071
- try {
1072
- const r = applyPlannedSlotsFromSpec(opts.flowYamlAbs, specAbs);
1073
- if (r.ok && r.applied.length > 0) {
1074
- const summary = r.applied.map((a) => {
1075
- const parts = [];
1076
- if (a.addedInputs.length) parts.push(`input += [${a.addedInputs.join(", ")}]`);
1077
- if (a.addedOutputs.length) parts.push(`output += [${a.addedOutputs.join(", ")}]`);
1078
- return ` ${a.instanceId}: ${parts.join(" / ")}`;
1079
- }).join("\n");
1080
- emit({
1081
- type: "natural",
1082
- kind: "assistant",
1083
- text: `✓ 脚本合并 spec.md 计划数据槽到 flow.yaml(已存在的跳过):\n${summary}`,
1084
- });
1085
- } else if (r.ok && r.applied.length === 0) {
1086
- emit({ type: "status", line: "spec.md 计划数据槽已全部落到 flow.yaml" });
1087
- } else if (!r.ok) {
1088
- emit({ type: "natural", kind: "error", text: `合并计划数据槽失败:${r.error}` });
1089
- }
1090
- } catch (e) {
1091
- emit({ type: "natural", kind: "error", text: `合并计划数据槽异常:${e.message}` });
1092
- }
1093
- }
1094
- }
1095
-
1096
- // ── 3. 校验(分阶段仅在最后阶段统一校验;非分阶段每次都校验) ──
1097
- const shouldRunValidation = !isPhased || currentPhase >= phases.length - 1;
1098
- if (!aborted && opts.flowYamlAbs && shouldRunValidation) {
1099
- await runComposerPostFlowValidationAndRepair({
1100
- uiWorkspaceRoot: uiRoot,
1101
- cliWorkspace: opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot(),
1102
- flowYamlAbs: opts.flowYamlAbs,
1103
- flowContext: opts.flowContext,
1104
- modelKey: opts.modelKey,
1105
- force: Boolean(opts.force),
1106
- agentflowUserId: opts.agentflowUserId || opts.flowContext?.userId,
1107
- onStreamEvent: emit,
1108
- getAborted: () => aborted,
1109
- setCurrentChild: (c) => {
1110
- currentChild = c;
1111
- },
1112
- });
1113
- } else if (!aborted && opts.flowYamlAbs && isPhased) {
1114
- emit({ type: "status", line: t("composer.skip_validation_for_phase") });
1115
- }
1116
-
1117
- // ── 4. 分阶段完成通知 ──────────────────────────────────────────
1118
- if (!aborted && isPhased) {
1119
- const isLastPhase = currentPhase >= phases.length - 1;
1120
- const phaseDef = phases[currentPhase];
1121
- const nextPhaseDef = !isLastPhase ? phases[currentPhase + 1] : null;
1122
- const userPromptOriginal = opts.phaseContext?.userPromptOriginal || opts.userPrompt;
1123
-
1124
- emit({
1125
- type: "phase-complete",
1126
- phaseIndex: currentPhase,
1127
- phaseTotal: phases.length,
1128
- phaseName: phaseDef?.label || t("composer.current_phase", { index: currentPhase + 1 }),
1129
- nextPhase: nextPhaseDef ? { index: currentPhase + 1, name: nextPhaseDef.name, label: nextPhaseDef.label } : null,
1130
- isLastPhase,
1131
- phases: phases.map((p, i) => ({
1132
- ...p,
1133
- status: i <= currentPhase ? "done" : "pending",
1134
- })),
1135
- userPromptOriginal,
1136
- });
1137
-
1138
- if (isLastPhase) {
1139
- emit({ type: "status", line: t("composer.all_phases_complete") });
1140
- } else {
1141
- emit({ type: "status", line: t("composer.phase_complete_waiting", { label: phaseDef?.label || t("composer.current_phase", { index: currentPhase + 1 }) }) });
1142
- }
1143
- } else if (!aborted) {
1144
- emit({ type: "status", line: t("composer.all_steps_complete") });
1145
- }
1146
- } catch (e) {
1147
- emit({ type: "natural", kind: "error", text: t("composer.multi_step_failed", { message: e.message }) });
1148
- throw e;
1149
- }
1150
- })();
1151
-
1152
- return { finished, abort };
1153
- }
1154
-
1155
- /**
1156
- * 根据任务复杂度判断是否走多步模式(async:用 Cursor/OpenCode CLI AI 判断,降级正则)。
1157
- * - "multi":新建流程、重构、复杂多节点操作 → 多步
1158
- * - "single":改标签、连一条边、小改动 → 单步
1159
- */
1160
- export async function shouldUseMultiStep(opts) {
1161
- if (!opts.flowYamlAbs) return false;
1162
- const prompt = (opts.userPrompt || "").trim();
1163
- if (!prompt) return false;
1164
- const cliWs = opts.cliWorkspace || (opts.flowYamlAbs ? path.dirname(opts.flowYamlAbs) : undefined);
1165
- const result = await classifyTaskComplexity(prompt, cliWs);
1166
- return result === "multi";
1167
- }