- 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
1727 lines
48 KiB
Markdown
1727 lines
48 KiB
Markdown
# 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/` и т.д. |