digitalsee-ai-flow-cli 0.9.15 → 0.9.24-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 CHANGED
@@ -1412,6 +1412,8 @@ ai-flow knowledge actions <linkId> --json # JSON 格式输出(含 outpu
1412
1412
 
1413
1413
  遍历所有分类下的所有节点和动作,为每个动作生成 Markdown 文档和 JSON 结构化数据文件,按分类类型和分类名组织存储。支持 `node_schema` 模式(包含 input/output fields 详情)。
1414
1414
 
1415
+ 每个 Markdown 文档包含 **连接 Handle (Input / Output)** 章节,列出该节点的输入/输出连接点(Handle、显示名、类型/协议、必填、最大连接数、连接约束)及 `flow edge connect` 示例命令;Handle 名称即 `--source-handle` / `--target-handle` 的取值。`node_schema` 动作直接取自 schema 的 `inputs` / `outputs`;无 `node_schema` 的内置节点回退到默认 `input` / `output`(触发器无输入 Handle),完整的内置端口声明见同目录 `builtin-nodes.md`。
1416
+
1415
1417
  ```bash
1416
1418
  ai-flow knowledge generate-skills ./docs
1417
1419
  ai-flow knowledge generate-skills ./docs --since "2026-01-01T00:00:00Z" # 增量更新
@@ -1422,6 +1424,7 @@ ai-flow knowledge generate-skills ./docs --json
1422
1424
  输出:
1423
1425
  ```
1424
1426
  docs/
1427
+ ├── builtin-nodes.md
1425
1428
  ├── normal/
1426
1429
  │ ├── HTTP/
1427
1430
  │ │ ├── HTTP_发送请求.md
@@ -15,6 +15,20 @@ const https_1 = __importDefault(require("https"));
15
15
  const axios_1 = __importDefault(require("axios"));
16
16
  const config_1 = require("@/utils/config");
17
17
  const refresh_1 = require("@/api/refresh");
18
+ /** 业务错误信封成功码:error/code 为这些值时视为成功(如 {error:"0", error_description:"SUCCESS"}) */
19
+ const ENVELOPE_SUCCESS_CODES = new Set(['', '0', '200', 'SUCCESS']);
20
+ function isEnvelopeBusinessError(body) {
21
+ if (body.result === false)
22
+ return true;
23
+ const code = String(body.error ?? body.code ?? '');
24
+ return code !== '' && !ENVELOPE_SUCCESS_CODES.has(code.toUpperCase());
25
+ }
26
+ function envelopeErrorMessage(body) {
27
+ const code = body.error ?? body.code;
28
+ return (body.error_description ||
29
+ body.message ||
30
+ (code !== undefined && code !== '' ? String(code) : JSON.stringify(body)));
31
+ }
18
32
  let clientInstance = null;
