Parent: #32
Blocked by: All prior sub-issues
Problem
Two halves of channel messaging are broken:
Inbound: The bridge's runtime.channel.reply() sends notifications/astrid.inboundMessage to the Rust host (see astrid_bridge.mjs:116-123). But AstridClientHandler (in astrid-mcp) doesn't handle this notification — messages are silently dropped.
Outbound: When the agent calls a send tool, it needs to route through the plugin. This is likely automatic once Items 1+2 land — if the Unicity plugin registers a send tool via registerTool(), the tool wiring makes it reachable. Verify before building anything.
Approach — Inbound
- Define an
InboundChannelMessage struct with fields: plugin_id, context (the context object from the bridge), and content
- Add a bounded
mpsc::channel(256) sender to either AstridClientHandler or McpPlugin's notification path
- Handle
notifications/astrid.inboundMessage in the notification dispatch — validate payload size (<1MB), extract fields, send through the channel
- The
DaemonServer consumes from the receiver, finds the appropriate agent session, and injects the message as a new user turn
The notification arrives via the rmcp ClientHandler trait. Check AstridClientHandler in crates/astrid-mcp/src/capabilities.rs — it currently handles on_tool_list_changed (line 682). Custom notifications may need a different hook point; check rmcp's ClientHandler trait for a on_notification or similar method.
Approach — Outbound
Verify after Items 1-3: load the Unicity plugin and check what tools it exports. If it registers a send tool via registerTool(), Items 1+2 make it automatically available (appears in LLM tool list, dispatches through Peer<RoleClient>, bridge routes to handler). If no send tool exists, add a synthetic one or use an MCP notification path.
Key Files
crates/astrid-mcp/src/capabilities.rs — AstridClientHandler (line 419), rmcp ClientHandler impl (line 445)
crates/astrid-plugins/src/mcp_plugin.rs — McpPlugin notification handling
crates/astrid-gateway/src/server.rs — DaemonServer and session routing
crates/openclaw-bridge/bridge/astrid_bridge.mjs — runtime.channel.reply() (line 116-123) shows the notification format
Gotchas
- The bridge sends:
{ method: "notifications/astrid.inboundMessage", params: { pluginId, context, content } }
- Use a bounded channel (
mpsc::channel(256)) to prevent unbounded memory growth. Log a warning and drop on full.
- Validate payload size before forwarding — reject >1MB to prevent DoS from a compromised plugin.
- The
DaemonServer doesn't currently have a message router — this may need a simple lookup from plugin ID to session. Consider which session should receive the message (the most recent one? all of them?).
- This is the most architecturally involved item. It may be worth splitting into two sub-PRs: (a) notification capture + channel, (b) session injection + routing.
Verification
- Send a message through the bridge's
channel.reply() → verify it appears on the Rust receiver
- Outbound: verify Unicity's send tool appears in tool list and is callable
- End-to-end: CLI → agent → Unicity channel → verify round-trip
cargo test --workspace -- --quiet passes
Parent: #32
Blocked by: All prior sub-issues
Problem
Two halves of channel messaging are broken:
Inbound: The bridge's
runtime.channel.reply()sendsnotifications/astrid.inboundMessageto the Rust host (seeastrid_bridge.mjs:116-123). ButAstridClientHandler(inastrid-mcp) doesn't handle this notification — messages are silently dropped.Outbound: When the agent calls a send tool, it needs to route through the plugin. This is likely automatic once Items 1+2 land — if the Unicity plugin registers a send tool via
registerTool(), the tool wiring makes it reachable. Verify before building anything.Approach — Inbound
InboundChannelMessagestruct with fields:plugin_id,context(the context object from the bridge), andcontentmpsc::channel(256)sender to eitherAstridClientHandlerorMcpPlugin's notification pathnotifications/astrid.inboundMessagein the notification dispatch — validate payload size (<1MB), extract fields, send through the channelDaemonServerconsumes from the receiver, finds the appropriate agent session, and injects the message as a new user turnThe notification arrives via the rmcp
ClientHandlertrait. CheckAstridClientHandlerincrates/astrid-mcp/src/capabilities.rs— it currently handleson_tool_list_changed(line 682). Custom notifications may need a different hook point; check rmcp'sClientHandlertrait for aon_notificationor similar method.Approach — Outbound
Verify after Items 1-3: load the Unicity plugin and check what tools it exports. If it registers a send tool via
registerTool(), Items 1+2 make it automatically available (appears in LLM tool list, dispatches throughPeer<RoleClient>, bridge routes to handler). If no send tool exists, add a synthetic one or use an MCP notification path.Key Files
crates/astrid-mcp/src/capabilities.rs—AstridClientHandler(line 419), rmcpClientHandlerimpl (line 445)crates/astrid-plugins/src/mcp_plugin.rs—McpPluginnotification handlingcrates/astrid-gateway/src/server.rs—DaemonServerand session routingcrates/openclaw-bridge/bridge/astrid_bridge.mjs—runtime.channel.reply()(line 116-123) shows the notification formatGotchas
{ method: "notifications/astrid.inboundMessage", params: { pluginId, context, content } }mpsc::channel(256)) to prevent unbounded memory growth. Log a warning and drop on full.DaemonServerdoesn't currently have a message router — this may need a simple lookup from plugin ID to session. Consider which session should receive the message (the most recent one? all of them?).Verification
channel.reply()→ verify it appears on the Rust receivercargo test --workspace -- --quietpasses