mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-02 01:29:23 +00:00
Unify webhook servers into single WebhookServer
Replace the dual-server architecture (HttpChannel + WasmChannelServer both competing for port 8080) with a single WebhookServer that composes route fragments from all sources. Channels define routes but never spawn servers. - Add WebhookServer struct that collects Router fragments and binds one listener - Extract routes() from HttpChannel, remove server-spawning from start/shutdown - Delete WasmChannelServer (keep WasmChannelRouter and route builder) - Rewire main.rs to compose all webhook routes into one server Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8439293df3
commit
9d156411fc
+52
-52
@@ -8,10 +8,10 @@ use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitEx
|
||||
use ironclaw::{
|
||||
agent::{Agent, AgentDeps},
|
||||
channels::{
|
||||
ChannelManager, HttpChannel, ReplChannel,
|
||||
ChannelManager, HttpChannel, ReplChannel, WebhookServer, WebhookServerConfig,
|
||||
wasm::{
|
||||
RegisteredEndpoint, SharedWasmChannel, WasmChannelLoader, WasmChannelRouter,
|
||||
WasmChannelRuntime, WasmChannelRuntimeConfig, WasmChannelServer,
|
||||
WasmChannelRuntime, WasmChannelRuntimeConfig, create_wasm_channel_router,
|
||||
},
|
||||
},
|
||||
cli::{
|
||||
@@ -476,19 +476,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Add HTTP channel if configured and not CLI-only mode
|
||||
if !cli.cli_only {
|
||||
if let Some(ref http_config) = config.channels.http {
|
||||
channels.add(Box::new(HttpChannel::new(http_config.clone())));
|
||||
tracing::info!(
|
||||
"HTTP channel enabled on {}:{}",
|
||||
http_config.host,
|
||||
http_config.port
|
||||
);
|
||||
}
|
||||
}
|
||||
// Collect webhook route fragments; a single WebhookServer hosts them all.
|
||||
let mut webhook_routes: Vec<axum::Router> = Vec::new();
|
||||
|
||||
// Load WASM channels if enabled
|
||||
// Load WASM channels and register their webhook routes.
|
||||
if config.channels.wasm_channels_enabled && config.channels.wasm_channels_dir.exists() {
|
||||
match WasmChannelRuntime::new(WasmChannelRuntimeConfig::default()) {
|
||||
Ok(runtime) => {
|
||||
@@ -500,7 +491,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
.await
|
||||
{
|
||||
Ok(results) => {
|
||||
// Create router for WASM channel webhooks
|
||||
let wasm_router = Arc::new(WasmChannelRouter::new());
|
||||
let mut has_webhook_channels = false;
|
||||
|
||||
@@ -508,10 +498,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
let channel_name = loaded.name().to_string();
|
||||
tracing::info!("Loaded WASM channel: {}", channel_name);
|
||||
|
||||
// Get webhook secret name from capabilities (generic)
|
||||
let secret_name = loaded.webhook_secret_name();
|
||||
|
||||
// Get webhook secret for this channel from secrets store
|
||||
let webhook_secret = if let Some(ref secrets) = secrets_store {
|
||||
secrets
|
||||
.get_decrypted("default", &secret_name)
|
||||
@@ -522,12 +510,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
// Get the secret header name from capabilities
|
||||
let secret_header =
|
||||
loaded.webhook_secret_header().map(|s| s.to_string());
|
||||
|
||||
// Register channel with router for webhook handling
|
||||
// Use known webhook path based on channel name
|
||||
let webhook_path = format!("/webhook/{}", channel_name);
|
||||
let endpoints = vec![RegisteredEndpoint {
|
||||
channel_name: channel_name.clone(),
|
||||
@@ -538,8 +523,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let channel_arc = Arc::new(loaded.channel);
|
||||
|
||||
// Inject runtime config into the channel (tunnel_url, webhook_secret)
|
||||
// This must be done before start() is called
|
||||
{
|
||||
let mut config_updates = std::collections::HashMap::new();
|
||||
|
||||
@@ -585,7 +568,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
.await;
|
||||
has_webhook_channels = true;
|
||||
|
||||
// Inject credentials for this channel (generic pattern-based injection)
|
||||
if let Some(ref secrets) = secrets_store {
|
||||
match inject_channel_credentials(
|
||||
&channel_arc,
|
||||
@@ -613,38 +595,14 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap in SharedWasmChannel for ChannelManager
|
||||
// Both the router and ChannelManager share the same underlying channel
|
||||
channels.add(Box::new(SharedWasmChannel::new(channel_arc)));
|
||||
}
|
||||
|
||||
// Start WASM channel webhook server if we have channels with webhooks.
|
||||
// Skip when the HTTP channel already occupies port 8080.
|
||||
let http_uses_port =
|
||||
config.channels.http.as_ref().map(|h| h.port) == Some(8080);
|
||||
if has_webhook_channels
|
||||
&& config.tunnel.public_url.is_some()
|
||||
&& !http_uses_port
|
||||
{
|
||||
let mut server = WasmChannelServer::new(wasm_router);
|
||||
if let Some(ref ext_mgr) = extension_manager {
|
||||
server = server.with_extension_manager(Arc::clone(ext_mgr));
|
||||
}
|
||||
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 8080));
|
||||
match server.start(addr).await {
|
||||
Ok(_handle) => {
|
||||
tracing::info!(
|
||||
"WASM channel webhook server started on {}",
|
||||
addr
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to start WASM channel webhook server: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
if has_webhook_channels && config.tunnel.public_url.is_some() {
|
||||
webhook_routes.push(create_wasm_channel_router(
|
||||
wasm_router,
|
||||
extension_manager.as_ref().map(Arc::clone),
|
||||
));
|
||||
}
|
||||
|
||||
for (path, err) in &results.errors {
|
||||
@@ -666,6 +624,43 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Add HTTP channel if configured and not CLI-only mode.
|
||||
// Extract its routes for the unified server; the channel itself just
|
||||
// provides the mpsc stream.
|
||||
let mut webhook_server_addr: Option<std::net::SocketAddr> = None;
|
||||
if !cli.cli_only {
|
||||
if let Some(ref http_config) = config.channels.http {
|
||||
let http_channel = HttpChannel::new(http_config.clone());
|
||||
webhook_routes.push(http_channel.routes());
|
||||
let (host, port) = http_channel.addr();
|
||||
webhook_server_addr = Some(
|
||||
format!("{}:{}", host, port)
|
||||
.parse()
|
||||
.expect("HttpConfig host:port must be a valid SocketAddr"),
|
||||
);
|
||||
channels.add(Box::new(http_channel));
|
||||
tracing::info!(
|
||||
"HTTP channel enabled on {}:{}",
|
||||
http_config.host,
|
||||
http_config.port
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the unified webhook server if any routes were registered.
|
||||
let mut webhook_server = if !webhook_routes.is_empty() {
|
||||
let addr =
|
||||
webhook_server_addr.unwrap_or_else(|| std::net::SocketAddr::from(([0, 0, 0, 0], 8080)));
|
||||
let mut server = WebhookServer::new(WebhookServerConfig { addr });
|
||||
for routes in webhook_routes {
|
||||
server.add_routes(routes);
|
||||
}
|
||||
server.start().await?;
|
||||
Some(server)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create workspace for agent (shared with memory tools)
|
||||
let workspace = store.as_ref().map(|s| {
|
||||
let mut ws = Workspace::new("default", s.pool());
|
||||
@@ -715,6 +710,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Run the agent (blocks until shutdown)
|
||||
agent.run().await?;
|
||||
|
||||
// Shut down the webhook server if one was started
|
||||
if let Some(ref mut server) = webhook_server {
|
||||
server.shutdown().await;
|
||||
}
|
||||
|
||||
tracing::info!("Agent shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user