flowmind 1.5.3 → 1.5.6
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 +31 -1
- package/README.md +2 -0
- package/README_CN.md +2 -0
- package/bin/flowmind.js +104 -118
- package/core/adapters/mcp-adapter.js +9 -8
- package/core/ai/providers/mimo.js +1 -1
- package/core/cli-ink.js +79 -0
- package/core/index.js +139 -1
- package/core/log-query-parser.js +324 -0
- package/core/providers/aliyun/dms-adapter.js +7 -35
- package/core/providers/aliyun/redis-adapter.js +4 -20
- package/core/providers/aliyun/sls-adapter.js +3 -10
- 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/update-notifier.js +74 -0
- package/dashboard/app.jsx +69 -10
- package/dashboard/components/ActivityFeed.jsx +5 -4
- package/dashboard/components/DragonPanel.jsx +17 -1
- package/dashboard/components/McpStatusBar.jsx +19 -1
- package/dashboard/components/StatsRow.jsx +27 -2
- package/package.json +2 -1
- package/scripts/check-update.js +52 -0
- package/skills/auto-flow/index.js +451 -122
- package/skills/log-audit/index.js +146 -25
- package/skills/sls-log-audit/index.js +7 -30
- package/skills/yapi-sync-interface/index.js +146 -13
- package/skills/yuque-sync-design/index.js +132 -13
- package/tui/app.jsx +86 -9
- package/tui/components/ChatPanel.jsx +10 -7
- package/tui/components/DragonTotem.jsx +12 -1
- package/tui/components/Sidebar.jsx +19 -7
- package/tui/components/StatusBar.jsx +28 -1
- package/tui/format-result.js +60 -0
- package/tui/layout.js +60 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const { callMcpTool } = require('../../core/mcp-http-client');
|
|
9
10
|
|
|
10
11
|
const BUILTIN_WORKFLOWS = {
|
|
11
12
|
'dev-workflow': {
|
|
@@ -72,19 +73,14 @@ module.exports = {
|
|
|
72
73
|
const workflow = createWorkflowClient(context);
|
|
73
74
|
|
|
74
75
|
if (params.action === 'define') {
|
|
76
|
+
const definition = buildWorkflowDefinitionSummary();
|
|
75
77
|
return {
|
|
76
78
|
type: 'result',
|
|
77
79
|
skill: 'auto-flow',
|
|
78
80
|
message: 'Define a workflow in YAML format',
|
|
79
81
|
data: {
|
|
80
82
|
format: 'YAML workflow definition',
|
|
81
|
-
|
|
82
|
-
name: 'My Workflow',
|
|
83
|
-
steps: [
|
|
84
|
-
{ id: 'step1', action: 'run-command', command: 'echo hello' },
|
|
85
|
-
{ id: 'step2', skill: 'code-review', depends_on: ['step1'] }
|
|
86
|
-
]
|
|
87
|
-
}
|
|
83
|
+
execution: definition
|
|
88
84
|
},
|
|
89
85
|
input,
|
|
90
86
|
timestamp: new Date().toISOString()
|
|
@@ -96,7 +92,7 @@ module.exports = {
|
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
if (params.action === 'status') {
|
|
99
|
-
await executeStatus(workflow, params, input);
|
|
95
|
+
const execution = await executeStatus(workflow, params, input);
|
|
100
96
|
return {
|
|
101
97
|
type: 'result',
|
|
102
98
|
skill: 'auto-flow',
|
|
@@ -106,7 +102,8 @@ module.exports = {
|
|
|
106
102
|
provider: workflow.provider,
|
|
107
103
|
binding: workflow.binding,
|
|
108
104
|
status: 'completed',
|
|
109
|
-
mcpServer: getWorkflowMcpServer(workflow)
|
|
105
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
106
|
+
execution: summarizeWorkflowExecution('status', params, execution)
|
|
110
107
|
},
|
|
111
108
|
input,
|
|
112
109
|
timestamp: new Date().toISOString()
|
|
@@ -118,7 +115,7 @@ module.exports = {
|
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
if (params.action === 'list' || params.serviceNames.length > 0) {
|
|
121
|
-
await workflow.client.listPipelines(buildListPipelineParams(params));
|
|
118
|
+
const execution = await workflow.client.listPipelines(buildListPipelineParams(params));
|
|
122
119
|
return {
|
|
123
120
|
type: 'result',
|
|
124
121
|
skill: 'auto-flow',
|
|
@@ -128,7 +125,8 @@ module.exports = {
|
|
|
128
125
|
provider: workflow.provider,
|
|
129
126
|
binding: workflow.binding,
|
|
130
127
|
status: 'completed',
|
|
131
|
-
mcpServer: getWorkflowMcpServer(workflow)
|
|
128
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
129
|
+
execution: summarizeWorkflowExecution('list', params, execution)
|
|
132
130
|
},
|
|
133
131
|
input,
|
|
134
132
|
timestamp: new Date().toISOString()
|
|
@@ -143,7 +141,7 @@ module.exports = {
|
|
|
143
141
|
message: `Workflow ready: ${workflowDef.name}`,
|
|
144
142
|
data: {
|
|
145
143
|
workflow: params.workflow,
|
|
146
|
-
|
|
144
|
+
execution: buildBuiltinWorkflowSummary(params.workflow, workflowDef),
|
|
147
145
|
provider: workflow.provider
|
|
148
146
|
},
|
|
149
147
|
input,
|
|
@@ -164,7 +162,8 @@ module.exports = {
|
|
|
164
162
|
],
|
|
165
163
|
builtinWorkflows: Object.keys(BUILTIN_WORKFLOWS),
|
|
166
164
|
provider: workflow.provider,
|
|
167
|
-
binding: workflow.binding
|
|
165
|
+
binding: workflow.binding,
|
|
166
|
+
execution: buildAutoFlowHintSummary(workflow)
|
|
168
167
|
},
|
|
169
168
|
input,
|
|
170
169
|
timestamp: new Date().toISOString()
|
|
@@ -191,40 +190,391 @@ function createWorkflowClient(context) {
|
|
|
191
190
|
return { client: null, provider: null, binding: null, transportBacked: false };
|
|
192
191
|
}
|
|
193
192
|
|
|
193
|
+
const transport = buildWorkflowTransport(binding);
|
|
194
|
+
if (!transport.url) {
|
|
195
|
+
return { client: null, provider: binding.provider || null, binding, transportBacked: false };
|
|
196
|
+
}
|
|
197
|
+
|
|
194
198
|
return {
|
|
195
199
|
client: {
|
|
196
200
|
providerName: binding.provider || 'workflow-binding',
|
|
197
201
|
async listPipelines(params) {
|
|
198
|
-
return
|
|
202
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.listPipelines, params);
|
|
199
203
|
},
|
|
200
204
|
async getCurrentIterate() {
|
|
201
|
-
return
|
|
205
|
+
return callWorkflowMcp(binding, transport, 'getCurrentIterate', {});
|
|
202
206
|
},
|
|
203
207
|
async listDeployChecklists(params) {
|
|
204
|
-
return
|
|
208
|
+
return callWorkflowMcp(binding, transport, 'listDeployChecklists', params);
|
|
205
209
|
},
|
|
206
210
|
async orderList(params) {
|
|
207
|
-
return
|
|
211
|
+
return callWorkflowMcp(binding, transport, 'orderList', params);
|
|
208
212
|
},
|
|
209
213
|
async startPipelineRun(pipelineId) {
|
|
210
|
-
return
|
|
214
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.startPipelineRun, { pipelineId });
|
|
211
215
|
},
|
|
212
216
|
async startBatchPipelineRun(params) {
|
|
213
|
-
return
|
|
217
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.startBatchPipelineRun, params);
|
|
214
218
|
},
|
|
215
219
|
async getPipelineRun(pipelineId, runId) {
|
|
216
|
-
return
|
|
220
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.getPipelineRun, { pipelineId, pipelineRunId: runId });
|
|
217
221
|
},
|
|
218
222
|
async listPipelineRuns(pipelineId, params) {
|
|
219
|
-
return
|
|
223
|
+
return callWorkflowMcp(binding, transport, TOOL_NAMES.listPipelineRuns, { pipelineId, ...(params || {}) });
|
|
220
224
|
}
|
|
221
225
|
},
|
|
222
226
|
provider: binding.provider || 'workflow-binding',
|
|
223
227
|
binding,
|
|
224
|
-
transportBacked:
|
|
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
|
|
247
|
+
};
|
|
248
|
+
}
|
|
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
|
+
}
|
|
225
309
|
};
|
|
226
310
|
}
|
|
227
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
|
+
|
|
228
578
|
function buildNoAdapterResult(input, params) {
|
|
229
579
|
if (params.workflow && BUILTIN_WORKFLOWS[params.workflow]) {
|
|
230
580
|
const workflow = BUILTIN_WORKFLOWS[params.workflow];
|
|
@@ -234,7 +584,7 @@ function buildNoAdapterResult(input, params) {
|
|
|
234
584
|
message: `Workflow ready: ${workflow.name}`,
|
|
235
585
|
data: {
|
|
236
586
|
workflow: params.workflow,
|
|
237
|
-
|
|
587
|
+
execution: buildBuiltinWorkflowSummary(params.workflow, workflow)
|
|
238
588
|
},
|
|
239
589
|
input,
|
|
240
590
|
timestamp: new Date().toISOString()
|
|
@@ -247,7 +597,12 @@ function buildNoAdapterResult(input, params) {
|
|
|
247
597
|
message: 'Workflow service not configured. Connect friday-auto-flow first.',
|
|
248
598
|
data: {
|
|
249
599
|
status: 'not_configured',
|
|
250
|
-
hint: 'Run `flowmind resource` to review current bindings, then save one like: `flowmind "绑定发布业务 mcp=friday-auto-flow token=xxx env=uat"`'
|
|
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
|
+
})
|
|
251
606
|
},
|
|
252
607
|
input,
|
|
253
608
|
timestamp: new Date().toISOString()
|
|
@@ -267,7 +622,10 @@ async function executeDeploy(workflow, params, input, context) {
|
|
|
267
622
|
binding: workflow.binding,
|
|
268
623
|
status: 'submitted',
|
|
269
624
|
mcpServer: getWorkflowMcpServer(workflow),
|
|
270
|
-
execution,
|
|
625
|
+
execution: summarizeDeployExecution(params, execution, {
|
|
626
|
+
source: 'direct-input',
|
|
627
|
+
pipelineId: params.pipelineId
|
|
628
|
+
}),
|
|
271
629
|
resolution: {
|
|
272
630
|
source: 'direct-input',
|
|
273
631
|
pipelineId: params.pipelineId
|
|
@@ -295,7 +653,11 @@ async function executeDeploy(workflow, params, input, context) {
|
|
|
295
653
|
binding: workflow.binding,
|
|
296
654
|
status: 'submitted',
|
|
297
655
|
mcpServer: getWorkflowMcpServer(workflow),
|
|
298
|
-
execution: executions.length === 1 ? executions[0] : executions,
|
|
656
|
+
execution: summarizeDeployExecution(params, executions.length === 1 ? executions[0] : executions, {
|
|
657
|
+
source: resolvedTargets.source,
|
|
658
|
+
pipelineIds: resolvedTargets.pipelineIds,
|
|
659
|
+
services: resolvedTargets.resolutions
|
|
660
|
+
}),
|
|
299
661
|
resolution: {
|
|
300
662
|
source: resolvedTargets.source,
|
|
301
663
|
pipelineIds: resolvedTargets.pipelineIds,
|
|
@@ -307,57 +669,22 @@ async function executeDeploy(workflow, params, input, context) {
|
|
|
307
669
|
};
|
|
308
670
|
}
|
|
309
671
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const batchParams = buildBatchRunParams(params);
|
|
313
|
-
const execution = await workflow.client.startBatchPipelineRun(batchParams);
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
type: 'result',
|
|
317
|
-
skill: 'auto-flow',
|
|
318
|
-
message: 'Workflow status: deployment submitted',
|
|
319
|
-
data: {
|
|
320
|
-
action: 'deploy',
|
|
321
|
-
provider: workflow.provider,
|
|
322
|
-
binding: workflow.binding,
|
|
323
|
-
status: 'submitted',
|
|
324
|
-
mcpServer: getWorkflowMcpServer(workflow),
|
|
325
|
-
execution,
|
|
326
|
-
resolution: {
|
|
327
|
-
source: 'batch-fallback',
|
|
328
|
-
lookup
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
input,
|
|
332
|
-
timestamp: new Date().toISOString()
|
|
333
|
-
};
|
|
334
|
-
}
|
|
672
|
+
return buildUnresolvedDeployResult(workflow, params, input, context);
|
|
673
|
+
}
|
|
335
674
|
|
|
336
675
|
async function executeStatus(workflow, params, input) {
|
|
676
|
+
let execution;
|
|
337
677
|
if (params.pipelineId && params.runId) {
|
|
338
|
-
await workflow.client.getPipelineRun(params.pipelineId, params.runId);
|
|
678
|
+
execution = await workflow.client.getPipelineRun(params.pipelineId, params.runId);
|
|
339
679
|
} else if (params.pipelineId) {
|
|
340
|
-
await workflow.client.listPipelineRuns(params.pipelineId, {
|
|
680
|
+
execution = await workflow.client.listPipelineRuns(params.pipelineId, {
|
|
341
681
|
env: params.environment
|
|
342
682
|
});
|
|
343
683
|
} else {
|
|
344
|
-
await workflow.client.listPipelines(buildListPipelineParams(params));
|
|
684
|
+
execution = await workflow.client.listPipelines(buildListPipelineParams(params));
|
|
345
685
|
}
|
|
346
686
|
|
|
347
|
-
return
|
|
348
|
-
type: 'result',
|
|
349
|
-
skill: 'auto-flow',
|
|
350
|
-
message: 'Workflow status: query completed',
|
|
351
|
-
data: {
|
|
352
|
-
action: 'status',
|
|
353
|
-
provider: workflow.provider,
|
|
354
|
-
binding: workflow.binding,
|
|
355
|
-
status: 'completed',
|
|
356
|
-
mcpServer: workflow.binding?.mcpServer || workflow.provider
|
|
357
|
-
},
|
|
358
|
-
input,
|
|
359
|
-
timestamp: new Date().toISOString()
|
|
360
|
-
};
|
|
687
|
+
return execution;
|
|
361
688
|
}
|
|
362
689
|
|
|
363
690
|
function parseFlowParams(input) {
|
|
@@ -541,7 +868,7 @@ function resolveLocalPipelineForService(serviceName, environment, context) {
|
|
|
541
868
|
const entry = pipelineMap[serviceName];
|
|
542
869
|
if (!entry) return null;
|
|
543
870
|
|
|
544
|
-
const pipeline = selectPipelineFromEntry(entry, environment);
|
|
871
|
+
const pipeline = selectPipelineFromEntry(entry, environment, serviceName);
|
|
545
872
|
if (!pipeline?.pipelineId) return null;
|
|
546
873
|
|
|
547
874
|
return {
|
|
@@ -570,19 +897,13 @@ function readLocalPipelineMap(context) {
|
|
|
570
897
|
return null;
|
|
571
898
|
}
|
|
572
899
|
|
|
573
|
-
function selectPipelineFromEntry(entry, environment) {
|
|
900
|
+
function selectPipelineFromEntry(entry, environment, serviceName) {
|
|
574
901
|
const preferredKeys = getEnvLookupOrder(environment);
|
|
575
902
|
for (const key of preferredKeys) {
|
|
576
903
|
const candidate = normalizePipelineCollection(entry?.[key]);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
for (const value of Object.values(entry || {})) {
|
|
583
|
-
const candidate = normalizePipelineCollection(value);
|
|
584
|
-
if (candidate.length > 0) {
|
|
585
|
-
return candidate[0];
|
|
904
|
+
const matched = candidate.find((item) => matchesPipelineName(item.pipelineName, environment, serviceName));
|
|
905
|
+
if (matched) {
|
|
906
|
+
return matched;
|
|
586
907
|
}
|
|
587
908
|
}
|
|
588
909
|
|
|
@@ -741,49 +1062,12 @@ function matchesRemoteService(fields, serviceName, environment) {
|
|
|
741
1062
|
}
|
|
742
1063
|
|
|
743
1064
|
function selectPipelineFromFields(fields, environment, serviceName) {
|
|
744
|
-
const indexedCandidate = selectIndexedPipelineForService(fields, environment, serviceName);
|
|
745
|
-
if (indexedCandidate?.pipelineId) {
|
|
746
|
-
return indexedCandidate;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
1065
|
const candidates = collectFieldPipelineDescriptors(fields, environment);
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const pipelineIds = String(fields.pipelineId || '')
|
|
755
|
-
.split(',')
|
|
756
|
-
.map((item) => item.trim())
|
|
757
|
-
.filter(Boolean);
|
|
758
|
-
|
|
759
|
-
if (pipelineIds.length === 0) {
|
|
760
|
-
return null;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return {
|
|
764
|
-
pipelineName: buildPrefixedPipelineName(
|
|
765
|
-
Array.isArray(fields.service) ? fields.service[0] : null,
|
|
766
|
-
environment
|
|
767
|
-
),
|
|
768
|
-
pipelineId: pipelineIds[0]
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function selectIndexedPipelineForService(fields, environment, serviceName) {
|
|
773
|
-
if (!serviceName) return null;
|
|
774
|
-
|
|
775
|
-
const services = Array.isArray(fields.service) ? fields.service.map((item) => String(item).trim()) : [];
|
|
776
|
-
const serviceIndex = services.indexOf(serviceName);
|
|
777
|
-
if (serviceIndex < 0) return null;
|
|
778
|
-
|
|
779
|
-
for (const key of getFieldKeysForEnvironment(environment)) {
|
|
780
|
-
const values = Array.isArray(fields[key]) ? fields[key] : [];
|
|
781
|
-
const parsed = parsePipelineDescriptor(values[serviceIndex]);
|
|
782
|
-
if (parsed?.pipelineId) {
|
|
783
|
-
return parsed;
|
|
1066
|
+
for (const candidate of candidates) {
|
|
1067
|
+
if (candidate?.pipelineId && matchesPipelineName(candidate.pipelineName, environment, serviceName)) {
|
|
1068
|
+
return candidate;
|
|
784
1069
|
}
|
|
785
1070
|
}
|
|
786
|
-
|
|
787
1071
|
return null;
|
|
788
1072
|
}
|
|
789
1073
|
|
|
@@ -824,10 +1108,11 @@ function parsePipelineDescriptor(value) {
|
|
|
824
1108
|
function getEnvLookupOrder(environment) {
|
|
825
1109
|
const normalized = normalizeEnvironment(environment || '');
|
|
826
1110
|
if (normalized === 'prod') return ['prod'];
|
|
827
|
-
if (normalized === 'gray') return ['gray'
|
|
1111
|
+
if (normalized === 'gray') return ['gray'];
|
|
828
1112
|
if (normalized === 'uat') return ['uat'];
|
|
829
|
-
if (normalized === 'test') return ['test', 'dev'
|
|
830
|
-
|
|
1113
|
+
if (normalized === 'test') return ['test', 'dev'];
|
|
1114
|
+
if (normalized === 'dev') return ['dev'];
|
|
1115
|
+
return [];
|
|
831
1116
|
}
|
|
832
1117
|
|
|
833
1118
|
function buildPrefixedPipelineName(serviceName, environment) {
|
|
@@ -838,6 +1123,24 @@ function buildPrefixedPipelineName(serviceName, environment) {
|
|
|
838
1123
|
return `${prefix}-${serviceName}`;
|
|
839
1124
|
}
|
|
840
1125
|
|
|
1126
|
+
function matchesPipelineName(pipelineName, environment, serviceName) {
|
|
1127
|
+
if (!pipelineName || !serviceName) return false;
|
|
1128
|
+
|
|
1129
|
+
const normalized = String(pipelineName).toLowerCase();
|
|
1130
|
+
const candidatePrefixes = getAllowedPipelinePrefixes(environment);
|
|
1131
|
+
return candidatePrefixes.some((prefix) => normalized === `${prefix}-${serviceName}`.toLowerCase());
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function getAllowedPipelinePrefixes(environment) {
|
|
1135
|
+
const normalized = normalizeEnvironment(environment || '');
|
|
1136
|
+
if (normalized === 'test') return ['test', 'dev'];
|
|
1137
|
+
if (normalized === 'dev') return ['dev'];
|
|
1138
|
+
if (normalized === 'uat') return ['uat'];
|
|
1139
|
+
if (normalized === 'gray') return ['gray'];
|
|
1140
|
+
if (normalized === 'prod') return ['prod'];
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
|
|
841
1144
|
function inferEnvironmentFromPipelineName(pipelineName) {
|
|
842
1145
|
const match = String(pipelineName || '').match(/^(test|dev|uat|gray|prod)-/i);
|
|
843
1146
|
return match ? (ENV_PREFIX_TO_ENV[match[1].toLowerCase()] || normalizeEnvironment(match[1])) : null;
|
|
@@ -866,6 +1169,32 @@ function collectNestedValues(payload, values = []) {
|
|
|
866
1169
|
return values;
|
|
867
1170
|
}
|
|
868
1171
|
|
|
1172
|
+
function buildUnresolvedDeployResult(workflow, params, input, context) {
|
|
1173
|
+
const target = buildWorkflowTargetLabel(params) || buildDeployTargetLabel(context) || 'deployment';
|
|
1174
|
+
return {
|
|
1175
|
+
type: 'error',
|
|
1176
|
+
success: false,
|
|
1177
|
+
skill: 'auto-flow',
|
|
1178
|
+
message: `Unable to resolve deployment target for ${target}. Specify an exact environment or pipelineId.`,
|
|
1179
|
+
data: {
|
|
1180
|
+
action: 'deploy',
|
|
1181
|
+
provider: workflow.provider,
|
|
1182
|
+
binding: workflow.binding,
|
|
1183
|
+
status: 'not_resolved',
|
|
1184
|
+
mcpServer: getWorkflowMcpServer(workflow),
|
|
1185
|
+
target,
|
|
1186
|
+
environment: params.environment || null,
|
|
1187
|
+
resolution: {
|
|
1188
|
+
source: 'not-resolved',
|
|
1189
|
+
serviceNames: params.serviceNames || [],
|
|
1190
|
+
environment: params.environment || null
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
input,
|
|
1194
|
+
timestamp: new Date().toISOString()
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
869
1198
|
function compactObject(value) {
|
|
870
1199
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ''));
|
|
871
1200
|
}
|