* fix(plugins): roll back cleared runtime registrations when an activating reload aborts
loadOpenClawPlugins clears the process-global agent-harness, plugin-command,
compaction, detached-task, interactive-handler, embedding, memory-embedding,
and memory stores up front on an activating load and only re-registers them at
the end via activatePluginRegistry. The body has a finally with no catch, so a
throw during discovery or manifest load (a corrupted or half-written plugin
manifest mid-upgrade) leaves the previously active registry marked active while
its runtime stores are gone process-wide; the gateway reload driver only logs
and swallows the error.
Snapshot the process-global stores before the clear with the existing
snapshotPluginProcessGlobalState helper and restore them on any throw before
activation. clearActivatedPluginRuntimeState moves next to the snapshot and
restore helpers it pairs with in plugin-registration-transaction.ts, which
already owns atomic plugin registration state and imports from every module the
clear touches; loader.ts re-exports it so existing callers are unchanged.
* fix(plugins): make reload rollback activation-safe
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): canonicalize manifest plugin ids to lowercase
Plugin config policy lists (plugins.deny, plugins.allow, plugins.entries)
are lowercase-normalized through normalizePluginId, but a plugin's
self-declared manifest id was only trimmed, never lowercased. A plugin
publishing "id": "Malicious-Scraper" therefore never matched an operator
denylist entry of "malicious-scraper" and fell through to default
activation, loading its hooks, channels, secret integrations, and tools.
The same gap let a mixed-case spelling evade the core reserved-id check.
Canonicalize the id at the manifest parse boundary where
PluginManifestRecord.id is minted, so every downstream policy consumer
compares against the same canonical form.
* test(plugins): prove mixed-case deny at gateway startup
* test(plugins): assert denied gateway record is absent
* test(plugins): materialize allowed secret fixture
* fix(plugins): compare a derived policy key instead of rewriting manifest identity
Manifest ids stay exactly as declared. Deny, allow, and per-entry checks now
compare a lowercase policy key derived at each enforcement boundary, matching
the lowercase-normalized config lists.
The previous approach lowercased PluginManifestRecord.id at the parse boundary.
That id is also matched against the plugin runtime export id, and a mismatch is
a hard load failure, so an existing plugin declaring the same mixed-case id in
both its manifest and its runtime export would stop loading after upgrade.
The Gateway fixture now declares a mixed-case manifest id with a matching
mixed-case runtime export, covering that upgrade case, and drops the allowlist
that previously scoped discovery by a lowercase id and masked the denylist
behavior under test.
* fix(plugins): close mixed-case policy gaps
* test(plugins): use tracked policy fixture temp dir
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>