Skip to content

feat: hot-activate WASM channels, channel-first prompts, unified artifact resolution#297

Merged
ilblackdragon merged 6 commits intomainfrom
feat/hot-activate-wasm-channels
Feb 22, 2026
Merged

feat: hot-activate WASM channels, channel-first prompts, unified artifact resolution#297
ilblackdragon merged 6 commits intomainfrom
feat/hot-activate-wasm-channels

Conversation

@ilblackdragon
Copy link
Copy Markdown
Member

@ilblackdragon ilblackdragon commented Feb 22, 2026

Summary

  • Hot-activate WASM channels without restart: Channels can now be loaded, started, and added to the running agent loop at runtime via ChannelManager::hot_add(). ExtensionManager::activate_wasm_channel() handles the full lifecycle (load WASM, inject credentials, register webhook router, start channel). Channels with missing required secrets are skipped at startup and activated later when the user saves credentials via the web UI.
  • Credential refresh for already-active channels: When credentials are saved for a channel that was loaded at startup, refresh_active_channel() re-injects credentials, updates the webhook secret in the router, and re-calls on_start() to re-register webhooks (e.g., Telegram setWebhook).
  • Channel-first prompt language: Tool descriptions and system prompt updated to list channels before MCP servers, preventing the LLM from generically calling everything an "MCP server" when users ask about connecting Telegram/Slack/Discord.
  • Unified WASM artifact resolution: Extracted registry/artifacts.rs with find_wasm_artifact() that searches across all WASM target triples, replacing hardcoded wasm32-wasip2 paths in bundled.rs and installer.rs.
  • Pre-existing clippy fixes: collapsible-if in bundled.rs, needless borrow in artifacts.rs, html_to_markdown test warnings.

Test plan

  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo test — all 1390 tests pass
  • Manual: start IronClaw without Telegram credentials → channel skipped at startup
  • Manual: save bot token via web UI → channel hot-activates, webhook registers successfully
  • Manual: ask agent "how to connect tg" → agent says "channel" not "MCP server"

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 22, 2026 03:34
@github-actions github-actions bot added scope: agent Agent core (agent loop, router, scheduler) scope: channel Channel infrastructure scope: channel/cli TUI / CLI channel scope: channel/web Web gateway channel scope: channel/wasm WASM channel runtime scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox scope: tool/builder Dynamic tool builder scope: llm LLM integration scope: extensions Extension management scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Feb 22, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ilblackdragon, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the agent's extensibility by introducing dynamic, runtime activation for WASM-based channels, eliminating the need for restarts when configuring new messaging integrations. It refines credential management for active channels, improves the agent's conversational understanding of different extension types, and centralizes WASM artifact handling for a more robust build and installation process.

Highlights

  • Hot-activate WASM channels: Channels can now be loaded, started, and added to the running agent loop at runtime via ChannelManager::hot_add(). The ExtensionManager::activate_wasm_channel() method handles the full lifecycle, including loading WASM, injecting credentials, registering webhook routers, and starting the channel. Channels with missing required secrets are skipped at startup and activated later when credentials are provided via the web UI.
  • Credential refresh for active channels: When credentials are saved for an already-active channel, refresh_active_channel() re-injects credentials, updates the webhook secret in the router, and re-calls on_start() to re-register webhooks (e.g., Telegram setWebhook).
  • Channel-first prompt language: Tool descriptions and the system prompt have been updated to list channels before MCP servers, aiming to prevent the LLM from generically referring to all integrations as 'MCP servers' when users inquire about connecting platforms like Telegram, Slack, or Discord.
  • Unified WASM artifact resolution: A new module registry/artifacts.rs was extracted to provide a unified find_wasm_artifact() function that searches across all WASM target triples. This replaces hardcoded wasm32-wasip2 paths previously found in bundled.rs and installer.rs, improving flexibility and maintainability.
  • Clippy fixes: Pre-existing clippy warnings related to collapsible-if in bundled.rs, needless borrow in artifacts.rs, and html_to_markdown test warnings have been addressed.
