Skip to content

WebSocket events

Every client holds one WebSocket at GET /api/ws (wss://api.novaterra.world/api/ws in production), authenticated by the same session cookie as the REST API. This is how the desktop tells you what you need to know at the moment you need to know it: widgets subscribe by topic and the server pushes typed events.

Both directions are discriminated unions on type, so a client can switch exhaustively:

import { ServerEvent } from '@novaterra/contracts';
socket.addEventListener('message', (raw) => {
const event = ServerEvent.parse(JSON.parse(raw.data));
if (event.type === 'signal.created') showSignal(event.signal);
});

Server → client

Pushed by the API. Anything that changes while you are looking at the world arrives here.

typePayload
hellobeingId: Id
serverTime: ISODate
pongserverTime: ISODate
signal.createdsignal: Signal
signal.updatedsignal: Signal
message.deltathreadId: Id
messageId: Id
delta: string
message.createdmessage: Message
message.toolthreadId: Id
messageId: Id
toolCall: Json
task.updatedtask: Task
project.updatedproject: Project
traceevent: TraceEvent
memory.createdmemory: Memory
widget.suggestwidgetType: string
reason: string
config?: Json
presence.updatebeingId: Id
status: 'online' | 'away' | 'offline'
doing?: string
wallet.updatedwallet: Wallet
twin.activitykind: string
summary: string
at: ISODate
square.postpost: Message
room.updatedthreadId: Id
meetingId: Id | null
floorBeingId: Id | null
presentBeingIds: Id[]
session: Json | null
ambientmood: string
hint?: string

Client → server

Sent by the browser to declare interest and presence.

typePayload
subscribetopics: string[]
presencestatus: 'online' | 'away'
doing?: string
ping