docs/archive/WS-PROTOCOL.md

219 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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" }
```