-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Labels
Description
Parent Issue
Part of #500 — depends on #501 and #502 (backend must be running)
Context
The web-ui currently has useEventSource (SSE, one-way) and useExecutionMonitor (aggregates SSE events into UI state). For interactive sessions, we need a bidirectional WebSocket hook that connects to /ws/sessions/{id}/chat, sends user messages, and accumulates streamed token events into structured chat messages.
Existing Code to Build On
web-ui/src/hooks/useEventSource.ts— reconnect pattern to adapt for WSweb-ui/src/hooks/useExecutionMonitor.ts— state accumulation pattern to follow- Reference: Optio
apps/web/src/hooks/use-logs.ts+use-websocket.ts
What to Build
New file: web-ui/src/hooks/useAgentChat.ts
type MessageRole =
| "user"
| "assistant"
| "tool_use"
| "tool_result"
| "thinking"
| "system"
| "error";
interface ChatMessage {
id: string; // client-generated UUID per turn
role: MessageRole;
content: string;
toolName?: string; // for tool_use / tool_result
toolInput?: unknown; // for tool_use
createdAt: string;
}
interface AgentChatState {
messages: ChatMessage[];
status: "idle" | "connecting" | "thinking" | "streaming" | "error" | "disconnected";
costUsd: number;
inputTokens: number;
outputTokens: number;
error: string | null;
connected: boolean;
}
interface UseAgentChat {
state: AgentChatState;
sendMessage: (content: string) => void;
interrupt: () => void;
clearMessages: () => void;
}
export function useAgentChat(sessionId: string | null): UseAgentChatBehavior
- On
sessionIdchange: openWebSocketto/ws/sessions/{sessionId}/chat?token=<JWT> - Send
{ type: "ping" }every 30s to keep alive - On
text_deltaevents: append content to the current in-progressassistantmessage - On
tool_use_start: push a newtool_usemessage - On
tool_result: push atool_resultmessage - On
thinking: push athinkingmessage - On
cost_update: updatecostUsd,inputTokens,outputTokensin state - On
done: finalize the in-progress assistant message, set status toidle - On
error: seterrorfield, set status toerror - On WS close: set
connected = false, status todisconnected, auto-reconnect with exponential backoff (max 5 attempts) sendMessage(content): send{ type: "message", content }over WS, push optimistic user message, set status tothinkinginterrupt(): send{ type: "interrupt" }over WS- Use
requestAnimationFramebatching fortext_deltaupdates to avoid render thrash
Auth
Get the JWT from the existing auth context/local storage — look at how other hooks pass credentials.
Acceptance Criteria
- Hook connects to WebSocket on mount, disconnects on unmount
- Streaming text deltas update the in-progress message character-by-character
- Tool use and tool result messages appear inline between assistant turns
- Thinking messages render separately from text content
-
sendMessagepushes optimistic user message immediately, then sends over WS -
interrupt()sends interrupt message - Reconnects automatically on disconnect (up to 5 attempts, exponential backoff)
-
statustransitions correctly:idle→thinking→streaming→idle - No memory leaks: cleanup on unmount
Out of Scope
- The visual chat panel (tracked in the next sub-issue)
- Terminal (separate sub-issue)
Reactions are currently unavailable