Comma Agents
@comma-agents/tui

WebSocket

Daemon communication via WebSocket — connection lifecycle, message dispatch, and typed command/event helpers.

WebSocket

The TUI communicates with the daemon over a single WebSocket connection. The daemon listens on ws://localhost:7422/ws by default. Messages are JSON-encoded discriminated unions -- every message has a type field that determines its shape.

Connection Layer

The useWebSocket hook manages the raw WebSocket lifecycle:

  • Connects eagerly on mount, tears down on unmount
  • Reconnects automatically when the URL changes
  • Buffers outbound messages sent during CONNECTING and flushes them on open

The daemon context provider (DaemonContextProvider) wraps useWebSocket and adds JSON serialization, Zod validation, and typed dispatch.

Message Types

All daemon message types are defined in @comma-agents/daemon as discriminated unions:

import type { ClientMessage, DaemonMessage } from "@comma-agents/daemon";

Client → Daemon (Commands)

TypePurpose
prepare_runLoad a strategy and initialize a pending run
start_runStart a prepared run with optional initial input
user_inputSend user input to a waiting agent
stop_runCancel a prepared or running run
permission_decisionResolve a sandbox permission request
list_runsRequest a list of persisted runs
load_sessionHydrate a persisted session from the daemon
pingKeepalive heartbeat

Daemon → Client (Events)

TypePurpose
strategy_startedA new strategy run has begun
strategy_completedA strategy run finished successfully
strategy_errorA strategy run encountered an error
step_startedA flow step is starting
step_completedA flow step finished
agent_streamingStreaming tokens from an agent (text, tool-call, tool-result, thinking)
agent_outputA completed agent turn (deduplicated against streaming)
request_inputAn agent is waiting for user input
request_permissionA sandbox operation requires user approval
policy_updatedThe sandbox policy changed
run_listResponse to list_runs
session_loadedA persisted session's full history
pongKeepalive response

Typed Helpers

The TUI provides generic type helpers for narrowing the full message unions:

import type {
  DaemonMessageOf,
  ClientMessageOf,
  DaemonMessageListener,
} from "@comma-agents/tui";
  • DaemonMessageOf<"agent_streaming"> -- Narrows to the streaming variant
  • ClientMessageOf<"prepare_run"> -- Narrows to the prepare command payload
  • DaemonMessageListener<"strategy_completed"> -- Typed listener callback

Sending Commands

Use useDaemonCommand for a typed send function that auto-injects type and requestId:

import { useDaemonCommand } from "@comma-agents/tui";

const prepareRun = useDaemonCommand("prepare_run");

// Returns the requestId, or null if the WebSocket is not open
const requestId = startStrategy({
  strategyPath: "/path/to/strategy.json",
  input: "Build a calculator",
});

Subscribing to Events

Use useDaemonSubscription for a typed listener that registers and auto-cleans up:

import { useDaemonSubscription } from "@comma-agents/tui";

useDaemonSubscription("strategy_completed", (message) => {
  // message is narrowed to the strategy_completed variant
  console.log(`Run ${message.runId} completed: ${message.summary}`);
});

Optionally scope the subscription to a specific run:

useDaemonSubscription("agent_streaming", handleStreaming, session.daemonRunId);

Raw Access

The useDaemon hook provides low-level access to the context:

import { useDaemon } from "@comma-agents/tui";

const { status, send, on, off } = useDaemon();
// status: "disconnected" | "connecting" | "connected" | "error"
// send(message): boolean
// on(type, listener): unsubscribe
// off(type, listener): void

For most use cases, prefer useDaemonCommand and useDaemonSubscription -- they handle the boilerplate of request IDs, listener lifecycle, and type narrowing.

On this page