444 lines
12 KiB
Markdown
444 lines
12 KiB
Markdown
# Team Board — WebSocket протокол
|
||
|
||
## Подключение
|
||
|
||
**URL:** `ws://localhost:8100/ws`
|
||
**Протокол:** JSON поверх WebSocket
|
||
|
||
### Аутентификация
|
||
|
||
Есть два способа аутентификации:
|
||
|
||
#### 1. Через query parameter:
|
||
```
|
||
ws://localhost:8100/ws?token=jwt_token_or_agent_token
|
||
```
|
||
|
||
#### 2. Первым сообщением:
|
||
```json
|
||
{
|
||
"type": "auth",
|
||
"token": "jwt_token_or_agent_token",
|
||
"on_behalf_of": "member_uuid" // опционально, только для bridge типа
|
||
}
|
||
```
|
||
|
||
### Типы токенов:
|
||
- **JWT токены** — для web пользователей (получаются через `/api/v1/auth/login`)
|
||
- **Bearer токены** — для агентов (формат `tb-xxxxx`)
|
||
|
||
### Bridge прокси:
|
||
- Участники типа `bridge` могут действовать от имени других участников
|
||
- Используется поле `on_behalf_of` с UUID участника
|
||
- Если участник не найден, логируется предупреждение
|
||
|
||
---
|
||
|
||
## Ответ на аутентификацию
|
||
|
||
### Успешная аутентификация:
|
||
```json
|
||
{
|
||
"type": "auth.ok",
|
||
"data": {
|
||
"member_id": "uuid",
|
||
"slug": "string",
|
||
"name": "string",
|
||
"lobby_chat_id": "uuid",
|
||
"projects": [
|
||
{
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string",
|
||
"chat_id": "uuid"
|
||
}
|
||
],
|
||
"online": [
|
||
{
|
||
"id": "uuid",
|
||
"slug": "string"
|
||
}
|
||
],
|
||
"assigned_tasks": [ // только для агентов
|
||
{
|
||
"id": "uuid",
|
||
"title": "string",
|
||
"status": "todo|in_progress|in_review",
|
||
"project_id": "uuid"
|
||
}
|
||
],
|
||
"agent_config": { // только для агентов
|
||
"model": "string",
|
||
"provider": "string",
|
||
"prompt": "string",
|
||
"chat_listen": "all|mentions|none",
|
||
"task_listen": "all|mentions|assigned|none",
|
||
"max_concurrent_tasks": 2,
|
||
"capabilities": ["string"],
|
||
"labels": ["string"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Ошибка аутентификации:
|
||
```json
|
||
{
|
||
"type": "auth.error",
|
||
"message": "Invalid token"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Heartbeat система
|
||
|
||
### Отправка heartbeat (клиент → сервер):
|
||
```json
|
||
{
|
||
"type": "heartbeat",
|
||
"status": "online|offline|busy" // опционально
|
||
}
|
||
```
|
||
|
||
**Рекомендации:**
|
||
- Отправлять каждые 30 секунд
|
||
- Обновляет `last_heartbeat` timestamp
|
||
- Позволяет менять статус участника
|
||
|
||
### Мониторинг timeout:
|
||
- Сервер проверяет heartbeat каждые 30 секунд
|
||
- Timeout: 90 секунд без heartbeat
|
||
- При timeout: отключение сессии, статус → offline, уведомление всем
|
||
|
||
---
|
||
|
||
## Подписки на проекты
|
||
|
||
По умолчанию участники автоматически подписываются на все доступные проекты при подключении.
|
||
|
||
### Подписаться на проект:
|
||
```json
|
||
{
|
||
"type": "project.subscribe",
|
||
"project_id": "uuid"
|
||
}
|
||
```
|
||
|
||
### Отписаться от проекта:
|
||
```json
|
||
{
|
||
"type": "project.unsubscribe",
|
||
"project_id": "uuid"
|
||
}
|
||
```
|
||
|
||
**Примечание:** подписки фильтруют события. Без подписки на проект участник не получает события из него.
|
||
|
||
---
|
||
|
||
## Отправка сообщений
|
||
|
||
### Отправить сообщение (клиент → сервер):
|
||
```json
|
||
{
|
||
"type": "chat.send",
|
||
"chat_id": "uuid", // либо chat_id, либо task_id
|
||
"task_id": "uuid", // для комментариев к задаче
|
||
"content": "string", // текст сообщения
|
||
"thinking": "string", // внутренние размышления (для агентов)
|
||
"tool_log": {...}, // JSON лог вызовов инструментов
|
||
"mentions": ["member_id"] // массив ID упоминаемых участников
|
||
}
|
||
```
|
||
|
||
### Получение нового сообщения:
|
||
```json
|
||
{
|
||
"type": "message.new",
|
||
"data": {
|
||
"id": "uuid",
|
||
"chat_id": "uuid",
|
||
"task_id": "uuid",
|
||
"parent_id": "uuid",
|
||
"author_type": "human|agent|system",
|
||
"author_id": "uuid",
|
||
"author": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
},
|
||
"actor": {...}, // для системных сообщений
|
||
"content": "string",
|
||
"thinking": "string",
|
||
"tool_log": {...},
|
||
"mentions": [
|
||
{
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
}
|
||
],
|
||
"voice_url": "string",
|
||
"attachments": [...],
|
||
"created_at": "iso_timestamp",
|
||
"project_id": "uuid" // автоматически добавляется сервером
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Task события
|
||
|
||
### Создание задачи:
|
||
```json
|
||
{
|
||
"type": "task.created",
|
||
"data": {
|
||
"id": "uuid",
|
||
"project": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"name": "string"
|
||
},
|
||
"number": 123,
|
||
"key": "XX-123",
|
||
"title": "string",
|
||
"description": "string",
|
||
"type": "task|bug|feature",
|
||
"status": "backlog|todo|in_progress|in_review|done",
|
||
"priority": "low|medium|high|critical",
|
||
"labels": ["string"],
|
||
"assignee": {...} | null,
|
||
"reviewer": {...} | null,
|
||
"parent": {...} | null,
|
||
"subtasks": [...],
|
||
"steps": [...],
|
||
"watcher_ids": ["uuid"],
|
||
"depends_on": ["uuid"],
|
||
"position": 0,
|
||
"created_at": "iso_timestamp",
|
||
"updated_at": "iso_timestamp",
|
||
"project_id": "uuid"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Обновление задачи:
|
||
```json
|
||
{
|
||
"type": "task.updated",
|
||
"data": {
|
||
// полный объект задачи (как в task.created)
|
||
"project_id": "uuid"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Назначение задачи:
|
||
```json
|
||
{
|
||
"type": "task.assigned",
|
||
"data": {
|
||
// полный объект задачи (как в task.created)
|
||
"project_id": "uuid"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Удаление задачи:
|
||
```json
|
||
{
|
||
"type": "task.deleted",
|
||
"data": {
|
||
"id": "uuid",
|
||
"project_id": "uuid"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Agent события
|
||
|
||
### Изменение статуса участника:
|
||
```json
|
||
{
|
||
"type": "agent.status",
|
||
"data": {
|
||
"id": "uuid",
|
||
"slug": "string",
|
||
"status": "online|offline|busy"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Agent streaming события:
|
||
#### Начало стрима:
|
||
```json
|
||
{
|
||
"type": "agent.stream.start",
|
||
"data": {
|
||
"stream_id": "uuid",
|
||
"project_id": "uuid",
|
||
"chat_id": "uuid",
|
||
"task_id": "uuid",
|
||
"agent_id": "uuid",
|
||
"agent_slug": "string"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Delta стрима (части сообщения):
|
||
```json
|
||
{
|
||
"type": "agent.stream.delta",
|
||
"data": {
|
||
"stream_id": "uuid",
|
||
"delta": "string",
|
||
"agent_id": "uuid",
|
||
"agent_slug": "string"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Инструмент агента:
|
||
```json
|
||
{
|
||
"type": "agent.stream.tool",
|
||
"data": {
|
||
"stream_id": "uuid",
|
||
"tool_name": "string",
|
||
"tool_args": {...},
|
||
"tool_result": {...},
|
||
"agent_id": "uuid",
|
||
"agent_slug": "string"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Конец стрима:
|
||
```json
|
||
{
|
||
"type": "agent.stream.end",
|
||
"data": {
|
||
"stream_id": "uuid",
|
||
"final_message": "string",
|
||
"tool_log": [...],
|
||
"agent_id": "uuid",
|
||
"agent_slug": "string"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Обновление конфигурации агента:
|
||
```json
|
||
{
|
||
"type": "config.updated",
|
||
"data": {
|
||
"model": "string",
|
||
"provider": "string",
|
||
"prompt": "string",
|
||
"chat_listen": "all|mentions|none",
|
||
"task_listen": "all|mentions|assigned|none",
|
||
"max_concurrent_tasks": 2,
|
||
"capabilities": ["string"],
|
||
"labels": ["string"]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Общие события
|
||
|
||
### ACK (подтверждение):
|
||
```json
|
||
{
|
||
"type": "ack"
|
||
}
|
||
```
|
||
|
||
### Ошибка:
|
||
```json
|
||
{
|
||
"type": "error",
|
||
"message": "string"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Фильтрация событий
|
||
|
||
### Для людей и мостов:
|
||
- **Получают ВСЕ события** в подписанных проектах
|
||
- Фильтрация только по подпискам проектов
|
||
|
||
### Для агентов:
|
||
События фильтруются по настройкам `agent_config`:
|
||
|
||
#### Сообщения (`message.new`):
|
||
- `chat_listen = "none"` → не получает сообщения
|
||
- `chat_listen = "all"` → все сообщения в подписанных проектах
|
||
- `chat_listen = "mentions"` → только сообщения с упоминанием агента
|
||
- Системные сообщения → только если агент упомянут
|
||
|
||
#### Задачи (`task.*`):
|
||
- `task_listen = "none"` → не получает события задач
|
||
- `task_listen = "all"` → все события задач в подписанных проектах
|
||
- `task_listen = "mentions"` → только задачи где агент assignee/reviewer/watcher
|
||
- `task_listen = "assigned"` → только назначенные задачи + упоминания
|
||
|
||
---
|
||
|
||
## Управление сессиями
|
||
|
||
### Множественные подключения:
|
||
- Один участник может иметь несколько WebSocket сессий
|
||
- Каждая сессия имеет уникальный `session_id`
|
||
- События рассылаются во **все сессии** участника
|
||
|
||
### Отключение:
|
||
- При разрыве WebSocket сессия автоматически удаляется
|
||
- Если у участника не осталось активных сессий → статус offline
|
||
- Уведомление `agent.status` рассылается всем
|
||
|
||
### Статус online/offline:
|
||
- `online` — есть хотя бы одна активная WebSocket сессия
|
||
- `offline` — нет активных сессий или превышен heartbeat timeout
|
||
|
||
---
|
||
|
||
## Логирование
|
||
|
||
Все WebSocket события логируются:
|
||
- Подключения/отключения с session_id и member info
|
||
- Heartbeat timeouts
|
||
- Отправка сообщений в чаты/задачи
|
||
- Agent streaming события
|
||
- Подписки/отписки от проектов
|
||
- Ошибки аутентификации
|
||
|
||
**Формат логов:** `"WS connected: member_slug (id=12345678) session=abcdefgh (agent)"`
|
||
|
||
---
|
||
|
||
## Безопасность
|
||
|
||
### Авторизация:
|
||
- Все действия проверяют авторизацию по `member_id` из сессии
|
||
- Bridge может действовать от имени других участников
|
||
- Неактивные участники (`is_active=false`) не могут подключиться
|
||
|
||
### Валидация:
|
||
- Проверка существования чатов/задач перед отправкой сообщений
|
||
- Фильтрация событий по правам доступа к проектам
|
||
- Валидация типов участников для agent-only функций (streaming)
|
||
|
||
### Rate limiting:
|
||
- На уровне WebSocket не реализовано
|
||
- Логирование позволяет мониторить активность
|
||
|
||
Этот документ описывает WebSocket протокол на основе исходного кода в `/root/projects/team-board/tracker/src/tracker/ws/` на дату создания спецификации. |