flowmind 1.5.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowmind",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Memory and workflow automation for MCP, Codex, and Claude Code. Reuse repeatable developer operations through skills and explicit feedback.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
@@ -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
- example: {
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
- steps: workflowDef.steps,
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 { mcpServer: binding.mcpServer, tool: TOOL_NAMES.listPipelines, params };
202
+ return callWorkflowMcp(binding, transport, TOOL_NAMES.listPipelines, params);
199
203
  },
200
204
  async getCurrentIterate() {
201
- return { mcpServer: binding.mcpServer, tool: 'getCurrentIterate', params: {} };
205
+ return callWorkflowMcp(binding, transport, 'getCurrentIterate', {});
202
206
  },
203
207
  async listDeployChecklists(params) {
204
- return { mcpServer: binding.mcpServer, tool: 'listDeployChecklists', params };
208
+ return callWorkflowMcp(binding, transport, 'listDeployChecklists', params);
205
209
  },
206
210
  async orderList(params) {
207
- return { mcpServer: binding.mcpServer, tool: 'orderList', params };
211
+ return callWorkflowMcp(binding, transport, 'orderList', params);
208
212
  },
209
213
  async startPipelineRun(pipelineId) {
210
- return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.startPipelineRun, params: { pipelineId } };
214
+ return callWorkflowMcp(binding, transport, TOOL_NAMES.startPipelineRun, { pipelineId });
211
215
  },
212
216
  async startBatchPipelineRun(params) {
213
- return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.startBatchPipelineRun, params };
217
+ return callWorkflowMcp(binding, transport, TOOL_NAMES.startBatchPipelineRun, params);
214
218
  },
215
219
  async getPipelineRun(pipelineId, runId) {
216
- return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.getPipelineRun, params: { pipelineId, pipelineRunId: runId } };
220
+ return callWorkflowMcp(binding, transport, TOOL_NAMES.getPipelineRun, { pipelineId, pipelineRunId: runId });
217
221
  },
218
222
  async listPipelineRuns(pipelineId, params) {
219
- return { mcpServer: binding.mcpServer, tool: TOOL_NAMES.listPipelineRuns, params: { pipelineId, ...(params || {}) } };
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: false
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
225
247
  };
226
248
  }
227
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
+
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
- steps: workflow.steps
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,
@@ -322,7 +684,10 @@ async function executeDeploy(workflow, params, input, context) {
322
684
  binding: workflow.binding,
323
685
  status: 'submitted',
324
686
  mcpServer: getWorkflowMcpServer(workflow),
325
- execution,
687
+ execution: summarizeDeployExecution(params, execution, {
688
+ source: 'batch-fallback',
689
+ lookup
690
+ }),
326
691
  resolution: {
327
692
  source: 'batch-fallback',
328
693
  lookup
@@ -334,30 +699,18 @@ async function executeDeploy(workflow, params, input, context) {
334
699
  }
335
700
 
336
701
  async function executeStatus(workflow, params, input) {
702
+ let execution;
337
703
  if (params.pipelineId && params.runId) {
338
- await workflow.client.getPipelineRun(params.pipelineId, params.runId);
704
+ execution = await workflow.client.getPipelineRun(params.pipelineId, params.runId);
339
705
  } else if (params.pipelineId) {
340
- await workflow.client.listPipelineRuns(params.pipelineId, {
706
+ execution = await workflow.client.listPipelineRuns(params.pipelineId, {
341
707
  env: params.environment
342
708
  });
343
709
  } else {
344
- await workflow.client.listPipelines(buildListPipelineParams(params));
710
+ execution = await workflow.client.listPipelines(buildListPipelineParams(params));
345
711
  }
346
712
 
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
- };
713
+ return execution;
361
714
  }
362
715
 
363
716
  function parseFlowParams(input) {
@@ -29,6 +29,7 @@ module.exports = {
29
29
  ? logService.buildQueryParams(queryInput)
30
30
  : queryInput;
31
31
  const execution = await logService.queryLogs(queryParams);
32
+ const summary = summarizeLogExecution(execution);
32
33
 
33
34
  return {
34
35
  type: 'result',
@@ -40,7 +41,7 @@ module.exports = {
40
41
  action: params.traceId ? 'trace' : 'query',
41
42
  provider: logService.providerName,
42
43
  queryParams,
43
- execution
44
+ execution: summary
44
45
  },
45
46
  input,
46
47
  timestamp: new Date().toISOString()
@@ -87,3 +88,95 @@ function buildLogQueryInput(params) {
87
88
  line: params.limit || 100
88
89
  };
89
90
  }
91
+
92
+ function summarizeLogExecution(execution) {
93
+ const payload = unwrapPayload(execution);
94
+ const data = payload && typeof payload === 'object' ? payload : {};
95
+ const records = extractRecords(data);
96
+
97
+ return {
98
+ status: extractStatus(data) || 'ok',
99
+ total: extractTotal(data, records),
100
+ records: records.slice(0, 5).map(summarizeRecord),
101
+ requestId: data.requestId || data.traceId || data.id || null
102
+ };
103
+ }
104
+
105
+ function unwrapPayload(payload) {
106
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
107
+ return payload;
108
+ }
109
+
110
+ const textPayload = extractContentText(payload);
111
+ if (textPayload) {
112
+ return parseMaybeJson(textPayload);
113
+ }
114
+
115
+ if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
116
+ return payload.data;
117
+ }
118
+
119
+ return payload;
120
+ }
121
+
122
+ function extractRecords(data) {
123
+ const candidates = [data.logs, data.records, data.items, data.result, data.data];
124
+ for (const candidate of candidates) {
125
+ if (Array.isArray(candidate)) {
126
+ return candidate;
127
+ }
128
+ }
129
+
130
+ return [];
131
+ }
132
+
133
+ function extractStatus(data) {
134
+ if (!data || typeof data !== 'object') {
135
+ return null;
136
+ }
137
+
138
+ if (typeof data.status === 'string') {
139
+ return data.status;
140
+ }
141
+
142
+ if (data.success === false) {
143
+ return 'error';
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ function extractTotal(data, records) {
150
+ return data.total || data.count || records.length || null;
151
+ }
152
+
153
+ function summarizeRecord(record) {
154
+ if (!record || typeof record !== 'object') {
155
+ return record;
156
+ }
157
+
158
+ return {
159
+ time: record.time || record.timestamp || record.__time__ || null,
160
+ level: record.level || record.severity || null,
161
+ message: record.message || record.content || record.msg || null,
162
+ traceId: record.traceId || record.trace_id || null
163
+ };
164
+ }
165
+
166
+ function extractContentText(payload) {
167
+ const content = Array.isArray(payload.content) ? payload.content : [];
168
+ const textItem = content.find((item) => item && typeof item.text === 'string');
169
+ return textItem ? textItem.text : null;
170
+ }
171
+
172
+ function parseMaybeJson(value) {
173
+ if (typeof value !== 'string') {
174
+ return value;
175
+ }
176
+
177
+ try {
178
+ return JSON.parse(value);
179
+ } catch (error) {
180
+ return { text: value };
181
+ }
182
+ }