From 71ef2ac6f6ce69503b6beec142950176993ec6c1 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 4 Apr 2026 01:45:45 -0700 Subject: [PATCH] feat(admin-web): add agent runtime console --- .../src/__tests__/agent-runtime.test.ts | 107 ++++++ .../app/(dashboard)/agent-runtime/page.tsx | 332 ++++++++++++++++++ .../src/app/api/agent-runtime/route.ts | 61 ++++ .../admin-web/src/components/sidebar-nav.tsx | 2 + .../ECOSYSTEM_IMPLEMENTATION_TRACKER.md | 12 + ...PHASE4_PERSONAL_TIMELINE_EXECUTION_PLAN.md | 12 +- ...5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md | 11 +- 7 files changed, 533 insertions(+), 4 deletions(-) create mode 100644 dashboards/admin-web/src/__tests__/agent-runtime.test.ts create mode 100644 dashboards/admin-web/src/app/(dashboard)/agent-runtime/page.tsx create mode 100644 dashboards/admin-web/src/app/api/agent-runtime/route.ts diff --git a/dashboards/admin-web/src/__tests__/agent-runtime.test.ts b/dashboards/admin-web/src/__tests__/agent-runtime.test.ts new file mode 100644 index 00000000..8c1494b6 --- /dev/null +++ b/dashboards/admin-web/src/__tests__/agent-runtime.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockGetCurrentUser = vi.fn(); +vi.mock('@/lib/auth-server', () => ({ + getCurrentUser: (...args: unknown[]) => mockGetCurrentUser(...args), + getCurrentUserFromRequest: (...args: unknown[]) => mockGetCurrentUser(...args), +})); + +const mockLogError = vi.fn(); +vi.mock('@/lib/logger', () => ({ + logError: (...args: unknown[]) => mockLogError(...args), +})); + +import { GET, POST } from '@/app/api/agent-runtime/route'; + +async function callGet(qs = '') { + const { NextRequest } = await import('next/server'); + return GET( + new NextRequest( + new Request(`http://localhost:3001/api/agent-runtime${qs}`, { + headers: { Authorization: 'Bearer test', 'x-product-id': 'lysnrai' }, + }) + ) + ); +} + +async function callPost(payload: unknown) { + const { NextRequest } = await import('next/server'); + return POST( + new NextRequest( + new Request('http://localhost:3001/api/agent-runtime', { + method: 'POST', + headers: { Authorization: 'Bearer test', 'x-product-id': 'lysnrai' }, + body: JSON.stringify(payload), + }) + ) + ); +} + +describe('agent runtime proxy route', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns 401 for unauthenticated callers', async () => { + mockGetCurrentUser.mockResolvedValue(null); + const res = await callGet(); + expect(res.status).toBe(401); + }); + + it('proxies GET session requests to platform-service', async () => { + mockGetCurrentUser.mockResolvedValue({ id: 'usr_admin', role: 'admin' }); + const fetchMock = vi.fn().mockResolvedValue({ + status: 200, + json: async () => ({ sessions: [{ sessionId: 'sess_1' }], count: 1 }), + }); + vi.stubGlobal('fetch', fetchMock); + + const res = await callGet('?section=sessions&userId=user_1'); + + expect(res.status).toBe(200); + expect(fetchMock).toHaveBeenCalledWith( + 'http://localhost:4003/api/agent-runtime/sessions?userId=user_1', + expect.objectContaining({ + method: 'GET', + headers: expect.objectContaining({ + 'x-user-id': 'usr_admin', + 'x-product-id': 'lysnrai', + }), + }) + ); + }); + + it('proxies POST dispatch validation requests to platform-service', async () => { + mockGetCurrentUser.mockResolvedValue({ id: 'usr_admin', role: 'admin' }); + const fetchMock = vi.fn().mockResolvedValue({ + status: 200, + json: async () => ({ valid: true }), + }); + vi.stubGlobal('fetch', fetchMock); + + const res = await callPost({ + dispatchId: 'dispatch_1', + targetProductId: 'lysnrai', + targetExecutor: 'generic-agent', + userId: 'user_1', + title: 'Test', + intent: 'Validate payload', + artifactRefs: [], + memoryRefs: [], + dispatchContext: { + originSurface: 'web', + originProductId: 'lysnrai', + dispatchMode: 'interactive', + initiatedAt: '2026-04-04T08:00:00.000Z', + }, + }); + + expect(res.status).toBe(200); + expect(fetchMock).toHaveBeenCalledWith( + 'http://localhost:4003/api/agent-runtime/dispatch/validate', + expect.objectContaining({ + method: 'POST', + }) + ); + }); +}); diff --git a/dashboards/admin-web/src/app/(dashboard)/agent-runtime/page.tsx b/dashboards/admin-web/src/app/(dashboard)/agent-runtime/page.tsx new file mode 100644 index 00000000..61c6f1eb --- /dev/null +++ b/dashboards/admin-web/src/app/(dashboard)/agent-runtime/page.tsx @@ -0,0 +1,332 @@ +'use client'; + +import { useCallback, useEffect, useState } from 'react'; +import { BadgeCheck, PlayCircle, RefreshCw, Send, TimerReset } from 'lucide-react'; +import { createProxyFetch } from '@/lib/proxy-fetch'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Textarea } from '@/components/ui/textarea'; + +type AgentSession = { + sessionId: string; + productId: string; + userId: string; + status: string; + startedAt: string; + updatedAt: string; + resumable: boolean; + dispatchContext?: { + originSurface: string; + dispatchMode: string; + } | null; +}; + +type AgentRun = { + runId: string; + sessionId: string; + productId: string; + status: string; + startedAt: string; + completedAt?: string | null; + correlationId?: string | null; +}; + +const apiFetch = createProxyFetch('/api/agent-runtime'); + +function formatDate(iso: string | null | undefined) { + if (!iso) return 'โ€”'; + return new Date(iso).toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} + +export default function AgentRuntimePage() { + const [sessions, setSessions] = useState([]); + const [runs, setRuns] = useState([]); + const [loading, setLoading] = useState(true); + const [userId, setUserId] = useState(''); + const [limit, setLimit] = useState('20'); + const [dispatchPayload, setDispatchPayload] = useState( + JSON.stringify( + { + dispatchId: 'dispatch_example', + targetProductId: 'lysnrai', + targetExecutor: 'generic-agent', + userId: 'user_1', + title: 'Summarize the latest transcript', + intent: 'Prepare a note and memory candidate.', + artifactRefs: ['art_transcript_1'], + memoryRefs: [], + dispatchContext: { + originSurface: 'web', + originProductId: 'lysnrai', + dispatchMode: 'interactive', + initiatedAt: '2026-04-04T08:00:00.000Z', + }, + }, + null, + 2 + ) + ); + const [dispatchResult, setDispatchResult] = useState(''); + + const loadData = useCallback(async () => { + setLoading(true); + const sessionParams = new URLSearchParams({ section: 'sessions' }); + if (userId.trim()) sessionParams.set('userId', userId.trim()); + + const runParams = new URLSearchParams({ section: 'runs', limit }); + + const [sessionData, runData] = await Promise.all([ + apiFetch(`?${sessionParams.toString()}`), + apiFetch(`?${runParams.toString()}`), + ]); + + setSessions(Array.isArray(sessionData?.sessions) ? sessionData.sessions : []); + setRuns(Array.isArray(runData?.runs) ? runData.runs : []); + setLoading(false); + }, [limit, userId]); + + useEffect(() => { + void loadData(); + }, [loadData]); + + async function validateDispatch() { + try { + const parsed = JSON.parse(dispatchPayload); + const result = await apiFetch('', { + method: 'POST', + body: JSON.stringify(parsed), + }); + setDispatchResult(JSON.stringify(result, null, 2)); + } catch (error) { + setDispatchResult( + JSON.stringify( + { + error: error instanceof Error ? error.message : 'Invalid JSON payload', + }, + null, + 2 + ) + ); + } + } + + return ( +
+
+
+

Agent Runtime

+

+ Inspect shared runtime projections and validate dispatch payloads +

+
+ +
+ +
+ + + Sessions + + +
{sessions.length}
+
+
+ + + Runs + + +
{runs.length}
+
+
+ + + Active Runs + + +
+ {runs.filter(run => run.status === 'running').length} +
+
+
+ + + + Resumable Sessions + + + +
+ {sessions.filter(session => session.resumable).length} +
+
+
+
+ +
+ setUserId(event.target.value)} + /> + setLimit(event.target.value)} + /> +
+ + + + Sessions + Runs + Dispatch Validation + + + + + + {loading ? ( +
Loading sessions...
+ ) : sessions.length === 0 ? ( +
No runtime sessions found.
+ ) : ( + sessions.map(session => ( +
+
+
{session.sessionId}
+ {session.status} + {session.productId} + {session.resumable && ( + resumable + )} +
+
+
+
+ User +
+
{session.userId}
+
+
+
+ Started +
+
{formatDate(session.startedAt)}
+
+
+
+ Updated +
+
{formatDate(session.updatedAt)}
+
+
+
+ Dispatch +
+
+ {session.dispatchContext + ? `${session.dispatchContext.originSurface} ยท ${session.dispatchContext.dispatchMode}` + : 'โ€”'} +
+
+
+
+ )) + )} +
+
+
+ + + + + {loading ? ( +
Loading runs...
+ ) : runs.length === 0 ? ( +
No runtime runs found.
+ ) : ( + runs.map(run => ( +
+
+
{run.runId}
+ {run.status} + {run.productId} +
+
+
+
+ Session +
+
{run.sessionId}
+
+
+
+ Started +
+
{formatDate(run.startedAt)}
+
+
+
+ Completed +
+
{formatDate(run.completedAt)}
+
+
+
+ Correlation +
+
{run.correlationId || 'โ€”'}
+
+
+
+ )) + )} +
+
+
+ + + + + + + Dispatch Validation + + + +