docs: full project specs

This commit is contained in:
Markov 2026-03-13 22:55:59 +01:00
parent cad887daf4
commit 3f46c46466
7 changed files with 3348 additions and 0 deletions

446
specs/AGENTS.md Normal file
View File

@ -0,0 +1,446 @@
# Team Board — AI агентная система
## Назначение
**Picogent** — система для запуска ИИ-агентов, интегрированных с Team Board Tracker. Агенты могут автономно работать с задачами, участвовать в проектных чатах и использовать специальные MCP инструменты для взаимодействия с системой.
**Основа:** Pi Agent Core v0.52.12
**Технологии:** TypeScript, Node.js, WebSocket
---
## Архитектура системы
```
AI Model (Claude/GPT) ↔ Pi Agent Core ↔ MCP Tools ↔ Tracker API/WebSocket
Agent Bootstrap & Memory
```
### Компоненты:
1. **Agent Core** — базовая логика агента на Pi Agent Core
2. **MCP Tools** — набор инструментов для работы с Team Board
3. **Transport Layer** — WebSocket/HTTP связь с Tracker
4. **Bootstrap Context** — начальное состояние и память агента
5. **Memory System** — персистентная память сессий
---
## Конфигурация агента
### Файлы конфигурации:
#### `agent.json` — основная конфигурация:
```json
{
"name": "Coder Agent",
"slug": "coder",
"tracker_url": "http://localhost:8100",
"ws_url": "ws://localhost:8100/ws",
"token": "tb-agent-token-here",
"transport": "ws",
"listen_port": 3200,
"work_dir": "/workspace",
"capabilities": ["coding", "testing"],
"max_concurrent_tasks": 2,
"heartbeat_interval_sec": 30,
"allowed_paths": ["/workspace", "/tmp"],
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
#### `config.json` — LLM конфигурация (переопределяет agent.json):
```json
{
"model": "claude-3-5-sonnet-20241022",
"provider": "anthropic",
"api_key": "sk-ant-...",
"prompt": "Системный промпт агента...",
"max_concurrent_tasks": 3
}
```
### Environment переменные:
```bash
# Tracker подключение
TRACKER_URL=http://localhost:8100
AGENT_TOKEN=tb-agent-token
AGENT_WS_URL=ws://localhost:8100/ws
# LLM настройки
PICOGENT_MODEL=claude-3-5-sonnet-20241022
PICOGENT_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
PICOGENT_API_KEY=sk-ant-...
# Рабочая среда
PICOGENT_WORK_DIR=/workspace
AGENT_TRANSPORT=ws
AGENT_PORT=3200
# Логи
LOG_LEVEL=info
```
---
## Режимы запуска
### Agent Mode (рекомендуемый):
```bash
# Директория агента (agent.json + config.json)
node dist/index.js ./agents/coder/
# Файл конфигурации
node dist/index.js ./agent.json
# Environment переменные
TRACKER_URL=... AGENT_TOKEN=... node dist/index.js
```
### WebSocket Server Mode:
```bash
# Общий режим сервера
node dist/index.js
```
**WebSocket Server** принимает подключения на порту 3100, **Agent Mode** подключается к Tracker как member.
---
## MCP Tools — Инструменты агента
**Файлы:** `src/tools/`
### Task Tools:
- **`list_tasks`** — список задач с фильтрами (project_id, status, assignee_id)
- **`get_task`** — детали задачи по UUID
- **`create_task`** — создание задачи в проекте
- **`update_task`** — обновление полей задачи
- **`take_task`** — взять задачу в работу (атомарно)
- **`reject_task`** — отклонить назначенную задачу с причиной
- **`watch_task`** — подписаться на уведомления по задаче
- **`unwatch_task`** — отписаться от уведомлений
### Step Tools (чеклисты):
- **`list_steps`** — этапы задачи
- **`create_step`** — создать этап
- **`update_step`** — обновить этап (название, статус выполнения)
- **`delete_step`** — удалить этап
### Message Tools:
- **`list_messages`** — сообщения чата/задачи
- **`send_message`** — отправить сообщение в чат проекта
- **`send_task_comment`** — комментарий к задаче
- **`upload_file`** — загрузить файл как вложение
### Project Tools:
- **`list_projects`** — список доступных проектов
- **`get_project`** — информация о проекте
- **`list_project_files`** — файлы проекта
- **`upload_project_file`** — загрузить файл в проект
- **`download_project_file`** — скачать файл проекта
### Member Tools:
- **`list_members`** — участники системы
- **`get_member`** — информация об участнике
- **`list_project_members`** — участники конкретного проекта
### File Tools:
- **`read_file`** — чтение файлов из файловой системы
- **`write_file`** — запись файлов (с проверкой allowed_paths)
- **`list_directory`** — содержимое директории
- **`file_exists`** — проверка существования файла
### Task Links (зависимости):
- **`create_task_link`** — создать зависимость между задачами
- **`list_task_links`** — зависимости задачи
---
## Bootstrap Context
Агент получает стартовую информацию при подключении к Tracker через WebSocket auth.ok:
```json
{
"member_id": "agent-uuid",
"slug": "coder",
"name": "Coder Agent",
"projects": [
{
"id": "project-uuid",
"slug": "team-board",
"name": "Team Board",
"chat_id": "chat-uuid"
}
],
"assigned_tasks": [
{
"id": "task-uuid",
"title": "Fix bug in login",
"status": "todo",
"project_id": "project-uuid"
}
],
"agent_config": {
"model": "claude-3-5-sonnet-20241022",
"provider": "anthropic",
"prompt": "Системный промпт...",
"chat_listen": "mentions",
"task_listen": "assigned",
"max_concurrent_tasks": 2,
"capabilities": ["coding", "testing"],
"labels": ["bug", "feature"]
},
"online": [
{"id": "member-uuid", "slug": "john"}
]
}
```
**Автоматические подписки:** агент подписывается на все доступные проекты
---
## Memory система
### Session ID:
```json
"session_id": "550e8400-e29b-41d4-a716-446655440000"
```
- Генерируется при первом запуске
- Сохраняется в `agent.json`
- Обеспечивает преемственность памяти между перезапусками
### Директории:
```
~/.picogent/sessions/
├── 550e8400-e29b-41d4-a716-446655440000/
│ ├── conversation.json # История диалогов
│ ├── memory.json # Ключевые факты
│ └── context.json # Рабочий контекст
```
### Рабочая директория:
- **`workDir`** — основная директория работы агента
- **`agentHome`** — домашняя директория агента (для config/logs)
- **`allowedPaths`** — ограничения доступа к файловой системе
---
## Transport уровни
### WebSocket Transport (рекомендуемый):
**Файл:** `src/transport/ws-client.ts`
**Особенности:**
- Реальное время получение событий
- Автоматическое переподключение
- Heartbeat поддержание соединения
- Фильтрация событий по подпискам
**Процесс подключения:**
1. WebSocket соединение с токеном агента
2. Аутентификация и получение bootstrap данных
3. Автоподписка на проекты
4. Обработка входящих событий через EventRouter
### HTTP Transport:
**Файл:** `src/transport/http.ts`
**Особенности:**
- HTTP сервер для получения webhook'ов
- Регистрация callback URL в Tracker
- Периодический heartbeat через HTTP
- Подходит для firewall окружений
---
## Event Router
**Файл:** `src/router.ts`
### Обработка событий:
- **`message.new`** — новые сообщения в чатах/задачах
- **`task.created`** — созданные задачи
- **`task.updated`** — изменения задач
- **`task.assigned`** — назначение задач
- **`config.updated`** — обновление конфигурации агента
### Фильтрация:
- По подпискам проектов
- По `chat_listen` режиму (all/mentions/none)
- По `task_listen` режиму (all/mentions/assigned/none)
- По типу участника (humans получают всё, агенты фильтруются)
---
## Безопасность
### Аутентификация:
- Bearer токен агента (tb-xxxxx формат)
- Проверка токена на каждое API обращение
- WebSocket аутентификация при подключении
### Файловая система:
```json
"allowed_paths": ["/workspace", "/tmp"]
```
**Ограничения:**
- Чтение/запись только в разрешённых директориях
- Валидация путей на каждую операцию
- Защита от path traversal атак
### Сетевые ограничения:
- Доступ только к Tracker API
- Валидация URLs в file operations
- Логирование всех внешних запросов
---
## Логирование
**Библиотека:** Pino
**Формат:** JSON structured logging
### Уровни логов:
```json
{
"level": "info",
"time": 1640995200000,
"msg": "Processing message",
"messageId": "msg-123",
"clientId": "agent-456",
"workDir": "/workspace",
"sessionId": "550e8400-..."
}
```
### Ключевые события:
- Agent startup с конфигурацией
- WebSocket подключение/отключение
- Обработка сообщений и вызовы tools
- Ошибки API и network issues
- Memory операции и session management
---
## Capabilities система
### Базовые capabilities:
- **`coding`** — разработка и рефакторинг кода
- **`testing`** — написание и запуск тестов
- **`debugging`** — анализ и исправление багов
- **`documentation`** — создание документации
- **`review`** — code review и анализ
### Label matching:
```json
{
"capabilities": ["coding", "testing"],
"labels": ["bug", "feature", "typescript"]
}
```
**Auto-assign логика:** если в проекте включён auto_assign, задачи с соответствующими лейблами автоматически назначаются на агентов с matching capabilities.
---
## Развёртывание
### Docker:
```dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
CMD ["node", "dist/index.js"]
```
### Systemd Service:
```ini
[Unit]
Description=Picogent Agent
After=network.target
[Service]
Type=simple
User=agent
WorkingDirectory=/opt/picogent
Environment=NODE_ENV=production
ExecStart=/usr/bin/node dist/index.js ./agents/coder/
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### Process Management:
```bash
# Development
npm run dev
# Production build
npm run build
npm start
# Agent mode
node dist/index.js ./agents/coder/
```
---
## Мониторинг и отладка
### Health Check:
- Heartbeat каждые 30 секунд в Tracker
- WebSocket connection status monitoring
- API response time tracking
### Debug режим:
```bash
LOG_LEVEL=debug node dist/index.js
```
### Metrics:
- Количество обработанных сообщений
- Время ответа AI модели
- Успешность выполнения tools
- Memory usage и session persistence
---
## Расширение системы
### Создание новых tools:
```typescript
export function createCustomTools(ctx: ToolContext): ToolDefinition[] {
return [{
name: 'custom_action',
description: 'Custom action description',
parameters: ActionParams,
async execute(id: string, params: any) {
// Implementation
return { content: [{ type: 'text', text: 'Result' }], details: {} };
}
}];
}
```
### Новые transport типы:
- Implement transport interface
- Register в router system
- Add configuration options
### Custom AI providers:
- Extend Pi Agent Core provider system
- Configure в `config.json`
- Test с existing MCP tools
Этот документ описывает AI агентную систему на основе исходного кода в `/root/projects/team-board/picogent/` на дату создания спецификации.

