mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
fix: fall back to build-from-source when extension download fails (#312)
* fix: fall back to build-from-source when extension download fails Extension manifests hardcode GitHub release URLs for WASM artifacts, but these artifacts are not yet published to any release. This causes all WASM extension installs to fail with HTTP 404. Add a fallback_source field to RegistryEntry so that when the primary WasmDownload source fails (e.g., 404), the installer automatically falls back to WasmBuildable (build from source). The manifest conversion now populates this fallback whenever a download URL is set. Fixes nearai/ironclaw#298 Co-Authored-By: Claude Opus 4.6 <[email protected]> * Address Copilot/Gemini review feedback - Skip fallback for AlreadyInstalled errors (Gemini) - Include both primary and fallback errors in combined message (Copilot) - Fix comment to match broader behavior (any error, not just download) (Copilot) Co-Authored-By: Claude Opus 4.6 <[email protected]> * Address serrrfirat review feedback - Forward AlreadyInstalled from fallback directly instead of wrapping in ExtensionError::Other (defensive, prevents misleading error message) Co-Authored-By: Claude Opus 4.6 <[email protected]> * Add unit tests for fallback install logic Extract fallback_decision() and combine_install_errors() from install_from_entry() to enable direct unit testing without requiring a full ExtensionManager setup. Tests cover: - Primary success returns directly (no fallback attempted) - AlreadyInstalled short-circuits (no fallback attempted) - Download failure with fallback available triggers fallback - Error without fallback source returns primary error - Both-fail produces combined error with both messages - AlreadyInstalled from fallback is forwarded directly Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: firat.sertgoz <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
firat.sertgoz
parent
ebb4ce95e3
commit
f4ba85ffa2
@@ -208,6 +208,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.notion.com/mcp".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -227,6 +228,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.linear.app".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -245,6 +247,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.google.com/calendar".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -263,6 +266,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.google.com/drive".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -282,6 +286,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.github.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -301,6 +306,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.slack.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -320,6 +326,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.sentry.dev/sse".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -339,6 +346,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.stripe.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -358,6 +366,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.cloudflare.com/sse".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -375,6 +384,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.asana.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
RegistryEntry {
|
||||
@@ -393,6 +403,7 @@ fn builtin_entries() -> Vec<RegistryEntry> {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.intercom.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
},
|
||||
// WASM channels (telegram, slack, discord, whatsapp) come from the embedded
|
||||
@@ -417,6 +428,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
|
||||
@@ -439,6 +451,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
|
||||
@@ -461,6 +474,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
|
||||
@@ -483,6 +497,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
|
||||
@@ -546,6 +561,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://custom.example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
|
||||
@@ -571,6 +587,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://example.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::None,
|
||||
};
|
||||
|
||||
@@ -595,6 +612,7 @@ mod tests {
|
||||
build_dir: Some("channels-src/telegram".to_string()),
|
||||
crate_name: Some("telegram-channel".to_string()),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::CapabilitiesAuth,
|
||||
},
|
||||
// This shares a name with the builtin slack-mcp but has a different kind, so both should appear
|
||||
@@ -609,6 +627,7 @@ mod tests {
|
||||
build_dir: Some("tools-src/slack".to_string()),
|
||||
crate_name: Some("slack-tool".to_string()),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::CapabilitiesAuth,
|
||||
},
|
||||
];
|
||||
@@ -644,6 +663,7 @@ mod tests {
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://other.slack.com".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
}];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user