@defai.digital/cli 13.4.4 → 13.4.7

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 (105) hide show
  1. package/bundled/agents/architect.json +117 -0
  2. package/bundled/agents/auditor.json +114 -0
  3. package/bundled/agents/bug-hunter.json +128 -0
  4. package/bundled/agents/builder.json +128 -0
  5. package/bundled/agents/ceo.json +6 -1
  6. package/bundled/agents/executor.json +150 -0
  7. package/bundled/agents/fullstack.json +10 -2
  8. package/bundled/agents/operator.json +119 -0
  9. package/bundled/agents/researcher.json +42 -13
  10. package/bundled/agents/reviewer.json +90 -42
  11. package/bundled/templates/monorepo/contract-index.ts.hbs +7 -0
  12. package/bundled/templates/monorepo/contract-test.ts.hbs +130 -0
  13. package/bundled/templates/monorepo/contracts-package.json.hbs +29 -0
  14. package/bundled/templates/monorepo/domain-index.ts.hbs +115 -0
  15. package/bundled/templates/monorepo/domain-package.json.hbs +27 -0
  16. package/bundled/templates/monorepo/gitignore.hbs +32 -0
  17. package/bundled/templates/monorepo/invariants.md.hbs +43 -0
  18. package/bundled/templates/monorepo/package.json.hbs +28 -0
  19. package/bundled/templates/monorepo/pnpm-workspace.yaml.hbs +5 -0
  20. package/bundled/templates/monorepo/schema.ts.hbs +82 -0
  21. package/bundled/templates/monorepo/template.json +106 -0
  22. package/bundled/templates/monorepo/tsconfig.json.hbs +22 -0
  23. package/bundled/templates/standalone/contract-index.ts.hbs +5 -0
  24. package/bundled/templates/standalone/contract-test.ts.hbs +95 -0
  25. package/bundled/templates/standalone/contracts-root-index.ts.hbs +7 -0
  26. package/bundled/templates/standalone/domain-index.ts.hbs +6 -0
  27. package/bundled/templates/standalone/domain-repository.ts.hbs +44 -0
  28. package/bundled/templates/standalone/domain-service.ts.hbs +102 -0
  29. package/bundled/templates/standalone/gitignore.hbs +27 -0
  30. package/bundled/templates/standalone/invariants.md.hbs +35 -0
  31. package/bundled/templates/standalone/package.json.hbs +41 -0
  32. package/bundled/templates/standalone/schema.ts.hbs +61 -0
  33. package/bundled/templates/standalone/src-index.ts.hbs +11 -0
  34. package/bundled/templates/standalone/template.json +91 -0
  35. package/bundled/templates/standalone/tsconfig.json.hbs +20 -0
  36. package/bundled/templates/standalone/vitest.config.ts.hbs +8 -0
  37. package/bundled/workflows/adversarial-debate.yaml +222 -0
  38. package/bundled/workflows/analyst.yaml +115 -0
  39. package/bundled/workflows/assistant.yaml +74 -0
  40. package/bundled/workflows/code-review-discussion.yaml +166 -0
  41. package/bundled/workflows/code-reviewer.yaml +94 -0
  42. package/bundled/workflows/contract-first-project.yaml +356 -0
  43. package/bundled/workflows/debugger.yaml +107 -0
  44. package/bundled/workflows/designer.yaml +113 -0
  45. package/bundled/workflows/developer.yaml +105 -0
  46. package/bundled/workflows/discuss-step-examples.yaml +153 -0
  47. package/bundled/workflows/infrastructure-automation.yaml +283 -0
  48. package/bundled/workflows/ml-ab-testing.yaml +311 -0
  49. package/bundled/workflows/ml-experiment-tracker.yaml +150 -0
  50. package/bundled/workflows/ml-feature-engineering.yaml +242 -0
  51. package/bundled/workflows/ml-model-evaluation.yaml +234 -0
  52. package/bundled/workflows/ml-model-monitoring.yaml +227 -0
  53. package/bundled/workflows/ml-model-registry.yaml +232 -0
  54. package/bundled/workflows/mlops-deployment.yaml +267 -0
  55. package/bundled/workflows/mobile-development.yaml +312 -0
  56. package/bundled/workflows/multi-model-discussion.yaml +243 -0
  57. package/bundled/workflows/product-discovery.yaml +295 -0
  58. package/bundled/workflows/qa-specialist.yaml +116 -0
  59. package/bundled/workflows/refactoring.yaml +105 -0
  60. package/bundled/workflows/security-audit.yaml +135 -0
  61. package/bundled/workflows/std/analysis.yaml +190 -0
  62. package/bundled/workflows/std/code-review.yaml +117 -0
  63. package/bundled/workflows/std/debugging.yaml +155 -0
  64. package/bundled/workflows/std/documentation.yaml +180 -0
  65. package/bundled/workflows/std/implementation.yaml +197 -0
  66. package/bundled/workflows/std/refactoring.yaml +180 -0
  67. package/bundled/workflows/std/testing.yaml +200 -0
  68. package/bundled/workflows/strategic-planning.yaml +235 -0
  69. package/bundled/workflows/technology-research.yaml +239 -0
  70. package/dist/bootstrap.d.ts.map +1 -1
  71. package/dist/bootstrap.js +10 -6
  72. package/dist/bootstrap.js.map +1 -1
  73. package/dist/commands/discuss.d.ts.map +1 -1
  74. package/dist/commands/discuss.js +4 -1
  75. package/dist/commands/discuss.js.map +1 -1
  76. package/dist/commands/doctor.d.ts +1 -1
  77. package/dist/commands/doctor.js +3 -3
  78. package/dist/commands/doctor.js.map +1 -1
  79. package/dist/commands/init.d.ts.map +1 -1
  80. package/dist/commands/init.js +65 -5
  81. package/dist/commands/init.js.map +1 -1
  82. package/dist/commands/monitor.d.ts.map +1 -1
  83. package/dist/commands/monitor.js +29 -1
  84. package/dist/commands/monitor.js.map +1 -1
  85. package/dist/commands/scaffold.d.ts.map +1 -1
  86. package/dist/commands/scaffold.js +6 -3
  87. package/dist/commands/scaffold.js.map +1 -1
  88. package/dist/commands/setup.d.ts.map +1 -1
  89. package/dist/commands/setup.js +119 -3
  90. package/dist/commands/setup.js.map +1 -1
  91. package/dist/commands/status.d.ts +10 -0
  92. package/dist/commands/status.d.ts.map +1 -1
  93. package/dist/commands/status.js +151 -49
  94. package/dist/commands/status.js.map +1 -1
  95. package/dist/commands/update.d.ts.map +1 -1
  96. package/dist/commands/update.js +1 -43
  97. package/dist/commands/update.js.map +1 -1
  98. package/dist/web/api.d.ts +18 -0
  99. package/dist/web/api.d.ts.map +1 -1
  100. package/dist/web/api.js +480 -39
  101. package/dist/web/api.js.map +1 -1
  102. package/dist/web/dashboard.d.ts.map +1 -1
  103. package/dist/web/dashboard.js +1449 -132
  104. package/dist/web/dashboard.js.map +1 -1
  105. package/package.json +21 -21
@@ -431,7 +431,6 @@ export function createDashboardHTML() {
431
431
  }
432
432
 
433
433
  .provider-latency.fast { color: var(--accent-green); }
434
- .provider-latency.medium { color: var(--accent-yellow); }
435
434
  .provider-latency.slow { color: var(--accent-red); }
436
435
 
437
436
  /* Agent list */
@@ -553,6 +552,26 @@ export function createDashboardHTML() {
553
552
  cursor: pointer;
554
553
  }
555
554
 
