feat: render workflow diagnostics in activity panel

Surface workflow-diagnostic session events (warnings and errors from
Agent Framework workflows) in the turn-event log of the Activity Panel.

- Add workflow-diagnostic case to formatTurnEventEntry with label/detail
  formatting that includes executor ID, subworkflow ID, exception type,
  and diagnostic message when present
- Add AlertTriangle icon for diagnostic events with error/warning color
- Add 5 tests covering executor-failed, workflow-warning, subworkflow-error,
  workflow-error, and missing diagnosticKind fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-01 18:54:21 +02:00
co-authored by Copilot
parent 11b36827f5
commit 0aed6240b9
3 changed files with 112 additions and 2 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
import { useMemo, type ReactNode } from 'react';
import { Activity, ArrowRight, BarChart3, CheckCircle2, Clock, Cog, ShieldAlert, Sparkles, Users, Zap } from 'lucide-react';
import { Activity, AlertTriangle, ArrowRight, BarChart3, CheckCircle2, Clock, Cog, ShieldAlert, Sparkles, Users, Zap } from 'lucide-react';
import {
buildAgentActivityRows,
@@ -190,6 +190,8 @@ function TurnEventIcon({ kind, phase, success }: { kind: SessionEventKind; phase
return <Sparkles className={`${base} text-[var(--color-accent-purple)]`} />;
case 'session-compaction':
return <CheckCircle2 className={`${base} ${phase === 'start' ? 'animate-pulse text-[var(--color-status-warning)]' : 'text-[var(--color-status-success)]'}`} />;
case 'workflow-diagnostic':
return <AlertTriangle className={`${base} ${success === false ? 'text-[var(--color-status-error)]' : 'text-[var(--color-status-warning)]'}`} />;
default:
return <Zap className={`${base} text-[var(--color-text-muted)]`} />;
}
+32 -1
View File
@@ -1,6 +1,6 @@
import type { PatternDefinition } from '@shared/domain/pattern';
import type { SessionEventRecord } from '@shared/domain/event';
import type { QuotaSnapshot } from '@shared/contracts/sidecar';
import type { QuotaSnapshot, WorkflowDiagnosticKind, WorkflowDiagnosticSeverity } from '@shared/contracts/sidecar';
export interface AgentActivityState {
agentId: string;
@@ -260,6 +260,22 @@ function formatHookType(hookType: string | undefined): string {
return hookTypeLabels[hookType] ?? hookType;
}
const diagnosticLabels: Record<WorkflowDiagnosticKind, string> = {
'workflow-warning': 'Workflow warning',
'workflow-error': 'Workflow error',
'executor-failed': 'Executor failed',
'subworkflow-warning': 'Subworkflow warning',
'subworkflow-error': 'Subworkflow error',
};
function formatDiagnosticLabel(
kind: WorkflowDiagnosticKind | undefined,
severity: WorkflowDiagnosticSeverity | undefined,
): string {
if (kind) return diagnosticLabels[kind] ?? kind;
return severity === 'error' ? 'Workflow error' : 'Workflow warning';
}
function formatTurnEventEntry(event: SessionEventRecord): TurnEventEntry | undefined {
switch (event.kind) {
case 'subagent':
@@ -300,6 +316,21 @@ function formatTurnEventEntry(event: SessionEventRecord): TurnEventEntry | undef
phase: event.compactionPhase,
success: event.compactionSuccess,
};
case 'workflow-diagnostic': {
const label = formatDiagnosticLabel(event.diagnosticKind, event.diagnosticSeverity);
const detailParts: string[] = [];
if (event.executorId) detailParts.push(event.executorId);
if (event.subworkflowId) detailParts.push(event.subworkflowId);
if (event.exceptionType) detailParts.push(event.exceptionType);
if (event.diagnosticMessage) detailParts.push(event.diagnosticMessage);
return {
kind: event.kind,
occurredAt: event.occurredAt,
label,
detail: detailParts.length > 0 ? detailParts.join(' · ') : undefined,
success: event.diagnosticSeverity === 'error' ? false : undefined,
};
}
default:
return undefined;
}