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
582 changes: 581 additions & 1 deletion src/apps/desktop/src/api/agentic_api.rs

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions src/apps/desktop/src/api/config_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bitfun_core::util::errors::BitFunError;
use log::{error, info};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use tauri::State;

#[derive(Debug, Deserialize)]
Expand All @@ -15,6 +16,14 @@ pub struct GetConfigRequest {
pub skip_retry_on_not_found: bool,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetConfigsRequest {
pub paths: Vec<String>,
#[serde(default)]
pub skip_retry_on_not_found: bool,
}

#[derive(Debug, Deserialize)]
pub struct SetConfigRequest {
pub path: String,
Expand Down Expand Up @@ -66,6 +75,41 @@ pub async fn get_config(
}
}

#[tauri::command]
pub async fn get_configs(
state: State<'_, AppState>,
request: GetConfigsRequest,
) -> Result<BTreeMap<String, Value>, String> {
let config_service = &state.config_service;
let mut configs = BTreeMap::new();

for path in request.paths {
if configs.contains_key(&path) {
continue;
}

match config_service
.get_config::<Value>(Some(path.as_str()))
.await
{
Ok(config) => {
configs.insert(path, config);
}
Err(e) => {
if request.skip_retry_on_not_found
&& is_expected_config_path_not_found(&e, Some(path.as_str()))
{
return Err(format!("Failed to get config: {}", e));
}
error!("Failed to get config: path={}, error={}", path, e);
return Err(format!("Failed to get config: {}", e));
}
}
}

Ok(configs)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
62 changes: 61 additions & 1 deletion src/apps/desktop/src/api/git_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use bitfun_core::service::git::{
GitBranch, GitCommit, GitOperationResult, GitRepository, GitStatus,
};
use bitfun_core::service::remote_ssh::{lookup_remote_connection, normalize_remote_workspace_path};
use log::{error, info};
use log::{debug, error, info};
use serde::{Deserialize, Serialize};
use tauri::State;

Expand Down Expand Up @@ -588,6 +588,66 @@ pub async fn git_get_repository(
})
}

#[tauri::command]
pub async fn git_get_repository_basic(
state: State<'_, AppState>,
request: GitRepositoryRequest,
) -> Result<GitRepository, String> {
let started_at = std::time::Instant::now();
let result = if let Some(target) = resolve_remote_git_target(&request.repository_path).await {
let current_branch = execute_remote_git_success(
&state,
&target,
&["branch".to_string(), "--show-current".to_string()],
)
.await
.map(|s| {
let branch = s.trim();
if branch.is_empty() {
"HEAD".to_string()
} else {
branch.to_string()
}
})?;

let name = target
.repository_path
.rsplit('/')
.find(|part| !part.is_empty())
.unwrap_or("/")
.to_string();

Ok(GitRepository {
path: target.repository_path,
name,
current_branch,
is_bare: false,
has_changes: false,
remotes: Vec::new(),
})
} else {
GitService::get_repository_basic(&request.repository_path)
.await
.map_err(|e| {
error!(
"Failed to get basic Git repository info: path={}, error={}",
request.repository_path, e
);
format!("Failed to get basic Git repository info: {}", e)
})
};

let duration_ms = started_at.elapsed().as_millis();
if duration_ms >= 80 {
debug!(
"Git basic repository lookup completed: path={}, duration_ms={}",
request.repository_path, duration_ms
);
}

result
}

#[tauri::command]
pub async fn git_get_status(
state: State<'_, AppState>,
Expand Down
58 changes: 46 additions & 12 deletions src/apps/desktop/src/api/snapshot_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,11 @@ async fn resolve_workspace_dir(workspace_path: &str) -> Result<PathBuf, String>
Ok(workspace_dir)
}