Changelog
  • Cargo.lock
    • Updated dependencies by reordering the 'servo_arc' package entry.
  • channels-src/telegram/Cargo.toml
    • Removed the [workspace] declaration, indicating this crate is no longer part of a workspace or its workspace definition has moved.
  • src/agent/agent_loop.rs
    • Updated the channels parameter in Agent::new to accept Arc<ChannelManager> instead of ChannelManager, and adjusted its initialization to directly use the provided Arc.
  • src/agent/dispatcher.rs
    • Modified the test setup to initialize ChannelManager within an Arc.
  • src/channels/manager.rs
    • Changed the add method signature to accept &self instead of &mut self.
    • Added a new asynchronous hot_add method to dynamically add and start channels to a running agent, including message forwarding to the agent loop.
  • src/channels/wasm/bundled.rs
    • Refactored WASM artifact location logic to utilize the new crate::registry::artifacts::find_wasm_artifact for searching across all WASM target triples.
    • Improved error messages to provide more helpful information about expected build paths.
  • src/channels/wasm/router.rs
    • Added an asynchronous update_secret method to allow updating the webhook secret for an already-registered channel at runtime.
  • src/channels/wasm/wrapper.rs
    • Changed the call_on_start method from private to public, allowing it to be re-called after credential refreshes.
    • Expanded documentation for call_on_start to clarify its use for re-triggering webhook registration.
  • src/channels/web/handlers/extensions.rs
    • Updated the ext_mgr.list call to include the new include_available boolean parameter.
  • src/channels/web/server.rs
    • Modified multiple ext_mgr.list calls to pass the include_available parameter.
  • src/cli/tool.rs
    • Removed the std::process::Command import, delegating WASM build functionality.
    • Updated WASM component build and artifact finding logic to use the centralized functions from crate::registry::artifacts.
  • src/extensions/discovery.rs
    • Updated module-level documentation to refer to 'extensions' instead of 'MCP servers' for broader applicability.
  • src/extensions/manager.rs
    • Added new fields to ExtensionManager for WASM channel hot-activation infrastructure, including wasm_channel_runtime, channel_manager, pairing_store, wasm_channel_router, and telegram_owner_id.
    • Introduced a with_channel_runtime builder method to configure the extension manager with channel-related dependencies.
    • Implemented activate_wasm_channel to handle the full lifecycle of hot-activating a WASM channel.
    • Added refresh_active_channel to re-inject credentials and re-register webhooks for already-active channels.
    • Refactored WASM extension installation into a new install_wasm_from_buildable method.
    • Modified the list method to accept an include_available parameter, allowing it to show registry entries not yet installed.
    • Updated the activate method to directly call activate_wasm_channel for WASM channels.
    • Removed the ChannelNeedsRestart error, reflecting the new hot-activation capability.
    • Adjusted the tool_auth success message to reflect immediate activation for channels.
  • src/extensions/mod.rs
    • Updated module documentation to provide a more comprehensive overview of the unified extension system, explicitly mentioning channels, tools, and MCP servers.
    • Modified the ExtensionKind::WasmChannel comment to reflect hot-activation support.
    • Added a crate_name field to the ExtensionSource::WasmBuildable enum for better artifact resolution.
    • Introduced a default_true helper function and an installed field to InstalledExtension to differentiate between installed and merely available extensions.
    • Removed the ChannelNeedsRestart variant from ExtensionError.
  • src/extensions/registry.rs
    • Updated module documentation to include channels in the description of managed extensions.
    • Added an all_entries method to retrieve all registry entries, including built-in and cached discoveries.
    • Updated test data for WasmBuildable sources to include crate_name.
  • src/llm/reasoning.rs
    • Added a build_extensions_section method to generate a dynamic section for the system prompt, guiding the LLM on how to interact with different extension types.
    • Integrated the new extensions section into the main system prompt template.
  • src/main.rs
    • Initialized ChannelManager, WasmChannelRuntime, PairingStore, and WasmChannelRouter earlier in the main function to support hot-activation.
    • Configured the ExtensionManager with the newly initialized channel runtime components.
    • Modified WASM channel loading logic to check for required secrets and skip channels with missing credentials at startup, allowing them to be hot-activated later.
    • Ensured the WASM channel router is always registered, even if no channels are initially loaded.
  • src/registry/artifacts.rs
    • Added a new file artifacts.rs to centralize WASM artifact resolution logic.
    • Implemented resolve_target_dir to determine the Cargo target directory.
    • Provided find_wasm_artifact and find_any_wasm_artifact for searching WASM binaries across various target triples.
    • Included build_wasm_component (async) and build_wasm_component_sync (sync) functions for building WASM components.
    • Added install_wasm_files to copy WASM binaries and associated capabilities files to an installation directory.
  • src/registry/installer.rs
    • Updated the build_wasm_component call to use the new crate::registry::artifacts::build_wasm_component function.
    • Removed the previously defined local build_wasm_component function, centralizing the logic.
  • src/registry/manifest.rs
    • Updated ExtensionSource::WasmBuildable creation to include the crate_name from the manifest source.
  • src/registry/mod.rs
    • Added pub mod artifacts; to expose the new WASM artifact resolution module.
  • src/tools/builder/core.rs
    • Updated the wasm_artifact_path call to use the new function from crate::tools::wasm.
  • src/tools/builtin/extension_tools.rs
    • Updated descriptions for tool_search, tool_install, tool_activate, tool_list, and tool_remove to reflect the unified extension system and channel-first language.
    • Added an include_available parameter to the tool_list tool, allowing it to show uninstalled registry entries.
  • src/tools/wasm/loader.rs
    • Added resolve_wasm_target_dir and wasm_artifact_path functions for consistent WASM target path resolution.
    • Updated discover_dev_tools to use the new wasm_artifact_path function.
  • src/tools/wasm/mod.rs
    • Exported new functions resolve_wasm_target_dir and wasm_artifact_path from the loader module.
  • tests/html_to_markdown.rs
    • Applied clippy fixes to normalize_smart_punctuation by using array literals for replace.
    • Refactored convert_test_pages_to_markdown to use an if let statement for metadata loading, addressing a clippy warning.
  • tools-src/okta/src/api.rs
    • Added a None argument to host::http_request calls, aligning with an updated function signature.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces significant new features, including hot-activation for WASM channels, channel-first prompt language, and unified WASM artifact resolution. The changes are well-structured and address several key areas of improvement. The new registry/artifacts.rs module centralizes WASM artifact handling, which aligns with the rule for specific artifact location. Error handling for tokio::spawn is improved, and the removal of ChannelNeedsRestart error variant is consistent with clear error messaging. Overall, the code changes enhance the system's flexibility and user experience, with several improvements aligning with established repository rules.

I am having trouble creating individual review comments. Click here to see my feedback.

Cargo.lock (4978-4985)

medium

This change in Cargo.lock indicates that the servo_arc package was moved within the file. While Cargo.lock is typically auto-generated, such changes can sometimes hide underlying dependency updates or removals. It's good practice to ensure that no unintended dependency changes occurred.

Cargo.lock (4993-5000)

medium

The servo_arc package has been re-added at a different location in Cargo.lock. This suggests a reordering or a re-evaluation of dependencies. Confirming that this reordering doesn't affect the build process or introduce any unexpected behavior is important.

channels-src/telegram/Cargo.toml (28-29)

medium

Removing [workspace] from channels-src/telegram/Cargo.toml is a significant change. This indicates that the telegram-channel crate is no longer part of a workspace. This is likely intentional to allow it to be built as a standalone WASM component, but it's important to ensure this aligns with the overall project structure and build system expectations. If this crate was previously managed by a workspace, this change might affect how it's built or tested in certain environments.

src/agent/agent_loop.rs (101)

medium

Changing channels: ChannelManager to channels: Arc<ChannelManager> in the Agent::new function signature is a breaking API change. While necessary for sharing the ChannelManager across tasks for hot-activation, it requires all call sites to be updated. This is correctly reflected in the diff, but it's a high-impact change that should be noted.

src/agent/agent_loop.rs (126)

medium

The channels field is now directly assigned the Arc<ChannelManager> instead of wrapping a ChannelManager in a new Arc. This is consistent with the change in the constructor signature and correctly reflects that the ChannelManager is now shared via Arc from its creation point.

src/agent/dispatcher.rs (893)

medium

The ChannelManager::new() is now wrapped in Arc::new() when passed to the Agent::new constructor in the test setup. This aligns with the API change in Agent::new to accept an Arc<ChannelManager>. This is a necessary adjustment for the test to remain consistent with the updated API.

src/channels/manager.rs (43)

medium

Changing the add method signature from &mut self to &self for ChannelManager implies that the method no longer requires exclusive mutable access to the ChannelManager. This is a significant change, likely enabled by the internal use of RwLock for the channels HashMap. This improves concurrency by allowing multiple readers or shared mutable access, which is beneficial for hot-adding channels.

src/channels/manager.rs (55-82)

medium

The new hot_add method in ChannelManager is a critical feature for dynamic channel activation. It correctly handles starting the channel, registering it, and forwarding its messages. However, the tokio::spawn block uses tx.send(msg).await.is_err() to detect if the inject channel is closed. While this works, explicitly checking the Err variant and logging the specific error (e.g., SendError) could provide more detailed debugging information if the channel unexpectedly closes.

References
  1. When handling errors from tokio::task::spawn_blocking, log the JoinError to capture debugging information. It is good practice to distinguish between panics and cancellations in the error message.

src/channels/wasm/bundled.rs (69-73)

medium

