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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ atspi = "0.29"
leptess = "0.14"
core-foundation = "0.9"
core-graphics = { version = "0.23", features = ["elcapitan", "highsierra"] }
foreign-types = "0.5"
dispatch = "0.2"
block2 = "0.6"
objc2 = "0.6"
Expand Down
1 change: 0 additions & 1 deletion src/apps/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,4 +768,3 @@ fn main() {
}
}
}

37 changes: 21 additions & 16 deletions src/apps/cli/src/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ use bitfun_core::service::session_usage::{
generate_session_usage_report, render_usage_report_markdown, SessionUsageReportRequest,
};


async fn ensure_global_config_service() -> Result<
std::sync::Arc<bitfun_core::service::config::ConfigService>,
> {
async fn ensure_global_config_service(
) -> Result<std::sync::Arc<bitfun_core::service::config::ConfigService>> {
initialize_global_config()
.await
.context("Failed to initialize global config service")?;
Expand Down Expand Up @@ -144,9 +142,10 @@ pub async fn print_mcp_servers() -> Result<()> {
.as_ref()
.map(|cmd| format!("{} {}", cmd, config.args.join(" ")))
.unwrap_or_else(|| "<missing command>".to_string()),
bitfun_core::service::mcp::server::MCPServerType::Remote => {
config.url.clone().unwrap_or_else(|| "<missing url>".to_string())
}
bitfun_core::service::mcp::server::MCPServerType::Remote => config
.url
.clone()
.unwrap_or_else(|| "<missing url>".to_string()),
};

println!("- {} ({:?})", config.id, config.server_type);
Expand Down Expand Up @@ -186,7 +185,10 @@ pub async fn set_mcp_server_enabled(server_id: &str, enabled: bool) -> Result<()
.await?
.ok_or_else(|| anyhow!("MCP server not found: {}", server_id))?;
config.enabled = enabled;
mcp_service.config_service().save_server_config(&config).await?;
mcp_service
.config_service()
.save_server_config(&config)
.await?;

println!(
"MCP server {} {}.",
Expand All @@ -200,19 +202,16 @@ pub async fn print_mcp_json_config() -> Result<()> {
let config_service = ensure_global_config_service().await?;
let mcp_service = bitfun_core::service::mcp::MCPService::new(config_service.clone())
.map_err(|error| anyhow!(error.to_string()))?;
let json = mcp_service
.config_service()
.load_mcp_json_config()
.await?;
let json = mcp_service.config_service().load_mcp_json_config().await?;
println!("{}", json);
Ok(())
}

pub async fn print_usage_report(session_id: Option<&str>) -> Result<()> {
let agentic_system = crate::agent::agentic_system::init_agentic_system_for_cli().await?;
let path_manager = try_get_path_manager_arc().map_err(|error| anyhow!(error.to_string()))?;
let persistence_manager = PersistenceManager::new(path_manager)
.map_err(|error| anyhow!(error.to_string()))?;
let persistence_manager =
PersistenceManager::new(path_manager).map_err(|error| anyhow!(error.to_string()))?;
let workspace_path = std::env::current_dir().context("Failed to resolve current directory")?;
let coordinator = agentic_system.coordinator.clone();
let resolved_session_id = match session_id {
Expand Down Expand Up @@ -250,7 +249,9 @@ pub async fn print_doctor() -> Result<bool> {
let models = config_service.get_ai_models().await?;
let agent_registry = get_agent_registry();
let modes = agent_registry.get_modes_info().await;
let subagents = agent_registry.get_subagents_info(Some(workspace.as_path())).await;
let subagents = agent_registry
.get_subagents_info(Some(workspace.as_path()))
.await;
let mcp_service = bitfun_core::service::mcp::MCPService::new(config_service.clone())
.map_err(|error| anyhow!(error.to_string()))?;
let mcp_configs = mcp_service.config_service().load_all_configs().await?;
Expand All @@ -261,7 +262,11 @@ pub async fn print_doctor() -> Result<bool> {
println!("[ok] Config directory: {}", config_dir.display());
println!("[ok] Agent modes: {}", modes.len());
println!("[ok] Subagents: {}", subagents.len());
println!("[ok] AI models: {} total, {} enabled", models.len(), models.iter().filter(|m| m.enabled).count());
println!(
"[ok] AI models: {} total, {} enabled",
models.len(),
models.iter().filter(|m| m.enabled).count()
);
println!("[ok] MCP servers: {}", mcp_configs.len());
println!();
println!("Doctor checks passed.");
Expand Down
10 changes: 2 additions & 8 deletions src/apps/cli/src/modes/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2914,14 +2914,8 @@ impl ChatMode {
});

let count = outcome.len();
chat_state.add_system_message(format!(
"Reloaded {} skill(s) from disk.",
count
));
chat_view.set_status(Some(format!(
"Skills reloaded ({} available)",
count
)));
chat_state.add_system_message(format!("Reloaded {} skill(s) from disk.", count));
chat_view.set_status(Some(format!("Skills reloaded ({} available)", count)));
}

fn show_available_skill_list(
Expand Down
35 changes: 25 additions & 10 deletions src/apps/cli/src/modes/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ impl ExecMode {
if parent_session_id.map(String::as_str) == Some(session_id.as_str()) {
use bitfun_events::ToolEventData;
match tool_event {
ToolEventData::Started { tool_name, tool_id, .. } => {
ToolEventData::Started {
tool_name, tool_id, ..
} => {
self.emit(json!({
"type": "subagent_tool_start",
"session_id": session_id,
Expand Down Expand Up @@ -184,11 +186,17 @@ impl ExecMode {
"summary": summary,
}))?;
self.print_text(|| {
println!(" [subagent] {} completed: {}", tool_name, summary)
println!(
" [subagent] {} completed: {}",
tool_name, summary
)
});
}
ToolEventData::Failed {
tool_name, tool_id, error, ..
tool_name,
tool_id,
error,
..
} => {
self.emit(json!({
"type": "subagent_tool_error",
Expand All @@ -197,7 +205,9 @@ impl ExecMode {
"tool_name": tool_name,
"error": error,
}))?;
self.print_text(|| println!(" [subagent] {} failed: {}", tool_name, error));
self.print_text(|| {
println!(" [subagent] {} failed: {}", tool_name, error)
});
}
_ => {}
}
Expand Down Expand Up @@ -289,7 +299,10 @@ impl ExecMode {
"summary": summary,
}))?;
self.print_text(|| {
println!(" [+] {} ({}ms): {}", tool_name, duration_ms, summary)
println!(
" [+] {} ({}ms): {}",
tool_name, duration_ms, summary
)
});
}
ToolEventData::Failed {
Expand Down Expand Up @@ -322,7 +335,10 @@ impl ExecMode {
println!("\n");
println!("Execution complete");
if total_tool_calls > 0 {
println!("\nTool call statistics: {} tools invoked", total_tool_calls);
println!(
"\nTool call statistics: {} tools invoked",
total_tool_calls
);
}
});
self.output_patch_if_needed();
Expand Down Expand Up @@ -407,10 +423,9 @@ impl ExecMode {
.ok_or_else(|| anyhow::anyhow!("Session has no persisted turns to fork"))?;
let path_manager = bitfun_core::infrastructure::try_get_path_manager_arc()
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
let persistence_manager = bitfun_core::agentic::persistence::PersistenceManager::new(
path_manager,
)
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
let persistence_manager =
bitfun_core::agentic::persistence::PersistenceManager::new(path_manager)
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
let result = persistence_manager
.branch_session(
&workspace,
Expand Down
8 changes: 8 additions & 0 deletions src/apps/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ serde_json = { workspace = true }
[dependencies]
# Internal crates
bitfun-core = { path = "../../crates/assembly/core", default-features = false, features = ["product-full"] }
bitfun-agent-tools = { path = "../../crates/execution/tool-contracts" }
bitfun-transport = { path = "../../crates/adapters/transport", features = ["tauri-adapter"] }
bitfun-webdriver = { path = "../../crates/adapters/webdriver" }
bitfun-acp = { path = "../../crates/interfaces/acp" }
Expand Down Expand Up @@ -71,6 +72,8 @@ bitflags = { workspace = true }
core-foundation = { workspace = true }
core-graphics = { workspace = true }
dispatch = { workspace = true }
foreign-types = { workspace = true }
libc = { workspace = true }
objc2 = { workspace = true, features = ["exception"] }
objc2-foundation = { workspace = true }
objc2-app-kit = { workspace = true }
Expand All @@ -85,7 +88,12 @@ windows = { workspace = true, features = [
"Media_Ocr",
"Storage_Streams",
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Storage_Xps",
"Win32_System_Com",
"Win32_System_Ole",
"Win32_System_Variant",
"Win32_UI_Accessibility",
"Win32_UI_WindowsAndMessaging",
] }
Expand Down
17 changes: 12 additions & 5 deletions src/apps/desktop/src/api/clipboard_file_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,9 @@ fn copy_directory_recursive(source: &Path, target: &Path) -> Result<(), String>

#[cfg(test)]
mod tests {
use super::{decode_file_uri, generate_unique_path, parse_clipboard_path_segments, parse_uri_list};
use super::{
decode_file_uri, generate_unique_path, parse_clipboard_path_segments, parse_uri_list,
};
use std::path::Path;

#[test]
Expand Down Expand Up @@ -495,14 +497,19 @@ mod tests {
#[test]
fn generate_unique_path_uses_current_dir_when_parent_missing() {
let unique = generate_unique_path(Path::new("example.txt"));
assert_eq!(unique.file_name(), Some(std::ffi::OsStr::new("example (1).txt")));
assert_eq!(
unique.file_name(),
Some(std::ffi::OsStr::new("example (1).txt"))
);
}

#[test]
fn parse_uri_list_ignores_comments_and_blank_lines() {
let files = parse_uri_list(
"# comment\n\nfile:///tmp/a.txt\r\nfile://localhost/tmp/b.txt\n",
let files =
parse_uri_list("# comment\n\nfile:///tmp/a.txt\r\nfile://localhost/tmp/b.txt\n");
assert_eq!(
files,
vec!["/tmp/a.txt".to_string(), "/tmp/b.txt".to_string()]
);
assert_eq!(files, vec!["/tmp/a.txt".to_string(), "/tmp/b.txt".to_string()]);
}
}
Loading
Loading