How Pi-Web Handles SSE Reconnection After Page Refresh During Streaming

When a user refreshes the browser during an active agent stream, Pi-Web automatically restores the Server-Sent Events (SSE) connection by fetching the current session state from GET /api/agent/[id] and reconnecting to the events endpoint if isStreaming is true, ensuring the UI resumes receiving the generation exactly where it left off.

The agegr/pi-web repository implements a resilient streaming architecture that survives browser refreshes without interrupting ongoing AI agent generations. This functionality relies on a coordinated sequence between the client-side React components and the Next.js API routes that maintain persistent session state.

Detecting Active Streams on Component Mount

When the ChatWindow component mounts—or remounts after a page refresh—it immediately queries the server to determine if a generation is still in progress. The component calls GET /api/agent/[id] to retrieve the current session state, which includes the critical boolean flag state.isStreaming.

If this flag returns true, it indicates that an agent is actively generating a response on the server side, even though the client connection was severed by the refresh. The component then passes this signal to the useAgentSession hook to initiate reconnection.

// components/ChatWindow.tsx
useEffect(() => {
  // On mount – fetch the latest session state
  fetch(`/api/agent/${sessionId}`)
    .then(r => r.json())
    .then(state => {
      // If a generation was in progress, reconnect the SSE stream
      if (state.isStreaming) {
        startSse(sessionId);               // opens /api/agent/[id]/events
        setThinkingLevel(state.thinkingLevel);
        setIsCompacting(state.isCompacting);
      }
    });
}, [sessionId]);

Restoring the SSE Connection via useAgentSession

The useAgentSession hook contains the lifecycle management for SSE connections. When instructed to reconnect, it opens a new EventSource connection to GET /api/agent/[id]/events, which streams agent_message, agent_end, and compaction events from the server.

This endpoint, defined in app/api/agent/[id]/events/route.ts, maintains the live stream of tokens from the running agent process. By reconnecting to this endpoint, the client receives the remainder of the generation that occurred during the disconnection window.

// hooks/useAgentSession.ts
function startSse(id: string) {
  const evtSource = new EventSource(`/api/agent/${id}/events`);
  evtSource.onmessage = e => handleAgentEvent(JSON.parse(e.data));
  // …cleanup logic omitted…
}

Synchronizing Auxiliary Session State

Beyond the binary streaming status, the initial state fetch synchronizes additional context required to render the UI correctly. The response from app/api/agent/[id]/route.ts includes:

  • thinkingLevel: Indicates the depth of the model's chain-of-thought reasoning, allowing the UI to display appropriate loading indicators.
  • isCompacting: Signals whether a context compaction operation is currently underway, which triggers specific UI cues in the chat window.

These values are applied locally via setThinkingLevel and setIsCompacting immediately upon reconnection, ensuring the user sees the correct state of the ongoing thought process.

Resilience Against Missed Events

While the SSE stream provides real-time updates, useAgentSession implements additional safeguards to handle events that might be lost during the reconnection window. The hook runs a periodic reconciliation loop via supplementary GET /api/agent/[id] calls and listens for browser visibilitychange and online events.

This dual approach ensures that if the SSE connection drops while the tab is backgrounded or during network instability, the client can recover any missed events through the state endpoint, preventing the UI from desynchronizing with the server-side agent state.

Summary

  • State detection: ChatWindow.tsx calls GET /api/agent/[id] on mount to check isStreaming status after refresh.
  • Automatic reconnection: When isStreaming is true, useAgentSession.ts reconnects to GET /api/agent/[id]/events to resume the live stream.
  • Context preservation: Auxiliary state including thinkingLevel and isCompacting is synchronized to maintain UI consistency.
  • Event resilience: Periodic polling and browser event listeners ensure no messages are lost during brief disconnections.

Frequently Asked Questions

How does Pi-Web detect if a stream is still active after a page refresh?

The ChatWindow component calls GET /api/agent/[id] immediately upon mounting, which returns the current isStreaming flag from the server-side session state. If this boolean is true, the application knows an agent generation is still running and triggers reconnection logic.

What server-side endpoint maintains the streaming state during a browser refresh?

The endpoint app/api/agent/[id]/route.ts persists and returns the session state including isStreaming, thinkingLevel, and isCompacting. The companion endpoint app/api/agent/[id]/events/route.ts handles the actual SSE stream of agent events.

Does Pi-Web handle events that might be missed during the reconnection window?

Yes, useAgentSession.ts implements a periodic reconciliation mechanism that polls the state endpoint and listens for browser visibility and online events. This ensures any events missed during brief disconnections or while the tab is backgrounded are recovered and applied to the UI.

Which component triggers the SSE reconnection logic?

The ChatWindow component in components/ChatWindow.tsx detects the active stream condition and invokes the reconnection function from useAgentSession.ts. This separation of concerns allows the hook to manage the EventSource lifecycle while the component handles the mount-time state verification.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →