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.
@@ -3,6 +3,8 @@
3
3
  * Verify SQL queries, Redis operations, and data processing logic against real data via MCP
4
4
  */
5
5
 
6
+ const { inferSourceContext } = require('../../core/source-inference');
7
+
6
8
  module.exports = {
7
9
  canHandle(input, context) {
8
10
  if (!input) return false;
@@ -13,6 +15,7 @@ module.exports = {
13
15
  const registry = context.componentRegistry;
14
16
  const params = parseValidationParams(input);
15
17
  const learnedBinding = context.resourceBinding;
18
+ const sourceInference = context.sourceInference || await safeInferSourceContext(input);
16
19
 
17
20
  // Check MCP component availability
18
21
  const dbManager = registry?.getAdapter('databaseManager');
@@ -26,7 +29,7 @@ module.exports = {
26
29
  };
27
30
 
28
31
  if (params.action === 'sql') {
29
- if (!dbQuery && !learnedBinding) {
32
+ if (!dbQuery) {
30
33
  return {
31
34
  type: 'result',
32
35
  skill: 'data-logic-validation',
@@ -37,16 +40,22 @@ module.exports = {
37
40
  };
38
41
  }
39
42
 
43
+ const target = resolveDatabaseTarget(params, learnedBinding, sourceInference);
44
+ const sql = params.query || 'SELECT 1';
45
+ const execution = await dbQuery.queryExec(buildSqlExecutionParams(target, sql));
46
+
40
47
  return {
41
48
  type: 'result',
42
49
  skill: 'data-logic-validation',
43
- message: `SQL validation ready for: ${params.query || 'provided query'}`,
50
+ message: `SQL validation executed for: ${target.project || target.database || 'resolved target'}`,
44
51
  data: {
45
52
  action: 'validate_sql',
46
- query: params.query,
47
- mcpTool: 'mcpQueryExec',
53
+ query: sql,
54
+ target,
55
+ execution,
48
56
  availableComponents,
49
- binding: learnedBinding
57
+ binding: learnedBinding,
58
+ inferredSource: summarizeSourceInference(sourceInference)
50
59
  },
51
60
  input,
52
61
  timestamp: new Date().toISOString()
@@ -54,27 +63,33 @@ module.exports = {
54
63
  }
55
64
 
56
65
  if (params.action === 'redis') {
57
- if (!redisMonitor && !learnedBinding) {
66
+ if (!dbQuery) {
58
67
  return {
59
68
  type: 'result',
60
69
  skill: 'data-logic-validation',
61
- message: 'Redis monitor not configured (need friday-aliyun-sz-rds-redis MCP)',
62
- data: { availableComponents, hint: 'Configure Redis monitor MCP server first' },
70
+ message: 'Redis direct query service not configured (need friday-rds-redis-query MCP)',
71
+ data: { availableComponents, hint: 'Configure database query MCP server first' },
63
72
  input,
64
73
  timestamp: new Date().toISOString()
65
74
  };
66
75
  }
67
76
 
77
+ const target = resolveRedisTarget(params, learnedBinding, sourceInference);
78
+ const execution = await executeRedisAction(dbQuery, params, target);
79
+
68
80
  return {
69
81
  type: 'result',
70
82
  skill: 'data-logic-validation',
71
- message: `Redis validation ready for key: ${params.key || 'provided key'}`,
83
+ message: `Redis validation executed for: ${target.project || target.host || 'resolved target'}`,
72
84
  data: {
73
85
  action: 'validate_redis',
74
86
  key: params.key,
75
- mcpTools: ['mcpRedisKeyGet', 'mcpRedisKeyInfo', 'mcpRedisKeys'],
87
+ target,
88
+ execution,
76
89
  availableComponents,
77
- binding: learnedBinding
90
+ binding: learnedBinding,
91
+ inferredSource: summarizeSourceInference(sourceInference),
92
+ monitorProvider: redisMonitor?.providerName || null
78
93
  },
79
94
  input,
80
95
  timestamp: new Date().toISOString()
@@ -89,7 +104,8 @@ module.exports = {
89
104
  actions: ['sql - Validate SQL queries', 'redis - Validate Redis operations'],
90
105
  availableComponents,
91
106
  requiredMCP: ['databaseManager', 'databaseQuery', 'redisMonitor'],
92
- binding: learnedBinding
107
+ binding: learnedBinding,
108
+ inferredSource: summarizeSourceInference(sourceInference)
93
109
  },
94
110
  input,
95
111
  timestamp: new Date().toISOString()
@@ -108,5 +124,110 @@ function parseValidationParams(input) {
108
124
  const keyMatch = input.match(/(?:key|键)\s*[:=]?\s*(\S+)/i);
109
125
  if (keyMatch) params.key = keyMatch[1];
110
126
 
127
+ const projectMatch = input.match(/(?:project|项目)\s*[:=]?\s*([\u4e00-\u9fa5A-Za-z0-9._-]+)/i);
128
+ if (projectMatch) params.project = projectMatch[1];
129
+
130
+ const envMatch = input.match(/(?:env|环境)\s*[:=]?\s*([A-Za-z_-]+)/i);
131
+ if (envMatch) params.env = envMatch[1].toLowerCase();
132
+
111
133
  return params;
112
134
  }
135
+
136
+ async function safeInferSourceContext(input) {
137
+ try {
138
+ return await inferSourceContext(input);
139
+ } catch (error) {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ function resolveDatabaseTarget(params, learnedBinding, sourceInference) {
145
+ const bindingConnection = learnedBinding?.connection || {};
146
+ const inferredDatabase = sourceInference?.database || {};
147
+
148
+ return compactObject({
149
+ project: params.project || sourceInference?.project || bindingConnection.project || null,
150
+ environment: params.env || sourceInference?.environment || bindingConnection.env || inferredDatabase.envType || null,
151
+ sourceId: bindingConnection.sourceId || inferredDatabase.sourceId || null,
152
+ databaseId: bindingConnection.databaseId || inferredDatabase.databaseId || null,
153
+ database: inferredDatabase.database || bindingConnection.database || null,
154
+ host: bindingConnection.host || inferredDatabase.host || null,
155
+ port: bindingConnection.port || inferredDatabase.port || null,
156
+ username: bindingConnection.username || bindingConnection.dbUser || inferredDatabase.username || inferredDatabase.dbUser || null,
157
+ password: bindingConnection.password || bindingConnection.dbPassword || inferredDatabase.password || inferredDatabase.dbPassword || null
158
+ });
159
+ }
160
+
161
+ function resolveRedisTarget(params, learnedBinding, sourceInference) {
162
+ const bindingConnection = learnedBinding?.connection || {};
163
+ const inferredRedis = sourceInference?.redis || {};
164
+
165
+ return compactObject({
166
+ project: params.project || sourceInference?.project || bindingConnection.project || null,
167
+ environment: params.env || sourceInference?.environment || bindingConnection.env || inferredRedis.envType || null,
168
+ sourceId: bindingConnection.sourceId || inferredRedis.sourceId || null,
169
+ host: bindingConnection.host || inferredRedis.host || null,
170
+ port: bindingConnection.port || inferredRedis.port || null,
171
+ password: bindingConnection.password || inferredRedis.password || null,
172
+ database: inferredRedis.database ?? bindingConnection.database ?? 1
173
+ });
174
+ }
175
+
176
+ function buildSqlExecutionParams(target, sql) {
177
+ return compactObject({
178
+ source_id: target.sourceId || null,
179
+ database_id: target.databaseId || null,
180
+ schema: target.database || null,
181
+ sql,
182
+ env: target.environment || null,
183
+ host: target.host || null,
184
+ port: target.port || null,
185
+ username: target.username || null,
186
+ password: target.password || null
187
+ });
188
+ }
189
+
190
+ async function executeRedisAction(dbQuery, params, target) {
191
+ const baseParams = compactObject({
192
+ source_id: target.sourceId || null,
193
+ env: target.environment || null,
194
+ host: target.host || null,
195
+ port: target.port || null,
196
+ password: target.password || null,
197
+ database: target.database ?? 1
198
+ });
199
+
200
+ if (params.key && !/[*?]/.test(params.key)) {
201
+ return dbQuery.redisKeyGet({
202
+ ...baseParams,
203
+ key: params.key
204
+ });
205
+ }
206
+
207
+ return dbQuery.redisScan({
208
+ ...baseParams,
209
+ pattern: params.key || '*',
210
+ count: 50
211
+ });
212
+ }
213
+
214
+ function summarizeSourceInference(sourceInference) {
215
+ if (!sourceInference?.matched) {
216
+ return sourceInference?.environment
217
+ ? { matched: false, environment: sourceInference.environment }
218
+ : null;
219
+ }
220
+
221
+ return {
222
+ matched: true,
223
+ project: sourceInference.project,
224
+ environment: sourceInference.environment,
225
+ database: sourceInference.database?.database || null,
226
+ redisDatabase: sourceInference.redis?.database ?? null,
227
+ matchedEntries: sourceInference.matchedEntries?.map((entry) => entry.title) || []
228
+ };
229
+ }
230
+
231
+ function compactObject(value) {
232
+ return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== null && fieldValue !== undefined && fieldValue !== ''));
233
+ }
@@ -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
+ }
@@ -3,6 +3,8 @@
3
3
  * Manage database, Redis, API, and other external resource connections
4
4
  */
5
5
 
6
+ const { inferSourceContext } = require('../../core/source-inference');
7
+
6
8
  const PROVIDER_MAP = {
7
9
  'yapi-mcp': { componentType: 'apiDoc', provider: 'yapi', mcpServer: 'yapi-mcp' },
8
10
  yapi: { componentType: 'apiDoc', provider: 'yapi', mcpServer: 'yapi-mcp' },
@@ -27,9 +29,10 @@ module.exports = {
27
29
  async execute(input, context) {
28
30
  const registry = context.componentRegistry;
29
31
  const params = parseResourceParams(input);
32
+ const sourceInference = context.sourceInference || await safeInferSourceContext(input);
30
33
 
31
34
  if (params.action === 'bind') {
32
- return bindResource(input, context, params);
35
+ return bindResource(input, context, params, sourceInference);
33
36
  }
34
37
 
35
38
  if (params.action === 'list') {
@@ -48,7 +51,8 @@ module.exports = {
48
51
  initialized: c.initialized,
49
52
  mcpServer: c.mcpServer
50
53
  })),
51
- bindings
54
+ bindings,
55
+ inferredSource: summarizeSourceInference(sourceInference)
52
56
  },
53
57
  input,
54
58
  timestamp: new Date().toISOString()
@@ -69,7 +73,8 @@ module.exports = {
69
73
  : 'Resource connection status',
70
74
  data: {
71
75
  status,
72
- matchedBinding
76
+ matchedBinding,
77
+ inferredSource: summarizeSourceInference(sourceInference)
73
78
  },
74
79
  input,
75
80
  timestamp: new Date().toISOString()
@@ -87,7 +92,8 @@ module.exports = {
87
92
  'status - Show connection status and matched binding',
88
93
  'use - Resolve a learned binding for the current business'
89
94
  ],
90
- supportedTypes: ['MySQL', 'PostgreSQL', 'Redis', 'REST API', 'YApi', 'Yuque', 'Workflow']
95
+ supportedTypes: ['MySQL', 'PostgreSQL', 'Redis', 'REST API', 'YApi', 'Yuque', 'Workflow'],
96
+ inferredSource: summarizeSourceInference(sourceInference)
91
97
  },
92
98
  input,
93
99
  timestamp: new Date().toISOString()
@@ -95,7 +101,7 @@ module.exports = {
95
101
  }
96
102
  };
97
103
 
98
- async function bindResource(input, context, params) {
104
+ async function bindResource(input, context, params, sourceInference) {
99
105
  const learning = context.flowmind?.learning;
100
106
  if (!learning?.saveResourceBinding) {
101
107
  return {
@@ -107,7 +113,7 @@ async function bindResource(input, context, params) {
107
113
  };
108
114
  }
109
115
 
110
- const binding = buildBinding(input, params, context);
116
+ const binding = buildBinding(input, params, context, sourceInference);
111
117
  const saved = await learning.saveResourceBinding(binding, {
112
118
  currentSkill: 'resource-bind'
113
119
  });
@@ -126,10 +132,20 @@ async function bindResource(input, context, params) {
126
132
  };
127
133
  }
128
134
 
129
- function buildBinding(input, params, context) {
135
+ function buildBinding(input, params, context, sourceInference) {
130
136
  const inferred = resolveProviderInfo(params);
131
- const business = inferBusiness(input, params, inferred);
132
- const aliases = [business, params.project, params.namespace, params.alias, params.env].filter(Boolean);
137
+ const inferredDatabase = sourceInference?.database || null;
138
+ const inferredRedis = sourceInference?.redis || null;
139
+ const business = inferBusiness(input, params, inferred, sourceInference);
140
+ const aliases = [
141
+ business,
142
+ params.project,
143
+ sourceInference?.project,
144
+ params.namespace,
145
+ params.alias,
146
+ params.env,
147
+ sourceInference?.environment
148
+ ].filter(Boolean);
133
149
  const keywords = extractKeywords(input, aliases);
134
150
 
135
151
  return {
@@ -140,18 +156,20 @@ function buildBinding(input, params, context) {
140
156
  mcpServer: params.mcpServer || inferred.mcpServer || null,
141
157
  connection: {
142
158
  address: params.address || params.url || params.endpoint || null,
143
- host: params.host || null,
144
- port: params.port || null,
159
+ host: params.host || inferredDatabase?.host || inferredRedis?.host || null,
160
+ port: params.port || inferredDatabase?.port || inferredRedis?.port || null,
145
161
  token: params.token || null,
146
162
  apiKey: params.apiKey || null,
147
163
  namespace: params.namespace || null,
148
- project: params.project || null,
149
- sourceId: params.sourceId || null,
150
- databaseId: params.databaseId || null,
151
- env: params.env || null
164
+ project: params.project || sourceInference?.project || null,
165
+ database: inferredDatabase?.database || null,
166
+ sourceId: params.sourceId || inferredDatabase?.sourceId || inferredRedis?.sourceId || null,
167
+ databaseId: params.databaseId || inferredDatabase?.databaseId || null,
168
+ env: params.env || sourceInference?.environment || null
152
169
  },
153
170
  metadata: {
154
- input
171
+ input,
172
+ sourceInference: summarizeSourceInference(sourceInference)
155
173
  },
156
174
  keywords
157
175
  };
@@ -231,8 +249,8 @@ function resolveProviderInfo(params) {
231
249
  return {};
232
250
  }
233
251
 
234
- function inferBusiness(input, params, inferred = {}) {
235
- const explicit = params.business || params.project || params.namespace;
252
+ function inferBusiness(input, params, inferred = {}, sourceInference = null) {
253
+ const explicit = params.business || params.project || sourceInference?.project || params.namespace;
236
254
  if (explicit) return explicit;
237
255
 
238
256
  const keywordMatch = String(input || '').match(/(?:绑定|bind)\s*([\u4e00-\u9fa5A-Za-z0-9._-]+?)\s*(平台|服务|环境|工程|项目)/i);
@@ -259,3 +277,28 @@ function extractKeywords(input, aliases = []) {
259
277
  function escapeRegex(value) {
260
278
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
261
279
  }
280
+
281
+ async function safeInferSourceContext(input) {
282
+ try {
283
+ return await inferSourceContext(input);
284
+ } catch (error) {
285
+ return null;
286
+ }
287
+ }
288
+
289
+ function summarizeSourceInference(sourceInference) {
290
+ if (!sourceInference?.matched) {
291
+ return sourceInference?.environment
292
+ ? { matched: false, environment: sourceInference.environment }
293
+ : null;
294
+ }
295
+
296
+ return {
297
+ matched: true,
298
+ project: sourceInference.project,
299
+ environment: sourceInference.environment,
300
+ database: sourceInference.database?.database || null,
301
+ redisDatabase: sourceInference.redis?.database ?? null,
302
+ matchedEntries: sourceInference.matchedEntries?.map((entry) => entry.title) || []
303
+ };
304
+ }
@@ -10,7 +10,9 @@ module.exports = {
10
10
  },
11
11
 
12
12
  async execute(input, context) {
13
- const apiDoc = context.componentRegistry?.getAdapter('apiDoc');
13
+ const apiDoc = context.componentRegistry?.getAdapter('apiDoc')
14
+ || context.componentRegistry?.getAdapterByProvider?.('apiDoc', 'yapi')
15
+ || null;
14
16
  const learnedBinding = context.resourceBinding?.componentType === 'apiDoc'
15
17
  ? context.resourceBinding
16
18
  : null;
@@ -26,17 +28,38 @@ module.exports = {
26
28
  };
27
29
  }
28
30
 
31
+ if (!apiDoc) {
32
+ return {
33
+ type: 'result',
34
+ skill: 'yapi-sync-interface',
35
+ message: 'YApi adapter is not available in the current registry.',
36
+ data: {
37
+ hint: 'Make sure the apiDoc component is configured and loaded',
38
+ binding: learnedBinding
39
+ },
40
+ input,
41
+ timestamp: new Date().toISOString()
42
+ };
43
+ }
44
+
29
45
  const params = parseYApiParams(input);
30
46
 
31
47
  if (params.action === 'search') {
48
+ const args = {
49
+ projectKeyword: params.project || learnedBinding?.connection?.project,
50
+ nameKeyword: params.keyword,
51
+ limit: 20
52
+ };
53
+ const execution = await apiDoc.searchApis(args);
32
54
  return {
33
55
  type: 'result',
34
56
  skill: 'yapi-sync-interface',
35
57
  message: `Searching YApi for: ${params.keyword || 'all'}`,
36
58
  data: {
37
- mcpTool: 'yapi_search_apis',
38
- args: { projectKeyword: params.project || learnedBinding?.connection?.project, nameKeyword: params.keyword, limit: 20 },
39
- binding: learnedBinding
59
+ action: 'search',
60
+ args,
61
+ binding: learnedBinding,
62
+ execution: summarizeYApiExecution('search', execution)
40
63
  },
41
64
  input,
42
65
  timestamp: new Date().toISOString()
@@ -44,14 +67,17 @@ module.exports = {
44
67
  }
45
68
 
46
69
  if (params.action === 'list') {
70
+ const projectId = params.project || learnedBinding?.connection?.project;
71
+ const execution = await apiDoc.getCategories(projectId);
47
72
  return {
48
73
  type: 'result',
49
74
  skill: 'yapi-sync-interface',
50
- message: `Listing YApi categories for project: ${params.project || learnedBinding?.connection?.project || 'default'}`,
75
+ message: `Listing YApi categories for project: ${projectId || 'default'}`,
51
76
  data: {
52
- mcpTool: 'yapi_get_categories',
53
- args: { projectId: params.project || learnedBinding?.connection?.project },
54
- binding: learnedBinding
77
+ action: 'list',
78
+ args: { projectId },
79
+ binding: learnedBinding,
80
+ execution: summarizeYApiExecution('list', execution)
55
81
  },
56
82
  input,
57
83
  timestamp: new Date().toISOString()
@@ -59,14 +85,17 @@ module.exports = {
59
85
  }
60
86
 
61
87
  if (params.action === 'export') {
88
+ const projectId = params.project || learnedBinding?.connection?.project;
89
+ const execution = await apiDoc.exportProject(projectId, params.format || 'swagger');
62
90
  return {
63
91
  type: 'result',
64
92
  skill: 'yapi-sync-interface',
65
- message: `Exporting YApi project: ${params.project || learnedBinding?.connection?.project || 'default'}`,
93
+ message: `Exporting YApi project: ${projectId || 'default'}`,
66
94
  data: {
67
- mcpTool: 'yapi_export_project',
68
- args: { projectId: params.project || learnedBinding?.connection?.project, type: params.format || 'swagger' },
69
- binding: learnedBinding
95
+ action: 'export',
96
+ args: { projectId, type: params.format || 'swagger' },
97
+ binding: learnedBinding,
98
+ execution: summarizeYApiExecution('export', execution)
70
99
  },
71
100
  input,
72
101
  timestamp: new Date().toISOString()
@@ -98,7 +127,7 @@ function parseYApiParams(input) {
98
127
  const projectMatch = input.match(/(?:项目|project)\s*[:=]?\s*(\S+)/i);
99
128
  if (projectMatch) params.project = projectMatch[1];
100
129
 
101
- const keywordMatch = input.match(/(?:关键词|keyword|搜索|名称|name)\s*[:=]?\s*(.+?)(?:\s*$)/i);
130
+ const keywordMatch = input.match(/(?:关键词|keyword|名称|name)\s*[:=]?\s*(.+?)(?=\s+(?:项目|project|格式|format)\s*[:=]|\s*$)/i);
102
131
  if (keywordMatch) params.keyword = keywordMatch[1].trim();
103
132
 
104
133
  const formatMatch = input.match(/(?:格式|format)\s*[:=]?\s*(json|markdown|swagger)/i);
@@ -106,3 +135,107 @@ function parseYApiParams(input) {
106
135
 
107
136
  return params;
108
137
  }
138
+
139
+ function summarizeYApiExecution(action, execution) {
140
+ const payload = unwrapPayload(execution);
141
+ const data = getPayloadData(payload);
142
+ const items = extractListItems(data);
143
+
144
+ return {
145
+ action,
146
+ status: extractStatus(data) || extractStatus(payload) || 'ok',
147
+ total: extractTotal(data, payload, items),
148
+ items: items.slice(0, 5).map(summarizeItem),
149
+ runId: extractRunId(data) || extractRunId(payload) || null
150
+ };
151
+ }
152
+
153
+ function unwrapPayload(payload) {
154
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
155
+ return payload;
156
+ }
157
+
158
+ const textPayload = extractContentText(payload);
159
+ if (textPayload) {
160
+ return parseMaybeJson(textPayload);
161
+ }
162
+
163
+ if ('data' in payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
164
+ return payload.data;
165
+ }
166
+
167
+ return payload;
168
+ }
169
+
170
+ function getPayloadData(payload) {
171
+ return payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
172
+ }
173
+
174
+ function extractListItems(data) {
175
+ const candidates = [data.list, data.items, data.records, data.result, data.data];
176
+ for (const candidate of candidates) {
177
+ if (Array.isArray(candidate)) {
178
+ return candidate;
179
+ }
180
+ }
181
+
182
+ return [];
183
+ }
184
+
185
+ function extractStatus(payload) {
186
+ if (!payload || typeof payload !== 'object') {
187
+ return null;
188
+ }
189
+
190
+ if (typeof payload.status === 'string') {
191
+ return payload.status;
192
+ }
193
+
194
+ if (payload.success === false) {
195
+ return 'error';
196
+ }
197
+
198
+ return typeof payload.code === 'string' ? payload.code : null;
199
+ }
200
+
201
+ function extractTotal(data, payload, items) {
202
+ return data.total || payload.total || items.length || null;
203
+ }
204
+
205
+ function extractRunId(payload) {
206
+ if (!payload || typeof payload !== 'object') {
207
+ return null;
208
+ }
209
+
210
+ return payload.runId || payload.id || payload.requestId || null;
211
+ }
212
+
213
+ function summarizeItem(item) {
214
+ if (!item || typeof item !== 'object') {
215
+ return item;
216
+ }
217
+
218
+ return {
219
+ id: item._id || item.id || item.api_id || item.catid || null,
220
+ name: item.name || item.title || item.path || item.url || null,
221
+ method: item.method || item.req_method || null
222
+ };
223
+ }
224
+
225
+ function extractContentText(payload) {
226
+ const content = Array.isArray(payload.content) ? payload.content : [];
227
+ const textItem = content.find((item) => item && typeof item.text === 'string');
228
+ return textItem ? textItem.text : null;
229
+ }
230
+
231
+ function parseMaybeJson(value) {
232
+ if (typeof value !== 'string') {
233
+ return value;
234
+ }
235
+
236
+ try {
237
+ return JSON.parse(value);
238
+ } catch (error) {
239
+ return { text: value };
240
+ }
241
+ }