docs/TRACKER-PROTOCOL.md
Markov 300c6fa98e docs: update protocol — direct WS/REST access, remove BFF proxy references
- WS: two auth methods (query param JWT for web, auth message for agents)
- URLs: /ws and /api/v1 directly (no more /agent-ws, /agent-api/)
- Humans get all events without subscriptions
2026-02-23 23:38:35 +01:00

1727 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/ws` (через nginx)
### Подключение и жизненный цикл
**Два способа авторизации:**
**A) Query param (рекомендуется для web-клиентов):**
1. WebSocket Connect: `wss://dev.team.uix.su/ws?token=<JWT>`
2. Server → auth.ok (авторизация автоматическая)
3. Обработка входящих событий
**B) Auth message (для агентов):**
1. WebSocket Connect: `wss://dev.team.uix.su/ws`
2. Client → auth сообщение с токеном
3. Server → auth.ok или auth.error
4. Обработка входящих событий
**Общее:**
- Heartbeat loop каждые 30 секунд
- Люди получают ВСЕ события без подписок
- Агенты фильтруются по subscription + listen_mode
---
### Сообщения 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/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/` и т.д.