Skip to content

Angular Agent Frontend Reference

Angular Agent Frontend Reference

This document is the authoritative reference for the Angular admin UI that surfaces the Cloudflare Agents SDK integration. It covers architecture, component catalog, service API, WebSocket lifecycle, signal patterns, route table, testing guide, and contributor notes.

Related docs:


Architecture Overview

sequenceDiagram
    participant Browser as 🌐 Angular Browser
    participant Guard as 🔐 adminGuard
    participant Component as 🖥️ AgentsDashboardComponent
    participant Service as ⚙️ AgentRpcService
    participant HTTP as 🔌 HttpClient (Auth Interceptor)
    participant Worker as ☁️ Cloudflare Worker
    participant DO as 🤖 Durable Object (MCP_AGENT)

    Browser->>Guard: navigate to /admin/agents
    Guard->>Guard: isAdmin() check
    Guard-->>Browser: allow / redirect /sign-in

    Browser->>Component: afterNextRender()
    Component->>Service: listSessions()
    Service->>HTTP: GET /admin/agents/sessions
    HTTP->>Worker: Authorization: Bearer <token>
    Worker-->>HTTP: AgentSessionsResponse
    HTTP-->>Service: Observable<AgentSessionsResponse>
    Service-->>Component: sessions[]
    Note over Component: derives agents[] from sessions[]

    Note over Browser,DO: WebSocket Connection (AgentSessionConsoleComponent)
    Browser->>Service: connect('mcp-agent', 'default', token)
    Service->>DO: new WebSocket(wss://.../agents/mcp-agent/default, ['bearer.<token>'])
    DO-->>Service: onopen → status = 'connected'
    Service-->>Browser: AgentConnection { status, messages, send, disconnect }
    DO-->>Service: onmessage → messages.update([...prev, msg])
    Browser->>Service: conn.send('{"type":"ping"}')
    Service->>DO: WebSocket.send(message)

Component Catalog

AgentsDashboardComponent

  • Route: /admin/agents
  • File: frontend/src/app/admin/agents/agents-dashboard.component.ts
  • Purpose: Main agent management panel showing the agent registry and active sessions.

Signals:

SignalTypeDescription
loadingsignal<boolean>True while data is loading
errorsignal<string|null>Non-null on API error; shown in dismissible banner
agentssignal<AgentListItem[]>Registered agents from KNOWN_AGENTS seed + session counts
sessionssignal<AgentSession[]>All sessions from the API
terminatingIdsignal<string|null>ID of session currently being terminated
activeSessionscomputed<AgentSession[]>Derived: sessions with ended_at === null

Methods:

MethodDescription
loadData()Fetches agents and sessions in parallel; updates all state signals
refresh()Calls loadData() — bound to Refresh button
terminateSession(session)Calls DELETE endpoint, optimistically updates the local sessions signal (marks ended_at), then shows a snackbar — no re-fetch required

AgentSessionConsoleComponent

  • Route: /admin/agents/:slug/:instanceId
  • File: frontend/src/app/admin/agents/agent-session-console.component.ts
  • Purpose: Live WebSocket terminal for a single agent DO instance.

Signals:

SignalTypeDescription
slugSignal<string>Agent slug from :slug route param
instanceIdSignal<string>DO instance from :instanceId route param
connectionsignal<AgentConnection|null>Active WebSocket handle; null before first render
connectionDurationSignal<string>Live Xs / Xm Xs / Xh Xm Xs counter via interval(1000)
statusLabelcomputed<string>Human-readable status (“Connecting…”, “Connected”, …)

Methods:

MethodDescription
openConnection()Fetches token + calls AgentRpcService.connect(). Called by afterNextRender().
reconnect()Closes current connection and calls openConnection() again
disconnectManually()Calls connection.disconnect()
sendMessage()Sends messageInput over the WebSocket and clears the input

CDK Virtual Scroll: The message log uses <cdk-virtual-scroll-viewport itemSize="64"> to keep DOM element count constant at O(visible rows) regardless of total message count.


AgentAuditLogComponent

  • Route: /admin/agents/audit
  • File: frontend/src/app/admin/agents/agent-audit-log.component.ts
  • Purpose: Paginated viewer for agent audit log events.

Signals:

SignalTypeDescription
loadingsignal<boolean>True while loading
errorsignal<string|null>API error message
entriessignal<AgentAuditLogEntry[]>Current page of entries
totalCountsignal<number>Total rows for paginator
pageIndexsignal<number>Current 0-based page
activeFiltersignal<string|null>Active event-type filter chip; null = all
filteredEntriescomputed<AgentAuditLogEntry[]>Client-side filtered entries

AgentRpcService API

File: frontend/src/app/services/agent-rpc.service.ts

HTTP Methods

MethodEndpointReturns
listAgents(page?)Derived from listSessions + KNOWN_AGENTSObservable<AgentListItem[]>
listSessions(page?, limit?)GET /admin/agents/sessionsObservable<AgentSessionsResponse>
getSession(id)GET /admin/agents/sessions/:idObservable<AgentSessionDetailResponse>
terminateSession(id)DELETE /admin/agents/sessions/:idObservable<{success, error?}>
listAuditLog(page?, limit?)GET /admin/agents/auditObservable<AgentAuditResponse>

WebSocket: connect(slug, instanceId?, token?, destroyRef?)

Returns an AgentConnection handle:

interface AgentConnection {
readonly status: Signal<AgentConnectionStatus>; // 'connecting' | 'connected' | 'disconnected' | 'error'
readonly messages: Signal<readonly AgentMessage[]>;
send(message: string): void;
disconnect(): void;
}

WebSocket Connection Lifecycle

stateDiagram-v2
    [*] --> connecting: connect() called
    connecting --> connected: socket.onopen
    connected --> disconnected: disconnect() called (code 1000)
    connected --> connecting: unexpected close (code != 1000), attempts < 5
    connecting --> error: attempts >= MAX_RECONNECT_ATTEMPTS
    connected --> error: socket.onerror
    error --> connecting: reconnect() called manually
    disconnected --> connecting: reconnect() called manually

Auth Token Attachment

WebSocket connections cannot use the Authorization HTTP header — the browser does not expose this during the HTTP→WebSocket upgrade handshake. The Cloudflare Agents SDK uses the Sec-WebSocket-Protocol header as a workaround:

// In AgentRpcService.connect():
const protocols: string[] = token ? [`bearer.${token}`] : [];
ws = new WebSocket(url, protocols);
// Server-side: Agents SDK reads the sub-protocol list and validates the bearer token.

Reference: https://developers.cloudflare.com/agents/configuration/authentication/

Reconnect Policy

AttemptDelay
11 s
22 s
34 s
48 s
516 s
>5Status → error, manual reconnect required

Admin Route Table

PathComponentGuardTitle
/admin/agentsAgentsDashboardComponentadminGuardAgent Management
/admin/agents/:slug/:instanceIdAgentSessionConsoleComponentadminGuardAgent Console
/admin/agents/auditAgentAuditLogComponentadminGuardAgent Audit Log

Important: The agents/audit route is registered before agents/:slug/:instanceId in admin.routes.ts to prevent the wildcard param from swallowing the literal audit segment.


Signal State Management Patterns

All components follow the same signal pattern used throughout the admin shell:

// State
readonly loading = signal(true);
readonly error = signal<string | null>(null);
readonly items = signal<Item[]>([]);
// Derived
readonly activeItems = computed(() => this.items().filter(i => i.active));
// Init
private readonly _init = afterNextRender(() => this.loadData());
// Update
loadData(): void {
this.loading.set(true);
this.service.fetchItems().pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
next: (res) => { this.items.set(res.items); this.loading.set(false); },
error: (err) => { this.error.set(err.error); this.loading.set(false); },
});
}

