SNA

WebSocket 프로토콜

Request/response op, 푸시 채널, 재연결 규칙을 정리합니다.

WebSocket은 라이브 채널입니다. 푸시 이벤트(assistant delta, 툴 호출, 권한 요청)와, HTTP API가 노출하는 같은 제어 op들을 호출 단위 HTTP 오버헤드 없이 운반합니다.

엔드포인트

ws://<host>:<port>/ws

클라이언트가 한 번 연결하고 그 단일 연결로 모든 세션을 다중화합니다.

인증

WebSocket upgrade는 보호된 HTTP 라우트와 같은 토큰을 요구합니다. Browser client는 임의의 upgrade header를 설정할 수 없으므로 TypeScript client는 토큰을 ws://<host>:<port>/ws?token=<authToken> 형태로 보냅니다. Native client는 upgrade 요청에 Authorization: Bearer <authToken> 또는 X-SNA-Token을 사용할 수도 있습니다.

서버에 origin allowlist가 있으면 browser WebSocket 연결은 허용된 Origin에서 와야 합니다. 토큰 실패는 upgrade를 401로 닫고, origin 실패는 403으로 닫습니다.

와이어 모양

모든 메시지는 type과 payload를 가진 JSON 객체입니다.

// Client → server
{ "id": "rpc-7", "type": "agent.send", "payload": { "session": "abc", "message": "hi" } }

// Server → client (reply)
{ "id": "rpc-7", "type": "agent.send", "status": "ok", "data": { "queued": true } }

// Server → client (push, id 없음)
{ "type": "agent.event", "data": { "session": "abc", "event": { "type": "assistant_delta", "delta": "Hello" } } }

id 필드는 클라이언트가 선택한 상관 토큰입니다. request/response 쌍에는 들어가고, 푸시 메시지에는 들어가지 않습니다.

제어 op

모든 HTTP 라우트에는 대응하는 WS op가 있습니다.

WS typeHTTP 동치
agent.startPOST /agent/start
agent.sendPOST /agent/send
agent.interruptPOST /agent/interrupt
agent.set-modelPOST /agent/set-model
agent.set-permission-modePOST /agent/set-permission-mode
agent.restartPOST /agent/restart
agent.resumePOST /agent/resume
agent.killPOST /agent/kill
agent.run-oncePOST /agent/run-once
agent.completionPOST /agent/completion
agent.statusGET /agent/status
sessions.listGET /agent/sessions
sessions.createPOST /agent/sessions
sessions.updatePATCH /agent/sessions/:id
sessions.removeDELETE /agent/sessions/:id
permission.subscribe(푸시 채널; HTTP 동치 없음)
permission.respondPOST /agent/permission-respond

푸시 채널

두 채널은 푸시 전용:

  • agent.event: 구독된 모든 세션의 모든 AgentEvent마다 발생합니다. agent.subscribe로 구독하고, agent.unsubscribe로 구독을 해제합니다.
  • permission.request: 어떤 세션이든 permission_needed를 emit하면 발생합니다. permission.subscribe로 구독합니다.

재연결

연결이 끊기면 클라이언트가 재연결하고 다시 구독해야 합니다. 끊긴 동안 버퍼링된 이벤트는 세션별 ring buffer(기본 1000개)에 여전히 있습니다. 새 agent.subscribe?since=<cursor>를 넘기면 replay됩니다.

왜 HTTP와 WS 둘 다

  • 순서와 ACK에는 HTTP. 뮤테이션은 추론하기 쉬운 request/response 계약이 필요합니다. HTTP는 이 계약을 자연스럽게 제공합니다.
  • 팬아웃에는 WS. 단일 연결이 N개의 세션 이벤트 + 권한 요청을 전달합니다. N개의 SSE 스트림을 여는 것보다 훨씬 효율적입니다.

TypeScript 클라이언트는 모든 뮤테이션에 HTTP, 푸시 채널에 WS를 씁니다. 둘 중 하나만으로도 동작합니다 (http: true, ws: false는 단명 스크립트용; ws: true, http: false는 HTTP를 못 쓰는 클라이언트용). 실제 앱에서는 둘을 함께 쓰는 구성이 가장 자연스럽습니다.

목차