Concept internals

TypeBox

TypeBox es una biblioteca de esquemas centrada en TypeScript. La usamos para definir el protocolo WebSocket del 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 app de macOS. Una única fuente de verdad; todo lo demás se genera.

Si quieres el contexto de protocolo de nivel superior, empieza con arquitectura del Gateway.

Modelo mental (30 segundos)

Cada mensaje WS del Gateway es uno de tres frames:

  • Solicitud: { type: "req", id, method, params }
  • Respuesta: { type: "res", id, ok, payload | error }
  • Evento: { type: "event", event, payload, seq?, stateVersion? }

El primer frame debe ser una solicitud connect. Después de eso, los clientes pueden llamar 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 y eventos comunes:

Categoría Ejemplos Notas
Núcleo connect, health, status connect debe ir 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 activación + control de cron
Nodos node.list, node.invoke, node.pair.* WS del Gateway + acciones de nodo
Eventos tick, presence, agent, chat, health, shutdown envío del servidor

El inventario autoritativo de descubrimiento anunciado vive en src/gateway/server-methods-list.ts (listGatewayMethods, GATEWAY_EVENTS).

Dónde viven los esquemas

  • Fuente: src/gateway/protocol/schema.ts
  • Validadores en tiempo de ejecución (AJV): src/gateway/protocol/index.ts
  • Registro de características/descubrimiento anunciado: src/gateway/server-methods-list.ts
  • 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

Pipeline actual

  • pnpm protocol:gen
    • escribe JSON Schema (draft-07) en dist/protocol.schema.json
  • pnpm protocol:gen:swift
    • genera modelos del Gateway para Swift
  • pnpm protocol:check
    • ejecuta ambos generadores y verifica que la salida esté confirmada

Cómo se usan los esquemas en tiempo de ejecución

  • Lado del servidor: cada frame entrante se valida con AJV. El handshake solo acepta una solicitud connect cuyos params coincidan con ConnectParams.
  • Lado del cliente: el cliente JS valida frames de eventos y respuestas antes de usarlos.
  • Descubrimiento de características: el Gateway envía una lista conservadora de features.methods y features.events en hello-ok desde listGatewayMethods() y GATEWAY_EVENTS.
  • Esa lista de descubrimiento no es un volcado generado de cada helper invocable en coreGatewayHandlers; algunos RPCs auxiliares se implementan en src/gateway/server-methods/*.ts sin estar enumerados en la lista de características anunciada.

Frames de ejemplo

Connect (primer mensaje):

{
  "type": "req",
  "id": "c1",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "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": 3,
    "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)

Flujo útil más pequeño: connect + health.


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: 3,
        maxProtocol: 3,
        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 desarrollado: añadir un método de principio a fin

Ejemplo: añadir una nueva solicitud system.echo que devuelva { ok: true, text }.

  1. Esquema (fuente de verdad)

Añade 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 },
);

Añade ambos a ProtocolSchemas y exporta los tipos:

  SystemEchoParams: SystemEchoParamsSchema,
  SystemEchoResult: SystemEchoResultSchema,
export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
  1. Validación

En src/gateway/protocol/index.ts, exporta un validador AJV:

export const validateSystemEchoParams = ajv.compile&lt;SystemEchoParams&gt;(SystemEchoParamsSchema);
  1. Comportamiento del servidor

Añade 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ístralo en src/gateway/server-methods.ts (ya fusiona systemHandlers), después añade "system.echo" a la entrada de listGatewayMethods en src/gateway/server-methods-list.ts.

Si el método puede ser invocado por clientes operador o Node, clasifícalo también en src/gateway/method-scopes.ts para que la aplicación de alcances y el anuncio de funciones de hello-ok permanezcan alineados.

  1. Regenerar
pnpm protocol:check
  1. Pruebas + documentación

Añade una prueba de servidor en src/gateway/server.*.test.ts y documenta el método en la documentación.

Comportamiento de codegen de Swift

El generador de Swift emite:

  • Enumeración GatewayFrame con casos req, res, event y unknown
  • Structs/enums de cargas útiles con tipos estrictos
  • Valores ErrorCode y GATEWAY_PROTOCOL_VERSION

Los tipos de frame desconocidos se conservan como cargas útiles sin procesar para compatibilidad futura.

Versionado + compatibilidad

  • PROTOCOL_VERSION está en src/gateway/protocol/schema.ts.
  • Los clientes envían minProtocol + maxProtocol; el servidor rechaza las incompatibilidades.
  • Los modelos Swift conservan los tipos de frame desconocidos para evitar romper clientes antiguos.

Patrones y convenciones de esquema

  • La mayoría de los objetos usan additionalProperties: false para cargas útiles estrictas.
  • NonEmptyString es el valor predeterminado para IDs y nombres de métodos/eventos.
  • El GatewayFrame de nivel superior usa un discriminador en type.
  • Los métodos con efectos secundarios normalmente requieren un idempotencyKey en params (ejemplo: send, poll, agent, chat.send).
  • agent acepta internalEvents opcional para el contexto de orquestación generado en tiempo de ejecución (por ejemplo, entrega de finalización de tareas subagent/cron); trata esto como superficie de API interna.

JSON de esquema en vivo

El JSON Schema generado está en el repositorio en dist/protocol.schema.json. El archivo sin procesar publicado normalmente está disponible en:

Cuando cambies esquemas

  1. Actualiza los esquemas TypeBox.
  2. Registra el método/evento en src/gateway/server-methods-list.ts.
  3. Actualiza src/gateway/method-scopes.ts cuando el nuevo RPC necesite clasificación de alcance de operador o Node.
  4. Ejecuta pnpm protocol:check.
  5. Confirma el esquema regenerado + los modelos Swift.

Relacionado