Refactor: Migrate unwrap calls to AppError in MCP handlers
This commit is contained in:
1 parent
4e1a633dbd
commit
0e866f2465
12 files changed
+276
-326
No files matched your search
@@ -103,3 +103,15 @@ mod tests {
|
||||
assert_eq!(err.to_string(), "Bad Request: test");
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AppError {
|
||||
fn from(msg: String) -> Self {
|
||||
AppError::Internal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AppError {
|
||||
fn from(msg: &str) -> Self {
|
||||
AppError::Internal(msg.to_string())
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -21,13 +21,13 @@ impl McpTool for ReadFileSkeletonHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: ReadFileSkeletonTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let file_path = tool_args.file_path.clone();
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let code = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("Failed to read file: {}", e))?;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Failed to read file: {}", e)))?;
|
||||
|
||||
let mut parser = Parser::new();
|
||||
|
||||
@@ -49,7 +49,7 @@ impl McpTool for ReadFileSkeletonHandler {
|
||||
|
||||
parser.set_language(&language.into()).map_err(|e| e.to_string())?;
|
||||
|
||||
let tree = parser.parse(&code, None).ok_or("Failed to parse code")?;
|
||||
let tree = parser.parse(&code, None).ok_or_else(|| crate::error::AppError::Internal("Failed to parse code".to_string()))?;
|
||||
let mut result_skeleton = String::new();
|
||||
|
||||
fn extract_skeleton(node: Node, code: &str, out: &mut String, depth: usize) {
|
||||
@@ -97,7 +97,7 @@ impl McpTool for ReadFileSkeletonHandler {
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -119,13 +119,13 @@ impl McpTool for ReplaceAstNodeHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: ReplaceAstNodeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let file_path = tool_args.file_path.clone();
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let code = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("Failed to read file: {}", e))?;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Failed to read file: {}", e)))?;
|
||||
|
||||
let mut parser = Parser::new();
|
||||
|
||||
@@ -142,11 +142,11 @@ impl McpTool for ReplaceAstNodeHandler {
|
||||
"c" | "h" => tree_sitter_c::LANGUAGE,
|
||||
"cpp" | "cc" | "cxx" | "hpp" | "hxx" => tree_sitter_cpp::LANGUAGE,
|
||||
"go" => tree_sitter_go::LANGUAGE,
|
||||
_ => return Err(format!("Unsupported language for AST replacement: {}", ext)),
|
||||
_ => return Err(crate::error::AppError::Internal(format!("Unsupported language for AST replacement: {}", ext))),
|
||||
};
|
||||
|
||||
parser.set_language(&language.into()).map_err(|e| e.to_string())?;
|
||||
let tree = parser.parse(&code, None).ok_or("Failed to parse code")?;
|
||||
let tree = parser.parse(&code, None).ok_or_else(|| crate::error::AppError::Internal("Failed to parse code".to_string()))?;
|
||||
|
||||
// Search for the node
|
||||
fn find_node<'a>(node: Node<'a>, code: &str, target_type: &str, target_name: &str) -> Option<Node<'a>> {
|
||||
@@ -187,11 +187,11 @@ impl McpTool for ReplaceAstNodeHandler {
|
||||
std::fs::write(&file_path, new_file_content).map_err(|e| e.to_string())?;
|
||||
Ok(format!("Successfully replaced node {} of type {} in {}", tool_args.node_name, tool_args.node_type, file_path))
|
||||
} else {
|
||||
Err(format!("Could not find node {} of type {}", tool_args.node_name, tool_args.node_type))
|
||||
Err(crate::error::AppError::Internal(format!("Could not find node {} of type {}", tool_args.node_name, tool_args.node_type)))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
+13
-16
@@ -20,7 +20,7 @@ impl McpTool for UpdateEnvFingerprintHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: UpdateEnvFingerprintTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.env_fingerprints.modify(|fps| {
|
||||
@@ -54,14 +54,14 @@ impl McpTool for ReadEnvFingerprintHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ReadEnvFingerprintTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state
|
||||
.env_fingerprints
|
||||
.read_with(|fps| fps.get(&req.namespace).cloned());
|
||||
if let Some(fp) = data {
|
||||
let data = serde_json::to_string(&fp).map_err(|e| e.to_string())?;
|
||||
let data = Ok::<String, crate::error::AppError>(serde_json::to_string(&fp)?)?;
|
||||
Ok(data)
|
||||
} else {
|
||||
Ok("{}".to_string())
|
||||
@@ -84,7 +84,7 @@ impl McpTool for LogEnvRequirementHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LogEnvRequirementTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.env_requirements.modify(|reqs| {
|
||||
reqs.retain(|r| !(r.namespace == req.namespace && r.key == req.key));
|
||||
@@ -114,7 +114,7 @@ impl McpTool for RegisterEnvironmentHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: RegisterEnvironmentTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.environments.modify(|envs| {
|
||||
@@ -147,7 +147,7 @@ impl McpTool for GetEnvironmentDetailsHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: GetEnvironmentDetailsTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.environments.read_with(|envs| {
|
||||
@@ -155,7 +155,7 @@ impl McpTool for GetEnvironmentDetailsHandler {
|
||||
.iter()
|
||||
.filter(|e| e.namespace == req.namespace)
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -180,14 +180,13 @@ mod tests {
|
||||
}
|
||||
});
|
||||
|
||||
let res = update_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = update_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res, "Env fingerprint updated");
|
||||
|
||||
let read_handler = ReadEnvFingerprintHandler;
|
||||
let res2 = read_handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("rustc"));
|
||||
assert!(res2.contains("1.70.0"));
|
||||
}
|
||||
@@ -212,8 +211,7 @@ mod tests {
|
||||
let handler = GetEnvironmentDetailsHandler;
|
||||
let res = handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
assert!(res.contains("global"));
|
||||
}
|
||||
@@ -232,7 +230,7 @@ mod tests {
|
||||
"context": "For database access",
|
||||
"namespace": "global"
|
||||
});
|
||||
let res1 = req_handler.execute(args_req, state.clone()).await.unwrap();
|
||||
let res1 = req_handler.execute(args_req, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res1, "Env requirement logged");
|
||||
|
||||
let reg_handler = RegisterEnvironmentHandler;
|
||||
@@ -243,14 +241,13 @@ mod tests {
|
||||
"requires_vpn": true,
|
||||
"namespace": "global"
|
||||
});
|
||||
let res2 = reg_handler.execute(args_reg, state.clone()).await.unwrap();
|
||||
let res2 = reg_handler.execute(args_reg, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res2, "Environment registered");
|
||||
|
||||
let get_handler = GetEnvironmentDetailsHandler;
|
||||
let res3 = get_handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res3.contains("prod.local"));
|
||||
assert!(!res3.is_empty());
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ impl McpTool for GetActiveWorktreeContextHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<Value, String> {
|
||||
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<serde_json::Value> {
|
||||
let cwd = env::current_dir().map_err(|e| e.to_string())?;
|
||||
let repo = git2::Repository::discover(&cwd).map_err(|e| format!("Not in a git repository: {}", e))?;
|
||||
let repo = git2::Repository::discover(&cwd).map_err(|e| crate::error::AppError::Internal(format!("Not in a git repository: {}", e)))?;
|
||||
|
||||
let mut branch_name = String::new();
|
||||
if let Ok(head) = repo.head()
|
||||
@@ -70,8 +70,8 @@ impl McpTool for GetActiveWorktreeContextHandler {
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(serde_json::to_string_pretty(&result).unwrap())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&result)?)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ impl McpTool for QueryGraphPathHandler {
|
||||
crate::mcp::tool_def::<QueryGraphPathTool>("query_graph_path", "Execute query_graph_path")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: crate::tools::QueryGraphPathTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.read_graph(|graph| {
|
||||
@@ -115,7 +115,7 @@ impl McpTool for CreateEntitiesHandler {
|
||||
crate::mcp::tool_def::<CreateEntitiesTool>("create_entities", "Execute create_entities")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: CreateEntitiesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut inserted = Vec::new();
|
||||
state.modify_graph(|g| {
|
||||
@@ -146,7 +146,7 @@ impl McpTool for CreateRelationsHandler {
|
||||
crate::mcp::tool_def::<CreateRelationsTool>("create_relations", "Execute create_relations")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: CreateRelationsTool = match serde_json::from_value(args.clone()) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
@@ -155,12 +155,12 @@ impl McpTool for CreateRelationsHandler {
|
||||
|| err_msg.contains("missing field `to`")
|
||||
|| err_msg.contains("missing field `relation_type`")
|
||||
{
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Schema error: {}. Note that the relation schema strictly uses 'from', 'to', and 'relation_type' (not 'source', 'target', or 'relationType'). Please correct your tool call arguments.",
|
||||
err_msg
|
||||
));
|
||||
)));
|
||||
}
|
||||
return Err(err_msg);
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
};
|
||||
let mut missing_nodes = std::collections::HashSet::new();
|
||||
@@ -184,10 +184,9 @@ impl McpTool for CreateRelationsHandler {
|
||||
});
|
||||
if !missing_nodes.is_empty() {
|
||||
let missing: Vec<_> = missing_nodes.into_iter().collect();
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Relations dropped due to missing entities: {}",
|
||||
missing.join(", ")
|
||||
));
|
||||
missing.join(", "))));
|
||||
}
|
||||
Ok("Relations created".to_string())
|
||||
}
|
||||
@@ -205,7 +204,7 @@ impl McpTool for AddObservationsHandler {
|
||||
crate::mcp::tool_def::<AddObservationsTool>("add_observations", "Execute add_observations")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddObservationsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut missing_entities = Vec::new();
|
||||
state.modify_graph(|g| {
|
||||
@@ -218,10 +217,9 @@ impl McpTool for AddObservationsHandler {
|
||||
}
|
||||
});
|
||||
if !missing_entities.is_empty() {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Observations dropped for missing entities: {}",
|
||||
missing_entities.join(", ")
|
||||
));
|
||||
missing_entities.join(", "))));
|
||||
}
|
||||
Ok("Observations added".to_string())
|
||||
}
|
||||
@@ -239,7 +237,7 @@ impl McpTool for DeleteEntitiesHandler {
|
||||
crate::mcp::tool_def::<DeleteEntitiesTool>("delete_entities", "Execute delete_entities")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteEntitiesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let to_delete: std::collections::HashSet<_> = req.entity_names.into_iter().collect();
|
||||
let mut missing = Vec::new();
|
||||
@@ -255,10 +253,9 @@ impl McpTool for DeleteEntitiesHandler {
|
||||
});
|
||||
|
||||
if !missing.is_empty() {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Entities not found: {}. Please use the search_nodes or read_graph tools to verify the exact entity names.",
|
||||
missing.join(", ")
|
||||
));
|
||||
missing.join(", "))));
|
||||
}
|
||||
|
||||
let idx = state.get_search_index();
|
||||
@@ -284,7 +281,7 @@ impl McpTool for DeleteObservationsHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteObservationsTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut missing = Vec::new();
|
||||
@@ -299,10 +296,9 @@ impl McpTool for DeleteObservationsHandler {
|
||||
}
|
||||
});
|
||||
if !missing.is_empty() {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Entities not found: {}. Please use the search_nodes or read_graph tools to verify the exact entity names.",
|
||||
missing.join(", ")
|
||||
));
|
||||
missing.join(", "))));
|
||||
}
|
||||
Ok("Observations deleted".to_string())
|
||||
}
|
||||
@@ -320,7 +316,7 @@ impl McpTool for DeleteRelationsHandler {
|
||||
crate::mcp::tool_def::<DeleteRelationsTool>("delete_relations", "Execute delete_relations")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteRelationsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut missing_count = 0;
|
||||
state.modify_graph(|master| {
|
||||
@@ -330,10 +326,10 @@ impl McpTool for DeleteRelationsHandler {
|
||||
missing_count = to_rem.len() - (initial_len - master.relations.len());
|
||||
});
|
||||
if missing_count > 0 {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: {} relation(s) not found in graph. Please verify exact relation properties using read_graph.",
|
||||
missing_count
|
||||
));
|
||||
)));
|
||||
}
|
||||
Ok("Relations deleted".to_string())
|
||||
}
|
||||
@@ -351,9 +347,9 @@ impl McpTool for ReadGraphHandler {
|
||||
crate::mcp::tool_def::<ReadGraphTool>("read_graph", "Execute read_graph")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ReadGraphTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.read_graph(|full| -> Result<String, String> {
|
||||
let data = state.read_graph(|full| -> crate::error::Result<String> {
|
||||
if let Some(ns) = req.namespace {
|
||||
let mut filtered = BorrowedGraph::default();
|
||||
for (k, v) in &full.entities {
|
||||
@@ -366,9 +362,9 @@ impl McpTool for ReadGraphHandler {
|
||||
filtered.relations.push(r);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
} else {
|
||||
serde_json::to_string(full).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(full)?)
|
||||
}
|
||||
})?;
|
||||
Ok(data)
|
||||
@@ -387,7 +383,7 @@ impl McpTool for SearchNodesHandler {
|
||||
crate::mcp::tool_def::<SearchNodesTool>("search_nodes", "Execute search_nodes")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: SearchNodesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let matches = if let Ok(idx) = state.search_index.read() {
|
||||
idx.search(&req.query, req.namespace.as_deref())
|
||||
@@ -396,7 +392,7 @@ impl McpTool for SearchNodesHandler {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let data = state.read_graph(|full| -> Result<String, String> {
|
||||
let data = state.read_graph(|full| -> crate::error::Result<String> {
|
||||
let mut result = BorrowedGraph::default();
|
||||
for (id, doc_type, _, _, _) in &matches {
|
||||
if doc_type == "entity"
|
||||
@@ -405,7 +401,7 @@ impl McpTool for SearchNodesHandler {
|
||||
result.entities.insert(id, e);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&result).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&result)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -423,9 +419,9 @@ impl McpTool for OpenNodesHandler {
|
||||
crate::mcp::tool_def::<OpenNodesTool>("open_nodes", "Execute open_nodes")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: OpenNodesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.read_graph(|full| -> Result<String, String> {
|
||||
let data = state.read_graph(|full| -> crate::error::Result<String> {
|
||||
let targets: HashSet<&str> = req.names.iter().map(|s| s.as_str()).collect();
|
||||
let mut result = BorrowedGraph::default();
|
||||
let mut connected: HashSet<&str> = HashSet::new();
|
||||
@@ -443,7 +439,7 @@ impl McpTool for OpenNodesHandler {
|
||||
result.entities.insert(name, e);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&result).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&result)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -461,7 +457,7 @@ impl McpTool for VisualizeGraphHandler {
|
||||
crate::mcp::tool_def::<VisualizeGraphTool>("visualize_graph", "Execute visualize_graph")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: VisualizeGraphTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let query = req.query.unwrap_or_default();
|
||||
let mut output = state.read_graph(|full| {
|
||||
@@ -550,7 +546,7 @@ impl McpTool for CondenseEntityHandler {
|
||||
crate::mcp::tool_def::<CondenseEntityTool>("condense_entity", "Execute condense_entity")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: CondenseEntityTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut missing = false;
|
||||
state.modify_graph(|master| {
|
||||
@@ -561,10 +557,10 @@ impl McpTool for CondenseEntityHandler {
|
||||
}
|
||||
});
|
||||
if missing {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Entity '{}' not found. Please verify the exact entity name using search_nodes.",
|
||||
req.entity_name
|
||||
));
|
||||
)));
|
||||
}
|
||||
Ok("Entity condensed".to_string())
|
||||
}
|
||||
@@ -582,7 +578,7 @@ impl McpTool for MergeEntitiesHandler {
|
||||
crate::mcp::tool_def::<MergeEntitiesTool>("merge_entities", "Execute merge_entities")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: MergeEntitiesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut missing = false;
|
||||
state.modify_graph(|master| {
|
||||
@@ -616,10 +612,10 @@ impl McpTool for MergeEntitiesHandler {
|
||||
});
|
||||
});
|
||||
if missing {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Source entity '{}' not found. Please verify the exact entity name using search_nodes.",
|
||||
req.source_entity
|
||||
));
|
||||
)));
|
||||
}
|
||||
Ok("Entities merged".to_string())
|
||||
}
|
||||
@@ -637,7 +633,7 @@ impl McpTool for FindOrphansHandler {
|
||||
crate::mcp::tool_def::<FindOrphansTool>("find_orphans", "Execute find_orphans")
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let orphans = state.read_graph(|full| {
|
||||
let mut connected = std::collections::HashSet::new();
|
||||
for r in &full.relations {
|
||||
@@ -650,7 +646,7 @@ impl McpTool for FindOrphansHandler {
|
||||
.cloned()
|
||||
.collect::<Vec<String>>()
|
||||
});
|
||||
let data = serde_json::to_string(&orphans).map_err(|e| e.to_string())?;
|
||||
let data = Ok::<String, crate::error::AppError>(serde_json::to_string(&orphans)?)?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -675,7 +671,7 @@ mod tests {
|
||||
]
|
||||
});
|
||||
|
||||
let res = create_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = create_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res, "Entities created");
|
||||
|
||||
// Ensure graph contains the entity
|
||||
@@ -720,7 +716,7 @@ mod tests {
|
||||
{"from": "A", "to": "B", "relation_type": "knows"}
|
||||
]
|
||||
});
|
||||
let res = handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res, "Relations created");
|
||||
|
||||
// Test semantic LLM schema feedback (User request)
|
||||
@@ -759,28 +755,25 @@ mod tests {
|
||||
{"entity_name": "A", "contents": ["Obs 1", "Obs 2"]}
|
||||
]
|
||||
});
|
||||
let res1 = add_obs.execute(args_obs, state.clone()).await.unwrap();
|
||||
let res1 = add_obs.execute(args_obs, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res1, "Observations added");
|
||||
|
||||
let read_graph = ReadGraphHandler;
|
||||
let res2 = read_graph
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("Obs 1"));
|
||||
assert!(res2.contains("Obs 2"));
|
||||
|
||||
let del_entity = DeleteEntitiesHandler;
|
||||
let res4 = del_entity
|
||||
.execute(json!({"entity_names": ["A"]}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res4, "Entities deleted");
|
||||
|
||||
let res5 = read_graph
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res5.contains("A"));
|
||||
}
|
||||
|
||||
@@ -798,8 +791,7 @@ mod tests {
|
||||
});
|
||||
create_handler
|
||||
.execute(args_ent, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
let rel_handler = CreateRelationsHandler;
|
||||
let args_rel = json!({
|
||||
@@ -807,28 +799,25 @@ mod tests {
|
||||
{"from": "X", "to": "Y", "relation_type": "depends_on", "namespace": "global"}
|
||||
]
|
||||
});
|
||||
rel_handler.execute(args_rel, state.clone()).await.unwrap();
|
||||
rel_handler.execute(args_rel, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
let read_handler = ReadGraphHandler;
|
||||
let res_read = read_handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res_read.contains("X"));
|
||||
assert!(res_read.contains("depends_on"));
|
||||
|
||||
let open_handler = OpenNodesHandler;
|
||||
let res_open = open_handler
|
||||
.execute(json!({"names": ["X"]}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res_open.contains("Y"));
|
||||
|
||||
let viz_handler = VisualizeGraphHandler;
|
||||
let res_viz = viz_handler
|
||||
.execute(json!({"query": "X"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res_viz.is_empty());
|
||||
|
||||
let condense = CondenseEntityHandler;
|
||||
@@ -837,8 +826,7 @@ mod tests {
|
||||
json!({"entity_name": "X", "summarized_observations": ["X condensed"]}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res_cond, "Entity condensed");
|
||||
|
||||
let merge = MergeEntitiesHandler;
|
||||
@@ -847,12 +835,11 @@ mod tests {
|
||||
json!({"source_entity": "X", "target_entity": "Y"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res_merge, "Entities merged");
|
||||
|
||||
let orphans = FindOrphansHandler;
|
||||
let res_orphans = orphans.execute(json!({}), state.clone()).await.unwrap();
|
||||
let res_orphans = orphans.execute(json!({}), state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res_orphans.contains("Y"));
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,10 @@ impl McpTool for WatchProcessLogsHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: WatchProcessLogsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
if !std::path::Path::new(&tool_args.file_path).exists() {
|
||||
return Err(format!("File does not exist: {}", tool_args.file_path));
|
||||
return Err(crate::error::AppError::Internal(format!("File does not exist: {}", tool_args.file_path)));
|
||||
}
|
||||
Ok(format!("Started watching logs for {}", tool_args.file_path))
|
||||
}
|
||||
@@ -46,11 +46,11 @@ impl McpTool for GetRecentLogsHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: GetRecentLogsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
|
||||
let mut file = File::open(&tool_args.file_path).map_err(|e| format!("Failed to open file: {}", e))?;
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let mut file = File::open(&tool_args.file_path).map_err(|e| crate::error::AppError::Internal(format!("Failed to open file: {}", e)))?;
|
||||
let len = file.metadata().map_err(|e| e.to_string())?.len();
|
||||
|
||||
let read_size = std::cmp::min(16 * 1024, len);
|
||||
@@ -69,7 +69,7 @@ impl McpTool for GetRecentLogsHandler {
|
||||
Ok(recent_lines)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
+36
-49
@@ -18,7 +18,7 @@ impl McpTool for LogDecisionHandler {
|
||||
crate::mcp::tool_def::<LogDecisionTool>("log_decision", "Execute log_decision")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LogDecisionTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let idx = state.get_search_index();
|
||||
@@ -68,7 +68,7 @@ impl McpTool for QueryDecisionsHandler {
|
||||
crate::mcp::tool_def::<QueryDecisionsTool>("query_decisions", "Execute query_decisions")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: QueryDecisionsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.adrs.read_with(|adrs| {
|
||||
let filtered: Vec<_> = adrs
|
||||
@@ -83,7 +83,7 @@ impl McpTool for QueryDecisionsHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -104,7 +104,7 @@ impl McpTool for DeleteDecisionHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: crate::tools::DeleteDecisionTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut found = false;
|
||||
@@ -119,7 +119,7 @@ impl McpTool for DeleteDecisionHandler {
|
||||
state.rebuild_index().await;
|
||||
Ok("Decision deleted successfully".to_string())
|
||||
} else {
|
||||
Err("Decision not found".to_string())
|
||||
Err(crate::error::AppError::Internal("Decision not found".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ impl McpTool for LogErrorFixHandler {
|
||||
crate::mcp::tool_def::<LogErrorFixTool>("log_error_fix", "Execute log_error_fix")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LogErrorFixTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let text_to_embed = format!("Signature: {}\nSolution: {}", req.signature, req.solution);
|
||||
let embedding = crate::embedding::generate_embedding_async(text_to_embed).await.ok();
|
||||
@@ -169,7 +169,7 @@ impl McpTool for SearchErrorFixesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: SearchErrorFixesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let q = req.query;
|
||||
let data = state.error_fixes.read_with(|fixes| {
|
||||
@@ -180,7 +180,7 @@ impl McpTool for SearchErrorFixesHandler {
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ impl McpTool for LogCodeChangeHandler {
|
||||
crate::mcp::tool_def::<LogCodeChangeTool>("log_code_change", "Execute log_code_change")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LogCodeChangeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.ledger.modify(|ledger| {
|
||||
ledger.push(CodeChange {
|
||||
@@ -228,10 +228,10 @@ impl McpTool for QueryRecentChangesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let data = state
|
||||
.ledger
|
||||
.read_with(|l| serde_json::to_string(l).map_err(|e| e.to_string()))?;
|
||||
.read_with(|l| Ok::<String, crate::error::AppError>(serde_json::to_string(l)?))?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ impl McpTool for LearnPreferenceHandler {
|
||||
crate::mcp::tool_def::<LearnPreferenceTool>("learn_preference", "Execute learn_preference")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LearnPreferenceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.prefs.modify(|prefs| {
|
||||
prefs.insert(
|
||||
@@ -276,10 +276,10 @@ impl McpTool for ReadPreferencesHandler {
|
||||
crate::mcp::tool_def::<ReadPreferencesTool>("read_preferences", "Execute read_preferences")
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
state
|
||||
.prefs
|
||||
.read_with(|prefs| serde_json::to_string(prefs).map_err(|e| e.to_string()))
|
||||
.read_with(|prefs| Ok::<String, crate::error::AppError>(serde_json::to_string(prefs)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ impl McpTool for LogTechDebtHandler {
|
||||
crate::mcp::tool_def::<LogTechDebtTool>("log_tech_debt", "Execute log_tech_debt")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LogTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let text_to_embed = format!("Description: {}\nIdeal Solution: {}", req.description, req.ideal_solution);
|
||||
let embedding = crate::embedding::generate_embedding_async(text_to_embed).await.ok();
|
||||
@@ -331,7 +331,7 @@ impl McpTool for ResolveTechDebtHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ResolveTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut found = false;
|
||||
state.tech_debts.modify(|debts| {
|
||||
@@ -346,10 +346,7 @@ impl McpTool for ResolveTechDebtHandler {
|
||||
if found {
|
||||
Ok("Tech debt resolved".to_string())
|
||||
} else {
|
||||
Err(
|
||||
"Tech debt not found. Please verify the tech debt ID using list_tech_debt."
|
||||
.to_string(),
|
||||
)
|
||||
Err(crate::error::AppError::Internal("Tech debt not found. Please verify the tech debt ID using list_tech_debt.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +363,7 @@ impl McpTool for ListTechDebtHandler {
|
||||
crate::mcp::tool_def::<ListTechDebtTool>("list_tech_debt", "Execute list_tech_debt")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ListTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.tech_debts.read_with(|debts| {
|
||||
let filtered: Vec<_> = debts
|
||||
@@ -375,7 +372,7 @@ impl McpTool for ListTechDebtHandler {
|
||||
d.namespace == req.namespace && (req.include_resolved || !d.is_resolved)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -393,7 +390,7 @@ impl McpTool for OmniSearchHandler {
|
||||
crate::mcp::tool_def::<OmniSearchTool>("omni_search", "Execute omni_search")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: OmniSearchTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let limit = req.limit.unwrap_or(5);
|
||||
let include_body = req.include_body.unwrap_or(false);
|
||||
@@ -403,10 +400,10 @@ impl McpTool for OmniSearchHandler {
|
||||
{
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"Search query failed (possibly malformed Lucene syntax). Error: {}",
|
||||
e
|
||||
));
|
||||
)));
|
||||
}
|
||||
};
|
||||
// tracing::info!("OMNI SEARCH MATCHES: {:?}", matches);
|
||||
@@ -614,7 +611,7 @@ impl McpTool for GetProjectHealthHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: GetProjectHealthTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let active_tasks = state
|
||||
.tasks
|
||||
@@ -677,7 +674,7 @@ mod tests {
|
||||
"git_branch": "main"
|
||||
});
|
||||
|
||||
let res = handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res.contains("Error fix logged"));
|
||||
}
|
||||
|
||||
@@ -689,7 +686,7 @@ mod tests {
|
||||
let handler = GetProjectHealthHandler;
|
||||
let args = json!({"namespace": "global"});
|
||||
|
||||
let res = handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res.contains("unresolved_tech_debt"));
|
||||
}
|
||||
|
||||
@@ -707,8 +704,7 @@ mod tests {
|
||||
});
|
||||
let res1 = decision_handler
|
||||
.execute(args_dec, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res1, "Decision logged as ADR-0001");
|
||||
|
||||
let debt_handler = LogTechDebtHandler;
|
||||
@@ -724,8 +720,7 @@ mod tests {
|
||||
});
|
||||
let res2 = debt_handler
|
||||
.execute(args_debt, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res2, "Tech debt logged");
|
||||
|
||||
let list_debt = ListTechDebtHandler;
|
||||
@@ -734,8 +729,7 @@ mod tests {
|
||||
json!({"namespace": "global", "include_resolved": false}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res3.contains("Hardcoded path"));
|
||||
|
||||
let pref_handler = LearnPreferenceHandler;
|
||||
@@ -745,12 +739,11 @@ mod tests {
|
||||
});
|
||||
let res4 = pref_handler
|
||||
.execute(args_pref, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res4, "Preference learned");
|
||||
|
||||
let read_pref = ReadPreferencesHandler;
|
||||
let res5 = read_pref.execute(json!({}), state.clone()).await.unwrap();
|
||||
let res5 = read_pref.execute(json!({}), state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res5.contains("use spaces"));
|
||||
}
|
||||
|
||||
@@ -768,14 +761,12 @@ mod tests {
|
||||
});
|
||||
code_handler
|
||||
.execute(args_code, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
let query_changes = QueryRecentChangesHandler;
|
||||
let res_changes = query_changes
|
||||
.execute(json!({}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res_changes.contains("main.rs"));
|
||||
|
||||
let debt_handler = LogTechDebtHandler;
|
||||
@@ -791,8 +782,7 @@ mod tests {
|
||||
});
|
||||
debt_handler
|
||||
.execute(args_debt, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
// resolve it
|
||||
let list_debt = ListTechDebtHandler;
|
||||
@@ -801,16 +791,14 @@ mod tests {
|
||||
json!({"namespace": "global", "include_resolved": false}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let uuid_start = debt_list.find("id\":\"").unwrap() + 5;
|
||||
let uuid = &debt_list[uuid_start..uuid_start + 36];
|
||||
|
||||
let resolve_debt = ResolveTechDebtHandler;
|
||||
resolve_debt
|
||||
.execute(json!({"id": uuid}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -844,8 +832,7 @@ mod tests {
|
||||
let omni = OmniSearchHandler;
|
||||
let omni_res = omni
|
||||
.execute(json!({"query": "Omni"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
// tracing::info!("OMNI RES: {}", omni_res);
|
||||
assert!(
|
||||
omni_res.contains("omni-1"),
|
||||
|
||||
@@ -19,7 +19,7 @@ impl McpTool for AddStickyNoteHandler {
|
||||
crate::mcp::tool_def::<AddStickyNoteTool>("add_sticky_note", "Execute add_sticky_note")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.sticky.modify(|notes| {
|
||||
notes.push(StickyNote {
|
||||
@@ -47,10 +47,10 @@ impl McpTool for ReadStickyNotesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let data = state
|
||||
.sticky
|
||||
.read_with(|s| serde_json::to_string(s).map_err(|e| e.to_string()))?;
|
||||
.read_with(|s| Ok::<String, crate::error::AppError>(serde_json::to_string(s)?))?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl McpTool for DeleteStickyNoteHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut success = false;
|
||||
state.sticky.modify(|notes| {
|
||||
@@ -82,7 +82,7 @@ impl McpTool for DeleteStickyNoteHandler {
|
||||
if success {
|
||||
Ok("Sticky note deleted.".to_string())
|
||||
} else {
|
||||
Err("Invalid sticky note index.".to_string())
|
||||
Err(crate::error::AppError::Internal("Invalid sticky note index.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ impl McpTool for ClearStickyNotesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
state.sticky.modify(|notes| {
|
||||
notes.clear();
|
||||
});
|
||||
@@ -125,7 +125,7 @@ impl McpTool for LeaveHandoffMemoHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LeaveHandoffMemoTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.handoff_memos.modify(|memos| {
|
||||
memos.push(crate::models::HandoffMemo {
|
||||
@@ -156,7 +156,7 @@ impl McpTool for ReadHandoffMemosHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ReadHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.handoff_memos.read_with(|items| {
|
||||
let filtered: Vec<_> = items
|
||||
@@ -169,7 +169,7 @@ impl McpTool for ReadHandoffMemosHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -190,7 +190,7 @@ impl McpTool for ClearHandoffMemosHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ClearHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ids: HashSet<_> = req.ids.into_iter().collect();
|
||||
state
|
||||
@@ -215,7 +215,7 @@ impl McpTool for AddSessionSummaryHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddSessionSummaryTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.session_summaries.modify(|summaries| {
|
||||
summaries.push(crate::models::SessionSummary {
|
||||
@@ -244,7 +244,7 @@ impl McpTool for GenerateStandupReportHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: GenerateStandupReportTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let cutoff = crate::handlers::utils::now_secs().saturating_sub(req.hours_lookback * 3600);
|
||||
@@ -280,25 +280,23 @@ mod tests {
|
||||
"content": "Buy milk",
|
||||
});
|
||||
|
||||
let res = add_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = add_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res.contains("Sticky note added"));
|
||||
|
||||
let read_handler = ReadStickyNotesHandler;
|
||||
let res2 = read_handler
|
||||
.execute(json!({}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("Buy milk"));
|
||||
|
||||
let delete_handler = DeleteStickyNoteHandler;
|
||||
let args2 = json!({"index": 1});
|
||||
let res3 = delete_handler.execute(args2, state.clone()).await.unwrap();
|
||||
let res3 = delete_handler.execute(args2, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res3, "Sticky note deleted.");
|
||||
|
||||
let res4 = read_handler
|
||||
.execute(json!({}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res4.contains("Buy milk"));
|
||||
}
|
||||
|
||||
@@ -314,14 +312,13 @@ mod tests {
|
||||
"namespace": "global"
|
||||
});
|
||||
|
||||
let res = handoff_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = handoff_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res, "Handoff memo left");
|
||||
|
||||
let read_handoff = ReadHandoffMemosHandler;
|
||||
let res2 = read_handoff
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("Finished implementing graph tests"));
|
||||
|
||||
let summary_handler = AddSessionSummaryHandler;
|
||||
@@ -331,8 +328,7 @@ mod tests {
|
||||
});
|
||||
let res3 = summary_handler
|
||||
.execute(args_sum, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res3, "Session summary added");
|
||||
|
||||
let standup_handler = GenerateStandupReportHandler;
|
||||
@@ -341,8 +337,7 @@ mod tests {
|
||||
json!({"namespace": "global", "hours_lookback": 24}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res4.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ impl McpTool for AddTaskHandler {
|
||||
crate::mcp::tool_def::<AddTaskTool>("add_task", "Execute add_task")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddTaskTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let now = crate::handlers::utils::now_secs();
|
||||
let task_id = uuid::Uuid::new_v4().to_string();
|
||||
@@ -59,7 +59,7 @@ impl McpTool for DeleteTaskHandler {
|
||||
crate::mcp::tool_def::<DeleteTaskTool>("delete_task", "Execute delete_task")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteTaskTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut deleted_count = 0;
|
||||
let mut actually_deleted = Vec::new();
|
||||
@@ -114,7 +114,7 @@ impl McpTool for DeleteTaskHandler {
|
||||
][0]
|
||||
.clone())
|
||||
} else {
|
||||
Err("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string())
|
||||
Err(crate::error::AppError::Internal("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ impl McpTool for UpdateTaskStatusHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: UpdateTaskStatusTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut found = false;
|
||||
let mut blocked = false;
|
||||
@@ -250,14 +250,14 @@ impl McpTool for UpdateTaskStatusHandler {
|
||||
});
|
||||
|
||||
if blocked {
|
||||
Err(format!(
|
||||
Err(crate::error::AppError::Internal(format!(
|
||||
"Error: Cannot transition task. {}",
|
||||
blocker_details
|
||||
))
|
||||
)))
|
||||
} else if found {
|
||||
Ok("Task status updated.".to_string())
|
||||
} else {
|
||||
Err("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string())
|
||||
Err(crate::error::AppError::Internal("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ impl McpTool for ListActiveTasksHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ListActiveTasksTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.tasks.read_with(|tasks| {
|
||||
let filtered: Vec<_> = tasks
|
||||
@@ -294,7 +294,7 @@ impl McpTool for ListActiveTasksHandler {
|
||||
status_match && branch_match
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -315,7 +315,7 @@ impl McpTool for SetAcceptanceCriteriaHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: SetAcceptanceCriteriaTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut success = false;
|
||||
@@ -337,7 +337,7 @@ impl McpTool for SetAcceptanceCriteriaHandler {
|
||||
if success {
|
||||
Ok("Acceptance criteria set successfully.".to_string())
|
||||
} else {
|
||||
Err("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string())
|
||||
Err(crate::error::AppError::Internal("Task not found. Please use the list_active_tasks tool to verify the correct task ID.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,7 @@ impl McpTool for VerifyAcceptanceCriteriaHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: VerifyAcceptanceCriteriaTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut success = false;
|
||||
@@ -386,7 +386,7 @@ impl McpTool for VerifyAcceptanceCriteriaHandler {
|
||||
} else if already_met {
|
||||
Ok("Acceptance criteria was already met.".to_string())
|
||||
} else {
|
||||
Err("Acceptance criteria or task not found.".to_string())
|
||||
Err(crate::error::AppError::Internal("Acceptance criteria or task not found.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,7 +403,7 @@ impl McpTool for AddMilestoneHandler {
|
||||
crate::mcp::tool_def::<AddMilestoneTool>("add_milestone", "Execute add_milestone")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddMilestoneTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.milestones.modify(|ms| {
|
||||
ms.push(crate::models::Milestone {
|
||||
@@ -430,7 +430,7 @@ impl McpTool for UpdateMilestoneHandler {
|
||||
crate::mcp::tool_def::<UpdateMilestoneTool>("update_milestone", "Execute update_milestone")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: UpdateMilestoneTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut found = false;
|
||||
state.milestones.modify(|ms| {
|
||||
@@ -445,10 +445,7 @@ impl McpTool for UpdateMilestoneHandler {
|
||||
if found {
|
||||
Ok("Milestone updated".to_string())
|
||||
} else {
|
||||
Err(
|
||||
"Milestone not found. Please verify the milestone ID using list_milestones."
|
||||
.to_string(),
|
||||
)
|
||||
Err(crate::error::AppError::Internal("Milestone not found. Please verify the milestone ID using list_milestones.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +462,7 @@ impl McpTool for ListMilestonesHandler {
|
||||
crate::mcp::tool_def::<ListMilestonesTool>("list_milestones", "Execute list_milestones")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ListMilestonesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.milestones.read_with(|items| {
|
||||
let filtered: Vec<_> = items
|
||||
@@ -478,7 +475,7 @@ impl McpTool for ListMilestonesHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -502,14 +499,13 @@ mod tests {
|
||||
"acceptance_criteria": ["Stop the noise", "Reach lightspeed"],
|
||||
});
|
||||
|
||||
let res = add_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = add_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res.contains("Task added with ID:"));
|
||||
|
||||
let list_handler = ListActiveTasksHandler;
|
||||
let res2 = list_handler
|
||||
.execute(json!({}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("Fix the hyperdrive"));
|
||||
}
|
||||
|
||||
@@ -524,8 +520,7 @@ mod tests {
|
||||
json!({"title": "Test", "description": "test"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
let id_start = res.find("ID: ").unwrap() + 4;
|
||||
let task_id = res[id_start..].trim();
|
||||
@@ -535,14 +530,13 @@ mod tests {
|
||||
"id": task_id,
|
||||
"status": "done"
|
||||
});
|
||||
let res3 = update_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res3 = update_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res3, "Task status updated.");
|
||||
|
||||
let list_handler = ListActiveTasksHandler;
|
||||
let res4 = list_handler
|
||||
.execute(json!({}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(!res4.contains(task_id));
|
||||
}
|
||||
|
||||
@@ -561,7 +555,7 @@ mod tests {
|
||||
"end_date": 1700000000,
|
||||
"namespace": "global"
|
||||
});
|
||||
let res1 = add_milestone.execute(args_ms, state.clone()).await.unwrap();
|
||||
let res1 = add_milestone.execute(args_ms, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res1.contains("Milestone added"));
|
||||
|
||||
// Fetch milestone ID from state directly to update
|
||||
@@ -573,15 +567,14 @@ mod tests {
|
||||
"id": ms_id,
|
||||
"status": "completed"
|
||||
});
|
||||
let res2 = update_ms.execute(args_ums, state.clone()).await.unwrap();
|
||||
let res2 = update_ms.execute(args_ums, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res2, "Milestone updated");
|
||||
|
||||
// List Milestones
|
||||
let list_ms = ListMilestonesHandler;
|
||||
let res3 = list_ms
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res3.contains("completed"));
|
||||
assert!(res3.contains("Release 1.0"));
|
||||
|
||||
@@ -592,8 +585,7 @@ mod tests {
|
||||
json!({"title": "Test", "description": "desc"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let task_id = res_task[res_task.find("ID: ").unwrap() + 4..].trim();
|
||||
|
||||
let set_ac = SetAcceptanceCriteriaHandler;
|
||||
@@ -602,7 +594,7 @@ mod tests {
|
||||
"task_title": "Test",
|
||||
"criteria": ["Do X", "Do Y"]
|
||||
});
|
||||
let res4 = set_ac.execute(args_ac, state.clone()).await.unwrap();
|
||||
let res4 = set_ac.execute(args_ac, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res4, "Acceptance criteria set successfully.");
|
||||
|
||||
let verify_ac = VerifyAcceptanceCriteriaHandler;
|
||||
@@ -611,7 +603,7 @@ mod tests {
|
||||
"criteria": "Do X",
|
||||
"proof": "I did X"
|
||||
});
|
||||
let res5 = verify_ac.execute(args_vac, state.clone()).await.unwrap();
|
||||
let res5 = verify_ac.execute(args_vac, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res5.contains("Acceptance criteria verified"));
|
||||
}
|
||||
|
||||
@@ -626,8 +618,7 @@ mod tests {
|
||||
json!({"title": "Parent", "description": "p"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let parent_id = parent[parent.find("ID: ").unwrap() + 4..]
|
||||
.trim()
|
||||
.to_string();
|
||||
@@ -637,15 +628,13 @@ mod tests {
|
||||
json!({"title": "Child", "description": "c", "parent_id": parent_id}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let _child_id = child[child.find("ID: ").unwrap() + 4..].trim().to_string();
|
||||
|
||||
let del_task = DeleteTaskHandler;
|
||||
let res_del = del_task
|
||||
.execute(json!({"id": parent_id}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res_del.contains("Deleted task and its children (2 total)."));
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ impl McpTool for WriteClipboardHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: WriteClipboardTool =
|
||||
serde_json::from_value(args).map_err(|e| format!("Invalid args: {}", e))?;
|
||||
serde_json::from_value(args).map_err(|e| crate::error::AppError::Internal(format!("Invalid args: {}", e)))?;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut msgs = Vec::new();
|
||||
@@ -63,11 +63,11 @@ impl McpTool for WriteClipboardHandler {
|
||||
if clipboard.set_image(img_data).is_ok() {
|
||||
msgs.push("Wrote image");
|
||||
} else {
|
||||
return Err("Failed to write image to clipboard".to_string());
|
||||
return Err(crate::error::AppError::Internal("Failed to write image to clipboard".to_string()));
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(format!("Failed to open image {}: {}", image_path, e)),
|
||||
Err(e) => return Err(crate::error::AppError::Internal(format!("Failed to open image {}: {}", image_path, e))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ impl McpTool for WriteClipboardHandler {
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))?
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ impl McpTool for ReadClipboardHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<Value, String> {
|
||||
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<serde_json::Value> {
|
||||
let mut out = serde_json::Map::new();
|
||||
|
||||
if let Ok(_clip) = Clipboard::new_attempts(3) {
|
||||
@@ -149,9 +149,9 @@ impl McpTool for ReadClipboardHandler {
|
||||
Ok(Value::Object(out))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(serde_json::to_string_pretty(&result).unwrap())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&result)?)
|
||||
}
|
||||
}
|
||||
use crate::tools::ToggleClipboardWatchModeTool;
|
||||
@@ -171,7 +171,7 @@ impl McpTool for ToggleClipboardWatchModeHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: ToggleClipboardWatchModeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut watch_mode = state.clipboard_watch_mode.write().await;
|
||||
|
||||
@@ -18,7 +18,7 @@ impl McpTool for PinFileHandler {
|
||||
crate::mcp::tool_def::<PinFileTool>("pin_file", "Execute pin_file")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: PinFileTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.pinned_files.modify(|pinned| {
|
||||
pinned.retain(|p| p.namespace != req.namespace || p.file_path != req.file_path);
|
||||
@@ -45,7 +45,7 @@ impl McpTool for UnpinFileHandler {
|
||||
crate::mcp::tool_def::<UnpinFileTool>("unpin_file", "Execute unpin_file")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: UnpinFileTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.pinned_files.modify(|pinned| {
|
||||
pinned.retain(|p| p.namespace != req.namespace || p.file_path != req.file_path)
|
||||
@@ -69,7 +69,7 @@ impl McpTool for ListPinnedFilesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ListPinnedFilesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.pinned_files.read_with(|pinned| {
|
||||
let filtered: Vec<_> = pinned
|
||||
@@ -89,7 +89,7 @@ impl McpTool for ListPinnedFilesHandler {
|
||||
ns_match && branch_match
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ impl McpTool for StoreSnippetHandler {
|
||||
crate::mcp::tool_def::<StoreSnippetTool>("store_snippet", "Execute store_snippet")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: StoreSnippetTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let req_name = req.name.clone(); // Keep for the OK message and retain closure
|
||||
let text_to_embed = format!("Name: {}\nLanguage: {}\nDescription: {}\nCode: {}", req.name, req.language, req.description, req.code);
|
||||
@@ -145,7 +145,7 @@ impl McpTool for SearchSnippetsHandler {
|
||||
crate::mcp::tool_def::<SearchSnippetsTool>("search_snippets", "Execute search_snippets")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: SearchSnippetsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let query = req.query;
|
||||
let data = state.snippets.read_with(|snippets| {
|
||||
@@ -157,7 +157,7 @@ impl McpTool for SearchSnippetsHandler {
|
||||
|| contains_ignore_ascii_case(&s.language, &query)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&results).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&results)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ impl McpTool for DeleteSnippetHandler {
|
||||
crate::mcp::tool_def::<DeleteSnippetTool>("delete_snippet", "Execute delete_snippet")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteSnippetTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut deleted = false;
|
||||
state.snippets.modify(|snippets| {
|
||||
@@ -188,10 +188,7 @@ impl McpTool for DeleteSnippetHandler {
|
||||
drop(idx.delete_document(&req.name));
|
||||
Ok("Snippet deleted.".to_string())
|
||||
} else {
|
||||
Err(
|
||||
"Snippet not found. Please verify the snippet ID using search_snippets."
|
||||
.to_string(),
|
||||
)
|
||||
Err(crate::error::AppError::Internal("Snippet not found. Please verify the snippet ID using search_snippets.".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +208,7 @@ impl McpTool for SaveContextWorkspaceHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: SaveContextWorkspaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.context_workspaces.modify(|ws| {
|
||||
@@ -243,7 +240,7 @@ impl McpTool for LoadContextWorkspaceHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: LoadContextWorkspaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.context_workspaces.read_with(|ws| {
|
||||
@@ -272,7 +269,7 @@ impl McpTool for ListContextWorkspacesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ListContextWorkspacesTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.context_workspaces.read_with(|ws| {
|
||||
@@ -280,7 +277,7 @@ impl McpTool for ListContextWorkspacesHandler {
|
||||
.iter()
|
||||
.filter(|w| req.namespace.as_ref().is_none_or(|ns| &w.namespace == ns))
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -301,7 +298,7 @@ impl McpTool for DeleteContextWorkspaceHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: crate::tools::DeleteContextWorkspaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -319,7 +316,7 @@ impl McpTool for DeleteContextWorkspaceHandler {
|
||||
if found {
|
||||
Ok("Context workspace deleted successfully".to_string())
|
||||
} else {
|
||||
Err("Context workspace not found".to_string())
|
||||
Err(crate::error::AppError::Internal("Context workspace not found".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +336,7 @@ impl McpTool for AddPrChecklistItemHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddPrChecklistItemTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.pr_checklists.modify(|items| {
|
||||
@@ -365,14 +362,14 @@ impl McpTool for GetPrChecklistHandler {
|
||||
crate::mcp::tool_def::<GetPrChecklistTool>("get_pr_checklist", "Execute get_pr_checklist")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: GetPrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.pr_checklists.read_with(|items| {
|
||||
let filtered: Vec<_> = items
|
||||
.iter()
|
||||
.filter(|i| i.namespace == req.namespace)
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
@@ -393,7 +390,7 @@ impl McpTool for ClearPrChecklistHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ClearPrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state
|
||||
.pr_checklists
|
||||
@@ -423,14 +420,13 @@ mod tests {
|
||||
"active_task_ids": ["123"]
|
||||
});
|
||||
|
||||
let res = save_handler.execute(args, state.clone()).await.unwrap();
|
||||
let res = save_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res, "Context workspace saved");
|
||||
|
||||
let list_handler = ListContextWorkspacesHandler;
|
||||
let res2 = list_handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res2.contains("wsl-session"));
|
||||
assert!(res2.contains("src/main.rs"));
|
||||
}
|
||||
@@ -450,8 +446,7 @@ mod tests {
|
||||
});
|
||||
let res1 = store_handler
|
||||
.execute(args_snip, state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res1, "Snippet 'init_db' stored.");
|
||||
|
||||
let search_handler = SearchSnippetsHandler;
|
||||
@@ -460,8 +455,7 @@ mod tests {
|
||||
json!({"query": "SELECT", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
// Skip assertion since it requires index rebuild
|
||||
|
||||
let pr_handler = AddPrChecklistItemHandler;
|
||||
@@ -469,14 +463,13 @@ mod tests {
|
||||
"description": "Check coverage",
|
||||
"namespace": "global"
|
||||
});
|
||||
let res3 = pr_handler.execute(args_pr, state.clone()).await.unwrap();
|
||||
let res3 = pr_handler.execute(args_pr, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res3, "PR checklist item added");
|
||||
|
||||
let get_pr = GetPrChecklistHandler;
|
||||
let res4 = get_pr
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res4.contains("Check coverage"));
|
||||
|
||||
// Pin lifecycle
|
||||
@@ -486,15 +479,13 @@ mod tests {
|
||||
json!({"file_path": "src/lib.rs", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res5, "File pinned");
|
||||
|
||||
let list_pins = ListPinnedFilesHandler;
|
||||
let res6 = list_pins
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res6.contains("src/lib.rs"));
|
||||
|
||||
let unpin = UnpinFileHandler;
|
||||
@@ -503,16 +494,14 @@ mod tests {
|
||||
json!({"file_path": "src/lib.rs", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res7, "File unpinned");
|
||||
|
||||
// Clear PR
|
||||
let clear_pr = ClearPrChecklistHandler;
|
||||
let res8 = clear_pr
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res8, "PR checklist cleared");
|
||||
}
|
||||
}
|
||||
@@ -534,11 +523,11 @@ impl McpTool for ReadDirectoryArchitectureHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: ReadDirectoryArchitectureTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let dir = tool_args.directory.clone();
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
|
||||
let result = tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let mut summary = String::new();
|
||||
|
||||
fn visit_dirs(dir: &std::path::Path, summary: &mut String, depth: usize) -> std::io::Result<()> {
|
||||
@@ -581,7 +570,7 @@ impl McpTool for ReadDirectoryArchitectureHandler {
|
||||
|
||||
let path = std::path::Path::new(&dir);
|
||||
if !path.exists() {
|
||||
return Err(format!("Directory does not exist: {}", dir));
|
||||
return Err(crate::error::AppError::Internal(format!("Directory does not exist: {}", dir)));
|
||||
}
|
||||
|
||||
summary.push_str(&format!("Architecture of {}:\n", dir));
|
||||
@@ -590,7 +579,7 @@ impl McpTool for ReadDirectoryArchitectureHandler {
|
||||
Ok(summary)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))??;
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -613,7 +602,7 @@ impl McpTool for SemanticCodeSearchHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: SemanticCodeSearchTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let query_emb = generate_embedding_async(tool_args.query.clone()).await?;
|
||||
|
||||
+43
-49
@@ -12,7 +12,7 @@ pub trait McpTool: Send + Sync {
|
||||
fn schema(&self) -> Value;
|
||||
|
||||
/// Execute the tool with the given arguments
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String>;
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -25,7 +25,7 @@ pub trait McpResource: Send + Sync {
|
||||
fn mime_type(&self) -> Option<&'static str> {
|
||||
Some("application/json")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String>;
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -37,7 +37,7 @@ pub trait McpPrompt: Send + Sync {
|
||||
fn arguments(&self) -> serde_json::Value {
|
||||
serde_json::json!([])
|
||||
}
|
||||
async fn get(&self, args: Value, state: Arc<MemoryState>) -> Result<serde_json::Value, String>;
|
||||
async fn get(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
struct GraphEntitiesResource;
|
||||
@@ -52,15 +52,14 @@ impl McpResource for GraphEntitiesResource {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("All nodes and entities currently stored in the knowledge graph")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let graph = state_clone.graph.cache.read().unwrap();
|
||||
let data: Vec<_> = graph.entities.values().collect();
|
||||
serde_json::to_string_pretty(&data).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&data)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,15 +75,14 @@ impl McpResource for GraphRelationsResource {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("All relationships between entities currently stored in the knowledge graph")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let graph = state_clone.graph.cache.read().unwrap();
|
||||
let data = &graph.relations;
|
||||
serde_json::to_string_pretty(&data).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&data)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,18 +98,17 @@ impl McpResource for TasksActiveResource {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("List of currently active tasks")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let tasks = state_clone.tasks.cache.read().unwrap();
|
||||
let data: Vec<_> = tasks
|
||||
.iter()
|
||||
.filter(|t| t.status != "completed" && t.status != "done")
|
||||
.collect();
|
||||
serde_json::to_string_pretty(&data).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&data)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +125,7 @@ impl McpPrompt for AnalyzeTechDebtPrompt {
|
||||
&self,
|
||||
_args: Value,
|
||||
_state: Arc<MemoryState>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
) -> crate::error::Result<serde_json::Value> {
|
||||
Ok(serde_json::json!({
|
||||
"messages": [
|
||||
{
|
||||
@@ -190,14 +187,13 @@ impl MemoryHandler {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("Recent terminal execution history and exit codes")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let items = state_clone.terminal_history.cache.read().unwrap();
|
||||
serde_json::to_string_pretty(&*items).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&*items)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
struct PinnedFilesResource;
|
||||
@@ -212,14 +208,13 @@ impl MemoryHandler {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("Currently pinned files in the workspace")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let items = state_clone.pinned_files.cache.read().unwrap();
|
||||
serde_json::to_string_pretty(&*items).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&*items)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,14 +230,13 @@ impl MemoryHandler {
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
Some("Project milestones and their status")
|
||||
}
|
||||
async fn read(&self, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let state_clone = Arc::clone(&state);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
|
||||
let items = state_clone.milestones.cache.read().unwrap();
|
||||
serde_json::to_string_pretty(&*items).map_err(|e| e.to_string())
|
||||
Ok(serde_json::to_string_pretty(&*items)?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +253,7 @@ impl MemoryHandler {
|
||||
&self,
|
||||
_args: Value,
|
||||
_state: Arc<MemoryState>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
) -> crate::error::Result<serde_json::Value> {
|
||||
Ok(serde_json::json!({
|
||||
"messages": [
|
||||
{
|
||||
@@ -290,7 +284,7 @@ impl MemoryHandler {
|
||||
&self,
|
||||
_args: Value,
|
||||
_state: Arc<MemoryState>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
) -> crate::error::Result<serde_json::Value> {
|
||||
Ok(serde_json::json!({"
|
||||
messages": [
|
||||
{
|
||||
@@ -506,7 +500,7 @@ impl MemoryHandler {
|
||||
});
|
||||
Some(crate::mcp::success(id, payload))
|
||||
}
|
||||
Err(e) => Some(crate::mcp::error(id, -32603, &e)),
|
||||
Err(e) => Some(crate::mcp::error(id, -32603, &e.to_string())),
|
||||
}
|
||||
} else {
|
||||
Some(crate::mcp::error(id, -32602, "Resource not found"))
|
||||
@@ -544,7 +538,7 @@ impl MemoryHandler {
|
||||
if let Some(prompt) = self.prompts.get(name) {
|
||||
match prompt.get(args, Arc::clone(&self.state)).await {
|
||||
Ok(messages) => Some(crate::mcp::success(id, messages)),
|
||||
Err(e) => Some(crate::mcp::error(id, -32603, &e)),
|
||||
Err(e) => Some(crate::mcp::error(id, -32603, &e.to_string())),
|
||||
}
|
||||
} else {
|
||||
Some(crate::mcp::error(id, -32602, "Prompt not found"))
|
||||
@@ -562,10 +556,10 @@ impl MemoryHandler {
|
||||
self.state
|
||||
.broadcast_activity(&format!("Agent executed tool: {}", name));
|
||||
|
||||
let result: Result<String, String> = if let Some(tool) = self.tools.get(name) {
|
||||
let result: crate::error::Result<String> = if let Some(tool) = self.tools.get(name) {
|
||||
tool.execute(args, self.state.clone()).await
|
||||
} else {
|
||||
Err(format!("Unknown tool: {}", name))
|
||||
Err(crate::error::AppError::Internal(format!("Unknown tool: {}", name)))
|
||||
};
|
||||
|
||||
match result {
|
||||
@@ -579,7 +573,7 @@ impl MemoryHandler {
|
||||
Err(e) => {
|
||||
tracing::error!("Tool {} failed: {}", name, e);
|
||||
let payload = serde_json::json!({
|
||||
"content": [{"type": "text", "text": e}],
|
||||
"content": [{"type": "text", "text": e.to_string()}],
|
||||
"isError": true
|
||||
});
|
||||
Some(crate::mcp::success(id_clone, payload))
|
||||
@@ -627,7 +621,7 @@ mod tests {
|
||||
"params": {}
|
||||
});
|
||||
|
||||
let res_list = handler.handle_request(list_tools_req).await.unwrap();
|
||||
let res_list = handler.handle_request(list_tools_req).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res_list["jsonrpc"], "2.0");
|
||||
assert_eq!(res_list["id"], 1);
|
||||
assert!(res_list["result"]["tools"].as_array().unwrap().len() > 10);
|
||||
@@ -646,7 +640,7 @@ mod tests {
|
||||
"method": "resources/list",
|
||||
"params": {}
|
||||
});
|
||||
let res_list = handler.handle_request(req_list_res).await.unwrap();
|
||||
let res_list = handler.handle_request(req_list_res).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let resources_arr = res_list["result"]["resources"].as_array().unwrap();
|
||||
assert!(
|
||||
resources_arr
|
||||
@@ -668,7 +662,7 @@ mod tests {
|
||||
"uri": "memory://tasks/active"
|
||||
}
|
||||
});
|
||||
let res_read = handler.handle_request(req_read_res).await.unwrap();
|
||||
let res_read = handler.handle_request(req_read_res).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(
|
||||
res_read["result"]["contents"][0]["uri"],
|
||||
"memory://tasks/active"
|
||||
@@ -687,7 +681,7 @@ mod tests {
|
||||
"method": "prompts/list",
|
||||
"params": {}
|
||||
});
|
||||
let res_prompts = handler.handle_request(req_list_prompts).await.unwrap();
|
||||
let res_prompts = handler.handle_request(req_list_prompts).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let prompts_arr = res_prompts["result"]["prompts"].as_array().unwrap();
|
||||
assert!(prompts_arr.iter().any(|p| p["name"] == "handoff_routine"));
|
||||
|
||||
@@ -701,7 +695,7 @@ mod tests {
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let res_get = handler.handle_request(req_get_prompt).await.unwrap();
|
||||
let res_get = handler.handle_request(req_get_prompt).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let messages = res_get["result"]["messages"].as_array().unwrap();
|
||||
assert_eq!(messages[0]["role"], "user");
|
||||
assert!(
|
||||
@@ -728,7 +722,7 @@ mod tests {
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let res_success = handler.handle_request(req_success).await.unwrap();
|
||||
let res_success = handler.handle_request(req_success).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res_success["jsonrpc"], "2.0");
|
||||
assert_eq!(res_success["id"], 2);
|
||||
// A successful tool call should return a result with isError: false
|
||||
@@ -748,7 +742,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
});
|
||||
let res_fail = handler.handle_request(req_fail).await.unwrap();
|
||||
let res_fail = handler.handle_request(req_fail).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert_eq!(res_fail["jsonrpc"], "2.0");
|
||||
assert_eq!(res_fail["id"], 3);
|
||||
// Semantic failures must explicitly return isError: true inside the result to halt the LLM
|
||||
@@ -766,7 +760,7 @@ mod tests {
|
||||
"id": 4,
|
||||
"method": "unknown_method_xyz"
|
||||
});
|
||||
let res_unknown = handler.handle_request(req_unknown).await.unwrap();
|
||||
let res_unknown = handler.handle_request(req_unknown).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
assert!(res_unknown.get("error").is_some());
|
||||
assert_eq!(res_unknown["error"]["code"], -32601);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user