Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Add `default_client()` HTTP helper with standard timeouts and user-agent in zeph-core and zeph-llm (#666)
- Replace 5 production `Client::new()` calls with `default_client()` for consistent HTTP config (#667)
- Decompose agent/mod.rs (2602→459 lines) into tool_execution, message_queue, builder, commands, and utils modules (#648, #649, #650)
- Replace `anyhow` in `zeph-core::config` with typed `ConfigError` enum (Io, Parse, Validation, Vault)
- Replace `anyhow` in `zeph-tui` with typed `TuiError` enum (Io, Channel); simplify `handle_event()` return to `()`
- Sort `[workspace.dependencies]` alphabetically in root Cargo.toml

### Fixed
- False positive: "sudoku" no longer matched by "sudo" blocked pattern (word-boundary matching)
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 23 additions & 23 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,33 @@ repository = "https://github.com/bug-ops/zeph"

[workspace.dependencies]
age = { version = "0.11.2", default-features = false }
clap = { version = "4.5", features = ["derive"] }
dialoguer = "0.11"
anyhow = "1.0"
axum = "0.8"
base64 = "0.22"
blake3 = "1.8"
candle-core = { version = "0.9", default-features = false }
candle-nn = { version = "0.9", default-features = false }
candle-transformers = { version = "0.9", default-features = false }
chrono = { version = "0.4", default-features = false, features = ["std"] }
crossterm = "0.29"
axum = "0.8"
blake3 = "1.8"
clap = { version = "4.5", features = ["derive"] }
criterion = "0.8"
glob = "0.3.3"
cron = "0.15"
crossterm = "0.29"
dialoguer = "0.11"
eventsource-stream = "0.2"
futures = "0.3"
ignore = "0.4"
futures-core = "0.3"
glob = "0.3.3"
hf-hub = { version = "0.4", default-features = false, features = ["tokio", "rustls-tls", "ureq"] }
eventsource-stream = "0.2"
http-body-util = "0.1"
futures-core = "0.3"
ignore = "0.4"
notify = "8"
nucleo-matcher = "0.3.1"
notify-debouncer-mini = "0.7"
nucleo-matcher = "0.3.1"
ollama-rs = { version = "0.3", default-features = false, features = ["rustls", "stream"] }
opentelemetry = "0.31"
opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"] }
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio"] }
pdf-extract = "0.7"
proptest = "1.6"
pulldown-cmark = "0.13"
Expand All @@ -43,33 +47,29 @@ ratatui = "0.30"
regex = "1.12"
reqwest = { version = "0.13", default-features = false }
rmcp = "0.15"
semver = "1.0.27"
scrape-core = "0.2.2"
subtle = "2.6"
rubato = "0.16"
schemars = "1.2"
similar = "2.7"
scrape-core = "0.2.2"
semver = "1.0.27"
serde = "1.0"
serde_json = "1.0"
serial_test = "3.3"
symphonia = { version = "0.5.5", default-features = false, features = ["mp3", "ogg", "wav", "flac", "pcm"] }
similar = "2.7"
sqlx = { version = "0.8", default-features = false, features = ["macros"] }
subtle = "2.6"
symphonia = { version = "0.5.5", default-features = false, features = ["mp3", "ogg", "wav", "flac", "pcm"] }
teloxide = { version = "0.17", default-features = false, features = ["rustls", "ctrlc_handler", "macros"] }
tempfile = "3"
testcontainers = "0.27"
wiremock = "0.6.5"
thiserror = "2.0"
tokenizers = { version = "0.22", default-features = false, features = ["fancy-regex"] }
tokio = "1"
tokio-tungstenite = { version = "0.28", default-features = false }
tokio-stream = "0.1"
tokio-tungstenite = { version = "0.28", default-features = false }
tokio-util = "0.7"
toml = "1.0"
tower = "0.5"
tower-http = "0.6"
opentelemetry = "0.31"
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"] }
tracing = "0.1"
tracing-opentelemetry = "0.32"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Expand All @@ -87,18 +87,18 @@ tree-sitter-typescript = "0.23"
unicode-width = "0.2"
url = "2.5"
uuid = "1.21"
cron = "0.15"
wiremock = "0.6.5"
zeph-a2a = { path = "crates/zeph-a2a", version = "0.11.2" }
zeph-channels = { path = "crates/zeph-channels", version = "0.11.2" }
zeph-core = { path = "crates/zeph-core", version = "0.11.2" }
zeph-gateway = { path = "crates/zeph-gateway", version = "0.11.2" }
zeph-index = { path = "crates/zeph-index", version = "0.11.2" }
zeph-llm = { path = "crates/zeph-llm", version = "0.11.2" }
zeph-mcp = { path = "crates/zeph-mcp", version = "0.11.2" }
zeph-memory = { path = "crates/zeph-memory", version = "0.11.2" }
zeph-scheduler = { path = "crates/zeph-scheduler", version = "0.11.2" }
zeph-skills = { path = "crates/zeph-skills", version = "0.11.2" }
zeph-tools = { path = "crates/zeph-tools", version = "0.11.2" }
zeph-gateway = { path = "crates/zeph-gateway", version = "0.11.2" }
zeph-scheduler = { path = "crates/zeph-scheduler", version = "0.11.2" }
zeph-tui = { path = "crates/zeph-tui", version = "0.11.2" }

[workspace.lints.clippy]
Expand Down
66 changes: 40 additions & 26 deletions crates/zeph-core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ pub use zeph_tools::AutonomyLevel;

use std::path::Path;

use anyhow::Context;

use crate::vault::VaultProvider;

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("failed to parse config file: {0}")]
Parse(#[from] toml::de::Error),
#[error("config validation failed: {0}")]
Validation(String),
#[error("vault error: {0}")]
Vault(#[from] anyhow::Error),
}

impl Config {
/// Load configuration from a TOML file with env var overrides.
///
Expand All @@ -21,10 +31,10 @@ impl Config {
/// # Errors
///
/// Returns an error if the file exists but cannot be read or parsed.
pub fn load(path: &Path) -> anyhow::Result<Self> {
pub fn load(path: &Path) -> Result<Self, ConfigError> {
let mut config = if path.exists() {
let content = std::fs::read_to_string(path).context("failed to read config file")?;
toml::from_str::<Self>(&content).context("failed to parse config file")?
let content = std::fs::read_to_string(path)?;
toml::from_str::<Self>(&content)?
} else {
Self::default()
};
Expand All @@ -38,29 +48,33 @@ impl Config {
/// # Errors
///
/// Returns an error if any value is out of range.
pub fn validate(&self) -> anyhow::Result<()> {
anyhow::ensure!(
self.memory.history_limit <= 10_000,
"history_limit must be <= 10000, got {}",
self.memory.history_limit
);
if self.memory.context_budget_tokens > 0 {
anyhow::ensure!(
self.memory.context_budget_tokens <= 1_000_000,
pub fn validate(&self) -> Result<(), ConfigError> {
if self.memory.history_limit > 10_000 {
return Err(ConfigError::Validation(format!(
"history_limit must be <= 10000, got {}",
self.memory.history_limit
)));
}
if self.memory.context_budget_tokens > 1_000_000 {
return Err(ConfigError::Validation(format!(
"context_budget_tokens must be <= 1000000, got {}",
self.memory.context_budget_tokens
);
)));
}
if self.agent.max_tool_iterations > 100 {
return Err(ConfigError::Validation(format!(
"max_tool_iterations must be <= 100, got {}",
self.agent.max_tool_iterations
)));
}
if self.a2a.rate_limit == 0 {
return Err(ConfigError::Validation("a2a.rate_limit must be > 0".into()));
}
if self.gateway.rate_limit == 0 {
return Err(ConfigError::Validation(
"gateway.rate_limit must be > 0".into(),
));
}
anyhow::ensure!(
self.agent.max_tool_iterations <= 100,
"max_tool_iterations must be <= 100, got {}",
self.agent.max_tool_iterations
);
anyhow::ensure!(self.a2a.rate_limit > 0, "a2a.rate_limit must be > 0");
anyhow::ensure!(
self.gateway.rate_limit > 0,
"gateway.rate_limit must be > 0"
);
Ok(())
}

Expand All @@ -69,7 +83,7 @@ impl Config {
/// # Errors
///
/// Returns an error if the vault backend fails.
pub async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> anyhow::Result<()> {
pub async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
use crate::vault::Secret;

if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
Expand Down
2 changes: 1 addition & 1 deletion crates/zeph-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ pub mod http;
pub use agent::Agent;
pub use agent::error::AgentError;
pub use channel::{Attachment, AttachmentKind, Channel, ChannelError, ChannelMessage};
pub use config::Config;
pub use config::{Config, ConfigError};
pub use diff::DiffData;
1 change: 0 additions & 1 deletion crates/zeph-tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ license.workspace = true
repository.workspace = true

[dependencies]
anyhow.workspace = true
ignore.workspace = true
nucleo-matcher.workspace = true
crossterm.workspace = true
Expand Down
Loading
Loading