dashboard(cli): reset persisted plugin state to stopped before autoload
Prior fix tried to flip started→installed but that transition is rejected by PluginStore.updatePluginState — only stopped, error, and the started→error path are allowed from started. The right reset target is "stopped"; from there loadPlugin's stopped→started is valid and the autoload completes cleanly. Also reset error→stopped so a plugin that failed its previous load (e.g. a transient missing dep that has since been fixed) gets one more chance at startup instead of staying stuck in error forever. Failures of the reset are warned, not thrown — autoload still gets to run on whatever it can.
This commit is contained in:
@@ -1395,20 +1395,31 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
// path that mirrors how runtime-providing plugins (paperclip etc.)
|
||||
// expect to be available immediately.
|
||||
//
|
||||
// Reset persisted state for enabled plugins back to "installed" before
|
||||
// loadAllPlugins runs. PluginStore.updatePluginState rejects no-op
|
||||
// transitions ("started" → "started"), and the persisted state from
|
||||
// the previous container generation is "started" for any plugin that
|
||||
// had been enabled — without this reset, autoload errors on every
|
||||
// restart for already-enabled plugins.
|
||||
// Reset persisted state for enabled plugins so loadPlugin can transition
|
||||
// them back to "started". The state machine in PluginStore is strict:
|
||||
// started → stopped | error
|
||||
// stopped → started | error
|
||||
// installed → started | stopped | error
|
||||
// error → installed | started | stopped
|
||||
// Persisted state from a previous container generation is typically
|
||||
// "started" (a healthy load that didn't get a clean shutdown), and
|
||||
// started → started is rejected. Walk the registry and flip every
|
||||
// enabled non-installed plugin to "stopped"; loadPlugin then drives
|
||||
// it forward to "started" cleanly.
|
||||
try {
|
||||
const persisted = await pluginStore.listPlugins();
|
||||
for (const p of persisted) {
|
||||
if (p.enabled && p.state === "started") {
|
||||
if (!p.enabled) continue;
|
||||
if (p.state === "started" || p.state === "error") {
|
||||
try {
|
||||
await pluginStore.updatePluginState(p.id, "installed");
|
||||
} catch {
|
||||
// Best effort — loader will surface the real failure if any.
|
||||
await pluginStore.updatePluginState(p.id, "stopped");
|
||||
} catch (resetErr) {
|
||||
logSink.warn(
|
||||
`Could not reset plugin state for ${p.id}: ${
|
||||
resetErr instanceof Error ? resetErr.message : String(resetErr)
|
||||
}`,
|
||||
"plugin-loader",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user