feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit
This commit is contained in:
1 parent
5bd8b1587a
commit
e4a0fe72df
47 files changed
+6292
-3503
No files matched your search
@@ -15,7 +15,10 @@ impl McpTool for PinFileHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<PinFileTool>("pin_file", "Pin a file path in the workspace context to prioritize it across subagent turns.")
|
||||
crate::mcp::tool_def::<PinFileTool>(
|
||||
"pin_file",
|
||||
"Pin a file path in the workspace context to prioritize it across subagent turns.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -27,6 +30,9 @@ impl McpTool for PinFileHandler {
|
||||
file_path: req.file_path,
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
git_branch: req.git_branch,
|
||||
repo_name: req.repo_name,
|
||||
symbol_or_line: req.symbol_or_line,
|
||||
pin_reason: req.pin_reason,
|
||||
});
|
||||
});
|
||||
Ok("File pinned".to_string())
|
||||
@@ -42,7 +48,10 @@ impl McpTool for UnpinFileHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<UnpinFileTool>("unpin_file", "Unpin a file path from the workspace context.")
|
||||
crate::mcp::tool_def::<UnpinFileTool>(
|
||||
"unpin_file",
|
||||
"Unpin a file path from the workspace context.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -104,7 +113,10 @@ impl McpTool for StoreSnippetHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<StoreSnippetTool>("store_snippet", "Store or replace a reusable code snippet with vector embedding and search tags.")
|
||||
crate::mcp::tool_def::<StoreSnippetTool>(
|
||||
"store_snippet",
|
||||
"Store or replace a reusable code snippet with vector embedding and search tags.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -125,6 +137,12 @@ impl McpTool for StoreSnippetHandler {
|
||||
updated_at: crate::handlers::utils::now_secs(),
|
||||
tags: req.tags.unwrap_or_default(),
|
||||
embedding,
|
||||
namespace: req
|
||||
.namespace
|
||||
.unwrap_or_else(|| crate::models::default_namespace()),
|
||||
repo_name: req.repo_name,
|
||||
origin_file: req.origin_file,
|
||||
line_range: req.line_range,
|
||||
};
|
||||
|
||||
let idx = state.get_search_index().await;
|
||||
@@ -148,7 +166,10 @@ impl McpTool for TagSnippetHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<TagSnippetTool>("tag_snippet", "Add organizational tags to an existing stored code snippet.")
|
||||
crate::mcp::tool_def::<TagSnippetTool>(
|
||||
"tag_snippet",
|
||||
"Add organizational tags to an existing stored code snippet.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -169,9 +190,15 @@ impl McpTool for TagSnippetHandler {
|
||||
});
|
||||
|
||||
if found {
|
||||
Ok(format!("Snippet '{}' tagged with: {:?}", req.name, req.tags))
|
||||
Ok(format!(
|
||||
"Snippet '{}' tagged with: {:?}",
|
||||
req.name, req.tags
|
||||
))
|
||||
} else {
|
||||
Err(crate::error::AppError::Internal(format!("Snippet '{}' not found.", req.name)))
|
||||
Err(crate::error::AppError::Internal(format!(
|
||||
"Snippet '{}' not found.",
|
||||
req.name
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +236,9 @@ impl McpTool for SearchSnippetsHandler {
|
||||
|| s.tags.iter().any(|t| contains_ignore_ascii_case(t, &query));
|
||||
|
||||
let matches_tags = tags_filter.is_empty()
|
||||
|| tags_filter.iter().any(|tf| s.tags.iter().any(|st| contains_ignore_ascii_case(st, tf)));
|
||||
|| tags_filter
|
||||
.iter()
|
||||
.any(|tf| s.tags.iter().any(|st| contains_ignore_ascii_case(st, tf)));
|
||||
|
||||
matches_text && matches_tags
|
||||
})
|
||||
@@ -292,6 +321,10 @@ impl McpTool for SaveContextWorkspaceHandler {
|
||||
pinned_files: req.pinned_files,
|
||||
active_task_ids: req.active_task_ids,
|
||||
saved_at: crate::handlers::utils::now_secs(),
|
||||
description: req.description,
|
||||
repo_name: req.repo_name,
|
||||
git_branch: req.git_branch,
|
||||
vcs_revision: req.vcs_revision,
|
||||
});
|
||||
});
|
||||
Ok("Context workspace saved".to_string())
|
||||
@@ -419,6 +452,10 @@ impl McpTool for AddPrChecklistItemHandler {
|
||||
namespace: req.namespace,
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
description: req.description,
|
||||
is_checked: req.is_checked.unwrap_or(false),
|
||||
category: req.category,
|
||||
repo_name: req.repo_name,
|
||||
automated_check: req.automated_check,
|
||||
})
|
||||
});
|
||||
Ok("PR checklist item added".to_string())
|
||||
@@ -492,22 +529,43 @@ impl McpTool for PinnedFilesHandler {
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: PinnedFilesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
|
||||
let ns = req
|
||||
.namespace
|
||||
.unwrap_or_else(|| crate::models::default_namespace());
|
||||
match req.action {
|
||||
PinnedFileAction::Pin => {
|
||||
let path = req.path.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'path' for action 'pin'. Next step: Provide absolute or relative file 'path' in request and retry.".to_string())
|
||||
})?;
|
||||
PinFileHandler.execute(serde_json::json!({"file_path": path, "namespace": ns}), state).await
|
||||
PinFileHandler
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"file_path": path,
|
||||
"namespace": ns,
|
||||
"git_branch": req.git_branch,
|
||||
"pin_reason": req.pin_reason,
|
||||
"symbol_or_line": req.symbol_or_line,
|
||||
"repo_name": req.repo_name,
|
||||
}),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
PinnedFileAction::Unpin => {
|
||||
let path = req.path.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'path' for action 'unpin'. Next step: Provide file 'path' in request and retry.".to_string())
|
||||
})?;
|
||||
UnpinFileHandler.execute(serde_json::json!({"file_path": path, "namespace": ns}), state).await
|
||||
UnpinFileHandler
|
||||
.execute(
|
||||
serde_json::json!({"file_path": path, "namespace": ns}),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
PinnedFileAction::List => {
|
||||
ListPinnedFilesHandler.execute(serde_json::json!({"namespace": ns}), state).await
|
||||
ListPinnedFilesHandler
|
||||
.execute(serde_json::json!({"namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +588,9 @@ impl McpTool for ContextWorkspacesHandler {
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ContextWorkspacesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
|
||||
let ns = req
|
||||
.namespace
|
||||
.unwrap_or_else(|| crate::models::default_namespace());
|
||||
match req.action {
|
||||
ContextWorkspaceAction::Save => {
|
||||
let name = req.name.ok_or_else(|| {
|
||||
@@ -538,30 +598,42 @@ impl McpTool for ContextWorkspacesHandler {
|
||||
})?;
|
||||
let pinned_files = req.pinned_files.unwrap_or_default();
|
||||
let active_task_ids = req.active_task_ids.unwrap_or_default();
|
||||
SaveContextWorkspaceHandler.execute(
|
||||
serde_json::json!({
|
||||
"name": name,
|
||||
"namespace": ns,
|
||||
"pinned_files": pinned_files,
|
||||
"active_task_ids": active_task_ids
|
||||
}),
|
||||
state,
|
||||
).await
|
||||
SaveContextWorkspaceHandler
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"name": name,
|
||||
"namespace": ns,
|
||||
"pinned_files": pinned_files,
|
||||
"active_task_ids": active_task_ids,
|
||||
"description": req.description,
|
||||
"repo_name": req.repo_name,
|
||||
"git_branch": req.git_branch,
|
||||
"vcs_revision": req.vcs_revision,
|
||||
}),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
ContextWorkspaceAction::Load => {
|
||||
let name = req.name.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'name' for action 'load'. Next step: Provide workspace 'name' string in request and retry.".to_string())
|
||||
})?;
|
||||
LoadContextWorkspaceHandler.execute(serde_json::json!({"name": name, "namespace": ns}), state).await
|
||||
LoadContextWorkspaceHandler
|
||||
.execute(serde_json::json!({"name": name, "namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
ContextWorkspaceAction::List => {
|
||||
ListContextWorkspacesHandler.execute(serde_json::json!({"namespace": ns}), state).await
|
||||
ListContextWorkspacesHandler
|
||||
.execute(serde_json::json!({"namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
ContextWorkspaceAction::Delete => {
|
||||
let name = req.name.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'name' for action 'delete'. Next step: Provide workspace 'name' string in request and retry.".to_string())
|
||||
})?;
|
||||
DeleteContextWorkspaceHandler.execute(serde_json::json!({"name": name, "namespace": ns}), state).await
|
||||
DeleteContextWorkspaceHandler
|
||||
.execute(serde_json::json!({"name": name, "namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
ContextWorkspaceAction::Diff => {
|
||||
let name = req.name.ok_or_else(|| {
|
||||
@@ -593,19 +665,37 @@ impl McpTool for PrChecklistHandler {
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: PrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
|
||||
let ns = req
|
||||
.namespace
|
||||
.unwrap_or_else(|| crate::models::default_namespace());
|
||||
match req.action {
|
||||
PrChecklistAction::Add => {
|
||||
let desc = req.description.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'description' for action 'add'. Next step: Provide checklist item 'description' string in request and retry.".to_string())
|
||||
})?;
|
||||
AddPrChecklistItemHandler.execute(serde_json::json!({"description": desc, "namespace": ns}), state).await
|
||||
AddPrChecklistItemHandler
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"description": desc,
|
||||
"namespace": ns,
|
||||
"category": req.category,
|
||||
"automated_check": req.automated_check,
|
||||
"is_checked": req.is_checked,
|
||||
"repo_name": req.repo_name,
|
||||
}),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
PrChecklistAction::Get => {
|
||||
GetPrChecklistHandler.execute(serde_json::json!({"namespace": ns}), state).await
|
||||
GetPrChecklistHandler
|
||||
.execute(serde_json::json!({"namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
PrChecklistAction::Clear => {
|
||||
ClearPrChecklistHandler.execute(serde_json::json!({"namespace": ns}), state).await
|
||||
ClearPrChecklistHandler
|
||||
.execute(serde_json::json!({"namespace": ns}), state)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,37 +726,51 @@ impl McpTool for SnippetsHandler {
|
||||
let lang = req.language.unwrap_or_else(|| "text".to_string());
|
||||
let code = req.code.unwrap_or_default();
|
||||
let desc = req.description.unwrap_or_default();
|
||||
StoreSnippetHandler.execute(
|
||||
serde_json::json!({
|
||||
"name": name,
|
||||
"language": lang,
|
||||
"code": code,
|
||||
"description": desc,
|
||||
"tags": req.tags
|
||||
}),
|
||||
state,
|
||||
).await
|
||||
StoreSnippetHandler
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"name": name,
|
||||
"language": lang,
|
||||
"code": code,
|
||||
"description": desc,
|
||||
"tags": req.tags,
|
||||
"namespace": req.namespace,
|
||||
"repo_name": req.repo_name,
|
||||
"origin_file": req.origin_file,
|
||||
"line_range": req.line_range,
|
||||
}),
|
||||
state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
SnippetAction::Search => {
|
||||
let q = req.query.or(req.name).unwrap_or_default();
|
||||
if req.hybrid.unwrap_or(false) {
|
||||
crate::handlers::meta::SearchSnippetsHybridHandler.execute(serde_json::json!({"query": q, "tags": req.tags}), state).await
|
||||
crate::handlers::meta::SearchSnippetsHybridHandler
|
||||
.execute(serde_json::json!({"query": q, "tags": req.tags}), state)
|
||||
.await
|
||||
} else {
|
||||
SearchSnippetsHandler.execute(serde_json::json!({"query": q, "tags": req.tags}), state).await
|
||||
SearchSnippetsHandler
|
||||
.execute(serde_json::json!({"query": q, "tags": req.tags}), state)
|
||||
.await
|
||||
}
|
||||
}
|
||||
SnippetAction::Delete => {
|
||||
let id = req.name.or(req.id).or(req.query).ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'name', 'id', or 'query' for action 'delete'. Next step: Provide snippet ID/name in request and retry.".to_string())
|
||||
})?;
|
||||
DeleteSnippetHandler.execute(serde_json::json!({"name": id}), state).await
|
||||
DeleteSnippetHandler
|
||||
.execute(serde_json::json!({"name": id}), state)
|
||||
.await
|
||||
}
|
||||
SnippetAction::Tag => {
|
||||
let id = req.name.or(req.id).or(req.query).ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'name', 'id', or 'query' for action 'tag'. Next step: Provide snippet ID/name and 'tags' array in request and retry.".to_string())
|
||||
})?;
|
||||
let tags = req.tags.unwrap_or_default();
|
||||
TagSnippetHandler.execute(serde_json::json!({"name": id, "tags": tags}), state).await
|
||||
TagSnippetHandler
|
||||
.execute(serde_json::json!({"name": id, "tags": tags}), state)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -793,24 +897,42 @@ mod tests {
|
||||
|
||||
// Snapshots
|
||||
let create_snap = CreateSnapshotHandler;
|
||||
let snap_res = create_snap.execute(json!({"description": "Test snap", "namespace": "global"}), state.clone()).await.unwrap();
|
||||
let snap_res = create_snap
|
||||
.execute(
|
||||
json!({"description": "Test snap", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(snap_res.contains("SNAP-"));
|
||||
let snap_id = snap_res.split_whitespace().nth(1).unwrap();
|
||||
|
||||
let restore_snap = RestoreSnapshotHandler;
|
||||
let restore_res = restore_snap.execute(json!({"snapshot_id": snap_id}), state.clone()).await.unwrap();
|
||||
let restore_res = restore_snap
|
||||
.execute(json!({"snapshot_id": snap_id}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(restore_res.contains("Successfully restored"));
|
||||
|
||||
// Subagent Namespace
|
||||
let create_sub_ns = CreateSubagentNamespaceHandler;
|
||||
let sub_ns_res = create_sub_ns.execute(json!({"subagent_id": "test_agent"}), state.clone()).await.unwrap();
|
||||
let sub_ns_res = create_sub_ns
|
||||
.execute(json!({"subagent_id": "test_agent"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(sub_ns_res.contains("subagent-test_agent"));
|
||||
|
||||
let manage_sub_ns = ManageSubagentNamespaceHandler;
|
||||
let manage_res = manage_sub_ns.execute(json!({
|
||||
"subagent_id": "test_agent",
|
||||
"action": "create"
|
||||
}), state.clone()).await.unwrap();
|
||||
let manage_res = manage_sub_ns
|
||||
.execute(
|
||||
json!({
|
||||
"subagent_id": "test_agent",
|
||||
"action": "create"
|
||||
}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(manage_res.contains("initialized"));
|
||||
|
||||
// Context Workspace Diff
|
||||
@@ -818,9 +940,14 @@ mod tests {
|
||||
save_ws.execute(json!({"name": "ws_a", "description": "Workspace A", "pinned_files": ["src/lib.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
|
||||
save_ws.execute(json!({"name": "ws_b", "description": "Workspace B", "pinned_files": ["src/main.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
|
||||
|
||||
|
||||
let diff_ws = DiffContextWorkspacesHandler;
|
||||
let diff_res = diff_ws.execute(json!({"workspace_a": "ws_a", "workspace_b": "ws_b"}), state.clone()).await.unwrap();
|
||||
let diff_res = diff_ws
|
||||
.execute(
|
||||
json!({"workspace_a": "ws_a", "workspace_b": "ws_b"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(diff_res.contains("workspace_a"));
|
||||
}
|
||||
|
||||
@@ -834,7 +961,13 @@ mod tests {
|
||||
save_ws.execute(json!({"name": "ws_2", "description": "Workspace 2", "pinned_files": ["src/main.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
|
||||
|
||||
let diff_ws = DiffContextWorkspacesHandler;
|
||||
let diff_res = diff_ws.execute(json!({"workspace_a": "ws_1", "workspace_b": "ws_2"}), state.clone()).await.unwrap();
|
||||
let diff_res = diff_ws
|
||||
.execute(
|
||||
json!({"workspace_a": "ws_1", "workspace_b": "ws_2"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(diff_res.contains("ws_1"));
|
||||
}
|
||||
}
|
||||
@@ -998,7 +1131,10 @@ impl McpTool for CreateSnapshotHandler {
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: CreateSnapshotTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let snapshot_id = format!("SNAP-{}", uuid::Uuid::new_v4().to_string()[..8].to_uppercase());
|
||||
let snapshot_id = format!(
|
||||
"SNAP-{}",
|
||||
uuid::Uuid::new_v4().to_string()[..8].to_uppercase()
|
||||
);
|
||||
let ns = req.namespace.unwrap_or_else(|| "global".to_string());
|
||||
|
||||
let snapshot = crate::models::StateSnapshot {
|
||||
@@ -1006,14 +1142,22 @@ impl McpTool for CreateSnapshotHandler {
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
description: req.description.clone(),
|
||||
namespace: ns.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
state.project.snapshots.modify(|snaps| {
|
||||
snaps.push(snapshot);
|
||||
});
|
||||
|
||||
state.record_activity("snapshot", &format!("Created snapshot {}: {}", snapshot_id, req.description), None);
|
||||
Ok(format!("Snapshot {} created for namespace '{}'", snapshot_id, ns))
|
||||
state.record_activity(
|
||||
"snapshot",
|
||||
&format!("Created snapshot {}: {}", snapshot_id, req.description),
|
||||
None,
|
||||
);
|
||||
Ok(format!(
|
||||
"Snapshot {} created for namespace '{}'",
|
||||
snapshot_id, ns
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1031,15 +1175,26 @@ impl McpTool for RestoreSnapshotHandler {
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: RestoreSnapshotTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let found = state.project.snapshots.read_with(|snaps| {
|
||||
snaps.iter().any(|s| s.id == req.snapshot_id)
|
||||
});
|
||||
let found = state
|
||||
.project
|
||||
.snapshots
|
||||
.read_with(|snaps| snaps.iter().any(|s| s.id == req.snapshot_id));
|
||||
|
||||
if found {
|
||||
state.record_activity("snapshot", &format!("Restored snapshot {}", req.snapshot_id), None);
|
||||
Ok(format!("Successfully restored memory state from snapshot {}", req.snapshot_id))
|
||||
state.record_activity(
|
||||
"snapshot",
|
||||
&format!("Restored snapshot {}", req.snapshot_id),
|
||||
None,
|
||||
);
|
||||
Ok(format!(
|
||||
"Successfully restored memory state from snapshot {}",
|
||||
req.snapshot_id
|
||||
))
|
||||
} else {
|
||||
Err(crate::error::AppError::Internal(format!("Snapshot {} not found", req.snapshot_id)))
|
||||
Err(crate::error::AppError::Internal(format!(
|
||||
"Snapshot {} not found",
|
||||
req.snapshot_id
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1060,20 +1215,35 @@ impl McpTool for ManageSubagentNamespaceHandler {
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ManageSubagentNamespaceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let req: ManageSubagentNamespaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let subagent_ns = format!("subagent-{}", req.subagent_id);
|
||||
|
||||
match req.action {
|
||||
SubagentNamespaceAction::Create => {
|
||||
state.record_activity("namespace", &format!("Created subagent namespace {}", subagent_ns), None);
|
||||
Ok(format!("Subagent memory namespace '{}' initialized.", subagent_ns))
|
||||
state.record_activity(
|
||||
"namespace",
|
||||
&format!("Created subagent namespace {}", subagent_ns),
|
||||
None,
|
||||
);
|
||||
Ok(format!(
|
||||
"Subagent memory namespace '{}' initialized.",
|
||||
subagent_ns
|
||||
))
|
||||
}
|
||||
SubagentNamespaceAction::Purge => {
|
||||
state.project.pinned_files.modify(|files| {
|
||||
files.retain(|f| f.namespace != subagent_ns);
|
||||
});
|
||||
state.record_activity("namespace", &format!("Purged subagent namespace {}", subagent_ns), None);
|
||||
Ok(format!("Subagent memory namespace '{}' purged.", subagent_ns))
|
||||
state.record_activity(
|
||||
"namespace",
|
||||
&format!("Purged subagent namespace {}", subagent_ns),
|
||||
None,
|
||||
);
|
||||
Ok(format!(
|
||||
"Subagent memory namespace '{}' purged.",
|
||||
subagent_ns
|
||||
))
|
||||
}
|
||||
SubagentNamespaceAction::Condense => {
|
||||
let purge = req.purge_after_promotion.unwrap_or(true);
|
||||
@@ -1091,13 +1261,15 @@ impl McpTool for ManageSubagentNamespaceHandler {
|
||||
for mut e in sub_entities {
|
||||
promoted_entities += 1;
|
||||
e.namespace = "global".to_string();
|
||||
let global_entry = g.entities.entry(e.name.clone()).or_insert_with(|| Entity {
|
||||
name: e.name.clone(),
|
||||
entity_type: e.entity_type.clone(),
|
||||
observations: Vec::new(),
|
||||
namespace: "global".to_string(),
|
||||
git_branch: e.git_branch.clone(),
|
||||
});
|
||||
let global_entry =
|
||||
g.entities.entry(e.name.clone()).or_insert_with(|| Entity {
|
||||
name: e.name.clone(),
|
||||
entity_type: e.entity_type.clone(),
|
||||
observations: Vec::new(),
|
||||
namespace: "global".to_string(),
|
||||
git_branch: e.git_branch.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
for obs in e.observations {
|
||||
if !global_entry.observations.contains(&obs) {
|
||||
global_entry.observations.push(obs);
|
||||
@@ -1142,13 +1314,21 @@ impl McpTool for CreateSubagentNamespaceHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<CreateSubagentNamespaceTool>("create_subagent_namespace", "Create subagent namespace")
|
||||
crate::mcp::tool_def::<CreateSubagentNamespaceTool>(
|
||||
"create_subagent_namespace",
|
||||
"Create subagent namespace",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: CreateSubagentNamespaceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let req: CreateSubagentNamespaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ns = format!("subagent-{}", req.subagent_id);
|
||||
state.record_activity("namespace", &format!("Created subagent namespace {}", ns), None);
|
||||
state.record_activity(
|
||||
"namespace",
|
||||
&format!("Created subagent namespace {}", ns),
|
||||
None,
|
||||
);
|
||||
Ok(format!("Subagent memory namespace '{}' initialized.", ns))
|
||||
}
|
||||
}
|
||||
@@ -1162,18 +1342,26 @@ impl McpTool for PurgeSubagentNamespaceHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<PurgeSubagentNamespaceTool>("purge_subagent_namespace", "Purge subagent namespace")
|
||||
crate::mcp::tool_def::<PurgeSubagentNamespaceTool>(
|
||||
"purge_subagent_namespace",
|
||||
"Purge subagent namespace",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: PurgeSubagentNamespaceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let req: PurgeSubagentNamespaceTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let ns = format!("subagent-{}", req.subagent_id);
|
||||
|
||||
|
||||
state.project.pinned_files.modify(|files| {
|
||||
files.retain(|f| f.namespace != ns);
|
||||
});
|
||||
|
||||
state.record_activity("namespace", &format!("Purged subagent namespace {}", ns), None);
|
||||
state.record_activity(
|
||||
"namespace",
|
||||
&format!("Purged subagent namespace {}", ns),
|
||||
None,
|
||||
);
|
||||
Ok(format!("Subagent memory namespace '{}' purged.", ns))
|
||||
}
|
||||
}
|
||||
@@ -1219,6 +1407,7 @@ impl McpTool for CondenseSubagentNamespaceHandler {
|
||||
observations: Vec::new(),
|
||||
namespace: "global".to_string(),
|
||||
git_branch: e.git_branch.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
for obs in e.observations {
|
||||
if !global_entry.observations.contains(&obs) {
|
||||
@@ -1262,11 +1451,15 @@ impl McpTool for DiffContextWorkspacesHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DiffContextWorkspacesTool>("diff_context_workspaces", "Execute diff_context_workspaces")
|
||||
crate::mcp::tool_def::<DiffContextWorkspacesTool>(
|
||||
"diff_context_workspaces",
|
||||
"Execute diff_context_workspaces",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DiffContextWorkspacesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let req: DiffContextWorkspacesTool =
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let workspaces = state.project.context_workspaces.read_with(|ws| ws.clone());
|
||||
let ws_a = workspaces.iter().find(|w| w.name == req.workspace_a);
|
||||
@@ -1275,7 +1468,10 @@ impl McpTool for DiffContextWorkspacesHandler {
|
||||
if ws_a.is_none() || ws_b.is_none() {
|
||||
return Err(crate::error::AppError::Internal(format!(
|
||||
"One or both context workspaces not found: '{}' found={}, '{}' found={}",
|
||||
req.workspace_a, ws_a.is_some(), req.workspace_b, ws_b.is_some()
|
||||
req.workspace_a,
|
||||
ws_a.is_some(),
|
||||
req.workspace_b,
|
||||
ws_b.is_some()
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -1314,4 +1510,3 @@ impl McpTool for DiffContextWorkspacesHandler {
|
||||
Ok(serde_json::to_string_pretty(&diff)?)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user