Understand how xbot’s plugin system works under the hood.
xbot’s plugin system follows a VSCode-like extension model. Plugins are discovered via plugin.json manifests, activated lazily based on events, and sandboxed through the PluginContext interface.
┌─────────────────────────────────────────────────────────┐
│ xbot Agent │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Tool Registry│ │ Hook Manager│ │WidgetRegistry│ │
│ └──────┬───────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────┴──────────────────┴────────────────┴──────┐ │
│ │ PluginManager │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Native │ │ Stdio │ │ Script │ │ │
│ │ │ Runtime │ │ Runtime │ │ Runtime │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ └───────┼────────────┼────────────┼────────────────┘ │
│ │ │ │ │
│ ┌───────┴────────────┴────────────┴────────────────┐ │
│ │ PluginContext │ │
│ │ (Permission-filtered API surface) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
The central orchestrator (plugin/manager.go). Responsibilities:
- Discovery: Scans
~/.xbot/plugins/and~/.xbot/plugins/builtin/forplugin.jsonfiles - Activation: Creates runtime instances and calls
Activate()based on activation events - Lifecycle: Manages plugin states (Discovered → Active → Inactive → Error)
- Dependency Resolution: Topological sort with cycle detection (Kahn’s algorithm)
- Hot Reload:
WatchConfigpollsconfig.jsonevery 30s for plugin enable/disable changes - Auto-retry: Exponential backoff (1s → 30s cap) for failed plugins
Every plugin implements three methods (plugin/plugin.go):
type Plugin interface {
Manifest() PluginManifest
Activate(ctx PluginContext) error
Deactivate(ctx PluginContext) error
}
Manifest()— Returns metadata (called once during discovery)Activate()— Registers capabilities (tools, hooks, widgets) via PluginContextDeactivate()— Cleans up resources (called on shutdown or unload)
Three runtime types (plugin/runtime_factory.go):
| Runtime | Description | Use Case |
|---|---|---|
native | In-process Go plugin | Maximum performance, direct API access |
stdio | External process via JSON-RPC over stdin/stdout | Any language, isolation |
script | External script execution | Simple widgets, bash/Python scripts |
grpc is a historical alias for stdio. WASM runtime is skeleton-only (planned).
The only interface plugins should use to interact with xbot (plugin/context.go). It’s a composite of sub-interfaces:
ToolRegistrar— Register tools and middlewareHookSubscriber— Subscribe to lifecycle hooksStorageProvider— Per-plugin KV storageSessionMetadata— Read-only session info (working dir, channel, chat ID)EventBusPublisher— Plugin-to-plugin eventsUIContributor— Register widgets, themes, overlaysCronScheduler— Schedule cron jobs
Access is filtered by declared permissions — plugins can only use what they declare.
Permissions are declared in plugin.json and enforced at runtime (plugin/permissions.go):
{
"permissions": ["tools.register", "ui.contribute", "bus.read"]
}
The PermissionChecker validates every PluginContext method call. Undeclared permissions are denied.
Discovered → Activating → Active → Deactivating → Inactive
↓ ↑
Error ←──────────────────────┘
| State | Description |
|---|---|
StateDiscovered | Manifest loaded, not yet activated |
StateActivating | Activate() in progress |
StateActive | Plugin is running and contributing |
StateInactive | Disabled by user or config |
StateError | Activation failed or runtime error |
PluginManager.Discover()scansDefaultPluginDirs():~/.xbot/plugins/— user-installed plugins~/.xbot/plugins/builtin/— built-in plugin packages
- Each directory is scanned for
plugin.json - Manifests are validated (ID format, version, runtime type)
- Runtime instances are created via
RuntimeFactory.Create() - Dependency activation order is resolved (topological sort)
ActivateAll()iterates plugins in dependency order- For each plugin,
Activate(ctx)is called - The plugin registers capabilities via
PluginContext - Capabilities are wired into xbot’s registries (tools, hooks, widgets)
- Channel providers are wired via
WireChannelProviders()
~/.xbot/
├── plugins/
│ ├── my-plugin/
│ │ ├── plugin.json # Manifest
│ │ ├── main.sh # Entry point
│ │ ├── data/
│ │ │ └── storage.json # KV storage
│ │ ├── config.json # User overrides
│ │ └── logs/ # Per-plugin logs
│ └── builtin/ # Built-in plugins
├── config.json # Global config (plugins.enabled, disabled_plugins)
- Plugin Manifest — Complete manifest specification
- PluginContext API — The plugin’s gateway to xbot
- Permissions — Capability-based security model
- Stdio Runtime — JSON-RPC protocol for any language