@fieldwangai/agentflow 0.1.153 → 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 (192) 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 +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 +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 +1087 -18122
  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-routes.mjs +3088 -0
  57. package/bin/lib/workspace-server.mjs +6199 -0
  58. package/bin/lib/workspace-state.mjs +331 -0
  59. package/bin/lib/workspace.mjs +2 -1
  60. package/builtin/nodes/agent_subAgent.md +1 -0
  61. package/builtin/nodes/control_agent_toBool.md +2 -0
  62. package/builtin/nodes/control_anyOne.md +2 -0
  63. package/builtin/nodes/control_cancelled.md +2 -0
  64. package/builtin/nodes/control_cd_workspace.md +5 -11
  65. package/builtin/nodes/control_delay.md +2 -0
  66. package/builtin/nodes/control_end.md +2 -0
  67. package/builtin/nodes/control_if.md +1 -0
  68. package/builtin/nodes/control_interval_loop.md +2 -0
  69. package/builtin/nodes/control_load_mcp.md +24 -0
  70. package/builtin/nodes/control_load_skills.md +8 -23
  71. package/builtin/nodes/control_start.md +2 -0
  72. package/builtin/nodes/control_toBool.md +2 -0
  73. package/builtin/nodes/control_user_workspace.md +2 -0
  74. package/builtin/nodes/control_wait_until.md +2 -0
  75. package/builtin/nodes/display_ascii.md +1 -0
  76. package/builtin/nodes/display_chart.md +1 -0
  77. package/builtin/nodes/display_html.md +1 -0
  78. package/builtin/nodes/display_image.md +1 -0
  79. package/builtin/nodes/display_markdown.md +1 -0
  80. package/builtin/nodes/display_mermaid.md +1 -0
  81. package/builtin/nodes/display_react_app.md +1 -0
  82. package/builtin/nodes/display_table.md +1 -0
  83. package/builtin/nodes/provide_bool.md +1 -0
  84. package/builtin/nodes/provide_file.md +1 -0
  85. package/builtin/nodes/provide_password.md +1 -0
  86. package/builtin/nodes/provide_str.md +1 -0
  87. package/builtin/nodes/tool_display_share_link.md +1 -0
  88. package/builtin/nodes/tool_get_env.md +2 -0
  89. package/builtin/nodes/tool_git_checkout.md +1 -0
  90. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  92. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  93. package/builtin/nodes/tool_jenkins_build.md +2 -0
  94. package/builtin/nodes/tool_load_key.md +2 -0
  95. package/builtin/nodes/tool_nodejs.md +17 -19
  96. package/builtin/nodes/tool_print.md +2 -0
  97. package/builtin/nodes/tool_save_key.md +2 -0
  98. package/builtin/nodes/tool_set_run_env.md +1 -0
  99. package/builtin/nodes/tool_user_ask.md +2 -0
  100. package/builtin/nodes/tool_user_check.md +2 -0
  101. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  102. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  103. package/builtin/nodes/workspace_one_click_task.md +44 -0
  104. package/builtin/nodes/workspace_run.md +16 -0
  105. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  106. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  107. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  108. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  109. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  110. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  111. package/builtin/pipelines/new/workspace.flow.js +91 -0
  112. package/builtin/pipelines/new/workspace.layout.json +70 -0
  113. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
  114. package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
  115. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  116. package/builtin/web-ui/dist/index.html +2 -2
  117. package/package.json +2 -1
  118. package/reference/flow-control-capabilities.md +77 -158
  119. package/reference/flow-layout.md +1 -1
  120. package/reference/flow-prompt-handler-check.md +2 -2
  121. package/skills/agentflow-author-flow/SKILL.md +1 -1
  122. package/skills/agentflow-cli/SKILL.md +139 -24
  123. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  124. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  125. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  126. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  127. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  128. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  129. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  130. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  131. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  132. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  133. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  134. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  135. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  136. package/skills/agentflow-node-reference/SKILL.md +2 -2
  137. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  138. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  139. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  140. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  141. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  142. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  143. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  144. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  145. package/bin/lib/api-runner.mjs +0 -387
  146. package/bin/lib/apply.mjs +0 -903
  147. package/bin/lib/composer-flow-instances.mjs +0 -68
  148. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  149. package/bin/lib/composer-flow-validate.mjs +0 -47
  150. package/bin/lib/composer-model-router.mjs +0 -185
  151. package/bin/lib/composer-node-schema.mjs +0 -303
  152. package/bin/lib/composer-planner.mjs +0 -751
  153. package/bin/lib/composer-script-ops.mjs +0 -233
  154. package/bin/lib/flow-static-preview.mjs +0 -104
  155. package/bin/lib/hub-login.mjs +0 -54
  156. package/bin/lib/hub-publish.mjs +0 -159
  157. package/bin/lib/hub-remote.mjs +0 -189
  158. package/bin/lib/hub.mjs +0 -299
  159. package/bin/lib/jenkins.mjs +0 -380
  160. package/bin/lib/node-execute.mjs +0 -539
  161. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  162. package/bin/lib/runtime-context.mjs +0 -243
  163. package/bin/lib/scheduler.mjs +0 -601
  164. package/bin/lib/ui-print.mjs +0 -94
  165. package/bin/pipeline/build-node-prompt.mjs +0 -271
  166. package/bin/pipeline/check-cache.mjs +0 -191
  167. package/bin/pipeline/check-flow.mjs +0 -543
  168. package/bin/pipeline/collect-nodes.mjs +0 -212
  169. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  170. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  171. package/bin/pipeline/gc.mjs +0 -129
  172. package/bin/pipeline/get-env.mjs +0 -59
  173. package/bin/pipeline/get-resolved-values.mjs +0 -344
  174. package/bin/pipeline/load-key.mjs +0 -62
  175. package/bin/pipeline/parse-flow.mjs +0 -708
  176. package/bin/pipeline/post-process-control-if.mjs +0 -23
  177. package/bin/pipeline/post-process-node.mjs +0 -490
  178. package/bin/pipeline/pre-process-node.mjs +0 -1430
  179. package/bin/pipeline/resolve-inputs.mjs +0 -201
  180. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  181. package/bin/pipeline/save-key.mjs +0 -93
  182. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  183. package/bin/pipeline/validate-for-ui.mjs +0 -234
  184. package/bin/pipeline/validate-script-output.mjs +0 -130
  185. package/bin/pipeline/write-result.mjs +0 -182
  186. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  187. package/builtin/pipelines/new/flow.yaml +0 -545
  188. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  189. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  190. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  191. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  192. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,8 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { machineReadable } from "./log.mjs";
