@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.
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/README.zh-CN.md +201 -0
- package/agents/agentflow-node-executor-code.md +32 -0
- package/agents/agentflow-node-executor-planning.md +32 -0
- package/agents/agentflow-node-executor-requirement.md +32 -0
- package/agents/agentflow-node-executor-test.md +32 -0
- package/agents/agentflow-node-executor-ui.md +32 -0
- package/agents/agentflow-node-executor.md +32 -0
- package/agents/agents.json +8 -0
- package/agents/en/agentflow-node-executor.md +32 -0
- package/agents/zh/agentflow-node-executor.md +32 -0
- package/bin/agentflow.mjs +52 -0
- package/bin/ensure-workspace-reference.mjs +35 -0
- package/bin/lib/agent-runners.mjs +1199 -0
- package/bin/lib/agents-path.mjs +61 -0
- package/bin/lib/api-runner.mjs +361 -0
- package/bin/lib/apply.mjs +852 -0
- package/bin/lib/catalog-agents.mjs +300 -0
- package/bin/lib/catalog-flows.mjs +532 -0
- package/bin/lib/composer-agent.mjs +884 -0
- package/bin/lib/composer-flow-instances.mjs +68 -0
- package/bin/lib/composer-flow-skeleton.mjs +334 -0
- package/bin/lib/composer-flow-validate.mjs +47 -0
- package/bin/lib/composer-log.mjs +197 -0
- package/bin/lib/composer-model-router.mjs +160 -0
- package/bin/lib/composer-node-schema.mjs +299 -0
- package/bin/lib/composer-planner.mjs +749 -0
- package/bin/lib/composer-script-ops.mjs +233 -0
- package/bin/lib/composer-skill-router.mjs +384 -0
- package/bin/lib/flow-import.mjs +305 -0
- package/bin/lib/flow-normalize.mjs +71 -0
- package/bin/lib/flow-write.mjs +395 -0
- package/bin/lib/help.mjs +139 -0
- package/bin/lib/hub-login.mjs +54 -0
- package/bin/lib/hub-publish.mjs +159 -0
- package/bin/lib/hub-remote.mjs +189 -0
- package/bin/lib/hub.mjs +299 -0
- package/bin/lib/i18n.mjs +233 -0
- package/bin/lib/locales/en.json +344 -0
- package/bin/lib/locales/zh.json +344 -0
- package/bin/lib/log.mjs +37 -0
- package/bin/lib/main.mjs +611 -0
- package/bin/lib/model-config.mjs +118 -0
- package/bin/lib/model-lists.mjs +188 -0
- package/bin/lib/node-exec-context.mjs +336 -0
- package/bin/lib/node-execute.mjs +513 -0
- package/bin/lib/normalize-node-tool-command.mjs +97 -0
- package/bin/lib/paths.mjs +216 -0
- package/bin/lib/pipeline-scripts.mjs +41 -0
- package/bin/lib/recent-runs.mjs +173 -0
- package/bin/lib/run-apply-active-lock.mjs +82 -0
- package/bin/lib/run-events.mjs +85 -0
- package/bin/lib/run-node-statuses-from-disk.mjs +85 -0
- package/bin/lib/schedule-config.mjs +227 -0
- package/bin/lib/scheduler.mjs +312 -0
- package/bin/lib/table.mjs +4 -0
- package/bin/lib/terminal.mjs +42 -0
- package/bin/lib/ui-print.mjs +94 -0
- package/bin/lib/ui-server.mjs +2113 -0
- package/bin/lib/workspace-tree.mjs +266 -0
- package/bin/lib/workspace.mjs +180 -0
- package/bin/pipeline/build-node-prompt.mjs +179 -0
- package/bin/pipeline/check-cache.mjs +191 -0
- package/bin/pipeline/check-flow.mjs +543 -0
- package/bin/pipeline/collect-nodes.mjs +212 -0
- package/bin/pipeline/compute-cache-md5.mjs +177 -0
- package/bin/pipeline/ensure-run-dir.mjs +71 -0
- package/bin/pipeline/extract-thinking.mjs +308 -0
- package/bin/pipeline/gc.mjs +129 -0
- package/bin/pipeline/get-env.mjs +83 -0
- package/bin/pipeline/get-exec-id.mjs +145 -0
- package/bin/pipeline/get-ready-nodes.mjs +435 -0
- package/bin/pipeline/get-resolved-values.mjs +337 -0
- package/bin/pipeline/load-key.mjs +62 -0
- package/bin/pipeline/parse-bool.mjs +33 -0
- package/bin/pipeline/parse-flow.mjs +698 -0
- package/bin/pipeline/post-process-control-if.mjs +23 -0
- package/bin/pipeline/post-process-node.mjs +490 -0
- package/bin/pipeline/pre-process-node.mjs +449 -0
- package/bin/pipeline/resolve-inputs.mjs +201 -0
- package/bin/pipeline/run-log.mjs +34 -0
- package/bin/pipeline/run-tool-nodejs.mjs +160 -0
- package/bin/pipeline/save-key.mjs +93 -0
- package/bin/pipeline/snapshot-prior-round.mjs +70 -0
- package/bin/pipeline/validate-flow.mjs +825 -0
- package/bin/pipeline/validate-for-ui.mjs +226 -0
- package/bin/pipeline/validate-script-output.mjs +130 -0
- package/bin/pipeline/write-result.mjs +182 -0
- package/builtin/nodes/agent_subAgent.md +14 -0
- package/builtin/nodes/control_agent_toBool.md +20 -0
- package/builtin/nodes/control_anyOne.md +17 -0
- package/builtin/nodes/control_end.md +11 -0
- package/builtin/nodes/control_if.md +20 -0
- package/builtin/nodes/control_start.md +11 -0
- package/builtin/nodes/control_toBool.md +21 -0
- package/builtin/nodes/provide_file.md +11 -0
- package/builtin/nodes/provide_str.md +11 -0
- package/builtin/nodes/tool_get_env.md +14 -0
- package/builtin/nodes/tool_load_key.md +20 -0
- package/builtin/nodes/tool_nodejs.md +40 -0
- package/builtin/nodes/tool_print.md +14 -0
- package/builtin/nodes/tool_save_key.md +20 -0
- package/builtin/nodes/tool_user_ask.md +23 -0
- package/builtin/nodes/tool_user_check.md +22 -0
- package/builtin/pipelines/module-migrate/flow.yaml +819 -0
- package/builtin/pipelines/module-migrate/scripts/check_imports.mjs +700 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Makefile +362 -0
- 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
- 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
- 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
- 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
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/tree_sitter_kotlin_binding.node.d +1 -0
- 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
- 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
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/tree_sitter_kotlin_binding.node +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/binding.Makefile +6 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/gyp-mac-tool +768 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
- 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
- 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
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/tree_sitter_kotlin_binding.target.mk +203 -0
- package/builtin/pipelines/new/flow.yaml +545 -0
- package/builtin/pipelines/new/scripts/check-flow.mjs +9 -0
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +211 -0
- package/builtin/pipelines/scripts/adjust-node-positions.mjs +113 -0
- package/builtin/web-ui/dist/agentflow-icon.svg +23 -0
- package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +1 -0
- package/builtin/web-ui/dist/assets/index-DkkhNESc.js +190 -0
- package/builtin/web-ui/dist/index.html +24 -0
- package/package.json +67 -0
- package/reference/flow-control-capabilities.md +274 -0
- package/reference/flow-layout.md +84 -0
- package/reference/flow-prompt-handler-check.md +12 -0
- package/reference/flow-result-semantics.md +14 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 收集所有节点(内置 + 当前流水线)的元数据,按 tool_nodejs 约定向 stdout 输出一行 JSON。
|
|
4
|
+
* 用法:agentflow apply -ai collect-nodes <workspaceRoot> <flowName> [runDir]
|
|
5
|
+
* 输出(仅 stdout 一行):{ "err_code": 0, "message": { "result": "<节点元数据 markdown>" } };err_code 0=成功 1=失败,无 next。
|
|
6
|
+
* 不写任何文件,由 agentflow apply -ai run-tool-nodejs 根据 message 写入 output。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import yaml from "js-yaml";
|
|
13
|
+
|
|
14
|
+
import { LEGACY_NODES_DIR, PIPELINES_DIR, PROJECT_NODES_DIR } from "../lib/paths.mjs";
|
|
15
|
+
import { getFlowDir } from "../lib/workspace.mjs";
|
|
16
|
+
|
|
17
|
+
const __dirnameCollect = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const PACKAGE_BUILTIN_NODES_DIR = path.join(path.resolve(__dirnameCollect, "..", ".."), "builtin", "nodes");
|
|
19
|
+
|
|
20
|
+
function extractFrontmatter(raw) {
|
|
21
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
22
|
+
return m ? m[1] : "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseDescription(fm) {
|
|
26
|
+
const match = fm.match(/\bdescription:\s*["']?([^"'\n#][^\n]*)["']?/);
|
|
27
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, "") : "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseDisplayName(fm) {
|
|
31
|
+
const match = fm.match(/\bdisplayName:\s*["']?([^"'\n#][^\n]*)["']?/);
|
|
32
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, "") : "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 解析 frontmatter 中的 input/output 数组,返回 [{ type, name, defaultOrValue }, ...] */
|
|
36
|
+
function parseSlots(fm, key) {
|
|
37
|
+
const slots = [];
|
|
38
|
+
const re = new RegExp(`\\b${key}:\\s*\\n([\\s\\S]*?)(?=\\n[a-zA-Z_][a-zA-Z0-9_]*\\s*:|---|$)`, "m");
|
|
39
|
+
const blockMatch = fm.match(re);
|
|
40
|
+
if (!blockMatch) return slots;
|
|
41
|
+
const block = blockMatch[1];
|
|
42
|
+
let current = {};
|
|
43
|
+
for (const line of block.split("\n")) {
|
|
44
|
+
const typeMatch = line.match(/^\s*-\s+type:\s*["']?([^"'\n]*)["']?/);
|
|
45
|
+
if (typeMatch) {
|
|
46
|
+
if (current.type) slots.push({ ...current });
|
|
47
|
+
current = { type: typeMatch[1].trim() };
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const nameMatch = line.match(/^\s+name:\s*["']?([^"'\n]*)["']?/);
|
|
51
|
+
if (nameMatch) {
|
|
52
|
+
current.name = nameMatch[1].trim();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const defaultMatch = line.match(/^\s+(?:default|value):\s*(.*)$/);
|
|
56
|
+
if (defaultMatch) {
|
|
57
|
+
current.defaultOrValue = defaultMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
58
|
+
slots.push({ ...current });
|
|
59
|
+
current = {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (current.type) slots.push({ ...current });
|
|
63
|
+
return slots;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readNodeMeta(filePath) {
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(filePath)) return null;
|
|
69
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
70
|
+
const fm = extractFrontmatter(raw);
|
|
71
|
+
return {
|
|
72
|
+
description: parseDescription(fm),
|
|
73
|
+
displayName: parseDisplayName(fm),
|
|
74
|
+
input: parseSlots(fm, "input"),
|
|
75
|
+
output: parseSlots(fm, "output"),
|
|
76
|
+
};
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 从 flow.yaml 的 instances 得到节点列表,每项 { id, label, definitionId, input, output },input/output 为 [{ type, name, defaultOrValue }] */
|
|
83
|
+
function loadFlowYamlNodes(flowDir) {
|
|
84
|
+
const flowPath = path.join(flowDir, "flow.yaml");
|
|
85
|
+
if (!fs.existsSync(flowPath)) return [];
|
|
86
|
+
try {
|
|
87
|
+
const raw = fs.readFileSync(flowPath, "utf-8");
|
|
88
|
+
const data = yaml.load(raw);
|
|
89
|
+
const instances = data?.instances && typeof data.instances === "object" ? data.instances : {};
|
|
90
|
+
return Object.entries(instances).map(([id, inst]) => {
|
|
91
|
+
const inp = Array.isArray(inst.input) ? inst.input : [];
|
|
92
|
+
const out = Array.isArray(inst.output) ? inst.output : [];
|
|
93
|
+
const input = inp.map((s) => ({
|
|
94
|
+
type: (s && s.type != null) ? String(s.type).trim() : "",
|
|
95
|
+
name: (s && s.name != null) ? String(s.name).trim() : "",
|
|
96
|
+
defaultOrValue: (s && (s.value != null || s.default != null)) ? String(s.value ?? s.default ?? "").trim() : "",
|
|
97
|
+
}));
|
|
98
|
+
const output = out.map((s) => ({
|
|
99
|
+
type: (s && s.type != null) ? String(s.type).trim() : "",
|
|
100
|
+
name: (s && s.name != null) ? String(s.name).trim() : "",
|
|
101
|
+
defaultOrValue: (s && (s.value != null || s.default != null)) ? String(s.value ?? s.default ?? "").trim() : "",
|
|
102
|
+
}));
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
label: (inst.label != null) ? String(inst.label) : id,
|
|
106
|
+
definitionId: (inst.definitionId != null) ? String(inst.definitionId) : id,
|
|
107
|
+
input,
|
|
108
|
+
output,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
} catch (_) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function main() {
|
|
117
|
+
const args = process.argv.slice(2);
|
|
118
|
+
if (args.length < 2) {
|
|
119
|
+
const payload = { err_code: 1, message: { result: "Usage: agentflow apply -ai collect-nodes <workspaceRoot> <flowName> [runDir]" } };
|
|
120
|
+
console.log(JSON.stringify(payload));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const [workspaceRoot, flowName] = args.map((p) => path.resolve(p));
|
|
125
|
+
const nodesDirLegacy = path.join(workspaceRoot, LEGACY_NODES_DIR);
|
|
126
|
+
const nodesDirNew = path.join(workspaceRoot, PROJECT_NODES_DIR);
|
|
127
|
+
const flowDir = getFlowDir(workspaceRoot, flowName) || path.join(workspaceRoot, PIPELINES_DIR, flowName);
|
|
128
|
+
|
|
129
|
+
const out = [];
|
|
130
|
+
|
|
131
|
+
// 1. 内置节点:包内 builtin/nodes + 项目 .workspace/agentflow/nodes(覆盖旧 .cursor/...),包内同 id 优先
|
|
132
|
+
out.push("# 节点元数据(内置 + 当前流水线)\n");
|
|
133
|
+
out.push("## 1. 内置节点元数据\n\n");
|
|
134
|
+
const builtinFiles = new Map();
|
|
135
|
+
if (fs.existsSync(PACKAGE_BUILTIN_NODES_DIR)) {
|
|
136
|
+
for (const file of fs.readdirSync(PACKAGE_BUILTIN_NODES_DIR).filter((f) => f.endsWith(".md")).sort()) {
|
|
137
|
+
builtinFiles.set(file.replace(/\.md$/, ""), path.join(PACKAGE_BUILTIN_NODES_DIR, file));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const projectById = new Map();
|
|
141
|
+
const mergeProjectDir = (dir) => {
|
|
142
|
+
if (!fs.existsSync(dir)) return;
|
|
143
|
+
for (const file of fs.readdirSync(dir).filter((f) => f.endsWith(".md")).sort()) {
|
|
144
|
+
projectById.set(file.replace(/\.md$/, ""), path.join(dir, file));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
mergeProjectDir(nodesDirLegacy);
|
|
148
|
+
mergeProjectDir(nodesDirNew);
|
|
149
|
+
for (const [id, fp] of projectById) {
|
|
150
|
+
if (!builtinFiles.has(id)) builtinFiles.set(id, fp);
|
|
151
|
+
}
|
|
152
|
+
for (const [definitionId, filePath] of builtinFiles) {
|
|
153
|
+
const meta = readNodeMeta(filePath);
|
|
154
|
+
if (!meta) continue;
|
|
155
|
+
out.push(`### ${definitionId}\n`);
|
|
156
|
+
out.push(`- **displayName**: ${meta.displayName || definitionId}\n`);
|
|
157
|
+
out.push(`- **description**: ${meta.description || ""}\n`);
|
|
158
|
+
out.push(`- **input** (handle: input-0, input-1, …):\n`);
|
|
159
|
+
if (meta.input.length) meta.input.forEach((s, i) => out.push(` - \`${s.name || "?"}\` (${s.type || "?"}) → input-${i}\n`));
|
|
160
|
+
else out.push(" - 无\n");
|
|
161
|
+
out.push(`- **output** (handle: output-0, output-1, …):\n`);
|
|
162
|
+
if (meta.output.length) meta.output.forEach((s, i) => out.push(` - \`${s.name || "?"}\` (${s.type || "?"}) → output-${i}\n`));
|
|
163
|
+
else out.push(" - 无\n");
|
|
164
|
+
out.push("\n");
|
|
165
|
+
}
|
|
166
|
+
if (builtinFiles.size === 0) out.push("(无内置节点目录)\n");
|
|
167
|
+
|
|
168
|
+
// 2. 当前流水线节点(来自 flow.yaml instances)
|
|
169
|
+
out.push("## 2. 当前流水线节点元数据\n\n");
|
|
170
|
+
const flowNodes = loadFlowYamlNodes(flowDir);
|
|
171
|
+
for (const node of flowNodes) {
|
|
172
|
+
const { id, label, definitionId, input: nodeInput, output: nodeOutput } = node;
|
|
173
|
+
let input = nodeInput || [];
|
|
174
|
+
let output = nodeOutput || [];
|
|
175
|
+
if (input.length === 0 && output.length === 0) {
|
|
176
|
+
const tryPaths = [
|
|
177
|
+
path.join(flowDir, "nodes", `${definitionId}.md`),
|
|
178
|
+
path.join(nodesDirNew, `${definitionId}.md`),
|
|
179
|
+
path.join(nodesDirLegacy, `${definitionId}.md`),
|
|
180
|
+
path.join(PACKAGE_BUILTIN_NODES_DIR, `${definitionId}.md`),
|
|
181
|
+
];
|
|
182
|
+
let meta = null;
|
|
183
|
+
for (const defPath of tryPaths) {
|
|
184
|
+
meta = readNodeMeta(defPath);
|
|
185
|
+
if (meta) break;
|
|
186
|
+
}
|
|
187
|
+
if (meta) {
|
|
188
|
+
input = meta.input;
|
|
189
|
+
output = meta.output;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
out.push(`### ${id}\n`);
|
|
193
|
+
out.push(`- **label**: ${label || id}\n`);
|
|
194
|
+
out.push(`- **definitionId**: ${definitionId || ""}\n`);
|
|
195
|
+
out.push(`- **input** (handle):\n`);
|
|
196
|
+
if (input.length) input.forEach((s, i) => out.push(` - \`${s.name || "?"}\` (${s.type || "?"}) → input-${i}\n`));
|
|
197
|
+
else out.push(" - 无\n");
|
|
198
|
+
out.push(`- **output** (handle):\n`);
|
|
199
|
+
if (output.length) output.forEach((s, i) => out.push(` - \`${s.name || "?"}\` (${s.type || "?"}) → output-${i}\n`));
|
|
200
|
+
else out.push(" - 无\n");
|
|
201
|
+
out.push("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const content = out.join("");
|
|
205
|
+
const payload = {
|
|
206
|
+
err_code: 0,
|
|
207
|
+
message: { result: content },
|
|
208
|
+
};
|
|
209
|
+
console.log(JSON.stringify(payload));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main();
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 根据 prompt 内容 + resolvedInputs 计算缓存键 MD5 与 cacheInputInfo,供前处理与 check-cache 共用。
|
|
4
|
+
* 用法(模块):import { computeCacheMd5 } from "./compute-cache-md5.mjs";
|
|
5
|
+
* 返回 { cacheMd5, cacheInputInfo, payload } 或 { ok: false, error }。payload 为计算 MD5 的完整字符串,供 pre/now 落盘排查。
|
|
6
|
+
*
|
|
7
|
+
* 当前 .cache.json 文件结构(写入路径:intermediate/<instanceId>/<instanceId>.cache.json):
|
|
8
|
+
* - cacheMd5: string — 对 payload 的 MD5 十六进制串
|
|
9
|
+
* - cacheInputInfo: string — JSON 字符串,解析后为 { instanceId, definitionId, inputPaths }
|
|
10
|
+
* - inputPaths: Array<{ slot: string, execId: number, value: string }>,即 forCache 各 key 及参与 MD5 的值
|
|
11
|
+
* - execId: number — 本轮写入时的 execId
|
|
12
|
+
* - inputHandlerExecIds?: Record<前驱节点 id, number> — 各前驱在本轮计算时用的 execId(可选)
|
|
13
|
+
* - payload?: string — 用于算 MD5 的完整字符串:prompt 全文 + "\\n" + forCache 规范化串(可选,便于排查)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import crypto from "crypto";
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
|
|
20
|
+
import { getRunDir, PIPELINES_DIR } from "../lib/paths.mjs";
|
|
21
|
+
import { getFlowDir } from "../lib/workspace.mjs";
|
|
22
|
+
|
|
23
|
+
import { buildNodePrompt } from "./build-node-prompt.mjs";
|
|
24
|
+
import { getResolvedValues } from "./get-resolved-values.mjs";
|
|
25
|
+
import { loadAllExecIds, latestResultExecId, intermediatePromptBasename, intermediateCacheBasename } from "./get-exec-id.mjs";
|
|
26
|
+
import { loadFlowDefinition, instanceEntryToSlots, parseInstanceSlots } from "./parse-flow.mjs";
|
|
27
|
+
|
|
28
|
+
/** 从 intermediate/<instanceId>/<instanceId>.cache.json 读取 cacheMd5,无则返回 "" */
|
|
29
|
+
function readUpstreamCacheMd5(intermediateDir, instanceId, execId) {
|
|
30
|
+
const basename = intermediateCacheBasename(instanceId, execId);
|
|
31
|
+
const p = path.join(intermediateDir, instanceId, basename);
|
|
32
|
+
if (!fs.existsSync(p)) return "";
|
|
33
|
+
try {
|
|
34
|
+
const o = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
35
|
+
return o.cacheMd5 != null ? String(o.cacheMd5) : "";
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 计算节点的 cacheMd5 与 cacheInputInfo。
|
|
43
|
+
* payload 不含 execId,使相同输入得到稳定 cache 键,避免缓存持续失效。
|
|
44
|
+
* 约定:payload = prompt 内容 + "\n" + 规范化的 forCache(仅 resolvedInputs、upstreamMd5)。
|
|
45
|
+
* @returns {{ ok: true, cacheMd5: string, cacheInputInfo: string, payload: string }} 或 {{ ok: false, error: string }}
|
|
46
|
+
*/
|
|
47
|
+
export function computeCacheMd5(workspaceRoot, flowName, uuid, instanceId, execId = 1) {
|
|
48
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
49
|
+
const intermediateDir = path.join(runDir, "intermediate");
|
|
50
|
+
const build = buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
51
|
+
if (!build.ok) {
|
|
52
|
+
return { ok: false, error: build.error || "buildNodePrompt failed" };
|
|
53
|
+
}
|
|
54
|
+
const promptBasename = intermediatePromptBasename(instanceId, execId);
|
|
55
|
+
const promptPath = path.join(intermediateDir, instanceId, promptBasename);
|
|
56
|
+
if (!fs.existsSync(promptPath)) {
|
|
57
|
+
return { ok: false, error: "Prompt file not found" };
|
|
58
|
+
}
|
|
59
|
+
const promptContent = fs.readFileSync(promptPath, "utf-8");
|
|
60
|
+
|
|
61
|
+
/** 所有写 .cache.json 的节点(含 control_if、control_anyOne、tool_load_key、tool_save_key、tool_get_env、普通节点)均由此处统一解析输入;
|
|
62
|
+
* 输入来自 getResolvedValues → computeResolvedInputsForInstance,按 flow.edges 的 targetHandle 与 instance 的 input 槽位一一对应赋值,无按 definitionId 的特殊分支。
|
|
63
|
+
* 只要 flow 中该节点的 edge 与 instance 定义正确,各 input 槽会正确赋值。 */
|
|
64
|
+
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
65
|
+
if (!data.ok) {
|
|
66
|
+
return { ok: false, error: data.error || "getResolvedValues failed" };
|
|
67
|
+
}
|
|
68
|
+
const resolvedInputs = data.resolvedInputs || {};
|
|
69
|
+
|
|
70
|
+
/** 上游 edge 对应 node 的 cacheMd5 与各 input handler 的 execId:从 flow.json 取 predecessors,再读各前驱的 .cache.json */
|
|
71
|
+
let upstreamMd5 = "";
|
|
72
|
+
/** 各 input handler(前驱节点)在本次计算时使用的 execId,用于落盘与参与 cache 计算 */
|
|
73
|
+
let inputHandlerExecIds = {};
|
|
74
|
+
const flowJsonPath = path.join(intermediateDir, "flow.json");
|
|
75
|
+
if (fs.existsSync(flowJsonPath)) {
|
|
76
|
+
try {
|
|
77
|
+
const flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
78
|
+
if (flow.ok) {
|
|
79
|
+
let preds = flow.predecessors && flow.predecessors[instanceId];
|
|
80
|
+
if (!preds && Array.isArray(flow.edges)) {
|
|
81
|
+
preds = [];
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
for (const e of flow.edges) {
|
|
84
|
+
if (e.target !== instanceId || !e.source) continue;
|
|
85
|
+
if (!seen.has(e.source)) {
|
|
86
|
+
seen.add(e.source);
|
|
87
|
+
preds.push(e.source);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(preds) && preds.length > 0) {
|
|
92
|
+
const order = flow.order || [];
|
|
93
|
+
const execIds = loadAllExecIds(workspaceRoot, flowName, uuid, order);
|
|
94
|
+
const sorted = [...new Set(preds)].sort();
|
|
95
|
+
upstreamMd5 = sorted
|
|
96
|
+
.map((predId) => {
|
|
97
|
+
const predExecId = latestResultExecId(execIds[predId] ?? 1);
|
|
98
|
+
inputHandlerExecIds[predId] = predExecId;
|
|
99
|
+
return `${predId}:${readUpstreamCacheMd5(intermediateDir, predId, 1)}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** forCache 仅含 resolvedInputs 与 upstreamMd5,不包含 execId,保证 payload 稳定 */
|
|
108
|
+
const forCache = {
|
|
109
|
+
...resolvedInputs,
|
|
110
|
+
upstreamMd5,
|
|
111
|
+
};
|
|
112
|
+
const keys = Object.keys(forCache).sort();
|
|
113
|
+
const parts = keys.map((k) => `${k}:${String(forCache[k] ?? "")}`);
|
|
114
|
+
const canonicalInputString = parts.join("\n");
|
|
115
|
+
|
|
116
|
+
const payload = promptContent + "\n" + canonicalInputString;
|
|
117
|
+
const cacheMd5 = crypto.createHash("md5").update(payload, "utf8").digest("hex");
|
|
118
|
+
|
|
119
|
+
let definitionId = "";
|
|
120
|
+
let flow = null;
|
|
121
|
+
if (fs.existsSync(flowJsonPath)) {
|
|
122
|
+
try {
|
|
123
|
+
flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
124
|
+
if (flow.ok && flow.nodes) {
|
|
125
|
+
const node = flow.nodes.find((n) => n.id === instanceId);
|
|
126
|
+
definitionId = node?.definitionId ?? "";
|
|
127
|
+
}
|
|
128
|
+
} catch (_) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 每个 slot 对应的 input handler 的 execId(该槽由哪次执行的 handler 提供);非 handler 槽用 0,本节点 execId 槽用当前 execId */
|
|
132
|
+
const slotToHandlerExecId = {};
|
|
133
|
+
if (flow?.ok && flow.edges && (inputHandlerExecIds && Object.keys(inputHandlerExecIds).length > 0)) {
|
|
134
|
+
const predsWithHandle = (flow.edges || [])
|
|
135
|
+
.filter((e) => e.target === instanceId && e.source)
|
|
136
|
+
.map((e) => ({ source: e.source, targetHandle: e.targetHandle || "input-0" }));
|
|
137
|
+
let flowDir = getFlowDir(workspaceRoot, flowName) || path.join(path.resolve(workspaceRoot), PIPELINES_DIR, flowName);
|
|
138
|
+
if (flow?.flowDir && typeof flow.flowDir === "string" && flow.flowDir.trim()) {
|
|
139
|
+
flowDir = path.isAbsolute(flow.flowDir) ? flow.flowDir : path.join(workspaceRoot, flow.flowDir);
|
|
140
|
+
}
|
|
141
|
+
const flowData = loadFlowDefinition(flowDir);
|
|
142
|
+
const instanceDir = path.join(flowDir, "instance");
|
|
143
|
+
const getSlotsFor = (id) => {
|
|
144
|
+
if (flowData?.instances?.[id]) return instanceEntryToSlots(flowData.instances[id]);
|
|
145
|
+
try {
|
|
146
|
+
return parseInstanceSlots(path.join(instanceDir, `${id}.md`));
|
|
147
|
+
} catch {
|
|
148
|
+
return { input: {}, output: {}, inputTypes: {}, outputTypes: {} };
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const inputSlotNames = Object.keys(getSlotsFor(instanceId).input || {});
|
|
152
|
+
for (let i = 0; i < inputSlotNames.length; i++) {
|
|
153
|
+
const slotName = inputSlotNames[i];
|
|
154
|
+
const targetHandle = `input-${i}`;
|
|
155
|
+
const pred = predsWithHandle.find((p) => p.targetHandle === targetHandle);
|
|
156
|
+
if (pred && inputHandlerExecIds[pred.source] != null) {
|
|
157
|
+
slotToHandlerExecId[slotName] = inputHandlerExecIds[pred.source];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const currentExecId = execId ?? 1;
|
|
162
|
+
const inputPaths = keys.map((k) => ({
|
|
163
|
+
slot: k,
|
|
164
|
+
execId: slotToHandlerExecId[k] ?? (k === "execId" ? currentExecId : 0),
|
|
165
|
+
value: forCache[k],
|
|
166
|
+
}));
|
|
167
|
+
const cacheInputInfo = JSON.stringify({ instanceId, definitionId, inputPaths });
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
cacheMd5,
|
|
172
|
+
cacheInputInfo,
|
|
173
|
+
payload,
|
|
174
|
+
execId: execId ?? 1,
|
|
175
|
+
inputHandlerExecIds,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 生成本次运行 uuid(未传时)并创建 run 目录,避免由 Agent 每次生成。
|
|
4
|
+
* 用法:node ensure-run-dir.mjs <workspaceRoot> [uuid] <flowName>
|
|
5
|
+
* uuid 须为 14 位数字(YYYYMMDDhhmmss),否则视为未传并自动生成。
|
|
6
|
+
* flowName 必传;run 目录为 <workspaceRoot>/.workspace/agentflow/runBuild/<flowName>/<uuid>/,其下创建 intermediate/、output/。
|
|
7
|
+
* 若传入合法 uuid:仅确保上述目录存在。
|
|
8
|
+
* 输出(stdout JSON):{ "ok": true, "uuid": "..." }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
import { getRunDir } from "../lib/paths.mjs";
|
|
15
|
+
|
|
16
|
+
/** 合法 uuid:14 位数字 YYYYMMDDhhmmss,避免误传 flow name 等当作 uuid */
|
|
17
|
+
function isValidUuid(value) {
|
|
18
|
+
return typeof value === "string" && /^\d{14}$/.test(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function generateUuid() {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const y = now.getFullYear();
|
|
24
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
25
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
26
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
27
|
+
const min = String(now.getMinutes()).padStart(2, "0");
|
|
28
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
29
|
+
return `${y}${m}${d}${h}${min}${s}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
if (args.length < 1) {
|
|
35
|
+
console.error(
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
ok: false,
|
|
38
|
+
error: "Usage: node ensure-run-dir.mjs <workspaceRoot> [uuid] <flowName>",
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const workspaceRoot = path.resolve(args[0]);
|
|
45
|
+
const uuid = isValidUuid(args[1]) ? args[1] : generateUuid();
|
|
46
|
+
const flowName = typeof args[2] === "string" && args[2].trim() !== "" ? args[2].trim() : null;
|
|
47
|
+
if (!flowName) {
|
|
48
|
+
console.error(
|
|
49
|
+
JSON.stringify({ ok: false, error: "Usage: node ensure-run-dir.mjs <workspaceRoot> [uuid] <flowName> (flowName required)" }),
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
55
|
+
const intermediateDir = path.join(runDir, "intermediate");
|
|
56
|
+
const outputDir = path.join(runDir, "output");
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(intermediateDir, { recursive: true });
|
|
60
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(
|
|
63
|
+
JSON.stringify({ ok: false, error: e.message || "Failed to create run dir" }),
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(JSON.stringify({ ok: true, uuid }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main();
|