From 16e85139b9f66d6684ad5ba573677c66dacc840d Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Sun, 11 Oct 2026 12:15:19 +0300 Subject: [PATCH] =?UTF-8?q?chore(opencode):=20=D1=8D=D0=BA=D1=81=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D1=82=20=D1=81=20permissio?= =?UTF-8?q?n-reviewer=20=E2=80=94=20=D0=B8=D1=82=D0=BE=D0=B3=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Плагин настроен и проверен (jev через Zen + прокси, enforce+deny), но признан не стоящим использования: хрупкая цепочка, нервные эскалации, запертая сессия. Запись в opencode.json отключена (закомментирована), выводы — в docs/research/. --- ...pencode-permission-reviewer-autoapprove.md | 75 +++++++++++++++++++ opencode/.config/opencode/opencode.json | 14 ++-- .../opencode/permission-reviewer.jsonc | 17 +++-- .../.config/opencode/permission-reviewer.md | 70 +++++++++++++++++ 4 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 docs/research/2026-10-11-opencode-permission-reviewer-autoapprove.md create mode 100644 opencode/.config/opencode/permission-reviewer.md diff --git a/docs/research/2026-10-11-opencode-permission-reviewer-autoapprove.md b/docs/research/2026-10-11-opencode-permission-reviewer-autoapprove.md new file mode 100644 index 0000000..263436b --- /dev/null +++ b/docs/research/2026-10-11-opencode-permission-reviewer-autoapprove.md @@ -0,0 +1,75 @@ +# Эксперимент: ИИ-ревьюер разрешений в OpenCode (autoapprove) + +**Дата:** 2026-10-11 +**Статус:** эксперимент закрыт, плагин снят. Документ сохранён как описание +системы и причин отказа — если идея вернётся, начинать отсюда. + +## Итог одним абзацем + +Плагин `opencode-permission-reviewer` (LLM-ревьюер каждого `ask`) настроен и +работоспособен, но признан не стоящим использования: хрупкая цепочка (ключ в +окружении сервиса → региональный 451 на endpoint jev → прокси), плюс ревьюер +нервно эскалует всё подряд в сессиях с «опасным» транскриптом. Боевой режим: +`permission: autoaccept` + deny-паттерны на секреты + snapshots/git как +обратимость. AFK для системного администрирования не достигнут: узкое место — +не разрешения, а компетентность модели-исполнителя (glm flash провалил простые +админские задачи: «делал странное с умным видом»). + +## Как была устроена система + +Цепочка решений для каждого `ask` из `opencode.json`: + +1. **Deny-паттерны** (`permission.bash`) — мгновенный отказ по строке команды, + до моделей. Только секреты: `*.env*`, `*.secrets*`, `~/.ssh/*` и т.п. + Паттерны слепы к содержимому скриптов — защита секретов, не рубеж. +2. **Jev** (`opencode/jev-1.13`, System One) — типизированный ревьюер: команда, + свидетельства из транскрипта, содержимое локальных скриптов (read-only + enrichment). Отвечает allow / deny / escalate. +3. **Второе мнение** (`opencode-go/glm-5.3-flash`) — трудные случаи, где jev + неуверен. +4. **Финал** — `escalationMode`: `manual` (спросить человека; при + `permission=autoaccept` на него отвечает auto) или `deny` (отказ с причиной). + `enforcementMode`: `observe` (только аудит, дефолт) или `enforce`. + +## Инфраструктурные грабли (главная ценность эксперимента) + +- Jev ходит на отдельный endpoint Zen `/zen/v1/systemone`. Zen закрывает его + по региону (HTTP 451) — нужен прокси в окружении сервиса + (`HTTPS_PROXY` в `~/.env.secrets`). Чат-модели Zen + (`/zen/v1/chat/completions`) доступны без прокси; блокирован только + systemone. Jev нет в общем каталоге моделей — это намеренно (не чат-модель), + на плагин не влияет. +- `OPENCODE_API_KEY` должен быть в окружении **процесса сервиса**. Сервис + наследует окружение шелла, из которого запущен; после правки + `~/.env.secrets` нужны `source` + рестарт из свежего шелла. Рестарт из + старого шелла ключ не подхватывает. +- Ревьюер зовётся из серверного процесса: рестарт сервиса из сессии агента + рвёт саму сессию. +- Emergency brake: команды, пахнущие выносом креденшелов + (`curl -H "Authorization: ... $KEY"`), режутся детерминированно. +- Конфиг плагина читается с O_NOFOLLOW: симлинк молча игнорируется, деплой + только копией (см. `.stow-local-ignore`). + +## Известные свойства поведения + +- Сессия, полная опасных команд в транскрипте, выглядит для ревьюера + подозрительно: jev эскалует всё подряд, и с `escalationMode: deny` такая + сессия становится неработоспособной — fail-closed по замыслу. +- В этой сессии это проявилось полностью: заперт даже `git status`, отменить + отказы изнутри сессии нельзя (явное разрешение в чате не помогло). +- Пороги тонкой настройки (не крутились): `systemOneConfidenceThreshold` + (дефолт 0.4), `systemOneReasoningThreshold` (дефолт 0.38), + `confidenceThreshold` (чат-ревьюер, 0.7). +- Установлено про авто-подтверждение без плагина: ручное approve при серии + запросов вырождается в слепое «да» — честный auto + обратимость лучше + декоративного ручного. + +## Ссылки + +- Плагин и все опции: https://github.com/warc0s/opencode-permission-reviewer + (README: https://npm.io/package/opencode-permission-reviewer) +- Модель разрешений OpenCode V2: https://opencode.ai/v2/docs/permissions +- Jev: https://jevmodel.org/ (прямой API TypeSafe с ключом `TYPESAFE_API_KEY` — + запасной маршрут мимо Zen) +- Аудит решений (на машине): `~/.local/share/opencode/permission-reviewer-audit.jsonl` +- Снятый конфиг сохранён закомментированным в `opencode/.config/opencode/opencode.json` diff --git a/opencode/.config/opencode/opencode.json b/opencode/.config/opencode/opencode.json index 4ade50c..f3d7451 100644 --- a/opencode/.config/opencode/opencode.json +++ b/opencode/.config/opencode/opencode.json @@ -1,11 +1,13 @@ { "$schema": "https://opencode.ai/config.json", - "plugins": [ - { - "package": "opencode-permission-reviewer@^2.4.0", - "options": {} - } - ], + // Плагин opencode-permission-reviewer снят (эксперимент 2026-10-11 признан + // неудачным — см. docs/research/). Блок сохранён для возврата: + // "plugins": [ + // { + // "package": "opencode-permission-reviewer@^2.4.0", + // "options": {} + // } + // ], "default_agent": "build", "permission": { "write": "ask", diff --git a/opencode/.config/opencode/permission-reviewer.jsonc b/opencode/.config/opencode/permission-reviewer.jsonc index fa8547a..eac3b2d 100644 --- a/opencode/.config/opencode/permission-reviewer.jsonc +++ b/opencode/.config/opencode/permission-reviewer.jsonc @@ -1,10 +1,17 @@ { - // Модель-ревьюер разрешений. Jev (System One) через OpenCode Zen: - // ключ OPENCODE_API_KEY читается из окружения процесса opencode (~/.env.secrets). - // Платный вариант без временного окна: opencode/jev-1.13 - "model": "opencode/jev-1.13-free", - // Ниже порога уверенности решение уходит человеку + // Как устроена вся система автоподтверждения — см. permission-reviewer.md рядом. + // Модель-ревьюер разрешений: Jev (System One) через OpenCode Zen. + // Zen закрывает /zen/v1/systemone по региону (451) — нужен прокси + // (HTTPS_PROXY в ~/.env.secrets, сейчас hproxy.local:12334). + // Ключ OPENCODE_API_KEY читается из окружения процесса opencode. + "model": "opencode/jev-1.13", + // Ниже порога уверенности решение уходит дальше по цепочке "confidenceThreshold": 0.7, + // enforce: политика рисков применяется (дефолт observe — только аудит) + "enforcementMode": "enforce", + // deny: финальная эскалация = отказ, а не запрос человеку + // (при permission=auto на запрос человека всё равно отвечает auto) + "escalationMode": "deny", // Сложные/неуверенные решения Jev получают второе мнение чат-модели // вместо эскалации к человеку. glm-5.3-flash из подписки opencode-go: // лимиты в 4 раза выше, чем у deepseek-v4.1-flash, интеллект сравним. diff --git a/opencode/.config/opencode/permission-reviewer.md b/opencode/.config/opencode/permission-reviewer.md new file mode 100644 index 0000000..e27a8ee --- /dev/null +++ b/opencode/.config/opencode/permission-reviewer.md @@ -0,0 +1,70 @@ +# Система автоподтверждения разрешений (autoapprove) + +Как устроено подтверждение команд в OpenCode на этой машине, и где что искать. + +## Цепочка решений + +Каждый `ask` из `opencode.json` проходит слои сверху вниз: + +1. **Deny-паттерны** (`permission.bash` в `opencode.json`) — мгновенный отказ по + строке команды, до всяких моделей. Только секреты: `*.env*`, `*.secrets*`, + `~/.ssh/*` и т.п. Паттерны слепы к содержимому скриптов — это не рубеж, а + только защита секретов. +2. **Jev** (`opencode/jev-1.13`, System One) — типизированный ревьюер: получает + команду, свидетельства из транскрипта и содержимое локальных скриптов + (read-only enrichment), отвечает allow / deny / escalate. +3. **Второе мнение** (`opencode-go/glm-5.3-flash`) — разбирает трудные случаи, + где jev неуверен, но есть шанс на решение. +4. **Финал** — `escalationMode: deny`: неразрешённое отклоняется с причиной + агенту. Человек в цепочке не участвует: при `permission=auto` запрос к + человеку всё равно решил бы auto. + +Ключевые настройки в `permission-reviewer.jsonc` рядом с этим файлом: +`enforcementMode: enforce` (применять политику рисков; дефолт observe — только +аудит), `escalationMode: deny` (дефолт manual — спросить человека). + +## Инфраструктура + +- Jev ходит на OpenCode Zen endpoint `/zen/v1/systemone`. Zen закрывает его + по региону (HTTP 451), поэтому сервису нужен прокси: `HTTPS_PROXY` в + `~/.env.secrets` (сейчас `hproxy.local:12334`). Чат-модели Zen + (`/zen/v1/chat/completions`) доступны без прокси — блокирован только + systemone. +- Ключ `OPENCODE_API_KEY` должен быть в окружении **процесса сервиса**: + `~/.env.secrets` сорсится из `~/.bash_custom`, сервис наследует окружение + шелла, из которого его запустили. После правки `.env.secrets`: + `source ~/.env.secrets && opencode service restart` — рестарт из старого + шелла ключ не подхватит. +- Jev нет в общем каталоге моделей OpenCode — это намеренно (не чат-модель), + на плагин не влияет: он зовёт systemone endpoint напрямую. + +## Аудит и отладка + +- Журнал решений: `~/.local/share/opencode/permission-reviewer-audit.jsonl` — + одна JSON-строка на ревью: вердикт, уверенность, причина, кто решал + (`decisionSource: system-one-reviewer / llm-reviewer / failure-safe`). +- Поле `reviewerEscalatedFrom` показывает, что первичный ревьюер (jev) + сработал, но эскалировал. +- `failure-safe` в `reason` = ревьюер недоступен (нет ключа, 451, таймаут) — + решение ушло в эскалацию. +- Emergency brake: команды, пахнущие выносом креденшелов наружу + (`curl -H "Authorization: ..."`), режутся детерминированно, мимо моделей. + +## Известные свойства + +- Сессия, полная опасных команд в транскрипте, выглядит для ревьюера + подозрительно: jev начинает эскаловать всё подряд, и с `deny` такая сессия + становится неработоспособной. Это fail-closed по замыслу. Лечение — работать + в чистой сессии. +- Тонкие пороги: `systemOneConfidenceThreshold` (дефолт 0.4) — насколько + уверенным должен быть jev, чтобы решать самому; + `systemOneReasoningThreshold` (дефолт 0.38) — сколько эскалаций уходит + на второе мнение. + +## Документация + +- Плагин и все опции: https://github.com/warc0s/opencode-permission-reviewer + (README на https://npm.io/package/opencode-permission-reviewer) +- Модель разрешений OpenCode V2: https://opencode.ai/v2/docs/permissions +- Jev у TypeSafe AI: https://jevmodel.org/ (есть прямой API с отдельным ключом + `TYPESAFE_API_KEY` — запасной маршрут, если Zen закроет systemone совсем)