docs: full project specs
This commit is contained in:
parent
cad887daf4
commit
3f46c46466
446
specs/AGENTS.md
Normal file
446
specs/AGENTS.md
Normal 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
883
specs/API.md
Normal 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
400
specs/BRIDGE.md
Normal 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("&", "&").replace("<", "<").replace(">", ">")
|
||||
```
|
||||
|
||||
**Формат отправки:** `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
452
specs/FRONTEND.md
Normal 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
543
specs/MODELS.md
Normal 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
180
specs/OVERVIEW.md
Normal 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
444
specs/WEBSOCKET.md
Normal 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/` на дату создания спецификации.
|
||||
Loading…
Reference in New Issue
Block a user