4
3
  import { RUN_LOG_REL } from "./paths.mjs";
5
- import { runNodeScript } from "./pipeline-scripts.mjs";
6
4
  import { getRunDir } from "./workspace.mjs";
7
5
 
8
6
  /**
@@ -19,67 +17,3 @@ export function appendRunLogLine(workspaceRoot, flowName, uuid, tag, message) {
19
17
  fs.appendFileSync(logPath, line, "utf-8");
20
18
  } catch (_) {}
21
19
  }
22
-
23
- /** 发送 CLI 事件:写 run log,且 machineReadable 时向 stdout 输出一行 JSON */
24
- export function emitEvent(workspaceRoot, flowName, uuid, payload) {
25
- appendRunLogLine(workspaceRoot, flowName, uuid, "cli", payload);
26
- if (machineReadable && workspaceRoot && flowName && uuid) {
27
- const line = JSON.stringify({ ...payload, ts: new Date().toISOString() }) + "\n";
28
- /* 同步写入,减少 pipe 缓冲导致 UI 长时间收不到首行 */
29
- try {
30
- fs.writeSync(1, Buffer.from(line, "utf8"));
31
- } catch {
32
- process.stdout.write(line);
33
- }
34
- }
35
- }
36
-
37
- /** 从 run 的 memory.md 读取 runStartTime */
38
- export function readRunStartTime(workspaceRoot, flowName, uuid) {
39
- const memoryPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "memory.md");
40
- if (!fs.existsSync(memoryPath)) return null;
41
- const content = fs.readFileSync(memoryPath, "utf-8");
42
- for (const line of (content || "").split(/\r?\n/)) {
43
- const idx = line.indexOf(": ");
44
- if (idx <= 0) continue;
45
- const k = line.slice(0, idx).trim();
46
- if (k !== "runStartTime") continue;
47
- const v = line.slice(idx + 2).trim();
48
- const n = parseInt(String(v), 10);
49
- return Number.isFinite(n) && n > 0 ? n : null;
50
- }
51
- return null;
52
- }
53
-
54
- /** 从 run 的 memory.md 读取 totalExecutedMs */
55
- export function readTotalExecutedMs(workspaceRoot, flowName, uuid) {
56
- const memoryPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "memory.md");
57
- if (!fs.existsSync(memoryPath)) return 0;
58
- const content = fs.readFileSync(memoryPath, "utf-8");
59
- for (const line of (content || "").split(/\r?\n/)) {
60
- const idx = line.indexOf(": ");
61
- if (idx <= 0) continue;
62
- const k = line.slice(0, idx).trim();
63
- if (k !== "totalExecutedMs") continue;
64
- const v = line.slice(idx + 2).trim();
65
- const n = parseInt(String(v), 10);
66
- return Number.isFinite(n) && n >= 0 ? n : 0;
67
- }
68
- return 0;
69
- }
70
-
71
- export function saveTotalExecutedMs(workspaceRoot, flowName, uuid, totalExecutedMs) {
72
- runNodeScript(workspaceRoot, "save-key.mjs", [workspaceRoot, flowName, uuid, "totalExecutedMs", String(totalExecutedMs)], {
73
- captureStdout: true,
74
- });
75
- }
76
-
77
- export function ensureRunStartTime(workspaceRoot, flowName, uuid) {
78
- const existing = readRunStartTime(workspaceRoot, flowName, uuid);
79
- if (existing != null) return existing;
80
- const runStartTime = Date.now();
81
- runNodeScript(workspaceRoot, "save-key.mjs", [workspaceRoot, flowName, uuid, "runStartTime", String(runStartTime)], {
82
- captureStdout: true,
83
- });
84
- return runStartTime;
85
- }
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { getFlowYamlAbs } from "./catalog-flows.mjs";
3
+ import { resolveFlowDirAbs } from "./catalog-flows.mjs";
4
4
 
