refactor: remove ExtensionSource::Bundled, use download-only install for WASM channels (#293)

The Bundled variant and its local-artifacts fallback are superseded by the
embedded registry catalog which provides WasmDownload entries with GitHub
release URLs. The in-chat extension manager now always downloads channel
WASM binaries from releases, simplifying the install path.

The setup wizard retains its own local install_bundled_channel path for
dev builds where build artifacts exist on disk.

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Henry Park
2026-02-21 13:55:49 -08:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 37c0158765
commit c1f3b83c98
4 changed files with 5 additions and 150 deletions
-1
View File
@@ -246,7 +246,6 @@ fn extract_url(source: &ExtensionSource) -> String {
ExtensionSource::Discovered { url } => url.clone(),
ExtensionSource::WasmDownload { wasm_url, .. } => wasm_url.clone(),
ExtensionSource::WasmBuildable { repo_url, .. } => repo_url.clone(),
ExtensionSource::Bundled { name } => name.clone(),
}
}
-36
View File
@@ -521,9 +521,6 @@ impl ExtensionManager {
entry.name, entry.name
)))
}
ExtensionSource::Bundled { name } => {
self.install_bundled_channel_from_artifacts(name).await
}
_ => Err(ExtensionError::InstallFailed(
"WASM channel entry has no download URL".to_string(),
)),
@@ -829,39 +826,6 @@ impl ExtensionManager {
Ok(())
}
async fn install_bundled_channel_from_artifacts(
&self,
name: &str,
) -> Result<InstallResult, ExtensionError> {
// Check if already installed
let channel_wasm = self.wasm_channels_dir.join(format!("{}.wasm", name));
if channel_wasm.exists() {
return Err(ExtensionError::AlreadyInstalled(name.to_string()));
}
crate::channels::wasm::install_bundled_channel(name, &self.wasm_channels_dir, false)
.await
.map_err(ExtensionError::InstallFailed)?;
tracing::info!(
"Installed bundled channel '{}' to {}",
name,
self.wasm_channels_dir.display()
);
Ok(InstallResult {
name: name.to_string(),
kind: ExtensionKind::WasmChannel,
message: format!(
"Channel '{}' installed to {}. Restart IronClaw for the channel to activate. \
Run tool_auth('{}') to configure authentication before restarting.",
name,
self.wasm_channels_dir.display(),
name,
),
})
}
async fn auth_mcp(
&self,
name: &str,
-5
View File
@@ -85,11 +85,6 @@ pub enum ExtensionSource {
},
/// Discovered online (not yet validated for a specific source type).
Discovered { url: String },
/// Bundled with the application (pre-built WASM, copied from build artifacts).
Bundled {
/// Channel or tool name used to locate build artifacts.
name: String,
},
}
/// Hint about what authentication method is needed.
+5 -108
View File
@@ -380,72 +380,9 @@ fn builtin_entries() -> Vec<RegistryEntry> {
},
auth_hint: AuthHint::Dcr,
},
// -- WASM Channels (bundled) --
RegistryEntry {
name: "telegram".to_string(),
display_name: "Telegram".to_string(),
kind: ExtensionKind::WasmChannel,
description: "Telegram Bot API channel for receiving and sending messages via Telegram"
.to_string(),
keywords: vec![
"chat".into(),
"messaging".into(),
"bot".into(),
"channel".into(),
],
source: ExtensionSource::Bundled {
name: "telegram".to_string(),
},
auth_hint: AuthHint::CapabilitiesAuth,
},
RegistryEntry {
name: "slack".to_string(),
display_name: "Slack".to_string(),
kind: ExtensionKind::WasmChannel,
description: "Slack Events API channel for receiving and sending messages via Slack"
.to_string(),
keywords: vec![
"chat".into(),
"messaging".into(),
"team".into(),
"channel".into(),
],
source: ExtensionSource::Bundled {
name: "slack".to_string(),
},
auth_hint: AuthHint::CapabilitiesAuth,
},
RegistryEntry {
name: "discord".to_string(),
display_name: "Discord".to_string(),
kind: ExtensionKind::WasmChannel,
description:
"Discord Gateway channel for handling slash commands, buttons, and messages"
.to_string(),
keywords: vec![
"chat".into(),
"messaging".into(),
"gaming".into(),
"channel".into(),
],
source: ExtensionSource::Bundled {
name: "discord".to_string(),
},
auth_hint: AuthHint::CapabilitiesAuth,
},
RegistryEntry {
name: "whatsapp".to_string(),
display_name: "WhatsApp".to_string(),
kind: ExtensionKind::WasmChannel,
description:
"WhatsApp Business API channel for receiving and sending WhatsApp messages"
.to_string(),
keywords: vec!["chat".into(), "messaging".into(), "channel".into()],
source: ExtensionSource::Bundled {
name: "whatsapp".to_string(),
},
auth_hint: AuthHint::CapabilitiesAuth,
},
// WASM channels (telegram, slack, discord, whatsapp) come from the embedded
// registry catalog (registry/channels/*.json) with WasmDownload URLs pointing
// to GitHub release artifacts. See new_with_catalog() for merging.
]
}
@@ -701,46 +638,6 @@ mod tests {
assert_eq!(entry.unwrap().display_name, "Slack MCP");
}
#[tokio::test]
async fn test_search_finds_telegram_channel() {
let registry = ExtensionRegistry::new();
let results = registry.search("telegram").await;
assert!(!results.is_empty(), "Should find telegram in registry");
assert_eq!(results[0].entry.name, "telegram");
assert_eq!(results[0].entry.kind, ExtensionKind::WasmChannel);
}
#[tokio::test]
async fn test_search_channel_by_keyword() {
let registry = ExtensionRegistry::new();
let results = registry.search("bot messaging").await;
let has_telegram = results.iter().any(|r| r.entry.name == "telegram");
assert!(
has_telegram,
"Telegram should appear in bot messaging search"
);
}
#[tokio::test]
async fn test_get_bundled_channels() {
let registry = ExtensionRegistry::new();
let telegram = registry.get("telegram").await;
assert!(telegram.is_some());
assert_eq!(telegram.unwrap().kind, ExtensionKind::WasmChannel);
let slack = registry.get("slack").await;
assert!(slack.is_some());
assert_eq!(slack.unwrap().kind, ExtensionKind::WasmChannel);
let discord = registry.get("discord").await;
assert!(discord.is_some());
assert_eq!(discord.unwrap().kind, ExtensionKind::WasmChannel);
let whatsapp = registry.get("whatsapp").await;
assert!(whatsapp.is_some());
assert_eq!(whatsapp.unwrap().kind, ExtensionKind::WasmChannel);
}
// Channel tests (telegram, slack, discord, whatsapp) require the embedded catalog
// to be loaded via new_with_catalog(). See test_new_with_catalog for catalog coverage.
}