@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,17 +1,9 @@
1
1
  /**
2
- * Composer Skill Router:基于用户输入意图动态加载 SKILL.md reference 文档。
2
+ * Composer Skill Router:按用户在 Workspace Composer 中显式勾选的 skill key
3
+ * 加载 SKILL.md 及其引用的 reference 文档,并渲染成可注入 prompt 的文本块。
3
4
  *
4
- * 意图分类:
5
- * query-explain — 纯问答/解释(不修改文件)
6
- * add-instances — 新增节点/实例
7
- * edit-fields — 改已有节点内容
8
- * optimize-flow — 优化/重构整体流程
9
- * optimize-nodes — 优化特定节点 prompt / body
10
- * create-flow — 从零创建新流程
11
- *
12
- * 每种意图映射到需要注入的 skills 和 reference 文档。
5
+ * 旧的「按 prompt 猜意图自动注入」随 Start/End Pipeline Composer 一并下线。
13
6
  */
14
- import fs from "fs";
15
7
  import path from "path";
16
8
  import {
17
9
  listSkills as registryListSkills,
@@ -19,116 +11,6 @@ import {
19
11
  readSkillDetail as registryReadSkillDetail,
20
12
  } from "./skill-registry.mjs";
21
13
 
22
- // ─── 意图模式定义 ─────────────────────────────────────────────────────────
23
-
24
- const INTENT_PATTERNS = [
25
- {
26
- id: "query-explain",
27
- patterns: [
28
- // 纯问答:问什么/怎么/为什么/是否/有没有
29
- /^(?:这个|该|当前|选中)?.*(?:是什么|是啥|啥意思|怎么(?:工作|运行|执行|回事)|为什么|为啥|哪些|有没有|有多少|能不能|是否|如何|有什么)/i,
30
- // 动词主导:解释/说明/介绍/查看/展示/分析
31
- /(?:解释|说明|介绍|描述|分析|查看|看看|展示|显示|告诉|帮我看|帮我理解)/i,
32
- // 名词主导:逻辑/作用/功能/原理
33
- /(?:逻辑|作用|功能|用途|含义|意义|目的|原理|机制|区别|流程图|概览|概述).*(?:是|呢|吗|?|\?)/i,
34
- // 英文
35
- /(?:explain|describe|what\s+(?:does|is|are)|how\s+does|why\s+(?:does|is)|show\s+me|tell\s+me|walk.*through)/i,
36
- // "X的逻辑/功能/作用"
37
- /(?:脚本|script|body|prompt|代码|内容|节点|node).{0,15}(?:是什么|什么意思|做什么|干什么|逻辑|功能|作用|怎么样)/i,
38
- ],
39
- },
40
- {
41
- id: "create-flow",
42
- patterns: [
43
- /(?:新建|创建|新增|生成|搭建|设计).*(?:流程|流水线|pipeline|flow|agentflow)/i,
44
- /(?:从零|从头|从无到有).*(?:搭|建|写|做)/i,
45
- /create\s+(?:a\s+)?(?:new\s+)?(?:flow|pipeline)/i,
46
- ],
47
- },
48
- {
49
- id: "add-instances",
50
- patterns: [
51
- /(?:新增|添加|加入|插入|增加|加个|加一个|补充|补一个).*(?:节点|实例|node|instance|步骤|环节)/i,
52
- /(?:加|添|增|补).*(?:分支|条件|判断|循环|环|if|toBool|agent_toBool|anyOne)/i,
53
- /add\s+(?:a\s+)?(?:new\s+)?(?:node|instance|step)/i,
54
- ],
55
- },
56
- {
57
- id: "edit-fields",
58
- patterns: [
59
- /(?:改|修改|更新|调整|编辑|更换|换成|设为|设置|改为|替换|重写).*(?:节点|label|body|role|标签|名称|角色|内容|描述|文案|prompt|提示词|输入|输出|value|值)/i,
60
- /(?:节点|label|body|role|标签|角色|内容|文案|prompt|提示词).*(?:改|修改|更新|调整|编辑|换|设|替换|重写)/i,
61
- /edit\s+(?:node|label|body|role|content)/i,
62
- /(?:把|将).{0,30}(?:改成|换成|设为|更新为|修改为|替换为)/i,
63
- ],
64
- },
65
- {
66
- id: "optimize-nodes",
67
- patterns: [
68
- /(?:优化|改善|提升|改进|完善|精炼|润色|增强).*(?:节点|node|body|prompt|提示词|描述|文案|内容)/i,
69
- /(?:节点|node|body|prompt|提示词).*(?:优化|改善|提升|改进|完善|精炼|润色|增强)/i,
70
- /(?:让|使).{0,20}(?:节点|node|prompt|body).{0,20}(?:更好|更准|更清晰|更高效)/i,
71
- /optimize\s+(?:node|prompt|body)/i,
72
- ],
73
- },
74
- {
75
- id: "optimize-flow",
76
- patterns: [
77
- /(?:优化|改善|提升|改进|完善|重构|重新设计|简化|精简).*(?:流程|流水线|pipeline|flow|拓扑|结构|架构|整体)/i,
78
- /(?:流程|流水线|pipeline|flow|拓扑|结构|架构|整体).*(?:优化|改善|提升|改进|完善|重构|重新设计|简化|精简)/i,
79
- /(?:重新|重).*(?:规划|设计|组织|编排).*(?:流程|节点|步骤)/i,
80
- /optimize\s+(?:flow|pipeline|topology)/i,
81
- /refactor/i,
82
- ],
83
- },
84
- ];
85
-
86
- // 意图 → 需要加载的 skills 和 references
87
- const INTENT_RESOURCES = {
88
- "query-explain": {
89
- skills: [],
90
- references: [],
91
- },
92
- "create-flow": {
93
- skills: ["agentflow-flow-add-instances"],
94
- references: ["flow-control-capabilities.md", "flow-layout.md"],
95
- },
96
- "add-instances": {
97
- skills: ["agentflow-flow-add-instances"],
98
- references: ["flow-control-capabilities.md", "flow-layout.md"],
99
- },
100
- "edit-fields": {
101
- skills: ["agentflow-flow-edit-node-fields"],
102
- references: ["flow-prompt-handler-check.md"],
103
- },
104
- "optimize-nodes": {
105
- skills: ["agentflow-flow-edit-node-fields", "agentflow-flow-add-instances"],
106
- references: ["flow-prompt-handler-check.md", "flow-control-capabilities.md"],
107
- },
108
- "optimize-flow": {
109
- skills: ["agentflow-flow-add-instances", "agentflow-flow-edit-node-fields"],
110
- references: ["flow-control-capabilities.md", "flow-layout.md", "flow-prompt-handler-check.md"],
111
- },
112
- };
113
-
114
- // ─── 文件缓存 ─────────────────────────────────────────────────────────────
115
-
116
- const _fileCache = new Map();
117
- const CACHE_TTL_MS = 60_000;
118
-
119
- function readFileCached(absPath) {
120
- const now = Date.now();
121
- const cached = _fileCache.get(absPath);
122
- if (cached && now - cached.ts < CACHE_TTL_MS) return cached.content;
123
- try {
124
- const content = fs.readFileSync(absPath, "utf-8");
125
- _fileCache.set(absPath, { content, ts: now });
126
- return content;
127
- } catch {
128
- return null;
129
- }
130
- }
131
-
132
14
  export function listComposerSkills(packageRoot, workspaceRoot) {
133
15
  return registryListUniqueSkills(packageRoot, workspaceRoot).map(({ body, content, ...skill }) => skill);
134
16
  }
@@ -187,84 +69,6 @@ export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot)
187
69
  };
