RUGitHubInstall

Рельсы плагинов: установка, Marketplace, сборка, MCP

ADR-0008 решил, что плагин — единица расширения, и оставил три расхождения кода со спекой. Это решение закрывает их и задаёт, как плагин попадает в инсталляцию, как живёт в ней и как её покидает. Строится сразу целиком, без промежуточных схем: рельсы переделывать у сотен чужих инсталляций дороже, чем построить один раз (решение владельца, 17.08.2026).

Что решено

Папка плагинов одна — data/custom/plugins/<name>/. Плагин лежит в Custom layer как git-checkout на запиненном sha и переживает iva update. Рядом, отдельными файлами: <name>.config.json (конфиг eve Extension, читается в рантайме) и <name>.env (env для MCP-серверов). PLUGIN_DATA плагина — data/plugin-data/<name>/, переживает апдейт плагина (требование спеки §9.1). Общую папку ~/.agents/plugins/ не читаем: второе место, откуда берётся плагин, — второе место, где он теряется.

Состояние — один файл data/custom/plugins.json. Запись на плагин: name, source, ref (что отслеживаем), sha (что стоит), digest (что лежит), enabled, trusted, installedAt. Плюс список маркетплейсов. Намерение и факт лежат рядом, отдельного lock-файла нет. enabled включает скиллы и код плагина; trusted отдельно разрешает поднимать его MCP-серверы — текст в промпте и процесс на машине не включаются одним тумблером (так у pi). iva plugin sync доставляет всё из этого файла: снёс папку — восстановится.

Источники: owner/repo[/подпапка][@ref], git-URL, локальная папка, имя из Marketplace. Установка: fetch --depth 1 --filter=blob:none origin <sha> → checkout FETCH_HEAD → сверка rev-parse HEAD; sha по ^[a-f0-9]{40,64}$. При установке записывается digest дерева плагина; update любого вида источника отказывает, если дерево в data/custom/plugins/ разошлось с записанным digest (правки владельца не затираются молча), --force снимает отказ. Так делают Codex и plugins.sh, только они проверяют git status.

Marketplace — конвенция Codex. Читаем .agents/plugins/marketplace.json в git-репо: name, plugins[{name, source, description}], source — строка-путь или объект local | url | git-subdir с ref/sha. npm и policy игнорируем. Дефолтный маркетплейс — smixs/iva-plugins; свои добавляются iva plugin marketplace add. Спека Agent Plugins дистрибуцию не описывает; три существующих формата маркетплейса несовместимы, а конвенция Codex — единственная у полного open-source конформного клиента, и наш список смогут прочитать его пользователи.

Клиент выполняет спеку буквально. Закрытая схема манифеста, валидация локально без сети, name по регэкспу спеки, только два нефатальных нарушения (лишнее поле верхнего уровня, extensions не объект). skills/ — только непосредственные дети с SKILL.md, без рекурсии. mcp.json — закрытый документ, версия $schema совпадает с манифестом. Containment всех путей; симлинки отвергаем; лимиты 2000 файлов / 50 МБ / 16 уровней (строже спеки, как NanoClaw — спека это разрешает). Битая компонента не валит остальные.

