TypeBox를 프로토콜의 단일 진실 공급원으로
최종 업데이트: 2026-01-10
TypeBox는 TypeScript 우선 스키마 라이브러리입니다. 게이트웨이 WebSocket 프로토콜(핸드셰이크, 요청/응답, 서버 이벤트)을 정의하는 데 사용합니다. 이 스키마가 런타임 검증, JSON Schema 내보내기, macOS 앱을 위한 Swift 코드 생성을 구동합니다. 단일 진실 공급원이며 나머지는 모두 생성됩니다.
상위 수준의 프로토콜 컨텍스트가 필요하면 게이트웨이 아키텍처부터 시작하세요.
멘탈 모델 (30초)
모든 게이트웨이 WS 메시지는 세 가지 프레임 중 하나입니다:
- Request:
{ type: "req", id, method, params } - Response:
{ type: "res", id, ok, payload | error } - Event:
{ type: "event", event, payload, seq?, stateVersion? }
첫 번째 프레임은 반드시 connect 요청이어야 합니다. 그 이후 클라이언트는 메서드(예: health, send, chat.send)를 호출하고 이벤트(예: presence, tick, agent)를 구독할 수 있습니다.
연결 플로우 (최소):
Client Gateway
|---- req:connect -------->|
|<---- res:hello-ok --------|
|<---- event:tick ----------|
|---- req:health ---------->|
|<---- res:health ----------|
주요 메서드 + 이벤트:
| 카테고리 | 예시 | 참고 |
|---|---|---|
| Core | connect, health, status | connect가 먼저 와야 함 |
| Messaging | send, poll, agent, agent.wait | 부작용이 있으면 idempotencyKey 필요 |
| Chat | chat.history, chat.send, chat.abort, chat.inject | WebChat에서 사용 |
| Sessions | sessions.list, sessions.patch, sessions.delete | 세션 관리 |
| Nodes | node.list, node.invoke, node.pair.* | 게이트웨이 WS + 노드 액션 |
| Events | tick, presence, agent, chat, health, shutdown | 서버 푸시 |
공식 목록은 src/gateway/server.ts (METHODS, EVENTS)에 있습니다.
스키마 위치
- 소스:
src/gateway/protocol/schema.ts - 런타임 검증기 (AJV):
src/gateway/protocol/index.ts - 서버 핸드셰이크 + 메서드 디스패치:
src/gateway/server.ts - 노드 클라이언트:
src/gateway/client.ts - 생성된 JSON Schema:
dist/protocol.schema.json - 생성된 Swift 모델:
apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
현재 파이프라인
pnpm protocol:gen- JSON Schema (draft-07)를
dist/protocol.schema.json에 기록
- JSON Schema (draft-07)를
pnpm protocol:gen:swift- Swift 게이트웨이 모델 생성
pnpm protocol:check- 두 생성기를 실행하고 출력이 커밋되었는지 확인
런타임에서 스키마 사용 방식
- 서버 측: 모든 수신 프레임이 AJV로 검증됩니다. 핸드셰이크는
ConnectParams와 일치하는connect요청만 수락합니다. - 클라이언트 측: JS 클라이언트가 사용 전에 이벤트 및 응답 프레임을 검증합니다.
- 메서드 표면: 게이트웨이는
hello-ok에서 지원되는methods와events를 공개합니다.
프레임 예시
Connect (첫 번째 메시지):
{
"type": "req",
"id": "c1",
"method": "connect",
"params": {
"minProtocol": 2,
"maxProtocol": 2,
"client": {
"id": "openclaw-macos",
"displayName": "macos",
"version": "1.0.0",
"platform": "macos 15.1",
"mode": "ui",
"instanceId": "A1B2"
}
}
}
Hello-ok 응답:
{
"type": "res",
"id": "c1",
"ok": true,
"payload": {
"type": "hello-ok",
"protocol": 2,
"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 }
}
}
Request + Response:
{ "type": "req", "id": "r1", "method": "health" }
{ "type": "res", "id": "r1", "ok": true, "payload": { "ok": true } }
Event:
{ "type": "event", "event": "tick", "payload": { "ts": 1730000000 }, "seq": 12 }
최소 클라이언트 (Node.js)
가장 작은 유용한 플로우: connect + 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: 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();
}
});
실전 예제: 메서드를 엔드 투 엔드로 추가
예시: 새로운 system.echo 요청을 추가하여 { ok: true, text }를 반환하는 경우.
- 스키마 (진실의 공급원)
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 },
);
ProtocolSchemas에 추가하고 타입을 내보냅니다:
SystemEchoParams: SystemEchoParamsSchema,
SystemEchoResult: SystemEchoResultSchema,
export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
- 검증
src/gateway/protocol/index.ts에 AJV 검증기를 내보냅니다:
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
- 서버 동작
src/gateway/server-methods/system.ts에 핸들러를 추가합니다:
export const systemHandlers: GatewayRequestHandlers = {
"system.echo": ({ params, respond }) => {
const text = String(params.text ?? "");
respond(true, { ok: true, text });
},
};
src/gateway/server-methods.ts에 등록하고(이미 systemHandlers를 병합), src/gateway/server.ts의 METHODS에 "system.echo"를 추가합니다.
- 재생성
pnpm protocol:check
- 테스트 + 문서
src/gateway/server.*.test.ts에 서버 테스트를 추가하고 문서에 메서드를 기재합니다.
Swift 코드 생성 동작
Swift 생성기가 출력하는 내용:
req,res,event,unknown케이스를 가진GatewayFrame열거형- 강력한 타입의 페이로드 구조체/열거형
ErrorCode값과GATEWAY_PROTOCOL_VERSION
알 수 없는 프레임 타입은 하위 호환성을 위해 원시 페이로드로 보존됩니다.
버전 관리 + 호환성
PROTOCOL_VERSION은src/gateway/protocol/schema.ts에 있습니다.- 클라이언트는
minProtocol+maxProtocol을 보내고, 서버는 불일치를 거부합니다. - Swift 모델은 이전 클라이언트의 호환성 유지를 위해 알 수 없는 프레임 타입을 보존합니다.
스키마 패턴 및 규칙
- 대부분의 객체는 엄격한 페이로드를 위해
additionalProperties: false를 사용합니다. NonEmptyString은 ID와 메서드/이벤트 이름의 기본 타입입니다.- 최상위
GatewayFrame은type에 대한 디스크리미네이터를 사용합니다. - 부작용이 있는 메서드는 보통 params에
idempotencyKey를 요구합니다 (예:send,poll,agent,chat.send). agent는 런타임 생성 오케스트레이션 컨텍스트(예: 서브에이전트/크론 작업 완료 핸드오프)를 위한 선택적internalEvents를 받습니다. 이를 내부 API 표면으로 취급하세요.
라이브 스키마 JSON
생성된 JSON Schema는 저장소의 dist/protocol.schema.json에 있습니다. 공개된 원시 파일은 일반적으로 다음에서 확인할 수 있습니다:
스키마 변경 시
- TypeBox 스키마를 업데이트합니다.
pnpm protocol:check를 실행합니다.- 재생성된 스키마 + Swift 모델을 커밋합니다.