@fieldwangai/agentflow 0.1.153 → 0.1.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -34
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +165 -90
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +277 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +8 -28
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +37 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +152 -286
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1092 -18123
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +12 -2
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +1 -1
- package/skills/agentflow-cli/SKILL.md +139 -24
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +7 -4
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +2 -2
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设计态图 <-> 规范 IR。
|
|
3
|
+
*
|
|
4
|
+
* IR 是代码生成和解析的共同中间表示,两条方向都以它为准,因此往返能证明。它与
|
|
5
|
+
* `workspace.graph.json` 的关键差别只有一处:
|
|
6
|
+
*
|
|
7
|
+
* **边按槽名记录,不按句柄下标。** 图里的边是 `output-1 -> input-2`,下标依赖实例
|
|
8
|
+
* 自己的槽位数组顺序;而槽位顺序是实例级的(语料里 32 个实例的顺序偏离定义表),
|
|
9
|
+
* 从代码反推不出来。改成 `源节点|源槽名|目标节点|目标槽名` 后,代码里写
|
|
10
|
+
* `{ content: agent1.result }` 就能无歧义地还原成一条边;下标由 layout 里的
|
|
11
|
+
* `pinOrder` 负责恢复。
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
CTRL_SLOTS,
|
|
15
|
+
STD_SLOTS,
|
|
16
|
+
definitionOf,
|
|
17
|
+
isControlSlot,
|
|
18
|
+
isDisplayDefinition,
|
|
19
|
+
isProvideDefinition,
|
|
20
|
+
} from "./defs.mjs";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 正文默认镜像自「第一个有值的文本入参」的节点类型。
|
|
24
|
+
*
|
|
25
|
+
* 展示节点的 `body` 和内容引脚在 UI 里本来就是同一份东西,代码里写两遍纯属噪音,所以
|
|
26
|
+
* 只写引脚、正文由 `irToGraph` 反推。**唯一的例外**——引脚有值但正文被清空了——反推会
|
|
27
|
+
* 凭空造出正文,所以那种情况在 nodes.json 里显式记 `bodyMirror: false`。
|
|
28
|
+
*/
|
|
29
|
+
export function mirrorsBodyFromPin(definitionId) {
|
|
30
|
+
return isDisplayDefinition(definitionId) || definitionId === "control_load_skills";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 这个实例是否属于上面说的例外:内容引脚有值,正文却是空的。 */
|
|
34
|
+
export function bodyMirrorSuppressed(instance) {
|
|
35
|
+
if (!mirrorsBodyFromPin(String(instance?.definitionId || ""))) return false;
|
|
36
|
+
if (String(instance?.body ?? "").trim()) return false;
|
|
37
|
+
return (Array.isArray(instance?.input) ? instance.input : []).some(
|
|
38
|
+
(s) => String(s?.type) === "text" && String(s?.value ?? "").trim(),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 代码里不体现、由 nodes.json 承载的实例属性。 */
|
|
43
|
+
export const NODE_META_KEYS = [
|
|
44
|
+
"model",
|
|
45
|
+
"marketplaceRef",
|
|
46
|
+
"marketplacePackageId",
|
|
47
|
+
"marketplaceVersion",
|
|
48
|
+
"sourceContextRunNodeId",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function handleIndex(handle) {
|
|
52
|
+
const m = /-(\d+)$/.exec(String(handle || ""));
|
|
53
|
+
return m ? Number(m[1]) : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 把设计态图转成 IR。
|
|
58
|
+
*
|
|
59
|
+
* 传进来的应当是 `splitWorkspaceGraph().design`——运行产出已经不在里面,所以这里
|
|
60
|
+
* 不必再判断哪些值是跑出来的。
|
|
61
|
+
*/
|
|
62
|
+
export function graphToIr(graph) {
|
|
63
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
64
|
+
const slotAt = (id, kind, handle) => (instances[id]?.[kind] || [])[handleIndex(handle)];
|
|
65
|
+
|
|
66
|
+
const edges = [];
|
|
67
|
+
const wiredInputs = new Set();
|
|
68
|
+
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
69
|
+
if (!instances[edge?.source] || !instances[edge?.target]) continue;
|
|
70
|
+
const from = slotAt(edge.source, "output", edge.sourceHandle);
|
|
71
|
+
const to = slotAt(edge.target, "input", edge.targetHandle);
|
|
72
|
+
if (!from || !to) continue;
|
|
73
|
+
edges.push(`${edge.source}|${from.name}|${edge.target}|${to.name}`);
|
|
74
|
+
if (!isControlSlot(to)) wiredInputs.add(`${edge.target}|${to.name}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const nodes = {};
|
|
78
|
+
const slotOrder = {};
|
|
79
|
+
for (const [id, instance] of Object.entries(instances)) {
|
|
80
|
+
const definitionId = String(instance.definitionId || "");
|
|
81
|
+
const def = definitionOf(definitionId);
|
|
82
|
+
const defInputs = new Set(def.input.map((s) => s.name));
|
|
83
|
+
const defOutputs = new Set(def.output.map((s) => s.name));
|
|
84
|
+
|
|
85
|
+
const node = { definitionId, attrs: {} };
|
|
86
|
+
if (String(instance.label || "").trim()) node.label = String(instance.label);
|
|
87
|
+
if (String(instance.script || "").trim()) node.script = String(instance.script);
|
|
88
|
+
for (const key of NODE_META_KEYS) {
|
|
89
|
+
if (String(instance[key] || "").trim()) node.attrs[key] = String(instance[key]);
|
|
90
|
+
}
|
|
91
|
+
if (String(instance.role || "").trim() && String(instance.role) !== "normal") {
|
|
92
|
+
node.attrs.role = String(instance.role);
|
|
93
|
+
}
|
|
94
|
+
if (instance.images !== undefined && instance.images !== null) node.attrs.images = instance.images;
|
|
95
|
+
if (instance.globalContext === true) node.attrs.globalContext = true;
|
|
96
|
+
|
|
97
|
+
node.inputs = {};
|
|
98
|
+
node.inputTypes = {};
|
|
99
|
+
node.outputs = {};
|
|
100
|
+
node.extraIn = [];
|
|
101
|
+
node.extraOut = [];
|
|
102
|
+
|
|
103
|
+
for (const slot of instance.input || []) {
|
|
104
|
+
const name = String(slot?.name || "");
|
|
105
|
+
if (!name) continue;
|
|
106
|
+
// 既不在定义表里、也不是标准槽的,是这个实例自己加的槽,代码里要显式声明
|
|
107
|
+
if (!STD_SLOTS.has(name) && !defInputs.has(name) && String(slot.type) !== "node") {
|
|
108
|
+
node.extraIn.push(name);
|
|
109
|
+
// 自定义槽的类型在定义表里查不到,只能随槽本身走一趟 IR,否则往返一次
|
|
110
|
+
// bool 槽就退化成 text,代码里的 `true` 变成 `"true"`
|
|
111
|
+
if (slot.type && String(slot.type) !== "text") node.inputTypes[name] = String(slot.type);
|
|
112
|
+
}
|
|
113
|
+
if (isControlSlot(slot) || wiredInputs.has(`${id}|${name}`)) continue;
|
|
114
|
+
const value = String(slot.value ?? slot.default ?? "");
|
|
115
|
+
if (value.trim()) node.inputs[name] = value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (String(instance.body || "").trim()) {
|
|
119
|
+
// 展示类节点的 body 常常只是「第一个有值的文本入参」的副本;这种镜像不进代码,
|
|
120
|
+
// 由 irToGraph 用同一条规则反推,两边逻辑闭合。
|
|
121
|
+
const firstText = (instance.input || []).find(
|
|
122
|
+
(s) => String(s?.type) === "text" && String(s?.value ?? "").trim(),
|
|
123
|
+
);
|
|
124
|
+
const mirrored = mirrorsBodyFromPin(definitionId)
|
|
125
|
+
&& firstText
|
|
126
|
+
&& String(firstText.value) === String(instance.body);
|
|
127
|
+
if (!mirrored) node.body = String(instance.body);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const slot of instance.output || []) {
|
|
131
|
+
const name = String(slot?.name || "");
|
|
132
|
+
if (!name) continue;
|
|
133
|
+
if (!STD_SLOTS.has(name) && !defOutputs.has(name) && String(slot.type) !== "node") node.extraOut.push(name);
|
|
134
|
+
// 设计态里只有 provide_* 的输出值是作者填的,其余都是运行产出(已被 Phase 0 剥离)
|
|
135
|
+
if (!isProvideDefinition(definitionId) || isControlSlot(slot)) continue;
|
|
136
|
+
const value = String(slot.value ?? slot.default ?? "");
|
|
137
|
+
if (value.trim()) node.outputs[name] = value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
nodes[id] = node;
|
|
141
|
+
slotOrder[id] = {
|
|
142
|
+
in: (instance.input || []).map((s) => String(s?.name || "")),
|
|
143
|
+
out: (instance.output || []).map((s) => String(s?.name || "")),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { nodes, edges: [...new Set(edges)].sort(), slotOrder };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 自定义输入槽的类型推断:**跟着连它的那个上游输出槽走**。
|
|
152
|
+
*
|
|
153
|
+
* 代码里 `{ sample: count.sample }` 只说了「接哪」,没说这个槽是什么类型——而类型决定运行时
|
|
154
|
+
* 怎么送值:`workspaceLinkedOutputShouldStayPath` 看的是**目标槽**的 type,`file` 才保留路径,
|
|
155
|
+
* 否则把文件内容读出来内联。自定义槽以前一律建成 `text`,于是一个 `file` 输出接过去,脚本
|
|
156
|
+
* 拿到的是整个文件的内容(还被 shell 引号包住):
|
|
157
|
+
*
|
|
158
|
+
* wc -l < '2026-08-10 row-1
|
|
159
|
+
* 2026-08-10 row-2' → No such file or directory
|
|
160
|
+
*
|
|
161
|
+
* 唯一能救的是名字启发式(`xxxPath` / `xxxFile` 结尾),也就是说对不对全看作者怎么起名。
|
|
162
|
+
* 改成随上游走之后,`{ sample: count.sample }` 直接就是 `file` 槽。
|
|
163
|
+
*
|
|
164
|
+
* 只有 `bool` 能被代码自己带回来(字面量 `true` 看得出类型);其余偏离推断的类型由
|
|
165
|
+
* `layout.json` 的 `pins.<kind>.<name>.type` 记一条——和 `pinOrder` 同一个套路,
|
|
166
|
+
* 只记偏离,历史数据因此原样往返。
|
|
167
|
+
*
|
|
168
|
+
* @param {object} ir
|
|
169
|
+
* @returns {(nodeId: string, slotName: string) => string}
|
|
170
|
+
*/
|
|
171
|
+
export function customInputTypeResolver(ir) {
|
|
172
|
+
const wired = new Map();
|
|
173
|
+
for (const key of ir?.edges || []) {
|
|
174
|
+
const [source, fromSlot, target, toSlot] = String(key).split("|");
|
|
175
|
+
if (target && toSlot && !wired.has(`${target}|${toSlot}`)) wired.set(`${target}|${toSlot}`, { source, fromSlot });
|
|
176
|
+
}
|
|
177
|
+
const outputTypeOf = (nodeId, slotName) => {
|
|
178
|
+
const node = ir?.nodes?.[nodeId];
|
|
179
|
+
if (!node) return "";
|
|
180
|
+
const defSlots = node.packageDef ? node.packageDef.output : definitionOf(node.definitionId).output;
|
|
181
|
+
return String((defSlots || []).find((s) => s?.name === slotName)?.type || "");
|
|
182
|
+
};
|
|
183
|
+
return (nodeId, slotName) => {
|
|
184
|
+
const link = wired.get(`${nodeId}|${slotName}`);
|
|
185
|
+
if (!link) return "text";
|
|
186
|
+
// 上游是自定义输出槽(解构出来的)时查不到类型,那就还是 text
|
|
187
|
+
return outputTypeOf(link.source, link.fromSlot) || "text";
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* IR + layout -> 设计态图。
|
|
193
|
+
*
|
|
194
|
+
* 槽位数组按「定义表顺序 + 代码里出现的自定义槽」重建;layout 里记了 `pinOrder`
|
|
195
|
+
* 的实例用记录的顺序,因为那是偏离规范序的历史数据。边的句柄下标由重建后的顺序反查。
|
|
196
|
+
*/
|
|
197
|
+
export function irToGraph(ir, layout = { nodes: {} }, nodeMeta = { nodes: {} }) {
|
|
198
|
+
const instances = {};
|
|
199
|
+
const slotIndex = {};
|
|
200
|
+
const inferInputType = customInputTypeResolver(ir);
|
|
201
|
+
|
|
202
|
+
for (const [id, node] of Object.entries(ir.nodes)) {
|
|
203
|
+
const def = definitionOf(node.definitionId);
|
|
204
|
+
const layoutEntry = layout.nodes?.[id] || {};
|
|
205
|
+
const meta = nodeMeta.nodes?.[id] || {};
|
|
206
|
+
|
|
207
|
+
const buildSlots = (kind) => {
|
|
208
|
+
// 代码节点包自带槽位表;基础类型只是运行方式,不决定这个节点有哪些引脚
|
|
209
|
+
const pkgDef = node.packageDef;
|
|
210
|
+
const defSlots = pkgDef
|
|
211
|
+
? (kind === "in" ? pkgDef.input : pkgDef.output)
|
|
212
|
+
: (kind === "in" ? def.input : def.output);
|
|
213
|
+
const extras = (kind === "in" ? node.extraIn : node.extraOut)
|
|
214
|
+
.filter((name) => !defSlots.some((s) => s.name === name));
|
|
215
|
+
const names = layoutEntry.pinOrder?.[kind] || [...defSlots.map((s) => s.name), ...extras];
|
|
216
|
+
const byName = new Map(defSlots.map((s) => [s.name, s]));
|
|
217
|
+
return names.map((name) => {
|
|
218
|
+
const d = byName.get(name);
|
|
219
|
+
const overrides = layoutEntry.pins?.[kind]?.[name] || {};
|
|
220
|
+
// 自定义槽的类型:代码里带得回来的(bool 字面量)优先,其次 layout 记的偏离,
|
|
221
|
+
// 最后随上游输出槽推断。输出槽没有上游可随,记了什么就是什么,没记就是 text。
|
|
222
|
+
const custom = kind === "in"
|
|
223
|
+
? (node.inputTypes?.[name] || overrides.type || inferInputType(id, name))
|
|
224
|
+
: overrides.type;
|
|
225
|
+
const slot = {
|
|
226
|
+
type: d ? d.type : (custom || (name === "prev" || name === "next" ? "node" : "text")),
|
|
227
|
+
name,
|
|
228
|
+
value: "",
|
|
229
|
+
};
|
|
230
|
+
if (d?.description) slot.description = d.description;
|
|
231
|
+
if (d?.required) slot.required = true;
|
|
232
|
+
if (d?.showOnNode) slot.showOnNode = true;
|
|
233
|
+
for (const key of ["showOnNode", "required"]) {
|
|
234
|
+
if (key in overrides) slot[key] = overrides[key];
|
|
235
|
+
}
|
|
236
|
+
return slot;
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const input = buildSlots("in");
|
|
241
|
+
const output = buildSlots("out");
|
|
242
|
+
slotIndex[id] = {
|
|
243
|
+
in: new Map(input.map((s, i) => [s.name, i])),
|
|
244
|
+
out: new Map(output.map((s, i) => [s.name, i])),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const instance = { definitionId: node.definitionId };
|
|
248
|
+
if (node.label) instance.label = node.label;
|
|
249
|
+
// `role: "normal"` 就是没写 role 的意思,别把默认值写回图里
|
|
250
|
+
if (meta.role !== undefined) instance.role = meta.role;
|
|
251
|
+
for (const key of NODE_META_KEYS) if (meta[key] !== undefined) instance[key] = meta[key];
|
|
252
|
+
// 从 `import x from "./nodes/x"` 推出来的引用信息压过 nodes.json——代码里写的那个
|
|
253
|
+
// import 才是作者的意思,nodes.json 只是上一次落盘的记录
|
|
254
|
+
for (const key of NODE_META_KEYS) if (node.attrs?.[key] !== undefined) instance[key] = node.attrs[key];
|
|
255
|
+
if (meta.images !== undefined) instance.images = meta.images;
|
|
256
|
+
if (meta.globalContext === true) instance.globalContext = true;
|
|
257
|
+
instance.input = input;
|
|
258
|
+
instance.output = output;
|
|
259
|
+
if (node.script) instance.script = node.script;
|
|
260
|
+
|
|
261
|
+
for (const [name, value] of Object.entries(node.inputs || {})) {
|
|
262
|
+
const i = slotIndex[id].in.get(name);
|
|
263
|
+
if (i != null) input[i].value = value;
|
|
264
|
+
}
|
|
265
|
+
for (const [name, value] of Object.entries(node.outputs || {})) {
|
|
266
|
+
const i = slotIndex[id].out.get(name);
|
|
267
|
+
if (i != null) output[i].value = value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (node.body) instance.body = node.body;
|
|
271
|
+
if (!instance.body && meta.bodyMirror !== false && mirrorsBodyFromPin(node.definitionId)) {
|
|
272
|
+
// 与 graphToIr 的镜像判断成对:body 省略了就从第一个有值的文本入参反推。
|
|
273
|
+
// 只补 body——同名输出槽的值属于运行态(在 workspace.state.json 里),这里凭空
|
|
274
|
+
// 造一个会让设计态多出一份原图没有的产出。
|
|
275
|
+
const primary = input.find((s) => s.type === "text" && String(s.value || "").trim());
|
|
276
|
+
if (primary) instance.body = primary.value;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
instances[id] = instance;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const edges = ir.edges.map((key) => {
|
|
283
|
+
const [source, fromSlot, target, toSlot] = key.split("|");
|
|
284
|
+
return {
|
|
285
|
+
source,
|
|
286
|
+
target,
|
|
287
|
+
sourceHandle: `output-${slotIndex[source]?.out.get(fromSlot) ?? 0}`,
|
|
288
|
+
targetHandle: `input-${slotIndex[target]?.in.get(toSlot) ?? 0}`,
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const ui = { nodePositions: {}, nodeSizes: {} };
|
|
293
|
+
for (const [id, entry] of Object.entries(layout.nodes || {})) {
|
|
294
|
+
if (entry.x !== undefined) ui.nodePositions[id] = { x: entry.x, y: entry.y };
|
|
295
|
+
if (entry.w !== undefined) ui.nodeSizes[id] = { width: entry.w, height: entry.h };
|
|
296
|
+
}
|
|
297
|
+
for (const [key, value] of Object.entries(layout)) {
|
|
298
|
+
if (!["version", "nodes", "viewport"].includes(key)) ui[key] = value;
|
|
299
|
+
}
|
|
300
|
+
if (layout.viewport) ui.viewport = layout.viewport;
|
|
301
|
+
|
|
302
|
+
return { version: 1, instances, edges, ui };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { CTRL_SLOTS, STD_SLOTS };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workspace.layout.json` / `workspace.nodes.json` 的抽取。
|
|
3
|
+
*
|
|
4
|
+
* 划分原则:**代码里说得清的就不进 JSON**。
|
|
5
|
+
*
|
|
6
|
+
* - layout.json —— 画布状态:坐标、尺寸、引脚显隐与顺序。这些和流程语义无关,
|
|
7
|
+
* 放代码里只会淹没结构。
|
|
8
|
+
* - nodes.json —— 说不清的节点级数据:粘贴的图片(base64,语料里单张 70 KB)、
|
|
9
|
+
* 机器管理的属性(marketplaceRef、model…)。
|
|
10
|
+
*
|
|
11
|
+
* 引脚显隐和顺序都只记**偏离定义表的部分**。字段缺失 = 继承定义表,不算漂移——
|
|
12
|
+
* 这一条踩过坑:把「没写 showOnNode」当成 `false` 会凭空造出一堆假的覆盖记录。
|
|
13
|
+
*/
|
|
14
|
+
import { definitionOf } from "./defs.mjs";
|
|
15
|
+
import { NODE_META_KEYS, bodyMirrorSuppressed, customInputTypeResolver } from "./ir.mjs";
|
|
16
|
+
|
|
17
|
+
function canonicalSlotNames(defSlots, extras) {
|
|
18
|
+
return [...defSlots.map((s) => s.name), ...extras.filter((x) => !defSlots.some((s) => s.name === x))];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} designGraph 设计态图
|
|
23
|
+
* @param {object} ir 同一张图的 IR(用来知道哪些是自定义槽)
|
|
24
|
+
* @returns {{ layout: object, nodeMeta: object }}
|
|
25
|
+
*/
|
|
26
|
+
export function extractLayout(designGraph, ir) {
|
|
27
|
+
const instances = designGraph?.instances && typeof designGraph.instances === "object"
|
|
28
|
+
? designGraph.instances
|
|
29
|
+
: {};
|
|
30
|
+
const ui = designGraph?.ui && typeof designGraph.ui === "object" ? designGraph.ui : {};
|
|
31
|
+
|
|
32
|
+
const layout = { version: 1, nodes: {} };
|
|
33
|
+
if (ui.viewport) layout.viewport = ui.viewport;
|
|
34
|
+
// ui 下除了坐标/尺寸/视口之外的键原样透传——语料里出现过 ui.groups: [],
|
|
35
|
+
// 用 Object.keys().length 判空会把它吃掉
|
|
36
|
+
for (const [key, value] of Object.entries(ui)) {
|
|
37
|
+
if (key === "nodePositions" || key === "nodeSizes" || key === "viewport") continue;
|
|
38
|
+
layout[key] = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const nodeMeta = { version: 1, nodes: {} };
|
|
42
|
+
const inferInputType = customInputTypeResolver(ir);
|
|
43
|
+
|
|
44
|
+
// 按 id 排序而不是按插入顺序:instances 的键序会随一次往返而变(irToGraph 按 IR 顺序
|
|
45
|
+
// 重建),不定序会让「没改任何东西的再保存」也产生 diff。
|
|
46
|
+
for (const id of Object.keys(instances).sort()) {
|
|
47
|
+
const instance = instances[id];
|
|
48
|
+
const def = definitionOf(String(instance.definitionId || ""));
|
|
49
|
+
const entry = {};
|
|
50
|
+
|
|
51
|
+
const pos = ui.nodePositions?.[id];
|
|
52
|
+
if (pos) {
|
|
53
|
+
entry.x = pos.x;
|
|
54
|
+
entry.y = pos.y;
|
|
55
|
+
}
|
|
56
|
+
const size = ui.nodeSizes?.[id];
|
|
57
|
+
if (size) {
|
|
58
|
+
entry.w = size.width;
|
|
59
|
+
entry.h = size.height;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const pins = {};
|
|
63
|
+
const irNode = ir.nodes[id];
|
|
64
|
+
for (const [kind, slots, defSlots] of [
|
|
65
|
+
["in", instance.input || [], def.input],
|
|
66
|
+
["out", instance.output || [], def.output],
|
|
67
|
+
]) {
|
|
68
|
+
const byName = new Map(defSlots.map((s) => [s.name, s]));
|
|
69
|
+
for (const slot of slots) {
|
|
70
|
+
const d = byName.get(slot.name);
|
|
71
|
+
for (const key of ["showOnNode", "required"]) {
|
|
72
|
+
if (!(key in slot)) continue; // 缺失 = 继承定义表,不是覆盖
|
|
73
|
+
const actual = Boolean(slot[key]);
|
|
74
|
+
if (d ? actual !== Boolean(d[key]) : actual) {
|
|
75
|
+
((pins[kind] ||= {})[slot.name] ||= {})[key] = actual;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// 自定义槽的类型偏离了默认时记一条。默认是什么两边不一样:
|
|
79
|
+
// 输入 —— 随上游输出槽推断,代码自己带得回来的 bool(字面量)不用记
|
|
80
|
+
// 输出 —— 没有上游可随,`irToGraph` 一律给 text
|
|
81
|
+
// 记了也只是重复的不记,否则存量流程的 layout 会平白多出 diff。
|
|
82
|
+
const extras = (kind === "in" ? irNode?.extraIn : irNode?.extraOut) || [];
|
|
83
|
+
if (d || !extras.includes(slot.name)) continue;
|
|
84
|
+
const actual = String(slot.type || "text");
|
|
85
|
+
if (kind === "in") {
|
|
86
|
+
if (actual === inferInputType(id, slot.name)) continue;
|
|
87
|
+
if (irNode.inputTypes?.[slot.name] === actual && actual === "bool") continue;
|
|
88
|
+
} else if (actual === "text") continue;
|
|
89
|
+
((pins[kind] ||= {})[slot.name] ||= {}).type = actual;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(pins).length) entry.pins = pins;
|
|
93
|
+
|
|
94
|
+
for (const [kind, slots, extras] of [
|
|
95
|
+
["in", instance.input || [], irNode?.extraIn || []],
|
|
96
|
+
["out", instance.output || [], irNode?.extraOut || []],
|
|
97
|
+
]) {
|
|
98
|
+
const actual = slots.map((s) => String(s.name || ""));
|
|
99
|
+
const canonical = canonicalSlotNames(kind === "in" ? def.input : def.output, extras);
|
|
100
|
+
if (JSON.stringify(actual) !== JSON.stringify(canonical)) (entry.pinOrder ||= {})[kind] = actual;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Object.keys(entry).length) layout.nodes[id] = entry;
|
|
104
|
+
|
|
105
|
+
const meta = {};
|
|
106
|
+
if (instance.images !== undefined && instance.images !== null) meta.images = instance.images;
|
|
107
|
+
for (const key of NODE_META_KEYS) if (String(instance[key] || "").trim()) meta[key] = instance[key];
|
|
108
|
+
if (instance.globalContext === true) meta.globalContext = true;
|
|
109
|
+
if (bodyMirrorSuppressed(instance)) meta.bodyMirror = false;
|
|
110
|
+
if (String(instance.role || "") && String(instance.role) !== "normal") meta.role = instance.role;
|
|
111
|
+
if (Object.keys(meta).length) nodeMeta.nodes[id] = meta;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { layout, nodeMeta };
|
|
115
|
+
}
|