Plugin system

Plugins extend the desktop agent. One manifest — open-bot.plugin.json — describes everything; GitHub repos hold the code and releases; the marketplace is the searchable index.

Manifest

Schema: packages/plugin-kit/schema/open-bot.plugin.schema.json (seeded into desktops and referenced by ob-plugin new scaffolds; kept in sync with validatePluginManifest).

Key fields:

FieldPurpose
files[]{name, source, exec} payload files fetched from the reviewed release tarball at install; text files only.
setup.commands / setup.uninstallRoot commands run at install (and after every desktop recreate via onDesktopReady → reapplyPluginSetup; the update recycle waits for that re-run to finish); uninstall runs best-effort. Logged to ~/.open-bot/plugin-init/<id>.log; shown in the consent dialog; security-reviewed. Must be idempotent — they re-run on an already-set-up desktop, so every step has to succeed twice (pipe grep/diff style exits through || true, guard installs with test).
opencode.agentsRegisters new agent workers (deep-merged into opencode.json; reserved names rejected). Helper sessions must use the worker: title prefix so the dashboard hides them.
opencode.agentToolsPatches tool globs on existing agents, e.g. {"build": {"blender_*": false}}.
opencode.agentsMdStanding instructions injected next to AGENTS.md (file under ~/.config/open-bot/agents-md/).
configs[]Typed settings: string (default), bool, number, enum + options[], secret. Validated/coerced by validateConfigValue; unknown keys rejected; upgrades prune dropped keys. Secrets land in ~/.config/open-bot/plugin-<id>.json.
hooks[]Max 8 {event: "session.idle"|"thread.created", message} prompts run in hidden worker: plugin-<id> <event> sessions. Loop-guarded: worker/cron sessions never re-trigger; 20 runs/day per plugin; 2 concurrent per event.
permissions.notificationstrue unlocks ob-plugin notify → POST /api/plugins/notice (30/hour) → dashboard toast + native notification.
versionSemver X.Y.Z[-prerelease] or dev tag (beta-1).

Config guide (CONFIG.md)

Every plugin project ships a repo-root CONFIG.md — the configuration guide the installing agent reads when it configures the plugin with the user. ob-plugin new scaffolds a template; fill it in whenever the manifest declares configs[] or vault slugs (ob-plugin doctor warns when it is missing or still the placeholder).

What goes in it: what the plugin needs configured, each setting and what to ask the user, each vaultCreate slug and where the user gets the key, the ask → fill → verify order (using ob-plugin settings / ob-plugin vault — never echo secret values), and how to verify the plugin works. It ships with the release tarball, is extracted by the control plane at install, and lands on the desktop as ~/.config/open-bot/plugin-<id>.CONFIG.md; the agent prints it with ob-plugin guide <id>. Lifecycle follows the plugin: overwritten on upgrade, restored on enable and boot re-sync, removed on disable/uninstall. Because it is agent-facing instructions, the marketplace security review reads CONFIG.md on every publish as a prompt-injection surface.

Dashboard pages

A plugin can ship a live dashboard — the Apps tab in the main navigation bar, where the user picks a plugin page and interacts with live data (stock lists, trackers, admin panels).

FieldPurpose
dashboard.pages.entryA files[] payload (Bun HTTP server) run by the control plane at install, enable, and after every desktop restart. Env: OPEN_BOT_PAGE_PORT, OPEN_BOT_PAGE_TOKEN, OPEN_BOT_PLUGIN_ID; settings/vault arrive via ~/.config/open-bot/plugin-<id>.json. GET /healthz recommended (any response counts as up).
dashboard.pages.publicOpt in to a public HTTP endpoint: /hooks/<pluginId>/<path> behaves like a normal web server in front of the page server — any method, pages/assets/streams, WebSocket upgrades. Requests wake the desktop (25 MB cap); the response returns verbatim. Optional per-install secret (X-Open-Bot-Webhook-Secret / ?secret=) makes it require authentication. ?ob_async=1 answers 202 immediately. URL copy + secret management on the Plugins tab → Settings → Public endpoint.
dashboard.tabs[].kind: "app"The server's full UI, proxied same-origin under /api/pages/<pluginId>/ — relative fetches hit the plugin server; SSE and WebSockets pass through. Optional path (default /).
dashboard.tabs[].kind: "page" + dataUrl / pollSeconds (5-3600)The dashboard polls the plugin server for JSON and feeds it to declarative cards ($.path bindings). No HTML needed for read-only views.

The proxy strips cookies/auth toward the plugin and injects x-open-bot-page-token; ports come from a dedicated range (4740-4719-style, per-plugin deterministic). Upgrades and settings changes restart the server so it observes new config; disable/uninstall stop it.

Install surfaces

  • Tools land in /usr/local/bin/ob-plugin-<id>-*; apply-plugin-auth.sh writes the vault config.
  • The config guide ships to ~/.config/open-bot/plugin-<id>.CONFIG.md when the plugin has a CONFIG.md.
  • The install log on the installed_plugins row reverses everything on uninstall.
  • Disable is deep: pauses cron, unloads OpenViking skills, drops vault config + opencode surfaces; enable restores from the stored manifest snapshot.
  • Failed root setup shows “setup failed — retry” on the Config page's Plugins tab (POST /api/plugins/installed/:id/setup).

Vault

permissions.vaultCreate declares key slugs the plugin owns (created empty at install); permissions.vaultRead consumes shared user keys. The config file's vault branch is generated from the key vault, so treat it as read-only — local edits are overwritten on enable/reinstall/settings changes. The durable write path is POST /api/plugins/installed/:id/vault (agent token, write-only — values are never returned) for slugs in that plugin's vaultCreate, or from the desktop: ob-plugin vault ID SLUG=value. The user can also fill any slug on the Config Keys page.

Persistence across desktop recreates

A desktop update (or any sleep/start cycle that recreates the container) wipes the container filesystem except the /home and /usr/local volumes. What survives without any re-run:

  • plugin tools and payload files (/usr/local/bin/ob-plugin-<id>-*)
  • vault config (~/.config/open-bot/plugin-<id>.json), agentsMd files, patched opencode.json surfaces, and anything else under /home/agent

What does not survive: anything a setup command installed into the container layer (apt packages, /venv, etc.). The boot re-apply re-runs setup.commands right after the container starts — it is what restores them, so keep the commands idempotent and expect a freshly recreated desktop to need a few minutes before root-installed dependencies are back.

Updates

  • installed_plugins.latest_version refreshes from the marketplace (throttled, on GET /api/plugins/installed).
  • ob-plugin outdated / update; dashboard “update to v…” button.
  • app_settings.plugin_auto_update: off (default) or stable (auto-installs newer approved stables).
  • Install policy app_settings.plugin_install_policy: manual (default, confirm dialog) or auto (also installs pending-review plugins).

Local development

ob-plugin install --path <plugin-root>

No publish needed; Re-install re-reads the path; Remove uninstalls. Schema changes need bun run db:generate and an image rebuild for existing desktops.

setup.commands run as root. Install policy auto also installs pending-review plugins — use it only on trusted instances.