mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-29 17:09:31 +00:00
* refactor: split large files and consolidate test stubs for contributor velocity - Extract 7 Database sub-traits (ConversationStore, JobStore, SandboxStore, RoutineStore, ToolFailureStore, SettingsStore, WorkspaceStore) with Database as a supertrait combining them all - Split libsql_backend.rs (2769 lines) into src/db/libsql/ directory with one file per sub-trait implementation - Split config.rs (1753 lines) into src/config/ directory with 16 domain files - Consolidate 3 duplicate test LLM stubs into shared StubLlm in src/testing.rs - Split server.rs handlers into src/channels/web/handlers/ directory - Extract main.rs init phases into AppBuilder (src/app.rs) - Add developer setup script (scripts/dev-setup.sh) Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: move heartbeat test from examples/ to tests/ Convert standalone example binary into a proper #[ignore] integration test, matching the convention of the other integration tests. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt formatting for CI Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments from Copilot - tunnel.rs: replace .ok().flatten() with ? to propagate env var errors - secrets.rs: remove misleading "process-wide cache" comment - database.rs: use uppercase "DATABASE_URL" in error key - testing.rs: gate harness tests with #[cfg(feature = "libsql")] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
127 lines
4.4 KiB
Rust
127 lines
4.4 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use secrecy::SecretString;
|
|
|
|
use crate::config::helpers::optional_env;
|
|
use crate::error::ConfigError;
|
|
use crate::settings::Settings;
|
|
|
|
/// Channel configurations.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ChannelsConfig {
|
|
pub cli: CliConfig,
|
|
pub http: Option<HttpConfig>,
|
|
pub gateway: Option<GatewayConfig>,
|
|
/// Directory containing WASM channel modules (default: ~/.ironclaw/channels/).
|
|
pub wasm_channels_dir: std::path::PathBuf,
|
|
/// Whether WASM channels are enabled.
|
|
pub wasm_channels_enabled: bool,
|
|
/// Telegram owner user ID. When set, the bot only responds to this user.
|
|
pub telegram_owner_id: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CliConfig {
|
|
pub enabled: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct HttpConfig {
|
|
pub host: String,
|
|
pub port: u16,
|
|
pub webhook_secret: Option<SecretString>,
|
|
pub user_id: String,
|
|
}
|
|
|
|
/// Web gateway configuration.
|
|
#[derive(Debug, Clone)]
|
|
pub struct GatewayConfig {
|
|
pub host: String,
|
|
pub port: u16,
|
|
/// Bearer token for authentication. Random hex generated at startup if unset.
|
|
pub auth_token: Option<String>,
|
|
pub user_id: String,
|
|
}
|
|
|
|
impl ChannelsConfig {
|
|
pub(crate) fn resolve(settings: &Settings) -> Result<Self, ConfigError> {
|
|
let http = if optional_env("HTTP_PORT")?.is_some() || optional_env("HTTP_HOST")?.is_some() {
|
|
Some(HttpConfig {
|
|
host: optional_env("HTTP_HOST")?.unwrap_or_else(|| "0.0.0.0".to_string()),
|
|
port: optional_env("HTTP_PORT")?
|
|
.map(|s| s.parse())
|
|
.transpose()
|
|
.map_err(|e| ConfigError::InvalidValue {
|
|
key: "HTTP_PORT".to_string(),
|
|
message: format!("must be a valid port number: {e}"),
|
|
})?
|
|
.unwrap_or(8080),
|
|
webhook_secret: optional_env("HTTP_WEBHOOK_SECRET")?.map(SecretString::from),
|
|
user_id: optional_env("HTTP_USER_ID")?.unwrap_or_else(|| "http".to_string()),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let gateway = if optional_env("GATEWAY_ENABLED")?
|
|
.map(|s| s.to_lowercase() == "true" || s == "1")
|
|
.unwrap_or(true)
|
|
{
|
|
Some(GatewayConfig {
|
|
host: optional_env("GATEWAY_HOST")?.unwrap_or_else(|| "127.0.0.1".to_string()),
|
|
port: optional_env("GATEWAY_PORT")?
|
|
.map(|s| s.parse())
|
|
.transpose()
|
|
.map_err(|e| ConfigError::InvalidValue {
|
|
key: "GATEWAY_PORT".to_string(),
|
|
message: format!("must be a valid port number: {e}"),
|
|
})?
|
|
.unwrap_or(3000),
|
|
auth_token: optional_env("GATEWAY_AUTH_TOKEN")?,
|
|
user_id: optional_env("GATEWAY_USER_ID")?.unwrap_or_else(|| "default".to_string()),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let cli_enabled = optional_env("CLI_ENABLED")?
|
|
.map(|s| s.to_lowercase() != "false" && s != "0")
|
|
.unwrap_or(true);
|
|
|
|
Ok(Self {
|
|
cli: CliConfig {
|
|
enabled: cli_enabled,
|
|
},
|
|
http,
|
|
gateway,
|
|
wasm_channels_dir: optional_env("WASM_CHANNELS_DIR")?
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(default_channels_dir),
|
|
wasm_channels_enabled: optional_env("WASM_CHANNELS_ENABLED")?
|
|
.map(|s| s.parse())
|
|
.transpose()
|
|
.map_err(|e| ConfigError::InvalidValue {
|
|
key: "WASM_CHANNELS_ENABLED".to_string(),
|
|
message: format!("must be 'true' or 'false': {e}"),
|
|
})?
|
|
.unwrap_or(true),
|
|
telegram_owner_id: optional_env("TELEGRAM_OWNER_ID")?
|
|
.map(|s| s.parse())
|
|
.transpose()
|
|
.map_err(|e| ConfigError::InvalidValue {
|
|
key: "TELEGRAM_OWNER_ID".to_string(),
|
|
message: format!("must be an integer: {e}"),
|
|
})?
|
|
.or(settings.channels.telegram_owner_id),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Get the default channels directory (~/.ironclaw/channels/).
|
|
fn default_channels_dir() -> PathBuf {
|
|
dirs::home_dir()
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
.join(".ironclaw")
|
|
.join("channels")
|
|
}
|