@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
@@ -1,12 +1,24 @@
1
1
  /**
2
- * flow.yaml 文本或 zip 解压结果导入流水线目录(user / workspace)。
2
+ * 从上传的流程文件或 zip 解压结果导入流水线目录(user / workspace)。
3
+ *
4
+ * 「包的根在哪」这件事以前只认 `flow.yaml`——代码化的流程目录里没有它,于是从平台发布通道
5
+ * 传上来只会得到「压缩包内未找到 flow.yaml」。判据改成和磁盘上完全一致的那一套
6
+ * (`FLOW_MARKER_FILENAMES`:`workspace.flow.js` / `workspace.graph.json` / `flow.yaml`),
7
+ * 两种格式都收。
3
8
  */
4
9
  import fs from "fs";
5
10
  import path from "path";
6
11
  import yaml from "js-yaml";
7
12
  import { unzipSync } from "fflate";
13
+ import { flowFilesToGraph } from "./flow-dsl/index.mjs";
14
+ import { marketplaceDependenciesFromSource } from "./flow-dsl/packages.mjs";
8
15
  import { resolveFlowDirForWrite, validateUserPipelineId } from "./flow-write.mjs";
9
16
  import { normalizeFlowYamlText } from "./flow-normalize.mjs";
17
+ import { FLOW_MARKER_FILENAMES } from "./paths.mjs";
18
+
19
+ /** `flow.yml` 是 `flow.yaml` 的历史别名,落盘时统一改名。 */
20
+ const YAML_ALIAS = "flow.yml";
21
+ const MARKERS = new Set([...FLOW_MARKER_FILENAMES, YAML_ALIAS].map((n) => n.toLowerCase()));
10
22
 
11
23
  export const IMPORT_MAX_UNCOMPRESSED_BYTES = 8 * 1024 * 1024;
12
24
  export const IMPORT_MAX_FILE_ENTRIES = 500;
@@ -34,15 +46,19 @@ function shouldIgnoreZipPath(norm) {
34
46
  return parts.some((seg) => seg === "__MACOSX" || seg.startsWith("._"));
35
47
  }
36
48
 
37
- function flowYamlParentDir(normPath) {
49
+ /**
50
+ * 这个文件是不是流程目录的标记文件;是的话返回它所在的目录(根目录为 `""`)。
51
+ *
52
+ * 只认根目录或一层子目录——再深的层级不是「单个流程的包」。
53
+ *
54
+ * @returns {string | null} 不是标记文件时返回 null
55
+ */
56
+ function flowMarkerParentDir(normPath) {
38
57
  const lower = normPath.toLowerCase();
39
- if (lower === "flow.yaml" || lower === "flow.yml") return "";
40
- const suf = "/flow.yaml";
41
- const sufYml = "/flow.yml";
42
- let parent = null;
43
- if (lower.endsWith(suf)) parent = normPath.slice(0, -suf.length);
44
- else if (lower.endsWith(sufYml)) parent = normPath.slice(0, -sufYml.length);
45
- else return null;
58
+ if (MARKERS.has(lower)) return "";
59
+ const slash = lower.lastIndexOf("/");
60
+ if (slash < 0 || !MARKERS.has(lower.slice(slash + 1))) return null;
61
+ const parent = normPath.slice(0, slash);
46
62
  if (parent.includes("/")) return null;
47
63
  return parent;
48
64
  }
@@ -85,6 +101,54 @@ export function validateImportedFlowYaml(content) {
85
101
  }
86
102
  }
87
103
 
