From 5ed1bc14bee170b26de009a1dfe7d7352d30acb8 Mon Sep 17 00:00:00 2001
From: szafranski
Date: Thu, 11 Jun 2026 19:48:05 +0200
Subject: [PATCH] fix(channel): reuse stored credentials when re-enabling a
plugin
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Settings re-enable toggle sends an empty config `{}` and relies on
the previously stored credentials. `enable_plugin` rejected this with
`InvalidConfig("missing field credentials")`, so a channel could be
enabled once but never re-enabled after being disabled — the toggle
reverted and the bot never restarted.
Resolve the effective config before starting: when an enable request
omits credentials, fall back to the persisted (encrypted) config instead
of failing. Configs that do carry credentials are still validated as
before, so genuinely malformed input is reported as `InvalidConfig`.
Co-Authored-By: Claude Opus 4.8
---
crates/aionui-channel/src/manager.rs | 59 ++++++++++++++++++-
crates/aionui-channel/src/types.rs | 20 +++++++
.../tests/manager_integration.rs | 28 +++++++++
3 files changed, 104 insertions(+), 3 deletions(-)
diff --git a/crates/aionui-channel/src/manager.rs b/crates/aionui-channel/src/manager.rs
index 2d874e27..c673c76c 100644
--- a/crates/aionui-channel/src/manager.rs
+++ b/crates/aionui-channel/src/manager.rs
@@ -109,9 +109,14 @@ impl ChannelManager {
let plugin_type =
PluginType::from_str_opt(plugin_id).ok_or_else(|| ChannelError::InvalidPluginType(plugin_id.to_owned()))?;
- // Parse and validate config structure
- let config: PluginConfig = serde_json::from_value(config_value.clone())
- .map_err(|e| ChannelError::InvalidConfig(format!("Invalid config: {e}")))?;
+ // Resolve the effective config. The Settings re-enable toggle sends an
+ // empty config and expects the previously stored credentials to be
+ // reused, so fall back to the persisted config when no new credentials
+ // are supplied.
+ let config: PluginConfig = match Self::config_with_credentials(config_value)? {
+ Some(config) => config,
+ None => self.load_stored_config(plugin_id).await?,
+ };
// Stop existing plugin if running
if self.plugins.contains_key(plugin_id) {
@@ -368,6 +373,54 @@ impl ChannelManager {
// ── Private helpers ──────────────────────────────────────────────
+ /// Parses a freshly supplied plugin config, returning it only when it
+ /// carries usable credentials.
+ ///
+ /// Returns `Ok(None)` when the caller supplied no credentials (an empty
+ /// `{}` config or one whose `credentials` field is absent or blank),
+ /// signalling that the stored configuration should be reused. A config that
+ /// does carry credentials but fails to parse is reported as `InvalidConfig`.
+ fn config_with_credentials(config_value: &serde_json::Value) -> Result