plugin_loader.scan()readsplugins/anduser/plugins/- Each
plugin.jsonis validated and signature-checked - Enabled plugins get hooks, tools, voice commands, routes, and schedules registered
- Scheduler tasks are deferred if the scheduler hasn't initialized yet
Settings > Plugins calls PUT /api/webui/plugins/toggle/{name}:
- Enable: Loads immediately — all capabilities register
- Disable: Unloads immediately — hooks, tools, routes, schedules removed
Unsigned/tampered plugins return 403 and the toggle reverts.
POST /api/plugins/{name}/reload unloads and reloads a single plugin.
Set SAPPHIRE_DEV=1 to enable file watching — plugins auto-reload when .py or .json files change (2s polling).
If reload fails, the plugin stays unloaded. No half-loaded state.
POST /api/plugins/rescan discovers new or removed plugin folders without restart. Returns {"added": [...], "removed": [...]}.
A buggy plugin never crashes the system. If a hook handler throws an exception, it's logged and skipped — the next handler fires normally. Tool execution errors are caught and returned as error messages to the AI.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/webui/plugins |
List all plugins with metadata |
| PUT | /api/webui/plugins/toggle/{name} |
Enable/disable (live) |
| POST | /api/plugins/rescan |
Discover new/removed plugins |
| POST | /api/plugins/{name}/reload |
Hot-reload (dev) |
| GET | /api/webui/plugins/{name}/settings |
Read plugin settings |
| PUT | /api/webui/plugins/{name}/settings |
Save plugin settings |
| DELETE | /api/webui/plugins/{name}/settings |
Reset plugin settings |
| GET | /plugin-web/{name}/{path} |
Serve plugin web assets |
| * | /api/plugin/{name}/{path} |
Plugin custom routes (auth enforced) |
{
"plugins": [
{
"name": "ssh",
"enabled": true,
"locked": false,
"title": "SSH",
"settingsUI": "plugin",
"verified": true,
"verify_msg": "verified",
"version": "1.0.0",
"author": "sapphire",
"url": "https://sapphireblue.dev"
}
],
"locked": ["setup-wizard", "backup", "continuity"]
}