Testing Guide

Running agent tests

Terminal window
cd frontend
npm run test -- --reporter=verbose --testPathPattern=agent

Running all frontend tests

Terminal window
cd frontend
npm run test

Test files

FileWhat it tests
services/agent-rpc.service.spec.tsHTTP methods (listSessions, terminateSession, listAuditLog), WebSocket lifecycle (connect, send, disconnect, auth token)
admin/agents/agents-dashboard.component.spec.tsLoading state, data population, error state, empty state, terminate action

Mocking pattern

// Mock AgentRpcService with signal-based stubs
const mock = {
listSessions: vi.fn(() => of(SESSIONS_RESPONSE)),
terminateSession: vi.fn(() => of({ success: true })),
};
{ provide: AgentRpcService, useValue: mock }

Adding a New Agent (Contributor Notes)

The UI is designed to mirror the backend “one registry entry = full integration” model:

  1. Backend: Add entry to AGENT_REGISTRY in worker/agents/registry.ts + wrangler.toml binding.
  2. Frontend: Add entry to KNOWN_AGENTS in frontend/src/app/models/agent.models.ts.
  3. No component changes needed — the dashboard automatically renders a card for each entry.

If the backend later exposes a /admin/agents/registry endpoint, replace the KNOWN_AGENTS seed in AgentRpcService.listAgents() with a direct HTTP call.


UX Security Considerations

  • Admin-only gating: All agent routes are protected by adminGuard (checks AuthFacadeService.isAdmin()). A non-admin user navigating to /admin/agents is redirected to /sign-in.
  • No token in localStorage: Auth tokens are managed by Clerk/Better-Auth SDK. AgentRpcService.connect() retrieves the token via AuthFacadeService.getToken() at connection time and passes it as a WebSocket sub-protocol — never stored in localStorage.
  • WebSocket sub-protocol auth: The Sec-WebSocket-Protocol: bearer.<token> pattern is the only standards-compliant way to pass credentials during a WebSocket upgrade. This matches the Cloudflare Agents SDK expectation.
  • Auto-cleanup on navigate: DestroyRef.onDestroy() is used to close the WebSocket when the user navigates away from the console view, preventing orphaned Durable Object connections.