@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.
- package/README.md +20 -33
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +167 -88
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +276 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +268 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +10 -30
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +43 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +153 -283
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1106 -18013
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DPbR2UaN.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +8 -47
- package/skills/agentflow-cli/SKILL.md +158 -25
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +668 -18
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +7 -248
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +4 -73
- package/skills/agentflow-flow-recipes/SKILL.md +7 -18
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +3 -54
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +5 -4
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-placeholder-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-BSCpd5la.js +0 -889
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
package/bin/lib/run-events.mjs
CHANGED
|
@@ -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 {
|
|
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
|
|
188
|
-
if (
|
|
189
|
-
const flowDir =
|
|
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
|
+
}
|