digitalsee-ai-flow-cli 0.9.24-beta → 0.9.29-beta
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 +146 -2
- package/dist/api/client.js +9 -0
- package/dist/api/flow.js +4 -1
- package/dist/api/node.js +78 -7
- package/dist/api/trigger.js +242 -0
- package/dist/commands/flow/account.js +439 -1
- package/dist/commands/flow/analyze.js +10 -2
- package/dist/commands/flow/export.js +29 -4
- package/dist/commands/flow/helpers.js +439 -53
- package/dist/commands/flow/node.js +290 -32
- package/dist/commands/flow/offline.js +3 -2
- package/dist/commands/flow/trigger.js +190 -0
- package/dist/commands/flow/validate.js +57 -0
- package/dist/commands/flow/variables.js +1 -0
- package/dist/commands/flow.js +2 -0
- package/dist/constants/node-type.js +23 -0
- package/dist/services/classicNodePorts.js +167 -368
- package/dist/services/fieldTypeRegistry.js +496 -0
- package/dist/services/flowAnalyzer.js +8 -1
- package/dist/services/flowValidator.js +145 -0
- package/dist/services/portValidator.js +76 -70
- package/dist/services/skillGenerator.js +236 -194
- package/dist/services/triggerConfig.js +252 -0
- package/dist/utils/conditionRules.js +128 -0
- package/dist/utils/format.js +268 -168
- package/dist/utils/nodeEnvelope.js +128 -0
- package/dist/utils/switchBranch.js +83 -0
- package/dist/utils/updateCheck.js +50 -6
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@ const normalizeNodeKeyList = (value) => Array.isArray(value)
|
|
|
12
12
|
: [];
|
|
13
13
|
const normalizePort = (port) => ({
|
|
14
14
|
name: port.name,
|
|
15
|
-
displayName: port.display_name || port.name,
|
|
16
15
|
type: typeof port.type === 'string' && port.type.trim() ? port.type.trim() : exports.DEFAULT_PORT_TYPE,
|
|
17
16
|
required: Boolean(port.required),
|
|
18
17
|
maxConnections: port.max_connections !== undefined &&
|
|
@@ -26,11 +25,8 @@ const normalizePort = (port) => ({
|
|
|
26
25
|
deniedTargetNodeKeys: normalizeNodeKeyList(port.denied_target_node_keys),
|
|
27
26
|
});
|
|
28
27
|
const displayHandle = (handle) => handle || '(未指定)';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.map((p) => p.displayName && p.displayName !== p.name ? `${p.name}(${p.displayName})` : p.name)
|
|
32
|
-
.join('、')
|
|
33
|
-
: '(无)';
|
|
28
|
+
/** 错误信息附带的合法端口清单提示(如 ",合法输出端口: true, false") */
|
|
29
|
+
const formatPortHint = (ports) => ports.length ? `,合法端口: ${ports.map((p) => p.name).join(', ')}` : '(该节点无可用端口)';
|
|
34
30
|
const includesKey = (keys, identity) => Boolean(identity && keys.includes(identity));
|
|
35
31
|
const rejectedByAllowlist = (keys, identity) => Boolean(keys.length && !includesKey(keys, identity));
|
|
36
32
|
/** 协议/type 列表交集匹配(大小写不敏感;对齐 RCS connectionCheck 多协议兼容逻辑) */
|
|
@@ -89,6 +85,30 @@ function countPortConnections(edges, nodeId, portName, side) {
|
|
|
89
85
|
edge.source !== edge.target);
|
|
90
86
|
}).length;
|
|
91
87
|
}
|
|
88
|
+
const normalizeClassicPort = (port) => ({
|
|
89
|
+
name: port.name,
|
|
90
|
+
type: port.type || exports.DEFAULT_PORT_TYPE,
|
|
91
|
+
required: false,
|
|
92
|
+
maxConnections: port.maxConnections,
|
|
93
|
+
allowedSourceNodeKeys: [],
|
|
94
|
+
allowedTargetNodeKeys: [],
|
|
95
|
+
deniedSourceNodeKeys: [],
|
|
96
|
+
deniedTargetNodeKeys: [],
|
|
97
|
+
});
|
|
98
|
+
/** classic 端口表来源:无 node_schema 身份的节点用 node_type 解析回退端口 */
|
|
99
|
+
function buildClassicEntry(node) {
|
|
100
|
+
const classic = (0, classicNodePorts_1.resolveClassicNodePorts)(node);
|
|
101
|
+
if (!classic)
|
|
102
|
+
return null;
|
|
103
|
+
const nodeId = node.n_id || node.id;
|
|
104
|
+
return {
|
|
105
|
+
nodeId,
|
|
106
|
+
schemaKey: node.node_type || 'ActionNode',
|
|
107
|
+
inputs: classic.inputs.map(normalizeClassicPort),
|
|
108
|
+
outputs: classic.outputs.map(normalizeClassicPort),
|
|
109
|
+
classicNode: node,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
92
112
|
async function resolveNodeSchemas(nodes) {
|
|
93
113
|
const entries = new Map();
|
|
94
114
|
const unresolved = [];
|
|
@@ -96,34 +116,32 @@ async function resolveNodeSchemas(nodes) {
|
|
|
96
116
|
const nodeId = node.n_id || node.id;
|
|
97
117
|
const actionKey = node.action_key;
|
|
98
118
|
const actionVer = node.action_ver;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
return;
|
|
119
|
+
// legacy 节点(无 action_key/action_ver,即无 node_schema 身份)回退 classic 端口表
|
|
120
|
+
if (!actionKey || actionVer === undefined || actionVer === null) {
|
|
121
|
+
const classicEntry = buildClassicEntry(node);
|
|
122
|
+
if (classicEntry) {
|
|
123
|
+
entries.set(nodeId, classicEntry);
|
|
105
124
|
}
|
|
106
|
-
schema = (0, classicNodePorts_1.applySwitchBranchesToSchema)(schema, node);
|
|
107
|
-
entries.set(nodeId, {
|
|
108
|
-
nodeId,
|
|
109
|
-
schemaKey: schema.key,
|
|
110
|
-
inputs: (schema.inputs || []).map(normalizePort),
|
|
111
|
-
outputs: (schema.outputs || []).map(normalizePort),
|
|
112
|
-
autoConnect: (0, classicNodePorts_1.getAutoConnectRulesForNodeType)(node.node_type),
|
|
113
|
-
});
|
|
114
125
|
return;
|
|
115
126
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
const schema = await (0, nodeSchemaCache_1.getNodePackageSchema)(String(actionKey), Number(actionVer));
|
|
128
|
+
if (!schema) {
|
|
129
|
+
// schema 拉取失败:有 classic 端口表则回退,否则记入 unresolved
|
|
130
|
+
const classicEntry = buildClassicEntry(node);
|
|
131
|
+
if (classicEntry) {
|
|
132
|
+
entries.set(nodeId, classicEntry);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
unresolved.push({ nodeId, actionKey: String(actionKey), actionVer: Number(actionVer) });
|
|
136
|
+
return;
|
|
126
137
|
}
|
|
138
|
+
entries.set(nodeId, {
|
|
139
|
+
nodeId,
|
|
140
|
+
schemaKey: schema.key,
|
|
141
|
+
inputs: (schema.inputs || []).map(normalizePort),
|
|
142
|
+
outputs: (schema.outputs || []).map(normalizePort),
|
|
143
|
+
classicNode: null,
|
|
144
|
+
});
|
|
127
145
|
}));
|
|
128
146
|
return { entries, unresolved };
|
|
129
147
|
}
|
|
@@ -137,29 +155,31 @@ function validateEdge(edge, sourceEntry, targetEntry, allEdges) {
|
|
|
137
155
|
targetHandle: edge.targetHandle,
|
|
138
156
|
};
|
|
139
157
|
if (edge.source === edge.target) {
|
|
140
|
-
//
|
|
141
|
-
|
|
158
|
+
// 自环必须命中节点声明的 auto_connect 规则(rcs connectionCheck.ts:43-56 语义,
|
|
159
|
+
// 如 LoopNode/SelfLoopNode 的 loop → input)
|
|
160
|
+
const selfNode = sourceEntry.classicNode ?? null;
|
|
161
|
+
const allowed = selfNode !== null &&
|
|
162
|
+
(0, classicNodePorts_1.isClassicSelfConnectionAllowed)(selfNode, edge.sourceHandle || exports.DEFAULT_PORT_TYPE, edge.targetHandle || exports.DEFAULT_PORT_TYPE);
|
|
142
163
|
if (!allowed) {
|
|
143
|
-
const hint = sourceEntry.autoConnect.length
|
|
144
|
-
? `;允许的自环端口对: ${sourceEntry.autoConnect
|
|
145
|
-
.map((rule) => `${rule.source} → ${rule.target}`)
|
|
146
|
-
.join('、')}`
|
|
147
|
-
: '';
|
|
148
164
|
violations.push({
|
|
149
165
|
...base,
|
|
150
166
|
code: 'SELF_CONNECTION_NOT_ALLOWED',
|
|
151
|
-
message: `节点 ${edge.source}
|
|
167
|
+
message: `节点 ${edge.source} 不能连接到自身(端口对未在 auto_connect 规则中声明)`,
|
|
152
168
|
});
|
|
153
169
|
return violations;
|
|
154
170
|
}
|
|
155
|
-
// 命中 autoConnect:放行并继续端口存在性 / type / max 校验
|
|
156
171
|
}
|
|
157
|
-
|
|
172
|
+
let sourcePort = sourceEntry.outputs.find((p) => p.name === (edge.sourceHandle || exports.DEFAULT_PORT_TYPE));
|
|
173
|
+
// classic slave 桩方向翻转(RCS 6fc6de5):Agent/Browser 的 ai_* 入桩在画布连线中
|
|
174
|
+
// 以边源形态出现(agent → tool),源端口查找需回退 classic 入桩表
|
|
175
|
+
if (!sourcePort && sourceEntry.classicNode) {
|
|
176
|
+
sourcePort = sourceEntry.inputs.find((p) => p.name === (edge.sourceHandle || exports.DEFAULT_PORT_TYPE));
|
|
177
|
+
}
|
|
158
178
|
if (!sourcePort) {
|
|
159
179
|
violations.push({
|
|
160
180
|
...base,
|
|
161
181
|
code: 'SOURCE_PORT_NOT_FOUND',
|
|
162
|
-
message: `源节点不存在输出端口 ${displayHandle(edge.sourceHandle)}
|
|
182
|
+
message: `源节点不存在输出端口 ${displayHandle(edge.sourceHandle)}${formatPortHint(sourceEntry.outputs)}`,
|
|
163
183
|
});
|
|
164
184
|
return violations;
|
|
165
185
|
}
|
|
@@ -168,7 +188,7 @@ function validateEdge(edge, sourceEntry, targetEntry, allEdges) {
|
|
|
168
188
|
violations.push({
|
|
169
189
|
...base,
|
|
170
190
|
code: 'TARGET_PORT_NOT_FOUND',
|
|
171
|
-
message: `目标节点不存在输入端口 ${displayHandle(edge.targetHandle)}
|
|
191
|
+
message: `目标节点不存在输入端口 ${displayHandle(edge.targetHandle)}${formatPortHint(targetEntry.inputs)}`,
|
|
172
192
|
});
|
|
173
193
|
return violations;
|
|
174
194
|
}
|
|
@@ -220,33 +240,25 @@ function validateRequiredInputs(entries, edges) {
|
|
|
220
240
|
}
|
|
221
241
|
return violations;
|
|
222
242
|
}
|
|
223
|
-
/** 边 handle 规范化:废弃 handle 名(chat_model/memory/tools 等)替换为新名(对齐 RCS 加载迁移) */
|
|
224
|
-
function normalizeEdgeHandles(edges) {
|
|
225
|
-
return edges.map((edge) => ({
|
|
226
|
-
...edge,
|
|
227
|
-
sourceHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(edge.sourceHandle),
|
|
228
|
-
targetHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(edge.targetHandle),
|
|
229
|
-
}));
|
|
230
|
-
}
|
|
231
243
|
/**
|
|
232
|
-
* 对 flow 全量 nodes + edges
|
|
244
|
+
* 对 flow 全量 nodes + edges 做端口级校验。
|
|
233
245
|
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
246
|
+
* NodePackage 节点优先走 node_schema;legacy 节点(无 action_key/action_ver)
|
|
247
|
+
* 回退 classicNodePorts 静态端口表(未知 node_type 跳过,行为不回退);
|
|
248
|
+
* schema 拉取失败且无 classic 端口表的节点记入 unresolvedNodes 并跳过。
|
|
236
249
|
*/
|
|
237
250
|
async function validateFlowPorts(input) {
|
|
238
251
|
const { entries, unresolved } = await resolveNodeSchemas(input.nodes);
|
|
239
|
-
const edges = normalizeEdgeHandles(input.edges);
|
|
240
252
|
const violations = [];
|
|
241
|
-
for (const edge of edges) {
|
|
253
|
+
for (const edge of input.edges) {
|
|
242
254
|
const sourceEntry = entries.get(edge.source);
|
|
243
255
|
const targetEntry = entries.get(edge.target);
|
|
244
|
-
//
|
|
256
|
+
// 只有两端都能解析端口表(node_schema 或 classic 回退)时才做端口级校验
|
|
245
257
|
if (sourceEntry && targetEntry) {
|
|
246
|
-
violations.push(...validateEdge(edge, sourceEntry, targetEntry, edges));
|
|
258
|
+
violations.push(...validateEdge(edge, sourceEntry, targetEntry, input.edges));
|
|
247
259
|
}
|
|
248
260
|
}
|
|
249
|
-
violations.push(...validateRequiredInputs(entries, edges));
|
|
261
|
+
violations.push(...validateRequiredInputs(entries, input.edges));
|
|
250
262
|
return {
|
|
251
263
|
violations,
|
|
252
264
|
checkedNodeCount: entries.size,
|
|
@@ -255,8 +267,7 @@ async function validateFlowPorts(input) {
|
|
|
255
267
|
}
|
|
256
268
|
/**
|
|
257
269
|
* 单边预检:校验一条准备创建的边(尚未加入现有 edges)。
|
|
258
|
-
*
|
|
259
|
-
* 任一端为未知 node_type 的 legacy 节点返回空数组。
|
|
270
|
+
* 两端任一节点无法解析端口表(node_schema 或 classic 均失败)时返回空数组。
|
|
260
271
|
*/
|
|
261
272
|
async function validateSingleEdge(input) {
|
|
262
273
|
const { entries } = await resolveNodeSchemas(input.nodes);
|
|
@@ -269,19 +280,14 @@ async function validateSingleEdge(input) {
|
|
|
269
280
|
id: '(new-edge)',
|
|
270
281
|
source: input.newEdge.source,
|
|
271
282
|
target: input.newEdge.target,
|
|
272
|
-
sourceHandle:
|
|
273
|
-
targetHandle:
|
|
283
|
+
sourceHandle: input.newEdge.sourceHandle,
|
|
284
|
+
targetHandle: input.newEdge.targetHandle,
|
|
274
285
|
};
|
|
275
|
-
return validateEdge(edge, sourceEntry, targetEntry, [
|
|
276
|
-
...normalizeEdgeHandles(input.existingEdges),
|
|
277
|
-
edge,
|
|
278
|
-
]);
|
|
286
|
+
return validateEdge(edge, sourceEntry, targetEntry, [...input.existingEdges, edge]);
|
|
279
287
|
}
|
|
280
288
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* 否则目标侧为 NP 时取其 schema 输入项的 type,都不是则 'main'。
|
|
284
|
-
* classic 节点的协议不写入连线 type。
|
|
289
|
+
* 连线保存 type 解析(对齐 RCS EdgeFactory):NodePackage 端 schema 上 handle
|
|
290
|
+
* 对应端口声明的 type 优先(source/target 任一命中),否则回退 main。
|
|
285
291
|
*/
|
|
286
292
|
async function resolveEdgeSaveType(nodes, edge) {
|
|
287
293
|
const findNode = (id) => nodes.find((n) => (n.n_id || n.id) === id);
|