refactor(web): read plugins through a PluginCatalog; only the display runs them (#688)

The web process built its own PluginManager and loaded plugins into itself:
store installs and updates loaded or reloaded a web-side copy, and config
saves and enable/disable called on_config_change, on_enable and on_disable
on it. None of that reached the panel, and /plugins/installed reported
runtime state from those copies.

- Add PluginCatalog (src/plugin_system/plugin_catalog.py): manifests,
  directories, display modes, installed version, schema and config reads,
  with no way to run a plugin. app.py and both blueprints use it; the
  plugin_manager blueprint attribute is gone.
- Remove every lifecycle call from the web routes. Config changes already
  reach the display through ConfigService (on_config_change) and the
  enabled-set reconcile.
- Health and metrics readers move to api_v3.health_tracker /
  resource_monitor. /plugins/installed reports loaded/state/error_info as
  null (the display does not publish them) and enabled by the display's
  rule.
- Store install, update and uninstall answer restart_required when the
  running display will not pick the change up by itself
  (display_restart_required). The restart banner follows the flag via
  window.noteRestartRequired instead of the /config/main URL heuristic;
  /config/main now sends restart_required: true.
- The one remaining in-process import of plugin code (Starlark helper
  modules, oauth_flow action scripts) goes through
  _import_plugin_code_in_web_process() until a web-entry contract.
- /plugins/installed reports vegas_participation (from #682) from the
  user's setting or the manifest, with vegas_participation_source; when
  only the plugin's code decides it, null with source 'runtime', since the
  web process no longer has plugin instances to ask.
- Check & Update All keeps its restart flags when the final list refresh
  fails, and asks for a restart when an enabled plugin's first request got
  no answer and the re-sent one found it up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-30 10:39:44 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ba6eccb489
commit 7ab6fb1aff
74 changed files with 1759 additions and 676 deletions
+55 -12
View File
@@ -154,10 +154,17 @@ there an unchecked checkbox — which the browser omits — is saved as
```json
{
"status": "success",
"message": "Configuration saved successfully"
"message": "Configuration saved successfully",
"restart_required": true
}
```
`restart_required` is always true here: display hardware, rotation,
durations and general settings take effect when the display restarts, and
the web UI shows its restart banner on the flag. (Plugin sections saved
through this route reach the running plugin live, like
`POST /plugins/config`.)
Invalid values (e.g. an out-of-range `target_fps`, a hardware option the
Raspberry Pi 5 driver cannot use) are rejected with `400` and nothing is
saved.
@@ -502,8 +509,8 @@ List all installed plugins with their status and metadata.
"tags": ["sports", "football", "nfl"],
"enabled": true,
"verified": true,
"loaded": true,
"state": "loaded",
"loaded": null,
"state": null,
"error_info": null,
"last_updated": "2025-01-15T10:30:00Z",
"last_commit": "abc1234",
@@ -512,19 +519,34 @@ List all installed plugins with their status and metadata.
"web_ui_actions": [],
"vegas_mode": null,
"vegas_content_type": null,
"vegas_participation": "scroll"
"vegas_participation": "scroll",
"vegas_participation_source": "manifest"
}
]
}
}
```
Metadata comes from each plugin's files on disk; `enabled` is the plugin's
`enabled` flag in `config.json` (missing means disabled, as the display
reads it). `loaded`, `state` and `error_info` are always `null`: the web
process runs no plugin code, and the display does not publish which plugins
it has loaded. What the display does publish is at
[`/plugins/health`](#get-plugin-health), `/plugins/metrics` and `/errors/*`.
`vegas_participation` is what Vegas mode does with the plugin: `"scroll"`,
`"pause"` or `"exclude"` (see
[PLUGIN_API_REFERENCE.md](PLUGIN_API_REFERENCE.md#vegas-participation)).
For a plugin that is not loaded it is only the user's own
`vegas_participation` setting, or `null`. `vegas_mode` and
`vegas_content_type` are the legacy hooks' raw answers.
[PLUGIN_API_REFERENCE.md](PLUGIN_API_REFERENCE.md#vegas-participation)),
and `vegas_participation_source` says where it came from. The web reads it
the way the display resolves it, as far as files can tell: the user's own
`vegas_participation` setting (`"config"`), else the manifest's declared
`vegas_participation` (`"manifest"`). Past those the display derives it from
the plugin's code -- a `get_vegas_participation()` override or the legacy
Vegas hooks -- which the web process never runs, so `vegas_participation`
is `null` and the source is `"runtime"`. A plugin that overrides
`get_vegas_participation()` decides at run time and can differ from its
manifest's declaration. `vegas_mode` is the plugin's configured
`vegas_mode`, or `null`; `vegas_content_type` is always `null`.
### Get Plugin Configuration
@@ -685,7 +707,13 @@ Install a plugin from the plugin store.
```
When the operation queue is unavailable the install runs synchronously and
the response has only a `message`.
the response has only a `message` and the restart fields below.
The finished operation's `result` (from `/plugins/operation/<operation_id>`)
carries `restart_required`: true when the plugin is already enabled in
`config.json`, because the running display does not load newly installed
files by itself; `restart_message` then holds the restart banner's wording.
A plugin that is not enabled needs no restart: enabling it loads it.
A plugin whose registry entry (or downloaded manifest) needs a newer
LEDMatrix is refused: the synchronous install answers `409` with a message
@@ -716,6 +744,11 @@ Remove an installed plugin.
}
```
The finished operation's `result` carries `restart_required`. Removing the
plugin's config (the default) lets the display unload it by itself, so it is
false; with `preserve_config: true` an enabled plugin keeps running until
the display restarts, and it is true.
### Update Plugin
**POST** `/api/v3/plugins/update`
@@ -736,11 +769,18 @@ Update a plugin to the latest version. Runs synchronously.
"message": "Plugin football-scoreboard updated ...",
"data": {
"last_updated": "2025-01-15T10:30:00Z",
"commit": "abc1234..."
}
"commit": "abc1234...",
"update_status": "updated"
},
"restart_required": true,
"restart_message": "Plugin updated — restart the display to run the new version"
}
```
`update_status` is `updated`, `up_to_date` or `local_only`.
`restart_required` is true when the plugin changed and is enabled: the
running display keeps the code it loaded until it restarts.
An update this core cannot run answers `409` with `Plugin update refused:`
and the reason; the installed version is left as it was.
@@ -772,10 +812,13 @@ Install a plugin directly from a GitHub repository URL. Runs synchronously.
"message": "Plugin my-plugin installed successfully",
"plugin_id": "my-plugin",
"name": "My Plugin",
"branch": "main"
"branch": "main",
"restart_required": false
}
```
`restart_required` follows the same rule as `/plugins/install`.
### Load Registry from URL
**POST** `/api/v3/plugins/registry-from-url`