feat(kernel): complete tree-first graph tasks 074-080

This commit is contained in:
lix-2026
2026-04-16 22:01:51 +08:00
parent 2ff10fa86c
commit b1d5d97142
65 changed files with 11579 additions and 4606 deletions
+925 -4
View File
@@ -8,10 +8,16 @@ use core_domain::Timestamp;
use core_protocol::{
default_tool_registry, invocation_kind_label, tool_effect_label, ActorPayload,
CommandEnvelope, EmbedBlock, GetBlock, GetBridgeCommand, GetBridgeRequest, GetBridgeTrace,
GetMindmap, InvocationKind, ListBridgeWorkspaceOverview, MindmapNodeData, MindmapNodeInput,
MindmapNodeRef, MindmapOp, MindmapTreeNode, MoveBlock, PatchBlock, PutMindmap,
QueryEnvelope, SearchDocuments, SearchRecent, SourcePayload, TargetRef, ToolExecutionMode,
ToolInvocation,
GetMindmap, InvocationKind, KernelAttachEdge, KernelAuditStamp, KernelContentPayload,
KernelCreateNode, KernelDetachEdge, KernelEdge, KernelEdgeListResult, KernelEdgeType,
KernelGetNode, KernelGetSubtree, KernelGraphDirection, KernelGraphTraversalResult,
KernelGraphVisit, KernelListChildren, KernelListEdges, KernelMoveSubtree, KernelNode,
KernelNodeMetadata, KernelNodeType, KernelProjectionFilter, KernelProjectionItem,
KernelProjectionKind, KernelProjectionRequest, KernelProjectionResult, KernelRefsPayload,
KernelSubtreeRef, KernelSubtreeResult, KernelTraverseGraph, KernelUpdateNode,
ListBridgeWorkspaceOverview, MindmapNodeData, MindmapNodeInput, MindmapNodeRef, MindmapOp,
MindmapTreeNode, MoveBlock, PatchBlock, PutMindmap, QueryEnvelope, SearchDocuments,
SearchRecent, SourcePayload, TargetRef, ToolExecutionMode, ToolInvocation,
};
use event_log::DomainEventRecord;
use index_fts::{
@@ -346,6 +352,63 @@ struct SidebarDatasetQueryPayload {
workspace_id: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelGetNodeQueryPayload {
node_id: String,
workspace_id: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelGetSubtreeQueryPayload {
root_node_id: String,
workspace_id: Option<String>,
depth: Option<u32>,
include_edges: Option<bool>,
node_types: Option<Vec<KernelNodeType>>,
edge_types: Option<Vec<KernelEdgeType>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelListChildrenQueryPayload {
parent_node_id: String,
workspace_id: Option<String>,
node_types: Option<Vec<KernelNodeType>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelListEdgesQueryPayload {
node_id: String,
workspace_id: Option<String>,
edge_types: Option<Vec<KernelEdgeType>>,
direction: Option<KernelGraphDirection>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelTraverseGraphQueryPayload {
start_node_id: String,
workspace_id: Option<String>,
edge_types: Option<Vec<KernelEdgeType>>,
max_depth: Option<u32>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelProjectViewQueryPayload {
projection: KernelProjectionKind,
workspace_id: Option<String>,
root_node_id: Option<String>,
depth: Option<u32>,
include_content: Option<bool>,
include_edges: Option<bool>,
node_types: Option<Vec<KernelNodeType>>,
edge_types: Option<Vec<KernelEdgeType>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SearchDocumentsQueryPayload {
@@ -425,6 +488,46 @@ struct DocumentSaveCommandPayload {
conflict_detection_key: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelCreateNodeCommandPayload {
node: KernelNode,
position: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelUpdateNodeCommandPayload {
node_id: String,
metadata: Option<KernelNodeMetadata>,
content: Option<KernelContentPayload>,
refs: Option<KernelRefsPayload>,
audit: Option<KernelAuditStamp>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelMoveSubtreeCommandPayload {
subtree: KernelSubtreeRef,
new_parent_node_id: Option<String>,
sort_order: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelAttachEdgeCommandPayload {
edge: KernelEdge,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KernelDetachEdgeCommandPayload {
edge_id: Option<String>,
from_node_id: Option<String>,
to_node_id: Option<String>,
edge_type: Option<KernelEdgeType>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapPutCommandPayload {
@@ -635,6 +738,193 @@ fn execute_query(
) -> Result<RuntimeExecutionPlan, BridgeError> {
let context = to_bridge_context(context_wire);
match query_wire.name.as_str() {
"kernel.node.get" => {
let payload: KernelGetNodeQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.node.get".into(),
payload: KernelGetNode {
node_id: payload.node_id.clone(),
workspace_id: payload.workspace_id.clone(),
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"nodeId": payload.node_id,
"workspaceId": payload.workspace_id,
}),
}))
}
"kernel.subtree.get" => {
let payload: KernelGetSubtreeQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.subtree.get".into(),
payload: KernelGetSubtree {
subtree: KernelSubtreeRef {
root_node_id: payload.root_node_id.clone(),
path: vec![payload.root_node_id.clone()],
depth: payload.depth,
},
workspace_id: payload.workspace_id.clone(),
include_edges: payload.include_edges.unwrap_or(true),
filters: KernelProjectionFilter {
node_types: payload.node_types.clone().unwrap_or_default(),
edge_types: payload.edge_types.clone().unwrap_or_default(),
include_deleted: false,
},
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"rootNodeId": payload.root_node_id,
"workspaceId": payload.workspace_id,
"depth": payload.depth,
"includeEdges": payload.include_edges.unwrap_or(true),
"nodeTypes": payload.node_types.unwrap_or_default(),
"edgeTypes": payload.edge_types.unwrap_or_default(),
}),
}))
}
"kernel.children.list" => {
let payload: KernelListChildrenQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.children.list".into(),
payload: KernelListChildren {
parent_node_id: payload.parent_node_id.clone(),
workspace_id: payload.workspace_id.clone(),
node_types: payload.node_types.clone().unwrap_or_default(),
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"parentNodeId": payload.parent_node_id,
"workspaceId": payload.workspace_id,
"nodeTypes": payload.node_types.unwrap_or_default(),
}),
}))
}
"kernel.edges.list" => {
let payload: KernelListEdgesQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.edges.list".into(),
payload: KernelListEdges {
node_id: payload.node_id.clone(),
workspace_id: payload.workspace_id.clone(),
edge_types: payload.edge_types.clone().unwrap_or_default(),
direction: payload.direction.clone(),
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"nodeId": payload.node_id,
"workspaceId": payload.workspace_id,
"edgeTypes": payload.edge_types.unwrap_or_default(),
"direction": payload.direction.unwrap_or(KernelGraphDirection::Both),
}),
}))
}
"kernel.graph.traverse" => {
let payload: KernelTraverseGraphQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.graph.traverse".into(),
payload: KernelTraverseGraph {
start_node_id: payload.start_node_id.clone(),
workspace_id: payload.workspace_id.clone(),
edge_types: payload.edge_types.clone().unwrap_or_default(),
max_depth: payload.max_depth.unwrap_or(2),
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"startNodeId": payload.start_node_id,
"workspaceId": payload.workspace_id,
"edgeTypes": payload.edge_types.unwrap_or_default(),
"maxDepth": payload.max_depth.unwrap_or(2),
}),
}))
}
"kernel.project_view" => {
let payload: KernelProjectViewQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
name: "kernel.project_view".into(),
payload: KernelProjectionRequest {
projection: payload.projection.clone(),
workspace_id: payload.workspace_id.clone(),
root_node_id: payload.root_node_id.clone(),
subtree: payload.root_node_id.as_ref().map(|root_node_id| KernelSubtreeRef {
root_node_id: root_node_id.clone(),
path: vec![root_node_id.clone()],
depth: payload.depth,
}),
filters: KernelProjectionFilter {
node_types: payload.node_types.clone().unwrap_or_default(),
edge_types: payload.edge_types.clone().unwrap_or_default(),
include_deleted: false,
},
include_content: payload.include_content.unwrap_or(false),
include_edges: payload.include_edges.unwrap_or(true),
},
};
let request = build_query_request(&context, &query)?;
Ok(RuntimeExecutionPlan::Query(RuntimeQueryExecutionPlan {
query_name: query.name,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
payload_json: request.payload_json,
args_json: json!({
"projection": payload.projection,
"workspaceId": payload.workspace_id,
"rootNodeId": payload.root_node_id,
"depth": payload.depth,
"includeContent": payload.include_content.unwrap_or(false),
"includeEdges": payload.include_edges.unwrap_or(true),
"nodeTypes": payload.node_types.unwrap_or_default(),
"edgeTypes": payload.edge_types.unwrap_or_default(),
}),
}))
}
"documents.content.get" => {
let payload: DocumentContentQueryPayload = parse_payload(query_wire.payload)?;
let query = QueryEnvelope {
@@ -3021,6 +3311,292 @@ fn delete_mindmap_node_in_children(nodes: &mut Vec<MindmapTreeNode>, uid: &str)
false
}
fn record_field<'a>(value: &'a Value, key: &str) -> Option<&'a Value> {
value.as_object().and_then(|map| map.get(key))
}
fn string_field(value: &Value, key: &str) -> Option<String> {
record_field(value, key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn bool_field(value: &Value, key: &str) -> Option<bool> {
record_field(value, key).and_then(Value::as_bool)
}
fn normalize_kernel_node_from_record(
value: &Value,
workspace_id: Option<&str>,
) -> Result<KernelNode, BridgeError> {
let id = string_field(value, "id")
.or_else(|| string_field(value, "documentId"))
.ok_or_else(|| BridgeError::validation("kernel node 缺少 id"))?;
let title = string_field(value, "title").or_else(|| string_field(value, "text"));
let parent_id = string_field(value, "parent_id")
.or_else(|| string_field(value, "parentId"))
.or_else(|| string_field(value, "document_id"));
let updated_at = string_field(value, "updated_at").or_else(|| string_field(value, "updatedAt"));
let created_at = string_field(value, "created_at").or_else(|| string_field(value, "createdAt"));
let access_scope = string_field(value, "access_scope").or_else(|| string_field(value, "accessScope"));
let is_mindmap = string_field(value, "asset_type")
.map(|asset_type| asset_type == "mindmap")
.unwrap_or(false);
let node_type = if is_mindmap {
KernelNodeType::Mindmap
} else if bool_field(value, "is_template").unwrap_or(false) {
KernelNodeType::Page
} else if value.get("content").is_some() || value.get("rawText").is_some() {
KernelNodeType::ContentNode
} else {
KernelNodeType::Page
};
let mut extra = BTreeMap::new();
if let Some(access_scope) = access_scope {
extra.insert("accessScope".into(), json!(access_scope));
}
if let Some(is_starred) = bool_field(value, "is_starred").or_else(|| bool_field(value, "isStarred")) {
extra.insert("isStarred".into(), json!(is_starred));
}
let content = value
.get("content")
.cloned()
.or_else(|| value.get("rawText").cloned())
.map(|body| KernelContentPayload {
format: if body.is_string() { "text".into() } else { "json".into() },
body,
});
Ok(KernelNode {
id: id.clone(),
node_type,
workspace_id: workspace_id.map(ToOwned::to_owned).or_else(|| string_field(value, "workspace_id")).or_else(|| string_field(value, "workspaceId")),
parent_id,
subtree: Some(KernelSubtreeRef {
root_node_id: id.clone(),
path: vec![id.clone()],
depth: Some(0),
}),
metadata: KernelNodeMetadata {
title,
icon: None,
tags: Vec::new(),
created_at,
updated_at,
extra,
},
content,
refs: None,
audit: KernelAuditStamp::default(),
})
}
fn build_sidebar_kernel_nodes(
data: &Value,
workspace_id: Option<&str>,
) -> Result<Vec<KernelNode>, BridgeError> {
let documents = data
.get("documents")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
documents
.iter()
.map(|item| normalize_kernel_node_from_record(item, workspace_id))
.collect::<Result<Vec<_>, _>>()
}
fn build_sidebar_kernel_edges(nodes: &[KernelNode]) -> Vec<KernelEdge> {
nodes.iter()
.filter_map(|node| {
node.parent_id.as_ref().map(|parent_id| KernelEdge {
id: format!("edge_parent_of_{}_{}", parent_id, node.id),
edge_type: KernelEdgeType::ParentOf,
workspace_id: node.workspace_id.clone(),
from_node_id: parent_id.clone(),
to_node_id: node.id.clone(),
metadata: BTreeMap::new(),
audit: KernelAuditStamp::default(),
})
})
.collect()
}
fn build_kernel_subtree_result(
data: &Value,
root_node_id: &str,
workspace_id: Option<&str>,
depth: Option<u32>,
) -> Result<KernelSubtreeResult, BridgeError> {
let mut all_nodes = build_sidebar_kernel_nodes(data, workspace_id)?;
let max_depth = depth.unwrap_or(u32::MAX);
let mut by_parent = BTreeMap::<Option<String>, Vec<String>>::new();
for node in &all_nodes {
by_parent
.entry(node.parent_id.clone())
.or_default()
.push(node.id.clone());
}
let mut queue = VecDeque::from([(root_node_id.to_string(), 0u32)]);
let mut visited = BTreeMap::<String, u32>::new();
while let Some((node_id, current_depth)) = queue.pop_front() {
if visited.contains_key(&node_id) || current_depth > max_depth {
continue;
}
visited.insert(node_id.clone(), current_depth);
if let Some(children) = by_parent.get(&Some(node_id.clone())) {
for child in children {
queue.push_back((child.clone(), current_depth + 1));
}
}
}
let nodes = all_nodes
.iter_mut()
.filter_map(|node| {
visited.get(&node.id).copied().map(|node_depth| {
node.subtree = Some(KernelSubtreeRef {
root_node_id: root_node_id.to_string(),
path: vec![root_node_id.to_string(), node.id.clone()],
depth: Some(node_depth),
});
node.clone()
})
})
.collect::<Vec<_>>();
let edges = build_sidebar_kernel_edges(&nodes);
Ok(KernelSubtreeResult {
root_node_id: root_node_id.to_string(),
nodes,
edges,
})
}
fn build_kernel_edge_list_result(
data: &Value,
node_id: &str,
workspace_id: Option<&str>,
) -> Result<KernelEdgeListResult, BridgeError> {
let nodes = build_sidebar_kernel_nodes(data, workspace_id)?;
let edges = build_sidebar_kernel_edges(&nodes)
.into_iter()
.filter(|edge| edge.from_node_id == node_id || edge.to_node_id == node_id)
.collect::<Vec<_>>();
Ok(KernelEdgeListResult {
node_id: node_id.to_string(),
edges,
})
}
fn build_kernel_graph_traversal_result(
data: &Value,
start_node_id: &str,
workspace_id: Option<&str>,
max_depth: u32,
) -> Result<KernelGraphTraversalResult, BridgeError> {
let subtree = build_kernel_subtree_result(data, start_node_id, workspace_id, Some(max_depth))?;
let mut visits = Vec::new();
let mut queue = VecDeque::from([(start_node_id.to_string(), 0u32)]);
let parent_map = subtree
.nodes
.iter()
.map(|node| (node.id.clone(), node.parent_id.clone()))
.collect::<BTreeMap<_, _>>();
let mut seen = BTreeMap::<String, bool>::new();
while let Some((node_id, depth)) = queue.pop_front() {
if seen.insert(node_id.clone(), true).is_some() || depth > max_depth {
continue;
}
visits.push(KernelGraphVisit { node_id: node_id.clone(), depth });
for (candidate_id, parent_id) in &parent_map {
if parent_id.as_deref() == Some(node_id.as_str()) {
queue.push_back((candidate_id.clone(), depth + 1));
}
}
}
Ok(KernelGraphTraversalResult {
start_node_id: start_node_id.to_string(),
visited: visits,
edges: subtree.edges,
})
}
fn build_kernel_projection_result(
data: &Value,
projection: KernelProjectionKind,
root_node_id: Option<&str>,
workspace_id: Option<&str>,
depth: Option<u32>,
) -> Result<KernelProjectionResult, BridgeError> {
let subtree = if let Some(root_node_id) = root_node_id {
build_kernel_subtree_result(data, root_node_id, workspace_id, depth)?
} else {
let mut nodes = build_sidebar_kernel_nodes(data, workspace_id)?;
for node in &mut nodes {
node.subtree = Some(KernelSubtreeRef {
root_node_id: node.id.clone(),
path: vec![node.id.clone()],
depth: Some(0),
});
}
let edges = build_sidebar_kernel_edges(&nodes);
KernelSubtreeResult {
root_node_id: "workspace_root".into(),
nodes,
edges,
}
};
let items = subtree
.nodes
.iter()
.map(|node| KernelProjectionItem {
node_id: node.id.clone(),
parent_node_id: node.parent_id.clone(),
node_type: node.node_type.clone(),
title: node.metadata.title.clone(),
depth: node
.subtree
.as_ref()
.and_then(|subtree| subtree.depth)
.unwrap_or(0),
position: node
.metadata
.extra
.get("sortOrder")
.and_then(Value::as_i64),
child_count: subtree
.edges
.iter()
.filter(|edge| edge.from_node_id == node.id && edge.edge_type == KernelEdgeType::ParentOf)
.count() as u32,
expanded_by_default: true,
})
.collect::<Vec<_>>();
Ok(KernelProjectionResult {
projection_id: format!(
"kernel_projection:{}:{}",
match projection {
KernelProjectionKind::SidebarTree => "sidebar_tree",
KernelProjectionKind::PageTree => "page_tree",
KernelProjectionKind::FileTree => "file_tree",
KernelProjectionKind::Mindmap => "mindmap",
KernelProjectionKind::ReadView => "read_view",
KernelProjectionKind::SearchResults => "search_results",
KernelProjectionKind::RagIndex => "rag_index",
},
root_node_id.unwrap_or("root")
),
projection,
root_node_id: root_node_id.map(ToOwned::to_owned),
items,
edges: subtree.edges,
})
}
fn find_mindmap_node_mut<'a>(
node: &'a mut MindmapTreeNode,
uid: &str,
@@ -3042,6 +3618,78 @@ fn execute_query_result(
data: Value,
) -> Result<Value, BridgeError> {
match query_wire.name.as_str() {
"kernel.node.get" => {
let payload: KernelGetNodeQueryPayload = parse_payload(query_wire.payload)?;
let node = normalize_kernel_node_from_record(&data, payload.workspace_id.as_deref())?;
serde_json::to_value(node).map_err(|error| {
BridgeError::transport(format!("kernel.node.get result 序列化失败: {error}"))
})
}
"kernel.subtree.get" => {
let payload: KernelGetSubtreeQueryPayload = parse_payload(query_wire.payload)?;
let result = build_kernel_subtree_result(
&data,
&payload.root_node_id,
payload.workspace_id.as_deref(),
payload.depth,
)?;
serde_json::to_value(result).map_err(|error| {
BridgeError::transport(format!("kernel.subtree.get result 序列化失败: {error}"))
})
}
"kernel.children.list" => {
let payload: KernelListChildrenQueryPayload = parse_payload(query_wire.payload)?;
let subtree = build_kernel_subtree_result(
&data,
&payload.parent_node_id,
payload.workspace_id.as_deref(),
Some(1),
)?;
let nodes = subtree
.nodes
.into_iter()
.filter(|node| node.parent_id.as_deref() == Some(payload.parent_node_id.as_str()))
.collect::<Vec<KernelNode>>();
serde_json::to_value(nodes).map_err(|error| {
BridgeError::transport(format!("kernel.children.list result 序列化失败: {error}"))
})
}
"kernel.edges.list" => {
let payload: KernelListEdgesQueryPayload = parse_payload(query_wire.payload)?;
let result = build_kernel_edge_list_result(
&data,
&payload.node_id,
payload.workspace_id.as_deref(),
)?;
serde_json::to_value(result).map_err(|error| {
BridgeError::transport(format!("kernel.edges.list result 序列化失败: {error}"))
})
}
"kernel.graph.traverse" => {
let payload: KernelTraverseGraphQueryPayload = parse_payload(query_wire.payload)?;
let result = build_kernel_graph_traversal_result(
&data,
&payload.start_node_id,
payload.workspace_id.as_deref(),
payload.max_depth.unwrap_or(2),
)?;
serde_json::to_value(result).map_err(|error| {
BridgeError::transport(format!("kernel.graph.traverse result 序列化失败: {error}"))
})
}
"kernel.project_view" => {
let payload: KernelProjectViewQueryPayload = parse_payload(query_wire.payload)?;
let result = build_kernel_projection_result(
&data,
payload.projection,
payload.root_node_id.as_deref(),
payload.workspace_id.as_deref(),
payload.depth,
)?;
serde_json::to_value(result).map_err(|error| {
BridgeError::transport(format!("kernel.project_view result 序列化失败: {error}"))
})
}
"mindmaps.get" => {
let tree = normalize_mindmap_from_value(&data)?;
serde_json::to_value(tree).map_err(|error| {
@@ -3118,6 +3766,181 @@ fn execute_command(
) -> Result<RuntimeExecutionPlan, BridgeError> {
let context = to_bridge_context(context_wire);
match command_wire.name.as_str() {
"kernel.node.create" => {
let payload: KernelCreateNodeCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
name: "kernel.node.create".into(),
command_id: command_wire.command_id.clone(),
idempotency_key: command_wire.idempotency_key.clone(),
actor: to_actor_payload(&command_wire.actor),
source: to_source_payload(&command_wire.source),
target: to_target_ref(command_wire.target.as_ref()),
payload: KernelCreateNode {
node: payload.node.clone(),
position: payload.position,
},
reason: command_wire.reason,
refs: command_wire.refs,
dry_run: command_wire.dry_run,
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: serde_json::to_value(&payload).map_err(|error| {
BridgeError::transport(format!("kernel.node.create 参数序列化失败: {error}"))
})?,
}))
}
"kernel.node.update" => {
let payload: KernelUpdateNodeCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
name: "kernel.node.update".into(),
command_id: command_wire.command_id.clone(),
idempotency_key: command_wire.idempotency_key.clone(),
actor: to_actor_payload(&command_wire.actor),
source: to_source_payload(&command_wire.source),
target: to_target_ref(command_wire.target.as_ref()),
payload: KernelUpdateNode {
node_id: payload.node_id.clone(),
metadata: payload.metadata.clone(),
content: payload.content.clone(),
refs: payload.refs.clone(),
audit: payload.audit.clone(),
},
reason: command_wire.reason,
refs: command_wire.refs,
dry_run: command_wire.dry_run,
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: serde_json::to_value(&payload).map_err(|error| {
BridgeError::transport(format!("kernel.node.update 参数序列化失败: {error}"))
})?,
}))
}
"kernel.subtree.move" => {
let payload: KernelMoveSubtreeCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
name: "kernel.subtree.move".into(),
command_id: command_wire.command_id.clone(),
idempotency_key: command_wire.idempotency_key.clone(),
actor: to_actor_payload(&command_wire.actor),
source: to_source_payload(&command_wire.source),
target: to_target_ref(command_wire.target.as_ref()),
payload: KernelMoveSubtree {
subtree: payload.subtree.clone(),
new_parent_node_id: payload.new_parent_node_id.clone(),
sort_order: payload.sort_order,
},
reason: command_wire.reason,
refs: command_wire.refs,
dry_run: command_wire.dry_run,
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: serde_json::to_value(&payload).map_err(|error| {
BridgeError::transport(format!("kernel.subtree.move 参数序列化失败: {error}"))
})?,
}))
}
"kernel.edge.attach" => {
let payload: KernelAttachEdgeCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
name: "kernel.edge.attach".into(),
command_id: command_wire.command_id.clone(),
idempotency_key: command_wire.idempotency_key.clone(),
actor: to_actor_payload(&command_wire.actor),
source: to_source_payload(&command_wire.source),
target: to_target_ref(command_wire.target.as_ref()),
payload: KernelAttachEdge {
edge: payload.edge.clone(),
},
reason: command_wire.reason,
refs: command_wire.refs,
dry_run: command_wire.dry_run,
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: serde_json::to_value(&payload).map_err(|error| {
BridgeError::transport(format!("kernel.edge.attach 参数序列化失败: {error}"))
})?,
}))
}
"kernel.edge.detach" => {
let payload: KernelDetachEdgeCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
name: "kernel.edge.detach".into(),
command_id: command_wire.command_id.clone(),
idempotency_key: command_wire.idempotency_key.clone(),
actor: to_actor_payload(&command_wire.actor),
source: to_source_payload(&command_wire.source),
target: to_target_ref(command_wire.target.as_ref()),
payload: KernelDetachEdge {
edge_id: payload.edge_id.clone(),
from_node_id: payload.from_node_id.clone(),
to_node_id: payload.to_node_id.clone(),
edge_type: payload.edge_type.clone(),
},
reason: command_wire.reason,
refs: command_wire.refs,
dry_run: command_wire.dry_run,
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: serde_json::to_value(&payload).map_err(|error| {
BridgeError::transport(format!("kernel.edge.detach 参数序列化失败: {error}"))
})?,
}))
}
"blocks.patch" => {
let payload: BlockPatchCommandPayload = parse_payload(command_wire.payload.clone())?;
let command = CommandEnvelope {
@@ -4842,6 +5665,104 @@ mod tests {
}
}
#[test]
fn kernel_project_view_query_executes_into_sidebar_projection() {
let result = execute_runtime_query(RuntimeInput::Query {
context: demo_context(),
query: RuntimeQueryEnvelopeWire {
name: "kernel.project_view".into(),
payload: json!({
"projection": "sidebar_tree",
"workspaceId": "ws_1",
"rootNodeId": "page_root",
"depth": 2,
"includeEdges": true,
"nodeTypes": ["page"],
}),
},
data: Some(json!({
"documents": [
{
"id": "page_root",
"workspace_id": "ws_1",
"title": "根页面",
"parent_id": null,
"sort_order": 0,
"is_starred": true,
"is_template": false,
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
},
{
"id": "page_child",
"workspace_id": "ws_1",
"title": "子页面",
"parent_id": "page_root",
"sort_order": 1,
"is_starred": false,
"is_template": false,
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
}
]
})),
})
.expect("kernel projection result should build");
assert_eq!(result["projection"], json!("sidebar_tree"));
assert_eq!(result["rootNodeId"], json!("page_root"));
assert_eq!(result["items"][0]["nodeId"], json!("page_root"));
assert_eq!(result["items"][1]["parentNodeId"], json!("page_root"));
}
#[test]
fn kernel_subtree_query_executes_into_unified_subtree() {
let result = execute_runtime_query(RuntimeInput::Query {
context: demo_context(),
query: RuntimeQueryEnvelopeWire {
name: "kernel.subtree.get".into(),
payload: json!({
"workspaceId": "ws_1",
"rootNodeId": "page_root",
"depth": 2,
"includeEdges": true,
"nodeTypes": ["page"],
}),
},
data: Some(json!({
"documents": [
{
"id": "page_root",
"workspace_id": "ws_1",
"title": "根页面",
"parent_id": null,
"sort_order": 0,
"is_starred": true,
"is_template": false,
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
},
{
"id": "page_child",
"workspace_id": "ws_1",
"title": "子页面",
"parent_id": "page_root",
"sort_order": 1,
"is_starred": false,
"is_template": false,
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
}
]
})),
})
.expect("kernel subtree result should build");
assert_eq!(result["rootNodeId"], json!("page_root"));
assert_eq!(result["nodes"].as_array().map(Vec::len), Some(2));
assert_eq!(result["edges"].as_array().map(Vec::len), Some(1));
}
#[test]
fn index_rebuild_tool_executes_in_rust_runtime() {
let result = execute_runtime_query(RuntimeInput::Tool {