@fieldwangai/agentflow 0.1.149 → 0.1.154

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 (195) hide show
  1. package/README.md +20 -33
  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 +167 -88
  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 +276 -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 +268 -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 +10 -30
  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 +43 -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 +153 -283
  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 +1106 -18013
  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 +74 -0
  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-DPbR2UaN.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BZ5KqLur.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 +2 -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 +8 -47
  123. package/skills/agentflow-cli/SKILL.md +158 -25
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +668 -18
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +7 -248
  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 +4 -73
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -18
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +3 -54
  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 +5 -4
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-placeholder-reference/SKILL.md +2 -2
  140. package/skills/agentflow-runtime-reference/SKILL.md +2 -2
  141. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  142. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  143. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  144. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  145. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  146. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  147. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  148. package/bin/lib/api-runner.mjs +0 -387
  149. package/bin/lib/apply.mjs +0 -903
  150. package/bin/lib/composer-flow-instances.mjs +0 -68
  151. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  152. package/bin/lib/composer-flow-validate.mjs +0 -47
  153. package/bin/lib/composer-model-router.mjs +0 -185
  154. package/bin/lib/composer-node-schema.mjs +0 -303
  155. package/bin/lib/composer-planner.mjs +0 -751
  156. package/bin/lib/composer-script-ops.mjs +0 -233
  157. package/bin/lib/flow-static-preview.mjs +0 -104
  158. package/bin/lib/hub-login.mjs +0 -54
  159. package/bin/lib/hub-publish.mjs +0 -159
  160. package/bin/lib/hub-remote.mjs +0 -189
  161. package/bin/lib/hub.mjs +0 -299
  162. package/bin/lib/jenkins.mjs +0 -380
  163. package/bin/lib/node-execute.mjs +0 -539
  164. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  165. package/bin/lib/runtime-context.mjs +0 -243
  166. package/bin/lib/scheduler.mjs +0 -601
  167. package/bin/lib/ui-print.mjs +0 -94
  168. package/bin/pipeline/build-node-prompt.mjs +0 -271
  169. package/bin/pipeline/check-cache.mjs +0 -191
  170. package/bin/pipeline/check-flow.mjs +0 -543
  171. package/bin/pipeline/collect-nodes.mjs +0 -212
  172. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  173. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  174. package/bin/pipeline/gc.mjs +0 -129
  175. package/bin/pipeline/get-env.mjs +0 -59
  176. package/bin/pipeline/get-resolved-values.mjs +0 -344
  177. package/bin/pipeline/load-key.mjs +0 -62
  178. package/bin/pipeline/parse-flow.mjs +0 -708
  179. package/bin/pipeline/post-process-control-if.mjs +0 -23
  180. package/bin/pipeline/post-process-node.mjs +0 -490
  181. package/bin/pipeline/pre-process-node.mjs +0 -1430
  182. package/bin/pipeline/resolve-inputs.mjs +0 -201
  183. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  184. package/bin/pipeline/save-key.mjs +0 -93
  185. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  186. package/bin/pipeline/validate-for-ui.mjs +0 -234
  187. package/bin/pipeline/validate-script-output.mjs +0 -130
  188. package/bin/pipeline/write-result.mjs +0 -182
  189. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  190. package/builtin/pipelines/new/flow.yaml +0 -545
  191. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  192. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  193. package/builtin/web-ui/dist/assets/index-BSCpd5la.js +0 -889
  194. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  195. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -0,0 +1,358 @@
