@pixelbyte-software/pixcode 1.41.1 → 1.41.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/dist/assets/index-BC8CXTJj.css +32 -0
- package/dist/assets/{index-xttxkI5U.js → index-Bq2twGSW.js} +174 -174
- package/dist/index.html +2 -2
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js +236 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +12 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/command-center-agent-writes.mjs +3 -1
- package/scripts/smoke/run-state-refresh.mjs +52 -0
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -0
- package/server/modules/orchestration/index.ts +1 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +1 -0
- package/server/modules/orchestration/workflows/workflow-trace.ts +270 -0
- package/server/modules/orchestration/workflows/workflow.routes.ts +14 -0
- package/server/modules/orchestration/workflows/workflow.types.ts +21 -0
- package/dist/assets/index-LZgOC7Q_.css +0 -32
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const refreshUtilPath = 'src/utils/runStateRefresh.ts';
|
|
7
|
+
assert.ok(existsSync(refreshUtilPath), 'Run state refresh utility should exist.');
|
|
8
|
+
|
|
9
|
+
const refreshUtil = readFileSync(refreshUtilPath, 'utf8');
|
|
10
|
+
const chatRealtime = readFileSync('src/components/chat/hooks/useChatRealtimeHandlers.ts', 'utf8');
|
|
11
|
+
const chatSession = readFileSync('src/components/chat/hooks/useChatSessionState.ts', 'utf8');
|
|
12
|
+
const chatInterface = readFileSync('src/components/chat/view/ChatInterface.tsx', 'utf8');
|
|
13
|
+
const changedFilesHook = readFileSync('src/hooks/useChangedFilesMonitor.ts', 'utf8');
|
|
14
|
+
const orchestrationPage = readFileSync('src/components/orchestration/OrchestrationPage.tsx', 'utf8');
|
|
15
|
+
const runPanel = readFileSync('src/components/orchestration/workflows/WorkflowRunPanel.tsx', 'utf8');
|
|
16
|
+
|
|
17
|
+
assert.ok(
|
|
18
|
+
refreshUtil.includes('PIXCODE_RUN_STATE_REFRESH_EVENT') && refreshUtil.includes('dispatchRunStateRefresh'),
|
|
19
|
+
'Run state refresh utility should expose a stable browser event and dispatcher.',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
assert.ok(
|
|
23
|
+
chatRealtime.includes('onSessionSettled') && chatRealtime.includes('dispatchRunStateRefresh'),
|
|
24
|
+
'Chat realtime handlers should dispatch and callback on completion/failure so persisted messages are rehydrated.',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.ok(
|
|
28
|
+
chatSession.includes('refreshActiveSessionMessages') && chatSession.includes('refreshActiveSessionMessages,'),
|
|
29
|
+
'Chat session state should expose its canonical server refresh path to realtime handlers.',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
assert.ok(
|
|
33
|
+
chatInterface.includes('handleSessionSettled') && chatInterface.includes('refreshActiveSessionMessages'),
|
|
34
|
+
'Chat interface should refresh the active session when a run settles.',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
assert.ok(
|
|
38
|
+
changedFilesHook.includes('PIXCODE_RUN_STATE_REFRESH_EVENT') && changedFilesHook.includes("run-state"),
|
|
39
|
+
'Changed-files monitor should refresh on canonical run-state events.',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
orchestrationPage.includes('mergeRunSnapshot') && orchestrationPage.includes('dispatchRunStateRefresh'),
|
|
44
|
+
'Orchestration page should merge run snapshots and dispatch terminal refresh events.',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
assert.ok(
|
|
48
|
+
runPanel.includes('onRunSnapshot') && runPanel.includes('onRunSnapshot?.(nextRun)'),
|
|
49
|
+
'Workflow run panel should push run snapshots back to the parent list.',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log('run state refresh smoke passed');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
|
|
8
|
+
function read(relativePath) {
|
|
9
|
+
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function assert(condition, message) {
|
|
13
|
+
if (!condition) {
|
|
14
|
+
throw new Error(message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const types = read('server/modules/orchestration/workflows/workflow.types.ts');
|
|
19
|
+
assert(types.includes('WorkflowTraceEvent'), 'Workflow trace event type is missing.');
|
|
20
|
+
assert(types.includes("type: 'run' | 'node' | 'provider' | 'message' | 'artifact' | 'file' | 'error'"), 'Trace event type taxonomy is missing.');
|
|
21
|
+
assert(types.includes("severity: 'info' | 'warning' | 'error'"), 'Trace event severity taxonomy is missing.');
|
|
22
|
+
|
|
23
|
+
const traceService = read('server/modules/orchestration/workflows/workflow-trace.ts');
|
|
24
|
+
assert(traceService.includes('export function buildWorkflowTrace'), 'Workflow trace builder is missing.');
|
|
25
|
+
assert(traceService.includes('redactTraceText'), 'Trace text redaction helper is missing.');
|
|
26
|
+
assert(traceService.includes('file-diff'), 'Trace builder must surface file edit artifacts.');
|
|
27
|
+
assert(traceService.includes('durationMs'), 'Trace builder must include event durations.');
|
|
28
|
+
assert(traceService.includes('workflow.trace.runStarted'), 'Trace builder must emit stable trace title keys.');
|
|
29
|
+
|
|
30
|
+
const routes = read('server/modules/orchestration/workflows/workflow.routes.ts');
|
|
31
|
+
assert(routes.includes("'/workflows/runs/:runId/trace'"), 'Workflow trace API endpoint is missing.');
|
|
32
|
+
assert(routes.includes('buildWorkflowTrace(run)'), 'Workflow trace route must use the shared trace builder.');
|
|
33
|
+
|
|
34
|
+
const panel = read('src/components/orchestration/workflows/WorkflowRunPanel.tsx');
|
|
35
|
+
assert(panel.includes('WorkflowTraceEvent'), 'Workflow run panel must type trace events.');
|
|
36
|
+
assert(panel.includes('loadTrace'), 'Workflow run panel must load trace events from the API.');
|
|
37
|
+
assert(panel.includes('traceFilters'), 'Workflow run panel must expose trace filters.');
|
|
38
|
+
assert(panel.includes('traceTimelineId'), 'Workflow trace timeline tab is missing.');
|
|
39
|
+
assert(panel.includes('orchestration.traceTimeline'), 'Workflow run panel must render the trace timeline label.');
|
|
40
|
+
|
|
41
|
+
const en = read('src/i18n/locales/en/common.json');
|
|
42
|
+
const tr = read('src/i18n/locales/tr/common.json');
|
|
43
|
+
assert(en.includes('"traceTimeline"'), 'English trace timeline translation is missing.');
|
|
44
|
+
assert(tr.includes('"traceTimeline"'), 'Turkish trace timeline translation is missing.');
|
|
45
|
+
|
|
46
|
+
console.log(JSON.stringify({ ok: true, checked: 'workflow trace timeline' }, null, 2));
|
|
@@ -1086,6 +1086,7 @@ function nodeRunFromNode(node: WorkflowNode): WorkflowNodeRun {
|
|
|
1086
1086
|
agentInstanceId: node.agentInstanceId,
|
|
1087
1087
|
agentLabel: node.agentLabel,
|
|
1088
1088
|
assignment: node.assignment,
|
|
1089
|
+
promptPreview: node.prompt,
|
|
1089
1090
|
model: node.model,
|
|
1090
1091
|
permissionMode: node.permissionMode,
|
|
1091
1092
|
timeoutMs: node.timeoutMs,
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
WorkflowNodeRun,
|
|
5
|
+
WorkflowRun,
|
|
6
|
+
WorkflowTraceEvent,
|
|
7
|
+
} from '@/modules/orchestration/workflows/workflow.types.js';
|
|
8
|
+
|
|
9
|
+
const MAX_TRACE_TEXT_CHARS = 2_400;
|
|
10
|
+
const TERMINAL_STATES = new Set(['completed', 'failed', 'canceled', 'skipped']);
|
|
11
|
+
|
|
12
|
+
function traceId(parts: Array<string | number | undefined>): string {
|
|
13
|
+
return parts.filter((part) => part !== undefined && part !== '').join(':');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function durationMs(startedAt?: number, finishedAt?: number): number | undefined {
|
|
17
|
+
if (!startedAt || !finishedAt) return undefined;
|
|
18
|
+
return Math.max(0, finishedAt - startedAt);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readString(value: unknown): string | undefined {
|
|
22
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function redactionValues(run: WorkflowRun): string[] {
|
|
26
|
+
const metadata = run.metadata ?? {};
|
|
27
|
+
const workspaceTarget = metadata.workspaceTarget && typeof metadata.workspaceTarget === 'object'
|
|
28
|
+
? metadata.workspaceTarget as Record<string, unknown>
|
|
29
|
+
: {};
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
os.homedir(),
|
|
33
|
+
readString(metadata.projectPath),
|
|
34
|
+
readString(metadata.selectedProjectPath),
|
|
35
|
+
readString(workspaceTarget.path),
|
|
36
|
+
readString(workspaceTarget.projectPath),
|
|
37
|
+
readString(workspaceTarget.selectedProjectPath),
|
|
38
|
+
].filter((value): value is string => Boolean(value && value.length > 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function escapeRegExp(value: string): string {
|
|
42
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function redactTraceText(value: string | undefined, run: WorkflowRun, maxLength = MAX_TRACE_TEXT_CHARS): string | undefined {
|
|
46
|
+
if (!value?.trim()) return undefined;
|
|
47
|
+
|
|
48
|
+
let text = value;
|
|
49
|
+
for (const secret of redactionValues(run)) {
|
|
50
|
+
text = text.replace(new RegExp(escapeRegExp(secret), 'g'), '[workspace]');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
text = text
|
|
54
|
+
.replace(/\b(?:sk|ghp|github_pat|glpat|npm)_[A-Za-z0-9_=-]{12,}\b/gu, '[redacted-token]')
|
|
55
|
+
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gu, '[redacted-email]')
|
|
56
|
+
.trim();
|
|
57
|
+
|
|
58
|
+
if (text.length <= maxLength) return text;
|
|
59
|
+
return `${text.slice(0, maxLength - 32).trimEnd()}\n...[trace truncated]`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function eventBase(
|
|
63
|
+
node: WorkflowNodeRun | undefined,
|
|
64
|
+
): Pick<WorkflowTraceEvent, 'actor' | 'adapterId' | 'agentInstanceId' | 'agentLabel' | 'model' | 'nodeId'> {
|
|
65
|
+
if (!node) {
|
|
66
|
+
return { actor: 'Pixcode' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
actor: node.agentLabel || node.adapterId || node.nodeId,
|
|
71
|
+
nodeId: node.nodeId,
|
|
72
|
+
adapterId: node.adapterId,
|
|
73
|
+
agentInstanceId: node.agentInstanceId,
|
|
74
|
+
agentLabel: node.agentLabel,
|
|
75
|
+
model: node.model,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pushEvent(
|
|
80
|
+
events: WorkflowTraceEvent[],
|
|
81
|
+
event: WorkflowTraceEvent,
|
|
82
|
+
): void {
|
|
83
|
+
events.push(event);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function artifactTitleKey(type: string): string {
|
|
87
|
+
if (type === 'file-diff') return 'workflow.trace.fileChanged';
|
|
88
|
+
if (type === 'preview-url') return 'workflow.trace.previewReady';
|
|
89
|
+
if (type === 'command-output') return 'workflow.trace.commandOutput';
|
|
90
|
+
return 'workflow.trace.artifact';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function artifactType(type: string): WorkflowTraceEvent['type'] {
|
|
94
|
+
return type === 'file-diff' ? 'file' : 'artifact';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function artifactTitle(type: string): string {
|
|
98
|
+
if (type === 'file-diff') return 'File changes captured';
|
|
99
|
+
if (type === 'preview-url') return 'Preview output captured';
|
|
100
|
+
if (type === 'command-output') return 'Command output captured';
|
|
101
|
+
return 'Artifact captured';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function artifactSummary(
|
|
105
|
+
artifact: NonNullable<WorkflowNodeRun['artifacts']>[number],
|
|
106
|
+
run: WorkflowRun,
|
|
107
|
+
): string | undefined {
|
|
108
|
+
if (artifact.text?.trim()) {
|
|
109
|
+
return redactTraceText(artifact.text, run);
|
|
110
|
+
}
|
|
111
|
+
if (artifact.data && Object.keys(artifact.data).length > 0) {
|
|
112
|
+
return redactTraceText(JSON.stringify(artifact.data, null, 2), run);
|
|
113
|
+
}
|
|
114
|
+
return artifact.type;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function nodeTimestamp(run: WorkflowRun, node: WorkflowNodeRun, index: number): number {
|
|
118
|
+
return node.startedAt ?? run.startedAt + index;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildWorkflowTrace(run: WorkflowRun): WorkflowTraceEvent[] {
|
|
122
|
+
const events: WorkflowTraceEvent[] = [];
|
|
123
|
+
|
|
124
|
+
pushEvent(events, {
|
|
125
|
+
id: traceId([run.id, 'run-started']),
|
|
126
|
+
type: 'run',
|
|
127
|
+
severity: 'info',
|
|
128
|
+
status: run.status,
|
|
129
|
+
timestamp: run.startedAt,
|
|
130
|
+
durationMs: durationMs(run.startedAt, run.finishedAt),
|
|
131
|
+
actor: 'Pixcode',
|
|
132
|
+
title: 'Workflow run started',
|
|
133
|
+
titleKey: 'workflow.trace.runStarted',
|
|
134
|
+
summary: redactTraceText(run.input, run),
|
|
135
|
+
metadata: {
|
|
136
|
+
workflowId: run.workflowId,
|
|
137
|
+
contextId: run.contextId,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
run.nodeRuns.forEach((node, index) => {
|
|
142
|
+
const base = eventBase(node);
|
|
143
|
+
const timestamp = nodeTimestamp(run, node, index);
|
|
144
|
+
const nodeDuration = durationMs(node.startedAt, node.finishedAt);
|
|
145
|
+
|
|
146
|
+
pushEvent(events, {
|
|
147
|
+
id: traceId([run.id, node.nodeId, 'node']),
|
|
148
|
+
type: 'node',
|
|
149
|
+
severity: node.status === 'failed' ? 'error' : 'info',
|
|
150
|
+
status: node.status,
|
|
151
|
+
timestamp,
|
|
152
|
+
durationMs: nodeDuration,
|
|
153
|
+
...base,
|
|
154
|
+
title: TERMINAL_STATES.has(node.status) ? 'Workflow step finished' : 'Workflow step started',
|
|
155
|
+
titleKey: TERMINAL_STATES.has(node.status) ? 'workflow.trace.nodeFinished' : 'workflow.trace.nodeStarted',
|
|
156
|
+
summary: redactTraceText(node.assignment, run),
|
|
157
|
+
metadata: {
|
|
158
|
+
stage: node.stage,
|
|
159
|
+
internal: node.internal,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (node.promptPreview) {
|
|
164
|
+
pushEvent(events, {
|
|
165
|
+
id: traceId([run.id, node.nodeId, 'prompt']),
|
|
166
|
+
type: 'message',
|
|
167
|
+
severity: 'info',
|
|
168
|
+
status: node.status,
|
|
169
|
+
timestamp: timestamp + 1,
|
|
170
|
+
...base,
|
|
171
|
+
title: 'Prompt prepared',
|
|
172
|
+
titleKey: 'workflow.trace.prompt',
|
|
173
|
+
summary: redactTraceText(node.promptPreview, run),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (node.adapterId || node.model) {
|
|
178
|
+
pushEvent(events, {
|
|
179
|
+
id: traceId([run.id, node.nodeId, 'provider']),
|
|
180
|
+
type: 'provider',
|
|
181
|
+
severity: node.status === 'failed' ? 'error' : 'info',
|
|
182
|
+
status: node.a2aTaskId ? 'submitted' : node.status,
|
|
183
|
+
timestamp: timestamp + 2,
|
|
184
|
+
durationMs: nodeDuration,
|
|
185
|
+
...base,
|
|
186
|
+
title: 'Provider call',
|
|
187
|
+
titleKey: 'workflow.trace.providerCall',
|
|
188
|
+
summary: [node.adapterId, node.model].filter(Boolean).join(' / '),
|
|
189
|
+
metadata: {
|
|
190
|
+
a2aTaskId: node.a2aTaskId,
|
|
191
|
+
permissionMode: node.permissionMode,
|
|
192
|
+
timeoutMs: node.timeoutMs,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
(node.messages ?? [])
|
|
198
|
+
.filter((message) => message.role !== 'user' && message.text.trim())
|
|
199
|
+
.forEach((message, messageIndex) => {
|
|
200
|
+
pushEvent(events, {
|
|
201
|
+
id: traceId([run.id, node.nodeId, 'message', messageIndex]),
|
|
202
|
+
type: 'message',
|
|
203
|
+
severity: 'info',
|
|
204
|
+
status: node.status,
|
|
205
|
+
timestamp: message.createdAt ?? timestamp + 10 + messageIndex,
|
|
206
|
+
...base,
|
|
207
|
+
title: 'Agent message',
|
|
208
|
+
titleKey: 'workflow.trace.agentMessage',
|
|
209
|
+
summary: redactTraceText(message.text, run),
|
|
210
|
+
metadata: {
|
|
211
|
+
role: message.role,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
(node.artifacts ?? []).forEach((artifact, artifactIndex) => {
|
|
217
|
+
pushEvent(events, {
|
|
218
|
+
id: traceId([run.id, node.nodeId, 'artifact', artifactIndex]),
|
|
219
|
+
type: artifactType(artifact.type),
|
|
220
|
+
severity: 'info',
|
|
221
|
+
status: node.status,
|
|
222
|
+
timestamp: node.finishedAt ?? timestamp + 20 + artifactIndex,
|
|
223
|
+
...base,
|
|
224
|
+
title: artifactTitle(artifact.type),
|
|
225
|
+
titleKey: artifactTitleKey(artifact.type),
|
|
226
|
+
summary: artifactSummary(artifact, run),
|
|
227
|
+
metadata: {
|
|
228
|
+
artifactType: artifact.type,
|
|
229
|
+
artifactMetadata: artifact.metadata,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (node.error) {
|
|
235
|
+
pushEvent(events, {
|
|
236
|
+
id: traceId([run.id, node.nodeId, 'error']),
|
|
237
|
+
type: 'error',
|
|
238
|
+
severity: 'error',
|
|
239
|
+
status: node.status,
|
|
240
|
+
timestamp: node.finishedAt ?? timestamp + 30,
|
|
241
|
+
durationMs: nodeDuration,
|
|
242
|
+
...base,
|
|
243
|
+
title: 'Step error',
|
|
244
|
+
titleKey: 'workflow.trace.error',
|
|
245
|
+
summary: redactTraceText(node.error, run),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (run.finishedAt) {
|
|
251
|
+
pushEvent(events, {
|
|
252
|
+
id: traceId([run.id, 'run-finished']),
|
|
253
|
+
type: 'run',
|
|
254
|
+
severity: run.status === 'failed' ? 'error' : 'info',
|
|
255
|
+
status: run.status,
|
|
256
|
+
timestamp: run.finishedAt,
|
|
257
|
+
durationMs: durationMs(run.startedAt, run.finishedAt),
|
|
258
|
+
actor: 'Pixcode',
|
|
259
|
+
title: 'Workflow run finished',
|
|
260
|
+
titleKey: 'workflow.trace.runFinished',
|
|
261
|
+
summary: redactTraceText(readString(run.metadata?.error), run),
|
|
262
|
+
metadata: {
|
|
263
|
+
workflowId: run.workflowId,
|
|
264
|
+
contextId: run.contextId,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return events.sort((a, b) => a.timestamp - b.timestamp || a.id.localeCompare(b.id));
|
|
270
|
+
}
|
|
@@ -3,6 +3,7 @@ import express from 'express';
|
|
|
3
3
|
|
|
4
4
|
import { workflowRunner } from '@/modules/orchestration/workflows/workflow-runner.js';
|
|
5
5
|
import { workflowStore } from '@/modules/orchestration/workflows/workflow-store.js';
|
|
6
|
+
import { buildWorkflowTrace } from '@/modules/orchestration/workflows/workflow-trace.js';
|
|
6
7
|
import { findPixcodeAppRoot } from '@/modules/orchestration/workflows/workspace-target.js';
|
|
7
8
|
|
|
8
9
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
@@ -120,6 +121,19 @@ export function createWorkflowRouter(): Router {
|
|
|
120
121
|
});
|
|
121
122
|
});
|
|
122
123
|
|
|
124
|
+
router.get('/workflows/runs/:runId/trace', (req, res) => {
|
|
125
|
+
const run = workflowStore.getRun(req.params.runId);
|
|
126
|
+
if (!run) {
|
|
127
|
+
res.status(404).json({ error: { code: 'RUN_NOT_FOUND', message: req.params.runId } });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
res.json({
|
|
132
|
+
runId: run.id,
|
|
133
|
+
trace: buildWorkflowTrace(run),
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
123
137
|
router.get('/workflows/runs/:runId', (req, res) => {
|
|
124
138
|
const run = workflowStore.getRun(req.params.runId);
|
|
125
139
|
if (!run) {
|
|
@@ -35,6 +35,7 @@ export interface WorkflowNodeRun {
|
|
|
35
35
|
agentInstanceId?: string;
|
|
36
36
|
agentLabel?: string;
|
|
37
37
|
assignment?: string;
|
|
38
|
+
promptPreview?: string;
|
|
38
39
|
model?: string;
|
|
39
40
|
permissionMode?: string;
|
|
40
41
|
timeoutMs?: number;
|
|
@@ -70,3 +71,23 @@ export interface WorkflowRun {
|
|
|
70
71
|
finishedAt?: number;
|
|
71
72
|
metadata?: Record<string, unknown>;
|
|
72
73
|
}
|
|
74
|
+
|
|
75
|
+
export interface WorkflowTraceEvent {
|
|
76
|
+
id: string;
|
|
77
|
+
type: 'run' | 'node' | 'provider' | 'message' | 'artifact' | 'file' | 'error';
|
|
78
|
+
severity: 'info' | 'warning' | 'error';
|
|
79
|
+
status: WorkflowRunStatus | WorkflowNodeStatus | 'submitted';
|
|
80
|
+
timestamp: number;
|
|
81
|
+
durationMs?: number;
|
|
82
|
+
actor: string;
|
|
83
|
+
title: string;
|
|
84
|
+
titleKey?: string;
|
|
85
|
+
summary?: string;
|
|
86
|
+
detail?: string;
|
|
87
|
+
nodeId?: string;
|
|
88
|
+
adapterId?: string;
|
|
89
|
+
agentInstanceId?: string;
|
|
90
|
+
agentLabel?: string;
|
|
91
|
+
model?: string;
|
|
92
|
+
metadata?: Record<string, unknown>;
|
|
93
|
+
}
|