Merge branch 'worktree-agent-af937df502b61b006' into feature_chore_bug_005
# Conflicts: # src-tauri/src/storage/mod.rs
This commit is contained in:
+135
-15
@@ -65,6 +65,11 @@ fn default_settings() -> Settings {
|
||||
audio_output_device: None,
|
||||
microphone_enabled: true,
|
||||
audio_input_device: None,
|
||||
mcp_enabled: false,
|
||||
mcp_transport: "http".into(),
|
||||
mcp_port: 4849,
|
||||
mcp_expose: "none".into(),
|
||||
mcp_expose_recordings: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1735,7 +1740,10 @@ pub(crate) fn serve_recording(
|
||||
if let Some((start, end)) = parse_byte_range(range, total) {
|
||||
return base()
|
||||
.status(StatusCode::PARTIAL_CONTENT)
|
||||
.header(header::CONTENT_RANGE, format!("bytes {start}-{end}/{total}"))
|
||||
.header(
|
||||
header::CONTENT_RANGE,
|
||||
format!("bytes {start}-{end}/{total}"),
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, (end - start + 1).to_string())
|
||||
.body(plain[start..=end].to_vec())
|
||||
.unwrap_or_else(|_| fail(StatusCode::INTERNAL_SERVER_ERROR));
|
||||
@@ -3233,9 +3241,18 @@ pub async fn list_feature_briefs(
|
||||
/// index (`docs/03-data-model.md`).
|
||||
#[tauri::command]
|
||||
pub async fn get_feature_brief(state: State<'_, AppState>, id: String) -> WaResult<FeatureBrief> {
|
||||
let row = state
|
||||
.store
|
||||
.get_feature_brief_row(&id)
|
||||
get_feature_brief_core(&state.store, &id).await
|
||||
}
|
||||
|
||||
/// Core of `get_feature_brief`, factored out of the `State`-taking command so
|
||||
/// the MCP `get_feature_brief` tool (`mcp::handler`, which only holds an
|
||||
/// `Arc<dyn Store>`, not a live Tauri `State`) can call the same logic.
|
||||
pub(crate) async fn get_feature_brief_core(
|
||||
store: &std::sync::Arc<dyn crate::storage::Store>,
|
||||
id: &str,
|
||||
) -> WaResult<FeatureBrief> {
|
||||
let row = store
|
||||
.get_feature_brief_row(id)
|
||||
.await
|
||||
.map_err(|e| WaError::new("storage", e.to_string()))?;
|
||||
let abs_path = meeting_dir(&row.meeting_id).join(&row.path);
|
||||
@@ -3270,29 +3287,132 @@ pub async fn set_brief_exposed(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mcp_status() -> WaResult<serde_json::Value> {
|
||||
Err(not_implemented("mcp_status"))
|
||||
pub async fn mcp_status(state: State<'_, AppState>, app: AppHandle) -> WaResult<serde_json::Value> {
|
||||
#[cfg(feature = "mcp")]
|
||||
{
|
||||
let settings = load_settings();
|
||||
let server = crate::mcp::server::instance(state.store.clone(), app);
|
||||
let running = server.is_running().await;
|
||||
let transport = crate::mcp::McpTransport::parse(&settings.mcp_transport);
|
||||
let endpoint = if running {
|
||||
mcp_endpoint_for(transport, settings.mcp_port)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
Ok(serde_json::json!({
|
||||
"enabled": running,
|
||||
"transport": transport.as_str(),
|
||||
"endpoint": endpoint,
|
||||
"tokenSet": crate::mcp::token::is_set(),
|
||||
"exposeScope": settings.mcp_expose,
|
||||
}))
|
||||
}
|
||||
#[cfg(not(feature = "mcp"))]
|
||||
{
|
||||
let _ = (state, app);
|
||||
Err(not_implemented("mcp_status"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the endpoint string surfaced by `mcp_status`/`set_mcp_enabled` —
|
||||
/// shared so both agree on the shape (`http://127.0.0.1:<port>/mcp` for
|
||||
/// Streamable HTTP, or the `--mcp-stdio` command line the agent should spawn).
|
||||
#[cfg(feature = "mcp")]
|
||||
fn mcp_endpoint_for(transport: crate::mcp::McpTransport, port: u16) -> String {
|
||||
match transport {
|
||||
crate::mcp::McpTransport::Http => format!("http://127.0.0.1:{port}/mcp"),
|
||||
crate::mcp::McpTransport::Stdio => {
|
||||
let exe = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.to_str().map(str::to_string))
|
||||
.unwrap_or_else(|| "whispassist.exe".to_string());
|
||||
format!("{exe} --mcp-stdio")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable/disable the loopback MCP server; returns endpoint + token on enable (FR-MCP-1/6).
|
||||
/// The token is minted fresh on every enable and lives only in the OS
|
||||
/// credential store (`mcp::token`) — this command's return value is the one
|
||||
/// time it's ever surfaced, exactly like a newly-created password.
|
||||
#[tauri::command]
|
||||
pub async fn set_mcp_enabled(
|
||||
_enabled: bool,
|
||||
_transport: Option<String>,
|
||||
_port: Option<u16>,
|
||||
state: State<'_, AppState>,
|
||||
app: AppHandle,
|
||||
enabled: bool,
|
||||
transport: Option<String>,
|
||||
port: Option<u16>,
|
||||
) -> WaResult<serde_json::Value> {
|
||||
Err(not_implemented("set_mcp_enabled"))
|
||||
#[cfg(feature = "mcp")]
|
||||
{
|
||||
use crate::mcp::McpServer;
|
||||
|
||||
let mut settings = load_settings();
|
||||
if let Some(t) = &transport {
|
||||
settings.mcp_transport = t.clone();
|
||||
}
|
||||
if let Some(p) = port {
|
||||
settings.mcp_port = p;
|
||||
}
|
||||
settings.mcp_enabled = enabled;
|
||||
save_settings(&settings)?;
|
||||
|
||||
let server = crate::mcp::server::instance(state.store.clone(), app);
|
||||
if enabled {
|
||||
let cfg = crate::mcp::McpConfig {
|
||||
transport: crate::mcp::McpTransport::parse(&settings.mcp_transport),
|
||||
port: settings.mcp_port,
|
||||
expose: crate::mcp::ExposeScope::parse(&settings.mcp_expose),
|
||||
expose_recordings: settings.mcp_expose_recordings,
|
||||
};
|
||||
let handle = server
|
||||
.start(cfg)
|
||||
.await
|
||||
.map_err(|e| WaError::new("mcp", e.to_string()))?;
|
||||
Ok(serde_json::json!({ "endpoint": handle.endpoint, "token": handle.token }))
|
||||
} else {
|
||||
server
|
||||
.stop(crate::mcp::McpHandle {
|
||||
endpoint: String::new(),
|
||||
token: String::new(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| WaError::new("mcp", e.to_string()))?;
|
||||
Ok(serde_json::json!({ "endpoint": "", "token": "" }))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "mcp"))]
|
||||
{
|
||||
let _ = (state, app, enabled, transport, port);
|
||||
Err(not_implemented("set_mcp_enabled"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Set exposure scope (none|selected|all) and whether recordings may be served (FR-MCP-3).
|
||||
/// Set exposure scope (none|selected|all) and whether recordings may be served
|
||||
/// (FR-MCP-3). Pure settings I/O — every MCP tool handler reads this live
|
||||
/// (`mcp::handler::WaMcpHandler::current_scope`), so a change here takes
|
||||
/// effect immediately without restarting the server.
|
||||
#[tauri::command]
|
||||
pub async fn set_mcp_scope(_expose: String, _expose_recordings: Option<bool>) -> WaResult<()> {
|
||||
Err(not_implemented("set_mcp_scope"))
|
||||
pub async fn set_mcp_scope(expose: String, expose_recordings: Option<bool>) -> WaResult<()> {
|
||||
let mut settings = load_settings();
|
||||
settings.mcp_expose = expose;
|
||||
if let Some(r) = expose_recordings {
|
||||
settings.mcp_expose_recordings = r;
|
||||
}
|
||||
save_settings(&settings)
|
||||
}
|
||||
|
||||
/// Audit trail (FR-MCP-5) — every tool read, allowed or denied.
|
||||
#[tauri::command]
|
||||
pub async fn mcp_access_log(_limit: Option<u32>) -> WaResult<Vec<McpAccessEntry>> {
|
||||
Err(not_implemented("mcp_access_log"))
|
||||
pub async fn mcp_access_log(
|
||||
state: State<'_, AppState>,
|
||||
limit: Option<u32>,
|
||||
) -> WaResult<Vec<McpAccessEntry>> {
|
||||
state
|
||||
.store
|
||||
.list_mcp_access_log(limit)
|
||||
.await
|
||||
.map_err(|e| WaError::new("mcp", e.to_string()))
|
||||
}
|
||||
|
||||
// ---- Agent push / task-tracker handoff (Phase 10c, later) ----
|
||||
|
||||
Reference in New Issue
Block a user