1
+ /**
2
+ * IR -> `workspace.flow.js`。
3
+ *
4
+ * 生成的是**受限 ESM**:只有 import、const 声明和调用表达式,没有控制流。这不是风格
5
+ * 选择——画布必须能靠静态解析渲染出来,绝不能为了画一张图去执行用户的代码。
6
+ *
7
+ * 图结构如何映射成代码:
8
+ *
9
+ * - 一个节点 = 一次 `const <nodeId> = <api>(label?, pins, body?)`
10
+ * - 控制边(连到 `prev` 的边)= `flow(a, b, c)` 里的参数顺序
11
+ * - 数据边 = 引脚对象里引用上游变量:`{ content: agent1.result }`
12
+ * - `control_if` 的两个分支 = 第 3、4 个参数 `flow(...)` / `flow(...)`
13
+ * - 控制流分叉 = `flow.fork(flow(...), flow(...))`
14
+ * - 运行入口 = `export const run = flow("Run", ...)`
15
+ *
16
+ * 超过 EXTERNALIZE_MIN 的长文本外置成独立文件,代码里写 `file("prompts/x.md")`:
17
+ * 一段 8 KB 的提示词塞进模板字符串会把流程结构淹没。
18
+ */
19
+ import { CTRL_SLOTS, RUN_DEFINITIONS, apiName, definitionOf, isDisplayDefinition, isProvideDefinition } from "./defs.mjs";
20
+
21
+ /** 长文本外置阈值(字节)。低于这个长度的直接内联,免得目录里全是碎文件。 */
22
+ export const EXTERNALIZE_MIN = 3000;
23
+
24
+ const isIdentifier = (s) => /^[A-Za-z_$][\w$]*$/.test(s);
25
+
26
+ /**
27
+ * 短的普通文本用双引号;带引号、换行或过长的用模板字符串。
28
+ * 含 `"` 的内容(排程 JSON、带引号的提示词)走双引号会被转义成一片 `\"`,读不动。
29
+ */
30
+ function literal(value) {
31
+ const text = String(value ?? "");
32
+ const inlineable = !text.includes("\n")
33
+ && text.length < 90
34
+ && !text.includes('"')
35
+ && !text.includes("`")
36
+ && !text.includes("${");
37
+ if (inlineable) return JSON.stringify(text);
38
+ return "`" + text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${") + "`";
39
+ }
40
+
41
+ /**
42
+ * 把正文写成模板字符串,`folds` 里的占位符还原成 JS 插值。
43
+ *
44
+ * 没被折叠的 `${...}` 一律转义成 `\${...}`——那是运行时自己要解析的占位符,不是 JS 表达式。
45
+ */
46
+ function interpolatedLiteral(text, folds) {
47
+ const escape = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
48
+ const parts = String(text ?? "").split(/(\$\{[A-Za-z_][A-Za-z0-9_-]*\})/);
49
+ return `\`${parts.map((part) => {
50
+ const m = /^\$\{([A-Za-z_][A-Za-z0-9_-]*)\}$/.exec(part);
51
+ return m && folds.has(m[1]) ? `\${${folds.get(m[1])}}` : escape(part);
52
+ }).join("")}\``;
53
+ }
54
+
55
+ function displayFileExt(kind) {
56
+ return { html: "html", chart: "json", table: "json", mermaid: "mmd", ascii: "txt", react: "json" }[kind] || "md";
57
+ }
58
+
59
+ /**
60
+ * @param {object} ir
61
+ * @param {{ packages?: Record<string, {binding?: string, specifier: string}> }} [opts]
62
+ * packages:nodeId -> 代码节点包,生成 `import x from "./nodes/x"` 并用绑定名做调用
63
+ * @returns {{ source: string, files: Array<{path: string, text: string}> }}
64
+ */
65
+ export function generateFlowSource(ir, opts = {}) {
66
+ const N = ir.nodes;
67
+ const files = [];
68
+ const emitFile = (rel, text) => {
69
+ files.push({ path: rel, text });
70
+ return rel;
71
+ };
72
+
73
+ // 同一个节点的 body 和某个引脚值可能都超阈值,文件名必须带槽名区分:都叫
74
+ // `prompts/<id>.md` 的话两段不同的文本会写进同一个文件,往返时后写的覆盖先写的。
75
+ const externalize = (id, slot, text) => {
76
+ if (text.length < EXTERNALIZE_MIN) return null;
77
+ const definitionId = N[id].definitionId;
78
+ const suffix = slot === "$body" || slot === "$script" ? "" : `.${slot.replace(/[^\w.-]/g, "_")}`;
79
+ if (isDisplayDefinition(definitionId)) {
80
+ return emitFile(`docs/${id}${suffix}.${displayFileExt(definitionId.slice("display_".length))}`, text);
81
+ }
82
+ return emitFile(slot === "$script" ? `scripts/${id}.sh` : `prompts/${id}${suffix}.md`, text);
83
+ };
84
+ const textArg = (id, slot, text) => {
85
+ const rel = externalize(id, slot, text);
86
+ return rel ? `file(${JSON.stringify(rel)})` : literal(text);
87
+ };
88
+
89
+ const controlNext = new Map();
90
+ const dataIn = new Map();
91
+ for (const key of ir.edges) {
92
+ const [src, fromSlot, dst, toSlot] = key.split("|");
93
+ if (toSlot === "prev") {
94
+ if (!controlNext.has(src)) controlNext.set(src, []);
95
+ controlNext.get(src).push({ to: dst, slot: fromSlot });
96
+ } else {
97
+ if (!dataIn.has(dst)) dataIn.set(dst, []);
98
+ dataIn.get(dst).push({ slot: toSlot, from: src, fromSlot });
99
+ }
100
+ }
101
+
102
+ const isIf = (id) => N[id]?.definitionId === "control_if";
103
+
104
+ // 代码节点包自己声明了输出槽,import 已经把它们带进来,不必解构;引用时直接写
105
+ // `pkgNode.total`。这个集合必须先算,`outVar` 只收真的会被解构出来的槽——否则会生成
106
+ // 一个引用了不存在变量的文件。
107
+ const packageOutputs = (id) => new Set(
108
+ (opts.packages?.[id]?.output || []).map((slot) => String(slot?.name || "")),
109
+ );
110
+ const destructured = new Map(
111
+ Object.entries(N).map(([id, node]) => {
112
+ const declared = packageOutputs(id);
113
+ return [id, node.extraOut.filter((slot) => !declared.has(slot))];
114
+ }),
115
+ );
116
+
117
+ // 文件里所有顶层标识符共用一个命名空间:节点 id、解构出来的输出变量、import 绑定名。
118
+ // 分开发号就会撞——两个节点各有一个叫 ok 的自定义输出槽,是完全正常的图,但会生成两条
119
+ // `const { ok } = ...`,文件根本解析不回来。
120
+ const taken = new Set(Object.keys(N));
121
+ const uniqueName = (base, fallback) => {
122
+ let name = isIdentifier(base) ? base : fallback;
123
+ if (taken.has(name)) name = fallback;
124
+ while (taken.has(name)) name += "_";
125
+ taken.add(name);
126
+ return name;
127
+ };
128
+
129
+ // 自定义输出槽通过解构暴露成变量:`const { storyId } = node;`
130
+ const outVar = new Map();
131
+ for (const [id, slots] of destructured) {
132
+ for (const slot of slots) {
133
+ outVar.set(`${id}|${slot}`, uniqueName(slot, `${id}_${slot}`.replace(/[^\w$]/g, "_")));
134
+ }
135
+ }
136
+
137
+ // 代码节点包的 import 绑定名
138
+ const bindingOf = new Map();
139
+ for (const [id, pkg] of Object.entries(opts.packages || {})) {
140
+ const base = pkg.binding
141
+ || pkg.specifier.split("/").pop().replace(/[-.](\w)/g, (_, c) => c.toUpperCase());
142
+ // 同一个包在多个节点上共用一个绑定名,别重复发号
143
+ const shared = [...bindingOf.values()].find((v) => v.spec === pkg.specifier);
144
+ bindingOf.set(id, shared || { name: uniqueName(base, `${base}Node`), spec: pkg.specifier });
145
+ }
146
+ const callee = (id) => bindingOf.get(id)?.name || apiName(N[id].definitionId);
147
+
148
+ const bodyTextOf = (id) => String(
149
+ (N[id].definitionId === "tool_nodejs" && N[id].script) ? N[id].script : (N[id].body || ""),
150
+ );
151
+
152
+ /**
153
+ * 正文里的 `${slot}` 占位符能写回成 JS 插值的那些槽。
154
+ *
155
+ * 条件很紧:引用表达式的**根标识符**必须和槽名一致。`{ date: date.value }` + 正文
156
+ * `${date}` 可以折叠成 `` `${date.value}` ``;而 `{ d: date.value }` 不行——解析器只能
157
+ * 从 `${date.value}` 推出槽名 `date`,折叠了就往返不回来。
158
+ */
159
+ function bodyFolds(id) {
160
+ const text = bodyTextOf(id);
161
+ // 正文超阈值会被外置成文件,文件里没有 JS 插值这回事
162
+ if (!text || text.length >= EXTERNALIZE_MIN) return new Map();
163
+ const candidates = new Map();
164
+ for (const x of dataIn.get(id) || []) {
165
+ if (!text.includes(`\${${x.slot}}`)) continue;
166
+ const ref = outVar.get(`${x.from}|${x.fromSlot}`) || `${x.from}.${x.fromSlot}`;
167
+ if (ref.split(".")[0] !== x.slot) continue;
168
+ candidates.set(x.slot, ref);
169
+ }
170
+ if (!candidates.size) return candidates;
171
+
172
+ // 定义表里的槽永远按定义顺序重建,折不折都在原位;自定义槽不一样——折进正文之后,
173
+ // 解析回来是「按正文里出现的先后追加到末尾」。所以自定义槽只能折**末尾那一段**,
174
+ // 而且那段的正文顺序要和槽序一致。否则往返一次槽序就变了,闸门会把整张图退回 JSON。
175
+ const extras = N[id].extraIn;
176
+ const folds = new Map([...candidates].filter(([slot]) => !extras.includes(slot)));
177
+ const atBody = (slot) => text.indexOf(`\${${slot}}`);
178
+ for (let k = extras.length; k > 0; k -= 1) {
179
+ const tail = extras.slice(-k);
180
+ if (!tail.every((slot) => candidates.has(slot))) continue;
181
+ const byBody = [...tail].sort((a, b) => atBody(a) - atBody(b));
182
+ if (JSON.stringify(byBody) !== JSON.stringify(tail)) continue;
183
+ for (const slot of tail) folds.set(slot, candidates.get(slot));
184
+ break;
185
+ }
186
+ return folds;
187
+ }
188
+
189
+ const pkgSlots = (id, kind) => (opts.packages?.[id]?.[kind] || []);
190
+ const slotTypeOf = (id, name) => {
191
+ const node = N[id];
192
+ const fromPkg = pkgSlots(id, "input").find((s) => s?.name === name);
193
+ if (fromPkg?.type) return String(fromPkg.type);
194
+ const fromDef = definitionOf(node.definitionId).input.find((s) => s.name === name);
195
+ if (fromDef?.type) return String(fromDef.type);
196
+ return String(node.inputTypes?.[name] || "text");
197
+ };
198
+ /** bool 槽写回成裸 `true` / `false`;其余一律是文本。 */
199
+ const pinValue = (id, name, value) => {
200
+ const text = String(value);
201
+ if (slotTypeOf(id, name) === "bool" && (text === "true" || text === "false")) return text;
202
+ return textArg(id, name, text);
203
+ };
204
+
205
+ function pinsObject(id, folds = new Map()) {
206
+ const node = N[id];
207
+ const def = definitionOf(node.definitionId);
208
+ const lines = [];
209
+ const wired = new Map((dataIn.get(id) || []).filter((x) => !folds.has(x.slot)).map((x) => [x.slot, x]));
210
+ const order = [...def.input.map((s) => s.name).filter((n) => !CTRL_SLOTS.has(n)), ...node.extraIn];
211
+ const seen = new Set();
212
+ for (const name of order) {
213
+ if (seen.has(name)) continue;
214
+ seen.add(name);
215
+ // 已经折进正文插值里的槽不再出现在引脚对象里;写两遍等于同一条边写两次
216
+ if (folds.has(name)) continue;
217
+ const key = isIdentifier(name) ? name : JSON.stringify(name);
218
+ if (wired.has(name)) {
219
+ const x = wired.get(name);
220
+ lines.push(`${key}: ${outVar.get(`${x.from}|${x.fromSlot}`) || `${x.from}.${x.fromSlot}`}`);
221
+ continue;
222
+ }
223
+ if (node.inputs[name] !== undefined) {
224
+ lines.push(`${key}: ${pinValue(id, name, node.inputs[name])}`);
225
+ continue;
226
+ }
227
+ // 声明了但没接线也没默认值的自定义槽写成 null,否则解析回来会丢掉这个槽
228
+ if (node.extraIn.includes(name)) lines.push(`${key}: null`);
229
+ }
230
+ for (const [name, value] of Object.entries(node.inputs)) {
231
+ if (!seen.has(name) && !folds.has(name)) lines.push(`${name}: ${pinValue(id, name, value)}`);
232
+ }
233
+ if (isProvideDefinition(node.definitionId)) {
234
+ for (const [name, value] of Object.entries(node.outputs)) {
235
+ lines.push(`${name}: ${textArg(id, name, String(value))}`);
236
+ }
237
+ }
238
+ return lines.length ? `{\n${lines.map((l) => ` ${l}`).join(",\n")},\n}` : "{}";
239
+ }
240
+
241
+ // 把控制链展开成嵌套序列;分叉处变成数组,由 printItem 打成 flow.fork(...)
242
+ function expandChain(id) {
243
+ const kids = (controlNext.get(id) || [])
244
+ .filter((x) => !RUN_DEFINITIONS.has(N[x.to].definitionId))
245
+ .map((x) => x.to);
246
+ if (isIf(id) || !kids.length) return [id];
247
+ if (kids.length === 1) return [id, ...expandChain(kids[0])];
248
+ return [id, kids.map(expandChain)];
249
+ }
250
+ const collectIds = (seq, acc = []) => {
251
+ for (const item of seq) {
252
+ if (Array.isArray(item)) item.forEach((s) => collectIds(s, acc));
253
+ else acc.push(item);
254
+ }
255
+ return acc;
256
+ };
257
+ const printItem = (item) => (
258
+ Array.isArray(item)
259
+ ? `flow.fork(${item.map((s) => `flow(${s.map(printItem).join(", ")})`).join(", ")})`
260
+ : item
261
+ );
262
+
263
+ const declared = new Set();
264
+ const out = [];
265
+
266
+ function chainFrom(roots) {
267
+ const seq = roots.length === 1
268
+ ? expandChain(roots[0])
269
+ : (roots.length ? [roots.map(expandChain)] : []);
270
+ for (const id of collectIds(seq)) declare(id, false);
271
+ return seq;
272
+ }
273
+
274
+ function declare(id, exported) {
275
+ if (declared.has(id)) return;
276
+ declared.add(id);
277
+ // 上游数据依赖必须先声明,否则引用的变量还不存在
278
+ for (const dep of dataIn.get(id) || []) if (!declared.has(dep.from)) declare(dep.from, true);
279
+
280
+ const node = N[id];
281
+ const folds = isIf(id) ? new Map() : bodyFolds(id);
282
+ const args = [];
283
+ if (node.label) args.push(literal(node.label));
284
+ args.push(pinsObject(id, folds));
285
+
286
+ if (isIf(id)) {
287
+ const thenIds = (controlNext.get(id) || []).filter((x) => x.slot === "next1").map((x) => x.to);
288
+ const elseIds = (controlNext.get(id) || []).filter((x) => x.slot === "next2").map((x) => x.to);
289
+ args.push(`flow(${chainFrom(thenIds).map(printItem).join(", ")})`);
290
+ args.push(`flow(${chainFrom(elseIds).map(printItem).join(", ")})`);
291
+ } else {
292
+ const usesScript = node.definitionId === "tool_nodejs" && node.script;
293
+ const body = usesScript ? node.script : node.body;
294
+ if (body) {
295
+ args.push(folds.size
296
+ ? interpolatedLiteral(body, folds)
297
+ : textArg(id, usesScript ? "$script" : "$body", String(body)));
298
+ }
299
+ }
300
+
301
+ out.push(`${exported ? "export " : ""}const ${id} = ${callee(id)}(${args.join(", ")});\n`);
302
+ const needsBinding = destructured.get(id) || [];
303
+ if (needsBinding.length) {
304
+ const bindings = needsBinding.map((slot) => {
305
+ const v = outVar.get(`${id}|${slot}`);
306
+ if (v === slot) return slot;
307
+ return `${isIdentifier(slot) ? slot : JSON.stringify(slot)}: ${v}`;
308
+ });
309
+ out.push(`const { ${bindings.join(", ")} } = ${id};\n`);
310
+ }
311
+ }
312
+
313
+ const ids = Object.keys(N).sort();
314
+
315
+ for (const runId of ids) {
316
+ const definitionId = N[runId].definitionId;
317
+ if (!RUN_DEFINITIONS.has(definitionId)) continue;
318
+ const roots = (controlNext.get(runId) || [])
319
+ .filter((x) => !RUN_DEFINITIONS.has(N[x.to].definitionId))
320
+ .map((x) => x.to);
321
+ const seq = chainFrom(roots).map(printItem);
322
+ const head = [];
323
+ if (N[runId].label) head.push(literal(N[runId].label));
324
+ // 排程配置存在 run 节点的 body 里,是 JSON 字符串
325
+ if (definitionId === "workspace_scheduled_run") head.push(N[runId].body ? literal(N[runId].body) : "null");
326
+ const fn = definitionId === "workspace_scheduled_run" ? "flow.schedule" : "flow";
327
+ out.push(`export const ${runId} = ${fn}(${[...head, ...seq].join(", ")});\n`);
328
+ }
329
+
330
+ // 一个 run 直接连到另一个 run:接力,不是子图
331
+ for (const [src, list] of [...controlNext].sort()) {
332
+ for (const x of list) {
333
+ if (RUN_DEFINITIONS.has(N[x.to].definitionId)) out.push(`flow.resume(${src}, ${x.to});\n`);
334
+ }
335
+ }
336
+
337
+ // 没有 run 入口、但自成控制链的孤儿链条——语料里真的有,丢掉就等于删图
338
+ const controlTargets = new Set(ir.edges.map((e) => e.split("|")).filter((p) => p[3] === "prev").map((p) => p[2]));
339
+ for (const id of ids) {
340
+ if (declared.has(id) || RUN_DEFINITIONS.has(N[id].definitionId) || controlTargets.has(id)) continue;
341
+ if (!(controlNext.get(id) || []).length) continue;
342
+ out.push(`flow.detached(${chainFrom([id]).map(printItem).join(", ")});\n`);
343
+ }
344
+ for (const id of ids) {
345
+ if (!declared.has(id) && !RUN_DEFINITIONS.has(N[id].definitionId)) declare(id, true);
346
+ }
347
+
348
+ const imports = [`import { agent, control, display, file, flow, provide, tool, workspace } from "agentflow/flow";`];
349
+ const seenBinding = new Set();
350
+ for (const id of ids) {
351
+ const binding = bindingOf.get(id);
352
+ if (!binding || seenBinding.has(binding.name)) continue;
353
+ seenBinding.add(binding.name);
354
+ imports.push(`import ${binding.name} from ${JSON.stringify(binding.spec)};`);
355
+ }
356
+
357
+ return { source: `${imports.join("\n")}\n\n${out.join("\n")}`, files };
358
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * DSL 用的节点定义表与 API 名映射。
3
+ *
4
+ * 定义表来自 `builtin/nodes/*.md`——与节点面板同一个来源(见
5
+ * docs/wiki/node-definitions.zh-CN.md),因此 DSL 认识的节点集合与用户能拖出来的
6
+ * 节点集合天然一致,不存在第二份清单。
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+
11
+ import { PACKAGE_BUILTIN_NODES_DIR } from "../paths.mjs";
12
+ import { parseNodeFrontmatter } from "../catalog-flows.mjs";
13
+
14
+ /** 控制槽:连这些槽的边表示执行顺序,不是数据流。 */
15
+ export const CTRL_SLOTS = new Set(["prev", "next", "next1", "next2"]);
16
+
17
+ /**
18
+ * 标准槽:控制槽 + 运行时统一注入的上下文槽。
19
+ * 这些名字不算「自定义槽」,代码里不必声明。
20
+ */
21
+ export const STD_SLOTS = new Set([
22
+ ...CTRL_SLOTS,
23
+ "workspaceContext",
24
+ "skillsContext",
25
+ "mcpContext",
26
+ "knowledgeContext",
27
+ "gitContext",
28
+ ]);
29
+
30
+ /** 运行入口节点——DSL 里对应 `flow()` / `flow.schedule()`,不是普通节点调用。 */
31
+ export const RUN_DEFINITIONS = new Set(["workspace_run", "workspace_scheduled_run"]);
32
+
33
+ export const isDisplayDefinition = (id) => String(id || "").startsWith("display_");
34
+ export const isProvideDefinition = (id) => String(id || "").startsWith("provide_");
35
+
36
+ /** 槽是否是控制槽(按类型或按名字,两者都要认——语料里两种写法都有)。 */
37
+ export const isControlSlot = (slot) =>
38
+ String(slot?.type) === "node" || CTRL_SLOTS.has(String(slot?.name));
39
+
40
+ function loadDefinitions(dir) {
41
+ const out = {};
42
+ if (!fs.existsSync(dir)) return out;
43
+ for (const file of fs.readdirSync(dir)) {
44
+ if (!file.endsWith(".md")) continue;
45
+ const meta = parseNodeFrontmatter(fs.readFileSync(path.join(dir, file), "utf-8"));
46
+ out[file.slice(0, -3)] = { input: meta.input, output: meta.output, runtime: meta.runtime };
47
+ }
48
+ return out;
49
+ }
50
+
51
+ export const DEFINITIONS = loadDefinitions(PACKAGE_BUILTIN_NODES_DIR);
52
+
53
+ /**
54
+ * definitionId -> DSL 调用名。多数是 `前缀_驼峰` 的机械转换,少数缩写要显式列出,
55
+ * 否则 `control_cd_workspace` 会变成 `control.cdWorkspace` 的错拼。
56
+ */
57
+ const API_OVERRIDES = {
58
+ control_agent_toBool: "control.agentToBool",
59
+ control_cd_workspace: "control.cdWorkspace",
60
+ control_user_workspace: "control.userWorkspace",
61
+ control_load_skills: "control.loadSkills",
62
+ control_load_mcp: "control.loadMcp",
63
+ control_toBool: "control.toBool",
64
+ control_anyOne: "control.anyOne",
65
+ tool_nodejs: "tool.nodejs",
66
+ workspace_one_click_task: "workspace.oneClickTask",
67
+ };
68
+
69
+ function deriveApiName(definitionId) {
70
+ if (API_OVERRIDES[definitionId]) return API_OVERRIDES[definitionId];
71
+ const at = definitionId.indexOf("_");
72
+ if (at < 0) return definitionId;
73
+ const tail = definitionId.slice(at + 1).replace(/_([a-zA-Z])/g, (_, c) => c.toUpperCase());
74
+ return `${definitionId.slice(0, at)}.${tail}`;
75
+ }
76
+
77
+ const API_BY_DEFINITION = {};
78
+ const DEFINITION_BY_API = {};
79
+ const collisions = [];
80
+ for (const definitionId of Object.keys(DEFINITIONS)) {
81
+ const name = deriveApiName(definitionId);
82
+ if (DEFINITION_BY_API[name] && DEFINITION_BY_API[name] !== definitionId) {
83
+ collisions.push(`${name}: ${DEFINITION_BY_API[name]} vs ${definitionId}`);
84
+ }
85
+ API_BY_DEFINITION[definitionId] = name;
86
+ DEFINITION_BY_API[name] = definitionId;
87
+ }
88
+
89
+ /**
90
+ * definitionId <-> DSL 名必须是双射,否则解析回来会指向错误的节点类型。
91
+ * 新增节点定义撞名时在这里直接炸掉,而不是等到某张图被静默解析错。
92
+ */
93
+ export const API_NAME_COLLISIONS = collisions;
94
+ if (collisions.length) {
95
+ throw new Error(`DSL 节点调用名冲突:${collisions.join("; ")}`);
96
+ }
97
+
98
+ export const apiName = (definitionId) => API_BY_DEFINITION[definitionId] || deriveApiName(definitionId);
99
+ export const definitionIdFromApi = (name) => DEFINITION_BY_API[name] || String(name).replace(".", "_");
100
+
101
+ export function definitionOf(definitionId) {
102
+ return DEFINITIONS[definitionId] || { input: [], output: [], runtime: "native" };
103
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Workspace 图的代码化表示。
3
+ *
4
+ * 一张图落成四个文件:
5
+ *
6
+ * ```
7
+ * workspace.flow.js 图结构——节点、连线、作者写的内容(受限 ESM)
8
+ * workspace.layout.json 画布状态——坐标、尺寸、引脚显隐与顺序
9
+ * workspace.nodes.json 代码里说不清的节点级数据——粘贴的图片、机器管理属性
10
+ * workspace.state.json 运行态——由 workspace-state.mjs 负责,这里只是路过
11
+ * prompts/ docs/ scripts/ 超过 3 KB 的长文本
12
+ * ```
13
+ *
14
+ * 为什么是代码而不是 JSON:流程编排本质上就是「main 调用一串 func、连线传数据」。
15
+ * 写成代码,AI 的编码能力可以直接用上,lint 能做语法校验,diff 能看懂。而 JSON 里
16
+ * 一条 `output-1 -> input-2` 的边,人和模型都读不出它连的是什么。
17
+ *
18
+ * 全流程**不执行**流程文件:acorn 静态解析。
19
+ */
20
+ import { splitWorkspaceGraph } from "../workspace-state.mjs";
21
+ import { generateFlowSource } from "./codegen.mjs";
22
+ import { extractLayout } from "./layout.mjs";
23
+ import { graphToIr, irToGraph } from "./ir.mjs";
24
+ import { parseFlowSource } from "./parser.mjs";
25
+
26
+ export const FLOW_SOURCE_FILENAME = "workspace.flow.js";
27
+ export const FLOW_LAYOUT_FILENAME = "workspace.layout.json";
28
+ export const FLOW_NODES_FILENAME = "workspace.nodes.json";
29
+
30
+ export { EXTERNALIZE_MIN } from "./codegen.mjs";
31
+ export { DEFINITIONS, apiName, definitionIdFromApi } from "./defs.mjs";
32
+ export { graphToIr, irToGraph } from "./ir.mjs";
33
+ export { generateFlowSource } from "./codegen.mjs";
34
+ export { parseFlowSource } from "./parser.mjs";
35
+
36
+ /**
37
+ * 完整图 -> 代码化的一组文件。
38
+ *
39
+ * 运行态先由 `splitWorkspaceGraph` 摘出去,DSL 只面对设计态;这样代码里不会混进
40
+ * 上次运行的产出。
41
+ *
42
+ * @param {object} graph 完整图(design + state 合并后的形态)
43
+ * @param {{ packages?: Record<string, {binding?: string, specifier: string}> }} [opts]
44
+ * @returns {{ source: string, layout: object, nodeMeta: object, state: object, files: Array<{path: string, text: string}> }}
45
+ */
46
+ export function graphToFlowFiles(graph, opts = {}) {
47
+ const { design, state } = splitWorkspaceGraph(graph);
48
+ const ir = graphToIr(design);
49
+ const { source, files } = generateFlowSource(ir, opts);
50
+ const { layout, nodeMeta } = extractLayout(design, ir);
51
+ return { source, layout, nodeMeta, state, files };
52
+ }
53
+
54
+ /**
55
+ * 代码化的一组文件 -> 设计态图。
56
+ *
57
+ * 返回的是设计态;要拿到完整图,再用 `mergeWorkspaceState(design, state)` 合上运行态。
58
+ *
59
+ * 默认是**严格模式**:解析器认不出的语句会抛错,而不是当它不存在。这是作为存储格式的
60
+ * 底线——静默丢一条语句,等于用户下一次保存时那部分图就没了。只想「尽力读出能读的部分」
61
+ * 时(lint 要把问题一次列全)传 `strict: false`。
62
+ *
63
+ * @param {{ source: string, layout?: object, nodeMeta?: object, files?: Array<{path:string,text:string}>|Record<string,string>, resolvePackage?: Function, strict?: boolean }} input
64
+ * @returns {object} 设计态图
65
+ */
66
+ export function flowFilesToGraph(input) {
67
+ const files = Array.isArray(input.files)
68
+ ? Object.fromEntries(input.files.map((f) => [f.path, f.text]))
69
+ : (input.files || {});
70
+ const ir = parseFlowSource(input.source, { files, resolvePackage: input.resolvePackage });
71
+ if (input.strict !== false && ir.unresolved.length) {
72
+ const shown = ir.unresolved.slice(0, 5).map((u) => `第 ${u.line} 行:${u.message}`);
73
+ const more = ir.unresolved.length > shown.length ? `(还有 ${ir.unresolved.length - shown.length} 处)` : "";
74
+ throw new Error(`有 ${ir.unresolved.length} 处内容解析不出图结构${more}\n ${shown.join("\n ")}`);
75
+ }
76
+ return irToGraph(ir, input.layout || { nodes: {} }, input.nodeMeta || { nodes: {} });
77
+ }