The previous hardcoded path for wasm32-wasip2 is replaced with a call to crate::registry::artifacts::find_wasm_artifact. This is a good improvement as it centralizes artifact resolution logic and makes it more flexible by searching across all WASM target triples. This change reduces duplication and improves maintainability.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/channels/wasm/bundled.rs (77-90)

medium

The error message for a missing WASM artifact is improved by including (build tree, and other triples) and referencing expected_build.display(). This provides more comprehensive information to the user about where the system looked for the artifact, which is helpful for debugging build issues.

References
  1. Create specific error variants for different failure modes (e.g., DownloadFailed with a URL string vs. ManifestRead with a file path) to provide semantically correct and clear error messages.

src/channels/wasm/router.rs (113-125)

medium

The new update_secret method is crucial for the hot-activation feature, allowing channels to update their webhook secrets dynamically. This is a well-designed addition that directly supports the PR's goal of enabling credential refresh for active channels.

src/channels/wasm/wrapper.rs (783-788)

medium

The call_on_start method's visibility is changed from fn to pub fn, and its documentation is updated to reflect its new role in hot-activation and credential refreshing. This is a necessary change to allow the ExtensionManager to re-trigger on_start for already-active channels, which is a core part of the hot-activation feature.

src/channels/web/handlers/extensions.rs (23)

medium

The list method of ext_mgr now includes a false argument. This indicates that the extensions_list_handler should only list installed extensions and not available ones from the registry. This is a consistent change with the new include_available parameter introduced in ExtensionManager::list.

src/channels/web/server.rs (1707)

medium

The list method of ext_mgr now includes a false argument. This indicates that the extensions_list_handler should only list installed extensions and not available ones from the registry. This is a consistent change with the new include_available parameter introduced in ExtensionManager::list.

src/channels/web/server.rs (1958)

medium

The list method of ext_mgr now includes a false argument. This indicates that the extensions_registry_handler should only list installed extensions and not available ones from the registry. This is a consistent change with the new include_available parameter introduced in ExtensionManager::list.

src/channels/web/server.rs (2001)

medium

The list method of ext_mgr now includes a false argument. This indicates that the extensions_setup_handler should only list installed extensions and not available ones from the registry. This is a consistent change with the new include_available parameter introduced in ExtensionManager::list.

src/cli/tool.rs (7)

medium

The std::process::Command import is removed from src/cli/tool.rs. This is consistent with the refactoring to move build logic into registry/artifacts.rs, which now handles process execution.

src/cli/tool.rs (160-169)

medium

The logic for finding and building WASM artifacts has been refactored to use the new crate::registry::artifacts module. This centralizes the artifact resolution and build process, making it more consistent across the codebase. The find_wasm_artifact and build_wasm_component_sync functions are now used, which is a good improvement for maintainability.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/cli/tool.rs (256-417)

medium

The build_wasm_component and find_wasm_artifact functions are removed from src/cli/tool.rs. This is a positive change as it indicates that the WASM artifact resolution and build logic has been successfully refactored into the new registry/artifacts.rs module, reducing code duplication and improving modularity.

src/extensions/discovery.rs (1)

medium

The comment is updated from "MCP servers" to "extensions" to reflect the broader scope of the discovery system, which now includes channels and tools. This is a minor but accurate update to the documentation.

src/extensions/manager.rs (3-5)

medium

The module-level documentation is updated to reflect the inclusion of channel runtime in the ExtensionManager's responsibilities. This accurately describes the expanded role of the manager in handling all types of extensions, including channels.

src/extensions/manager.rs (13-16)

medium

New use statements are added for ChannelManager, SharedWasmChannel, WasmChannelLoader, WasmChannelRouter, WasmChannelRuntime, and PairingStore. These are necessary imports to support the new hot-activation features for WASM channels within the ExtensionManager.

src/extensions/manager.rs (58-64)

medium

New fields are added to ExtensionManager to hold references to WASM channel hot-activation infrastructure, including wasm_channel_runtime, channel_manager, pairing_store, wasm_channel_router, and telegram_owner_id. These fields are essential for enabling dynamic activation and management of WASM channels.

src/extensions/manager.rs (71-72)

medium

The _tunnel_url field is renamed to tunnel_url and its comment is updated to reflect its use for webhook configuration and remote OAuth callbacks. This is a minor but good clarification of the field's purpose.

src/extensions/manager.rs (108-112)

medium

The new constructor now initializes the new WASM channel-related fields to None. This is appropriate as these components are configured via the with_channel_runtime builder method after initial construction.

src/extensions/manager.rs (124-141)

medium

The new with_channel_runtime builder method is added to configure the WASM channel hot-activation infrastructure. This method allows injecting the necessary Arc references after the ExtensionManager has been constructed, ensuring proper setup for dynamic channel management.

src/extensions/manager.rs (252-259)

medium

The list method's documentation is updated to clarify the new include_available parameter, which allows listing registry entries that are not yet installed. This improves the clarity of the API and its usage.

src/extensions/manager.rs (294)

medium

The installed: true field is added to InstalledExtension when listing MCP servers. This new field is part of the InstalledExtension struct and helps differentiate between installed and merely available extensions, especially when include_available is true.

src/extensions/manager.rs (322)

medium

The installed: true field is added to InstalledExtension when listing WASM tools. This new field is part of the InstalledExtension struct and helps differentiate between installed and merely available extensions, especially when include_available is true.

src/extensions/manager.rs (352)

medium

The installed: true field is added to InstalledExtension when listing WASM channels. This new field is part of the InstalledExtension struct and helps differentiate between installed and merely available extensions, especially when include_available is true.

src/extensions/manager.rs (362-389)

medium

This new block of code appends available-but-not-installed registry entries to the list of extensions if include_available is true. This is a key part of the new list functionality, allowing users to see extensions that are discoverable but not yet installed. It correctly filters out already installed extensions and applies the kind_filter.

src/extensions/manager.rs (569-581)

medium

The ExtensionSource::WasmBuildable case for WasmTool installation is updated to use the new install_wasm_from_buildable helper method. This centralizes the logic for installing WASM extensions from local build artifacts, improving code reuse and consistency.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/extensions/manager.rs (600-611)

medium

The ExtensionSource::WasmBuildable case for WasmChannel installation is updated to use the new install_wasm_from_buildable helper method. This centralizes the logic for installing WASM extensions from local build artifacts, improving code reuse and consistency.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/extensions/manager.rs (691-694)

medium

The message for installing a WASM channel is updated to remove the mention of restarting IronClaw and the installation path. This reflects the new hot-activation capability and provides a more concise message.

src/extensions/manager.rs (856-860)

medium

The message for installing a WASM channel is updated to remove the mention of restarting IronClaw and the installation path. This reflects the new hot-activation capability and provides a more concise message.

src/extensions/manager.rs (951-1025)

medium

