mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
fix: persist channel activation state across restarts (#432)
* fix: persist channel activation state across restarts (#392) Channels activated via the web UI were lost on restart because active_channel_names was only in memory. Now persist activation state to the settings store under "activated_channels" and auto-activate persisted channels on startup. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: log warnings for channel activation load failures Replace silent catch-all with explicit error logging when database queries or deserialization fails for activated channels. Addresses Gemini review feedback on PR #432. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
Illia Polosukhin
gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
parent
7b883a02c0
commit
fa52df593d
@@ -168,6 +168,53 @@ impl ExtensionManager {
|
||||
active.extend(names);
|
||||
}
|
||||
|
||||
/// Persist the set of active channel names to the settings store.
|
||||
///
|
||||
/// Saved under key `activated_channels` so channels auto-activate on restart.
|
||||
async fn persist_active_channels(&self) {
|
||||
let Some(ref store) = self.store else {
|
||||
return;
|
||||
};
|
||||
let names: Vec<String> = self
|
||||
.active_channel_names
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let value = serde_json::json!(names);
|
||||
if let Err(e) = store
|
||||
.set_setting(&self.user_id, "activated_channels", &value)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(error = %e, "Failed to persist activated_channels setting");
|
||||
}
|
||||
}
|
||||
|
||||
/// Load previously activated channel names from the settings store.
|
||||
///
|
||||
/// Returns channel names that were activated in a prior session so they can
|
||||
/// be auto-activated at startup.
|
||||
pub async fn load_persisted_active_channels(&self) -> Vec<String> {
|
||||
let Some(ref store) = self.store else {
|
||||
return Vec::new();
|
||||
};
|
||||
match store.get_setting(&self.user_id, "activated_channels").await {
|
||||
Ok(Some(value)) => match serde_json::from_value(value) {
|
||||
Ok(names) => names,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Failed to deserialize activated_channels");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Ok(None) => Vec::new(),
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Failed to load activated_channels setting");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the SSE broadcast sender for pushing extension status events to the web UI.
|
||||
pub async fn set_sse_sender(
|
||||
&self,
|
||||
@@ -530,6 +577,10 @@ impl ExtensionManager {
|
||||
Ok(format!("Removed WASM tool '{}'", name))
|
||||
}
|
||||
ExtensionKind::WasmChannel => {
|
||||
// Remove from active set and persist
|
||||
self.active_channel_names.write().await.remove(name);
|
||||
self.persist_active_channels().await;
|
||||
|
||||
// Delete channel files
|
||||
let wasm_path = self.wasm_channels_dir.join(format!("{}.wasm", name));
|
||||
let cap_path = self
|
||||
@@ -1993,6 +2044,9 @@ impl ExtensionManager {
|
||||
.await
|
||||
.insert(channel_name.clone());
|
||||
|
||||
// Persist activation state so the channel auto-activates on restart
|
||||
self.persist_active_channels().await;
|
||||
|
||||
tracing::info!(channel = %channel_name, "Hot-activated WASM channel");
|
||||
|
||||
Ok(ActivateResult {
|
||||
|
||||
+25
@@ -609,6 +609,8 @@ async fn async_main() -> anyhow::Result<()> {
|
||||
if let Some(ref ext_mgr) = components.extension_manager
|
||||
&& let Some((rt, ps, router)) = wasm_channel_runtime_state.take()
|
||||
{
|
||||
let active_at_startup: std::collections::HashSet<String> =
|
||||
loaded_wasm_channel_names.iter().cloned().collect();
|
||||
ext_mgr.set_active_channels(loaded_wasm_channel_names).await;
|
||||
ext_mgr
|
||||
.set_channel_runtime(
|
||||
@@ -620,6 +622,29 @@ async fn async_main() -> anyhow::Result<()> {
|
||||
)
|
||||
.await;
|
||||
tracing::info!("Channel runtime wired into extension manager for hot-activation");
|
||||
|
||||
// Auto-activate channels that were active in a previous session.
|
||||
let persisted = ext_mgr.load_persisted_active_channels().await;
|
||||
for name in &persisted {
|
||||
if !active_at_startup.contains(name) {
|
||||
match ext_mgr.activate(name).await {
|
||||
Ok(result) => {
|
||||
tracing::info!(
|
||||
channel = %name,
|
||||
message = %result.message,
|
||||
"Auto-activated persisted channel"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
channel = %name,
|
||||
error = %e,
|
||||
"Failed to auto-activate persisted channel"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wire SSE sender into extension manager for broadcasting status events.
|
||||
|
||||
Reference in New Issue
Block a user