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
|
@@ -1,8 +1,271 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.extractAccountModelMeta = extractAccountModelMeta;
|
|
37
|
+
exports.buildLegacyAccountConfig = buildLegacyAccountConfig;
|
|
38
|
+
exports.validateCredentialFields = validateCredentialFields;
|
|
39
|
+
exports.maskCredentialConfig = maskCredentialConfig;
|
|
40
|
+
exports.resolveCredentialValues = resolveCredentialValues;
|
|
41
|
+
exports.extractFieldErrorsFromApiError = extractFieldErrorsFromApiError;
|
|
42
|
+
exports.maskAccountInstancesForList = maskAccountInstancesForList;
|
|
43
|
+
exports.isAccountTestFailed = isAccountTestFailed;
|
|
3
44
|
exports.registerFlowAccount = registerFlowAccount;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
4
46
|
const node_1 = require("@/api/node");
|
|
5
47
|
const output_1 = require("@/utils/output");
|
|
48
|
+
const exit_code_1 = require("@/constants/exit-code");
|
|
49
|
+
const mask_1 = require("@/utils/mask");
|
|
50
|
+
const common_1 = require("@/utils/common");
|
|
51
|
+
const format_1 = require("@/utils/format");
|
|
52
|
+
const helpers_1 = require("@/commands/flow/helpers");
|
|
53
|
+
function asRecord(value) {
|
|
54
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
55
|
+
? value
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
function findLegacySchema(model) {
|
|
59
|
+
const candidates = [model.auth_schema, model.config_schema, model.schema, model].map(asRecord);
|
|
60
|
+
return candidates.find((s) => s && Array.isArray(s.sub_params));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 从账号模型响应中提取创建/测试所需的元数据。
|
|
64
|
+
* 优先走 credential_schema.fields(对齐 rcs CredentialAccountForm 新路径),
|
|
65
|
+
* 无 credential fields 时回退 legacy auth_schema/config_schema(sub_params 路径)。
|
|
66
|
+
*/
|
|
67
|
+
function extractAccountModelMeta(model, fallbackKey) {
|
|
68
|
+
const credentialSchema = asRecord(model.credential_schema);
|
|
69
|
+
const rawFields = credentialSchema?.fields;
|
|
70
|
+
const fields = Array.isArray(rawFields) ? rawFields : [];
|
|
71
|
+
return {
|
|
72
|
+
accountSchemaId: String(model.id ?? fallbackKey),
|
|
73
|
+
name: String(model.name ?? ''),
|
|
74
|
+
authType: String(model.auth_type ?? credentialSchema?.type ?? ''),
|
|
75
|
+
credentialFields: fields,
|
|
76
|
+
accountKey: model.account_key ?? credentialSchema?.key ?? null,
|
|
77
|
+
accountVer: model.account_ver ??
|
|
78
|
+
credentialSchema?.version ??
|
|
79
|
+
null,
|
|
80
|
+
legacySubParams: fields.length === 0 && !!findLegacySchema(model),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 将 --data 提供的凭据值回填到 legacy ConfigSchema(sub_params[].value),
|
|
85
|
+
* 并复用 classic 校验(required + page_control 类型契约,PASSWORD 按字符串断言)。
|
|
86
|
+
*/
|
|
87
|
+
function buildLegacyAccountConfig(model, values) {
|
|
88
|
+
const legacyModel = findLegacySchema(model);
|
|
89
|
+
const subParams = Array.isArray(legacyModel?.sub_params)
|
|
90
|
+
? legacyModel.sub_params
|
|
91
|
+
: [];
|
|
92
|
+
const filled = subParams.map((param) => {
|
|
93
|
+
const key = String(param.name ?? '');
|
|
94
|
+
const provided = values[key];
|
|
95
|
+
const next = { ...param };
|
|
96
|
+
if (provided !== undefined) {
|
|
97
|
+
next.value = provided;
|
|
98
|
+
}
|
|
99
|
+
else if (next.value === undefined) {
|
|
100
|
+
next.value = '';
|
|
101
|
+
}
|
|
102
|
+
return next;
|
|
103
|
+
});
|
|
104
|
+
const schemaTree = {
|
|
105
|
+
...(legacyModel ?? {}),
|
|
106
|
+
name: String(model.name ?? ''),
|
|
107
|
+
sub_params: filled,
|
|
108
|
+
};
|
|
109
|
+
const errors = (0, format_1.validateNodePropertyValues)(schemaTree);
|
|
110
|
+
return { config: { sub_params: filled }, errors };
|
|
111
|
+
}
|
|
112
|
+
/** 凭据值校验:新路径复用注册表驱动的 node_schema 校验(任务 15 fieldTypeRegistry) */
|
|
113
|
+
function validateCredentialFields(values, fields) {
|
|
114
|
+
return (0, helpers_1.validateNodeSchemaProperties)(values, fields);
|
|
115
|
+
}
|
|
116
|
+
/** PASSWORD 类字段(及常见敏感键)脱敏后的 config 副本 */
|
|
117
|
+
function maskCredentialConfig(config, fields) {
|
|
118
|
+
const passwordKeys = fields
|
|
119
|
+
.filter((f) => String(f.type || '').toUpperCase() === 'PASSWORD')
|
|
120
|
+
.map((f) => f.key.toLowerCase());
|
|
121
|
+
return (0, mask_1.maskJson)(config, {
|
|
122
|
+
maskPatterns: false,
|
|
123
|
+
sensitiveFields: passwordKeys,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function printValidationErrors(errors, json) {
|
|
127
|
+
if (errors.length === 0)
|
|
128
|
+
return;
|
|
129
|
+
if (json) {
|
|
130
|
+
(0, output_1.printStructuredResult)({
|
|
131
|
+
ok: false,
|
|
132
|
+
message: `凭据字段校验失败 (${errors.length} 处)`,
|
|
133
|
+
errors: errors.map((e) => ({ field: e.field, rule: e.rule, message: e.message })),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
(0, output_1.printError)(`凭据字段校验失败 (${errors.length} 处):`);
|
|
138
|
+
for (const e of errors) {
|
|
139
|
+
(0, output_1.printError)(` - [${e.rule}] ${e.field}: ${e.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
143
|
+
}
|
|
144
|
+
/** 解析 --data / --data-file 为凭据值对象 */
|
|
145
|
+
function resolveCredentialValues(options) {
|
|
146
|
+
const hasData = options.data !== undefined;
|
|
147
|
+
const hasFile = options.dataFile !== undefined;
|
|
148
|
+
if (hasData && hasFile) {
|
|
149
|
+
throw new Error('--data 和 --data-file 不能同时使用');
|
|
150
|
+
}
|
|
151
|
+
if (!hasData && !hasFile) {
|
|
152
|
+
throw new Error('必须提供 --data <json> 或 --data-file <path>');
|
|
153
|
+
}
|
|
154
|
+
let parsed;
|
|
155
|
+
if (hasFile) {
|
|
156
|
+
const dataFile = options.dataFile;
|
|
157
|
+
if (!fs.existsSync(dataFile)) {
|
|
158
|
+
throw new Error(`文件不存在: ${dataFile}`);
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(fs.readFileSync(dataFile, 'utf-8'));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw new Error(`JSON 格式无效: ${dataFile}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
try {
|
|
169
|
+
parsed = JSON.parse(options.data);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new Error('JSON 格式无效: --data');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
176
|
+
throw new Error('凭据数据必须是 JSON 对象');
|
|
177
|
+
}
|
|
178
|
+
return parsed;
|
|
179
|
+
}
|
|
180
|
+
/** 从服务端错误消息中提取字段级错误(message 为 JSON 数组/对象时) */
|
|
181
|
+
function extractFieldErrorsFromApiError(error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
const parsed = (0, common_1.safeParseJSON)(message, null);
|
|
184
|
+
if (Array.isArray(parsed)) {
|
|
185
|
+
return { message, fieldErrors: parsed };
|
|
186
|
+
}
|
|
187
|
+
if (parsed && typeof parsed === 'object') {
|
|
188
|
+
const record = parsed;
|
|
189
|
+
const errors = record.errors ?? record.fields ?? record.details ?? null;
|
|
190
|
+
return { message, fieldErrors: Array.isArray(errors) ? errors : [parsed] };
|
|
191
|
+
}
|
|
192
|
+
return { message, fieldErrors: null };
|
|
193
|
+
}
|
|
194
|
+
/** 凭据字段常见别名(小写),账号模型不可得时的兜底脱敏范围 */
|
|
195
|
+
const ACCOUNT_SENSITIVE_KEY_FALLBACK = [
|
|
196
|
+
'appsecret',
|
|
197
|
+
'app_secret',
|
|
198
|
+
'appkey',
|
|
199
|
+
'app_key',
|
|
200
|
+
'clientsecret',
|
|
201
|
+
'client_secret',
|
|
202
|
+
'passphrase',
|
|
203
|
+
];
|
|
204
|
+
/** 从账号模型提取 PASSWORD 字段键(小写):新路径 credential_schema.fields + legacy sub_params */
|
|
205
|
+
function readAccountPasswordFieldKeys(model) {
|
|
206
|
+
const meta = extractAccountModelMeta(model, '');
|
|
207
|
+
const keys = meta.credentialFields
|
|
208
|
+
.filter((f) => String(f.type || '').toUpperCase() === 'PASSWORD')
|
|
209
|
+
.map((f) => f.key.toLowerCase());
|
|
210
|
+
if (keys.length === 0) {
|
|
211
|
+
const legacyModel = findLegacySchema(model);
|
|
212
|
+
const subParams = Array.isArray(legacyModel?.sub_params)
|
|
213
|
+
? legacyModel.sub_params
|
|
214
|
+
: [];
|
|
215
|
+
for (const param of subParams) {
|
|
216
|
+
const control = String(param.page_control || param.type || '').toUpperCase();
|
|
217
|
+
if (control === 'PASSWORD' && param.name)
|
|
218
|
+
keys.push(String(param.name).toLowerCase());
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return keys;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 账号实例 config 的列表输出脱敏:按 (account_key|account_schema_id, account_ver)
|
|
225
|
+
* 去重拉取账号模型,收集 PASSWORD 字段键;模型拉取失败不阻塞,回退通用别名。
|
|
226
|
+
*/
|
|
227
|
+
async function maskAccountInstancesForList(accounts) {
|
|
228
|
+
const passwordKeys = new Set();
|
|
229
|
+
const seenSchemas = new Set();
|
|
230
|
+
for (const account of accounts) {
|
|
231
|
+
const lookup = account.account_key || String(account.account_schema_id ?? '');
|
|
232
|
+
if (!lookup)
|
|
233
|
+
continue;
|
|
234
|
+
const ver = account.account_ver;
|
|
235
|
+
const cacheKey = `${lookup}@${ver ?? ''}`;
|
|
236
|
+
if (seenSchemas.has(cacheKey))
|
|
237
|
+
continue;
|
|
238
|
+
seenSchemas.add(cacheKey);
|
|
239
|
+
try {
|
|
240
|
+
const model = await (0, node_1.getAccountModel)(lookup, ver === undefined || ver === null ? undefined : String(ver));
|
|
241
|
+
for (const key of readAccountPasswordFieldKeys(model))
|
|
242
|
+
passwordKeys.add(key);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// 模型拉取失败不阻塞列表,仅用通用别名兜底
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const sensitiveFields = [...passwordKeys, ...ACCOUNT_SENSITIVE_KEY_FALLBACK];
|
|
249
|
+
return accounts.map((account) => {
|
|
250
|
+
const config = asRecord(account.config);
|
|
251
|
+
if (!config)
|
|
252
|
+
return account;
|
|
253
|
+
return {
|
|
254
|
+
...account,
|
|
255
|
+
config: (0, mask_1.maskJson)(config, { maskPatterns: false, sensitiveFields }),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* account test 业务判读:连通失败走 HTTP 200,失败标志有两种形态——
|
|
261
|
+
* data 为裸布尔 false({error:"0", data:false},实测 0.9.29-beta)或
|
|
262
|
+
* data 为对象且 result:false。其余(true / {result:true} / 无标志)视为成功。
|
|
263
|
+
*/
|
|
264
|
+
function isAccountTestFailed(result) {
|
|
265
|
+
if (result === false)
|
|
266
|
+
return true;
|
|
267
|
+
return asRecord(result)?.result === false;
|
|
268
|
+
}
|
|
6
269
|
function registerFlowAccount(flowCmd) {
|
|
7
270
|
const accountCmd = flowCmd.command('account').description('账号操作');
|
|
8
271
|
accountCmd
|
|
@@ -16,7 +279,9 @@ function registerFlowAccount(flowCmd) {
|
|
|
16
279
|
(0, output_1.setJsonMode)(true);
|
|
17
280
|
const accounts = await (0, node_1.getAccountInstances)(connectorId, options.accountSchemaId);
|
|
18
281
|
if (options.json) {
|
|
19
|
-
|
|
282
|
+
// config 含服务端回传的凭据明文(appSecret 等),列表输出统一脱敏
|
|
283
|
+
const masked = await maskAccountInstancesForList(accounts);
|
|
284
|
+
(0, output_1.printStructuredResult)({ ok: true, data: masked, total: masked.length });
|
|
20
285
|
return;
|
|
21
286
|
}
|
|
22
287
|
if (accounts.length === 0) {
|
|
@@ -45,5 +310,178 @@ function registerFlowAccount(flowCmd) {
|
|
|
45
310
|
}
|
|
46
311
|
}
|
|
47
312
|
});
|
|
313
|
+
accountCmd
|
|
314
|
+
.command('create <connectorId>')
|
|
315
|
+
.description('创建账号实例(凭据字段校验复用字段类型契约注册表)')
|
|
316
|
+
.requiredOption('--account-schema-id <id>', '账号模型 ID(数字 schema id 或 account_key)')
|
|
317
|
+
.option('--data <json>', '凭据字段 JSON(key → 值,如 \'{"host":"...","password":"..."}\')')
|
|
318
|
+
.option('--data-file <path>', '凭据字段 JSON 文件路径(与 --data 互斥)')
|
|
319
|
+
.option('--name <name>', '实例显示名称(缺省使用账号模型名称)')
|
|
320
|
+
.option('--proxy-id <id>', '代理网关 ID')
|
|
321
|
+
.option('--json', '以 JSON 格式输出')
|
|
322
|
+
.action(async (connectorId, options) => {
|
|
323
|
+
try {
|
|
324
|
+
if (options.json)
|
|
325
|
+
(0, output_1.setJsonMode)(true);
|
|
326
|
+
const values = resolveCredentialValues(options);
|
|
327
|
+
const model = await (0, node_1.getAccountModel)(options.accountSchemaId);
|
|
328
|
+
const meta = extractAccountModelMeta(model, options.accountSchemaId);
|
|
329
|
+
if (meta.credentialFields.length === 0 && !meta.legacySubParams) {
|
|
330
|
+
throw new Error(`账号模型 ${options.accountSchemaId} 未定义 credential_schema.fields,也无法回退 legacy sub_params,无法创建`);
|
|
331
|
+
}
|
|
332
|
+
let config;
|
|
333
|
+
let validationErrors;
|
|
334
|
+
if (meta.legacySubParams) {
|
|
335
|
+
const built = buildLegacyAccountConfig(model, values);
|
|
336
|
+
config = built.config;
|
|
337
|
+
validationErrors = built.errors;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
validationErrors = validateCredentialFields(values, meta.credentialFields);
|
|
341
|
+
config = values;
|
|
342
|
+
}
|
|
343
|
+
if (validationErrors.length > 0) {
|
|
344
|
+
printValidationErrors(validationErrors, options.json);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const payload = {
|
|
348
|
+
connector_id: connectorId,
|
|
349
|
+
account_schema_id: meta.accountSchemaId,
|
|
350
|
+
auth_type: meta.authType,
|
|
351
|
+
name: String(options.name || meta.name || options.accountSchemaId),
|
|
352
|
+
config,
|
|
353
|
+
...(options.proxyId ? { proxy_id: options.proxyId } : {}),
|
|
354
|
+
...(meta.accountKey ? { account_key: meta.accountKey } : {}),
|
|
355
|
+
...(meta.accountVer !== null && meta.accountVer !== undefined
|
|
356
|
+
? { account_ver: meta.accountVer }
|
|
357
|
+
: {}),
|
|
358
|
+
};
|
|
359
|
+
const result = await (0, node_1.createAccountInstance)(payload);
|
|
360
|
+
const resultRecord = asRecord(result) ?? {};
|
|
361
|
+
const instanceId = String(resultRecord.id ?? asRecord(resultRecord.data)?.id ?? '');
|
|
362
|
+
if (options.json) {
|
|
363
|
+
(0, output_1.printStructuredResult)({
|
|
364
|
+
ok: true,
|
|
365
|
+
id: instanceId,
|
|
366
|
+
message: '账号实例创建成功',
|
|
367
|
+
account: {
|
|
368
|
+
connector_id: connectorId,
|
|
369
|
+
account_schema_id: meta.accountSchemaId,
|
|
370
|
+
auth_type: meta.authType,
|
|
371
|
+
name: payload.name,
|
|
372
|
+
config: maskCredentialConfig(config, meta.credentialFields),
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
(0, output_1.printSuccess)('账号实例创建成功');
|
|
378
|
+
(0, output_1.printKeyValue)([
|
|
379
|
+
['实例 ID', instanceId || '(服务端未返回)'],
|
|
380
|
+
['名称', payload.name],
|
|
381
|
+
['认证类型', meta.authType || '-'],
|
|
382
|
+
['账号模型', meta.accountSchemaId],
|
|
383
|
+
['连接器', connectorId],
|
|
384
|
+
]);
|
|
385
|
+
if (instanceId) {
|
|
386
|
+
(0, output_1.printInfo)(`可通过 flow account test ${instanceId} 验证连通性`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (options.json) {
|
|
391
|
+
(0, output_1.printStructuredResult)({ ok: false, message: error.message });
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
(0, output_1.printError)(`创建账号实例失败: ${error.message}`);
|
|
395
|
+
(0, output_1.printError)(`提示: ai-flow flow account create ${connectorId} --account-schema-id ${options.accountSchemaId ?? '<id>'} --data '<凭据JSON>'`);
|
|
396
|
+
}
|
|
397
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
accountCmd
|
|
401
|
+
.command('test <accountId>')
|
|
402
|
+
.description('测试账号实例连通性(服务端校验凭据,失败时给出字段级错误)')
|
|
403
|
+
.option('--json', '以 JSON 格式输出')
|
|
404
|
+
.action(async (accountId, options) => {
|
|
405
|
+
try {
|
|
406
|
+
if (options.json)
|
|
407
|
+
(0, output_1.setJsonMode)(true);
|
|
408
|
+
const detail = asRecord(await (0, node_1.getAccountInstanceDetail)(accountId)) ?? {};
|
|
409
|
+
const accountKey = detail.account_key;
|
|
410
|
+
const accountVer = detail.account_ver;
|
|
411
|
+
const schemaLookup = accountKey || detail.account_schema_id;
|
|
412
|
+
let model = {};
|
|
413
|
+
if (schemaLookup) {
|
|
414
|
+
try {
|
|
415
|
+
model = await (0, node_1.getAccountModel)(String(schemaLookup), accountVer);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// 模型拉取失败不阻塞测试,直接用实例详情构造请求
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const meta = extractAccountModelMeta(model, String(detail.account_schema_id ?? ''));
|
|
422
|
+
const payload = {
|
|
423
|
+
connector_id: detail.connector_id ?? null,
|
|
424
|
+
account_schema_id: String(detail.account_schema_id ?? meta.accountSchemaId ?? schemaLookup ?? ''),
|
|
425
|
+
auth_type: String(detail.auth_type ?? meta.authType ?? ''),
|
|
426
|
+
name: String(detail.name ?? ''),
|
|
427
|
+
config: (asRecord(detail.config) ?? {}),
|
|
428
|
+
id: accountId,
|
|
429
|
+
...(accountKey ? { account_key: accountKey } : {}),
|
|
430
|
+
...(accountVer !== undefined && accountVer !== null ? { account_ver: accountVer } : {}),
|
|
431
|
+
};
|
|
432
|
+
const result = await (0, node_1.testAccountInstance)(payload);
|
|
433
|
+
const resultRecord = asRecord(result);
|
|
434
|
+
// 业务判读:连通性校验失败走 HTTP 200(不进 catch),唯一可信标志是
|
|
435
|
+
// data 本身(裸布尔 false)或 data.result —— ok/message 均不可信
|
|
436
|
+
const testFailed = isAccountTestFailed(result);
|
|
437
|
+
if (options.json) {
|
|
438
|
+
(0, output_1.printStructuredResult)({
|
|
439
|
+
ok: !testFailed,
|
|
440
|
+
data: { account_id: accountId, result },
|
|
441
|
+
message: testFailed
|
|
442
|
+
? '测试连接失败: 服务端连通性校验未通过(判读 data 为 false 或 data.result)'
|
|
443
|
+
: '测试连接成功',
|
|
444
|
+
});
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (testFailed) {
|
|
448
|
+
(0, output_1.printError)(`测试连接失败 (账号: ${payload.name || accountId})`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
(0, output_1.printSuccess)(`测试连接成功 (账号: ${payload.name || accountId})`);
|
|
452
|
+
}
|
|
453
|
+
if (resultRecord) {
|
|
454
|
+
const kv = [];
|
|
455
|
+
for (const key of Object.keys(resultRecord).slice(0, 10)) {
|
|
456
|
+
kv.push([key, String(JSON.stringify(resultRecord[key]).substring(0, 120))]);
|
|
457
|
+
}
|
|
458
|
+
if (kv.length)
|
|
459
|
+
(0, output_1.printKeyValue)(kv);
|
|
460
|
+
}
|
|
461
|
+
if (testFailed) {
|
|
462
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
const { message, fieldErrors } = extractFieldErrorsFromApiError(error);
|
|
467
|
+
if (options.json) {
|
|
468
|
+
(0, output_1.printStructuredResult)({
|
|
469
|
+
ok: false,
|
|
470
|
+
message: `测试连接失败: ${message}`,
|
|
471
|
+
...(fieldErrors ? { fieldErrors } : {}),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
(0, output_1.printError)(`测试连接失败: ${message}`);
|
|
476
|
+
if (fieldErrors) {
|
|
477
|
+
(0, output_1.printError)('字段级错误:');
|
|
478
|
+
for (const fe of fieldErrors) {
|
|
479
|
+
(0, output_1.printError)(` - ${JSON.stringify(fe)}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
484
|
+
}
|
|
485
|
+
});
|
|
48
486
|
}
|
|
49
487
|
//# sourceMappingURL=account.js.map
|
|
@@ -7,6 +7,7 @@ exports.registerFlowAnalyze = registerFlowAnalyze;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const flow_1 = require("@/api/flow");
|
|
9
9
|
const flowAnalyzer_1 = require("@/services/flowAnalyzer");
|
|
10
|
+
const flowValidator_1 = require("@/services/flowValidator");
|
|
10
11
|
const helpers_1 = require("@/commands/flow/helpers");
|
|
11
12
|
const output_1 = require("@/utils/output");
|
|
12
13
|
function registerFlowAnalyze(flowCmd) {
|
|
@@ -94,8 +95,15 @@ function registerFlowAnalyze(flowCmd) {
|
|
|
94
95
|
console.log(` ${chalk_1.default.gray(String(i + 1).padStart(3))}. ${chalk_1.default.cyan(nid)} ${chalk_1.default.white(`[${name}]`)}`);
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
const graphResult = (0, flowValidator_1.validateFlowGraph)({
|
|
99
|
+
nodes: analysis.nodes,
|
|
100
|
+
edges: analysis.edges,
|
|
101
|
+
});
|
|
102
|
+
for (const issue of graphResult.issues) {
|
|
103
|
+
if (issue.severity === 'error')
|
|
104
|
+
(0, output_1.printError)(issue.message);
|
|
105
|
+
else
|
|
106
|
+
(0, output_1.printWarning)(issue.message);
|
|
99
107
|
}
|
|
100
108
|
if (options.format === 'brief') {
|
|
101
109
|
(0, output_1.printSuccess)('分析完成');
|
|
@@ -33,11 +33,23 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectEnvelopeKeysInExport = detectEnvelopeKeysInExport;
|
|
36
37
|
exports.registerFlowExport = registerFlowExport;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const flow_1 = require("@/api/flow");
|
|
40
41
|
const output_1 = require("@/utils/output");
|
|
42
|
+
/** NodePackage 提交信封中由宿主处理的顶层键(详见 @/utils/nodeEnvelope) */
|
|
43
|
+
const ENVELOPE_HOST_KEYS = ['proxy_id', 'mcp_tool_description'];
|
|
44
|
+
/**
|
|
45
|
+
* 扫描导出 YAML 文本中出现的信封宿主键(proxy_id / mcp_tool_description)。
|
|
46
|
+
*
|
|
47
|
+
* 导出文件保持服务端序列化原样(保证 flow import 回导语义),
|
|
48
|
+
* 此处仅做展示层识别:返回命中的键名列表,供命令输出单独提示。
|
|
49
|
+
*/
|
|
50
|
+
function detectEnvelopeKeysInExport(yamlText) {
|
|
51
|
+
return ENVELOPE_HOST_KEYS.filter((key) => new RegExp(`^[ \\t-]+\\b${key}\\s*:`, 'm').test(yamlText));
|
|
52
|
+
}
|
|
41
53
|
function registerFlowExport(flowCmd) {
|
|
42
54
|
flowCmd
|
|
43
55
|
.command('export <flowId>')
|
|
@@ -61,19 +73,32 @@ function registerFlowExport(flowCmd) {
|
|
|
61
73
|
else {
|
|
62
74
|
outputPath = `./${flow.name || flowId}.yaml`;
|
|
63
75
|
}
|
|
76
|
+
const envelopeKeys = await (0, flow_1.exportFlow)(flowId, outputPath);
|
|
77
|
+
const detected = detectEnvelopeKeysInExport(String(envelopeKeys));
|
|
64
78
|
if (options.json) {
|
|
65
|
-
|
|
66
|
-
(0, output_1.printStructuredResult)({
|
|
79
|
+
const result = {
|
|
67
80
|
ok: true,
|
|
68
81
|
id: flowId,
|
|
69
82
|
file: outputPath,
|
|
70
83
|
message: `连接流已导出至 ${outputPath}`,
|
|
71
|
-
}
|
|
84
|
+
};
|
|
85
|
+
if (detected.length > 0) {
|
|
86
|
+
result.envelopeKeys = detected;
|
|
87
|
+
}
|
|
88
|
+
(0, output_1.printStructuredResult)(result);
|
|
72
89
|
}
|
|
73
90
|
else {
|
|
74
91
|
(0, output_1.printInfo)(`正在导出连接流 ${flow.name || flowId}...`);
|
|
75
|
-
await (0, flow_1.exportFlow)(flowId, outputPath);
|
|
76
92
|
(0, output_1.printSuccess)(`连接流已导出至 ${outputPath}`);
|
|
93
|
+
if (detected.length > 0) {
|
|
94
|
+
(0, output_1.printWarning)(`导出内容包含 NodePackage 信封字段: ${detected.join(', ')}`);
|
|
95
|
+
(0, output_1.printKeyValue)([
|
|
96
|
+
[
|
|
97
|
+
'说明',
|
|
98
|
+
'proxy_id / mcp_tool_description 为节点信封顶层字段(不属于业务 properties),文件保持服务端原样以保证导入兼容;可用 flow node show 查看解包后的可读结构',
|
|
99
|
+
],
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
77
102
|
}
|
|
78
103
|
}
|
|
79
104
|
catch (error) {
|