Перейти к содержимому

Разработка плагина

Третьесторонний плагин — это TypeScript плюс декларативный манифест yttri-plugin.json. Код обращается к Yttri только через типизированный YttriHost.

Плагин — это папка с манифестом и одним ES-модулем. Минимальный рабочий вариант создаётся вручную:

Терминал
mkdir -p my-plugin/dist && cd my-plugin
yttri-plugin.json
{
"manifestVersion": "1",
"id": "acme.hello",
"name": "Hello",
"version": "0.1.0",
"sdkVersion": "^1.1.0",
"yttriVersion": ">=0.80.0",
"runtime": { "kind": "js_ts_v1", "entry": "dist/index.js" },
"permissions": [
{ "domain": "notes", "access": "read", "reason": "Показать число заметок" }
],
"capabilities": {
"tools": [
{ "name": "hello_count", "description": "Сколько заметок", "sideEffect": "read_only" }
]
}
}
dist/index.js
import { definePlugin } from '@yttri/plugin-api';
export default definePlugin({
async activate(host) {
host.log.info('activated');
},
tools: {
// Первый аргумент — host, второй — аргументы вызова.
async hello_count(host) {
const res = await host.invoke({
capability: 'notes',
access: 'read',
operation: 'notes.list',
payload: { limit: 100 },
});
return { count: Array.isArray(res) ? res.length : 0 };
},
},
});

Импорт @yttri/plugin-api резолвится самим рантаймом: это синтетический модуль внутри песочницы, устанавливать его не нужно. Пакет из npm понадобится только ради типов TypeScript — когда он будет опубликован.

Установите папку через Настройки → Плагины → Папка разработчика. После правки файлов нажмите Обновить: файлы заменятся, настройки и состояние сохранятся.

Чтобы получить пакет для распространения, заархивируйте папку — формат .yttri-plugin это обычный zip с манифестом в корне:

Терминал
zip -r my-plugin-0.1.0.yttri-plugin . -x 'node_modules/*' -x '.git/*'

Писать плагин на TypeScript можно уже сейчас, но типы @yttri/plugin-api придётся объявить самостоятельно или писать на чистом JavaScript: пакет с типами не опубликован. Компилировать нужно в ES-модуль — рантайм исполняет только JavaScript, TypeScript в пакете не собирается.

src/index.ts
import { definePlugin, type YttriHost } from '@yttri/plugin-api';
export default definePlugin({
async activate(host: YttriHost) {
host.log.info('activated');
},
tools: {
// Ключ совпадает с capabilities.tools[].name в манифесте.
async acme_ping(host: YttriHost) {
const token = await host.secrets.get('api_token');
const res = await host.network.fetch({
url: 'https://api.acme.com/ping',
headers: token ? { authorization: `Bearer ${token}` } : {},
});
return { ok: res.ok, status: res.status };
},
},
jobs: {
async sync(host: YttriHost) {
// Фоновая задача из capabilities.jobs.
},
},
});
  • host.invoke({ capability, operation, payload }) — вызовы в домены данных: notes, tasks, calendar, contacts, projects и search (чтение и создание), documents, mail и meetings (чтение). Write-операции требуют write-гранта.
  • host.secrets.{get,set,delete,list} — секреты в изолированном пространстве плагина; значения шифруются.
  • host.network.fetch(req) — HTTP только к хостам, объявленным в манифесте.
  • host.settings.getAll() — настройки плагина; форму по схеме генерирует Yttri.
  • host.accounts.* — реестр подключённых аккаунтов плагина: регистрация, список, статус, отметка синхронизации, удаление.
  • host.log.{info,warn,error} — лог без чувствительных данных.

Плагин может отдать ситуационному слою небольшой нормализованный факт и прочитать компактный статус связанных ситуаций:

import { publishObservation, lookupSituations } from '@yttri/plugin-api';
await publishObservation(host, { kind: 'source_updated', payload: { … } });
const status = await lookupSituations(host, { … });

Публикация требует гранта agent/write, чтение — agent/read. Плагин не получает доступ к таблицам и не может создавать или менять ситуации, планы, исходы и оценки — только внести факт и прочитать статус. Поля observationKinds и outcomeCapabilities в манифесте требуют sdkVersion, явно исключающего SDK 1.0 (например ^1.1.0).

  • activate(host) вызывается один раз при запуске плагина. Здесь регистрируют состояние модуля и читают настройки; сеть и запись в домены на этом этапе недоступны.
  • Дальше плагин живёт в своём процессе: состояние модуля между вызовами сохраняется, повторный вызов инструмента не перезапускает плагин.
  • Выключение останавливает процесс. Состояние в памяти теряется — всё, что должно пережить перезапуск, храните в данных Yttri или в секретах.
  • Ошибка инструмента терминальна для этого вызова: Yttri не повторяет его автоматически.

Инструменты включённого плагина попадают в реестр AI-агента как plugin_<имя>. Первым аргументом функция получает host, вторым — аргументы вызова, разобранные по схеме parameters из манифеста.

По умолчанию каждый вызов требует подтверждения пользователя. Доверенному плагину можно разрешить выполнение без подтверждений отдельным переключателем на его странице — права при этом не расширяются, гранты продолжают проверяться.

Пометьте инструмент честным sideEffect: read_only, mutating или external. Он влияет на то, что видит агент и как вызов проходит проверки.

capabilities.jobs объявляет задачи: ручные (кнопка на странице плагина) и по cron-расписанию. Они идут через общую очередь Yttri с таймаутами и повторами и не требуют открытого окна — плагин исполняется в фоновом процессе.

Задача не должна рассчитывать на состояние предыдущего запуска: между запусками процесс может быть остановлен.

  • host.log.{info,warn,error} — логи плагина видны на его странице во вкладке «Логи»; чувствительные данные из них вычищаются.
  • Ошибки установки и включения показываются с причиной: не тот entry, несовместимая версия SDK, пакет не загружается.
  • Правьте файлы и нажимайте Обновить в папке разработчика — переустановка не теряет настройки и выданные права.