The new install_wasm_from_buildable helper method encapsulates the logic for installing WASM extensions from local build artifacts. This is a significant improvement for code organization, as it centralizes the complex logic of resolving build directories, finding artifacts across triples, and copying files. This method is used by both WasmTool and WasmChannel installation paths.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/extensions/manager.rs (1651-1826)

medium

The new activate_wasm_channel method implements the core logic for hot-activating WASM channels. This is a critical new feature, handling the loading, credential injection, webhook registration, and integration into the channel manager. The method includes checks for already active channels and proper error handling for missing runtime infrastructure.

src/extensions/manager.rs (1829-1943)

medium

The new refresh_active_channel method is a crucial component of the hot-activation feature. It allows updating credentials and re-registering webhooks for channels that were already active, which is essential when a user saves credentials via the web UI after a channel has started. This method ensures that runtime configuration changes are applied without requiring a full restart.

src/extensions/manager.rs (2104-2122)

medium

The save_setup_secrets method now attempts to hot-activate the WASM channel after saving secrets. This provides a more seamless user experience by immediately activating the channel if possible, rather than requiring a manual activation or restart. Error handling is included to inform the user if hot-activation fails.

src/extensions/manager.rs (2141-2186)

medium

The new inject_channel_credentials_from_secrets function centralizes the logic for injecting credentials into WASM channels based on a naming convention. This is a good refactoring that improves code reuse and consistency for credential management across different parts of the system.

src/extensions/mod.rs (1-17)

medium

The module-level documentation is significantly updated to provide a clearer overview of the unified extension system. It now explicitly lists the three types of extensions (Channels, Tools, MCP servers) and provides a more relevant example (add telegram). This improves the clarity and accuracy of the documentation.

src/extensions/mod.rs (37)

medium

The comment for WasmChannel is updated from "future: dynamic activation, currently needs restart" to "WASM channel module with hot-activation support". This reflects the successful implementation of the hot-activation feature, which is a major improvement.

src/extensions/mod.rs (88-90)

medium

New fields crate_name are added to ExtensionSource::WasmBuildable. This allows specifying the crate name explicitly, which is useful for locating build artifacts when the crate name differs from the extension name. This improves the flexibility of the buildable source type.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/extensions/mod.rs (183-185)

medium

The default_true function is added to provide a default value for the installed field in InstalledExtension. This is a helper for the serde(default = "default_true") attribute.

src/extensions/mod.rs (205-207)

medium

The installed field is added to the InstalledExtension struct. This field indicates whether an extension is installed locally, which is crucial for the new list functionality that can include available-but-not-installed registry entries.

src/extensions/mod.rs (230-231)

medium

The ChannelNeedsRestart error variant is removed from ExtensionError. This is a direct consequence of implementing the hot-activation feature for WASM channels, as they no longer require a restart to activate.

References
  1. Create specific error variants for different failure modes (e.g., DownloadFailed with a URL string vs. ManifestRead with a file path) to provide semantically correct and clear error messages.

src/extensions/registry.rs (1-4)

medium

The module-level documentation is updated to reflect that the registry now holds channels, tools, and MCP servers, not just MCP servers and WASM tools. This accurately describes the expanded scope of the extension system.

src/extensions/registry.rs (119-131)

medium

The new all_entries method is added to return all registry entries, including built-in and cached discoveries. This is essential for the ExtensionManager::list method when include_available is true, as it provides a comprehensive list of all known extensions.

src/extensions/registry.rs (659)

medium

The crate_name field is added to the WasmBuildable source for the telegram channel in the test registry entry. This explicitly specifies the crate name for locating build artifacts, which is a new feature in ExtensionSource::WasmBuildable.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/extensions/registry.rs (673)

medium

The crate_name field is added to the WasmBuildable source for the slack tool in the test registry entry. This explicitly specifies the crate name for locating build artifacts, which is a new feature in ExtensionSource::WasmBuildable.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/llm/reasoning.rs (610-611)

medium

A new extensions_section is added to the system prompt, which provides guidance on how the LLM should interact with extensions. This is a crucial addition for the agent to correctly understand and utilize the new extension management tools and channel-first language.

src/llm/reasoning.rs (654)

medium

The format string for the system prompt is updated to include the new extensions_section. This ensures that the extension guidance is properly inserted into the overall system prompt.

src/llm/reasoning.rs (666-684)

medium

The new build_extensions_section method constructs the guidance text for extensions. This section explicitly defines the different types of extensions (channels, tools, MCP servers) and instructs the LLM to use tool_search and refer to them by their specific kind. This is a key part of the "channel-first prompt language" feature.

src/main.rs (932-949)

medium

The channels, wasm_channel_runtime, wasm_pairing_store, and wasm_channel_router are now initialized early in main. This is essential because these components need to be shared with the ExtensionManager for hot-activation of WASM channels. Initializing them as Arcs allows them to be safely shared across different parts of the application.

src/main.rs (965)

medium

The extension_manager is now initialized as a mutable variable (let mut manager) to allow its configuration via the with_channel_runtime builder method. This is a necessary change to integrate the new WASM channel hot-activation infrastructure.

src/main.rs (978-987)

medium

This new block wires up the channel runtime infrastructure to the ExtensionManager using the with_channel_runtime builder method. This is a critical step for enabling hot-activation of WASM channels, passing the necessary Arc references for ChannelManager, WasmChannelRuntime, PairingStore, WasmChannelRouter, and telegram_owner_id.

src/main.rs (1087-1156)

medium

The logic for loading WASM channels is updated to include a check for required secrets. If a channel has required (non-optional) secrets and they are missing from the secrets store, the channel is skipped during startup. This allows the hot-activation path to activate them later when credentials are provided via the web UI, which is a key part of the hot-activation feature.

src/main.rs (1293-1298)

medium

This new conditional block ensures that the WASM channel catch-all webhook route is always registered if the WASM channel runtime is enabled, even if no WASM channels were loaded at startup. This is important for handling webhooks for channels that might be hot-activated later.

src/registry/artifacts.rs (1-362)

medium

The new file src/registry/artifacts.rs is a significant and positive addition. It centralizes all WASM artifact resolution, building, and installation logic, which was previously duplicated across multiple modules. This greatly improves maintainability, reduces redundancy, and provides a single source of truth for handling WASM components. The functions like find_wasm_artifact, build_wasm_component, and install_wasm_files are well-defined and cover the necessary operations.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/registry/installer.rs (97-103)

medium

The build_wasm_component call is replaced with crate::registry::artifacts::build_wasm_component. This change correctly delegates the WASM component build process to the new centralized artifacts module, ensuring consistency and reusability of the build logic.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/registry/installer.rs (356-412)

medium

The build_wasm_component function is removed from src/registry/installer.rs. This is a positive change as it indicates that the WASM component build logic has been successfully refactored into the new registry/artifacts.rs module, reducing code duplication and improving modularity.

src/registry/manifest.rs (168-169)

