@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,337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 获取指定 instance 的 resolvedInputs、resolvedOutputs、systemPrompt,用于占位符替换。
|
|
4
|
+
* systemPrompt 来自 instance 或 node 定义的 description,占位符 ${input.xxx}、${output.xxx}、${xxx} 会被替换。
|
|
5
|
+
* 用法:node get-resolved-values.mjs <workspaceRoot> <flowName> <uuid> <instanceId>
|
|
6
|
+
* 输出(stdout JSON):{ "ok": true, "resolvedInputs": {...}, "resolvedOutputs": {...}, "systemPrompt": "..." }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
import { getRunDir, LEGACY_NODES_DIR, PIPELINES_DIR, PROJECT_NODES_DIR } from "../lib/paths.mjs";
|
|
13
|
+
import { getFlowDir } from "../lib/workspace.mjs";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
|
|
16
|
+
const __dirnameResolved = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PACKAGE_BUILTIN_NODES_DIR = path.join(path.resolve(__dirnameResolved, "..", ".."), "builtin", "nodes");
|
|
18
|
+
|
|
19
|
+
import { loadFlowDefinition } from "./parse-flow.mjs";
|
|
20
|
+
import { loadAllExecIds, outputDirForNode } from "./get-exec-id.mjs";
|
|
21
|
+
import { computeResolvedInputsForInstance } from "./resolve-inputs.mjs";
|
|
22
|
+
|
|
23
|
+
/** 仅多行标记(无实际内容)时视为空,应回退到节点定义的 description */
|
|
24
|
+
function isEmptyDescription(v) {
|
|
25
|
+
if (!v || typeof v !== "string") return true;
|
|
26
|
+
const t = v.trim();
|
|
27
|
+
return t === "|" || t === ">" || /^[|>]\s*$/.test(t);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 解析 frontmatter(如 instance .md),支持 description 多行(| / >)。当前仅 flow.yaml instances 生效,此函数保留供复用。 */
|
|
31
|
+
function parseFrontmatter(raw) {
|
|
32
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
33
|
+
if (!m) return {};
|
|
34
|
+
const fm = m[1];
|
|
35
|
+
const data = {};
|
|
36
|
+
const lines = fm.split("\n");
|
|
37
|
+
for (let i = 0; i < lines.length; i++) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
if (/^\s*definitionId:\s*(.*)$/.test(line)) {
|
|
40
|
+
const v = line.replace(/^\s*definitionId:\s*/, "").replace(/^["']|["']$/g, "").trim();
|
|
41
|
+
data.definitionId = v;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const descMatch = line.match(/^\s*description:\s*(.+)$/);
|
|
45
|
+
if (descMatch) {
|
|
46
|
+
const rest = descMatch[1].replace(/^["']|["']$/g, "").trim();
|
|
47
|
+
if (rest === "|" || rest === ">" || /^[|>]\s*$/.test(rest)) {
|
|
48
|
+
const keyIndent = line.search(/\S/);
|
|
49
|
+
const contentLines = [];
|
|
50
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
51
|
+
const contentLine = lines[j];
|
|
52
|
+
if (contentLine.trim() === "") {
|
|
53
|
+
contentLines.push("");
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const lineIndent = contentLine.search(/\S/);
|
|
57
|
+
if (lineIndent <= keyIndent) break;
|
|
58
|
+
contentLines.push(lineIndent >= 0 ? contentLine.slice(lineIndent) : contentLine);
|
|
59
|
+
}
|
|
60
|
+
data.description = contentLines.join("\n").trim();
|
|
61
|
+
} else if (rest) {
|
|
62
|
+
data.description = rest;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 从 frontmatter 文本中解析 description 字段,支持单行与 YAML 多行(| 或 >)。
|
|
72
|
+
* @param {string} frontmatter - --- 与 --- 之间的内容(不含首尾 ---)
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function extractDescriptionFromFrontmatter(frontmatter) {
|
|
76
|
+
const lines = frontmatter.split("\n");
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
const singleLineMatch = line.match(/^\s*description:\s*(.+)$/);
|
|
80
|
+
if (singleLineMatch) {
|
|
81
|
+
const rest = singleLineMatch[1].replace(/^["']|["']$/g, "").trim();
|
|
82
|
+
// 多行标记:仅 "|" 或 ">" 或带空格的 "| " / "> "
|
|
83
|
+
if (rest === "|" || rest === ">" || /^[|>]\s*$/.test(rest)) {
|
|
84
|
+
const keyIndent = line.search(/\S/);
|
|
85
|
+
const contentLines = [];
|
|
86
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
87
|
+
const contentLine = lines[j];
|
|
88
|
+
if (contentLine.trim() === "") {
|
|
89
|
+
contentLines.push("");
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const lineIndent = contentLine.search(/\S/);
|
|
93
|
+
if (lineIndent <= keyIndent && contentLine.trim() !== "") break;
|
|
94
|
+
contentLines.push(lineIndent >= 0 ? contentLine.slice(lineIndent) : contentLine);
|
|
95
|
+
}
|
|
96
|
+
return contentLines.join("\n").trim();
|
|
97
|
+
}
|
|
98
|
+
if (rest) return rest;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readNodeDescription(workspaceRoot, flowDir, definitionId) {
|
|
105
|
+
const fileName = definitionId.endsWith(".md") ? definitionId : `${definitionId}.md`;
|
|
106
|
+
const flowNodesPath = path.join(flowDir, "nodes", fileName);
|
|
107
|
+
const projectNodesNew = path.join(workspaceRoot, PROJECT_NODES_DIR, fileName);
|
|
108
|
+
const projectNodesLegacy = path.join(workspaceRoot, LEGACY_NODES_DIR, fileName);
|
|
109
|
+
const packageNodesPath = path.join(PACKAGE_BUILTIN_NODES_DIR, fileName);
|
|
110
|
+
for (const p of [flowNodesPath, projectNodesNew, projectNodesLegacy, packageNodesPath]) {
|
|
111
|
+
try {
|
|
112
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
113
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
114
|
+
if (!m) continue;
|
|
115
|
+
const v = extractDescriptionFromFrontmatter(m[1]);
|
|
116
|
+
if (v) return v;
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 输出槽 slot 名对应的目标输出路径(run 目录内相对路径),固定文件名不含 _execId。
|
|
124
|
+
* 约定:output/<instanceId>/node_<instanceId>_<base>.<ext>
|
|
125
|
+
*/
|
|
126
|
+
export function getOutputPathForSlot(instanceId, execId, slotName) {
|
|
127
|
+
const base = slotName.replace(/\.(md|txt|json|html?)$/i, "") || slotName;
|
|
128
|
+
const ext = (slotName.match(/\.(md|txt|json|html?)$/i) || ["", "md"])[1];
|
|
129
|
+
return `${outputDirForNode(instanceId)}/node_${instanceId}_${base}.${ext}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolvePlaceholdersInText(
|
|
133
|
+
text,
|
|
134
|
+
resolvedInputs,
|
|
135
|
+
resolvedOutputs,
|
|
136
|
+
opts = {},
|
|
137
|
+
) {
|
|
138
|
+
if (!text || typeof text !== "string") return "";
|
|
139
|
+
const { instanceId, runDir } = opts;
|
|
140
|
+
const toAbs = (rel) => (runDir && rel ? path.join(runDir, rel) : rel);
|
|
141
|
+
return text.replace(/\$\{([^}]+)\}/g, (_, key) => {
|
|
142
|
+
const k = key.trim();
|
|
143
|
+
if (k.startsWith("input.")) {
|
|
144
|
+
const slot = k.slice(6);
|
|
145
|
+
return resolvedInputs[slot] ?? resolvedInputs._ ?? "";
|
|
146
|
+
}
|
|
147
|
+
if (k.startsWith("output.")) {
|
|
148
|
+
const slot = k.slice(7);
|
|
149
|
+
const v = resolvedOutputs[slot] ?? resolvedOutputs._ ?? "";
|
|
150
|
+
if (v) return v;
|
|
151
|
+
if (instanceId && slot in resolvedOutputs && opts.currentExecId != null) {
|
|
152
|
+
return toAbs(getOutputPathForSlot(instanceId, opts.currentExecId, slot));
|
|
153
|
+
}
|
|
154
|
+
if (instanceId && slot in resolvedOutputs) {
|
|
155
|
+
return toAbs(getOutputPathForSlot(instanceId, 1, slot));
|
|
156
|
+
}
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
let v = resolvedInputs[k] ?? resolvedOutputs[k] ?? "";
|
|
160
|
+
// 兼容槽位名带 .md 等后缀:如模板写 ${message},resolved 里可能是 message.md
|
|
161
|
+
if (!v && !k.includes(".")) {
|
|
162
|
+
v = resolvedInputs[k + ".md"] ?? resolvedOutputs[k + ".md"] ?? "";
|
|
163
|
+
}
|
|
164
|
+
if (!v && instanceId && (k in resolvedOutputs || (k + ".md") in resolvedOutputs)) {
|
|
165
|
+
const slot = k in resolvedOutputs ? k : k + ".md";
|
|
166
|
+
v = toAbs(getOutputPathForSlot(instanceId, opts.currentExecId ?? 1, slot));
|
|
167
|
+
}
|
|
168
|
+
return v;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取指定 instance 的 resolvedInputs、resolvedOutputs、systemPrompt。
|
|
174
|
+
* @returns {{ ok: boolean, resolvedInputs?: object, resolvedOutputs?: object, systemPrompt?: string, error?: string }}
|
|
175
|
+
*/
|
|
176
|
+
export function getResolvedValues(workspaceRoot, flowName, uuid, instanceId) {
|
|
177
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
178
|
+
const flowJsonPath = path.join(runDir, "intermediate", "flow.json");
|
|
179
|
+
|
|
180
|
+
if (!fs.existsSync(flowJsonPath)) {
|
|
181
|
+
return { ok: false, error: `flow.json not found: ${flowJsonPath}. Run parse-flow.mjs first.` };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
186
|
+
if (!flow.ok) {
|
|
187
|
+
return { ok: false, error: flow.error || "flow.json indicates error" };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let flowDir = getFlowDir(workspaceRoot, flowName) || path.join(workspaceRoot, PIPELINES_DIR, flowName);
|
|
191
|
+
if (flow.flowDir && typeof flow.flowDir === "string" && flow.flowDir.trim()) {
|
|
192
|
+
flowDir = path.isAbsolute(flow.flowDir) ? flow.flowDir : path.join(workspaceRoot, flow.flowDir);
|
|
193
|
+
}
|
|
194
|
+
const raw = computeResolvedInputsForInstance(workspaceRoot, flowName, uuid, instanceId);
|
|
195
|
+
if (!raw.ok) {
|
|
196
|
+
return { ok: false, error: raw.error || "computeResolvedInputsForInstance failed" };
|
|
197
|
+
}
|
|
198
|
+
let resolvedInputs = raw.resolvedInputs || {};
|
|
199
|
+
|
|
200
|
+
const runDirRel = path.join(".workspace", "agentflow", "runBuild", flowName, uuid);
|
|
201
|
+
|
|
202
|
+
const order = flow.order || [];
|
|
203
|
+
const execIds = loadAllExecIds(workspaceRoot, flowName, uuid, order);
|
|
204
|
+
const currentExecId = execIds[instanceId] ?? 1;
|
|
205
|
+
|
|
206
|
+
// 当前节点 output 路径从结构(outputSlotTypes / nodes)得到槽名,固定路径不含 _execId;拼入 prompt 时使用绝对路径
|
|
207
|
+
const resolvedOutputs = {};
|
|
208
|
+
const outSlotNames = (flow.outputSlotTypes && flow.outputSlotTypes[instanceId])
|
|
209
|
+
? Object.keys(flow.outputSlotTypes[instanceId])
|
|
210
|
+
: [];
|
|
211
|
+
for (const slotName of outSlotNames) {
|
|
212
|
+
const rel = getOutputPathForSlot(instanceId, currentExecId, slotName);
|
|
213
|
+
resolvedOutputs[slotName] = path.join(runDir, rel);
|
|
214
|
+
}
|
|
215
|
+
if (Object.keys(resolvedOutputs).length === 0 && order.includes(instanceId)) {
|
|
216
|
+
const node = flow.nodes?.find((n) => n.id === instanceId);
|
|
217
|
+
const outSlots = node?.output || flow.outputSlotTypes?.[instanceId];
|
|
218
|
+
if (outSlots && typeof outSlots === "object") {
|
|
219
|
+
for (const slotName of Object.keys(outSlots)) {
|
|
220
|
+
const rel = getOutputPathForSlot(instanceId, currentExecId, slotName);
|
|
221
|
+
resolvedOutputs[slotName] = path.join(runDir, rel);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 注入运行时常量,供 tool_nodejs 等节点在 instance body / script 中用 ${workspaceRoot} ${flowName} ${runDir} ${flowDir} 引用
|
|
227
|
+
// 运行时常量放在后面,确保不会被 input 槽位的空值覆盖
|
|
228
|
+
const runtimeConstants = {
|
|
229
|
+
workspaceRoot: path.resolve(workspaceRoot),
|
|
230
|
+
flowName,
|
|
231
|
+
runDir: runDirRel,
|
|
232
|
+
flowDir: path.resolve(flowDir),
|
|
233
|
+
};
|
|
234
|
+
for (const [key, value] of Object.entries(runtimeConstants)) {
|
|
235
|
+
// 仅在 input 槽位的值为空或占位符时才使用运行时常量
|
|
236
|
+
const existing = resolvedInputs[key];
|
|
237
|
+
if (!existing || existing === "${" + key + "}" || existing.trim() === "") {
|
|
238
|
+
resolvedInputs[key] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 对上游 output 路径:若文件已存在且该槽位类型不是「文件」,用文件内容替换路径(便于 cache 一致)。
|
|
243
|
+
// 类型为「文件」的 input 槽:保留路径(文件名/引用),不替换为内容,供 prompt 中「引用文件」使用。
|
|
244
|
+
const inputSlotTypes = (flow.inputSlotTypes && flow.inputSlotTypes[instanceId]) || {};
|
|
245
|
+
for (const slotName of Object.keys(resolvedInputs)) {
|
|
246
|
+
if (inputSlotTypes[slotName] === "文件" || inputSlotTypes[slotName] === "file") continue;
|
|
247
|
+
const v = resolvedInputs[slotName];
|
|
248
|
+
if (typeof v !== "string" || !v) continue;
|
|
249
|
+
if (!v.startsWith("output/")) continue;
|
|
250
|
+
const absPath = path.join(runDir, v);
|
|
251
|
+
try {
|
|
252
|
+
if (fs.existsSync(absPath)) {
|
|
253
|
+
resolvedInputs[slotName] = fs.readFileSync(absPath, "utf-8").trim();
|
|
254
|
+
} else {
|
|
255
|
+
// 备份机制(snapshotPriorRoundIfNeeded)会将 foo.md 重命名为 foo_N.md,
|
|
256
|
+
// 循环节点第二轮起原始文件不存在时,回退查找最新的 _N 备份文件。
|
|
257
|
+
const dir = path.dirname(absPath);
|
|
258
|
+
const ext = path.extname(absPath);
|
|
259
|
+
const base = path.basename(absPath, ext);
|
|
260
|
+
if (fs.existsSync(dir)) {
|
|
261
|
+
const candidates = fs.readdirSync(dir).filter(f =>
|
|
262
|
+
f.startsWith(base + "_") && f.endsWith(ext) &&
|
|
263
|
+
/^\d+$/.test(f.slice(base.length + 1, -ext.length))
|
|
264
|
+
);
|
|
265
|
+
if (candidates.length > 0) {
|
|
266
|
+
candidates.sort((a, b) => {
|
|
267
|
+
const na = parseInt(a.slice(base.length + 1, -ext.length), 10);
|
|
268
|
+
const nb = parseInt(b.slice(base.length + 1, -ext.length), 10);
|
|
269
|
+
return nb - na;
|
|
270
|
+
});
|
|
271
|
+
resolvedInputs[slotName] = fs.readFileSync(path.join(dir, candidates[0]), "utf-8").trim();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (_) {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 拼入 prompt 的 resolveInput/output 统一使用绝对路径,避免相对路径歧义
|
|
279
|
+
for (const slotName of Object.keys(resolvedInputs)) {
|
|
280
|
+
const v = resolvedInputs[slotName];
|
|
281
|
+
if (typeof v === "string" && v && (v.startsWith("output/") || v.startsWith("intermediate/"))) {
|
|
282
|
+
resolvedInputs[slotName] = path.join(runDir, v);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let description = "";
|
|
287
|
+
let definitionId = "";
|
|
288
|
+
const flowNode = flow.nodes?.find((n) => n.id === instanceId);
|
|
289
|
+
const nameForFile = flowNode?.definitionName ?? flowNode?.definitionId;
|
|
290
|
+
const flowData = loadFlowDefinition(flowDir);
|
|
291
|
+
if (flowData?.instances?.[instanceId] != null) {
|
|
292
|
+
const inst = flowData.instances[instanceId];
|
|
293
|
+
definitionId = (flowNode?.definitionId ?? inst.definitionId ?? "").trim();
|
|
294
|
+
if (nameForFile || inst.definitionId) {
|
|
295
|
+
description = readNodeDescription(workspaceRoot, flowDir, nameForFile || inst.definitionId);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
definitionId = (flowNode?.definitionId ?? flowNode?.definitionName ?? "").trim();
|
|
299
|
+
if (nameForFile || definitionId) {
|
|
300
|
+
description = readNodeDescription(workspaceRoot, flowDir, nameForFile || definitionId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const systemPrompt = resolvePlaceholdersInText(
|
|
305
|
+
description,
|
|
306
|
+
resolvedInputs,
|
|
307
|
+
resolvedOutputs,
|
|
308
|
+
{ instanceId, currentExecId, runDir },
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
return { ok: true, resolvedInputs, resolvedOutputs, systemPrompt };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
return { ok: false, error: err.message };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function main() {
|
|
318
|
+
const args = process.argv.slice(2);
|
|
319
|
+
if (args.length < 4) {
|
|
320
|
+
console.error(
|
|
321
|
+
JSON.stringify({
|
|
322
|
+
ok: false,
|
|
323
|
+
error: "Usage: node get-resolved-values.mjs <workspaceRoot> <flowName> <uuid> <instanceId>",
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const [root, flowName, uuid, instanceId] = args;
|
|
330
|
+
const workspaceRoot = path.resolve(root);
|
|
331
|
+
const result = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
332
|
+
console.log(JSON.stringify(result));
|
|
333
|
+
if (!result.ok) process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const isMain = process.argv[1] && path.resolve(fileURLToPath(import.meta.url)) === path.resolve(process.argv[1]);
|
|
337
|
+
if (isMain) main();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* tool_load_key 执行脚本:从 run 目录下 memory 存储按 key 读取 value,stdout 输出 tool_nodejs 约定 JSON。
|
|
4
|
+
* 存储路径与格式由本脚本内部实现,节点不感知。key 由命令行参数传入,不读 flow。
|
|
5
|
+
* 用法:node load-key.mjs <workspaceRoot> <flowName> <uuid> <key>
|
|
6
|
+
* 输出(stdout 一行 JSON):{ "err_code": 0, "message": { "result": "<value>" } };err_code 0=成功 1=失败,无 next。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
import { getRunDir } from "../lib/paths.mjs";
|
|
13
|
+
|
|
14
|
+
const MEMORY_FILENAME = "memory.md";
|
|
15
|
+
|
|
16
|
+
function parseMemory(content) {
|
|
17
|
+
const map = new Map();
|
|
18
|
+
for (const line of (content || "").split(/\r?\n/)) {
|
|
19
|
+
const idx = line.indexOf(": ");
|
|
20
|
+
if (idx <= 0) continue;
|
|
21
|
+
const k = line.slice(0, idx).trim();
|
|
22
|
+
const v = line.slice(idx + 2).trim();
|
|
23
|
+
if (k) map.set(k, v);
|
|
24
|
+
}
|
|
25
|
+
return map;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function main() {
|
|
29
|
+
const [root, flowName, uuid, key] = process.argv.slice(2);
|
|
30
|
+
if (!root || !flowName || !uuid) {
|
|
31
|
+
console.log(
|
|
32
|
+
JSON.stringify({
|
|
33
|
+
err_code: 1,
|
|
34
|
+
message: { result: "" },
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const keyStr = key != null ? String(key).trim() : "";
|
|
41
|
+
const workspaceRoot = path.resolve(root);
|
|
42
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
43
|
+
const memoryPath = path.join(runDir, MEMORY_FILENAME);
|
|
44
|
+
|
|
45
|
+
let value = "";
|
|
46
|
+
if (keyStr && fs.existsSync(memoryPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(memoryPath, "utf-8");
|
|
49
|
+
const map = parseMemory(content);
|
|
50
|
+
value = map.get(keyStr) ?? "";
|
|
51
|
+
} catch (_) {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
err_code: 0,
|
|
57
|
+
message: { result: value },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 布尔解析工具,供 control_if 等节点在 pre-process / post-process 中共用。
|
|
4
|
+
* 用法(模块):import { parseBool, getFirstBoolInputValue } from "./parse-bool.mjs";
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 将字符串或值解析为布尔:true/1/yes/on 为 true,其余为 false。
|
|
9
|
+
* @param {*} val
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function parseBool(val) {
|
|
13
|
+
if (val == null || val === "") return false;
|
|
14
|
+
const s = String(val).trim().toLowerCase();
|
|
15
|
+
return ["true", "1", "yes", "on"].includes(s);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 从 resolvedInputs 中取第一个 type 为 bool 的槽位值(名称不限)。
|
|
20
|
+
* @param {Record<string, unknown>} resolvedInputs
|
|
21
|
+
* @param {Record<string, string>|null} inputSlotTypes - inputSlotTypes[instanceId]
|
|
22
|
+
* @returns {string|null} 槽位原始值(可能为路径或 "true"/"false" 等),无则 null
|
|
23
|
+
*/
|
|
24
|
+
export function getFirstBoolInputValue(resolvedInputs, inputSlotTypes) {
|
|
25
|
+
if (!resolvedInputs || !inputSlotTypes) return null;
|
|
26
|
+
for (const [slotName, type] of Object.entries(inputSlotTypes)) {
|
|
27
|
+
if (String(type).toLowerCase() !== "bool") continue;
|
|
28
|
+
const v = resolvedInputs[slotName];
|
|
29
|
+
if (v == null) continue;
|
|
30
|
+
return typeof v === "string" ? v : String(v);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|