# WebSocket протокол Team Board ## Общая схема ``` ┌──────────┐ ┌─────┐ ┌─────────┐ │ Next.js │ ←http→ │ BFF │ ←ws→ │ Tracker │ │ (браузер)│ │ │ │ │ └──────────┘ └─────┘ └─────────┘ ↑ ws (JWT) ↑ ws (agent token) Браузер Agent Instance ``` ### Два типа клиентов: | Клиент | Подключение | Аутентификация | |--------|-------------|----------------| | **Человек** (браузер) | `wss://team.uix.su/ws?token=` → BFF → Tracker | JWT токен | | **Агент** | `ws://tracker:8100/ws?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:` — все события проекта (задачи, чат) - `task:` — события конкретной задачи (комментарии, файлы, статус) - `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= 2. BFF validates JWT 3. BFF → Tracker: ws://tracker:8100/ws?client_type=human&client_id= 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= 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:` | | `task.updated` | Подписчики `project:` + `task:` | | `comment.created` | Подписчики `task:` | | `chat.message` | Подписчики `project:` (для чата проекта) | | `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" } ```