@fieldwangai/agentflow 0.1.25

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/README.zh-CN.md +201 -0
  4. package/agents/agentflow-node-executor-code.md +32 -0
  5. package/agents/agentflow-node-executor-planning.md +32 -0
  6. package/agents/agentflow-node-executor-requirement.md +32 -0
  7. package/agents/agentflow-node-executor-test.md +32 -0
  8. package/agents/agentflow-node-executor-ui.md +32 -0
  9. package/agents/agentflow-node-executor.md +32 -0
  10. package/agents/agents.json +8 -0
  11. package/agents/en/agentflow-node-executor.md +32 -0
  12. package/agents/zh/agentflow-node-executor.md +32 -0
  13. package/bin/agentflow.mjs +52 -0
  14. package/bin/ensure-workspace-reference.mjs +35 -0
  15. package/bin/lib/agent-runners.mjs +1199 -0
  16. package/bin/lib/agents-path.mjs +61 -0
  17. package/bin/lib/api-runner.mjs +361 -0
  18. package/bin/lib/apply.mjs +852 -0
  19. package/bin/lib/catalog-agents.mjs +300 -0
  20. package/bin/lib/catalog-flows.mjs +532 -0
  21. package/bin/lib/composer-agent.mjs +884 -0
  22. package/bin/lib/composer-flow-instances.mjs +68 -0
  23. package/bin/lib/composer-flow-skeleton.mjs +334 -0
  24. package/bin/lib/composer-flow-validate.mjs +47 -0
  25. package/bin/lib/composer-log.mjs +197 -0
  26. package/bin/lib/composer-model-router.mjs +160 -0
  27. package/bin/lib/composer-node-schema.mjs +299 -0
  28. package/bin/lib/composer-planner.mjs +749 -0
  29. package/bin/lib/composer-script-ops.mjs +233 -0
  30. package/bin/lib/composer-skill-router.mjs +384 -0
  31. package/bin/lib/flow-import.mjs +305 -0
  32. package/bin/lib/flow-normalize.mjs +71 -0
  33. package/bin/lib/flow-write.mjs +395 -0
  34. package/bin/lib/help.mjs +139 -0
  35. package/bin/lib/hub-login.mjs +54 -0
  36. package/bin/lib/hub-publish.mjs +159 -0
  37. package/bin/lib/hub-remote.mjs +189 -0
  38. package/bin/lib/hub.mjs +299 -0
  39. package/bin/lib/i18n.mjs +233 -0
  40. package/bin/lib/locales/en.json +344 -0
  41. package/bin/lib/locales/zh.json +344 -0
  42. package/bin/lib/log.mjs +37 -0
  43. package/bin/lib/main.mjs +611 -0
  44. package/bin/lib/model-config.mjs +118 -0
  45. package/bin/lib/model-lists.mjs +188 -0
  46. package/bin/lib/node-exec-context.mjs +336 -0
  47. package/bin/lib/node-execute.mjs +513 -0
  48. package/bin/lib/normalize-node-tool-command.mjs +97 -0
  49. package/bin/lib/paths.mjs +216 -0
  50. package/bin/lib/pipeline-scripts.mjs +41 -0
  51. package/bin/lib/recent-runs.mjs +173 -0
  52. package/bin/lib/run-apply-active-lock.mjs +82 -0
  53. package/bin/lib/run-events.mjs +85 -0
  54. package/bin/lib/run-node-statuses-from-disk.mjs +85 -0
  55. package/bin/lib/schedule-config.mjs +227 -0
  56. package/bin/lib/scheduler.mjs +312 -0
  57. package/bin/lib/table.mjs +4 -0
  58. package/bin/lib/terminal.mjs +42 -0
  59. package/bin/lib/ui-print.mjs +94 -0
  60. package/bin/lib/ui-server.mjs +2113 -0
  61. package/bin/lib/workspace-tree.mjs +266 -0
  62. package/bin/lib/workspace.mjs +180 -0
  63. package/bin/pipeline/build-node-prompt.mjs +179 -0
  64. package/bin/pipeline/check-cache.mjs +191 -0
  65. package/bin/pipeline/check-flow.mjs +543 -0
  66. package/bin/pipeline/collect-nodes.mjs +212 -0
  67. package/bin/pipeline/compute-cache-md5.mjs +177 -0
  68. package/bin/pipeline/ensure-run-dir.mjs +71 -0
  69. package/bin/pipeline/extract-thinking.mjs +308 -0
  70. package/bin/pipeline/gc.mjs +129 -0
  71. package/bin/pipeline/get-env.mjs +83 -0
  72. package/bin/pipeline/get-exec-id.mjs +145 -0
  73. package/bin/pipeline/get-ready-nodes.mjs +435 -0
  74. package/bin/pipeline/get-resolved-values.mjs +337 -0
  75. package/bin/pipeline/load-key.mjs +62 -0
  76. package/bin/pipeline/parse-bool.mjs +33 -0
  77. package/bin/pipeline/parse-flow.mjs +698 -0
  78. package/bin/pipeline/post-process-control-if.mjs +23 -0
  79. package/bin/pipeline/post-process-node.mjs +490 -0
  80. package/bin/pipeline/pre-process-node.mjs +449 -0
  81. package/bin/pipeline/resolve-inputs.mjs +201 -0
  82. package/bin/pipeline/run-log.mjs +34 -0
  83. package/bin/pipeline/run-tool-nodejs.mjs +160 -0
  84. package/bin/pipeline/save-key.mjs +93 -0
  85. package/bin/pipeline/snapshot-prior-round.mjs +70 -0
  86. package/bin/pipeline/validate-flow.mjs +825 -0
  87. package/bin/pipeline/validate-for-ui.mjs +226 -0
  88. package/bin/pipeline/validate-script-output.mjs +130 -0
  89. package/bin/pipeline/write-result.mjs +182 -0
  90. package/builtin/nodes/agent_subAgent.md +14 -0
  91. package/builtin/nodes/control_agent_toBool.md +20 -0
  92. package/builtin/nodes/control_anyOne.md +17 -0
  93. package/builtin/nodes/control_end.md +11 -0
  94. package/builtin/nodes/control_if.md +20 -0
  95. package/builtin/nodes/control_start.md +11 -0
  96. package/builtin/nodes/control_toBool.md +21 -0
  97. package/builtin/nodes/provide_file.md +11 -0
  98. package/builtin/nodes/provide_str.md +11 -0
  99. package/builtin/nodes/tool_get_env.md +14 -0
  100. package/builtin/nodes/tool_load_key.md +20 -0
  101. package/builtin/nodes/tool_nodejs.md +40 -0
  102. package/builtin/nodes/tool_print.md +14 -0
  103. package/builtin/nodes/tool_save_key.md +20 -0
  104. package/builtin/nodes/tool_user_ask.md +23 -0
  105. package/builtin/nodes/tool_user_check.md +22 -0
  106. package/builtin/pipelines/module-migrate/flow.yaml +819 -0
  107. package/builtin/pipelines/module-migrate/scripts/check_imports.mjs +700 -0
  108. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Makefile +362 -0
  109. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
  110. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o.d +17 -0
  111. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o.d +5 -0
  112. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o.d +8 -0
  113. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/tree_sitter_kotlin_binding.node.d +1 -0
  114. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  115. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o +0 -0
  116. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o +0 -0
  117. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o +0 -0
  118. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/tree_sitter_kotlin_binding.node +0 -0
  119. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/binding.Makefile +6 -0
  120. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/gyp-mac-tool +768 -0
  121. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
  122. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
  123. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
  124. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
  125. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/tree_sitter_kotlin_binding.target.mk +203 -0
  126. package/builtin/pipelines/new/flow.yaml +545 -0
  127. package/builtin/pipelines/new/scripts/check-flow.mjs +9 -0
  128. package/builtin/pipelines/new/scripts/collect-nodes.mjs +211 -0
  129. package/builtin/pipelines/scripts/adjust-node-positions.mjs +113 -0
  130. package/builtin/web-ui/dist/agentflow-icon.svg +23 -0
  131. package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +1 -0
  132. package/builtin/web-ui/dist/assets/index-DkkhNESc.js +190 -0
  133. package/builtin/web-ui/dist/index.html +24 -0
  134. package/package.json +67 -0
  135. package/reference/flow-control-capabilities.md +274 -0
  136. package/reference/flow-layout.md +84 -0
  137. package/reference/flow-prompt-handler-check.md +12 -0
  138. package/reference/flow-result-semantics.md +14 -0
