@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
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,13 +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";
30
+ import { isWorkspacePreviewDir } from "./workspace-preview.mjs";
31
+ import { RETIRED_NODE_IDS } from "./legacy-flow-execution.mjs";
24
32
 
25
33
  /** 从指定目录收集含 flow.yaml 的子目录名。 */
26
34
  export function collectPipelineNamesFromDir(dirPath) {
@@ -28,27 +36,45 @@ export function collectPipelineNamesFromDir(dirPath) {
28
36
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
29
37
  return entries
30
38
  .filter((e) => e.isDirectory())
31
- .filter((e) => fs.existsSync(path.join(dirPath, e.name, "flow.yaml")))
39
+ .filter((e) => isFlowDir(path.join(dirPath, e.name)))
40
+ .filter((e) => !isWorkspacePreviewDir(path.join(dirPath, e.name)))
32
41
  .map((e) => e.name);
33
42
  }
34
43
 
44
+ const trimmedOrUndefined = (value) => {
45
+ if (typeof value !== "string") return undefined;
46
+ const t = value.trim();
47
+ return t === "" ? undefined : t;
48
+ };
49
+
35
50
  /**
36
- * 读取 flow.yaml 中流水线级说明(与 Web UI serialize 一致:ui.description)。
37
- * @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
38
58
  * @returns {string | undefined}
39
59
  */
40
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
+ }
41
71
  const yamlPath = path.join(flowDir, "flow.yaml");
42
72
  if (!fs.existsSync(yamlPath)) return undefined;
43
73
  try {
44
- const raw = fs.readFileSync(yamlPath, "utf-8");
45
- const data = yaml.load(raw);
74
+ const data = yaml.load(fs.readFileSync(yamlPath, "utf-8"));
46
75
  if (!data || typeof data !== "object") return undefined;
47
76
  const ui = data.ui && typeof data.ui === "object" ? data.ui : {};
48
- const d = ui.description;
49
- if (typeof d !== "string") return undefined;
50
- const t = d.trim();
51
- return t === "" ? undefined : t;
77
+ return trimmedOrUndefined(ui.description);
52
78
  } catch {
53
79
  return undefined;
54
80
  }
@@ -164,6 +190,22 @@ function normalizeFrontmatterSlots(arr) {
164
190
  });
165
191
  }
166
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
+
167
209
  /**
168
210
  * 解析 .md 节点文件的 frontmatter。
169
211
  * 优先用 js-yaml 解析整块 frontmatter,以支持 description: | / >- 等多行字段;
@@ -171,7 +213,16 @@ function normalizeFrontmatterSlots(arr) {
171
213
  */
172
214
  export function parseNodeFrontmatter(raw) {
173
215
  const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
174
- 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
+ };
175
226
  if (!m) return data;
176
227
  const fm = m[1];
