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

Как работает плагин

Плагин — это npm-пакет, в котором бывает до трёх точек входа, по одной на процесс bb. Серверная точка входа обязательна и работает внутри сервера. Клиентская точка входа (необязательная) работает в окне приложения, а хостовая (тоже необязательная) — на машине агента. Точки входа не делят память. Они общаются через RPC, push-канал реального времени и хостовый RPC, а всё долговременное состояние живёт в серверной точке входа. bb загружает каждую точку входа с полным доверием. Если вы ещё не знакомы с этими тремя процессами, сначала прочитайте Как работает bb.

Манифест — это package.json плагина. Блок bb в нём перечисляет точки входа:

"bb": {
"name": "Hello",
"server": "./server.ts", // required
"app": "./app.tsx", // optional: UI in the app window
"host": "./host.ts" // optional: code on the agent's machine
}
Точка входаРаботает вДля чего нужнаФормат
серверная точка входа, bb.serverпроцессе сервера bbхранилища, RPC- и HTTP-маршруты, CLI-команды, инструменты агента, провайдеры, расписанияэкспортируемая по умолчанию фабрика (bb: BbPluginApi) => …
клиентская точка входа, bb.appокне приложения, в том же JS-контексте, что и собственный интерфейс bbпанели, контролы и рендереры в окне, дополнения в композере, команды палитрыdefinePluginApp((app) => …)
хостовая точка входа, bb.hostпроцессе хост-воркера, который демон хоста запускает на своей машинефайлы, процессы и мост агента на машине агентаexperimental_defineHostEntry({ contract, handlers })

Серверная точка входа обязательна, даже если плагин добавляет только интерфейс: в файле plugins/scheduled-send/server.ts лежит намеренная заглушка. Все поля манифеста описаны в Анатомии пакета.

Ни серверная, ни клиентская точка входа не запускают главный цикл. Серверная точка входа — это функция, которую bb вызывает один раз за загрузку и передаёт ей свежий объект bb. Эта функция регистрирует всё, что предоставляет плагин (RPC-метод, CLI-команду, настройку, инструмент агента), и возвращает управление. Клиентская точка входа делает то же самое через объект app: регистрирует панель, компонент слота или команду, а bb монтирует этот компонент там, где в окне находится нужный слот. С этого момента bb вызывает код плагина, когда он нужен человеку, агенту или другому процессу. Вызовы регистрации перечислены в Неймспейсах бэкенда и Слотах фронтенда.

app.tsx

useRpc<typeof rpcContract>()

rpc.call("listIssues", { … })

import type — the backend is erased from the bundle

1↓ rpc.call(…) → POST /plugins/<id>/rpc/<method>

POST /plugins/<id>/rpc/<method>

input schema → handler → output schema

2↓ validation → server.ts — the factory, in the server process

server.ts — the factory, in the server process

bb.rpc.register(…)

bb.http.route(…)

bb.cli.register({ name, run })

bb.agents.registerTool(…)

bb.realtime.publish(…)

the result is strict JSON

3↓ bb.realtime.publish → WebSocket /ws — one shared socket (signal back)

8↓ host RPC → host.ts, on the agent’s machine

↓ read and write → state and the outside world

useRealtime("issues:changed", fn)

the same component refetches its data

WebSocket /ws — one shared socket

{ type, pluginId, channel, payload }

broadcast to all; the client filters

4↓ useRealtime → refetch → useRealtime("issues:changed", fn) (signal back)

bb CLI

bb <command> … · argv without the command name

5↓ POST /cli → POST /plugins/<id>/cli

POST /plugins/<id>/cli

run() executes on the server

↓ server.ts — the factory, in the server process

agent tool

the tool set is frozen at session start

6↓ execute(…) → called inside the server process

called inside the server process

errors come back as text

↓ server.ts — the factory, in the server process

external service · webhook

POST with the signature in the request body

7↓ signature in the body → ALL /plugins/<id>/http/<path>

ALL /plugins/<id>/http/<path>

your own route, exact path match

↓ server.ts — the factory, in the server process

state and the outside world

bb.storage.kv · bb.storage.database()

fetch · fs · child_process

host.ts, on the agent’s machine

called through bb.hosts.experimental_client(…)

