# Задача: реализовать механику скиллов и тулсетов агента «Порфирий» Прочитай ТЗ: TASK-agent-tools.md (в корне репо). Реализуй ТОЛЬКО механику, БЕЗ привязки к медиа-данным (медиа-тулы будут позже, отдельно). ## Объём работ ### 1. Механика скиллов - Абстракция `Skill`: name, description (триггер, одна строка), body (текст) - `SkillRepository`: читает скиллы из папки `files/skills/` (YAML frontmatter: `---\nname: ...\ndescription: ...\n---\n` + markdown body) - `SkillRegistry`: индекс (имя + триггер) для промпта; `readSkill(name)` возвращает полное содержимое - Скиллов реальных пока НЕТ (механика работает на пустом каталоге или тестовых скиллах в юнит-тестах) ### 2. Механика тулсетов - `Toolset`: name, описание набора (для каталога), список тул (имя с префиксом набора + описание + сигнатура аргументов) - `ToolsetRegistry`: активность НА СЕССИЮ (Map сессия→(набор→deadline)), хипп, таймаут 10 минут с последнего вызова тулы набора, проверка при сборке промпта (никаких фоновых таймеров) - `enable_toolset|имя`: идемпотентно (повторный = продлить), неактивный вызов НЕ пишется в историю (системная тула-невидимка) - `disable_toolset|имя`: снять, идемпотентно («нет активных наборов» — не ошибка) - Неизвестное имя набора — «набора X нет, доступны: ...» ### 3. Сборщик промпта (три состояния) - Состояние 1 (набор не активен): ядро + каталог тулсетов + индекс скиллов - Состояние 2 (активация): enable вызывает пересборку, вызов в историю НЕ пишется - Состояние 3 (активен): ядро + описания тул набора + «Активные тулсеты: имя» + скилл, связанный с набором, УБРАН из промпта - Промпт пересобирается на каждый запрос ### 4. Бесшовная подмена вызова - Вызов тулы набора при НЕактивном наборе (например media_search): НЕ выполнять, подменить как будто вызван enable_toolset|media, ответить «Тулсет media активирован. Реестр: ... Поиск ещё НЕ выполнялся. Для поиска вызови media_search с аргументом query.» - Если аргументы вызова совпадают со схемой из реестра — можно выполнить сразу ### 5. Обработчик вызовов - Префиксная маршрутизация: имя тулы с префиксом набора (media_*) → набор - Ядро-тулы: get_current_time (есть), show_text (есть ShowText), read_skill, enable_toolset, disable_toolset - Все тулы идемпотентные, прощающие, с понятными ошибками - Валидация аргументов по реестру ### 6. Юнит-тесты (JVM, без Android) — ОБЯЗАТЕЛЬНО Покрыть механику полностью: - сборка промпта: 3 состояния - подмена вызова при неактивном наборе (включая «поиск ещё не выполнен») - идемпотентность enable/disable - таймаут (инжектируемые часы — не ждать реальные 10 минут) - активность на сессию (переключение сессий) - префиксная маршрутизация, валидация аргументов - read_skill: чтение из files/skills, парсинг frontmatter ## Ограничения - Код только Kotlin (как в проекте) - НЕ трогать существующую логику ассистента без необходимости — встраиваться аккуратно - НЕ делать реальные медиа-тулы (search по каталогу и т.п.) — это отдельный этап - Юнит-тесты должны быть зелёными: ./gradlew :app-phone:testDebugUnitTest - Не коммитить без подтверждения ## Проверка 1. ./gradlew :app-phone:testDebugUnitTest — зелёные 2. ./gradlew :app-phone:assembleDebug — собирается