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.
@@ -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
- const formatHandleList = (ports) => ports.length
30
- ? ports
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
- if (actionKey && actionVer !== undefined && actionVer !== null) {
100
- // NodePackage 节点:远端 schema + switch 实例分支派生 + Loop/SelfLoop 自环兜底
101
- let schema = await (0, nodeSchemaCache_1.getNodePackageSchema)(String(actionKey), Number(actionVer));
102
- if (!schema) {
103
- unresolved.push({ nodeId, actionKey: String(actionKey), actionVer: Number(actionVer) });
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
- // classic/legacy 节点:内置端口表(未知 node_type 跳过,避免误报)
117
- const classic = (0, classicNodePorts_1.resolveClassicNodeSchema)(node);
118
- if (classic) {
119
- entries.set(nodeId, {
120
- nodeId,
121
- schemaKey: classic.schemaKey,
122
- inputs: classic.inputs.map(normalizePort),
123
- outputs: classic.outputs.map(normalizePort),
124
- autoConnect: classic.autoConnect,
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
- // 自环边须命中节点声明的 autoConnect 配对(如 LOOP 的 loop → input)才放行
141
- const allowed = sourceEntry.autoConnect.some((rule) => rule.source === edge.sourceHandle && rule.target === edge.targetHandle);
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} 不能连接到自身${hint}`,
167
+ message: `节点 ${edge.source} 不能连接到自身(端口对未在 auto_connect 规则中声明)`,
152
168
  });
153
169
  return violations;
154
170
  }
155
- // 命中 autoConnect:放行并继续端口存在性 / type / max 校验
156
171
  }
157
- const sourcePort = sourceEntry.outputs.find((p) => p.name === (edge.sourceHandle || exports.DEFAULT_PORT_TYPE));
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)},可用输出端口: ${formatHandleList(sourceEntry.outputs)}`,
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)},可用输入端口: ${formatHandleList(targetEntry.inputs)}`,
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 做 node_schema 端口级校验。
244
+ * 对 flow 全量 nodes + edges 做端口级校验。
233
245
  *
234
- * classic 节点(无 action_key/action_ver)按内置端口表(classicNodePorts)参与校验,
235
- * 未知 node_type 的 legacy 节点跳过;NodePackage schema 拉取失败记入 unresolvedNodes 并跳过。
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
- // 只有两端都有 node_schema 时才做端口级校验(legacy 端跳过,避免误报)
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
- * 两端均可解析端口(NodePackage schema 或 classic 内置表)时才校验;
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: (0, classicNodePorts_1.normalizeLegacyHandleName)(input.newEdge.sourceHandle),
273
- targetHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(input.newEdge.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
- * 解析连线的保存 type(对齐 RCS EdgeFactory ef7558b 语义):
282
- * 仅 NodePackage 节点的 handle 参与——源侧为 NP 时取其 schema 输出项的 type,
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);