flowmind 1.5.1 → 1.5.3
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 +9 -0
- package/core/adapters/mcp-adapter.js +25 -0
- package/core/adapters/workflow-adapter.js +26 -0
- package/core/component-registry.js +19 -1
- package/core/config-manager.js +7 -2
- package/core/index.js +14 -1
- package/core/mcp-http-client.js +63 -0
- package/core/providers/aliyun/rds-query-adapter.js +70 -0
- package/core/providers/friday/flow-adapter.js +19 -30
- package/core/sdd-agent-sync.js +341 -17
- package/core/source-inference.js +324 -0
- package/package.json +1 -1
- package/skills/auto-flow/index.js +528 -52
- package/skills/data-logic-validation/index.js +133 -12
- package/skills/resource-bind/index.js +61 -18
- package/tui/app.jsx +2 -1
- package/tui/format-result.js +43 -4
|
@@ -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
|
|
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
|
|
50
|
+
message: `SQL validation executed for: ${target.project || target.database || 'resolved target'}`,
|
|
44
51
|
data: {
|
|
45
52
|
action: 'validate_sql',
|
|
46
|
-
query:
|
|
47
|
-
|
|
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 (!
|
|
66
|
+
if (!dbQuery) {
|
|
58
67
|
return {
|
|
59
68
|
type: 'result',
|
|
60
69
|
skill: 'data-logic-validation',
|
|
61
|
-
message: 'Redis
|
|
62
|
-
data: { availableComponents, hint: 'Configure
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
|
132
|
-
const
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
package/tui/app.jsx
CHANGED
|
@@ -6,6 +6,7 @@ const StatusBar = require('./components/StatusBar.jsx');
|
|
|
6
6
|
const { formatResultText } = require('./format-result');
|
|
7
7
|
|
|
8
8
|
function App({ flowmind, asciiMode = false }) {
|
|
9
|
+
const MAX_MESSAGES = 60;
|
|
9
10
|
const [messages, setMessages] = React.useState([]);
|
|
10
11
|
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
11
12
|
const [focusPanel, setFocusPanel] = React.useState('chat'); // 'chat' | 'sidebar'
|
|
@@ -19,7 +20,7 @@ function App({ flowmind, asciiMode = false }) {
|
|
|
19
20
|
|
|
20
21
|
const appendMessage = React.useCallback((role, text, metadata = {}) => {
|
|
21
22
|
const id = ++messageIdRef.current;
|
|
22
|
-
setMessages(prev => [...prev, { id, role, text, metadata }]);
|
|
23
|
+
setMessages(prev => [...prev, { id, role, text, metadata }].slice(-MAX_MESSAGES));
|
|
23
24
|
}, []);
|
|
24
25
|
|
|
25
26
|
// Ctrl+C always exits; Tab switches focus between panels
|
package/tui/format-result.js
CHANGED
|
@@ -2,6 +2,12 @@ function isPlainObject(value) {
|
|
|
2
2
|
return value && typeof value === 'object' && !Array.isArray(value);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
const MAX_RENDER_LINES = 120;
|
|
6
|
+
const MAX_LINE_LENGTH = 220;
|
|
7
|
+
const MAX_RENDER_CHARS = 8000;
|
|
8
|
+
const MAX_ARRAY_ITEMS = 20;
|
|
9
|
+
const MAX_SCALAR_LENGTH = 160;
|
|
10
|
+
|
|
5
11
|
function unwrapResultPayload(result) {
|
|
6
12
|
let current = result;
|
|
7
13
|
|
|
@@ -58,11 +64,40 @@ function pruneDisplayValue(value) {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
function formatScalar(value) {
|
|
61
|
-
if (typeof value === 'string')
|
|
67
|
+
if (typeof value === 'string') {
|
|
68
|
+
if (value.length <= MAX_SCALAR_LENGTH) return value;
|
|
69
|
+
return `${value.slice(0, MAX_SCALAR_LENGTH - 1)}…`;
|
|
70
|
+
}
|
|
62
71
|
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
63
72
|
return JSON.stringify(value);
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
function sanitizeForTerminal(text) {
|
|
76
|
+
return String(text)
|
|
77
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, ' ')
|
|
78
|
+
.replace(/\r\n/g, '\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function truncateRenderedText(text) {
|
|
82
|
+
const sanitized = sanitizeForTerminal(text);
|
|
83
|
+
const lines = sanitized.split('\n');
|
|
84
|
+
const clippedLines = lines.slice(0, MAX_RENDER_LINES).map((line) => {
|
|
85
|
+
if (line.length <= MAX_LINE_LENGTH) return line;
|
|
86
|
+
return `${line.slice(0, MAX_LINE_LENGTH - 1)}…`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (lines.length > MAX_RENDER_LINES) {
|
|
90
|
+
clippedLines.push(`… truncated ${lines.length - MAX_RENDER_LINES} line(s)`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let output = clippedLines.join('\n');
|
|
94
|
+
if (output.length > MAX_RENDER_CHARS) {
|
|
95
|
+
output = `${output.slice(0, MAX_RENDER_CHARS - 1)}…`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
function renderValueLines(value, indent = '') {
|
|
67
102
|
if (value === undefined) return [];
|
|
68
103
|
|
|
@@ -71,7 +106,11 @@ function renderValueLines(value, indent = '') {
|
|
|
71
106
|
|
|
72
107
|
const allScalars = value.every((item) => !isPlainObject(item) && !Array.isArray(item));
|
|
73
108
|
if (allScalars) {
|
|
74
|
-
|
|
109
|
+
const shown = value.slice(0, MAX_ARRAY_ITEMS).map(formatScalar);
|
|
110
|
+
if (value.length > MAX_ARRAY_ITEMS) {
|
|
111
|
+
shown.push(`… +${value.length - MAX_ARRAY_ITEMS} more`);
|
|
112
|
+
}
|
|
113
|
+
return [indent + shown.join(', ')];
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
const lines = [];
|
|
@@ -150,10 +189,10 @@ function formatResultText(result) {
|
|
|
150
189
|
}
|
|
151
190
|
|
|
152
191
|
if (detailLines.length > 0) {
|
|
153
|
-
return detailLines.join('\n');
|
|
192
|
+
return truncateRenderedText(detailLines.join('\n'));
|
|
154
193
|
}
|
|
155
194
|
|
|
156
|
-
return JSON.stringify(payload, null, 2);
|
|
195
|
+
return truncateRenderedText(JSON.stringify(payload, null, 2));
|
|
157
196
|
}
|
|
158
197
|
|
|
159
198
|
module.exports = {
|