883
specs/API.md Normal file
View File

@ -0,0 +1,883 @@
# Team Board — REST API спецификация
## Общие сведения
**Базовый URL**: `/api/v1/`
**Аутентификация**: Bearer токен в заголовке `Authorization` или query parameter `?token=`
**Формат данных**: JSON
**Middleware**: все API endpoints требуют авторизации (кроме `/api/v1/auth/login`)
### Типы токенов:
- **JWT** — для web пользователей (срок жизни 72 часа)
- **Bearer токены** — для агентов (формат `tb-xxxxx`, постоянные)
### CORS:
- `https://team.uix.su`
- `https://dev.team.uix.su`
- `http://localhost:3100`
---
## Auth — Аутентификация
### `POST /api/v1/auth/login`
Вход в систему для пользователей
**Тело запроса:**
```json
{
"login": "string", // slug или name пользователя
"password": "string"
}
```
**Ответ (200):**
```json
{
"token": "jwt_token_here",
"member_id": "uuid",
"slug": "string",
"role": "owner|admin|member"
}
```
**Ошибки:** `401` - неверный логин/пароль
---
## Members — Участники (люди + агенты)
### `GET /api/v1/members`
Список всех участников
**Query параметры:**
- `include_inactive=boolean` — включить неактивных (по умолчанию false)
**Ответ (200):**
```json
[
{
"id": "uuid",
"name": "string",
"slug": "string",
"type": "human|agent|bridge",
"role": "owner|admin|member",
"status": "online|offline|busy",
"is_active": true,
"avatar_url": "string|null",
"last_seen_at": "iso_timestamp",
"agent_config": {
"capabilities": ["string"],
"labels": ["string"],
"chat_listen": "all|mentions",
"task_listen": "all|mentions",
"prompt": "string|null",
"model": "string|null"
}
}
]
```
### `GET /api/v1/members/{member_id}`
Получить участника по ID
**Ответ (200):** объект Member (как в списке)
**Ошибки:** `404` - участник не найден
### `POST /api/v1/members`
Создать нового участника (человека или агента)
**Тело запроса:**
```json
{
"name": "string",
"slug": "string",
"type": "human|agent", // по умолчанию "agent"
"role": "owner|admin|member", // по умолчанию "member"
"agent_config": { // только для type="agent"
"capabilities": ["string"],
"labels": ["string"],
"chat_listen": "all|mentions",
"task_listen": "all|mentions",
"prompt": "string|null",
"model": "string|null"
}
}
```
**Ответ (201):**
```json
{
"id": "uuid",
"name": "string",
"slug": "string",
"type": "human|agent",
"role": "owner|admin|member",
"token": "tb-xxxxx", // только при создании агента, показывается один раз
"agent_config": {...} // если был передан
}
```
**Ошибки:** `409` - slug уже занят
### `PATCH /api/v1/members/{member_id}`
Обновить участника
**Тело запроса:**
```json
{
"name": "string|null",
"role": "string|null",
"status": "string|null",
"avatar_url": "string|null",
"agent_config": { // только для агентов
"capabilities": ["string"],
"labels": ["string"],
"chat_listen": "all|mentions",
"task_listen": "all|mentions",
"prompt": "string|null",
"model": "string|null"
}
}
```
**Ответ (200):** обновлённый объект Member
**Примечания:**
- Изменение agent_config отправляет WebSocket событие `config.updated` агенту
- При изменении конфигурации агент получает новые настройки в реальном времени
### `POST /api/v1/members/{member_id}/regenerate-token`
Сгенерировать новый токен для агента
**Ответ (200):**
```json
{
"token": "tb-xxxxx"
}
```
**Ошибки:** `400` - только для агентов
### `POST /api/v1/members/{member_id}/revoke-token`
Отозвать токен агента
**Ответ (200):**
```json
{
"ok": true
}
```
**Ошибки:** `400` - только для агентов
### `PATCH /api/v1/members/me/status`
Обновить свой статус (используется агентами)
**Query параметры:**
- `status=string` — новый статус
**Ответ (200):**
```json
{
"status": "online|offline|busy"
}
```
**Примечания:** отправляет WebSocket событие `agent.status` всем подключённым клиентам
### `DELETE /api/v1/members/{member_id}`
Удалить участника (soft delete — is_active=false)
**Авторизация:** только владельцы
**Ответ (200):** `{"ok": true}`
**Ошибки:** `403` - недостаточно прав
---
## Projects — Проекты
### `GET /api/v1/projects`
Список активных проектов
**Ответ (200):**
```json
[
{
"id": "uuid",
"name": "string",
"slug": "string",
"description": "string|null",
"repo_urls": ["string"],
"status": "active|archived",
"task_counter": 42,
"chat_id": "uuid|null",
"auto_assign": true
}
]
```
### `GET /api/v1/projects/{project_id}`
Получить проект по ID
**Ответ (200):** объект Project (как в списке)
**Ошибки:** `404` - проект не найден
### `POST /api/v1/projects`
Создать новый проект
**Тело запроса:**
```json
{
"name": "string",
"slug": "string",
"description": "string|null",
"repo_urls": ["string"]
}
```
**Ответ (201):** объект Project
**Ошибки:** `409` - slug уже занят
**Примечания:**
- Автоматически создаётся основной чат проекта
- Отправляет WebSocket событие `project.created`
### `PATCH /api/v1/projects/{project_id}`
Обновить проект
**Тело запроса:**
```json
{
"name": "string|null",
"slug": "string|null",
"description": "string|null",
"repo_urls": ["string"]|null,
"status": "active|archived|null",
"auto_assign": "boolean|null"
}
```
**Ответ (200):** обновлённый объект Project
**Ошибки:** `409` - slug уже занят
### `DELETE /api/v1/projects/{project_id}`
Удалить проект
**Авторизация:** только владельцы
**Ответ (200):** `{"ok": true}`
**Ошибки:** `403` - недостаточно прав
### `GET /api/v1/projects/{project_id}/members`
Участники проекта
**Ответ (200):**
```json
[
{
"id": "uuid",
"name": "string",
"slug": "string",
"type": "human|agent|bridge",
"role": "owner|admin|member"
}
]
```
### `POST /api/v1/projects/{project_id}/members`
Добавить участника в проект
**Тело запроса:**
```json
{
"member_id": "uuid"
}
```
**Ответ (200):** `{"ok": true}`
**Ошибки:** `404` - участник не найден, `409` - участник уже в проекте
### `DELETE /api/v1/projects/{project_id}/members/{member_id}`
Исключить участника из проекта
**Ответ (200):** `{"ok": true}`
**Ошибки:** `400` - нельзя исключить владельца, `404` - участник не в проекте
---
## Tasks — Задачи
### `GET /api/v1/tasks`
Список задач с фильтрацией
**Query параметры:**
- `project_id=uuid` — фильтр по проекту
- `status=string` — фильтр по статусу (backlog|todo|in_progress|review|done)
- `assignee_id=uuid` — фильтр по исполнителю
- `label=string` — фильтр по лейблу
- `q=string` — поиск по номеру (XX-123) или заголовку
**Ответ (200):**
```json
[
{
"id": "uuid",
"project": {
"id": "uuid",
"slug": "string",
"name": "string"
},
"number": 123,
"key": "XX-123",
"title": "string",
"description": "string|null",
"type": "task|bug|epic",
"status": "backlog|todo|in_progress|review|done",
"priority": "low|medium|high|urgent",
"labels": ["string"],
"assignee": {
"id": "uuid",
"slug": "string",
"name": "string"
} | null,
"reviewer": {...} | null,
"parent": {...} | null,
"subtasks": [...],
"steps": [
{
"id": "uuid",
"title": "string",
"done": true,
"position": 0
}
],
"watcher_ids": ["uuid"],
"depends_on": ["uuid"],
"position": 0,
"created_at": "iso_timestamp",
"updated_at": "iso_timestamp"
}
]
```
### `GET /api/v1/tasks/{task_id}`
Получить задачу по ID
**Ответ (200):** объект Task (как в списке)
**Ошибки:** `404` - задача не найдена
### `POST /api/v1/tasks`
Создать новую задачу
**Query параметры:**
- `project_id=uuid` — ID проекта (обязательно)
**Тело запроса:**
```json
{
"title": "string",
"description": "string|null",
"type": "task|bug|epic", // по умолчанию "task"
"status": "backlog|todo|in_progress|review|done", // по умолчанию "backlog"
"priority": "low|medium|high|urgent", // по умолчанию "medium"
"labels": ["string"],
"parent_id": "uuid|null",
"assignee_id": "uuid|null",
"reviewer_id": "uuid|null",
"depends_on": ["uuid"]
}
```
**Ответ (201):** объект Task
**Примечания:**
- Автоматически назначается номер задачи (инкрементальный счётчик проекта)
- При auto_assign=true проекта может автоматически назначаться на агента по лейблам
- Отправляет WebSocket событие `task.created`
- Создаёт системное сообщение в чате проекта и комментариях задачи
### `PATCH /api/v1/tasks/{task_id}`
Обновить задачу
**Тело запроса:**
```json
{
"title": "string|null",
"description": "string|null",
"type": "string|null",
"status": "string|null",
"priority": "string|null",
"labels": ["string"]|null,
"assignee_id": "string|null",
"reviewer_id": "string|null",
"position": "number|null"
}
```
**Ответ (200):** обновлённый объект Task
**Примечания:**
- Каждое изменение записывается в audit log (TaskAction)
- Отправляет WebSocket событие `task.updated`
- Создаёт системные сообщения для значимых изменений (статус, назначение)
### `DELETE /api/v1/tasks/{task_id}`
Удалить задачу
**Ответ (200):** `{"ok": true}`
**Примечания:** отправляет WebSocket событие `task.deleted`
### `POST /api/v1/tasks/{task_id}/take`
Взять задачу в работу (для агентов)
**Условия:**
- Задача не назначена (assignee_id=null)
- Статус backlog или todo
**Действие:**
- assignee_id = текущий пользователь
- status = in_progress
- Добавление в watcher_ids
**Ответ (200):** обновлённый объект Task
**Ошибки:** `409` - задача уже назначена или неподходящий статус
### `POST /api/v1/tasks/{task_id}/reject`
Отклонить назначенную задачу
**Тело запроса:**
```json
{
"reason": "string"
}
```
**Действие:**
- assignee_id = null
- status = backlog
- Создание системного сообщения с причиной
**Ответ (200):** `{"ok": true, "reason": "string"}`
### `POST /api/v1/tasks/{task_id}/assign`
Назначить задачу на участника
**Тело запроса:**
```json
{
"assignee_id": "uuid"
}
```
**Ответ (200):** обновлённый объект Task
**Ошибки:** `404` - участник не найден
### `POST /api/v1/tasks/{task_id}/watch`
Подписаться на уведомления по задаче
**Ответ (200):**
```json
{
"ok": true,
"watcher_ids": ["uuid"]
}
```
### `DELETE /api/v1/tasks/{task_id}/watch`
Отписаться от уведомлений по задаче
**Ответ (200):**
```json
{
"ok": true,
"watcher_ids": ["uuid"]
}
```
### `POST /api/v1/tasks/{task_id}/links`
Создать связь между задачами (зависимость)
**Тело запроса:**
```json
{
"target_id": "uuid",
"link_type": "blocks|depends_on|relates_to"
}
```
**Ответ (201):**
```json
{
"id": "uuid",
"source_id": "uuid",
"target_id": "uuid",
"link_type": "string",
"target_key": "XX-123",
"target_title": "string",
"source_key": "XX-124",
"source_title": "string"
}
```
**Ошибки:** `400` - нельзя связать задачу с самой собой, `409` - связь уже существует
---
## Steps — Этапы задач (чеклист)
### `GET /api/v1/tasks/{task_id}/steps`
Список этапов задачи
**Ответ (200):**
```json
[
{
"id": "uuid",
"title": "string",
"done": true,
"position": 0
}
]
```
### `POST /api/v1/tasks/{task_id}/steps`
Создать новый этап
**Тело запроса:**
```json
{
"title": "string"
}
```
**Ответ (201):** объект Step
**Ошибки:** `404` - задача не найдена
### `PATCH /api/v1/tasks/{task_id}/steps/{step_id}`
Обновить этап
**Тело запроса:**
```json
{
"title": "string|null",
"done": "boolean|null"
}
```
**Ответ (200):** обновлённый объект Step
### `DELETE /api/v1/tasks/{task_id}/steps/{step_id}`
Удалить этап
**Ответ (200):** `{"ok": true}`
---
## Messages — Сообщения (чаты + комментарии к задачам)
### `GET /api/v1/messages`
Список сообщений
**Query параметры:**
- `chat_id=uuid` — сообщения из чата
- `task_id=uuid` — комментарии к задаче
- `parent_id=uuid` — ответы в треде
- `limit=number` — лимит (по умолчанию 50, макс 200)
- `offset=number` — смещение (по умолчанию 0)
**Ответ (200):**
```json
[
{
"id": "uuid",
"chat_id": "uuid|null",
"task_id": "uuid|null",
"parent_id": "uuid|null",
"author_type": "human|agent|system",
"author_id": "uuid|null",
"author": {
"id": "uuid",
"slug": "string",
"name": "string"
} | null,
"actor": {...} | null, // для системных сообщений
"content": "string",
"thinking": "string|null",
"mentions": [
{
"id": "uuid",
"slug": "string",
"name": "string"
}
],
"voice_url": "string|null",
"attachments": [
{
"id": "uuid",
"filename": "string",
"mime_type": "string",
"size": 1024
}
],
"created_at": "iso_timestamp"
}
]
```
**Примечания:** возвращает последние N сообщений в хронологическом порядке
### `POST /api/v1/messages`
Отправить сообщение
**Тело запроса:**
```json
{
"chat_id": "uuid|null", // либо chat_id, либо task_id обязательно
"task_id": "uuid|null",
"parent_id": "uuid|null", // для ответов в треде
"content": "string",
"thinking": "string|null", // внутренние размышления (для агентов)
"mentions": ["member_id"], // упоминания по ID
"voice_url": "string|null", // ссылка на голосовое сообщение
"attachments": [ // прикреплённые файлы из /upload
{
"file_id": "uuid",
"filename": "string",
"mime_type": "string",
"size": 1024,
"storage_name": "string"
}
]
}
```
**Ответ (201):** объект Message
**Ошибки:** `400` - не указан chat_id или task_id, `401` - не авторизован
**Примечания:**
- author_id берётся из токена авторизации
- Отправляет WebSocket событие `message.new`
- Для project чатов событие фильтруется по подпискам участников
### `GET /api/v1/messages/{message_id}/replies`
Ответы в треде
**Ответ (200):** массив объектов Message (отсортированы по created_at)
---
## Attachments — Файловые вложения
### `POST /api/v1/upload`
Загрузить файл
**Content-Type:** `multipart/form-data`
**Поля:**
- `file` — файловое поле
**Ответ (200):**
```json
{
"file_id": "uuid",
"filename": "string",
"mime_type": "string",
"size": 1024,
"storage_name": "string"
}
```
**Ошибки:** `413` - файл слишком большой (лимит 50MB)
**Примечания:**
- Файл сохраняется в `/data/uploads/`
- Возвращаемые данные используются в attachments при создании сообщений
- Файл пока не привязан к сообщению
### `GET /api/v1/attachments/{attachment_id}/download`
Скачать файл
**Query параметры:**
- `token=string` — токен авторизации (опционально, для img src)
**Ответ (200):** файл с правильными заголовками
**Ошибки:** `404` - файл не найден
---
## Project Files — Файлы проектов
### `GET /api/v1/projects/{project_id}/files`
Список файлов проекта
**Query параметры:**
- `search=string` — поиск по имени файла
**Ответ (200):**
```json
[
{
"id": "uuid",
"filename": "string",
"description": "string|null",
"mime_type": "string",
"size": 1024,
"uploaded_by": {
"id": "uuid",
"slug": "string",
"name": "string"
},
"created_at": "iso_timestamp",
"updated_at": "iso_timestamp"
}
]
```
### `POST /api/v1/projects/{project_id}/files`
Загрузить файл в проект
**Content-Type:** `multipart/form-data`
**Поля:**
- `file` — файловое поле
- `description` — описание файла (опционально)
**Ответ (201):** объект ProjectFile
**Ошибки:** `413` - файл слишком большой, `400` - некорректное имя файла
**Примечания:**
- Файлы сохраняются в `/data/projects/{project_slug}/`
- При загрузке файла с существующим именем — перезаписывается
### `GET /api/v1/projects/{project_id}/files/{file_id}`
Информация о файле
**Ответ (200):** объект ProjectFile
### `GET /api/v1/projects/{project_id}/files/{file_id}/download`
Скачать файл проекта
**Ответ (200):** файл с правильными заголовками
**Ошибки:** `404` - файл не найден
### `PATCH /api/v1/projects/{project_id}/files/{file_id}`
Обновить описание файла
**Тело запроса:**
```json
{
"description": "string|null"
}
```
**Ответ (200):** обновлённый объект ProjectFile
### `DELETE /api/v1/projects/{project_id}/files/{file_id}`
Удалить файл проекта
**Ответ (200):** `{"ok": true}`
---
## Labels — Глобальные лейблы
### `GET /api/v1/labels`
Список всех лейблов
**Ответ (200):**
```json
[
{
"id": "uuid",
"name": "string",
"color": "#6366f1"
}
]
```
### `POST /api/v1/labels`
Создать новый лейбл
**Тело запроса:**
```json
{
"name": "string",
"color": "#6366f1" // по умолчанию
}
```
**Ответ (201):** объект Label
### `PATCH /api/v1/labels/{label_id}`
Обновить лейбл
**Тело запроса:**
```json
{
"name": "string|null",
"color": "string|null"
}
```
**Ответ (200):** обновлённый объект Label
### `DELETE /api/v1/labels/{label_id}`
Удалить лейбл
**Ответ (200):** `{"ok": true}`
### `POST /api/v1/tasks/{task_id}/labels/{label_id}`
Добавить лейбл к задаче
**Ответ (200):** `{"ok": true}`
### `DELETE /api/v1/tasks/{task_id}/labels/{label_id}`
Убрать лейбл с задачи
**Ответ (200):** `{"ok": true}`
---
## Utility Endpoints
### `GET /health`
Health check
**Авторизация:** не требуется
**Ответ (200):**
```json
{
"status": "ok",
"version": "0.2.0"
}
```
### `GET /docs`
Swagger UI документация
**Авторизация:** не требуется
**Доступность:** только в dev режиме
---
## Коды ошибок
- `400` — Bad Request (некорректные данные)
- `401` — Unauthorized (не авторизован или неверный токен)
- `403` — Forbidden (недостаточно прав)
- `404` — Not Found (ресурс не найден)
- `409` — Conflict (конфликт данных, например дублирование slug)
- `413` — Payload Too Large (файл слишком большой)
- `500` — Internal Server Error (внутренняя ошибка сервера)
## Middleware и логирование
**Логирование запросов:** все HTTP запросы логируются с телом (для POST/PUT/PATCH), временем выполнения и кодом ответа
**Извлечение участника:** middleware автоматически извлекает Member из JWT/agent token и сохраняет в `request.state.member`
**CORS:** автоматически обрабатывается для разрешённых origins
Этот документ описывает API на основе исходного кода в `/root/projects/team-board/tracker/src/tracker/api/` на дату создания спецификации.
### Обновление slug (добавлено 2026-03-13)
`PATCH /api/v1/members/{member_id}` теперь принимает поле `slug`.
- Валидация: slug уникален, при конфликте → 409
- Формат: lowercase, только `[a-z0-9_-]`