104
+ /**
105
+ * 单文件上传(不是 zip)时,判断这一份是什么、能不能收。
106
+ *
107
+ * 代码化的流程只上传 `workspace.flow.js` 时,布局和节点元数据都缺席——那不影响读图,
108
+ * 缺省会被补齐。但源码必须**当场解析得过**:写进去一个解析不出图的文件,用户下次打开画布
109
+ * 才发现,而那时已经离现场很远。
110
+ *
111
+ * @param {string} content
112
+ * @param {string} filename 上传时的文件名,用来决定按哪种格式解析
113
+ * @param {{ resolvePackage?: Function }} [opts]
114
+ * @returns {{ ok: true, entryName: string } | { ok: false, error: string }}
115
+ */
116
+ export function validateImportedFlowSource(content, filename = "", opts = {}) {
117
+ const name = String(filename || "").toLowerCase();
118
+ if (!name.endsWith(".js") && !name.endsWith(".mjs")) {
119
+ const checked = validateImportedFlowYaml(content);
120
+ return checked.ok ? { ok: true, entryName: "flow.yaml" } : checked;
121
+ }
122
+ if (Buffer.byteLength(String(content || ""), "utf8") > IMPORT_MAX_UNCOMPRESSED_BYTES) {
123
+ return { ok: false, error: "workspace.flow.js 过大" };
124
+ }
125
+ const dependencies = marketplaceDependenciesFromSource(content);
126
+ if (dependencies.errors.length) {
127
+ return { ok: false, error: dependencies.errors.join(";") };
128
+ }
129
+ if (typeof opts.resolvePackage === "function") {
130
+ const missing = dependencies.dependencies.filter((dependency) => !opts.resolvePackage(dependency.specifier));
131
+ if (missing.length) {
132
+ return {
133
+ ok: false,
134
+ error: `服务端缺少节点包:${missing.map((dependency) => dependency.specifier).join(", ")};请先上传这些精确版本`,
135
+ };
136
+ }
137
+ }
138
+ try {
139
+ flowFilesToGraph({
140
+ source: String(content || ""),
141
+ layout: {},
142
+ nodeMeta: {},
143
+ files: {},
144
+ resolvePackage: opts.resolvePackage,
145
+ });
146
+ return { ok: true, entryName: "workspace.flow.js" };
147
+ } catch (e) {
148
+ return { ok: false, error: `workspace.flow.js 解析失败:${(e && e.message) || e}` };
149
+ }
150
+ }
151
+
88
152
  /**
89
153
  * @param {Record<string, Uint8Array>} unzipped
90
154
  * @returns {{ ok: true, files: Map<string, Buffer> } | { ok: false, error: string }}
@@ -118,15 +182,15 @@ export function normalizeZipToPipelineFiles(unzipped) {
118
182
  /** @type {Set<string>} */
119
183
  const parents = new Set();
120
184
  for (const k of raw.keys()) {
121
- const p = flowYamlParentDir(k);
185
+ const p = flowMarkerParentDir(k);
122
186
  if (p !== null) parents.add(p);
123
187
  }
124
188
 
125
189
  if (parents.size === 0) {
126
- return { ok: false, error: "压缩包内未找到 flow.yaml" };
190
+ return { ok: false, error: `压缩包内未找到流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
127
191
  }
128
192
  if (parents.size > 1) {
129
- return { ok: false, error: "压缩包内存在多个 pipeline(多个 flow.yaml),请分别打包" };
193
+ return { ok: false, error: "压缩包内存在多个 pipeline,请分别打包" };
130
194
  }
131
195
 
132
196
  const [prefix] = [...parents];
@@ -136,7 +200,7 @@ export function normalizeZipToPipelineFiles(unzipped) {
136
200
  if (!need) {
137
201
  return {
138
202
  ok: false,
139
- error: "ZIP 目录结构无效:存在不属于该流水线目录的文件(请使用单文件夹或根目录 flow.yaml)",
203
+ error: "ZIP 目录结构无效:存在不属于该流水线目录的文件(请使用单文件夹,或把流程文件放在根目录)",
140
204
  };
141
205
  }
142
206
  }
@@ -155,22 +219,23 @@ export function normalizeZipToPipelineFiles(unzipped) {
155
219
  }
156
220
 
157
221
  const yamlKeys = [...out.keys()].filter(
158
- (k) => k.toLowerCase() === "flow.yaml" || k.toLowerCase() === "flow.yml",
222
+ (k) => k.toLowerCase() === "flow.yaml" || k.toLowerCase() === YAML_ALIAS,
159
223
  );
160
- if (yamlKeys.length === 0) {
161
- return { ok: false, error: "归一化后缺少 flow.yaml" };
162
- }
163
224
  if (yamlKeys.length > 1) {
164
225
  return { ok: false, error: "流水线目录内不能同时存在多个 flow.yaml / flow.yml" };
165
226
  }
166
-
167
227
  const yamlKey = yamlKeys[0];
168
- if (yamlKey !== "flow.yaml") {
228
+ if (yamlKey && yamlKey !== "flow.yaml") {
169
229
  const body = out.get(yamlKey);
170
230
  out.delete(yamlKey);
171
231
  out.set("flow.yaml", body);
172
232
  }
173
233
 
234
+ // 归一化会剥掉外层目录,标记文件可能因此换了位置——重新确认一次
235
+ if (![...out.keys()].some((k) => MARKERS.has(k.toLowerCase()))) {
236
+ return { ok: false, error: `归一化后缺少流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
237
+ }
238
+
174
239
  return { ok: true, files: out };
175
240
  }
