@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.
Files changed (57) hide show
  1. package/dist/bootstrap.d.ts +12 -0
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +23 -0
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +5 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/agent.d.ts +1 -0
  9. package/dist/commands/agent.d.ts.map +1 -1
  10. package/dist/commands/agent.js +211 -27
  11. package/dist/commands/agent.js.map +1 -1
  12. package/dist/commands/call.d.ts +2 -1
  13. package/dist/commands/call.d.ts.map +1 -1
  14. package/dist/commands/call.js +199 -10
  15. package/dist/commands/call.js.map +1 -1
  16. package/dist/commands/cleanup.d.ts +3 -1
  17. package/dist/commands/cleanup.d.ts.map +1 -1
  18. package/dist/commands/cleanup.js +123 -11
  19. package/dist/commands/cleanup.js.map +1 -1
  20. package/dist/commands/discuss.d.ts +2 -1
  21. package/dist/commands/discuss.d.ts.map +1 -1
  22. package/dist/commands/discuss.js +604 -105
  23. package/dist/commands/discuss.js.map +1 -1
  24. package/dist/commands/index.d.ts +1 -0
  25. package/dist/commands/index.d.ts.map +1 -1
  26. package/dist/commands/index.js +2 -0
  27. package/dist/commands/index.js.map +1 -1
  28. package/dist/commands/monitor.d.ts +22 -0
  29. package/dist/commands/monitor.d.ts.map +1 -0
  30. package/dist/commands/monitor.js +255 -0
  31. package/dist/commands/monitor.js.map +1 -0
  32. package/dist/commands/setup.d.ts.map +1 -1
  33. package/dist/commands/setup.js +107 -2
  34. package/dist/commands/setup.js.map +1 -1
  35. package/dist/commands/status.d.ts +6 -5
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +260 -75
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/parser.d.ts.map +1 -1
  40. package/dist/parser.js +30 -0
  41. package/dist/parser.js.map +1 -1
  42. package/dist/types.d.ts +4 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +1 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/utils/database.d.ts.map +1 -1
  47. package/dist/utils/database.js +5 -0
  48. package/dist/utils/database.js.map +1 -1
  49. package/dist/web/api.d.ts +12 -0
  50. package/dist/web/api.d.ts.map +1 -0
  51. package/dist/web/api.js +1189 -0
  52. package/dist/web/api.js.map +1 -0
  53. package/dist/web/dashboard.d.ts +13 -0
  54. package/dist/web/dashboard.d.ts.map +1 -0
  55. package/dist/web/dashboard.js +5290 -0
  56. package/dist/web/dashboard.js.map +1 -0
  57. package/package.json +21 -21
@@ -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