> 🇬🇧 [English version](../plugins.md) — каноническая версия, она может быть новее.

<!-- ru-sync: docs/plugins.md@v0.3.26 -->

# Плагины

Плагин - это то, как Ива растёт: папка, которую вы приносите откуда угодно, ставите одной
командой и снимаете другой. Внутри скиллы, код и MCP-серверы, поэтому новая способность не
стоит ни правки поставляемого дерева, ни форка (ADR-0008). Формат -
[Agent Plugins 1.0.0](https://agent-plugins.org), та же папка, которую читают Codex, Cursor и
Kiro. Своего формата Ива не придумывала.

Плагин ставит только владелец и только из терминала. Ни команды в Telegram, ни инструмента у
модели нет намеренно: код плагина работает внутри процесса агента, а в окружении процесса
лежат ключи этой инсталляции, поэтому подсунутое сообщение не должно уметь поставить плагин
(ADR-0009). Что это значит для вас - в конце, в разделе [Чем вы рискуете](#чем-вы-рискуете).

## Поставить плагин

```bash
iva plugin add trace                         # имя из Marketplace
iva plugin add smixs/iva-plugins/trace       # owner/repo[/подпапка][@ref]
iva plugin add smixs/iva-plugins/trace@v1.2  # запинить тег, ветку или полный sha
iva plugin add https://github.com/you/my-plugin.git
iva plugin add ./my-plugin                   # папка на этой машине
```

`add` печатает состав папки до того, как что-то встанет на место:

```
  trace 0.1.0 — skills: trace; code: sh.iva; mcp: files
```

Перед самой первой установкой один раз печатается принятый риск. Если плагин несёт stdio
MCP-серверы или свои сервисы, команда покажет их команды запуска и спросит
`Start these processes on this machine? [y/N]`. Флаг `--trust` отвечает «да» без вопроса, а
неинтерактивная оболочка без `--trust` отвечает «нет».

Что будет дальше, зависит от состава:

- 📋 **Только скиллы** - работают со следующего хода. Без сборки и рестарта.
- ⚙️ **eve Extension в `sh.iva/` или MCP-серверы, которым вы доверяете** - их код и их
  connections живут внутри версии, поэтому Ива собирает новую с плагином внутри и
  перезапускает агента, по тем же рельсам, что `iva update`. Сборка не удалась - установка
  откатывается, работающая версия не тронута: поставить плагин не может сломать коробку
  (ADR-0003).
- 🔌 **Только сервисы** - папка `sh.iva/` с `services/` и без `package.json` в версию не
  попадает вообще. Ничего не пересобирается и ничего не рестартует: `trust` пишет юниты и
  поднимает их, вся установка на этом кончается.

## Команды

| Команда                                    | Что делает                                                                                          |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `iva plugin add <имя\|источник> [--trust]` | Поставить по имени из Marketplace или из папки, `owner/repo[/подпапка][@ref]`, `https://…`, `git@…` |
| `iva plugin list`                          | Что стоит: версия, sha, `enabled`/`disabled`, `trusted`/`untrusted`, состав, источник               |
| `iva plugin list --available`              | Что предлагают ваши маркетплейсы и что из этого уже стоит                                           |
| `iva plugin update [имя] [--force]`        | Подтянуть отслеживаемый ref, печатая `old → new`; без имени - все плагины                           |
| `iva plugin enable <имя>`                  | Включить плагин обратно, скиллы вернутся со следующего хода                                         |
| `iva plugin disable <имя>`                 | Выключить, не удаляя                                                                                |
| `iva plugin trust <имя>`                   | Разрешить его MCP-серверам и сервисам работать процессами на этой машине                            |
| `iva plugin untrust <имя>`                 | Погасить эти процессы и снять их юниты; скиллы продолжают работать                                  |
| `iva plugin remove <имя>`                  | Удалить плагин; его данные в `data/plugin-data/<имя>/` остаются                                     |
| `iva plugin sync`                          | Починка: пересобрать `plugins.json` по папкам и доставить недостающее                               |
| `iva plugin marketplace add\|remove\|list` | Ваши списки плагинов, которые ставятся по имени                                                     |

`iva plugin` без аргументов печатает тот же список. Все команды, меняющие состояние, берут тот
же лок, что `iva update`: работает одна за раз, вторая посреди обновления скажет об этом, а не
полезет в те же каталоги.

## Где живёт плагин

Всё лежит в Custom layer или рядом с ним, поэтому `iva update` этого не касается:

| Путь                                    | Что это                                                                                                                                     |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `data/custom/plugins/<имя>/`            | сам плагин: git-checkout, запиненный на один sha, или копия вашей папки                                                                     |
| `data/custom/plugins/<имя>.config.json` | ваши настройки для кода плагина, читаются на старте и в сборку не зашиваются                                                                |
| `data/custom/plugins/<имя>.env`         | окружение MCP-серверов плагина, по одной строке `KEY=value`                                                                                 |
| `data/plugin-data/<имя>/`               | данные самого плагина (`PLUGIN_DATA`), переживают его обновление и даже удаление                                                            |
| `data/custom/plugins.json`              | один файл состояния: `name`, `source`, `ref`, `sha`, `digest`, `enabled`, `trusted`, `installedAt`, плюс ваши маркетплейсы и выданные порты |
| `data/marketplace-cache/`               | списки маркетплейсов в том виде, в каком их прочитали последний раз                                                                         |

`plugins.json` - источник истины. Снесли папки, переехали на другой сервер, развернули старый
бэкап - `iva plugin sync` вернёт каждый плагин на записанный sha. Папку без записи он примет
обратно в файл, запись без папки доставит заново. Если восстановленный плагин несёт код, `sync`
об этом скажет: код попадёт в работающую версию следующим `iva update`.

Файлы конфига и env лежат рядом с папкой плагина, а не внутри: папку обновление
перезаписывает, а ваши значения обязаны это пережить.

## enabled и trusted

Два независимых тумблера, и `list` печатает оба:

- **enabled** - скиллы и код плагина загружены. `iva plugin disable` выключает их, оставляя всё
  на диске.
- **trusted** - MCP-серверам и долгоживущим сервисам плагина разрешено работать процессами на
  вашей машине. Свежая установка недоверенная: текст в промпте и процесс на хосте - это не один
  тумблер.

Недоверенный плагин работает, просто тише: скиллы грузятся, stdio MCP-серверы и сервисы не
поднимаются. `iva plugin trust <имя>` их запускает, а `untrust`, `disable` и `remove` гасят и
снимают юниты. Доверие плагину с MCP-серверами ещё и пересобирает версию: его connections
генерируются именно туда. Плагину с одними сервисами хватает юнитов.

Имена скиллов общие на всю Иву, поэтому `add` отказывает двум плагинам с одноимённым скиллом:
один из них молча погас бы. Ваш собственный скилл в `data/custom/agent/skills/` побеждает
одноимённый скилл плагина, а скилл плагина побеждает ядровой.

## Что делает с плагинами `iva update`

Каждый включённый плагин с кодом собирается заново тем eve, который запустит новая версия.
Поэтому плагин не коммитит `dist/` и не отстаёт от бампа eve: папка, конфиг, данные и флаг
доверия остаются как были, переделывается только сборка.

Плагин, чей код не собрался с новым eve, обновление не роняет. Версия собирается без него,
плагин выключается, а один Alert называет виновника:

```bash
iva plugin update <имя>
iva plugin enable <имя>
```

`iva plugin update` идёт по ref, записанному при установке: ветка, тег или sha, - и печатает
`old → new`. Папку, которую вы правили руками, он не трогает (`digest` в `plugins.json`
показывает, что содержимое разошлось), пока не передадите `--force`. Так локальные правки не
затираются молча. Записи, которую Marketplace запинил на коммит, идти некуда: чтобы её
сдвинуть, сделайте `remove` и `add` заново.

Если обновление сменило содержимое плагина, его юниты - сервисы и MCP proxy - тут же
перезапускаются на новый код. Поэтому плагину с одними сервисами `iva update` для новой
версии его кода не нужен.

## Marketplace

Marketplace - это список плагинов в git-репозитории: имя и источник, больше ничего. Ни центра,
ни модерации, ни чьего-либо разрешения на публикацию. Ива читает
`.agents/plugins/marketplace.json` по конвенции Codex, поэтому тот же список работает и для его
пользователей.

```bash
iva plugin list --available          # что предлагают
iva plugin marketplace add you/your-plugins
iva plugin marketplace list
iva plugin marketplace remove your-plugins
```

Список по умолчанию - `smixs/iva-plugins`, и свой добавляется рядом, не вместо него. Списки
кэшируются в `data/marketplace-cache/`; без сети берётся кэш с пометкой, что он может быть
устаревшим. Имя, которое предлагают два списка, отклоняется с подсказкой `add <имя>@<список>`. А запись,
у которой манифест называет плагин иначе, не ставится вообще: список - это файл, который
никто не проверял.

## Написать свой

Минимальный плагин - манифест и один скилл:

```
my-plugin/
  plugin.json
  skills/
    weather/
      SKILL.md
```

```json
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "Что он делает, одной строкой."
}
```

Обязательных полей два: `$schema` и `name`. Имя - от 1 до 64 знаков, строчные буквы, цифры,
дефисы и точки, первый и последний знак буква или цифра, без `--` и без `..`. Манифест
проверяется локально, схема из сети никогда не тянется.

Два ограничения стоит знать до упаковки: симлинки отвергаются целиком, а на плагин действуют
потолки 2000 файлов, 50 МБ и 16 уровней вложенности. Битая компонента не роняет остальные:
плагин с непригодным `mcp.json` всё равно отдаст свои скиллы, а `iva plugin list` напечатает
диагностику.

Пока пишете, ставьте саму папку: `iva plugin add ./my-plugin`. Она копируется в стор, а не
линкуется, и `iva plugin update my-plugin` копирует её снова из того же пути.

### Скиллы

`skills/<имя>/SKILL.md`, ровно один уровень вложенности, формат
[Agent Skills](https://agentskills.io) с `name` и `description` во frontmatter. Описание
пишется как условие срабатывания («Use when…»): до загрузки скилла модель видит только эту
строку. Всё остальное про скиллы - в [extending.md](../extending.md): скилл плагина это обычный
скилл, который приехал в папке.

### Код: eve Extension в `sh.iva/`

`sh.iva` - namespace Ивы, и внутри него живут две разные вещи: eve Extension, то есть
`sh.iva/package.json`, и сервисы, то есть `sh.iva/services/<svc>/service.json`. Любая из них
годится по отдельности. Namespace обязан быть объявлен в манифесте в обоих случаях: папку
`sh.iva/`, на которую манифест не заявился, Ива игнорирует и пишет об этом диагностику в
`iva plugin list`.

Для кода объявите ключ и положите в папку настоящий пакет eve Extension:

```json
{ "extensions": { "sh.iva": {} } }
```

```
my-plugin/
  sh.iva/
    package.json          # eve.extension.{source,dist}, пишется командой: eve extension init
    tsconfig.json         # обязателен: eve эмитит декларации через tsc и без него отказывается
    extension/extension.ts
```

`dist/` не коммитьте. Ива собирает его внутри версии: копия плагина, `npm ci` без dev- и
peer-зависимостей, `eve extension build` тем самым eve, который его запустит, сгенерированный
mount `agent/extensions/<ns>.ts`, дальше обычная сборка, проверка старта, переключение и
рестарт. `<ns>` - имя плагина, где `.` и `-` свёрнуты в `_`; оно же становится префиксом каждого
тула вашего расширения: `my_plugin__forecast`.

Настройки владельца приходят из `data/custom/plugins/<имя>.config.json`. Объявите их форму и
читайте значения через handle: сгенерированный mount передаёт ему содержимое файла на старте,
поэтому изменённая настройка стоит рестарта, а не пересборки.

```ts
// sh.iva/extension/extension.ts
import { defineExtension } from "eve/extension";
import { z } from "zod";

export default defineExtension({ config: z.object({ city: z.string() }) });
```

Расширение не может объявить `agent.ts`, `sandbox`, `schedules` и вложенные extensions: это
принадлежит агенту, который его монтирует. Своих расписаний у плагина нет по той же причине -
eve их запрещает.

### MCP-серверы: `mcp.json`

```json
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "files": {
      "type": "stdio",
      "command": "node",
      "args": ["server.mjs", "--root", "${PLUGIN_DATA}"],
      "env": { "FILES_LOG": "${PLUGIN_DATA}/log" }
    },
    "search": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer ${SEARCH_TOKEN}" }
    }
  }
}
```

Работают оба транспорта, и обоим нужен `trusted`:

- **`streamable-http` и `sse`** превращаются в сгенерированный connection с именем
  `mcp-<имя>--<server>`: файл `agent/connections/mcp-<имя>--<server>.ts` внутри версии. В имени
  connection eve принимает только строчные буквы, цифры и дефисы, поэтому обе половины
  сворачиваются в этот алфавит: сервер `Trace.Viewer` становится `trace-viewer`. Тулы модель
  находит через `connection_search`, как у любого другого подключения, и зовёт их
  `connection__mcp-trace--viewer__<tool>`. `${VAR}` в заголовке подставляется в рантайме из
  `data/custom/plugins/<имя>.env`, поэтому токен в сборку не попадает. Адрес обязан быть
  абсолютным `https://` (простой `http://` только на loopback), без user info и без фрагмента.
- **`stdio`** работает systemd-юнитом `iva-mcp-<имя>-<server>.service`, а перед ним стоит
  **MCP proxy**: сервис Ивы держит ваш сервер на его stdio и отдаёт агенту по streamable-http на
  `127.0.0.1:<порт>/mcp` за bearer. Токен лежит в
  `data/plugin-data/<имя>/mcp-<server>.token` с режимом `0600`; `GET /health` отвечает без него.
  `Restart=on-failure`, поэтому упавший сервер поднимается снова.

`command` - один токен: голое имя исполняемого файла или `./`-путь внутри плагина, но никогда
шелл-строка. `${PLUGIN_ROOT}` и `${PLUGIN_DATA}` раскрываются один проход в `args`, `env` и
`cwd`; рабочий каталог по умолчанию - `PLUGIN_ROOT`.

Что stdio-сервер видит в своём окружении - ради этого прокси и стоит: `PATH`, `HOME`,
`PLUGIN_ROOT`, `PLUGIN_DATA`, объявленное в `mcp.json` и то, что вы написали в `<имя>.env`.
Больше из окружения агента ему не достаётся ничего: ни токена Telegram, ни ключа провайдера.

Порты выдаются при `trust`, от 8730 вверх, записываются в `plugins.json` и держатся до
удаления плагина.

### Долгоживущий сервис

Плагин, которому нужен свой процесс - вьюер, воркер, что угодно, что должно пережить рестарт
агента, - объявляет его в `sh.iva/` рядом с тем же ключом `extensions` и без всякого
`package.json`:

```
my-plugin/
  sh.iva/
    services/
      viewer/
        service.json
        server.mjs
```

```json
{ "command": "node", "args": ["server.mjs"], "port": 8726 }
```

Ива пишет юнит `iva-plugin-<имя>-<svc>.service` и поднимает его только пока плагин включён и
доверен, оба условия сразу, с `Restart=on-failure`. Рабочий каталог - папка самого сервиса, а в окружении
ровно четыре переменные:

| Переменная         | Что в ней                                                                    |
| ------------------ | ---------------------------------------------------------------------------- |
| `IVA_SERVICE_PORT` | порт, который надо слушать; слушать только на `127.0.0.1`                    |
| `IVA_DATA_DIR`     | абсолютный путь каталога данных Ивы, чтобы читать оттуда, например, `trace/` |
| `PLUGIN_ROOT`      | папка плагина в сторе                                                        |
| `PLUGIN_DATA`      | `data/plugin-data/<имя>/`, ваш каталог для записи                            |

Плагины, которые Ива привозит из `smixs/iva-plugins`, написаны на TypeScript и запускаются
прямо через Node - `"command": "node", "args": ["server.ts"]`, без всякой сборки, - а ваш
собственный сервис может быть любой командой: контракт - это только `service.json` и те же
четыре переменные.

`port` в `service.json` - это пожелание: Ива берёт его, если свободен, иначе выдаёт следующий
свободный. Поэтому номер приезжает через `IVA_SERVICE_PORT`, а не константой в вашем коде.
Плагину с сервисами и без расширения сборка версии не нужна, ему нужны только юниты, а
`iva plugin update` перезапускает их на код, который только что привёз.

Юниты работают на Linux. На машине без systemd команды печатают `no systemd` и идут дальше:
состояние всё равно записано, поэтому та же инсталляция на сервере поднимет процессы.

## Проверить

```bash
iva plugin list      # по каждому плагину: тумблеры, состав, источник, диагностика
iva doctor           # раздел Plugins
iva plugin sync      # то, что doctor велит запустить, когда они расходятся
```

`iva doctor` читает манифесты, сверяет `plugins.json` с `data/custom/plugins/`, говорит, правда
ли код каждого плагина собран в работающую версию, и проверяет ожидаемые юниты: `is-active` для
каждого плюс `GET /health` для каждого MCP proxy.

## Чем вы рискуете

Прочитайте один раз, дальше решайте по каждому плагину отдельно.

Код плагина работает внутри процесса агента, а `bash` внутри его скилла - с окружением агента.
Значит оба видят все ключи этой инсталляции: токен Telegram, провайдера модели, всё остальное
из `.env`. Поставить чужой плагин - отдать ему эти ключи. Проверка манифеста от этого не
спасает: `plugin.json` описывает состав, а не поведение.

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

- ставит только владелец и только из терминала, никогда модель и никогда из Telegram;
- `trusted` - второе, отдельное «да» перед тем, как процесс плагина запустится на машине;
- MCP-сервер получает меньше агента: своё окружение, `PLUGIN_ROOT` и `PLUGIN_DATA`, и ничего
  сверх;
- плагин, который не собрался, до работающей версии не доходит.

Свой плагин вы видите насквозь, чужой - нет. Читайте код до установки или пиньте плагин на
тот sha, который прочитали.

Остальная картина по безопасности - в [security.md](security.md).
