Раздел: AI & Agents
Идентификатор:tasks· Экран:ui/tasks/TasksScreen.kt(410) +TasksViewModel.kt(118)
Снимок: 2026-10-11
1. Зачем и что это
Реестр ведёт backend сам (auto-task-registry): связь «key задачи → session_id сабагента» и статусы. Задача появляется в реестре, когда промпт сабагента начинается со строки вида «Задача auto-task-registry: …» — тогда реестр извлекает key и привязывает его к сессии. Это соглашение оркестратора, а не функция приложения.
Главная ценность экрана — кнопка Resume: она пишет сообщение прямо в чужую сессию сабагента, которая потерялась, зависла в busy или была прервана.
2. Как попасть
Хаб инструментов → секция AI & Agents → карточка «Задачи» (hub_tasks_title, подпись «Фоновые задачи и возобновление прерванных прогонов», тайл «Задачи»).
| Откуда ещё | Путь |
|---|---|
| Мост | app_navigate с screen: "tasks" |
Маршрут: Screen.kt:156 ("tasks" -> Screen.Tasks) → MainRouter.kt:250 (TasksScreen(onBack = …)).
3. Экран: элементы и действия
| Элемент | Что делает | Где в коде |
|---|---|---|
Шапка y83@tasks :~$ | Терминальная шапка; meta: «N tasks · serve up/down/unknown» | TASKS_PROMPT, tasksMetaLabel |
| Кнопка Refresh | Перезагрузка списка | viewModel.refresh() |
Кнопка MoreVert | Меню с единственным пунктом «Rescan registry» | DropdownMenu |
| Ряд чипов фильтра | All / Running / Completed / Error / Aborted | TASK_FILTERS + SelectChip |
| Pull-to-refresh | Обновить список жестом | PullToRefreshBox |
| Карточка задачи | Точка статуса, key (моноширинный), время «сколько назад», title, agent · status · attempt N, ошибка | TaskRow |
| Пустое состояние | «No tasks in the registry» + пояснение про «Задача <key>: …» + кнопка Rescan | TasksEmptyState |
| Диалог Resume | key, session, status, attempt, ошибка + поле сообщения | ResumeTaskDialog |
Точка статуса окрашена функцией taskStatusColor: running — успех (зелёный), error/aborted — ошибка (красный), остальное — нейтральный серый. Время показывается через elapsedLabel — «just now», «5m ago», «2h ago», «3d ago»; если метка неизвестна — «unknown».
4. Возможности подробно
Получение списка
TasksViewModel.refresh() вызывает tasksList(status = filter, limit = 50). Экран грузит первые 50 задач текущего фильтра; полный total приходит в ответе, но пагинации на экране нет — если задач больше 50, остальные не видны без REST-запроса вручную.
При первом открытии (tasks.isEmpty()) показывается полный спиннер, при повторном обновлении — только индикатор pull-to-refresh (isRefreshing = busy && !loading).
Фильтры
TASK_FILTERS = null→All, running, completed, error, aborted. Фильтр уходит на сервер, а не режется на клиенте. Смена фильтра немедленно вызывает refresh().
Resume по key
Диалог открывается тапом по карточке. В нём видно session <id> и attempt N, а поле сообщения по умолчанию — «продолжай». По кнопке Resume вызывается tasksResume(key, text).
Важно: resume идёт по key, а не по session_id — сессию подставляет сервер из реестра. Это защита от рассинхрона между экраном и живой сессией.
Сервер со своей стороны отправляет в сессию сабагента сообщение вида «Продолжай задачу <key>. <text>» и инкрементит attempt. Ответ содержит session_id, message_id, attempt, а экран показывает плашку «Resumed <key> · attempt N».
Rescan реестра
rescan() дёргает POST /api/v1/tasks/refresh — внеочередной backfill, который перечитывает сессии у opencode serve. Нужен, когда задача точно существует, но её нет в реестре (например, бэкенд перезапустился). После успеха экран автоматически перечитывает список и показывает «Registry refreshed».
Статусы и ошибки
Статусы: running | completed | error | aborted | unknown, переходы только вперёд. Причины aborted приходят в last_error:
| Текст ошибки | Что означает |
|---|---|
session lost (serve) | сессия пропала из opencode serve (3 поллинга подряд) |
stuck busy (opencode #6573) | running-сессия без активности дольше таймаута (по умолчанию 15 мин) |
Поле serve в метрике шапки говорит, отвечает ли сейчас opencode serve. Если serve: "down", данные реестра могли устареть — resume в этом случае вернёт 503.
Соглашение по промпту
Ключ задачи извлекается из task-part input.prompt, а не из title сессии (модель может переписать title и потерять ключ). Промпт всегда должен начинаться со строки «Задача <key>: …», где key без пробелов и двоеточий. Без этого задача в реестр не попадёт — и экран покажет пустое состояние с этой же подсказкой.
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Реестр задач | Память backend (in-memory), не в SQLite | События opencode serve + backfill | GET /api/v1/tasks |
| Восстановление реестра | Backfill GET /session при старте + поллинг 45 с + подписка /event | Автоматически | Backend |
DTO задачи (data/api/TasksApi.kt): key, session_id, parent_session_id, agent, title, status, task_part_status, attempt, last_error, created, updated, last_seen.
6. Backend: REST/роуты
| Метод и путь | Назначение | Обработчик |
|---|---|---|
GET /api/v1/tasks | весь реестр (фильтры status, key, parent, limit) | tr.List |
POST /api/v1/tasks/refresh | внеочередной backfill | tr.Refresh |
POST /api/v1/tasks/{key}/resume | продолжить задачу (пишет в сессию сабагента) | tr.Resume |
Роуты: backend/internal/api/v1/routes.go:317–319. Реализация: backend/internal/tasks/* (Store/Service/event/backfill/worker). Серверная версия реестра — 0.6.0.
Ошибки resume: 404 «task not found», 503 «opencode serve недоступен», 400 «bad json». Refresh может вернуть 409 «refresh in progress», если сканирование уже идёт.
7. Мост и MCP
| Что | Как | Примечание |
|---|---|---|
| Открыть экран | app_navigate с screen: "tasks" | BridgeScreenCatalog |
| Список задач | GET /api/v1/tasks?status=running | Скилл y83-tasks |
| Продолжить | POST /api/v1/tasks/{key}/resume | Скилл y83-tasks |
| Обновить реестр | POST /api/v1/tasks/refresh | Скилл y83-tasks |
Отдельных MCP-тулов для задач в инвентаре /mcp нет — агент работает с реестром через REST. Документация — docs/tasks.md, план — /root/plans/auto-task-registry.md.
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Лимит выборки на экране | 50 | tasksList(limit = 50) |
| Поллинг реестра | 45 с | Backend worker |
| Таймаут «stuck busy» | 15 мин (TASK_REGISTRY_BUSY_TIMEOUT) | Backend env |
| Порог «session lost» | 3 поллинга | TASK_REGISTRY_LOST_POLLS |
| Фильтров статуса | 5 (All + 4 статуса) | TASK_FILTERS |
9. Ловушки и ограничения
- Показ ограничен 50 задачами — пагинации на экране нет. Если задач больше, список обрезан; за полной картиной нужно идти в REST.
- Ключ задачи зависит от формата промпта. Если промпт не начинается с «Задача <key>: …», задача просто не появится в реестре — экран покажет пустое состояние с этой подсказкой.
serve: downозначает «данные могли устареть». Реестр живёт в памяти backend, при падении serve он не обновляется; resume вернёт 503.- Resume при
stuck busyсоздаёт новое сообщение в той же сессии — статус пересчитается при активности. Приsession lostтак не выйдет: сессии больше нет, задачу нужно перезапустить с новым промптом «Задача <key>: …». - Refresh может вернуть 409, если сканирование уже идёт (это защита от параллельных запросов).
- Все строки захардкожены по-английски — экран не локализован.
- Кнопка Resume пишет в чужую сессию. Это осознанное действие оркестратора, поэтому оно спрятано за подтверждением в диалоге, а не делается одним тапом.
- Статусы переходят только вперёд. После
completedтаск не вернётся вrunning, даже если вызвать resume.
10. Источники правды
app/src/main/java/com/y83/ws/ui/tasks/TasksScreen.kt— экран, форматирование, диалог Resume.app/src/main/java/com/y83/ws/ui/tasks/TasksViewModel.kt— состояние, фильтры, rescan, resume.app/src/main/java/com/y83/ws/data/api/TasksApi.kt— DTO и клиент.backend/internal/api/v1/routes.go:317–319— роуты.backend/internal/tasks/*— Store/Service/event/backfill/worker реестра.backend/internal/handlers/tasks/tasks.go— обработчики.skills/y83-tasks/SKILL.md— соглашение оркестратора и сценарии.docs/tasks.md— документация реестра.