From 47811ceb91382b2194970aa8b32a21552b89d9eb Mon Sep 17 00:00:00 2001 From: markov Date: Fri, 13 Mar 2026 15:16:53 +0100 Subject: [PATCH] docs: full project specs generated from source code --- specs/AGENTS.md | 446 +++++++++++++++++++++++ specs/API.md | 879 +++++++++++++++++++++++++++++++++++++++++++++ specs/BRIDGE.md | 400 +++++++++++++++++++++ specs/FRONTEND.md | 452 +++++++++++++++++++++++ specs/MODELS.md | 543 ++++++++++++++++++++++++++++ specs/OVERVIEW.md | 180 ++++++++++ specs/WEBSOCKET.md | 444 +++++++++++++++++++++++ 7 files changed, 3344 insertions(+) create mode 100644 specs/AGENTS.md create mode 100644 specs/API.md create mode 100644 specs/BRIDGE.md create mode 100644 specs/FRONTEND.md create mode 100644 specs/MODELS.md create mode 100644 specs/OVERVIEW.md create mode 100644 specs/WEBSOCKET.md diff --git a/specs/AGENTS.md b/specs/AGENTS.md new file mode 100644 index 0000000..93ac006 --- /dev/null +++ b/specs/AGENTS.md @@ -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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/API.md b/specs/API.md new file mode 100644 index 0000000..5b95ea1 --- /dev/null +++ b/specs/API.md @@ -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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/BRIDGE.md b/specs/BRIDGE.md new file mode 100644 index 0000000..33a11eb --- /dev/null +++ b/specs/BRIDGE.md @@ -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 ` +Привязать текущий топик к проекту + +**Использование:** +``` +/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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/FRONTEND.md b/specs/FRONTEND.md new file mode 100644 index 0000000..1b81441 --- /dev/null +++ b/specs/FRONTEND.md @@ -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 + + + } /> + } /> + } /> + } /> + }> + } /> + } /> + } /> + + + +``` + +--- + +## Основные компоненты + +### 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([]); +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) => { + // Оптимистично обновляем 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(path: string, options: RequestInit = {}): Promise { + 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 ; + 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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/MODELS.md b/specs/MODELS.md new file mode 100644 index 0000000..a6cd9ac --- /dev/null +++ b/specs/MODELS.md @@ -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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/OVERVIEW.md b/specs/OVERVIEW.md new file mode 100644 index 0000000..105cae8 --- /dev/null +++ b/specs/OVERVIEW.md @@ -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/` на дату создания спецификации. \ No newline at end of file diff --git a/specs/WEBSOCKET.md b/specs/WEBSOCKET.md new file mode 100644 index 0000000..b9c7c9e --- /dev/null +++ b/specs/WEBSOCKET.md @@ -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/` на дату создания спецификации. \ No newline at end of file