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.
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateAuthToken = generateAuthToken;
37
+ exports.resetFieldTypesCache = resetFieldTypesCache;
38
+ exports.findFieldTypeContract = findFieldTypeContract;
39
+ exports.isKnownFieldType = isKnownFieldType;
40
+ exports.getDefaultValueForType = getDefaultValueForType;
41
+ exports.validateByContract = validateByContract;
42
+ exports.validateNodeSchemaValueByContract = validateNodeSchemaValueByContract;
43
+ exports.getDefaultValueForSchemaField = getDefaultValueForSchemaField;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ const conditionRules_1 = require("@/utils/conditionRules");
48
+ const config_1 = require("@/utils/config");
49
+ const FIELD_TYPES_FILE_PREFIX = 'field-types';
50
+ function isPlainObject(value) {
51
+ return (value !== null &&
52
+ typeof value === 'object' &&
53
+ !Array.isArray(value) &&
54
+ Object.prototype.toString.call(value) === '[object Object]');
55
+ }
56
+ function typeError(field, rule, message, sample) {
57
+ return {
58
+ field: String(field.name || ''),
59
+ rule,
60
+ message: `${field.page_control || field.type} 字段 "${field.display_name || field.name}" ${message}`,
61
+ sample,
62
+ };
63
+ }
64
+ const TOKEN_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
65
+ /** 生成 32 位随机 token(对齐 rcs AuthTokenField 行为) */
66
+ function generateAuthToken(length = 32) {
67
+ let token = '';
68
+ for (let i = 0; i < length; i++) {
69
+ token += TOKEN_CHARS[Math.floor(Math.random() * TOKEN_CHARS.length)];
70
+ }
71
+ return token;
72
+ }
73
+ const HH_MM_SS_PATTERN = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/;
74
+ function validateGuardRules(value, field) {
75
+ if (!Array.isArray(value)) {
76
+ return [
77
+ typeError(field, 'type', `期望 GuardRule[] (array) 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
78
+ ];
79
+ }
80
+ const errors = [];
81
+ value.forEach((rule, i) => {
82
+ if (!isPlainObject(rule)) {
83
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 object 类型,实际为 ${typeof rule}`, (0, conditionRules_1.safeSample)(rule)));
84
+ }
85
+ });
86
+ return errors;
87
+ }
88
+ function validateMultiRowExtract(value, field) {
89
+ if (!Array.isArray(value)) {
90
+ return [typeError(field, 'type', `期望 array 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value))];
91
+ }
92
+ const errors = [];
93
+ value.forEach((row, i) => {
94
+ if (!isPlainObject(row)) {
95
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 object 类型,实际为 ${typeof row}`, (0, conditionRules_1.safeSample)(row)));
96
+ }
97
+ });
98
+ return errors;
99
+ }
100
+ function validateKeyValueArray(value, field) {
101
+ if (!Array.isArray(value)) {
102
+ return [
103
+ typeError(field, 'type', `期望 Array<{key,value}> 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
104
+ ];
105
+ }
106
+ const errors = [];
107
+ value.forEach((kv, i) => {
108
+ if (!isPlainObject(kv)) {
109
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 {key,value} object 类型,实际为 ${typeof kv}`, (0, conditionRules_1.safeSample)(kv)));
110
+ }
111
+ });
112
+ return errors;
113
+ }
114
+ function validateStringArray(value, field) {
115
+ if (!Array.isArray(value)) {
116
+ return [
117
+ typeError(field, 'type', `期望 string[] 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
118
+ ];
119
+ }
120
+ const errors = [];
121
+ value.forEach((item, i) => {
122
+ if (typeof item !== 'string') {
123
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 string 类型,实际为 ${typeof item}`, (0, conditionRules_1.safeSample)(item)));
124
+ }
125
+ });
126
+ return errors;
127
+ }
128
+ function validateRecordArray(value, field) {
129
+ if (!Array.isArray(value)) {
130
+ return [
131
+ typeError(field, 'type', `期望 Record[] (array) 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
132
+ ];
133
+ }
134
+ const errors = [];
135
+ value.forEach((item, i) => {
136
+ if (!isPlainObject(item)) {
137
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 Record (object) 类型,实际为 ${typeof item}`, (0, conditionRules_1.safeSample)(item)));
138
+ }
139
+ });
140
+ return errors;
141
+ }
142
+ function validateSyncFilteringRules(value, field) {
143
+ if (!isPlainObject(value)) {
144
+ return [
145
+ typeError(field, 'type', `期望 {or_list:[...]} object 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
146
+ ];
147
+ }
148
+ if (value.or_list === undefined || value.or_list === null) {
149
+ return [typeError(field, 'required', '缺少 or_list 属性', '')];
150
+ }
151
+ return (0, conditionRules_1.validateOrList)(value.or_list, String(field.name || ''));
152
+ }
153
+ function validateSyncAccountLinkage(value, field) {
154
+ if (!Array.isArray(value)) {
155
+ return [
156
+ typeError(field, 'type', `期望 Array<{source_exp,target_path}> 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
157
+ ];
158
+ }
159
+ const errors = [];
160
+ value.forEach((item, i) => {
161
+ if (!isPlainObject(item)) {
162
+ errors.push(typeError(field, 'type', `的第 ${i} 项期望 object 类型,实际为 ${typeof item}`, (0, conditionRules_1.safeSample)(item)));
163
+ return;
164
+ }
165
+ if (typeof item.source_exp !== 'string' || !item.source_exp.trim()) {
166
+ errors.push(typeError(field, 'required', `的第 ${i} 项缺少有效 source_exp`, (0, conditionRules_1.safeSample)(item.source_exp)));
167
+ }
168
+ if (typeof item.target_path !== 'string' || !item.target_path.trim()) {
169
+ errors.push(typeError(field, 'required', `的第 ${i} 项缺少有效 target_path`, (0, conditionRules_1.safeSample)(item.target_path)));
170
+ }
171
+ });
172
+ return errors;
173
+ }
174
+ function validateSyncHourMinute(value, field) {
175
+ if (typeof value !== 'string') {
176
+ return [
177
+ typeError(field, 'type', `期望 HH:mm:00 时间字符串,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
178
+ ];
179
+ }
180
+ if (!HH_MM_SS_PATTERN.test(value)) {
181
+ return [
182
+ typeError(field, 'format', `的值 "${value}" 不符合 HH:mm:00 时间格式`, value.substring(0, 80)),
183
+ ];
184
+ }
185
+ return [];
186
+ }
187
+ function validateConfigSubFlow(value, field) {
188
+ if (!isPlainObject(value)) {
189
+ return [
190
+ typeError(field, 'type', `期望 {flowId,input} object 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
191
+ ];
192
+ }
193
+ const errors = [];
194
+ if (typeof value.flowId !== 'string' || !value.flowId.trim()) {
195
+ errors.push(typeError(field, 'required', '缺少有效 flowId', (0, conditionRules_1.safeSample)(value.flowId)));
196
+ }
197
+ return errors;
198
+ }
199
+ function validateSyncDefaultMounting(value, field) {
200
+ if (!isPlainObject(value)) {
201
+ return [
202
+ typeError(field, 'type', `期望 org 节点 object 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
203
+ ];
204
+ }
205
+ const errors = [];
206
+ if (typeof value.id !== 'string' || !value.id.trim()) {
207
+ errors.push(typeError(field, 'format', '的 id 属性缺失或为空', (0, conditionRules_1.safeSample)(value.id)));
208
+ }
209
+ return errors;
210
+ }
211
+ function validateArray(value, field) {
212
+ if (!Array.isArray(value)) {
213
+ return [typeError(field, 'type', `期望 array 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value))];
214
+ }
215
+ return [];
216
+ }
217
+ function validateRecord(value, field) {
218
+ if (!isPlainObject(value)) {
219
+ return [
220
+ typeError(field, 'type', `期望 Record (object) 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
221
+ ];
222
+ }
223
+ return [];
224
+ }
225
+ function validateBoolean(value, field) {
226
+ if (typeof value !== 'boolean') {
227
+ return [
228
+ typeError(field, 'type', `期望 boolean 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
229
+ ];
230
+ }
231
+ return [];
232
+ }
233
+ function validateNumber(value, field) {
234
+ if (typeof value !== 'number') {
235
+ return [
236
+ typeError(field, 'type', `期望 number 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
237
+ ];
238
+ }
239
+ return [];
240
+ }
241
+ function validateString(value, field) {
242
+ if (typeof value !== 'string') {
243
+ return [
244
+ typeError(field, 'type', `期望 string 类型,实际为 ${typeof value}`, (0, conditionRules_1.safeSample)(value)),
245
+ ];
246
+ }
247
+ return [];
248
+ }
249
+ /**
250
+ * 种子契约表。
251
+ *
252
+ * 三组来源:
253
+ * 1. DIP 复杂类型(OBJECT / ARRAY / DYNAMIC_ARRAY / KEYVALUE / CODE / PROXY / GLOBAL_VARIABLE_KEY)
254
+ * 2. rcs 内置特殊 page_control(MULTI_ROW_EXTRACT / MULTI_ROW_GUARD / MULTI_SELECT_LIST /
255
+ * SYNC_HOUR_MINUTE / AUTH_TOKEN / SECRET_KEY / CONFIG_SUB_FLOW)
256
+ * 3. iam-console 宿主注册类型(MCP_SCOPE / SYNC_ACCOUNT_LINKAGE / SYNC_ORG_TREE /
257
+ * SYNC_FILTERING_RULES / SYNC_DEFAULT_MOUNTING / SYNC_MAPPING_CHECK)
258
+ */
259
+ const SEED_CONTRACTS = [
260
+ // ── DIP 复杂类型 ────────────────────────────────────────────
261
+ { name: 'OBJECT', kind: 'record', default: () => ({}) },
262
+ { name: 'ARRAY', kind: 'array', default: () => [] },
263
+ {
264
+ name: 'DYNAMIC_ARRAY',
265
+ kind: 'recordArray',
266
+ default: (field) => ((field.value_type || '').toUpperCase() === 'OBJECT' ? [{}] : []),
267
+ validate: validateRecordArray,
268
+ },
269
+ { name: 'KEYVALUE', kind: 'keyValueArray', default: () => [], validate: validateKeyValueArray },
270
+ { name: 'CODE', kind: 'code', default: () => '' },
271
+ { name: 'PROXY', kind: 'string', default: () => '' },
272
+ { name: 'GLOBAL_VARIABLE_KEY', kind: 'string', default: () => '' },
273
+ // ── rcs 内置特殊 page_control ───────────────────────────────
274
+ {
275
+ name: 'MULTI_ROW_EXTRACT',
276
+ kind: 'multiRowExtract',
277
+ default: () => [],
278
+ validate: validateMultiRowExtract,
279
+ },
280
+ { name: 'MULTI_ROW_GUARD', kind: 'guardRules', default: () => [], validate: validateGuardRules },
281
+ { name: 'MULTI_SELECT_LIST', kind: 'array', default: () => [] },
282
+ {
283
+ name: 'SYNC_HOUR_MINUTE',
284
+ kind: 'time',
285
+ default: () => '00:00:00',
286
+ validate: validateSyncHourMinute,
287
+ },
288
+ { name: 'AUTH_TOKEN', kind: 'token', default: () => generateAuthToken() },
289
+ { name: 'SECRET_KEY', kind: 'token', default: () => generateAuthToken() },
290
+ {
291
+ name: 'CONFIG_SUB_FLOW',
292
+ kind: 'flowRef',
293
+ default: () => ({ flowId: '', input: {} }),
294
+ validate: validateConfigSubFlow,
295
+ },
296
+ // ── iam-console 宿主注册类型 ────────────────────────────────
297
+ { name: 'MCP_SCOPE', kind: 'stringArray', default: () => [], validate: validateStringArray },
298
+ {
299
+ name: 'SYNC_ACCOUNT_LINKAGE',
300
+ kind: 'array',
301
+ default: () => [],
302
+ validate: validateSyncAccountLinkage,
303
+ },
304
+ { name: 'SYNC_ORG_TREE', kind: 'stringArray', default: () => [], validate: validateStringArray },
305
+ {
306
+ name: 'SYNC_FILTERING_RULES',
307
+ kind: 'orList',
308
+ default: () => ({ or_list: [] }),
309
+ validate: validateSyncFilteringRules,
310
+ },
311
+ {
312
+ name: 'SYNC_DEFAULT_MOUNTING',
313
+ kind: 'orgNode',
314
+ default: () => null,
315
+ validate: validateSyncDefaultMounting,
316
+ },
317
+ { name: 'SYNC_MAPPING_CHECK', kind: 'boolean', default: () => false, validate: validateBoolean },
318
+ ];
319
+ const KIND_FALLBACK_DEFAULTS = {
320
+ string: '',
321
+ number: 0,
322
+ boolean: false,
323
+ object: {},
324
+ array: [],
325
+ record: {},
326
+ recordArray: [],
327
+ keyValueArray: [],
328
+ stringArray: [],
329
+ token: '',
330
+ time: null,
331
+ code: '',
332
+ flowRef: null,
333
+ guardRules: [],
334
+ orList: null,
335
+ orgNode: null,
336
+ multiRowExtract: [],
337
+ };
338
+ const KIND_FALLBACK_VALIDATORS = {
339
+ string: validateString,
340
+ number: validateNumber,
341
+ boolean: validateBoolean,
342
+ record: validateRecord,
343
+ array: validateArray,
344
+ object: validateRecord,
345
+ stringArray: validateStringArray,
346
+ recordArray: validateRecordArray,
347
+ keyValueArray: validateKeyValueArray,
348
+ time: validateSyncHourMinute,
349
+ guardRules: validateGuardRules,
350
+ multiRowExtract: validateMultiRowExtract,
351
+ token: undefined,
352
+ code: undefined,
353
+ flowRef: undefined,
354
+ orList: undefined,
355
+ orgNode: undefined,
356
+ };
357
+ /* ── 内置类型(内置 switch 已处理,注册表跳过) ─────────────── */
358
+ const BUILT_IN_TYPES = new Set([
359
+ 'SELECT',
360
+ 'NUMBER',
361
+ 'BOOLEAN',
362
+ 'SWITCH',
363
+ 'DATE',
364
+ 'TIME',
365
+ 'DATETIME',
366
+ 'JSON',
367
+ 'EXTEND_OBJ',
368
+ 'FIXED_OBJ',
369
+ 'ORG_TREE',
370
+ 'TEXT',
371
+ 'TEXTAREA',
372
+ 'PASSWORD',
373
+ 'HIDDEN',
374
+ 'LABEL',
375
+ 'STRING',
376
+ 'INTEGER',
377
+ 'FLOAT',
378
+ ]);
379
+ /* ── 逃生舱:~/.ai-flow-cli/field-types-<profile>.json ─────── */
380
+ let customContractsCache = null;
381
+ function getFieldTypesFilePath() {
382
+ return path.join(os.homedir(), '.ai-flow-cli', `${FIELD_TYPES_FILE_PREFIX}-${(0, config_1.getActiveProfile)()}.json`);
383
+ }
384
+ function normalizeKind(raw) {
385
+ if (typeof raw !== 'string')
386
+ return undefined;
387
+ const kind = raw.trim();
388
+ return kind in KIND_FALLBACK_DEFAULTS ? kind : undefined;
389
+ }
390
+ function loadCustomContracts() {
391
+ if (customContractsCache)
392
+ return customContractsCache;
393
+ const map = new Map();
394
+ try {
395
+ const filePath = getFieldTypesFilePath();
396
+ if (fs.existsSync(filePath)) {
397
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
398
+ for (const [typeName, decl] of Object.entries(raw)) {
399
+ if (!decl || typeof decl !== 'object')
400
+ continue;
401
+ const { kind, default: defaultValue } = decl;
402
+ const normalizedKind = normalizeKind(kind);
403
+ if (!normalizedKind)
404
+ continue;
405
+ map.set(typeName.toUpperCase(), {
406
+ name: typeName.toUpperCase(),
407
+ kind: normalizedKind,
408
+ default: defaultValue === undefined
409
+ ? () => KIND_FALLBACK_DEFAULTS[normalizedKind]
410
+ : () => defaultValue,
411
+ });
412
+ }
413
+ }
414
+ }
415
+ catch {
416
+ // 逃生舱文件损坏时静默忽略,仅使用种子契约
417
+ }
418
+ customContractsCache = map;
419
+ return map;
420
+ }
421
+ /** 重置逃生舱契约缓存(测试用) */
422
+ function resetFieldTypesCache() {
423
+ customContractsCache = null;
424
+ }
425
+ /* ── 查询接口 ─────────────────────────────────────────────── */
426
+ const seedContractIndex = new Map(SEED_CONTRACTS.map((c) => [c.name, c]));
427
+ /**
428
+ * 按类型名查找契约(大小写不敏感)。
429
+ * 逃生舱声明优先于种子数据,允许覆盖内置以外的类型形状。
430
+ */
431
+ function findFieldTypeContract(typeName) {
432
+ if (!typeName)
433
+ return undefined;
434
+ const key = typeName.toUpperCase();
435
+ if (BUILT_IN_TYPES.has(key))
436
+ return undefined;
437
+ const custom = loadCustomContracts().get(key);
438
+ if (custom)
439
+ return custom;
440
+ return seedContractIndex.get(key);
441
+ }
442
+ /** 按类型名是否为已知契约类型 */
443
+ function isKnownFieldType(typeName) {
444
+ return findFieldTypeContract(typeName) !== undefined;
445
+ }
446
+ /**
447
+ * 推断字段默认值:先查注册表契约,未命中返回 undefined(由调用方兜底)。
448
+ */
449
+ function getDefaultValueForType(param) {
450
+ const typeName = String(param.page_control || param.type || '');
451
+ const contract = findFieldTypeContract(typeName);
452
+ if (!contract)
453
+ return undefined;
454
+ if (contract.default)
455
+ return contract.default(param);
456
+ return KIND_FALLBACK_DEFAULTS[contract.kind];
457
+ }
458
+ /**
459
+ * 按契约校验字段值。返回错误数组;类型未命中注册表时返回 null(调用方放行)。
460
+ */
461
+ function validateByContract(value, param) {
462
+ const typeName = String(param.page_control || param.type || '');
463
+ const contract = findFieldTypeContract(typeName);
464
+ if (!contract)
465
+ return null;
466
+ const validate = contract.validate ?? KIND_FALLBACK_VALIDATORS[contract.kind];
467
+ if (!validate)
468
+ return [];
469
+ return validate(value, param);
470
+ }
471
+ /** NodeSchemaInputField 适配为 ConfigSchemaItem 以复用契约校验 */
472
+ function schemaFieldToConfigItem(field) {
473
+ return {
474
+ ui_id: '',
475
+ name: field.key,
476
+ type: field.type,
477
+ display_name: field.label,
478
+ required: field.required,
479
+ };
480
+ }
481
+ /**
482
+ * 按 node_schema.field.type 契约校验 properties 值。
483
+ * 返回错误数组;类型未命中注册表时返回 null(调用方放行)。
484
+ */
485
+ function validateNodeSchemaValueByContract(value, field) {
486
+ return validateByContract(value, schemaFieldToConfigItem(field));
487
+ }
488
+ /**
489
+ * 推断 node_schema 字段默认值(先查注册表,未命中返回 undefined)。
490
+ */
491
+ function getDefaultValueForSchemaField(field) {
492
+ if (field.default_value !== undefined && field.default_value !== null)
493
+ return field.default_value;
494
+ return getDefaultValueForType(schemaFieldToConfigItem(field));
495
+ }
496
+ //# sourceMappingURL=fieldTypeRegistry.js.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.analyzeFlow = analyzeFlow;
4
4
  exports.formatNodeType = formatNodeType;
5
+ const node_type_1 = require("@/constants/node-type");
5
6
  const NODE_TYPE_LABELS = {
6
7
  StartEventNode: '触发器',
7
8
  ActionNode: '动作',
@@ -9,6 +10,7 @@ const NODE_TYPE_LABELS = {
9
10
  LoopNode: '循环',
10
11
  SelfLoopNode: '自循环',
11
12
  SwitchNode: '分支',
13
+ ParallelNode: '并行',
12
14
  EndNode: '结束',
13
15
  AgentNode: 'AI Agent',
14
16
  AISubNode: 'AI 子节点',
@@ -16,8 +18,13 @@ const NODE_TYPE_LABELS = {
16
18
  MCPServerNode: 'MCP 服务',
17
19
  BuiltinMCPNode: '内置 MCP',
18
20
  MCPToolNode: 'MCP 工具',
21
+ MCP_TRIGGER_NODE: 'MCP 触发',
22
+ MCP_TOOLS_NODE: 'MCP 工具列表',
19
23
  VectorStoreNode: '向量存储',
20
24
  StickyNoteNode: '便签',
25
+ // 通讯录集成新版协议 trigger 节点(uep-service acm NodeEnum)
26
+ UserSyncAiNode: '通讯录集成-智能版',
27
+ UserPushAiNode: '通讯录同步-智能版',
21
28
  START: '开始',
22
29
  };
23
30
  function analyzeFlow(flow) {
@@ -74,7 +81,7 @@ function analyzeFlow(flow) {
74
81
  }
75
82
  function isTriggerNode(node) {
76
83
  const type = node.node_type || 'ActionNode';
77
- return (type === 'StartEventNode' ||
84
+ return ((0, node_type_1.isTriggerNodeType)(type) ||
78
85
  type === 'START' ||
79
86
  (node.connector_name?.toLowerCase().includes('webhook') ?? false));
80
87
  }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectCycles = detectCycles;
4
+ exports.validateFlowGraph = validateFlowGraph;
5
+ const node_type_1 = require("@/constants/node-type");
6
+ /** 与 flowAnalyzer.isTriggerNode 同语义,触发器类型集合统一取自 constants/node-type */
7
+ function isTriggerNode(node) {
8
+ const type = node.node_type || 'ActionNode';
9
+ return ((0, node_type_1.isTriggerNodeType)(type) ||
10
+ type === 'START' ||
11
+ (node.connector_name?.toLowerCase().includes('webhook') ?? false));
12
+ }
13
+ /** 非参与拓扑的辅助节点类型:便签等画布装饰节点不算孤立节点 */
14
+ const NON_EXECUTION_NODE_TYPES = new Set(['StickyNoteNode']);
15
+ /**
16
+ * DFS 三色染色环检测:WHITE 未访问 / GRAY 当前栈中 / BLACK 已完成。
17
+ * 遇到 GRAY 邻居即找到环,从栈中截取环上节点序列。
18
+ */
19
+ function detectCycles(nodes, edges) {
20
+ const nodeIds = nodes.map((n) => n.n_id || n.id);
21
+ const adjacency = new Map();
22
+ for (const id of nodeIds)
23
+ adjacency.set(id, []);
24
+ const nodeSet = new Set(nodeIds);
25
+ for (const edge of edges) {
26
+ if (nodeSet.has(edge.source) && nodeSet.has(edge.target)) {
27
+ adjacency.get(edge.source).push(edge.target);
28
+ }
29
+ }
30
+ const WHITE = 0;
31
+ const GRAY = 1;
32
+ const BLACK = 2;
33
+ const color = new Map();
34
+ for (const id of nodeIds)
35
+ color.set(id, WHITE);
36
+ const stack = [];
37
+ const stackIndex = new Map();
38
+ const cycles = [];
39
+ const seenCycleKeys = new Set();
40
+ function dfs(nodeId) {
41
+ color.set(nodeId, GRAY);
42
+ stackIndex.set(nodeId, stack.length);
43
+ stack.push(nodeId);
44
+ for (const next of adjacency.get(nodeId) || []) {
45
+ const nextColor = color.get(next);
46
+ if (nextColor === GRAY) {
47
+ // 找到环:从 next 在栈中的位置截取到栈顶
48
+ const startIdx = stackIndex.get(next) ?? 0;
49
+ const cycleNodes = stack.slice(startIdx);
50
+ const key = normalizeCycleKey(cycleNodes);
51
+ if (!seenCycleKeys.has(key)) {
52
+ seenCycleKeys.add(key);
53
+ cycles.push({ nodes: [...cycleNodes, next] });
54
+ }
55
+ }
56
+ else if (nextColor === WHITE) {
57
+ dfs(next);
58
+ }
59
+ }
60
+ stack.pop();
61
+ stackIndex.delete(nodeId);
62
+ color.set(nodeId, BLACK);
63
+ }
64
+ for (const id of nodeIds) {
65
+ if (color.get(id) === WHITE) {
66
+ dfs(id);
67
+ }
68
+ }
69
+ return cycles;
70
+ }
71
+ /** 环去重 key:旋转同构的环视为同一个 */
72
+ function normalizeCycleKey(cycleNodes) {
73
+ if (cycleNodes.length <= 1)
74
+ return cycleNodes.join('|');
75
+ let minIdx = 0;
76
+ for (let i = 1; i < cycleNodes.length; i++) {
77
+ if (cycleNodes[i] < cycleNodes[minIdx])
78
+ minIdx = i;
79
+ }
80
+ const rotated = [];
81
+ for (let i = 0; i < cycleNodes.length; i++) {
82
+ rotated.push(cycleNodes[(minIdx + i) % cycleNodes.length]);
83
+ }
84
+ return rotated.join('|');
85
+ }
86
+ /**
87
+ * 流图结构校验:环检测(error)、孤立节点(warning)、触发器检查
88
+ * (多触发器 error / 无触发器 warning)。
89
+ */
90
+ function validateFlowGraph(input) {
91
+ const { nodes, edges } = input;
92
+ const issues = [];
93
+ // 1. 环检测
94
+ const cycles = detectCycles(nodes, edges);
95
+ for (const cycle of cycles) {
96
+ issues.push({
97
+ code: 'CYCLE_DETECTED',
98
+ severity: 'error',
99
+ message: `检测到循环依赖: ${cycle.nodes.join(' → ')}`,
100
+ });
101
+ }
102
+ // 2. 孤立节点(无入边无出边的非触发节点)
103
+ if (nodes.length > 0) {
104
+ const connectedIds = new Set();
105
+ for (const edge of edges) {
106
+ connectedIds.add(edge.source);
107
+ connectedIds.add(edge.target);
108
+ }
109
+ for (const node of nodes) {
110
+ const nid = node.n_id || node.id;
111
+ const type = node.node_type || 'ActionNode';
112
+ if (NON_EXECUTION_NODE_TYPES.has(type))
113
+ continue;
114
+ if (isTriggerNode(node))
115
+ continue;
116
+ if (!connectedIds.has(nid)) {
117
+ issues.push({
118
+ code: 'ORPHAN_NODE',
119
+ severity: 'warning',
120
+ message: `节点 ${nid} 是孤立节点(无入边无出边),不会被执行`,
121
+ nodeId: nid,
122
+ });
123
+ }
124
+ }
125
+ }
126
+ // 3. 触发器检查
127
+ const triggers = nodes.filter((n) => isTriggerNode(n));
128
+ if (triggers.length > 1) {
129
+ const ids = triggers.map((n) => n.n_id || n.id);
130
+ issues.push({
131
+ code: 'MULTIPLE_TRIGGERS',
132
+ severity: 'error',
133
+ message: `流中存在 ${triggers.length} 个触发器节点 (${ids.join(', ')}),仅允许一个触发器`,
134
+ });
135
+ }
136
+ else if (triggers.length === 0 && nodes.length > 0) {
137
+ issues.push({
138
+ code: 'NO_TRIGGER',
139
+ severity: 'warning',
140
+ message: '流中没有触发器节点,无法启动执行',
141
+ });
142
+ }
143
+ return { issues, cycles };
144
+ }
145
+ //# sourceMappingURL=flowValidator.js.map