diff --git a/src/renderer/components/ActivityPanel.tsx b/src/renderer/components/ActivityPanel.tsx
index 771a659..dad54f5 100644
--- a/src/renderer/components/ActivityPanel.tsx
+++ b/src/renderer/components/ActivityPanel.tsx
@@ -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 ;
case 'session-compaction':
return ;
+ case 'workflow-diagnostic':
+ return ;
default:
return ;
}
diff --git a/src/renderer/lib/sessionActivity.ts b/src/renderer/lib/sessionActivity.ts
index 6a3f489..8111a74 100644
--- a/src/renderer/lib/sessionActivity.ts
+++ b/src/renderer/lib/sessionActivity.ts
@@ -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 = {
+ '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;
}
diff --git a/tests/renderer/sessionActivity.test.ts b/tests/renderer/sessionActivity.test.ts
index 00aaef5..3be8a6b 100644
--- a/tests/renderer/sessionActivity.test.ts
+++ b/tests/renderer/sessionActivity.test.ts
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test';
import {
applySessionEventActivity,
applyAssistantUsageEvent,
+ applyTurnEventLog,
buildAgentActivityRows,
formatAgentActivityLabel,
formatDuration,
@@ -527,3 +528,79 @@ describe('usage formatting helpers', () => {
expect(formatDuration(150_000)).toBe('2.5m');
});
});
+
+describe('workflow diagnostic turn events', () => {
+ function makeDiagnosticEvent(overrides: Partial = {}): SessionEventRecord {
+ return {
+ sessionId: 'session-1',
+ kind: 'workflow-diagnostic',
+ occurredAt: '2026-03-23T00:00:00.000Z',
+ diagnosticSeverity: 'error',
+ diagnosticKind: 'executor-failed',
+ diagnosticMessage: 'Tool crashed.',
+ ...overrides,
+ };
+ }
+
+ test('formats executor-failed with full metadata', () => {
+ const result = applyTurnEventLog({}, makeDiagnosticEvent({
+ executorId: 'Primary',
+ exceptionType: 'InvalidOperationException',
+ }));
+ const entries = result['session-1']!;
+ expect(entries).toHaveLength(1);
+ expect(entries[0].label).toBe('Executor failed');
+ expect(entries[0].detail).toBe('Primary · InvalidOperationException · Tool crashed.');
+ expect(entries[0].success).toBe(false);
+ });
+
+ test('formats workflow-warning with message only', () => {
+ const result = applyTurnEventLog({}, makeDiagnosticEvent({
+ diagnosticSeverity: 'warning',
+ diagnosticKind: 'workflow-warning',
+ diagnosticMessage: 'Token budget is nearly exhausted.',
+ executorId: undefined,
+ exceptionType: undefined,
+ }));
+ const entries = result['session-1']!;
+ expect(entries[0].label).toBe('Workflow warning');
+ expect(entries[0].detail).toBe('Token budget is nearly exhausted.');
+ expect(entries[0].success).toBeUndefined();
+ });
+
+ test('formats subworkflow-error with subworkflow ID', () => {
+ const result = applyTurnEventLog({}, makeDiagnosticEvent({
+ diagnosticKind: 'subworkflow-error',
+ subworkflowId: 'subworkflow-review',
+ exceptionType: 'InvalidOperationException',
+ diagnosticMessage: 'Reviewer agent failed.',
+ }));
+ const entries = result['session-1']!;
+ expect(entries[0].label).toBe('Subworkflow error');
+ expect(entries[0].detail).toBe('subworkflow-review · InvalidOperationException · Reviewer agent failed.');
+ });
+
+ test('formats workflow-error without optional fields', () => {
+ const result = applyTurnEventLog({}, makeDiagnosticEvent({
+ diagnosticKind: 'workflow-error',
+ diagnosticMessage: 'Workflow terminated unexpectedly.',
+ executorId: undefined,
+ subworkflowId: undefined,
+ exceptionType: undefined,
+ }));
+ const entries = result['session-1']!;
+ expect(entries[0].label).toBe('Workflow error');
+ expect(entries[0].detail).toBe('Workflow terminated unexpectedly.');
+ expect(entries[0].success).toBe(false);
+ });
+
+ test('falls back to severity when diagnosticKind is missing', () => {
+ const result = applyTurnEventLog({}, makeDiagnosticEvent({
+ diagnosticKind: undefined,
+ diagnosticSeverity: 'warning',
+ diagnosticMessage: 'Something odd happened.',
+ }));
+ const entries = result['session-1']!;
+ expect(entries[0].label).toBe('Workflow warning');
+ });
+});