medium

The crate_name field is added to ExtensionSource::WasmBuildable when converting the manifest to a registry entry. This ensures that the crate name is correctly propagated for locating build artifacts, which is a new requirement for the unified artifact resolution.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/registry/manifest.rs (175-176)

medium

The crate_name field is added to ExtensionSource::WasmBuildable when converting the manifest to a registry entry. This ensures that the crate name is correctly propagated for locating build artifacts, which is a new requirement for the unified artifact resolution.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/registry/mod.rs (14)

medium

The new artifacts module is added to the registry module. This is a good structural change, centralizing WASM artifact handling logic and making it easily accessible within the registry system.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/tools/builder/core.rs (801-804)

medium

The logic for determining the WASM output path is refactored to use crate::tools::wasm::wasm_artifact_path. This centralizes the path resolution logic and ensures consistency with other parts of the codebase that deal with WASM artifacts.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/tools/builtin/extension_tools.rs (33-34)

medium

The description for tool_search is updated to emphasize "channels (Telegram, Slack, Discord — for messaging)" before other extension types. This aligns with the "channel-first prompt language" goal, guiding the LLM to prioritize channels when users ask about messaging platforms.

src/tools/builtin/extension_tools.rs (104)

medium

The description for tool_install is updated to prioritize "channel" before "tool" and "MCP server". This aligns with the "channel-first prompt language" goal, guiding the LLM to think of channels as a primary extension type.

src/tools/builtin/extension_tools.rs (282)

medium

The description for tool_activate is updated to explicitly mention "starts channels" first, followed by "loads tools" and "connects to MCP servers". This reinforces the channel-first language and clarifies the activation process for different extension types.

src/tools/builtin/extension_tools.rs (376-377)

medium

The description for tool_list is updated to include the new include_available parameter, which allows showing registry entries that are not yet installed. This improves the clarity of the tool's functionality and its new capabilities.

src/tools/builtin/extension_tools.rs (389-392)

medium

The include_available parameter is added to the tool_list schema. This new boolean parameter allows the LLM to request a list of all available extensions, including those not yet installed, which is a key part of the enhanced extension management.

src/tools/builtin/extension_tools.rs (415-418)

medium

The include_available parameter is extracted from the tool parameters. This value is then passed to the manager.list method, enabling the tool to filter results based on whether available-but-not-installed extensions should be included.

src/tools/builtin/extension_tools.rs (422)

medium

The include_available parameter is now passed to the manager.list method. This is a direct consequence of adding the new parameter to the tool_list tool, allowing it to control whether available-but-not-installed extensions are returned.

src/tools/builtin/extension_tools.rs (454)

medium

The description for tool_remove is updated to prioritize "channel" before "tool" and "MCP server". This aligns with the "channel-first prompt language" goal, guiding the LLM to think of channels as a primary extension type.

src/tools/wasm/loader.rs (386-407)

medium

New public functions resolve_wasm_target_dir and wasm_artifact_path are added. These functions centralize the logic for resolving WASM target directories and constructing artifact paths, which is a good refactoring that improves consistency and reusability across the codebase.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/tools/wasm/loader.rs (452)

medium

The wasm_path is now resolved using the new wasm_artifact_path helper function. This centralizes the logic for determining the WASM artifact path, making it more robust and consistent with the new unified artifact resolution.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

src/tools/wasm/mod.rs (125-126)

medium

The resolve_wasm_target_dir and wasm_artifact_path functions are now publicly exported from the wasm module. This makes these utility functions accessible for other modules that need to resolve WASM artifact paths, such as the builder and CLI tools.

References
  1. When locating a build artifact in an output directory, search for a specific file based on the crate/package name rather than picking the first file with the correct extension to avoid ambiguity in projects with multiple targets.

tests/html_to_markdown.rs (30-31)

medium

The normalize_smart_punctuation function is updated to use replace with an array of characters for typographic quotes. This is a more concise and efficient way to replace multiple characters with a single one, improving code readability and potentially performance.

tests/html_to_markdown.rs (59-65)

medium

The metadata parsing logic is refactored to use an if let statement instead of .then(|| ...).unwrap_or_default(). This improves readability and is a more idiomatic way to handle optional file reading and deserialization.

tools-src/okta/src/api.rs (36)

medium

The http_request host function now includes an additional None argument. This indicates a change in the host function's signature, likely adding a new optional parameter (e.g., for timeouts or other request options). All call sites need to be updated to match the new signature.

tools-src/okta/src/api.rs (227)

medium

The http_request host function now includes an additional None argument. This indicates a change in the host function's signature, likely adding a new optional parameter (e.g., for timeouts or other request options). All call sites need to be updated to match the new signature.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the extension system to support runtime lifecycle management for WASM channels (hot activation + credential refresh), updates prompt/tool language to be “channel-first”, and centralizes WASM artifact discovery/build/install logic across the registry/CLI/dev loader.

