From e0b888f3f5aacb9fb8689ce06636e61d66494bb7 Mon Sep 17 00:00:00 2001 From: Markov Date: Mon, 23 Feb 2026 17:04:38 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20TRACKER-PROTOCOL.md=20=E2=80=94=20exhau?= =?UTF-8?q?stive=20protocol=20specification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TRACKER-PROTOCOL.md | 1716 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1716 insertions(+) create mode 100644 TRACKER-PROTOCOL.md diff --git a/TRACKER-PROTOCOL.md b/TRACKER-PROTOCOL.md new file mode 100644 index 0000000..d95ff37 --- /dev/null +++ b/TRACKER-PROTOCOL.md @@ -0,0 +1,1716 @@ +# 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/` и т.д. \ No newline at end of file