Skip to main content

Пользовательские расширения

Расширение — это модуль на TypeScript или JavaScript, который добавляет агенту свой инструмент, команду /, флаг запуска или хук на событие. Расширение подключается со следующего запуска.

Агент напишет расширение сам

Опишите в чате, что нужно: «Сделай расширение, которое блокирует команды с rm -rf». Агент выберет подходящий механизм, напишет файл в ~/.veai/extensions/, проверит его командой veai extensions check и скажет, как включить.

Когда нужно расширение​

Расширение — это произвольный код, который выполняется в каждой сессии каждого проекта. Если задачу решает более лёгкий механизм, используйте его:

Что нужноЧто использовать
Повторно используемая инструкция или сценарийскилл
«Всегда делай X» в проекте или вездеправило
Инструмент, который обращается к сервису, API или CLIMCP-сервер
Хук на вызов инструмента, новая команда / или инструмент, который работает внутри процесса агентарасширение (эта страница)

Где лежат расширения​

Только в ~/.veai/extensions/. Проектных расширений нет: файл в <проект>/.veai/extensions/ не загружается. Так склонированный репозиторий не может запустить свой код в вашей сессии. Если каталога нет, создайте его.

Что лежит в ~/.veai/extensions/Загружается
foo.ts или foo.jsда, этот файл
foo/index.ts или foo/index.jsда, файл index
foo/package.json с "pi": { "extensions": ["./src/main.ts"] }да, каждый существующий файл из списка; путь за пределами foo/ пропускает расширение
foo/package.json с пустым pi.extensions или с несуществующими файлами и без indexпропускается: файла входа нет
foo/skills/, foo/prompts/, foo/themes/нет, загружаются только файлы входа
foo/bar/baz.ts без index или манифеста в foo/нет
Имя с суффиксом .off или .bak, файл или каталогнет, никогда
notes.md, foo.mjsнет
Путь с символом "пропускается

Кандидаты загружаются в порядке путей. Расширение из нескольких файлов кладите в каталог: вспомогательный файл рядом с одиночным расширением не входит в проверку, и правка только в нём не будет замечена. Всё нужное расширению держите внутри его каталога: символьная ссылка за его пределы и каталог .git/ в проверку тоже не входят.

Как написать расширение​

Расширение экспортирует функцию по умолчанию, которая получает ExtensionAPI. Функция может быть асинхронной. TypeScript выполняется как есть, сборка не нужна. Пакет typebox и пакеты самого движка доступны без установки.

Инструмент для модели:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
pi.registerTool({
name: "word_count",
label: "Word count",
description: "Count words in a text",
parameters: Type.Object({ text: Type.String() }),
async execute(_toolCallId, { text }) {
// content читает модель, details — структурированные данные для интерфейса и сессии
return { content: [{ type: "text", text: String(text.split(/\s+/).filter(Boolean).length) }], details: {} };
},
});
}

Только команда:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
pi.registerCommand("today", {
description: "Show today's date",
handler: async (_args, ctx) => {
ctx.ui.notify(new Date().toDateString(), "info");
},
});
}

Только хук: блокирует опасную команду до выполнения:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event) => {
if (event.toolName === "bash" && String((event.input as { command?: string }).command ?? "").includes("rm -rf")) {
return { block: true, reason: "rm -rf is blocked by a user extension" };
}
});
}

Полное описание API — все события, ctx, отрисовка, провайдеры — в собственной документации движка. Команда veai extensions check печатает абсолютный путь к ней в строке engine API:.

Зависимости​

Сначала используйте встроенные модули Node (node:fs, node:path, node:child_process) и глобальный fetch. Если без пакета не обойтись, сделайте расширение каталогом ~/.veai/extensions/<имя>/ с package.json, где пакет указан в dependencies, и файлом index.ts, а затем выполните npm install в этом каталоге. node_modules/ оттуда подхватывается автоматически.

Расширение, у которого не установлен импортируемый пакет, пропускается с подсказкой выполнить npm install в его каталоге. После установки оно загрузится со следующего запуска без правок файла.

Зарезервированные имена​

Расширение не может зарегистрировать инструмент или флаг, имя которых уже занято:

  • встроенные инструменты движка: read, bash, powershell, edit, write, grep, find, ls;
  • встроенные флаги движка, например --model и --extension;
  • все инструменты и флаги самого Veai CLI, например bash_output, fork, subagent, use_skill, web_search, --mcp-config;
  • имя, которое раньше зарегистрировало другое пользовательское расширение.

