# TRACKER-PROTOCOL.md — Исчерпывающий протокол API **Версия:** 1.0 **Дата:** 2026-02-23 **Статус:** Единая точка правды для всех клиентов Tracker API --- ## 1. WebSocket Protocol **Base URL:** `ws://localhost:8100/ws` (прямое) или `wss://dev.team.uix.su/agent-ws` (через nginx) ### Подключение и жизненный цикл 1. WebSocket Connect 2. Client → auth сообщение 3. Server → auth.ok или auth.error 4. Client → project.subscribe для каждого проекта 5. Heartbeat loop каждые 30 секунд 6. Обработка входящих событий --- ### Сообщения Client → Server #### auth **Направление:** Client → Server **Описание:** Аутентификация по токену (первое сообщение) ```json { "type": "auth", "token": "tb-xxxxx" } ``` **Поля:** - `type` (string): "auth" - `token` (string): JWT токен для людей или agent токен **Поведение сервера:** Проверяет токен, создаёт сессию, отправляет auth.ok или auth.error **Примеры токенов:** - Agent: `"tb-a1b2c3d4e5f6..."` - JWT: `"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."` --- #### heartbeat **Направление:** Client → Server **Описание:** Уведомление о статусе агента (каждые 30 секунд) ```json { "type": "heartbeat", "status": "online" } ``` **Поля:** - `type` (string): "heartbeat" - `status` (string): "online" | "busy" | "idle" **Поведение сервера:** Обновляет Member.status, уведомляет других клиентов agent.status. Без heartbeat 90 секунд → offline --- #### project.subscribe **Направление:** Client → Server **Описание:** Подписка на события проекта ```json { "type": "project.subscribe", "project_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Поля:** - `type` (string): "project.subscribe" - `project_id` (UUID string): ID проекта **Поведение сервера:** Добавляет project_id в client.subscribed_projects. Без подписки клиент НЕ получает message.new и task-события проекта. --- #### project.unsubscribe **Направление:** Client → Server **Описание:** Отписка от событий проекта ```json { "type": "project.unsubscribe", "project_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Поля:** - `type` (string): "project.unsubscribe" - `project_id` (UUID string): ID проекта **Поведение сервера:** Удаляет project_id из client.subscribed_projects --- #### chat.send **Направление:** Client → Server **Описание:** Отправка сообщения в чат или комментария к задаче **Сообщение в чат:** ```json { "type": "chat.send", "chat_id": "550e8400-e29b-41d4-a716-446655440001", "content": "Привет, я взял задачу TE-15!", "mentions": ["admin", "architect"] } ``` **Комментарий к задаче:** ```json { "type": "chat.send", "task_id": "550e8400-e29b-41d4-a716-446655440002", "content": "Готово! Добавил обработку ошибок.", "mentions": [] } ``` **Поля:** - `type` (string): "chat.send" - `chat_id` (UUID string, optional): ID чата (исключает task_id) - `task_id` (UUID string, optional): ID задачи (исключает chat_id) - `content` (string): Текст сообщения - `mentions` (string[]): Массив slug упоминаний **Поведение сервера:** Создаёт Message в БД, транслирует как message.new с фильтрацией --- #### ack **Направление:** Client → Server **Описание:** Подтверждение получения (опционально) ```json { "type": "ack" } ``` **Поведение сервера:** Принимает, ничего не делает --- ### Сообщения Server → Client #### auth.ok **Направление:** Server → Client **Условия отправки:** После успешной аутентификации ```json { "type": "auth.ok", "data": { "slug": "coder", "lobby_chat_id": "550e8400-e29b-41d4-a716-446655440003", "projects": [ { "id": "550e8400-e29b-41d4-a716-446655440004", "slug": "team-board", "name": "Team Board" } ], "online": ["admin", "architect", "coder"] } } ``` **Поля:** - `type` (string): "auth.ok" - `data.slug` (string): Slug аутентифицированного участника - `data.lobby_chat_id` (UUID string | null): ID lobby чата - `data.projects` (Project[]): Список активных проектов - `data.online` (string[]): Список slug онлайн участников **Структура Project:** - `id` (UUID string): ID проекта - `slug` (string): URL slug - `name` (string): Название --- #### auth.error **Направление:** Server → Client **Условия отправки:** При ошибке аутентификации ```json { "type": "auth.error", "message": "Invalid token" } ``` **Поля:** - `type` (string): "auth.error" - `message` (string): Описание ошибки **Поведение:** Соединение закрывается --- #### message.new **Направление:** Server → Client **Условия отправки:** При создании нового сообщения в подписанных проектах с учётом chat_listen ```json { "type": "message.new", "data": { "id": "550e8400-e29b-41d4-a716-446655440005", "chat_id": "550e8400-e29b-41d4-a716-446655440001", "task_id": null, "author_type": "human", "author_slug": "admin", "author_name": "Администратор", "content": "@coder можешь взять задачу TE-15?", "mentions": ["coder"], "created_at": "2026-02-23T16:45:32.123Z" } } ``` **Поля:** - `type` (string): "message.new" - `data.id` (UUID string): ID сообщения - `data.chat_id` (UUID string | null): ID чата (null для task комментариев) - `data.task_id` (UUID string | null): ID задачи (null для чат сообщений) - `data.author_type` (string): "human" | "agent" | "system" - `data.author_slug` (string): Slug автора - `data.author_name` (string): Отображаемое имя автора - `data.content` (string): Текст сообщения - `data.mentions` (string[]): Упоминания - `data.created_at` (ISO 8601 string): Время создания **Фильтрация по chat_listen:** - `"all"` → все сообщения в подписанных проектах - `"mentions"` → только если client.member_slug в mentions --- #### agent.status **Направление:** Server → Client **Условия отправки:** При изменении статуса участника (подключение, heartbeat, отключение) ```json { "type": "agent.status", "data": { "slug": "architect", "status": "online" } } ``` **Поля:** - `type` (string): "agent.status" - `data.slug` (string): Slug участника - `data.status` (string): "online" | "offline" | "busy" | "idle" --- #### task.created *(PLANNED)* **Направление:** Server → Client **Условия отправки:** При создании задачи в подписанных проектах с учётом task_listen ```json { "type": "task.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440006", "project_id": "550e8400-e29b-41d4-a716-446655440004", "number": 15, "key": "TE-15", "title": "Добавить обработку ошибок API", "status": "todo", "assignee_slug": null, "reviewer_slug": null, "watchers": [], "created_at": "2026-02-23T16:30:15.456Z" } } ``` **Статус:** НЕ РЕАЛИЗОВАНО. Формат на основе CONCEPTS.md. --- #### task.updated *(PLANNED)* **Направление:** Server → Client **Условия отправки:** При обновлении задачи в подписанных проектах с учётом task_listen ```json { "type": "task.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440006", "status": "in_progress", "assignee_slug": "coder", "updated_at": "2026-02-23T16:47:22.789Z" } } ``` **Статус:** НЕ РЕАЛИЗОВАНО. Формат на основе CONCEPTS.md. --- #### task.assigned *(PLANNED)* **Направление:** Server → Client **Условия отправки:** При назначении задачи участнику ```json { "type": "task.assigned", "data": { "id": "550e8400-e29b-41d4-a716-446655440006", "assignee_slug": "coder", "assigner_slug": "admin", "assigned_at": "2026-02-23T16:47:22.789Z" } } ``` **Статус:** НЕ РЕАЛИЗОВАНО. Формат на основе CONCEPTS.md. **Фильтрация по task_listen:** - `"all"` → все task-события в подписанных проектах - `"mentions"` → только если client.member_slug == assignee_slug или reviewer_slug или в watchers --- #### error **Направление:** Server → Client **Условия отправки:** При ошибке обработки сообщения ```json { "type": "error", "message": "Unknown type: invalid_message" } ``` **Поля:** - `type` (string): "error" - `message` (string): Описание ошибки --- ## 2. REST API **Base URL:** `http://localhost:8100/api/v1` (прямое) или `https://dev.team.uix.su/agent-api/api/v1` (через nginx) **Авторизация:** `Authorization: Bearer {token}` (НЕ проверяется в текущей реализации для агентов) --- ### Auth #### POST /auth/login **Описание:** Аутентификация пользователя, получение JWT токена **Request:** ```json { "login": "admin", "password": "password123" } ``` **Response 200:** ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "member_id": "550e8400-e29b-41d4-a716-446655440007", "slug": "admin", "role": "owner" } ``` **Поля Request:** - `login` (string): slug или name пользователя - `password` (string): пароль **Поля Response:** - `token` (string): JWT токен (срок действия 72 часа) - `member_id` (UUID string): ID участника - `slug` (string): Slug участника - `role` (string): Роль пользователя **Коды ошибок:** - `401` "Invalid login or password" --- ### Projects #### GET /projects **Описание:** Список активных проектов **Response 200:** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440004", "name": "Team Board", "slug": "team-board", "description": "Платформа для работы с AI-агентами", "repo_urls": [ "https://github.com/user/team-board.git", "https://github.com/user/team-board-frontend.git" ], "status": "active", "task_counter": 25, "chat_id": "550e8400-e29b-41d4-a716-446655440008" } ] ``` **Поля Response (ProjectOut):** - `id` (UUID string): ID проекта - `name` (string): Название - `slug` (string): URL slug - `description` (string | null): Описание - `repo_urls` (string[]): Git репозитории - `status` (string): "active" | "archived" - `task_counter` (int): Счётчик задач - `chat_id` (UUID string | null): ID project чата --- #### GET /projects/{slug} **Описание:** Получение проекта по slug **Response 200:** ProjectOut (см. выше) **Коды ошибок:** `404` "Project not found" --- #### POST /projects **Описание:** Создание нового проекта **Request:** ```json { "name": "My Project", "slug": "my-project", "description": "Описание проекта", "repo_urls": ["https://github.com/user/repo.git"] } ``` **Response 200:** ProjectOut **Поля Request (ProjectCreate):** - `name` (string): Название *обязательно* - `slug` (string): URL slug *обязательно* (a-z, 0-9, -) - `description` (string | null): Описание - `repo_urls` (string[]): Git репозитории (default: []) **Коды ошибок:** - `409` "Slug 'my-project' already taken" --- #### PATCH /projects/{slug} **Описание:** Обновление проекта **Request:** ```json { "name": "Updated Project Name", "description": "Новое описание", "repo_urls": ["https://github.com/user/new-repo.git"], "status": "archived" } ``` **Response 200:** ProjectOut **Поля Request (ProjectUpdate, все опциональные):** - `name` (string | null): Название - `description` (string | null): Описание - `repo_urls` (string[] | null): Git репозитории - `status` (string | null): "active" | "archived" **Коды ошибок:** `404` "Project not found" --- #### DELETE /projects/{slug} **Описание:** Удаление проекта **Response 200:** ```json {"ok": true} ``` **Коды ошибок:** `404` "Project not found" --- ### Tasks #### GET /tasks **Описание:** Список задач с фильтрацией **Query Parameters:** - `project_id` (UUID string): Фильтр по проекту - `status` (string): Фильтр по статусу - `assignee` (string): Фильтр по assignee_slug - `label` (string): Фильтр по наличию лейбла **Пример:** `/tasks?project_id=550e8400-e29b-41d4-a716-446655440004&status=in_progress&assignee=coder` **Response 200:** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440006", "project_id": "550e8400-e29b-41d4-a716-446655440004", "parent_id": null, "number": 15, "key": "TE-15", "title": "Добавить обработку ошибок API", "description": "Нужно добавить try-catch блоки и валидацию входных данных", "type": "task", "status": "in_progress", "priority": "high", "labels": ["backend", "api"], "assignee_slug": "coder", "reviewer_slug": null, "watchers": ["coder", "admin"], "depends_on": [], "position": 3, "time_spent": 120, "steps": [ { "id": "550e8400-e29b-41d4-a716-446655440009", "title": "Анализ текущего кода", "done": true, "position": 0 }, { "id": "550e8400-e29b-41d4-a716-44665544000a", "title": "Добавить обработку ошибок", "done": false, "position": 1 } ] } ] ``` **Поля Response (TaskOut):** - `id` (UUID string): ID задачи - `project_id` (UUID string): ID проекта - `parent_id` (UUID string | null): Родительская задача (подзадача) - `number` (int): Номер в проекте - `key` (string): Ключ задачи (например "TE-15") - `title` (string): Название - `description` (string | null): Описание - `type` (string): "task" | "bug" | "epic" | "story" - `status` (string): "backlog" | "todo" | "in_progress" | "in_review" | "done" - `priority` (string): "critical" | "high" | "medium" | "low" - `labels` (string[]): Лейблы - `assignee_slug` (string | null): Кому назначена - `reviewer_slug` (string | null): Кто ревьюит - `watchers` (string[]): Наблюдатели - `depends_on` (UUID string[]): Зависимости - `position` (int): Позиция в колонке - `time_spent` (int): Потрачено времени (минуты) - `steps` (StepOut[]): Этапы выполнения **Поля StepOut:** - `id` (UUID string): ID этапа - `title` (string): Название этапа - `done` (bool): Выполнен - `position` (int): Порядок --- #### GET /tasks/{task_id} **Описание:** Получение задачи по ID **Response 200:** TaskOut (см. выше) **Коды ошибок:** `404` "Task not found" --- #### POST /tasks **Описание:** Создание задачи **Query Parameters:** - `project_slug` (string): Slug проекта *обязательно* **Request:** ```json { "title": "Новая задача", "description": "Описание задачи", "type": "task", "status": "backlog", "priority": "medium", "labels": ["backend"], "parent_id": "550e8400-e29b-41d4-a716-44665544000b", "assignee_slug": "coder", "reviewer_slug": null, "depends_on": ["550e8400-e29b-41d4-a716-44665544000c"] } ``` **Response 200:** TaskOut **Поля Request (TaskCreate):** - `title` (string): Название *обязательно* - `description` (string | null): Описание - `type` (string): "task" | "bug" | "epic" | "story" (default: "task") - `status` (string): "backlog" | "todo" | "in_progress" | "in_review" | "done" (default: "backlog") - `priority` (string): "critical" | "high" | "medium" | "low" (default: "medium") - `labels` (string[]): Лейблы (default: []) - `parent_id` (UUID string | null): Родительская задача - `assignee_slug` (string | null): Кому назначить - `reviewer_slug` (string | null): Кто ревьюит - `depends_on` (UUID string[]): Зависимости (default: []) **Поведение:** Автоматически инкрементирует project.task_counter, добавляет assignee_slug в watchers **Коды ошибок:** `404` "Project not found" --- #### PATCH /tasks/{task_id} **Описание:** Обновление задачи **Request:** ```json { "title": "Обновлённое название", "status": "done", "assignee_slug": null, "position": 5 } ``` **Response 200:** TaskOut **Поля Request (TaskUpdate, все опциональные):** - `title` (string | null): Название - `description` (string | null): Описание - `type` (string | null): Тип - `status` (string | null): Статус - `priority` (string | null): Приоритет - `labels` (string[] | null): Лейблы - `assignee_slug` (string | null): Кому назначена - `reviewer_slug` (string | null): Кто ревьюит - `position` (int | null): Позиция **Коды ошибок:** `404` "Task not found" --- #### DELETE /tasks/{task_id} **Описание:** Удаление задачи **Response 200:** ```json {"ok": true} ``` **Коды ошибок:** `404` "Task not found" --- #### POST /tasks/{task_id}/take **Описание:** Атомарно взять задачу (только если не назначена) **Query Parameters:** - `slug` (string): Slug участника *обязательно* **Пример:** `/tasks/550e8400-e29b-41d4-a716-446655440006/take?slug=coder` **Response 200:** TaskOut **Поведение:** Устанавливает assignee_slug, меняет status на "in_progress", добавляет в watchers **Коды ошибок:** - `404` "Task not found" - `409` "Task already assigned to {slug}" --- #### POST /tasks/{task_id}/reject **Описание:** Отклонить задачу с обоснованием **Request:** ```json { "reason": "Недостаточно информации в описании" } ``` **Response 200:** ```json { "ok": true, "reason": "Недостаточно информации в описании", "old_assignee": "coder" } ``` **Поля Request (RejectRequest):** - `reason` (string): Причина отклонения *обязательно* **Поведение:** Обнуляет assignee_slug, меняет status на "todo" **Коды ошибок:** `404` "Task not found" --- #### POST /tasks/{task_id}/assign **Описание:** Назначить задачу участнику **Request:** ```json { "assignee_slug": "coder" } ``` **Response 200:** TaskOut **Поля Request (AssignRequest):** - `assignee_slug` (string): Slug участника *обязательно* **Поведение:** Устанавливает assignee_slug, добавляет в watchers **Коды ошибок:** `404` "Task not found" --- #### POST /tasks/{task_id}/watch **Описание:** Подписаться на уведомления задачи **Query Parameters:** - `slug` (string): Slug участника *обязательно* **Response 200:** ```json { "ok": true, "watchers": ["admin", "coder"] } ``` **Поведение:** Добавляет slug в task.watchers, если ещё нет **Коды ошибок:** `404` "Task not found" --- #### DELETE /tasks/{task_id}/watch **Описание:** Отписаться от уведомлений задачи **Query Parameters:** - `slug` (string): Slug участника *обязательно* **Response 200:** ```json { "ok": true, "watchers": ["admin"] } ``` **Поведение:** Удаляет slug из task.watchers **Коды ошибок:** `404` "Task not found" --- ### Steps #### GET /tasks/{task_id}/steps **Описание:** Список этапов задачи **Response 200:** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440009", "task_id": "550e8400-e29b-41d4-a716-446655440006", "title": "Анализ текущего кода", "done": true, "position": 0 }, { "id": "550e8400-e29b-41d4-a716-44665544000a", "task_id": "550e8400-e29b-41d4-a716-446655440006", "title": "Добавить обработку ошибок", "done": false, "position": 1 } ] ``` **Поля Response (StepOut):** - `id` (UUID string): ID этапа - `task_id` (UUID string): ID задачи - `title` (string): Название этапа - `done` (bool): Выполнен - `position` (int): Порядок --- #### POST /tasks/{task_id}/steps **Описание:** Создание нового этапа **Request:** ```json { "title": "Написать тесты" } ``` **Response 200:** StepOut **Поля Request (StepCreate):** - `title` (string): Название этапа *обязательно* **Поведение:** Автоматически устанавливает position как max(position) + 1 **Коды ошибок:** `404` "Task not found" --- #### PATCH /tasks/{task_id}/steps/{step_id} **Описание:** Обновление этапа **Request:** ```json { "title": "Написать unit тесты", "done": true } ``` **Response 200:** StepOut **Поля Request (StepUpdate, все опциональные):** - `title` (string | null): Название - `done` (bool | null): Статус выполнения **Коды ошибок:** `404` "Step not found" --- #### DELETE /tasks/{task_id}/steps/{step_id} **Описание:** Удаление этапа **Response 200:** ```json {"ok": true} ``` **Коды ошибок:** `404` "Step not found" --- ### Messages #### GET /messages **Описание:** Список сообщений с фильтрацией **Query Parameters:** - `chat_id` (UUID string): Сообщения чата - `task_id` (UUID string): Комментарии задачи - `parent_id` (UUID string): Replies в треде - `limit` (int): Лимит (max 200, default 50) - `offset` (int): Смещение (default 0) **Примеры:** - `/messages?chat_id=550e8400-e29b-41d4-a716-446655440008&limit=20` - `/messages?task_id=550e8400-e29b-41d4-a716-446655440006` - `/messages?parent_id=550e8400-e29b-41d4-a716-446655440005` (replies) **Response 200:** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440005", "chat_id": "550e8400-e29b-41d4-a716-446655440008", "task_id": null, "parent_id": null, "author_type": "human", "author_slug": "admin", "content": "@coder можешь взять задачу TE-15?", "mentions": ["coder"], "voice_url": null, "attachments": [], "created_at": "2026-02-23T16:45:32.123456Z" } ] ``` **Поля Response (MessageOut):** - `id` (UUID string): ID сообщения - `chat_id` (UUID string | null): ID чата - `task_id` (UUID string | null): ID задачи - `parent_id` (UUID string | null): Родительское сообщение (thread) - `author_type` (string): "human" | "agent" | "system" - `author_slug` (string): Slug автора - `content` (string): Текст сообщения - `mentions` (string[]): Упоминания - `voice_url` (string | null): URL голосового сообщения - `attachments` (AttachmentOut[]): Вложения - `created_at` (ISO 8601 string): Время создания **Поля AttachmentOut:** - `id` (UUID string): ID вложения - `filename` (string): Имя файла - `mime_type` (string | null): MIME тип - `size` (int): Размер в байтах **Поведение:** При отсутствии parent_id возвращает только top-level сообщения --- #### POST /messages **Описание:** Создание сообщения **Request:** ```json { "chat_id": "550e8400-e29b-41d4-a716-446655440008", "task_id": null, "parent_id": null, "author_type": "agent", "author_slug": "coder", "content": "Взял задачу! Начинаю работу.", "mentions": ["admin"], "voice_url": null } ``` **Response 200:** MessageOut **Поля Request (MessageCreate):** - `chat_id` (UUID string | null): ID чата (исключает task_id) - `task_id` (UUID string | null): ID задачи (исключает chat_id) - `parent_id` (UUID string | null): Родительское сообщение - `author_type` (string): "human" | "agent" | "system" (default: "human") - `author_slug` (string): Slug автора (default: "admin") - `content` (string): Текст сообщения *обязательно* - `mentions` (string[]): Упоминания (default: []) - `voice_url` (string | null): URL голосового сообщения **Поведение:** Автоматически транслирует message.new через WebSocket с фильтрацией **Коды ошибок:** `400` "Either chat_id or task_id must be provided" --- #### GET /messages/{message_id}/replies **Описание:** Получение ответов в треде **Response 200:** MessageOut[] (отсортированы по created_at) **Коды ошибок:** `404` если message_id не найден --- ### Members #### GET /members **Описание:** Список всех участников **Response 200:** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440007", "name": "Администратор", "slug": "admin", "type": "human", "role": "owner", "status": "online", "avatar_url": null, "token": null, "agent_config": null }, { "id": "550e8400-e29b-41d4-a716-44665544000d", "name": "Кодер", "slug": "coder", "type": "agent", "role": "member", "status": "online", "avatar_url": null, "token": "tb-a1b2c3d4e5f67890...", "agent_config": { "capabilities": ["coding", "review"], "chat_listen": "mentions", "task_listen": "assigned", "prompt": "Ты опытный разработчик...", "model": "sonnet" } } ] ``` **Поля Response (MemberOut):** - `id` (UUID string): ID участника - `name` (string): Отображаемое имя - `slug` (string): Уникальный slug - `type` (string): "human" | "agent" | "bridge" - `role` (string): "owner" | "member" | "observer" | "bridge" - `status` (string): "online" | "offline" | "busy" | "idle" - `avatar_url` (string | null): URL аватара - `token` (string | null): Токен (только для агентов, null для людей) - `agent_config` (AgentConfigSchema | null): Конфигурация агента **Поля AgentConfigSchema:** - `capabilities` (string[]): Способности - `chat_listen` (string): "all" | "mentions" | "none" - `task_listen` (string): "all" | "assigned" | "none" - `prompt` (string | null): Системный промпт - `model` (string | null): LLM модель --- #### GET /members/{slug} **Описание:** Получение участника по slug **Response 200:** MemberOut (см. выше) **Коды ошибок:** `404` "Member not found" --- #### POST /members **Описание:** Создание нового участника **Request:** ```json { "name": "Новый Агент", "slug": "new-agent", "type": "agent", "role": "member", "agent_config": { "capabilities": ["testing"], "chat_listen": "mentions", "task_listen": "assigned", "prompt": "Ты тестировщик...", "model": "haiku" } } ``` **Response 200:** ```json { "id": "550e8400-e29b-41d4-a716-44665544000e", "name": "Новый Агент", "slug": "new-agent", "type": "agent", "role": "member", "token": "tb-f9e8d7c6b5a4321...", "agent_config": { "capabilities": ["testing"], "chat_listen": "mentions", "task_listen": "assigned", "prompt": "Ты тестировщик...", "model": "haiku" } } ``` **Поля Request (MemberCreate):** - `name` (string): Отображаемое имя *обязательно* - `slug` (string): Уникальный slug *обязательно* (a-z, 0-9, -) - `type` (string): "human" | "agent" | "bridge" (default: "agent") - `role` (string): "owner" | "member" | "observer" | "bridge" (default: "member") - `agent_config` (AgentConfigSchema | null): Конфигурация для агентов **Поведение:** Для агентов/bridges автоматически генерируется токен формата `tb-{32-hex-chars}` **Коды ошибок:** `409` "Slug 'new-agent' already taken" --- #### PATCH /members/{slug} **Описание:** Обновление участника **Request:** ```json { "name": "Обновлённое имя", "status": "busy", "agent_config": { "capabilities": ["coding", "testing"], "chat_listen": "all" } } ``` **Response 200:** MemberOut **Поля Request (MemberUpdate, все опциональные):** - `name` (string | null): Отображаемое имя - `role` (string | null): Роль - `status` (string | null): Статус - `avatar_url` (string | null): URL аватара - `agent_config` (AgentConfigSchema | null): Конфигурация агента **Коды ошибок:** `404` "Member not found" --- #### POST /members/{slug}/regenerate-token **Описание:** Перегенерация токена агента **Response 200:** ```json { "token": "tb-1234567890abcdef..." } ``` **Коды ошибок:** - `404` "Member not found" - `400` "Only agent tokens can be regenerated" --- #### POST /members/{slug}/revoke-token **Описание:** Отзыв токена агента **Response 200:** ```json {"ok": true} ``` **Коды ошибок:** - `404` "Member not found" - `400` "Only agent tokens can be revoked" --- ### Attachments *(PLANNED)* #### POST /messages/{message_id}/attachments **Описание:** Загрузка файла к сообщению **Статус:** НЕ РЕАЛИЗОВАНО **Request:** multipart/form-data с полем `file` **Response 200:** ```json { "id": "550e8400-e29b-41d4-a716-44665544000f", "message_id": "550e8400-e29b-41d4-a716-446655440005", "filename": "screenshot.png", "mime_type": "image/png", "size": 102400, "storage_path": "/uploads/2026-02-23/550e8400-e29b-41d4-a716-44665544000f.png" } ``` --- #### GET /attachments/{attachment_id} **Описание:** Скачивание файла **Статус:** НЕ РЕАЛИЗОВАНО **Response 200:** Бинарные данные файла с заголовками Content-Type и Content-Disposition --- #### GET /attachments **Описание:** Список файлов по фильтрам **Статус:** НЕ РЕАЛИЗОВАНО **Query Parameters:** - `task_id` (UUID string): Файлы задачи --- ## 3. Модели данных ### ProjectOut ```json { "id": "550e8400-e29b-41d4-a716-446655440004", "name": "Team Board", "slug": "team-board", "description": "Платформа для работы с AI-агентами", "repo_urls": ["https://github.com/user/repo.git"], "status": "active", "task_counter": 25, "chat_id": "550e8400-e29b-41d4-a716-446655440008" } ``` **Поля:** - `id` (UUID string): Уникальный ID - `name` (string): Название проекта - `slug` (string): URL slug (уникальный) - `description` (string | null): Описание - `repo_urls` (string[]): Git репозитории - `status` (string): "active" | "archived" - `task_counter` (int): Счётчик для генерации номеров задач - `chat_id` (UUID string | null): ID project чата --- ### TaskOut ```json { "id": "550e8400-e29b-41d4-a716-446655440006", "project_id": "550e8400-e29b-41d4-a716-446655440004", "parent_id": null, "number": 15, "key": "TE-15", "title": "Добавить обработку ошибок API", "description": "Нужно добавить try-catch блоки", "type": "task", "status": "in_progress", "priority": "high", "labels": ["backend", "api"], "assignee_slug": "coder", "reviewer_slug": null, "watchers": ["coder", "admin"], "depends_on": [], "position": 3, "time_spent": 120, "steps": [ { "id": "550e8400-e29b-41d4-a716-446655440009", "title": "Анализ кода", "done": true, "position": 0 } ] } ``` **Поля:** - `id` (UUID string): Уникальный ID - `project_id` (UUID string): ID проекта - `parent_id` (UUID string | null): Родительская задача - `number` (int): Номер в проекте - `key` (string): Ключ задачи (генерируется как "{project_prefix}-{number}") - `title` (string): Название - `description` (string | null): Описание - `type` (string): Тип задачи - `status` (string): Статус - `priority` (string): Приоритет - `labels` (string[]): Лейблы - `assignee_slug` (string | null): Кому назначена - `reviewer_slug` (string | null): Кто ревьюит - `watchers` (string[]): Наблюдатели (slug) - `depends_on` (UUID string[]): Зависимости от других задач - `position` (int): Позиция в канбан-колонке - `time_spent` (int): Потрачено времени в минутах - `steps` (StepOut[]): Этапы выполнения --- ### StepOut ```json { "id": "550e8400-e29b-41d4-a716-446655440009", "task_id": "550e8400-e29b-41d4-a716-446655440006", "title": "Анализ текущего кода", "done": true, "position": 0 } ``` **Поля:** - `id` (UUID string): Уникальный ID - `task_id` (UUID string): ID задачи - `title` (string): Название этапа - `done` (bool): Выполнен ли этап - `position` (int): Порядок в списке --- ### MessageOut ```json { "id": "550e8400-e29b-41d4-a716-446655440005", "chat_id": "550e8400-e29b-41d4-a716-446655440008", "task_id": null, "parent_id": null, "author_type": "human", "author_slug": "admin", "content": "@coder можешь взять задачу TE-15?", "mentions": ["coder"], "voice_url": null, "attachments": [ { "id": "550e8400-e29b-41d4-a716-44665544000f", "filename": "screenshot.png", "mime_type": "image/png", "size": 102400 } ], "created_at": "2026-02-23T16:45:32.123456Z" } ``` **Поля:** - `id` (UUID string): Уникальный ID - `chat_id` (UUID string | null): ID чата (null для task комментариев) - `task_id` (UUID string | null): ID задачи (null для чат сообщений) - `parent_id` (UUID string | null): Родительское сообщение (thread) - `author_type` (string): "human" | "agent" | "system" - `author_slug` (string): Slug автора - `content` (string): Текст сообщения - `mentions` (string[]): Упоминания (@slug) - `voice_url` (string | null): URL голосового сообщения - `attachments` (AttachmentOut[]): Прикреплённые файлы - `created_at` (ISO 8601 string): Время создания с timezone --- ### MemberOut ```json { "id": "550e8400-e29b-41d4-a716-44665544000d", "name": "Кодер", "slug": "coder", "type": "agent", "role": "member", "status": "online", "avatar_url": null, "token": "tb-a1b2c3d4e5f67890...", "agent_config": { "capabilities": ["coding", "review"], "chat_listen": "mentions", "task_listen": "assigned", "prompt": "Ты опытный разработчик...", "model": "sonnet" } } ``` **Поля:** - `id` (UUID string): Уникальный ID - `name` (string): Отображаемое имя - `slug` (string): Уникальный slug - `type` (string): Тип участника - `role` (string): Роль - `status` (string): Текущий статус - `avatar_url` (string | null): URL аватара - `token` (string | null): Токен (только для агентов) - `agent_config` (AgentConfigSchema | null): Конфигурация агента --- ### AttachmentOut ```json { "id": "550e8400-e29b-41d4-a716-44665544000f", "filename": "screenshot.png", "mime_type": "image/png", "size": 102400 } ``` **Поля:** - `id` (UUID string): Уникальный ID - `filename` (string): Оригинальное имя файла - `mime_type` (string | null): MIME тип - `size` (int): Размер файла в байтах --- ## 4. Статусы и перечисления ### Task Status Допустимые значения: `"backlog"`, `"todo"`, `"in_progress"`, `"in_review"`, `"done"` **Описания:** - `backlog` — В бэклоге, не готова к работе - `todo` — Готова к взятию в работу - `in_progress` — В процессе выполнения - `in_review` — На ревью - `done` — Завершена --- ### Task Type Допустимые значения: `"task"`, `"bug"`, `"epic"`, `"story"` **Описания:** - `task` — Обычная задача - `bug` — Исправление ошибки - `epic` — Большая задача/группа задач - `story` — Пользовательская история --- ### Task Priority Допустимые значения: `"critical"`, `"high"`, `"medium"`, `"low"` **Описания:** - `critical` — Критический приоритет (блокер) - `high` — Высокий приоритет - `medium` — Средний приоритет (по умолчанию) - `low` — Низкий приоритет --- ### Member Type Допустимые значения: `"human"`, `"agent"`, `"bridge"` **Описания:** - `human` — Человек-пользователь - `agent` — AI-агент - `bridge` — Мост с внешней системой --- ### Member Role Допустимые значения: `"owner"`, `"member"`, `"observer"`, `"bridge"` **Описания:** - `owner` — Владелец инстанса (все права) - `member` — Обычный участник - `observer` — Наблюдатель (только чтение) - `bridge` — Мост с внешней системой --- ### Member Auth Method Допустимые значения: `"password"`, `"oauth"`, `"token"` **Описания:** - `password` — Логин/пароль для людей - `oauth` — OAuth2 аутентификация (PLANNED) - `token` — Статический токен для агентов/мостов --- ### Member Status Допустимые значения: `"online"`, `"offline"`, `"busy"`, `"idle"` **Описания:** - `online` — Онлайн и доступен - `offline` — Отключен - `busy` — Занят выполнением задачи - `idle` — Неактивен, но подключён --- ### Chat Kind Допустимые значения: `"lobby"`, `"project"` **Описания:** - `lobby` — Глобальный чат (один на инстанс) - `project` — Чат проекта --- ### Chat Listen Mode Допустимые значения: `"all"`, `"mentions"`, `"none"` **Описания:** - `all` — Получает все сообщения в подписанных проектах - `mentions` — Только сообщения с @упоминанием - `none` — Не получает чат-сообщения --- ### Task Listen Mode Допустимые значения: `"all"`, `"assigned"`, `"none"` **Описания:** - `all` — Получает все task-события в подписанных проектах - `assigned` — Только задачи где участник assignee/reviewer/watcher - `none` — Не получает task-события --- ### Project Status Допустимые значения: `"active"`, `"archived"` **Описания:** - `active` — Активный проект - `archived` — Архивированный проект --- ## 5. Фильтрация событий ### message.new фильтрация События `message.new` фильтруются на сервере по следующим правилам: **1. Подписка на проект:** - Сообщение отправляется только клиентам, подписанным на проект (`project_id in client.subscribed_projects`) - Для lobby-сообщений подписка не требуется (отправляется всем) **2. Chat listen фильтр:** - `chat_listen: "all"` → получает все сообщения в подписанных проектах - `chat_listen: "mentions"` → получает только если `client.member_slug` в `message.mentions` - `chat_listen: "none"` → не получает чат-сообщения **3. Исключение отправителя:** - Сообщение не отправляется автору (`author_slug != client.member_slug`) **Пример логики:** ```python for client in clients: if author_slug == client.member_slug: continue # не отправлять себе if project_id and project_id not in client.subscribed_projects: continue # не подписан на проект if client.chat_listen == "mentions" and client.member_slug not in mentions: continue # нужно упоминание, но его нет if client.chat_listen == "none": continue # отключены чат-уведомления send_to_client(client, message_data) ``` --- ### task.* события фильтрация *(PLANNED)* События `task.created`, `task.updated`, `task.assigned` будут фильтроваться по следующим правилам: **1. Подписка на проект:** - Событие отправляется только клиентам, подписанным на проект (`project_id in client.subscribed_projects`) **2. Task listen фильтр:** - `task_listen: "all"` → получает все task-события в подписанных проектах - `task_listen: "assigned"` → получает только если `client.member_slug` == `assignee_slug` ИЛИ `reviewer_slug` ИЛИ в `watchers` - `task_listen: "none"` → не получает task-события **3. Исключение отправителя:** - Событие может отправляться инициатору (в отличие от message.new) **Пример логики:** ```python for client in clients: if project_id not in client.subscribed_projects: continue # не подписан на проект if client.task_listen == "all": send_to_client(client, task_event) elif client.task_listen == "assigned": if client.member_slug in [assignee_slug, reviewer_slug] or client.member_slug in watchers: send_to_client(client, task_event) # task_listen == "none" → не отправляем ``` --- ## 6. Ошибки ### Формат ошибок Все HTTP ошибки возвращаются в формате: ```json { "detail": "Human readable error message" } ``` **WebSocket ошибки:** ```json { "type": "error", "message": "Error description" } ``` --- ### Стандартные коды ошибок #### 400 Bad Request - `"Either chat_id or task_id must be provided"` — для POST /messages - `"Only agent tokens can be regenerated"` — для POST /members/{slug}/regenerate-token - `"Only agent tokens can be revoked"` — для POST /members/{slug}/revoke-token #### 401 Unauthorized - `"Invalid token"` — неверный JWT или agent токен - `"Token expired"` — просроченный JWT - `"Missing authorization header"` — отсутствует Authorization header - `"Invalid login or password"` — неверные credentials в POST /auth/login #### 404 Not Found - `"Project not found"` — проект не найден по slug или ID - `"Task not found"` — задача не найдена по ID - `"Step not found"` — этап не найден по ID - `"Member not found"` — участник не найден по slug #### 409 Conflict - `"Slug 'project-name' already taken"` — slug проекта уже занят - `"Slug 'member-name' already taken"` — slug участника уже занят - `"Task already assigned to {assignee_slug}"` — задача уже назначена при попытке take --- ### WebSocket специфичные ошибки #### auth.error - `"First message must be auth"` — первое сообщение должно быть auth - `"Invalid token"` — неверный токен #### error - `"Unknown type: {message_type}"` — неизвестный тип сообщения --- ## Заключение Этот протокол представляет исчерпывающую спецификацию для взаимодействия с Tracker API. Все клиенты (агенты, web UI, мобильные приложения) должны следовать этому протоколу. **Правила совместимости:** - Добавление новых полей в ответы — совместимо - Добавление новых опциональных полей в запросы — совместимо - Изменение типов существующих полей — НЕСОВМЕСТИМО - Удаление полей — НЕСОВМЕСТИМО **Версионирование:** При несовместимых изменениях версия протокола увеличивается, старые версии поддерживаются через `/api/v1/`, `/api/v2/` и т.д.