188
70
  }
189
71
 
190
- // ─── 意图检测 ─────────────────────────────────────────────────────────────
191
-
192
- /**
193
- * 分析用户输入,返回匹配的意图 ID 列表(按优先级排序,去重)。
194
- * 可能返回多个意图(如"优化节点并新增一个分支"同时匹配 optimize-nodes 和 add-instances)。
195
- * @param {string} userPrompt
196
- * @returns {string[]}
197
- */
198
- export function detectIntents(userPrompt) {
199
- if (!userPrompt || typeof userPrompt !== "string") return [];
200
- const text = userPrompt.trim();
201
- if (!text) return [];
202
-
203
- const matched = [];
204
- for (const { id, patterns } of INTENT_PATTERNS) {
205
- for (const re of patterns) {
206
- if (re.test(text)) {
207
- matched.push(id);
208
- break;
209
- }
210
- }
211
- }
212
- return [...new Set(matched)];
213
- }
214
-
215
- // ─── 加载 skill 和 reference 内容 ─────────────────────────────────────────
216
-
217
- /**
218
- * 根据意图列表加载对应的 SKILL.md 和 reference 文档内容。
219
- *
220
- * @param {string[]} intents - detectIntents 返回的意图 ID 列表
221
- * @param {string} packageRoot - AgentFlow 包根目录
222
- * @returns {{ skills: Array<{id: string, content: string}>, references: Array<{name: string, content: string}>, skillsHint: string, hasContext: boolean }}
223
- */
224
- export function loadResourcesForIntents(intents, packageRoot) {
225
- if (!intents || intents.length === 0) {
226
- return { skills: [], references: [], skillsHint: "", hasContext: false };
227
- }
228
-
229
- const skillIds = new Set();
230
- const refNames = new Set();
231
-
232
- for (const intent of intents) {
233
- const res = INTENT_RESOURCES[intent];
234
- if (!res) continue;
235
- for (const s of res.skills) skillIds.add(s);
236
- for (const r of res.references) refNames.add(r);
237
- }
238
-
239
- const skills = [];
240
- for (const id of skillIds) {
241
- const absPath = path.join(packageRoot, "skills", id, "SKILL.md");
242
- const content = readFileCached(absPath);
243
- if (content) {
244
- const body = stripFrontmatter(content);
245
- skills.push({ id, content: body, absPath });
246
- }
247
- }
248
-
249
- const references = [];
250
- for (const name of refNames) {
251
- const absPath = path.join(packageRoot, "reference", name);
252
- const content = readFileCached(absPath);
253
- if (content) {
254
- references.push({ name, content, absPath });
255
- }
256
- }
257
-
258
- const skillsHint = buildSkillsHint(intents, skills, references);
259
-
260
- return {
261
- skills,
262
- references,
263
- skillsHint,
264
- hasContext: skills.length > 0 || references.length > 0,
265
- };
266
- }
267
-
268
72
  // ─── 构建注入到 prompt 的文本块 ───────────────────────────────────────────
