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

48 KiB
Raw Permalink Blame History

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
Описание: Аутентификация по токену (первое сообщение)

{
  "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 секунд)

{
  "type": "heartbeat",
  "status": "online"
}

Поля:

  • type (string): "heartbeat"
  • status (string): "online" | "busy" | "idle"

Поведение сервера: Обновляет Member.status, уведомляет других клиентов agent.status. Без heartbeat 90 секунд → offline


project.subscribe

Направление: Client → Server
Описание: Подписка на события проекта

{
  "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
Описание: Отписка от событий проекта

{
  "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
Описание: Отправка сообщения в чат или комментария к задаче

Сообщение в чат:

{
  "type": "chat.send",
  "chat_id": "550e8400-e29b-41d4-a716-446655440001",
  "content": "Привет, я взял задачу TE-15!",
  "mentions": ["admin", "architect"]
}

Комментарий к задаче:

{
  "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
Описание: Подтверждение получения (опционально)

{
  "type": "ack"
}

Поведение сервера: Принимает, ничего не делает


Сообщения Server → Client

auth.ok

Направление: Server → Client
Условия отправки: После успешной аутентификации

{
  "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
Условия отправки: При ошибке аутентификации

{
  "type": "auth.error",
  "message": "Invalid token"
}

Поля:

  • type (string): "auth.error"
  • message (string): Описание ошибки

Поведение: Соединение закрывается


message.new

Направление: Server → Client
Условия отправки: При создании нового сообщения в подписанных проектах с учётом chat_listen

{
  "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, отключение)

{
  "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

{
  "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

{
  "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
Условия отправки: При назначении задачи участнику

{
  "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
Условия отправки: При ошибке обработки сообщения

{
  "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:

{
  "login": "admin",
  "password": "password123"
}

Response 200:

{
  "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:

[
  {
    "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:

{
  "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:

{
  "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:

{"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:

[
  {
    "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:

{
  "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:

{
  "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:

{"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:

{
  "reason": "Недостаточно информации в описании"
}

Response 200:

{
  "ok": true,
  "reason": "Недостаточно информации в описании",
  "old_assignee": "coder"
}

Поля Request (RejectRequest):

  • reason (string): Причина отклонения обязательно

Поведение: Обнуляет assignee_slug, меняет status на "todo"

Коды ошибок: 404 "Task not found"


POST /tasks/{task_id}/assign

Описание: Назначить задачу участнику

Request:

{
  "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:

{
  "ok": true,
  "watchers": ["admin", "coder"]
}

Поведение: Добавляет slug в task.watchers, если ещё нет

Коды ошибок: 404 "Task not found"


DELETE /tasks/{task_id}/watch

Описание: Отписаться от уведомлений задачи

Query Parameters:

  • slug (string): Slug участника обязательно

Response 200:

{
  "ok": true,
  "watchers": ["admin"]
}

Поведение: Удаляет slug из task.watchers

Коды ошибок: 404 "Task not found"


Steps

GET /tasks/{task_id}/steps

Описание: Список этапов задачи

Response 200:

[
  {
    "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:

{
  "title": "Написать тесты"
}

Response 200: StepOut

Поля Request (StepCreate):

  • title (string): Название этапа обязательно

Поведение: Автоматически устанавливает position как max(position) + 1

Коды ошибок: 404 "Task not found"


PATCH /tasks/{task_id}/steps/{step_id}

Описание: Обновление этапа

Request:

{
  "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:

{"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:

[
  {
    "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:

{
  "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:

[
  {
    "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:

{
  "name": "Новый Агент",
  "slug": "new-agent",
  "type": "agent",
  "role": "member",
  "agent_config": {
    "capabilities": ["testing"],
    "chat_listen": "mentions",
    "task_listen": "assigned",
    "prompt": "Ты тестировщик...",
    "model": "haiku"
  }
}

Response 200:

{
  "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:

{
  "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:

{
  "token": "tb-1234567890abcdef..."
}

Коды ошибок:

  • 404 "Member not found"
  • 400 "Only agent tokens can be regenerated"

POST /members/{slug}/revoke-token

Описание: Отзыв токена агента

Response 200:

{"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:

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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)

Пример логики:

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)

Пример логики:

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 ошибки возвращаются в формате:

{
  "detail": "Human readable error message"
}

WebSocket ошибки:

{
  "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/ и т.д.