555
+ .refresh-button {
556
+ padding: 8px 16px;
557
+ background: var(--accent-blue);
558
+ border: none;
559
+ border-radius: 6px;
560
+ color: white;
561
+ font-size: 14px;
562
+ cursor: pointer;
563
+ transition: background 0.2s, opacity 0.2s;
564
+ }
565
+
566
+ .refresh-button:hover:not(:disabled) {
567
+ background: var(--accent-blue-hover, #2563eb);
568
+ }
569
+
570
+ .refresh-button:disabled {
571
+ opacity: 0.6;
572
+ cursor: not-allowed;
573
+ }
574
+
556
575
  .history-table {
557
576
  width: 100%;
558
577
  border-collapse: collapse;
@@ -1485,6 +1504,17 @@ export function createDashboardHTML() {
1485
1504
  .conversation-message .metadata-badges {
1486
1505
  opacity: 1 !important;
1487
1506
  }
1507
+
1508
+ .conversation-message .message-bubble {
1509
+ padding: 16px !important;
1510
+ }
1511
+ }
1512
+
1513
+ /* Remove border from last conversation message */
1514
+ .conversation-message:last-child {
1515
+ border-bottom: none !important;
1516
+ margin-bottom: 0 !important;
1517
+ padding-bottom: 0 !important;
1488
1518
  }
1489
1519
 
1490
1520
  @media (max-width: 480px) {
@@ -1621,6 +1651,10 @@ export function createDashboardHTML() {
1621
1651
  'discussion.provider': '\\u25C6', // ◆ Provider response
1622
1652
  'discussion.consensus': '\\u2726', // ✦ Consensus reached
1623
1653
  'discussion.end': '\\u2713',
1654
+ // Workflow/Agent step events
1655
+ 'workflow.start': '\\u25B6',
1656
+ 'workflow.step': '\\u2699', // ⚙ Step execution
1657
+ 'workflow.end': '\\u25A0',
1624
1658
  'memory.write': '\\u270E',
1625
1659
  'memory.read': '\\u25B7',
1626
1660
  'error': '\\u26A0',
@@ -1948,9 +1982,10 @@ export function createDashboardHTML() {
1948
1982
  };
1949
1983
 
1950
1984
  // Render expanded details
1951
- const renderDetails = (startEvent, endEvent) => {
1985
+ const renderDetails = (startEvent, endEvent, eventType) => {
1952
1986
  const payload = { ...(startEvent?.payload || {}), ...(endEvent?.payload || {}) };
1953
1987
  const context = { ...(startEvent?.context || {}), ...(endEvent?.context || {}) };
1988
+ const type = eventType || startEvent?.type || endEvent?.type || '';
1954
1989
 
1955
1990
  const detailItems = [];
1956
1991
 
@@ -1989,6 +2024,69 @@ export function createDashboardHTML() {
1989
2024
  detailItems.push({ label: 'Votes', value: voteStr });
1990
2025
  }
1991
2026
 
2027
+ // workflow.step event details (Agent execution steps)
2028
+ if (type === 'workflow.step') {
2029
+ if (payload.stepId) detailItems.push({ label: 'Step', value: payload.stepId });
2030
+ if (payload.stepIndex !== undefined) detailItems.push({ label: 'Index', value: \`#\${payload.stepIndex + 1}\` });
2031
+ if (payload.provider) detailItems.push({ label: 'Provider', value: payload.provider });
2032
+ if (context.tokenUsage) {
2033
+ const usage = context.tokenUsage;
2034
+ if (usage.total) detailItems.push({ label: 'Tokens', value: usage.total.toLocaleString() });
2035
+ else if (usage.input || usage.output) {
2036
+ detailItems.push({ label: 'Tokens', value: \`\${(usage.input || 0).toLocaleString()} in / \${(usage.output || 0).toLocaleString()} out\` });
2037
+ }
2038
+ }
2039
+ }
2040
+
2041
+ // Agent-specific details (from enriched run.end payload)
2042
+ if (payload.agentDisplayName) detailItems.push({ label: 'Agent', value: payload.agentDisplayName });
2043
+ if (payload.inputTask && !payload.topic) {
2044
+ const taskPreview = String(payload.inputTask).substring(0, 150);
2045
+ detailItems.push({ label: 'Task', value: taskPreview + (payload.inputTask.length > 150 ? '...' : '') });
2046
+ }
2047
+ if (payload.tokenUsage && typeof payload.tokenUsage === 'object') {
2048
+ const usage = payload.tokenUsage;
2049
+ if (usage.total) detailItems.push({ label: 'Tokens', value: usage.total.toLocaleString() });
2050
+ else if (usage.input || usage.output) {
2051
+ detailItems.push({ label: 'Tokens', value: \`\${(usage.input || 0).toLocaleString()} in / \${(usage.output || 0).toLocaleString()} out\` });
2052
+ }
2053
+ }
2054
+
2055
+ // Research-specific details
2056
+ if (payload.tool?.startsWith('research')) {
2057
+ if (payload.query) {
2058
+ const queryPreview = String(payload.query).substring(0, 150);
2059
+ detailItems.push({ label: 'Query', value: queryPreview + (payload.query.length > 150 ? '...' : '') });
2060
+ }
2061
+ if (payload.sourceCount !== undefined) detailItems.push({ label: 'Sources', value: payload.sourceCount });
2062
+ if (payload.confidence !== undefined) detailItems.push({ label: 'Confidence', value: \`\${(payload.confidence * 100).toFixed(0)}%\` });
2063
+ if (payload.url) detailItems.push({ label: 'URL', value: payload.url });
2064
+ if (payload.title) detailItems.push({ label: 'Title', value: payload.title });
2065
+ if (payload.reliability) detailItems.push({ label: 'Reliability', value: payload.reliability });
2066
+ if (payload.contentLength) detailItems.push({ label: 'Content', value: \`\${payload.contentLength.toLocaleString()} chars\` });
2067
+ }
2068
+
2069
+ // Review-specific details
2070
+ if (payload.tool?.startsWith('review')) {
2071
+ if (payload.focus) detailItems.push({ label: 'Focus', value: payload.focus });
2072
+ if (payload.summary?.verdict) detailItems.push({ label: 'Verdict', value: payload.summary.verdict });
2073
+ if (payload.summary?.healthScore !== undefined) {
2074
+ detailItems.push({ label: 'Health', value: \`\${(payload.summary.healthScore * 100).toFixed(0)}%\` });
2075
+ }
2076
+ if (payload.summary?.bySeverity) {
2077
+ const sev = payload.summary.bySeverity;
2078
+ const parts = [];
2079
+ if (sev.critical) parts.push(\`\${sev.critical} critical\`);
2080
+ if (sev.warning) parts.push(\`\${sev.warning} warning\`);
2081
+ if (sev.suggestion) parts.push(\`\${sev.suggestion} suggestion\`);
2082
+ if (sev.note) parts.push(\`\${sev.note} note\`);
2083
+ if (parts.length > 0) detailItems.push({ label: 'Issues', value: parts.join(', ') });
2084
+ }
2085
+ if (payload.filesReviewedCount !== undefined) detailItems.push({ label: 'Files', value: payload.filesReviewedCount });
2086
+ if (payload.linesAnalyzed !== undefined) detailItems.push({ label: 'Lines', value: payload.linesAnalyzed.toLocaleString() });
2087
+ if (payload.providerId) detailItems.push({ label: 'Provider', value: payload.providerId });
2088
+ }
2089
+
1992
2090
  if (payload.error) detailItems.push({ label: 'Error', value: typeof payload.error === 'object' ? payload.error.message : payload.error, isError: true });
1993
2091
 
1994
2092
  if (detailItems.length === 0) return null;
@@ -2137,7 +2235,7 @@ export function createDashboardHTML() {
2137
2235
  </div>
2138
2236
  )}
2139
2237
 
2140
- {isExpanded && renderDetails(event, endEvent)}
2238
+ {isExpanded && renderDetails(event, endEvent, baseType)}
2141
2239
  </div>
2142
2240
  </div>
2143
2241
  );
@@ -2609,57 +2707,104 @@ export function createDashboardHTML() {
2609
2707
  }
2610
2708
  // For 'discuss' command: topic as input, responses grouped by provider with multi-round support
2611
2709
  else if (traceData.commandType === 'discuss') {
2612
- // Get the discussion topic and configured round count
2710
+ // Get the discussion topic
2613
2711
  const topic = traceData.input?.topic;
2614
- const configuredRounds = traceData.input?.rounds || 1;
2615
-
2616
- // Provider responses: {provider: [response1, response2, ...]} or {provider: "single response"}
2617
- const responses = traceData.output?.responses;
2618
- if (responses && typeof responses === 'object') {
2619
- Object.entries(responses).forEach(([provider, providerResponses]) => {
2620
- const exchanges = [];
2621
2712
 
2622
- // Handle both array (multi-round) and string (single response) formats
2623
- const responseArray = Array.isArray(providerResponses)
2624
- ? providerResponses
2625
- : [providerResponses];
2713
+ // Use providerConversations for real prompt/response content (preferred)
2714
+ // Falls back to output.responses if providerConversations not available
2715
+ if (traceData.providerConversations && traceData.providerConversations.length > 0) {
2716
+ // Group by provider
2717
+ const providerMap = {};
2718
+ traceData.providerConversations.forEach(conv => {
2719
+ if (!providerMap[conv.provider]) {
2720
+ providerMap[conv.provider] = [];
2721
+ }
2722
+ providerMap[conv.provider].push(conv);
2723
+ });
2626
2724
 
2627
- responseArray.forEach((response, roundIndex) => {
2628
- const roundNum = roundIndex + 1;
2725
+ // Build exchanges for each provider
2726
+ Object.entries(providerMap).forEach(([provider, conversations]) => {
2727
+ const exchanges = [];
2728
+ // Sort by round then timestamp
2729
+ const sorted = conversations.sort((a, b) => {
2730
+ if (a.round !== b.round) return a.round - b.round;
2731
+ return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
2732
+ });
2629
2733
 
2630
- // Add the prompt/ask for this round
2631
- if (roundNum === 1) {
2632
- // Round 1: The original topic
2734
+ sorted.forEach((conv, idx) => {
2735
+ // Add the actual prompt
2736
+ if (conv.prompt) {
2633
2737
  exchanges.push({
2634
- input: topic || 'Discussion topic',
2738
+ input: conv.prompt,
2635
2739
  output: null,
2636
- roundLabel: \`Ask \${roundNum}\`
2637
- });
2638
- } else {
2639
- // Rounds 2+: Cross-discussion prompt
2640
- exchanges.push({
2641
- input: \`Round \${roundNum}: Respond to other providers' perspectives from the previous round.\`,
2642
- output: null,
2643
- roundLabel: \`Ask \${roundNum}\`
2740
+ roundLabel: \`Ask \${conv.round}\`
2644
2741
  });
2645
2742
  }
2646
-
2647
- // Add the response/reply for this round
2648
- if (response) {
2743
+ // Add the response
2744
+ if (conv.content) {
2649
2745
  exchanges.push({
2650
2746
  input: null,
2651
- output: response,
2652
- roundLabel: \`Reply \${roundNum}\`
2747
+ output: conv.content,
2748
+ latencyMs: conv.durationMs,
2749
+ roundLabel: \`Reply \${conv.round}\`
2653
2750
  });
2654
2751
  }
2655
2752
  });
2656
2753
 
2657
- groups.push({
2658
- provider: provider,
2659
- exchanges
2660
- });
2754
+ if (exchanges.length > 0) {
2755
+ groups.push({ provider, exchanges });
2756
+ }
2661
2757
  });
2662
2758
  }
2759
+ // Fallback: use output.responses (legacy format)
2760
+ else {
2761
+ const responses = traceData.output?.responses;
2762
+ if (responses && typeof responses === 'object') {
2763
+ Object.entries(responses).forEach(([provider, providerResponses]) => {
2764
+ const exchanges = [];
2765
+
2766
+ // Handle both array (multi-round) and string (single response) formats
2767
+ const responseArray = Array.isArray(providerResponses)
2768
+ ? providerResponses
2769
+ : [providerResponses];
2770
+
2771
+ responseArray.forEach((response, roundIndex) => {
2772
+ const roundNum = roundIndex + 1;
2773
+
2774
+ // Add the prompt/ask for this round
2775
+ if (roundNum === 1) {
2776
+ // Round 1: The original topic
2777
+ exchanges.push({
2778
+ input: topic || 'Discussion topic',
2779
+ output: null,
2780
+ roundLabel: \`Ask \${roundNum}\`
2781
+ });
2782
+ } else {
2783
+ // Rounds 2+: Cross-discussion prompt
2784
+ exchanges.push({
2785
+ input: \`Round \${roundNum}: Respond to other providers' perspectives from the previous round.\`,
2786
+ output: null,
2787
+ roundLabel: \`Ask \${roundNum}\`
2788
+ });
2789
+ }
2790
+
2791
+ // Add the response/reply for this round
2792
+ if (response) {
2793
+ exchanges.push({
2794
+ input: null,
2795
+ output: response,
2796
+ roundLabel: \`Reply \${roundNum}\`
2797
+ });
2798
+ }
2799
+ });
2800
+
2801
+ groups.push({
2802
+ provider: provider,
2803
+ exchanges
2804
+ });
2805
+ });
2806
+ }
2807
+ }
2663
2808
 
2664
2809
  // Show synthesis/consensus if available
2665
2810
  const synthesisText = traceData.output?.synthesis;
@@ -2694,20 +2839,56 @@ export function createDashboardHTML() {
2694
2839
  }
2695
2840
  // For 'agent' command: task as initial input, then workflow steps
2696
2841
  else if (traceData.commandType === 'agent') {
2697
- // Initial task
2842
+ // Get trace-level provider as fallback (from run.end finalProvider)
2843
+ const traceProvider = traceData.provider || traceData.output?.finalProvider;
2844
+
2845
+ // Initial task - show agent name and provider
2698
2846
  const task = traceData.input?.task;
2699
2847
  if (task) {
2700
2848
  const taskContent = typeof task === 'object' ? task.task || JSON.stringify(task) : task;
2849
+ const agentHeader = traceProvider
2850
+ ? \`Agent: \${traceData.input?.agentId || 'unknown'} (via \${traceProvider})\`
2851
+ : \`Agent: \${traceData.input?.agentId || 'unknown'}\`;
2701
2852
  groups.push({
2702
- provider: \`Agent: \${traceData.input?.agentId || 'unknown'}\`,
2853
+ provider: agentHeader,
2703
2854
  exchanges: [{ input: taskContent, output: null }]
2704
2855
  });
2705
2856
  }
2706
2857
 
2707
- // Workflow steps with prompt/response
2708
- if (traceData.workflowSteps && traceData.workflowSteps.length > 0) {
2858
+ // Use agentStepConversations for real LLM content (preferred, from workflow.step events)
2859
+ if (traceData.agentStepConversations && traceData.agentStepConversations.length > 0) {
2860
+ traceData.agentStepConversations.forEach((step, idx) => {
2861
+ // Use step provider, fallback to trace-level provider, then 'unknown'
2862
+ const stepProvider = step.provider || traceProvider || 'unknown';
2863
+ const exchanges = [];
2864
+
2865
+ // Add response content if available
2866
+ if (step.content) {
2867
+ exchanges.push({
2868
+ input: null,
2869
+ output: step.content,
2870
+ latencyMs: step.durationMs,
2871
+ success: step.success,
2872
+ error: step.error,
2873
+ tokenCount: step.tokenCount,
2874
+ roundLabel: \`Step \${step.stepIndex + 1}\`
2875
+ });
2876
+ }
2877
+
2878
+ if (exchanges.length > 0) {
2879
+ groups.push({
2880
+ provider: stepProvider,
2881
+ stepId: step.stepId || \`Step \${idx + 1}\`,
2882
+ exchanges
2883
+ });
2884
+ }
2885
+ });
2886
+ }
2887
+ // Fallback: use workflowSteps (legacy format without LLM content)
2888
+ else if (traceData.workflowSteps && traceData.workflowSteps.length > 0) {
2709
2889
  traceData.workflowSteps.forEach((step, idx) => {
2710
- const stepProvider = step.provider || 'claude';
2890
+ // Use step provider, fallback to trace-level provider, then 'unknown'
2891
+ const stepProvider = step.provider || traceProvider || 'unknown';
2711
2892
  const exchanges = [];
2712
2893
 
2713
2894
  // Add prompt if available
@@ -2741,18 +2922,158 @@ export function createDashboardHTML() {
2741
2922
  });
2742
2923
  }
2743
2924
 
2744
- // Final result
2925
+ // Final result (or use finalContent from run.end payload)
2745
2926
  const result = traceData.output?.result;
2746
- if (result) {
2747
- const resultContent = typeof result === 'object'
2748
- ? (result.content || JSON.stringify(result, null, 2))
2749
- : result;
2927
+ const finalContent = traceData.output?.finalContent;
2928
+ const displayResult = result || finalContent;
2929
+ if (displayResult) {
2930
+ const resultContent = typeof displayResult === 'object'
2931
+ ? (displayResult.content || JSON.stringify(displayResult, null, 2))
2932
+ : displayResult;
2750
2933
  groups.push({
2751
2934
  provider: 'Final Result',
2752
2935
  exchanges: [{ input: null, output: resultContent }]
2753
2936
  });
2754
2937
  }
2755
2938
  }
2939
+ // For 'review' command: show review summary and findings
2940
+ else if (traceData.commandType === 'review' || traceData.workflowId?.startsWith('review')) {
2941
+ const output = traceData.output || {};
2942
+ const input = traceData.input || {};
2943
+ const summary = output.summary;
2944
+ const comments = output.comments || [];
2945
+ const filesReviewed = output.filesReviewed || [];
2946
+ // Get provider from output or top-level trace data
2947
+ const reviewProvider = output.providerId || traceData.provider;
2948
+
2949
+ // Review parameters
2950
+ const paths = input.paths || [];
2951
+ const focus = input.focus || 'all';
2952
+ const pathsText = Array.isArray(paths) ? paths.join(', ') : paths;
2953
+
2954
+ // Show provider in the header if available
2955
+ const headerLabel = reviewProvider
2956
+ ? \`Code Review (\${focus}) via \${reviewProvider}\`
2957
+ : \`Code Review (\${focus})\`;
2958
+
2959
+ groups.push({
2960
+ provider: headerLabel,
2961
+ exchanges: [{
2962
+ input: \`Review \${pathsText}\`,
2963
+ output: null
2964
+ }]
2965
+ });
2966
+
2967
+ // Summary section
2968
+ if (summary) {
2969
+ const severityLine = summary.bySeverity
2970
+ ? \`Critical: \${summary.bySeverity.critical || 0}, Warning: \${summary.bySeverity.warning || 0}, Suggestion: \${summary.bySeverity.suggestion || 0}, Note: \${summary.bySeverity.note || 0}\`
2971
+ : '';
2972
+ const summaryText = [
2973
+ \`**Verdict**: \${summary.verdict || 'N/A'}\`,
2974
+ \`**Health Score**: \${summary.healthScore !== undefined ? (summary.healthScore * 100).toFixed(0) + '%' : 'N/A'}\`,
2975
+ severityLine ? \`**Issues**: \${severityLine}\` : '',
2976
+ \`**Files Reviewed**: \${output.filesReviewedCount || filesReviewed.length}\`,
2977
+ \`**Lines Analyzed**: \${output.linesAnalyzed?.toLocaleString() || 'N/A'}\`,
2978
+ ].filter(Boolean).join('\\n');
2979
+
2980
+ groups.push({
2981
+ provider: 'Summary',
2982
+ exchanges: [{ input: null, output: summaryText }]
2983
+ });
2984
+ }
2985
+
2986
+ // Issues found
2987
+ if (comments.length > 0) {
2988
+ const issuesText = comments.map((c, idx) => {
2989
+ const severityIcon = c.severity === 'critical' ? '🔴' :
2990
+ c.severity === 'warning' ? '🟠' :
2991
+ c.severity === 'suggestion' ? '🟡' : '🔵';
2992
+ const location = c.file ? \`\${c.file}\${c.line ? ':' + c.line : ''}\` : '';
2993
+ return [
2994
+ \`### \${severityIcon} \${c.title || 'Issue ' + (idx + 1)}\`,
2995
+ location ? \`**Location**: \${location}\` : '',
2996
+ c.category ? \`**Category**: \${c.category}\` : '',
2997
+ c.body ? \`\\n\${c.body}\` : '',
2998
+ c.suggestion ? \`\\n**Suggestion**: \${c.suggestion}\` : '',
2999
+ c.confidence ? \`\\n*Confidence: \${(c.confidence * 100).toFixed(0)}%*\` : '',
3000
+ ].filter(Boolean).join('\\n');
3001
+ }).join('\\n\\n---\\n\\n');
3002
+
3003
+ groups.push({
3004
+ provider: \`Issues Found (\${output.commentCount || comments.length})\`,
3005
+ exchanges: [{ input: null, output: issuesText }]
3006
+ });
3007
+ } else if (output.commentCount === 0) {
3008
+ groups.push({
3009
+ provider: 'Result',
3010
+ exchanges: [{ input: null, output: '✅ No issues found! Code looks good.' }]
3011
+ });
3012
+ }
3013
+
3014
+ // Files reviewed
3015
+ if (filesReviewed.length > 0 && filesReviewed.length <= 20) {
3016
+ const filesText = filesReviewed.map((f, idx) => \`\${idx + 1}. \${f}\`).join('\\n');
3017
+ groups.push({
3018
+ provider: 'Files Reviewed',
3019
+ exchanges: [{ input: null, output: filesText }]
3020
+ });
3021
+ }
3022
+ }
3023
+ // For 'research' command: query as input, sources and synthesis as output
3024
+ else if (traceData.commandType === 'research' || traceData.workflowId?.startsWith('research')) {
3025
+ const output = traceData.output || {};
3026
+ const query = output.query || traceData.input?.query;
3027
+ const sources = output.sources || [];
3028
+ const synthesis = output.synthesis;
3029
+ const confidence = output.confidence;
3030
+ const codeExamples = output.codeExamples || [];
3031
+
3032
+ // Research query
3033
+ if (query) {
3034
+ groups.push({
3035
+ provider: 'Research Query',
3036
+ exchanges: [{ input: query, output: null }]
3037
+ });
3038
+ }
3039
+
3040
+ // Sources found
3041
+ if (sources.length > 0) {
3042
+ const sourcesText = sources.map((s, idx) => {
3043
+ const reliability = s.reliability ? \` [\${s.reliability}]\` : '';
3044
+ const relevance = s.relevanceScore ? \` (relevance: \${(s.relevanceScore * 100).toFixed(0)}%)\` : '';
3045
+ return \`\${idx + 1}. **\${s.title || 'Untitled'}**\${reliability}\${relevance}\\n \${s.url || 'No URL'}\\n \${s.snippet ? s.snippet.substring(0, 200) + (s.snippet.length > 200 ? '...' : '') : ''}\`;
3046
+ }).join('\\n\\n');
3047
+
3048
+ groups.push({
3049
+ provider: \`Sources (\${sources.length})\`,
3050
+ exchanges: [{ input: null, output: sourcesText }]
3051
+ });
3052
+ }
3053
+
3054
+ // Code examples
3055
+ if (codeExamples.length > 0) {
3056
+ const codeText = codeExamples.map((code, idx) => {
3057
+ const lang = code.language || 'code';
3058
+ const content = typeof code === 'string' ? code : (code.content || code.code || JSON.stringify(code));
3059
+ return \`**Example \${idx + 1}** (\${lang}):\\n\\\`\\\`\\\`\${lang}\\n\${content}\\n\\\`\\\`\\\`\`;
3060
+ }).join('\\n\\n');
3061
+
3062
+ groups.push({
3063
+ provider: \`Code Examples (\${codeExamples.length})\`,
3064
+ exchanges: [{ input: null, output: codeText }]
3065
+ });
3066
+ }
3067
+
3068
+ // Synthesis result
3069
+ if (synthesis) {
3070
+ const confidenceText = confidence !== undefined ? \` (Confidence: \${(confidence * 100).toFixed(0)}%)\` : '';
3071
+ groups.push({
3072
+ provider: 'Synthesis' + confidenceText,
3073
+ exchanges: [{ input: 'Synthesized answer from all sources', output: synthesis }]
3074
+ });
3075
+ }
3076
+ }
2756
3077
 
2757
3078
  return groups;
2758
3079
  };
@@ -2966,24 +3287,27 @@ export function createDashboardHTML() {
2966
3287
  onMouseEnter={() => setShowMeta(true)}
2967
3288
  onMouseLeave={() => setShowMeta(false)}
2968
3289
  style={{
2969
- marginBottom: '24px',
3290
+ marginBottom: '32px',
3291
+ paddingBottom: '24px',
3292
+ borderBottom: '1px solid var(--border-color)',
2970
3293
  }}
2971
3294
  >
2972
3295
  <div className="message-container" style={{
2973
3296
  display: 'flex',
2974
- gap: '12px',
3297
+ gap: '16px',
2975
3298
  }}>
2976
3299
  {/* Avatar - hidden on mobile via CSS class */}
2977
3300
  <div className="message-avatar" style={{
2978
- width: '36px',
2979
- height: '36px',
3301
+ width: '44px',
3302
+ height: '44px',
2980
3303
  borderRadius: '50%',
2981
- background: isAsk ? 'rgba(88, 166, 255, 0.15)' : 'rgba(63, 185, 80, 0.15)',
3304
+ background: isAsk ? 'rgba(88, 166, 255, 0.2)' : 'rgba(63, 185, 80, 0.2)',
2982
3305
  display: 'flex',
2983
3306
  alignItems: 'center',
2984
3307
  justifyContent: 'center',
2985
- fontSize: '16px',
3308
+ fontSize: '20px',
2986
3309
  flexShrink: 0,
3310
+ border: isAsk ? '2px solid rgba(88, 166, 255, 0.3)' : '2px solid rgba(63, 185, 80, 0.3)',
2987
3311
  }}>
2988
3312
  {isAsk ? '👤' : '🤖'}
2989
3313
  </div>
@@ -2994,16 +3318,18 @@ export function createDashboardHTML() {
2994
3318
  <div style={{
2995
3319
  display: 'flex',
2996
3320
  alignItems: 'center',
2997
- gap: '8px',
2998
- marginBottom: '8px',
3321
+ gap: '12px',
3322
+ marginBottom: '12px',
2999
3323
  flexWrap: 'wrap',
3000
3324
  }}>
3001
3325
  <span style={{
3002
- fontSize: '13px',
3003
- fontWeight: 600,
3326
+ fontSize: '14px',
3327
+ fontWeight: 700,
3004
3328
  color: isAsk ? 'var(--accent-blue)' : 'var(--accent-green)',
3329
+ textTransform: 'uppercase',
3330
+ letterSpacing: '0.05em',
3005
3331
  }}>
3006
- {label}
3332
+ {isAsk ? '📤 ' : '📥 '}{label}
3007
3333
  </span>
3008
3334
 
3009
3335
  {/* Metadata badges - hidden by default, show on hover */}
@@ -3042,19 +3368,21 @@ export function createDashboardHTML() {
3042
3368
  <div
3043
3369
  className="message-bubble"
3044
3370
  style={{
3045
- background: isAsk ? 'rgba(88, 166, 255, 0.06)' : 'var(--bg-tertiary)',
3046
- border: isAsk ? '1px solid rgba(88, 166, 255, 0.15)' : '1px solid var(--border-color)',
3047
- borderLeft: \`4px solid \${isAsk ? 'var(--accent-blue)' : 'var(--accent-green)'}\`,
3048
- padding: '16px',
3371
+ background: isAsk ? 'rgba(88, 166, 255, 0.08)' : 'rgba(63, 185, 80, 0.04)',
3372
+ border: isAsk ? '1px solid rgba(88, 166, 255, 0.2)' : '1px solid rgba(63, 185, 80, 0.15)',
3373
+ borderLeft: \`5px solid \${isAsk ? 'var(--accent-blue)' : 'var(--accent-green)'}\`,
3374
+ padding: '20px 24px',
3049
3375
  borderRadius: '4px 12px 12px 4px',
3050
- maxWidth: '75ch',
3376
+ maxWidth: '100%',
3377
+ boxShadow: isAsk ? '0 2px 8px rgba(88, 166, 255, 0.08)' : '0 2px 8px rgba(0, 0, 0, 0.1)',
3051
3378
  }}
3052
3379
  >
3053
3380
  {/* Rendered content with code blocks */}
3054
3381
  <div style={{
3055
- fontSize: '14px',
3056
- lineHeight: '1.7',
3382
+ fontSize: '15px',
3383
+ lineHeight: '1.8',
3057
3384
  color: 'var(--text-primary)',
3385
+ letterSpacing: '0.01em',
3058
3386
  }}>
3059
3387
  {contentParts.map((part, idx) => {
3060
3388
  if (part.type === 'code') {
@@ -3064,6 +3392,7 @@ export function createDashboardHTML() {
3064
3392
  <div key={idx} style={{
3065
3393
  whiteSpace: 'pre-wrap',
3066
3394
  wordBreak: 'break-word',
3395
+ marginBottom: idx < contentParts.length - 1 ? '12px' : 0,
3067
3396
  }}>
3068
3397
  {part.content}
3069
3398
  </div>
@@ -3185,24 +3514,58 @@ export function createDashboardHTML() {
3185
3514
  <span>Events: {trace.summary?.eventCount || trace.eventCount}</span>
3186
3515
  {trace.durationMs && <span>Duration: {trace.durationMs}ms</span>}
3187
3516
  {trace.commandType && <span>Type: {trace.commandType}</span>}
3517
+ {/* Provider info - prominently displayed */}
3518
+ {trace.provider && (
3519
+ <span style={{
3520
+ background: 'var(--accent-blue)',
3521
+ color: 'white',
3522
+ padding: '2px 8px',
3523
+ borderRadius: '4px',
3524
+ fontWeight: 600,
3525
+ }}>
3526
+ Provider: {trace.provider}
3527
+ </span>
3528
+ )}
3529
+ {trace.model && <span>Model: {trace.model}</span>}
3530
+ {/* For discuss: show providers list */}
3531
+ {trace.commandType === 'discuss' && trace.input?.providers && Array.isArray(trace.input.providers) && (
3532
+ <span style={{
3533
+ background: 'var(--accent-purple)',
3534
+ color: 'white',
3535
+ padding: '2px 8px',
3536
+ borderRadius: '4px',
3537
+ fontWeight: 600,
3538
+ }}>
3539
+ Providers: {trace.input.providers.join(', ')}
3540
+ </span>
3541
+ )}
3542
+ {/* PRD-2026-003: Show classification badge if available */}
3543
+ {trace.classification && (
3544
+ <ClassificationHealthBadge classification={trace.classification} showDetails={true} />
3545
+ )}
3188
3546
  </div>
3189
3547
  </div>
3190
3548
 
3191
3549
  {/* Provider Sub-tabs for Conversations */}
3192
3550
  {providerKeys.length > 0 && (
3193
- <div className="card" style={{ marginBottom: 16 }}>
3194
- <div className="card-header" style={{ borderBottom: 'none', paddingBottom: 0 }}>
3195
- <span className="card-title">Conversations</span>
3196
- <span className="card-badge">{providerKeys.length} provider{providerKeys.length !== 1 ? 's' : ''}</span>
3551
+ <div className="card" style={{ marginBottom: 16, border: '2px solid var(--accent-purple)', borderRadius: '12px' }}>
3552
+ <div className="card-header" style={{ borderBottom: 'none', paddingBottom: 0, background: 'rgba(163, 113, 247, 0.08)' }}>
3553
+ <span className="card-title" style={{ fontSize: '18px', display: 'flex', alignItems: 'center', gap: '8px' }}>
3554
+ 💬 Conversations
3555
+ </span>
3556
+ <span className="card-badge" style={{ background: 'var(--accent-purple)', color: 'white' }}>
3557
+ {providerKeys.length} provider{providerKeys.length !== 1 ? 's' : ''}
3558
+ </span>
3197
3559
  </div>
3198
3560
 
3199
3561
  {/* Provider tabs */}
3200
3562
  <div style={{
3201
3563
  display: 'flex',
3202
- gap: 0,
3203
- borderBottom: '1px solid var(--border-color)',
3204
- padding: '0 16px',
3205
- overflowX: 'auto'
3564
+ gap: '8px',
3565
+ padding: '12px 16px',
3566
+ overflowX: 'auto',
3567
+ background: 'var(--bg-tertiary)',
3568
+ borderBottom: '2px solid var(--border-color)',
3206
3569
  }}>
3207
3570
  {providerKeys.map(providerKey => {
3208
3571
  const group = providerGroups[providerKey];
@@ -3215,34 +3578,43 @@ export function createDashboardHTML() {
3215
3578
  key={providerKey}
3216
3579
  onClick={() => setActiveProviderTab(providerKey)}
3217
3580
  style={{
3218
- padding: '12px 16px',
3219
- background: 'transparent',
3220
- border: 'none',
3221
- borderBottom: isActive ? '2px solid var(--accent-blue)' : '2px solid transparent',
3222
- color: isActive ? 'var(--text-primary)' : 'var(--text-muted)',
3581
+ padding: '10px 16px',
3582
+ background: isActive ? 'var(--accent-blue)' : 'var(--bg-secondary)',
3583
+ border: isActive ? '2px solid var(--accent-blue)' : '2px solid var(--border-color)',
3584
+ borderRadius: '8px',
3585
+ color: isActive ? 'white' : 'var(--text-secondary)',
3223
3586
  cursor: 'pointer',
3224
3587
  fontSize: 13,
3225
- fontWeight: isActive ? 600 : 400,
3588
+ fontWeight: 600,
3226
3589
  display: 'flex',
3227
3590
  alignItems: 'center',
3228
- gap: 8,
3591
+ gap: 10,
3229
3592
  whiteSpace: 'nowrap',
3230
- transition: 'all 0.15s ease'
3593
+ transition: 'all 0.15s ease',
3594
+ boxShadow: isActive ? '0 2px 8px rgba(88, 166, 255, 0.3)' : 'none',
3231
3595
  }}
3232
3596
  >
3233
3597
  <span style={{
3234
- background: isActive ? 'var(--accent-purple)' : 'var(--bg-tertiary)',
3235
- color: isActive ? 'white' : 'var(--text-secondary)',
3236
- padding: '2px 8px',
3237
- borderRadius: 4,
3238
- fontSize: 11,
3239
- fontWeight: 600
3598
+ background: isActive ? 'rgba(255, 255, 255, 0.2)' : 'var(--bg-tertiary)',
3599
+ color: isActive ? 'white' : 'var(--text-primary)',
3600
+ padding: '4px 10px',
3601
+ borderRadius: 6,
3602
+ fontSize: 12,
3603
+ fontWeight: 700,
3604
+ textTransform: 'uppercase',
3605
+ letterSpacing: '0.03em',
3240
3606
  }}>
3241
3607
  {providerKey}
3242
3608
  </span>
3243
- <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
3609
+ <span style={{
3610
+ fontSize: 12,
3611
+ color: isActive ? 'rgba(255, 255, 255, 0.9)' : 'var(--text-muted)',
3612
+ background: isActive ? 'rgba(255, 255, 255, 0.15)' : 'var(--bg-tertiary)',
3613
+ padding: '3px 8px',
3614
+ borderRadius: 4,
3615
+ }}>
3244
3616
  {askCount > 0 && \`\${askCount} ask\`}
3245
- {askCount > 0 && replyCount > 0 && ' / '}
3617
+ {askCount > 0 && replyCount > 0 && ' · '}
3246
3618
  {replyCount > 0 && \`\${replyCount} reply\`}
3247
3619
  </span>
3248
3620
  </button>
@@ -3255,7 +3627,13 @@ export function createDashboardHTML() {
3255
3627
  <section
3256
3628
  aria-label="Chat History"
3257
3629
  role="log"
3258
- style={{ padding: 16 }}
3630
+ style={{
3631
+ padding: '24px',
3632
+ background: 'var(--bg-secondary)',
3633
+ borderRadius: '0 0 8px 8px',
3634
+ maxHeight: '80vh',
3635
+ overflowY: 'auto',
3636
+ }}
3259
3637
  >
3260
3638
  {providerGroups[effectiveActiveTab].model && (
3261
3639
  <div style={{
@@ -3296,12 +3674,16 @@ export function createDashboardHTML() {
3296
3674
  <div style={{
3297
3675
  color: 'var(--text-muted)',
3298
3676
  textAlign: 'center',
3299
- padding: '48px 24px',
3677
+ padding: '64px 32px',
3300
3678
  background: 'var(--bg-tertiary)',
3301
- borderRadius: '8px',
3679
+ borderRadius: '12px',
3680
+ border: '2px dashed var(--border-color)',
3302
3681
  }}>
3303
- <div style={{ fontSize: '24px', marginBottom: '8px', opacity: 0.5 }}>💬</div>
3304
- <div>No conversation data available</div>
3682
+ <div style={{ fontSize: '48px', marginBottom: '16px', opacity: 0.3 }}>💬</div>
3683
+ <div style={{ fontSize: '16px', fontWeight: 500 }}>No conversation data available</div>
3684
+ <div style={{ fontSize: '13px', marginTop: '8px', opacity: 0.7 }}>
3685
+ Conversation history will appear here when available
3686
+ </div>
3305
3687
  </div>
3306
3688
  )}
3307
3689
  </section>
@@ -3424,6 +3806,40 @@ export function createDashboardHTML() {
3424
3806
  );
3425
3807
  }
3426
3808
 
3809
+ const formatDuration = (ms) => {
3810
+ if (!ms) return '-';
3811
+ if (ms < 1000) return \`\${ms}ms\`;
3812
+ if (ms < 60000) return \`\${(ms / 1000).toFixed(1)}s\`;
3813
+ return \`\${(ms / 60000).toFixed(1)}m\`;
3814
+ };
3815
+
3816
+ const formatTime = (isoString) => {
3817
+ if (!isoString) return '-';
3818
+ const date = new Date(isoString);
3819
+ return date.toLocaleString();
3820
+ };
3821
+
3822
+ const getStepTypeIcon = (type) => {
3823
+ switch (type) {
3824
+ case 'prompt': return '💬';
3825
+ case 'tool': return '🔧';
3826
+ case 'conditional': return '🔀';
3827
+ case 'loop': return '🔄';
3828
+ case 'parallel': return '⚡';
3829
+ case 'delegate': return '👥';
3830
+ default: return '📋';
3831
+ }
3832
+ };
3833
+
3834
+ const getStatusColor = (status) => {
3835
+ switch (status) {
3836
+ case 'success': return 'var(--accent-green)';
3837
+ case 'failure': return 'var(--accent-red)';
3838
+ case 'running': return 'var(--accent-blue)';
3839
+ default: return 'var(--text-muted)';
3840
+ }
3841
+ };
3842
+
3427
3843
  return (
3428
3844
  <div>
3429
3845
  <button className="back-button" onClick={onBack}>
@@ -3435,11 +3851,219 @@ export function createDashboardHTML() {
3435
3851
  <span>ID: {workflow.workflowId}</span>
3436
3852
  <span>Version: {workflow.version}</span>
3437
3853
  <span>Steps: {(workflow.steps && workflow.steps.length) || 0}</span>
3854
+ {workflow.executionCount > 0 && (
3855
+ <span>Executions: {workflow.executionCount}</span>
3856
+ )}
3438
3857
  </div>
3439
3858
  {workflow.description && (
3440
3859
  <p style={{ marginTop: 8, color: 'var(--text-secondary)' }}>{workflow.description}</p>
3441
3860
  )}
3442
3861
  </div>
3862
+
3863
+ {/* Metadata section */}
3864
+ {workflow.metadata && (
3865
+ <div className="card" style={{ marginBottom: 16 }}>
3866
+ <div className="card-header">
3867
+ <span className="card-title">Metadata</span>
3868
+ </div>
3869
+ <div style={{ padding: '12px 16px', display: 'flex', flexWrap: 'wrap', gap: 16 }}>
3870
+ {workflow.metadata.category && (
3871
+ <div>
3872
+ <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Category</span>
3873
+ <div style={{ color: 'var(--accent-blue)', fontWeight: 500 }}>{workflow.metadata.category}</div>
3874
+ </div>
3875
+ )}
3876
+ {workflow.metadata.tags && workflow.metadata.tags.length > 0 && (
3877
+ <div>
3878
+ <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Tags</span>
3879
+ <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 2 }}>
3880
+ {workflow.metadata.tags.map((tag, i) => (
3881
+ <span key={i} style={{
3882
+ background: 'var(--bg-tertiary)',
3883
+ padding: '2px 8px',
3884
+ borderRadius: 4,
3885
+ fontSize: 11,
3886
+ color: 'var(--text-secondary)'
3887
+ }}>{tag}</span>
3888
+ ))}
3889
+ </div>
3890
+ </div>
3891
+ )}
3892
+ {workflow.metadata.requiredAbilities && workflow.metadata.requiredAbilities.length > 0 && (
3893
+ <div>
3894
+ <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Required Abilities</span>
3895
+ <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 2 }}>
3896
+ {workflow.metadata.requiredAbilities.map((ability, i) => (
3897
+ <span key={i} style={{
3898
+ background: 'rgba(163, 113, 247, 0.15)',
3899
+ padding: '2px 8px',
3900
+ borderRadius: 4,
3901
+ fontSize: 11,
3902
+ color: 'var(--accent-purple)'
3903
+ }}>{ability}</span>
3904
+ ))}
3905
+ </div>
3906
+ </div>
3907
+ )}
3908
+ </div>
3909
+ </div>
3910
+ )}
3911
+
3912
+ {/* Workflow Steps with details */}
3913
+ <div className="card" style={{ marginBottom: 16 }}>
3914
+ <div className="card-header">
3915
+ <span className="card-title">Workflow Steps ({workflow.steps?.length || 0})</span>
3916
+ </div>
3917
+ <div style={{ padding: '8px 0' }}>
3918
+ {workflow.steps && workflow.steps.map((step, index) => (
3919
+ <div key={step.stepId} style={{
3920
+ padding: '12px 16px',
3921
+ borderBottom: index < workflow.steps.length - 1 ? '1px solid var(--border-color)' : 'none'
3922
+ }}>
3923
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
3924
+ <div style={{
3925
+ width: 32,
3926
+ height: 32,
3927
+ borderRadius: 8,
3928
+ background: 'var(--bg-tertiary)',
3929
+ display: 'flex',
3930
+ alignItems: 'center',
3931
+ justifyContent: 'center',
3932
+ fontSize: 16,
3933
+ flexShrink: 0
3934
+ }}>
3935
+ {getStepTypeIcon(step.type)}
3936
+ </div>
3937
+ <div style={{ flex: 1, minWidth: 0 }}>
3938
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
3939
+ <span style={{ fontWeight: 500 }}>{step.name}</span>
3940
+ <span style={{
3941
+ background: 'var(--bg-tertiary)',
3942
+ padding: '2px 6px',
3943
+ borderRadius: 4,
3944
+ fontSize: 10,
3945
+ color: 'var(--text-muted)',
3946
+ textTransform: 'uppercase'
3947
+ }}>{step.type}</span>
3948
+ {step.timeout && (
3949
+ <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
3950
+ ⏱ {formatDuration(step.timeout)}
3951
+ </span>
3952
+ )}
3953
+ </div>
3954
+ <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'monospace', marginTop: 2 }}>
3955
+ {step.stepId}
3956
+ </div>
3957
+ {/* Step configuration details */}
3958
+ {step.config && (
3959
+ <div style={{
3960
+ marginTop: 8,
3961
+ padding: 10,
3962
+ background: 'var(--bg-primary)',
3963
+ borderRadius: 6,
3964
+ fontSize: 12
3965
+ }}>
3966
+ {step.config.agentId && (
3967
+ <div style={{ marginBottom: 4 }}>
3968
+ <span style={{ color: 'var(--text-muted)' }}>Agent: </span>
3969
+ <span style={{ color: 'var(--accent-blue)' }}>{step.config.agentId}</span>
3970
+ </div>
3971
+ )}
3972
+ {step.config.tool && (
3973
+ <div style={{ marginBottom: 4 }}>
3974
+ <span style={{ color: 'var(--text-muted)' }}>Tool: </span>
3975
+ <span style={{ color: 'var(--accent-green)' }}>{step.config.tool}</span>
3976
+ </div>
3977
+ )}
3978
+ {step.config.prompt && (
3979
+ <div>
3980
+ <span style={{ color: 'var(--text-muted)' }}>Prompt: </span>
3981
+ <pre style={{
3982
+ margin: '4px 0 0 0',
3983
+ padding: 8,
3984
+ background: 'var(--bg-secondary)',
3985
+ borderRadius: 4,
3986
+ fontSize: 11,
3987
+ color: 'var(--text-secondary)',
3988
+ whiteSpace: 'pre-wrap',
3989
+ wordBreak: 'break-word',
3990
+ maxHeight: 150,
3991
+ overflow: 'auto'
3992
+ }}>{step.config.prompt}</pre>
3993
+ </div>
3994
+ )}
3995
+ {step.config.args && Object.keys(step.config.args).length > 0 && (
3996
+ <div style={{ marginTop: 4 }}>
3997
+ <span style={{ color: 'var(--text-muted)' }}>Args: </span>
3998
+ <code style={{
3999
+ fontSize: 11,
4000
+ color: 'var(--text-secondary)',
4001
+ background: 'var(--bg-secondary)',
4002
+ padding: '2px 4px',
4003
+ borderRadius: 2
4004
+ }}>{JSON.stringify(step.config.args)}</code>
4005
+ </div>
4006
+ )}
4007
+ </div>
4008
+ )}
4009
+ {/* Dependencies */}
4010
+ {step.dependencies && step.dependencies.length > 0 && (
4011
+ <div style={{ marginTop: 6, fontSize: 11, color: 'var(--text-muted)' }}>
4012
+ Depends on: {step.dependencies.join(', ')}
4013
+ </div>
4014
+ )}
4015
+ </div>
4016
+ </div>
4017
+ </div>
4018
+ ))}
4019
+ </div>
4020
+ </div>
4021
+
4022
+ {/* Recent Executions */}
4023
+ {workflow.recentExecutions && workflow.recentExecutions.length > 0 && (
4024
+ <div className="card" style={{ marginBottom: 16 }}>
4025
+ <div className="card-header">
4026
+ <span className="card-title">Recent Executions ({workflow.executionCount})</span>
4027
+ </div>
4028
+ <div style={{ padding: '8px 0' }}>
4029
+ {workflow.recentExecutions.map((exec, index) => (
4030
+ <div key={exec.traceId} style={{
4031
+ padding: '10px 16px',
4032
+ borderBottom: index < workflow.recentExecutions.length - 1 ? '1px solid var(--border-color)' : 'none',
4033
+ display: 'flex',
4034
+ alignItems: 'center',
4035
+ justifyContent: 'space-between'
4036
+ }}>
4037
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
4038
+ <span style={{
4039
+ width: 8,
4040
+ height: 8,
4041
+ borderRadius: '50%',
4042
+ background: getStatusColor(exec.status)
4043
+ }}></span>
4044
+ <span style={{ fontFamily: 'monospace', fontSize: 12, color: 'var(--text-secondary)' }}>
4045
+ {exec.traceId.slice(0, 8)}
4046
+ </span>
4047
+ <span style={{
4048
+ fontSize: 10,
4049
+ padding: '2px 6px',
4050
+ borderRadius: 4,
4051
+ background: exec.status === 'success' ? 'rgba(63, 185, 80, 0.15)' :
4052
+ exec.status === 'failure' ? 'rgba(248, 81, 73, 0.15)' : 'var(--bg-tertiary)',
4053
+ color: getStatusColor(exec.status)
4054
+ }}>{exec.status}</span>
4055
+ </div>
4056
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, fontSize: 12, color: 'var(--text-muted)' }}>
4057
+ <span>{formatDuration(exec.durationMs)}</span>
4058
+ <span>{formatTime(exec.startTime)}</span>
4059
+ </div>
4060
+ </div>
4061
+ ))}
4062
+ </div>
4063
+ </div>
4064
+ )}
4065
+
4066
+ {/* Visual DAG */}
3443
4067
  <div className="card">
3444
4068
  <div className="card-header">
3445
4069
  <span className="card-title">Workflow DAG</span>
@@ -3457,6 +4081,23 @@ export function createDashboardHTML() {
3457
4081
  const [currentPage, setCurrentPage] = useState(0);
3458
4082
  const pageSize = 20;
3459
4083
 
4084
+ // Provider colors matching the Provider Usage histogram
4085
+ const providerColors = {
4086
+ claude: { bg: 'rgba(255, 145, 77, 0.15)', color: 'var(--accent-orange)' },
4087
+ grok: { bg: 'rgba(163, 113, 247, 0.15)', color: 'var(--accent-purple)' },
4088
+ gemini: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
4089
+ codex: { bg: 'rgba(63, 185, 80, 0.15)', color: 'var(--accent-green)' },
4090
+ opencode: { bg: 'rgba(63, 185, 185, 0.15)', color: 'var(--accent-cyan)' },
4091
+ antigravity: { bg: 'rgba(255, 200, 77, 0.15)', color: '#ffc84d' },
4092
+ cursor: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
4093
+ 'local-llm': { bg: 'rgba(163, 163, 163, 0.15)', color: 'var(--text-secondary)' },
4094
+ };
4095
+
4096
+ const getProviderStyle = (provider) => {
4097
+ const lowerProvider = provider?.toLowerCase() || '';
4098
+ return providerColors[lowerProvider] || { bg: 'var(--bg-secondary)', color: 'var(--text-secondary)' };
4099
+ };
4100
+
3460
4101
  const filteredTraces = traces.filter(trace => {
3461
4102
  if (filter !== 'all' && trace.status !== filter) return false;
3462
4103
  if (search && !trace.traceId.toLowerCase().includes(search.toLowerCase()) &&
@@ -3539,6 +4180,7 @@ export function createDashboardHTML() {
3539
4180
  <thead>
3540
4181
  <tr>
3541
4182
  <th>Name</th>
4183
+ <th>Providers</th>
3542
4184
  <th>Status</th>
3543
4185
  <th>Events</th>
3544
4186
  <th>Duration</th>
@@ -3548,12 +4190,12 @@ export function createDashboardHTML() {
3548
4190
  <tbody>
3549
4191
  {paginatedTraces.length === 0 ? (
3550
4192
  <tr>
3551
- <td colSpan="5" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
4193
+ <td colSpan="6" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
3552
4194
  <div className="table-empty-state">
3553
4195
  <div style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>📋</div>
3554
4196
  <div style={{ fontWeight: 500, marginBottom: '0.25rem' }}>No traces found</div>
3555
4197
  <div style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}>
3556
- {statusFilter === 'all'
4198
+ {filter === 'all'
3557
4199
  ? 'Run an agent or workflow to see execution traces here'
3558
4200
  : 'Try changing the status filter to see more results'}
3559
4201
  </div>
@@ -3565,7 +4207,44 @@ export function createDashboardHTML() {
3565
4207
  <tr key={trace.traceId} onClick={() => onSelectTrace(trace.traceId)}>
3566
4208
  <td style={{ fontFamily: 'monospace' }} title={trace.traceId}>{trace.name || trace.traceId.slice(0, 12) + '...'}</td>
3567
4209
  <td>
3568
- <span className={\`history-status \${trace.status}\`}>
4210
+ {trace.providers && trace.providers.length > 0 ? (
4211
+ <div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
4212
+ {trace.providers.slice(0, 3).map(provider => {
4213
+ const style = getProviderStyle(provider);
4214
+ return (
4215
+ <span
4216
+ key={provider}
4217
+ style={{
4218
+ padding: '2px 8px',
4219
+ borderRadius: '10px',
4220
+ background: style.bg,
4221
+ color: style.color,
4222
+ fontSize: '10px',
4223
+ fontWeight: 600,
4224
+ border: \`1px solid \${style.color}30\`,
4225
+ whiteSpace: 'nowrap',
4226
+ }}
4227
+ >
4228
+ {provider}
4229
+ </span>
4230
+ );
4231
+ })}
4232
+ {trace.providers.length > 3 && (
4233
+ <span style={{
4234
+ padding: '2px 6px',
4235
+ color: 'var(--text-muted)',
4236
+ fontSize: '10px',
4237
+ }}>
4238
+ +{trace.providers.length - 3}
4239
+ </span>
4240
+ )}
4241
+ </div>
4242
+ ) : (
4243
+ <span style={{ color: 'var(--text-muted)', fontSize: '11px' }}>-</span>
4244
+ )}
4245
+ </td>
4246
+ <td>
4247
+ <span className={\`history-status \${trace.status}\`}>
3569
4248
  <StatusIcon status={trace.status} />
3570
4249
  {trace.status}
3571
4250
  </span>
@@ -3642,9 +4321,28 @@ export function createDashboardHTML() {
3642
4321
  }
3643
4322
 
3644
4323
  // Full-page Providers view
3645
- function ProvidersView({ providers }) {
4324
+ function ProvidersView({ providers: initialProviders }) {
3646
4325
  const [filter, setFilter] = useState('all');
3647
4326
  const [search, setSearch] = useState('');
4327
+ const [refreshing, setRefreshing] = useState(false);
4328
+ const [providers, setProviders] = useState(initialProviders);
4329
+ const [lastRefresh, setLastRefresh] = useState(null);
4330
+
4331
+ const handleRefresh = async () => {
4332
+ setRefreshing(true);
4333
+ try {
4334
+ const response = await fetch('/api/providers/refresh', { method: 'POST' });
4335
+ const result = await response.json();
4336
+ if (result.success && result.data?.providers) {
4337
+ setProviders(result.data.providers);
4338
+ setLastRefresh(new Date().toLocaleTimeString());
4339
+ }
4340
+ } catch (err) {
4341
+ console.error('Failed to refresh providers:', err);
4342
+ } finally {
4343
+ setRefreshing(false);
4344
+ }
4345
+ };
3648
4346
 
3649
4347
  const filteredProviders = providers.filter(provider => {
3650
4348
  if (filter === 'available' && !provider.available) return false;
@@ -3674,6 +4372,19 @@ export function createDashboardHTML() {
3674
4372
  <option value="available">Available ({availableCount})</option>
3675
4373
  <option value="unavailable">Unavailable ({providers.length - availableCount})</option>
3676
4374
  </select>
4375
+ <button
4376
+ className="refresh-button"
4377
+ onClick={handleRefresh}
4378
+ disabled={refreshing}
4379
+ title="Check provider health (sends test prompt to each provider)"
4380
+ >
4381
+ {refreshing ? '⏳ Checking...' : '🔄 Refresh Status'}
4382
+ </button>
4383
+ {lastRefresh && (
4384
+ <span style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginLeft: '0.5rem' }}>
4385
+ Last refresh: {lastRefresh}
4386
+ </span>
4387
+ )}
3677
4388
  </div>
3678
4389
 
3679
4390
  <div className="card">
@@ -3682,14 +4393,13 @@ export function createDashboardHTML() {
3682
4393
  <tr>
3683
4394
  <th>Provider</th>
3684
4395
  <th>Status</th>
3685
- <th>Latency</th>
3686
- <th>Last Check</th>
4396
+ <th>Response Time</th>
3687
4397
  </tr>
3688
4398
  </thead>
3689
4399
  <tbody>
3690
4400
  {filteredProviders.length === 0 ? (
3691
4401
  <tr>
3692
- <td colSpan="4" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
4402
+ <td colSpan="3" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
3693
4403
  <div className="table-empty-state">
3694
4404
  <div style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>🔌</div>
3695
4405
  <div style={{ fontWeight: 500, marginBottom: '0.25rem' }}>No providers found</div>
@@ -3717,15 +4427,11 @@ export function createDashboardHTML() {
3717
4427
  <td style={{ fontFamily: 'monospace' }}>
3718
4428
  <span className={\`provider-latency \${
3719
4429
  !provider.latencyMs ? '' :
3720
- provider.latencyMs < 100 ? 'fast' :
3721
- provider.latencyMs < 300 ? 'medium' : 'slow'
4430
+ provider.latencyMs < 5000 ? 'fast' : 'slow'
3722
4431
  }\`}>
3723
4432
  {provider.latencyMs ? \`\${provider.latencyMs}ms\` : '-'}
3724
4433
  </span>
3725
4434
  </td>
3726
- <td style={{ color: 'var(--text-muted)' }}>
3727
- {provider.lastCheck ? formatRelativeTime(provider.lastCheck) : '-'}
3728
- </td>
3729
4435
  </tr>
3730
4436
  ))
3731
4437
  )}
@@ -4151,8 +4857,7 @@ export function createDashboardHTML() {
4151
4857
  </div>
4152
4858
  <span className={\`provider-latency \${
4153
4859
  !provider.latencyMs ? '' :
4154
- provider.latencyMs < 100 ? 'fast' :
4155
- provider.latencyMs < 300 ? 'medium' : 'slow'
4860
+ provider.latencyMs < 5000 ? 'fast' : 'slow'
4156
4861
  }\`}>
4157
4862
  {provider.latencyMs ? \`\${provider.latencyMs}ms\` : '-'}
4158
4863
  </span>
@@ -4245,9 +4950,48 @@ export function createDashboardHTML() {
4245
4950
  <div className="card" style={{ marginBottom: 16 }}>
4246
4951
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
4247
4952
  <div>
4248
- <h2 style={{ margin: 0, marginBottom: 8, color: 'var(--text-primary)' }}>
4249
- {agent.displayName || agent.agentId}
4250
- </h2>
4953
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8 }}>
4954
+ <h2 style={{ margin: 0, color: 'var(--text-primary)' }}>
4955
+ {agent.displayName || agent.agentId}
4956
+ </h2>
4957
+ {/* PRD-2026-004: Meta-agent badges */}
4958
+ {agent.metaAgent && (
4959
+ <span style={{
4960
+ padding: '2px 8px',
4961
+ borderRadius: 10,
4962
+ background: 'rgba(163, 113, 247, 0.2)',
4963
+ color: 'var(--accent-purple)',
4964
+ fontSize: 11,
4965
+ fontWeight: 600,
4966
+ }}>
4967
+ 🎯 Meta-Agent
4968
+ </span>
4969
+ )}
4970
+ {agent.archetype && (
4971
+ <span style={{
4972
+ padding: '2px 8px',
4973
+ borderRadius: 10,
4974
+ background: 'rgba(88, 166, 255, 0.2)',
4975
+ color: 'var(--accent-blue)',
4976
+ fontSize: 11,
4977
+ fontWeight: 600,
4978
+ }}>
4979
+ 🏗️ Archetype
4980
+ </span>
4981
+ )}
4982
+ {agent.category && (
4983
+ <span style={{
4984
+ padding: '2px 8px',
4985
+ borderRadius: 10,
4986
+ background: 'rgba(139, 148, 158, 0.15)',
4987
+ color: 'var(--text-muted)',
4988
+ fontSize: 11,
4989
+ textTransform: 'capitalize',
4990
+ }}>
4991
+ {agent.category}
4992
+ </span>
4993
+ )}
4994
+ </div>
4251
4995
  <p style={{ margin: 0, color: 'var(--text-secondary)', maxWidth: 600 }}>
4252
4996
  {agent.description}
4253
4997
  </p>
@@ -4349,6 +5093,239 @@ export function createDashboardHTML() {
4349
5093
  </div>
4350
5094
  )}
4351
5095
 
5096
+ {/* PRD-2026-004: Orchestration Relationships */}
5097
+ {(agent.orchestrates?.length > 0 || agent.canDelegateToArchetypes?.length > 0 || agent.canDelegateToMetaAgents?.length > 0 || agent.replaces?.length > 0) && (
5098
+ <div className="card" style={{ marginBottom: 16 }}>
5099
+ <h3 style={{ margin: 0, marginBottom: 16, fontSize: 14, color: 'var(--text-secondary)' }}>
5100
+ 🔗 Orchestration
5101
+ </h3>
5102
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
5103
+ {agent.orchestrates?.length > 0 && (
5104
+ <div>
5105
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Orchestrates</div>
5106
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
5107
+ {agent.orchestrates.map((id, idx) => (
5108
+ <span key={idx} style={{
5109
+ padding: '4px 10px',
5110
+ borderRadius: 12,
5111
+ background: 'rgba(163, 113, 247, 0.15)',
5112
+ color: 'var(--accent-purple)',
5113
+ fontSize: 12,
5114
+ }}>
5115
+ {id}
5116
+ </span>
5117
+ ))}
5118
+ </div>
5119
+ </div>
5120
+ )}
5121
+ {agent.canDelegateToArchetypes?.length > 0 && (
5122
+ <div>
5123
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Can Delegate to Archetypes</div>
5124
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
5125
+ {agent.canDelegateToArchetypes.map((id, idx) => (
5126
+ <span key={idx} style={{
5127
+ padding: '4px 10px',
5128
+ borderRadius: 12,
5129
+ background: 'rgba(88, 166, 255, 0.15)',
5130
+ color: 'var(--accent-blue)',
5131
+ fontSize: 12,
5132
+ }}>
5133
+ {id}
5134
+ </span>
5135
+ ))}
5136
+ </div>
5137
+ </div>
5138
+ )}
5139
+ {agent.canDelegateToMetaAgents?.length > 0 && (
5140
+ <div>
5141
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Can Delegate to Meta-Agents</div>
5142
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
5143
+ {agent.canDelegateToMetaAgents.map((id, idx) => (
5144
+ <span key={idx} style={{
5145
+ padding: '4px 10px',
5146
+ borderRadius: 12,
5147
+ background: 'rgba(163, 113, 247, 0.15)',
5148
+ color: 'var(--accent-purple)',
5149
+ fontSize: 12,
5150
+ }}>
5151
+ {id}
5152
+ </span>
5153
+ ))}
5154
+ </div>
5155
+ </div>
5156
+ )}
5157
+ {agent.replaces?.length > 0 && (
5158
+ <div>
5159
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Replaces (Legacy Agents)</div>
5160
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
5161
+ {agent.replaces.map((id, idx) => (
5162
+ <span key={idx} style={{
5163
+ padding: '4px 10px',
5164
+ borderRadius: 12,
5165
+ background: 'rgba(139, 148, 158, 0.15)',
5166
+ color: 'var(--text-muted)',
5167
+ fontSize: 12,
5168
+ textDecoration: 'line-through',
5169
+ }}>
5170
+ {id}
5171
+ </span>
5172
+ ))}
5173
+ </div>
5174
+ </div>
5175
+ )}
5176
+ </div>
5177
+ </div>
5178
+ )}
5179
+
5180
+ {/* PRD-2026-004: Task Classifier Config */}
5181
+ {agent.taskClassifier && (
5182
+ <div className="card" style={{ marginBottom: 16 }}>
5183
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
5184
+ <h3 style={{ margin: 0, fontSize: 14, color: 'var(--text-secondary)' }}>
5185
+ 🎯 Task Classifier
5186
+ </h3>
5187
+ <span style={{
5188
+ padding: '2px 8px',
5189
+ borderRadius: 10,
5190
+ background: agent.taskClassifier.enabled !== false ? 'rgba(63, 185, 80, 0.2)' : 'rgba(139, 148, 158, 0.2)',
5191
+ color: agent.taskClassifier.enabled !== false ? 'var(--accent-green)' : 'var(--text-muted)',
5192
+ fontSize: 11,
5193
+ }}>
5194
+ {agent.taskClassifier.enabled !== false ? 'Enabled' : 'Disabled'}
5195
+ </span>
5196
+ </div>
5197
+ {agent.taskClassifier.defaultWorkflow && (
5198
+ <div style={{ marginBottom: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8 }}>
5199
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 }}>Default Workflow</div>
5200
+ <code style={{ color: 'var(--accent-cyan)', fontSize: 13 }}>{agent.taskClassifier.defaultWorkflow}</code>
5201
+ </div>
5202
+ )}
5203
+ {agent.taskClassifier.rules?.length > 0 && (
5204
+ <div>
5205
+ <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>
5206
+ Classification Rules ({agent.taskClassifier.rules.length})
5207
+ </div>
5208
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
5209
+ {agent.taskClassifier.rules
5210
+ .sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50))
5211
+ .map((rule, idx) => {
5212
+ const priority = rule.priority ?? 50;
5213
+ return (
5214
+ <div key={idx} style={{
5215
+ padding: 12,
5216
+ background: 'var(--bg-tertiary)',
5217
+ borderRadius: 8,
5218
+ borderLeft: \`3px solid \${
5219
+ priority >= 80 ? 'var(--accent-red)' :
5220
+ priority >= 70 ? 'var(--accent-orange)' :
5221
+ 'var(--accent-blue)'
5222
+ }\`,
5223
+ }}>
5224
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8 }}>
5225
+ <span style={{
5226
+ padding: '2px 8px',
5227
+ borderRadius: 10,
5228
+ background: 'rgba(88, 166, 255, 0.15)',
5229
+ color: 'var(--accent-blue)',
5230
+ fontSize: 11,
5231
+ fontWeight: 500,
5232
+ }}>
5233
+ {rule.taskType}
5234
+ </span>
5235
+ <span style={{
5236
+ padding: '2px 8px',
5237
+ borderRadius: 10,
5238
+ background: priority >= 80 ? 'rgba(248, 81, 73, 0.15)' :
5239
+ priority >= 70 ? 'rgba(227, 179, 65, 0.15)' :
5240
+ 'rgba(139, 148, 158, 0.15)',
5241
+ color: priority >= 80 ? 'var(--accent-red)' :
5242
+ priority >= 70 ? 'var(--accent-orange)' :
5243
+ 'var(--text-muted)',
5244
+ fontSize: 10,
5245
+ }}>
5246
+ Priority: {priority}
5247
+ </span>
5248
+ </div>
5249
+ <div style={{ marginBottom: 4 }}>
5250
+ <code style={{ fontSize: 11, color: 'var(--accent-purple)', wordBreak: 'break-all' }}>
5251
+ /{rule.pattern}/i
5252
+ </code>
5253
+ </div>
5254
+ <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
5255
+ → {rule.workflow}
5256
+ </div>
5257
+ </div>
5258
+ );})}
5259
+ </div>
5260
+ </div>
5261
+ )}
5262
+ </div>
5263
+ )}
5264
+
5265
+ {/* PRD-2026-004: Capability Mappings */}
5266
+ {agent.capabilityMappings?.length > 0 && (
5267
+ <div className="card" style={{ marginBottom: 16 }}>
5268
+ <h3 style={{ margin: 0, marginBottom: 16, fontSize: 14, color: 'var(--text-secondary)' }}>
5269
+ 📋 Capability Mappings ({agent.capabilityMappings.length})
5270
+ </h3>
5271
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
5272
+ {agent.capabilityMappings.map((mapping, idx) => (
5273
+ <div key={idx} style={{
5274
+ padding: 12,
5275
+ background: 'var(--bg-tertiary)',
5276
+ borderRadius: 8,
5277
+ }}>
5278
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
5279
+ <span style={{
5280
+ padding: '2px 8px',
5281
+ borderRadius: 10,
5282
+ background: 'rgba(88, 166, 255, 0.15)',
5283
+ color: 'var(--accent-blue)',
5284
+ fontSize: 11,
5285
+ fontWeight: 500,
5286
+ }}>
5287
+ {mapping.taskType}
5288
+ </span>
5289
+ {mapping.priority !== undefined && (
5290
+ <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
5291
+ Priority: {mapping.priority}
5292
+ </span>
5293
+ )}
5294
+ </div>
5295
+ {mapping.description && (
5296
+ <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 8 }}>
5297
+ {mapping.description}
5298
+ </div>
5299
+ )}
5300
+ {mapping.abilities?.length > 0 && (
5301
+ <div style={{ marginBottom: 8 }}>
5302
+ <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>Abilities:</div>
5303
+ <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
5304
+ {mapping.abilities.map((ability, i) => (
5305
+ <span key={i} style={{
5306
+ padding: '2px 6px',
5307
+ borderRadius: 8,
5308
+ background: 'rgba(63, 185, 80, 0.15)',
5309
+ color: 'var(--accent-green)',
5310
+ fontSize: 10,
5311
+ }}>
5312
+ {ability}
5313
+ </span>
5314
+ ))}
5315
+ </div>
5316
+ </div>
5317
+ )}
5318
+ {mapping.workflowRef && (
5319
+ <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
5320
+ Workflow: <code style={{ color: 'var(--accent-cyan)' }}>{mapping.workflowRef}</code>
5321
+ </div>
5322
+ )}
5323
+ </div>
5324
+ ))}
5325
+ </div>
5326
+ </div>
5327
+ )}
5328
+
4352
5329
  {/* Workflow Steps */}
4353
5330
  {agent.workflow?.length > 0 && (
4354
5331
  <div className="card" style={{ marginBottom: 16 }}>
@@ -4482,10 +5459,39 @@ export function createDashboardHTML() {
4482
5459
  onClick={() => onSelectAgent(agent.agentId)}
4483
5460
  style={{ cursor: 'pointer' }}
4484
5461
  >
4485
- <span className="agent-name" title={agent.description}>
4486
- {agent.displayName || agent.agentId}
4487
- </span>
4488
- <span style={{ color: agent.enabled ? 'var(--accent-green)' : 'var(--text-muted)' }}>
5462
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 1, minWidth: 0 }}>
5463
+ <span className="agent-name" title={agent.description || ''} style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
5464
+ {agent.displayName || agent.agentId}
5465
+ </span>
5466
+ {/* PRD-2026-004: Meta-agent badges */}
5467
+ {agent.metaAgent && (
5468
+ <span style={{
5469
+ padding: '1px 6px',
5470
+ borderRadius: 8,
5471
+ background: 'rgba(163, 113, 247, 0.2)',
5472
+ color: 'var(--accent-purple)',
5473
+ fontSize: 9,
5474
+ fontWeight: 600,
5475
+ flexShrink: 0,
5476
+ }}>
5477
+ META
5478
+ </span>
5479
+ )}
5480
+ {agent.archetype && (
5481
+ <span style={{
5482
+ padding: '1px 6px',
5483
+ borderRadius: 8,
5484
+ background: 'rgba(88, 166, 255, 0.2)',
5485
+ color: 'var(--accent-blue)',
5486
+ fontSize: 9,
5487
+ fontWeight: 600,
5488
+ flexShrink: 0,
5489
+ }}>
5490
+ ARCH
5491
+ </span>
5492
+ )}
5493
+ </div>
5494
+ <span style={{ color: agent.enabled ? 'var(--accent-green)' : 'var(--text-muted)', flexShrink: 0 }}>
4489
5495
  {agent.enabled ? 'active' : 'disabled'}
4490
5496
  </span>
4491
5497
  </div>
@@ -4542,6 +5548,24 @@ export function createDashboardHTML() {
4542
5548
 
4543
5549
  // Traces card component (clickable)
4544
5550
  function TracesCard({ traces, onSelectTrace }) {
5551
+ // Provider colors matching the Provider Usage histogram
5552
+ const providerColors = {
5553
+ claude: { bg: 'rgba(255, 145, 77, 0.15)', color: 'var(--accent-orange)' },
5554
+ grok: { bg: 'rgba(163, 113, 247, 0.15)', color: 'var(--accent-purple)' },
5555
+ gemini: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
5556
+ codex: { bg: 'rgba(63, 185, 80, 0.15)', color: 'var(--accent-green)' },
5557
+ opencode: { bg: 'rgba(63, 185, 185, 0.15)', color: 'var(--accent-cyan)' },
5558
+ antigravity: { bg: 'rgba(255, 200, 77, 0.15)', color: '#ffc84d' },
5559
+ cursor: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
5560
+ 'local-llm': { bg: 'rgba(163, 163, 163, 0.15)', color: 'var(--text-secondary)' },
5561
+ };
5562
+
5563
+ const getProviderStyle = (provider) => {
5564
+ const lowerProvider = provider?.toLowerCase() || '';
5565
+ const colors = providerColors[lowerProvider] || { bg: 'var(--bg-secondary)', color: 'var(--text-secondary)' };
5566
+ return colors;
5567
+ };
5568
+
4545
5569
  const formatDuration = (ms) => {
4546
5570
  if (!ms) return null;
4547
5571
  if (ms < 1000) return \`\${Math.round(ms)}ms\`;
@@ -4708,7 +5732,7 @@ export function createDashboardHTML() {
4708
5732
  </span>
4709
5733
  </div>
4710
5734
 
4711
- {/* Providers row */}
5735
+ {/* Providers row - colored bubbles */}
4712
5736
  {trace.providers && trace.providers.length > 0 && (
4713
5737
  <div style={{
4714
5738
  display: 'flex',
@@ -4716,21 +5740,25 @@ export function createDashboardHTML() {
4716
5740
  marginTop: '6px',
4717
5741
  flexWrap: 'wrap',
4718
5742
  }}>
4719
- {trace.providers.slice(0, 4).map(provider => (
4720
- <span
4721
- key={provider}
4722
- style={{
4723
- padding: '2px 6px',
4724
- borderRadius: '4px',
4725
- background: 'var(--bg-secondary)',
4726
- color: 'var(--text-secondary)',
4727
- fontSize: '10px',
4728
- fontWeight: 500,
4729
- }}
4730
- >
4731
- {provider}
4732
- </span>
4733
- ))}
5743
+ {trace.providers.slice(0, 4).map(provider => {
5744
+ const style = getProviderStyle(provider);
5745
+ return (
5746
+ <span
5747
+ key={provider}
5748
+ style={{
5749
+ padding: '2px 8px',
5750
+ borderRadius: '10px',
5751
+ background: style.bg,
5752
+ color: style.color,
5753
+ fontSize: '10px',
5754
+ fontWeight: 600,
5755
+ border: \`1px solid \${style.color}30\`,
5756
+ }}
5757
+ >
5758
+ {provider}
5759
+ </span>
5760
+ );
5761
+ })}
4734
5762
  {trace.providers.length > 4 && (
4735
5763
  <span style={{
4736
5764
  padding: '2px 6px',
@@ -4944,6 +5972,293 @@ export function createDashboardHTML() {
4944
5972
  );
4945
5973
  }
4946
5974
 
5975
+ // PRD-2026-003: Classification Health Badge
5976
+ // Compact badge showing classification confidence and guard status
5977
+ function ClassificationHealthBadge({ classification, showDetails = false }) {
5978
+ if (!classification) {
5979
+ return null;
5980
+ }
5981
+
5982
+ const { taskType, confidence, guardResults } = classification;
5983
+ const guardPassRate = guardResults && guardResults.length > 0
5984
+ ? guardResults.filter(g => g.passed).length / guardResults.length
5985
+ : 1.0;
5986
+
5987
+ // Determine badge color based on confidence
5988
+ const getConfidenceLevel = (conf) => {
5989
+ if (conf >= 0.8) return { color: 'var(--accent-green)', label: 'high', bg: 'rgba(63, 185, 80, 0.15)' };
5990
+ if (conf >= 0.6) return { color: 'var(--accent-yellow)', label: 'medium', bg: 'rgba(210, 153, 34, 0.15)' };
5991
+ return { color: 'var(--accent-red)', label: 'low', bg: 'rgba(248, 81, 73, 0.15)' };
5992
+ };
5993
+
5994
+ const confidenceLevel = getConfidenceLevel(confidence || 0);
5995
+ const guardStatus = guardPassRate >= 0.95 ? 'pass' : guardPassRate >= 0.5 ? 'partial' : 'fail';
5996
+
5997
+ const badgeStyle = {
5998
+ display: 'inline-flex',
5999
+ alignItems: 'center',
6000
+ gap: '4px',
6001
+ padding: '2px 8px',
6002
+ borderRadius: '12px',
6003
+ fontSize: '11px',
6004
+ fontWeight: 500,
6005
+ background: confidenceLevel.bg,
6006
+ color: confidenceLevel.color,
6007
+ };
6008
+
6009
+ if (!showDetails) {
6010
+ // Compact mode - just show task type with confidence indicator
6011
+ return (
6012
+ <span style={badgeStyle} title={\`\${taskType} (confidence: \${((confidence || 0) * 100).toFixed(0)}%)\`}>
6013
+ 🎯 {taskType}
6014
+ </span>
6015
+ );
6016
+ }
6017
+
6018
+ // Detailed mode - show confidence and guard status
6019
+ return (
6020
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
6021
+ <span style={badgeStyle}>
6022
+ 🎯 {taskType}
6023
+ </span>
6024
+ <span style={{
6025
+ ...badgeStyle,
6026
+ background: confidenceLevel.bg,
6027
+ color: confidenceLevel.color,
6028
+ }}>
6029
+ {((confidence || 0) * 100).toFixed(0)}% conf
6030
+ </span>
6031
+ {guardResults && guardResults.length > 0 && (
6032
+ <span style={{
6033
+ ...badgeStyle,
6034
+ background: guardStatus === 'pass' ? 'rgba(63, 185, 80, 0.15)' :
6035
+ guardStatus === 'partial' ? 'rgba(210, 153, 34, 0.15)' :
6036
+ 'rgba(248, 81, 73, 0.15)',
6037
+ color: guardStatus === 'pass' ? 'var(--accent-green)' :
6038
+ guardStatus === 'partial' ? 'var(--accent-yellow)' :
6039
+ 'var(--accent-red)',
6040
+ }}>
6041
+ 🛡️ {guardResults.filter(g => g.passed).length}/{guardResults.length}
6042
+ </span>
6043
+ )}
6044
+ </div>
6045
+ );
6046
+ }
6047
+
6048
+ // PRD-2026-003: Task Classification Card
6049
+ function TaskClassificationCard({ classification }) {
6050
+ if (!classification || classification.totalClassifications === 0) {
6051
+ return null; // Don't show card if no classification data
6052
+ }
6053
+
6054
+ const {
6055
+ totalClassifications,
6056
+ byTaskType,
6057
+ guardPassRate,
6058
+ averageConfidence,
6059
+ fallbackRate,
6060
+ sampleSize,
6061
+ } = classification;
6062
+
6063
+ const taskTypes = Object.entries(byTaskType || {}).sort((a, b) => b[1] - a[1]);
6064
+ const topTaskTypes = taskTypes.slice(0, 5);
6065
+
6066
+ const getConfidenceColor = (confidence) => {
6067
+ if (confidence >= 0.8) return 'var(--accent-green)';
6068
+ if (confidence >= 0.6) return 'var(--accent-yellow)';
6069
+ return 'var(--accent-red)';
6070
+ };
6071
+
6072
+ const getGuardColor = (rate) => {
6073
+ if (rate >= 0.95) return 'var(--accent-green)';
6074
+ if (rate >= 0.8) return 'var(--accent-yellow)';
6075
+ return 'var(--accent-red)';
6076
+ };
6077
+
6078
+ const statBoxStyle = {
6079
+ background: 'var(--bg-tertiary)',
6080
+ borderRadius: '8px',
6081
+ padding: '16px',
6082
+ display: 'flex',
6083
+ flexDirection: 'column',
6084
+ gap: '8px',
6085
+ };
6086
+
6087
+ const iconStyle = {
6088
+ width: '32px',
6089
+ height: '32px',
6090
+ borderRadius: '8px',
6091
+ display: 'flex',
6092
+ alignItems: 'center',
6093
+ justifyContent: 'center',
6094
+ fontSize: '16px',
6095
+ marginBottom: '4px',
6096
+ };
6097
+
6098
+ return (
6099
+ <div className="card">
6100
+ <div className="card-header">
6101
+ <span className="card-title">Task Classification (PRD-2026-003)</span>
6102
+ <span style={{
6103
+ background: 'var(--accent-purple)',
6104
+ color: 'white',
6105
+ padding: '2px 8px',
6106
+ borderRadius: '12px',
6107
+ fontSize: '11px',
6108
+ fontWeight: 500,
6109
+ }}>
6110
+ {totalClassifications} classified
6111
+ </span>
6112
+ </div>
6113
+
6114
+ <div style={{
6115
+ display: 'grid',
6116
+ gridTemplateColumns: 'repeat(4, 1fr)',
6117
+ gap: '12px',
6118
+ marginTop: '12px'
6119
+ }}>
6120
+ {/* Total Classifications */}
6121
+ <div style={statBoxStyle}>
6122
+ <div style={{ ...iconStyle, background: 'rgba(163, 113, 247, 0.15)' }}>
6123
+ <span style={{ color: 'var(--accent-purple)' }}>🎯</span>
6124
+ </div>
6125
+ <div style={{ fontSize: '28px', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1 }}>
6126
+ {totalClassifications}
6127
+ </div>
6128
+ <div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Classifications</div>
6129
+ </div>
6130
+
6131
+ {/* Average Confidence */}
6132
+ <div style={statBoxStyle}>
6133
+ <div style={{ ...iconStyle, background: 'rgba(63, 185, 80, 0.15)' }}>
6134
+ <span style={{ color: 'var(--accent-green)' }}>📊</span>
6135
+ </div>
6136
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
6137
+ <span style={{ fontSize: '28px', fontWeight: 700, color: getConfidenceColor(averageConfidence), lineHeight: 1 }}>
6138
+ {(averageConfidence * 100).toFixed(0)}
6139
+ </span>
6140
+ <span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
6141
+ </div>
6142
+ <div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Avg Confidence</div>
6143
+ <div style={{
6144
+ height: '4px',
6145
+ background: 'var(--bg-secondary)',
6146
+ borderRadius: '2px',
6147
+ overflow: 'hidden',
6148
+ marginTop: '4px'
6149
+ }}>
6150
+ <div style={{
6151
+ height: '100%',
6152
+ width: \`\${averageConfidence * 100}%\`,
6153
+ background: getConfidenceColor(averageConfidence),
6154
+ borderRadius: '2px',
6155
+ transition: 'width 0.3s ease'
6156
+ }}></div>
6157
+ </div>
6158
+ </div>
6159
+
6160
+ {/* Guard Pass Rate */}
6161
+ <div style={statBoxStyle}>
6162
+ <div style={{ ...iconStyle, background: 'rgba(56, 211, 159, 0.15)' }}>
6163
+ <span style={{ color: 'var(--accent-cyan)' }}>🛡️</span>
6164
+ </div>
6165
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
6166
+ <span style={{ fontSize: '28px', fontWeight: 700, color: getGuardColor(guardPassRate), lineHeight: 1 }}>
6167
+ {(guardPassRate * 100).toFixed(0)}
6168
+ </span>
6169
+ <span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
6170
+ </div>
6171
+ <div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Guard Pass Rate</div>
6172
+ <div style={{
6173
+ height: '4px',
6174
+ background: 'var(--bg-secondary)',
6175
+ borderRadius: '2px',
6176
+ overflow: 'hidden',
6177
+ marginTop: '4px'
6178
+ }}>
6179
+ <div style={{
6180
+ height: '100%',
6181
+ width: \`\${guardPassRate * 100}%\`,
6182
+ background: getGuardColor(guardPassRate),
6183
+ borderRadius: '2px',
6184
+ transition: 'width 0.3s ease'
6185
+ }}></div>
6186
+ </div>
6187
+ </div>
6188
+
6189
+ {/* Fallback Rate */}
6190
+ <div style={statBoxStyle}>
6191
+ <div style={{ ...iconStyle, background: 'rgba(248, 81, 73, 0.15)' }}>
6192
+ <span style={{ color: 'var(--accent-red)' }}>⚠️</span>
6193
+ </div>
6194
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
6195
+ <span style={{ fontSize: '28px', fontWeight: 700, color: fallbackRate > 0.1 ? 'var(--accent-yellow)' : 'var(--accent-green)', lineHeight: 1 }}>
6196
+ {(fallbackRate * 100).toFixed(0)}
6197
+ </span>
6198
+ <span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
6199
+ </div>
6200
+ <div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Fallback Rate</div>
6201
+ <div style={{ fontSize: '10px', color: 'var(--text-muted)', marginTop: '2px' }}>
6202
+ No mapping selected
6203
+ </div>
6204
+ </div>
6205
+ </div>
6206
+
6207
+ {/* Task Type Distribution */}
6208
+ {topTaskTypes.length > 0 && (
6209
+ <div style={{ marginTop: '16px' }}>
6210
+ <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginBottom: '8px', fontWeight: 500 }}>
6211
+ Task Type Distribution
6212
+ </div>
6213
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
6214
+ {topTaskTypes.map(([taskType, count]) => {
6215
+ const percentage = (count / totalClassifications) * 100;
6216
+ // PRD-2026-004: Task type colors including new types
6217
+ const taskTypeColors = {
6218
+ implementation: 'var(--accent-blue)',
6219
+ debugging: 'var(--accent-red)',
6220
+ refactoring: 'var(--accent-orange)',
6221
+ testing: 'var(--accent-green)',
6222
+ documentation: 'var(--accent-cyan)',
6223
+ analysis: 'var(--accent-purple)',
6224
+ review: 'var(--accent-yellow)',
6225
+ deployment: '#f85149', // Red-orange for deploy actions
6226
+ research: '#a371f7', // Purple for research
6227
+ unknown: 'var(--text-muted)',
6228
+ };
6229
+ const barColor = taskTypeColors[taskType] || 'var(--accent-purple)';
6230
+ return (
6231
+ <div key={taskType} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
6232
+ <div style={{ width: '100px', fontSize: '12px', color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
6233
+ {taskType}
6234
+ </div>
6235
+ <div style={{ flex: 1, height: '8px', background: 'var(--bg-secondary)', borderRadius: '4px', overflow: 'hidden' }}>
6236
+ <div style={{
6237
+ height: '100%',
6238
+ width: \`\${percentage}%\`,
6239
+ background: barColor,
6240
+ borderRadius: '4px',
6241
+ transition: 'width 0.3s ease'
6242
+ }}></div>
6243
+ </div>
6244
+ <div style={{ width: '60px', fontSize: '11px', color: 'var(--text-muted)', textAlign: 'right' }}>
6245
+ {count} ({percentage.toFixed(0)}%)
6246
+ </div>
6247
+ </div>
6248
+ );
6249
+ })}
6250
+ </div>
6251
+ {taskTypes.length > 5 && (
6252
+ <div style={{ fontSize: '10px', color: 'var(--text-muted)', marginTop: '8px' }}>
6253
+ +{taskTypes.length - 5} more task types
6254
+ </div>
6255
+ )}
6256
+ </div>
6257
+ )}
6258
+ </div>
6259
+ );
6260
+ }
6261
+
4947
6262
  // Main Dashboard component
4948
6263
  function Dashboard() {
4949
6264
  const [view, setView] = useState('dashboard');
@@ -5204,6 +6519,8 @@ export function createDashboardHTML() {
5204
6519
 
5205
6520
  <ExecutionStatsCard traces={data.traces || []} />
5206
6521
 
6522
+ <TaskClassificationCard classification={data.classification} />
6523
+
5207
6524
  <TracesCard traces={data.traces || []} onSelectTrace={handleSelectTrace} />
5208
6525
  </section>
5209
6526
  )}