Files
view-mate/TASK-opencode.md
subochev 7527cf8848 LLM: интерфейс + две реализации (Qwen удалённо / Gemma 4 E2B локально) + переключатель
- LlmClient интерфейс; RemoteLlmClient (OkHttp, llm.binom.pw) и LocalLlmClient (LiteRT-LM, Gemma 4 E2B)
- LocalLlm: Gemma4E2B (SHA-256, GPU/CPU-бэкенды), buildLocalLlmPrompt (SYSTEM→systemInstruction, TOOL→user), GemmaModelDownloader (стриминг, прогресс, фолбэк источников), спекулятивное декодирование
- LlmPrefs (персистентность выбора), PhoneApp: фабрика по llmChoice, пересоздание ассистента, скачивание/отмена
- UI: GlassesInfoScreen — выбор сервер/локальная + прогресс; ChatScreen — индикатор активной модели
- Тесты: +12 (LlmLocalTest) — phone 154 зелёных
2026-08-23 02:52:22 +03:00

56 lines
5.0 KiB
Markdown

# Задача: реализовать механику скиллов и тулсетов агента «Порфирий»
Прочитай ТЗ: 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 — собирается