signals back with experimental_emitSignal(…)

1–2 RPC · 3–4 realtime · 5 CLI · 6 agent tool · 7 webhook over an HTTP route · 8 host RPC. Five callers, one server entry, and no shared memory between any of them.

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

КаналОткуда → кудаДля чего нуженСправочник
RPCклиентская точка входа → серверная точка входачтение и изменение данных из интерфейса; контракт определяется один раз в серверной точке входа, а клиентская импортирует только его типbb.rpc
realtimeсерверная точка входа → каждое открытое окноуведомление страниц об изменении данных, чтобы они запросили их заново; ничего не сохраняется и не воспроизводитсяbb.realtime
CLICLI bb → серверная точка входакоманда bb <command> для людей и агента; выполняется на сервере, а не в CLIbb.cli
инструмент агентаагент → серверная точка входанативный инструмент в сессии агента, вызываемый внутри процесса сервераbb.agents
HTTPвнешний сервис → серверная точка входавебхуки и другие вызывающие стороны за пределами bbbb.http
хостовый RPCсерверная точка входа ⇄ хостовая точка входаработа на машине агента с отправкой сигналов обратноbb.hosts

Двух каналов намеренно нет. Клиентская точка входа не может напрямую вызывать хостовую: она вызывает серверную точку входа по RPC, а та уже обращается к хостовой. Также ни одна точка входа не может напрямую обратиться к другому плагину.

Скилл завершает картину для агента. Плагин поставляет файл skills/<name>/SKILL.md, bb внедряет его в треды агента, и скилл говорит агенту, какую команду bb выполнить. Затем агент проходит через CLI-канал так же, как человек, вместо того чтобы напрямую обращаться к хранилищу плагина. В Первом плагине разобран шаблон плагина, который использует RPC, realtime, CLI и скилл для работы с единым хранилищем.

Серверная точка входа владеет всем долговременным состоянием: значениями в bb.storage.kv, собственной базой данных SQLite из bb.storage.database() и настройками, объявленными через bb.settings.define. Клиентская точка входа хранит только то, что рендерит. Она запрашивает данные по RPC и запрашивает их заново при получении realtime-сигнала, поэтому каждое открытое окно показывает одни и те же данные. Хостовая точка входа держит собственные файлы на своей машине в каталоге, который ей выдаёт bb.

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

bb запускает фабрику серверной точки входа при каждой загрузке плагина: при старте сервера, после установки, по команде bb plugin reload и после каждого сохранения, пока работает bb plugin dev. Из этого следуют четыре правила:

  1. Работа фабрики ограничена 30 секундами. То, что длится дольше или выполняется бесконечно, помещайте в фоновый сервис через bb.background.service.
  2. Объект bb валиден ровно одну загрузку. После перезагрузки сохранённая ссылка на него выбрасывает ошибку PluginContextStaleError, поэтому никогда не сохраняйте bb в переменную на уровне модуля.
  3. При перезагрузке bb сначала загружает новый экземпляр, а затем вызывает dispose для старого. Если новая фабрика падает с ошибкой, старый экземпляр продолжает работать, а его статус остаётся running с пометкой «reload failed: …».
  4. Помещайте код очистки в bb.onDispose. bb вызывает эти хуки в обратном порядке регистрации, когда плагин перезагружается, отключается или когда сервер останавливается.

В окне приложения bb заново вызывает фабрику клиентской точки входа при каждом изменении бандла. Если компонент слота выбрасывает ошибку, её ловит внутренний error boundary: bb показывает fallback, остальная часть окна продолжает работать, а слот остаётся отключённым до перезагрузки плагина. Точные шаги загрузки и dispose, восемь статусов и цикл горячей перезагрузки описаны в Рантайме и жизненном цикле.

bb не прячет плагины в песочницу. Серверная точка входа работает внутри процесса сервера и имеет неограниченный доступ к fs, net, child_process и fetch. Клиентская точка входа — это код страницы с тем же источником (same-origin) в собственном окне bb. Хостовая точка входа работает от имени того же пользователя ОС, что и агент. Граница безопасности — это решение человека установить плагин. В Модели доверия перечислено, к чему может получить доступ код плагина, и описаны правила проектирования, которые из этого следуют.