177
228
  try {
@@ -186,6 +237,9 @@ export function parseNodeFrontmatter(raw) {
186
237
  if (parsed.guide && typeof parsed.guide === "object" && !Array.isArray(parsed.guide)) {
187
238
  data.guide = parsed.guide;
188
239
  }
240
+ data.runtime = normalizeNodeRuntimeTier(parsed.runtime);
241
+ data.type = normalizeNodeCategory(parsed.type);
242
+ data.paletteHidden = String(parsed.palette ?? "").trim().toLowerCase() === "hidden";
189
243
  data.input = normalizeFrontmatterSlots(parsed.input);
190
244
  data.output = normalizeFrontmatterSlots(parsed.output);
191
245
  return data;
@@ -222,6 +276,9 @@ export function parseNodeFrontmatter(raw) {
222
276
  const displayM = fm.match(/\bdisplayName:\s*["']?([^"'\n#][^\n]*)["']?/);
223
277
  if (descM) data.description = descM[1].trim().replace(/^["']|["']$/g, "");
224
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);
225
282
  return data;
226
283
  }
227
284
 
@@ -229,7 +286,7 @@ export function parseNodeFrontmatter(raw) {
229
286
  * @param {string} workspaceRoot
230
287
  * @param {string} flowId
231
288
  * @param {string} flowSource
232
- * @param {{ archived?: boolean, staticFlowPath?: string }} [opts]
289
+ * @param {{ archived?: boolean }} [opts]
233
290
  */
234
291
  export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
235
292
  const root = path.resolve(workspaceRoot);
@@ -238,14 +295,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
238
295
  const byId = new Map();
239
296
  const pipelineTranslations = {};
240
297
  let marketplaceFlowData = null;
241
- const staticFlowPath = opts.staticFlowPath ? path.resolve(String(opts.staticFlowPath)) : "";
242
- const staticFlowDir = staticFlowPath ? path.dirname(staticFlowPath) : "";
243
- if (staticFlowPath && fs.existsSync(staticFlowPath) && fs.statSync(staticFlowPath).isFile()) {
244
- try {
245
- const parsed = yaml.load(fs.readFileSync(staticFlowPath, "utf-8"));
246
- if (parsed && typeof parsed === "object") marketplaceFlowData = parsed;
247
- } catch (_) {}
248
- } else if (flowId && flowSource) {
298
+ if (flowId && flowSource) {
249
299
  const flowPath = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
250
300
  if (flowPath.path && fs.existsSync(flowPath.path)) {
251
301
  try {
@@ -259,6 +309,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
259
309
  const files = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md"));
260
310
  for (const e of files) {
261
311
  const id = e.name.replace(/\.mdx?$/i, "").replace(/\.markdown$/i, "");
312
+ if (RETIRED_NODE_IDS.has(id)) continue;
262
313
  let type = "agent";
263
314
  if (/^control/i.test(id)) type = "control";
264
315
  else if (/^provide/i.test(id)) type = "provide";
@@ -266,6 +317,10 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
266
317
  try {
267
318
  const raw = fs.readFileSync(path.join(dir, e.name), "utf-8");
268
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;
269
324
  const strippedId =
270
325
  id.replace(/^agent_?/i, "").replace(/^control_?/i, "").replace(/^provide_?/i, "").replace(/^tool_?/i, "") || id;
271
326
  const label = data.displayName ?? strippedId;
@@ -283,6 +338,7 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
283
338
  byId.set(id, {
284
339
  id,
285
340
  type,
341
+ runtimeTier: data.runtime,
286
342
  label: translatedDisplayName || label,
287
343
  displayName: translatedDisplayName || data.displayName,
288
344
  description: translatedDescription || data.description,
@@ -294,6 +350,29 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
294
350
  });
295
351
  } catch (_) {}
296
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
+ }
297
376
  };
298
377
  addFromDir(PACKAGE_BUILTIN_NODES_DIR, "project");
299
378
  addFromDir(path.join(root, LEGACY_NODES_DIR), "project");
@@ -322,24 +401,6 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
322
401
  runtime: manifest.runtime,
323
402
  });
324
403
  }
325
- if (staticFlowDir) {
326
- addFromDir(path.join(staticFlowDir, "nodes"), "flow", flowId);
327
- const flowData = marketplaceFlowData;
328
- if (flowData?.instances) {
329
- pipelineTranslations[flowId] = pipelineTranslations[flowId] || {};
330
- for (const [nodeId, inst] of Object.entries(flowData.instances)) {
331
- pipelineTranslations[flowId][nodeId] = {
332
- label: inst?.label,
333
- body: inst?.body,
334
- description: inst?.description || inst?.userDescription,
335
- };
336
- }
337
- }
338
- if (flowData?.ui?.description) {
339
- pipelineTranslations[flowId] = pipelineTranslations[flowId] || {};
340
- pipelineTranslations[flowId].__flowDescription = flowData.ui.description;
341
- }
342
- }
343
404
  if (flowId && flowSource) {
344
405
  if (flowSource === "builtin") {
345
406
  addFromDir(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
@@ -491,63 +552,79 @@ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
491
552
  }
492
553
 
493
554
  /**
494
- * 解析 flow.yaml 绝对路径(与 readFlowJson 一致;userworkspace 回退)。
495
- * @param {{ archived?: boolean }} [options]
496
- * @returns {{ path: string } | { error: string }}
555
+ * flowSource 列出候选目录(含 userworkspace→legacy 的历史回退顺序)。
556
+ * @returns {{ dirs: string[] } | { error: string }}
497
557
  */
498
- export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {}) {
558
+ function flowDirCandidates(workspaceRoot, flowId, flowSource, options = {}) {
499
559
  const root = path.resolve(workspaceRoot);
500
- const archived = Boolean(options.archived);
501
560
  const userPipelinesRoot = getUserPipelinesRoot(options.userId);
502
- let yamlPath;
503
- if (archived) {
504
- if (flowSource === "builtin") {
505
- return { error: t("catalog.builtin_flow_archive_path_not_supported") };
506
- }
561
+ if (Boolean(options.archived)) {
562
+ if (flowSource === "builtin") return { error: t("catalog.builtin_flow_archive_path_not_supported") };
507
563
  if (flowSource === "user") {
508
- yamlPath = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
509
- } else if (flowSource === "workspace") {
510
- yamlPath = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
511
- if (!fs.existsSync(yamlPath)) {
512
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
513
- if (fs.existsSync(altLeg)) yamlPath = altLeg;
514
- }
515
- } else {
516
- return { error: "Invalid flowSource" };
517
- }
518
- if (!fs.existsSync(yamlPath)) {
519
- return { error: "Flow not found: " + flowId };
520
- }
521
- return { path: yamlPath };
522
- }
523
-
524
- if (flowSource === "builtin") {
525
- yamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
526
- } else if (flowSource === "admin") {
527
- const flowDir = resolveAdminBuiltinPipelineDir(flowId);
528
- yamlPath = flowDir ? path.join(flowDir, "flow.yaml") : "";
529
- } else if (flowSource === "user") {
530
- yamlPath = path.join(userPipelinesRoot, flowId, "flow.yaml");
531
- if (!fs.existsSync(yamlPath)) {
532
- const alt = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
533
- if (fs.existsSync(alt)) yamlPath = alt;
534
- }
535
- if (!fs.existsSync(yamlPath)) {
536
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
537
- if (fs.existsSync(altLeg)) yamlPath = altLeg;
564
+ return { dirs: [path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId)] };
538
565
  }
539
- } else if (flowSource === "workspace") {
540
- yamlPath = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
541
- if (!fs.existsSync(yamlPath)) {
542
- const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
543
- 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
+ };
544
573
  }
545
- } else {
546
574
  return { error: "Invalid flowSource" };
547
575
  }
548
- if (!fs.existsSync(yamlPath)) {
549
- 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] : [] };
550
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 };
551
628
  return { path: yamlPath };
552
629
  }
553
630
 
@@ -628,6 +705,7 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
628
705
  if (/^control/i.test(nodeId)) type = "control";
629
706
  else if (/^provide/i.test(nodeId)) type = "provide";
630
707
  else if (/^tool/i.test(nodeId)) type = "agent";
708
+ if (data.type) type = data.type;
631
709
  const strippedId =
632
710
  nodeId
633
711
  .replace(/\.md$/, "")
@@ -638,6 +716,7 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
638
716
  const label = data.displayName ?? strippedId;
639
717
  return {
640
718
  type,
719
+ runtimeTier: data.runtime,
641
720
  label,
642
721
  displayName: data.displayName,
643
722
  inputs: data.input,
@@ -870,13 +949,13 @@ export function listPipelines(workspaceRoot) {
870
949
  return;
871
950
  }
872
951
  const table = new Table({
873
- 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"))],
874
953
  colWidths: [24, 10, 48],
875
954
  style: { head: [], border: ["grey"] },
876
955
  });
877
956
  for (const row of rows) {
878
957
  const sourceLabel = row.source === "builtin" || row.source === "admin" ? "builtin" : row.source === "workspace" ? "workspace" : "user";
879
- table.push([row.id, sourceLabel, `agentflow apply ${row.id}`]);
958
+ table.push([row.id, sourceLabel, `agentflow validate ${row.id}`]);
880
959
  }
881
960
  log.info("\n" + chalk.bold("Pipelines"));
882
961
  log.info(table.toString());