@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,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer 脚本操作层:直接用 Node.js 修改 flow.yaml,无需 AI。
|
|
3
|
+
* 适用于确定性操作(改 label、body、role、value、连线、位置等)。
|
|
4
|
+
*/
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
|
|
8
|
+
// ─── YAML 读写 ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function readFlowYaml(flowYamlAbs) {
|
|
11
|
+
const raw = fs.readFileSync(flowYamlAbs, "utf-8");
|
|
12
|
+
const doc = yaml.load(raw);
|
|
13
|
+
if (!doc || typeof doc !== "object") throw new Error("flow.yaml 解析失败或为空");
|
|
14
|
+
return { doc, raw };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function writeFlowYaml(flowYamlAbs, doc) {
|
|
18
|
+
const out = yaml.dump(doc, {
|
|
19
|
+
lineWidth: -1,
|
|
20
|
+
noRefs: true,
|
|
21
|
+
quotingType: "'",
|
|
22
|
+
forceQuotes: false,
|
|
23
|
+
sortKeys: false,
|
|
24
|
+
});
|
|
25
|
+
fs.writeFileSync(flowYamlAbs, out, "utf-8");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureInstances(doc) {
|
|
29
|
+
if (!doc.instances || typeof doc.instances !== "object") {
|
|
30
|
+
doc.instances = {};
|
|
31
|
+
}
|
|
32
|
+
return doc.instances;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureEdges(doc) {
|
|
36
|
+
if (!Array.isArray(doc.edges)) doc.edges = [];
|
|
37
|
+
return doc.edges;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ensureUi(doc) {
|
|
41
|
+
if (!doc.ui || typeof doc.ui !== "object") doc.ui = {};
|
|
42
|
+
if (!doc.ui.nodePositions || typeof doc.ui.nodePositions !== "object") doc.ui.nodePositions = {};
|
|
43
|
+
return doc.ui;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── 操作实现 ──────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function opEditLabel(doc, params) {
|
|
49
|
+
const inst = ensureInstances(doc);
|
|
50
|
+
const { instanceId, value } = params;
|
|
51
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
52
|
+
inst[instanceId].label = value;
|
|
53
|
+
return `节点 ${instanceId} 的 label 已改为「${value}」`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function opEditBody(doc, params) {
|
|
57
|
+
const inst = ensureInstances(doc);
|
|
58
|
+
const { instanceId, value } = params;
|
|
59
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
60
|
+
inst[instanceId].body = value;
|
|
61
|
+
return `节点 ${instanceId} 的 body 已更新`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function opEditScript(doc, params) {
|
|
65
|
+
const inst = ensureInstances(doc);
|
|
66
|
+
const { instanceId, value } = params;
|
|
67
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
68
|
+
if (inst[instanceId].definitionId !== "tool_nodejs") {
|
|
69
|
+
throw new Error(`节点 ${instanceId} 的 definitionId 不是 tool_nodejs,不能设置 script 字段`);
|
|
70
|
+
}
|
|
71
|
+
inst[instanceId].script = value;
|
|
72
|
+
return `节点 ${instanceId} 的 script 已更新`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function opEditRole(doc, params) {
|
|
76
|
+
const inst = ensureInstances(doc);
|
|
77
|
+
const { instanceId, value } = params;
|
|
78
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
79
|
+
inst[instanceId].role = value;
|
|
80
|
+
return `节点 ${instanceId} 的 role 已改为「${value}」`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function opEditModel(doc, params) {
|
|
84
|
+
const inst = ensureInstances(doc);
|
|
85
|
+
const { instanceId, value } = params;
|
|
86
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
87
|
+
inst[instanceId].model = value;
|
|
88
|
+
return `节点 ${instanceId} 的 model 已改为「${value}」`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function opEditInputValue(doc, params) {
|
|
92
|
+
const inst = ensureInstances(doc);
|
|
93
|
+
const { instanceId, inputName, value } = params;
|
|
94
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
95
|
+
const inputs = inst[instanceId].input;
|
|
96
|
+
if (!Array.isArray(inputs)) throw new Error(`实例 ${instanceId} 没有 input 数组`);
|
|
97
|
+
const slot = inputs.find((s) => s.name === inputName);
|
|
98
|
+
if (!slot) throw new Error(`实例 ${instanceId} 没有名为 ${inputName} 的输入`);
|
|
99
|
+
slot.value = value;
|
|
100
|
+
return `节点 ${instanceId} 输入 ${inputName} 的 value 已更新`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function opEditOutputValue(doc, params) {
|
|
104
|
+
const inst = ensureInstances(doc);
|
|
105
|
+
const { instanceId, outputName, value } = params;
|
|
106
|
+
if (!inst[instanceId]) throw new Error(`实例 ${instanceId} 不存在`);
|
|
107
|
+
const outputs = inst[instanceId].output;
|
|
108
|
+
if (!Array.isArray(outputs)) throw new Error(`实例 ${instanceId} 没有 output 数组`);
|
|
109
|
+
const slot = outputs.find((s) => s.name === outputName);
|
|
110
|
+
if (!slot) throw new Error(`实例 ${instanceId} 没有名为 ${outputName} 的输出`);
|
|
111
|
+
slot.value = value;
|
|
112
|
+
return `节点 ${instanceId} 输出 ${outputName} 的 value 已更新`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function opAddEdge(doc, params) {
|
|
116
|
+
const edges = ensureEdges(doc);
|
|
117
|
+
const { source, target, sourceHandle, targetHandle } = params;
|
|
118
|
+
if (!source || !target) throw new Error("连线需要 source 和 target");
|
|
119
|
+
const exists = edges.some(
|
|
120
|
+
(e) =>
|
|
121
|
+
e.source === source &&
|
|
122
|
+
e.target === target &&
|
|
123
|
+
(!sourceHandle || e.sourceHandle === sourceHandle) &&
|
|
124
|
+
(!targetHandle || e.targetHandle === targetHandle),
|
|
125
|
+
);
|
|
126
|
+
if (exists) return `边 ${source} → ${target} 已存在,跳过`;
|
|
127
|
+
const edge = { source, target };
|
|
128
|
+
if (sourceHandle) edge.sourceHandle = sourceHandle;
|
|
129
|
+
if (targetHandle) edge.targetHandle = targetHandle;
|
|
130
|
+
edges.push(edge);
|
|
131
|
+
return `已添加边 ${source} → ${target}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function opRemoveEdge(doc, params) {
|
|
135
|
+
const edges = ensureEdges(doc);
|
|
136
|
+
const { source, target, sourceHandle, targetHandle } = params;
|
|
137
|
+
const before = edges.length;
|
|
138
|
+
const filtered = edges.filter((e) => {
|
|
139
|
+
if (source && e.source !== source) return true;
|
|
140
|
+
if (target && e.target !== target) return true;
|
|
141
|
+
if (sourceHandle && e.sourceHandle !== sourceHandle) return true;
|
|
142
|
+
if (targetHandle && e.targetHandle !== targetHandle) return true;
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
doc.edges = filtered;
|
|
146
|
+
const removed = before - filtered.length;
|
|
147
|
+
return removed > 0 ? `已删除 ${removed} 条边` : "未找到匹配的边";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function opUpdatePosition(doc, params) {
|
|
151
|
+
const ui = ensureUi(doc);
|
|
152
|
+
const { instanceId, x, y } = params;
|
|
153
|
+
if (!ui.nodePositions[instanceId]) ui.nodePositions[instanceId] = {};
|
|
154
|
+
if (x != null) ui.nodePositions[instanceId].x = Number(x);
|
|
155
|
+
if (y != null) ui.nodePositions[instanceId].y = Number(y);
|
|
156
|
+
return `节点 ${instanceId} 位置已更新为 (${x}, ${y})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const OP_HANDLERS = {
|
|
160
|
+
"edit-label": opEditLabel,
|
|
161
|
+
"edit-body": opEditBody,
|
|
162
|
+
"edit-script": opEditScript,
|
|
163
|
+
"edit-role": opEditRole,
|
|
164
|
+
"edit-model": opEditModel,
|
|
165
|
+
"edit-input-value": opEditInputValue,
|
|
166
|
+
"edit-output-value": opEditOutputValue,
|
|
167
|
+
"add-edge": opAddEdge,
|
|
168
|
+
"remove-edge": opRemoveEdge,
|
|
169
|
+
"update-position": opUpdatePosition,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ─── 公开接口 ──────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 执行一个 script 类型步骤。
|
|
176
|
+
* @param {string} flowYamlAbs flow.yaml 绝对路径
|
|
177
|
+
* @param {{ op: string, params: object }} step
|
|
178
|
+
* @returns {{ success: boolean, message: string }}
|
|
179
|
+
*/
|
|
180
|
+
export function executeScriptOp(flowYamlAbs, step) {
|
|
181
|
+
const handler = OP_HANDLERS[step.op];
|
|
182
|
+
if (!handler) {
|
|
183
|
+
return { success: false, message: `未知操作: ${step.op}` };
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const { doc } = readFlowYaml(flowYamlAbs);
|
|
187
|
+
const message = handler(doc, step.params || {});
|
|
188
|
+
writeFlowYaml(flowYamlAbs, doc);
|
|
189
|
+
return { success: true, message };
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return { success: false, message: e.message || String(e) };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 批量执行多个 script 操作(共享一次 YAML 读写)。
|
|
197
|
+
* @param {string} flowYamlAbs
|
|
198
|
+
* @param {Array<{ op: string, params: object }>} steps
|
|
199
|
+
* @returns {Array<{ success: boolean, message: string }>}
|
|
200
|
+
*/
|
|
201
|
+
export function executeScriptOpsBatch(flowYamlAbs, steps) {
|
|
202
|
+
const { doc } = readFlowYaml(flowYamlAbs);
|
|
203
|
+
const results = [];
|
|
204
|
+
for (const step of steps) {
|
|
205
|
+
const handler = OP_HANDLERS[step.op];
|
|
206
|
+
if (!handler) {
|
|
207
|
+
results.push({ success: false, message: `未知操作: ${step.op}` });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const message = handler(doc, step.params || {});
|
|
212
|
+
results.push({ success: true, message });
|
|
213
|
+
} catch (e) {
|
|
214
|
+
results.push({ success: false, message: e.message || String(e) });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
writeFlowYaml(flowYamlAbs, doc);
|
|
218
|
+
return results;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 检查一个操作名称是否属于已支持的 script 操作。
|
|
223
|
+
*/
|
|
224
|
+
export function isSupportedScriptOp(op) {
|
|
225
|
+
return op in OP_HANDLERS;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 返回所有支持的 script 操作名称列表。
|
|
230
|
+
*/
|
|
231
|
+
export function listScriptOps() {
|
|
232
|
+
return Object.keys(OP_HANDLERS);
|
|
233
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer Skill Router:基于用户输入意图动态加载 SKILL.md 和 reference 文档。
|
|
3
|
+
*
|
|
4
|
+
* 意图分类:
|
|
5
|
+
* query-explain — 纯问答/解释(不修改文件)
|
|
6
|
+
* add-instances — 新增节点/实例
|
|
7
|
+
* edit-fields — 改已有节点内容
|
|
8
|
+
* optimize-flow — 优化/重构整体流程
|
|
9
|
+
* optimize-nodes — 优化特定节点 prompt / body
|
|
10
|
+
* create-flow — 从零创建新流程
|
|
11
|
+
*
|
|
12
|
+
* 每种意图映射到需要注入的 skills 和 reference 文档。
|
|
13
|
+
*/
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
|
|
17
|
+
// ─── 意图模式定义 ─────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const INTENT_PATTERNS = [
|
|
20
|
+
{
|
|
21
|
+
id: "query-explain",
|
|
22
|
+
patterns: [
|
|
23
|
+
// 纯问答:问什么/怎么/为什么/是否/有没有
|
|
24
|
+
/^(?:这个|该|当前|选中)?.*(?:是什么|是啥|啥意思|怎么(?:工作|运行|执行|回事)|为什么|为啥|哪些|有没有|有多少|能不能|是否|如何|有什么)/i,
|
|
25
|
+
// 动词主导:解释/说明/介绍/查看/展示/分析
|
|
26
|
+
/(?:解释|说明|介绍|描述|分析|查看|看看|展示|显示|告诉|帮我看|帮我理解)/i,
|
|
27
|
+
// 名词主导:逻辑/作用/功能/原理
|
|
28
|
+
/(?:逻辑|作用|功能|用途|含义|意义|目的|原理|机制|区别|流程图|概览|概述).*(?:是|呢|吗|?|\?)/i,
|
|
29
|
+
// 英文
|
|
30
|
+
/(?:explain|describe|what\s+(?:does|is|are)|how\s+does|why\s+(?:does|is)|show\s+me|tell\s+me|walk.*through)/i,
|
|
31
|
+
// "X的逻辑/功能/作用"
|
|
32
|
+
/(?:脚本|script|body|prompt|代码|内容|节点|node).{0,15}(?:是什么|什么意思|做什么|干什么|逻辑|功能|作用|怎么样)/i,
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "create-flow",
|
|
37
|
+
patterns: [
|
|
38
|
+
/(?:新建|创建|新增|生成|搭建|设计).*(?:流程|流水线|pipeline|flow|agentflow)/i,
|
|
39
|
+
/(?:从零|从头|从无到有).*(?:搭|建|写|做)/i,
|
|
40
|
+
/create\s+(?:a\s+)?(?:new\s+)?(?:flow|pipeline)/i,
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "add-instances",
|
|
45
|
+
patterns: [
|
|
46
|
+
/(?:新增|添加|加入|插入|增加|加个|加一个|补充|补一个).*(?:节点|实例|node|instance|步骤|环节)/i,
|
|
47
|
+
/(?:加|添|增|补).*(?:分支|条件|判断|循环|环|if|toBool|agent_toBool|anyOne)/i,
|
|
48
|
+
/add\s+(?:a\s+)?(?:new\s+)?(?:node|instance|step)/i,
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "edit-fields",
|
|
53
|
+
patterns: [
|
|
54
|
+
/(?:改|修改|更新|调整|编辑|更换|换成|设为|设置|改为|替换|重写).*(?:节点|label|body|role|标签|名称|角色|内容|描述|文案|prompt|提示词|输入|输出|value|值)/i,
|
|
55
|
+
/(?:节点|label|body|role|标签|角色|内容|文案|prompt|提示词).*(?:改|修改|更新|调整|编辑|换|设|替换|重写)/i,
|
|
56
|
+
/edit\s+(?:node|label|body|role|content)/i,
|
|
57
|
+
/(?:把|将).{0,30}(?:改成|换成|设为|更新为|修改为|替换为)/i,
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "optimize-nodes",
|
|
62
|
+
patterns: [
|
|
63
|
+
/(?:优化|改善|提升|改进|完善|精炼|润色|增强).*(?:节点|node|body|prompt|提示词|描述|文案|内容)/i,
|
|
64
|
+
/(?:节点|node|body|prompt|提示词).*(?:优化|改善|提升|改进|完善|精炼|润色|增强)/i,
|
|
65
|
+
/(?:让|使).{0,20}(?:节点|node|prompt|body).{0,20}(?:更好|更准|更清晰|更高效)/i,
|
|
66
|
+
/optimize\s+(?:node|prompt|body)/i,
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "optimize-flow",
|
|
71
|
+
patterns: [
|
|
72
|
+
/(?:优化|改善|提升|改进|完善|重构|重新设计|简化|精简).*(?:流程|流水线|pipeline|flow|拓扑|结构|架构|整体)/i,
|
|
73
|
+
/(?:流程|流水线|pipeline|flow|拓扑|结构|架构|整体).*(?:优化|改善|提升|改进|完善|重构|重新设计|简化|精简)/i,
|
|
74
|
+
/(?:重新|重).*(?:规划|设计|组织|编排).*(?:流程|节点|步骤)/i,
|
|
75
|
+
/optimize\s+(?:flow|pipeline|topology)/i,
|
|
76
|
+
/refactor/i,
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// 意图 → 需要加载的 skills 和 references
|
|
82
|
+
const INTENT_RESOURCES = {
|
|
83
|
+
"query-explain": {
|
|
84
|
+
skills: [],
|
|
85
|
+
references: [],
|
|
86
|
+
},
|
|
87
|
+
"create-flow": {
|
|
88
|
+
skills: ["agentflow-flow-add-instances"],
|
|
89
|
+
references: ["flow-control-capabilities.md", "flow-layout.md"],
|
|
90
|
+
},
|
|
91
|
+
"add-instances": {
|
|
92
|
+
skills: ["agentflow-flow-add-instances"],
|
|
93
|
+
references: ["flow-control-capabilities.md", "flow-layout.md"],
|
|
94
|
+
},
|
|
95
|
+
"edit-fields": {
|
|
96
|
+
skills: ["agentflow-flow-edit-node-fields"],
|
|
97
|
+
references: ["flow-prompt-handler-check.md"],
|
|
98
|
+
},
|
|
99
|
+
"optimize-nodes": {
|
|
100
|
+
skills: ["agentflow-flow-edit-node-fields", "agentflow-flow-add-instances"],
|
|
101
|
+
references: ["flow-prompt-handler-check.md", "flow-control-capabilities.md"],
|
|
102
|
+
},
|
|
103
|
+
"optimize-flow": {
|
|
104
|
+
skills: ["agentflow-flow-add-instances", "agentflow-flow-edit-node-fields"],
|
|
105
|
+
references: ["flow-control-capabilities.md", "flow-layout.md", "flow-prompt-handler-check.md"],
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ─── 文件缓存 ─────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
const _fileCache = new Map();
|
|
112
|
+
const CACHE_TTL_MS = 60_000;
|
|
113
|
+
|
|
114
|
+
function readFileCached(absPath) {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const cached = _fileCache.get(absPath);
|
|
117
|
+
if (cached && now - cached.ts < CACHE_TTL_MS) return cached.content;
|
|
118
|
+
try {
|
|
119
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
120
|
+
_fileCache.set(absPath, { content, ts: now });
|
|
121
|
+
return content;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── 意图检测 ─────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 分析用户输入,返回匹配的意图 ID 列表(按优先级排序,去重)。
|
|
131
|
+
* 可能返回多个意图(如"优化节点并新增一个分支"同时匹配 optimize-nodes 和 add-instances)。
|
|
132
|
+
* @param {string} userPrompt
|
|
133
|
+
* @returns {string[]}
|
|
134
|
+
*/
|
|
135
|
+
export function detectIntents(userPrompt) {
|
|
136
|
+
if (!userPrompt || typeof userPrompt !== "string") return [];
|
|
137
|
+
const text = userPrompt.trim();
|
|
138
|
+
if (!text) return [];
|
|
139
|
+
|
|
140
|
+
const matched = [];
|
|
141
|
+
for (const { id, patterns } of INTENT_PATTERNS) {
|
|
142
|
+
for (const re of patterns) {
|
|
143
|
+
if (re.test(text)) {
|
|
144
|
+
matched.push(id);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return [...new Set(matched)];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 将意图列表归类为注入策略类别。
|
|
154
|
+
* @param {string[]} intents
|
|
155
|
+
* @returns {"query" | "edit-node" | "add-node" | "add-flow" | "edit-flow" | "generic"}
|
|
156
|
+
*/
|
|
157
|
+
export function classifyIntentCategory(intents) {
|
|
158
|
+
if (!intents || intents.length === 0) return "generic";
|
|
159
|
+
// 纯 query(无任何编辑意图混入)
|
|
160
|
+
const editIntents = intents.filter(i => i !== "query-explain");
|
|
161
|
+
if (editIntents.length === 0) return "query";
|
|
162
|
+
// 混合 query + 编辑 → 按编辑侧分类
|
|
163
|
+
if (editIntents.includes("create-flow")) return "add-flow";
|
|
164
|
+
if (editIntents.includes("optimize-flow")) return "edit-flow";
|
|
165
|
+
if (editIntents.includes("add-instances")) return "add-node";
|
|
166
|
+
if (editIntents.includes("edit-fields") || editIntents.includes("optimize-nodes")) return "edit-node";
|
|
167
|
+
return "generic";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── 加载 skill 和 reference 内容 ─────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 根据意图列表加载对应的 SKILL.md 和 reference 文档内容。
|
|
174
|
+
*
|
|
175
|
+
* @param {string[]} intents - detectIntents 返回的意图 ID 列表
|
|
176
|
+
* @param {string} packageRoot - AgentFlow 包根目录
|
|
177
|
+
* @returns {{ skills: Array<{id: string, content: string}>, references: Array<{name: string, content: string}>, skillsHint: string, hasContext: boolean }}
|
|
178
|
+
*/
|
|
179
|
+
export function loadResourcesForIntents(intents, packageRoot) {
|
|
180
|
+
if (!intents || intents.length === 0) {
|
|
181
|
+
return { skills: [], references: [], skillsHint: "", hasContext: false };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const skillIds = new Set();
|
|
185
|
+
const refNames = new Set();
|
|
186
|
+
|
|
187
|
+
for (const intent of intents) {
|
|
188
|
+
const res = INTENT_RESOURCES[intent];
|
|
189
|
+
if (!res) continue;
|
|
190
|
+
for (const s of res.skills) skillIds.add(s);
|
|
191
|
+
for (const r of res.references) refNames.add(r);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const skills = [];
|
|
195
|
+
for (const id of skillIds) {
|
|
196
|
+
const absPath = path.join(packageRoot, "skills", id, "SKILL.md");
|
|
197
|
+
const content = readFileCached(absPath);
|
|
198
|
+
if (content) {
|
|
199
|
+
const body = stripFrontmatter(content);
|
|
200
|
+
skills.push({ id, content: body, absPath });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const references = [];
|
|
205
|
+
for (const name of refNames) {
|
|
206
|
+
const absPath = path.join(packageRoot, "reference", name);
|
|
207
|
+
const content = readFileCached(absPath);
|
|
208
|
+
if (content) {
|
|
209
|
+
references.push({ name, content, absPath });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const skillsHint = buildSkillsHint(intents, skills, references);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
skills,
|
|
217
|
+
references,
|
|
218
|
+
skillsHint,
|
|
219
|
+
hasContext: skills.length > 0 || references.length > 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── 构建注入到 prompt 的文本块 ───────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
// 已知 reference / skill 的一行摘要(compact 模式注入)
|
|
226
|
+
const RESOURCE_SUMMARIES = {
|
|
227
|
+
"agentflow-flow-add-instances": "新增 instance 与边的规则、handle 速查、布局原则、节点类型选择",
|
|
228
|
+
"agentflow-flow-edit-node-fields": "编辑已有 instance 字段白名单、tool_nodejs script 规则",
|
|
229
|
+
"flow-control-capabilities.md": "控制节点语义、handle 索引、循环模式(check→fix→re-check)",
|
|
230
|
+
"flow-layout.md": "ui.nodePositions 布局原则(主链 x+=280、分支 y±200)",
|
|
231
|
+
"flow-prompt-handler-check.md": "USER_PROMPT 中读写描述与节点 input/output edge 一致性",
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Compact 注入:仅给绝对路径 + 一行摘要,agent 按需 Read。
|
|
236
|
+
* 比 buildSkillInjectionBlock 省 ~20-30KB/step。
|
|
237
|
+
* @param {Array<{id: string, content: string, absPath: string}>} skills
|
|
238
|
+
* @param {Array<{name: string, content: string, absPath: string}>} references
|
|
239
|
+
* @returns {string}
|
|
240
|
+
*/
|
|
241
|
+
export function buildSkillCompactInjectionBlock(skills, references) {
|
|
242
|
+
const parts = [];
|
|
243
|
+
if (skills.length === 0 && references.length === 0) return "";
|
|
244
|
+
|
|
245
|
+
parts.push("### 编辑技能与参考文档(按需 Read 绝对路径)");
|
|
246
|
+
parts.push("");
|
|
247
|
+
for (const s of skills) {
|
|
248
|
+
const summary = RESOURCE_SUMMARIES[s.id] || "";
|
|
249
|
+
parts.push(`- **skill** \`${s.id}\` — ${summary}`);
|
|
250
|
+
parts.push(` 路径:${s.absPath}`);
|
|
251
|
+
}
|
|
252
|
+
for (const r of references) {
|
|
253
|
+
const summary = RESOURCE_SUMMARIES[r.name] || "";
|
|
254
|
+
parts.push(`- **reference** \`${r.name}\` — ${summary}`);
|
|
255
|
+
parts.push(` 路径:${r.absPath}`);
|
|
256
|
+
}
|
|
257
|
+
parts.push("");
|
|
258
|
+
parts.push("**默认不需要 Read** — 节点 schema 表与阶段规则已覆盖 90% 场景。仅当遇到上述摘要明确涉及的特殊情况时再 Read 对应文件。");
|
|
259
|
+
return parts.join("\n");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 为单步 prompt 构建完整的 skill + reference 注入块。
|
|
264
|
+
* @param {Array<{id: string, content: string}>} skills
|
|
265
|
+
* @param {Array<{name: string, content: string}>} references
|
|
266
|
+
* @returns {string}
|
|
267
|
+
*/
|
|
268
|
+
export function buildSkillInjectionBlock(skills, references) {
|
|
269
|
+
const parts = [];
|
|
270
|
+
|
|
271
|
+
if (skills.length > 0) {
|
|
272
|
+
parts.push("### 相关编辑技能(请严格遵循)");
|
|
273
|
+
parts.push("");
|
|
274
|
+
for (const s of skills) {
|
|
275
|
+
parts.push(`<skill name="${s.id}">`);
|
|
276
|
+
parts.push(s.content.trim());
|
|
277
|
+
parts.push("</skill>");
|
|
278
|
+
parts.push("");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (references.length > 0) {
|
|
283
|
+
parts.push("### 参考文档");
|
|
284
|
+
parts.push("");
|
|
285
|
+
for (const r of references) {
|
|
286
|
+
parts.push(`<reference name="${r.name}">`);
|
|
287
|
+
parts.push(r.content.trim());
|
|
288
|
+
parts.push("</reference>");
|
|
289
|
+
parts.push("");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return parts.join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 为多步模式的 flowContext.skillsHint 构建精简版。
|
|
298
|
+
*/
|
|
299
|
+
function buildSkillsHint(intents, skills, references) {
|
|
300
|
+
const lines = [];
|
|
301
|
+
|
|
302
|
+
if (intents.includes("add-instances") || intents.includes("create-flow")) {
|
|
303
|
+
lines.push("- 新增实例与边:遵循 skill `skills/agentflow-flow-add-instances/SKILL.md`(内容已注入上下文)");
|
|
304
|
+
}
|
|
305
|
+
if (intents.includes("edit-fields") || intents.includes("optimize-nodes")) {
|
|
306
|
+
lines.push("- 仅改已有实例文案/占位等:遵循 `skills/agentflow-flow-edit-node-fields/SKILL.md`(内容已注入上下文)");
|
|
307
|
+
}
|
|
308
|
+
if (intents.includes("optimize-flow")) {
|
|
309
|
+
lines.push("- 流程结构优化:参考 `reference/flow-control-capabilities.md` 和 `reference/flow-layout.md`(内容已注入上下文)");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
lines.push(
|
|
313
|
+
"- **节点类型选择(必须遵守)**:**确定性任务 → tool_nodejs;非确定性任务 → agent_subAgent**。" +
|
|
314
|
+
"确定性 = 相同输入必出相同输出、可用普通代码完整描述(CLI/npm、读写文件、转换格式、调 API)。" +
|
|
315
|
+
"非确定性 = 需语义理解或创造(代码翻译/生成、源码/文本理解、多步决策、创意写作)。" +
|
|
316
|
+
"醒目输出 → tool_print。" +
|
|
317
|
+
"反例:『Android 转 RN』『代码 review』必须 agent。"
|
|
318
|
+
);
|
|
319
|
+
lines.push(
|
|
320
|
+
"- **tool_nodejs 必须写 script 字段**:script 是实际执行的命令代码,body 仅为文档注释(有 script 时不执行)。" +
|
|
321
|
+
"禁止 tool_nodejs 只有 body 没有 script(body 中的自然语言不会被执行)。" +
|
|
322
|
+
"如果无法写出完整可执行的 script,必须改用 agent_subAgent。"
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─── 辅助 ─────────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
function stripFrontmatter(content) {
|
|
331
|
+
const match = content.match(/^---\n[\s\S]*?\n---\n?/);
|
|
332
|
+
return match ? content.slice(match[0].length) : content;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 为规划器提供精简的 skill 上下文摘要(不含完整文档内容,仅规则要点)。
|
|
337
|
+
* @param {string[]} intents
|
|
338
|
+
* @returns {string}
|
|
339
|
+
*/
|
|
340
|
+
export function buildPlannerSkillContext(intents) {
|
|
341
|
+
if (!intents || intents.length === 0) return "";
|
|
342
|
+
|
|
343
|
+
const parts = ["## 编辑技能约束(规划时须遵守)"];
|
|
344
|
+
|
|
345
|
+
if (intents.includes("add-instances") || intents.includes("create-flow")) {
|
|
346
|
+
parts.push(
|
|
347
|
+
"- 新增节点:须从 builtin/pipelines/new/flow.yaml 拷贝同类 definitionId 的实例模板;" +
|
|
348
|
+
"不要自造 input/output 顺序和 name;每个新 instanceId 须在 ui.nodePositions 写入坐标(主链从左到右 x 递增 280,起始 x:100 y:300;分支 y 错开 200);" +
|
|
349
|
+
"默认不连线,仅当用户明确要求时才在 edges 中增加边。"
|
|
350
|
+
);
|
|
351
|
+
parts.push(
|
|
352
|
+
"- 节点类型选择:**确定性任务 → tool_nodejs;非确定性任务 → agent_subAgent**。" +
|
|
353
|
+
"确定性 = 相同输入永远相同输出(CLI/npm/读写文件/转换/调 API);" +
|
|
354
|
+
"非确定性 = 需语义理解或创造(代码翻译/生成、理解源码、多步决策、创意写作);" +
|
|
355
|
+
"醒目输出 → tool_print。反例:『Android 转 RN』『代码 review』必须 agent。"
|
|
356
|
+
);
|
|
357
|
+
parts.push(
|
|
358
|
+
"- tool_nodejs 的 script 与 body 区分:script 是实际执行的命令(必填),body 仅为文档注释(有 script 时不执行);" +
|
|
359
|
+
"禁止 tool_nodejs 只写 body 不写 script(自然语言不会被执行);如果无法写出完整可执行的 script,必须改用 agent_subAgent。"
|
|
360
|
+
);
|
|
361
|
+
parts.push("- 每个节点单一职责,不要把多个操作塞进同一个 body。");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (intents.includes("edit-fields") || intents.includes("optimize-nodes")) {
|
|
365
|
+
parts.push(
|
|
366
|
+
"- 修改已有节点:可改白名单字段(label、body、role、script、input[].value、output[].value);" +
|
|
367
|
+
"禁改 definitionId、instanceId 键名、input/output 数组结构与顺序、edges。"
|
|
368
|
+
);
|
|
369
|
+
parts.push(
|
|
370
|
+
"- **tool_nodejs 的 script 必须写**:`definitionId: tool_nodejs` 的节点核心是 `script`(实际执行的 shell/node 命令)," +
|
|
371
|
+
"`body` 有 script 时不执行。优化 tool_nodejs 节点时务必检查并完善 `script` 字段,禁止只在 `body` 里写自然语言描述。" +
|
|
372
|
+
"如果无法写出可执行 script,应建议改用 `agent_subAgent`。"
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (intents.includes("optimize-flow")) {
|
|
377
|
+
parts.push(
|
|
378
|
+
"- 流程优化可能涉及:调整拓扑(改边)、增删节点、改 body 内容、调整布局坐标。" +
|
|
379
|
+
"如果需要多种操作,拆成多个步骤(先改内容再连线,先加节点再连线)。"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return parts.join("\n");
|
|
384
|
+
}
|