@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,331 @@
1
+ /**
2
+ * Workspace 图的设计态 / 运行态分离。
3
+ *
4
+ * `workspace.graph.json` 原本一个文件装三样东西:图结构、运行产出、画布布局。真实语料
5
+ * 里运行产出占 33.6%(21 个流程 1.38 MB 中的 465 KB),每跑一次就变一次——既污染 diff,
6
+ * 也让 AI 生成的图和跑过的图无法比对。
7
+ *
8
+ * 这里把运行态抽到 `workspace.state.json`:
9
+ *
10
+ * ```
11
+ * workspace.graph.json 设计态:节点、连线、位置、作者写的内容
12
+ * workspace.state.json 运行态:输出槽产出、展示节点的运行内容、视口
13
+ * ```
14
+ *
15
+ * **这是纯存储层的改动。** `readWorkspaceGraph` 合并两个文件后返回的图与拆分前逐字节
16
+ * 相同,上层(含 designRevision 的计算)看不到任何区别。
17
+ *
18
+ * 哪些算运行态,与 `workspace-graph-merge.mjs` 的 `isRuntimePath` 保持同一套判断:
19
+ *
20
+ * - 非 provide 节点的 `output[*].value` / `.default`——provide 节点的输出值是用户填的
21
+ * - **接了非语义入边**的 `input[*].value` / `.default`——每次运行都会被上游覆写
22
+ * - `displayReloadKey`
23
+ * - `runFingerprint`——节点上次成功执行时的输入指纹,缓存命中判据,见 `workspace-server.mjs`
24
+ * - `ui.viewport`
25
+ * - **有内容入边**的展示节点的 `body`
26
+ *
27
+ * 后两条需要看图结构才能判断。展示节点没有内容入边时,`body` 是作者手写的文档(语料里
28
+ * 有 29 个这样的节点、64 KB),当成运行态外移就等于删掉它们;判断规则与 ui-server 运行
29
+ * 循环里的 `workspaceContentInputEdge` 完全一致。入槽同理:没有入边的槽,值是作者填的
30
+ * 默认值;有入边的槽,值只是上一次跑进来的东西。
31
+ */
32
+
33
+ export const WORKSPACE_STATE_FILENAME = "workspace.state.json";
34
+
35
+ const STATE_VERSION = 1;
36
+
37
+ function isPlainObject(value) {
38
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
39
+ }
40
+
41
+ function isProvideDefinition(definitionId) {
42
+ return String(definitionId || "").startsWith("provide_");
43
+ }
44
+
45
+ function isDisplayDefinition(definitionId) {
46
+ return String(definitionId || "").startsWith("display_");
47
+ }
48
+
49
+ /**
50
+ * 语义入槽——连到这些槽上的边不代表「内容由上游驱动」。
51
+ * 与 ui-server 的 `isWorkspaceSemanticInputSlot` 逐字一致;改一处必须改两处。
52
+ */
53
+ function isSemanticInputSlot(slot) {
54
+ const name = String(slot?.name || "");
55
+ const type = String(slot?.type || "");
56
+ return type === "node"
57
+ || name === "prev" || name === "next"
58
+ || CONTEXT_SLOT_NAMES.has(name);
59
+ }
60
+
61
+ /**
62
+ * 上下文注入槽。这些槽的值一律由运行时灌入——skills 正文、MCP 清单、工作区摘要——
63
+ * 有没有入边都一样。语料里见过 15 KB 的 HTML 正文被复制进 `workspaceContext`,
64
+ * 那显然不是作者手填的默认值。
65
+ */
66
+ const CONTEXT_SLOT_NAMES = new Set([
67
+ "skillsContext",
68
+ "mcpContext",
69
+ "knowledgeContext",
70
+ "workspaceContext",
71
+ "gitContext",
72
+ ]);
73
+
74
+ function handleIndex(handle, prefix) {
75
+ const match = String(handle || "").match(new RegExp(`^${prefix}-(\\d+)$`));
76
+ return match ? Number(match[1]) : 0;
77
+ }
78
+
79
+ /**
80
+ * 展示节点是否由上游内容驱动。复刻 ui-server 的 `workspaceContentInputEdge`:
81
+ * 收集入边,去掉指向语义槽的,剩下任意一条就说明 body 是运行产出。
82
+ *
83
+ * 不能简单地按 `targetHandle !== "input-0"` 近似——语料里有 32 个实例的槽位顺序不规范,
84
+ * input-0 未必是 prev;语义槽也可能出现在更靠后的位置。
85
+ */
86
+ function displayNodeIdsDrivenByEdges(graph) {
87
+ const out = new Set();
88
+ for (const [nodeId, slots] of edgeDrivenInputSlots(graph)) {
89
+ const definitionId = graph?.instances?.[nodeId]?.definitionId;
90
+ if (isDisplayDefinition(definitionId) && slots.size) out.add(nodeId);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /**
96
+ * 每个节点上「值由上游驱动」的入槽名。
97
+ *
98
+ * 语义槽(prev / skillsContext / …)不算:连到它们的边传的是控制流或上下文,不会覆写
99
+ * 槽里的值。其余槽只要有入边,运行时就会用上游的值覆盖——留在设计态里等于每跑一次
100
+ * 就把设计文件改一次。
101
+ *
102
+ * @returns {Map<string, Set<string>>}
103
+ */
104
+ function edgeDrivenInputSlots(graph) {
105
+ const out = new Map();
106
+ const instances = isPlainObject(graph?.instances) ? graph.instances : {};
107
+ for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
108
+ const targetId = String(edge?.target || "");
109
+ const target = instances[targetId];
110
+ if (!targetId || !isPlainObject(target)) continue;
111
+ const input = Array.isArray(target.input) ? target.input : [];
112
+ const slot = input[handleIndex(edge?.targetHandle, "input")] || null;
113
+ if (!slot || isSemanticInputSlot(slot)) continue;
114
+ const name = String(slot.name ?? "");
115
+ if (!name) continue;
116
+ if (!out.has(targetId)) out.set(targetId, new Set());
117
+ out.get(targetId).add(name);
118
+ }
119
+ return out;
120
+ }
121
+
122
+ /** 槽名在实例内唯一时才按名字外移;重名就整个数组放弃外移,宁可留在设计里也不丢值。 */
123
+ function slotNamesAreUnique(slots) {
124
+ const names = (Array.isArray(slots) ? slots : []).map((s) => String(s?.name ?? ""));
125
+ return new Set(names).size === names.length;
126
+ }
127
+
128
+ /**
129
+ * 把一组槽里的 value/default 摘出来。
130
+ * @returns {{ slots: any[], state: Record<string, {value?: any, default?: any}> }}
131
+ */
132
+ function extractSlotValues(slots, shouldExtract) {
133
+ const state = {};
134
+ const next = slots.map((slot) => {
135
+ if (!isPlainObject(slot)) return slot;
136
+ const name = String(slot.name ?? "");
137
+ if (!shouldExtract(name, slot)) return slot;
138
+ const entry = {};
139
+ if (slot.value !== undefined) entry.value = slot.value;
140
+ if (slot.default !== undefined) entry.default = slot.default;
141
+ if (!Object.keys(entry).length) return slot;
142
+ state[name] = entry;
143
+ const clean = { ...slot };
144
+ delete clean.value;
145
+ delete clean.default;
146
+ return clean;
147
+ });
148
+ return { slots: next, state };
149
+ }
150
+
151
+ function restoreSlotValues(slots, state) {
152
+ if (!isPlainObject(state) || !Array.isArray(slots)) return slots;
153
+ return slots.map((slot) => {
154
+ if (!isPlainObject(slot)) return slot;
155
+ const entry = state[String(slot.name ?? "")];
156
+ if (!isPlainObject(entry)) return slot;
157
+ const next = { ...slot };
158
+ if (entry.value !== undefined) next.value = entry.value;
159
+ if (entry.default !== undefined) next.default = entry.default;
160
+ return next;
161
+ });
162
+ }
163
+
164
+ /**
165
+ * 一张图里「哪些位置是运行态」的完整答案,供存储拆分、三路合并、revision 计算共用。
166
+ *
167
+ * 这三处必须用同一份判断,否则会得到很难查的错觉:存储层把某个值当运行产出移走了,
168
+ * revision 却仍然把它算进设计——于是跑一次流程,所有协作者手里的 designRevision 全部
169
+ * 作废,明明没人动过图。
170
+ *
171
+ * @param {object} graph
172
+ * @returns {{ inputs: Map<string, Set<string>>, displayBodies: Set<string>, isProvide: (nodeId: string) => boolean }}
173
+ */
174
+ export function workspaceRuntimeSurface(graph) {
175
+ const instances = isPlainObject(graph?.instances) ? graph.instances : {};
176
+ const driven = edgeDrivenInputSlots(graph);
177
+ const inputs = new Map();
178
+ for (const [nodeId, instance] of Object.entries(instances)) {
179
+ const names = new Set(driven.get(nodeId) || []);
180
+ for (const slot of Array.isArray(instance?.input) ? instance.input : []) {
181
+ const name = String(slot?.name ?? "");
182
+ if (name && CONTEXT_SLOT_NAMES.has(name)) names.add(name);
183
+ }
184
+ if (names.size) inputs.set(nodeId, names);
185
+ }
186
+ return {
187
+ inputs,
188
+ displayBodies: displayNodeIdsDrivenByEdges(graph),
189
+ isProvide: (nodeId) => isProvideDefinition(instances[nodeId]?.definitionId),
190
+ };
191
+ }
192
+
193
+ /**
194
+ * 把一张完整的图拆成设计态和运行态。
195
+ * @param {object} graph
196
+ * @returns {{ design: object, state: object }}
197
+ */
198
+ export function splitWorkspaceGraph(graph) {
199
+ const source = isPlainObject(graph) ? graph : {};
200
+ const instances = isPlainObject(source.instances) ? source.instances : {};
201
+ const displayDriven = displayNodeIdsDrivenByEdges(source);
202
+ const drivenInputs = edgeDrivenInputSlots(source);
203
+
204
+ const inputs = {};
205
+ const outputs = {};
206
+ const displayBodies = {};
207
+ const displayReloadKeys = {};
208
+ const fingerprints = {};
209
+ const designInstances = {};
210
+
211
+ for (const [nodeId, raw] of Object.entries(instances)) {
212
+ if (!isPlainObject(raw)) {
213
+ designInstances[nodeId] = raw;
214
+ continue;
215
+ }
216
+ const instance = { ...raw };
217
+
218
+ if (instance.displayReloadKey !== undefined) {
219
+ displayReloadKeys[nodeId] = instance.displayReloadKey;
220
+ delete instance.displayReloadKey;
221
+ }
222
+
223
+ if (instance.runFingerprint !== undefined) {
224
+ fingerprints[nodeId] = instance.runFingerprint;
225
+ delete instance.runFingerprint;
226
+ }
227
+
228
+ if (isDisplayDefinition(instance.definitionId) && displayDriven.has(nodeId) && instance.body !== undefined) {
229
+ displayBodies[nodeId] = instance.body;
230
+ delete instance.body;
231
+ }
232
+
233
+ const driven = drivenInputs.get(nodeId) || new Set();
234
+ if (Array.isArray(instance.input) && slotNamesAreUnique(instance.input)) {
235
+ const extracted = extractSlotValues(
236
+ instance.input,
237
+ (name) => driven.has(name) || CONTEXT_SLOT_NAMES.has(name),
238
+ );
239
+ instance.input = extracted.slots;
240
+ if (Object.keys(extracted.state).length) inputs[nodeId] = extracted.state;
241
+ }
242
+
243
+ if (!isProvideDefinition(instance.definitionId)
244
+ && Array.isArray(instance.output)
245
+ && slotNamesAreUnique(instance.output)) {
246
+ const extracted = extractSlotValues(instance.output, () => true);
247
+ instance.output = extracted.slots;
248
+ if (Object.keys(extracted.state).length) outputs[nodeId] = extracted.state;
249
+ }
250
+
251
+ designInstances[nodeId] = instance;
252
+ }
253
+
254
+ const design = { ...source, instances: designInstances };
255
+ if (isPlainObject(source.ui)) {
256
+ const ui = { ...source.ui };
257
+ delete ui.viewport;
258
+ design.ui = ui;
259
+ }
260
+
261
+ const state = { version: STATE_VERSION };
262
+ if (Object.keys(inputs).length) state.inputs = inputs;
263
+ if (Object.keys(outputs).length) state.outputs = outputs;
264
+ if (Object.keys(displayBodies).length) state.displayBodies = displayBodies;
265
+ if (Object.keys(displayReloadKeys).length) state.displayReloadKeys = displayReloadKeys;
266
+ if (Object.keys(fingerprints).length) state.fingerprints = fingerprints;
267
+ if (isPlainObject(source.ui) && source.ui.viewport !== undefined) state.viewport = source.ui.viewport;
268
+
269
+ return { design, state };
270
+ }
271
+
272
+ /**
273
+ * 把运行态合回设计态。`state` 为空时原样返回设计态,因此对尚未拆分的旧图是恒等操作。
274
+ * @param {object} design
275
+ * @param {object|null} state
276
+ * @returns {object}
277
+ */
278
+ export function mergeWorkspaceState(design, state) {
279
+ const base = isPlainObject(design) ? design : {};
280
+ if (!isPlainObject(state)) return base;
281
+
282
+ const inputs = isPlainObject(state.inputs) ? state.inputs : {};
283
+ const outputs = isPlainObject(state.outputs) ? state.outputs : {};
284
+ const displayBodies = isPlainObject(state.displayBodies) ? state.displayBodies : {};
285
+ const displayReloadKeys = isPlainObject(state.displayReloadKeys) ? state.displayReloadKeys : {};
286
+ const fingerprints = isPlainObject(state.fingerprints) ? state.fingerprints : {};
287
+ const instances = isPlainObject(base.instances) ? base.instances : {};
288
+
289
+ const merged = {};
290
+ for (const [nodeId, raw] of Object.entries(instances)) {
291
+ if (!isPlainObject(raw)) {
292
+ merged[nodeId] = raw;
293
+ continue;
294
+ }
295
+ const instance = { ...raw };
296
+
297
+ if (Object.prototype.hasOwnProperty.call(displayBodies, nodeId)) {
298
+ instance.body = displayBodies[nodeId];
299
+ }
300
+ if (Object.prototype.hasOwnProperty.call(displayReloadKeys, nodeId)) {
301
+ instance.displayReloadKey = displayReloadKeys[nodeId];
302
+ }
303
+ if (Object.prototype.hasOwnProperty.call(fingerprints, nodeId)) {
304
+ instance.runFingerprint = fingerprints[nodeId];
305
+ }
306
+
307
+ // 只在原来就有这个数组时回填——直接赋值会给没有 input/output 的实例凭空加上
308
+ // `input: undefined`,合并前后就不再逐字节相同了
309
+ if (Array.isArray(instance.input)) instance.input = restoreSlotValues(instance.input, inputs[nodeId]);
310
+ if (Array.isArray(instance.output)) instance.output = restoreSlotValues(instance.output, outputs[nodeId]);
311
+
312
+ merged[nodeId] = instance;
313
+ }
314
+
315
+ const out = { ...base, instances: merged };
316
+ if (state.viewport !== undefined) {
317
+ out.ui = { ...(isPlainObject(base.ui) ? base.ui : {}), viewport: state.viewport };
318
+ }
319
+ return out;
320
+ }
321
+
322
+ /** 运行态里没有任何内容时为 true——此时不必落盘 state 文件。 */
323
+ export function isEmptyWorkspaceState(state) {
324
+ if (!isPlainObject(state)) return true;
325
+ return !state.inputs
326
+ && !state.outputs
327
+ && !state.displayBodies
328
+ && !state.displayReloadKeys
329
+ && !state.fingerprints
330
+ && state.viewport === undefined;
331
+ }
@@ -11,6 +11,7 @@ import {
11
11
  getUserPipelinesRoot,
12
12
  resolveUniqueUserPipelineDir,
13
13
  ARCHIVED_PIPELINES_DIR_NAME,
14
+ isFlowDir,
14
15
  } from "./paths.mjs";
15
16
 
16
17
  export { getRunDir } from "./paths.mjs";
@@ -144,7 +145,7 @@ export function listRunsWithLogs(workspaceRoot) {
144
145
  /** 解析活跃 flow 目录:user → workspace → legacy workspace → builtin。归档 flow 必须走显式 archived resolver。 */
145
146
  export function getFlowDir(workspaceRoot, flowName, opts = {}) {
146
147
  const root = path.resolve(workspaceRoot);
147
- const hasFlow = (dir) => fs.existsSync(dir) && fs.existsSync(path.join(dir, "flow.yaml"));
148
+ const hasFlow = (dir) => isFlowDir(dir);
148
149
 
149
150
  // user pipelines
150
151
  const userRoot = getUserPipelinesRoot(opts.userId);
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:子 Agent
3
+ runtime: native
3
4
  description: 利用子 Agent 执行任务;可接收 knowledgeContext 读取知识库,可接收 workspaceContext 切换执行工作区,并接收 skillsContext / mcpContext 注入已加载 skills 与 MCP 工具清单。
4
5
  displayName: 子 Agent
5
6
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:AI 转布尔(agent 执行)
3
+ runtime: degraded
4
+ palette: hidden
3
5
  description: "AI-powered boolean judgment: an agent evaluates the input value and writes true/false to prediction. Use for non-deterministic scenarios requiring semantic understanding."
4
6
  displayName: Agent ToBool
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:任一满足
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Continues to next when any upstream input is ready
4
6
  displayName: AnyOne
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:取消判断
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Check whether the current wait/run has been cancelled. Use cancelled output with control_if.
4
6
  displayName: Cancel Check
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:加载知识库
3
+ runtime: native
4
+ type: control
3
5
  description: |
4
6
  Load one or more read-only knowledge sources for downstream Agent nodes.
5
7
 
@@ -11,20 +13,16 @@ input:
11
13
  - type: node
12
14
  name: prev
13
15
  default: ""
14
- - type: text
15
- name: knowledgeContext
16
- default: ""
17
- showOnNode: false
18
16
  - type: text
19
17
  name: path
20
18
  default: ""
21
19
  showOnNode: false
22
20
  - type: text
23
- name: mode
24
- default: "set"
21
+ name: label
22
+ default: ""
25
23
  showOnNode: false
26
24
  - type: text
27
- name: label
25
+ name: knowledgeContext
28
26
  default: ""
29
27
  showOnNode: false
30
28
  - type: text
@@ -48,9 +46,5 @@ output:
48
46
  name: cwd
49
47
  default: ""
50
48
  showOnNode: false
51
- - type: text
52
- name: previous
53
- default: ""
54
- showOnNode: false
55
49
  ---
56
50
  Load selected knowledge sources for downstream agents.
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:延迟等待
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Persistently wait for a relative duration, then continue when scheduler resumes this run.
4
6
  displayName: Delay
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:结束
3
+ runtime: none
4
+ palette: hidden
3
5
  description: End point of AgentFlow, flow terminates after this node
4
6
  displayName: End
5
7
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:If 分支
3
+ runtime: native
3
4
  description: Has exactly one bool type input. Continues to next1 if true, next2 if false
4
5
  displayName: If
5
6
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:间隔循环
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Wait by interval and branch to continue, done, timeout, or cancelled for watch-style flows.
4
6
  displayName: IntervalLoop
5
7
  input:
@@ -0,0 +1,24 @@
1
+ ---
2
+ # 内置节点:Load MCP
3
+ runtime: native
4
+ type: control
5
+ description: 加载所选 Cursor MCP Server 的工具清单,通过 mcpContext 传给下游 agent 节点。
6
+ displayName: Load MCP
7
+ input:
8
+ - type: node
9
+ name: prev
10
+ default: ""
11
+ - type: text
12
+ name: serverNames
13
+ default: ""
14
+ showOnNode: false
15
+ output:
16
+ - type: node
17
+ name: next
18
+ default: ""
19
+ - type: text
20
+ name: mcpContext
21
+ default: ""
22
+ showOnNode: true
23
+ ---
24
+ Load selected Cursor MCP server tool manifests for downstream agent nodes.
@@ -1,19 +1,18 @@
1
1
  ---
2
2
  # 内置节点:Load Skills
3
+ runtime: native
4
+ type: control
3
5
  description: |
4
- Load selected public Skills from the AgentFlow skill registry into the current workspace context.
6
+ Load the currently selected Workspace skill collection for downstream agent nodes.
5
7
 
6
- Connect `workspaceContext` from CD Workspace, set `skillKeys` to skill names or registry keys, then connect `skillsContext` to downstream agent/tool nodes. Loaded skills are injected into the node prompt under "已加载 Skills" while downstream execution still uses the CD Workspace context.
8
+ Set `skillKeys` to skill names or registry keys, then connect `skillsContext` to
9
+ downstream agent/tool nodes. Loaded skills are injected into the node prompt under
10
+ "已加载 Skills"。
7
11
 
8
12
  Skill key examples:
9
13
  - `agentflow-flow-add-instances`
10
14
  - `workspace-agents:agentflow-flow-edit-node-fields`
11
15
  - `global-codex:some-skill`
12
-
13
- Merge modes:
14
- - `replace`
15
- - `append`
16
- - `prepend`
17
16
  displayName: Load Skills
18
17
  input:
19
18
  - type: node
@@ -24,15 +23,6 @@ input:
24
23
  default: ""
25
24
  required: true
26
25
  showOnNode: true
27
- - type: text
28
- name: mergeMode
29
- default: "replace"
30
- - type: text
31
- name: workspaceContext
32
- default: ""
33
- - type: text
34
- name: skillsContext
35
- default: ""
36
26
  output:
37
27
  - type: node
38
28
  name: next
@@ -40,11 +30,6 @@ output:
40
30
  - type: text
41
31
  name: skillsContext
42
32
  default: ""
43
- - type: text
44
- name: loadedCount
45
- default: ""
46
- - type: text
47
- name: summary
48
- default: ""
33
+ showOnNode: true
49
34
  ---
50
- Load public skills `${skillKeys}` into `${workspaceContext}` and pass them to downstream nodes.
35
+ Load public skills `${skillKeys}` and pass them to downstream nodes.
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:开始
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Entry point of AgentFlow, all flows should start from this node
4
6
  displayName: Start
5
7
  input: []
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:代码转布尔(本地脚本执行,★ 可扩展输入)
3
+ runtime: none
4
+ palette: hidden
3
5
  description: "Script-based boolean conversion: executes script to produce true/false prediction. Like tool_nodejs but enforces bool output. Must have script field."
4
6
  displayName: Code ToBool
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # Built-in node: User Workspace
3
+ runtime: native
4
+ palette: hidden
3
5
  description: Output a workspace context pointing to the current user's home directory.
4
6
  displayName: User Workspace
5
7
  input:
@@ -1,5 +1,7 @@
1
1
  ---
2
2
  # 内置节点:等待到指定时间
3
+ runtime: none
4
+ palette: hidden
3
5
  description: Persistently wait until an absolute time, then continue when scheduler resumes this run.
4
6
  displayName: WaitUntil
5
7
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:ASCII 图展示
3
+ runtime: native
3
4
  description: Display ASCII diagram content in workspace canvas; passes diagram text downstream as text
4
5
  displayName: ASCII Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: Chart Display
3
+ runtime: native
3
4
  description: Display a JSON ChartSpec with ECharts in workspace canvas; passes the JSON downstream as text
4
5
  displayName: Chart Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: HTML Display
3
+ runtime: native
3
4
  description: Display HTML content in workspace canvas; passes HTML downstream as text
4
5
  displayName: HTML Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: Image Display
3
+ runtime: native
3
4
  description: Display an image URL, data URL, or image path in workspace canvas; passes source downstream as text
4
5
  displayName: Image Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:Markdown 展示
3
+ runtime: native
3
4
  description: Display Markdown content in workspace canvas; passes content downstream as text
4
5
  displayName: Markdown Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:Mermaid 展示
3
+ runtime: native
3
4
  description: Display Mermaid diagram source in workspace canvas; passes diagram source downstream as text
4
5
  displayName: Mermaid Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: React App Display
3
+ runtime: native
3
4
  description: Display a small React project in a sandboxed workspace iframe and pass the project JSON downstream
4
5
  displayName: React App
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: Table Display
3
+ runtime: native
3
4
  description: Display table data in workspace canvas; accepts JSON, Markdown table, CSV, or TSV and passes the text downstream
4
5
  displayName: Table Display
5
6
  input:
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:直接提供布尔值
3
+ runtime: native
3
4
  description: Provide a boolean value directly, value will be passed to downstream as true or false
4
5
  displayName: Boolean
5
6
  input: []
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:直接提供文件
3
+ runtime: native
3
4
  description: Provide file path or content directly, value will be passed to downstream as-is
4
5
  displayName: File
5
6
  input: []
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Built-in node: directly provide a hidden password/secret value
3
+ runtime: native
3
4
  description: Provide a secret text value without showing it on the node card
4
5
  displayName: Password
5
6
  input: []
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # 内置节点:直接提供文本
3
+ runtime: native
3
4
  description: Provide a text value directly, value will be passed to downstream as-is
4
5
  displayName: String
5
6
  input: []