5
5
  export const SCHEDULE_CONFIG_FILENAME = "schedule.json";
6
6
  export const SCHEDULE_STATE_FILENAME = "schedule-state.json";
@@ -184,9 +184,9 @@ export function writeScheduleState(workspaceRoot, flowId, flowSource, state, opt
184
184
  }
185
185
 
186
186
  export function getFlowSchedulePaths(workspaceRoot, flowId, flowSource, opts = {}) {
187
- const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
188
- if (yamlRes.error || !yamlRes.path) return { error: yamlRes.error || "Could not resolve flow.yaml" };
189
- const flowDir = path.dirname(yamlRes.path);
187
+ const dirRes = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, opts);
188
+ if (dirRes.error || !dirRes.dir) return { error: dirRes.error || "Could not resolve flow dir" };
189
+ const flowDir = dirRes.dir;
190
190
  return {
191
191
  flowDir,
192
192
  configPath: path.join(flowDir, SCHEDULE_CONFIG_FILENAME),
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Bundled skills 使用的稳定本地 runtime facade。
3
+ *
4
+ * Skill 可以装在任意目录,不能通过 `../../../bin/lib` 猜 AgentFlow 源码位置;它只定位
5
+ * 已安装的 @fieldwangai/agentflow 包,再从这一处入口取本地 DSL、节点包和 marketplace
6
+ * 能力。不要让 skill 继续直接依赖内部模块路径。
7
+ */
8
+ export {
9
+ readWorkspaceDesign,
10
+ writeWorkspaceGraphFiles,
11
+ } from "./workspace-flow-store.mjs";
12
+
13
+ export {
14
+ WORKSPACE_STATE_FILENAME,
15
+ mergeWorkspaceState,
16
+ splitWorkspaceGraph,
17
+ } from "./workspace-state.mjs";
18
+
19
+ export {
20
+ marketplaceDependenciesFromSource,
21
+ rewriteFlowLocalPackageImports,
22
+ scanFlowLocalPackages,
23
+ } from "./flow-dsl/packages.mjs";
24
+
25
+ export {
26
+ listMarketplacePackages,
27
+ parseMarketplaceDefinitionId,
28
+ publishNodePackageArchive,
29
+ } from "./marketplace.mjs";
30
+
31
+ export {
32
+ createNodePackageArchive,
33
+ inspectNodePackageArchive,
34
+ inspectNodePackageDirectory,
35
+ } from "./node-package-archive.mjs";
@@ -0,0 +1,274 @@
1
+ /**
2
+ * 服务端启动前的数据迁移。
3
+ *
4
+ * 迁移直接在物理存储上执行,不经过用户 Token:服务端已经知道 AGENTFLOW_HOME、全部用户
5
+ * pipelines 和当前 workspaceRoot。这里只自动做无损转换;需要 `--allow-loss` 的老 YAML
6
+ * 永远留给人工决定。
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+
11
+ import { migrateFlowDirToDsl } from "./flow-dsl/cli.mjs";
12
+ import {
13
+ ARCHIVED_PIPELINES_DIR_NAME,
14
+ LEGACY_PIPELINES_DIR,
15
+ PIPELINES_DIR,
16
+ getAgentflowDataRoot,
17
+ isFlowDir,
18
+ } from "./paths.mjs";
19
+
20
+ export const STORAGE_SCHEMA_VERSION = 1;
21
+ export const WORKSPACE_FLOW_DSL_MIGRATION_ID = "workspace-flow-dsl-v1";
22
+
23
+ const STATE_FILENAME = "storage-migrations.json";
24
+ const LOCK_FILENAME = "storage-migrations.lock";
25
+ const GRAPH_FILENAME = "workspace.graph.json";
26
+
27
+ function readState(filePath) {
28
+ if (!fs.existsSync(filePath)) return { schemaVersion: 0, migrations: {} };
29
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
30
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31
+ throw new Error(`Invalid storage migration state: ${filePath}`);
32
+ }
33
+ return {
34
+ ...parsed,
35
+ schemaVersion: Number(parsed.schemaVersion || 0),
36
+ migrations: parsed.migrations && typeof parsed.migrations === "object" && !Array.isArray(parsed.migrations)
37
+ ? parsed.migrations
38
+ : {},
39
+ };
40
+ }
41
+
42
+ function writeJsonAtomic(filePath, value) {
43
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
44
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
45
+ fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
46
+ fs.renameSync(tempPath, filePath);
47
+ }
48
+
49
+ function acquireLock(lockPath) {
50
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
51
+ let fd;
52
+ try {
53
+ fd = fs.openSync(lockPath, "wx");
54
+ fs.writeFileSync(fd, `${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() })}\n`, "utf-8");
55
+ } catch (error) {
56
+ if (error?.code === "EEXIST") {
57
+ throw new Error(`Storage migration is already running: ${lockPath}`);
58
+ }
59
+ throw error;
60
+ }
61
+ return () => {
62
+ try { fs.closeSync(fd); } catch { /* already closed */ }
63
+ try { fs.rmSync(lockPath, { force: true }); } catch { /* a stale lock is safer than deleting another path */ }
64
+ };
65
+ }
66
+
67
+ function listFlowDirs(pipelinesRoot, scope) {
68
+ const root = path.resolve(pipelinesRoot);
69
+ if (!fs.existsSync(root)) return [];
70
+ let entries;
71
+ try {
72
+ entries = fs.readdirSync(root, { withFileTypes: true });
73
+ } catch (error) {
74
+ throw new Error(`Cannot list pipeline root ${root}: ${error?.message || String(error)}`);
75
+ }
76
+ const flows = [];
77
+ for (const entry of entries) {
78
+ if (!entry.isDirectory() || entry.name === ARCHIVED_PIPELINES_DIR_NAME) continue;
79
+ const flowDir = path.join(root, entry.name);
80
+ if (isFlowDir(flowDir)) flows.push({ flowId: entry.name, flowDir, archived: false, scope });
81
+ }
82
+ const archivedRoot = path.join(root, ARCHIVED_PIPELINES_DIR_NAME);
83
+ if (fs.existsSync(archivedRoot)) {
84
+ for (const entry of fs.readdirSync(archivedRoot, { withFileTypes: true })) {
85
+ if (!entry.isDirectory()) continue;
86
+ const flowDir = path.join(archivedRoot, entry.name);
87
+ if (isFlowDir(flowDir)) flows.push({ flowId: entry.name, flowDir, archived: true, scope });
88
+ }
89
+ }
90
+ return flows;
91
+ }
92
+
93
+ function dataPipelineRoots(dataRoot) {
94
+ const roots = [{ path: path.join(dataRoot, "pipelines"), scope: "user:legacy" }];
95
+ const usersRoot = path.join(dataRoot, "users");
96
+ if (!fs.existsSync(usersRoot)) return roots;
97
+ for (const entry of fs.readdirSync(usersRoot, { withFileTypes: true })) {
98
+ if (!entry.isDirectory()) continue;
99
+ roots.push({ path: path.join(usersRoot, entry.name, "pipelines"), scope: `user:${entry.name}` });
100
+ }
101
+ return roots;
102
+ }
103
+
104
+ function workspacePipelineRoots(workspaceRoot) {
105
+ return [
106
+ { path: path.join(workspaceRoot, PIPELINES_DIR), scope: "workspace" },
107
+ { path: path.join(workspaceRoot, LEGACY_PIPELINES_DIR), scope: "workspace:legacy" },
108
+ ];
109
+ }
110
+
111
+ function uniqueFlows(roots) {
112
+ const seen = new Set();
113
+ const flows = [];
114
+ for (const root of roots) {
115
+ for (const flow of listFlowDirs(root.path, root.scope)) {
116
+ let key = path.resolve(flow.flowDir);
117
+ try { key = fs.realpathSync(flow.flowDir); } catch { /* keep resolved path */ }
118
+ if (seen.has(key)) continue;
119
+ seen.add(key);
120
+ flows.push(flow);
121
+ }
122
+ }
123
+ return flows.sort((a, b) => a.flowDir.localeCompare(b.flowDir));
124
+ }
125
+
126
+ function backupLegacyGraph(flowDir) {
127
+ const graphPath = path.join(flowDir, GRAPH_FILENAME);
128
+ if (!fs.existsSync(graphPath)) return "";
129
+ const backupPath = path.join(
130
+ flowDir,
131
+ ".agentflow-migrations",
132
+ WORKSPACE_FLOW_DSL_MIGRATION_ID,
133
+ GRAPH_FILENAME,
134
+ );
135
+ if (!fs.existsSync(backupPath)) {
136
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
137
+ fs.copyFileSync(graphPath, backupPath, fs.constants.COPYFILE_EXCL);
138
+ }
139
+ return backupPath;
140
+ }
141
+
142
+ function migrateFlows(flows, { marketplaceRoot = "" } = {}) {
143
+ const rows = [];
144
+ for (const flow of flows) {
145
+ try {
146
+ const backup = backupLegacyGraph(flow.flowDir);
147
+ const result = migrateFlowDirToDsl(flow.flowDir, { marketplaceRoot });
148
+ const lost = [...(result.dropped || []), ...(result.droppedEdges || [])]
149
+ .filter((item) => !item?.benign)
150
+ .map((item) => item.id || `${item.source}->${item.target}`);
151
+ rows.push({
152
+ flowId: flow.flowId,
153
+ scope: flow.scope,
154
+ archived: flow.archived,
155
+ flowDir: flow.flowDir,
156
+ source: result.source || result.format,
157
+ result: result.format === "yaml" ? "needs-decision"
158
+ : result.migrated ? "migrated-dsl"
159
+ : result.leftYaml ? "migrated-json"
160
+ : result.format === "dsl" ? "already-current"
161
+ : result.format === "json" ? "degraded-json"
162
+ : "empty",
163
+ ...(backup ? { backup } : {}),
164
+ ...(result.degradedReason ? { degradedReason: result.degradedReason } : {}),
165
+ ...(lost.length ? { lost } : {}),
166
+ ...(result.warnings?.length ? { warnings: result.warnings } : {}),
167
+ });
168
+ } catch (error) {
169
+ rows.push({
170
+ flowId: flow.flowId,
171
+ scope: flow.scope,
172
+ archived: flow.archived,
173
+ flowDir: flow.flowDir,
174
+ result: "failed",
175
+ error: error?.message || String(error),
176
+ });
177
+ }
178
+ }
179
+ return rows;
180
+ }
181
+
182
+ function summarizeRows(rows) {
183
+ const count = (result) => rows.filter((row) => row.result === result).length;
184
+ return {
185
+ total: rows.length,
186
+ migrated: count("migrated-dsl") + count("migrated-json"),
187
+ alreadyCurrent: count("already-current"),
188
+ degraded: count("degraded-json") + count("migrated-json"),
189
+ needsDecision: rows.filter((row) => row.result === "needs-decision")
190
+ .map((row) => `${row.flowId} [${row.scope}${row.archived ? ":archived" : ""}]`),
191
+ failed: rows.filter((row) => row.result === "failed")
192
+ .map((row) => `${row.flowId} [${row.scope}${row.archived ? ":archived" : ""}]: ${row.error}`),
193
+ };
194
+ }
195
+
196
+ /**
197
+ * 根据独立的存储结构版本执行一次启动迁移。重复启动幂等;同一个 dataRoot 下换了新的
198
+ * workspaceRoot 时,只补迁这个新 Workspace,不会重扫已经完成的目标。
199
+ */
200
+ export function runStartupStorageMigrations({
201
+ workspaceRoot,
202
+ dataRoot = getAgentflowDataRoot(),
203
+ force = false,
204
+ } = {}) {
205
+ const resolvedDataRoot = path.resolve(dataRoot);
206
+ const resolvedWorkspaceRoot = path.resolve(workspaceRoot || process.cwd());
207
+ const adminRoot = path.join(resolvedDataRoot, "admin");
208
+ const statePath = path.join(adminRoot, STATE_FILENAME);
209
+ const lockPath = path.join(adminRoot, LOCK_FILENAME);
210
+ const releaseLock = acquireLock(lockPath);
211
+ try {
212
+ const state = readState(statePath);
213
+ const migration = state.migrations[WORKSPACE_FLOW_DSL_MIGRATION_ID] || { targets: {} };
214
+ const migrationVersion = Number(migration.version || 0);
215
+ const targets = migration.targets && typeof migration.targets === "object" ? migration.targets : {};
216
+ const definitions = [
217
+ {
218
+ key: `data:${resolvedDataRoot}`,
219
+ kind: "data",
220
+ root: resolvedDataRoot,
221
+ pipelineRoots: dataPipelineRoots(resolvedDataRoot),
222
+ },
223
+ {
224
+ key: `workspace:${resolvedWorkspaceRoot}`,
225
+ kind: "workspace",
226
+ root: resolvedWorkspaceRoot,
227
+ pipelineRoots: workspacePipelineRoots(resolvedWorkspaceRoot),
228
+ },
229
+ ];
230
+ const attempted = [];
231
+ const skipped = [];
232
+ for (const target of definitions) {
233
+ if (!force && migrationVersion >= STORAGE_SCHEMA_VERSION && targets[target.key]?.completed === true) {
234
+ skipped.push(target.key);
235
+ continue;
236
+ }
237
+ const rows = migrateFlows(uniqueFlows(target.pipelineRoots), { marketplaceRoot: resolvedWorkspaceRoot });
238
+ const summary = summarizeRows(rows);
239
+ const record = {
240
+ kind: target.kind,
241
+ root: target.root,
242
+ attemptedAt: new Date().toISOString(),
243
+ completed: summary.failed.length === 0,
244
+ summary,
245
+ rows,
246
+ };
247
+ if (record.completed) record.completedAt = record.attemptedAt;
248
+ targets[target.key] = record;
249
+ attempted.push(record);
250
+ }
251
+ const nextState = {
252
+ ...state,
253
+ schemaVersion: Math.max(Number(state.schemaVersion || 0), STORAGE_SCHEMA_VERSION),
254
+ updatedAt: new Date().toISOString(),
255
+ migrations: {
256
+ ...state.migrations,
257
+ [WORKSPACE_FLOW_DSL_MIGRATION_ID]: {
258
+ version: STORAGE_SCHEMA_VERSION,
259
+ targets,
260
+ },
261
+ },
262
+ };
263
+ writeJsonAtomic(statePath, nextState);
264
+ return {
265
+ schemaVersion: nextState.schemaVersion,
266
+ migrationId: WORKSPACE_FLOW_DSL_MIGRATION_ID,
267
+ statePath,
268
+ attempted,
269
+ skipped,
270
+ };
271
+ } finally {
272
+ releaseLock();
273
+ }
274
+ }