@defai.digital/cli 13.4.5 → 13.4.8
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/bundled/agents/architect.json +117 -0
- package/bundled/agents/auditor.json +114 -0
- package/bundled/agents/bug-hunter.json +128 -0
- package/bundled/agents/builder.json +128 -0
- package/bundled/agents/ceo.json +6 -1
- package/bundled/agents/executor.json +150 -0
- package/bundled/agents/fullstack.json +10 -2
- package/bundled/agents/operator.json +119 -0
- package/bundled/agents/researcher.json +42 -13
- package/bundled/agents/reviewer.json +90 -42
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/commands/discuss.d.ts.map +1 -1
- package/dist/commands/discuss.js +4 -1
- package/dist/commands/discuss.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +65 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/monitor.d.ts.map +1 -1
- package/dist/commands/monitor.js +29 -1
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +119 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts +10 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +151 -49
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +1 -43
- package/dist/commands/update.js.map +1 -1
- package/dist/web/api.d.ts +28 -0
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +508 -40
- package/dist/web/api.js.map +1 -1
- package/dist/web/dashboard.d.ts.map +1 -1
- package/dist/web/dashboard.js +1460 -134
- package/dist/web/dashboard.js.map +1 -1
- package/package.json +21 -21
package/dist/web/api.js
CHANGED
|
@@ -3,12 +3,45 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides REST API endpoints for the web dashboard.
|
|
5
5
|
* Wraps existing domain services to expose via HTTP.
|
|
6
|
+
*
|
|
7
|
+
* KNOWN PERFORMANCE ISSUE (INV-API-PERF-001):
|
|
8
|
+
* Several handlers use an N+1 query pattern where traces are fetched first,
|
|
9
|
+
* then events are fetched for each trace individually. This is acceptable for
|
|
10
|
+
* the local dashboard use case (typically < 200 traces), but would need
|
|
11
|
+
* refactoring for production scale. Consider batch fetching or pagination
|
|
12
|
+
* if performance becomes an issue.
|
|
13
|
+
*
|
|
14
|
+
* Affected handlers: handleStatus, handleProviderHistory, handleAgentHistory,
|
|
15
|
+
* handleTraces, handleWorkflowEvents, handleClassificationStats
|
|
6
16
|
*/
|
|
7
17
|
import * as os from 'node:os';
|
|
8
18
|
import * as fs from 'node:fs';
|
|
9
19
|
import * as path from 'node:path';
|
|
10
20
|
import { fileURLToPath } from 'node:url';
|
|
11
21
|
import { DATA_DIR_NAME, AGENTS_FILENAME, getErrorMessage } from '@defai.digital/contracts';
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// INV-API-VAL-001: Input Validation Constants
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/** Maximum length for any ID parameter to prevent DoS via long strings */
|
|
26
|
+
const MAX_ID_LENGTH = 128;
|
|
27
|
+
/** UUID v4 format regex for trace IDs */
|
|
28
|
+
const UUID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-[4][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
29
|
+
/**
|
|
30
|
+
* Validates a trace ID is a valid UUID and within length limits
|
|
31
|
+
* INV-API-VAL-001: Strict UUID validation for trace IDs
|
|
32
|
+
*/
|
|
33
|
+
function isValidTraceId(id) {
|
|
34
|
+
if (!id || id.length > MAX_ID_LENGTH)
|
|
35
|
+
return false;
|
|
36
|
+
return UUID_REGEX.test(id);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validates a generic ID (workflow, agent, provider) is within length limits
|
|
40
|
+
* INV-API-VAL-001: Length validation for all IDs
|
|
41
|
+
*/
|
|
42
|
+
function isValidId(id) {
|
|
43
|
+
return typeof id === 'string' && id.length > 0 && id.length <= MAX_ID_LENGTH;
|
|
44
|
+
}
|
|
12
45
|
// Get the directory of this module (for finding bundled examples)
|
|
13
46
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
47
|
const __dirname = path.dirname(__filename);
|
|
@@ -16,6 +49,7 @@ import { getProviderRegistry, getTraceStore, getSessionManager as getSharedSessi
|
|
|
16
49
|
import { createPersistentAgentRegistry, } from '@defai.digital/agent-domain';
|
|
17
50
|
import { createWorkflowLoader, findWorkflowDir, } from '@defai.digital/workflow-engine';
|
|
18
51
|
import { CLI_VERSION } from '../commands/help.js';
|
|
52
|
+
import { getProviderStatus as checkProviderHealth } from '../commands/status.js';
|
|
19
53
|
// Workflow loader singleton
|
|
20
54
|
let workflowLoader;
|
|
21
55
|
let workflowLoaderPromise;
|
|
@@ -109,6 +143,16 @@ async function getAgentRegistry() {
|
|
|
109
143
|
}
|
|
110
144
|
return agentRegistryPromise;
|
|
111
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Cached provider status (set at monitor startup)
|
|
148
|
+
*/
|
|
149
|
+
let cachedProviderStatus = null;
|
|
150
|
+
/**
|
|
151
|
+
* Set the cached provider status (called from monitor.ts at startup)
|
|
152
|
+
*/
|
|
153
|
+
export function setCachedProviderStatus(providers) {
|
|
154
|
+
cachedProviderStatus = providers;
|
|
155
|
+
}
|
|
112
156
|
/**
|
|
113
157
|
* Create the API handler function
|
|
114
158
|
*/
|
|
@@ -120,6 +164,7 @@ export function createAPIHandler() {
|
|
|
120
164
|
let response;
|
|
121
165
|
// Check for parameterized routes first
|
|
122
166
|
const traceTreeMatch = apiPath.match(/^\/traces\/([a-f0-9-]+)\/tree$/i);
|
|
167
|
+
const traceClassificationMatch = apiPath.match(/^\/traces\/([a-f0-9-]+)\/classification$/i);
|
|
123
168
|
const traceDetailMatch = apiPath.match(/^\/traces\/([a-f0-9-]+)$/i);
|
|
124
169
|
const workflowDetailMatch = apiPath.match(/^\/workflows\/([a-z0-9-]+)$/i);
|
|
125
170
|
const providerHistoryMatch = apiPath.match(/^\/providers\/([a-z0-9-]+)\/history$/i);
|
|
@@ -128,31 +173,37 @@ export function createAPIHandler() {
|
|
|
128
173
|
const workflowEventsMatch = apiPath.match(/^\/workflows\/([a-z0-9-]+)\/events$/i);
|
|
129
174
|
const traceSearchMatch = apiPath.match(/^\/traces\/search\?(.*)$/i) || apiPath.match(/^\/traces\/search$/i);
|
|
130
175
|
// INV-TR-020 through INV-TR-024: Hierarchical trace tree endpoint
|
|
131
|
-
|
|
176
|
+
// INV-API-VAL-001: Validate all IDs before processing
|
|
177
|
+
if (traceTreeMatch && isValidTraceId(traceTreeMatch[1])) {
|
|
132
178
|
const traceId = traceTreeMatch[1];
|
|
133
179
|
response = await handleTraceTree(traceId);
|
|
134
180
|
}
|
|
135
|
-
else if (
|
|
181
|
+
else if (traceClassificationMatch && isValidTraceId(traceClassificationMatch[1])) {
|
|
182
|
+
// PRD-2026-003: Classification observability endpoint
|
|
183
|
+
const traceId = traceClassificationMatch[1];
|
|
184
|
+
response = await handleTraceClassification(traceId);
|
|
185
|
+
}
|
|
186
|
+
else if (traceDetailMatch && isValidTraceId(traceDetailMatch[1])) {
|
|
136
187
|
const traceId = traceDetailMatch[1];
|
|
137
188
|
response = await handleTraceDetail(traceId);
|
|
138
189
|
}
|
|
139
|
-
else if (workflowDetailMatch && workflowDetailMatch[1]) {
|
|
190
|
+
else if (workflowDetailMatch && isValidId(workflowDetailMatch[1])) {
|
|
140
191
|
const workflowId = workflowDetailMatch[1];
|
|
141
192
|
response = await handleWorkflowDetail(workflowId);
|
|
142
193
|
}
|
|
143
|
-
else if (providerHistoryMatch && providerHistoryMatch[1]) {
|
|
194
|
+
else if (providerHistoryMatch && isValidId(providerHistoryMatch[1])) {
|
|
144
195
|
const providerId = providerHistoryMatch[1];
|
|
145
196
|
response = await handleProviderHistory(providerId);
|
|
146
197
|
}
|
|
147
|
-
else if (agentHistoryMatch && agentHistoryMatch[1]) {
|
|
198
|
+
else if (agentHistoryMatch && isValidId(agentHistoryMatch[1])) {
|
|
148
199
|
const agentId = agentHistoryMatch[1];
|
|
149
200
|
response = await handleAgentHistory(agentId);
|
|
150
201
|
}
|
|
151
|
-
else if (agentDetailMatch && agentDetailMatch[1]) {
|
|
202
|
+
else if (agentDetailMatch && isValidId(agentDetailMatch[1])) {
|
|
152
203
|
const agentId = agentDetailMatch[1];
|
|
153
204
|
response = await handleAgentDetail(agentId);
|
|
154
205
|
}
|
|
155
|
-
else if (workflowEventsMatch && workflowEventsMatch[1]) {
|
|
206
|
+
else if (workflowEventsMatch && isValidId(workflowEventsMatch[1])) {
|
|
156
207
|
const workflowId = workflowEventsMatch[1];
|
|
157
208
|
response = await handleWorkflowEvents(workflowId);
|
|
158
209
|
}
|
|
@@ -187,6 +238,17 @@ export function createAPIHandler() {
|
|
|
187
238
|
case '/providers':
|
|
188
239
|
response = await handleProviders();
|
|
189
240
|
break;
|
|
241
|
+
case '/providers/refresh':
|
|
242
|
+
// POST endpoint to refresh provider health status
|
|
243
|
+
if (req.method === 'POST') {
|
|
244
|
+
response = await handleProviderRefresh();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
248
|
+
res.end(JSON.stringify({ success: false, error: 'Method not allowed' }));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
190
252
|
case '/sessions':
|
|
191
253
|
response = await handleSessions();
|
|
192
254
|
break;
|
|
@@ -232,12 +294,13 @@ export function createAPIHandler() {
|
|
|
232
294
|
* Get full system status
|
|
233
295
|
*/
|
|
234
296
|
async function handleStatus() {
|
|
235
|
-
const [providers, sessions, agents, traces, metrics] = await Promise.all([
|
|
297
|
+
const [providers, sessions, agents, traces, metrics, classificationMetrics] = await Promise.all([
|
|
236
298
|
getProviderData(),
|
|
237
299
|
getSessionData(),
|
|
238
300
|
getAgentData(),
|
|
239
301
|
getTraceData(),
|
|
240
302
|
getMetricsData(),
|
|
303
|
+
getClassificationMetrics(),
|
|
241
304
|
]);
|
|
242
305
|
// Determine health status
|
|
243
306
|
const healthyProviders = providers.filter(p => p.available).length;
|
|
@@ -256,6 +319,8 @@ async function handleStatus() {
|
|
|
256
319
|
agents,
|
|
257
320
|
traces,
|
|
258
321
|
metrics,
|
|
322
|
+
// PRD-2026-003: Classification observability
|
|
323
|
+
classification: classificationMetrics,
|
|
259
324
|
},
|
|
260
325
|
};
|
|
261
326
|
}
|
|
@@ -266,6 +331,50 @@ async function handleProviders() {
|
|
|
266
331
|
const providers = await getProviderData();
|
|
267
332
|
return { success: true, data: providers };
|
|
268
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Helper to normalize circuit state for dashboard
|
|
336
|
+
*/
|
|
337
|
+
function normalizeCircuitState(state) {
|
|
338
|
+
if (state === 'halfOpen')
|
|
339
|
+
return 'half-open';
|
|
340
|
+
if (state === 'open')
|
|
341
|
+
return 'open';
|
|
342
|
+
return 'closed';
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Refresh provider status by running actual health checks
|
|
346
|
+
* This triggers real LLM calls to test each provider
|
|
347
|
+
*/
|
|
348
|
+
async function handleProviderRefresh() {
|
|
349
|
+
try {
|
|
350
|
+
// Run real health checks (sends "hello" to each provider)
|
|
351
|
+
const providerStatuses = await checkProviderHealth(false);
|
|
352
|
+
// Convert to dashboard format and update cache
|
|
353
|
+
const dashboardProviders = providerStatuses.map(p => ({
|
|
354
|
+
providerId: p.providerId,
|
|
355
|
+
name: p.providerId,
|
|
356
|
+
available: p.available,
|
|
357
|
+
latencyMs: p.latencyMs,
|
|
358
|
+
circuitState: normalizeCircuitState(p.circuitState),
|
|
359
|
+
lastUsed: undefined,
|
|
360
|
+
}));
|
|
361
|
+
// Update the cached status
|
|
362
|
+
setCachedProviderStatus(dashboardProviders);
|
|
363
|
+
return {
|
|
364
|
+
success: true,
|
|
365
|
+
data: {
|
|
366
|
+
providers: dashboardProviders,
|
|
367
|
+
refreshedAt: new Date().toISOString(),
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: getErrorMessage(error, 'Failed to refresh provider status'),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
269
378
|
/**
|
|
270
379
|
* Get sessions
|
|
271
380
|
*/
|
|
@@ -310,37 +419,85 @@ async function handleAgentDetail(agentId) {
|
|
|
310
419
|
totalDurationMs += trace.durationMs;
|
|
311
420
|
}
|
|
312
421
|
}
|
|
422
|
+
// PRD-2026-004: Build response with all meta-agent fields
|
|
423
|
+
const responseData = {
|
|
424
|
+
agentId: agent.agentId,
|
|
425
|
+
displayName: agent.displayName ?? agent.agentId,
|
|
426
|
+
description: agent.description,
|
|
427
|
+
version: agent.version,
|
|
428
|
+
role: agent.role,
|
|
429
|
+
team: agent.team,
|
|
430
|
+
enabled: agent.enabled,
|
|
431
|
+
capabilities: agent.capabilities ?? [],
|
|
432
|
+
tags: agent.tags ?? [],
|
|
433
|
+
systemPrompt: agent.systemPrompt,
|
|
434
|
+
workflow: agent.workflow?.map(step => ({
|
|
435
|
+
stepId: step.stepId,
|
|
436
|
+
name: step.name,
|
|
437
|
+
type: step.type,
|
|
438
|
+
config: step.config,
|
|
439
|
+
dependencies: step.dependencies,
|
|
440
|
+
condition: step.condition,
|
|
441
|
+
})) ?? [],
|
|
442
|
+
orchestration: agent.orchestration,
|
|
443
|
+
personality: agent.personality,
|
|
444
|
+
expertise: agent.expertise,
|
|
445
|
+
// Stats
|
|
446
|
+
stats: {
|
|
447
|
+
executionCount,
|
|
448
|
+
successRate: executionCount > 0 ? successCount / executionCount : 0,
|
|
449
|
+
avgDurationMs: executionCount > 0 ? Math.round(totalDurationMs / executionCount) : 0,
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
// PRD-2026-004: Add meta-agent architecture fields
|
|
453
|
+
if (agent.metaAgent !== undefined)
|
|
454
|
+
responseData.metaAgent = agent.metaAgent;
|
|
455
|
+
if (agent.archetype !== undefined)
|
|
456
|
+
responseData.archetype = agent.archetype;
|
|
457
|
+
if (agent.orchestrates?.length)
|
|
458
|
+
responseData.orchestrates = agent.orchestrates;
|
|
459
|
+
if (agent.replaces?.length)
|
|
460
|
+
responseData.replaces = agent.replaces;
|
|
461
|
+
if (agent.canDelegateToArchetypes?.length)
|
|
462
|
+
responseData.canDelegateToArchetypes = agent.canDelegateToArchetypes;
|
|
463
|
+
if (agent.canDelegateToMetaAgents?.length)
|
|
464
|
+
responseData.canDelegateToMetaAgents = agent.canDelegateToMetaAgents;
|
|
465
|
+
// PRD-2026-004: Add task classifier config
|
|
466
|
+
if (agent.taskClassifier) {
|
|
467
|
+
responseData.taskClassifier = {
|
|
468
|
+
enabled: agent.taskClassifier.enabled ?? true,
|
|
469
|
+
rules: agent.taskClassifier.rules?.map(rule => ({
|
|
470
|
+
pattern: rule.pattern,
|
|
471
|
+
taskType: rule.taskType,
|
|
472
|
+
workflow: rule.workflow,
|
|
473
|
+
priority: rule.priority ?? 50,
|
|
474
|
+
})) ?? [],
|
|
475
|
+
defaultWorkflow: agent.taskClassifier.defaultWorkflow,
|
|
476
|
+
fuzzyMatching: agent.taskClassifier.fuzzyMatching,
|
|
477
|
+
minConfidence: agent.taskClassifier.minConfidence,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
// PRD-2026-004: Add dynamic capabilities config
|
|
481
|
+
if (agent.dynamicCapabilities) {
|
|
482
|
+
responseData.dynamicCapabilities = agent.dynamicCapabilities;
|
|
483
|
+
}
|
|
484
|
+
// PRD-2026-004: Add review modes config
|
|
485
|
+
if (agent.reviewModes) {
|
|
486
|
+
responseData.reviewModes = agent.reviewModes;
|
|
487
|
+
}
|
|
488
|
+
// PRD-2026-004: Add capability mappings
|
|
489
|
+
if (agent.capabilityMappings?.length) {
|
|
490
|
+
responseData.capabilityMappings = agent.capabilityMappings.map(mapping => ({
|
|
491
|
+
taskType: mapping.taskType,
|
|
492
|
+
workflowRef: mapping.workflowRef,
|
|
493
|
+
abilities: mapping.abilities,
|
|
494
|
+
priority: mapping.priority,
|
|
495
|
+
description: mapping.description,
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
313
498
|
return {
|
|
314
499
|
success: true,
|
|
315
|
-
data:
|
|
316
|
-
agentId: agent.agentId,
|
|
317
|
-
displayName: agent.displayName ?? agent.agentId,
|
|
318
|
-
description: agent.description,
|
|
319
|
-
version: agent.version,
|
|
320
|
-
role: agent.role,
|
|
321
|
-
team: agent.team,
|
|
322
|
-
enabled: agent.enabled,
|
|
323
|
-
capabilities: agent.capabilities ?? [],
|
|
324
|
-
tags: agent.tags ?? [],
|
|
325
|
-
systemPrompt: agent.systemPrompt,
|
|
326
|
-
workflow: agent.workflow?.map(step => ({
|
|
327
|
-
stepId: step.stepId,
|
|
328
|
-
name: step.name,
|
|
329
|
-
type: step.type,
|
|
330
|
-
config: step.config,
|
|
331
|
-
dependencies: step.dependencies,
|
|
332
|
-
condition: step.condition,
|
|
333
|
-
})) ?? [],
|
|
334
|
-
orchestration: agent.orchestration,
|
|
335
|
-
personality: agent.personality,
|
|
336
|
-
expertise: agent.expertise,
|
|
337
|
-
// Stats
|
|
338
|
-
stats: {
|
|
339
|
-
executionCount,
|
|
340
|
-
successRate: executionCount > 0 ? successCount / executionCount : 0,
|
|
341
|
-
avgDurationMs: executionCount > 0 ? Math.round(totalDurationMs / executionCount) : 0,
|
|
342
|
-
},
|
|
343
|
-
},
|
|
500
|
+
data: responseData,
|
|
344
501
|
};
|
|
345
502
|
}
|
|
346
503
|
catch (error) {
|
|
@@ -402,7 +559,9 @@ async function handleTraceDetail(traceId) {
|
|
|
402
559
|
const context = startEvent?.context;
|
|
403
560
|
// Determine command type and extract relevant info
|
|
404
561
|
const command = startPayload?.command;
|
|
405
|
-
|
|
562
|
+
const tool = startPayload?.tool;
|
|
563
|
+
const workflowId = context?.workflowId;
|
|
564
|
+
// First try to detect from explicit command/tool field
|
|
406
565
|
let commandType;
|
|
407
566
|
if (command?.includes('discuss')) {
|
|
408
567
|
commandType = 'discuss';
|
|
@@ -413,14 +572,22 @@ async function handleTraceDetail(traceId) {
|
|
|
413
572
|
else if (command?.includes('call')) {
|
|
414
573
|
commandType = 'call';
|
|
415
574
|
}
|
|
575
|
+
else if (tool?.startsWith('research') || workflowId?.startsWith('research')) {
|
|
576
|
+
commandType = 'research';
|
|
577
|
+
}
|
|
578
|
+
else if (tool?.startsWith('review') || workflowId?.startsWith('review')) {
|
|
579
|
+
commandType = 'review';
|
|
580
|
+
}
|
|
416
581
|
else {
|
|
417
582
|
// Fallback: detect from context/payload fields when command is not set
|
|
418
583
|
const hasAgentId = context?.agentId || startPayload?.agentId;
|
|
419
584
|
const hasTopic = startPayload?.topic;
|
|
420
585
|
const hasPrompt = startPayload?.prompt;
|
|
586
|
+
const hasQuery = startPayload?.query;
|
|
421
587
|
commandType = hasAgentId ? 'agent' :
|
|
422
588
|
hasTopic ? 'discuss' :
|
|
423
|
-
|
|
589
|
+
hasQuery ? 'research' :
|
|
590
|
+
hasPrompt ? 'call' : 'unknown';
|
|
424
591
|
}
|
|
425
592
|
// Extract input/output based on command type
|
|
426
593
|
let input;
|
|
@@ -465,6 +632,8 @@ async function handleTraceDetail(traceId) {
|
|
|
465
632
|
const agentId = startPayload?.agentId;
|
|
466
633
|
// Support both 'task' (CLI) and 'input' (MCP) field names
|
|
467
634
|
const taskOrInput = startPayload?.task ?? startPayload?.input;
|
|
635
|
+
// Extract provider from run.end payload (finalProvider)
|
|
636
|
+
provider = endPayload?.finalProvider;
|
|
468
637
|
input = {
|
|
469
638
|
agentId,
|
|
470
639
|
task: taskOrInput,
|
|
@@ -473,6 +642,62 @@ async function handleTraceDetail(traceId) {
|
|
|
473
642
|
output = {
|
|
474
643
|
stepCount: endPayload?.stepCount,
|
|
475
644
|
result: endPayload?.result ?? endPayload?.output,
|
|
645
|
+
// Include rich payload data for dashboard visibility
|
|
646
|
+
finalContent: endPayload?.finalContent,
|
|
647
|
+
agentDisplayName: endPayload?.agentDisplayName,
|
|
648
|
+
agentDescription: endPayload?.agentDescription,
|
|
649
|
+
inputTask: endPayload?.inputTask,
|
|
650
|
+
tokenUsage: endPayload?.tokenUsage,
|
|
651
|
+
stepResults: endPayload?.stepResults,
|
|
652
|
+
success: endPayload?.success,
|
|
653
|
+
error: endPayload?.error,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
else if (commandType === 'research') {
|
|
657
|
+
input = {
|
|
658
|
+
query: startPayload?.query,
|
|
659
|
+
tool: startPayload?.tool,
|
|
660
|
+
sources: startPayload?.sources,
|
|
661
|
+
maxSources: startPayload?.maxSources,
|
|
662
|
+
synthesize: startPayload?.synthesize,
|
|
663
|
+
};
|
|
664
|
+
output = {
|
|
665
|
+
// Full artifacts for dashboard visibility
|
|
666
|
+
query: endPayload?.query,
|
|
667
|
+
sources: endPayload?.sources,
|
|
668
|
+
synthesis: endPayload?.synthesis,
|
|
669
|
+
codeExamples: endPayload?.codeExamples,
|
|
670
|
+
confidence: endPayload?.confidence,
|
|
671
|
+
warnings: endPayload?.warnings,
|
|
672
|
+
// For fetch operations
|
|
673
|
+
url: endPayload?.url,
|
|
674
|
+
title: endPayload?.title,
|
|
675
|
+
contentPreview: endPayload?.contentPreview,
|
|
676
|
+
codeBlocks: endPayload?.codeBlocks,
|
|
677
|
+
reliability: endPayload?.reliability,
|
|
678
|
+
success: endPayload?.success,
|
|
679
|
+
error: endPayload?.error,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
else if (commandType === 'review') {
|
|
683
|
+
// Extract provider from run.end payload (providerId)
|
|
684
|
+
provider = endPayload?.providerId;
|
|
685
|
+
input = {
|
|
686
|
+
paths: startPayload?.paths,
|
|
687
|
+
focus: startPayload?.focus,
|
|
688
|
+
context: startPayload?.context,
|
|
689
|
+
tool: startPayload?.tool,
|
|
690
|
+
dryRun: startPayload?.dryRun,
|
|
691
|
+
};
|
|
692
|
+
output = {
|
|
693
|
+
// Full review results for dashboard visibility
|
|
694
|
+
summary: endPayload?.summary,
|
|
695
|
+
comments: endPayload?.comments,
|
|
696
|
+
filesReviewed: endPayload?.filesReviewed,
|
|
697
|
+
filesReviewedCount: endPayload?.filesReviewedCount,
|
|
698
|
+
linesAnalyzed: endPayload?.linesAnalyzed,
|
|
699
|
+
commentCount: endPayload?.commentCount,
|
|
700
|
+
providerId: endPayload?.providerId,
|
|
476
701
|
success: endPayload?.success,
|
|
477
702
|
error: endPayload?.error,
|
|
478
703
|
};
|
|
@@ -499,6 +724,66 @@ async function handleTraceDetail(traceId) {
|
|
|
499
724
|
timestamp: step.timestamp,
|
|
500
725
|
};
|
|
501
726
|
});
|
|
727
|
+
// Extract provider conversations from discussion.provider events
|
|
728
|
+
// This provides real-time visibility into running discussions
|
|
729
|
+
const discussionProviderEvents = events.filter(e => e.type === 'discussion.provider');
|
|
730
|
+
const providerConversations = discussionProviderEvents.map(event => {
|
|
731
|
+
const payload = event.payload;
|
|
732
|
+
const context = event.context;
|
|
733
|
+
const promptValue = payload?.prompt;
|
|
734
|
+
const contentValue = payload?.content;
|
|
735
|
+
const durationMsValue = event.durationMs ?? payload?.durationMs;
|
|
736
|
+
const tokenCountValue = payload?.tokenCount;
|
|
737
|
+
const result = {
|
|
738
|
+
provider: (context?.providerId ?? payload?.providerId ?? 'unknown'),
|
|
739
|
+
round: (payload?.roundNumber ?? 1),
|
|
740
|
+
success: (payload?.success ?? event.status === 'success'),
|
|
741
|
+
timestamp: event.timestamp,
|
|
742
|
+
};
|
|
743
|
+
// Only add optional fields if they have values
|
|
744
|
+
if (promptValue !== undefined)
|
|
745
|
+
result.prompt = promptValue;
|
|
746
|
+
if (contentValue !== undefined)
|
|
747
|
+
result.content = contentValue;
|
|
748
|
+
if (durationMsValue !== undefined)
|
|
749
|
+
result.durationMs = durationMsValue;
|
|
750
|
+
if (tokenCountValue !== undefined)
|
|
751
|
+
result.tokenCount = tokenCountValue;
|
|
752
|
+
return result;
|
|
753
|
+
});
|
|
754
|
+
// Extract agent step conversations from workflow.step events
|
|
755
|
+
// This provides visibility into agent execution steps with LLM responses
|
|
756
|
+
const agentStepEvents = events.filter(e => e.type === 'workflow.step');
|
|
757
|
+
const agentStepConversations = agentStepEvents.map(event => {
|
|
758
|
+
const payload = event.payload;
|
|
759
|
+
const context = event.context;
|
|
760
|
+
const contentValue = payload?.content;
|
|
761
|
+
const durationMsValue = event.durationMs ?? payload?.durationMs;
|
|
762
|
+
const tokenUsage = context?.tokenUsage;
|
|
763
|
+
const result = {
|
|
764
|
+
stepId: (payload?.stepId ?? 'unknown'),
|
|
765
|
+
stepIndex: (payload?.stepIndex ?? 0),
|
|
766
|
+
success: (payload?.success ?? event.status === 'success'),
|
|
767
|
+
timestamp: event.timestamp,
|
|
768
|
+
};
|
|
769
|
+
// Only add optional fields if they have values
|
|
770
|
+
const providerValue = (context?.providerId ?? payload?.provider);
|
|
771
|
+
if (providerValue !== undefined)
|
|
772
|
+
result.provider = providerValue;
|
|
773
|
+
if (contentValue !== undefined)
|
|
774
|
+
result.content = contentValue;
|
|
775
|
+
if (durationMsValue !== undefined)
|
|
776
|
+
result.durationMs = durationMsValue;
|
|
777
|
+
if (tokenUsage?.total !== undefined)
|
|
778
|
+
result.tokenCount = tokenUsage.total;
|
|
779
|
+
else if (tokenUsage?.input !== undefined && tokenUsage?.output !== undefined) {
|
|
780
|
+
result.tokenCount = tokenUsage.input + tokenUsage.output;
|
|
781
|
+
}
|
|
782
|
+
const errorValue = payload?.error;
|
|
783
|
+
if (errorValue !== undefined)
|
|
784
|
+
result.error = errorValue;
|
|
785
|
+
return result;
|
|
786
|
+
});
|
|
502
787
|
// Determine status from events
|
|
503
788
|
let status = 'pending';
|
|
504
789
|
if (endEvent) {
|
|
@@ -515,6 +800,8 @@ async function handleTraceDetail(traceId) {
|
|
|
515
800
|
(endEvent && startEvent ?
|
|
516
801
|
new Date(endEvent.timestamp).getTime() - new Date(startEvent.timestamp).getTime() :
|
|
517
802
|
undefined);
|
|
803
|
+
// PRD-2026-003: Extract classification data from start event
|
|
804
|
+
const classificationData = startPayload?.classification;
|
|
518
805
|
return {
|
|
519
806
|
success: true,
|
|
520
807
|
data: {
|
|
@@ -533,12 +820,18 @@ async function handleTraceDetail(traceId) {
|
|
|
533
820
|
output,
|
|
534
821
|
// Workflow steps (for agent runs)
|
|
535
822
|
workflowSteps: workflowSteps.length > 0 ? workflowSteps : undefined,
|
|
823
|
+
// Provider conversations from discussion.provider events (for discussions)
|
|
824
|
+
providerConversations: providerConversations.length > 0 ? providerConversations : undefined,
|
|
825
|
+
// Agent step conversations from workflow.step events (for agents)
|
|
826
|
+
agentStepConversations: agentStepConversations.length > 0 ? agentStepConversations : undefined,
|
|
536
827
|
// Summary
|
|
537
828
|
summary: {
|
|
538
829
|
eventCount: events.length,
|
|
539
830
|
stepCount: stepEvents.length,
|
|
540
831
|
errorCount: errorEvents.length,
|
|
541
832
|
},
|
|
833
|
+
// PRD-2026-003: Classification data
|
|
834
|
+
classification: classificationData,
|
|
542
835
|
// Full timeline for advanced view
|
|
543
836
|
timeline,
|
|
544
837
|
},
|
|
@@ -581,6 +874,25 @@ async function handleWorkflowDetail(workflowId) {
|
|
|
581
874
|
});
|
|
582
875
|
}
|
|
583
876
|
}
|
|
877
|
+
// Get recent executions of this workflow from trace store
|
|
878
|
+
const traceStore = getTraceStore();
|
|
879
|
+
const allTraces = await traceStore.listTraces(100);
|
|
880
|
+
const workflowExecutions = [];
|
|
881
|
+
for (const trace of allTraces) {
|
|
882
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
883
|
+
const startEvent = events.find(e => e.type === 'run.start');
|
|
884
|
+
const context = startEvent?.context;
|
|
885
|
+
// Check if this trace is for this workflow
|
|
886
|
+
if (context?.workflowId === workflowId ||
|
|
887
|
+
startEvent?.payload?.workflowId === workflowId) {
|
|
888
|
+
workflowExecutions.push({
|
|
889
|
+
traceId: trace.traceId,
|
|
890
|
+
status: trace.status,
|
|
891
|
+
startTime: trace.startTime,
|
|
892
|
+
...(trace.durationMs !== undefined && { durationMs: trace.durationMs }),
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
584
896
|
return {
|
|
585
897
|
success: true,
|
|
586
898
|
data: {
|
|
@@ -588,8 +900,22 @@ async function handleWorkflowDetail(workflowId) {
|
|
|
588
900
|
version: workflow.version,
|
|
589
901
|
name: workflow.name,
|
|
590
902
|
description: workflow.description,
|
|
591
|
-
|
|
903
|
+
// Full step details with configs
|
|
904
|
+
steps: workflow.steps.map(step => ({
|
|
905
|
+
stepId: step.stepId,
|
|
906
|
+
name: step.name ?? step.stepId,
|
|
907
|
+
type: step.type,
|
|
908
|
+
timeout: step.timeout,
|
|
909
|
+
dependencies: step.dependencies,
|
|
910
|
+
config: step.config,
|
|
911
|
+
})),
|
|
912
|
+
// Metadata if available
|
|
913
|
+
metadata: workflow.metadata,
|
|
914
|
+
// DAG for visualization
|
|
592
915
|
dag: { nodes, edges },
|
|
916
|
+
// Recent executions
|
|
917
|
+
recentExecutions: workflowExecutions.slice(0, 10),
|
|
918
|
+
executionCount: workflowExecutions.length,
|
|
593
919
|
},
|
|
594
920
|
};
|
|
595
921
|
}
|
|
@@ -874,6 +1200,74 @@ async function handleTraceTree(traceId) {
|
|
|
874
1200
|
};
|
|
875
1201
|
}
|
|
876
1202
|
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Get classification data for a trace
|
|
1205
|
+
* PRD-2026-003: Classification observability endpoint
|
|
1206
|
+
* INV-TR-030: Classification snapshot is immutable once recorded
|
|
1207
|
+
*/
|
|
1208
|
+
async function handleTraceClassification(traceId) {
|
|
1209
|
+
try {
|
|
1210
|
+
const traceStore = getTraceStore();
|
|
1211
|
+
const events = await traceStore.getTrace(traceId);
|
|
1212
|
+
if (!events || events.length === 0) {
|
|
1213
|
+
return { success: false, error: `Trace not found: ${traceId}` };
|
|
1214
|
+
}
|
|
1215
|
+
// Find run.start event which contains classification data
|
|
1216
|
+
const startEvent = events.find(e => e.type === 'run.start');
|
|
1217
|
+
if (!startEvent) {
|
|
1218
|
+
return {
|
|
1219
|
+
success: true,
|
|
1220
|
+
data: {
|
|
1221
|
+
traceId,
|
|
1222
|
+
hasClassification: false,
|
|
1223
|
+
message: 'No classification data available for this trace',
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
const payload = startEvent.payload;
|
|
1228
|
+
const classification = payload?.classification;
|
|
1229
|
+
if (!classification) {
|
|
1230
|
+
return {
|
|
1231
|
+
success: true,
|
|
1232
|
+
data: {
|
|
1233
|
+
traceId,
|
|
1234
|
+
hasClassification: false,
|
|
1235
|
+
message: 'No classification data available for this trace',
|
|
1236
|
+
},
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
// Calculate guard pass rate
|
|
1240
|
+
const guardResults = classification.guardResults ?? [];
|
|
1241
|
+
const guardPassRate = guardResults.length > 0
|
|
1242
|
+
? guardResults.filter(g => g.passed).length / guardResults.length
|
|
1243
|
+
: 1.0;
|
|
1244
|
+
return {
|
|
1245
|
+
success: true,
|
|
1246
|
+
data: {
|
|
1247
|
+
traceId,
|
|
1248
|
+
hasClassification: true,
|
|
1249
|
+
classification: {
|
|
1250
|
+
taskType: classification.taskType,
|
|
1251
|
+
confidence: classification.confidence,
|
|
1252
|
+
matchedPatterns: classification.matchedPatterns ?? [],
|
|
1253
|
+
selectedMapping: classification.selectedMapping,
|
|
1254
|
+
alternatives: classification.alternatives ?? [],
|
|
1255
|
+
classificationTimeMs: classification.classificationTimeMs,
|
|
1256
|
+
guardResults,
|
|
1257
|
+
guardPassRate,
|
|
1258
|
+
taskDescription: classification.taskDescription,
|
|
1259
|
+
},
|
|
1260
|
+
timestamp: startEvent.timestamp,
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
catch (error) {
|
|
1265
|
+
return {
|
|
1266
|
+
success: false,
|
|
1267
|
+
error: getErrorMessage(error, 'Failed to fetch classification data'),
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
877
1271
|
/**
|
|
878
1272
|
* Get workflow execution events - timeline of a workflow execution
|
|
879
1273
|
* Uses workflow.start, workflow.step, workflow.end events (INV-TR-013)
|
|
@@ -985,8 +1379,14 @@ async function handleWorkflowEvents(workflowId) {
|
|
|
985
1379
|
}
|
|
986
1380
|
/**
|
|
987
1381
|
* Fetch provider data
|
|
1382
|
+
* Uses cached status if available (set at monitor startup), otherwise checks CLI availability only
|
|
988
1383
|
*/
|
|
989
1384
|
async function getProviderData() {
|
|
1385
|
+
// Use cached status if available (set at monitor startup with real health check)
|
|
1386
|
+
if (cachedProviderStatus !== null) {
|
|
1387
|
+
return cachedProviderStatus;
|
|
1388
|
+
}
|
|
1389
|
+
// Fallback: quick CLI availability check (no actual LLM call)
|
|
990
1390
|
try {
|
|
991
1391
|
const registry = getProviderRegistry();
|
|
992
1392
|
const providerIds = registry.getProviderIds();
|
|
@@ -1060,6 +1460,9 @@ async function getAgentData() {
|
|
|
1060
1460
|
capabilities: agent.capabilities ?? [],
|
|
1061
1461
|
executionCount: 0,
|
|
1062
1462
|
lastExecuted: undefined,
|
|
1463
|
+
// PRD-2026-004: Meta-agent fields for list display
|
|
1464
|
+
metaAgent: agent.metaAgent,
|
|
1465
|
+
archetype: agent.archetype,
|
|
1063
1466
|
}));
|
|
1064
1467
|
}
|
|
1065
1468
|
catch {
|
|
@@ -1172,6 +1575,71 @@ async function getWorkflowData() {
|
|
|
1172
1575
|
return [];
|
|
1173
1576
|
}
|
|
1174
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Calculate classification metrics from recent traces
|
|
1580
|
+
* PRD-2026-003: Classification observability
|
|
1581
|
+
*/
|
|
1582
|
+
async function getClassificationMetrics() {
|
|
1583
|
+
try {
|
|
1584
|
+
const traceStore = getTraceStore();
|
|
1585
|
+
const traces = await traceStore.listTraces(200);
|
|
1586
|
+
let totalClassifications = 0;
|
|
1587
|
+
const byTaskType = {};
|
|
1588
|
+
let totalConfidence = 0;
|
|
1589
|
+
let totalGuardChecks = 0;
|
|
1590
|
+
let passedGuardChecks = 0;
|
|
1591
|
+
let fallbackCount = 0;
|
|
1592
|
+
for (const trace of traces) {
|
|
1593
|
+
try {
|
|
1594
|
+
const events = await traceStore.getTrace(trace.traceId);
|
|
1595
|
+
const startEvent = events.find(e => e.type === 'run.start');
|
|
1596
|
+
if (!startEvent)
|
|
1597
|
+
continue;
|
|
1598
|
+
const payload = startEvent.payload;
|
|
1599
|
+
const classification = payload?.classification;
|
|
1600
|
+
if (!classification || !classification.taskType)
|
|
1601
|
+
continue;
|
|
1602
|
+
totalClassifications++;
|
|
1603
|
+
const taskType = classification.taskType;
|
|
1604
|
+
byTaskType[taskType] = (byTaskType[taskType] ?? 0) + 1;
|
|
1605
|
+
if (classification.confidence !== undefined) {
|
|
1606
|
+
totalConfidence += classification.confidence;
|
|
1607
|
+
}
|
|
1608
|
+
if (classification.selectedMapping === null) {
|
|
1609
|
+
fallbackCount++;
|
|
1610
|
+
}
|
|
1611
|
+
if (classification.guardResults) {
|
|
1612
|
+
for (const gate of classification.guardResults) {
|
|
1613
|
+
totalGuardChecks++;
|
|
1614
|
+
if (gate.passed)
|
|
1615
|
+
passedGuardChecks++;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
catch {
|
|
1620
|
+
// Ignore individual trace errors
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return {
|
|
1624
|
+
totalClassifications,
|
|
1625
|
+
byTaskType,
|
|
1626
|
+
guardPassRate: totalGuardChecks > 0 ? passedGuardChecks / totalGuardChecks : 1.0,
|
|
1627
|
+
averageConfidence: totalClassifications > 0 ? totalConfidence / totalClassifications : 0,
|
|
1628
|
+
fallbackRate: totalClassifications > 0 ? fallbackCount / totalClassifications : 0,
|
|
1629
|
+
sampleSize: traces.length,
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
catch {
|
|
1633
|
+
return {
|
|
1634
|
+
totalClassifications: 0,
|
|
1635
|
+
byTaskType: {},
|
|
1636
|
+
guardPassRate: 1.0,
|
|
1637
|
+
averageConfidence: 0,
|
|
1638
|
+
fallbackRate: 0,
|
|
1639
|
+
sampleSize: 0,
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1175
1643
|
/**
|
|
1176
1644
|
* Fetch metrics data
|
|
1177
1645
|
*/
|