Files
flynn/docs/plans/2026-02-05-phase2-websocket-gateway.md
T
William Valentin f30a8bc318 feat(gateway): add WebSocket gateway with JSON-RPC protocol and auth
Phase 2 of the Flynn roadmap. Adds a WebSocket gateway server that
starts alongside the Telegram bot, providing real-time API access to
the agent, sessions, and tools.

Protocol: JSON-RPC-like (request/response/event) over WebSocket.
8 methods: agent.send, agent.cancel, sessions.list, sessions.history,
sessions.create, tools.list, tools.invoke, system.health.

Auth: Bearer token + Tailscale identity header support.
Session bridge: per-connection agent instances with shared model router.

New files: src/gateway/ (protocol, router, server, auth, session-bridge,
handlers for agent/sessions/tools/system).
57 new tests (181 total), typecheck clean.
2026-02-05 19:11:25 -08:00

6.0 KiB

Phase 2: WebSocket Gateway — Implementation Plan

Goal

Add a WebSocket gateway to the Flynn daemon that allows real-time communication from web clients (and eventually any WS client). The gateway wraps existing daemon components (session manager, agent, tool registry) — no refactoring of existing code.

Approach

Incremental, additive. Existing Telegram bot and TUI continue working unchanged. The gateway is a new module that starts alongside them.

Architecture

src/gateway/
├── protocol.ts         # JSON-RPC message types + event types
├── server.ts           # WebSocket server (ws library), connection lifecycle
├── router.ts           # Method routing → handler dispatch
├── auth.ts             # Token auth + Tailscale identity headers
├── session-bridge.ts   # Maps WS client connections to agent sessions
└── handlers/
    ├── agent.ts        # agent.send (streaming), agent.cancel, agent.status
    ├── sessions.ts     # sessions.list, sessions.history, sessions.create
    ├── tools.ts        # tools.list, tools.invoke
    └── system.ts       # system.health, system.info

Protocol

JSON-RPC-like over WebSocket (not full JSON-RPC 2.0 — simpler):

// Client → Server
interface GatewayRequest {
  id: number;         // Client-assigned request ID
  method: string;     // e.g. "agent.send"
  params: object;     // Method-specific parameters
}

// Server → Client (success)
interface GatewayResponse {
  id: number;         // Matches request ID
  result: object;     // Method-specific result
}

// Server → Client (error)
interface GatewayError {
  id: number;         // Matches request ID
  error: {
    code: number;     // Error code (negative = protocol, positive = app)
    message: string;  // Human-readable description
  };
}

// Server → Client (streaming event, multiple per request)
interface GatewayEvent {
  id: number;         // Matches originating request ID
  event: string;      // Event type name
  data: object;       // Event-specific payload
}

Event Types (for agent.send streaming)

Event Data When
content { text: string } Text chunk from model
tool_start { tool: string, args: object } Tool execution beginning
tool_end { tool: string, result: { success, output, error? } } Tool execution complete
done { content: string, usage: { inputTokens, outputTokens } } Final response
error { code: number, message: string } Error during processing

Error Codes

Code Meaning
-1 Parse error (invalid JSON)
-2 Invalid request (missing id/method)
-3 Method not found
-4 Authentication required
-5 Authentication failed
1 Session not found
2 Tool not found
3 Agent busy (already processing)
4 Request cancelled

Methods

agent.send

Send a message to the agent and receive streaming response.

Params: { message: string, sessionId?: string }
Events: content, tool_start, tool_end, done, error
Response: (none — final state sent as "done" event)

agent.cancel

Cancel the currently running agent request.

Params: { sessionId?: string }
Response: { cancelled: boolean }

sessions.list

List all active sessions.

Params: {}
Response: { sessions: [{ id: string, messageCount: number }] }

sessions.history

Get message history for a session.

Params: { sessionId: string, limit?: number, offset?: number }
Response: { messages: Message[], total: number }

sessions.create

Create a new session.

Params: { sessionId?: string }
Response: { sessionId: string }

tools.list

List all registered tools.

Params: {}
Response: { tools: [{ name, description, inputSchema }] }

tools.invoke

Directly invoke a tool (bypasses agent).

Params: { tool: string, args: object }
Response: { success: boolean, output: string, error?: string }

system.health

Health check.

Params: {}
Response: { status: "ok", uptime: number, version: string, sessions: number, tools: number }

Session Bridge

Each WebSocket connection is associated with a session:

  1. Client connects → assigned default session ID ws:{connectionId}
  2. Client can specify sessionId in agent.send to use a named session
  3. Sessions are created on demand via SessionManager
  4. Multiple WS clients can share a session (e.g. multiple browser tabs)
  5. Disconnection does NOT destroy the session (persistence via SQLite)

Auth (Phase 2b)

Two auth modes (checked in order):

  1. Token auth: Authorization: Bearer <token> header on WS upgrade, or { method: "auth", params: { token: "..." } } as first message
  2. Tailscale identity: Tailscale-User-Login header set by Tailscale Funnel/proxy

Config addition:

server:
  port: 18800
  auth:
    token: "optional-static-token"  # If set, required for all WS connections
    tailscale_identity: true         # Trust Tailscale-User-Login header

No auth initially (Phase 2a) — gateway only listens on localhost.

Implementation Order

  1. protocol.ts — Types only, no runtime code
  2. router.ts — Method dispatch (pure function, easy to test)
  3. session-bridge.ts — Client-to-session mapping
  4. handlers/system.ts — Simplest handler, proves the pattern
  5. handlers/sessions.ts — Session listing/history
  6. handlers/tools.ts — Tool listing/invocation
  7. handlers/agent.ts — The main handler (streaming, tool events)
  8. server.ts — WebSocket server, ties everything together
  9. Wire into daemon/index.ts
  10. Tests throughout

Dependencies

Need ws package:

pnpm add ws
pnpm add -D @types/ws

Test Strategy

  • Unit tests for protocol message validation
  • Unit tests for router dispatch
  • Unit tests for each handler (mock session manager, agent, tool registry)
  • Integration test: WS client → server → handler → response
  • Test streaming events for agent.send

Plan Version: 1.0 Created: 2026-02-05 Parent: docs/plans/2026-02-05-openclaw-parity-design.md Phase 2