- 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
8.1 KiB
8.1 KiB
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:
{
"event": "task.updated",
"data": { ... },
"ts": 1771181000,
"id": "msg-uuid" // для дедупликации
}
Запросы (клиент → трекер):
{
"event": "task.update",
"data": { "task_id": "...", "status": "in_progress" },
"req_id": "client-uuid" // для сопоставления ответа
}
Ответы (трекер → клиент):
{
"event": "task.update.ok",
"data": { ... },
"req_id": "client-uuid" // тот же req_id
}
Ошибки:
{
"event": "error",
"data": { "message": "Task not found", "code": 404 },
"req_id": "client-uuid"
}
Подписки (subscriptions)
При подключении клиент подписывается на события:
{ "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" }