@fieldwangai/agentflow 0.1.153 → 0.1.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +277 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1092 -18123
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-preview.mjs +12 -2
  57. package/bin/lib/workspace-routes.mjs +3088 -0
  58. package/bin/lib/workspace-server.mjs +6199 -0
  59. package/bin/lib/workspace-state.mjs +331 -0
  60. package/bin/lib/workspace.mjs +2 -1
  61. package/builtin/nodes/agent_subAgent.md +1 -0
  62. package/builtin/nodes/control_agent_toBool.md +2 -0
  63. package/builtin/nodes/control_anyOne.md +2 -0
  64. package/builtin/nodes/control_cancelled.md +2 -0
  65. package/builtin/nodes/control_cd_workspace.md +5 -11
  66. package/builtin/nodes/control_delay.md +2 -0
  67. package/builtin/nodes/control_end.md +2 -0
  68. package/builtin/nodes/control_if.md +1 -0
  69. package/builtin/nodes/control_interval_loop.md +2 -0
  70. package/builtin/nodes/control_load_mcp.md +24 -0
  71. package/builtin/nodes/control_load_skills.md +8 -23
  72. package/builtin/nodes/control_start.md +2 -0
  73. package/builtin/nodes/control_toBool.md +2 -0
  74. package/builtin/nodes/control_user_workspace.md +2 -0
  75. package/builtin/nodes/control_wait_until.md +2 -0
  76. package/builtin/nodes/display_ascii.md +1 -0
  77. package/builtin/nodes/display_chart.md +1 -0
  78. package/builtin/nodes/display_html.md +1 -0
  79. package/builtin/nodes/display_image.md +1 -0
  80. package/builtin/nodes/display_markdown.md +1 -0
  81. package/builtin/nodes/display_mermaid.md +1 -0
  82. package/builtin/nodes/display_react_app.md +1 -0
  83. package/builtin/nodes/display_table.md +1 -0
  84. package/builtin/nodes/provide_bool.md +1 -0
  85. package/builtin/nodes/provide_file.md +1 -0
  86. package/builtin/nodes/provide_password.md +1 -0
  87. package/builtin/nodes/provide_str.md +1 -0
  88. package/builtin/nodes/tool_display_share_link.md +1 -0
  89. package/builtin/nodes/tool_get_env.md +2 -0
  90. package/builtin/nodes/tool_git_checkout.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  92. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  93. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  94. package/builtin/nodes/tool_jenkins_build.md +2 -0
  95. package/builtin/nodes/tool_load_key.md +2 -0
  96. package/builtin/nodes/tool_nodejs.md +17 -19
  97. package/builtin/nodes/tool_print.md +2 -0
  98. package/builtin/nodes/tool_save_key.md +2 -0
  99. package/builtin/nodes/tool_set_run_env.md +1 -0
  100. package/builtin/nodes/tool_user_ask.md +2 -0
  101. package/builtin/nodes/tool_user_check.md +2 -0
  102. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  103. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  104. package/builtin/nodes/workspace_one_click_task.md +44 -0
  105. package/builtin/nodes/workspace_run.md +16 -0
  106. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  107. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  108. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  109. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  110. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  111. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  112. package/builtin/pipelines/new/workspace.flow.js +91 -0
  113. package/builtin/pipelines/new/workspace.layout.json +70 -0
  114. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
  116. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  117. package/builtin/web-ui/dist/index.html +2 -2
  118. package/package.json +3 -1
  119. package/reference/flow-control-capabilities.md +77 -158
  120. package/reference/flow-layout.md +1 -1
  121. package/reference/flow-prompt-handler-check.md +2 -2
  122. package/skills/agentflow-author-flow/SKILL.md +1 -1
  123. package/skills/agentflow-cli/SKILL.md +139 -24
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  128. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  129. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  130. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  131. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  135. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  136. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  137. package/skills/agentflow-node-reference/SKILL.md +2 -2
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  140. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  141. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  142. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  143. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  144. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  145. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  146. package/bin/lib/api-runner.mjs +0 -387
  147. package/bin/lib/apply.mjs +0 -903
  148. package/bin/lib/composer-flow-instances.mjs +0 -68
  149. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  150. package/bin/lib/composer-flow-validate.mjs +0 -47
  151. package/bin/lib/composer-model-router.mjs +0 -185
  152. package/bin/lib/composer-node-schema.mjs +0 -303
  153. package/bin/lib/composer-planner.mjs +0 -751
  154. package/bin/lib/composer-script-ops.mjs +0 -233
  155. package/bin/lib/flow-static-preview.mjs +0 -104
  156. package/bin/lib/hub-login.mjs +0 -54
  157. package/bin/lib/hub-publish.mjs +0 -159
  158. package/bin/lib/hub-remote.mjs +0 -189
  159. package/bin/lib/hub.mjs +0 -299
  160. package/bin/lib/jenkins.mjs +0 -380
  161. package/bin/lib/node-execute.mjs +0 -539
  162. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  163. package/bin/lib/runtime-context.mjs +0 -243
  164. package/bin/lib/scheduler.mjs +0 -601
  165. package/bin/lib/ui-print.mjs +0 -94
  166. package/bin/pipeline/build-node-prompt.mjs +0 -271
  167. package/bin/pipeline/check-cache.mjs +0 -191
  168. package/bin/pipeline/check-flow.mjs +0 -543
  169. package/bin/pipeline/collect-nodes.mjs +0 -212
  170. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  171. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  172. package/bin/pipeline/gc.mjs +0 -129
  173. package/bin/pipeline/get-env.mjs +0 -59
  174. package/bin/pipeline/get-resolved-values.mjs +0 -344
  175. package/bin/pipeline/load-key.mjs +0 -62
  176. package/bin/pipeline/parse-flow.mjs +0 -708
  177. package/bin/pipeline/post-process-control-if.mjs +0 -23
  178. package/bin/pipeline/post-process-node.mjs +0 -490
  179. package/bin/pipeline/pre-process-node.mjs +0 -1430
  180. package/bin/pipeline/resolve-inputs.mjs +0 -201
  181. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  182. package/bin/pipeline/save-key.mjs +0 -93
  183. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  184. package/bin/pipeline/validate-for-ui.mjs +0 -234
  185. package/bin/pipeline/validate-script-output.mjs +0 -130
  186. package/bin/pipeline/write-result.mjs +0 -182
  187. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  188. package/builtin/pipelines/new/flow.yaml +0 -545
  189. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  190. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  191. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  192. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  193. package/skills/agentflow-node-authoring/SKILL.md +0 -57
