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
|
@@ -37,10 +37,11 @@ exports.generateAllSkills = generateAllSkills;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const knowledge_1 = require("@/api/knowledge");
|
|
40
|
+
const node_1 = require("@/api/node");
|
|
40
41
|
const format_1 = require("@/utils/format");
|
|
41
42
|
const output_1 = require("@/utils/output");
|
|
42
43
|
const nodePackage_1 = require("@/utils/nodePackage");
|
|
43
|
-
const
|
|
44
|
+
const fieldTypeRegistry_1 = require("@/services/fieldTypeRegistry");
|
|
44
45
|
async function generateAllSkills(outputDir) {
|
|
45
46
|
const progress = {
|
|
46
47
|
totalCategories: 0,
|
|
@@ -50,22 +51,21 @@ async function generateAllSkills(outputDir) {
|
|
|
50
51
|
totalActions: 0,
|
|
51
52
|
processedActions: 0,
|
|
52
53
|
skillsGenerated: 0,
|
|
53
|
-
builtinNodeTypes: 0,
|
|
54
54
|
errors: [],
|
|
55
55
|
};
|
|
56
|
-
// builtin-nodes.md:内置节点类型端口参考(纯本地数据,探针之前写入,
|
|
57
|
-
// 即使目标环境不支持 NodePackage 也能产出连线知识)
|
|
58
|
-
writeBuiltinNodesDoc(outputDir, progress);
|
|
59
56
|
const categoryTypes = [
|
|
60
57
|
{ type: 'normal', scene: 'normal', trigger: false, label: '自动化' },
|
|
61
58
|
{ type: 'normal-trigger', scene: 'normal', trigger: true, label: '自动化触发器' },
|
|
62
59
|
{ type: 'mcp', scene: 'mcp', trigger: false, label: 'MCP' },
|
|
63
60
|
{ type: 'mcp-trigger', scene: 'mcp', trigger: true, label: 'MCP触发器' },
|
|
64
61
|
];
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
// NodePackage 支持探测(走缓存),connectable 路径优先取 matched_actions
|
|
63
|
+
let nodePackageSupported = false;
|
|
64
|
+
try {
|
|
65
|
+
nodePackageSupported = await (0, nodePackage_1.checkAndCacheNodePackageSupport)();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
nodePackageSupported = false;
|
|
69
69
|
}
|
|
70
70
|
for (const ct of categoryTypes) {
|
|
71
71
|
try {
|
|
@@ -81,7 +81,7 @@ async function generateAllSkills(outputDir) {
|
|
|
81
81
|
for (const category of categories) {
|
|
82
82
|
const categoryMdDir = path.join(mdDir, category.name);
|
|
83
83
|
fs.mkdirSync(categoryMdDir, { recursive: true });
|
|
84
|
-
await processCategory(category, categoryMdDir, progress, ct.trigger, ct.scene);
|
|
84
|
+
await processCategory(category, categoryMdDir, progress, ct.trigger, ct.scene, nodePackageSupported);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
catch (error) {
|
|
@@ -91,7 +91,7 @@ async function generateAllSkills(outputDir) {
|
|
|
91
91
|
}
|
|
92
92
|
return progress;
|
|
93
93
|
}
|
|
94
|
-
async function processCategory(category, mdDir, progress, trigger = false, scene = '') {
|
|
94
|
+
async function processCategory(category, mdDir, progress, trigger = false, scene = 'normal', nodePackageSupported = false) {
|
|
95
95
|
const categoryKey = String(category.name || '');
|
|
96
96
|
const categoryLabel = String(category.label || categoryKey);
|
|
97
97
|
try {
|
|
@@ -113,7 +113,7 @@ async function processCategory(category, mdDir, progress, trigger = false, scene
|
|
|
113
113
|
const connectorBatchSize = 3;
|
|
114
114
|
for (let i = 0; i < items.length; i += connectorBatchSize) {
|
|
115
115
|
const batch = items.slice(i, i + connectorBatchSize);
|
|
116
|
-
await Promise.all(batch.map((item) => processConnector(item.connector, category, mdDir, progress, trigger, scene)));
|
|
116
|
+
await Promise.all(batch.map((item) => processConnector(item.connector, category, mdDir, progress, trigger, scene, nodePackageSupported)));
|
|
117
117
|
progress.processedNodes += batch.length;
|
|
118
118
|
}
|
|
119
119
|
processedCount += items.length;
|
|
@@ -137,17 +137,30 @@ async function processCategory(category, mdDir, progress, trigger = false, scene
|
|
|
137
137
|
}
|
|
138
138
|
progress.processedCategories++;
|
|
139
139
|
}
|
|
140
|
-
async function processConnector(connector, category, mdDir, progress, trigger = false, scene = '') {
|
|
140
|
+
async function processConnector(connector, category, mdDir, progress, trigger = false, scene = 'normal', nodePackageSupported = false) {
|
|
141
141
|
try {
|
|
142
|
-
// connectable 按 link_id 重查,一次请求同时拿 connector
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
// NodePackage 连接器:connectable 按 link_id 重查,一次请求同时拿 connector
|
|
143
|
+
// 与 matched_actions(含 node_schema);false 或请求失败回退 legacy getActions
|
|
144
|
+
let actions = null;
|
|
145
|
+
if (nodePackageSupported) {
|
|
146
|
+
try {
|
|
147
|
+
const result = await (0, knowledge_1.searchNodesByCategory)({
|
|
148
|
+
mode: 'independent',
|
|
149
|
+
scene,
|
|
150
|
+
trigger,
|
|
151
|
+
linkId: connector.id,
|
|
152
|
+
});
|
|
153
|
+
const exact = result.items.find((candidate) => candidate.connector.id === connector.id) ??
|
|
154
|
+
result.items[0];
|
|
155
|
+
actions = (exact?.matched_actions ?? []) || [];
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
actions = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (actions === null) {
|
|
162
|
+
actions = await (0, node_1.getActions)({ linkId: connector.id, trigger });
|
|
163
|
+
}
|
|
151
164
|
progress.totalActions += actions.length;
|
|
152
165
|
const actionBatchSize = 5;
|
|
153
166
|
for (let i = 0; i < actions.length; i += actionBatchSize) {
|
|
@@ -178,7 +191,6 @@ async function generateSkillFile(action, category, connector, mdDir, progress) {
|
|
|
178
191
|
action_type: action.action_type || 'action',
|
|
179
192
|
action_key: hasNodeSchema ? action.node_schema?.key || action.action_key : action.action_key,
|
|
180
193
|
action_ver: action.action_ver ?? action.node_schema?.version,
|
|
181
|
-
is_trigger: Boolean(action.is_trigger),
|
|
182
194
|
connector_id: action.connector_id,
|
|
183
195
|
link_id: connector.id,
|
|
184
196
|
action_id: action.id,
|
|
@@ -198,9 +210,9 @@ async function generateSkillFile(action, category, connector, mdDir, progress) {
|
|
|
198
210
|
version: action.node_schema.version,
|
|
199
211
|
name: action.node_schema.name,
|
|
200
212
|
description: action.node_schema.description,
|
|
201
|
-
inputs: action.node_schema.inputs ?? [],
|
|
202
|
-
outputs: action.node_schema.outputs ?? [],
|
|
203
213
|
input_fields: (0, format_1.nodeSchemaToParams)(action.node_schema.input_fields),
|
|
214
|
+
// 原始 schema 兜底:保留 item_schema/item_type 等完整描述符,防归一化丢信息
|
|
215
|
+
raw_input_fields: action.node_schema.input_fields || [],
|
|
204
216
|
output_fields: (action.node_schema.output_fields || []).map((o) => ({
|
|
205
217
|
key: o.key,
|
|
206
218
|
label: o.label,
|
|
@@ -219,152 +231,6 @@ async function generateSkillFile(action, category, connector, mdDir, progress) {
|
|
|
219
231
|
progress.errors.push(`写入 skill 文件 ${safeName} 失败: ${error.message}`);
|
|
220
232
|
}
|
|
221
233
|
}
|
|
222
|
-
function renderClassicPortRow(port) {
|
|
223
|
-
return `| \`${port.name}\` | ${port.displayName} | \`${port.type ?? 'main'}\` | ${port.maxConnections !== undefined && port.maxConnections !== null
|
|
224
|
-
? String(port.maxConnections)
|
|
225
|
-
: '不限'} |`;
|
|
226
|
-
}
|
|
227
|
-
function renderClassicPortsTable(ports) {
|
|
228
|
-
if (ports.length === 0)
|
|
229
|
-
return ['(无端口)', ''];
|
|
230
|
-
return [
|
|
231
|
-
'| 端口 | 显示名 | 类型/协议 | 最大连接数 |',
|
|
232
|
-
'|------|--------|-----------|------------|',
|
|
233
|
-
...ports.map(renderClassicPortRow),
|
|
234
|
-
'',
|
|
235
|
-
];
|
|
236
|
-
}
|
|
237
|
-
function renderClassicExampleCommands(descriptor) {
|
|
238
|
-
const lines = ['### 连线示例', '', '```bash'];
|
|
239
|
-
const firstOutput = descriptor.outputs[0];
|
|
240
|
-
const firstInput = descriptor.inputs[0];
|
|
241
|
-
if (firstOutput) {
|
|
242
|
-
// 目标侧描述该类型自己的输入桩(若有),否则提示连接下游节点
|
|
243
|
-
const targetPart = firstInput
|
|
244
|
-
? `--target <目标节点ID> --target-handle ${firstInput.name}`
|
|
245
|
-
: '--target <下游节点ID> --target-handle input';
|
|
246
|
-
lines.push(`ai-flow flow edge connect --flow-id <flowId> --source <${descriptor.nodeType}的节点ID> ${targetPart} --source-handle ${firstOutput.name}`);
|
|
247
|
-
}
|
|
248
|
-
else if (firstInput) {
|
|
249
|
-
lines.push(`ai-flow flow edge connect --flow-id <flowId> --source <上游节点ID> --target <${descriptor.nodeType}的节点ID> --source-handle output --target-handle ${firstInput.name}`);
|
|
250
|
-
}
|
|
251
|
-
for (const rule of descriptor.autoConnect ?? []) {
|
|
252
|
-
lines.push(`# 自环边(必需结构):`, `ai-flow flow edge connect --flow-id <flowId> --source <${descriptor.nodeType}的节点ID> --target <同节点ID> --source-handle ${rule.source} --target-handle ${rule.target}`);
|
|
253
|
-
}
|
|
254
|
-
// AI slave 桩(连线方向对齐 RCS 82b5976:slave 桩为源,指向 AI 子节点顶部 input 桩)
|
|
255
|
-
const slaveOutput = descriptor.outputs.find((p) => p.name.startsWith('ai_'));
|
|
256
|
-
if (slaveOutput) {
|
|
257
|
-
lines.push(`# AI 子节点接入(${descriptor.nodeType} 的 ${slaveOutput.name} 桩为连线源,AISubNode 顶部 input 桩为目标):`, `ai-flow flow edge connect --flow-id <flowId> --source <${descriptor.nodeType}的节点ID> --source-handle ${slaveOutput.name} --target <AISubNode节点ID> --target-handle input`);
|
|
258
|
-
}
|
|
259
|
-
// 仍作为目标的 slave 入桩(如 BrowserNode ai_model):协议匹配的源是 AgentNode 的 ai_model 出桩
|
|
260
|
-
const slaveInput = descriptor.inputs.find((p) => p.name.startsWith('ai_'));
|
|
261
|
-
if (slaveInput) {
|
|
262
|
-
lines.push(`# AI 子节点接入 ${slaveInput.name} 桩(源侧用 AgentNode 的同协议 ai_model 出桩):`, `ai-flow flow edge connect --flow-id <flowId> --source <AgentNode节点ID> --source-handle ai_model --target <${descriptor.nodeType}的节点ID> --target-handle ${slaveInput.name}`);
|
|
263
|
-
}
|
|
264
|
-
lines.push('```', '');
|
|
265
|
-
return lines;
|
|
266
|
-
}
|
|
267
|
-
function builtinNodesDocToMarkdown() {
|
|
268
|
-
const lines = [
|
|
269
|
-
'# 内置节点类型端口参考(classic/legacy)',
|
|
270
|
-
'',
|
|
271
|
-
'非 NodePackage 节点的内置 handle 声明(数据源:CLI 内置端口表,与 `flow edge connect` / `flow validate` 的端口校验同源)。',
|
|
272
|
-
'',
|
|
273
|
-
];
|
|
274
|
-
for (const descriptor of Object.values(classicNodePorts_1.CLASSIC_NODE_DESCRIPTORS)) {
|
|
275
|
-
lines.push(`## ${descriptor.nodeType}(${descriptor.label})`, '', descriptor.description, '');
|
|
276
|
-
lines.push('### 输入端口', '', ...renderClassicPortsTable(descriptor.inputs));
|
|
277
|
-
lines.push('### 输出端口', '', ...renderClassicPortsTable(descriptor.outputs));
|
|
278
|
-
if (descriptor.nodeType === 'SwitchNode') {
|
|
279
|
-
lines.push('> 输出端口随 `properties.branches` 实例派生:端口名 = 分支 `id`(`branch` 前缀),显示名 = 分支 `name`;`default-branch` 固定兜底。', '');
|
|
280
|
-
}
|
|
281
|
-
if (descriptor.byProtocol && Object.keys(descriptor.byProtocol).length > 0) {
|
|
282
|
-
lines.push('### 协议变体', '');
|
|
283
|
-
for (const [protocol, override] of Object.entries(descriptor.byProtocol)) {
|
|
284
|
-
lines.push(`- 节点 protocol = \`${protocol}\` 时:`);
|
|
285
|
-
if (override.outputs) {
|
|
286
|
-
lines.push(` - 输出端口变为 ${override.outputs.length === 0 ? '(无)' : override.outputs.map((p) => `\`${p.name}\``).join('、')}`);
|
|
287
|
-
}
|
|
288
|
-
if (override.inputs) {
|
|
289
|
-
lines.push(` - 输入端口变为 ${override.inputs.length === 0 ? '(无)' : override.inputs.map((p) => `\`${p.name}\``).join('、')}`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
lines.push('');
|
|
293
|
-
}
|
|
294
|
-
if ((descriptor.autoConnect ?? []).length > 0) {
|
|
295
|
-
lines.push('### 自动连线', '', ...descriptor.autoConnect.map((rule) => `- \`${rule.source}\` → \`${rule.target}\`(自环边)`), '');
|
|
296
|
-
}
|
|
297
|
-
lines.push(...renderClassicExampleCommands(descriptor));
|
|
298
|
-
}
|
|
299
|
-
return lines.join('\n');
|
|
300
|
-
}
|
|
301
|
-
function writeBuiltinNodesDoc(outputDir, progress) {
|
|
302
|
-
try {
|
|
303
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
304
|
-
fs.writeFileSync(path.join(outputDir, 'builtin-nodes.md'), builtinNodesDocToMarkdown(), 'utf-8');
|
|
305
|
-
progress.builtinNodeTypes = Object.keys(classicNodePorts_1.CLASSIC_NODE_DESCRIPTORS).length;
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
progress.errors.push(`生成 builtin-nodes.md 失败: ${error.message}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
const DEFAULT_INPUT_HANDLE = { name: 'input', display_name: '输入', type: 'main' };
|
|
312
|
-
const DEFAULT_OUTPUT_HANDLE = {
|
|
313
|
-
name: 'output',
|
|
314
|
-
display_name: '输出',
|
|
315
|
-
type: 'main',
|
|
316
|
-
};
|
|
317
|
-
function formatHandleConstraints(port, direction) {
|
|
318
|
-
const allowed = direction === 'input' ? port.allowed_source_node_keys : port.allowed_target_node_keys;
|
|
319
|
-
const denied = direction === 'input' ? port.denied_source_node_keys : port.denied_target_node_keys;
|
|
320
|
-
const parts = [];
|
|
321
|
-
if ((allowed ?? []).length > 0)
|
|
322
|
-
parts.push(`仅允许 ${allowed.join('、')}`);
|
|
323
|
-
if ((denied ?? []).length > 0)
|
|
324
|
-
parts.push(`禁止 ${denied.join('、')}`);
|
|
325
|
-
return parts.length > 0 ? parts.join(';') : '-';
|
|
326
|
-
}
|
|
327
|
-
function renderHandleTable(ports, direction) {
|
|
328
|
-
if (ports.length === 0) {
|
|
329
|
-
return [`(无${direction === 'input' ? '输入' : '输出'} Handle)`, ''];
|
|
330
|
-
}
|
|
331
|
-
const lines = [
|
|
332
|
-
'| Handle | 显示名 | 类型/协议 | 必填 | 最大连接数 | 连接约束 |',
|
|
333
|
-
'|--------|--------|-----------|------|------------|----------|',
|
|
334
|
-
];
|
|
335
|
-
for (const port of ports) {
|
|
336
|
-
const maxConnections = port.max_connections === undefined || port.max_connections === null
|
|
337
|
-
? '不限'
|
|
338
|
-
: String(port.max_connections);
|
|
339
|
-
lines.push(`| \`${port.name}\` | ${port.display_name || port.name} | \`${port.type || 'main'}\` | ${port.required ? '是' : '否'} | ${maxConnections} | ${formatHandleConstraints(port, direction)} |`);
|
|
340
|
-
}
|
|
341
|
-
lines.push('');
|
|
342
|
-
return lines;
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* 渲染连线的 input/output Handle 说明(连接点位的 wire 名称 + 连线命令示例)。
|
|
346
|
-
* Handle 名称即 `flow edge connect` 的 `--source-handle` / `--target-handle` 取值。
|
|
347
|
-
*/
|
|
348
|
-
function renderHandleSection(inputs, outputs, note) {
|
|
349
|
-
const lines = ['## 连接 Handle (Input / Output)', ''];
|
|
350
|
-
if (note)
|
|
351
|
-
lines.push(`> ${note}`, '');
|
|
352
|
-
lines.push('### 输入 Handle', '', ...renderHandleTable(inputs, 'input'));
|
|
353
|
-
lines.push('### 输出 Handle', '', ...renderHandleTable(outputs, 'output'));
|
|
354
|
-
if (inputs.length > 0 || outputs.length > 0) {
|
|
355
|
-
lines.push('```bash');
|
|
356
|
-
if (inputs.length > 0) {
|
|
357
|
-
const names = inputs.map((p) => `\`${p.name}\``).join('、');
|
|
358
|
-
lines.push(`# 作为下游节点连入(本节点 --target-handle 可选:${names})`, `ai-flow flow edge connect --flow-id <flowId> --source <上游节点ID> --source-handle <上游输出Handle> --target <本节点ID> --target-handle ${inputs[0].name}`);
|
|
359
|
-
}
|
|
360
|
-
if (outputs.length > 0) {
|
|
361
|
-
const names = outputs.map((p) => `\`${p.name}\``).join('、');
|
|
362
|
-
lines.push(`# 作为上游节点连出(本节点 --source-handle 可选:${names})`, `ai-flow flow edge connect --flow-id <flowId> --source <本节点ID> --source-handle ${outputs[0].name} --target <下游节点ID> --target-handle <下游输入Handle>`);
|
|
363
|
-
}
|
|
364
|
-
lines.push('```', '');
|
|
365
|
-
}
|
|
366
|
-
return lines;
|
|
367
|
-
}
|
|
368
234
|
function skillToMarkdown(skill) {
|
|
369
235
|
const lines = [`# ${skill.name}`, ''];
|
|
370
236
|
if (skill.description) {
|
|
@@ -380,18 +246,11 @@ function skillToMarkdown(skill) {
|
|
|
380
246
|
if (ns.description)
|
|
381
247
|
lines.push(`- **Description**: ${ns.description}`);
|
|
382
248
|
lines.push('');
|
|
383
|
-
lines.push(...renderHandleSection(ns.inputs, ns.outputs, '连线时通过 `--source-handle` 选择输出 Handle、`--target-handle` 选择输入 Handle;Handle 即端口在流程数据中的 wire 名称。'));
|
|
384
249
|
if (ns.input_fields.length > 0) {
|
|
385
250
|
lines.push('## 配置参数 (Input Fields)', '');
|
|
386
|
-
lines.
|
|
387
|
-
lines.push('|--------|--------|------|------|------|--------|--------|');
|
|
388
|
-
for (const param of ns.input_fields) {
|
|
389
|
-
const options = (param.options || [])
|
|
390
|
-
?.map((o) => (o.label !== o.key ? `\`${o.key}\` (${o.label})` : `\`${o.key}\``))
|
|
391
|
-
.join(', ') || '-';
|
|
392
|
-
lines.push(`| \`${param.name}\` | ${param.display_name} | ${param.type} | ${param.required ? '是' : '否'} | ${param.description || '-'} | ${param.default ?? '-'} | ${options} |`);
|
|
393
|
-
}
|
|
251
|
+
renderParamsTable(lines, ns.input_fields);
|
|
394
252
|
lines.push('');
|
|
253
|
+
renderNestedShapes(lines, ns.input_fields);
|
|
395
254
|
}
|
|
396
255
|
if (ns.output_fields.length > 0) {
|
|
397
256
|
lines.push('## 输出字段', '');
|
|
@@ -403,22 +262,205 @@ function skillToMarkdown(skill) {
|
|
|
403
262
|
lines.push('');
|
|
404
263
|
}
|
|
405
264
|
}
|
|
406
|
-
else {
|
|
407
|
-
lines.push(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
lines.push('|--------|--------|------|------|------|--------|--------|');
|
|
412
|
-
for (const param of skill.parameters) {
|
|
413
|
-
const options = (param.options || [])
|
|
414
|
-
?.map((o) => (o.label !== o.key ? `\`${o.key}\` (${o.label})` : `\`${o.key}\``))
|
|
415
|
-
.join(', ') || '-';
|
|
416
|
-
lines.push(`| \`${param.name}\` | ${param.display_name} | ${param.type} | ${param.required ? '是' : '否'} | ${param.description || '-'} | ${param.default ?? '-'} | ${options} |`);
|
|
417
|
-
}
|
|
418
|
-
lines.push('');
|
|
419
|
-
}
|
|
265
|
+
else if (skill.parameters.length > 0) {
|
|
266
|
+
lines.push('## 配置参数', '');
|
|
267
|
+
renderParamsTable(lines, skill.parameters);
|
|
268
|
+
lines.push('');
|
|
269
|
+
renderNestedShapes(lines, skill.parameters);
|
|
420
270
|
}
|
|
421
271
|
lines.push('## 原始数据', '', '```json', JSON.stringify(skill, null, 2), '```', '');
|
|
422
272
|
return lines.join('\n');
|
|
423
273
|
}
|
|
274
|
+
/** 参数总表:字段名 / 显示名 / 类型(含注册表值形状)/ 必填 / 说明 / 默认值 / 可选值 / 约束 */
|
|
275
|
+
function renderParamsTable(lines, params) {
|
|
276
|
+
lines.push('| 字段名 | 显示名 | 类型 | 必填 | 说明 | 默认值 | 可选值 | 约束 |');
|
|
277
|
+
lines.push('|--------|--------|------|------|------|--------|--------|------|');
|
|
278
|
+
for (const param of params) {
|
|
279
|
+
const options = (param.options || [])
|
|
280
|
+
?.map((o) => (o.label !== o.key ? `\`${o.key}\` (${o.label})` : `\`${o.key}\``))
|
|
281
|
+
.join(', ') || '-';
|
|
282
|
+
const typeLabel = formatTypeLabel(param.type, param);
|
|
283
|
+
const constraints = formatConstraints(param);
|
|
284
|
+
lines.push(`| \`${param.name}\` | ${param.display_name} | ${typeLabel} | ${param.required ? '是' : '否'} | ${param.description || '-'} | ${formatDefaultValue(param.default)} | ${options} | ${constraints} |`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** 类型标签:注册表命中的类型附带值形状契约(kind → 值形状) */
|
|
288
|
+
function formatTypeLabel(typeName, param) {
|
|
289
|
+
const contract = (0, fieldTypeRegistry_1.findFieldTypeContract)(typeName);
|
|
290
|
+
let label = contract || (0, fieldTypeRegistry_1.isKnownFieldType)(typeName) ? typeName : `${typeName} (宿主自定义类型)`;
|
|
291
|
+
if (contract) {
|
|
292
|
+
const shape = contractShapeLabel(contract);
|
|
293
|
+
if (shape)
|
|
294
|
+
label = `${label}: ${shape}`;
|
|
295
|
+
}
|
|
296
|
+
if (param) {
|
|
297
|
+
if (param.properties && param.properties.length > 0) {
|
|
298
|
+
label = `${label} {${param.properties.map((p) => p.name).join(', ')}}`;
|
|
299
|
+
}
|
|
300
|
+
else if (param.item_type) {
|
|
301
|
+
label = `${label} <${param.item_type}${param.item_fields && param.item_fields.length > 0 ? ` {${param.item_fields.map((f) => f.name).join(', ')}}` : ''}>`;
|
|
302
|
+
}
|
|
303
|
+
else if (typeName.toUpperCase() === 'KEYVALUE') {
|
|
304
|
+
label = `${label} {key,value}[]`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return label;
|
|
308
|
+
}
|
|
309
|
+
/** 注册表契约的值形状描述(kind 分类 → 可构造值形状) */
|
|
310
|
+
function contractShapeLabel(contract) {
|
|
311
|
+
switch (contract.kind) {
|
|
312
|
+
case 'stringArray':
|
|
313
|
+
return 'string[]';
|
|
314
|
+
case 'recordArray':
|
|
315
|
+
return 'object[]';
|
|
316
|
+
case 'keyValueArray':
|
|
317
|
+
return '{key,value}[]';
|
|
318
|
+
case 'record':
|
|
319
|
+
return 'object';
|
|
320
|
+
case 'array':
|
|
321
|
+
return 'array';
|
|
322
|
+
case 'object':
|
|
323
|
+
return 'object';
|
|
324
|
+
case 'boolean':
|
|
325
|
+
return 'boolean';
|
|
326
|
+
case 'number':
|
|
327
|
+
return 'number';
|
|
328
|
+
case 'string':
|
|
329
|
+
return 'string';
|
|
330
|
+
case 'time':
|
|
331
|
+
return 'HH:mm:00';
|
|
332
|
+
case 'code':
|
|
333
|
+
return 'string (代码,支持 ${...} 表达式编码)';
|
|
334
|
+
case 'token':
|
|
335
|
+
return 'string (自动生成的随机 token)';
|
|
336
|
+
case 'flowRef':
|
|
337
|
+
return '{flowId,input}';
|
|
338
|
+
case 'guardRules':
|
|
339
|
+
return 'GuardRule[]';
|
|
340
|
+
case 'orList':
|
|
341
|
+
return '{or_list:[{and_list:[...]}]}';
|
|
342
|
+
case 'orgNode':
|
|
343
|
+
return '{id,name}';
|
|
344
|
+
case 'multiRowExtract':
|
|
345
|
+
return 'object[]';
|
|
346
|
+
default:
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** 字段级约束摘要:required 之外的 pattern/min/max/长度/数量/变量/枚举提示 */
|
|
351
|
+
function formatConstraints(param) {
|
|
352
|
+
const parts = [];
|
|
353
|
+
if (param.validation) {
|
|
354
|
+
const v = param.validation;
|
|
355
|
+
if (v.min !== undefined)
|
|
356
|
+
parts.push(`min=${v.min}`);
|
|
357
|
+
if (v.max !== undefined)
|
|
358
|
+
parts.push(`max=${v.max}`);
|
|
359
|
+
if (v.pattern)
|
|
360
|
+
parts.push(`pattern: \`${v.pattern}\``);
|
|
361
|
+
if (v.min_length !== undefined)
|
|
362
|
+
parts.push(`minLength=${v.min_length}`);
|
|
363
|
+
if (v.max_length !== undefined)
|
|
364
|
+
parts.push(`maxLength=${v.max_length}`);
|
|
365
|
+
}
|
|
366
|
+
if (param.min_items !== undefined && param.min_items !== null) {
|
|
367
|
+
parts.push(`minItems=${param.min_items}`);
|
|
368
|
+
}
|
|
369
|
+
if (param.max_items !== undefined && param.max_items !== null) {
|
|
370
|
+
parts.push(`maxItems=${param.max_items}`);
|
|
371
|
+
}
|
|
372
|
+
if (param.supports_variables)
|
|
373
|
+
parts.push('支持变量引用 {{N1.x}}/{{global.x}}');
|
|
374
|
+
if (param.options && param.options.length > 0)
|
|
375
|
+
parts.push('枚举值见可选值列');
|
|
376
|
+
return parts.length > 0 ? parts.join('; ') : '-';
|
|
377
|
+
}
|
|
378
|
+
function formatDefaultValue(value) {
|
|
379
|
+
if (value === undefined || value === null)
|
|
380
|
+
return '-';
|
|
381
|
+
if (typeof value === 'string')
|
|
382
|
+
return value || '-';
|
|
383
|
+
return JSON.stringify(value);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 嵌套字段形状明细:OBJECT 子字段 / ARRAY 元素形状 / KEYVALUE 键值对形状。
|
|
387
|
+
* 递归展开,直到无嵌套字段。
|
|
388
|
+
*/
|
|
389
|
+
function renderNestedShapes(lines, params) {
|
|
390
|
+
const sections = [];
|
|
391
|
+
collectNestedSections(sections, params, '', 0);
|
|
392
|
+
if (sections.length === 0)
|
|
393
|
+
return;
|
|
394
|
+
lines.push('## 嵌套字段形状', '');
|
|
395
|
+
lines.push('复杂类型字段(OBJECT / ARRAY / KEYVALUE 等)的值形状明细。构造 `--data` 时按下列形状填写 `properties` 中的对应字段:', '');
|
|
396
|
+
lines.push(...sections);
|
|
397
|
+
}
|
|
398
|
+
function collectNestedSections(sections, params, prefix, depth) {
|
|
399
|
+
if (depth > 5)
|
|
400
|
+
return; // 防御极深嵌套
|
|
401
|
+
for (const param of params) {
|
|
402
|
+
const path = prefix ? `${prefix}.${param.name}` : param.name;
|
|
403
|
+
const typeUpper = param.type.toUpperCase();
|
|
404
|
+
if (param.properties && param.properties.length > 0) {
|
|
405
|
+
sections.push(`### \`${path}\` (${param.type})`, '');
|
|
406
|
+
if (param.description)
|
|
407
|
+
sections.push(param.description, '');
|
|
408
|
+
sections.push('Object,包含以下子字段:', '');
|
|
409
|
+
sections.push('| 子字段 | 显示名 | 类型 | 必填 | 说明 | 默认值 |');
|
|
410
|
+
sections.push('|--------|--------|------|------|------|--------|');
|
|
411
|
+
for (const child of param.properties) {
|
|
412
|
+
const childType = formatTypeLabel(child.type, child);
|
|
413
|
+
sections.push(`| \`${child.name}\` | ${child.display_name} | ${childType} | ${child.required ? '是' : '否'} | ${child.description || '-'} | ${formatDefaultValue(child.default)} |`);
|
|
414
|
+
}
|
|
415
|
+
sections.push('');
|
|
416
|
+
collectNestedSections(sections, param.properties, path, depth + 1);
|
|
417
|
+
}
|
|
418
|
+
if (param.item_type || param.item_fields || typeUpper === 'KEYVALUE') {
|
|
419
|
+
sections.push(`### \`${path}\` (${param.type})`, '');
|
|
420
|
+
if (param.description)
|
|
421
|
+
sections.push(param.description, '');
|
|
422
|
+
const bounds = [];
|
|
423
|
+
if (param.min_items !== undefined && param.min_items !== null) {
|
|
424
|
+
bounds.push(`至少 ${param.min_items} 项`);
|
|
425
|
+
}
|
|
426
|
+
if (param.max_items !== undefined && param.max_items !== null) {
|
|
427
|
+
bounds.push(`至多 ${param.max_items} 项`);
|
|
428
|
+
}
|
|
429
|
+
if (typeUpper === 'KEYVALUE') {
|
|
430
|
+
const keyPh = param.key_placeholder || 'key';
|
|
431
|
+
const valPh = param.value_placeholder || 'value';
|
|
432
|
+
sections.push('键值对数组,每项形状:', '');
|
|
433
|
+
sections.push('```json');
|
|
434
|
+
sections.push(`[{"key": "${keyPh}", "value": "${valPh}"}]`);
|
|
435
|
+
sections.push('```', '');
|
|
436
|
+
}
|
|
437
|
+
else if (param.item_fields && param.item_fields.length > 0) {
|
|
438
|
+
sections.push(`数组${bounds.length > 0 ? `(${bounds.join(',')})` : ''},元素为 object,每个元素的字段:`, '');
|
|
439
|
+
sections.push('| 元素字段 | 显示名 | 类型 | 必填 | 说明 |');
|
|
440
|
+
sections.push('|----------|--------|------|------|------|');
|
|
441
|
+
for (const f of param.item_fields) {
|
|
442
|
+
sections.push(`| \`${f.name}\` | ${f.display_name} | ${formatTypeLabel(f.type, f)} | ${f.required ? '是' : '否'} | ${f.description || '-'} |`);
|
|
443
|
+
}
|
|
444
|
+
sections.push('');
|
|
445
|
+
}
|
|
446
|
+
else if (param.item_type) {
|
|
447
|
+
sections.push(`数组${bounds.length > 0 ? `(${bounds.join(',')})` : ''},元素类型为 \`${param.item_type}\`:`, '');
|
|
448
|
+
sections.push('```json');
|
|
449
|
+
if (param.item_type.toUpperCase() === 'OBJECT') {
|
|
450
|
+
sections.push('[{}]');
|
|
451
|
+
}
|
|
452
|
+
else if (param.item_type.toUpperCase() === 'NUMBER') {
|
|
453
|
+
sections.push('[0]');
|
|
454
|
+
}
|
|
455
|
+
else if (param.item_type.toUpperCase() === 'BOOLEAN') {
|
|
456
|
+
sections.push('[false]');
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
sections.push('[""]');
|
|
460
|
+
}
|
|
461
|
+
sections.push('```', '');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
424
466
|
//# sourceMappingURL=skillGenerator.js.map
|