269
73
 
270
74
  // 已知 reference / skill 的一行摘要(compact 模式注入)
@@ -304,75 +108,6 @@ export function buildSkillCompactInjectionBlock(skills, references) {
304
108
  return parts.join("\n");
305
109
  }
306
110
 
307
- /**
308
- * 为单步 prompt 构建完整的 skill + reference 注入块。
309
- * @param {Array<{id: string, content: string}>} skills
310
- * @param {Array<{name: string, content: string}>} references
311
- * @returns {string}
312
- */
313
- export function buildSkillInjectionBlock(skills, references) {
314
- const parts = [];
315
- if ((skills?.length || 0) + (references?.length || 0) > 8) {
316
- return buildSkillCompactInjectionBlock(skills || [], references || []);
317
- }
318
-
319
- if (skills.length > 0) {
320
- parts.push("### 相关编辑技能(请严格遵循)");
321
- parts.push("");
322
- for (const s of skills) {
323
- parts.push(`<skill name="${s.id}">`);
324
- parts.push(s.content.trim());
325
- parts.push("</skill>");
326
- parts.push("");
327
- }
328
- }
329
-
330
- if (references.length > 0) {
331
- parts.push("### 参考文档");
332
- parts.push("");
333
- for (const r of references) {
334
- parts.push(`<reference name="${r.name}">`);
335
- parts.push(r.content.trim());
336
- parts.push("</reference>");
337
- parts.push("");
338
- }
339
- }
340
-
341
- return parts.join("\n");
342
- }
343
-
344
- /**
345
- * 为多步模式的 flowContext.skillsHint 构建精简版。
346
- */
347
- function buildSkillsHint(intents, skills, references) {
348
- const lines = [];
349
-
350
- if (intents.includes("add-instances") || intents.includes("create-flow")) {
351
- lines.push("- 新增实例与边:遵循 skill `skills/agentflow-flow-add-instances/SKILL.md`(内容已注入上下文)");
352
- }
353
- if (intents.includes("edit-fields") || intents.includes("optimize-nodes")) {
354
- lines.push("- 仅改已有实例文案/占位等:遵循 `skills/agentflow-flow-edit-node-fields/SKILL.md`(内容已注入上下文)");
355
- }
356
- if (intents.includes("optimize-flow")) {
357
- lines.push("- 流程结构优化:参考 `reference/flow-control-capabilities.md` 和 `reference/flow-layout.md`(内容已注入上下文)");
358
- }
359
-
360
- lines.push(
361
- "- **节点类型选择(必须遵守)**:**确定性任务 → tool_nodejs;非确定性任务 → agent_subAgent**。" +
362
- "确定性 = 相同输入必出相同输出、可用普通代码完整描述(CLI/npm、读写文件、转换格式、调 API)。" +
363
- "非确定性 = 需语义理解或创造(代码翻译/生成、源码/文本理解、多步决策、创意写作)。" +
364
- "醒目输出 → tool_print。" +
365
- "反例:『Android 转 RN』『代码 review』必须 agent。"
366
- );
367
- lines.push(
368
- "- **tool_nodejs 必须写 script 字段**:script 是实际执行的命令代码,body 仅为文档注释(有 script 时不执行)。" +
369
- "禁止 tool_nodejs 只有 body 没有 script(body 中的自然语言不会被执行)。" +
370
- "如果无法写出完整可执行的 script,必须改用 agent_subAgent。"
371
- );
372
-
373
- return lines.join("\n");
374
- }
375
-
376
111
  function buildSelectedSkillsHint(skills) {
377
112
  if (!Array.isArray(skills) || skills.length === 0) return "";
378
113
  const lines = ["## 用户选择的 skills"];
@@ -385,58 +120,3 @@ function buildSelectedSkillsHint(skills) {
385
120
 
386
121
  // ─── 辅助 ─────────────────────────────────────────────────────────────────
387
122
 
388
- function stripFrontmatter(content) {
389
- const match = content.match(/^---\n[\s\S]*?\n---\n?/);
390
- return match ? content.slice(match[0].length) : content;
391
- }
392
-
393
- /**
394
- * 为规划器提供精简的 skill 上下文摘要(不含完整文档内容,仅规则要点)。
395
- * @param {string[]} intents
396
- * @returns {string}
397
- */
398
- export function buildPlannerSkillContext(intents) {
399
- if (!intents || intents.length === 0) return "";
400
-
401
- const parts = ["## 编辑技能约束(规划时须遵守)"];
402
-
403
- if (intents.includes("add-instances") || intents.includes("create-flow")) {
404
- parts.push(
405
- "- 新增节点:须从 builtin/pipelines/new/flow.yaml 拷贝同类 definitionId 的实例模板;" +
406
- "不要自造 input/output 顺序和 name;每个新 instanceId 须在 ui.nodePositions 写入坐标(主链从左到右 x 递增 280,起始 x:100 y:300;分支 y 错开 200);" +
407
- "默认不连线,仅当用户明确要求时才在 edges 中增加边。"
408
- );
409
- parts.push(
410
- "- 节点类型选择:**确定性任务 → tool_nodejs;非确定性任务 → agent_subAgent**。" +
411
- "确定性 = 相同输入永远相同输出(CLI/npm/读写文件/转换/调 API);" +
412
- "非确定性 = 需语义理解或创造(代码翻译/生成、理解源码、多步决策、创意写作);" +
413
- "醒目输出 → tool_print。反例:『Android 转 RN』『代码 review』必须 agent。"
414
- );
415
- parts.push(
416
- "- tool_nodejs 的 script 与 body 区分:script 是实际执行的命令(必填),body 仅为文档注释(有 script 时不执行);" +
417
- "禁止 tool_nodejs 只写 body 不写 script(自然语言不会被执行);如果无法写出完整可执行的 script,必须改用 agent_subAgent。"
418
- );
419
- parts.push("- 每个节点单一职责,不要把多个操作塞进同一个 body。");
420
- }
421
-
422
- if (intents.includes("edit-fields") || intents.includes("optimize-nodes")) {
423
- parts.push(
424
- "- 修改已有节点:可改白名单字段(label、body、role、script、input[].value、output[].value);" +
425
- "禁改 definitionId、instanceId 键名、input/output 数组结构与顺序、edges。"
426
- );
427
- parts.push(
428
- "- **tool_nodejs 的 script 必须写**:`definitionId: tool_nodejs` 的节点核心是 `script`(实际执行的 shell/node 命令)," +
429
- "`body` 有 script 时不执行。优化 tool_nodejs 节点时务必检查并完善 `script` 字段,禁止只在 `body` 里写自然语言描述。" +
430
- "如果无法写出可执行 script,应建议改用 `agent_subAgent`。"
431
- );
432
- }
433
-
434
- if (intents.includes("optimize-flow")) {
435
- parts.push(
436
- "- 流程优化可能涉及:调整拓扑(改边)、增删节点、改 body 内容、调整布局坐标。" +
437
- "如果需要多种操作,拆成多个步骤(先改内容再连线,先加节点再连线)。"
438
- );
439
- }
440
-
441
- return parts.join("\n");
442
- }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 带超时和缓冲上限的 execFile。
3
+ *
4
+ * 失败时把 stdout / stderr 挂在 error 上再抛——调用方通常要靠子进程的输出判断出了什么事,
5
+ * 只拿到一个 exit code 没法处理。
6
+ */
7
+ import { execFile } from "child_process";
8
+
9
+ /**
10
+ * @param {string} command
11
+ * @param {string[]} args
12
+ * @param {{ timeout?: number, maxBuffer?: number, cwd?: string, env?: object }} [options]
13
+ * @returns {Promise<{ stdout: string, stderr: string }>}
14
+ */
15
+ export function execFileBuffered(command, args, options = {}) {
16
+ return new Promise((resolve, reject) => {
17
+ execFile(command, args, {
18
+ timeout: Number(options.timeout || 30000),
19
+ maxBuffer: Number(options.maxBuffer || 2 * 1024 * 1024),
20
+ cwd: options.cwd || process.cwd(),
21
+ env: options.env || process.env,
22
+ }, (error, stdout, stderr) => {
23
+ if (error) {
24
+ error.stdout = stdout;
25
+ error.stderr = stderr;
26
+ reject(error);
27
+ return;
28
+ }
29
+ resolve({ stdout, stderr });
30
+ });
31
+ });
32
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * `agentflow flow dsl <export|import|lint>` 的实现。
3
+ *
4
+ * export / import 是互逆的,用同一套 IR,所以往返可证;lint 只读,不改任何文件。
5
+ */
6
+ import fs from "fs";
7
+ import os from "os";
8
+ import path from "path";
9
+
10
+ import { WORKSPACE_STATE_FILENAME, mergeWorkspaceState } from "../workspace-state.mjs";
11
+ import {
12
+ FLOW_LAYOUT_FILENAME,
13
+ FLOW_NODES_FILENAME,
14
+ FLOW_SOURCE_FILENAME,
15
+ flowFilesToGraph,
16
+ graphToFlowFiles,
17
+ } from "./index.mjs";
18
+ import { legacyYamlToDesignGraph } from "./legacy-yaml.mjs";
19
+ import { lintFlowDir } from "./lint.mjs";
20
+ import { readWorkspaceGraphFiles, writeWorkspaceGraphFiles } from "../workspace-flow-store.mjs";
21
+ import { applyWorkspaceAutoLayout } from "../workspace-auto-layout.mjs";
22
+
23
+ const GRAPH_FILENAME = "workspace.graph.json";
24
+ const LEGACY_YAML_FILENAME = "flow.yaml";
25
+
26
+ function readJson(file, fallback) {
27
+ if (!fs.existsSync(file)) return fallback;
28
+ try {
29
+ const raw = fs.readFileSync(file, "utf-8");
30
+ if (!raw.trim()) return fallback;
31
+ const parsed = JSON.parse(raw);
32
+ return parsed && typeof parsed === "object" ? parsed : fallback;
33
+ } catch {
34
+ return fallback;
35
+ }
36
+ }
37
+
38
+ function writeFileEnsuringDir(file, text) {
39
+ fs.mkdirSync(path.dirname(file), { recursive: true });
40
+ fs.writeFileSync(file, text, "utf-8");
41
+ }
42
+
43
+ /** 读一个流程目录里的完整图(设计态 + 运行态)。 */
44
+ function readFullGraph(flowDir) {
45
+ const graphPath = path.join(flowDir, GRAPH_FILENAME);
46
+ if (!fs.existsSync(graphPath)) throw new Error(`找不到 ${graphPath}`);
47
+ const design = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
48
+ return mergeWorkspaceState(design, readJson(path.join(flowDir, WORKSPACE_STATE_FILENAME), null));
49
+ }
50
+
51
+ /**
52
+ * workspace.graph.json -> workspace.flow.js + layout/nodes + 外置长文本。
53
+ * @returns {{ outDir: string, written: string[] }}
54
+ */
55
+ export function exportFlowDsl(flowDir, outDir) {
56
+ const graph = readFullGraph(flowDir);
57
+ const out = graphToFlowFiles(graph);
58
+ const target = path.resolve(outDir || flowDir);
59
+ fs.mkdirSync(target, { recursive: true });
60
+
61
+ const written = [];
62
+ const put = (rel, text) => {
63
+ writeFileEnsuringDir(path.join(target, rel), text);
64
+ written.push(rel);
65
+ };
66
+
67
+ put(FLOW_SOURCE_FILENAME, out.source);
68
+ put(FLOW_LAYOUT_FILENAME, `${JSON.stringify(out.layout, null, 2)}\n`);
69
+ if (Object.keys(out.nodeMeta.nodes).length) {
70
+ put(FLOW_NODES_FILENAME, `${JSON.stringify(out.nodeMeta, null, 2)}\n`);
71
+ }
72
+ for (const file of out.files) put(file.path, file.text);
73
+
74
+ return { outDir: target, written };
75
+ }
76
+
77
+ /**
78
+ * workspace.flow.js + layout/nodes -> workspace.graph.json(设计态)。
79
+ * 运行态原样保留在 workspace.state.json 里,不受导入影响。
80
+ */
81
+ export function importFlowDsl(srcDir, outFlowDir) {
82
+ const dir = path.resolve(srcDir);
83
+ const sourcePath = path.join(dir, FLOW_SOURCE_FILENAME);
84
+ if (!fs.existsSync(sourcePath)) throw new Error(`找不到 ${sourcePath}`);
85
+
86
+ const lint = lintFlowDir(dir);
87
+ if (lint.errors.length) {
88
+ throw new Error(`lint 未通过,拒绝导入:\n ${lint.errors.join("\n ")}`);
89
+ }
90
+
91
+ // file() 引用的外置文本:把目录下所有非结构文件都喂进去,解析时按引用取用
92
+ const files = {};
93
+ const collect = (rel) => {
94
+ const abs = path.join(dir, rel);
95
+ if (!fs.existsSync(abs)) return;
96
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
97
+ if (entry.isDirectory()) continue;
98
+ files[`${rel}/${entry.name}`] = fs.readFileSync(path.join(abs, entry.name), "utf-8");
99
+ }
100
+ };
101
+ for (const sub of ["prompts", "docs", "scripts"]) collect(sub);
102
+
103
+ const design = flowFilesToGraph({
104
+ source: fs.readFileSync(sourcePath, "utf-8"),
105
+ layout: readJson(path.join(dir, FLOW_LAYOUT_FILENAME), { nodes: {} }),
106
+ nodeMeta: readJson(path.join(dir, FLOW_NODES_FILENAME), { nodes: {} }),
107
+ files,
108
+ });
109
+
110
+ const target = path.resolve(outFlowDir || dir);
111
+ fs.mkdirSync(target, { recursive: true });
112
+ writeFileEnsuringDir(path.join(target, GRAPH_FILENAME), `${JSON.stringify(design, null, 2)}\n`);
113
+ return {
114
+ graphPath: path.join(target, GRAPH_FILENAME),
115
+ nodeCount: Object.keys(design.instances).length,
116
+ edgeCount: design.edges.length,
117
+ warnings: lint.warnings,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * 为 Workspace 图生成确定性坐标。默认只补缺失坐标,保护用户已经拖过的节点;`all` 用于
123
+ * AI 新建流程或用户明确要求整理整张图时的全量重排。
124
+ */
125
+ export function layoutWorkspaceFlowDir(flowDir, { all = false, workspaceRoot = "" } = {}) {
126
+ const dir = path.resolve(flowDir);
127
+ const lint = lintWorkspaceFlowDir(dir, { workspaceRoot });
128
+ if (lint.errors.length) {
129
+ throw new Error(`lint 未通过,拒绝排版:\n ${lint.errors.join("\n ")}`);
130
+ }
131
+ const current = readWorkspaceGraphFiles(dir, { marketplaceRoot: workspaceRoot });
132
+ if (current.format === "empty") throw new Error("这个流程还没有 Workspace 图");
133
+
134
+ const before = current.graph?.ui?.nodePositions || {};
135
+ const next = applyWorkspaceAutoLayout(current.graph, { preserveExisting: !all });
136
+ const after = next.ui.nodePositions;
137
+ let positioned = 0;
138
+ for (const id of Object.keys(next.instances || {})) {
139
+ if (before[id]?.x !== after[id]?.x || before[id]?.y !== after[id]?.y) positioned += 1;
140
+ }
141
+ const written = writeWorkspaceGraphFiles(dir, next, { marketplaceRoot: workspaceRoot });
142
+ return {
143
+ flowDir: dir,
144
+ format: written.format,
145
+ mode: all ? "all" : "missing",
146
+ nodeCount: Object.keys(next.instances || {}).length,
147
+ positioned,
148
+ layoutPath: path.join(dir, FLOW_LAYOUT_FILENAME),
149
+ };
150
+ }
151
+
152
+ /** 没有节点丢失时的空损耗清单,让返回值形状始终一致。 */
153
+ const NO_LOSS = { remapped: [], dropped: [], droppedEdges: [], renamedIds: [], warnings: [] };
154
+
155
+ /**
156
+ * 把一个流程目录就地迁移成代码形态。两个来源:
157
+ *
158
+ * - `workspace.graph.json` —— 同一套词汇表,纯换存储格式,无损
159
+ * - `flow.yaml` —— 老执行栈的词汇表,要换词,**可能有损**
160
+ *
161
+ * 都走 Web UI 保存时的同一条路径(`writeWorkspaceDesign`),因此同样带往返比对闸门:
162
+ * 生成的代码解析不回原图就不迁移,原文件原样留着。
163
+ *
164
+ * yaml 那条路默认**拒绝有损迁移**:只要有节点或边接不过去就停下来把清单报出去,磁盘不动。
165
+ * `allowLoss` 才落盘。理由和存储层那条闸门一样——宁可不迁移,也不能悄悄弄丢。
166
+ *
167
+ * yaml 原文不删。迁移完 `workspace.flow.js` 成为权威(读图先看它),`flow.yaml` 退到
168
+ * 一边当原始材料,出了问题还能对着看。
169
+ *
170
+ * @param {string} flowDir
171
+ * @param {{ force?: boolean, marketplaceRoot?: string }} [opts]
172
+ * @returns {{ flowDir: string, format: "dsl"|"json"|"yaml"|"empty", migrated: boolean,
173
+ * degradedReason: string|null, externals: string[], source?: "graph.json"|"flow.yaml",
174
+ * remapped: Array, dropped: Array, droppedEdges: Array, warnings: string[] }}
175
+ */
176
+ export function migrateFlowDirToDsl(flowDir, opts = {}) {
177
+ const dir = path.resolve(flowDir);
178
+ const storeOpts = { marketplaceRoot: opts.marketplaceRoot || "" };
179
+ const current = readWorkspaceGraphFiles(dir, storeOpts);
180
+ if (current.format === "dsl") {
181
+ return { flowDir: dir, format: "dsl", migrated: false, degradedReason: null, externals: [], ...NO_LOSS };
182
+ }
183
+ if (current.format === "empty") return migrateLegacyYamlDir(dir, opts);
184
+
185
+ // 走完整图这条路:历史 graph.json 里运行产出还是内联的,得先拆出去,否则那些每跑一次
186
+ // 就变一次的值会被当成设计态参与往返比对
187
+ const result = writeWorkspaceGraphFiles(dir, current.graph, storeOpts);
188
+ return {
189
+ flowDir: dir,
190
+ format: result.format,
191
+ migrated: result.format === "dsl",
192
+ degradedReason: result.degradedReason,
193
+ externals: result.externals,
194
+ source: "graph.json",
195
+ ...NO_LOSS,
196
+ };
197
+ }
198
+
199
+ /** `flow.yaml` -> 代码。没有 yaml 就是真的没图。 */
200
+ function migrateLegacyYamlDir(dir, { force = false, marketplaceRoot = "" } = {}) {
201
+ const yamlPath = path.join(dir, LEGACY_YAML_FILENAME);
202
+ if (!fs.existsSync(yamlPath)) {
203
+ return { flowDir: dir, format: "empty", migrated: false, degradedReason: null, externals: [], ...NO_LOSS };
204
+ }
205
+ const converted = legacyYamlToDesignGraph(fs.readFileSync(yamlPath, "utf-8"));
206
+ const loss = {
207
+ remapped: converted.remapped,
208
+ dropped: converted.dropped,
209
+ droppedEdges: converted.droppedEdges,
210
+ renamedIds: converted.renamedIds,
211
+ warnings: converted.warnings,
212
+ };
213
+ // `control_end` 和指向它的边标了 benign——丢了等于没丢,不该拦住迁移。
214
+ const lossy = [...converted.dropped, ...converted.droppedEdges].some((x) => !x.benign);
215
+ if (lossy && !force) {
216
+ return {
217
+ flowDir: dir,
218
+ format: "yaml",
219
+ migrated: false,
220
+ degradedReason: "有节点或边接不过去(--allow-loss 忽略并继续)",
221
+ externals: [],
222
+ source: "flow.yaml",
223
+ ...loss,
224
+ };
225
+ }
226
+ const result = writeWorkspaceGraphFiles(dir, converted.graph, { marketplaceRoot });
227
+ return {
228
+ flowDir: dir,
229
+ format: result.format,
230
+ migrated: result.format === "dsl",
231
+ // 从 yaml 出发时,退回 JSON 也是**成功**:`workspace.graph.json` 读得出、画得出、
232
+ // 跑得动,而 yaml 三样都不行。够不着代码形态只是差最后一步,不是这次迁移失败。
233
+ leftYaml: true,
234
+ degradedReason: result.degradedReason,
235
+ externals: result.externals,
236
+ source: "flow.yaml",
237
+ ...loss,
238
+ };
239
+ }
240
+
241
+ /**
242
+ * 校验一个流程目录里的 Workspace 图,不管它是代码形态还是历史 JSON。
243
+ *
244
+ * 历史 JSON 的做法是先在临时目录里渲染成代码再 lint——校验的正是它迁移之后会变成的
245
+ * 样子,比「这份 JSON 能不能 parse」有用得多:引脚名、未知节点类型、环、fan-in 这些
246
+ * 问题在两种形态下都一样存在,只有代码形态能查出来。
247
+ *
248
+ * @returns {{ format: "dsl"|"json"|"empty", errors: string[], warnings: string[] }}
249
+ */
250
+ export function lintWorkspaceFlowDir(flowDir, opts = {}) {
251
+ const dir = path.resolve(flowDir);
252
+ if (fs.existsSync(path.join(dir, FLOW_SOURCE_FILENAME))) {
253
+ return { format: "dsl", ...lintFlowDir(dir, opts) };
254
+ }
255
+
256
+ const current = readWorkspaceGraphFiles(dir);
257
+ if (current.format === "empty") {
258
+ return { format: "empty", errors: [], warnings: ["这个流程还没有 Workspace 图"] };
259
+ }
260
+
261
+ const staging = fs.mkdtempSync(path.join(os.tmpdir(), "agentflow-lint-"));
262
+ try {
263
+ const out = graphToFlowFiles(current.graph);
264
+ writeFileEnsuringDir(path.join(staging, FLOW_SOURCE_FILENAME), out.source);
265
+ writeFileEnsuringDir(path.join(staging, FLOW_LAYOUT_FILENAME), `${JSON.stringify(out.layout, null, 2)}\n`);
266
+ writeFileEnsuringDir(path.join(staging, FLOW_NODES_FILENAME), `${JSON.stringify(out.nodeMeta, null, 2)}\n`);
267
+ for (const file of out.files) writeFileEnsuringDir(path.join(staging, file.path), file.text);
268
+ // 代码节点包在原目录里,不复制过去的话每个 import 都会报「节点包不存在」
269
+ const localNodes = path.join(dir, "nodes");
270
+ if (fs.existsSync(localNodes)) fs.cpSync(localNodes, path.join(staging, "nodes"), { recursive: true });
271
+ return { format: "json", ...lintFlowDir(staging, opts) };
272
+ } finally {
273
+ fs.rmSync(staging, { recursive: true, force: true });
274
+ }
275
+ }
276
+
277
+ export { lintFlowDir };