package/bin/lib/auth.mjs CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  getAgentflowDataRoot,
7
7
  getUserPipelinesRoot,
8
8
  sanitizeAgentflowUserId,
9
+ isFlowDir,
9
10
  } from "./paths.mjs";
10
11
 
11
12
  const SESSION_COOKIE = "af_session";
@@ -229,7 +230,7 @@ function listFlowDirs(root) {
229
230
  return fs.readdirSync(root, { withFileTypes: true })
230
231
  .filter((entry) => entry.isDirectory())
231
232
  .filter((entry) => entry.name !== ARCHIVED_PIPELINES_DIR_NAME)
232
- .filter((entry) => fs.existsSync(path.join(root, entry.name, "flow.yaml")))
233
+ .filter((entry) => isFlowDir(path.join(root, entry.name)))
233
234
  .map((entry) => entry.name)
234
235
  .sort((a, b) => a.localeCompare(b));
235
236
  } catch {
@@ -13,6 +13,7 @@ import {
13
13
  getUserAgentsDirAbs,
14
14
  getUserAgentsJsonAbs,
15
15
  getUserPipelinesRoot,
16
+ isFlowDir,
16
17
  } from "./paths.mjs";
17
18
  import { Table } from "./table.mjs";
18
19
 
@@ -283,7 +284,7 @@ export function copyBuiltinJson(workspaceRoot, flowId, targetFlowId, opts = {})
283
284
  const srcDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
284
285
  const pipelinesRoot = getUserPipelinesRoot(opts.userId);
285
286
  const destDir = path.join(pipelinesRoot, destId);
286
- if (!fs.existsSync(srcDir) || !fs.existsSync(path.join(srcDir, "flow.yaml"))) {
287
+ if (!fs.existsSync(srcDir) || !isFlowDir(srcDir)) {
287
288
  return { success: false, error: t("catalog.builtin_flow_not_found") };
288
289
  }
289
290
  const existing = collectPipelineNamesFromDir(pipelinesRoot);
@@ -14,15 +14,21 @@ import {
14
14
  PROJECT_NODES_DIR,
15
15
  USER_AGENTFLOW_PIPELINES_LABEL,
16
16
  getUserPipelinesRoot,
17
+ isFlowDir,
17
18
  } from "./paths.mjs";
18
19
  import {
19
20
  readAdminBuiltinPipelineConfig,
20
21
  resolveAdminBuiltinPipelineDir,
21
22
  } from "./admin-builtin-pipelines.mjs";
22
23
  import { Table } from "./table.mjs";
23
- import { listMarketplaceNodes, parseMarketplaceDefinitionId, resolveMarketplaceNodePackage } from "./marketplace.mjs";
24
+ import {
25
+ listMarketplaceNodes,
26
+ listNodePackagesInDir,
27
+ parseMarketplaceDefinitionId,
28
+ resolveMarketplaceNodePackage,
29
+ } from "./marketplace.mjs";
24
30
  import { isWorkspacePreviewDir } from "./workspace-preview.mjs";
25
- import { LEGACY_FLOW_NODE_IDS } from "./legacy-flow-execution.mjs";
31
+ import { RETIRED_NODE_IDS } from "./legacy-flow-execution.mjs";
26
32
 
27
33
  /** 从指定目录收集含 flow.yaml 的子目录名。 */
28
34
  export function collectPipelineNamesFromDir(dirPath) {
@@ -30,28 +36,45 @@ export function collectPipelineNamesFromDir(dirPath) {
30
36
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
31
37
  return entries
32
38
  .filter((e) => e.isDirectory())
33
- .filter((e) => fs.existsSync(path.join(dirPath, e.name, "flow.yaml")))
39
+ .filter((e) => isFlowDir(path.join(dirPath, e.name)))
34
40
  .filter((e) => !isWorkspacePreviewDir(path.join(dirPath, e.name)))
35
41
  .map((e) => e.name);
36
42
  }
37
43
 
44
+ const trimmedOrUndefined = (value) => {
45
+ if (typeof value !== "string") return undefined;
46
+ const t = value.trim();
47
+ return t === "" ? undefined : t;
48
+ };
49
+
38
50
  /**
39
- * 读取 flow.yaml 中流水线级说明(与 Web UI serialize 一致:ui.description)。
40
- * @param {string} flowDir 含 flow.yaml 的目录
51
+ * 流水线级说明(列表里那一行)。
52
+ *
53
+ * 两个来源,代码化的图优先:`ui.description` 在往返时由 `extractLayout` 透传进
54
+ * `workspace.layout.json` 的顶层,所以代码化的流程说明在 layout.json 里;只有 flow.yaml
55
+ * 的老流程还从 yaml 的 `ui.description` 读。
56
+ *
57
+ * @param {string} flowDir
41
58
  * @returns {string | undefined}
42
59
  */
43
60
  export function readPipelineListDescription(flowDir) {
61
+ const layoutPath = path.join(flowDir, "workspace.layout.json");
62
+ if (fs.existsSync(layoutPath)) {
63
+ try {
64
+ const layout = JSON.parse(fs.readFileSync(layoutPath, "utf-8"));
65
+ const fromLayout = trimmedOrUndefined(layout?.description);
66
+ if (fromLayout) return fromLayout;
67
+ } catch {
68
+ /* layout 读不动就退回 yaml */
69
+ }
70
+ }
44
71
  const yamlPath = path.join(flowDir, "flow.yaml");
45
72
  if (!fs.existsSync(yamlPath)) return undefined;
46
73
  try {
47
- const raw = fs.readFileSync(yamlPath, "utf-8");
48
- const data = yaml.load(raw);
74
+ const data = yaml.load(fs.readFileSync(yamlPath, "utf-8"));
49
75
  if (!data || typeof data !== "object") return undefined;
50
76
  const ui = data.ui && typeof data.ui === "object" ? data.ui : {};
51
- const d = ui.description;
52
- if (typeof d !== "string") return undefined;
53
- const t = d.trim();
54
- return t === "" ? undefined : t;
77
+ return trimmedOrUndefined(ui.description);
55
78
  } catch {
56
79
  return undefined;
57
80
  }
@@ -167,6 +190,22 @@ function normalizeFrontmatterSlots(arr) {
167
190
  });
168
191
  }
169
192
 
193
+ /** Workspace 运行时对该节点类型的支持程度;未声明按 native 处理(兼容旧的自定义节点 .md)。 */
194
+ const NODE_RUNTIME_TIERS = new Set(["native", "degraded", "none"]);
195
+
196
+ function normalizeNodeRuntimeTier(value) {
197
+ const tier = String(value ?? "").trim().toLowerCase();
198
+ return NODE_RUNTIME_TIERS.has(tier) ? tier : "native";
199
+ }
200
+
201
+ /** 节点分类;未声明时由 id 前缀推断。 */
202
+ const NODE_CATEGORY_TYPES = new Set(["control", "provide", "agent"]);
203
+
204
+ function normalizeNodeCategory(value) {
205
+ const type = String(value ?? "").trim().toLowerCase();
206
+ return NODE_CATEGORY_TYPES.has(type) ? type : "";
207
+ }
208
+
170
209
  /**
171
210
  * 解析 .md 节点文件的 frontmatter。
172
211
  * 优先用 js-yaml 解析整块 frontmatter,以支持 description: | / >- 等多行字段;
@@ -174,7 +213,16 @@ function normalizeFrontmatterSlots(arr) {
174
213
  */
175
214
  export function parseNodeFrontmatter(raw) {
176
215
  const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
177
- const data = { input: [], output: [], displayName: undefined, description: undefined, guide: undefined };
216
+ const data = {
217
+ input: [],
218
+ output: [],
219
+ displayName: undefined,
220
+ description: undefined,
221
+ guide: undefined,
222
+ runtime: "native",
223
+ type: "",
224
+ paletteHidden: false,
225
+ };
178
226
  if (!m) return data;
179
227
  const fm = m[1];
180
228
  try {
@@ -189,6 +237,9 @@ export function parseNodeFrontmatter(raw) {
189
237
  if (parsed.guide && typeof parsed.guide === "object" && !Array.isArray(parsed.guide)) {
190
238
  data.guide = parsed.guide;
191
239
  }
240
+ data.runtime = normalizeNodeRuntimeTier(parsed.runtime);
241
+ data.type = normalizeNodeCategory(parsed.type);
242
+ data.paletteHidden = String(parsed.palette ?? "").trim().toLowerCase() === "hidden";
192
243
  data.input = normalizeFrontmatterSlots(parsed.input);
193
244
  data.output = normalizeFrontmatterSlots(parsed.output);
194
245
  return data;
@@ -225,6 +276,9 @@ export function parseNodeFrontmatter(raw) {
225
276
  const displayM = fm.match(/\bdisplayName:\s*["']?([^"'\n#][^\n]*)["']?/);
226
277
  if (descM) data.description = descM[1].trim().replace(/^["']|["']$/g, "");
227
278
  if (displayM) data.displayName = displayM[1].trim().replace(/^["']|["']$/g, "");
279
+ data.runtime = normalizeNodeRuntimeTier((fm.match(/^\s*runtime:\s*(\S+)/m) || [])[1]);
280
+ data.type = normalizeNodeCategory((fm.match(/^\s*type:\s*(\S+)/m) || [])[1]);
281
+ data.paletteHidden = /^\s*palette:\s*hidden\s*$/m.test(fm);
228
282
  return data;
229
283
  }
230
284
 
@@ -232,7 +286,7 @@ export function parseNodeFrontmatter(raw) {
232
286
  * @param {string} workspaceRoot
233
287
  * @param {string} flowId
234
288
  * @param {string} flowSource
235
- * @param {{ archived?: boolean, staticFlowPath?: string }} [opts]
289
+ * @param {{ archived?: boolean }} [opts]
236
290
  */
237
291
  export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
238
292
  const root = path.resolve(workspaceRoot);
@@ -241,14 +295,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
241
295
  const byId = new Map();
242
296
  const pipelineTranslations = {};
243
297
  let marketplaceFlowData = null;
244
- const staticFlowPath = opts.staticFlowPath ? path.resolve(String(opts.staticFlowPath)) : "";
245
- const staticFlowDir = staticFlowPath ? path.dirname(staticFlowPath) : "";
246
- if (staticFlowPath && fs.existsSync(staticFlowPath) && fs.statSync(staticFlowPath).isFile()) {
247
- try {
248
- const parsed = yaml.load(fs.readFileSync(staticFlowPath, "utf-8"));
249
- if (parsed && typeof parsed === "object") marketplaceFlowData = parsed;
250
- } catch (_) {}
251
- } else if (flowId && flowSource) {
298
+ if (flowId && flowSource) {
252
299
  const flowPath = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
253
300
  if (flowPath.path && fs.existsSync(flowPath.path)) {
254
301
  try {
@@ -262,7 +309,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
262
309
  const files = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md"));
263
310
  for (const e of files) {
264
311
  const id = e.name.replace(/\.mdx?$/i, "").replace(/\.markdown$/i, "");
265
- if (LEGACY_FLOW_NODE_IDS.has(id)) continue;
312
+ if (RETIRED_NODE_IDS.has(id)) continue;
266
313
  let type = "agent";
267
314
  if (/^control/i.test(id)) type = "control";
268
315
  else if (/^provide/i.test(id)) type = "provide";
@@ -270,6 +317,10 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
270
317
  try {
271
318
  const raw = fs.readFileSync(path.join(dir, e.name), "utf-8");
272
319
  const data = parseNodeFrontmatter(raw);
320
+ // frontmatter 的 type: 优先于按 id 前缀的推断(workspace_run 等不带前缀的节点靠它归类)
321
+ if (data.type) type = data.type;
322
+ // frontmatter 的 palette: hidden 与 RETIRED_NODE_IDS 等价,让节点自带可见性
323
+ if (data.paletteHidden) continue;
273
324
  const strippedId =
274
325
  id.replace(/^agent_?/i, "").replace(/^control_?/i, "").replace(/^provide_?/i, "").replace(/^tool_?/i, "") || id;
275
326
  const label = data.displayName ?? strippedId;
@@ -287,6 +338,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
287
338
  byId.set(id, {
288
339
  id,
289
340
  type,
341
+ runtimeTier: data.runtime,
290
342
  label: translatedDisplayName || label,
291
343
  displayName: translatedDisplayName || data.displayName,
292
344
  description: translatedDescription || data.description,
@@ -298,6 +350,29 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
298
350
  });
299
351
  } catch (_) {}
300
352
  }
353
+ // 同一个 nodes/ 目录里,子目录形式的代码节点包(index.mjs 声明 + 实现)也进目录。
354
+ // 这样 AI 可以把节点实现直接写在流程目录里,不必先发布到 marketplace。
355
+ for (const manifest of listNodePackagesInDir(dir)) {
356
+ byId.set(manifest.definitionId, {
357
+ id: manifest.definitionId,
358
+ baseDefinitionId: manifest.baseDefinitionId || manifest.runtime?.type || "",
359
+ marketplaceDefinitionId: manifest.definitionId,
360
+ packageId: manifest.id,
361
+ version: manifest.version,
362
+ type: "agent",
363
+ runtimeTier: "native",
364
+ label: manifest.displayName,
365
+ displayName: manifest.displayName,
366
+ description: manifest.description,
367
+ inputs: manifest.input,
368
+ outputs: manifest.output,
369
+ source: flowIdOpt ? "flow" : "project",
370
+ flowId: flowIdOpt,
371
+ packageDir: manifest.packageDir,
372
+ // 与 marketplace 分支同名:前端 scriptFromMarketplaceRuntime 读的就是这个对象
373
+ runtime: manifest.runtime,
374
+ });
375
+ }
301
376
  };
302
377
  addFromDir(PACKAGE_BUILTIN_NODES_DIR, "project");
303
378
  addFromDir(path.join(root, LEGACY_NODES_DIR), "project");
@@ -326,24 +401,6 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
326
401
  runtime: manifest.runtime,
327
402
  });
328
403
  }
329
- if (staticFlowDir) {
330
- addFromDir(path.join(staticFlowDir, "nodes"), "flow", flowId);
331
- const flowData = marketplaceFlowData;
332
- if (flowData?.instances) {
333
- pipelineTranslations[flowId] = pipelineTranslations[flowId] || {};
334
- for (const [nodeId, inst] of Object.entries(flowData.instances)) {
335
- pipelineTranslations[flowId][nodeId] = {
336
- label: inst?.label,
337
- body: inst?.body,
338
- description: inst?.description || inst?.userDescription,
339
- };
340
- }
341
- }
342
- if (flowData?.ui?.description) {
343
- pipelineTranslations[flowId] = pipelineTranslations[flowId] || {};
344
- pipelineTranslations[flowId].__flowDescription = flowData.ui.description;
345
- }
346
- }
347
404
  if (flowId && flowSource) {
348
405
  if (flowSource === "builtin") {
349
406
  addFromDir(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
@@ -495,63 +552,79 @@ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
495
552
  }
496
553
 
497
554
  /**
498
- * 解析 flow.yaml 绝对路径(与 readFlowJson 一致;userworkspace 回退)。
499
- * @param {{ archived?: boolean }} [options]
500
- * @returns {{ path: string } | { error: string }}
555
+ * flowSource 列出候选目录(含 userworkspace→legacy 的历史回退顺序)。
556
+ * @returns {{ dirs: string[] } | { error: string }}
501
557
  */
502
- export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {}) {
558
+ function flowDirCandidates(workspaceRoot, flowId, flowSource, options = {}) {
503
559
  const root = path.resolve(workspaceRoot);
504
- const archived = Boolean(options.archived);
505
560
  const userPipelinesRoot = getUserPipelinesRoot(options.userId);
506
- let yamlPath;
507
- if (archived) {
508
- if (flowSource === "builtin") {
509
- return { error: t("catalog.builtin_flow_archive_path_not_supported") };
510
- }
561
+ if (Boolean(options.archived)) {
562
+ if (flowSource === "builtin") return { error: t("catalog.builtin_flow_archive_path_not_supported") };
511
563
  if (flowSource === "user") {
512
- yamlPath = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
513
- } else if (flowSource === "workspace") {
514
- yamlPath = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
515
- if (!fs.existsSync(yamlPath)) {
516
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
517
- if (fs.existsSync(altLeg)) yamlPath = altLeg;
518
- }
519
- } else {
520
- return { error: "Invalid flowSource" };
521
- }
522
- if (!fs.existsSync(yamlPath)) {
523
- return { error: "Flow not found: " + flowId };
524
- }
525
- return { path: yamlPath };
526
- }
527
-
528
- if (flowSource === "builtin") {
529
- yamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
530
- } else if (flowSource === "admin") {
531
- const flowDir = resolveAdminBuiltinPipelineDir(flowId);
532
- yamlPath = flowDir ? path.join(flowDir, "flow.yaml") : "";
533
- } else if (flowSource === "user") {
534
- yamlPath = path.join(userPipelinesRoot, flowId, "flow.yaml");
535
- if (!fs.existsSync(yamlPath)) {
536
- const alt = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
537
- if (fs.existsSync(alt)) yamlPath = alt;
538
- }
539
- if (!fs.existsSync(yamlPath)) {
540
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
541
- if (fs.existsSync(altLeg)) yamlPath = altLeg;
564
+ return { dirs: [path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId)] };
542
565
  }
543
- } else if (flowSource === "workspace") {
544
- yamlPath = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
545
- if (!fs.existsSync(yamlPath)) {
546
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
547
- if (fs.existsSync(altLeg)) yamlPath = altLeg;
566
+ if (flowSource === "workspace") {
567
+ return {
568
+ dirs: [
569
+ path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId),
570
+ path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId),
571
+ ],
572
+ };
548
573
  }
549
- } else {
550
574
  return { error: "Invalid flowSource" };
551
575
  }
552
- if (!fs.existsSync(yamlPath)) {
553
- return { error: "Flow not found: " + flowId };
576
+ if (flowSource === "builtin") return { dirs: [path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId)] };
577
+ if (flowSource === "admin") {
578
+ const dir = resolveAdminBuiltinPipelineDir(flowId);
579
+ return { dirs: dir ? [dir] : [] };
554
580
  }
581
+ if (flowSource === "user") {
582
+ return {
583
+ dirs: [
584
+ path.join(userPipelinesRoot, flowId),
585
+ path.join(root, PIPELINES_DIR, flowId),
586
+ path.join(root, LEGACY_PIPELINES_DIR, flowId),
587
+ ],
588
+ };
589
+ }
590
+ if (flowSource === "workspace") {
591
+ return {
592
+ dirs: [path.join(root, PIPELINES_DIR, flowId), path.join(root, LEGACY_PIPELINES_DIR, flowId)],
593
+ };
594
+ }
595
+ return { error: "Invalid flowSource" };
596
+ }
597
+
598
+ /**
599
+ * 解析流程目录绝对路径。
600
+ *
601
+ * 大多数调用方要的其实是**目录**,以前却只能 `path.dirname(getFlowYamlAbs().path)` —— 于是
602
+ * 「改个名」这种和 yaml 毫无关系的操作,也被 `flow.yaml` 是否存在卡住。目录存不存在现在由
603
+ * `isFlowDir` 说了算(见 paths.mjs),三种标记文件任一即可。
604
+ *
605
+ * @param {{ archived?: boolean, userId?: string }} [options]
606
+ * @returns {{ dir: string } | { error: string }}
607
+ */
608
+ export function resolveFlowDirAbs(workspaceRoot, flowId, flowSource, options = {}) {
609
+ const candidates = flowDirCandidates(workspaceRoot, flowId, flowSource, options);
610
+ if (candidates.error) return { error: candidates.error };
611
+ for (const dir of candidates.dirs) {
612
+ if (isFlowDir(dir)) return { dir };
613
+ }
614
+ return { error: "Flow not found: " + flowId };
615
+ }
616
+
617
+ /**
618
+ * 解析 flow.yaml 绝对路径。**只给真的要读 yaml 内容的调用方用**;要目录请用
619
+ * `resolveFlowDirAbs`。
620
+ * @param {{ archived?: boolean }} [options]
621
+ * @returns {{ path: string } | { error: string }}
622
+ */
623
+ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {}) {
624
+ const resolved = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, options);
625
+ if (resolved.error) return { error: resolved.error };
626
+ const yamlPath = path.join(resolved.dir, "flow.yaml");
627
+ if (!fs.existsSync(yamlPath)) return { error: "Flow not found: " + flowId };
555
628
  return { path: yamlPath };
556
629
  }
557
630
 
@@ -632,6 +705,7 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
632
705
  if (/^control/i.test(nodeId)) type = "control";
633
706
  else if (/^provide/i.test(nodeId)) type = "provide";
634
707
  else if (/^tool/i.test(nodeId)) type = "agent";
708
+ if (data.type) type = data.type;
635
709
  const strippedId =
636
710
  nodeId
637
711
  .replace(/\.md$/, "")
@@ -642,6 +716,7 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
642
716
  const label = data.displayName ?? strippedId;
643
717
  return {
644
718
  type,
719
+ runtimeTier: data.runtime,
645
720
  label,
646
721
  displayName: data.displayName,
647
722
  inputs: data.input,
@@ -874,13 +949,13 @@ export function listPipelines(workspaceRoot) {
874
949
  return;
875
950
  }
876
951
  const table = new Table({
877
- head: [chalk.cyan(t("catalog.pipeline_header")), chalk.cyan(t("catalog.source_header")), chalk.cyan(t("catalog.apply_example_header"))],
952
+ head: [chalk.cyan(t("catalog.pipeline_header")), chalk.cyan(t("catalog.source_header")), chalk.cyan(t("catalog.open_example_header"))],
878
953
  colWidths: [24, 10, 48],
879
954
  style: { head: [], border: ["grey"] },
880
955
  });
881
956
  for (const row of rows) {
882
957
  const sourceLabel = row.source === "builtin" || row.source === "admin" ? "builtin" : row.source === "workspace" ? "workspace" : "user";
883
- table.push([row.id, sourceLabel, `agentflow apply ${row.id}`]);
958
+ table.push([row.id, sourceLabel, `agentflow validate ${row.id}`]);
884
959
  }
885
960
  log.info("\n" + chalk.bold("Pipelines"));
886
961
  log.info(table.toString());