From 629e29245502d7625ad8630ca98d7772010a310f Mon Sep 17 00:00:00 2001 From: David Hagerty Date: Wed, 11 Feb 2026 19:25:03 +0000 Subject: [PATCH] feat(web): add AgentMonitor view with real-time event feed Implements Agent Monitor view showing real-time feed of WebSocket events: - Active agents bar at top with status badges - Event feed showing agent_spawned, agent_progress, agent_completed, tool_execution - Auto-scroll to newest events with pause/resume toggle - Clear feed button - Relative time formatting (2s ago, 1m ago) updating every 10s - Event type-specific styling and formatting - Max 200 event items in feed ring buffer Adds formatRelativeTime utility to date-formatting.ts Updates App.svelte to import and render AgentMonitor for agent-monitor route Co-Authored-By: Claude Opus 4.6 --- web/src/App.svelte | 9 ++++++--- web/src/lib/date-formatting.ts | 32 ++++++++++++++++++++++++++++++++ web/src/views/AgentMonitor.svelte | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 file(s) changed, 329 insertion(s)(+), 3 deletion(s)(-) diff --git a/web/src/App.svelte b/web/src/App.svelte --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -16,6 +16,9 @@ import ProjectDetail from './views/ProjectDetail.svelte'; import TaskTree from './views/TaskTree.svelte'; import DecisionGraph from './views/DecisionGraph.svelte'; + import AgentMonitor from './views/AgentMonitor.svelte'; + import SessionHistory from './views/SessionHistory.svelte'; + import GraphSearch from './views/GraphSearch.svelte'; import Placeholder from './views/Placeholder.svelte'; let wsConnection: ReturnType | null = null; @@ -63,11 +66,11 @@ {:else if currentRoute.name === 'decision-graph'} {:else if currentRoute.name === 'agent-monitor'} - + {:else if currentRoute.name === 'session-history'} - + {:else if currentRoute.name === 'graph-search'} - + {:else} {/if} diff --git a/web/src/lib/date-formatting.ts b/web/src/lib/date-formatting.ts --- a/web/src/lib/date-formatting.ts +++ b/web/src/lib/date-formatting.ts @@ -19,3 +19,35 @@ return dateStr; } } + +/** + * Format a date string into relative time format (e.g., "2s ago", "1m ago"). + * @param dateStr - ISO date string + * @returns Relative time string or "unknown" if parsing fails + */ +export function formatRelativeTime(dateStr: string): string { + try { + const date = new Date(dateStr); + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + if (seconds < 60) { + return `${seconds}s ago`; + } + + const minutes = Math.floor(seconds / 60); + if (minutes < 60) { + return `${minutes}m ago`; + } + + const hours = Math.floor(minutes / 60); + if (hours < 24) { + return `${hours}h ago`; + } + + const days = Math.floor(hours / 24); + return `${days}d ago`; + } catch { + return 'unknown'; + } +} diff --git a/web/src/views/AgentMonitor.svelte b/web/src/views/AgentMonitor.svelte new file mode 100644 --- /dev/null +++ b/web/src/views/AgentMonitor.svelte @@ -0,0 +1,291 @@ + + +
+
+

Agent Monitor

+
+ +
+ {#if agentsState.loading} + + {:else if goalId} +
+
+ {agentsState.activeAgents.length} + {agentsState.activeAgents.length === 1 ? 'agent' : 'agents'} running +
+
+ {#each agentsState.activeAgents as agent (agent.agent_id)} + + {/each} +
+
+ +
+ + +
+ +
+ {#if agentsState.eventFeed.length === 0} +
+

No agent activity yet. Events will appear here as agents run.

+
+ {/if} + {#each agentsState.eventFeed as event (event.type + Math.random())} +
+ {getEventTime(event)} + {formatEventMessage(event)} +
+ {/each} +
+ {:else} +
+

No goal ID provided. Navigate to a goal first.

+
+ {/if} +
+
+ + -- tangled.sh