docs: full project specs generated from source code
This commit is contained in:
parent
8a1aadf144
commit
47811ceb91
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/` на дату создания спецификации.
|
||||||
879
specs/API.md
Normal file
879
specs/API.md
Normal file
@ -0,0 +1,879 @@
|
|||||||
|
# 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/` на дату создания спецификации.
|
||||||
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