Як написати плагін
Плагін реалізує один трейт — Plugin з опублікованого крейта
rtok-plugin-sdk. Це так незалежно від того, чи
постачається плагін у цьому репозиторії, чи живе в чужому (D25): контракт один,
і жодного обхідного шляху для вбудованих плагінів немає. crates/rtok-plugin-sdk/examples/shrink.rs — повний плагін в
одному файлі; examples/hello_plugin.rs — те саме, підключене до справжнього хоста.
0. Чи має він існувати?
Перегляньте plan.md §1 (каталог) і §0 (рішення), а також roadmap.md щодо порядку задач
цього плагіна. Якщо методу там немає, додайте його в ideas.md (вихідний інструмент + плагін; Пізніше, якщо це
v0.2+) і спершу запропонуйте зміну плану. Кожен плагін тут пишеться з нуля: він не має
викликати через оболонку, лінкувати чи читати дані стороннього інструмента (D6). Інструмент, який він замінює, — це
специфікація; research.md — докази.
1. Трейт
use rtok_plugin_sdk::{
Ctx, DashboardPage, Manifest, Plugin, PreToolDecision, PreToolUse, Surface,
};
pub struct MyPlugin;
impl Plugin for MyPlugin {
// Обов'язково: що це за плагін.
fn manifest(&self) -> Manifest {
Manifest { id: "<id>", surfaces: &[Surface::Hook], default_on: true }
}
// Обов'язково: сторінка, яку відображають і `rtok web`, і `rtok tui` (D23).
fn dashboard_page(&self) -> DashboardPage {
DashboardPage::new("<Title>", "One sentence for the operator.", true)
}
fn pre_tool(&self, ev: &PreToolUse, cx: &Ctx) -> Option<PreToolDecision> {
let cfg = cx.plugin_config::<crate::config::MySection>("<id>");
let _ = (ev, cfg);
None
}
}
Два обов'язкові методи обов'язкові вже під час компіляції: плагін, який не каже, що він таке
і яку сторінку показує, не збирається. Кожен метод подій — pre_tool, post_tool,
session_start, prompt_submit, pre_compact, mcp_tools, proxy_filter — має типову
реалізацію, що нічого не робить, тож реалізуйте лише інтерфейси, які оголошує маніфест. Документація крейта містить таблицю
того, коли хост запитує кожен із них.
cx — це хост: cx.estimate(text, Class::Code), cx.record(&Measurement { .. }),
cx.log(..), cx.plugin_config::<T>("<id>") для вашого власного розділу [plugins.<id>] і
cx.config::<T>("proxy") для рідкісного значення на рівні всього хоста. До збереженого стану звертаються так само —
cx.put_archive(..), cx.search_notes(..), cx.symbol_defs(..) — через трейти можливостей,
до яких розіменовується Ctx. Плагін ніколи не звертається до бази даних хоста напряму; якщо якоїсь можливості
бракує, її свідомо додають до SDK, а не обходять.
2. Усередині цього репозиторію: підключіть його (три однорядкові зміни)
Cargo.toml→[features]: додайте<id> = []і допишіть його доdefault.src/plugins/mod.rs:#[cfg(feature = "<id>")] pub mod <id>;і відповіднийv.push(Box::new(<id>::MyPlugin));вall(). Позиція = порядок диспетчеризації.src/config.rs→CATALOGUE:("<id>", default_on)на тій самій позиції.
Тест registry_matches_catalogue падає, доки всі три не узгоджено. Імпортуйте з
rtok_plugin_sdk напряму — crate::plugin реекспортує ті самі типи для власного
коду диспетчеризації хоста, але плагін називає крейт, який він реалізує.
3. Поза цим репозиторієм
[dependencies]
rtok-plugin-sdk = "0.0.1"
Реалізуйте Plugin на основі крейта — три залежності, без SQLite, без tree-sitter, без вебсервера.
Щоб запустити його, зберіть бінарник, який залежить від rtok і передає ваш плагін
реєстру: Registry::from_plugins(vec![Box::new(Mine)], &Config::load()?). Цей репозиторій
не постачає сторонніх плагінів і жодних адаптерів. v0.2 (P32) додає необов'язковий шлях для того самого
контракту у вигляді .wasm у ~/.rtok/plugins/ лише для rtok mcp / rtok proxy — ніколи для
rtok hook; див. crates/rtok-plugin-sdk/PLAN.md § v0.2 survey — WASM plugin host.
4. Документація поруч із кодом
src/plugins/<id>/PLAN.md— проєктна нотатка D15 (скопіюйтеdocs/plugin-plan-template.md). Потрапляє в коміт перед першою задачею реалізації плагіна. Обов'язкові заголовки, ≥ 3 розглянуті альтернативи, одинTarget:і одинFalsified by:. Перевіряєтьсяcargo test plugin_plans.src/plugins/<id>/README.md— що він робить, що замінює, інтерфейси, механізм, ключі конфігурації, задачі плану, статус. Для користувачів.src/plugins/<id>/AGENTS.md— файли, якими він володіє, інваріанти, які не можна порушувати, дозволені залежності, Checks. Для того (людини чи моделі), хто редагуватиме його наступним.
Скопіюйте структуру з наявного плагіна; README і AGENTS.md мають уміщатися на один екран.
5. Правила, яких дотримується кожен плагін
- Fail open. За будь-якої помилки повертайте
None/порожнє значення. Ніколи не панікуйте на шляху хука; диспетчер перехоплює паніки, але паніка все одно є багом. - Без втрат. Якщо ви щось скорочуєте, спершу збережіть оригінал через
cx.put_archiveі покладіть id уMeasurement::ref_idта в текст, який бачить модель. Модель має мати змогу виконати для ньогоexpand. - Вимірюйте. Кожна дія, що змінює те, що бачить модель, викликає
cx.record(&Measurement)з байтами до/після й оціненими токенами. Економії без рядка не існує. - Бюджет. Усе для SessionStart/UserPromptSubmit повертається як
Injectionі ніколи не записується вadditionalContextнапряму. Текст має бути побайтово стабільним між ходами. - Швидкість. Шлях хука ≤ 10 мс p95 у release-збірці. Один індексований запит до БД — нормально; обхід файлової системи чи мережевий виклик — ні.
- Жодної нової залежності без однорядкового обґрунтування в повідомленні коміту, і лише з
базового списку в
plan.md§2. - Не дублюйте. Повторно використайте наявну допоміжну функцію або винесіть одну спільну на рівень, якому належить ця поведінка.
6. Тестування
Модульні тести живуть у модулі. Поза деревом або для плагіна, якому не потрібне сховище, використовуйте
rtok_plugin_sdk::testing::MemoryHost — він зберігає те, що плагін записує й архівує:
use rtok_plugin_sdk::testing::MemoryHost;
use rtok_plugin_sdk::Ctx;
#[test]
fn rewrites_recursive_ls() {
let host = MemoryHost::new();
let input = serde_json::json!({ "command": "ls -R src" });
let ev = PreToolUse { tool_name: "Bash", tool_input: &input };
assert!(MyPlugin.pre_tool(&ev, &Ctx::new(&host)).is_some());
assert_eq!(host.recorded().len(), 1);
}
У цьому репозиторії crate::plugin::Runtime::in_memory("test") — це справжній хост без
файлу: використовуйте його, коли перевірка стосується того, що насправді потрапило до сховища, а фікстури
в tests/fixtures/hooks/ — для реалістичних подій. Потім Check задачі з plan.md (порядок див. у
roadmap.md), а потім just check.
7. Випуск
Закомітьте <task-id>: <title> у власній гілці задачі й відкрийте для неї один пул-реквест (D16). У тому самому пул-реквесті позначте задачу виконаною і
перенесіть її з plan.md до done.md з датою та виводом Check. Реалізована робота, яка досі
значиться в plan.md, незавершена.