400
specs/BRIDGE.md Normal file
View File

@ -0,0 +1,400 @@
# Team Board — Telegram Bridge
## Назначение
**Telegram Bridge** — микросервис для интеграции Team Board с Telegram. Обеспечивает двустороннюю синхронизацию сообщений между проектными чатами Tracker и топиками Telegram группы.
**Основной файл:** `bridge.py`
**Архитектура:** Standalone Python сервис
---
## Архитектура
```
Telegram Bot API ↔ Bridge ↔ Tracker WebSocket
Topic Mapping
(JSON файл хранение)
```
### Компоненты:
1. **bridge.py** — основной сервис с двусторонней синхронизацией
2. **config.py** — конфигурация через environment переменные
3. **topic_map.py** — персистентное маппинг Telegram топик ↔ Tracker проект
4. **tracker_client.py** — WebSocket клиент для подключения к Tracker
---
## Конфигурация
**Файл:** `.env`
```bash
# Telegram Bot
TELEGRAM_BOT_TOKEN=1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ
TELEGRAM_GROUP_ID=-1001234567890
# Tracker
TRACKER_URL=https://dev.team.uix.su
TRACKER_WS_URL=wss://dev.team.uix.su/ws
BRIDGE_TOKEN=tb-bridge-dev-token
```
### Переменные окружения:
- **TELEGRAM_BOT_TOKEN** — токен бота от @BotFather
- **TELEGRAM_GROUP_ID** — ID группы с топиками (отрицательное число)
- **TRACKER_URL** — HTTP URL Tracker API
- **TRACKER_WS_URL** — WebSocket URL для подключения к Tracker
- **BRIDGE_TOKEN** — Bearer токен агента типа "bridge" в Tracker
---
## Маппинг топиков
**Файл:** `data/topic_map.json`
### Структура:
```json
{
"123": "550e8400-e29b-41d4-a716-446655440000",
"456": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
```
**Формат:** `{telegram_topic_id: tracker_project_uuid}`
### Класс TopicMap:
```python
class TopicMap:
def set(topic_id: int, project_uuid: str) # создать маппинг
def get_project(topic_id: int) -> str | None # получить проект по топику
def get_topic(project_uuid: str) -> int | None # получить топик по проекту
def remove_by_topic(topic_id: int) # удалить маппинг
def all() -> dict[int, str] # все маппинги
```
**Персистентность:** автоматическое сохранение в JSON файл при изменениях
---
## Telegram → Tracker
### Обработка сообщений:
```python
async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Фильтрация по группе и наличию topic
if message.chat_id != config.group_id or not message.message_thread_id:
return
# Поиск соответствующего проекта
project_uuid = topic_map.get_project(message.message_thread_id)
if not project_uuid:
return
# Формат сообщения: [Имя пользователя] текст
text = f"[{user.full_name}] {message.text}"
await tracker.send_message(project_uuid, text)
```
### Telegram команды:
#### `/link <project_slug>`
Привязать текущий топик к проекту
**Использование:**
```
/link team-board
```
**Логика:**
1. Проверка, что команда выполнена в топике
2. Поиск проекта по slug через Tracker API
3. Создание маппинга в TopicMap
4. Подтверждение в чате
#### `/unlink`
Отвязать текущий топик от проекта
**Логика:**
1. Проверка выполнения в топике
2. Удаление маппинга из TopicMap
3. Подтверждение в чате
#### `/bridge_status`
Показать статус bridge и все активные маппинги
**Пример ответа:**
```
Привязки топик → проект:
• topic 123 → 550e8400...
• topic 456 → 6ba7b810...
```
---
## Tracker → Telegram
### Поддерживаемые события:
#### `message.new`
Новое сообщение в проектном чате
**Фильтрация:**
- Пропуск сообщений от самого bridge (избежание эха)
- Пропуск сообщений с префиксом `[Username]` (отправленных bridge'ом)
**Формат в Telegram:**
```
[Автор]: текст сообщения
```
**Silent режим:** если есть онлайн участники-люди в web интерфейсе
#### `task.created`
Создание новой задачи
**Формат в Telegram:**
```
📋 Новая задача XX-123: Название задачи
```
#### `project.created`
Создание нового проекта
**Автоматическое действие:**
1. Создание топика в Telegram с именем проекта
2. Автоматический маппинг топик → проект
3. Уведомление о привязке
#### `agent.status`
Изменение статуса участника
**Отслеживание:** обновление множества онлайн участников для silent режима
---
## TrackerClient
**Файл:** `tracker_client.py`
### WebSocket подключение:
```python
class TrackerClient:
async def connect() # подключение с авто-переподключением
async def send_message(project_uuid, text) # отправка сообщения в проект
def stop() # остановка клиента
```
### Особенности:
- **Auto-reconnect:** переподключение каждые 5 секунд при разрыве
- **Heartbeat:** отправка heartbeat каждые 25 секунд
- **Queue система:** очередь сообщений до успешного подключения
- **Project caching:** кэширование project_uuid → chat_id маппингов
### Аутентификация:
```python
url = f"{config.tracker_ws_url}?token={config.bridge_token}"
```
**Тип участника:** `bridge` в Tracker системе
---
## Работа с проектами
### Кэширование chat_id:
```python
self._project_chat_ids: dict[str, str] = {} # project_uuid -> chat_id
```
**Загрузка при подключении:**
```python
GET /api/v1/projects
Authorization: Bearer {bridge_token}
```
**Lazy loading:** автоматическая подгрузка chat_id при необходимости
### Отправка сообщений:
1. Получение chat_id для проекта
2. Формирование WebSocket сообщения:
```json
{
"type": "chat.send",
"chat_id": "project_chat_uuid",
"content": "[Username] message text"
}
```
---
## Silent режим
**Логика:** если в системе есть онлайн участники-люди (не агенты/bridge), уведомления в Telegram отправляются беззвучно
**Отслеживание онлайн:**
```python
_online_members: set[str] = set() # member slugs
# При получении agent.status события:
if status == "online":
_online_members.add(slug)
else:
_online_members.discard(slug)
# При отправке в Telegram:
silent = any(s for s in _online_members if s not in ("coder", "architect", "bridge"))
```
**Исключения:** агенты `coder`, `architect`, `bridge` не считаются "людьми"
---
## HTML Escaping
Все сообщения в Telegram экранируются для HTML:
```python
def _escape_html(text: str) -> str:
return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
```
**Формат отправки:** `parse_mode="HTML"`
---
## Предотвращение эха
### Проблема:
Bridge отправляет сообщение в Tracker → Tracker рассылает `message.new` event → Bridge получает и отправляет в Telegram → возможное дублирование
### Решения:
#### 1. Пропуск сообщений от bridge:
```python
author_slug = author.get("slug", "")
if author_slug == "bridge":
return
```
#### 2. Пропуск сообщений с Telegram префиксом:
```python
if text_content.startswith("[") and "] " in text_content[:50]:
return
```
#### 3. Исключение собственной сессии в WebSocket:
Tracker автоматически не отправляет события отправителю
---
## Логирование
**Уровень:** INFO
**Формат:** `%(asctime)s [%(name)s] %(levelname)s: %(message)s`
### Ключевые логи:
```
Connected to Tracker WS
Authenticated as bridge
TG->Tracker: topic=123 project=550e8400
Auto-created topic 456 for project team-board (6ba7b810)
Linked topic 123 -> project team-board (550e8400)
```
---
## Запуск и развёртывание
### Зависимости:
```txt
python-telegram-bot[webhooks]
websockets
httpx
python-dotenv
```
**Файл:** `requirements.txt`
### Запуск:
```bash
python bridge.py
```
### Архитектура событий:
```python
async def main():
# Запуск Telegram polling
await app.updater.start_polling()
# Запуск Tracker WebSocket (блокирует)
await tracker.connect()
```
**Concurrent execution:** Telegram bot и Tracker WebSocket клиент работают параллельно
---
## Обработка ошибок
### Telegram API:
```python
try:
await _bot.send_message(...)
except Exception as e:
logger.error("Failed to send to topic %d: %s", topic_id, e)
```
### Tracker WebSocket:
```python
try:
# WebSocket операции
except websockets.ConnectionClosed:
logger.warning("Tracker WS closed, reconnecting in 5s...")
await asyncio.sleep(5)
```
### API запросы:
```python
try:
async with httpx.AsyncClient() as client:
resp = await client.get(...)
if resp.status_code != 200:
return None
except Exception as e:
logger.error("API error: %s", e)
```
**Стратегия:** логирование ошибок + graceful degradation без остановки сервиса
---
## Масштабирование
### Ограничения:
- Один bridge instance на Telegram группу
- JSON файл для маппингов (не подходит для кластера)
- In-memory кэш проектов
### Потенциальные улучшения:
- Redis для маппингов и кэша
- Database-backed конфигурация
- Multiple bot support
- Rate limiting для Telegram API
---
## Безопасность
### Аутентификация:
- Bridge токен для Tracker API/WebSocket
- Фильтрация по group_id в Telegram
- Только авторизованные команды
### Данные:
- Маппинги хранятся локально в JSON
- Логирование без чувствительной информации
- Автоматическое переподключение при разрыве
Этот документ описывает Telegram Bridge на основе исходного кода в `/root/projects/team-board/bridge/` на дату создания спецификации.

452
specs/FRONTEND.md Normal file
View File

@ -0,0 +1,452 @@
# Team Board — Frontend спецификация
## Общие сведения
**Технологии:**
- React 19.2.0
- TypeScript (~5.9.3)
- Vite 7.3.1 (bundler + dev server)
- React Router DOM 7.13.1 (роутинг)
- Tailwind CSS 4.2.1 (стили)
**Файл конфигурации:** `package.json`
**Архитектура:** SPA (Single Page Application)
---
## Структура проекта
```
src/
├── App.tsx # Главный компонент с роутингом
├── pages/ # Страницы приложения
│ ├── HomePage.tsx # Главная страница (список проектов)
│ ├── LoginPage.tsx # Страница входа
│ ├── ProjectPage.tsx # Страница проекта (канбан + чат)
│ ├── CreateProjectPage.tsx # Создание проекта
│ └── settings/ # Настройки
│ ├── SettingsLayout.tsx # Layout для настроек
│ ├── SettingsPage.tsx # Общие настройки
│ ├── LabelsPage.tsx # Управление лейблами
│ └── AgentsPage.tsx # Управление агентами
├── components/ # Переиспользуемые компоненты
│ ├── AuthGuard.tsx # Защита роутов авторизацией
│ ├── Sidebar.tsx # Боковая панель навигации
│ ├── KanbanBoard.tsx # Канбан доска задач
│ ├── TaskModal.tsx # Модальное окно задачи
│ ├── CreateTaskModal.tsx # Создание задачи
│ ├── CreateProjectModal.tsx # Создание проекта
│ ├── CreateAgentModal.tsx # Создание агента
│ ├── AgentModal.tsx # Просмотр/редактирование агента
│ ├── ProjectSettings.tsx # Настройки проекта
│ ├── ProjectFiles.tsx # Файлы проекта
│ └── ChatPanel.tsx # Панель чата
├── lib/ # Библиотеки и утилиты
│ ├── api.ts # REST API клиент
│ ├── ws.ts # WebSocket клиент
│ └── auth-client.ts # Авторизация
└── assets/ # Статические ресурсы
```
---
## Роуты и страницы
### Публичные роуты:
- `/login`**LoginPage** — вход в систему
### Защищённые роуты (AuthGuard):
- `/`**HomePage** — список проектов
- `/projects/:id`**ProjectPage** — страница проекта с канбаном и чатом
- `/projects/new`**CreateProjectPage** — создание нового проекта
- `/settings`**SettingsLayout** — настройки (nested routes)
- `/settings`**SettingsPage** — общие настройки
- `/settings/labels`**LabelsPage** — управление лейблами
- `/settings/agents`**AgentsPage** — управление агентами
### Роутинг:
```tsx
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/" element={<AuthGuard><HomePage /></AuthGuard>} />
<Route path="/projects/:id" element={<AuthGuard><ProjectPage /></AuthGuard>} />
<Route path="/projects/new" element={<AuthGuard><CreateProjectPage /></AuthGuard>} />
<Route path="/settings" element={<AuthGuard><SettingsLayout /></AuthGuard>}>
<Route index element={<SettingsPage />} />
<Route path="labels" element={<LabelsPage />} />
<Route path="agents" element={<AgentsPage />} />
</Route>
</Routes>
</BrowserRouter>
```
---
## Основные компоненты
### AuthGuard
**Файл:** `components/AuthGuard.tsx`
**Назначение:** проверяет авторизацию, перенаправляет на /login если токена нет
### KanbanBoard
**Файл:** `components/KanbanBoard.tsx`
**Назначение:** канбан доска с drag & drop задач между колонками
**Колонки:**
- Backlog (#737373)
- TODO (#3b82f6)
- In Progress (#f59e0b)
- Review (#a855f7)
- Done (#22c55e)
**Функции:**
- Drag & drop задач между статусами
- Фильтрация по исполнителю/лейблам
- Real-time обновления через WebSocket
- Создание задач в определённой колонке
### TaskModal
**Файл:** `components/TaskModal.tsx`
**Назначение:** полное отображение и редактирование задачи
**Секции:**
- Заголовок и описание
- Метаданные (статус, приоритет, лейблы, исполнитель)
- Steps (чеклист этапов)
- Комментарии и обсуждения
- Файловые вложения
### ChatPanel
**Файл:** `components/ChatPanel.tsx`
**Назначение:** панель чата проекта с real-time сообщениями
**Функции:**
- Отправка сообщений
- Упоминания участников (@username)
- Загрузка файлов
- Real-time обновления через WebSocket
### Sidebar
**Файл:** `components/Sidebar.tsx`
**Назначение:** боковая панель навигации
**Элементы:**
- Список проектов
- Общий чат (Lobby)
- Настройки
- Онлайн участники
---
## State Management
**Подход:** Локальный state с React hooks
**Глобального стора нет** — каждый компонент управляет своим состоянием
### Паттерны управления состоянием:
#### Загрузка данных:
```tsx
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function load() {
try {
const result = await apiCall();
setData(result);
} finally {
setLoading(false);
}
}
load();
}, [dependency]);
```
#### Real-time обновления:
```tsx
useEffect(() => {
const unsubscribe = wsClient.on("event.type", (data) => {
setItems(prev => prev.map(item =>
item.id === data.id ? data : item
));
});
return unsubscribe;
}, []);
```
#### Оптимистичные обновления:
```tsx
const handleUpdate = async (id: string, changes: Partial<T>) => {
// Оптимистично обновляем UI
setItems(prev => prev.map(item =>
item.id === id ? {...item, ...changes} : item
));
try {
await updateItem(id, changes);
} catch {
// Откат в случае ошибки
loadItems();
}
};
```
---
## API интеграция
**Файл:** `lib/api.ts`
**Базовый URL:** определяется через `VITE_API_URL` environment variable
### HTTP клиент:
```typescript
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = getToken();
const headers = {
"Content-Type": "application/json",
...(token && { "Authorization": `Bearer ${token}` })
};
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
if (!res.ok) {
if (res.status === 401) {
localStorage.removeItem("tb_token");
window.location.href = "/login";
}
throw new Error(await res.json().error);
}
return res.json();
}
```
### API методы по категориям:
#### Auth:
- `login(login, password)` — вход в систему
#### Projects:
- `getProjects()` — список проектов
- `getProject(id)` — получить проект
- `createProject(data)` — создать проект
- `updateProject(id, data)` — обновить проект
- `deleteProject(id)` — удалить проект
#### Tasks:
- `getTasks(projectId)` — задачи проекта
- `getTask(id)` — получить задачу
- `createTask(projectId, data)` — создать задачу
- `updateTask(id, data)` — обновить задачу
- `deleteTask(id)` — удалить задачу
- `takeTask(id)` — взять задачу (для агентов)
- `rejectTask(id, reason)` — отклонить задачу
- `assignTask(id, assigneeId)` — назначить задачу
- `watchTask(id)` / `unwatchTask(id)` — подписки на задачу
#### Steps:
- `getTaskSteps(taskId)` — этапы задачи
- `createTaskStep(taskId, data)` — создать этап
- `updateTaskStep(taskId, stepId, data)` — обновить этап
- `deleteTaskStep(taskId, stepId)` — удалить этап
#### Messages:
- `getMessages(params)` — сообщения чата/задачи
- `createMessage(data)` — отправить сообщение
#### Members:
- `getMembers()` — список участников
- `getMember(id)` — получить участника
- `createMember(data)` — создать участника/агента
- `updateMember(id, data)` — обновить участника
- `regenerateMemberToken(id)` — новый токен агента
- `revokeMemberToken(id)` — отозвать токен
#### Labels:
- `getLabels()` — глобальные лейблы
- `createLabel(data)` — создать лейбл
- `updateLabel(id, data)` — обновить лейбл
- `deleteLabel(id)` — удалить лейбл
#### Files:
- `uploadFile(file)` — загрузить временный файл
- `downloadAttachment(id)` — скачать вложение
- `getProjectFiles(projectId)` — файлы проекта
- `uploadProjectFile(projectId, file, description)` — загрузить файл проекта
- `deleteProjectFile(projectId, fileId)` — удалить файл проекта
---
## WebSocket интеграция
**Файл:** `lib/ws.ts`
**URL:** определяется через `VITE_WS_URL` environment variable
### WSClient класс:
```typescript
class WSClient {
connect() // подключение с токеном из localStorage
disconnect() // отключение
on(type, handler) // подписка на события
send(data) // отправка сообщения
// Convenience методы:
subscribeProject(projectId)
unsubscribeProject(projectId)
sendChat(chatId, content, mentions)
sendTaskComment(taskId, content, mentions)
heartbeat(status)
}
```
### Обработка событий:
```typescript
useEffect(() => {
const unsubscribe = wsClient.on("message.new", (message) => {
setMessages(prev => [...prev, message]);
});
return unsubscribe;
}, []);
```
### Поддерживаемые события:
- `auth.ok` — успешная авторизация
- `auth.error` — ошибка авторизации
- `message.new` — новое сообщение
- `task.created` — задача создана
- `task.updated` — задача обновлена
- `task.assigned` — задача назначена
- `task.deleted` — задача удалена
- `agent.status` — изменение статуса участника
- `agent.stream.*` — streaming от агентов
### Auto-reconnect:
- При разрыве соединения автоматический переподключение через 3 секунды
- Heartbeat каждые 30 секунд для поддержания соединения
- Очередь сообщений до подключения и авторизации
---
## Авторизация
**Файл:** `lib/auth-client.ts`
### Токены:
- Хранение: `localStorage.getItem("tb_token")`
- Формат: JWT токен
- Автоматическое добавление в заголовки API запросов
- Автоматический redirect на /login при 401 ошибке
### AuthGuard компонент:
```tsx
function AuthGuard({ children }: { children: React.ReactNode }) {
const token = localStorage.getItem("tb_token");
if (!token) return <Navigate to="/login" />;
return <>{children}</>;
}
```
---
## Стили и UI
### Tailwind CSS 4.2.1:
- Utility-first подход
- CSS переменные для темизации
- Responsive дизайн
### Цветовая схема:
```css
:root {
--background: #ffffff;
--foreground: #0f0f0f;
--muted: #6b7280;
--border: #e5e7eb;
--primary: #3b82f6;
--destructive: #ef4444;
}
```
### Компоненты UI:
- Модальные окна
- Drag & drop интерфейсы
- Kanban колонки
- Формы с валидацией
- Файл загрузчики
- Chat интерфейс
---
## Environment переменные
**Файл:** `.env`
```bash
VITE_API_URL=http://localhost:8100 # Базовый URL REST API
VITE_WS_URL=ws://localhost:8100 # Базовый URL WebSocket
```
**Использование:**
```typescript
const API_BASE = import.meta.env.VITE_API_URL!;
const WS_BASE = import.meta.env.VITE_WS_URL!;
```
---
## Типизация
### TypeScript конфигурация:
- Строгий режим
- Path mapping (`@/` → `src/`)
- DOM типы
### API типы:
Все типы синхронизированы с backend Pydantic схемами:
- `Member`, `AgentConfig`, `MemberBrief`
- `Project`, `ProjectMember`
- `Task`, `Step`, `SubtaskBrief`
- `Message`, `Attachment`
- `Label`
---
## Производительность
### Оптимизации:
- Lazy loading компонентов
- Мемоизация через React.memo для тяжёлых компонентов
- Дебаунсинг поисковых запросов
- Пагинация сообщений чата
- Оптимистичные обновления UI
### Bundle размер:
- Tree shaking неиспользуемого кода
- Code splitting по роутам
- Минификация в production
---
## Особенности архитектуры
### Real-time синхронизация:
- WebSocket для мгновенных обновлений
- Оптимистичные обновления с откатом при ошибках
- Автоматическая подписка на события проекта
### Drag & Drop:
- Нативный HTML5 drag & drop API
- Визуальная обратная связь при перетаскивании
- Оптимистичное обновление с откатом
### Файловая система:
- Поддержка загрузки файлов через drag & drop
- Preview для изображений
- Скачивание файлов через API
### Offline-first:
- Кэширование в localStorage (токен авторизации)
- Graceful degradation при отсутствии сети
- Очередь WebSocket сообщений
Этот документ описывает фронтенд на основе исходного кода в `/root/projects/team-board/web-client-vite/src/` на дату создания спецификации.

543
specs/MODELS.md Normal file
View File

@ -0,0 +1,543 @@
# Team Board — Модели данных
## Базовая модель (Base)
**Таблица:** все таблицы наследуются от Base
**Файл:** `base.py`
### Общие поля:
```python
id: UUID # Primary key, автогенерируемый UUID4
created_at: TIMESTAMP WITH TIMEZONE # Автоматически при создании
updated_at: TIMESTAMP WITH TIMEZONE # Автоматически при обновлении
```
---
## Members — Участники системы
**Таблица:** `members`
**Файл:** `member.py`
### Поля:
```python
id: UUID # Primary key
name: str # Отображаемое имя
slug: str # Уникальный идентификатор (URL-safe)
type: str # human | agent | bridge
role: str # owner | member | observer | bridge
auth_method: str # password | oauth | token
password_hash: str? # Bcrypt hash (только для type=human)
token: str? # Bearer токен (только для type=agent/bridge)
status: str # online | offline | busy
avatar_url: str? # Ссылка на аватар
is_active: bool # Флаг активности (soft delete)
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `agent_config`**AgentConfig** (1:1, optional)
- `project_memberships`**ProjectMember[]** (1:many)
### Индексы:
- `UNIQUE (slug)`
- `UNIQUE (token)` где `token IS NOT NULL`
---
## AgentConfig — Конфигурация агентов
**Таблица:** `agent_configs`
**Файл:** `member.py`
### Поля:
```python
id: UUID # Primary key
member_id: UUID # FK → members.id (UNIQUE)
capabilities: str[] # Массив возможностей агента
labels: str[] # Лейблы для auto-assign (соответствие с задачами)
chat_listen: str # all | mentions | none
task_listen: str # all | mentions | none
prompt: text? # Системный промпт
model: str? # Модель ИИ (gpt-4, claude-3, etc)
provider: str? # Провайдер (openai, anthropic, etc)
max_concurrent_tasks: int # Лимит одновременных задач (по умолчанию 2)
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `member`**Member** (1:1)
---
## Projects — Проекты
**Таблица:** `projects`
**Файл:** `project.py`
### Поля:
```python
id: UUID # Primary key
name: str # Название проекта
slug: str # Уникальный идентификатор (для URL и префиксов задач)
description: text? # Описание проекта
repo_urls: str[] # Массив ссылок на репозитории
status: str # active | archived | paused
task_counter: int # Счётчик задач для генерации номеров (инкрементальный)
auto_assign: bool # Включена ли автоназначение задач по лейблам
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `tasks`**Task[]** (1:many, CASCADE DELETE)
- `chats`**Chat[]** (1:many, CASCADE DELETE)
- `members`**ProjectMember[]** (1:many, CASCADE DELETE)
### Индексы:
- `UNIQUE (slug)`
---
## ProjectMember — Участники проектов
**Таблица:** `project_members`
**Файл:** `project.py`
### Поля:
```python
id: UUID # Primary key
project_id: UUID # FK → projects.id (CASCADE DELETE)
member_id: UUID # FK → members.id (CASCADE DELETE)
role: str # owner | member
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `project`**Project** (many:1)
- `member`**Member** (many:1)
### Ограничения:
- `UNIQUE (project_id, member_id)` — один участник не может быть дважды в проекте
---
## Tasks — Задачи
**Таблица:** `tasks`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
project_id: UUID # FK → projects.id
parent_id: UUID? # FK → tasks.id (для иерархии: эпик → история → задача)
number: int # Номер в рамках проекта (XX-1, XX-2, XX-3)
title: str # Заголовок задачи
description: text? # Детальное описание
type: str # task | bug | feature
status: str # backlog | todo | in_progress | in_review | done
priority: str # critical | high | medium | low
labels: str[] # Массив лейблов (для фильтрации и auto-assign)
assignee_id: UUID? # FK → members.id (исполнитель)
reviewer_id: UUID? # FK → members.id (ревьюер)
watcher_ids: UUID[] # Массив ID участников, подписанных на уведомления
depends_on: UUID[] # Массив ID задач-зависимостей
position: int # Позиция в колонке канбана
time_spent: int # Потраченное время в минутах
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `project`**Project** (many:1)
- `assignee`**Member** (many:1, optional)
- `reviewer`**Member** (many:1, optional)
- `parent`**Task** (many:1, self-reference)
- `subtasks`**Task[]** (1:many, self-reference)
- `steps`**Step[]** (1:many, CASCADE DELETE, ordered by position)
---
## Steps — Этапы задач (чеклист)
**Таблица:** `steps`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
task_id: UUID # FK → tasks.id (CASCADE DELETE)
title: str # Название этапа
done: bool # Выполнен ли (по умолчанию false)
position: int # Порядок в списке
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `task`**Task** (many:1)
---
## Labels — Глобальные лейблы
**Таблица:** `labels`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
name: str # Название лейбла
color: str # HEX цвет (по умолчанию "#6366f1")
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Индексы:
- `UNIQUE (name)`
---
## TaskLabel — Связи задач и лейблов
**Таблица:** `task_labels`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
task_id: UUID # FK → tasks.id (CASCADE DELETE)
label_id: UUID # FK → labels.id (CASCADE DELETE)
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- Связывает **Task** и **Label** (many:many)
---
## TaskLink — Зависимости между задачами
**Таблица:** `task_links`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
source_id: UUID # FK → tasks.id (CASCADE DELETE) - исходная задача
target_id: UUID # FK → tasks.id (CASCADE DELETE) - целевая задача
link_type: str # blocks | depends_on | relates_to
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `source`**Task** (many:1)
- `target`**Task** (many:1)
### Типы связей:
- `blocks` — source блокирует target (target заблокирована source)
- `depends_on` — source зависит от target (source заблокирована target)
- `relates_to` — информационная связь (двусторонняя)
---
## TaskAction — Аудит действий с задачами
**Таблица:** `task_actions`
**Файл:** `task.py`
### Поля:
```python
id: UUID # Primary key
task_id: UUID # FK → tasks.id (CASCADE DELETE)
actor_id: UUID # FK → members.id (кто совершил действие)
action: str # created | status_changed | assigned | etc.
field: str? # Какое поле изменилось
old_value: text? # Старое значение
new_value: text? # Новое значение
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `task`**Task** (many:1)
- `actor`**Member** (many:1)
---
## Chats — Чаты
**Таблица:** `chats`
**Файл:** `chat.py`
### Поля:
```python
id: UUID # Primary key
project_id: UUID? # FK → projects.id (null для lobby чата)
kind: str # lobby | project
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `project`**Project** (many:1, optional)
- `messages`**Message[]** (1:many, CASCADE DELETE)
---
## Messages — Сообщения
**Таблица:** `messages`
**Файл:** `chat.py`
**Универсальная модель:** работает и для чатов, и для комментариев к задачам.
### Поля:
```python
id: UUID # Primary key
chat_id: UUID? # FK → chats.id (для сообщений в чате)
task_id: UUID? # FK → tasks.id (для комментариев к задаче, CASCADE DELETE)
parent_id: UUID? # FK → messages.id (для тредов - ответов на сообщения)
author_type: str # human | agent | system
author_id: UUID? # FK → members.id (автор сообщения)
actor_id: UUID? # FK → members.id (инициатор действия для системных сообщений)
tool_log: JSON? # JSON массив вызовов инструментов [{name, args, result, error}]
content: text # Текст сообщения
thinking: text? # Внутренние размышления ИИ (для агентов)
mentions: str[] # Массив ID упомянутых участников
voice_url: str? # Ссылка на голосовое сообщение
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `chat`**Chat** (many:1, optional)
- `author`**Member** (many:1, optional)
- `actor`**Member** (many:1, optional)
- `attachments`**Attachment[]** (1:many, CASCADE DELETE)
- `replies`**Message[]** (1:many, self-reference для тредов)
- `parent`**Message** (many:1, self-reference)
### Ограничения:
- Должно быть заполнено либо `chat_id`, либо `task_id` (не оба)
---
## Attachments — Файловые вложения
**Таблица:** `attachments`
**Файл:** `chat.py`
### Поля:
```python
id: UUID # Primary key
message_id: UUID # FK → messages.id
filename: str # Исходное имя файла
mime_type: str? # MIME тип файла
size: int # Размер в байтах (по умолчанию 0)
storage_path: str # Путь к файлу в файловой системе
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `message`**Message** (many:1)
### Хранение:
- Файлы сохраняются в `/data/uploads/`
- `storage_path` содержит имя файла на диске (UUID + расширение)
---
## ProjectFile — Файлы проектов
**Таблица:** `project_files`
**Файл:** `project_file.py`
### Поля:
```python
id: UUID # Primary key
project_id: UUID # FK → projects.id (CASCADE DELETE)
filename: str # Имя файла
description: text? # Описание файла
mime_type: str? # MIME тип файла
size: int # Размер в байтах
uploaded_by: UUID # FK → members.id (кто загрузил, CASCADE DELETE)
storage_path: str # Относительный путь внутри папки проекта
created_at: TIMESTAMP
updated_at: TIMESTAMP
```
### Связи:
- `project`**Project** (many:1)
- `uploader`**Member** (many:1)
### Хранение:
- Файлы сохраняются в `/data/projects/{project_slug}/`
- `storage_path` содержит относительный путь (обычно совпадает с `filename`)
---
## Enums — Перечисления
**Файл:** `enums.py`
### MemberType
```python
HUMAN = "human" # Обычный пользователь
AGENT = "agent" # ИИ агент
BRIDGE = "bridge" # Мост (например, Telegram)
```
### MemberRole
```python
OWNER = "owner" # Владелец (все права)
MEMBER = "member" # Обычный участник
BRIDGE = "bridge" # Мост (ограниченные права)
```
### MemberStatus
```python
ONLINE = "online" # В сети
OFFLINE = "offline" # Не в сети
```
### AuthMethod
```python
PASSWORD = "password" # Аутентификация по паролю
TOKEN = "token" # Аутентификация по Bearer токену
```
### ChatKind
```python
LOBBY = "lobby" # Общий чат
PROJECT = "project" # Чат проекта
```
### ListenMode
```python
ALL = "all" # Получать все уведомления
MENTIONS = "mentions" # Только при упоминаниях
ASSIGNED = "assigned" # Назначенные задачи + упоминания
NONE = "none" # Не получать уведомления
```
### ProjectStatus
```python
ACTIVE = "active" # Активный проект
ARCHIVED = "archived" # Архивный проект
PAUSED = "paused" # Приостановленный проект
```
### TaskStatus
```python
BACKLOG = "backlog" # В беклоге
TODO = "todo" # Готово к работе
IN_PROGRESS = "in_progress" # В работе
IN_REVIEW = "in_review" # На ревью
DONE = "done" # Завершено
```
### TaskType
```python
TASK = "task" # Обычная задача
BUG = "bug" # Баг
FEATURE = "feature" # Новая функциональность
```
### TaskPriority
```python
LOW = "low" # Низкий приоритет
MEDIUM = "medium" # Средний приоритет
HIGH = "high" # Высокий приоритет
CRITICAL = "critical" # Критический приоритет
```
### AuthorType
```python
HUMAN = "human" # Сообщение от человека
AGENT = "agent" # Сообщение от агента
SYSTEM = "system" # Системное сообщение
```
### TaskActionType
```python
CREATED = "created"
STATUS_CHANGED = "status_changed"
ASSIGNED = "assigned"
UNASSIGNED = "unassigned"
PRIORITY_CHANGED = "priority_changed"
TITLE_CHANGED = "title_changed"
DESCRIPTION_CHANGED = "description_changed"
REVIEWER_CHANGED = "reviewer_changed"
WATCHER_ADDED = "watcher_added"
WATCHER_REMOVED = "watcher_removed"
DELETED = "deleted"
```
### TaskLinkType
```python
BLOCKS = "blocks" # source блокирует target
DEPENDS_ON = "depends_on" # source зависит от target
RELATES_TO = "relates_to" # информационная связь
```
### WSEventType
```python
# Auth события
AUTH = "auth"
AUTH_OK = "auth.ok"
AUTH_ERROR = "auth.error"
# Системные
HEARTBEAT = "heartbeat"
ACK = "ack"
ERROR = "error"
# Чат и подписки
CHAT_SEND = "chat.send"
PROJECT_SUBSCRIBE = "project.subscribe"
PROJECT_UNSUBSCRIBE = "project.unsubscribe"
MESSAGE_NEW = "message.new"
# Задачи
TASK_CREATED = "task.created"
TASK_UPDATED = "task.updated"
TASK_ASSIGNED = "task.assigned"
TASK_DELETED = "task.deleted"
# Агенты
AGENT_STATUS = "agent.status"
AGENT_STREAM_START = "agent.stream.start"
AGENT_STREAM_DELTA = "agent.stream.delta"
AGENT_STREAM_TOOL = "agent.stream.tool"
AGENT_STREAM_END = "agent.stream.end"
CONFIG_UPDATED = "config.updated"
```
---
## Схема связей
```
Member 1:1 AgentConfig
Member 1:N ProjectMember N:1 Project
Project 1:N Task
Task 1:N Step
Task N:M Label (через TaskLabel)
Task 1:N TaskLink N:1 Task
Task 1:N TaskAction N:1 Member
Project 1:N Chat
Chat 1:N Message
Message 1:N Attachment
Message 1:N Message (parent/replies)
Project 1:N ProjectFile N:1 Member
```
Этот документ описывает структуру базы данных на основе моделей SQLAlchemy в `/root/projects/team-board/tracker/src/tracker/models/` на дату создания спецификации.

180
specs/OVERVIEW.md Normal file
View File

@ -0,0 +1,180 @@
# Team Board — Обзор проекта
## Что такое Team Board
**Team Board** — система управления задачами для команд разработки с интеграцией ИИ-агентов. Основная идея: агенты могут автономно брать задачи, выполнять их и взаимодействовать с людьми через единое пространство.
### Ключевые концепции:
- **Unified workspace** — люди и агенты работают в одной системе
- **Task automation** — агенты могут брать задачи из backlog, выполнять и отчитываться
- **Real-time collaboration** — WebSocket для мгновенных уведомлений
- **Multi-channel access** — Web UI, Telegram Bridge, REST API, WebSocket
## Архитектура системы
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Web Client │ │ Bridge │ │ AI Agents │
│ (React) │ │ (Telegram) │ │ (Picogent) │
└─────┬───────┘ └─────┬───────┘ └─────┬───────┘
│ HTTP │ WebSocket │ REST API
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ BFF │
│ (Backend for Frontend) │
│ auth proxy + WebSocket hub │
└─────────────────────┬───────────────────────────────────┘
│ HTTP + WebSocket
┌─────────────────────────────────────────────────────────┐
│ Tracker │
│ (Core Backend) │
│ FastAPI + PostgreSQL + WebSocket │
└─────────────────────────────────────────────────────────┘
```
### Компоненты:
1. **Tracker** — основной бэкенд на FastAPI
- REST API для всех операций
- WebSocket для real-time events
- PostgreSQL для данных
- Аутентификация (JWT + токены для агентов)
2. **Web Client** — фронтенд на React + Vite
- Канбан-доска задач
- Чаты по проектам
- Управление агентами
- Real-time обновления через WebSocket
3. **Bridge** — Telegram интеграция
- Получает уведомления из Tracker
- Отправляет в личные чаты Telegram
- Фильтрация по типу событий
4. **Picogent** — ИИ-агенты
- MCP (Model Context Protocol) инструменты
- Автономная работа с задачами
- Memory система для контекста
- Bootstrap конфигурация
## Стек технологий
### Backend (Tracker):
- **FastAPI** — веб-фреймворк
- **SQLAlchemy** — ORM с async поддержкой
- **PostgreSQL** — основная база данных
- **WebSocket** — real-time коммуникация
- **Pydantic** — валидация данных
- **JWT** — аутентификация пользователей
- **Bearer tokens** — аутентификация агентов
### Frontend (Web Client):
- **React 19** — UI библиотека
- **TypeScript** — типизация
- **Vite** — bundler и dev server
- **Tailwind CSS 4** — стили
- **React Router DOM** — роутинг
### Infrastructure:
- **Docker** — контейнеризация
- **Docker Compose** — локальная разработка
- **Uvicorn** — ASGI сервер
## Деплой
### Конфигурация портов:
- **Tracker**: `8100` (FastAPI backend)
- **Web Client**: `3100` (Vite dev server)
- **PostgreSQL**: `5432` (база данных)
### Docker Compose
Основной файл: `/root/projects/team-board/tracker/docker-compose.yml`
```yaml
services:
tracker:
build: .
ports:
- "8100:8100"
env_file:
- .env.dev
volumes:
- ./src:/app/src
- ./data/uploads:/data/uploads
- ./data/projects:/data/projects
command: uvicorn tracker.app:app --host 0.0.0.0 --port 8100 --reload
```
### Systemd (production)
*Пока не реализовано — в планах на будущее*
### Nginx (reverse proxy)
*Пока не реализовано — в планах на будущее*
## URL-ы и эндпоинты
### Публичные URL-ы:
- `https://team.uix.su` — production web UI
- `https://dev.team.uix.su` — development web UI
- `http://localhost:3100` — локальная разработка
### API endpoints:
- **Tracker API**: `http://localhost:8100/api/v1/`
- **WebSocket**: `ws://localhost:8100/ws`
- **API Docs**: `http://localhost:8100/docs` (только в dev режиме)
- **Health check**: `http://localhost:8100/health`
### CORS настройки:
```python
allow_origins=[
"https://team.uix.su",
"https://dev.team.uix.su",
"http://localhost:3100"
]
```
## Конфигурация
### Environment Variables (Tracker):
```bash
TRACKER_DATABASE_URL=postgresql+asyncpg://team_board:team_board@localhost:5432/team_board
TRACKER_HOST=0.0.0.0
TRACKER_PORT=8100
TRACKER_JWT_SECRET=change-me-in-production
TRACKER_AGENT_HEARTBEAT_INTERVAL=30
TRACKER_AGENT_HEARTBEAT_TIMEOUT=90
TRACKER_ENV=dev
```
### Файловая система:
- `/data/uploads` — загруженные файлы (attachments)
- `/data/projects` — файлы проектов
- `src/` — исходный код (dev mode с hot reload)
## Особенности архитектуры
### Аутентификация:
- **JWT токены** — для web пользователей
- **Bearer токены** (`tb-xxxxx`) — для агентов
- Middleware на всех API эндпоинтах (кроме `/docs`, `/health`, `/ws`)
### WebSocket система:
- Heartbeat мониторинг (90 сек timeout)
- Автоматический offline статус при разрыве
- Фильтрация событий по подпискам проектов
- Поддержка множественных соединений одного пользователя
### Database Seeding:
- Автоматическое создание таблиц в dev режиме
- Auto-seed данными если БД пуста
- Seeding скрипт: `src/tracker/init_db.py`
### Логирование:
- Request/Response логи всех HTTP запросов
- WebSocket события
- Heartbeat таймауты
- Ошибки с полным traceback
Этот документ описывает текущее состояние проекта по коду в `/root/projects/team-board/` на дату создания спецификации.

444
specs/WEBSOCKET.md Normal file
View File

@ -0,0 +1,444 @@
# 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/` на дату создания спецификации.