@defai.digital/cli 13.3.1 → 13.4.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/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +23 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +211 -27
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/call.d.ts +2 -1
- package/dist/commands/call.d.ts.map +1 -1
- package/dist/commands/call.js +199 -10
- package/dist/commands/call.js.map +1 -1
- package/dist/commands/cleanup.d.ts +3 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +123 -11
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/discuss.d.ts +2 -1
- package/dist/commands/discuss.d.ts.map +1 -1
- package/dist/commands/discuss.js +604 -105
- package/dist/commands/discuss.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/monitor.d.ts +22 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +255 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +107 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts +6 -5
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +260 -75
- package/dist/commands/status.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +30 -0
- package/dist/parser.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/database.d.ts.map +1 -1
- package/dist/utils/database.js +5 -0
- package/dist/utils/database.js.map +1 -1
- package/dist/web/api.d.ts +12 -0
- package/dist/web/api.d.ts.map +1 -0
- package/dist/web/api.js +1189 -0
- package/dist/web/api.js.map +1 -0
- package/dist/web/dashboard.d.ts +13 -0
- package/dist/web/dashboard.d.ts.map +1 -0
- package/dist/web/dashboard.js +5290 -0
- package/dist/web/dashboard.js.map +1 -0
- package/package.json +21 -21
package/dist/web/api.js
ADDED
|
@@ -0,0 +1,1189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web API Handler
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API endpoints for the web dashboard.
|
|
5
|
+
* Wraps existing domain services to expose via HTTP.
|
|
6
|
+
*/
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { DATA_DIR_NAME, AGENTS_FILENAME } from '@defai.digital/contracts';
|
|
12
|
+
// Get the directory of this module (for finding bundled examples)
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
import { getProviderRegistry, getTraceStore, getSessionManager as getSharedSessionManager, } from '../bootstrap.js';
|
|
16
|
+
import { createPersistentAgentRegistry, } from '@defai.digital/agent-domain';
|
|
17
|
+
import { createWorkflowLoader, findWorkflowDir, } from '@defai.digital/workflow-engine';
|
|
18
|
+
import { CLI_VERSION } from '../commands/help.js';
|
|
19
|
+
// Workflow loader singleton
|
|
20
|
+
let workflowLoader;
|
|
21
|
+
let workflowLoaderPromise;
|
|
22
|
+
/**
|
|
23
|
+
* Find the example workflows directory.
|
|
24
|
+
* Searches multiple locations to work correctly regardless of working directory:
|
|
25
|
+
* 1. Package's examples directory (for development/source)
|
|
26
|
+
* 2. Monorepo root (for pnpm workspace)
|
|
27
|
+
* 3. Relative to cwd as fallback (for backward compatibility)
|
|
28
|
+
*
|
|
29
|
+
* This is the same pattern used for agents (see shared-registry.ts)
|
|
30
|
+
*/
|
|
31
|
+
function getExampleWorkflowsDir() {
|
|
32
|
+
// Try development path first (when running from source repo)
|
|
33
|
+
// __dirname is packages/cli/src/web, so go up 4 levels to repo root
|
|
34
|
+
const devPath = path.join(__dirname, '..', '..', '..', '..', 'examples', 'workflows');
|
|
35
|
+
if (fs.existsSync(devPath)) {
|
|
36
|
+
return devPath;
|
|
37
|
+
}
|
|
38
|
+
// Try monorepo root (for pnpm workspace, when running from dist)
|
|
39
|
+
const monorepoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'examples', 'workflows');
|
|
40
|
+
if (fs.existsSync(monorepoPath)) {
|
|
41
|
+
return monorepoPath;
|
|
42
|
+
}
|
|
43
|
+
// Try relative to cwd as fallback (for backward compatibility)
|
|
44
|
+
const cwdPath = findWorkflowDir(process.cwd());
|
|
45
|
+
if (cwdPath) {
|
|
46
|
+
return cwdPath;
|
|
47
|
+
}
|
|
48
|
+
// Return development path as default (most likely for dev workflow)
|
|
49
|
+
return devPath;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get or create the workflow loader (thread-safe)
|
|
53
|
+
* Uses atomic promise assignment pattern for safety
|
|
54
|
+
*/
|
|
55
|
+
async function getWorkflowLoader() {
|
|
56
|
+
if (workflowLoader)
|
|
57
|
+
return workflowLoader;
|
|
58
|
+
if (!workflowLoaderPromise) {
|
|
59
|
+
// Assign promise immediately to prevent potential race conditions
|
|
60
|
+
workflowLoaderPromise = Promise.resolve().then(() => {
|
|
61
|
+
const workflowsDir = getExampleWorkflowsDir();
|
|
62
|
+
const loader = createWorkflowLoader({ workflowsDir });
|
|
63
|
+
workflowLoader = loader;
|
|
64
|
+
return loader;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return workflowLoaderPromise;
|
|
68
|
+
}
|
|
69
|
+
// Lazy singletons with Promise-based initialization to prevent race conditions
|
|
70
|
+
let agentRegistry;
|
|
71
|
+
let agentRegistryPromise;
|
|
72
|
+
/**
|
|
73
|
+
* Get the global agent storage path (same as MCP server).
|
|
74
|
+
* Uses home directory for consistency across all AutomatosX components.
|
|
75
|
+
*/
|
|
76
|
+
function getGlobalAgentStoragePath() {
|
|
77
|
+
return path.join(os.homedir(), DATA_DIR_NAME, AGENTS_FILENAME);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the session manager from CLI bootstrap (shared instance)
|
|
81
|
+
* This ensures the web dashboard sees the same sessions as other CLI components
|
|
82
|
+
*/
|
|
83
|
+
function getSessionManager() {
|
|
84
|
+
return getSharedSessionManager();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get or create the agent registry (thread-safe)
|
|
88
|
+
* Uses atomic promise assignment pattern for safety
|
|
89
|
+
*/
|
|
90
|
+
async function getAgentRegistry() {
|
|
91
|
+
if (agentRegistry)
|
|
92
|
+
return agentRegistry;
|
|
93
|
+
if (!agentRegistryPromise) {
|
|
94
|
+
// Assign promise immediately to prevent potential race conditions
|
|
95
|
+
agentRegistryPromise = Promise.resolve().then(() => {
|
|
96
|
+
const registry = createPersistentAgentRegistry({
|
|
97
|
+
storagePath: getGlobalAgentStoragePath(),
|
|
98
|
+
});
|
|
99
|
+
agentRegistry = registry;
|
|
100
|
+
return registry;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return agentRegistryPromise;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create the API handler function
|
|
107
|
+
*/
|
|
108
|
+
export function createAPIHandler() {
|
|
109
|
+
return async (req, res) => {
|
|
110
|
+
const url = req.url ?? '';
|
|
111
|
+
const apiPath = url.replace('/api', '');
|
|
112
|
+
try {
|
|
113
|
+
let response;
|
|
114
|
+
// Check for parameterized routes first
|
|
115
|
+
const traceTreeMatch = apiPath.match(/^\/traces\/([a-f0-9-]+)\/tree$/i);
|
|
116
|
+
const traceDetailMatch = apiPath.match(/^\/traces\/([a-f0-9-]+)$/i);
|
|
117
|
+
const workflowDetailMatch = apiPath.match(/^\/workflows\/([a-z0-9-]+)$/i);
|
|
118
|
+
const providerHistoryMatch = apiPath.match(/^\/providers\/([a-z0-9-]+)\/history$/i);
|
|
119
|
+
const agentHistoryMatch = apiPath.match(/^\/agents\/([a-z0-9-_]+)\/history$/i);
|
|
120
|
+
const agentDetailMatch = apiPath.match(/^\/agents\/([a-z0-9-_]+)$/i);
|
|
121
|
+
const workflowEventsMatch = apiPath.match(/^\/workflows\/([a-z0-9-]+)\/events$/i);
|
|
122
|
+
const traceSearchMatch = apiPath.match(/^\/traces\/search\?(.*)$/i) || apiPath.match(/^\/traces\/search$/i);
|
|
123
|
+
// INV-TR-020 through INV-TR-024: Hierarchical trace tree endpoint
|
|
124
|
+
if (traceTreeMatch && traceTreeMatch[1]) {
|
|
125
|
+
const traceId = traceTreeMatch[1];
|
|
126
|
+
response = await handleTraceTree(traceId);
|
|
127
|
+
}
|
|
128
|
+
else if (traceDetailMatch && traceDetailMatch[1]) {
|
|
129
|
+
const traceId = traceDetailMatch[1];
|
|
130
|
+
response = await handleTraceDetail(traceId);
|
|
131
|
+
}
|
|
132
|
+
else if (workflowDetailMatch && workflowDetailMatch[1]) {
|
|
133
|
+
const workflowId = workflowDetailMatch[1];
|
|
134
|
+
response = await handleWorkflowDetail(workflowId);
|
|
135
|
+
}
|
|
136
|
+
else if (providerHistoryMatch && providerHistoryMatch[1]) {
|
|
137
|
+
const providerId = providerHistoryMatch[1];
|
|
138
|
+
response = await handleProviderHistory(providerId);
|
|
139
|
+
}
|
|
140
|
+
else if (agentHistoryMatch && agentHistoryMatch[1]) {
|
|
141
|
+
const agentId = agentHistoryMatch[1];
|
|
142
|
+
response = await handleAgentHistory(agentId);
|
|
143
|
+
}
|
|
144
|
+
else if (agentDetailMatch && agentDetailMatch[1]) {
|
|
145
|
+
const agentId = agentDetailMatch[1];
|
|
146
|
+
response = await handleAgentDetail(agentId);
|
|
147
|
+
}
|
|
148
|
+
else if (workflowEventsMatch && workflowEventsMatch[1]) {
|
|
149
|
+
const workflowId = workflowEventsMatch[1];
|
|
150
|
+
response = await handleWorkflowEvents(workflowId);
|
|
151
|
+
}
|
|
152
|
+
else if (traceSearchMatch) {
|
|
153
|
+
const queryString = traceSearchMatch[1] ?? '';
|
|
154
|
+
const params = new URLSearchParams(queryString);
|
|
155
|
+
const searchFilters = {};
|
|
156
|
+
const pId = params.get('providerId');
|
|
157
|
+
const aId = params.get('agentId');
|
|
158
|
+
const pType = params.get('type');
|
|
159
|
+
const pLimit = params.get('limit');
|
|
160
|
+
if (pId)
|
|
161
|
+
searchFilters.providerId = pId;
|
|
162
|
+
if (aId)
|
|
163
|
+
searchFilters.agentId = aId;
|
|
164
|
+
if (pType)
|
|
165
|
+
searchFilters.type = pType;
|
|
166
|
+
if (pLimit)
|
|
167
|
+
searchFilters.limit = parseInt(pLimit, 10);
|
|
168
|
+
response = await handleTraceSearch(searchFilters);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Static routes
|
|
172
|
+
switch (apiPath) {
|
|
173
|
+
case '/status':
|
|
174
|
+
response = await handleStatus();
|
|
175
|
+
break;
|
|
176
|
+
case '/providers':
|
|
177
|
+
response = await handleProviders();
|
|
178
|
+
break;
|
|
179
|
+
case '/sessions':
|
|
180
|
+
response = await handleSessions();
|
|
181
|
+
break;
|
|
182
|
+
case '/agents':
|
|
183
|
+
response = await handleAgents();
|
|
184
|
+
break;
|
|
185
|
+
case '/traces':
|
|
186
|
+
response = await handleTraces();
|
|
187
|
+
break;
|
|
188
|
+
case '/workflows':
|
|
189
|
+
response = await handleWorkflows();
|
|
190
|
+
break;
|
|
191
|
+
case '/metrics':
|
|
192
|
+
response = await handleMetrics();
|
|
193
|
+
break;
|
|
194
|
+
case '/health':
|
|
195
|
+
response = { success: true, data: { status: 'ok', version: CLI_VERSION } };
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
199
|
+
res.end(JSON.stringify({ success: false, error: 'Not found' }));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
204
|
+
res.end(JSON.stringify(response));
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
208
|
+
res.end(JSON.stringify({
|
|
209
|
+
success: false,
|
|
210
|
+
error: error instanceof Error ? error.message : 'Internal server error',
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get full system status
|
|
217
|
+
*/
|
|
218
|
+
async function handleStatus() {
|
|
219
|
+
const [providers, sessions, agents, traces, metrics] = await Promise.all([
|
|
220
|
+
getProviderData(),
|
|
221
|
+
getSessionData(),
|
|
222
|
+
getAgentData(),
|
|
223
|
+
getTraceData(),
|
|
224
|
+
getMetricsData(),
|
|
225
|
+
]);
|
|
226
|
+
// Determine health status
|
|
227
|
+
const healthyProviders = providers.filter(p => p.available).length;
|
|
228
|
+
const totalProviders = providers.length;
|
|
229
|
+
const healthStatus = totalProviders === 0 ? 'healthy' :
|
|
230
|
+
healthyProviders === totalProviders ? 'healthy' :
|
|
231
|
+
healthyProviders > 0 ? 'degraded' : 'unhealthy';
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
data: {
|
|
235
|
+
status: healthStatus,
|
|
236
|
+
version: CLI_VERSION,
|
|
237
|
+
uptime: formatUptime(process.uptime()),
|
|
238
|
+
providers,
|
|
239
|
+
sessions,
|
|
240
|
+
agents,
|
|
241
|
+
traces,
|
|
242
|
+
metrics,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get provider status
|
|
248
|
+
*/
|
|
249
|
+
async function handleProviders() {
|
|
250
|
+
const providers = await getProviderData();
|
|
251
|
+
return { success: true, data: providers };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get sessions
|
|
255
|
+
*/
|
|
256
|
+
async function handleSessions() {
|
|
257
|
+
const sessions = await getSessionData();
|
|
258
|
+
return { success: true, data: sessions };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get agents
|
|
262
|
+
*/
|
|
263
|
+
async function handleAgents() {
|
|
264
|
+
const agents = await getAgentData();
|
|
265
|
+
return { success: true, data: agents };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get agent detail with full profile including workflow
|
|
269
|
+
*/
|
|
270
|
+
async function handleAgentDetail(agentId) {
|
|
271
|
+
try {
|
|
272
|
+
const registry = await getAgentRegistry();
|
|
273
|
+
const agent = await registry.get(agentId);
|
|
274
|
+
if (!agent) {
|
|
275
|
+
return { success: false, error: `Agent not found: ${agentId}` };
|
|
276
|
+
}
|
|
277
|
+
// Get execution stats from traces
|
|
278
|
+
const traceStore = getTraceStore();
|
|
279
|
+
const traces = await traceStore.listTraces(100);
|
|
280
|
+
let executionCount = 0;
|
|
281
|
+
let successCount = 0;
|
|
282
|
+
let totalDurationMs = 0;
|
|
283
|
+
for (const trace of traces) {
|
|
284
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
285
|
+
const hasAgent = events.some(e => {
|
|
286
|
+
const ctx = e.context;
|
|
287
|
+
return ctx?.agentId === agentId;
|
|
288
|
+
});
|
|
289
|
+
if (hasAgent) {
|
|
290
|
+
executionCount++;
|
|
291
|
+
if (trace.status === 'success')
|
|
292
|
+
successCount++;
|
|
293
|
+
if (trace.durationMs)
|
|
294
|
+
totalDurationMs += trace.durationMs;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
data: {
|
|
300
|
+
agentId: agent.agentId,
|
|
301
|
+
displayName: agent.displayName ?? agent.agentId,
|
|
302
|
+
description: agent.description,
|
|
303
|
+
version: agent.version,
|
|
304
|
+
role: agent.role,
|
|
305
|
+
team: agent.team,
|
|
306
|
+
enabled: agent.enabled,
|
|
307
|
+
capabilities: agent.capabilities ?? [],
|
|
308
|
+
tags: agent.tags ?? [],
|
|
309
|
+
systemPrompt: agent.systemPrompt,
|
|
310
|
+
workflow: agent.workflow?.map(step => ({
|
|
311
|
+
stepId: step.stepId,
|
|
312
|
+
name: step.name,
|
|
313
|
+
type: step.type,
|
|
314
|
+
config: step.config,
|
|
315
|
+
dependencies: step.dependencies,
|
|
316
|
+
condition: step.condition,
|
|
317
|
+
})) ?? [],
|
|
318
|
+
orchestration: agent.orchestration,
|
|
319
|
+
personality: agent.personality,
|
|
320
|
+
expertise: agent.expertise,
|
|
321
|
+
// Stats
|
|
322
|
+
stats: {
|
|
323
|
+
executionCount,
|
|
324
|
+
successRate: executionCount > 0 ? successCount / executionCount : 1.0,
|
|
325
|
+
avgDurationMs: executionCount > 0 ? Math.round(totalDurationMs / executionCount) : 0,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
return {
|
|
332
|
+
success: false,
|
|
333
|
+
error: error instanceof Error ? error.message : 'Failed to fetch agent details',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get traces
|
|
339
|
+
*/
|
|
340
|
+
async function handleTraces() {
|
|
341
|
+
const traces = await getTraceData();
|
|
342
|
+
return { success: true, data: traces };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get metrics
|
|
346
|
+
*/
|
|
347
|
+
async function handleMetrics() {
|
|
348
|
+
const metrics = await getMetricsData();
|
|
349
|
+
return { success: true, data: metrics };
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get workflows list
|
|
353
|
+
*/
|
|
354
|
+
async function handleWorkflows() {
|
|
355
|
+
const workflows = await getWorkflowData();
|
|
356
|
+
return { success: true, data: workflows };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get detailed trace with events - enhanced with input/output info
|
|
360
|
+
*/
|
|
361
|
+
async function handleTraceDetail(traceId) {
|
|
362
|
+
try {
|
|
363
|
+
const traceStore = getTraceStore();
|
|
364
|
+
const events = await traceStore.getTrace(traceId);
|
|
365
|
+
if (!events || events.length === 0) {
|
|
366
|
+
return { success: false, error: `Trace not found: ${traceId}` };
|
|
367
|
+
}
|
|
368
|
+
// Build timeline from events
|
|
369
|
+
const timeline = events.map(event => ({
|
|
370
|
+
eventId: event.eventId,
|
|
371
|
+
type: event.type,
|
|
372
|
+
timestamp: event.timestamp,
|
|
373
|
+
durationMs: event.durationMs,
|
|
374
|
+
status: event.status,
|
|
375
|
+
context: event.context,
|
|
376
|
+
payload: event.payload,
|
|
377
|
+
}));
|
|
378
|
+
// Derive trace metadata from events
|
|
379
|
+
const startEvent = events.find(e => e.type === 'run.start' || e.type === 'discussion.start');
|
|
380
|
+
const endEvent = events.find(e => e.type === 'run.end' || e.type === 'discussion.end');
|
|
381
|
+
const stepEvents = events.filter(e => e.type === 'step.end');
|
|
382
|
+
const errorEvents = events.filter(e => e.type === 'error');
|
|
383
|
+
// Extract detailed info from start event
|
|
384
|
+
const startPayload = startEvent?.payload;
|
|
385
|
+
const endPayload = endEvent?.payload;
|
|
386
|
+
const context = startEvent?.context;
|
|
387
|
+
// Determine command type and extract relevant info
|
|
388
|
+
const command = startPayload?.command;
|
|
389
|
+
// First try to detect from explicit command field
|
|
390
|
+
let commandType;
|
|
391
|
+
if (command?.includes('discuss')) {
|
|
392
|
+
commandType = 'discuss';
|
|
393
|
+
}
|
|
394
|
+
else if (command?.includes('agent') || command === 'agent') {
|
|
395
|
+
commandType = 'agent';
|
|
396
|
+
}
|
|
397
|
+
else if (command?.includes('call')) {
|
|
398
|
+
commandType = 'call';
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// Fallback: detect from context/payload fields when command is not set
|
|
402
|
+
const hasAgentId = context?.agentId || startPayload?.agentId;
|
|
403
|
+
const hasTopic = startPayload?.topic;
|
|
404
|
+
const hasPrompt = startPayload?.prompt;
|
|
405
|
+
commandType = hasAgentId ? 'agent' :
|
|
406
|
+
hasTopic ? 'discuss' :
|
|
407
|
+
hasPrompt ? 'call' : 'unknown';
|
|
408
|
+
}
|
|
409
|
+
// Extract input/output based on command type
|
|
410
|
+
let input;
|
|
411
|
+
let output;
|
|
412
|
+
let provider;
|
|
413
|
+
let model;
|
|
414
|
+
if (commandType === 'call') {
|
|
415
|
+
provider = context?.provider;
|
|
416
|
+
model = context?.model;
|
|
417
|
+
input = {
|
|
418
|
+
prompt: startPayload?.prompt, // Full prompt content
|
|
419
|
+
promptLength: startPayload?.promptLength,
|
|
420
|
+
mode: startPayload?.mode,
|
|
421
|
+
hasSystemPrompt: startPayload?.hasSystemPrompt,
|
|
422
|
+
};
|
|
423
|
+
output = {
|
|
424
|
+
response: endPayload?.response, // Full response content
|
|
425
|
+
responseLength: endPayload?.responseLength,
|
|
426
|
+
success: endPayload?.success,
|
|
427
|
+
latencyMs: endPayload?.latencyMs,
|
|
428
|
+
usage: endPayload?.usage,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
else if (commandType === 'discuss') {
|
|
432
|
+
input = {
|
|
433
|
+
topic: startPayload?.topic,
|
|
434
|
+
pattern: startPayload?.pattern,
|
|
435
|
+
providers: startPayload?.providers,
|
|
436
|
+
rounds: startPayload?.rounds,
|
|
437
|
+
recursive: startPayload?.recursive,
|
|
438
|
+
};
|
|
439
|
+
output = {
|
|
440
|
+
responses: endPayload?.responses, // Provider responses
|
|
441
|
+
consensusReached: endPayload?.consensusReached,
|
|
442
|
+
consensus: endPayload?.consensus, // Consensus metadata (method, agreementScore, etc.)
|
|
443
|
+
synthesis: endPayload?.synthesis, // Final synthesized text
|
|
444
|
+
rounds: endPayload?.rounds,
|
|
445
|
+
success: endPayload?.success,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
else if (commandType === 'agent') {
|
|
449
|
+
const agentId = startPayload?.agentId;
|
|
450
|
+
// Support both 'task' (CLI) and 'input' (MCP) field names
|
|
451
|
+
const taskOrInput = startPayload?.task ?? startPayload?.input;
|
|
452
|
+
input = {
|
|
453
|
+
agentId,
|
|
454
|
+
task: taskOrInput,
|
|
455
|
+
command: startPayload?.command,
|
|
456
|
+
};
|
|
457
|
+
output = {
|
|
458
|
+
stepCount: endPayload?.stepCount,
|
|
459
|
+
result: endPayload?.result ?? endPayload?.output,
|
|
460
|
+
success: endPayload?.success,
|
|
461
|
+
error: endPayload?.error,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
// Build workflow steps from step events with full details
|
|
465
|
+
const workflowSteps = stepEvents.map(step => {
|
|
466
|
+
const stepPayload = step.payload;
|
|
467
|
+
const stepContext = step.context;
|
|
468
|
+
return {
|
|
469
|
+
stepId: stepPayload?.stepId ?? stepContext?.stepId,
|
|
470
|
+
iteration: stepPayload?.iteration,
|
|
471
|
+
intent: stepPayload?.intent,
|
|
472
|
+
action: stepPayload?.action,
|
|
473
|
+
prompt: stepPayload?.prompt, // Full prompt for this step
|
|
474
|
+
response: stepPayload?.response, // Full response for this step
|
|
475
|
+
responseLength: stepPayload?.responseLength,
|
|
476
|
+
success: stepPayload?.success,
|
|
477
|
+
durationMs: step.durationMs,
|
|
478
|
+
latencyMs: stepPayload?.latencyMs,
|
|
479
|
+
provider: stepContext?.provider,
|
|
480
|
+
model: stepContext?.model,
|
|
481
|
+
output: stepPayload?.output,
|
|
482
|
+
error: stepPayload?.error,
|
|
483
|
+
timestamp: step.timestamp,
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
// Determine status from events
|
|
487
|
+
let status = 'pending';
|
|
488
|
+
if (endEvent) {
|
|
489
|
+
status = endPayload?.success === true ? 'success' : 'failure';
|
|
490
|
+
}
|
|
491
|
+
else if (startEvent) {
|
|
492
|
+
status = 'running';
|
|
493
|
+
}
|
|
494
|
+
if (errorEvents.length > 0 && status !== 'failure') {
|
|
495
|
+
status = 'failure';
|
|
496
|
+
}
|
|
497
|
+
// Calculate total duration
|
|
498
|
+
const durationMs = endEvent?.durationMs ??
|
|
499
|
+
(endEvent && startEvent ?
|
|
500
|
+
new Date(endEvent.timestamp).getTime() - new Date(startEvent.timestamp).getTime() :
|
|
501
|
+
undefined);
|
|
502
|
+
return {
|
|
503
|
+
success: true,
|
|
504
|
+
data: {
|
|
505
|
+
traceId,
|
|
506
|
+
status,
|
|
507
|
+
command,
|
|
508
|
+
commandType,
|
|
509
|
+
startTime: startEvent?.timestamp ?? events[0]?.timestamp,
|
|
510
|
+
endTime: endEvent?.timestamp,
|
|
511
|
+
durationMs,
|
|
512
|
+
// Execution context
|
|
513
|
+
provider,
|
|
514
|
+
model,
|
|
515
|
+
// Input/Output
|
|
516
|
+
input,
|
|
517
|
+
output,
|
|
518
|
+
// Workflow steps (for agent runs)
|
|
519
|
+
workflowSteps: workflowSteps.length > 0 ? workflowSteps : undefined,
|
|
520
|
+
// Summary
|
|
521
|
+
summary: {
|
|
522
|
+
eventCount: events.length,
|
|
523
|
+
stepCount: stepEvents.length,
|
|
524
|
+
errorCount: errorEvents.length,
|
|
525
|
+
},
|
|
526
|
+
// Full timeline for advanced view
|
|
527
|
+
timeline,
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
return {
|
|
533
|
+
success: false,
|
|
534
|
+
error: error instanceof Error ? error.message : 'Failed to fetch trace',
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get workflow definition
|
|
540
|
+
*/
|
|
541
|
+
async function handleWorkflowDetail(workflowId) {
|
|
542
|
+
try {
|
|
543
|
+
const loader = await getWorkflowLoader();
|
|
544
|
+
const workflow = await loader.load(workflowId);
|
|
545
|
+
if (!workflow) {
|
|
546
|
+
return { success: false, error: `Workflow not found: ${workflowId}` };
|
|
547
|
+
}
|
|
548
|
+
// Build DAG structure for visualization
|
|
549
|
+
const nodes = workflow.steps.map((step, index) => ({
|
|
550
|
+
id: step.stepId,
|
|
551
|
+
type: step.type,
|
|
552
|
+
name: step.name ?? step.stepId,
|
|
553
|
+
config: step.config,
|
|
554
|
+
position: index,
|
|
555
|
+
}));
|
|
556
|
+
// Build edges based on step order (sequential by default)
|
|
557
|
+
const edges = [];
|
|
558
|
+
for (let i = 0; i < workflow.steps.length - 1; i++) {
|
|
559
|
+
const currentStep = workflow.steps[i];
|
|
560
|
+
const nextStep = workflow.steps[i + 1];
|
|
561
|
+
if (currentStep && nextStep) {
|
|
562
|
+
edges.push({
|
|
563
|
+
from: currentStep.stepId,
|
|
564
|
+
to: nextStep.stepId,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
success: true,
|
|
570
|
+
data: {
|
|
571
|
+
workflowId: workflow.workflowId,
|
|
572
|
+
version: workflow.version,
|
|
573
|
+
name: workflow.name,
|
|
574
|
+
description: workflow.description,
|
|
575
|
+
steps: workflow.steps,
|
|
576
|
+
dag: { nodes, edges },
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
return {
|
|
582
|
+
success: false,
|
|
583
|
+
error: error instanceof Error ? error.message : 'Failed to fetch workflow',
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get provider history - all trace events associated with a provider
|
|
589
|
+
* Uses context.providerId for filtering (INV-TR-010)
|
|
590
|
+
*/
|
|
591
|
+
async function handleProviderHistory(providerId) {
|
|
592
|
+
try {
|
|
593
|
+
const traceStore = getTraceStore();
|
|
594
|
+
const traces = await traceStore.listTraces(100);
|
|
595
|
+
const providerTraces = [];
|
|
596
|
+
// Filter traces by providerId
|
|
597
|
+
for (const trace of traces) {
|
|
598
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
599
|
+
const startEvent = events.find(e => e.type === 'run.start');
|
|
600
|
+
const endEvent = events.find(e => e.type === 'run.end');
|
|
601
|
+
// Check if any event in this trace has the matching providerId
|
|
602
|
+
const hasProvider = events.some(e => {
|
|
603
|
+
const ctx = e.context;
|
|
604
|
+
return ctx?.providerId === providerId || ctx?.provider === providerId;
|
|
605
|
+
});
|
|
606
|
+
if (hasProvider) {
|
|
607
|
+
const startPayload = startEvent?.payload;
|
|
608
|
+
const endPayload = endEvent?.payload;
|
|
609
|
+
const context = startEvent?.context;
|
|
610
|
+
const traceEntry = {
|
|
611
|
+
traceId: trace.traceId,
|
|
612
|
+
command: startPayload?.command ?? 'unknown',
|
|
613
|
+
status: trace.status,
|
|
614
|
+
startTime: trace.startTime,
|
|
615
|
+
};
|
|
616
|
+
if (trace.durationMs !== undefined)
|
|
617
|
+
traceEntry.durationMs = trace.durationMs;
|
|
618
|
+
if (context?.model)
|
|
619
|
+
traceEntry.model = context.model;
|
|
620
|
+
const prompt = startPayload?.prompt ?? '';
|
|
621
|
+
if (prompt)
|
|
622
|
+
traceEntry.promptPreview = prompt.slice(0, 100);
|
|
623
|
+
const response = endPayload?.response ?? '';
|
|
624
|
+
if (response)
|
|
625
|
+
traceEntry.responsePreview = response.slice(0, 100);
|
|
626
|
+
if (context?.tokenUsage)
|
|
627
|
+
traceEntry.tokenUsage = context.tokenUsage;
|
|
628
|
+
providerTraces.push(traceEntry);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Get provider info
|
|
632
|
+
const registry = getProviderRegistry();
|
|
633
|
+
const provider = registry.get(providerId);
|
|
634
|
+
return {
|
|
635
|
+
success: true,
|
|
636
|
+
data: {
|
|
637
|
+
providerId,
|
|
638
|
+
providerName: providerId,
|
|
639
|
+
available: provider ? await provider.isAvailable() : false,
|
|
640
|
+
totalRequests: providerTraces.length,
|
|
641
|
+
successRate: providerTraces.length > 0
|
|
642
|
+
? providerTraces.filter(t => t.status === 'success').length / providerTraces.length
|
|
643
|
+
: 1.0,
|
|
644
|
+
avgLatencyMs: providerTraces.length > 0
|
|
645
|
+
? providerTraces.reduce((sum, t) => sum + (t.durationMs ?? 0), 0) / providerTraces.length
|
|
646
|
+
: 0,
|
|
647
|
+
requests: providerTraces,
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
return {
|
|
653
|
+
success: false,
|
|
654
|
+
error: error instanceof Error ? error.message : 'Failed to fetch provider history',
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Get agent history - all trace events associated with an agent
|
|
660
|
+
* Uses context.agentId for filtering (INV-TR-011)
|
|
661
|
+
*/
|
|
662
|
+
async function handleAgentHistory(agentId) {
|
|
663
|
+
try {
|
|
664
|
+
const traceStore = getTraceStore();
|
|
665
|
+
const traces = await traceStore.listTraces(100);
|
|
666
|
+
const agentTraces = [];
|
|
667
|
+
// Filter traces by agentId
|
|
668
|
+
for (const trace of traces) {
|
|
669
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
670
|
+
const startEvent = events.find(e => e.type === 'run.start');
|
|
671
|
+
const endEvent = events.find(e => e.type === 'run.end');
|
|
672
|
+
// Check if any event in this trace has the matching agentId
|
|
673
|
+
const hasAgent = events.some(e => {
|
|
674
|
+
const ctx = e.context;
|
|
675
|
+
return ctx?.agentId === agentId;
|
|
676
|
+
});
|
|
677
|
+
if (hasAgent) {
|
|
678
|
+
const startPayload = startEvent?.payload;
|
|
679
|
+
const endPayload = endEvent?.payload;
|
|
680
|
+
const traceEntry = {
|
|
681
|
+
traceId: trace.traceId,
|
|
682
|
+
status: trace.status,
|
|
683
|
+
startTime: trace.startTime,
|
|
684
|
+
};
|
|
685
|
+
if (startPayload?.task)
|
|
686
|
+
traceEntry.task = startPayload.task;
|
|
687
|
+
if (trace.durationMs !== undefined)
|
|
688
|
+
traceEntry.durationMs = trace.durationMs;
|
|
689
|
+
if (endPayload?.stepCount !== undefined)
|
|
690
|
+
traceEntry.stepCount = endPayload.stepCount;
|
|
691
|
+
if (endPayload?.successfulSteps !== undefined)
|
|
692
|
+
traceEntry.successfulSteps = endPayload.successfulSteps;
|
|
693
|
+
const resultStr = endPayload?.result ?? '';
|
|
694
|
+
if (resultStr)
|
|
695
|
+
traceEntry.result = resultStr.slice(0, 200);
|
|
696
|
+
if (endPayload?.error)
|
|
697
|
+
traceEntry.error = endPayload.error;
|
|
698
|
+
agentTraces.push(traceEntry);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Get agent info
|
|
702
|
+
const registry = await getAgentRegistry();
|
|
703
|
+
const agent = await registry.get(agentId);
|
|
704
|
+
return {
|
|
705
|
+
success: true,
|
|
706
|
+
data: {
|
|
707
|
+
agentId,
|
|
708
|
+
displayName: agent?.displayName ?? agentId,
|
|
709
|
+
description: agent?.description,
|
|
710
|
+
enabled: agent?.enabled ?? false,
|
|
711
|
+
capabilities: agent?.capabilities ?? [],
|
|
712
|
+
totalExecutions: agentTraces.length,
|
|
713
|
+
successRate: agentTraces.length > 0
|
|
714
|
+
? agentTraces.filter(t => t.status === 'success').length / agentTraces.length
|
|
715
|
+
: 1.0,
|
|
716
|
+
avgDurationMs: agentTraces.length > 0
|
|
717
|
+
? agentTraces.reduce((sum, t) => sum + (t.durationMs ?? 0), 0) / agentTraces.length
|
|
718
|
+
: 0,
|
|
719
|
+
executions: agentTraces,
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
return {
|
|
725
|
+
success: false,
|
|
726
|
+
error: error instanceof Error ? error.message : 'Failed to fetch agent history',
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Search traces with filters
|
|
732
|
+
*/
|
|
733
|
+
async function handleTraceSearch(filters) {
|
|
734
|
+
try {
|
|
735
|
+
const traceStore = getTraceStore();
|
|
736
|
+
const traces = await traceStore.listTraces(filters.limit ?? 100);
|
|
737
|
+
const filteredTraces = [];
|
|
738
|
+
for (const trace of traces) {
|
|
739
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
740
|
+
const startEvent = events.find(e => e.type === 'run.start' || e.type === 'discussion.start' || e.type === 'workflow.start');
|
|
741
|
+
// Extract context from first event
|
|
742
|
+
const context = startEvent?.context;
|
|
743
|
+
const payload = startEvent?.payload;
|
|
744
|
+
const eventProviderId = context?.providerId;
|
|
745
|
+
const eventAgentId = context?.agentId;
|
|
746
|
+
const eventType = startEvent?.type;
|
|
747
|
+
// Apply filters
|
|
748
|
+
if (filters.providerId && eventProviderId !== filters.providerId)
|
|
749
|
+
continue;
|
|
750
|
+
if (filters.agentId && eventAgentId !== filters.agentId)
|
|
751
|
+
continue;
|
|
752
|
+
if (filters.type && eventType !== filters.type)
|
|
753
|
+
continue;
|
|
754
|
+
const traceEntry = {
|
|
755
|
+
traceId: trace.traceId,
|
|
756
|
+
status: trace.status,
|
|
757
|
+
startTime: trace.startTime,
|
|
758
|
+
};
|
|
759
|
+
if (payload?.command)
|
|
760
|
+
traceEntry.command = payload.command;
|
|
761
|
+
if (trace.durationMs !== undefined)
|
|
762
|
+
traceEntry.durationMs = trace.durationMs;
|
|
763
|
+
if (eventProviderId)
|
|
764
|
+
traceEntry.providerId = eventProviderId;
|
|
765
|
+
if (eventAgentId)
|
|
766
|
+
traceEntry.agentId = eventAgentId;
|
|
767
|
+
if (eventType)
|
|
768
|
+
traceEntry.eventType = eventType;
|
|
769
|
+
filteredTraces.push(traceEntry);
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
success: true,
|
|
773
|
+
data: {
|
|
774
|
+
count: filteredTraces.length,
|
|
775
|
+
filters,
|
|
776
|
+
traces: filteredTraces,
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
return {
|
|
782
|
+
success: false,
|
|
783
|
+
error: error instanceof Error ? error.message : 'Failed to search traces',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get trace tree - hierarchical view of trace and its children
|
|
789
|
+
* INV-TR-020: All traces in hierarchy share rootTraceId
|
|
790
|
+
* INV-TR-021: Shows parent-child relationships
|
|
791
|
+
*/
|
|
792
|
+
async function handleTraceTree(traceId) {
|
|
793
|
+
try {
|
|
794
|
+
const traceStore = getTraceStore();
|
|
795
|
+
const tree = await traceStore.getTraceTree(traceId);
|
|
796
|
+
if (!tree) {
|
|
797
|
+
return { success: false, error: `Trace not found: ${traceId}` };
|
|
798
|
+
}
|
|
799
|
+
const formatNode = (node) => ({
|
|
800
|
+
traceId: node.trace.traceId,
|
|
801
|
+
shortId: node.trace.traceId.slice(0, 8),
|
|
802
|
+
status: node.trace.status,
|
|
803
|
+
agentId: node.trace.agentId,
|
|
804
|
+
durationMs: node.trace.durationMs,
|
|
805
|
+
depth: node.trace.traceDepth ?? 0,
|
|
806
|
+
eventCount: node.trace.eventCount,
|
|
807
|
+
startTime: node.trace.startTime,
|
|
808
|
+
children: (node.children).map(formatNode),
|
|
809
|
+
});
|
|
810
|
+
// Count total nodes
|
|
811
|
+
const countNodes = (node) => 1 + (node.children).reduce((sum, child) => sum + countNodes(child), 0);
|
|
812
|
+
// Get max depth
|
|
813
|
+
const getMaxDepth = (node) => {
|
|
814
|
+
const childDepths = (node.children).map(getMaxDepth);
|
|
815
|
+
return childDepths.length > 0 ? Math.max(...childDepths) + 1 : 0;
|
|
816
|
+
};
|
|
817
|
+
const totalTraces = countNodes(tree);
|
|
818
|
+
const maxDepth = getMaxDepth(tree);
|
|
819
|
+
// Build text tree view for display
|
|
820
|
+
const buildTreeView = (node, indent = '') => {
|
|
821
|
+
const statusIcon = node.trace.status === 'success' ? '[OK]' :
|
|
822
|
+
node.trace.status === 'failure' ? '[FAIL]' :
|
|
823
|
+
node.trace.status === 'running' ? '[...]' : '[?]';
|
|
824
|
+
const agentLabel = node.trace.agentId ? ` - ${node.trace.agentId}` : '';
|
|
825
|
+
const durationLabel = node.trace.durationMs ? ` (${node.trace.durationMs}ms)` : '';
|
|
826
|
+
const line = `${indent}${statusIcon} ${node.trace.traceId.slice(0, 8)}${agentLabel}${durationLabel}`;
|
|
827
|
+
const lines = [line];
|
|
828
|
+
const children = node.children;
|
|
829
|
+
for (let i = 0; i < children.length; i++) {
|
|
830
|
+
const isLast = i === children.length - 1;
|
|
831
|
+
const childIndent = indent + (isLast ? ' └─ ' : ' ├─ ');
|
|
832
|
+
const nextIndent = indent + (isLast ? ' ' : ' │ ');
|
|
833
|
+
const childLines = buildTreeView(children[i], nextIndent);
|
|
834
|
+
childLines[0] = childIndent.slice(0, -4) + (isLast ? ' └─ ' : ' ├─ ') + childLines[0].slice(nextIndent.length);
|
|
835
|
+
lines.push(...childLines);
|
|
836
|
+
}
|
|
837
|
+
return lines;
|
|
838
|
+
};
|
|
839
|
+
const treeView = buildTreeView(tree).join('\n');
|
|
840
|
+
return {
|
|
841
|
+
success: true,
|
|
842
|
+
data: {
|
|
843
|
+
traceId,
|
|
844
|
+
rootTraceId: tree.trace.rootTraceId ?? traceId,
|
|
845
|
+
traceCount: totalTraces,
|
|
846
|
+
maxDepth,
|
|
847
|
+
totalDurationMs: tree.totalDurationMs,
|
|
848
|
+
totalEventCount: tree.totalEventCount,
|
|
849
|
+
treeView,
|
|
850
|
+
tree: formatNode(tree),
|
|
851
|
+
},
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
return {
|
|
856
|
+
success: false,
|
|
857
|
+
error: error instanceof Error ? error.message : 'Failed to get trace tree',
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Get workflow execution events - timeline of a workflow execution
|
|
863
|
+
* Uses workflow.start, workflow.step, workflow.end events (INV-TR-013)
|
|
864
|
+
*/
|
|
865
|
+
async function handleWorkflowEvents(workflowId) {
|
|
866
|
+
try {
|
|
867
|
+
const traceStore = getTraceStore();
|
|
868
|
+
const traces = await traceStore.listTraces(100);
|
|
869
|
+
const workflowExecutions = [];
|
|
870
|
+
// Find traces with workflow events for this workflowId
|
|
871
|
+
for (const trace of traces) {
|
|
872
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
873
|
+
// Look for workflow events
|
|
874
|
+
const workflowStart = events.find(e => e.type === 'workflow.start');
|
|
875
|
+
const workflowEnd = events.find(e => e.type === 'workflow.end');
|
|
876
|
+
const workflowSteps = events.filter(e => e.type === 'workflow.step');
|
|
877
|
+
// Check if this is the right workflow
|
|
878
|
+
const startContext = workflowStart?.context;
|
|
879
|
+
const endContext = workflowEnd?.context;
|
|
880
|
+
if (startContext?.workflowId === workflowId || endContext?.workflowId === workflowId) {
|
|
881
|
+
const startPayload = workflowStart?.payload;
|
|
882
|
+
const endPayload = workflowEnd?.payload;
|
|
883
|
+
// Build timeline
|
|
884
|
+
const timeline = [];
|
|
885
|
+
// Add start event
|
|
886
|
+
if (workflowStart) {
|
|
887
|
+
timeline.push({
|
|
888
|
+
type: 'workflow.start',
|
|
889
|
+
timestamp: workflowStart.timestamp,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
// Add step events
|
|
893
|
+
for (const step of workflowSteps) {
|
|
894
|
+
const stepPayload = step.payload;
|
|
895
|
+
const stepEntry = {
|
|
896
|
+
type: 'workflow.step',
|
|
897
|
+
timestamp: step.timestamp,
|
|
898
|
+
};
|
|
899
|
+
if (stepPayload?.stepId)
|
|
900
|
+
stepEntry.stepId = stepPayload.stepId;
|
|
901
|
+
if (stepPayload?.stepName)
|
|
902
|
+
stepEntry.stepName = stepPayload.stepName;
|
|
903
|
+
if (stepPayload?.success !== undefined)
|
|
904
|
+
stepEntry.success = stepPayload.success;
|
|
905
|
+
if (step.durationMs !== undefined)
|
|
906
|
+
stepEntry.durationMs = step.durationMs;
|
|
907
|
+
if (stepPayload?.error)
|
|
908
|
+
stepEntry.error = stepPayload.error;
|
|
909
|
+
timeline.push(stepEntry);
|
|
910
|
+
}
|
|
911
|
+
// Add end event
|
|
912
|
+
if (workflowEnd) {
|
|
913
|
+
const endEntry = {
|
|
914
|
+
type: 'workflow.end',
|
|
915
|
+
timestamp: workflowEnd.timestamp,
|
|
916
|
+
};
|
|
917
|
+
if (endPayload?.success !== undefined)
|
|
918
|
+
endEntry.success = endPayload.success;
|
|
919
|
+
if (workflowEnd.durationMs !== undefined)
|
|
920
|
+
endEntry.durationMs = workflowEnd.durationMs;
|
|
921
|
+
if (endPayload?.error)
|
|
922
|
+
endEntry.error = endPayload.error;
|
|
923
|
+
timeline.push(endEntry);
|
|
924
|
+
}
|
|
925
|
+
const executionEntry = {
|
|
926
|
+
traceId: trace.traceId,
|
|
927
|
+
status: endPayload?.success === true ? 'success' : (workflowEnd ? 'failure' : 'running'),
|
|
928
|
+
startTime: workflowStart?.timestamp ?? trace.startTime,
|
|
929
|
+
totalSteps: startPayload?.stepCount ?? endPayload?.totalSteps ?? workflowSteps.length,
|
|
930
|
+
completedSteps: endPayload?.completedSteps ?? workflowSteps.length,
|
|
931
|
+
failedSteps: endPayload?.failedSteps ?? workflowSteps.filter(s => {
|
|
932
|
+
const p = s.payload;
|
|
933
|
+
return p?.success === false;
|
|
934
|
+
}).length,
|
|
935
|
+
timeline,
|
|
936
|
+
};
|
|
937
|
+
if (workflowEnd?.timestamp)
|
|
938
|
+
executionEntry.endTime = workflowEnd.timestamp;
|
|
939
|
+
const durationMs = endPayload?.totalDurationMs ?? trace.durationMs;
|
|
940
|
+
if (durationMs !== undefined)
|
|
941
|
+
executionEntry.durationMs = durationMs;
|
|
942
|
+
workflowExecutions.push(executionEntry);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
// Get workflow definition
|
|
946
|
+
const loader = await getWorkflowLoader();
|
|
947
|
+
const workflow = await loader.load(workflowId);
|
|
948
|
+
return {
|
|
949
|
+
success: true,
|
|
950
|
+
data: {
|
|
951
|
+
workflowId,
|
|
952
|
+
workflowName: workflow?.name,
|
|
953
|
+
workflowVersion: workflow?.version,
|
|
954
|
+
stepCount: workflow?.steps.length ?? 0,
|
|
955
|
+
totalExecutions: workflowExecutions.length,
|
|
956
|
+
successRate: workflowExecutions.length > 0
|
|
957
|
+
? workflowExecutions.filter(e => e.status === 'success').length / workflowExecutions.length
|
|
958
|
+
: 1.0,
|
|
959
|
+
executions: workflowExecutions,
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
return {
|
|
965
|
+
success: false,
|
|
966
|
+
error: error instanceof Error ? error.message : 'Failed to fetch workflow events',
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Fetch provider data
|
|
972
|
+
*/
|
|
973
|
+
async function getProviderData() {
|
|
974
|
+
try {
|
|
975
|
+
const registry = getProviderRegistry();
|
|
976
|
+
const providerIds = registry.getProviderIds();
|
|
977
|
+
const results = await Promise.all(providerIds.map(async (providerId) => {
|
|
978
|
+
const provider = registry.get(providerId);
|
|
979
|
+
if (!provider)
|
|
980
|
+
return null;
|
|
981
|
+
const startTime = Date.now();
|
|
982
|
+
try {
|
|
983
|
+
const available = await provider.isAvailable();
|
|
984
|
+
const latencyMs = Date.now() - startTime;
|
|
985
|
+
return {
|
|
986
|
+
providerId,
|
|
987
|
+
name: providerId,
|
|
988
|
+
available,
|
|
989
|
+
latencyMs: available ? latencyMs : undefined,
|
|
990
|
+
circuitState: 'closed',
|
|
991
|
+
lastUsed: undefined,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
catch {
|
|
995
|
+
return {
|
|
996
|
+
providerId,
|
|
997
|
+
name: providerId,
|
|
998
|
+
available: false,
|
|
999
|
+
latencyMs: undefined,
|
|
1000
|
+
circuitState: 'closed',
|
|
1001
|
+
lastUsed: undefined,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
}));
|
|
1005
|
+
return results.filter((r) => r !== null);
|
|
1006
|
+
}
|
|
1007
|
+
catch {
|
|
1008
|
+
return [];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Fetch session data
|
|
1013
|
+
*/
|
|
1014
|
+
async function getSessionData() {
|
|
1015
|
+
try {
|
|
1016
|
+
const manager = getSessionManager();
|
|
1017
|
+
const sessions = await manager.listSessions({ status: 'active' });
|
|
1018
|
+
return sessions.slice(0, 20).map(session => ({
|
|
1019
|
+
sessionId: session.sessionId,
|
|
1020
|
+
initiator: session.initiator,
|
|
1021
|
+
task: session.task,
|
|
1022
|
+
status: session.status,
|
|
1023
|
+
participantCount: session.participants.filter(p => !p.leftAt).length,
|
|
1024
|
+
createdAt: session.createdAt,
|
|
1025
|
+
durationMs: undefined,
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
catch {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Fetch agent data
|
|
1034
|
+
*/
|
|
1035
|
+
async function getAgentData() {
|
|
1036
|
+
try {
|
|
1037
|
+
const registry = await getAgentRegistry();
|
|
1038
|
+
const agents = await registry.list();
|
|
1039
|
+
return agents.slice(0, 50).map(agent => ({
|
|
1040
|
+
agentId: agent.agentId,
|
|
1041
|
+
displayName: agent.displayName ?? agent.agentId,
|
|
1042
|
+
description: agent.description,
|
|
1043
|
+
enabled: agent.enabled ?? true,
|
|
1044
|
+
capabilities: agent.capabilities ?? [],
|
|
1045
|
+
executionCount: 0,
|
|
1046
|
+
lastExecuted: undefined,
|
|
1047
|
+
}));
|
|
1048
|
+
}
|
|
1049
|
+
catch {
|
|
1050
|
+
return [];
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Fetch trace data with command info from events
|
|
1055
|
+
*/
|
|
1056
|
+
async function getTraceData() {
|
|
1057
|
+
try {
|
|
1058
|
+
const traceStore = getTraceStore();
|
|
1059
|
+
const traces = await traceStore.listTraces(200); // Increased for histogram
|
|
1060
|
+
// Fetch first event of each trace to get command info
|
|
1061
|
+
const tracesWithNames = await Promise.all(traces.map(async (trace) => {
|
|
1062
|
+
let name = `Trace ${trace.traceId.slice(0, 8)}`;
|
|
1063
|
+
let command;
|
|
1064
|
+
let providers;
|
|
1065
|
+
try {
|
|
1066
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
1067
|
+
const firstEvent = events[0];
|
|
1068
|
+
if (firstEvent) {
|
|
1069
|
+
const payload = firstEvent.payload;
|
|
1070
|
+
const context = firstEvent.context;
|
|
1071
|
+
// Extract command from payload
|
|
1072
|
+
if (payload?.command) {
|
|
1073
|
+
command = String(payload.command);
|
|
1074
|
+
name = command;
|
|
1075
|
+
}
|
|
1076
|
+
// Extract providers from payload (for discussions) or context (for single calls)
|
|
1077
|
+
if (payload?.providers && Array.isArray(payload.providers)) {
|
|
1078
|
+
providers = payload.providers;
|
|
1079
|
+
}
|
|
1080
|
+
else if (context?.providerId) {
|
|
1081
|
+
providers = [String(context.providerId)];
|
|
1082
|
+
}
|
|
1083
|
+
// Add additional context for different command types
|
|
1084
|
+
if (payload?.agentId) {
|
|
1085
|
+
name = `ax agent run ${payload.agentId}`;
|
|
1086
|
+
}
|
|
1087
|
+
else if (payload?.topic) {
|
|
1088
|
+
const topic = String(payload.topic).slice(0, 40);
|
|
1089
|
+
name = `ax discuss "${topic}${String(payload.topic).length > 40 ? '...' : ''}"`;
|
|
1090
|
+
}
|
|
1091
|
+
else if (payload?.prompt) {
|
|
1092
|
+
const prompt = String(payload.prompt).slice(0, 40);
|
|
1093
|
+
name = `${command ?? 'ax call'} "${prompt}${String(payload.prompt).length > 40 ? '...' : ''}"`;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
catch {
|
|
1098
|
+
// Ignore errors fetching events
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
traceId: trace.traceId,
|
|
1102
|
+
name,
|
|
1103
|
+
command,
|
|
1104
|
+
status: trace.status === 'pending' || trace.status === 'skipped' ? 'running' : trace.status,
|
|
1105
|
+
eventCount: trace.eventCount,
|
|
1106
|
+
durationMs: trace.durationMs,
|
|
1107
|
+
startTime: trace.startTime,
|
|
1108
|
+
providers,
|
|
1109
|
+
};
|
|
1110
|
+
}));
|
|
1111
|
+
return tracesWithNames;
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
return [];
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Fetch workflow data
|
|
1119
|
+
*/
|
|
1120
|
+
async function getWorkflowData() {
|
|
1121
|
+
try {
|
|
1122
|
+
const loader = await getWorkflowLoader();
|
|
1123
|
+
const workflows = await loader.loadAll();
|
|
1124
|
+
return workflows.slice(0, 50).map(workflow => ({
|
|
1125
|
+
workflowId: workflow.workflowId,
|
|
1126
|
+
name: workflow.name ?? workflow.workflowId,
|
|
1127
|
+
version: workflow.version,
|
|
1128
|
+
stepCount: workflow.steps.length,
|
|
1129
|
+
}));
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
return [];
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Fetch metrics data
|
|
1137
|
+
*/
|
|
1138
|
+
async function getMetricsData() {
|
|
1139
|
+
const memUsage = process.memoryUsage();
|
|
1140
|
+
let activeSessions = 0;
|
|
1141
|
+
let activeAgents = 0;
|
|
1142
|
+
try {
|
|
1143
|
+
const manager = getSessionManager();
|
|
1144
|
+
activeSessions = await manager.countActiveSessions();
|
|
1145
|
+
}
|
|
1146
|
+
catch {
|
|
1147
|
+
// ignore
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
const registry = await getAgentRegistry();
|
|
1151
|
+
const agents = await registry.list();
|
|
1152
|
+
activeAgents = agents.filter(a => a.enabled !== false).length;
|
|
1153
|
+
}
|
|
1154
|
+
catch {
|
|
1155
|
+
// ignore
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
totalRequests: 0, // Would need request tracking
|
|
1159
|
+
successRate: 1.0,
|
|
1160
|
+
avgLatencyMs: 0,
|
|
1161
|
+
activeAgents,
|
|
1162
|
+
activeSessions,
|
|
1163
|
+
memoryUsageMb: memUsage.heapUsed / (1024 * 1024),
|
|
1164
|
+
heapTotalMb: memUsage.heapTotal / (1024 * 1024),
|
|
1165
|
+
rssMb: memUsage.rss / (1024 * 1024),
|
|
1166
|
+
uptime: formatUptime(process.uptime()),
|
|
1167
|
+
uptimeSeconds: process.uptime(),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Format uptime
|
|
1172
|
+
*/
|
|
1173
|
+
function formatUptime(seconds) {
|
|
1174
|
+
const days = Math.floor(seconds / 86400);
|
|
1175
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
1176
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
1177
|
+
const secs = Math.floor(seconds % 60);
|
|
1178
|
+
const parts = [];
|
|
1179
|
+
if (days > 0)
|
|
1180
|
+
parts.push(`${days}d`);
|
|
1181
|
+
if (hours > 0)
|
|
1182
|
+
parts.push(`${hours}h`);
|
|
1183
|
+
if (minutes > 0)
|
|
1184
|
+
parts.push(`${minutes}m`);
|
|
1185
|
+
if (secs > 0 || parts.length === 0)
|
|
1186
|
+
parts.push(`${secs}s`);
|
|
1187
|
+
return parts.join(' ');
|
|
1188
|
+
}
|
|
1189
|
+
//# sourceMappingURL=api.js.map
|