@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.0-dev.pr156.f70e0a1
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 +15 -39
- package/dist/commands/workflow/debug.d.ts +0 -1
- package/dist/commands/workflow/debug.js +4 -8
- package/dist/commands/workflow/debug.test.js +4 -7
- package/dist/services/trace_reader.d.ts +35 -1
- package/dist/services/trace_reader.js +7 -1
- package/dist/utils/trace_formatter.d.ts +22 -6
- package/dist/utils/trace_formatter.js +120 -93
- package/package.json +1 -1
|
@@ -32,20 +32,10 @@ export interface Workflow {
|
|
|
32
32
|
inputSchema?: JSONSchema;
|
|
33
33
|
outputSchema?: JSONSchema;
|
|
34
34
|
}
|
|
35
|
-
export type PostWorkflowRunBody = {
|
|
36
|
-
/** The name of the workflow to execute */
|
|
37
|
-
workflowName: string;
|
|
38
|
-
/** The payload to send to the workflow */
|
|
39
|
-
input: unknown;
|
|
40
|
-
/** (Optional) The workflowId to use. Must be unique */
|
|
41
|
-
workflowId?: string;
|
|
42
|
-
/** The name of the task queue to send the workflow to */
|
|
43
|
-
taskQueue?: string;
|
|
44
|
-
};
|
|
45
35
|
/**
|
|
46
36
|
* File destinations for trace data
|
|
47
37
|
*/
|
|
48
|
-
export type
|
|
38
|
+
export type TraceInfoDestinations = {
|
|
49
39
|
/**
|
|
50
40
|
* Absolute path to local trace file, or null if not saved locally
|
|
51
41
|
* @nullable
|
|
@@ -60,17 +50,26 @@ export type PostWorkflowRun200TraceDestinations = {
|
|
|
60
50
|
/**
|
|
61
51
|
* An object with information about the trace generated by the execution
|
|
62
52
|
*/
|
|
63
|
-
export
|
|
53
|
+
export interface TraceInfo {
|
|
64
54
|
/** File destinations for trace data */
|
|
65
|
-
destinations?:
|
|
55
|
+
destinations?: TraceInfoDestinations;
|
|
56
|
+
}
|
|
57
|
+
export type PostWorkflowRunBody = {
|
|
58
|
+
/** The name of the workflow to execute */
|
|
59
|
+
workflowName: string;
|
|
60
|
+
/** The payload to send to the workflow */
|
|
61
|
+
input: unknown;
|
|
62
|
+
/** (Optional) The workflowId to use. Must be unique */
|
|
63
|
+
workflowId?: string;
|
|
64
|
+
/** The name of the task queue to send the workflow to */
|
|
65
|
+
taskQueue?: string;
|
|
66
66
|
};
|
|
67
67
|
export type PostWorkflowRun200 = {
|
|
68
68
|
/** The workflow execution id */
|
|
69
69
|
workflowId?: string;
|
|
70
70
|
/** The output of the workflow */
|
|
71
71
|
output?: unknown;
|
|
72
|
-
|
|
73
|
-
trace?: PostWorkflowRun200Trace;
|
|
72
|
+
trace?: TraceInfo;
|
|
74
73
|
};
|
|
75
74
|
export type PostWorkflowStartBody = {
|
|
76
75
|
/** The name of the workflow to execute */
|
|
@@ -110,35 +109,12 @@ export type GetWorkflowIdStatus200 = {
|
|
|
110
109
|
/** An epoch timestamp representing when the workflow ended */
|
|
111
110
|
completedAt?: number;
|
|
112
111
|
};
|
|
113
|
-
/**
|
|
114
|
-
* File destinations for trace data
|
|
115
|
-
*/
|
|
116
|
-
export type GetWorkflowIdOutput200TraceDestinations = {
|
|
117
|
-
/**
|
|
118
|
-
* Absolute path to local trace file, or null if not saved locally
|
|
119
|
-
* @nullable
|
|
120
|
-
*/
|
|
121
|
-
local: string | null;
|
|
122
|
-
/**
|
|
123
|
-
* Remote trace location (e.g., S3 URI), or null if not saved remotely
|
|
124
|
-
* @nullable
|
|
125
|
-
*/
|
|
126
|
-
remote: string | null;
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* An object with information about the trace generated by the execution
|
|
130
|
-
*/
|
|
131
|
-
export type GetWorkflowIdOutput200Trace = {
|
|
132
|
-
/** File destinations for trace data */
|
|
133
|
-
destinations?: GetWorkflowIdOutput200TraceDestinations;
|
|
134
|
-
};
|
|
135
112
|
export type GetWorkflowIdOutput200 = {
|
|
136
113
|
/** The workflow execution id */
|
|
137
114
|
workflowId?: string;
|
|
138
115
|
/** The output of workflow */
|
|
139
116
|
output?: unknown;
|
|
140
|
-
|
|
141
|
-
trace?: GetWorkflowIdOutput200Trace;
|
|
117
|
+
trace?: TraceInfo;
|
|
142
118
|
};
|
|
143
119
|
export type GetWorkflowCatalogId200 = {
|
|
144
120
|
/** Each workflow available in this catalog */
|
|
@@ -9,7 +9,6 @@ export default class WorkflowDebug extends Command {
|
|
|
9
9
|
format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
10
|
};
|
|
11
11
|
run(): Promise<void>;
|
|
12
|
-
private getTrace;
|
|
13
12
|
private outputJson;
|
|
14
13
|
private displayTextTrace;
|
|
15
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { OUTPUT_FORMAT } from '#utils/constants.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { displayDebugTree } from '#utils/trace_formatter.js';
|
|
4
|
+
import { getTrace } from '#services/trace_reader.js';
|
|
5
5
|
export default class WorkflowDebug extends Command {
|
|
6
6
|
static description = 'Get and display workflow execution trace for debugging';
|
|
7
7
|
static examples = [
|
|
@@ -29,7 +29,7 @@ export default class WorkflowDebug extends Command {
|
|
|
29
29
|
if (!isJsonFormat) {
|
|
30
30
|
this.log(`Fetching debug information for workflow: ${args.workflowId}...`);
|
|
31
31
|
}
|
|
32
|
-
const traceData = await
|
|
32
|
+
const traceData = await getTrace(args.workflowId);
|
|
33
33
|
// Output based on format
|
|
34
34
|
if (isJsonFormat) {
|
|
35
35
|
this.outputJson(traceData);
|
|
@@ -38,17 +38,13 @@ export default class WorkflowDebug extends Command {
|
|
|
38
38
|
// Display text format
|
|
39
39
|
this.displayTextTrace(traceData);
|
|
40
40
|
}
|
|
41
|
-
async getTrace(workflowId) {
|
|
42
|
-
const tracePath = await findTraceFile(workflowId);
|
|
43
|
-
return readTraceFile(tracePath);
|
|
44
|
-
}
|
|
45
41
|
outputJson(data) {
|
|
46
42
|
this.log(JSON.stringify(data, null, 2));
|
|
47
43
|
}
|
|
48
44
|
displayTextTrace(traceData) {
|
|
49
45
|
this.log('\nTrace Log:');
|
|
50
46
|
this.log('─'.repeat(80));
|
|
51
|
-
this.log(
|
|
47
|
+
this.log(displayDebugTree(traceData));
|
|
52
48
|
this.log('\n' + '─'.repeat(80));
|
|
53
49
|
this.log('Tip: Use --format json for complete verbose output');
|
|
54
50
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
// Mock the TraceReader service
|
|
3
3
|
vi.mock('../../services/trace_reader.js', () => ({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}))
|
|
4
|
+
findTraceFile: vi.fn(),
|
|
5
|
+
readTraceFile: vi.fn(),
|
|
6
|
+
getTrace: vi.fn()
|
|
8
7
|
}));
|
|
9
8
|
// Mock the utilities
|
|
10
9
|
vi.mock('../../utils/trace_formatter.js', () => ({
|
|
11
|
-
|
|
12
|
-
displayDebugTree: vi.fn()
|
|
13
|
-
}
|
|
10
|
+
displayDebugTree: vi.fn()
|
|
14
11
|
}));
|
|
15
12
|
describe('workflow debug command', () => {
|
|
16
13
|
beforeEach(() => {
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
export interface TraceData {
|
|
2
|
+
root: {
|
|
3
|
+
workflowName: string;
|
|
4
|
+
workflowId: string;
|
|
5
|
+
startTime: number;
|
|
6
|
+
endTime?: number;
|
|
7
|
+
duration?: number;
|
|
8
|
+
status?: string;
|
|
9
|
+
error?: unknown;
|
|
10
|
+
};
|
|
11
|
+
events?: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
phase: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
details?: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
children?: TraceNode[];
|
|
18
|
+
}
|
|
19
|
+
interface TraceNode {
|
|
20
|
+
name: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
startTime?: number;
|
|
23
|
+
endTime?: number;
|
|
24
|
+
duration?: number;
|
|
25
|
+
status?: string;
|
|
26
|
+
details?: unknown;
|
|
27
|
+
error?: unknown;
|
|
28
|
+
children?: TraceNode[];
|
|
29
|
+
}
|
|
1
30
|
/**
|
|
2
31
|
* Find trace file from workflow metadata
|
|
3
32
|
*/
|
|
@@ -5,4 +34,9 @@ export declare function findTraceFile(workflowId: string): Promise<string>;
|
|
|
5
34
|
/**
|
|
6
35
|
* Read and parse trace file
|
|
7
36
|
*/
|
|
8
|
-
export declare function readTraceFile(path: string): Promise<
|
|
37
|
+
export declare function readTraceFile(path: string): Promise<TraceData>;
|
|
38
|
+
/**
|
|
39
|
+
* Get trace data from workflow ID
|
|
40
|
+
*/
|
|
41
|
+
export declare function getTrace(workflowId: string): Promise<TraceData>;
|
|
42
|
+
export {};
|
|
@@ -33,7 +33,6 @@ export async function findTraceFile(workflowId) {
|
|
|
33
33
|
/**
|
|
34
34
|
* Read and parse trace file
|
|
35
35
|
*/
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
36
|
export async function readTraceFile(path) {
|
|
38
37
|
try {
|
|
39
38
|
const content = await readFile(path, 'utf-8');
|
|
@@ -49,3 +48,10 @@ export async function readTraceFile(path) {
|
|
|
49
48
|
throw error;
|
|
50
49
|
}
|
|
51
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Get trace data from workflow ID
|
|
53
|
+
*/
|
|
54
|
+
export async function getTrace(workflowId) {
|
|
55
|
+
const tracePath = await findTraceFile(workflowId);
|
|
56
|
+
return readTraceFile(tracePath);
|
|
57
|
+
}
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
type ErrorLike = string | {
|
|
2
|
+
message: string;
|
|
3
|
+
} | Error | unknown;
|
|
4
|
+
interface DebugNode {
|
|
5
|
+
kind?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
workflowName?: string;
|
|
9
|
+
stepName?: string;
|
|
10
|
+
activityName?: string;
|
|
11
|
+
phase?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
startTime?: number;
|
|
14
|
+
endTime?: number;
|
|
15
|
+
duration?: number;
|
|
16
|
+
details?: Record<string, unknown>;
|
|
17
|
+
error?: ErrorLike;
|
|
18
|
+
children?: DebugNode[];
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
1
21
|
/**
|
|
2
22
|
* Format duration in human-readable format
|
|
3
23
|
*/
|
|
@@ -19,9 +39,5 @@ export declare function getSummary(traceData: string | object): {
|
|
|
19
39
|
/**
|
|
20
40
|
* Display trace tree with debug command formatting style
|
|
21
41
|
*/
|
|
22
|
-
export declare function displayDebugTree(node:
|
|
23
|
-
export
|
|
24
|
-
format: typeof format;
|
|
25
|
-
getSummary: typeof getSummary;
|
|
26
|
-
displayDebugTree: typeof displayDebugTree;
|
|
27
|
-
};
|
|
42
|
+
export declare function displayDebugTree(node: DebugNode | unknown): string;
|
|
43
|
+
export {};
|
|
@@ -17,20 +17,18 @@ export function formatDuration(ms) {
|
|
|
17
17
|
/**
|
|
18
18
|
* Format error for display
|
|
19
19
|
*/
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
20
|
function formatError(error) {
|
|
22
21
|
if (typeof error === 'string') {
|
|
23
22
|
return error;
|
|
24
23
|
}
|
|
25
|
-
if (error
|
|
26
|
-
return error.message;
|
|
24
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
25
|
+
return String(error.message);
|
|
27
26
|
}
|
|
28
27
|
return JSON.stringify(error);
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
30
|
* Truncate long values for display
|
|
32
31
|
*/
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
32
|
function truncateValue(value, maxLength = 50) {
|
|
35
33
|
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
36
34
|
if (str.length <= maxLength) {
|
|
@@ -71,7 +69,6 @@ function formatPhase(phase) {
|
|
|
71
69
|
/**
|
|
72
70
|
* Format details for table display
|
|
73
71
|
*/
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
72
|
function formatDetails(details) {
|
|
76
73
|
if (!details) {
|
|
77
74
|
return '-';
|
|
@@ -98,7 +95,6 @@ function formatDetails(details) {
|
|
|
98
95
|
/**
|
|
99
96
|
* Format details for tree display
|
|
100
97
|
*/
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
98
|
function formatTreeDetails(details, depth) {
|
|
103
99
|
const lines = [];
|
|
104
100
|
const indent = ' '.repeat(depth);
|
|
@@ -117,17 +113,26 @@ function formatTreeDetails(details, depth) {
|
|
|
117
113
|
function formatHeader(root) {
|
|
118
114
|
const lines = [];
|
|
119
115
|
lines.push('═'.repeat(60));
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
// Handle both TraceEvent and DebugNode types
|
|
117
|
+
const workflowName = 'workflowName' in root ? root.workflowName : (root.name || 'Unknown');
|
|
118
|
+
const workflowId = 'workflowId' in root ? root.workflowId : 'N/A';
|
|
119
|
+
const timestamp = 'timestamp' in root ? root.timestamp : root.startTime;
|
|
120
|
+
lines.push(`Workflow: ${workflowName}`);
|
|
121
|
+
lines.push(`Workflow ID: ${workflowId}`);
|
|
122
|
+
if (timestamp && (typeof timestamp === 'number' || typeof timestamp === 'string')) {
|
|
123
|
+
lines.push(`Start Time: ${new Date(timestamp).toISOString()}`);
|
|
124
|
+
}
|
|
123
125
|
if (root.duration) {
|
|
124
126
|
lines.push(`Duration: ${formatDuration(root.duration)}`);
|
|
125
127
|
}
|
|
126
|
-
|
|
128
|
+
// Handle status based on available properties
|
|
129
|
+
const phase = 'phase' in root ? root.phase : undefined;
|
|
130
|
+
const status = 'status' in root ? root.status : undefined;
|
|
131
|
+
if ((phase === 'error' || status === 'failed') && root.error) {
|
|
127
132
|
lines.push('Status: Failed');
|
|
128
133
|
lines.push(`Error: ${formatError(root.error)}`);
|
|
129
134
|
}
|
|
130
|
-
else if (
|
|
135
|
+
else if (phase === 'end' || status === 'completed') {
|
|
131
136
|
lines.push('Status: Completed');
|
|
132
137
|
}
|
|
133
138
|
else {
|
|
@@ -158,6 +163,57 @@ function formatEventsTable(events) {
|
|
|
158
163
|
}
|
|
159
164
|
return table.toString();
|
|
160
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Get debug node display information
|
|
168
|
+
*/
|
|
169
|
+
function getDebugNodeInfo(node) {
|
|
170
|
+
if (typeof node === 'string') {
|
|
171
|
+
return node;
|
|
172
|
+
}
|
|
173
|
+
if (typeof node !== 'object' || node === null) {
|
|
174
|
+
return String(node);
|
|
175
|
+
}
|
|
176
|
+
const debugNode = node;
|
|
177
|
+
const parts = [];
|
|
178
|
+
// Look for common identifying properties
|
|
179
|
+
if (debugNode.kind) {
|
|
180
|
+
parts.push(`[${debugNode.kind}]`);
|
|
181
|
+
}
|
|
182
|
+
else if (debugNode.type) {
|
|
183
|
+
parts.push(`[${debugNode.type}]`);
|
|
184
|
+
}
|
|
185
|
+
if (debugNode.name) {
|
|
186
|
+
parts.push(debugNode.name);
|
|
187
|
+
}
|
|
188
|
+
else if (debugNode.workflowName) {
|
|
189
|
+
parts.push(debugNode.workflowName);
|
|
190
|
+
}
|
|
191
|
+
else if (debugNode.stepName) {
|
|
192
|
+
parts.push(debugNode.stepName);
|
|
193
|
+
}
|
|
194
|
+
else if (debugNode.activityName) {
|
|
195
|
+
parts.push(debugNode.activityName);
|
|
196
|
+
}
|
|
197
|
+
// Add phase/status indicators
|
|
198
|
+
if (debugNode.phase === 'error' || debugNode.status === 'failed') {
|
|
199
|
+
parts.push('[FAILED]');
|
|
200
|
+
}
|
|
201
|
+
else if (debugNode.phase === 'end' || debugNode.status === 'completed') {
|
|
202
|
+
parts.push('[COMPLETED]');
|
|
203
|
+
}
|
|
204
|
+
else if (debugNode.status === 'running') {
|
|
205
|
+
parts.push('[RUNNING]');
|
|
206
|
+
}
|
|
207
|
+
// If we have no meaningful parts, show a summary of the object
|
|
208
|
+
if (parts.length === 0) {
|
|
209
|
+
const keys = Object.keys(debugNode).filter(k => k !== 'children' && k !== 'parent');
|
|
210
|
+
if (keys.length > 0) {
|
|
211
|
+
return `Node {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', ...' : ''}}`;
|
|
212
|
+
}
|
|
213
|
+
return 'Node';
|
|
214
|
+
}
|
|
215
|
+
return parts.join(' ');
|
|
216
|
+
}
|
|
161
217
|
/**
|
|
162
218
|
* Format trace as a tree structure
|
|
163
219
|
*/
|
|
@@ -165,11 +221,28 @@ function formatTree(node, depth) {
|
|
|
165
221
|
const lines = [];
|
|
166
222
|
const indent = ' '.repeat(depth);
|
|
167
223
|
const marker = depth === 0 ? '' : '├─';
|
|
168
|
-
// Format current node
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
224
|
+
// Format current node - handle both types
|
|
225
|
+
const nodeInfo = (() => {
|
|
226
|
+
if ('workflowId' in node && 'timestamp' in node) {
|
|
227
|
+
// It's a TraceEvent
|
|
228
|
+
const traceNode = node;
|
|
229
|
+
return {
|
|
230
|
+
nodeName: getEventName(traceNode),
|
|
231
|
+
phase: formatPhase(traceNode.phase),
|
|
232
|
+
duration: traceNode.duration ? ` (${formatDuration(traceNode.duration)})` : ''
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// It's a DebugNode
|
|
237
|
+
const debugNode = node;
|
|
238
|
+
return {
|
|
239
|
+
nodeName: getDebugNodeInfo(node),
|
|
240
|
+
phase: debugNode.phase ? formatPhase(debugNode.phase) : '',
|
|
241
|
+
duration: debugNode.duration ? ` (${formatDuration(debugNode.duration)})` : ''
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
})();
|
|
245
|
+
lines.push(`${indent}${marker} ${nodeInfo.nodeName} ${nodeInfo.phase}${nodeInfo.duration}`);
|
|
173
246
|
// Add error details if present
|
|
174
247
|
if (node.error) {
|
|
175
248
|
lines.push(`${indent} └─ ERROR: ${formatError(node.error)}`);
|
|
@@ -192,7 +265,6 @@ function formatTree(node, depth) {
|
|
|
192
265
|
/**
|
|
193
266
|
* Format trace as human-readable text with tree structure
|
|
194
267
|
*/
|
|
195
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
268
|
function formatAsText(trace) {
|
|
197
269
|
const output = [];
|
|
198
270
|
// Add header with workflow info
|
|
@@ -261,7 +333,6 @@ function getConnector(isLast) {
|
|
|
261
333
|
/**
|
|
262
334
|
* Format value for debug detail display
|
|
263
335
|
*/
|
|
264
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
265
336
|
function formatDebugDetailValue(value) {
|
|
266
337
|
if (value === null) {
|
|
267
338
|
return 'null';
|
|
@@ -290,14 +361,15 @@ function formatDebugDetailValue(value) {
|
|
|
290
361
|
return `[Array with ${value.length} items]`;
|
|
291
362
|
}
|
|
292
363
|
if (typeof value === 'object') {
|
|
293
|
-
const
|
|
364
|
+
const obj = value;
|
|
365
|
+
const keys = Object.keys(obj);
|
|
294
366
|
if (keys.length === 0) {
|
|
295
367
|
return '{}';
|
|
296
368
|
}
|
|
297
369
|
// For simple objects with few keys, show inline
|
|
298
370
|
if (keys.length <= 2) {
|
|
299
371
|
const pairs = keys.map(k => {
|
|
300
|
-
const v =
|
|
372
|
+
const v = obj[k];
|
|
301
373
|
if (typeof v === 'string' && v.length > 30) {
|
|
302
374
|
return `${k}: "${v.substring(0, 30)}..."`;
|
|
303
375
|
}
|
|
@@ -313,82 +385,30 @@ function formatDebugDetailValue(value) {
|
|
|
313
385
|
}
|
|
314
386
|
return String(value);
|
|
315
387
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Get debug node display information
|
|
318
|
-
*/
|
|
319
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
320
|
-
function getDebugNodeInfo(node) {
|
|
321
|
-
if (typeof node === 'string') {
|
|
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 ? ', ...' : ''}}`;
|
|
362
|
-
}
|
|
363
|
-
return 'Node';
|
|
364
|
-
}
|
|
365
|
-
return parts.join(' ');
|
|
366
|
-
}
|
|
367
388
|
/**
|
|
368
389
|
* Add debug node details to lines array
|
|
369
390
|
*/
|
|
370
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
371
391
|
function addDebugNodeDetails(node, prefix, lines) {
|
|
372
392
|
if (typeof node !== 'object' || node === null) {
|
|
373
393
|
return;
|
|
374
394
|
}
|
|
375
395
|
const details = [];
|
|
376
396
|
// Add timing information
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const startDate = new Date(
|
|
397
|
+
const startedAt = node.startedAt || node.timestamp;
|
|
398
|
+
if (startedAt && (typeof startedAt === 'number' || typeof startedAt === 'string')) {
|
|
399
|
+
const startDate = new Date(startedAt);
|
|
380
400
|
if (!isNaN(startDate.getTime())) {
|
|
381
401
|
details.push(`${prefix}Started: ${startDate.toISOString()}`);
|
|
382
402
|
}
|
|
383
403
|
}
|
|
384
|
-
if (node.endedAt) {
|
|
404
|
+
if (node.endedAt && (typeof node.endedAt === 'number' || typeof node.endedAt === 'string')) {
|
|
385
405
|
const endDate = new Date(node.endedAt);
|
|
386
406
|
if (!isNaN(endDate.getTime())) {
|
|
387
407
|
details.push(`${prefix}Ended: ${endDate.toISOString()}`);
|
|
388
408
|
}
|
|
389
409
|
}
|
|
390
410
|
// Calculate and show duration
|
|
391
|
-
if (node.startedAt && node.endedAt) {
|
|
411
|
+
if (node.startedAt && node.endedAt && typeof node.startedAt === 'number' && typeof node.endedAt === 'number') {
|
|
392
412
|
const duration = node.endedAt - node.startedAt;
|
|
393
413
|
details.push(`${prefix}Duration: ${formatDuration(duration)}`);
|
|
394
414
|
}
|
|
@@ -407,7 +427,17 @@ function addDebugNodeDetails(node, prefix, lines) {
|
|
|
407
427
|
}
|
|
408
428
|
// Show error if present
|
|
409
429
|
if (node.error) {
|
|
410
|
-
const errorMsg =
|
|
430
|
+
const errorMsg = (() => {
|
|
431
|
+
if (typeof node.error === 'string') {
|
|
432
|
+
return node.error;
|
|
433
|
+
}
|
|
434
|
+
else if (node.error && typeof node.error === 'object' && 'message' in node.error) {
|
|
435
|
+
return String(node.error.message);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
return JSON.stringify(node.error);
|
|
439
|
+
}
|
|
440
|
+
})();
|
|
411
441
|
details.push(`${prefix}Error: ${errorMsg}`);
|
|
412
442
|
}
|
|
413
443
|
// Add all details to lines
|
|
@@ -422,7 +452,6 @@ function addDebugNodeDetails(node, prefix, lines) {
|
|
|
422
452
|
/**
|
|
423
453
|
* Build debug tree lines recursively
|
|
424
454
|
*/
|
|
425
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
455
|
function buildDebugTreeLines(node, depth, isLast, prefix, lines) {
|
|
427
456
|
if (node === null || node === undefined) {
|
|
428
457
|
return;
|
|
@@ -436,30 +465,28 @@ function buildDebugTreeLines(node, depth, isLast, prefix, lines) {
|
|
|
436
465
|
lines.push(indent + nodeInfo);
|
|
437
466
|
// Display additional details with proper indentation
|
|
438
467
|
const detailPrefix = isRoot ? ' ' : prefix + (isLast ? ' ' : '│ ');
|
|
439
|
-
|
|
468
|
+
if (typeof node === 'object' && node !== null) {
|
|
469
|
+
addDebugNodeDetails(node, detailPrefix, lines);
|
|
470
|
+
}
|
|
440
471
|
// Update prefix for children
|
|
441
472
|
const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
|
|
442
473
|
// Process children if they exist
|
|
443
|
-
if (node
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
474
|
+
if (typeof node === 'object' && node !== null) {
|
|
475
|
+
const debugNode = node;
|
|
476
|
+
if (debugNode.children && Array.isArray(debugNode.children)) {
|
|
477
|
+
const children = debugNode.children;
|
|
478
|
+
children.forEach((child, i) => {
|
|
479
|
+
const isLastChild = i === children.length - 1;
|
|
480
|
+
buildDebugTreeLines(child, depth + 1, isLastChild, childPrefix, lines);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
449
483
|
}
|
|
450
484
|
}
|
|
451
485
|
/**
|
|
452
486
|
* Display trace tree with debug command formatting style
|
|
453
487
|
*/
|
|
454
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
488
|
export function displayDebugTree(node) {
|
|
456
489
|
const lines = [];
|
|
457
490
|
buildDebugTreeLines(node, 0, false, '', lines);
|
|
458
491
|
return lines.join('\n');
|
|
459
492
|
}
|
|
460
|
-
// For backward compatibility, export an object with the main functions
|
|
461
|
-
export const traceFormatter = {
|
|
462
|
-
format,
|
|
463
|
-
getSummary,
|
|
464
|
-
displayDebugTree
|
|
465
|
-
};
|