fix: use selected vault for mcp bridge
This commit is contained in:
@@ -131,6 +131,22 @@ pub async fn check_mcp_status(vault_path: String) -> Result<crate::mcp::McpStatu
|
||||
.map_err(|e| format!("MCP status check failed: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub async fn sync_mcp_bridge_vault(
|
||||
app: tauri::AppHandle,
|
||||
vault_path: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
let expanded_vault_path = vault_path
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|path| !path.is_empty())
|
||||
.map(|path| super::expand_tilde(path).into_owned());
|
||||
let vault_path = expanded_vault_path.as_deref().map(std::path::Path::new);
|
||||
|
||||
crate::sync_ws_bridge_for_vault(&app, vault_path).map(str::to_string)
|
||||
}
|
||||
|
||||
// ── MCP commands (mobile stubs) ─────────────────────────────────────────────
|
||||
|
||||
#[cfg(mobile)]
|
||||
@@ -151,6 +167,12 @@ pub async fn check_mcp_status(_vault_path: String) -> Result<crate::mcp::McpStat
|
||||
Ok(crate::mcp::McpStatus::NotInstalled)
|
||||
}
|
||||
|
||||
#[cfg(mobile)]
|
||||
#[tauri::command]
|
||||
pub async fn sync_mcp_bridge_vault(_vault_path: Option<String>) -> Result<String, String> {
|
||||
Err("MCP is not available on mobile".into())
|
||||
}
|
||||
|
||||
// ── Menu commands ───────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -102,6 +102,78 @@ fn log_startup_result(label: &str, result: Result<usize, String>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn selected_mcp_bridge_vault_path(vault_list: &vault_list::VaultList) -> Option<PathBuf> {
|
||||
vault_list
|
||||
.active_vault
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|path| !path.is_empty())
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn validate_mcp_bridge_vault_path(vault_path: &Path) -> Result<PathBuf, String> {
|
||||
let resolved = std::fs::canonicalize(vault_path).map_err(|e| {
|
||||
format!(
|
||||
"MCP bridge vault is not available: {} ({e})",
|
||||
vault_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !resolved.is_dir() {
|
||||
return Err(format!(
|
||||
"MCP bridge vault is not available: {} is not a directory",
|
||||
vault_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn stop_ws_bridge_child(active_child: &mut Option<Child>) {
|
||||
if let Some(mut child) = active_child.take() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
log::info!("ws-bridge child process stopped");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub(crate) fn sync_ws_bridge_for_vault(
|
||||
app_handle: &tauri::AppHandle,
|
||||
vault_path: Option<&Path>,
|
||||
) -> Result<&'static str, String> {
|
||||
use tauri::Manager;
|
||||
|
||||
let state: tauri::State<'_, WsBridgeChild> = app_handle.state();
|
||||
let mut active_child = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock ws-bridge state".to_string())?;
|
||||
|
||||
let Some(vault_path) = vault_path else {
|
||||
stop_ws_bridge_child(&mut active_child);
|
||||
return Ok("stopped");
|
||||
};
|
||||
|
||||
let resolved_vault_path = match validate_mcp_bridge_vault_path(vault_path) {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
stop_ws_bridge_child(&mut active_child);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
stop_ws_bridge_child(&mut active_child);
|
||||
|
||||
let child = mcp::spawn_ws_bridge(&resolved_vault_path)?;
|
||||
|
||||
*active_child = Some(child);
|
||||
Ok("started")
|
||||
}
|
||||
|
||||
/// Run startup housekeeping on the default vault (migrate legacy frontmatter, seed configs).
|
||||
#[cfg(desktop)]
|
||||
fn run_startup_tasks() {
|
||||
@@ -124,17 +196,21 @@ fn run_startup_tasks() {
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn spawn_ws_bridge(app: &mut tauri::App) {
|
||||
use tauri::Manager;
|
||||
let vault_path = dirs::home_dir()
|
||||
.map(|h| h.join("Laputa"))
|
||||
.unwrap_or_default();
|
||||
let vp_str = vault_path.to_string_lossy().to_string();
|
||||
match mcp::spawn_ws_bridge(&vp_str) {
|
||||
Ok(child) => {
|
||||
let state: tauri::State<'_, WsBridgeChild> = app.state();
|
||||
*state.0.lock().unwrap() = Some(child);
|
||||
let vault_path = match vault_list::load_vault_list() {
|
||||
Ok(vault_list) => selected_mcp_bridge_vault_path(&vault_list),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load active vault for ws-bridge startup: {}", e);
|
||||
None
|
||||
}
|
||||
Err(e) => log::warn!("Failed to start ws-bridge: {}", e),
|
||||
};
|
||||
|
||||
let Some(vault_path) = vault_path else {
|
||||
log::info!("ws-bridge not started: no active vault selected");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = sync_ws_bridge_for_vault(app.handle(), Some(&vault_path)) {
|
||||
log::warn!("Failed to start ws-bridge: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +432,7 @@ macro_rules! app_invoke_handler {
|
||||
commands::register_mcp_tools,
|
||||
commands::remove_mcp_tools,
|
||||
commands::check_mcp_status,
|
||||
commands::sync_mcp_bridge_vault,
|
||||
commands::repair_vault,
|
||||
commands::reinit_telemetry,
|
||||
commands::list_views,
|
||||
@@ -416,6 +493,11 @@ mod tests {
|
||||
use super::MACOS_WEBVIEW_RESERVED_COMMAND_KEYS;
|
||||
use super::MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use super::{selected_mcp_bridge_vault_path, validate_mcp_bridge_vault_path};
|
||||
#[cfg(desktop)]
|
||||
use crate::vault_list::VaultList;
|
||||
|
||||
#[cfg(all(desktop, unix))]
|
||||
use super::vault_asset_scope_roots;
|
||||
|
||||
@@ -459,6 +541,48 @@ mod tests {
|
||||
assert!(overrides.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[test]
|
||||
fn selected_mcp_bridge_vault_path_uses_persisted_active_vault() {
|
||||
let list = VaultList {
|
||||
vaults: Vec::new(),
|
||||
active_vault: Some("/tmp/Selected Vault".to_string()),
|
||||
hidden_defaults: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
selected_mcp_bridge_vault_path(&list),
|
||||
Some(std::path::PathBuf::from("/tmp/Selected Vault"))
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[test]
|
||||
fn selected_mcp_bridge_vault_path_ignores_blank_active_vault() {
|
||||
let list = VaultList {
|
||||
vaults: Vec::new(),
|
||||
active_vault: Some(" ".to_string()),
|
||||
hidden_defaults: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(selected_mcp_bridge_vault_path(&list), None);
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[test]
|
||||
fn validate_mcp_bridge_vault_path_requires_existing_directory() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let vault = dir.path().join("Vault With Spaces");
|
||||
std::fs::create_dir(&vault).unwrap();
|
||||
|
||||
let resolved = validate_mcp_bridge_vault_path(&vault).unwrap();
|
||||
assert_eq!(resolved, vault.canonicalize().unwrap());
|
||||
|
||||
let missing = dir.path().join("Missing Vault");
|
||||
let err = validate_mcp_bridge_vault_path(&missing).unwrap_err();
|
||||
assert!(err.contains("MCP bridge vault is not available"));
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, unix))]
|
||||
#[test]
|
||||
fn vault_asset_scope_roots_include_requested_symlink_path() {
|
||||
|
||||
@@ -215,10 +215,11 @@ fn mcp_server_dir_has_files(path: &Path) -> bool {
|
||||
}
|
||||
|
||||
/// Spawn the WebSocket bridge as a child process.
|
||||
pub fn spawn_ws_bridge(vault_path: &str) -> Result<Child, String> {
|
||||
pub fn spawn_ws_bridge(vault_path: impl AsRef<Path>) -> Result<Child, String> {
|
||||
let node = find_node()?;
|
||||
let server_dir = mcp_server_dir()?;
|
||||
let script = server_dir.join("ws-bridge.js");
|
||||
let vault_path = vault_path.as_ref();
|
||||
|
||||
let child = crate::hidden_command(node)
|
||||
.arg(&script)
|
||||
@@ -231,7 +232,11 @@ pub fn spawn_ws_bridge(vault_path: &str) -> Result<Child, String> {
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to spawn ws-bridge: {e}"))?;
|
||||
|
||||
log::info!("ws-bridge spawned (pid: {})", child.id());
|
||||
log::info!(
|
||||
"ws-bridge spawned (pid: {}, vault: {})",
|
||||
child.id(),
|
||||
vault_path.display()
|
||||
);
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user