@fieldwangai/agentflow 0.1.154 → 0.1.157
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/bin/lib/flow-dsl/cli.mjs +2 -1
- package/bin/lib/flow-dsl/codegen.mjs +32 -21
- package/bin/lib/flow-dsl/legacy-yaml.mjs +47 -12
- package/bin/lib/flow-dsl/parser.mjs +24 -4
- package/bin/lib/jenkins.mjs +368 -0
- package/bin/lib/legacy-flow-execution.mjs +0 -1
- package/bin/lib/ui-server.mjs +79 -3
- package/bin/lib/workspace-preview.mjs +12 -2
- package/bin/lib/workspace-routes.mjs +117 -13
- package/bin/lib/workspace-server.mjs +407 -5
- package/builtin/nodes/tool_jenkins_build.md +5 -4
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CKClwj96.js → WorkflowAssistantThread-g_ljSViJ.js} +1 -1
- package/builtin/web-ui/dist/assets/index-C0iq6zHl.js +870 -0
- package/builtin/web-ui/dist/assets/index-mmXs3H9P.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -1
- package/skills/agentflow-flow-dsl/SKILL.md +13 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +1 -0
- package/skills/agentflow-node-dsl/SKILL.md +27 -3
- package/skills/agentflow-node-reference/references/builtin-nodes.md +8 -0
- package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +0 -870
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +0 -1
package/bin/lib/flow-dsl/cli.mjs
CHANGED
|
@@ -150,7 +150,7 @@ export function layoutWorkspaceFlowDir(flowDir, { all = false, workspaceRoot = "
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/** 没有节点丢失时的空损耗清单,让返回值形状始终一致。 */
|
|
153
|
-
const NO_LOSS = { remapped: [], dropped: [], droppedEdges: [], warnings: [] };
|
|
153
|
+
const NO_LOSS = { remapped: [], dropped: [], droppedEdges: [], renamedIds: [], warnings: [] };
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
156
|
* 把一个流程目录就地迁移成代码形态。两个来源:
|
|
@@ -207,6 +207,7 @@ function migrateLegacyYamlDir(dir, { force = false, marketplaceRoot = "" } = {})
|
|
|
207
207
|
remapped: converted.remapped,
|
|
208
208
|
dropped: converted.dropped,
|
|
209
209
|
droppedEdges: converted.droppedEdges,
|
|
210
|
+
renamedIds: converted.renamedIds,
|
|
210
211
|
warnings: converted.warnings,
|
|
211
212
|
};
|
|
212
213
|
// `control_end` 和指向它的边标了 benign——丢了等于没丢,不该拦住迁移。
|
|
@@ -70,6 +70,25 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
70
70
|
return rel;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
// 节点 id 就是顶层变量名,可能刚好叫 display / flow / file。节点 id 不能为了代码生成
|
|
74
|
+
// 偷偷改掉(那会变成另一张图),所以冲突时给 DSL API import 起别名:
|
|
75
|
+
// `import { display as displayApi } ...; const display = displayApi.markdown(...)`。
|
|
76
|
+
// 所有顶层标识符共用 taken;先给内置 API 占位,后面的输出解构和包 import 也会避开它们。
|
|
77
|
+
const taken = new Set(Object.keys(N));
|
|
78
|
+
const uniqueName = (base, fallback) => {
|
|
79
|
+
let name = isIdentifier(base) ? base : fallback;
|
|
80
|
+
if (taken.has(name)) name = fallback;
|
|
81
|
+
while (taken.has(name)) name += "_";
|
|
82
|
+
taken.add(name);
|
|
83
|
+
return name;
|
|
84
|
+
};
|
|
85
|
+
const flowApiRoots = ["agent", "control", "display", "file", "flow", "provide", "tool", "workspace"];
|
|
86
|
+
const flowApiBinding = new Map(flowApiRoots.map((root) => [root, uniqueName(root, `${root}Api`)]));
|
|
87
|
+
const apiCall = (name) => {
|
|
88
|
+
const [root, ...tail] = String(name || "").split(".");
|
|
89
|
+
return [flowApiBinding.get(root) || root, ...tail].join(".");
|
|
90
|
+
};
|
|
91
|
+
|
|
73
92
|
// 同一个节点的 body 和某个引脚值可能都超阈值,文件名必须带槽名区分:都叫
|
|
74
93
|
// `prompts/<id>.md` 的话两段不同的文本会写进同一个文件,往返时后写的覆盖先写的。
|
|
75
94
|
const externalize = (id, slot, text) => {
|
|
@@ -83,7 +102,7 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
83
102
|
};
|
|
84
103
|
const textArg = (id, slot, text) => {
|
|
85
104
|
const rel = externalize(id, slot, text);
|
|
86
|
-
return rel ?
|
|
105
|
+
return rel ? `${apiCall("file")}(${JSON.stringify(rel)})` : literal(text);
|
|
87
106
|
};
|
|
88
107
|
|
|
89
108
|
const controlNext = new Map();
|
|
@@ -114,18 +133,6 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
114
133
|
}),
|
|
115
134
|
);
|
|
116
135
|
|
|
117
|
-
// 文件里所有顶层标识符共用一个命名空间:节点 id、解构出来的输出变量、import 绑定名。
|
|
118
|
-
// 分开发号就会撞——两个节点各有一个叫 ok 的自定义输出槽,是完全正常的图,但会生成两条
|
|
119
|
-
// `const { ok } = ...`,文件根本解析不回来。
|
|
120
|
-
const taken = new Set(Object.keys(N));
|
|
121
|
-
const uniqueName = (base, fallback) => {
|
|
122
|
-
let name = isIdentifier(base) ? base : fallback;
|
|
123
|
-
if (taken.has(name)) name = fallback;
|
|
124
|
-
while (taken.has(name)) name += "_";
|
|
125
|
-
taken.add(name);
|
|
126
|
-
return name;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
136
|
// 自定义输出槽通过解构暴露成变量:`const { storyId } = node;`
|
|
130
137
|
const outVar = new Map();
|
|
131
138
|
for (const [id, slots] of destructured) {
|
|
@@ -143,7 +150,7 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
143
150
|
const shared = [...bindingOf.values()].find((v) => v.spec === pkg.specifier);
|
|
144
151
|
bindingOf.set(id, shared || { name: uniqueName(base, `${base}Node`), spec: pkg.specifier });
|
|
145
152
|
}
|
|
146
|
-
const callee = (id) => bindingOf.get(id)?.name || apiName(N[id].definitionId);
|
|
153
|
+
const callee = (id) => bindingOf.get(id)?.name || apiCall(apiName(N[id].definitionId));
|
|
147
154
|
|
|
148
155
|
const bodyTextOf = (id) => String(
|
|
149
156
|
(N[id].definitionId === "tool_nodejs" && N[id].script) ? N[id].script : (N[id].body || ""),
|
|
@@ -256,7 +263,7 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
256
263
|
};
|
|
257
264
|
const printItem = (item) => (
|
|
258
265
|
Array.isArray(item)
|
|
259
|
-
?
|
|
266
|
+
? `${apiCall("flow.fork")}(${item.map((s) => `${apiCall("flow")}(${s.map(printItem).join(", ")})`).join(", ")})`
|
|
260
267
|
: item
|
|
261
268
|
);
|
|
262
269
|
|
|
@@ -286,8 +293,8 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
286
293
|
if (isIf(id)) {
|
|
287
294
|
const thenIds = (controlNext.get(id) || []).filter((x) => x.slot === "next1").map((x) => x.to);
|
|
288
295
|
const elseIds = (controlNext.get(id) || []).filter((x) => x.slot === "next2").map((x) => x.to);
|
|
289
|
-
args.push(
|
|
290
|
-
args.push(
|
|
296
|
+
args.push(`${apiCall("flow")}(${chainFrom(thenIds).map(printItem).join(", ")})`);
|
|
297
|
+
args.push(`${apiCall("flow")}(${chainFrom(elseIds).map(printItem).join(", ")})`);
|
|
291
298
|
} else {
|
|
292
299
|
const usesScript = node.definitionId === "tool_nodejs" && node.script;
|
|
293
300
|
const body = usesScript ? node.script : node.body;
|
|
@@ -323,14 +330,14 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
323
330
|
if (N[runId].label) head.push(literal(N[runId].label));
|
|
324
331
|
// 排程配置存在 run 节点的 body 里,是 JSON 字符串
|
|
325
332
|
if (definitionId === "workspace_scheduled_run") head.push(N[runId].body ? literal(N[runId].body) : "null");
|
|
326
|
-
const fn = definitionId === "workspace_scheduled_run" ? "flow.schedule" : "flow";
|
|
333
|
+
const fn = apiCall(definitionId === "workspace_scheduled_run" ? "flow.schedule" : "flow");
|
|
327
334
|
out.push(`export const ${runId} = ${fn}(${[...head, ...seq].join(", ")});\n`);
|
|
328
335
|
}
|
|
329
336
|
|
|
330
337
|
// 一个 run 直接连到另一个 run:接力,不是子图
|
|
331
338
|
for (const [src, list] of [...controlNext].sort()) {
|
|
332
339
|
for (const x of list) {
|
|
333
|
-
if (RUN_DEFINITIONS.has(N[x.to].definitionId)) out.push(
|
|
340
|
+
if (RUN_DEFINITIONS.has(N[x.to].definitionId)) out.push(`${apiCall("flow.resume")}(${src}, ${x.to});\n`);
|
|
334
341
|
}
|
|
335
342
|
}
|
|
336
343
|
|
|
@@ -339,13 +346,17 @@ export function generateFlowSource(ir, opts = {}) {
|
|
|
339
346
|
for (const id of ids) {
|
|
340
347
|
if (declared.has(id) || RUN_DEFINITIONS.has(N[id].definitionId) || controlTargets.has(id)) continue;
|
|
341
348
|
if (!(controlNext.get(id) || []).length) continue;
|
|
342
|
-
out.push(
|
|
349
|
+
out.push(`${apiCall("flow.detached")}(${chainFrom([id]).map(printItem).join(", ")});\n`);
|
|
343
350
|
}
|
|
344
351
|
for (const id of ids) {
|
|
345
352
|
if (!declared.has(id) && !RUN_DEFINITIONS.has(N[id].definitionId)) declare(id, true);
|
|
346
353
|
}
|
|
347
354
|
|
|
348
|
-
const
|
|
355
|
+
const flowImports = flowApiRoots.map((root) => {
|
|
356
|
+
const local = flowApiBinding.get(root);
|
|
357
|
+
return local === root ? root : `${root} as ${local}`;
|
|
358
|
+
});
|
|
359
|
+
const imports = [`import { ${flowImports.join(", ")} } from "agentflow/flow";`];
|
|
349
360
|
const seenBinding = new Set();
|
|
350
361
|
for (const id of ids) {
|
|
351
362
|
const binding = bindingOf.get(id);
|
|
@@ -67,6 +67,34 @@ function slotsOf(instance, definitionId, kind) {
|
|
|
67
67
|
|
|
68
68
|
const isNodeSlot = (slot) => String(slot?.type || "") === "node";
|
|
69
69
|
|
|
70
|
+
// DSL 里的节点 id 同时也是顶层 `const` 变量名。旧 YAML 允许 `foo-bar`、数字开头,
|
|
71
|
+
// 甚至 JS 关键字;直接交给 codegen 会生成无法解析的 workspace.flow.js,整张图只能退回
|
|
72
|
+
// graph.json。迁移时稳定地改成合法且唯一的绑定名,并把改名清单返回给调用方。
|
|
73
|
+
const JS_RESERVED_WORDS = new Set([
|
|
74
|
+
"arguments", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default",
|
|
75
|
+
"delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function",
|
|
76
|
+
"if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "eval",
|
|
77
|
+
"private", "protected", "public", "return", "static", "super", "switch", "this", "throw", "true",
|
|
78
|
+
"try", "typeof", "var", "void", "while", "with", "yield",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
function legacyNodeIdMap(ids) {
|
|
82
|
+
const used = new Set();
|
|
83
|
+
const mapping = new Map();
|
|
84
|
+
for (const raw of ids) {
|
|
85
|
+
let base = String(raw || "").replace(/[^A-Za-z0-9_$]/g, "_");
|
|
86
|
+
if (!/^[A-Za-z_$]/.test(base)) base = `node_${base}`;
|
|
87
|
+
if (!base) base = "node";
|
|
88
|
+
if (JS_RESERVED_WORDS.has(base)) base = `${base}_node`;
|
|
89
|
+
let next = base;
|
|
90
|
+
let suffix = 2;
|
|
91
|
+
while (used.has(next)) next = `${base}_${suffix++}`;
|
|
92
|
+
used.add(next);
|
|
93
|
+
mapping.set(raw, next);
|
|
94
|
+
}
|
|
95
|
+
return mapping;
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
/**
|
|
71
99
|
* 把老实例的槽位搬到新定义的槽位表上:先对名字,对不上的按位置兜底。
|
|
72
100
|
*
|
|
@@ -121,6 +149,7 @@ function rebuildSlots(oldSlots, newDefSlots) {
|
|
|
121
149
|
* remapped: Array<{ id: string, from: string, to: string, caveat?: string }>,
|
|
122
150
|
* dropped: Array<{ id: string, definitionId: string, reason: string }>,
|
|
123
151
|
* droppedEdges: Array<{ source: string, target: string, reason: string }>,
|
|
152
|
+
* renamedIds: Array<{ from: string, to: string }>,
|
|
124
153
|
* warnings: string[],
|
|
125
154
|
* }}
|
|
126
155
|
*/
|
|
@@ -129,10 +158,14 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
129
158
|
if (!parsed || typeof parsed !== "object") throw new Error("flow.yaml 解析不出对象");
|
|
130
159
|
const srcInstances = parsed.instances && typeof parsed.instances === "object" ? parsed.instances : {};
|
|
131
160
|
const srcEdges = Array.isArray(parsed.edges) ? parsed.edges : [];
|
|
161
|
+
const idMap = legacyNodeIdMap(Object.keys(srcInstances));
|
|
132
162
|
|
|
133
163
|
const remapped = [];
|
|
134
164
|
const dropped = [];
|
|
135
165
|
const droppedEdges = [];
|
|
166
|
+
const renamedIds = [...idMap]
|
|
167
|
+
.filter(([from, to]) => from !== to)
|
|
168
|
+
.map(([from, to]) => ({ from, to }));
|
|
136
169
|
const warnings = [];
|
|
137
170
|
|
|
138
171
|
/** id -> { input, output, renameIn, renameOut },用来重接边。 */
|
|
@@ -141,7 +174,8 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
141
174
|
const benignDrops = new Set();
|
|
142
175
|
const instances = {};
|
|
143
176
|
|
|
144
|
-
for (const [
|
|
177
|
+
for (const [legacyId, raw] of Object.entries(srcInstances)) {
|
|
178
|
+
const id = idMap.get(legacyId);
|
|
145
179
|
const instance = raw && typeof raw === "object" ? raw : {};
|
|
146
180
|
const from = String(instance.definitionId || "");
|
|
147
181
|
const oldIn = slotsOf(instance, from, "input");
|
|
@@ -154,17 +188,17 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
154
188
|
// 终点节点本来就没有对应物需要表达。所以标 benign——报出来,但不算有损。
|
|
155
189
|
if (hasRemap && to === null) {
|
|
156
190
|
dropped.push({
|
|
157
|
-
id,
|
|
191
|
+
id: legacyId,
|
|
158
192
|
definitionId: from,
|
|
159
193
|
benign: true,
|
|
160
194
|
reason: "Workspace 没有终点节点,跑到没有后继就结束",
|
|
161
195
|
});
|
|
162
|
-
benignDrops.add(
|
|
196
|
+
benignDrops.add(legacyId);
|
|
163
197
|
continue;
|
|
164
198
|
}
|
|
165
199
|
if (!hasRemap && definitionOf(from).runtime === "none") {
|
|
166
200
|
dropped.push({
|
|
167
|
-
id,
|
|
201
|
+
id: legacyId,
|
|
168
202
|
definitionId: from,
|
|
169
203
|
benign: false,
|
|
170
204
|
reason: DEFINITIONS[from]
|
|
@@ -180,13 +214,14 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
180
214
|
const rebuiltOut = rebuildSlots(oldOut, def.output || []);
|
|
181
215
|
const next = { ...instance, definitionId: to, input: rebuiltIn.slots, output: rebuiltOut.slots };
|
|
182
216
|
instances[id] = next;
|
|
183
|
-
kept.set(
|
|
217
|
+
kept.set(legacyId, {
|
|
218
|
+
id,
|
|
184
219
|
input: next.input,
|
|
185
220
|
output: next.output,
|
|
186
221
|
renameIn: rebuiltIn.renames,
|
|
187
222
|
renameOut: rebuiltOut.renames,
|
|
188
223
|
});
|
|
189
|
-
const entry = { id, from, to };
|
|
224
|
+
const entry = { id: legacyId, from, to };
|
|
190
225
|
const droppedFields = [];
|
|
191
226
|
for (const field of REMAP_DROPPED_FIELDS[to] || []) {
|
|
192
227
|
const text = String(next[field] ?? "").trim();
|
|
@@ -204,7 +239,7 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
204
239
|
// 拿定义去覆盖会把作者加的输入抹掉。
|
|
205
240
|
const next = { ...instance, input: [...oldIn], output: [...oldOut] };
|
|
206
241
|
instances[id] = next;
|
|
207
|
-
kept.set(
|
|
242
|
+
kept.set(legacyId, { id, input: next.input, output: next.output, renameIn: new Map(), renameOut: new Map() });
|
|
208
243
|
}
|
|
209
244
|
}
|
|
210
245
|
|
|
@@ -241,28 +276,28 @@ export function legacyYamlToDesignGraph(yamlText) {
|
|
|
241
276
|
}
|
|
242
277
|
// Workspace 禁止 fan-in:同一个输入槽只能有一条入边。老图里靠 control_anyOne
|
|
243
278
|
// 汇合的分支,删掉汇合点之后会撞在同一个槽上。
|
|
244
|
-
const key = `${
|
|
279
|
+
const key = `${to.id} input-${tgtIdx}`;
|
|
245
280
|
if (takenTargets.has(key)) {
|
|
246
281
|
droppedEdges.push({ source, target, benign: false, reason: `${target}.${tgtFinal} 已经有入边了,Workspace 不允许 fan-in` });
|
|
247
282
|
continue;
|
|
248
283
|
}
|
|
249
284
|
takenTargets.add(key);
|
|
250
|
-
edges.push({ source, target, sourceHandle: `output-${srcIdx}`, targetHandle: `input-${tgtIdx}` });
|
|
285
|
+
edges.push({ source: from.id, target: to.id, sourceHandle: `output-${srcIdx}`, targetHandle: `input-${tgtIdx}` });
|
|
251
286
|
}
|
|
252
287
|
|
|
253
288
|
// ── ui ────────────────────────────────────────────────────────────────────
|
|
254
289
|
const srcUi = parsed.ui && typeof parsed.ui === "object" ? parsed.ui : {};
|
|
255
290
|
const nodePositions = {};
|
|
256
291
|
for (const [id, pos] of Object.entries(srcUi.nodePositions || {})) {
|
|
257
|
-
if (kept.has(id)) nodePositions[id] = pos;
|
|
292
|
+
if (kept.has(id)) nodePositions[kept.get(id).id] = pos;
|
|
258
293
|
}
|
|
259
294
|
const ui = { nodePositions, nodeSizes: {} };
|
|
260
295
|
if (typeof srcUi.description === "string" && srcUi.description.trim()) ui.description = srcUi.description;
|
|
261
296
|
|
|
262
297
|
if (!Object.keys(instances).length) warnings.push("迁移之后一个节点都不剩");
|
|
263
|
-
else if (![...kept.
|
|
298
|
+
else if (![...kept.values()].some(({ id }) => instances[id].definitionId === "workspace_run")) {
|
|
264
299
|
warnings.push("图里没有运行节点(老流程缺 control_start);补一个 workspace_run 才能跑");
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
return { graph: { version: 1, instances, edges, ui }, remapped, dropped, droppedEdges, warnings };
|
|
302
|
+
return { graph: { version: 1, instances, edges, ui }, remapped, dropped, droppedEdges, renamedIds, warnings };
|
|
268
303
|
}
|
|
@@ -59,6 +59,26 @@ export function parseFlowSource(source, opts = {}) {
|
|
|
59
59
|
unresolved.push({ line: node?.loc?.start?.line || 0, message });
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
// codegen 在节点 id 与 DSL API 名冲突时会生成
|
|
63
|
+
// `import { display as displayApi } from "agentflow/flow"`。静态解析时先把 local 名还原成
|
|
64
|
+
// canonical API 名,后面的节点类型、flow 控制语句和 file() 才仍走同一套规则。
|
|
65
|
+
const flowApiOf = new Map();
|
|
66
|
+
for (const stmt of ast.body) {
|
|
67
|
+
if (stmt.type !== "ImportDeclaration" || String(stmt.source.value) !== "agentflow/flow") continue;
|
|
68
|
+
for (const specifier of stmt.specifiers || []) {
|
|
69
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
70
|
+
const imported = specifier.imported?.name ?? specifier.imported?.value;
|
|
71
|
+
const local = specifier.local?.name;
|
|
72
|
+
if (imported && local) flowApiOf.set(local, String(imported));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const apiCalleePath = (node) => {
|
|
76
|
+
const raw = calleePath(node);
|
|
77
|
+
if (!raw) return raw;
|
|
78
|
+
const [root, ...tail] = raw.split(".");
|
|
79
|
+
return [flowApiOf.get(root) || root, ...tail].join(".");
|
|
80
|
+
};
|
|
81
|
+
|
|
62
82
|
function stringOf(node) {
|
|
63
83
|
if (!node) return null;
|
|
64
84
|
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
@@ -72,7 +92,7 @@ export function parseFlowSource(source, opts = {}) {
|
|
|
72
92
|
return String(node.operator === "-" ? -node.argument.value : node.argument.value);
|
|
73
93
|
}
|
|
74
94
|
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked;
|
|
75
|
-
if (node.type === "CallExpression" &&
|
|
95
|
+
if (node.type === "CallExpression" && apiCalleePath(node.callee) === "file") {
|
|
76
96
|
const rel = stringOf(node.arguments[0]);
|
|
77
97
|
if (rel == null) return null;
|
|
78
98
|
if (!(rel in files)) throw new Error(`file(${JSON.stringify(rel)}) 找不到对应文件`);
|
|
@@ -229,7 +249,7 @@ export function parseFlowSource(source, opts = {}) {
|
|
|
229
249
|
const out = [];
|
|
230
250
|
for (const arg of call.arguments) {
|
|
231
251
|
if (arg.type === "Identifier") out.push(arg.name);
|
|
232
|
-
else if (arg.type === "CallExpression" &&
|
|
252
|
+
else if (arg.type === "CallExpression" && apiCalleePath(arg.callee) === "flow.fork") {
|
|
233
253
|
out.push({ fork: arg.arguments.map(itemsOf) });
|
|
234
254
|
} else {
|
|
235
255
|
unresolvedAt(arg, "控制流参数只能是节点变量名或 flow.fork(...)");
|
|
@@ -277,7 +297,7 @@ export function parseFlowSource(source, opts = {}) {
|
|
|
277
297
|
unresolvedAt(d, `${id}: 节点声明右边必须是一次节点调用`);
|
|
278
298
|
continue;
|
|
279
299
|
}
|
|
280
|
-
const path =
|
|
300
|
+
const path = apiCalleePath(init.callee);
|
|
281
301
|
const args = [...init.arguments];
|
|
282
302
|
|
|
283
303
|
const first = args[0];
|
|
@@ -384,7 +404,7 @@ export function parseFlowSource(source, opts = {}) {
|
|
|
384
404
|
}
|
|
385
405
|
|
|
386
406
|
if (decl?.type === "ExpressionStatement" && decl.expression.type === "CallExpression") {
|
|
387
|
-
const path =
|
|
407
|
+
const path = apiCalleePath(decl.expression.callee);
|
|
388
408
|
if (path === "flow.resume") {
|
|
389
409
|
const [a, b] = decl.expression.arguments.map((x) => x.name);
|
|
390
410
|
if (a && b) edges.push(`${a}|next|${b}|prev`);
|