WebSocket

WebSocket

One socket, any REST call as a frame, streamed events included. Useful in browsers and long-lived clients.

Connect and authenticate

Endpoint: ws://127.0.0.1:8765/ws. Browsers cannot set headers on a WebSocket, so the first frame authenticates:

Frames

→ { "type": "auth", "token": "<token>" }
← { "type": "ready", "protocol": "clarkcant.ws.v1" }

→ { "type": "request", "id": "1", "method": "POST", "path": "/conversations/<conversationId>/messages/stream", "body": { "text": "hi" } }
← { "type": "event", "id": "1", "event": "delta", "data": { "text": "…" } }
← …
← { "type": "response", "id": "1", "status": 200, "body": null }

→ { "type": "ping" }
← { "type": "pong" }
FrameDirectionShape
authclient → node{ type, token }, first frame only
readynode → client{ type, protocol: "clarkcant.ws.v1" }
requestclient → node{ type, id, method, path, query?, body? }: any REST route except a person's decision (403 PERSON_ONLY); id is echoed exactly as sent, string or number; query parameters go in query, not in path
eventnode → client{ type, id, event, data }: one per SSE event on streamed routes
responsenode → client{ type, id, status, body }: always last for its id; body is null after a stream
ping / pongboth{ "type": "ping" } → { "type": "pong" }

Example

const ws = new WebSocket("ws://127.0.0.1:8765/ws");

// Browsers cannot set headers on a WebSocket, so the first frame authenticates.
ws.onopen = () => ws.send(JSON.stringify({ type: "auth", token: "<token>" }));

ws.onmessage = (message) => {
  const frame = JSON.parse(message.data);
  if (frame.type === "ready") {
    ws.send(JSON.stringify({
      type: "request",
      id: "1",
      method: "POST",
      path: "/conversations/<conversationId>/messages/stream",
      body: { text: "hi" },
    }));
  }
  if (frame.type === "event" && frame.event === "delta") console.log(frame.data.text);
  if (frame.type === "response") console.log("finished", frame.id, frame.status);
};

ws.onclose = (event) => { if (event.code === 4401) console.log("token missing, wrong or too late"); };

Do not ship the token in a public page. Whoever holds it can drive your Clark. Use this from your own machine or behind your own login.

Other sockets

The node also serves /voice (a voice session) and /terminal (the Terminal card). These are app sockets, not public integration surfaces or part of this frame protocol.