@@ -0,0 +1,532 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import yaml from "js-yaml";
4
+ import chalk from "chalk";
5
+ import { log } from "./log.mjs";
6
+ import { t, translateNodeDef } from "./i18n.mjs";
7
+ import {
8
+ ARCHIVED_PIPELINES_DIR_NAME,
9
+ LEGACY_NODES_DIR,
10
+ LEGACY_PIPELINES_DIR,
11
+ PACKAGE_BUILTIN_NODES_DIR,
12
+ PACKAGE_BUILTIN_PIPELINES_DIR,
13
+ PIPELINES_DIR,
14
+ PROJECT_NODES_DIR,
15
+ USER_AGENTFLOW_PIPELINES_LABEL,
16
+ getUserPipelinesRoot,
17
+ } from "./paths.mjs";
18
+ import { Table } from "./table.mjs";
19
+
20
+ /** 从指定目录收集含 flow.yaml 的子目录名。 */
21
+ export function collectPipelineNamesFromDir(dirPath) {
22
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) return [];
23
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
24
+ return entries
25
+ .filter((e) => e.isDirectory())
26
+ .filter((e) => fs.existsSync(path.join(dirPath, e.name, "flow.yaml")))
27
+ .map((e) => e.name);
28
+ }
29
+
30
+ /**
31
+ * 读取 flow.yaml 中流水线级说明(与 Web UI serialize 一致:ui.description)。
32
+ * @param {string} flowDir 含 flow.yaml 的目录
33
+ * @returns {string | undefined}
34
+ */
35
+ export function readPipelineListDescription(flowDir) {
36
+ const yamlPath = path.join(flowDir, "flow.yaml");
37
+ if (!fs.existsSync(yamlPath)) return undefined;
38
+ try {
39
+ const raw = fs.readFileSync(yamlPath, "utf-8");
40
+ const data = yaml.load(raw);
41
+ if (!data || typeof data !== "object") return undefined;
42
+ const ui = data.ui && typeof data.ui === "object" ? data.ui : {};
43
+ const d = ui.description;
44
+ if (typeof d !== "string") return undefined;
45
+ const t = d.trim();
46
+ return t === "" ? undefined : t;
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ }
51
+
52
+ export function listFlowsJson(workspaceRoot) {
53
+ const root = path.resolve(workspaceRoot);
54
+ const out = [];
55
+ const fromBuiltin = collectPipelineNamesFromDir(PACKAGE_BUILTIN_PIPELINES_DIR);
56
+ for (const name of fromBuiltin) {
57
+ const dir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, name);
58
+ const description = readPipelineListDescription(dir);
59
+ out.push({ id: name, path: dir, source: "builtin", ...(description ? { description } : {}) });
60
+ }
61
+ const userPipelinesRoot = getUserPipelinesRoot();
62
+ const fromUserData = collectPipelineNamesFromDir(userPipelinesRoot);
63
+ for (const name of fromUserData) {
64
+ if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
65
+ const dir = path.join(userPipelinesRoot, name);
66
+ const description = readPipelineListDescription(dir);
67
+ out.push({ id: name, path: dir, source: "user", ...(description ? { description } : {}) });
68
+ }
69
+ const userArchivedRoot = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME);
70
+ const fromUserArchived = collectPipelineNamesFromDir(userArchivedRoot);
71
+ for (const name of fromUserArchived) {
72
+ const dir = path.join(userArchivedRoot, name);
73
+ const description = readPipelineListDescription(dir);
74
+ out.push({ id: name, path: dir, source: "user", archived: true, ...(description ? { description } : {}) });
75
+ }
76
+ const wsPrimary = path.join(root, PIPELINES_DIR);
77
+ const fromWorkspace = collectPipelineNamesFromDir(wsPrimary);
78
+ const workspaceIds = new Set(fromWorkspace);
79
+ for (const name of fromWorkspace) {
80
+ if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
81
+ const dir = path.join(wsPrimary, name);
82
+ const description = readPipelineListDescription(dir);
83
+ out.push({ id: name, path: dir, source: "workspace", ...(description ? { description } : {}) });
84
+ }
85
+ const wsArchivedPrimary = path.join(wsPrimary, ARCHIVED_PIPELINES_DIR_NAME);
86
+ const fromWsArchived = collectPipelineNamesFromDir(wsArchivedPrimary);
87
+ const workspaceArchivedIds = new Set(fromWsArchived);
88
+ for (const name of fromWsArchived) {
89
+ const dir = path.join(wsArchivedPrimary, name);
90
+ const description = readPipelineListDescription(dir);
91
+ out.push({ id: name, path: dir, source: "workspace", archived: true, ...(description ? { description } : {}) });
92
+ }
93
+ const fromLegacyWs = collectPipelineNamesFromDir(path.join(root, LEGACY_PIPELINES_DIR));
94
+ for (const name of fromLegacyWs) {
95
+ if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
96
+ if (workspaceIds.has(name)) continue;
97
+ const legDir = path.join(root, LEGACY_PIPELINES_DIR, name);
98
+ const description = readPipelineListDescription(legDir);
99
+ out.push({ id: name, path: legDir, source: "workspace", ...(description ? { description } : {}) });
100
+ }
101
+ const legArchivedRoot = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME);
102
+ const fromLegArchived = collectPipelineNamesFromDir(legArchivedRoot);
103
+ for (const name of fromLegArchived) {
104
+ if (workspaceArchivedIds.has(name)) continue;
105
+ const dir = path.join(legArchivedRoot, name);
106
+ const description = readPipelineListDescription(dir);
107
+ out.push({ id: name, path: dir, source: "workspace", archived: true, ...(description ? { description } : {}) });
108
+ workspaceArchivedIds.add(name);
109
+ }
110
+ const sourceRank = (s) => (s === "builtin" ? 0 : s === "user" ? 1 : 2);
111
+ const archRank = (a) => (a.archived ? 1 : 0);
112
+ out.sort(
113
+ (a, b) =>
114
+ sourceRank(a.source) - sourceRank(b.source) ||
115
+ archRank(a) - archRank(b) ||
116
+ a.id.localeCompare(b.id),
117
+ );
118
+ return out;
119
+ }
120
+
121
+ /** 将 YAML 解析得到的 input/output 项转为 Web UI / 校验使用的槽位结构 */
122
+ function normalizeFrontmatterSlots(arr) {
123
+ if (!Array.isArray(arr)) return [];
124
+ return arr.map((item) => {
125
+ if (!item || typeof item !== "object") return { type: t("catalog.type_text"), name: "", default: "" };
126
+ const type = item.type != null ? String(item.type).trim() : t("catalog.type_text");
127
+ const name = item.name != null ? String(item.name).trim() : "";
128
+ let def = item.default !== undefined && item.default !== null ? item.default : item.value;
129
+ if (def === undefined || def === null) def = "";
130
+ else if (typeof def !== "string") def = String(def);
131
+ return { type, name, default: def };
132
+ });
133
+ }
134
+
135
+ /**
136
+ * 解析 .md 节点文件的 frontmatter。
137
+ * 优先用 js-yaml 解析整块 frontmatter,以支持 description: | / >- 等多行字段;
138
+ * 旧版单行正则会把 `description: |` 误解析成仅一个 `|` 字符,导致 Web UI「系统说明」几乎空白。
139
+ */
140
+ export function parseNodeFrontmatter(raw) {
141
+ const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
142
+ const data = { input: [], output: [], displayName: undefined, description: undefined };
143
+ if (!m) return data;
144
+ const fm = m[1];
145
+ try {
146
+ const parsed = yaml.load(fm);
147
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
148
+ if (parsed.description != null && String(parsed.description).trim() !== "") {
149
+ data.description = String(parsed.description).trim();
150
+ }
151
+ if (parsed.displayName != null && String(parsed.displayName).trim() !== "") {
152
+ data.displayName = String(parsed.displayName).trim();
153
+ }
154
+ data.input = normalizeFrontmatterSlots(parsed.input);
155
+ data.output = normalizeFrontmatterSlots(parsed.output);
156
+ return data;
157
+ }
158
+ } catch {
159
+ /* 非严格 YAML 时回退到正则 */
160
+ }
161
+
162
+ const inputBlock = fm.match(/(?:^|\n)\s*input:\s*\n([\s\S]*?)\noutput\s*:/m);
163
+ const outputBlock = fm.match(/(?:^|\n)\s*output:\s*\n([\s\S]*)/m);
164
+ const normalizeSlots = (block) => {
165
+ if (!block) return [];
166
+ const text = block[1];
167
+ const slots = [];
168
+ const parts = text.split(/\n\s*-\s+type:/).filter(Boolean);
169
+ for (let i = 0; i < parts.length; i++) {
170
+ const chunk = (i === 0 ? parts[i] : " - type:" + parts[i]).trim();
171
+ const typeM = chunk.match(/type:\s*["']?([^"'\n]*)["']?/);
172
+ const nameM = chunk.match(/name:\s*["']?([^"'\n]*)["']?/);
173
+ const defaultM = chunk.match(/(?:default|value):\s*(.*)$/m);
174
+ let defaultVal = defaultM ? defaultM[1].trim().replace(/^["']|["']$/g, "") : undefined;
175
+ if (defaultVal === '""' || defaultVal === "''") defaultVal = "";
176
+ slots.push({
177
+ type: (typeM && typeM[1].trim()) || t("catalog.type_text"),
178
+ name: nameM ? nameM[1].trim() : "",
179
+ default: defaultVal !== undefined ? defaultVal : "",
180
+ });
181
+ }
182
+ return slots;
183
+ };
184
+ data.input = normalizeSlots(inputBlock);
185
+ data.output = normalizeSlots(outputBlock);
186
+ const descM = fm.match(/\bdescription:\s*["']?([^"'\n#][^\n]*)["']?/);
187
+ const displayM = fm.match(/\bdisplayName:\s*["']?([^"'\n#][^\n]*)["']?/);
188
+ if (descM) data.description = descM[1].trim().replace(/^["']|["']$/g, "");
189
+ if (displayM) data.displayName = displayM[1].trim().replace(/^["']|["']$/g, "");
190
+ return data;
191
+ }
192
+
193
+ /**
194
+ * @param {string} workspaceRoot
195
+ * @param {string} flowId
196
+ * @param {string} flowSource
197
+ * @param {{ archived?: boolean }} [opts]
198
+ */
199
+ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
200
+ const root = path.resolve(workspaceRoot);
201
+ const archived = Boolean(opts.archived);
202
+ const byId = new Map();
203
+ const pipelineTranslations = {};
204
+ const addFromDir = (dir, source, flowIdOpt) => {
205
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return;
206
+ const files = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md"));
207
+ for (const e of files) {
208
+ const id = e.name.replace(/\.mdx?$/i, "").replace(/\.markdown$/i, "");
209
+ let type = "agent";
210
+ if (/^control/i.test(id)) type = "control";
211
+ else if (/^provide/i.test(id)) type = "provide";
212
+ else if (/^tool/i.test(id)) type = "agent";
213
+ try {
214
+ const raw = fs.readFileSync(path.join(dir, e.name), "utf-8");
215
+ const data = parseNodeFrontmatter(raw);
216
+ const strippedId =
217
+ id.replace(/^agent_?/i, "").replace(/^control_?/i, "").replace(/^provide_?/i, "").replace(/^tool_?/i, "") || id;
218
+ const label = data.displayName ?? strippedId;
219
+ const translatedDisplayName = translateNodeDef(id, "displayName");
220
+ const translatedDescription = translateNodeDef(id, "description");
221
+ byId.set(id, {
222
+ id,
223
+ type,
224
+ label: translatedDisplayName || label,
225
+ displayName: translatedDisplayName || data.displayName,
226
+ description: translatedDescription || data.description,
227
+ inputs: data.input,
228
+ outputs: data.output,
229
+ source: flowIdOpt ? "flow" : "project",
230
+ flowId: flowIdOpt,
231
+ });
232
+ } catch (_) {}
233
+ }
234
+ };
235
+ addFromDir(PACKAGE_BUILTIN_NODES_DIR, "project");
236
+ addFromDir(path.join(root, LEGACY_NODES_DIR), "project");
237
+ addFromDir(path.join(root, PROJECT_NODES_DIR), "project");
238
+ if (flowId && flowSource) {
239
+ if (flowSource === "builtin") {
240
+ addFromDir(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
241
+ try {
242
+ const flowYamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
243
+ if (fs.existsSync(flowYamlPath)) {
244
+ const flowData = yaml.load(fs.readFileSync(flowYamlPath, "utf-8"));
245
+ if (flowData?.instances) {
246
+ for (const [nodeId, inst] of Object.entries(flowData.instances)) {
247
+ pipelineTranslations[flowId] = pipelineTranslations[flowId] || {};
248
+ const trans = t(`pipeline.${flowId}.${nodeId}`);
249
+ pipelineTranslations[flowId][nodeId] = {
250
+ label: trans !== `pipeline.${flowId}.${nodeId}` ? trans : inst.label,
251
+ body: inst.body,
252
+ description: inst.description || inst.userDescription,
253
+ };
254
+ }
255
+ }
256
+ if (flowData?.ui?.description) {
257
+ pipelineTranslations[flowId].__flowDescription = t(`pipeline.${flowId}.description`) !== `pipeline.${flowId}.description`
258
+ ? t(`pipeline.${flowId}.description`)
259
+ : flowData.ui.description;
260
+ }
261
+ }
262
+ } catch (_) {}
263
+ } else if (flowSource === "user") {
264
+ if (archived) {
265
+ addFromDir(
266
+ path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"),
267
+ "flow",
268
+ flowId,
269
+ );
270
+ } else {
271
+ addFromDir(path.join(getUserPipelinesRoot(), flowId, "nodes"), "flow", flowId);
272
+ addFromDir(path.join(root, PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
273
+ addFromDir(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
274
+ }
275
+ } else if (flowSource === "workspace") {
276
+ if (archived) {
277
+ addFromDir(path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"), "flow", flowId);
278
+ addFromDir(path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"), "flow", flowId);
279
+ } else {
280
+ addFromDir(path.join(root, PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
281
+ addFromDir(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
282
+ }
283
+ }
284
+ }
285
+ return { nodes: Array.from(byId.values()).sort((a, b) => a.id.localeCompare(b.id)), pipelineTranslations };
286
+ }
287
+
288
+ export function printNodesTable(list) {
289
+ const maxDesc = 56;
290
+ const truncate = (s) => {
291
+ if (s == null) return "";
292
+ const t = String(s).replace(/\r?\n/g, " ").trim();
293
+ return t.length <= maxDesc ? t : t.slice(0, maxDesc - 2) + "…";
294
+ };
295
+ const table = new Table({
296
+ head: [chalk.bold("id"), chalk.bold("type"), chalk.bold("label"), chalk.bold("source"), chalk.bold("description")],
297
+ colWidths: [28, 12, 16, 10, maxDesc + 2],
298
+ });
299
+ for (const n of list) {
300
+ table.push([n.id, n.type, n.displayName ?? n.label, n.source, truncate(n.description)]);
301
+ }
302
+ process.stdout.write(table.toString() + "\n");
303
+ }
304
+
305
+ /**
306
+ * @param {string} workspaceRoot
307
+ * @param {string} flowId
308
+ * @param {string} flowSource
309
+ * @param {{ archived?: boolean }} [options]
310
+ */
311
+ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
312
+ const root = path.resolve(workspaceRoot);
313
+ const archived = Boolean(options.archived);
314
+ let flowDir;
315
+ if (archived) {
316
+ if (flowSource === "builtin") {
317
+ return { error: t("catalog.builtin_flow_archive_not_supported") };
318
+ }
319
+ if (flowSource === "user") {
320
+ flowDir = path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId);
321
+ } else if (flowSource === "workspace") {
322
+ flowDir = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId);
323
+ } else {
324
+ return { error: "Invalid flowSource" };
325
+ }
326
+ let yamlPath = path.join(flowDir, "flow.yaml");
327
+ if (flowSource === "workspace" && !fs.existsSync(yamlPath)) {
328
+ const legDir = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId);
329
+ const legYaml = path.join(legDir, "flow.yaml");
330
+ if (fs.existsSync(legYaml)) {
331
+ flowDir = legDir;
332
+ yamlPath = legYaml;
333
+ }
334
+ }
335
+ if (!fs.existsSync(yamlPath)) {
336
+ return { error: "Flow not found: " + flowId };
337
+ }
338
+ try {
339
+ const flowYaml = fs.readFileSync(yamlPath, "utf-8");
340
+ return { flowYaml };
341
+ } catch (e) {
342
+ return { error: (e && e.message) || String(e) };
343
+ }
344
+ }
345
+
346
+ if (flowSource === "builtin") {
347
+ flowDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
348
+ } else if (flowSource === "user") {
349
+ flowDir = path.join(getUserPipelinesRoot(), flowId);
350
+ } else if (flowSource === "workspace") {
351
+ flowDir = path.join(root, PIPELINES_DIR, flowId);
352
+ } else {
353
+ return { error: "Invalid flowSource" };
354
+ }
355
+ let yamlPath = path.join(flowDir, "flow.yaml");
356
+ if (flowSource === "user" && !fs.existsSync(yamlPath)) {
357
+ for (const rel of [PIPELINES_DIR, LEGACY_PIPELINES_DIR]) {
358
+ const wsDir = path.join(root, rel, flowId);
359
+ const wsYaml = path.join(wsDir, "flow.yaml");
360
+ if (fs.existsSync(wsYaml)) {
361
+ flowDir = wsDir;
362
+ yamlPath = wsYaml;
363
+ break;
364
+ }
365
+ }
366
+ }
367
+ if (flowSource === "workspace" && !fs.existsSync(yamlPath)) {
368
+ const legDir = path.join(root, LEGACY_PIPELINES_DIR, flowId);
369
+ const legYaml = path.join(legDir, "flow.yaml");
370
+ if (fs.existsSync(legYaml)) {
371
+ flowDir = legDir;
372
+ yamlPath = legYaml;
373
+ }
374
+ }
375
+ if (!fs.existsSync(yamlPath)) {
376
+ return { error: "Flow not found: " + flowId };
377
+ }
378
+ try {
379
+ const flowYaml = fs.readFileSync(yamlPath, "utf-8");
380
+ return { flowYaml };
381
+ } catch (e) {
382
+ return { error: (e && e.message) || String(e) };
383
+ }
384
+ }
385
+
386
+ /**
387
+ * 解析 flow.yaml 绝对路径(与 readFlowJson 一致;user 含 workspace 回退)。
388
+ * @param {{ archived?: boolean }} [options]
389
+ * @returns {{ path: string } | { error: string }}
390
+ */
391
+ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {}) {
392
+ const root = path.resolve(workspaceRoot);
393
+ const archived = Boolean(options.archived);
394
+ let yamlPath;
395
+ if (archived) {
396
+ if (flowSource === "builtin") {
397
+ return { error: t("catalog.builtin_flow_archive_path_not_supported") };
398
+ }
399
+ if (flowSource === "user") {
400
+ yamlPath = path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
401
+ } else if (flowSource === "workspace") {
402
+ yamlPath = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
403
+ if (!fs.existsSync(yamlPath)) {
404
+ const altLeg = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
405
+ if (fs.existsSync(altLeg)) yamlPath = altLeg;
406
+ }
407
+ } else {
408
+ return { error: "Invalid flowSource" };
409
+ }
410
+ if (!fs.existsSync(yamlPath)) {
411
+ return { error: "Flow not found: " + flowId };
412
+ }
413
+ return { path: yamlPath };
414
+ }
415
+
416
+ if (flowSource === "builtin") {
417
+ yamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
418
+ } else if (flowSource === "user") {
419
+ yamlPath = path.join(getUserPipelinesRoot(), flowId, "flow.yaml");
420
+ if (!fs.existsSync(yamlPath)) {
421
+ const alt = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
422
+ if (fs.existsSync(alt)) yamlPath = alt;
423
+ }
424
+ if (!fs.existsSync(yamlPath)) {
425
+ const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
426
+ if (fs.existsSync(altLeg)) yamlPath = altLeg;
427
+ }
428
+ } else if (flowSource === "workspace") {
429
+ yamlPath = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
430
+ if (!fs.existsSync(yamlPath)) {
431
+ const altLeg = path.join(root, LEGACY_PIPELINES_DIR, flowId, "flow.yaml");
432
+ if (fs.existsSync(altLeg)) yamlPath = altLeg;
433
+ }
434
+ } else {
435
+ return { error: "Invalid flowSource" };
436
+ }
437
+ if (!fs.existsSync(yamlPath)) {
438
+ return { error: "Flow not found: " + flowId };
439
+ }
440
+ return { path: yamlPath };
441
+ }
442
+
443
+ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {}) {
444
+ const root = path.resolve(workspaceRoot);
445
+ const archived = Boolean(opts.archived);
446
+ const fileName = nodeId.endsWith(".md") ? nodeId : `${nodeId}.md`;
447
+ const pathsToTry = [];
448
+ if (flowId && flowSource) {
449
+ if (flowSource === "builtin") {
450
+ pathsToTry.push(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes", fileName));
451
+ } else if (flowSource === "user") {
452
+ if (archived) {
453
+ pathsToTry.push(
454
+ path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName),
455
+ );
456
+ } else {
457
+ pathsToTry.push(path.join(getUserPipelinesRoot(), flowId, "nodes", fileName));
458
+ pathsToTry.push(path.join(root, PIPELINES_DIR, flowId, "nodes", fileName));
459
+ pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes", fileName));
460
+ }
461
+ } else if (flowSource === "workspace") {
462
+ if (archived) {
463
+ pathsToTry.push(path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
464
+ pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
465
+ } else {
466
+ pathsToTry.push(path.join(root, PIPELINES_DIR, flowId, "nodes", fileName));
467
+ pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes", fileName));
468
+ }
469
+ }
470
+ }
471
+ pathsToTry.push(path.join(root, PROJECT_NODES_DIR, fileName));
472
+ pathsToTry.push(path.join(root, LEGACY_NODES_DIR, fileName));
473
+ pathsToTry.push(path.join(PACKAGE_BUILTIN_NODES_DIR, fileName));
474
+ for (const filePath of pathsToTry) {
475
+ if (!fs.existsSync(filePath)) continue;
476
+ try {
477
+ const raw = fs.readFileSync(filePath, "utf-8");
478
+ const data = parseNodeFrontmatter(raw);
479
+ const content = raw.replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/, "").trim();
480
+ let type = "agent";
481
+ if (/^control/i.test(nodeId)) type = "control";
482
+ else if (/^provide/i.test(nodeId)) type = "provide";
483
+ else if (/^tool/i.test(nodeId)) type = "agent";
484
+ const strippedId =
485
+ nodeId
486
+ .replace(/\.md$/, "")
487
+ .replace(/^agent_?/i, "")
488
+ .replace(/^control_?/i, "")
489
+ .replace(/^provide_?/i, "")
490
+ .replace(/^tool_?/i, "") || nodeId;
491
+ const label = data.displayName ?? strippedId;
492
+ return {
493
+ type,
494
+ label,
495
+ displayName: data.displayName,
496
+ inputs: data.input,
497
+ outputs: data.output,
498
+ executionLogic: content || undefined,
499
+ description: data.description,
500
+ };
501
+ } catch (_) {}
502
+ }
503
+ return { error: "Node not found: " + nodeId };
504
+ }
505
+
506
+ /** 列出所有 pipeline(包内 builtin + ~/agentflow/pipelines + 项目内 .workspace/.cursor agentflow/pipelines);nodes 见 PROJECT_NODES_DIR / LEGACY_NODES_DIR */
507
+ export function listPipelines(workspaceRoot) {
508
+ const rows = listFlowsJson(workspaceRoot);
509
+ if (rows.length === 0) {
510
+ log.info(
511
+ "No pipelines found (no subdirs with flow.yaml under builtin, " +
512
+ USER_AGENTFLOW_PIPELINES_LABEL +
513
+ ", " +
514
+ PIPELINES_DIR +
515
+ " or " +
516
+ LEGACY_PIPELINES_DIR +
517
+ ").",
518
+ );
519
+ return;
520
+ }
521
+ const table = new Table({
522
+ head: [chalk.cyan(t("catalog.pipeline_header")), chalk.cyan(t("catalog.source_header")), chalk.cyan(t("catalog.apply_example_header"))],
523
+ colWidths: [24, 10, 48],
524
+ style: { head: [], border: ["grey"] },
525
+ });
526
+ for (const row of rows) {
527
+ const sourceLabel = row.source === "builtin" ? "builtin" : row.source === "workspace" ? "workspace" : "user";
528
+ table.push([row.id, sourceLabel, `agentflow apply ${row.id}`]);
529
+ }
530
+ log.info("\n" + chalk.bold("Pipelines"));
531
+ log.info(table.toString());
532
+ }