176
241
 
@@ -214,7 +279,7 @@ export function suggestFlowIdFromZip(zipBuffer) {
214
279
  /** @type {Set<string>} */
215
280
  const parents = new Set();
216
281
  for (const k of raw.keys()) {
217
- const p = flowYamlParentDir(k);
282
+ const p = flowMarkerParentDir(k);
218
283
  if (p !== null) parents.add(p);
219
284
  }
220
285
 
@@ -270,12 +335,17 @@ export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelati
270
335
  return { success: false, error: "目标目录已存在" };
271
336
  }
272
337
 
338
+ // 有 yaml 就照旧校验并规范化;代码化的包没有 yaml,认标记文件即可
273
339
  const yamlBuf = filesRelative.get("flow.yaml");
274
- if (!yamlBuf) return { success: false, error: "缺少 flow.yaml" };
275
- const text = yamlBuf.toString("utf8");
276
- const v = validateImportedFlowYaml(text);
277
- if (!v.ok) return { success: false, error: v.error };
278
- const normalizedYaml = normalizeFlowYamlText(text).text;
340
+ let normalizedYaml = "";
341
+ if (yamlBuf) {
342
+ const text = yamlBuf.toString("utf8");
343
+ const v = validateImportedFlowYaml(text);
344
+ if (!v.ok) return { success: false, error: v.error };
345
+ normalizedYaml = normalizeFlowYamlText(text).text;
346
+ } else if (![...filesRelative.keys()].some((k) => MARKERS.has(k.toLowerCase()))) {
347
+ return { success: false, error: `缺少流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
348
+ }
279
349
 
280
350
  try {
281
351
  fs.mkdirSync(flowDir, { recursive: true });
@@ -290,7 +360,7 @@ export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelati
290
360
  }
291
361
  const parent = path.dirname(abs);
292
362
  fs.mkdirSync(parent, { recursive: true });
293
- const payload = safe === "flow.yaml" ? Buffer.from(normalizedYaml, "utf8") : buf;
363
+ const payload = safe === "flow.yaml" && normalizedYaml ? Buffer.from(normalizedYaml, "utf8") : buf;
294
364
  fs.writeFileSync(abs, payload);
295
365
  }
296
366
  return { success: true };
@@ -13,9 +13,11 @@ import {
13
13
  getUserPipelinesRoot,
14
14
  LEGACY_PIPELINES_DIR,
15
15
  PIPELINES_DIR,
16
+ isFlowDir,
16
17
  } from "./paths.mjs";
17
- import { getFlowYamlAbs } from "./catalog-flows.mjs";
18
+ import { resolveFlowDirAbs } from "./catalog-flows.mjs";
18
19
  import { normalizeFlowYamlText } from "./flow-normalize.mjs";
20
+ import { writeWorkspaceGraphFiles } from "./workspace-flow-store.mjs";
19
21
 
20
22
  export const FLOW_YAML_FILENAME = "flow.yaml";
21
23
 
@@ -83,7 +85,7 @@ function resolveExistingWorkspaceFlowDir(workspaceRoot, flowId) {
83
85
  const root = path.resolve(workspaceRoot);
84
86
  for (const rel of [PIPELINES_DIR, LEGACY_PIPELINES_DIR]) {
85
87
  const d = path.join(root, rel, flowId);
86
- if (fs.existsSync(path.join(d, FLOW_YAML_FILENAME))) return d;
88
+ if (isFlowDir(d)) return d;
87
89
  }
88
90
  return null;
89
91
  }
@@ -186,6 +188,40 @@ export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource
186
188
  return { flowDir };
187
189
  }
188
190
 
191
+ /**
192
+ * 新建一个空流程:目录 + 空的 `workspace.flow.js` + 承载说明的 `workspace.layout.json`。
193
+ *
194
+ * 以前新建流程写的是一个空 `flow.yaml`——那个格式的执行栈早就退休了,写它纯粹是因为
195
+ * 目录识别和列表说明当时只认它。两件事现在都不依赖 yaml 了(见 paths.mjs 的 `isFlowDir`
196
+ * 和 catalog-flows 的 `readPipelineListDescription`),所以新流程生下来就是代码。
197
+ *
198
+ * @param {string} workspaceRoot
199
+ * @param {string} flowId
200
+ * @param {FlowWriteSource} flowSource
201
+ * @param {{ description?: string, userId?: string }} [opts]
202
+ * @returns {{ success: true, flowDir: string } | { success: false, error: string }}
203
+ */
204
+ export function createEmptyFlow(workspaceRoot, flowId, flowSource, opts = {}) {
205
+ const { flowDir, error } = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
206
+ if (error) return { success: false, error };
207
+ const description = String(opts.description || "").trim();
208
+ try {
209
+ fs.mkdirSync(flowDir, { recursive: true });
210
+ const result = writeWorkspaceGraphFiles(flowDir, {
211
+ version: 1,
212
+ instances: {},
213
+ edges: [],
214
+ ui: { nodePositions: {}, ...(description ? { description } : {}) },
215
+ });
216
+ if (result.format !== "dsl") {
217
+ return { success: false, error: `新建流程没能写成代码:${result.degradedReason || "未知原因"}` };
218
+ }
219
+ return { success: true, flowDir };
220
+ } catch (e) {
221
+ return { success: false, error: (e && e.message) || String(e) };
222
+ }
223
+ }
224
+
189
225
  /**
190
226
  * @param {string} workspaceRoot
191
227
  * @param {string} flowId
@@ -221,11 +257,11 @@ export function archiveFlowPipeline(workspaceRoot, flowId, flowSource, opts = {}
221
257
  if (flowSource !== "user" && flowSource !== "workspace") {
222
258
  return { success: false, error: "仅支持用户目录或工作区流水线归档" };
223
259
  }
224
- const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived: false, userId: opts.userId });
225
- if (yamlRes.error || !yamlRes.path) {
226
- return { success: false, error: yamlRes.error || "找不到流水线" };
260
+ const dirRes = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, { archived: false, userId: opts.userId });
261
+ if (dirRes.error || !dirRes.dir) {
262
+ return { success: false, error: dirRes.error || "找不到流水线" };
227
263
  }
228
- const fromDir = path.dirname(yamlRes.path);
264
+ const fromDir = dirRes.dir;
229
265
  const sep = path.sep;
230
266
  if (fromDir.split(sep).includes(ARCHIVED_PIPELINES_DIR_NAME)) {
231
267
  return { success: false, error: "该流水线已在归档目录中" };
@@ -266,7 +302,7 @@ export function restoreArchivedFlowPipeline(workspaceRoot, flowId, flowSource, o
266
302
  return { success: false, error: archivedRes.error || "无法解析归档路径" };
267
303
  }
268
304
  const fromDir = archivedRes.flowDir;
269
- if (!fs.existsSync(path.join(fromDir, FLOW_YAML_FILENAME))) {
305
+ if (!isFlowDir(fromDir)) {
270
306
  return { success: false, error: "找不到归档流水线" };
271
307
  }
272
308
  const activeRes = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
@@ -317,7 +353,7 @@ export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource, o
317
353
  const toRes = resolveFlowDirForWrite(workspaceRoot, flowId, toSource, opts);
318
354
  if (toRes.error || !toRes.flowDir) return { success: false, error: toRes.error || "invalid target path" };
319
355
  const toDir = toRes.flowDir;
320
- if (!fs.existsSync(path.join(fromDir, FLOW_YAML_FILENAME))) {
356
+ if (!isFlowDir(fromDir)) {
321
357
  return { success: false, error: "source flow not found" };
322
358
  }
323
359
  if (fs.existsSync(toDir)) {
@@ -417,11 +453,11 @@ export function deleteFlowPipeline(workspaceRoot, flowId, flowSource, opts = {})
417
453
  return { success: false, error: "invalid flowId" };
418
454
  }
419
455
  const archived = Boolean(opts.archived);
420
- const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived, userId: opts.userId });
421
- if (yamlRes.error || !yamlRes.path) {
422
- return { success: false, error: yamlRes.error || "找不到流水线" };
456
+ const dirRes = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, { archived, userId: opts.userId });
457
+ if (dirRes.error || !dirRes.dir) {
458
+ return { success: false, error: dirRes.error || "找不到流水线" };
423
459
  }
424
- const flowDir = path.dirname(yamlRes.path);
460
+ const flowDir = dirRes.dir;
425
461
  const guard = assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId, opts);
426
462
  if (!guard.ok) return { success: false, error: guard.error };
427
463
  try {
package/bin/lib/help.mjs CHANGED
@@ -11,30 +11,20 @@ export function printHelp() {
11
11
  AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code / Codex CLI 流式输出驱动 apply/replay。
12
12
 
13
13
  用法:
14
- agentflow login [--provider github|google] 登录 AgentFlow Hub(默认 GitHub)
15
- agentflow logout 退出 Hub 登录
16
- agentflow publish <FlowName> [--title <t>] [--description <d>] [--tags <t1,t2>] 发布流程到 Hub
17
- agentflow list-remote [--search <q>] [--sort popular|trending] [--json] 浏览 Hub 上的流程
18
- agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] 从 Hub 下载流程(默认 --user 安装到 ~/agentflow/pipelines/<id>;--workspace 安装到当前工程 .workspace/agentflow/pipelines/<id>;--raw 仅保留压缩包)
19
14
  agentflow list 列出所有流水线
20
- agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
21
- agentflow flow preview <FlowName|flow.yaml> [--output <preview.html>] [--no-open] 生成平台同款画布的单文件静态 Flow 预览
15
+ agentflow ui [--host <addr>] [--port <n>] [--no-open] [--hide-community-links] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
16
+ agentflow flow dsl <export|import|lint|migrate> <FlowName|dir> [--out <dir>] 流程图与 workspace.flow.js 代码互转 / 静态校验 / 就地迁移
22
17
  agentflow mcp 启动 AgentFlow MCP stdio server,供 Cursor/Codex 运行流程并读取 display 输出
23
- agentflow scheduler start [--poll-ms <ms>] 启动定时执行调度器(读取各流水线 schedule.json)
24
- agentflow scheduler status [--json] 查看定时执行配置与状态
25
- agentflow scheduler cancel <FlowName> <uuid> 取消某次等待中的 watch/run
26
18
  agentflow marketplace list [--json] 查看 workspace 本地节点市场
27
19
  agentflow marketplace publish-node <dir> 发布本地节点包到 workspace market
28
- agentflow marketplace install-node <FlowName> <nodeSpec> 将 market 节点依赖写入 flow
29
- agentflow apply <FlowName> [uuid] 或 agentflow apply <uuid>(由 uuid 反查 pipeline)
20
+ agentflow apply <FlowName> [uuid] 已下线;请改用 Workspace Run
30
21
  agentflow validate <FlowName> [uuid] 校验流程;终端下输出易读结果,--json 或管道时输出 JSON;传 uuid 时写入 runDir/intermediate/validation.json
31
- agentflow resume <FlowName> <uuid> [instanceId] pending 与 failed 节点标为已确认并继续 apply
32
- agentflow replay [flowName] <uuid> <instanceId>
22
+ agentflow resume <FlowName> <uuid> [instanceId] 已下线;请改用 Workspace Run
23
+ agentflow replay [flowName] <uuid> <instanceId> 已下线;请改用 Workspace Run
33
24
  agentflow run-status <flowName> <uuid> 输出该次运行的节点状态 JSON(供 UI 展示 success/pending 等角标)
34
25
  agentflow extract-thinking <flowName> <uuid> 从该次 run 的 logs/log.txt 提取 thinking,写入 logs/thinking_by_session_and_nodes.md
35
26
  agentflow extract-thinking -list 列出所有存在 logs/log.txt 的 run(可接 --json)
36
27
  agentflow update-model-lists 拉取 Cursor / OpenCode / Claude Code / Codex 模型列表并写入 ~/agentflow/model-lists.json
37
- agentflow write-flow <flowId> --json --flow-source <user|workspace> 从 stdin 读入 YAML 写入用户目录或工作区(builtin 已弃用,将视为 workspace)
38
28
  agentflow --help
39
29
 
40
30
  选项:
@@ -82,30 +72,20 @@ Apply/replay 脚本已打包在 agentflow 包中(bin/pipeline/)。
82
72
  AgentFlow CLI — drive apply/replay with Cursor / OpenCode / Claude Code / Codex CLI streaming.
83
73
 
84
74
  Usage:
85
- agentflow login [--provider github|google] Login to AgentFlow Hub (default: GitHub)
86
- agentflow logout Sign out of Hub
87
- agentflow publish <FlowName> [--title <t>] [--description <d>] [--tags <t1,t2>] Publish flow to Hub
88
- agentflow list-remote [--search <q>] [--sort popular|trending] [--json] Browse flows on Hub
89
- agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] Download flow (default --user → ~/agentflow/pipelines/<id>; --workspace → current project's .workspace/agentflow/pipelines/<id>; --raw keeps the archive)
90
75
  agentflow list List all pipelines
91
- agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
92
- agentflow flow preview <FlowName|flow.yaml> [--output <preview.html>] [--no-open] Generate a single-file static Flow preview using the platform canvas
76
+ agentflow ui [--host <addr>] [--port <n>] [--no-open] [--hide-community-links] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
77
+ agentflow flow dsl <export|import|lint|migrate> <FlowName|dir> [--out <dir>] Convert a graph to/from workspace.flow.js, lint it, or migrate in place
93
78
  agentflow mcp Start the AgentFlow MCP stdio server for Cursor/Codex to run flows and read display outputs
94
- agentflow scheduler start [--poll-ms <ms>] Start the scheduled-run scheduler (reads each pipeline schedule.json)
95
- agentflow scheduler status [--json] Show scheduled-run configuration and state
96
- agentflow scheduler cancel <FlowName> <uuid> Cancel a waiting watch/run
97
79
  agentflow marketplace list [--json] Show workspace local marketplace packages
98
80
  agentflow marketplace publish-node <dir> Publish a local node package to the workspace market
99
- agentflow marketplace install-node <FlowName> <nodeSpec> Add a marketplace node dependency to a flow
100
- agentflow apply <FlowName> [uuid] Or agentflow apply <uuid> (resolve pipeline from uuid)
81
+ agentflow apply <FlowName> [uuid] Retired; use Workspace Run instead
101
82
  agentflow validate <FlowName> [uuid] Validate flow; readable output in terminal, JSON with --json or pipe; writes to runDir/intermediate/validation.json when uuid provided
102
- agentflow resume <FlowName> <uuid> [instanceId] Mark pending and failed nodes as acknowledged and continue apply
103
- agentflow replay [flowName] <uuid> <instanceId>
83
+ agentflow resume <FlowName> <uuid> [instanceId] Retired; use Workspace Run instead
84
+ agentflow replay [flowName] <uuid> <instanceId> Retired; use Workspace Run instead
104
85
  agentflow run-status <flowName> <uuid> Output node status JSON for this run (for UI success/pending badges)
105
86
  agentflow extract-thinking <flowName> <uuid> Extract thinking from run logs/log.txt, write to logs/thinking_by_session_and_nodes.md
106
87
  agentflow extract-thinking -list List all runs with logs/log.txt (use --json)
107
88
  agentflow update-model-lists Fetch Cursor / OpenCode / Claude Code / Codex model lists to ~/agentflow/model-lists.json
108
- agentflow write-flow <flowId> --json --flow-source <user|workspace> Read YAML from stdin and write to user dir or workspace (builtin deprecated, treated as workspace)
109
89
  agentflow --help
110
90
 
111
91
  Options:
@@ -0,0 +1,19 @@
1
+ /**
2
+ * HTML 转义。
3
+ *
4
+ * 单独成模块是为了让 PRD workflow 那套服务端渲染不必反向依赖 ui-server——它和
5
+ * `injectHtmlBaseHref` 是仅有的两个使用者,各自 import 一份比互相牵扯干净。
6
+ */
7
+
8
+ /**
9
+ * 转义成可以放进属性值的文本。
10
+ * @param {unknown} value
11
+ * @returns {string}
12
+ */
13
+ export function htmlEscapeAttribute(value) {
14
+ return String(value || "")
15
+ .replace(/&/g, "&amp;")
16
+ .replace(/"/g, "&quot;")
17
+ .replace(/</g, "&lt;")
18
+ .replace(/>/g, "&gt;");
19
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 两个最基础的 HTTP 工具。
3
+ *
4
+ * 独立成模块只有一个原因:路由拆分出去之后,`json(res, ...)` / `readBody(req)` 在两个文件
5
+ * 里都要以**同名标识符**出现——搬走的路由体一行都不改,靠的就是这个。
6
+ */
7
+
8
+ /**
9
+ * @param {import('http').ServerResponse} res
10
+ * @param {number} status
11
+ * @param {unknown} obj
12
+ */
13
+ export function json(res, status, obj) {
14
+ const body = JSON.stringify(obj);
15
+ res.writeHead(status, {
16
+ "Content-Type": "application/json; charset=utf-8",
17
+ "Content-Length": Buffer.byteLength(body),
18
+ });
19
+ res.end(body);
20
+ }
21
+
22
+ export function readBody(req, maxBytes = 5 * 1024 * 1024) {
23
+ return new Promise((resolve, reject) => {
24
+ const chunks = [];
25
+ let total = 0;
26
+ let exceeded = false;
27
+ req.on("data", (chunk) => {
28
+ if (exceeded) return;
29
+ const value = Buffer.from(chunk);
30
+ total += value.length;
31
+ if (total > maxBytes) {
32
+ exceeded = true;
33
+ chunks.length = 0;
34
+ return;
35
+ }
36
+ chunks.push(value);
37
+ });
38
+ req.on("end", () => {
39
+ if (exceeded) {
40
+ const error = new Error(`Request body exceeds ${maxBytes} bytes`);
41
+ error.status = 413;
42
+ reject(error);
43
+ return;
44
+ }
45
+ resolve(Buffer.concat(chunks).toString("utf8"));
46
+ });
47
+ req.on("error", reject);
48
+ });
49
+ }
@@ -0,0 +1,43 @@
1
+ export const LEGACY_FLOW_EXECUTION_DISABLED = true;
2
+
3
+ export const LEGACY_FLOW_EXECUTION_MESSAGE =
4
+ "Legacy Start/End Pipeline execution has been retired. Use the Workspace graph with Run or Scheduled Run.";
5
+
6
+ export const LEGACY_FLOW_NODE_IDS = new Set(["control_start", "control_end"]);
7
+
8
+ /**
9
+ * 只有旧 Start/End 运行时实现、Workspace 运行时没有对应 handler 的节点类型。
10
+ *
11
+ * 这些定义留在 `builtin/nodes/` 只为让历史 flow.yaml / workspace.graph.json 仍能解析出
12
+ * 节点元数据;节点面板不再提供它们,避免用户新建出跑不了的图(拖出来只会落到通用
13
+ * agent 路径,拿不到文档承诺的语义)。
14
+ *
15
+ * 想恢复某个类型,就在 ui-server 的 workspace 运行循环里补上 handler,再从这里删掉。
16
+ */
17
+ export const WORKSPACE_UNSUPPORTED_NODE_IDS = new Set([
18
+ // 循环 / 汇合:Workspace 运行计划是 DAG,有环会被直接拒绝
19
+ "control_anyOne",
20
+ "control_toBool",
21
+ "control_agent_toBool",
22
+ "control_interval_loop",
23
+ // 时序与取消
24
+ "control_delay",
25
+ "control_wait_until",
26
+ "control_cancelled",
27
+ // 人工卡点
28
+ "tool_user_check",
29
+ "tool_user_ask",
30
+ // 运行期键值 / 环境变量
31
+ "tool_load_key",
32
+ "tool_save_key",
33
+ "tool_get_env",
34
+ // 其它
35
+ "tool_print",
36
+ "tool_jenkins_build",
37
+ ]);
38
+
39
+ /** 节点面板与目录需要隐藏的全部 definitionId */
40
+ export const RETIRED_NODE_IDS = new Set([
41
+ ...LEGACY_FLOW_NODE_IDS,
42
+ ...WORKSPACE_UNSUPPORTED_NODE_IDS,
43
+ ]);
@@ -106,7 +106,7 @@
106
106
  "type_text": "Text",
107
107
  "pipeline_header": "Pipeline",
108
108
  "source_header": "Source",
109
- "apply_example_header": "Apply Example"
109
+ "open_example_header": "Validate Command"
110
110
  },
111
111
  "composer": {
112
112
  "edit_context": "## AgentFlow Edit Context",
@@ -106,7 +106,7 @@
106
106
  "type_text": "文本",
107
107
  "pipeline_header": "流水线",
108
108
  "source_header": "来源",
109
- "apply_example_header": "Apply 示例"
109
+ "open_example_header": "校验命令"
110
110
  },
111
111
  "composer": {
112
112
  "edit_context": "## AgentFlow 编辑上下文",