19
33
  function classifyError(error) {
20
34
  if (error && typeof error === 'object') {
@@ -61,9 +75,11 @@ function classifyError(error) {
61
75
  }
62
76
  if (status === 400 || status === 422) {
63
77
  const body = response.data;
64
- const msg = body?.data?.message ||
65
- body?.data?.error ||
78
+ const msg = body?.error_description ||
79
+ body?.data?.message ||
80
+ response.data?.error_description ||
66
81
  response.data?.message ||
82
+ body?.error ||
67
83
  response.data?.error ||
68
84
  JSON.stringify(response.data);
69
85
  return {
@@ -80,9 +96,11 @@ function classifyError(error) {
80
96
  };
81
97
  }
82
98
  const body = response.data;
83
- const msg = body?.data?.message ||
84
- body?.data?.error ||
99
+ const msg = body?.error_description ||
100
+ body?.data?.message ||
101
+ response.data?.error_description ||
85
102
  response.data?.message ||
103
+ body?.error ||
86
104
  response.data?.error ||
87
105
  JSON.stringify(response.data);
88
106
  return {
@@ -135,11 +153,16 @@ function buildClient() {
135
153
  });
136
154
  instance.interceptors.response.use((response) => {
137
155
  const body = response.data;
138
- if (body && typeof body === 'object' && 'data' in body) {
139
- response.data = body.data;
140
- //钉钉网关可能会触发 code: -1, Access Denied
141
- if (body.data === null && body.code === '-1') {
142
- throw new Error(humanizeError(body.message ?? body));
156
+ if (body && typeof body === 'object' && !Array.isArray(body)) {
157
+ // HTTP 200 但业务失败的信封(如 {result:false, error:"1", error_description:"..."},
158
+ // 或无 data 键的 {code:"202", message:"..."}):抛错并优先透出 error_description,
159
+ // 避免错误被当成功放行(下游 undefined 崩溃 / "vnull" 之类的假成功)。
160
+ const looksLikeEnvelope = 'data' in body || 'result' in body || 'error' in body || 'code' in body;
161
+ if (looksLikeEnvelope && isEnvelopeBusinessError(body)) {
162
+ throw new Error(envelopeErrorMessage(body));
163
+ }
164
+ if ('data' in body) {
165
+ response.data = body.data;
143
166
  }
144
167
  }
145
168
  return response;
@@ -160,7 +183,10 @@ function buildClient() {
160
183
  }
161
184
  }
162
185
  }
163
- throw new Error(humanizeError(error));
186
+ const structured = classifyError(error);
187
+ const apiError = new Error(structured.message);
188
+ apiError.code = structured.code;
189
+ throw apiError;
164
190
  });
165
191
  return instance;
166
192
  }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.publishFlowVersion = publishFlowVersion;
4
+ exports.getFlowVersions = getFlowVersions;
5
+ exports.getFlowVersionDetail = getFlowVersionDetail;
6
+ exports.rollbackFlowVersion = rollbackFlowVersion;
7
+ exports.loadFlowVersionAsDraft = loadFlowVersionAsDraft;
8
+ exports.getFlowVersionStatus = getFlowVersionStatus;
9
+ exports.deleteFlowVersion = deleteFlowVersion;
10
+ exports.offlineFlowVersion = offlineFlowVersion;
11
+ const client_1 = require("@/api/client");
12
+ const BASE = (flowId) => `/acm/flows/v2/${flowId}/versions`;
13
+ /** 发布当前草稿为新的生效版本。 */
14
+ async function publishFlowVersion(flowId, data) {
15
+ return (0, client_1.apiPost)(`${BASE(flowId)}/publish`, data);
16
+ }
17
+ /** 查询发布版本历史。 */
18
+ async function getFlowVersions(flowId, params = { page: 1, size: 20 }) {
19
+ return (0, client_1.apiGet)(BASE(flowId), params);
20
+ }
21
+ /** 查询历史版本详情。 */
22
+ async function getFlowVersionDetail(flowId, versionFlowId) {
23
+ return (0, client_1.apiGet)(`${BASE(flowId)}/${encodeURIComponent(versionFlowId)}`);
24
+ }
25
+ /** 将线上生效版本切换到指定历史版本(成功即隐式发布)。 */
26
+ async function rollbackFlowVersion(flowId, versionFlowId) {
27
+ return (0, client_1.apiPost)(`${BASE(flowId)}/${encodeURIComponent(versionFlowId)}/rollback`, {});
28
+ }
29
+ /** 使用指定历史版本覆盖唯一草稿。 */
30
+ async function loadFlowVersionAsDraft(flowId, versionFlowId) {
31
+ return (0, client_1.apiPost)(`${BASE(flowId)}/${encodeURIComponent(versionFlowId)}/load-draft`, {});
32
+ }
33
+ /** 查询草稿与线上生效版本的关系。 */
34
+ async function getFlowVersionStatus(flowId) {
35
+ return (0, client_1.apiGet)(`${BASE(flowId)}/status`);
36
+ }
37
+ /** 删除发布版本。 */
38
+ async function deleteFlowVersion(flowId, versionFlowId) {
39
+ return (0, client_1.apiDelete)(`${BASE(flowId)}/${encodeURIComponent(versionFlowId)}`);
40
+ }
41
+ /** 版本化下线(替代废弃的 op/v2/:id/OFFLINE 下线场景)。 */
42
+ async function offlineFlowVersion(flowId) {
43
+ return (0, client_1.apiPost)(`${BASE(flowId)}/offline`);
44
+ }
45
+ //# sourceMappingURL=flowVersion.js.map
@@ -1,29 +1,70 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildConnectableRequestBody = buildConnectableRequestBody;
3
4
  exports.getNodeCategories = getNodeCategories;
4
5
  exports.searchNodesByCategory = searchNodesByCategory;
5
6
  exports.getConnectorDetail = getConnectorDetail;
6
7
  exports.getAllConnectors = getAllConnectors;
7
8
  const client_1 = require("@/api/client");
8
- async function getNodeCategories(params) {
9
- return (0, client_1.apiPost)('/acm/node-package/categories/connectable', {
10
- scene: params?.scene || 'normal',
11
- trigger: params?.trigger || false,
12
- });
13
- }
14
- async function searchNodesByCategory(params) {
9
+ const connectable_1 = require("@/utils/connectable");
10
+ const trimOptionalText = (value) => {
11
+ const normalized = value?.trim();
12
+ return normalized ? normalized : undefined;
13
+ };
14
+ const parsePositiveOption = (value) => {
15
+ if (value === undefined)
16
+ return undefined;
17
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined;
18
+ };
19
+ function buildConnectableRequestBody(query) {
15
20
  const body = {
16
- scene: 'normal',
17
- page: params.page || 1,
18
- size: params.size || 50,
19
- trigger: params.trigger,
21
+ // scene 默认空(对齐 RCS config.scene ?? '';仅 MCP Server 画布场景传 'mcp')
22
+ scene: trimOptionalText(query.scene) ?? '',
23
+ // 服务端要求 trigger 必填;undefined 会被 JSON 序列化丢弃导致 1010002 错误
24
+ trigger: query.trigger ?? false,
20
25
  };
21
- if (params.keyword)
22
- body.keyword = params.keyword;
23
- if (params.category_name)
24
- body.category_name = params.category_name;
25
- const data = await (0, client_1.apiPost)('/acm/node-package/plugins/connectable', body);
26
- return data || { items: [], total: 0, page: params.page || 1, size: params.size || 50 };
26
+ const categoryName = trimOptionalText(query.categoryName);
27
+ if (categoryName !== undefined)
28
+ body.category_name = categoryName;
29
+ const linkId = trimOptionalText(query.linkId);
30
+ if (linkId !== undefined)
31
+ body.link_id = linkId;
32
+ const keyword = trimOptionalText(query.keyword);
33
+ if (keyword !== undefined)
34
+ body.keyword = keyword;
35
+ const flowId = trimOptionalText(query.flowId);
36
+ if (flowId !== undefined)
37
+ body.flow_id = flowId;
38
+ const page = parsePositiveOption(query.page);
39
+ if (page !== undefined)
40
+ body.page = page;
41
+ const size = parsePositiveOption(query.size);
42
+ if (size !== undefined)
43
+ body.size = size;
44
+ if (query.mode === 'source') {
45
+ body.source_id = query.sourceId;
46
+ body.source_handle = query.sourceHandle;
47
+ }
48
+ else if (query.mode === 'target') {
49
+ body.target_id = query.targetId;
50
+ body.target_handle = query.targetHandle;
51
+ }
52
+ return body;
53
+ }
54
+ /**
55
+ * 节点分类只走 connectable API(POST /acm/node-package/categories/connectable),
56
+ * 本版本不兼容旧版接口与旧版入参(scene/trigger 裸参数形式已移除,统一 ConnectableQuery)。
57
+ */
58
+ async function getNodeCategories(query) {
59
+ const data = await (0, client_1.apiPost)('/acm/node-package/categories/connectable', buildConnectableRequestBody(query));
60
+ return (0, connectable_1.normalizeConnectableCategories)(data);
61
+ }
62
+ async function searchNodesByCategory(query) {
63
+ const data = await (0, client_1.apiPost)('/acm/node-package/plugins/connectable', buildConnectableRequestBody(query));
64
+ if (data === null || data === undefined) {
65
+ return { items: [], total: 0, page: query.page || 1, size: query.size || 50 };
66
+ }
67
+ return (0, connectable_1.normalizeConnectablePluginPage)(data);
27
68
  }
28
69
  async function getConnectorDetail(connectorId) {
29
70
  return (0, client_1.apiGet)(`/acm/links/${connectorId}`);
package/dist/api/node.js CHANGED
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getActionDetail = getActionDetail;
4
- exports.getActions = getActions;
5
- exports.searchActions = searchActions;
4
+ exports.getActionNodeSchema = getActionNodeSchema;
6
5
  exports.isNodePackageSupported = isNodePackageSupported;
7
6
  exports.getNodeDetail = getNodeDetail;
8
7
  exports.createNode = createNode;
@@ -14,53 +13,92 @@ exports.cancelDebugNode = cancelDebugNode;
14
13
  const client_1 = require("@/api/client");
15
14
  const knowledge_1 = require("@/api/knowledge");
16
15
  const encoding_1 = require("@/utils/encoding");
16
+ /**
17
+ * Legacy action detail lookup by numeric backend action id.
18
+ *
19
+ * Use `getActionNodeSchema(nodeKey, version)` instead for NodePackage nodes,
20
+ * whose persistent identity is `action_key + action_ver` — the path params
21
+ * there are the string action key and the numeric action version, not a
22
+ * backend id.
23
+ */
17
24
  async function getActionDetail(id, version) {
18
25
  return await (0, client_1.apiGet)(`/acm/actions/${id}${version ? '/' + version : ''}`);
19
26
  }
20
- async function getActions(params) {
21
- const data = await (0, client_1.apiGet)('/acm/actions/v2', params);
22
- if (Array.isArray(data))
23
- return data;
24
- if (data && Array.isArray(data.items))
25
- return data.items;
26
- if (data && Array.isArray(data.actions))
27
- return data.actions;
28
- if (data && Array.isArray(data.data))
29
- return data.data;
30
- return [];
27
+ function parseMaybeJson(value) {
28
+ if (typeof value !== 'string') {
29
+ return value;
30
+ }
31
+ try {
32
+ return JSON.parse(value);
33
+ }
34
+ catch {
35
+ return value;
36
+ }
31
37
  }
32
- async function searchActions(params) {
33
- const data = await (0, client_1.apiPost)('/acm/links/search', params);
34
- const actions = [];
35
- if (data && data.items && Array.isArray(data.items)) {
36
- for (const item of data.items) {
37
- if (item.matched_actions && Array.isArray(item.matched_actions)) {
38
- actions.push(...item.matched_actions);
39
- }
38
+ function pickFirstDefined(...values) {
39
+ for (const value of values) {
40
+ if (value !== undefined && value !== null) {
41
+ return value;
40
42
  }
41
43
  }
42
- return actions;
44
+ return undefined;
45
+ }
46
+ function normalizeActionNodeSchemaResponse(response, nodeKey, version) {
47
+ const parsedResponse = parseMaybeJson(response);
48
+ const rawSchema = parseMaybeJson(parsedResponse?.node_schema ?? parsedResponse?.nodeSchema ?? parsedResponse);
49
+ return {
50
+ ...rawSchema,
51
+ key: (pickFirstDefined(rawSchema?.key, parsedResponse?.action_key, parsedResponse?.node_key, parsedResponse?.nodeKey, nodeKey) ?? nodeKey),
52
+ version: (pickFirstDefined(rawSchema?.version, parsedResponse?.action_ver, parsedResponse?.node_ver, parsedResponse?.nodeVersion, version) ?? version),
53
+ name: (pickFirstDefined(rawSchema?.name, parsedResponse?.name, parsedResponse?.action_name, nodeKey) ?? nodeKey),
54
+ description: pickFirstDefined(rawSchema?.description, parsedResponse?.description),
55
+ inputs: Array.isArray(rawSchema?.inputs) ? rawSchema.inputs : [],
56
+ outputs: Array.isArray(rawSchema?.outputs) ? rawSchema.outputs : [],
57
+ input_fields: Array.isArray(rawSchema?.input_fields)
58
+ ? rawSchema.input_fields
59
+ : [],
60
+ output_fields: Array.isArray(rawSchema?.output_fields)
61
+ ? rawSchema.output_fields
62
+ : [],
63
+ credentials: Array.isArray(rawSchema?.credentials)
64
+ ? rawSchema.credentials
65
+ : [],
66
+ };
67
+ }
68
+ /**
69
+ * Fetch the authoritative NodePackage node schema.
70
+ *
71
+ * GET `/acm/actions/:nodeKey/:version` where `nodeKey` is the action_key
72
+ * string (e.g. `slack.sendMessage`) and `version` is the numeric action_ver.
73
+ * The response is normalized: the body may be a JSON string, the schema may
74
+ * be nested under `node_schema`/`nodeSchema`, and missing key/version/name
75
+ * fields fall back through the outer envelope down to the call arguments.
76
+ */
77
+ async function getActionNodeSchema(nodeKey, version) {
78
+ const response = await (0, client_1.apiGet)(`/acm/actions/${encodeURIComponent(nodeKey)}/${version}`);
79
+ return normalizeActionNodeSchemaResponse(response, nodeKey, version);
43
80
  }
81
+ /**
82
+ * Single-request probe for NodePackage support.
83
+ *
84
+ * Calls `getNodeCategories()` once: a successful array response (including an
85
+ * empty array) means the connectable API exists → true. A 404/NOT_FOUND (or a
86
+ * non-array payload) means unsupported → false. Any other error (network,
87
+ * auth, 5xx) is re-thrown so callers can distinguish transient failures from
88
+ * a definitive "unsupported" conclusion.
89
+ */
44
90
  async function isNodePackageSupported() {
91
+ let categories;
45
92
  try {
46
- const categories = await (0, knowledge_1.getNodeCategories)();
47
- const firstName = categories[0]?.name;
48
- if (!firstName)
49
- return false;
50
- const { items } = await (0, knowledge_1.searchNodesByCategory)({
51
- category_name: firstName,
52
- trigger: true,
53
- size: 1,
54
- });
55
- const linkId = items?.[0]?.connector?.id;
56
- if (!linkId)
57
- return false;
58
- const actions = await getActions({ linkId });
59
- return actions.some((action) => action.hasOwnProperty('node_schema'));
93
+ categories = await (0, knowledge_1.getNodeCategories)({ mode: 'independent', scene: 'normal', trigger: false });
60
94
  }
61
- catch {
62
- return false;
95
+ catch (error) {
96
+ if (error instanceof Error && error.code === 'NOT_FOUND') {
97
+ return false;
98
+ }
99
+ throw error;
63
100
  }
101
+ return Array.isArray(categories);
64
102
  }
65
103
  async function getNodeDetail(nodeId) {
66
104
  return (0, client_1.apiGet)(`/acm/flows/nodes/${nodeId}`);
@@ -37,6 +37,8 @@ exports.registerFlowEdge = registerFlowEdge;
37
37
  const fs = __importStar(require("fs"));
38
38
  const flow_1 = require("@/api/flow");
39
39
  const helpers_1 = require("@/commands/flow/helpers");
40
+ const portValidator_1 = require("@/services/portValidator");
41
+ const classicNodePorts_1 = require("@/services/classicNodePorts");
40
42
  const output_1 = require("@/utils/output");
41
43
  function registerFlowEdge(flowCmd) {
42
44
  const edgeCmd = flowCmd.command('edge').description('边(连接)操作');
@@ -49,6 +51,7 @@ function registerFlowEdge(flowCmd) {
49
51
  .option('--source-handle <handle>', '源节点输出句柄', 'output')
50
52
  .option('--target-handle <handle>', '目标节点输入句柄', 'input')
51
53
  .option('--edges-file <path>', '批量连边 JSON 文件路径')
54
+ .option('--force', '跳过 node_schema 端口校验,强制连接')
52
55
  .option('--json', '以 JSON 格式输出')
53
56
  .action(async (options) => {
54
57
  try {
@@ -70,12 +73,14 @@ function registerFlowEdge(flowCmd) {
70
73
  const flow = await (0, flow_1.getFlowDetail)(options.flowId);
71
74
  if ((0, helpers_1.checkFlowNotOnline)(flow, !!options.json))
72
75
  return;
76
+ // 存量边读取时做废弃 handle 名迁移(对齐 RCS useDataLoading REPLACE_HANDLE_MAP,
77
+ // 迁移后的新名随本次保存写回后端)
73
78
  const existingEdges = (flow.edges || []).map((e) => ({
74
79
  id: e.id,
75
80
  source: e.source,
76
81
  target: e.target,
77
- sourceHandle: e.sourceHandle || 'output',
78
- targetHandle: e.targetHandle || 'input',
82
+ sourceHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(e.sourceHandle) || 'output',
83
+ targetHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(e.targetHandle) || 'input',
79
84
  type: e.type || 'main',
80
85
  }));
81
86
  let newEdges = [];
@@ -115,34 +120,87 @@ function registerFlowEdge(flowCmd) {
115
120
  }
116
121
  return;
117
122
  }
118
- newEdges = edgesData.map((e, i) => {
123
+ newEdges = await Promise.all(edgesData.map(async (e, i) => {
119
124
  const edge = e;
120
125
  if (!edge.source || !edge.target) {
121
126
  throw new Error(`第 ${i + 1} 条边缺少 source 或 target`);
122
127
  }
128
+ const sourceHandle = String(edge.sourceHandle || 'output');
129
+ const targetHandle = String(edge.targetHandle || 'input');
123
130
  return {
124
131
  id: `${String(edge.source)}-${String(edge.target)}-${Date.now() + i}`,
125
132
  source: String(edge.source),
126
133
  target: String(edge.target),
127
- sourceHandle: String(edge.sourceHandle || 'output'),
128
- targetHandle: String(edge.targetHandle || 'input'),
129
- type: String(edge.type || 'main'),
134
+ sourceHandle,
135
+ targetHandle,
136
+ // 显式 type 优先(兼容手工构造的批量 JSON),否则按 NP schema 解析
137
+ type: edge.type !== undefined && edge.type !== null && String(edge.type).trim() !== ''
138
+ ? String(edge.type)
139
+ : await (0, portValidator_1.resolveEdgeSaveType)(flow.nodes, {
140
+ source: String(edge.source),
141
+ target: String(edge.target),
142
+ sourceHandle,
143
+ targetHandle,
144
+ }),
130
145
  };
131
- });
146
+ }));
132
147
  }
133
148
  else {
149
+ // 连线保存 type 对齐 RCS EdgeFactory:NodePackage handle 项的 type,否则 main
150
+ const sourceHandle = options.sourceHandle || 'output';
151
+ const targetHandle = options.targetHandle || 'input';
134
152
  newEdges = [
135
153
  {
136
154
  id: `${options.source}-${options.target}-${Date.now()}`,
137
155
  source: options.source,
138
156
  target: options.target,
139
- sourceHandle: options.sourceHandle || 'output',
140
- targetHandle: options.targetHandle || 'input',
141
- type: 'main',
157
+ sourceHandle,
158
+ targetHandle,
159
+ type: await (0, portValidator_1.resolveEdgeSaveType)(flow.nodes, {
160
+ source: options.source,
161
+ target: options.target,
162
+ sourceHandle,
163
+ targetHandle,
164
+ }),
142
165
  },
143
166
  ];
144
167
  }
145
168
  const allEdges = [...existingEdges, ...newEdges];
169
+ // node_schema 端口级预检(legacy 节点自动跳过;--force 可跳过)
170
+ if (!options.force) {
171
+ const violations = [];
172
+ for (const edge of newEdges) {
173
+ violations.push(...(await (0, portValidator_1.validateSingleEdge)({
174
+ nodes: flow.nodes,
175
+ existingEdges,
176
+ newEdge: {
177
+ source: edge.source,
178
+ target: edge.target,
179
+ sourceHandle: edge.sourceHandle,
180
+ targetHandle: edge.targetHandle,
181
+ },
182
+ })));
183
+ }
184
+ if (violations.length > 0) {
185
+ if (options.json) {
186
+ (0, output_1.printStructuredResult)({
187
+ ok: false,
188
+ message: `端口校验未通过: ${violations.length} 个违规(可使用 --force 跳过)`,
189
+ portViolations: violations,
190
+ });
191
+ }
192
+ else {
193
+ (0, output_1.printError)(`端口校验未通过: ${violations.length} 个违规(可使用 --force 跳过):`);
194
+ for (const v of violations) {
195
+ (0, output_1.printError)(` [${v.code}] ${v.edgeId}: ${v.message}`);
196
+ }
197
+ }
198
+ return;
199
+ }
200
+ }
201
+ else {
202
+ (0, output_1.printWarning)('已使用 --force 跳过端口校验');
203
+ }
146
204
  const connections = (0, helpers_1.edgesToConnections)(allEdges);
147
205
  (0, output_1.printInfo)(`正在更新连接流(新增 ${newEdges.length} 条边)...`);
148
206
  await (0, flow_1.updateFlow)(options.flowId, {
@@ -188,14 +246,15 @@ function registerFlowEdge(flowCmd) {
188
246
  const flow = await (0, flow_1.getFlowDetail)(options.flowId);
189
247
  if ((0, helpers_1.checkFlowNotOnline)(flow, !!options.json))
190
248
  return;
249
+ // 同 connect:存量边做废弃 handle 名迁移后写回
191
250
  const remainingEdges = (flow.edges || [])
192
251
  .filter((e) => !edgeIds.includes(e.id))
193
252
  .map((e) => ({
194
253
  id: e.id,
195
254
  source: e.source,
196
255
  target: e.target,
197
- sourceHandle: e.sourceHandle || 'output',
198
- targetHandle: e.targetHandle || 'input',
256
+ sourceHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(e.sourceHandle) || 'output',
257
+ targetHandle: (0, classicNodePorts_1.normalizeLegacyHandleName)(e.targetHandle) || 'input',
199
258
  type: e.type || 'main',
200
259
  }));
201
260
  const connections = (0, helpers_1.edgesToConnections)(remainingEdges);
@@ -26,11 +26,12 @@ function registerFlowList(flowCmd) {
26
26
  return;
27
27
  }
28
28
  (0, output_1.printHeader)(`连接流列表 (共 ${data.total} 条)`);
29
- const headers = ['ID', '名称', '状态', '创建时间', '更新时间'];
29
+ const headers = ['ID', '名称', '状态', '生效版本', '创建时间', '更新时间'];
30
30
  const rows = data.items.map((item) => [
31
31
  item.id,
32
32
  item.name,
33
33
  (0, helpers_1.formatOpStatus)(item.op_status),
34
+ item.effective_version_no != null ? String(item.effective_version_no) : '-',
34
35
  (0, helpers_1.formatDate)(item.create_time),
35
36
  (0, helpers_1.formatDate)(item.update_time),
36
37
  ]);