@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.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/README.md +0 -53
- package/dist/api/generated/api.d.ts +2 -34
- package/dist/assets/docker/docker-compose-dev.yml +0 -1
- package/dist/commands/workflow/generate.js +3 -10
- package/dist/commands/workflow/generate.spec.js +6 -4
- package/dist/services/coding_agents.js +30 -0
- package/dist/services/coding_agents.spec.js +36 -61
- package/dist/services/messages.d.ts +1 -0
- package/dist/services/messages.js +65 -1
- package/dist/services/workflow_generator.spec.js +77 -0
- package/dist/templates/agent_instructions/AGENTS.md.template +209 -19
- package/dist/templates/agent_instructions/agents/context_fetcher.md.template +82 -0
- package/dist/templates/agent_instructions/agents/prompt_writer.md.template +595 -0
- package/dist/templates/agent_instructions/agents/workflow_planner.md.template +13 -4
- package/dist/templates/agent_instructions/agents/workflow_quality.md.template +244 -0
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +52 -9
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +4 -4
- package/dist/templates/project/package.json.template +2 -2
- package/dist/templates/project/src/simple/scenarios/question_ada_lovelace.json.template +3 -0
- package/dist/templates/workflow/scenarios/test_input.json.template +7 -0
- package/dist/utils/template.spec.js +6 -0
- package/package.json +1 -2
- package/dist/commands/workflow/debug.d.ts +0 -15
- package/dist/commands/workflow/debug.js +0 -55
- package/dist/commands/workflow/debug.test.js +0 -75
- 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
- package/dist/utils/trace_formatter.d.ts +0 -27
- package/dist/utils/trace_formatter.js +0 -465
- /package/dist/{commands/workflow/debug.test.d.ts → services/workflow_generator.spec.d.ts} +0 -0
- /package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template} +0 -0
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import { findTraceFile, readTraceFile } from './trace_reader.js';
|
|
3
|
-
// Mock file system operations
|
|
4
|
-
vi.mock('node:fs/promises', () => ({
|
|
5
|
-
readFile: vi.fn(),
|
|
6
|
-
stat: vi.fn()
|
|
7
|
-
}));
|
|
8
|
-
// Mock API
|
|
9
|
-
vi.mock('../api/generated/api.js', () => ({
|
|
10
|
-
getWorkflowIdOutput: vi.fn()
|
|
11
|
-
}));
|
|
12
|
-
describe('TraceReader', () => {
|
|
13
|
-
const getMocks = async () => {
|
|
14
|
-
const fsModule = await import('node:fs/promises');
|
|
15
|
-
const apiModule = await import('../api/generated/api.js');
|
|
16
|
-
return {
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
mockReadFile: fsModule.readFile,
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
mockStat: fsModule.stat,
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
-
mockGetWorkflowIdOutput: apiModule.getWorkflowIdOutput
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.clearAllMocks();
|
|
27
|
-
});
|
|
28
|
-
describe('findTraceFile', () => {
|
|
29
|
-
it('should find trace file from workflow output metadata', async () => {
|
|
30
|
-
const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
|
|
31
|
-
const workflowId = 'test-workflow-123';
|
|
32
|
-
const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-123.json';
|
|
33
|
-
mockGetWorkflowIdOutput.mockResolvedValue({
|
|
34
|
-
status: 200,
|
|
35
|
-
data: {
|
|
36
|
-
workflowId,
|
|
37
|
-
output: { result: 'test result' },
|
|
38
|
-
trace: {
|
|
39
|
-
destinations: {
|
|
40
|
-
local: expectedPath,
|
|
41
|
-
remote: null
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
mockStat.mockResolvedValue({ isFile: () => true });
|
|
47
|
-
const result = await findTraceFile(workflowId);
|
|
48
|
-
expect(result).toBe(expectedPath);
|
|
49
|
-
expect(mockGetWorkflowIdOutput).toHaveBeenCalledWith(workflowId);
|
|
50
|
-
expect(mockStat).toHaveBeenCalledWith(expectedPath);
|
|
51
|
-
});
|
|
52
|
-
it('should throw error when no trace path in metadata', async () => {
|
|
53
|
-
const { mockGetWorkflowIdOutput } = await getMocks();
|
|
54
|
-
const workflowId = 'test-workflow-456';
|
|
55
|
-
mockGetWorkflowIdOutput.mockResolvedValue({
|
|
56
|
-
status: 200,
|
|
57
|
-
data: {
|
|
58
|
-
workflowId,
|
|
59
|
-
output: { result: 'test result' },
|
|
60
|
-
trace: {
|
|
61
|
-
destinations: {
|
|
62
|
-
local: null,
|
|
63
|
-
remote: null
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
await expect(findTraceFile(workflowId))
|
|
69
|
-
.rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
|
|
70
|
-
});
|
|
71
|
-
it('should throw error when trace file not on disk', async () => {
|
|
72
|
-
const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
|
|
73
|
-
const workflowId = 'test-workflow-789';
|
|
74
|
-
const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-789.json';
|
|
75
|
-
mockGetWorkflowIdOutput.mockResolvedValue({
|
|
76
|
-
status: 200,
|
|
77
|
-
data: {
|
|
78
|
-
workflowId,
|
|
79
|
-
output: { result: 'test result' },
|
|
80
|
-
trace: {
|
|
81
|
-
destinations: {
|
|
82
|
-
local: expectedPath,
|
|
83
|
-
remote: null
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
mockStat.mockRejectedValue(new Error('ENOENT'));
|
|
89
|
-
await expect(findTraceFile(workflowId))
|
|
90
|
-
.rejects.toThrow(`Trace file not found at path: ${expectedPath}`);
|
|
91
|
-
});
|
|
92
|
-
it('should throw error when API call fails', async () => {
|
|
93
|
-
const { mockGetWorkflowIdOutput } = await getMocks();
|
|
94
|
-
const workflowId = 'non-existent';
|
|
95
|
-
mockGetWorkflowIdOutput.mockRejectedValue(new Error('Workflow not found'));
|
|
96
|
-
await expect(findTraceFile(workflowId))
|
|
97
|
-
.rejects.toThrow('Workflow not found');
|
|
98
|
-
});
|
|
99
|
-
it('should handle missing trace property gracefully', async () => {
|
|
100
|
-
const { mockGetWorkflowIdOutput } = await getMocks();
|
|
101
|
-
const workflowId = 'test-workflow-no-trace';
|
|
102
|
-
mockGetWorkflowIdOutput.mockResolvedValue({
|
|
103
|
-
status: 200,
|
|
104
|
-
data: {
|
|
105
|
-
workflowId,
|
|
106
|
-
output: { result: 'test result' }
|
|
107
|
-
// No trace property at all
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
await expect(findTraceFile(workflowId))
|
|
111
|
-
.rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
|
|
112
|
-
});
|
|
113
|
-
it('should throw error when workflow not found (404)', async () => {
|
|
114
|
-
const { mockGetWorkflowIdOutput } = await getMocks();
|
|
115
|
-
const workflowId = 'non-existent-workflow';
|
|
116
|
-
mockGetWorkflowIdOutput.mockResolvedValue({
|
|
117
|
-
status: 404,
|
|
118
|
-
data: void 0
|
|
119
|
-
});
|
|
120
|
-
await expect(findTraceFile(workflowId))
|
|
121
|
-
.rejects.toThrow(`Failed to get workflow output for ${workflowId}`);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
describe('readTraceFile', () => {
|
|
125
|
-
it('should read and parse JSON file successfully', async () => {
|
|
126
|
-
const { mockReadFile } = await getMocks();
|
|
127
|
-
const path = '/logs/test.json';
|
|
128
|
-
const traceData = {
|
|
129
|
-
root: { workflowName: 'test' },
|
|
130
|
-
events: []
|
|
131
|
-
};
|
|
132
|
-
mockReadFile.mockResolvedValue(JSON.stringify(traceData));
|
|
133
|
-
const result = await readTraceFile(path);
|
|
134
|
-
expect(result).toEqual(traceData);
|
|
135
|
-
expect(mockReadFile).toHaveBeenCalledWith(path, 'utf-8');
|
|
136
|
-
});
|
|
137
|
-
it('should throw error for non-existent file', async () => {
|
|
138
|
-
const { mockReadFile } = await getMocks();
|
|
139
|
-
const path = '/logs/missing.json';
|
|
140
|
-
const error = new Error('ENOENT');
|
|
141
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
|
-
error.code = 'ENOENT';
|
|
143
|
-
mockReadFile.mockRejectedValue(error);
|
|
144
|
-
await expect(readTraceFile(path))
|
|
145
|
-
.rejects.toThrow(`Trace file not found at path: ${path}`);
|
|
146
|
-
});
|
|
147
|
-
it('should throw error for invalid JSON', async () => {
|
|
148
|
-
const { mockReadFile } = await getMocks();
|
|
149
|
-
const path = '/logs/invalid.json';
|
|
150
|
-
mockReadFile.mockResolvedValue('invalid json {');
|
|
151
|
-
await expect(readTraceFile(path))
|
|
152
|
-
.rejects.toThrow(`Invalid JSON in trace file: ${path}`);
|
|
153
|
-
});
|
|
154
|
-
it('should rethrow other errors', async () => {
|
|
155
|
-
const { mockReadFile } = await getMocks();
|
|
156
|
-
const path = '/logs/test.json';
|
|
157
|
-
const error = new Error('Permission denied');
|
|
158
|
-
mockReadFile.mockRejectedValue(error);
|
|
159
|
-
await expect(readTraceFile(path))
|
|
160
|
-
.rejects.toThrow('Permission denied');
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format duration in human-readable format
|
|
3
|
-
*/
|
|
4
|
-
export declare function formatDuration(ms: number): string;
|
|
5
|
-
/**
|
|
6
|
-
* Format trace data based on the requested format
|
|
7
|
-
*/
|
|
8
|
-
export declare function format(traceData: string | object, outputFormat?: 'json' | 'text'): string;
|
|
9
|
-
/**
|
|
10
|
-
* Get summary statistics from trace
|
|
11
|
-
*/
|
|
12
|
-
export declare function getSummary(traceData: string | object): {
|
|
13
|
-
totalDuration: number;
|
|
14
|
-
totalEvents: number;
|
|
15
|
-
totalSteps: number;
|
|
16
|
-
totalActivities: number;
|
|
17
|
-
hasErrors: boolean;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Display trace tree with debug command formatting style
|
|
21
|
-
*/
|
|
22
|
-
export declare function displayDebugTree(node: any): string;
|
|
23
|
-
export declare const traceFormatter: {
|
|
24
|
-
format: typeof format;
|
|
25
|
-
getSummary: typeof getSummary;
|
|
26
|
-
displayDebugTree: typeof displayDebugTree;
|
|
27
|
-
};
|
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import Table from 'cli-table3';
|
|
2
|
-
import { formatOutput } from '#utils/output_formatter.js';
|
|
3
|
-
/**
|
|
4
|
-
* Format duration in human-readable format
|
|
5
|
-
*/
|
|
6
|
-
export function formatDuration(ms) {
|
|
7
|
-
if (ms < 1000) {
|
|
8
|
-
return `${ms}ms`;
|
|
9
|
-
}
|
|
10
|
-
if (ms < 60000) {
|
|
11
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
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);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
// Process children
|
|
185
|
-
if (node.children && node.children.length > 0) {
|
|
186
|
-
for (const child of node.children) {
|
|
187
|
-
lines.push(formatTree(child, depth + 1));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return lines.join('\n');
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Format trace as human-readable text with tree structure
|
|
194
|
-
*/
|
|
195
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
-
function formatAsText(trace) {
|
|
197
|
-
const output = [];
|
|
198
|
-
// Add header with workflow info
|
|
199
|
-
if (trace.root) {
|
|
200
|
-
output.push(formatHeader(trace.root));
|
|
201
|
-
output.push('');
|
|
202
|
-
}
|
|
203
|
-
// Create execution timeline table
|
|
204
|
-
if (trace.events && trace.events.length > 0) {
|
|
205
|
-
output.push('Execution Timeline:');
|
|
206
|
-
output.push(formatEventsTable(trace.events));
|
|
207
|
-
output.push('');
|
|
208
|
-
}
|
|
209
|
-
// Show tree structure
|
|
210
|
-
if (trace.root) {
|
|
211
|
-
output.push('Execution Tree:');
|
|
212
|
-
output.push(formatTree(trace.root, 0));
|
|
213
|
-
}
|
|
214
|
-
return output.join('\n');
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Format trace data based on the requested format
|
|
218
|
-
*/
|
|
219
|
-
export function format(traceData, outputFormat = 'text') {
|
|
220
|
-
// Parse if string
|
|
221
|
-
const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
|
|
222
|
-
if (outputFormat === 'json') {
|
|
223
|
-
return formatOutput(trace, 'json');
|
|
224
|
-
}
|
|
225
|
-
// Format as human-readable text
|
|
226
|
-
return formatAsText(trace);
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Get summary statistics from trace
|
|
230
|
-
*/
|
|
231
|
-
export function getSummary(traceData) {
|
|
232
|
-
const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
|
|
233
|
-
const stats = {
|
|
234
|
-
totalDuration: trace.root?.duration || 0,
|
|
235
|
-
totalEvents: trace.events?.length || 0,
|
|
236
|
-
totalSteps: 0,
|
|
237
|
-
totalActivities: 0,
|
|
238
|
-
hasErrors: false
|
|
239
|
-
};
|
|
240
|
-
if (trace.events) {
|
|
241
|
-
for (const event of trace.events) {
|
|
242
|
-
if (event.kind === 'step') {
|
|
243
|
-
stats.totalSteps++;
|
|
244
|
-
}
|
|
245
|
-
if (event.kind === 'activity') {
|
|
246
|
-
stats.totalActivities++;
|
|
247
|
-
}
|
|
248
|
-
if (event.phase === 'error') {
|
|
249
|
-
stats.hasErrors = true;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return stats;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Get tree connector character
|
|
257
|
-
*/
|
|
258
|
-
function getConnector(isLast) {
|
|
259
|
-
return isLast ? '└─ ' : '├─ ';
|
|
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;
|
|
276
|
-
}
|
|
277
|
-
// For longer strings, truncate and suggest JSON format
|
|
278
|
-
return `${value.substring(0, 60)}... (truncated)`;
|
|
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 '[]';
|
|
286
|
-
}
|
|
287
|
-
if (value.length <= 3 && value.every(v => typeof v === 'string' || typeof v === 'number')) {
|
|
288
|
-
return `[${value.join(', ')}]`;
|
|
289
|
-
}
|
|
290
|
-
return `[Array with ${value.length} items]`;
|
|
291
|
-
}
|
|
292
|
-
if (typeof value === 'object') {
|
|
293
|
-
const keys = Object.keys(value);
|
|
294
|
-
if (keys.length === 0) {
|
|
295
|
-
return '{}';
|
|
296
|
-
}
|
|
297
|
-
// For simple objects with few keys, show inline
|
|
298
|
-
if (keys.length <= 2) {
|
|
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(', ')} }`;
|
|
310
|
-
}
|
|
311
|
-
// For complex objects, just show key count
|
|
312
|
-
return `{ ${keys.length} properties }`;
|
|
313
|
-
}
|
|
314
|
-
return String(value);
|
|
315
|
-
}
|
|
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
|
-
/**
|
|
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()}`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (node.endedAt) {
|
|
385
|
-
const endDate = new Date(node.endedAt);
|
|
386
|
-
if (!isNaN(endDate.getTime())) {
|
|
387
|
-
details.push(`${prefix}Ended: ${endDate.toISOString()}`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
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
|
-
}
|
|
460
|
-
// For backward compatibility, export an object with the main functions
|
|
461
|
-
export const traceFormatter = {
|
|
462
|
-
format,
|
|
463
|
-
getSummary,
|
|
464
|
-
displayDebugTree
|
|
465
|
-
};
|
|
File without changes
|
/package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template}
RENAMED
|
File without changes
|