# model-roles

## Назначение

`model-roles` теперь является active compatibility wrapper вокруг OMP-style model selection. Primary команда `/models` открывает интерактивный temporary selector через `ctx.ui.select` и `ctx.setModel`; `/models status` показывает доступные модели, provider tabs, query-filtered list и role assignments. Основной source of truth для ролей — OMP-native settings key `modelRoles`; старые JSON-файлы `model-roles` читаются только как read-only fallback. `/models assign <role> [query]` пишет роль в `modelRoles`, а `/models use <role>` применяет назначенную роль к текущей session model.

## Почему он есть в проекте

OMP уже имеет сильную модель выбора: список моделей с provider grouping, role tags, `cycleOrder`, thinking levels, temporary session selection и assignment в роли. Локальный `miloc-pi` не должен развивать отдельный product contract под названием `model-roles`; он должен забрать OMP semantics и дать здесь свой plugin/API surface. Поэтому текущий slice использует OMP settings key `modelRoles` для новых role assignments: `models` становится primary command и активным default wrapper, а `model-roles` — legacy status alias для старого config.

## Пользовательская поверхность

- Пользователь вызывает `/models`, чтобы интерактивно выбрать временную session model. Эта команда не пишет role/default settings.
- Пользователь вызывает `/models status`, чтобы увидеть доступные модели и текущие role badges.
- Пользователь вызывает `/models <query>` или `/models status <query>`, чтобы отфильтровать список по provider, model id или readable name.
- Пользователь вызывает `/models select [query]`, чтобы временно выбрать текущую модель из отфильтрованного списка. Это OMP-style temporary selection: модель меняется в текущем session context, но role/default settings не записываются.
- Пользователь вызывает `/models assign <role> [query]`, чтобы выбрать модель из отфильтрованного списка и записать selector в `ctx.settings.set("modelRoles", ...)`.
- Пользователь вызывает `/models use <role>`, чтобы применить роль из `modelRoles` к текущей session model. Если selector содержит thinking suffix вроде `:high`, extension вызывает `ctx.setThinkingLevel`.
- `/models cycle` сейчас показывает fail-closed status: полный OMP carousel еще не портирован.
- Legacy `/model-roles status` показывает старый user/project config. Legacy `/model-roles select`, `/model-roles set`, `/model-roles inherit`, `/model-roles use` и `/model-roles cycle` fail-closed.
- User defaults read-only импортируются из `~/.pi/agent/model-roles/config.json`. Если задан `$PI_MODEL_ROLES_HOME`, extension читает user config из `$PI_MODEL_ROLES_HOME/model-roles/config.json`.
- Project override read-only импортируется из `.pi/model-roles/config.json`. Project role имеет приоритет над user role; значение `null` в project config означает, что роль должна наследовать user default.
- Extension больше не регистрирует providers из старого config и не пишет user/project JSON.

## Как работает по коду

Entrypoint регистрирует primary команду `models` и legacy команду `model-roles`. Handler сначала читает OMP-native settings через `ctx.settings.get("modelRoles")` и `ctx.settings.get("cycleOrder")`, затем читает оба legacy config-файла как fallback. `buildState` объединяет стандартные роли, settings roles, legacy config roles и роли из `cycleOrder`; settings assignment перекрывает project/user legacy assignment, project assignment перекрывает user assignment, а project `null` сохраняет наследование.

`/models` без аргументов вызывает тот же temporary selection path, что и `/models select`: берет доступные модели из `ctx.modelRegistry.getAvailable()` или `ctx.modelRegistry.getAll()`, показывает `ctx.ui.select`, вызывает `ctx.setModel(model)` и выводит widget `models` с `persisted: false`.

`/models status` вызывает `formatModelsStatus`, который берет доступные модели из model registry, строит OMP-style tab header вида `Models: ALL CANONICAL <PROVIDER>`, поддерживает простой query filter, строит selector вида `provider/model`, добавляет role badges с thinking level вроде `[SMOL (low)]` и выводит widget `models`.

`/models select [query]` использует тот же model list, вызывает `ctx.ui.select`, затем `ctx.setModel(model)` и показывает `persisted: false`. Это соответствует OMP temporary selector path, где selection меняет текущую session model, но не сохраняет role assignment.

`/models assign <role> [query]` использует тот же model list, вызывает `ctx.ui.select`, затем пишет selector в `modelRoles` через `ctx.settings.set`. `/models use <role>` читает effective state, ищет модель через `ctx.modelRegistry.find()` или доступный model list, вызывает `ctx.setModel`, а при thinking suffix вызывает `ctx.setThinkingLevel`.

`/models cycle` и mutating legacy commands вызывают fail-closed status через `showDisabledModelMutation`. Они не пишут legacy config и не регистрируют providers.

- Entrypoint: `./extensions/model-roles/index.ts`
- Manifest: `extensions/model-roles/manifest.json`
- Commands: `models`, `model-roles`
- Tools: none
- Hooks: none
- Permissions: fs.read=`~/.pi/agent/model-roles/config.json`, `.pi/model-roles/config.json`, fs.write=none, subprocess=none, network=none, browser=false, models=true, ui=`select`, `setWidget`, `notify`
- State: OMP-native `modelRoles` и `cycleOrder` читаются через `ctx.settings`; старые user/project JSON configs читаются read-only fallback; effective state каждый раз вычисляется заново при чтении.
- Review: status=reviewed, source=copy-after-audit, reviewedBy=miloc-pi, reviewedAt=2026-06-01, risk=medium

## OMP source evidence

- `/tmp/oh-my-pi-review/packages/coding-agent/src/modes/components/model-selector.ts`
- `/tmp/oh-my-pi-review/packages/coding-agent/src/config/model-registry.ts`
- `/tmp/oh-my-pi-review/packages/coding-agent/src/config/model-resolver.ts`
- `/tmp/oh-my-pi-review/packages/coding-agent/src/cli/list-models.ts`
- `/tmp/oh-my-pi-review/packages/coding-agent/src/config/settings-schema.ts`
- `/tmp/oh-my-pi-review/docs/models.md`
- `/tmp/oh-my-pi-review/LICENSE`

## Ограничения и риски

Тесты покрывают read-only import старого user/project config, приоритет OMP-native `modelRoles` settings над legacy config, default `/models` temporary selector через `ctx.setModel`, OMP-style `/models status` list с provider tabs, query filter, role badges с thinking level, `/models select`, `/models assign`, `/models use`, fail-closed legacy mutation commands и отсутствие provider registration on `session_start`. Главный оставшийся риск в том, что настоящий OMP selector UI еще не портирован.

Compatibility gap: настоящий OMP selector имеет интерактивное переключение provider tabs, canonical model tab, fuzzy search, role context menu, scoped models и richer thinking metadata. Local `/models` пока дает simple select UI, text status widget и settings-backed role assignment; он еще не является полной selector UI.

## Решение

Решение: `compat-wrapper`, active by default. Extension достаточно тонко упаковывает OMP temporary model selection и `modelRoles` settings, чтобы быть default command surface; full OMP selector parity остается отдельным enhancement, а не причиной держать базовый `/models` выключенным.
