feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit

This commit is contained in:
Riz Ashraf committed 2026-10-07 06:36:09 +01:00
1 parent 5bd8b1587a
commit e4a0fe72df
47 files changed
+6292 -3503

No files matched your search

+120 -33
View File
@@ -45,21 +45,34 @@ impl McpTool for TasksHandler {
dependencies: deps,
acceptance_criteria: vec![],
expires_at: None,
namespace: req.namespace.unwrap_or_else(|| "default".to_string()),
repo_name: req.repo_name,
priority: req.priority,
assigned_agent: req.assigned_agent,
verification_command: req.verification_command,
..Default::default()
};
let idx = state.get_search_index().await;
drop(idx.index_task(&task));
state.project.tasks.modify(|tasks| {
tasks.push(task.clone());
});
state.record_activity("task_create", &format!("Created task: {}", task.title), Some(&task.description));
state.record_activity(
"task_create",
&format!("Created task: {}", task.title),
Some(&task.description),
);
state.broadcast_task_event(TaskEvent {
task_id: task_id.clone(),
status: "created".to_string(),
action: Some("add".to_string()),
result: Some(serde_json::json!({ "title": task.title, "git_branch": task.git_branch })),
result: Some(
serde_json::json!({ "title": task.title, "git_branch": task.git_branch }),
),
error: None,
timestamp: now,
session_id: None,
..Default::default()
});
Ok(format!("Task added with ID: {}", task_id))
}
@@ -75,6 +88,8 @@ impl McpTool for TasksHandler {
let mut blocked = false;
let mut blocker_details = String::new();
let mut updated_task: Option<Task> = None;
state.project.tasks.modify(|tasks| {
let target_idx = tasks.iter().position(|t| t.id == id || t.title == id);
let target_idx = match target_idx {
@@ -84,7 +99,11 @@ impl McpTool for TasksHandler {
found = true;
if target_status == "done" || target_status == "completed" {
if tasks[target_idx].acceptance_criteria.iter().any(|c| !c.is_met) {
if tasks[target_idx]
.acceptance_criteria
.iter()
.any(|c| !c.is_met)
{
blocked = true;
blocker_details = "Unmet acceptance criteria exist.".to_string();
}
@@ -100,20 +119,29 @@ impl McpTool for TasksHandler {
}
if !uncompleted_deps.is_empty() {
blocked = true;
blocker_details = format!("Blocked by dependencies: {}", uncompleted_deps.join(", "));
blocker_details = format!(
"Blocked by dependencies: {}",
uncompleted_deps.join(", ")
);
}
}
if !blocked {
let target_id_ref = tasks[target_idx].id.as_str();
let mut uncompleted_children = Vec::new();
for child in tasks.iter().filter(|t| t.parent_id.as_deref() == Some(target_id_ref)) {
for child in tasks
.iter()
.filter(|t| t.parent_id.as_deref() == Some(target_id_ref))
{
if child.status != "completed" && child.status != "done" {
uncompleted_children.push(child.title.as_str());
}
}
if !uncompleted_children.is_empty() {
blocked = true;
blocker_details = format!("Blocked by child tasks: {}", uncompleted_children.join(", "));
blocker_details = format!(
"Blocked by child tasks: {}",
uncompleted_children.join(", ")
);
}
}
}
@@ -121,6 +149,7 @@ impl McpTool for TasksHandler {
if !blocked {
tasks[target_idx].status = target_status.clone();
tasks[target_idx].updated_at = crate::handlers::utils::now_secs();
updated_task = Some(tasks[target_idx].clone());
}
});
@@ -133,10 +162,22 @@ impl McpTool for TasksHandler {
error: Some(blocker_details.clone()),
timestamp: crate::handlers::utils::now_secs(),
session_id: None,
..Default::default()
});
Err(crate::error::AppError::Internal(format!("Error: Cannot transition task. {}", blocker_details)))
Err(crate::error::AppError::Internal(format!(
"Error: Cannot transition task. {}",
blocker_details
)))
} else if found {
state.record_activity("task_update", &format!("Task {} status -> {}", id, status), None);
if let Some(ref ut) = updated_task {
let idx = state.get_search_index().await;
drop(idx.index_task(ut));
}
state.record_activity(
"task_update",
&format!("Task {} status -> {}", id, status),
None,
);
state.broadcast_task_event(TaskEvent {
task_id: id.clone(),
status: target_status.clone(),
@@ -145,10 +186,13 @@ impl McpTool for TasksHandler {
error: None,
timestamp: crate::handlers::utils::now_secs(),
session_id: None,
..Default::default()
});
Ok("Task status updated.".to_string())
} else {
Err(crate::error::AppError::Internal("Task not found. Please verify the task ID.".to_string()))
Err(crate::error::AppError::Internal(
"Task not found. Please verify the task ID.".to_string(),
))
}
}
TaskAction::Delete => {
@@ -163,9 +207,12 @@ impl McpTool for TasksHandler {
for (idx, t) in tasks.iter().enumerate() {
id_to_index.insert(t.id.as_str(), idx);
}
let mut children_map: std::collections::HashMap<usize, Vec<usize>> = std::collections::HashMap::new();
let mut children_map: std::collections::HashMap<usize, Vec<usize>> =
std::collections::HashMap::new();
for (idx, t) in tasks.iter().enumerate() {
if let Some(pid) = &t.parent_id && let Some(&p_idx) = id_to_index.get(pid.as_str()) {
if let Some(pid) = &t.parent_id
&& let Some(&p_idx) = id_to_index.get(pid.as_str())
{
children_map.entry(p_idx).or_default().push(idx);
}
}
@@ -174,7 +221,9 @@ impl McpTool for TasksHandler {
let mut queue = std::collections::VecDeque::new();
queue.push_back(start_idx);
while let Some(curr) = queue.pop_front() {
if to_delete_idx.insert(curr) && let Some(children) = children_map.get(&curr) {
if to_delete_idx.insert(curr)
&& let Some(children) = children_map.get(&curr)
{
queue.extend(children.iter().copied());
}
}
@@ -199,19 +248,29 @@ impl McpTool for TasksHandler {
error: None,
timestamp: crate::handlers::utils::now_secs(),
session_id: None,
..Default::default()
});
Ok(format!("Deleted task and its children ({} total).", deleted_count))
Ok(format!(
"Deleted task and its children ({} total).",
deleted_count
))
} else {
Err(crate::error::AppError::Internal("Task not found. Please verify the task ID.".to_string()))
Err(crate::error::AppError::Internal(
"Task not found. Please verify the task ID.".to_string(),
))
}
}
TaskAction::List => {
let level = req.summary_level.as_deref().unwrap_or("detailed");
let data = state.project.tasks.read_with(|tasks| {
let filtered: Vec<_> = tasks
let mut filtered: Vec<_> = tasks
.iter()
.filter(|t| {
let status_match = t.status != "done" && t.status != "completed";
let status_match = match req.status.as_deref() {
Some("all") => true,
Some(s) => t.status.eq_ignore_ascii_case(s),
None => t.status != "done" && t.status != "completed",
};
let branch_match = match &req.git_branch {
Some(branch) => t.git_branch.is_none() || t.git_branch.as_deref() == Some(branch.as_str()),
None => true,
@@ -227,10 +286,9 @@ impl McpTool for TasksHandler {
let mut json_str = serde_json::to_string(&filtered)?;
if let Some(max_t) = req.max_tokens {
let char_limit = max_t * 4;
if json_str.len() > char_limit {
let boundary = json_str.floor_char_boundary(char_limit);
json_str.truncate(boundary);
json_str.push_str(" ...[truncated due to max_tokens]");
while json_str.len() > char_limit && !filtered.is_empty() {
filtered.pop();
json_str = serde_json::to_string(&filtered)?;
}
}
Ok::<String, crate::error::AppError>(json_str)
@@ -253,6 +311,7 @@ impl McpTool for TasksHandler {
id: uuid::Uuid::new_v4().to_string(),
description: desc,
is_met: false,
..Default::default()
})
.collect();
task.updated_at = crate::handlers::utils::now_secs();
@@ -268,10 +327,13 @@ impl McpTool for TasksHandler {
error: None,
timestamp: crate::handlers::utils::now_secs(),
session_id: None,
..Default::default()
});
Ok("Acceptance criteria set successfully.".to_string())
} else {
Err(crate::error::AppError::Internal("Task not found. Please verify the task ID.".to_string()))
Err(crate::error::AppError::Internal(
"Task not found. Please verify the task ID.".to_string(),
))
}
}
TaskAction::Verify => {
@@ -292,11 +354,13 @@ impl McpTool for TasksHandler {
task.updated_at = crate::handlers::utils::now_secs();
}
} else {
task.acceptance_criteria.push(crate::models::AcceptanceCriteria {
id: uuid::Uuid::new_v4().to_string(),
description: proof_str.clone(),
is_met: true,
});
task.acceptance_criteria
.push(crate::models::AcceptanceCriteria {
id: uuid::Uuid::new_v4().to_string(),
description: proof_str.clone(),
is_met: true,
..Default::default()
});
task.updated_at = crate::handlers::utils::now_secs();
success = true;
}
@@ -311,12 +375,18 @@ impl McpTool for TasksHandler {
error: None,
timestamp: crate::handlers::utils::now_secs(),
session_id: None,
..Default::default()
});
Ok(format!("Acceptance criteria verified with proof: {}", proof_str))
Ok(format!(
"Acceptance criteria verified with proof: {}",
proof_str
))
} else if already_met {
Ok("Acceptance criteria was already met.".to_string())
} else {
Err(crate::error::AppError::Internal("Task not found. Please verify the task ID.".to_string()))
Err(crate::error::AppError::Internal(
"Task not found. Please verify the task ID.".to_string(),
))
}
}
}
@@ -345,14 +415,23 @@ impl McpTool for MilestonesHandler {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'title' for action 'add'. Next step: Provide non-empty 'title' string in request and retry.".to_string())
})?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
let ns = req
.namespace
.unwrap_or_else(|| crate::models::default_namespace());
state.project.milestones.modify(|ms| {
ms.push(crate::models::Milestone {
id: uuid::Uuid::new_v4().to_string(),
title,
status: "pending".to_string(),
namespace: ns,
target_date: None,
target_date: req
.target_date
.as_deref()
.and_then(|s| s.parse::<u64>().ok()),
description: req.description,
deliverables: req.deliverables.unwrap_or_default(),
repo_name: req.repo_name,
..Default::default()
})
});
Ok("Milestone added".to_string())
@@ -377,7 +456,9 @@ impl McpTool for MilestonesHandler {
if found {
Ok("Milestone updated".to_string())
} else {
Err(crate::error::AppError::Internal("Milestone not found. Please verify the milestone ID.".to_string()))
Err(crate::error::AppError::Internal(
"Milestone not found. Please verify the milestone ID.".to_string(),
))
}
}
MilestoneAction::List => {
@@ -504,7 +585,10 @@ mod tests {
assert_eq!(res2, "Milestone updated");
let res3 = handler_ms
.execute(json!({"action": "list", "namespace": "global"}), state.clone())
.execute(
json!({"action": "list", "namespace": "global"}),
state.clone(),
)
.await
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
.unwrap();
@@ -602,7 +686,10 @@ mod tests {
assert!(res1.contains("Milestone added"));
let res2 = handler
.execute(serde_json::json!({"action": "list", "namespace": "global"}), state.clone())
.execute(
serde_json::json!({"action": "list", "namespace": "global"}),
state.clone(),
)
.await
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
.unwrap();