async fn ensure_snapshot_manager_ready(
async fn ensure_snapshot_manager_ready_for(
workspace_path: &str,
caller: &str,
) -> Result<Arc<SnapshotManager>, String> {
let started_at = std::time::Instant::now();
// Remote workspaces don't support the snapshot system
if is_remote_path(workspace_path).await {
return Err(format!(
Expand All @@ -247,11 +249,21 @@ async fn ensure_snapshot_manager_ready(
let workspace_dir = resolve_workspace_dir(workspace_path).await?;

if let Some(manager) = get_snapshot_manager_for_workspace(&workspace_dir) {
let duration_ms = started_at.elapsed().as_millis();
if duration_ms >= 20 {
log::debug!(
"Snapshot manager ready: caller={}, workspace={}, source=cache, duration_ms={}",
caller,
workspace_dir.display(),
duration_ms
);
}
return Ok(manager);
}

info!(
"Snapshot manager missing, initializing lazily: workspace={}",
"Snapshot manager missing, initializing lazily: caller={}, workspace={}",
caller,
workspace_dir.display()
);

Expand All @@ -265,16 +277,30 @@ async fn ensure_snapshot_manager_ready(
)
})?;

ensure_snapshot_manager_for_workspace(&workspace_dir)
.map_err(|e| format!("Failed to get snapshot manager: {}", e))
let manager = ensure_snapshot_manager_for_workspace(&workspace_dir)
.map_err(|e| format!("Failed to get snapshot manager: {}", e))?;
log::debug!(
"Snapshot manager ready: caller={}, workspace={}, source=lazy_init, duration_ms={}",
caller,
workspace_dir.display(),
started_at.elapsed().as_millis()
);
Ok(manager)
}

async fn ensure_snapshot_manager_ready(
workspace_path: &str,
) -> Result<Arc<SnapshotManager>, String> {
ensure_snapshot_manager_ready_for(workspace_path, "unspecified").await
}

#[tauri::command]
pub async fn record_file_change(
app_handle: AppHandle,
request: RecordFileChangeRequest,
) -> Result<String, String> {
let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "record_file_change").await?;

let operation_type = match request.operation_type.as_str() {
"Create" => OperationType::Create,
Expand Down Expand Up @@ -323,7 +349,8 @@ pub async fn rollback_session(
return Ok(vec![]);
}

let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "rollback_session").await?;

let restored_files = manager
.rollback_session(&request.session_id)
Expand Down Expand Up @@ -373,7 +400,8 @@ pub async fn rollback_to_turn(
}
}

let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "rollback_to_turn").await?;

let restored_files = manager
.rollback_to_turn(&request.session_id, request.turn_index)
Expand Down Expand Up @@ -469,7 +497,8 @@ pub async fn accept_session(
app_handle: AppHandle,
request: AcceptSessionRequest,
) -> Result<serde_json::Value, String> {
let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "accept_session").await?;

manager
.accept_session(&request.session_id)
Expand Down Expand Up @@ -553,7 +582,8 @@ pub async fn get_session_files(request: GetSessionFilesRequest) -> Result<Vec<St
return Ok(vec![]);
}

let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "get_session_files").await?;

let files = manager
.get_session_files(&request.session_id)
Expand Down Expand Up @@ -690,7 +720,9 @@ pub async fn get_operation_diff(
pub async fn get_session_file_diff_stats(
request: GetSessionFileDiffStatsRequest,
) -> Result<serde_json::Value, String> {
let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "get_session_file_diff_stats")
.await?;

let stats = manager
.get_session_file_diff_stats(&request.sessionId, &request.filePath)
Expand All @@ -704,7 +736,8 @@ pub async fn get_session_file_diff_stats(
pub async fn get_operation_summary(
request: GetOperationSummaryRequest,
) -> Result<serde_json::Value, String> {
let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "get_operation_summary").await?;

let summary = manager
.get_operation_summary(&request.sessionId, &request.operationId)
Expand Down Expand Up @@ -859,7 +892,8 @@ pub async fn get_session_stats(
}));
}

let manager = ensure_snapshot_manager_ready(&request.workspace_path).await?;
let manager =
ensure_snapshot_manager_ready_for(&request.workspace_path, "get_session_stats").await?;

let stats = manager
.get_session_stats(&request.session_id)
Expand Down
4 changes: 4 additions & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,8 @@ pub async fn run() {
api::agentic_api::set_subagent_timeout,
api::agentic_api::delete_session,
api::agentic_api::restore_session,
api::agentic_api::restore_session_view,
api::agentic_api::restore_session_with_turns,
webdriver_bridge_result,
api::agentic_api::list_sessions,
api::agentic_api::confirm_tool_execution,
Expand Down Expand Up @@ -669,6 +671,7 @@ pub async fn run() {
get_clipboard_files,
paste_files,
get_config,
get_configs,
computer_use_get_status,
computer_use_request_permissions,
computer_use_open_system_settings,
Expand Down Expand Up @@ -708,6 +711,7 @@ pub async fn run() {
add_skill,
delete_skill,
git_is_repository,
git_get_repository_basic,
git_get_repository,
git_get_status,
git_get_branches,
Expand Down
Loading
Loading