Расширение с занятым именем пропускается целиком. Полный список зарезервированных имён печатает veai extensions check --verbose в строке reserved:. Встроенный инструмент нельзя переопределить, зарегистрировав то же имя.

С хуками иначе: обработчик на tool_call, tool_result или любое другое событие может наблюдать за любым инструментом и блокировать его, включая встроенные.

Для команд / резервирования нет. Движок оставляет одинаковые команды под именами /имя:1, /имя:2 и скрывает команду, совпавшую со встроенной.

Субагенты, форки и /btw загружают те же расширения, что и основной агент, поэтому хуки действуют во всех дочерних агентах. Если в файле агента задан список tools:, инструмент расширения доступен такому агенту, только если список его называет.

Проверка: veai extensions check​

Сразу после создания или правки расширения выполните:

veai extensions check

Команда загружает каждого кандидата так же, как это сделает следующий запуск, но без кэша результатов, и печатает:

СтрокаЧто значит
<файл> loads: tools=… flags=… commands=… handlers=…расширение принято
<файл> skipped: <причина>пропущено, причина указана
<имя> disabled (settings: extensions.disabled)отключено в настройках, не проверялось
no user extensions in ~/.veai/extensions/загружать нечего
user extensions not loaded: the preflight failed: <причина>сама проверка не смогла выполниться
last restart: <файл>: <ошибка>движок не смог загрузить этот файл при прошлом запуске
<имя>.bak backup present: delete it once the edit is confirmedостался файл для отката
reserved: … и engine API: <путь>зарезервированные имена и путь к документации движка

Команда завершается с кодом 1, если включённое расширение пропущено или проверка не удалась, иначе с кодом 0.

Строка loads доказывает, что файл загружается и что он регистрирует. Правильно ли работает execute инструмента или хук, видно только при реальном вызове.

Как включить​

Расширение подхватывается при запуске. Что нужно сделать после изменения:

ИзменениеЧто сделать
Новое расширениеПерезапустите агента в том же каталоге проекта командой veai -c: она продолжает последнюю сессию
Код расширения, которое уже загружается/reload в запущенной сессии
Имя инструмента или флагаПерезапуск через veai -c, не /reload: /reload проверку не выполняет
Отключение или включение расширенияПерезапуск через veai -c

Если расширение при запуске пропущено, терминал показывает предупреждение с причиной и подсказкой запустить veai extensions check. Загрузка, которая не уложилась в 5 секунд проверки, тоже пропускает расширение: такое расширение замедляло бы каждый запуск.

Правка, откат и отключение​

Перед правкой существующего расширения скопируйте его в <имя>.bak (guard.ts.bak или каталог jira.bak/). Если <имя>.bak уже есть, не перезаписывайте его: в нём последняя подтверждённая версия. Имя с суффиксом .bak никогда не загружается.

Затем внесите правку, выполните проверку и включите изменение по таблице выше. Когда работа подтверждена, удалите .bak. Если не работает, верните .bak на место расширения и включите его тем же способом. Если проверка сообщает о забытом <имя>.bak, решите, работает ли последняя правка, и удалите копию или восстановите её. Агент делает то же самое, когда правит расширение по вашей просьбе.

Чтобы отключить расширение, не удаляя его, добавьте его имя в extensions.disabled в ~/.veai/settings.json и перезапустите агента. Имя — это имя файла, например guard.ts, или имя каталога, например jira:

{
"extensions": {
"disabled": ["guard.ts", "jira"]
}
}

Отключить расширение можно и переименованием в имя с суффиксом .off. Настройка действует только на глобальном уровне: проектный settings.json отключить расширение не может. См. «Настройки».

Если движок всё равно не загрузил расширение​

Если движок не загрузил расширение, прошедшее проверку, агент один раз перезапускается без пользовательских расширений и называет проблемный файл.

  • Не хватает зависимости (Cannot find module): выполните npm install в каталоге расширения, затем запустите проверку. Править файл не нужно: следующий запуск проверит его заново.
  • Любая другая ошибка: файл остаётся пропущенным, пока его содержимое не изменится. Исправьте файл и запустите проверку.

Совместимость​

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