mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
feat: hot-activate WASM channels, channel-first prompts, unified artifact resolution (#297)
* refactor: unify WASM artifact resolution into registry/artifacts.rs Consolidate duplicated WASM find/build/install logic from 5+ files into a single src/registry/artifacts.rs module. This fixes two bugs: - registry/installer.rs now respects CARGO_TARGET_DIR (was hardcoded) - channels/wasm/bundled.rs now searches all WASM triples (was wasip2 only) Also includes: extension manager hot-activation for WASM channels, extension guidance in LLM prompts, channel manager hot-add support, webhook router channel lookup, and minor cleanups. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: send approval prompts as messages on WASM channels (Telegram, Slack) WASM channels mapped ApprovalNeeded status to a typing indicator, so users on Telegram never saw tool approval prompts — the agent got stuck in AwaitingApproval and all subsequent messages failed with "Waiting for approval". - Intercept ApprovalNeeded in WasmChannel::handle_status_update and send the prompt as an actual message via call_on_respond, showing tool name, description, parameters, and yes/no/always instructions - Guard against empty LLM responses after clean_response() strips reasoning_content think-tags (defense-in-depth for reasoning models) - Add reasoning_content fallback to NearAiChatProvider::complete() for consistency with complete_with_tools() - Add debug logging when empty responses are suppressed - Improve error logging for channel respond() failures - Register WASM channel webhook routes before credential checks so platforms don't deactivate webhook URLs with 404s Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR #297 review comments - ChannelManager::add: use async write().await instead of try_write() - resolve_target_dir: resolve relative CARGO_TARGET_DIR against crate_dir - install_wasm_files: log warning on capabilities copy failure - refresh_active_channel: load capabilities file for webhook secret name - activate_wasm_channel: validate name against path traversal - Fix cargo fmt formatting in nearai_chat.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: wire up channel runtime for hot-activation and address PR review round 2 - Wire up set_channel_runtime() in main.rs so hot-activation actually works (with_channel_runtime was never called — hot-activation was dead code) - Change ExtensionManager channel runtime fields to RwLock<Option<...>> interior mutability so set_channel_runtime(&self) works after Arc wrapping - Fix artifact tests to use resolve_target_dir() instead of hardcoding "target/" (breaks when CARGO_TARGET_DIR is set) - Fix bundled.rs build hint: cargo component build (not cargo build --target) - Fix wasm_artifact_path doc: binary_name should not include .wasm extension Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: use char-aware truncation to prevent UTF-8 panic in approval prompt &s[..77] panics on multi-byte UTF-8 (CJK, emoji). Use s.chars().take(77) for safe truncation at character boundaries. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a320f265b3
commit
ea57447649
@@ -798,10 +798,10 @@ Create alongside the .wasm file to grant capabilities:
|
||||
match (&requirement.software_type, &requirement.language) {
|
||||
(SoftwareType::WasmTool, Language::Rust) => {
|
||||
// WASM output location
|
||||
project_dir.join(format!(
|
||||
"target/wasm32-wasip2/release/{}.wasm",
|
||||
requirement.name.replace('-', "_")
|
||||
))
|
||||
crate::tools::wasm::wasm_artifact_path(
|
||||
project_dir,
|
||||
&requirement.name.replace('-', "_"),
|
||||
)
|
||||
}
|
||||
(SoftwareType::CliBinary, Language::Rust) => project_dir.join(format!(
|
||||
"target/release/{}",
|
||||
|
||||
@@ -30,7 +30,8 @@ impl Tool for ToolSearchTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Search for available extensions (MCP servers, WASM tools, WASM channels) to add. \
|
||||
"Search for available extensions to add new capabilities. Extensions include \
|
||||
channels (Telegram, Slack, Discord — for messaging), tools, and MCP servers. \
|
||||
Use discover:true to search online if the built-in registry has no results."
|
||||
}
|
||||
|
||||
@@ -100,7 +101,7 @@ impl Tool for ToolInstallTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Install an extension (MCP server, WASM tool, or WASM channel). \
|
||||
"Install an extension (channel, tool, or MCP server). \
|
||||
Use the name from tool_search results, or provide an explicit URL."
|
||||
}
|
||||
|
||||
@@ -278,7 +279,7 @@ impl Tool for ToolActivateTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Activate an installed extension, connecting to MCP servers or loading WASM tools into the runtime."
|
||||
"Activate an installed extension — starts channels, loads tools, or connects to MCP servers."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
@@ -372,7 +373,8 @@ impl Tool for ToolListTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"List all installed extensions with their authentication and activation status."
|
||||
"List extensions with their authentication and activation status. \
|
||||
Set include_available:true to also show registry entries not yet installed."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
@@ -383,6 +385,11 @@ impl Tool for ToolListTool {
|
||||
"type": "string",
|
||||
"enum": ["mcp_server", "wasm_tool", "wasm_channel"],
|
||||
"description": "Filter by extension type (omit to list all)"
|
||||
},
|
||||
"include_available": {
|
||||
"type": "boolean",
|
||||
"description": "If true, also include registry entries that are not yet installed",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -405,9 +412,14 @@ impl Tool for ToolListTool {
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let include_available = params
|
||||
.get("include_available")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let extensions = self
|
||||
.manager
|
||||
.list(kind_filter)
|
||||
.list(kind_filter, include_available)
|
||||
.await
|
||||
.map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
|
||||
|
||||
@@ -439,7 +451,7 @@ impl Tool for ToolRemoveTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Remove an installed extension (MCP server or WASM tool). \
|
||||
"Remove an installed extension (channel, tool, or MCP server). \
|
||||
Unregisters tools and deletes configuration."
|
||||
}
|
||||
|
||||
|
||||
@@ -383,6 +383,31 @@ impl LoadResults {
|
||||
/// Compile-time project root, used to locate tools-src/ in dev builds.
|
||||
const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
/// Resolve the WASM target directory for a given crate directory.
|
||||
///
|
||||
/// Checks (in order):
|
||||
/// 1. `CARGO_TARGET_DIR` env var (shared target dir)
|
||||
/// 2. `<crate_dir>/target/` (default per-crate layout)
|
||||
pub fn resolve_wasm_target_dir(crate_dir: &Path) -> PathBuf {
|
||||
crate::registry::artifacts::resolve_target_dir(crate_dir)
|
||||
}
|
||||
|
||||
/// Return the expected path to a compiled WASM artifact for a given crate.
|
||||
///
|
||||
/// Combines [`resolve_wasm_target_dir`] with the `wasm32-wasip2/release/` subdirectory
|
||||
/// and the binary name without extension (e.g. `slack_tool`).
|
||||
///
|
||||
/// `binary_name` should not include the `.wasm` extension; it is appended automatically.
|
||||
///
|
||||
/// This is a convenience function for callers that know the exact triple (wasip2)
|
||||
/// and binary name. For multi-triple search, use
|
||||
/// [`crate::registry::artifacts::find_wasm_artifact`] instead.
|
||||
pub fn wasm_artifact_path(crate_dir: &Path, binary_name: &str) -> PathBuf {
|
||||
resolve_wasm_target_dir(crate_dir)
|
||||
.join("wasm32-wasip2/release")
|
||||
.join(format!("{}.wasm", binary_name))
|
||||
}
|
||||
|
||||
/// Resolve the tools source directory.
|
||||
///
|
||||
/// Checks (in order):
|
||||
@@ -426,9 +451,7 @@ pub async fn discover_dev_tools() -> Result<HashMap<String, DiscoveredTool>, std
|
||||
let crate_name = dir_name.replace('-', "_");
|
||||
let install_name = format!("{}-tool", dir_name);
|
||||
|
||||
let wasm_path = path
|
||||
.join("target/wasm32-wasip2/release")
|
||||
.join(format!("{}_tool.wasm", crate_name));
|
||||
let wasm_path = wasm_artifact_path(&path, &format!("{}_tool", crate_name));
|
||||
|
||||
if !wasm_path.exists() {
|
||||
continue;
|
||||
|
||||
@@ -123,7 +123,7 @@ pub use storage::{
|
||||
// Loader
|
||||
pub use loader::{
|
||||
DiscoveredTool, LoadResults, WasmLoadError, WasmToolLoader, discover_dev_tools, discover_tools,
|
||||
load_dev_tools,
|
||||
load_dev_tools, resolve_wasm_target_dir, wasm_artifact_path,
|
||||
};
|
||||
|
||||
// Capabilities schema (for parsing *.capabilities.json files)
|
||||
|
||||
Reference in New Issue
Block a user