TypeBox
TypeBox es una biblioteca de esquemas con prioridad para TypeScript. La usamos para definir el protocolo WebSocket de Gateway (handshake, solicitud/respuesta, eventos del servidor). Esos esquemas impulsan la validación en tiempo de ejecución, la exportación de JSON Schema y la generación de código Swift para la aplicación macOS. Una única fuente de verdad; todo lo demás se genera.
Si deseas el contexto de protocolo de nivel superior, comienza con Arquitectura de Gateway.
Modelo mental (30 segundos)
Sección titulada «Modelo mental (30 segundos)»Cada mensaje WS de Gateway es uno de tres tramas (frames):
- Solicitud:
{ type: "req", id, method, params } - Respuesta:
{ type: "res", id, ok, payload | error } - Evento:
{ type: "event", event, payload, seq?, stateVersion? }
La primera trama debe ser una solicitud connect. Después de eso, los clientes pueden llamar a métodos (p. ej. health, send, chat.send) y suscribirse a eventos (p. ej.
presence, tick, agent).
Flujo de conexión (mínimo):
Client Gateway |---- req:connect -------->| |<---- res:hello-ok --------| |<---- event:tick ----------| |---- req:health ---------->| |<---- res:health ----------|Métodos + eventos comunes:
| Categoría | Ejemplos | Notas |
|---|---|---|
| Núcleo | connect, health, status | connect debe ser primero |
| Mensajería | send, agent, agent.wait, system-event, logs.tail | los efectos secundarios necesitan idempotencyKey |
| Chat | chat.history, chat.send, chat.abort | WebChat usa estos |
| Sesiones | sessions.list, sessions.patch, sessions.delete | administración de sesiones |
| Automatización | wake, cron.list, cron.run, cron.runs | control de activación + cron |
| Nodos | node.list, node.invoke, node.pair.* | Gateway WS + acciones de nodo |
| Eventos | tick, presence, agent, chat, health, shutdown | envío del servidor |
El inventario de descubrimiento (discovery) autoritativo y anunciado se encuentra en
src/gateway/server-methods-list.ts (listGatewayMethods, GATEWAY_EVENTS).
Dónde se encuentran los esquemas
Sección titulada «Dónde se encuentran los esquemas»- Fuente:
src/gateway/protocol/schema.ts - Validadores de tiempo de ejecución (AJV):
src/gateway/protocol/index.ts - Registro de características/descubrimiento anunciadas:
src/gateway/server-methods-list.ts - Protocolo de enlace (handshake) del servidor + despacho de métodos:
src/gateway/server.impl.ts - Cliente Node:
src/gateway/client.ts - JSON Schema generado:
dist/protocol.schema.json - Modelos Swift generados:
apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
Canalización actual
Sección titulada «Canalización actual»pnpm protocol:gen- escribe JSON Schema (draft-07) en
dist/protocol.schema.json
- escribe JSON Schema (draft-07) en
pnpm protocol:gen:swift- genera modelos de Gateway en Swift
pnpm protocol:check- ejecuta ambos generadores y verifica que la salida se haya confirmado (committed)
Cómo se usan los esquemas en tiempo de ejecución
Sección titulada «Cómo se usan los esquemas en tiempo de ejecución»- Lado del servidor: cada trama entrante se valida con AJV. El protocolo de enlace (handshake) solo
acepta una solicitud
connectcuyos parámetros coincidan conConnectParams. - Lado del cliente: el cliente JS valida las tramas de eventos y respuestas antes de utilizarlas.
- Descubrimiento de características: el Gateway envía una lista
features.methodsyfeatures.eventsconservadora enhello-okdesdelistGatewayMethods()yGATEWAY_EVENTS. - Esa lista de descubrimiento no es un volcado generado de cada ayudante invocable en
coreGatewayHandlers; algunos RPC auxiliares se implementan ensrc/gateway/server-methods/*.tssin ser enumerados en la lista de características anunciadas.
Tramas de ejemplo
Sección titulada «Tramas de ejemplo»Conexión (primer mensaje):
{ "type": "req", "id": "c1", "method": "connect", "params": { "minProtocol": 3, "maxProtocol": 4, "client": { "id": "openclaw-macos", "displayName": "macos", "version": "1.0.0", "platform": "macos 15.1", "mode": "ui", "instanceId": "A1B2" } }}Respuesta Hello-ok:
{ "type": "res", "id": "c1", "ok": true, "payload": { "type": "hello-ok", "protocol": 4, "server": { "version": "dev", "connId": "ws-1" }, "features": { "methods": ["health"], "events": ["tick"] }, "snapshot": { "presence": [], "health": {}, "stateVersion": { "presence": 0, "health": 0 }, "uptimeMs": 0 }, "policy": { "maxPayload": 1048576, "maxBufferedBytes": 1048576, "tickIntervalMs": 30000 } }}Solicitud + respuesta:
{ "type": "req", "id": "r1", "method": "health" }{ "type": "res", "id": "r1", "ok": true, "payload": { "ok": true } }Evento:
{ "type": "event", "event": "tick", "payload": { "ts": 1730000000 }, "seq": 12 }Cliente mínimo (Node.js)
Sección titulada «Cliente mínimo (Node.js)»Flujo útil más pequeño: conectar + estado de salud (health).
import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => { ws.send( JSON.stringify({ type: "req", id: "c1", method: "connect", params: { minProtocol: 4, maxProtocol: 4, client: { id: "cli", displayName: "example", version: "dev", platform: "node", mode: "cli", }, }, }), );});
ws.on("message", (data) => { const msg = JSON.parse(String(data)); if (msg.type === "res" && msg.id === "c1" && msg.ok) { ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" })); } if (msg.type === "res" && msg.id === "h1") { console.log("health:", msg.payload); ws.close(); }});Ejemplo práctico: agregar un método de extremo a extremo
Sección titulada «Ejemplo práctico: agregar un método de extremo a extremo»Ejemplo: agregar una nueva solicitud system.echo que devuelva { ok: true, text }.
- Esquema (fuente de verdad)
Agregar a src/gateway/protocol/schema.ts:
export const SystemEchoParamsSchema = Type.Object({ text: NonEmptyString }, { additionalProperties: false });
export const SystemEchoResultSchema = Type.Object({ ok: Type.Boolean(), text: NonEmptyString }, { additionalProperties: false });Agregar ambos a ProtocolSchemas y exportar tipos:
SystemEchoParams: SystemEchoParamsSchema, SystemEchoResult: SystemEchoResultSchema,export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;- Validación
En src/gateway/protocol/index.ts, exportar un validador AJV:
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);- Comportamiento del servidor
Añada un controlador en src/gateway/server-methods/system.ts:
export const systemHandlers: GatewayRequestHandlers = { "system.echo": ({ params, respond }) => { const text = String(params.text ?? ""); respond(true, { ok: true, text }); },};Regístrelo en src/gateway/server-methods.ts (ya fusiona systemHandlers),
luego agregue "system.echo" a la entrada listGatewayMethods en
src/gateway/server-methods-list.ts.
Si el método puede ser llamado por clientes operadores o nodos, también clasifíquelo en
src/gateway/method-scopes.ts para que la aplicación de alcance y la publicidad de características hello-ok
se mantengan alineadas.
- Regenerar
pnpm protocol:check- Pruebas + documentación
Agregue una prueba de servidor en src/gateway/server.*.test.ts y note el método en la documentación.
Comportamiento de la generación de código Swift
Sección titulada «Comportamiento de la generación de código Swift»El generador de Swift emite:
- Enum
GatewayFramecon casosreq,res,eventyunknown - Structs/enums de carga útil fuertemente tipados
- valores
ErrorCode,GATEWAY_PROTOCOL_VERSIONyGATEWAY_MIN_PROTOCOL_VERSION
Los tipos de trama desconocidos se conservan como cargas útiles sin procesar para la compatibilidad futura.
Versionado + compatibilidad
Sección titulada «Versionado + compatibilidad»PROTOCOL_VERSIONvive ensrc/gateway/protocol/version.ts.- Los clientes envían
minProtocol+maxProtocol; el servidor rechaza los rangos que no incluyen su protocolo actual. - Los modelos Swift mantienen tipos de trama desconocidos para evitar romper clientes antiguos.
Patrones y convenciones de esquemas
Sección titulada «Patrones y convenciones de esquemas»- La mayoría de los objetos usan
additionalProperties: falsepara cargas útiles estrictas. NonEmptyStringes el valor predeterminado para IDs y nombres de métodos/eventos.- El
GatewayFramede nivel superior usa un discriminador entype. - Los métodos con efectos secundarios generalmente requieren un
idempotencyKeyen los parámetros (ejemplo:send,poll,agent,chat.send). agentaceptainternalEventsopcional para el contexto de orquestación generado en tiempo de ejecución (por ejemplo, traspaso de finalización de tareas de subagente/cron); trata esto como superficie de API interna.
JSON de esquema en vivo
Sección titulada «JSON de esquema en vivo»El JSON Schema generado está en el repositorio en dist/protocol.schema.json. El
archivo raw publicado típicamente está disponible en:
Cuando cambias los esquemas
Sección titulada «Cuando cambias los esquemas»- Actualiza los esquemas de TypeBox.
- Registra el método/evento en
src/gateway/server-methods-list.ts. - Actualiza
src/gateway/method-scopes.tscuando el nuevo RPC necesite clasificación de ámbito de operador o nodo. - Ejecuta
pnpm protocol:check. - Confirma el esquema regenerado + los modelos Swift.