Changes:

  • Add WASM channel hot-activation plumbing: ChannelManager::hot_add(), ExtensionManager::activate_wasm_channel() + refresh flow, and early runtime/router wiring in main.
  • Introduce src/registry/artifacts.rs for unified WASM artifact resolution (multi-triple search) and refactor installer/CLI/dev discovery to use it.
  • Update LLM/tool descriptions and listing behavior (channels before MCP servers; tool_list supports include_available).

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tools-src/okta/src/api.rs Update host HTTP request call signature to include the new trailing argument.
tests/html_to_markdown.rs Refactor normalization logic and metadata loading in HTML→MD integration tests.
src/tools/wasm/mod.rs Re-export additional WASM loader helpers for artifact path/target dir resolution.
src/tools/wasm/loader.rs Use shared artifact-path helper instead of hardcoded target paths for dev tool discovery.
src/tools/builtin/extension_tools.rs Update tool descriptions and add include_available support to tool_list.
src/tools/builder/core.rs Use shared helper to compute expected WASM output paths (avoid hardcoded target dir).
src/registry/mod.rs Export new registry::artifacts module.
src/registry/manifest.rs Include crate_name in buildable WASM source metadata.
src/registry/installer.rs Delegate WASM builds to the new registry::artifacts::build_wasm_component.
src/registry/artifacts.rs New module: multi-triple artifact search + build helpers + install helper.
src/main.rs Initialize channel infra early and load/register WASM channels with “skip until secrets exist” behavior; register catch-all webhook route.
src/llm/reasoning.rs Add an “Extensions” prompt section and reorder guidance to emphasize channels before MCP servers.
src/extensions/registry.rs Add all_entries() to return builtins + cached discoveries (for list/include_available).
src/extensions/mod.rs Extend extension model to include channel-first framing, add crate_name, add installed flag; remove “channels require restart” error.
src/extensions/manager.rs Implement WASM channel activation/refresh; add list(..., include_available); support install-from-build-artifacts for buildable WASM entries.
src/extensions/discovery.rs Update module doc wording to be extension-generic (not MCP-only).
src/cli/tool.rs Refactor CLI install flow to use registry::artifacts for build/find logic.
src/channels/web/server.rs Update extension manager list calls to pass the new include_available arg.
src/channels/web/handlers/extensions.rs Update extension manager list calls to pass the new include_available arg.
src/channels/wasm/wrapper.rs Make call_on_start() public to allow re-running it after credential refresh.
src/channels/wasm/router.rs Add update_secret() for refreshing webhook secrets of registered channels.
src/channels/wasm/bundled.rs Use unified artifact search (multi-triple) for dev build-tree channel artifact lookup.
src/channels/manager.rs Change add to take &self and add hot_add() to inject new channel streams at runtime.
src/agent/dispatcher.rs Update test agent construction to pass Arc<ChannelManager>.
src/agent/agent_loop.rs Update Agent::new to accept an Arc<ChannelManager> directly.
channels-src/telegram/Cargo.toml Remove stray [workspace] section from channel crate manifest.
Cargo.lock Lockfile reordering/update from dependency graph changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/channels/manager.rs Outdated
Comment on lines 42 to 52
@@ -52,6 +52,35 @@ impl ChannelManager {
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChannelManager::add uses try_write() and, on contention, logs an error but drops the channel without registering it. This can lead to missing channels nondeterministically (especially now that the manager is shared via Arc). Consider making add async and using write().await (or otherwise guaranteeing insertion) so channel registration cannot fail silently.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d939125. Changed add() to use async write().await instead of try_write(), and updated all 4 callers in main.rs to .await the call.

Comment thread tests/html_to_markdown.rs
Comment on lines +30 to +31
s.replace(['\u{2019}', '\u{2018}'], "'")
.replace(['\u{201C}', '\u{201D}'], "\"")
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str::replace does not reliably accept a [char; N] pattern across toolchains; passing an array here is likely to fail to compile. Use a slice pattern instead (e.g., pass a reference like &['…', '…'][..]) or keep the explicit chained replace(char, ..) calls.

Suggested change
s.replace(['\u{2019}', '\u{2018}'], "'")
.replace(['\u{201C}', '\u{201D}'], "\"")
s.replace(&['\u{2019}', '\u{2018}'][..], "'")
.replace(&['\u{201C}', '\u{201D}'][..], "\"")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pre-existing test that was not changed in this PR. The str::replace with a char array pattern compiles and passes on our CI toolchain. No change needed.

Comment thread src/registry/artifacts.rs Outdated
/// 2. `<crate_dir>/target/` (default per-crate layout)
pub fn resolve_target_dir(crate_dir: &Path) -> PathBuf {
if let Ok(dir) = std::env::var("CARGO_TARGET_DIR") {
return PathBuf::from(dir);
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve_target_dir returns CARGO_TARGET_DIR verbatim. If the env var is set to a relative path (common in CI/dev), the resulting target base becomes relative to the current process CWD rather than crate_dir, and artifact lookup may fail. Consider resolving relative CARGO_TARGET_DIR paths against crate_dir (or canonicalizing) before returning.

Suggested change
return PathBuf::from(dir);
let path = Path::new(&dir);
return if path.is_absolute() {
path.to_path_buf()
} else {
crate_dir.join(path)
};

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d939125. resolve_target_dir now resolves relative CARGO_TARGET_DIR against crate_dir.

Comment thread src/registry/artifacts.rs
Comment on lines +230 to +234
for caps_src in &caps_candidates {
if caps_src.exists() {
let _ = fs::copy(caps_src, &caps_dst).await;
break;
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

install_wasm_files ignores errors when copying the capabilities sidecar (let _ = fs::copy(...)). If the copy fails, the install still returns Ok, leaving the extension without capabilities and potentially breaking auth/permissions. Propagate the error (or at least log it) so failed installs are visible.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d939125. Now logs a tracing::warn! on capabilities copy failure instead of silently discarding the error.

Comment thread src/extensions/manager.rs
Comment on lines +1879 to +1888
// Also refresh the webhook secret in the router
let webhook_secret_name = format!("{}_webhook_secret", name);
if let Ok(secret) = self
.secrets
.get_decrypted(&self.user_id, &webhook_secret_name)
.await
{
router
.update_secret(name, secret.expose().to_string())
.await;
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refresh_active_channel assumes the webhook secret name is always {name}_webhook_secret, but capabilities can override the secret name (and activation uses LoadedChannel::webhook_secret_name() which honors that). This can prevent the router secret from being refreshed for channels with a custom secret_name. Consider reading the installed capabilities file to compute the correct secret name (same logic as LoadedChannel::webhook_secret_name()), or persisting the resolved secret name when registering the channel.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d939125. refresh_active_channel now loads the capabilities file to get the correct webhook_secret_name(), falling back to the default format if the file can't be loaded.

Comment thread src/extensions/manager.rs
Comment on lines +1693 to +1697
// Load the channel from files
let wasm_path = self.wasm_channels_dir.join(format!("{}.wasm", name));
let cap_path = self
.wasm_channels_dir
.join(format!("{}.capabilities.json", name));
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activate_wasm_channel builds wasm_path / cap_path directly from the user-controlled name. Without validating that name is a simple filename (no /, .., etc.), this can enable path traversal to load arbitrary .wasm files outside wasm_channels_dir. Reject names containing path separators or traversal sequences before joining, and consider normalizing to a strict [a-z0-9_-]+ allowlist.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d939125. Added path traversal validation in activate_wasm_channel — rejects names containing /, \\, .., or null bytes before constructing file paths.

Comment thread src/main.rs Outdated
}
}

// Always register the WASM channel catch-all webhook route when the runtime
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment reads as an unfinished sentence (// Always register the WASM channel catch-all webhook route when the runtime). Consider completing it (e.g., "when the runtime is enabled") to avoid confusion for future readers.

Suggested change
// Always register the WASM channel catch-all webhook route when the runtime
// Always register the WASM channel catch-all webhook route when the runtime is available but no specific WASM channels were loaded.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment and the associated code were removed during the merge from main in 775ff9f — the main branch refactored startup into the AppComponents builder pattern which no longer has this code path. Already resolved.

Comment thread src/extensions/manager.rs
Comment on lines +1651 to +1664
/// Activate a WASM channel at runtime without restarting.
///
/// Loads the channel from its WASM file, injects credentials and config,
/// registers it with the webhook router, and hot-adds it to the channel manager
/// so its stream feeds into the agent loop.
async fn activate_wasm_channel(&self, name: &str) -> Result<ActivateResult, ExtensionError> {
// If already active, re-inject credentials and refresh webhook secret.
// Handles the case where a channel was loaded at startup before the
// user saved secrets via the web UI.
{
let active = self.active_channel_names.read().await;
if active.contains(name) {
return self.refresh_active_channel(name).await;
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hot-activation (activate_wasm_channel / refresh_active_channel) introduces substantial new runtime behavior (loading WASM, registering webhooks, hot-adding streams). src/extensions/manager.rs currently only has a small URL-kind unit test; consider adding targeted tests for the new channel activation/refresh paths (including missing secrets and router secret updates) to prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. The hot-activation path is inherently more complex than static startup since it must handle the running state. The credential injection, webhook registration, and channel loading each have their own error paths. We've added path traversal validation and proper capabilities loading in this round. Additional hardening (timeouts, structured error types) can be tracked as follow-up work.

ilblackdragon and others added 3 commits February 21, 2026 21:38
…ack)

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 <noreply@anthropic.com>
…m-channels

# Conflicts:
#	src/extensions/manager.rs
#	src/main.rs
- 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 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 22, 2026 06:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/registry/artifacts.rs
Comment on lines +273 to +277
fn test_find_wasm_artifact_found() {
let dir = TempDir::new().unwrap();
let wasm_dir = dir.path().join("target/wasm32-wasip2/release");
std::fs::create_dir_all(&wasm_dir).unwrap();
std::fs::File::create(wasm_dir.join("my_tool.wasm")).unwrap();
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_wasm_artifact tests create fixtures under dir/target/..., but find_wasm_artifact uses resolve_target_dir which may honor CARGO_TARGET_DIR. If that env var is set, the fixture directory won’t be searched and the test will fail. Consider creating wasm_dir under resolve_target_dir(dir.path()) instead of hardcoding target/.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a1349fe. Tests now use resolve_target_dir(dir.path()) to derive fixture paths instead of hardcoding target/, so they work correctly when CARGO_TARGET_DIR is set.

Comment on lines 83 to 87
- {} (flat/packaged)\n \
- {} (build tree)\n \
- {} (build tree, and other triples)\n \
Build it first:\n \
cd {} && cargo build --target wasm32-wasip2 --release",
name,
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message/build hint still hardcodes cargo build --target wasm32-wasip2 --release, but this code now searches across multiple WASM triples. That hint can be misleading if the artifact is produced under a different triple (e.g. wasip1). Consider updating the message to either mention cargo component build --release without a hardcoded target, or list the set of supported triples you search.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a1349fe. Build hint now says cargo component build --release instead of hardcoding --target wasm32-wasip2.

Comment thread src/extensions/manager.rs
Comment on lines +124 to +142
/// Configure the channel runtime infrastructure for hot-activating WASM channels.
///
/// Must be called after construction to enable `activate_wasm_channel()`.
/// Without this, channel activation falls back to an error.
pub fn with_channel_runtime(
mut self,
channel_manager: Arc<ChannelManager>,
wasm_channel_runtime: Arc<WasmChannelRuntime>,
pairing_store: Arc<PairingStore>,
wasm_channel_router: Arc<WasmChannelRouter>,
telegram_owner_id: Option<i64>,
) -> Self {
self.channel_manager = Some(channel_manager);
self.wasm_channel_runtime = Some(wasm_channel_runtime);
self.pairing_store = Some(pairing_store);
self.wasm_channel_router = Some(wasm_channel_router);
self.telegram_owner_id = telegram_owner_id;
self
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_channel_runtime is required for hot-activating WASM channels, but there are no call sites in the repo (search only finds this definition). As a result, activate_wasm_channel() will consistently return "WASM channel runtime not available" and the hot-activation flow described in the PR won’t actually be reachable. Consider wiring this up where ExtensionManager is constructed (passing the live ChannelManager, WasmChannelRuntime, PairingStore, and WasmChannelRouter).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a1349fe. Changed with_channel_runtime(mut self) to set_channel_runtime(&self) using interior mutability (RwLock<Option<ChannelRuntimeState>>), and wired it up in main.rs after setup_wasm_channels() returns. Hot-activation is now reachable.

Comment thread src/tools/wasm/loader.rs Outdated
Comment on lines +398 to +399
/// and the binary name (e.g. `slack_tool.wasm`).
///
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment says binary_name is e.g. slack_tool.wasm, but wasm_artifact_path appends .wasm itself. This is misleading and can lead to callers passing a name that already ends with .wasm and getting *.wasm.wasm. Consider updating the comment/example to use slack_tool (no extension).

Suggested change
/// and the binary name (e.g. `slack_tool.wasm`).
///
/// and the binary name without extension (e.g. `slack_tool`).
///
/// `binary_name` should not include the `.wasm` extension; it is appended automatically.
///

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a1349fe. Doc now says binary_name is e.g. slack_tool (no extension) and notes that .wasm is appended automatically.

Comment thread src/llm/reasoning.rs
Comment on lines +702 to +708
You can search, install, and activate extensions to add new capabilities:\n\
- **Channels** (Telegram, Slack, Discord) — messaging integrations. \
When users ask about connecting a messaging platform, search for it as a channel.\n\
- **Tools** — sandboxed functions that extend your abilities.\n\
- **MCP servers** — external API integrations via the Model Context Protocol.\n\n\
Use `tool_search` to find extensions by name. Refer to them by their kind \
(channel, tool, or server) — not as \"MCP server\" generically."
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_extensions_section uses "\n\ ..." with indentation after the line-continuation. Those leading spaces become part of the prompt and (in Markdown) can turn the body into a code block, making the guidance less effective. Consider removing the indentation in the literal (or using an indoc/dedent-style helper) so the section renders as normal Markdown text.

Suggested change
You can search, install, and activate extensions to add new capabilities:\n\
- **Channels** (Telegram, Slack, Discord) — messaging integrations. \
When users ask about connecting a messaging platform, search for it as a channel.\n\
- **Tools** — sandboxed functions that extend your abilities.\n\
- **MCP servers** — external API integrations via the Model Context Protocol.\n\n\
Use `tool_search` to find extensions by name. Refer to them by their kind \
(channel, tool, or server) — not as \"MCP server\" generically."
You can search, install, and activate extensions to add new capabilities:\n\
- **Channels** (Telegram, Slack, Discord) — messaging integrations. \
When users ask about connecting a messaging platform, search for it as a channel.\n\
- **Tools** — sandboxed functions that extend your abilities.\n\
- **MCP servers** — external API integrations via the Model Context Protocol.\n\n\
Use `tool_search` to find extensions by name. Refer to them by their kind \
(channel, tool, or server) — not as \"MCP server\" generically."

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. In Rust string literals, \ at end of line is a line continuation that consumes the newline and all leading whitespace on the next line. The indentation here is part of the source formatting, not part of the string value. No extra spaces appear in the output.

Comment on lines +1486 to +1492
"Approval needed: {tool_name}\n\
{description}\n\
\n\
Parameters:\n\
{params_preview}\n\
\n\
Reply \"yes\" to approve, \"no\" to deny, or \"always\" to auto-approve."
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prompt string literal includes large leading indentation on wrapped lines (because of the \ line continuations), which will be sent to users verbatim (e.g., the description/parameters lines will start with many spaces). Consider removing the indentation (or building the message with join / a dedent helper) so the approval prompt is readable in chat UIs.

Suggested change
"Approval needed: {tool_name}\n\
{description}\n\
\n\
Parameters:\n\
{params_preview}\n\
\n\
Reply \"yes\" to approve, \"no\" to deny, or \"always\" to auto-approve."
concat!(
"Approval needed: {tool_name}\n",
"{description}\n",
"\n",
"Parameters:\n",
"{params_preview}\n",
"\n",
"Reply \"yes\" to approve, \"no\" to deny, or \"always\" to auto-approve."
)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive — same reason as above. Rust's \ line continuation consumes the newline and all leading whitespace. The indentation is source formatting only; the approval prompt string has no extra spaces.

Comment thread src/registry/artifacts.rs
Comment on lines +258 to +264
#[test]
fn test_resolve_target_dir_default() {
// When CARGO_TARGET_DIR is not set, should return <crate_dir>/target
let dir = Path::new("/some/crate");
let result = resolve_target_dir(dir);
assert!(result.ends_with("target"));
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests assume CARGO_TARGET_DIR is unset and hardcode .../target/.... If CARGO_TARGET_DIR is set in the environment (common in CI), resolve_target_dir() will return a different base and these assertions/fixtures will fail. Consider making the tests derive their expected paths from resolve_target_dir(dir) (and/or branching expectations based on the env var) so they’re environment-independent.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a1349fe. Tests now use resolve_target_dir(dir.path()) to derive fixture paths instead of hardcoding target/, so they work correctly when CARGO_TARGET_DIR is set.

Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall

Well-structured PR that adds hot-activation for WASM channels, unifies duplicated artifact resolution, and improves prompt language. The d939125 commit addressed earlier feedback well. However, there is one critical wiring gap that makes the primary feature unreachable at runtime, plus a UTF-8 panic risk.


Critical

1. with_channel_runtime() is never called -- hot-activation is dead code

ExtensionManager::with_channel_runtime() populates five fields required by activate_wasm_channel(), but has zero call sites anywhere in the codebase. It's not wired up in main.rs or anywhere else.

This means activate_wasm_channel() will always hit:

let channel_runtime = self.wasm_channel_runtime.as_ref().ok_or_else(|| {
    ExtensionError::ActivationFailed(
        "WASM channel runtime not available. Restart IronClaw to activate.".to_string(),
    )
})?;

The entire hot-activation flow -- activate_wasm_channel(), refresh_active_channel(), hot_add(), and the auto-activate in save_setup_secrets() -- is unreachable. The PR's primary feature does not work at runtime.

Fix: Wire with_channel_runtime() in main.rs after constructing ExtensionManager and before wrapping it in Arc, passing the live ChannelManager, WasmChannelRuntime, PairingStore, and WasmChannelRouter.

2. UTF-8 panic in approval parameter truncation (src/channels/wasm/wrapper.rs)

Two instances of byte-offset slicing that will panic on multi-byte UTF-8 input:

if s.len() > 80 {
    format!("\"{}...\"", &s[..77])  // panics if byte 77 is mid-character
}

s.len() is byte count; &s[..77] slices at byte offset 77. CJK text, emoji, or accented characters will trigger a panic. Same class of bug just fixed in catalog.rs (PR #300).

Fix: Use s.get(..77).unwrap_or(s) or s.char_indices() for safe truncation.


Important

3. wasm_artifact_path doc comment misleading (src/tools/wasm/loader.rs)

Doc says the binary name example is slack_tool.wasm, but the function appends .wasm itself. Callers should pass "slack_tool", not "slack_tool.wasm". Update the doc example.

4. Error message in bundled.rs hardcodes wasm32-wasip2

The build hint in locate_channel_artifacts suggests cargo build --target wasm32-wasip2 --release but the search now uses find_wasm_artifact() across all triples. Consider making the suggestion triple-agnostic: cargo component build --release.

5. Tests fragile under CARGO_TARGET_DIR (src/registry/artifacts.rs)

Tests create fixtures under dir.path().join("target/...") but resolve_target_dir honors CARGO_TARGET_DIR env. If set in CI, these tests will look in the wrong place and fail.


What's Done Well

  • ChannelManager::hot_add() design is clean -- start, register, spawn forwarding task via inject_tx
  • Unified registry/artifacts.rs eliminates real duplication across 5+ files with proper multi-triple search
  • reasoning_content fallback for GLM-5 is well-placed in both complete() and complete_with_tools()
  • Empty-response guard after clean_response() strips think-tags is good defense-in-depth
  • Error handling improvements in agent_loop.rs (logging respond() failures instead of silently dropping)
  • Path traversal validation in activate_wasm_channel() is thorough

… 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 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

&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 <noreply@anthropic.com>
@ilblackdragon
Copy link
Copy Markdown
Member Author

Thanks for the thorough review @zmanian! Here's where each item stands:

Critical #1with_channel_runtime() never called: Fixed in a1349fe. Changed to set_channel_runtime(&self) with RwLock<Option<ChannelRuntimeState>> interior mutability, wired up in main.rs after setup_wasm_channels() returns the runtime/pairing/router. Hot-activation is now reachable.

Critical #2 — UTF-8 panic in &s[..77]: Fixed in 72e840f. Replaced byte-offset slicing with s.chars().count() / s.chars().take(77).collect() for safe truncation at character boundaries.

Important #3wasm_artifact_path doc misleading: Fixed in a1349fe. Doc now says binary_name is e.g. slack_tool (no extension) and notes .wasm is appended automatically.

Important #4 — Build hint hardcodes wasm32-wasip2: Fixed in a1349fe. Changed to cargo component build --release.

Important #5 — Tests fragile under CARGO_TARGET_DIR: Fixed in a1349fe. Tests now use resolve_target_dir(dir.path()) to derive fixture paths.

All fixes pass cargo fmt + cargo clippy (zero warnings) + cargo test (1397 passed).

@ilblackdragon ilblackdragon merged commit ea57447 into main Feb 22, 2026
4 checks passed
@ilblackdragon ilblackdragon deleted the feat/hot-activate-wasm-channels branch February 22, 2026 08:09
This was referenced Feb 22, 2026
jaswinder6991 pushed a commit to jaswinder6991/ironclaw that referenced this pull request Feb 26, 2026
…fact resolution (nearai#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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* fix: address PR nearai#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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…fact resolution (nearai#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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* fix: address PR nearai#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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: channel Channel infrastructure scope: dependencies Dependency updates scope: extensions Extension management scope: llm LLM integration scope: tool/builder Dynamic tool builder scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants