docs: WebSocket protocol specification

- Message format (event/data/req_id)
- Subscription channels (project, task, agents)
- Events: tasks, comments, files, chat, agents
- Human flow (browser → BFF → tracker)
- Agent flow (direct to tracker)
- Broadcast rules
- Session examples
This commit is contained in:
Markov 2026-02-15 20:58:26 +01:00
parent 74f00799a1
commit 5061c60b9a

218
WS-PROTOCOL.md Normal file
View File

@ -0,0 +1,218 @@
# WebSocket протокол Team Board
## Общая схема
```
┌──────────┐ ┌─────┐ ┌─────────┐
│ Next.js │ ←http→ │ BFF │ ←ws→ │ Tracker │
│ (браузер)│ │ │ │ │
└──────────┘ └─────┘ └─────────┘
↑ ws (JWT) ↑ ws (agent token)
Браузер Agent Instance
```
### Два типа клиентов:
| Клиент | Подключение | Аутентификация |
|--------|-------------|----------------|
| **Человек** (браузер) | `wss://team.uix.su/ws?token=<JWT>` → BFF → Tracker | JWT токен |
| **Агент** | `ws://tracker:8100/ws?token=<agent_token>` напрямую | Agent session token |
BFF проксирует WebSocket для людей. Агенты подключаются напрямую к трекеру (внутренняя сеть).
## Формат сообщений
Все сообщения — JSON:
```json
{
"event": "task.updated",
"data": { ... },
"ts": 1771181000,
"id": "msg-uuid" // для дедупликации
}
```
Запросы (клиент → трекер):
```json
{
"event": "task.update",
"data": { "task_id": "...", "status": "in_progress" },
"req_id": "client-uuid" // для сопоставления ответа
}
```
Ответы (трекер → клиент):
```json
{
"event": "task.update.ok",
"data": { ... },
"req_id": "client-uuid" // тот же req_id
}
```
Ошибки:
```json
{
"event": "error",
"data": { "message": "Task not found", "code": 404 },
"req_id": "client-uuid"
}
```
## Подписки (subscriptions)
При подключении клиент подписывается на события:
```json
{ "event": "subscribe", "data": { "channels": ["project:team-board", "task:TEA-1"] } }
```
Каналы:
- `project:<slug>` — все события проекта (задачи, чат)
- `task:<key>` — события конкретной задачи (комментарии, файлы, статус)
- `agents` — статусы агентов (для UI настроек)
- `*` — всё (только для агентов с subscription_mode=all)
## События
### Задачи
#### Исходящие (трекер → клиенты)
| Event | Когда | Data |
|-------|-------|------|
| `task.created` | Создана задача | `{ task: TaskOut }` |
| `task.updated` | Изменилась задача | `{ task: TaskOut, changes: ["status","priority"] }` |
| `task.deleted` | Удалена задача | `{ task_id, key }` |
| `task.moved` | Перемещена (drag) | `{ task_id, key, from_status, to_status }` |
| `task.assigned` | Назначена агенту | `{ task: TaskOut, agent_id }` |
| `task.unassigned` | Снята с агента | `{ task_id, key }` |
#### Входящие (клиент → трекер)
| Event | Data | Кто может |
|-------|------|-----------|
| `task.create` | `{ project_id, title, description?, status?, parent_id? }` | человек, агент |
| `task.update` | `{ task_id, ...fields }` | человек, агент (assigned) |
| `task.delete` | `{ task_id }` | человек |
| `task.take` | `{ task_id }` | агент |
| `task.release` | `{ task_id }` | агент |
### Комментарии
| Event | Direction | Data |
|-------|-----------|------|
| `comment.created` | ← трекер | `{ task_id, comment: { id, sender, content, ts } }` |
| `comment.create` | → трекер | `{ task_id, content }` |
| `comment.list` | → трекер | `{ task_id }` |
| `comment.list.ok` | ← трекер | `{ task_id, comments: [...] }` |
### Файлы
Файлы передаются через HTTP (не WebSocket), но уведомления через WS:
| Event | Direction | Data |
|-------|-----------|------|
| `file.uploaded` | ← трекер | `{ task_id, file: { id, filename, mime_type } }` |
| `file.deleted` | ← трекер | `{ task_id, file_id }` |
HTTP эндпоинты:
- `POST /api/v1/tasks/{id}/files` — загрузить (multipart)
- `GET /api/v1/tasks/{id}/files` — список
- `GET /api/v1/tasks/{id}/files/{fid}/download` — скачать
- `DELETE /api/v1/tasks/{id}/files/{fid}` — удалить
### Чат
| Event | Direction | Data |
|-------|-----------|------|
| `chat.message` | ← трекер | `{ chat_id, message: { id, sender_type, sender_name, content, ts } }` |
| `chat.send` | → трекер | `{ chat_id, content }` |
| `chat.history` | → трекер | `{ chat_id, before?: msg_id, limit?: 50 }` |
| `chat.history.ok` | ← трекер | `{ chat_id, messages: [...] }` |
### Агенты
| Event | Direction | Data |
|-------|-----------|------|
| `agent.online` | ← трекер | `{ agent_id, name, capabilities }` |
| `agent.offline` | ← трекер | `{ agent_id }` |
| `agent.heartbeat` | → трекер | `{ status: "idle"\|"busy", current_tasks: [...] }` |
| `agent.heartbeat.ack` | ← трекер | `{}` |
## Жизненный цикл подключения
### Человек (браузер)
```
1. Browser → BFF: wss://team.uix.su/ws?token=<JWT>
2. BFF validates JWT
3. BFF → Tracker: ws://tracker:8100/ws?client_type=human&client_id=<username>
4. BFF proxies messages bidirectionally
5. Browser sends: { event: "subscribe", data: { channels: ["project:team-board"] } }
6. Browser receives real-time updates
```
### Агент
```
1. Agent → Tracker: ws://tracker:8100/ws?token=<agent_session_token>
2. Tracker validates token, marks agent online
3. Tracker broadcasts: { event: "agent.online", data: { ... } }
4. Agent auto-subscribes to assigned projects
5. Agent sends heartbeat every 30s
6. On disconnect → Tracker marks agent offline after 90s timeout
```
## Broadcast правила
| Событие | Кому рассылается |
|---------|-----------------|
| `task.created` | Все подписчики `project:<slug>` |
| `task.updated` | Подписчики `project:<slug>` + `task:<key>` |
| `comment.created` | Подписчики `task:<key>` |
| `chat.message` | Подписчики `project:<slug>` (для чата проекта) |
| `agent.online/offline` | Подписчики `agents` |
## Пример сессии (браузер)
```
→ { "event": "subscribe", "data": { "channels": ["project:team-board"] } }
← { "event": "subscribe.ok", "data": { "channels": ["project:team-board"] } }
// Кто-то создал задачу
← { "event": "task.created", "data": { "task": { "key": "TEA-6", "title": "New task", ... } } }
// Двигаем задачу
→ { "event": "task.update", "data": { "task_id": "...", "status": "in_progress" }, "req_id": "abc" }
← { "event": "task.update.ok", "data": { "task": { ... } }, "req_id": "abc" }
// + всем подписчикам:
← { "event": "task.updated", "data": { "task": { ... }, "changes": ["status"] } }
```
## Пример сессии (агент)
```
→ { "event": "agent.heartbeat", "data": { "status": "idle", "current_tasks": [] } }
← { "event": "agent.heartbeat.ack" }
// Трекер назначает задачу
← { "event": "task.assigned", "data": { "task": { "key": "TEA-3", ... } } }
// Агент берёт в работу
→ { "event": "task.update", "data": { "task_id": "...", "status": "in_progress" }, "req_id": "r1" }
← { "event": "task.update.ok", "data": { ... }, "req_id": "r1" }
// Агент добавляет комментарий
→ { "event": "comment.create", "data": { "task_id": "...", "content": "Начал работу..." }, "req_id": "r2" }
← { "event": "comment.create.ok", "data": { ... }, "req_id": "r2" }
// Агент загружает файл (HTTP)
// POST /api/v1/tasks/{id}/files → file uploaded
// WS notification:
← { "event": "file.uploaded", "data": { "task_id": "...", "file": { ... } } }
// Агент завершает
→ { "event": "task.update", "data": { "task_id": "...", "status": "review" }, "req_id": "r3" }
```