Skip to content

Build a custom UI

@coframe/editor-session gives you the building blocks Coframe Agent‘s own frontend is built on: the full RoomState types, the typed RPC interface (EditSessionAgentLike), and the realtime wire-protocol parser. Reach for it when you want to design your own editor UI with full control over state and rendering, rather than streaming a curated event feed.

Terminal window
pnpm add @coframe/editor-session
@coframe/agent-client@coframe/editor-session
EventsA curated, app-facing event feedThe full RoomState snapshot + event stream
RPCSend messages, approvals, cancelEvery typed method the editor frontend calls
StateYou keep your ownTyped RoomState slices for every service
Best forRendering the agent’s workA bespoke editor frontend with full control

RoomState is the single broadcast snapshot every viewer sees, with a typed slice per concern:

import type { RoomState } from "@coframe/editor-session/state";
function render(state: RoomState) {
state.sandbox; // dev sandbox lifecycle + preview URL
state.kernel; // live browser status + live-view URL
state.planning; // task list the agent is working through
state.storybook; // Storybook service status
state.vitest; // unit-test runner status
state.testResults; // live test results streamed from the sandbox
state.filesWatch; // file-watch subscriptions
state.skills; // available skills
state.requirements; // requirements the agent is gathering
state.pendingAnnotationSubmission; // latest human annotation, if any
}

EditSessionAgentLike is a type-only mirror of the callable RPC surface. Every method takes a typed input object:

import type { EditSessionAgentLike } from "@coframe/editor-session/rpc";
// `agent` is your Agents SDK RPC stub, typed as EditSessionAgentLike.
await agent.approveToolCall({ toolCallId });
await agent.rejectToolCall({ toolCallId, reason: "not now" });
await agent.stopAgentTurn();
await agent.ensureKernelBrowser();
await agent.reloadKernelBrowser();
await agent.writeFile({ path: "src/App.tsx", content });
const file = await agent.readFile({ path: "src/App.tsx" });
await agent.upsertTask({ /* … */ });
await agent.completeTask({ taskId });
await agent.restartService({ service: "storybook" });
await agent.setPreviewTarget({ url: "https://example.com" });

Frames arriving on the Agents SDK WebSocket are parsed with parseRealtimeEnvelope, which classifies each frame into one of three kinds:

import { parseRealtimeEnvelope } from "@coframe/editor-session/realtime";
socket.addEventListener("message", (event) => {
const envelope = parseRealtimeEnvelope(event.data);
if (!envelope) return; // SDK framing or unrecognized — skip
switch (envelope.kind) {
case "snapshot":
// Full replay (e.g. on reconnect) — fold each event into your state.
for (const evt of envelope.events) apply(evt);
break;
case "event":
// Incremental update — apply a single event.
apply(envelope.event);
break;
case "error":
console.error("Session error:", envelope.message);
break;
}
});

A custom frontend follows the same shape Coframe Agent uses internally:

  1. Open the Agents SDK WebSocket for the session.
  2. Parse each frame with parseRealtimeEnvelope.
  3. Fold events into local state typed as RoomState.
  4. Call EditSessionAgentLike methods on your RPC stub to drive the session.
  5. Render your own components from the typed state.

Connect to the session WebSocket and watch RoomState snapshots arrive in real time: