@artflo-ai/artflo-openclaw-plugin 0.0.13 → 0.0.14
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/dist/index.js +64 -76
- package/dist/src/core/executor/element-builders.js +38 -4
- package/openclaw.plugin.json +18 -6
- package/package.json +2 -1
- package/skills/artflo-canvas/SKILL.md +1 -0
- package/skills/artflo-canvas/references/node-schema.json +16 -2
- package/skills/artflo-canvas/references/node-schema.md +18 -0
package/dist/index.js
CHANGED
|
@@ -30,93 +30,54 @@ const plugin = {
|
|
|
30
30
|
name: PLUGIN_NAME,
|
|
31
31
|
description: PLUGIN_DESCRIPTION,
|
|
32
32
|
configSchema: pluginConfigSchema,
|
|
33
|
-
|
|
33
|
+
register(api) {
|
|
34
34
|
const config = createPluginConfig(api);
|
|
35
|
-
// ── 确保 tools.alsoAllow 包含所有 Artflo 工具 ─────────────────────
|
|
36
|
-
try {
|
|
37
|
-
const allToolNames = Object.values(ARTFLO_TOOL_NAMES);
|
|
38
|
-
const cfg = api.runtime.config.loadConfig();
|
|
39
|
-
const tools = cfg.tools ?? {};
|
|
40
|
-
if (tools.allow && tools.allow.length > 0) {
|
|
41
|
-
const missing = allToolNames.filter((t) => !tools.allow.includes(t));
|
|
42
|
-
if (missing.length > 0) {
|
|
43
|
-
console.warn(`[artflo] tools.allow is set. Please add ${JSON.stringify(missing)} manually.`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
const existing = tools.alsoAllow ?? [];
|
|
48
|
-
const missing = allToolNames.filter((t) => !existing.includes(t));
|
|
49
|
-
if (missing.length > 0) {
|
|
50
|
-
await api.runtime.config.writeConfigFile({
|
|
51
|
-
...cfg,
|
|
52
|
-
tools: { ...tools, alsoAllow: [...existing, ...missing] },
|
|
53
|
-
});
|
|
54
|
-
console.log(`[artflo] Added ${missing.length} tools to tools.alsoAllow`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
console.warn(`[artflo] Failed to update tools.alsoAllow:`, err instanceof Error ? err.message : String(err));
|
|
60
|
-
}
|
|
61
|
-
// ── 没有 apiKey 时跳过所有初始化,只注册 tool(tool 内部会检查 apiKey)──
|
|
62
|
-
if (!config.apiKey) {
|
|
63
|
-
console.log(`[artflo] API Key not configured. Skipping initialization.`);
|
|
64
|
-
const sessions = createSessionRegistryService(config);
|
|
65
|
-
api.registerService(sessions.service);
|
|
66
|
-
registerArtfloTools(api, { config, workspaceDir: api.rootDir, sessions: sessions.registry });
|
|
67
|
-
api.on('before_prompt_build', async () => {
|
|
68
|
-
return {
|
|
69
|
-
appendSystemContext: [
|
|
70
|
-
`[Artflo Plugin] env=${config.env}, apiKey=未配置`,
|
|
71
|
-
'[MANDATORY] 在调用任何 artflo_* 工具之前,你必须先读取 artflo_canvas Skill(skills/artflo-canvas/SKILL.md)。' +
|
|
72
|
-
'该 Skill 包含路由优先级、API Key 前置检查、操作策略、澄清标准、推荐流程、任务收尾规范等关键规则。' +
|
|
73
|
-
'未读取 Skill 直接调用工具将导致参数错误、流程遗漏和用户体验问题。' +
|
|
74
|
-
'如果你已在当前会话中读取过该 Skill,可以跳过此步骤。',
|
|
75
|
-
'API Key 未配置,请引导用户通过 artflo_set_api_key 设置。',
|
|
76
|
-
].join('\n'),
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
// ── 自动获取 appKey / vipAppId / vipGroup ─────────────────────────
|
|
82
|
-
if (!config.appKey || !config.vipAppId || !config.vipGroup) {
|
|
83
|
-
try {
|
|
84
|
-
const params = await fetchClientParams(config.webApiBaseUrl);
|
|
85
|
-
if (!config.appKey)
|
|
86
|
-
config.appKey = params.appKey;
|
|
87
|
-
if (!config.vipAppId)
|
|
88
|
-
config.vipAppId = params.vipAppId;
|
|
89
|
-
if (!config.vipGroup)
|
|
90
|
-
config.vipGroup = params.vipGroup;
|
|
91
|
-
console.log(`[artflo] client params fetched: appKey=${config.appKey}, env=${config.env}`);
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
console.warn(`[artflo] Failed to fetch client params, using defaults:`, err instanceof Error ? err.message : String(err));
|
|
95
|
-
if (!config.appKey)
|
|
96
|
-
config.appKey = 'A40CB4D42E28F808';
|
|
97
|
-
if (!config.vipAppId)
|
|
98
|
-
config.vipAppId = '7029803307044000000';
|
|
99
|
-
if (!config.vipGroup)
|
|
100
|
-
config.vipGroup = 'group_artflo';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
console.log(`[artflo] env=${config.env}, api=${config.apiBaseUrl}`);
|
|
104
35
|
const sessions = createSessionRegistryService(config);
|
|
105
36
|
api.registerService(sessions.service);
|
|
106
|
-
registerArtfloTools(api, {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
37
|
+
registerArtfloTools(api, { config, workspaceDir: api.rootDir, sessions: sessions.registry });
|
|
38
|
+
// ── 确保 tools.alsoAllow 包含所有 Artflo 工具(在 service start 中异步执行)──
|
|
39
|
+
api.registerService({
|
|
40
|
+
id: 'artflo-tools-allowlist',
|
|
41
|
+
async start() {
|
|
42
|
+
try {
|
|
43
|
+
const allToolNames = Object.values(ARTFLO_TOOL_NAMES);
|
|
44
|
+
const cfg = api.runtime.config.loadConfig();
|
|
45
|
+
const tools = cfg.tools ?? {};
|
|
46
|
+
if (tools.allow && tools.allow.length > 0) {
|
|
47
|
+
const missing = allToolNames.filter((t) => !tools.allow.includes(t));
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
console.warn(`[artflo] tools.allow is set. Please add ${JSON.stringify(missing)} manually.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const existing = tools.alsoAllow ?? [];
|
|
54
|
+
const missing = allToolNames.filter((t) => !existing.includes(t));
|
|
55
|
+
if (missing.length > 0) {
|
|
56
|
+
await api.runtime.config.writeConfigFile({
|
|
57
|
+
...cfg,
|
|
58
|
+
tools: { ...tools, alsoAllow: [...existing, ...missing] },
|
|
59
|
+
});
|
|
60
|
+
console.log(`[artflo] Added ${missing.length} tools to tools.alsoAllow`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.warn(`[artflo] Failed to update tools.alsoAllow:`, err instanceof Error ? err.message : String(err));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
110
68
|
});
|
|
69
|
+
// ── 异步初始化(不阻塞同步注册)──────────────────────────────
|
|
70
|
+
if (config.apiKey) {
|
|
71
|
+
initClientParams(config);
|
|
72
|
+
}
|
|
111
73
|
// ── 每次 prompt 构建时注入 Artflo 配置、用户状态和 Skill 强制提醒 ──
|
|
112
74
|
api.on('before_prompt_build', async () => {
|
|
113
75
|
const masked = config.apiKey
|
|
114
76
|
? `****${config.apiKey.slice(-4)}`
|
|
115
77
|
: '未配置';
|
|
116
78
|
const lines = [
|
|
117
|
-
`[Artflo Plugin] env=${config.env}, apiKey=${masked}
|
|
79
|
+
`[Artflo Plugin] env=${config.env}, apiKey=${masked}`,
|
|
118
80
|
];
|
|
119
|
-
// ── Skill 强制读取提醒 ──
|
|
120
81
|
lines.push('[MANDATORY] 在调用任何 artflo_* 工具之前,你必须先读取 artflo_canvas Skill(skills/artflo-canvas/SKILL.md)。' +
|
|
121
82
|
'该 Skill 包含路由优先级、API Key 前置检查、操作策略、澄清标准、推荐流程、任务收尾规范等关键规则。' +
|
|
122
83
|
'未读取 Skill 直接调用工具将导致参数错误、流程遗漏和用户体验问题。' +
|
|
@@ -135,6 +96,33 @@ const plugin = {
|
|
|
135
96
|
}
|
|
136
97
|
return { appendSystemContext: lines.join('\n') };
|
|
137
98
|
});
|
|
99
|
+
console.log(`[artflo] registered (env=${config.env}, apiKey=${config.apiKey ? 'set' : 'not set'})`);
|
|
138
100
|
},
|
|
139
101
|
};
|
|
102
|
+
/** 后台获取 appKey / vipAppId / vipGroup,不阻塞同步注册 */
|
|
103
|
+
function initClientParams(config) {
|
|
104
|
+
if (config.appKey && config.vipAppId && config.vipGroup) {
|
|
105
|
+
console.log(`[artflo] client params already set: appKey=${config.appKey}, env=${config.env}`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
fetchClientParams(config.webApiBaseUrl)
|
|
109
|
+
.then((params) => {
|
|
110
|
+
if (!config.appKey)
|
|
111
|
+
config.appKey = params.appKey;
|
|
112
|
+
if (!config.vipAppId)
|
|
113
|
+
config.vipAppId = params.vipAppId;
|
|
114
|
+
if (!config.vipGroup)
|
|
115
|
+
config.vipGroup = params.vipGroup;
|
|
116
|
+
console.log(`[artflo] client params fetched: appKey=${config.appKey}, env=${config.env}`);
|
|
117
|
+
})
|
|
118
|
+
.catch((err) => {
|
|
119
|
+
console.warn(`[artflo] Failed to fetch client params, using defaults:`, err instanceof Error ? err.message : String(err));
|
|
120
|
+
if (!config.appKey)
|
|
121
|
+
config.appKey = 'A40CB4D42E28F808';
|
|
122
|
+
if (!config.vipAppId)
|
|
123
|
+
config.vipAppId = '7029803307044000000';
|
|
124
|
+
if (!config.vipGroup)
|
|
125
|
+
config.vipGroup = 'group_artflo';
|
|
126
|
+
});
|
|
127
|
+
}
|
|
140
128
|
export default plugin;
|
|
@@ -50,16 +50,47 @@ export function buildNodeElement(args) {
|
|
|
50
50
|
break;
|
|
51
51
|
case 'process':
|
|
52
52
|
canvasType = CANVAS_NODE_TYPES.PROCESS;
|
|
53
|
+
// ── Normalize camelCase / alias field names to canonical snake_case ──
|
|
54
|
+
if (elementData.toolType !== undefined && elementData.tool_type === undefined) {
|
|
55
|
+
elementData.tool_type = elementData.toolType;
|
|
56
|
+
delete elementData.toolType;
|
|
57
|
+
}
|
|
58
|
+
if (elementData.imageCount !== undefined && elementData.count === undefined) {
|
|
59
|
+
elementData.count = elementData.imageCount;
|
|
60
|
+
delete elementData.imageCount;
|
|
61
|
+
}
|
|
62
|
+
if (elementData.outputType !== undefined && elementData.output_type === undefined) {
|
|
63
|
+
elementData.output_type = elementData.outputType;
|
|
64
|
+
delete elementData.outputType;
|
|
65
|
+
}
|
|
66
|
+
if (elementData.videoDuration !== undefined && elementData.video_duration === undefined) {
|
|
67
|
+
elementData.video_duration = elementData.videoDuration;
|
|
68
|
+
delete elementData.videoDuration;
|
|
69
|
+
}
|
|
70
|
+
if (elementData.videoSound !== undefined && elementData.video_sound === undefined) {
|
|
71
|
+
elementData.video_sound = elementData.videoSound;
|
|
72
|
+
delete elementData.videoSound;
|
|
73
|
+
}
|
|
74
|
+
if (elementData.showExecute !== undefined && elementData.show_execute === undefined) {
|
|
75
|
+
elementData.show_execute = elementData.showExecute;
|
|
76
|
+
delete elementData.showExecute;
|
|
77
|
+
}
|
|
53
78
|
elementData.tool_type = elementData.tool_type ?? 1;
|
|
54
79
|
elementData.status = elementData.status ?? 0;
|
|
55
80
|
elementData.show_execute = elementData.show_execute ?? true;
|
|
56
81
|
elementData.planning = false;
|
|
57
82
|
elementData.executed = false;
|
|
58
83
|
elementData.count = elementData.count ?? 1;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
// Normalize model field: accept model, modelKey, model_key → current_model
|
|
85
|
+
if (!elementData.current_model) {
|
|
86
|
+
const modelAlias = elementData.model || elementData.modelKey || elementData.model_key;
|
|
87
|
+
if (modelAlias) {
|
|
88
|
+
elementData.current_model = modelAlias;
|
|
89
|
+
}
|
|
62
90
|
}
|
|
91
|
+
delete elementData.model;
|
|
92
|
+
delete elementData.modelKey;
|
|
93
|
+
delete elementData.model_key;
|
|
63
94
|
elementData.output_type =
|
|
64
95
|
elementData.output_type ?? getOutputType(Number(elementData.tool_type ?? 1));
|
|
65
96
|
{
|
|
@@ -114,7 +145,10 @@ export function buildNodeElement(args) {
|
|
|
114
145
|
elementData.status = elementData.status ?? 0;
|
|
115
146
|
elementData.show_execute = elementData.show_execute ?? true;
|
|
116
147
|
elementData.current_model =
|
|
117
|
-
elementData.model || elementData.
|
|
148
|
+
elementData.current_model || elementData.model || elementData.modelKey || elementData.model_key || 'gemini-2.5-flash-preview';
|
|
149
|
+
delete elementData.model;
|
|
150
|
+
delete elementData.modelKey;
|
|
151
|
+
delete elementData.model_key;
|
|
118
152
|
if (elementData.promptCategory) {
|
|
119
153
|
elementData.prompt_category = elementData.promptCategory;
|
|
120
154
|
delete elementData.promptCategory;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
"id": "artflo-openclaw-plugin",
|
|
3
3
|
"name": "Artflo OpenClaw Plugin",
|
|
4
4
|
"description": "Artflo canvas tools over direct WebSocket.",
|
|
5
|
-
"version": "0.0.
|
|
6
|
-
"skills": [
|
|
5
|
+
"version": "0.0.14",
|
|
6
|
+
"skills": [
|
|
7
|
+
"skills/artflo-canvas"
|
|
8
|
+
],
|
|
7
9
|
"configSchema": {
|
|
8
10
|
"type": "object",
|
|
9
11
|
"additionalProperties": false,
|
|
10
12
|
"properties": {
|
|
11
13
|
"env": {
|
|
12
14
|
"type": "string",
|
|
13
|
-
"enum": [
|
|
15
|
+
"enum": [
|
|
16
|
+
"release",
|
|
17
|
+
"test"
|
|
18
|
+
],
|
|
14
19
|
"default": "release",
|
|
15
20
|
"description": "Environment: release or test."
|
|
16
21
|
},
|
|
@@ -26,8 +31,15 @@
|
|
|
26
31
|
}
|
|
27
32
|
},
|
|
28
33
|
"uiHints": {
|
|
29
|
-
"env": {
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
"env": {
|
|
35
|
+
"label": "Environment"
|
|
36
|
+
},
|
|
37
|
+
"apiKey": {
|
|
38
|
+
"label": "API Key",
|
|
39
|
+
"sensitive": true
|
|
40
|
+
},
|
|
41
|
+
"operSystem": {
|
|
42
|
+
"label": "Oper System"
|
|
43
|
+
}
|
|
32
44
|
}
|
|
33
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artflo-ai/artflo-openclaw-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin that connects directly to Artflo canvas WebSocket runtime.",
|
|
6
6
|
"keywords": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc -p tsconfig.json",
|
|
36
36
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"version": "node scripts/sync-version.mjs && git add openclaw.plugin.json",
|
|
37
38
|
"notify": "node scripts/notify-publish.mjs",
|
|
38
39
|
"release": "npm version patch && npm publish && node scripts/notify-publish.mjs",
|
|
39
40
|
"release:minor": "npm version minor && npm publish && node scripts/notify-publish.mjs",
|
|
@@ -47,6 +47,7 @@ metadata:
|
|
|
47
47
|
|
|
48
48
|
## 操作策略
|
|
49
49
|
|
|
50
|
+
- **输出格式硬约束**:禁止使用 markdown 超链接语法 `[文字](url)`、`[查看](url)`、`[🖼️ 查看图片](url)` 等。运行环境(微信/企业微信)不渲染 markdown,用户会看到原始语法。所有 URL 必须以纯文本直接输出,如 `画布:https://artflo.ai/project/xxx`。这条规则适用于所有面向用户的回复,包括任务收尾、错误提示、画布链接、资源链接等。
|
|
50
51
|
- 优先采用先规划、后执行的工作流,而不是临时拼接低层级修改步骤。
|
|
51
52
|
- 在创建或扩展多节点工作流之前,先判断用户需求是否已经足够明确,能否安全规划。
|
|
52
53
|
- 澄清遵循两级标准(见下方「澄清标准」),只有必须确认的字段缺失时才提问,可以合理假设的字段直接用默认值。
|
|
@@ -26,7 +26,21 @@
|
|
|
26
26
|
"properties": { "current_model": "Praline_2" }
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
"fieldsNote": "The 'fields' object in each nodeTypes entry below lists fields that belong INSIDE 'data'. For example, Process.fields.tool_type means data.tool_type, NOT a top-level tool_type."
|
|
29
|
+
"fieldsNote": "The 'fields' object in each nodeTypes entry below lists fields that belong INSIDE 'data'. For example, Process.fields.tool_type means data.tool_type, NOT a top-level tool_type.",
|
|
30
|
+
"fieldNamingConvention": {
|
|
31
|
+
"description": "Canvas element data fields use snake_case naming. Do NOT use camelCase aliases. The executor will attempt to normalize common mistakes, but the plan should use the correct names.",
|
|
32
|
+
"correctNames": {
|
|
33
|
+
"current_model": "模型标识(如 Praline_2)。不要用 model、modelKey、model_key",
|
|
34
|
+
"tool_type": "工具类型(如 1=文生图)。不要用 toolType",
|
|
35
|
+
"output_type": "输出类型(如 0=图片)。不要用 outputType",
|
|
36
|
+
"show_execute": "是否显示执行按钮。不要用 showExecute",
|
|
37
|
+
"video_duration": "视频时长。不要用 videoDuration",
|
|
38
|
+
"video_sound": "视频音效。不要用 videoSound",
|
|
39
|
+
"split_mode": "分割模式。不要用 splitMode",
|
|
40
|
+
"prompt_category": "Prompt 类别。不要用 promptCategory",
|
|
41
|
+
"custom_prompt": "自定义 Prompt。不要用 customPrompt"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
30
44
|
},
|
|
31
45
|
"generalRules": [
|
|
32
46
|
"Use plan node types such as input, refine, process, selector, and batch.",
|
|
@@ -593,7 +607,7 @@
|
|
|
593
607
|
"Process": {
|
|
594
608
|
"code": 130000,
|
|
595
609
|
"name": "处理节点",
|
|
596
|
-
"llmDescription": "执行 AI 生成任务的核心节点,支持 Image、Video、3D Model、Upscale、Remove BG、GIF 等。关键字段: tool_type (工具类型 1=文生图 2=图生图 3=文生视频 4=图生视频 5=去背景 6=视频转GIF 8=图生3D 10=高清), status (状态 0=空闲 1=等待 2=处理中 3=完成 400=错误), output_type (输出类型 0=图片 1=视频 3=3D模型), count (生成数量), current_model (模型名称), ratio (输出比例如 1:1 16:9), resolution (分辨率如 1K 2K),最多仅支持生成4
|
|
610
|
+
"llmDescription": "执行 AI 生成任务的核心节点,支持 Image、Video、3D Model、Upscale、Remove BG、GIF 等。关键字段: tool_type (工具类型 1=文生图 2=图生图 3=文生视频 4=图生视频 5=去背景 6=视频转GIF 8=图生3D 10=高清), status (状态 0=空闲 1=等待 2=处理中 3=完成 400=错误), output_type (输出类型 0=图片 1=视频 3=3D模型), count (生成数量), current_model (模型名称), ratio (输出比例如 1:1 16:9), resolution (分辨率如 1K 2K),最多仅支持生成4张图片。注意: 模型字段必须使用 current_model,不要使用 model、modelKey 或 model_key。工具类型字段必须使用 tool_type(下划线),不要使用 toolType(驼峰)",
|
|
597
611
|
"fields": {
|
|
598
612
|
"tool_type": {
|
|
599
613
|
"type": "number",
|
|
@@ -91,6 +91,24 @@ element.data.error_message → 错误信息
|
|
|
91
91
|
|
|
92
92
|
`node-schema.json` 中每个节点类型的 `fields` 对象列出的是 `data` 内部的字段定义。例如 Process 节点的 `fields.tool_type` 表示的是 `data.tool_type`,不是顶层的 `tool_type`。
|
|
93
93
|
|
|
94
|
+
### 字段命名规范(snake_case)
|
|
95
|
+
|
|
96
|
+
画布节点 `data` 内的字段统一使用 snake_case 命名。以下是常见的正确字段名和容易混淆的错误写法:
|
|
97
|
+
|
|
98
|
+
| 正确字段名 | 错误写法(不要使用) | 说明 |
|
|
99
|
+
|-----------|-------------------|------|
|
|
100
|
+
| `current_model` | `model`、`modelKey`、`model_key` | 模型标识,如 `Praline_2` |
|
|
101
|
+
| `tool_type` | `toolType` | 工具类型,如 1=文生图 |
|
|
102
|
+
| `output_type` | `outputType` | 输出类型,如 0=图片 |
|
|
103
|
+
| `show_execute` | `showExecute` | 是否显示执行按钮 |
|
|
104
|
+
| `video_duration` | `videoDuration` | 视频时长 |
|
|
105
|
+
| `video_sound` | `videoSound` | 视频音效 |
|
|
106
|
+
| `split_mode` | `splitMode` | Refine 分割模式 |
|
|
107
|
+
| `prompt_category` | `promptCategory` | Prompt 类别 |
|
|
108
|
+
| `custom_prompt` | `customPrompt` | 自定义 Prompt |
|
|
109
|
+
|
|
110
|
+
注意:PlanDSL V2 的 `NodeData` 中允许使用 `model`(作为 `current_model` 的简写)和部分驼峰字段名(如 `customPrompt`),执行器会自动归一化。但在使用 `artflo_canvas_change_elements` 直接修改画布节点时,必须使用上表中的正确字段名,因为低层级工具不经过执行器的归一化逻辑。
|
|
111
|
+
|
|
94
112
|
## General Rules
|
|
95
113
|
|
|
96
114
|
- Use plan node types such as `input`, `refine`, `process`, `selector`, and `batch`.
|