Скиллы плагина — живьём. Резолвер custom-скиллов читает data/custom/agent/skills/ и plugins/*/skills/ включённых плагинов; сборка и рестарт не нужны. Одноимённый скилл плагина побеждает ядровой (ближний слой, как у eve). Два плагина с одноимённым скиллом — add отказывает.

Код плагина — eve Extension под sh.iva/, собирает Ива. При сборке версии: копия плагина в <version>/plugins/<name>/ → npm ci --omit=dev в ней → npx eve extension build нашим eve → сгенерированный mount agent/extensions/<name>.ts с относительным спецификатором (eve его резолвит; package.json ядра не трогаем) → npm run build → probe → flip → рестарт. Всё по рельсам апдейтера v2. Плагин с кодом всегда совместим с текущим eve; апдейт eve пересобирает плагины сам.

Отказ сборки не роняет коробку (ADR-0003). iva plugin add: установка отменяется, текущая версия не тронута. iva update, где плагин сломался о новый eve: версия собирается без него, плагин получает enabled: false, владелец — Alert (ADR-0007). Один плагин не валит агента (в отличие от fail-loud DSH).

MCP — оба транспорта. streamable-http/sse → сгенерированный agent/connections/mcp-<name>--<server>.ts с defineMcpClientConnection (имя файла складывается в алфавит, который eve принимает под connection — [a-z][a-z0-9-], без подчёркиваний и точек; файл с другим именем eve не регистрирует вовсе). stdio → systemd-юнит iva-mcp-<name>-<server>.service с MCP proxy — своим сервисом на @modelcontextprotocol/sdk (services/mcp-proxy/), loopback-порт из plugins.json + bearer, Restart=on-failure, тот же connection-файл. Env сервера — только из mcp.json и <name>.env, не весь env агента; PLUGIN_ROOT/PLUGIN_DATA заданы; плейсхолдеры раскрываются один проход в args/env/cwd. Тот же механизм юнитов служит долгоживущим сервисам плагина, объявленным в sh.iva/.

CLI; код и MCP — только по тапу владельца (правка 04.10.2026). iva plugin add|remove| update|enable|disable|trust|untrust|list [--available]|sync|propose, iva plugin marketplace add|remove|list. Тула модели для установки нет. Плагин из одних скиллов модель ставит сама (iva plugin add из bash): это текст, который и так читается из Custom layer. Плагин с mcp.json или sh.iva/ — код в процессе с токенами инсталляции и процессы на машине, поэтому его ставит Bridge по тапу владельца на предложении, собранном кодом: iva plugin propose копирует папку в data/plugin-proposals/<name>-<digest12>/ и шлёт в личный чат владельца, что плагин будет запускать и сколько в нём файлов, с кнопкой «Установить»; тап забирает копию, сверяет её хеш дерева и запускает iva plugin install-proposal вне хода модели; установщик сверяет хеш ещё раз у копии в своём staging и ставит только её. Предложения и забранные копии старше суток удаляет следующий propose по времени папки — явное исключение из запрета «удаление по mtime» (docs/philosophy.md): удаляется только копия, исходная папка плагина остаётся у модели, худший исход — «предложи заново». Тап ставит забранной копии время тапа, поэтому идущая установка свою копию не теряет; копия, возвращённая после неудачного запуска установщика, получает обратно время propose, и срок предложения не продлевается. add такого плагина без TTY и не из установщика отказывает и называет propose; plugin trust|enable|update|sync|install-proposal при IVA_AGENT_TURN=1 отклоняет CLI с указанием iva plugin propose <папка>; эти команды выполняет Bridge по тапу владельца вне хода модели. Честная оговорка: пока ступень 2 ADR-0005 отложена, модель с bash может поставить что угодно мимо CLI; тап — граница намерения владельца на штатном пути, не защита от взломанной модели. При первом add печатается принятый риск из ADR-0008; при mcp.json со stdio — команды серверов и запрос доверия. iva doctor проверяет манифесты, plugins.json, сборку, юниты MCP proxy.

sh.iva/ — двух видов. eve Extension — это sh.iva/package.json (собирается в версию); сервисы — папки sh.iva/services/<svc>/ с service.json (поднимаются юнитом прямо из Custom layer и версию не пересобирают никогда). Ключ extensions["sh.iva"] обязателен для обоих: без него папка игнорируется.

Namespace — sh.iva. Ключ в extensions и папка sh.iva/ в корне плагина. Домен iva.sh проекту не принадлежит; спека требует reverse-domain как SHOULD и не проверяет владение. Свободен среди занятых (com.openai, dev.kiro, ai.openclaw, ai.nanoco.nanoclaw, dev.pi.agent, dev.opencode). Принятый риск, не менять.

Владение службами плагинов

Установка плагинов определяется canonical dataRoot: plugins.json, порты и замок общие для всех копий кода над этим каталогом. Имена новых служб содержат полный SHA-256 JSON-кортежа ["iva-plugin-data", 1, dataRoot]. Прежняя диагностика коллизии a-b/c и a/b-c сохраняется. Метка владельца кодирует обратимый JSON-кортеж в UTF-8 hex и связывает scope, ключ плагина, вид имени и хеш точного содержимого службы.

Один inspector устанавливает фактическое имя для reconcile, перезапуска и doctor. Старое имя сохраняется при доказанном каталоге данных в файле и загруженной службе: WorkingDirectory для сервиса, аргументы data/token/port для MCP. Добавление одной метки работающей службе сохраняет её процесс. Перед управлением scoped-службой также сверяется загруженная папка данных. Чужое содержимое, ссылки и неизвестное владение сохраняются; конфликт под ожидаемым именем останавливает изменение этого ключа.

Это защита штатных установок одного пользователя, а не граница против кода того же пользователя. Старые CLI, включая откат на прежнюю версию, этих правил владения не знают; их глобальный reconcile остаётся принятым риском совместимости. Отдельный файл владельцев, дополнительный замок и ремонт отката сюда не входят.

Что отвергнуто

Принятый риск

Плагин с кодом работает в процессе агента и видит его env (ADR-0008). Рельсы это не меняют: доверие — решение владельца при add. MCP-серверы получают меньше: только свой env, PLUGIN_ROOT и PLUGIN_DATA.

Изоляция здесь одна и узкая: от env агента. Друг от друга и от каталога данных плагины не изолированы — все юниты идут от того же пользователя, что и Ива, а токен прокси лежит в PLUGIN_DATA, то есть доступен любому процессу этого пользователя. Песочница за это не отвечает (ADR-0005 ступень 2 отложена владельцем).

Статус: решение принято 17.08.2026, реализация — волна «плагины», первый плагин — trace (ADR-0010).

View original Markdown ↗