@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.1-dev.pr156.0
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/dist/api/generated/api.d.ts +34 -34
- package/dist/api/generated/api.js +9 -0
- package/dist/assets/docker/docker-compose-dev.yml +2 -1
- package/dist/commands/workflow/debug.d.ts +9 -3
- package/dist/commands/workflow/debug.js +167 -25
- package/dist/commands/workflow/debug.test.js +53 -21
- package/dist/utils/s3_downloader.d.ts +49 -0
- package/dist/utils/s3_downloader.js +154 -0
- package/dist/utils/trace_formatter.d.ts +61 -27
- package/dist/utils/trace_formatter.js +239 -447
- package/package.json +1 -1
- package/dist/services/trace_reader.d.ts +0 -8
- package/dist/services/trace_reader.js +0 -51
- package/dist/services/trace_reader.test.d.ts +0 -1
- package/dist/services/trace_reader.test.js +0 -163
|
@@ -1,465 +1,257 @@
|
|
|
1
1
|
import Table from 'cli-table3';
|
|
2
2
|
import { formatOutput } from '#utils/output_formatter.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
const minutes = Math.floor(ms / 60000);
|
|
14
|
-
const seconds = ((ms % 60000) / 1000).toFixed(0);
|
|
15
|
-
return `${minutes}m ${seconds}s`;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Format error for display
|
|
19
|
-
*/
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
-
function formatError(error) {
|
|
22
|
-
if (typeof error === 'string') {
|
|
23
|
-
return error;
|
|
24
|
-
}
|
|
25
|
-
if (error.message) {
|
|
26
|
-
return error.message;
|
|
27
|
-
}
|
|
28
|
-
return JSON.stringify(error);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Truncate long values for display
|
|
32
|
-
*/
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
-
function truncateValue(value, maxLength = 50) {
|
|
35
|
-
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
36
|
-
if (str.length <= maxLength) {
|
|
37
|
-
return str;
|
|
38
|
-
}
|
|
39
|
-
return str.substring(0, maxLength) + '...';
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Get a readable name for an event
|
|
43
|
-
*/
|
|
44
|
-
function getEventName(event) {
|
|
45
|
-
if (event.kind === 'workflow') {
|
|
46
|
-
return `Workflow: ${event.workflowName}`;
|
|
47
|
-
}
|
|
48
|
-
if (event.kind === 'activity') {
|
|
49
|
-
return `Activity: ${event.details?.activityName || 'unknown'}`;
|
|
50
|
-
}
|
|
51
|
-
if (event.kind === 'step') {
|
|
52
|
-
return `Step: ${event.details?.stepName || event.details?.name || 'unknown'}`;
|
|
53
|
-
}
|
|
54
|
-
return event.kind || 'Unknown Event';
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Format the phase
|
|
58
|
-
*/
|
|
59
|
-
function formatPhase(phase) {
|
|
60
|
-
switch (phase) {
|
|
61
|
-
case 'start':
|
|
62
|
-
return '[START]';
|
|
63
|
-
case 'end':
|
|
64
|
-
return '[END]';
|
|
65
|
-
case 'error':
|
|
66
|
-
return '[ERROR]';
|
|
67
|
-
default:
|
|
68
|
-
return phase;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Format details for table display
|
|
73
|
-
*/
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
-
function formatDetails(details) {
|
|
76
|
-
if (!details) {
|
|
77
|
-
return '-';
|
|
78
|
-
}
|
|
79
|
-
if (typeof details === 'string') {
|
|
80
|
-
return details;
|
|
81
|
-
}
|
|
82
|
-
// Extract key information
|
|
83
|
-
const info = [];
|
|
84
|
-
if (details.input) {
|
|
85
|
-
info.push(`Input: ${truncateValue(details.input)}`);
|
|
86
|
-
}
|
|
87
|
-
if (details.output) {
|
|
88
|
-
info.push(`Output: ${truncateValue(details.output)}`);
|
|
89
|
-
}
|
|
90
|
-
if (details.activityName) {
|
|
91
|
-
info.push(`Activity: ${details.activityName}`);
|
|
92
|
-
}
|
|
93
|
-
if (details.stepName || details.name) {
|
|
94
|
-
info.push(`Step: ${details.stepName || details.name}`);
|
|
95
|
-
}
|
|
96
|
-
return info.length > 0 ? info.join(', ') : JSON.stringify(details).substring(0, 50) + '...';
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Format details for tree display
|
|
100
|
-
*/
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
-
function formatTreeDetails(details, depth) {
|
|
103
|
-
const lines = [];
|
|
104
|
-
const indent = ' '.repeat(depth);
|
|
105
|
-
// Only show key details
|
|
106
|
-
if (details.input && details.input !== null) {
|
|
107
|
-
lines.push(`${indent}Input: ${truncateValue(details.input)}`);
|
|
108
|
-
}
|
|
109
|
-
if (details.output && details.output !== null) {
|
|
110
|
-
lines.push(`${indent}Output: ${truncateValue(details.output)}`);
|
|
111
|
-
}
|
|
112
|
-
return lines.join('\n');
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Format the header with workflow information
|
|
116
|
-
*/
|
|
117
|
-
function formatHeader(root) {
|
|
118
|
-
const lines = [];
|
|
119
|
-
lines.push('═'.repeat(60));
|
|
120
|
-
lines.push(`Workflow: ${root.workflowName}`);
|
|
121
|
-
lines.push(`Workflow ID: ${root.workflowId}`);
|
|
122
|
-
lines.push(`Start Time: ${new Date(root.timestamp).toISOString()}`);
|
|
123
|
-
if (root.duration) {
|
|
124
|
-
lines.push(`Duration: ${formatDuration(root.duration)}`);
|
|
125
|
-
}
|
|
126
|
-
if (root.phase === 'error' && root.error) {
|
|
127
|
-
lines.push('Status: Failed');
|
|
128
|
-
lines.push(`Error: ${formatError(root.error)}`);
|
|
129
|
-
}
|
|
130
|
-
else if (root.phase === 'end') {
|
|
131
|
-
lines.push('Status: Completed');
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
lines.push('Status: In Progress');
|
|
135
|
-
}
|
|
136
|
-
lines.push('═'.repeat(60));
|
|
137
|
-
return lines.join('\n');
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Format events as a timeline table
|
|
141
|
-
*/
|
|
142
|
-
function formatEventsTable(events) {
|
|
143
|
-
const table = new Table({
|
|
144
|
-
head: ['Time', 'Event', 'Phase', 'Duration', 'Details'],
|
|
145
|
-
style: {
|
|
146
|
-
head: ['cyan']
|
|
147
|
-
},
|
|
148
|
-
colWidths: [20, 25, 10, 12, null],
|
|
149
|
-
wordWrap: true
|
|
150
|
-
});
|
|
151
|
-
for (const event of events) {
|
|
152
|
-
const time = new Date(event.timestamp).toISOString().substring(11, 23);
|
|
153
|
-
const eventName = getEventName(event);
|
|
154
|
-
const phase = formatPhase(event.phase);
|
|
155
|
-
const duration = event.duration ? formatDuration(event.duration) : '-';
|
|
156
|
-
const details = formatDetails(event.details);
|
|
157
|
-
table.push([time, eventName, phase, duration, details]);
|
|
158
|
-
}
|
|
159
|
-
return table.toString();
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Format trace as a tree structure
|
|
163
|
-
*/
|
|
164
|
-
function formatTree(node, depth) {
|
|
165
|
-
const lines = [];
|
|
166
|
-
const indent = ' '.repeat(depth);
|
|
167
|
-
const marker = depth === 0 ? '' : '├─';
|
|
168
|
-
// Format current node
|
|
169
|
-
const nodeName = getEventName(node);
|
|
170
|
-
const phase = formatPhase(node.phase);
|
|
171
|
-
const duration = node.duration ? ` (${formatDuration(node.duration)})` : '';
|
|
172
|
-
lines.push(`${indent}${marker} ${nodeName} ${phase}${duration}`);
|
|
173
|
-
// Add error details if present
|
|
174
|
-
if (node.error) {
|
|
175
|
-
lines.push(`${indent} └─ ERROR: ${formatError(node.error)}`);
|
|
176
|
-
}
|
|
177
|
-
// Add important details
|
|
178
|
-
if (node.details && typeof node.details === 'object') {
|
|
179
|
-
const detailLines = formatTreeDetails(node.details, depth + 1);
|
|
180
|
-
if (detailLines) {
|
|
181
|
-
lines.push(detailLines);
|
|
3
|
+
export class TraceFormatter {
|
|
4
|
+
/**
|
|
5
|
+
* Format trace data based on the requested format
|
|
6
|
+
*/
|
|
7
|
+
format(traceData, format = 'text') {
|
|
8
|
+
// Parse if string
|
|
9
|
+
const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
|
|
10
|
+
if (format === 'json') {
|
|
11
|
+
return formatOutput(trace, 'json');
|
|
182
12
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
13
|
+
// Format as human-readable text
|
|
14
|
+
return this.formatAsText(trace);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Format trace as human-readable text with tree structure
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
formatAsText(trace) {
|
|
21
|
+
const output = [];
|
|
22
|
+
// Add header with workflow info
|
|
23
|
+
if (trace.root) {
|
|
24
|
+
output.push(this.formatHeader(trace.root));
|
|
25
|
+
output.push('');
|
|
188
26
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
output.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
27
|
+
// Create execution timeline table
|
|
28
|
+
if (trace.events && trace.events.length > 0) {
|
|
29
|
+
output.push('Execution Timeline:');
|
|
30
|
+
output.push(this.formatEventsTable(trace.events));
|
|
31
|
+
output.push('');
|
|
32
|
+
}
|
|
33
|
+
// Show tree structure
|
|
34
|
+
if (trace.root) {
|
|
35
|
+
output.push('Execution Tree:');
|
|
36
|
+
output.push(this.formatTree(trace.root, 0));
|
|
37
|
+
}
|
|
38
|
+
return output.join('\n');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format the header with workflow information
|
|
42
|
+
*/
|
|
43
|
+
formatHeader(root) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
lines.push('═'.repeat(60));
|
|
46
|
+
lines.push(`Workflow: ${root.workflowName}`);
|
|
47
|
+
lines.push(`Workflow ID: ${root.workflowId}`);
|
|
48
|
+
lines.push(`Start Time: ${new Date(root.timestamp).toISOString()}`);
|
|
49
|
+
if (root.duration) {
|
|
50
|
+
lines.push(`Duration: ${this.formatDuration(root.duration)}`);
|
|
51
|
+
}
|
|
52
|
+
if (root.phase === 'error' && root.error) {
|
|
53
|
+
lines.push('Status: ❌ Failed');
|
|
54
|
+
lines.push(`Error: ${this.formatError(root.error)}`);
|
|
55
|
+
}
|
|
56
|
+
else if (root.phase === 'end') {
|
|
57
|
+
lines.push('Status: ✅ Completed');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lines.push('Status: 🔄 In Progress');
|
|
61
|
+
}
|
|
62
|
+
lines.push('═'.repeat(60));
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Format events as a timeline table
|
|
67
|
+
*/
|
|
68
|
+
formatEventsTable(events) {
|
|
69
|
+
const table = new Table({
|
|
70
|
+
head: ['Time', 'Event', 'Phase', 'Duration', 'Details'],
|
|
71
|
+
style: {
|
|
72
|
+
head: ['cyan']
|
|
73
|
+
},
|
|
74
|
+
colWidths: [20, 25, 10, 12, null],
|
|
75
|
+
wordWrap: true
|
|
76
|
+
});
|
|
77
|
+
for (const event of events) {
|
|
78
|
+
const time = new Date(event.timestamp).toISOString().substring(11, 23);
|
|
79
|
+
const eventName = this.getEventName(event);
|
|
80
|
+
const phase = this.formatPhase(event.phase);
|
|
81
|
+
const duration = event.duration ? this.formatDuration(event.duration) : '-';
|
|
82
|
+
const details = this.formatDetails(event.details);
|
|
83
|
+
table.push([time, eventName, phase, duration, details]);
|
|
84
|
+
}
|
|
85
|
+
return table.toString();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Format trace as a tree structure
|
|
89
|
+
*/
|
|
90
|
+
formatTree(node, depth) {
|
|
91
|
+
const lines = [];
|
|
92
|
+
const indent = ' '.repeat(depth);
|
|
93
|
+
const marker = depth === 0 ? '🌳' : '├─';
|
|
94
|
+
// Format current node
|
|
95
|
+
const nodeName = this.getEventName(node);
|
|
96
|
+
const phase = this.formatPhase(node.phase);
|
|
97
|
+
const duration = node.duration ? ` (${this.formatDuration(node.duration)})` : '';
|
|
98
|
+
lines.push(`${indent}${marker} ${nodeName} ${phase}${duration}`);
|
|
99
|
+
// Add error details if present
|
|
100
|
+
if (node.error) {
|
|
101
|
+
lines.push(`${indent} └─ ❌ ${this.formatError(node.error)}`);
|
|
102
|
+
}
|
|
103
|
+
// Add important details
|
|
104
|
+
if (node.details && typeof node.details === 'object') {
|
|
105
|
+
const detailLines = this.formatTreeDetails(node.details, depth + 1);
|
|
106
|
+
if (detailLines) {
|
|
107
|
+
lines.push(detailLines);
|
|
247
108
|
}
|
|
248
|
-
|
|
249
|
-
|
|
109
|
+
}
|
|
110
|
+
// Process children
|
|
111
|
+
if (node.children && node.children.length > 0) {
|
|
112
|
+
for (const child of node.children) {
|
|
113
|
+
lines.push(this.formatTree(child, depth + 1));
|
|
250
114
|
}
|
|
251
115
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Format value for debug detail display
|
|
263
|
-
*/
|
|
264
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
265
|
-
function formatDebugDetailValue(value) {
|
|
266
|
-
if (value === null) {
|
|
267
|
-
return 'null';
|
|
268
|
-
}
|
|
269
|
-
if (value === undefined) {
|
|
270
|
-
return 'undefined';
|
|
271
|
-
}
|
|
272
|
-
if (typeof value === 'string') {
|
|
273
|
-
// For short strings, show inline
|
|
274
|
-
if (value.length <= 60) {
|
|
275
|
-
return value;
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get a readable name for an event
|
|
120
|
+
*/
|
|
121
|
+
getEventName(event) {
|
|
122
|
+
if (event.kind === 'workflow') {
|
|
123
|
+
return `Workflow: ${event.workflowName}`;
|
|
276
124
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
281
|
-
return String(value);
|
|
282
|
-
}
|
|
283
|
-
if (Array.isArray(value)) {
|
|
284
|
-
if (value.length === 0) {
|
|
285
|
-
return '[]';
|
|
125
|
+
if (event.kind === 'activity') {
|
|
126
|
+
return `Activity: ${event.details?.activityName || 'unknown'}`;
|
|
286
127
|
}
|
|
287
|
-
if (
|
|
288
|
-
return `
|
|
128
|
+
if (event.kind === 'step') {
|
|
129
|
+
return `Step: ${event.details?.stepName || event.details?.name || 'unknown'}`;
|
|
130
|
+
}
|
|
131
|
+
return event.kind || 'Unknown Event';
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format the phase with icons
|
|
135
|
+
*/
|
|
136
|
+
formatPhase(phase) {
|
|
137
|
+
switch (phase) {
|
|
138
|
+
case 'start':
|
|
139
|
+
return '▶️';
|
|
140
|
+
case 'end':
|
|
141
|
+
return '✅';
|
|
142
|
+
case 'error':
|
|
143
|
+
return '❌';
|
|
144
|
+
default:
|
|
145
|
+
return phase;
|
|
289
146
|
}
|
|
290
|
-
return `[Array with ${value.length} items]`;
|
|
291
147
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Format duration in human-readable format
|
|
150
|
+
*/
|
|
151
|
+
formatDuration(ms) {
|
|
152
|
+
if (ms < 1000) {
|
|
153
|
+
return `${ms}ms`;
|
|
296
154
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const pairs = keys.map(k => {
|
|
300
|
-
const v = value[k];
|
|
301
|
-
if (typeof v === 'string' && v.length > 30) {
|
|
302
|
-
return `${k}: "${v.substring(0, 30)}..."`;
|
|
303
|
-
}
|
|
304
|
-
if (typeof v === 'object') {
|
|
305
|
-
return `${k}: {...}`;
|
|
306
|
-
}
|
|
307
|
-
return `${k}: ${JSON.stringify(v)}`;
|
|
308
|
-
});
|
|
309
|
-
return `{ ${pairs.join(', ')} }`;
|
|
155
|
+
if (ms < 60000) {
|
|
156
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
310
157
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return node;
|
|
323
|
-
}
|
|
324
|
-
if (typeof node !== 'object') {
|
|
325
|
-
return String(node);
|
|
326
|
-
}
|
|
327
|
-
const parts = [];
|
|
328
|
-
// Look for common identifying properties
|
|
329
|
-
if (node.kind) {
|
|
330
|
-
parts.push(`[${node.kind}]`);
|
|
331
|
-
}
|
|
332
|
-
else if (node.type) {
|
|
333
|
-
parts.push(`[${node.type}]`);
|
|
334
|
-
}
|
|
335
|
-
if (node.name) {
|
|
336
|
-
parts.push(node.name);
|
|
337
|
-
}
|
|
338
|
-
else if (node.workflowName) {
|
|
339
|
-
parts.push(node.workflowName);
|
|
340
|
-
}
|
|
341
|
-
else if (node.stepName) {
|
|
342
|
-
parts.push(node.stepName);
|
|
343
|
-
}
|
|
344
|
-
else if (node.activityName) {
|
|
345
|
-
parts.push(node.activityName);
|
|
346
|
-
}
|
|
347
|
-
// Add phase/status indicators
|
|
348
|
-
if (node.phase === 'error' || node.status === 'failed') {
|
|
349
|
-
parts.push('[FAILED]');
|
|
350
|
-
}
|
|
351
|
-
else if (node.phase === 'end' || node.status === 'completed') {
|
|
352
|
-
parts.push('[COMPLETED]');
|
|
353
|
-
}
|
|
354
|
-
else if (node.status === 'running') {
|
|
355
|
-
parts.push('[RUNNING]');
|
|
356
|
-
}
|
|
357
|
-
// If we have no meaningful parts, show a summary of the object
|
|
358
|
-
if (parts.length === 0) {
|
|
359
|
-
const keys = Object.keys(node).filter(k => k !== 'children' && k !== 'parent');
|
|
360
|
-
if (keys.length > 0) {
|
|
361
|
-
return `Node {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', ...' : ''}}`;
|
|
158
|
+
const minutes = Math.floor(ms / 60000);
|
|
159
|
+
const seconds = ((ms % 60000) / 1000).toFixed(0);
|
|
160
|
+
return `${minutes}m ${seconds}s`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Format error for display
|
|
164
|
+
*/
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
+
formatError(error) {
|
|
167
|
+
if (typeof error === 'string') {
|
|
168
|
+
return error;
|
|
362
169
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return parts.join(' ');
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Add debug node details to lines array
|
|
369
|
-
*/
|
|
370
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
371
|
-
function addDebugNodeDetails(node, prefix, lines) {
|
|
372
|
-
if (typeof node !== 'object' || node === null) {
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const details = [];
|
|
376
|
-
// Add timing information
|
|
377
|
-
if (node.startedAt || node.timestamp) {
|
|
378
|
-
const startTime = node.startedAt || node.timestamp;
|
|
379
|
-
const startDate = new Date(startTime);
|
|
380
|
-
if (!isNaN(startDate.getTime())) {
|
|
381
|
-
details.push(`${prefix}Started: ${startDate.toISOString()}`);
|
|
170
|
+
if (error.message) {
|
|
171
|
+
return error.message;
|
|
382
172
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
173
|
+
return JSON.stringify(error);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Format details for table display
|
|
177
|
+
*/
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
+
formatDetails(details) {
|
|
180
|
+
if (!details) {
|
|
181
|
+
return '-';
|
|
388
182
|
}
|
|
183
|
+
if (typeof details === 'string') {
|
|
184
|
+
return details;
|
|
185
|
+
}
|
|
186
|
+
// Extract key information
|
|
187
|
+
const info = [];
|
|
188
|
+
if (details.input) {
|
|
189
|
+
info.push(`Input: ${this.truncateValue(details.input)}`);
|
|
190
|
+
}
|
|
191
|
+
if (details.output) {
|
|
192
|
+
info.push(`Output: ${this.truncateValue(details.output)}`);
|
|
193
|
+
}
|
|
194
|
+
if (details.activityName) {
|
|
195
|
+
info.push(`Activity: ${details.activityName}`);
|
|
196
|
+
}
|
|
197
|
+
if (details.stepName || details.name) {
|
|
198
|
+
info.push(`Step: ${details.stepName || details.name}`);
|
|
199
|
+
}
|
|
200
|
+
return info.length > 0 ? info.join(', ') : JSON.stringify(details).substring(0, 50) + '...';
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Format details for tree display
|
|
204
|
+
*/
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
206
|
+
formatTreeDetails(details, depth) {
|
|
207
|
+
const lines = [];
|
|
208
|
+
const indent = ' '.repeat(depth);
|
|
209
|
+
// Only show key details
|
|
210
|
+
if (details.input && details.input !== null) {
|
|
211
|
+
lines.push(`${indent}📥 Input: ${this.truncateValue(details.input)}`);
|
|
212
|
+
}
|
|
213
|
+
if (details.output && details.output !== null) {
|
|
214
|
+
lines.push(`${indent}📤 Output: ${this.truncateValue(details.output)}`);
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Truncate long values for display
|
|
220
|
+
*/
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
+
truncateValue(value, maxLength = 50) {
|
|
223
|
+
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
224
|
+
if (str.length <= maxLength) {
|
|
225
|
+
return str;
|
|
226
|
+
}
|
|
227
|
+
return str.substring(0, maxLength) + '...';
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get summary statistics from trace
|
|
231
|
+
*/
|
|
232
|
+
getSummary(traceData) {
|
|
233
|
+
const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
|
|
234
|
+
const stats = {
|
|
235
|
+
totalDuration: trace.root?.duration || 0,
|
|
236
|
+
totalEvents: trace.events?.length || 0,
|
|
237
|
+
totalSteps: 0,
|
|
238
|
+
totalActivities: 0,
|
|
239
|
+
hasErrors: false
|
|
240
|
+
};
|
|
241
|
+
if (trace.events) {
|
|
242
|
+
for (const event of trace.events) {
|
|
243
|
+
if (event.kind === 'step') {
|
|
244
|
+
stats.totalSteps++;
|
|
245
|
+
}
|
|
246
|
+
if (event.kind === 'activity') {
|
|
247
|
+
stats.totalActivities++;
|
|
248
|
+
}
|
|
249
|
+
if (event.phase === 'error') {
|
|
250
|
+
stats.hasErrors = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return stats;
|
|
389
255
|
}
|
|
390
|
-
// Calculate and show duration
|
|
391
|
-
if (node.startedAt && node.endedAt) {
|
|
392
|
-
const duration = node.endedAt - node.startedAt;
|
|
393
|
-
details.push(`${prefix}Duration: ${formatDuration(duration)}`);
|
|
394
|
-
}
|
|
395
|
-
else if (node.duration) {
|
|
396
|
-
details.push(`${prefix}Duration: ${formatDuration(node.duration)}`);
|
|
397
|
-
}
|
|
398
|
-
// Show input
|
|
399
|
-
if (node.input !== undefined && node.input !== null) {
|
|
400
|
-
const inputStr = formatDebugDetailValue(node.input);
|
|
401
|
-
details.push(`${prefix}Input: ${inputStr}`);
|
|
402
|
-
}
|
|
403
|
-
// Show output
|
|
404
|
-
if (node.output !== undefined && node.output !== null) {
|
|
405
|
-
const outputStr = formatDebugDetailValue(node.output);
|
|
406
|
-
details.push(`${prefix}Output: ${outputStr}`);
|
|
407
|
-
}
|
|
408
|
-
// Show error if present
|
|
409
|
-
if (node.error) {
|
|
410
|
-
const errorMsg = typeof node.error === 'string' ? node.error : (node.error.message || JSON.stringify(node.error));
|
|
411
|
-
details.push(`${prefix}Error: ${errorMsg}`);
|
|
412
|
-
}
|
|
413
|
-
// Add all details to lines
|
|
414
|
-
for (const detail of details) {
|
|
415
|
-
lines.push(detail);
|
|
416
|
-
}
|
|
417
|
-
// Add spacing if we had details
|
|
418
|
-
if (details.length > 0) {
|
|
419
|
-
lines.push('');
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Build debug tree lines recursively
|
|
424
|
-
*/
|
|
425
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
-
function buildDebugTreeLines(node, depth, isLast, prefix, lines) {
|
|
427
|
-
if (node === null || node === undefined) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
// Create the tree structure characters
|
|
431
|
-
const isRoot = depth === 0;
|
|
432
|
-
const connector = isRoot ? '' : getConnector(isLast);
|
|
433
|
-
const indent = isRoot ? '' : prefix + connector;
|
|
434
|
-
// Build the node display string
|
|
435
|
-
const nodeInfo = getDebugNodeInfo(node);
|
|
436
|
-
lines.push(indent + nodeInfo);
|
|
437
|
-
// Display additional details with proper indentation
|
|
438
|
-
const detailPrefix = isRoot ? ' ' : prefix + (isLast ? ' ' : '│ ');
|
|
439
|
-
addDebugNodeDetails(node, detailPrefix, lines);
|
|
440
|
-
// Update prefix for children
|
|
441
|
-
const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
|
|
442
|
-
// Process children if they exist
|
|
443
|
-
if (node.children && Array.isArray(node.children)) {
|
|
444
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
445
|
-
node.children.forEach((child, i) => {
|
|
446
|
-
const isLastChild = i === node.children.length - 1;
|
|
447
|
-
buildDebugTreeLines(child, depth + 1, isLastChild, childPrefix, lines);
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Display trace tree with debug command formatting style
|
|
453
|
-
*/
|
|
454
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
|
-
export function displayDebugTree(node) {
|
|
456
|
-
const lines = [];
|
|
457
|
-
buildDebugTreeLines(node, 0, false, '', lines);
|
|
458
|
-
return lines.join('\n');
|
|
459
256
|
}
|
|
460
|
-
|
|
461
|
-
export const traceFormatter = {
|
|
462
|
-
format,
|
|
463
|
-
getSummary,
|
|
464
|
-
displayDebugTree
|
|
465
|
-
};
|
|
257
|
+
export const traceFormatter = new TraceFormatter();
|