flowmind 1.5.2 → 1.5.4
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/CHANGELOG.md +14 -0
- package/bin/flowmind.js +85 -8
- package/core/adapters/mcp-adapter.js +26 -0
- package/core/adapters/workflow-adapter.js +26 -0
- package/core/ai/providers/mimo.js +1 -1
- package/core/component-registry.js +19 -1
- package/core/config-manager.js +7 -2
- package/core/index.js +153 -2
- package/core/mcp-http-client.js +63 -0
- package/core/providers/aliyun/dms-adapter.js +7 -35
- package/core/providers/aliyun/rds-query-adapter.js +70 -0
- package/core/providers/aliyun/redis-adapter.js +4 -20
- package/core/providers/aliyun/sls-adapter.js +3 -10
- package/core/providers/friday/flow-adapter.js +19 -30
- package/core/providers/friday/report-adapter.js +5 -25
- package/core/providers/yapi/yapi-adapter.js +6 -30
- package/core/providers/yuque/yuque-adapter.js +7 -35
- package/core/sdd-agent-sync.js +240 -16
- package/core/source-inference.js +324 -0
- package/package.json +1 -1
- package/skills/auto-flow/index.js +903 -74
- package/skills/data-logic-validation/index.js +133 -12
- package/skills/log-audit/index.js +94 -1
- package/skills/resource-bind/index.js +61 -18
- package/skills/yapi-sync-interface/index.js +146 -13
- package/skills/yuque-sync-design/index.js +130 -11
- package/tui/app.jsx +15 -5
- package/tui/components/ChatPanel.jsx +1 -1
- package/tui/format-result.js +43 -4
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* Route deployment and workflow requests to the configured workflow MCP adapter.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { callMcpTool } = require('../../core/mcp-http-client');
|
|
10
|
+
|
|
6
11
|
const BUILTIN_WORKFLOWS = {
|
|
7
12
|
'dev-workflow': {
|
|
8
13
|
name: 'Development Workflow',
|
|
@@ -39,6 +44,21 @@ const TOOL_NAMES = {
|
|
|
39
44
|
listPipelineRuns: 'flowListPipelineRuns'
|
|
40
45
|
};
|
|
41
46
|
|
|
47
|
+
const LOCAL_PIPELINE_MAP_CANDIDATES = [
|
|
48
|
+
process.env.FLOWMIND_AUTO_FLOW_MAP,
|
|
49
|
+
path.join(process.env.FLOWMIND_HOME || process.env.HOME || process.env.USERPROFILE || os.homedir(), '.flowmind', 'source', 'auto-flow-pipeline-map.json'),
|
|
50
|
+
path.join(process.cwd(), 'source', 'auto-flow-pipeline-map.json'),
|
|
51
|
+
path.join(__dirname, '..', '..', '..', 'source', 'auto-flow-pipeline-map.json')
|
|
52
|
+
].filter(Boolean);
|
|
53
|
+
|
|
54
|
+
const ENV_PREFIX_TO_ENV = {
|
|
55
|
+
test: 'test',
|
|
56
|
+
dev: 'test',
|
|
57
|
+
uat: 'uat',
|
|
58
|
+
gray: 'gray',
|
|
59
|
+
prod: 'prod'
|
|
60
|
+
};
|
|
61
|
+
|
|
42
62
|
module.exports = {
|
|
43
63
|
canHandle(input) {
|
|
44
64
|
if (!input) return false;
|
|
@@ -53,19 +73,14 @@ module.exports = {
|
|
|
53
73
|
const workflow = createWorkflowClient(context);
|
|
54
74
|
|
|
55
75
|
if (params.action === 'define') {
|
|
76
|
+
const definition = buildWorkflowDefinitionSummary();
|
|
56
77
|
return {
|
|
57
78
|
type: 'result',
|
|
58
79
|
skill: 'auto-flow',
|
|
59
80
|
message: 'Define a workflow in YAML format',
|
|
60
81
|
data: {
|
|
61
82
|
format: 'YAML workflow definition',
|
|
62
|
-
|
|
63
|
-
name: 'My Workflow',
|
|
64
|
-
steps: [
|
|
65
|
-
{ id: 'step1', action: 'run-command', command: 'echo hello' },
|
|
66
|
-
{ id: 'step2', skill: 'code-review', depends_on: ['step1'] }
|
|
67
|
-
]
|
|
68
|
-
}
|
|
83
|
+
execution: definition
|
|
69
84
|
},
|
|
70
85
|
input,
|
|
71
86
|
timestamp: new Date().toISOString()
|
|
@@ -77,11 +92,26 @@ module.exports = {
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
if (params.action === 'status') {
|
|
80
|
-
|
|
95
|
+
const execution = await executeStatus(workflow, params, input);
|
|
96
|
+
return {
|
|
97
|
+
type: 'result',
|
|
98
|
+
skill: 'auto-flow',
|
|
99
|
+
message: 'Workflow status: query completed',
|
|
100
|
+
data: {
|
|
101
|
+
action: 'status',
|
|
102
|
+
provider: workflow.provider,
|
|
103
|
+
binding: workflow.binding,
|
|
104
|
+
status: 'completed',
|
|
105
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
106
|
+
execution: summarizeWorkflowExecution('status', params, execution)
|
|
107
|
+
},
|
|
108
|
+
input,
|
|
109
|
+
timestamp: new Date().toISOString()
|
|
110
|
+
};
|
|
81
111
|
}
|
|
82
112
|
|
|
83
113
|
if (params.action === 'deploy' || params.action === 'run') {
|
|
84
|
-
return executeDeploy(workflow, params, input);
|
|
114
|
+
return executeDeploy(workflow, params, input, context);
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
if (params.action === 'list' || params.serviceNames.length > 0) {
|
|
@@ -89,15 +119,14 @@ module.exports = {
|
|
|
89
119
|
return {
|
|
90
120
|
type: 'result',
|
|
91
121
|
skill: 'auto-flow',
|
|
92
|
-
message:
|
|
93
|
-
? `Resolved workflow query for: ${params.serviceNames.join(', ')}`
|
|
94
|
-
: 'Listing available deployment pipelines',
|
|
122
|
+
message: 'Workflow status: pipeline lookup completed',
|
|
95
123
|
data: {
|
|
96
124
|
action: 'list',
|
|
97
125
|
provider: workflow.provider,
|
|
98
126
|
binding: workflow.binding,
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
status: 'completed',
|
|
128
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
129
|
+
execution: summarizeWorkflowExecution('list', params, execution)
|
|
101
130
|
},
|
|
102
131
|
input,
|
|
103
132
|
timestamp: new Date().toISOString()
|
|
@@ -112,7 +141,7 @@ module.exports = {
|
|
|
112
141
|
message: `Workflow ready: ${workflowDef.name}`,
|
|
113
142
|
data: {
|
|
114
143
|
workflow: params.workflow,
|
|
115
|
-
|
|
144
|
+
execution: buildBuiltinWorkflowSummary(params.workflow, workflowDef),
|
|
116
145
|
provider: workflow.provider
|
|
117
146
|
},
|
|
118
147
|
input,
|
|
@@ -133,7 +162,8 @@ module.exports = {
|
|
|
133
162
|
],
|
|
134
163
|
builtinWorkflows: Object.keys(BUILTIN_WORKFLOWS),
|
|
135
164
|
provider: workflow.provider,
|
|
136
|
-
binding: workflow.binding
|
|
165
|
+
binding: workflow.binding,
|
|
166
|
+
execution: buildAutoFlowHintSummary(workflow)
|
|
137
167
|
},
|
|
138
168
|
input,
|
|
139
169
|
timestamp: new Date().toISOString()
|
|
@@ -147,7 +177,8 @@ function createWorkflowClient(context) {
|
|
|
147
177
|
return {
|
|
148
178
|
client: adapter,
|
|
149
179
|
provider: adapter.providerName,
|
|
150
|
-
binding: context.resourceBinding?.componentType === 'workflow' ? context.resourceBinding : null
|
|
180
|
+
binding: context.resourceBinding?.componentType === 'workflow' ? context.resourceBinding : null,
|
|
181
|
+
transportBacked: true
|
|
151
182
|
};
|
|
152
183
|
}
|
|
153
184
|
|
|
@@ -156,33 +187,394 @@ function createWorkflowClient(context) {
|
|
|
156
187
|
: null;
|
|
157
188
|
|
|
158
189
|
if (!binding?.mcpServer) {
|
|
159
|
-
return { client: null, provider: null, binding: null };
|
|
190
|
+
return { client: null, provider: null, binding: null, transportBacked: false };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const transport = buildWorkflowTransport(binding);
|
|
194
|
+
if (!transport.url) {
|
|
195
|
+
return { client: null, provider: binding.provider || null, binding, transportBacked: false };
|
|
160
196
|
}
|
|
161
197
|
|
|
162
198
|
return {
|
|
163
199
|
client: {
|
|
164
200
|
providerName: binding.provider || 'workflow-binding',
|
|
165
201
|
async listPipelines(params) {
|
|
166
|
-
return
|
|
202
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.listPipelines, params);
|
|
203
|
+
},
|
|
204
|
+
async getCurrentIterate() {
|
|
205
|
+
return callWorkflowMcp(binding, transport, 'getCurrentIterate', {});
|
|
206
|
+
},
|
|
207
|
+
async listDeployChecklists(params) {
|
|
208
|
+
return callWorkflowMcp(binding, transport, 'listDeployChecklists', params);
|
|
209
|
+
},
|
|
210
|
+
async orderList(params) {
|
|
211
|
+
return callWorkflowMcp(binding, transport, 'orderList', params);
|
|
167
212
|
},
|
|
168
213
|
async startPipelineRun(pipelineId) {
|
|
169
|
-
return
|
|
214
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.startPipelineRun, { pipelineId });
|
|
170
215
|
},
|
|
171
216
|
async startBatchPipelineRun(params) {
|
|
172
|
-
return
|
|
217
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.startBatchPipelineRun, params);
|
|
173
218
|
},
|
|
174
219
|
async getPipelineRun(pipelineId, runId) {
|
|
175
|
-
return
|
|
220
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.getPipelineRun, { pipelineId, pipelineRunId: runId });
|
|
176
221
|
},
|
|
177
222
|
async listPipelineRuns(pipelineId, params) {
|
|
178
|
-
return
|
|
223
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.listPipelineRuns, { pipelineId, ...(params || {}) });
|
|
179
224
|
}
|
|
180
225
|
},
|
|
181
226
|
provider: binding.provider || 'workflow-binding',
|
|
182
|
-
binding
|
|
227
|
+
binding,
|
|
228
|
+
transportBacked: true
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildWorkflowTransport(binding) {
|
|
233
|
+
const connection = binding?.connection || {};
|
|
234
|
+
const headers = {};
|
|
235
|
+
|
|
236
|
+
if (connection.token) {
|
|
237
|
+
headers.Authorization = `Bearer ${connection.token}`;
|
|
238
|
+
}
|
|
239
|
+
if (connection.apiKey) {
|
|
240
|
+
headers['X-API-Key'] = connection.apiKey;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
url: connection.url || connection.address || connection.endpoint || null,
|
|
245
|
+
headers,
|
|
246
|
+
timeoutMs: connection.timeoutMs || 60000
|
|
183
247
|
};
|
|
184
248
|
}
|
|
185
249
|
|
|
250
|
+
function callWorkflowMcp(binding, transport, tool, params) {
|
|
251
|
+
return callMcpTool({
|
|
252
|
+
url: transport.url,
|
|
253
|
+
headers: transport.headers,
|
|
254
|
+
tool,
|
|
255
|
+
args: params || {},
|
|
256
|
+
timeoutMs: transport.timeoutMs || 60000,
|
|
257
|
+
serverName: binding.mcpServer || binding.provider || 'workflow'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function summarizeWorkflowExecution(action, params, execution) {
|
|
262
|
+
const payload = unwrapMcpResult(execution);
|
|
263
|
+
const records = extractRecords(payload);
|
|
264
|
+
const total = extractTotalCount(payload, records);
|
|
265
|
+
|
|
266
|
+
return compactObject({
|
|
267
|
+
action,
|
|
268
|
+
summary: buildWorkflowSummaryText(action, params, total, records),
|
|
269
|
+
total,
|
|
270
|
+
status: extractWorkflowStatus(payload, action),
|
|
271
|
+
target: buildWorkflowTargetLabel(params),
|
|
272
|
+
environment: params.environment || null,
|
|
273
|
+
items: records.slice(0, 5).map((record) => summarizeWorkflowRecord(record))
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function summarizeDeployExecution(params, execution, context = {}) {
|
|
278
|
+
const payload = unwrapMcpResult(execution);
|
|
279
|
+
const payloadList = Array.isArray(payload) ? payload : [payload];
|
|
280
|
+
const primary = payloadList.find((item) => item !== null && item !== undefined) || null;
|
|
281
|
+
const summaryTarget = buildWorkflowTargetLabel(params) || buildDeployTargetLabel(context) || 'deployment';
|
|
282
|
+
|
|
283
|
+
return compactObject({
|
|
284
|
+
action: 'deploy',
|
|
285
|
+
summary: buildDeploySummaryText(summaryTarget, primary, context, payloadList.length),
|
|
286
|
+
status: extractDeployStatus(primary),
|
|
287
|
+
target: summaryTarget,
|
|
288
|
+
pipelineId: context.pipelineId || params.pipelineId || (Array.isArray(context.pipelineIds) && context.pipelineIds.length === 1 ? context.pipelineIds[0] : null),
|
|
289
|
+
pipelineIds: Array.isArray(context.pipelineIds) && context.pipelineIds.length > 0 ? context.pipelineIds : null,
|
|
290
|
+
source: context.source || null,
|
|
291
|
+
runId: extractDeployRunId(primary),
|
|
292
|
+
count: payloadList.length > 1 ? payloadList.length : null,
|
|
293
|
+
details: summarizeDeployDetails(primary)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function buildWorkflowDefinitionSummary() {
|
|
298
|
+
return {
|
|
299
|
+
action: 'define',
|
|
300
|
+
summary: 'Prepared a YAML workflow template',
|
|
301
|
+
format: 'YAML workflow definition',
|
|
302
|
+
example: {
|
|
303
|
+
name: 'My Workflow',
|
|
304
|
+
steps: [
|
|
305
|
+
{ id: 'step1', action: 'run-command', command: 'echo hello' },
|
|
306
|
+
{ id: 'step2', skill: 'code-review', depends_on: ['step1'] }
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function buildBuiltinWorkflowSummary(workflowName, workflowDef) {
|
|
313
|
+
return {
|
|
314
|
+
action: 'workflow',
|
|
315
|
+
summary: `Workflow ready: ${workflowDef.name}`,
|
|
316
|
+
workflow: workflowName,
|
|
317
|
+
name: workflowDef.name,
|
|
318
|
+
stepCount: Array.isArray(workflowDef.steps) ? workflowDef.steps.length : 0,
|
|
319
|
+
steps: (workflowDef.steps || []).map((step) => compactObject({
|
|
320
|
+
id: step.id,
|
|
321
|
+
skill: step.skill || null,
|
|
322
|
+
action: step.action || null,
|
|
323
|
+
name: step.name || null,
|
|
324
|
+
depends_on: Array.isArray(step.depends_on) ? step.depends_on : null
|
|
325
|
+
}))
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function buildAutoFlowHintSummary(workflow) {
|
|
330
|
+
return {
|
|
331
|
+
action: 'hint',
|
|
332
|
+
summary: workflow?.binding
|
|
333
|
+
? `Workflow service bound to ${workflow.binding.mcpServer}`
|
|
334
|
+
: 'Workflow service not configured',
|
|
335
|
+
provider: workflow?.provider || null,
|
|
336
|
+
binding: workflow?.binding || null,
|
|
337
|
+
configured: !!workflow?.client
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function buildWorkflowSummaryText(action, params, total, records) {
|
|
342
|
+
const target = buildWorkflowTargetLabel(params) || 'all workflows';
|
|
343
|
+
const count = typeof total === 'number' ? total : records.length;
|
|
344
|
+
|
|
345
|
+
if (action === 'status') {
|
|
346
|
+
return `Checked ${target}${count !== null ? `, ${count} record(s) found` : ''}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (action === 'list') {
|
|
350
|
+
return `Listed workflows for ${target}${count !== null ? `, ${count} pipeline(s) found` : ''}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return `Workflow ${action} completed`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function buildWorkflowTargetLabel(params) {
|
|
357
|
+
if (params.pipelineId && params.runId) {
|
|
358
|
+
return `pipeline ${params.pipelineId} / run ${params.runId}`;
|
|
359
|
+
}
|
|
360
|
+
if (params.pipelineId) {
|
|
361
|
+
return `pipeline ${params.pipelineId}`;
|
|
362
|
+
}
|
|
363
|
+
if (params.serviceNames?.length > 0) {
|
|
364
|
+
return params.serviceNames.join(', ');
|
|
365
|
+
}
|
|
366
|
+
if (params.workflow) {
|
|
367
|
+
return params.workflow;
|
|
368
|
+
}
|
|
369
|
+
return params.environment || null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function buildDeployTargetLabel(context) {
|
|
373
|
+
if (Array.isArray(context.pipelineIds) && context.pipelineIds.length > 0) {
|
|
374
|
+
return `pipeline ${context.pipelineIds.join(', ')}`;
|
|
375
|
+
}
|
|
376
|
+
if (context.pipelineId) {
|
|
377
|
+
return `pipeline ${context.pipelineId}`;
|
|
378
|
+
}
|
|
379
|
+
if (Array.isArray(context.services) && context.services.length > 0) {
|
|
380
|
+
return context.services.map((item) => item.serviceName || item.service || item.pipelineName || item.pipelineId).filter(Boolean).join(', ');
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function buildDeploySummaryText(target, payload, context, count = 1) {
|
|
386
|
+
const status = extractDeployStatus(payload) || 'submitted';
|
|
387
|
+
const runId = extractDeployRunId(payload);
|
|
388
|
+
const source = context.source ? ` via ${context.source}` : '';
|
|
389
|
+
const countSuffix = count > 1 ? ` (${count} items)` : '';
|
|
390
|
+
|
|
391
|
+
if (runId) {
|
|
392
|
+
return `Deployment ${status} for ${target}${source}, run ${runId}${countSuffix}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return `Deployment ${status} for ${target}${source}${countSuffix}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function extractDeployStatus(payload) {
|
|
399
|
+
if (payload && typeof payload === 'object') {
|
|
400
|
+
const candidates = [
|
|
401
|
+
payload.status,
|
|
402
|
+
payload.state,
|
|
403
|
+
payload.result?.status,
|
|
404
|
+
payload.result?.state
|
|
405
|
+
].filter(Boolean);
|
|
406
|
+
if (candidates.length > 0) {
|
|
407
|
+
return String(candidates[0]);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (payload.submitted === true) {
|
|
411
|
+
return 'submitted';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function extractDeployRunId(payload) {
|
|
419
|
+
if (!payload || typeof payload !== 'object') {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const candidates = [
|
|
424
|
+
payload.pipelineRunId,
|
|
425
|
+
payload.pipeline_run_id,
|
|
426
|
+
payload.runId,
|
|
427
|
+
payload.run_id,
|
|
428
|
+
payload.result?.pipelineRunId,
|
|
429
|
+
payload.result?.pipeline_run_id,
|
|
430
|
+
payload.result?.runId,
|
|
431
|
+
payload.result?.run_id
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
for (const candidate of candidates) {
|
|
435
|
+
if (candidate === null || candidate === undefined || candidate === '') continue;
|
|
436
|
+
return String(candidate);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function summarizeDeployDetails(payload) {
|
|
443
|
+
if (!payload || typeof payload !== 'object') {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const details = compactObject({
|
|
448
|
+
submitted: typeof payload.submitted === 'boolean' ? payload.submitted : null,
|
|
449
|
+
pipelineRunId: payload.pipelineRunId || payload.pipeline_run_id || null,
|
|
450
|
+
pipelineId: payload.pipelineId || payload.pipeline_id || null,
|
|
451
|
+
message: payload.message || payload.result?.message || null
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
return Object.keys(details).length > 0 ? details : null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function unwrapMcpResult(payload) {
|
|
458
|
+
if (!payload || typeof payload !== 'object') {
|
|
459
|
+
return payload;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const textContent = Array.isArray(payload.content) ? payload.content : payload.result?.content;
|
|
463
|
+
if (Array.isArray(textContent)) {
|
|
464
|
+
const textItem = textContent.find((item) => item?.type === 'text' && typeof item.text === 'string');
|
|
465
|
+
if (textItem?.text) {
|
|
466
|
+
try {
|
|
467
|
+
return JSON.parse(textItem.text);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
return payload;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return payload;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function extractRecords(payload) {
|
|
478
|
+
const normalized = unwrapMcpResult(payload);
|
|
479
|
+
if (!normalized) return [];
|
|
480
|
+
if (Array.isArray(normalized)) return normalized;
|
|
481
|
+
|
|
482
|
+
const candidates = [
|
|
483
|
+
normalized.data,
|
|
484
|
+
normalized.rows,
|
|
485
|
+
normalized.list,
|
|
486
|
+
normalized.records,
|
|
487
|
+
normalized.result?.data,
|
|
488
|
+
normalized.result?.rows,
|
|
489
|
+
normalized.result?.list,
|
|
490
|
+
normalized.result?.records
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
for (const candidate of candidates) {
|
|
494
|
+
if (Array.isArray(candidate)) {
|
|
495
|
+
return candidate;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function extractTotalCount(payload, records) {
|
|
503
|
+
if (payload && typeof payload === 'object') {
|
|
504
|
+
const candidates = [payload.total, payload.count, payload.result?.total, payload.result?.count];
|
|
505
|
+
for (const candidate of candidates) {
|
|
506
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
507
|
+
return candidate;
|
|
508
|
+
}
|
|
509
|
+
if (typeof candidate === 'string' && candidate.trim() !== '' && !Number.isNaN(Number(candidate))) {
|
|
510
|
+
return Number(candidate);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return records.length;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function extractWorkflowStatus(payload, action) {
|
|
519
|
+
if (payload && typeof payload === 'object') {
|
|
520
|
+
const candidates = [
|
|
521
|
+
payload.status,
|
|
522
|
+
payload.state,
|
|
523
|
+
payload.result?.status,
|
|
524
|
+
payload.result?.state
|
|
525
|
+
].filter(Boolean);
|
|
526
|
+
if (candidates.length > 0) {
|
|
527
|
+
return String(candidates[0]);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return action === 'status' ? 'completed' : 'completed';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function summarizeWorkflowRecord(record) {
|
|
535
|
+
const fields = parseWorkflowFields(record?.fields);
|
|
536
|
+
return compactObject({
|
|
537
|
+
pipelineName: record?.pipelineName || record?.name || fields?.pipelineName || fields?.name || null,
|
|
538
|
+
pipelineId: record?.pipelineId || fields?.pipelineId || extractIdFromFields(fields) || null,
|
|
539
|
+
service: extractWorkflowService(fields),
|
|
540
|
+
status: record?.status || fields?.status || fields?.state || null,
|
|
541
|
+
env: record?.env || fields?.env || fields?.environment || null
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function parseWorkflowFields(fields) {
|
|
546
|
+
if (!fields) return null;
|
|
547
|
+
if (typeof fields === 'object') return fields;
|
|
548
|
+
if (typeof fields !== 'string') return null;
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
return JSON.parse(fields);
|
|
552
|
+
} catch (error) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function extractWorkflowService(fields) {
|
|
558
|
+
if (!fields) return null;
|
|
559
|
+
if (Array.isArray(fields.service) && fields.service.length > 0) {
|
|
560
|
+
return fields.service[0];
|
|
561
|
+
}
|
|
562
|
+
if (typeof fields.service === 'string') {
|
|
563
|
+
return fields.service;
|
|
564
|
+
}
|
|
565
|
+
return fields.serviceName || fields.service_name || null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function extractIdFromFields(fields) {
|
|
569
|
+
if (!fields || typeof fields !== 'object') return null;
|
|
570
|
+
const candidates = [fields.pipelineId, fields.pipeline_id, fields.id];
|
|
571
|
+
for (const candidate of candidates) {
|
|
572
|
+
if (candidate === null || candidate === undefined || candidate === '') continue;
|
|
573
|
+
return String(candidate);
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
|
|
186
578
|
function buildNoAdapterResult(input, params) {
|
|
187
579
|
if (params.workflow && BUILTIN_WORKFLOWS[params.workflow]) {
|
|
188
580
|
const workflow = BUILTIN_WORKFLOWS[params.workflow];
|
|
@@ -192,7 +584,7 @@ function buildNoAdapterResult(input, params) {
|
|
|
192
584
|
message: `Workflow ready: ${workflow.name}`,
|
|
193
585
|
data: {
|
|
194
586
|
workflow: params.workflow,
|
|
195
|
-
|
|
587
|
+
execution: buildBuiltinWorkflowSummary(params.workflow, workflow)
|
|
196
588
|
},
|
|
197
589
|
input,
|
|
198
590
|
timestamp: new Date().toISOString()
|
|
@@ -204,28 +596,73 @@ function buildNoAdapterResult(input, params) {
|
|
|
204
596
|
skill: 'auto-flow',
|
|
205
597
|
message: 'Workflow service not configured. Connect friday-auto-flow first.',
|
|
206
598
|
data: {
|
|
207
|
-
|
|
208
|
-
hint: 'Run `flowmind resource` to review current bindings, then save one like: `flowmind "绑定发布业务 mcp=friday-auto-flow token=xxx env=uat"`'
|
|
599
|
+
status: 'not_configured',
|
|
600
|
+
hint: 'Run `flowmind resource` to review current bindings, then save one like: `flowmind "绑定发布业务 mcp=friday-auto-flow token=xxx env=uat"`',
|
|
601
|
+
execution: buildAutoFlowHintSummary({
|
|
602
|
+
client: null,
|
|
603
|
+
provider: null,
|
|
604
|
+
binding: null
|
|
605
|
+
})
|
|
209
606
|
},
|
|
210
607
|
input,
|
|
211
608
|
timestamp: new Date().toISOString()
|
|
212
609
|
};
|
|
213
610
|
}
|
|
214
611
|
|
|
215
|
-
async function executeDeploy(workflow, params, input) {
|
|
612
|
+
async function executeDeploy(workflow, params, input, context) {
|
|
216
613
|
if (params.pipelineId) {
|
|
217
614
|
const execution = await workflow.client.startPipelineRun(params.pipelineId);
|
|
218
615
|
return {
|
|
219
616
|
type: 'result',
|
|
220
617
|
skill: 'auto-flow',
|
|
221
|
-
message:
|
|
618
|
+
message: 'Workflow status: deployment submitted',
|
|
222
619
|
data: {
|
|
223
620
|
action: 'deploy',
|
|
224
|
-
pipelineId: params.pipelineId,
|
|
225
|
-
environment: params.environment,
|
|
226
621
|
provider: workflow.provider,
|
|
227
622
|
binding: workflow.binding,
|
|
228
|
-
|
|
623
|
+
status: 'submitted',
|
|
624
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
625
|
+
execution: summarizeDeployExecution(params, execution, {
|
|
626
|
+
source: 'direct-input',
|
|
627
|
+
pipelineId: params.pipelineId
|
|
628
|
+
}),
|
|
629
|
+
resolution: {
|
|
630
|
+
source: 'direct-input',
|
|
631
|
+
pipelineId: params.pipelineId
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
input,
|
|
635
|
+
timestamp: new Date().toISOString()
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const resolvedTargets = await resolvePipelineTargets(workflow, params, context);
|
|
640
|
+
if (resolvedTargets.pipelineIds.length > 0) {
|
|
641
|
+
const executions = [];
|
|
642
|
+
for (const pipelineId of resolvedTargets.pipelineIds) {
|
|
643
|
+
executions.push(await workflow.client.startPipelineRun(pipelineId));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
type: 'result',
|
|
648
|
+
skill: 'auto-flow',
|
|
649
|
+
message: 'Workflow status: deployment submitted',
|
|
650
|
+
data: {
|
|
651
|
+
action: 'deploy',
|
|
652
|
+
provider: workflow.provider,
|
|
653
|
+
binding: workflow.binding,
|
|
654
|
+
status: 'submitted',
|
|
655
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
656
|
+
execution: summarizeDeployExecution(params, executions.length === 1 ? executions[0] : executions, {
|
|
657
|
+
source: resolvedTargets.source,
|
|
658
|
+
pipelineIds: resolvedTargets.pipelineIds,
|
|
659
|
+
services: resolvedTargets.resolutions
|
|
660
|
+
}),
|
|
661
|
+
resolution: {
|
|
662
|
+
source: resolvedTargets.source,
|
|
663
|
+
pipelineIds: resolvedTargets.pipelineIds,
|
|
664
|
+
services: resolvedTargets.resolutions
|
|
665
|
+
}
|
|
229
666
|
},
|
|
230
667
|
input,
|
|
231
668
|
timestamp: new Date().toISOString()
|
|
@@ -233,33 +670,33 @@ async function executeDeploy(workflow, params, input) {
|
|
|
233
670
|
}
|
|
234
671
|
|
|
235
672
|
const listParams = buildListPipelineParams(params);
|
|
236
|
-
const
|
|
673
|
+
const lookup = await workflow.client.listPipelines(listParams);
|
|
237
674
|
const batchParams = buildBatchRunParams(params);
|
|
238
675
|
const execution = await workflow.client.startBatchPipelineRun(batchParams);
|
|
239
676
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
677
|
+
return {
|
|
678
|
+
type: 'result',
|
|
679
|
+
skill: 'auto-flow',
|
|
680
|
+
message: 'Workflow status: deployment submitted',
|
|
681
|
+
data: {
|
|
682
|
+
action: 'deploy',
|
|
683
|
+
provider: workflow.provider,
|
|
684
|
+
binding: workflow.binding,
|
|
685
|
+
status: 'submitted',
|
|
686
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
687
|
+
execution: summarizeDeployExecution(params, execution, {
|
|
688
|
+
source: 'batch-fallback',
|
|
689
|
+
lookup
|
|
690
|
+
}),
|
|
691
|
+
resolution: {
|
|
692
|
+
source: 'batch-fallback',
|
|
693
|
+
lookup
|
|
694
|
+
}
|
|
256
695
|
},
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
};
|
|
262
|
-
}
|
|
696
|
+
input,
|
|
697
|
+
timestamp: new Date().toISOString()
|
|
698
|
+
};
|
|
699
|
+
}
|
|
263
700
|
|
|
264
701
|
async function executeStatus(workflow, params, input) {
|
|
265
702
|
let execution;
|
|
@@ -273,25 +710,7 @@ async function executeStatus(workflow, params, input) {
|
|
|
273
710
|
execution = await workflow.client.listPipelines(buildListPipelineParams(params));
|
|
274
711
|
}
|
|
275
712
|
|
|
276
|
-
return
|
|
277
|
-
type: 'result',
|
|
278
|
-
skill: 'auto-flow',
|
|
279
|
-
message: params.runId
|
|
280
|
-
? `Querying run status for ${params.runId}`
|
|
281
|
-
: 'Querying pipeline status',
|
|
282
|
-
data: {
|
|
283
|
-
action: 'status',
|
|
284
|
-
services: params.serviceNames,
|
|
285
|
-
pipelineId: params.pipelineId,
|
|
286
|
-
runId: params.runId,
|
|
287
|
-
environment: params.environment,
|
|
288
|
-
provider: workflow.provider,
|
|
289
|
-
binding: workflow.binding,
|
|
290
|
-
execution
|
|
291
|
-
},
|
|
292
|
-
input,
|
|
293
|
-
timestamp: new Date().toISOString()
|
|
294
|
-
};
|
|
713
|
+
return execution;
|
|
295
714
|
}
|
|
296
715
|
|
|
297
716
|
function parseFlowParams(input) {
|
|
@@ -337,6 +756,7 @@ function parseFlowParams(input) {
|
|
|
337
756
|
}
|
|
338
757
|
|
|
339
758
|
params.serviceNames = extractServiceNames(input);
|
|
759
|
+
applyPrefixedServiceNormalization(params);
|
|
340
760
|
|
|
341
761
|
if (!params.workflow && /deploy-workflow|dev-workflow/i.test(input)) {
|
|
342
762
|
params.workflow = input.match(/deploy-workflow|dev-workflow/i)[0];
|
|
@@ -394,6 +814,415 @@ function normalizeEnvironment(value) {
|
|
|
394
814
|
return normalized;
|
|
395
815
|
}
|
|
396
816
|
|
|
817
|
+
function applyPrefixedServiceNormalization(params) {
|
|
818
|
+
const normalizedServices = [];
|
|
819
|
+
|
|
820
|
+
for (const originalName of params.serviceNames) {
|
|
821
|
+
const prefixed = splitPrefixedServiceName(originalName);
|
|
822
|
+
if (prefixed) {
|
|
823
|
+
if (!params.environment) {
|
|
824
|
+
params.environment = prefixed.environment;
|
|
825
|
+
}
|
|
826
|
+
normalizedServices.push(prefixed.serviceName);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
normalizedServices.push(originalName);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
params.serviceNames = [...new Set(normalizedServices)];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function splitPrefixedServiceName(serviceName) {
|
|
836
|
+
const match = String(serviceName || '').match(/^(test|dev|uat|gray|prod)-(.+)$/i);
|
|
837
|
+
if (!match || !match[2] || !match[2].includes('-')) {
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return {
|
|
842
|
+
environment: ENV_PREFIX_TO_ENV[match[1].toLowerCase()] || normalizeEnvironment(match[1]),
|
|
843
|
+
serviceName: match[2]
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async function resolvePipelineTargets(workflow, params, context) {
|
|
848
|
+
if (params.serviceNames.length === 0) {
|
|
849
|
+
return { source: null, resolutions: [], pipelineIds: [] };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const resolutions = [];
|
|
853
|
+
let source = null;
|
|
854
|
+
|
|
855
|
+
for (const serviceName of params.serviceNames) {
|
|
856
|
+
const resolution = await resolvePipelineForService(workflow, {
|
|
857
|
+
serviceName,
|
|
858
|
+
environment: params.environment,
|
|
859
|
+
context
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
if (!resolution?.pipelineId) {
|
|
863
|
+
return { source: null, resolutions: [], pipelineIds: [] };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
source = source || resolution.source;
|
|
867
|
+
resolutions.push(resolution);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return {
|
|
871
|
+
source,
|
|
872
|
+
resolutions,
|
|
873
|
+
pipelineIds: [...new Set(resolutions.map((item) => String(item.pipelineId)))]
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function resolvePipelineForService(workflow, { serviceName, environment, context }) {
|
|
878
|
+
const localResolution = resolveLocalPipelineForService(serviceName, environment, context);
|
|
879
|
+
if (localResolution) {
|
|
880
|
+
return localResolution;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (!workflow.transportBacked || typeof workflow.client?.getCurrentIterate !== 'function') {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return resolveRemotePipelineForService(workflow.client, serviceName, environment);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function resolveLocalPipelineForService(serviceName, environment, context) {
|
|
891
|
+
const pipelineMap = readLocalPipelineMap(context);
|
|
892
|
+
if (!pipelineMap) return null;
|
|
893
|
+
|
|
894
|
+
const entry = pipelineMap[serviceName];
|
|
895
|
+
if (!entry) return null;
|
|
896
|
+
|
|
897
|
+
const pipeline = selectPipelineFromEntry(entry, environment);
|
|
898
|
+
if (!pipeline?.pipelineId) return null;
|
|
899
|
+
|
|
900
|
+
return {
|
|
901
|
+
source: 'local-map',
|
|
902
|
+
serviceName,
|
|
903
|
+
environment: environment || inferEnvironmentFromPipelineName(pipeline.pipelineName),
|
|
904
|
+
pipelineId: String(pipeline.pipelineId),
|
|
905
|
+
pipelineName: pipeline.pipelineName || null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function readLocalPipelineMap(context) {
|
|
910
|
+
if (context?.pipelineMap && typeof context.pipelineMap === 'object') {
|
|
911
|
+
return context.pipelineMap;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
for (const candidate of LOCAL_PIPELINE_MAP_CANDIDATES) {
|
|
915
|
+
if (!candidate || !fs.existsSync(candidate)) continue;
|
|
916
|
+
try {
|
|
917
|
+
return JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
918
|
+
} catch (error) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function selectPipelineFromEntry(entry, environment) {
|
|
927
|
+
const preferredKeys = getEnvLookupOrder(environment);
|
|
928
|
+
for (const key of preferredKeys) {
|
|
929
|
+
const candidate = normalizePipelineCollection(entry?.[key]);
|
|
930
|
+
if (candidate.length > 0) {
|
|
931
|
+
return candidate[0];
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
for (const value of Object.values(entry || {})) {
|
|
936
|
+
const candidate = normalizePipelineCollection(value);
|
|
937
|
+
if (candidate.length > 0) {
|
|
938
|
+
return candidate[0];
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function normalizePipelineCollection(value) {
|
|
946
|
+
if (!value) return [];
|
|
947
|
+
const items = Array.isArray(value) ? value : [value];
|
|
948
|
+
return items
|
|
949
|
+
.map((item) => {
|
|
950
|
+
if (!item) return null;
|
|
951
|
+
if (typeof item === 'string') {
|
|
952
|
+
return parsePipelineDescriptor(item);
|
|
953
|
+
}
|
|
954
|
+
if (typeof item === 'object' && item.pipelineId) {
|
|
955
|
+
return {
|
|
956
|
+
pipelineName: item.pipelineName || item.name || null,
|
|
957
|
+
pipelineId: String(item.pipelineId)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
})
|
|
962
|
+
.filter(Boolean);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async function resolveRemotePipelineForService(client, serviceName, environment) {
|
|
966
|
+
const iterateName = await lookupCurrentIterateName(client);
|
|
967
|
+
const searchers = [
|
|
968
|
+
async () => parseRemotePipelineLookup(
|
|
969
|
+
await client.listDeployChecklists({ pageNum: '1', pageSize: '200', iterate: iterateName }),
|
|
970
|
+
serviceName,
|
|
971
|
+
environment,
|
|
972
|
+
'deploy-checklists'
|
|
973
|
+
),
|
|
974
|
+
async () => parseRemotePipelineLookup(
|
|
975
|
+
await client.orderList({ pageNum: '1', pageSize: '200', iterateName }),
|
|
976
|
+
serviceName,
|
|
977
|
+
environment,
|
|
978
|
+
'order-list'
|
|
979
|
+
)
|
|
980
|
+
];
|
|
981
|
+
|
|
982
|
+
for (const search of searchers) {
|
|
983
|
+
const resolution = await search();
|
|
984
|
+
if (resolution?.pipelineId) {
|
|
985
|
+
return resolution;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
async function lookupCurrentIterateName(client) {
|
|
993
|
+
const payload = unwrapMcpPayload(await client.getCurrentIterate());
|
|
994
|
+
if (payload?.name && typeof payload.name === 'string') {
|
|
995
|
+
return payload.name;
|
|
996
|
+
}
|
|
997
|
+
const values = collectNestedValues(payload);
|
|
998
|
+
const iterateName = values.find((value) => typeof value === 'string' && /\d+\.\d+/.test(value));
|
|
999
|
+
return iterateName || null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function parseRemotePipelineLookup(payload, serviceName, environment, source) {
|
|
1003
|
+
const records = extractRecordArray(payload);
|
|
1004
|
+
for (const record of records) {
|
|
1005
|
+
const fields = parseFields(record.fields);
|
|
1006
|
+
if (!fields || !matchesRemoteService(fields, serviceName, environment)) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const pipeline = selectPipelineFromFields(fields, environment, serviceName);
|
|
1011
|
+
if (pipeline?.pipelineId) {
|
|
1012
|
+
return {
|
|
1013
|
+
source,
|
|
1014
|
+
serviceName,
|
|
1015
|
+
environment: environment || inferEnvironmentFromPipelineName(pipeline.pipelineName),
|
|
1016
|
+
pipelineId: String(pipeline.pipelineId),
|
|
1017
|
+
pipelineName: pipeline.pipelineName || null
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function extractRecordArray(payload) {
|
|
1026
|
+
const normalized = unwrapMcpPayload(payload);
|
|
1027
|
+
if (!normalized) return [];
|
|
1028
|
+
if (Array.isArray(normalized)) return normalized;
|
|
1029
|
+
|
|
1030
|
+
const candidates = [
|
|
1031
|
+
normalized.data,
|
|
1032
|
+
normalized.rows,
|
|
1033
|
+
normalized.list,
|
|
1034
|
+
normalized.records,
|
|
1035
|
+
normalized.result?.data,
|
|
1036
|
+
normalized.result?.rows,
|
|
1037
|
+
normalized.result?.list,
|
|
1038
|
+
normalized.result?.records
|
|
1039
|
+
];
|
|
1040
|
+
|
|
1041
|
+
for (const candidate of candidates) {
|
|
1042
|
+
if (Array.isArray(candidate)) {
|
|
1043
|
+
return candidate;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return [];
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function parseFields(fields) {
|
|
1051
|
+
if (!fields) return null;
|
|
1052
|
+
if (typeof fields === 'object') return fields;
|
|
1053
|
+
if (typeof fields !== 'string') return null;
|
|
1054
|
+
|
|
1055
|
+
try {
|
|
1056
|
+
return JSON.parse(fields);
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function unwrapMcpPayload(payload) {
|
|
1063
|
+
if (!payload || typeof payload !== 'object') {
|
|
1064
|
+
return payload;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const textContent = Array.isArray(payload.content) ? payload.content : payload.result?.content;
|
|
1068
|
+
if (Array.isArray(textContent)) {
|
|
1069
|
+
const textItem = textContent.find((item) => item?.type === 'text' && typeof item.text === 'string');
|
|
1070
|
+
if (textItem?.text) {
|
|
1071
|
+
try {
|
|
1072
|
+
return JSON.parse(textItem.text);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
return payload;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return payload;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function matchesRemoteService(fields, serviceName, environment) {
|
|
1083
|
+
const services = Array.isArray(fields.service) ? fields.service.map((item) => String(item).trim()) : [];
|
|
1084
|
+
if (services.includes(serviceName)) {
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const expectedPipelineName = buildPrefixedPipelineName(serviceName, environment);
|
|
1089
|
+
const pipelineCandidates = collectFieldPipelineDescriptors(fields, environment)
|
|
1090
|
+
.map((item) => item.pipelineName)
|
|
1091
|
+
.filter(Boolean);
|
|
1092
|
+
|
|
1093
|
+
return expectedPipelineName ? pipelineCandidates.includes(expectedPipelineName) : false;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function selectPipelineFromFields(fields, environment, serviceName) {
|
|
1097
|
+
const indexedCandidate = selectIndexedPipelineForService(fields, environment, serviceName);
|
|
1098
|
+
if (indexedCandidate?.pipelineId) {
|
|
1099
|
+
return indexedCandidate;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const candidates = collectFieldPipelineDescriptors(fields, environment);
|
|
1103
|
+
if (candidates.length > 0) {
|
|
1104
|
+
return candidates[0];
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const pipelineIds = String(fields.pipelineId || '')
|
|
1108
|
+
.split(',')
|
|
1109
|
+
.map((item) => item.trim())
|
|
1110
|
+
.filter(Boolean);
|
|
1111
|
+
|
|
1112
|
+
if (pipelineIds.length === 0) {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return {
|
|
1117
|
+
pipelineName: buildPrefixedPipelineName(
|
|
1118
|
+
Array.isArray(fields.service) ? fields.service[0] : null,
|
|
1119
|
+
environment
|
|
1120
|
+
),
|
|
1121
|
+
pipelineId: pipelineIds[0]
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function selectIndexedPipelineForService(fields, environment, serviceName) {
|
|
1126
|
+
if (!serviceName) return null;
|
|
1127
|
+
|
|
1128
|
+
const services = Array.isArray(fields.service) ? fields.service.map((item) => String(item).trim()) : [];
|
|
1129
|
+
const serviceIndex = services.indexOf(serviceName);
|
|
1130
|
+
if (serviceIndex < 0) return null;
|
|
1131
|
+
|
|
1132
|
+
for (const key of getFieldKeysForEnvironment(environment)) {
|
|
1133
|
+
const values = Array.isArray(fields[key]) ? fields[key] : [];
|
|
1134
|
+
const parsed = parsePipelineDescriptor(values[serviceIndex]);
|
|
1135
|
+
if (parsed?.pipelineId) {
|
|
1136
|
+
return parsed;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function collectFieldPipelineDescriptors(fields, environment) {
|
|
1144
|
+
const keys = getFieldKeysForEnvironment(environment);
|
|
1145
|
+
const results = [];
|
|
1146
|
+
|
|
1147
|
+
for (const key of keys) {
|
|
1148
|
+
const values = Array.isArray(fields[key]) ? fields[key] : (fields[key] ? [fields[key]] : []);
|
|
1149
|
+
for (const value of values) {
|
|
1150
|
+
const parsed = parsePipelineDescriptor(value);
|
|
1151
|
+
if (parsed?.pipelineId) {
|
|
1152
|
+
results.push(parsed);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return results;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function getFieldKeysForEnvironment(environment) {
|
|
1161
|
+
const normalized = normalizeEnvironment(environment || '');
|
|
1162
|
+
if (normalized === 'prod') return ['prodPipeline', 'pipeline'];
|
|
1163
|
+
if (normalized === 'gray') return ['grayPipeline', 'prodPipeline', 'pipeline'];
|
|
1164
|
+
return ['pipeline', 'grayPipeline', 'prodPipeline'];
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function parsePipelineDescriptor(value) {
|
|
1168
|
+
if (!value) return null;
|
|
1169
|
+
const parts = String(value).split('#@').map((item) => item.trim()).filter(Boolean);
|
|
1170
|
+
if (parts.length < 2) return null;
|
|
1171
|
+
return {
|
|
1172
|
+
pipelineName: parts[0] || null,
|
|
1173
|
+
pipelineId: parts[1] || null
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function getEnvLookupOrder(environment) {
|
|
1178
|
+
const normalized = normalizeEnvironment(environment || '');
|
|
1179
|
+
if (normalized === 'prod') return ['prod'];
|
|
1180
|
+
if (normalized === 'gray') return ['gray', 'prod'];
|
|
1181
|
+
if (normalized === 'uat') return ['uat'];
|
|
1182
|
+
if (normalized === 'test') return ['test', 'dev', 'uat'];
|
|
1183
|
+
return ['uat', 'test', 'dev', 'gray', 'prod'];
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function buildPrefixedPipelineName(serviceName, environment) {
|
|
1187
|
+
if (!serviceName) return null;
|
|
1188
|
+
const normalized = normalizeEnvironment(environment || '');
|
|
1189
|
+
if (!normalized) return null;
|
|
1190
|
+
const prefix = normalized === 'test' ? 'test' : normalized;
|
|
1191
|
+
return `${prefix}-${serviceName}`;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function inferEnvironmentFromPipelineName(pipelineName) {
|
|
1195
|
+
const match = String(pipelineName || '').match(/^(test|dev|uat|gray|prod)-/i);
|
|
1196
|
+
return match ? (ENV_PREFIX_TO_ENV[match[1].toLowerCase()] || normalizeEnvironment(match[1])) : null;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function collectNestedValues(payload, values = []) {
|
|
1200
|
+
if (payload == null) {
|
|
1201
|
+
return values;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (Array.isArray(payload)) {
|
|
1205
|
+
for (const item of payload) {
|
|
1206
|
+
collectNestedValues(item, values);
|
|
1207
|
+
}
|
|
1208
|
+
return values;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (typeof payload === 'object') {
|
|
1212
|
+
for (const value of Object.values(payload)) {
|
|
1213
|
+
collectNestedValues(value, values);
|
|
1214
|
+
}
|
|
1215
|
+
return values;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
values.push(payload);
|
|
1219
|
+
return values;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
397
1222
|
function compactObject(value) {
|
|
398
1223
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ''));
|
|
399
1224
|
}
|
|
1225
|
+
|
|
1226
|
+
function getWorkflowMcpServer(workflow) {
|
|
1227
|
+
return workflow.client?.mcpServer || workflow.binding?.mcpServer || workflow.provider;
|
|
1228
|
+
}
|