use adapter_onlyoffice::{
    prepare_callback, prepare_forcesave, prepare_proxy_request, resolve_session, sign_config,
    OnlyOfficeCallbackPreparationInput, OnlyOfficeForcesavePreparationInput,
    OnlyOfficeProxyPreparationInput, OnlyOfficeSessionResolveInput,
};
use core_domain::Timestamp;
use core_protocol::editor::{EditorBlockDocumentTiptapBridge, TiptapNode};
use core_protocol::{
    default_tool_registry, invocation_kind_label, tool_effect_label, ActorPayload, BlockProps,
    CommandEnvelope, ContentNode, ContentNodePayload, DocumentContentResult,
    DocumentReadEvidenceItem, DocumentReadEvidenceKind, DocumentReadNode, DocumentReadNodeMeta,
    DocumentReadNodeType, DocumentReadOutlineEntry, DocumentReadPageSubtree, DocumentReadStats,
    DocumentReadSubtree, EditorBlock, EditorBlockDocument, EditorBlockType, EditorCommand,
    EditorInsertBlockAfter, EditorReplaceBlock, EmbedBlock, GetBlock, GetBridgeCommand,
    GetBridgeRequest, GetBridgeTrace, GetMindmap, InvocationKind, KernelAttachEdge,
    KernelAuditStamp, KernelContentPayload, KernelCreateNode, KernelDetachEdge, KernelEdge,
    KernelEdgeListResult, KernelEdgeType, KernelGetNode, KernelGetSubtree, KernelGraphDirection,
    KernelGraphTraversalResult, KernelGraphVisit, KernelListChildren, KernelListEdges,
    KernelMoveSubtree, KernelNode, KernelNodeMetadata, KernelNodeType, KernelProjectionAssetKind,
    KernelProjectionCapability, KernelProjectionFilter, KernelProjectionItem, KernelProjectionKind,
    KernelProjectionRequest, KernelProjectionResourceKind, KernelProjectionResourceMeta,
    KernelProjectionResult, KernelProjectionRowKind, 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::{
    can_rebuild_from_events, evaluate_search_documents, rebuild_from_events, IndexCursor,
    MinimalWorkspaceProjector, SearchDocumentsDataset, SearchDocumentsEvaluation,
    SearchDocumentsRequest,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::{BTreeMap, VecDeque};
use std::env;
use std::sync::atomic::{AtomicU64, Ordering};
use storage_convex_bridge::{
    build_query_request, build_write_request, BridgeContext, BridgeError, BridgeErrorKind,
};

static TOOL_BLOCK_COUNTER: AtomicU64 = AtomicU64::new(1);
static MINDMAP_UID_COUNTER: AtomicU64 = AtomicU64::new(1);

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum RuntimeInput {
    Query {
        context: RuntimeBridgeContextWire,
        query: RuntimeQueryEnvelopeWire,
        #[serde(default)]
        data: Option<Value>,
    },
    Command {
        context: RuntimeBridgeContextWire,
        command: RuntimeCommandEnvelopeWire,
    },
    Tool {
        context: RuntimeBridgeContextWire,
        tool: RuntimeToolInvocationWire,
        #[serde(default)]
        data: Option<Value>,
    },
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeBridgeContextWire {
    pub deployment_id: Option<String>,
    pub project_id: Option<String>,
    pub workspace_id: Option<String>,
    pub request_id: String,
    pub trace_id: String,
    pub actor: RuntimeActorWire,
    pub source: RuntimeSourceWire,
    pub tenant_id: Option<String>,
    pub auth_token: Option<String>,
    pub idempotency_key: Option<String>,
    pub validate_only: bool,
    pub dry_run: bool,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeActorWire {
    pub actor_type: String,
    pub actor_id: String,
    pub session_id: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeSourceWire {
    pub channel: String,
    pub client: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeTargetWire {
    pub workspace_id: Option<String>,
    pub page_id: Option<String>,
    pub block_id: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeQueryEnvelopeWire {
    pub name: String,
    pub payload: Value,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeCommandEnvelopeWire {
    pub name: String,
    pub command_id: String,
    pub idempotency_key: Option<String>,
    pub actor: RuntimeActorWire,
    pub source: RuntimeSourceWire,
    pub target: Option<RuntimeTargetWire>,
    pub payload: Value,
    pub reason: Option<String>,
    pub refs: Vec<String>,
    pub dry_run: bool,
    pub validate_only: bool,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeToolInvocationWire {
    pub tool: String,
    pub kind: String,
    pub mode: Option<String>,
    pub args_json: Value,
    pub target: Option<RuntimeTargetWire>,
    pub reason: Option<String>,
    #[serde(default)]
    pub refs: Vec<String>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapEmptyTrashToolPayload {
    workspace_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapOutlineItemPayload {
    title: String,
    level: u32,
    page: u32,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapOutlineToolPayload {
    document_id: String,
    mindmap_id: String,
    root_title: String,
    page_link_pattern: String,
    outline: Vec<MindmapOutlineItemPayload>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocsSearchDatasetPayload {
    workspace_id: String,
    documents: Vec<index_fts::SearchDocumentRecord>,
    mindmaps: Vec<index_fts::SearchMindmapRecord>,
    tables: Vec<index_fts::SearchTableRecord>,
    table_rows: Vec<index_fts::SearchTableRowRecord>,
    assets: Vec<index_fts::SearchAssetRecord>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocsSearchToolPayload {
    query: String,
    workspace_id: Option<String>,
    limit: Option<u32>,
    include_deleted: Option<bool>,
    page_id: Option<String>,
    title_only: Option<bool>,
    exact: Option<bool>,
    include_ocr: Option<bool>,
    time_range: Option<String>,
    time_field: Option<String>,
    custom_range_from: Option<String>,
    custom_range_to: Option<String>,
    #[serde(default)]
    datasets: Vec<DocsSearchDatasetPayload>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocsReadToolPayload {
    document_id: String,
    max_chars: Option<u32>,
    include_content: Option<bool>,
    title: Option<String>,
    workspace_id: Option<String>,
    parent_id: Option<String>,
    updated_at: Option<String>,
    raw_text_length: Option<u64>,
    raw_text: Option<String>,
    content: Option<Value>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeSuccess {
    pub ok: bool,
    pub plan: RuntimeExecutionPlan,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeFailure {
    pub ok: bool,
    pub error: RuntimeErrorPayload,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct RuntimeErrorPayload {
    pub kind: String,
    pub message: String,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum RuntimeExecutionPlan {
    Query(RuntimeQueryExecutionPlan),
    Command(RuntimeCommandExecutionPlan),
    Tool(RuntimeToolExecutionPlan),
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeQueryResult {
    pub result: Value,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeQueryExecutionPlan {
    pub query_name: String,
    pub function_name: String,
    pub workspace_id: Option<String>,
    pub request_id: String,
    pub trace_id: String,
    pub actor_id: String,
    pub payload_json: String,
    pub args_json: Value,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeCommandExecutionPlan {
    pub command_name: String,
    pub command_id: String,
    pub function_name: String,
    pub workspace_id: Option<String>,
    pub request_id: String,
    pub trace_id: String,
    pub actor_id: String,
    pub idempotency_key: Option<String>,
    pub payload_json: String,
    pub args_json: Value,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeToolExecutionPlan {
    pub tool_name: String,
    pub invocation_kind: String,
    pub execution_mode: String,
    pub effect: String,
    pub toolset_id: String,
    pub requires_confirmation: bool,
    pub request_id: String,
    pub trace_id: String,
    pub actor_id: String,
    pub validate_only: bool,
    pub dry_run: bool,
    pub payload_json: String,
    pub args_json: Value,
    pub target: Option<Value>,
    pub steps: Vec<RuntimeToolPlanStep>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeToolPlanStep {
    pub kind: String,
    pub name: String,
    pub function_name: Option<String>,
    pub description: String,
    pub args_json: Value,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BlockGetQueryPayload {
    block_id: String,
    workspace_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BlockPatchCommandPayload {
    document_id: String,
    workspace_id: Option<String>,
    block_id: String,
    next_block: Value,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BlockMoveCommandPayload {
    source_document_id: String,
    block_id: String,
    target_document_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BlockEmbedCommandPayload {
    source_document_id: String,
    block_id: String,
    target_document_id: String,
    target_block_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentContentQueryPayload {
    document_id: String,
    workspace_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapGetQueryPayload {
    document_id: String,
    mindmap_id: String,
    workspace_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
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 {
    query: String,
    workspace_id: String,
    page_id: Option<String>,
    limit: Option<u32>,
    cursor: Option<String>,
    title_only: Option<bool>,
    exact: Option<bool>,
    include_ocr: Option<bool>,
    time_range: Option<String>,
    time_field: Option<String>,
    custom_range_from: Option<String>,
    custom_range_to: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SearchRecentQueryPayload {
    workspace_id: String,
    limit: Option<u32>,
    cursor: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BridgeRequestQueryPayload {
    workspace_id: String,
    request_id: String,
    command_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BridgeTraceQueryPayload {
    workspace_id: String,
    trace_id: String,
    command_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BridgeCommandQueryPayload {
    workspace_id: String,
    command_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BridgeWorkspaceOverviewQueryPayload {
    workspace_id: String,
    limit: Option<u32>,
    cursor: Option<String>,
    command_status: Option<String>,
    event_status: Option<String>,
    target_page_id: Option<String>,
    target_block_id: Option<String>,
    aggregate_type: Option<String>,
    aggregate_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentTitleCommandPayload {
    document_id: String,
    title: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentSaveCommandPayload {
    document_id: String,
    workspace_id: Option<String>,
    revision: Option<u64>,
    #[serde(default)]
    editor_document: Option<Value>,
    #[serde(default)]
    content: Value,
    #[serde(default)]
    tiptap_document: Option<Value>,
    conflict_detection_key: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentEmbedCommandPayload {
    document_id: String,
    workspace_id: Option<String>,
    revision: Option<u64>,
    content: Value,
    conflict_detection_key: Option<String>,
    source_document_id: String,
    target_document_id: String,
    anchor_block_id: 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 {
    document_id: String,
    mindmap_id: String,
    data: Value,
    create_only: Option<bool>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapDeleteCommandPayload {
    document_id: String,
    mindmap_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapRestoreCommandPayload {
    document_id: String,
    mindmap_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapPurgeCommandPayload {
    document_id: String,
    mindmap_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MindmapEmptyTrashCommandPayload {
    workspace_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentCreateCommandPayload {
    document_id: String,
    workspace_id: String,
    parent_id: Option<String>,
    title: String,
    access_scope: String,
    content: Value,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentMoveCommandPayload {
    document_id: String,
    parent_id: Option<String>,
    sort_order: i64,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentDeleteCommandPayload {
    document_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentRestoreCommandPayload {
    document_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentDuplicateCommandPayload {
    source_document_id: String,
    new_document_id: String,
    title: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentTemplateCommandPayload {
    document_id: String,
    is_template: bool,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentEmptyTrashCommandPayload {
    workspace_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentPurgeCommandPayload {
    document_id: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentCopyTreeItemPayload {
    document_id: String,
    recursive: bool,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DocumentCopyTreeCommandPayload {
    items: Vec<DocumentCopyTreeItemPayload>,
    target_parent_id: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct RuntimeBlockSummary {
    id: String,
    #[serde(rename = "type")]
    block_type: String,
    text: String,
    depth: usize,
    child_count: usize,
}

#[derive(Debug, Clone)]
struct RuntimeInsertSpec {
    block_type: String,
    text: String,
    level: u64,
}

#[derive(Debug)]
struct InsertBlocksResult {
    inserted: Vec<String>,
    blocks: Vec<Value>,
    editor_commands: Vec<EditorCommand>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct RuntimeMindmapSummary {
    uid: String,
    text: String,
    parent_uid: Option<String>,
    depth: usize,
    child_count: usize,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct RuntimeMindmapSubtreeSummary {
    uid: String,
    text: String,
    depth: usize,
    child_count: usize,
}

pub fn execute_runtime_input(input: RuntimeInput) -> Result<RuntimeExecutionPlan, BridgeError> {
    match input {
        RuntimeInput::Query { context, query, .. } => execute_query(context, query),
        RuntimeInput::Command { context, command } => execute_command(context, command),
        RuntimeInput::Tool { context, tool, .. } => execute_tool_plan(context, tool),
    }
}

pub fn execute_runtime_query(input: RuntimeInput) -> Result<Value, BridgeError> {
    match input {
        RuntimeInput::Query {
            context,
            query,
            data,
        } => execute_query_result(context, query, data.unwrap_or(Value::Null)),
        RuntimeInput::Tool {
            context,
            tool,
            data,
        } => execute_tool_result(context, tool, data.unwrap_or(Value::Null)),
        RuntimeInput::Command { .. } => Err(BridgeError::validation(
            "execute_runtime_query 仅支持 query 输入",
        )),
    }
}

pub fn build_success_response(plan: RuntimeExecutionPlan) -> RuntimeSuccess {
    RuntimeSuccess { ok: true, plan }
}

pub fn build_failure_response(error: BridgeError) -> RuntimeFailure {
    RuntimeFailure {
        ok: false,
        error: RuntimeErrorPayload {
            kind: bridge_error_kind_to_wire(&error.kind).into(),
            message: error.message,
        },
    }
}

pub fn runtime_input_requests_result(input: &RuntimeInput) -> bool {
    match input {
        RuntimeInput::Query { data: Some(_), .. } => true,
        RuntimeInput::Tool { tool, .. } => {
            parse_tool_mode(tool.mode.as_deref()).unwrap_or(ToolExecutionMode::Plan)
                == ToolExecutionMode::Result
        }
        RuntimeInput::Command { .. } | RuntimeInput::Query { data: None, .. } => false,
    }
}

fn execute_query(
    context_wire: RuntimeBridgeContextWire,
    query_wire: RuntimeQueryEnvelopeWire,
) -> 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 {
                name: "documents.content.get".into(),
                payload: core_protocol::GetPageContent {
                    page_id: payload.document_id.clone(),
                    workspace_id: payload.workspace_id,
                },
            };
            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!({
                    "id": payload.document_id,
                }),
            }))
        }
        "mindmaps.get" => {
            let payload: MindmapGetQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "mindmaps.get".into(),
                payload: GetMindmap {
                    document_id: payload.document_id.clone(),
                    mindmap_id: payload.mindmap_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!({
                    "docId": payload.document_id,
                    "mindmapId": payload.mindmap_id,
                }),
            }))
        }
        "blocks.get" => {
            let payload: BlockGetQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "blocks.get".into(),
                payload: GetBlock {
                    block_id: payload.block_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!({
                    "id": payload.block_id,
                    "workspaceId": payload.workspace_id,
                }),
            }))
        }
        "sidebar.dataset.list" => {
            let payload: SidebarDatasetQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "sidebar.dataset.list".into(),
                payload: core_protocol::ListSidebarDataset {
                    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!({
                    "workspaceId": payload.workspace_id,
                }),
            }))
        }
        "search.documents" => {
            let payload: SearchDocumentsQueryPayload = parse_payload(query_wire.payload)?;
            let time_range = payload.time_range.clone().unwrap_or_else(|| "any".into());
            let time_field = payload
                .time_field
                .clone()
                .unwrap_or_else(|| "updated".into());
            let query = QueryEnvelope {
                name: "search.documents".into(),
                payload: SearchDocuments {
                    query: payload.query.clone(),
                    workspace_id: payload.workspace_id.clone(),
                    page_id: payload.page_id.clone(),
                    pagination: core_protocol::query::Pagination {
                        limit: payload.limit.unwrap_or(30),
                        cursor: payload.cursor.clone(),
                    },
                    title_only: payload.title_only.unwrap_or(false),
                    exact: payload.exact.unwrap_or(false),
                    include_ocr: payload.include_ocr.unwrap_or(false),
                    time_range: time_range.clone(),
                    time_field: time_field.clone(),
                    custom_range_from: payload.custom_range_from.clone(),
                    custom_range_to: payload.custom_range_to.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!({
                    "query": payload.query,
                    "workspaceId": payload.workspace_id,
                    "pageId": payload.page_id,
                    "limit": payload.limit.unwrap_or(30),
                    "cursor": payload.cursor,
                    "titleOnly": payload.title_only.unwrap_or(false),
                    "exact": payload.exact.unwrap_or(false),
                    "includeOcr": payload.include_ocr.unwrap_or(false),
                    "timeRange": time_range,
                    "timeField": time_field,
                    "customRangeFrom": payload.custom_range_from,
                    "customRangeTo": payload.custom_range_to,
                }),
            }))
        }
        "search.recent" => {
            let payload: SearchRecentQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "search.recent".into(),
                payload: SearchRecent {
                    workspace_id: payload.workspace_id.clone(),
                    pagination: core_protocol::query::Pagination {
                        limit: payload.limit.unwrap_or(10),
                        cursor: payload.cursor.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!({
                    "workspaceId": payload.workspace_id,
                    "limit": payload.limit.unwrap_or(10),
                    "cursor": payload.cursor,
                }),
            }))
        }
        "bridge.request.get" => {
            let payload: BridgeRequestQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "bridge.request.get".into(),
                payload: GetBridgeRequest {
                    workspace_id: payload.workspace_id.clone(),
                    request_id: payload.request_id.clone(),
                    command_id: payload.command_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!({
                    "workspaceId": payload.workspace_id,
                    "requestId": payload.request_id,
                    "commandId": payload.command_id,
                }),
            }))
        }
        "bridge.trace.get" => {
            let payload: BridgeTraceQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "bridge.trace.get".into(),
                payload: GetBridgeTrace {
                    workspace_id: payload.workspace_id.clone(),
                    trace_id: payload.trace_id.clone(),
                    command_id: payload.command_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!({
                    "workspaceId": payload.workspace_id,
                    "traceId": payload.trace_id,
                    "commandId": payload.command_id,
                }),
            }))
        }
        "bridge.command.get" => {
            let payload: BridgeCommandQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "bridge.command.get".into(),
                payload: GetBridgeCommand {
                    workspace_id: payload.workspace_id.clone(),
                    command_id: payload.command_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!({
                    "workspaceId": payload.workspace_id,
                    "commandId": payload.command_id,
                }),
            }))
        }
        "bridge.workspace.overview" => {
            let payload: BridgeWorkspaceOverviewQueryPayload = parse_payload(query_wire.payload)?;
            let query = QueryEnvelope {
                name: "bridge.workspace.overview".into(),
                payload: ListBridgeWorkspaceOverview {
                    workspace_id: payload.workspace_id.clone(),
                    pagination: core_protocol::query::Pagination {
                        limit: payload.limit.unwrap_or(50),
                        cursor: payload.cursor.clone(),
                    },
                    command_status: payload.command_status.clone(),
                    event_status: payload.event_status.clone(),
                    target_page_id: payload.target_page_id.clone(),
                    target_block_id: payload.target_block_id.clone(),
                    aggregate_type: payload.aggregate_type.clone(),
                    aggregate_id: payload.aggregate_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!({
                    "workspaceId": payload.workspace_id,
                    "limit": payload.limit.unwrap_or(50),
                    "cursor": payload.cursor,
                    "commandStatus": payload.command_status,
                    "eventStatus": payload.event_status,
                    "targetPageId": payload.target_page_id,
                    "targetBlockId": payload.target_block_id,
                    "aggregateType": payload.aggregate_type,
                    "aggregateId": payload.aggregate_id,
                }),
            }))
        }
        other => Err(BridgeError::validation(format!(
            "bridge runtime 暂不支持 query: {other}"
        ))),
    }
}

fn execute_tool_plan(
    context_wire: RuntimeBridgeContextWire,
    tool_wire: RuntimeToolInvocationWire,
) -> Result<RuntimeExecutionPlan, BridgeError> {
    let context = to_bridge_context(context_wire.clone());
    let invocation = to_tool_invocation(&tool_wire)?;
    let spec = default_tool_registry()
        .tool(invocation.tool.as_str())
        .ok_or_else(|| {
            BridgeError::validation(format!("bridge runtime 暂不支持 tool: {}", invocation.tool))
        })?;
    let payload_json = build_tool_payload_json(&context_wire, &invocation);
    let steps = build_tool_plan_steps(&context, &tool_wire, &invocation)?;

    Ok(RuntimeExecutionPlan::Tool(RuntimeToolExecutionPlan {
        tool_name: invocation.tool,
        invocation_kind: invocation_kind_label(&invocation.kind).into(),
        execution_mode: core_protocol::tool_mode_label(&invocation.mode).into(),
        effect: tool_effect_label(&spec.effect).into(),
        toolset_id: spec.toolset_id.into(),
        requires_confirmation: spec.requires_confirmation,
        request_id: context.request_id,
        trace_id: context.trace_id,
        actor_id: context.actor_id,
        validate_only: context.validate_only,
        dry_run: context.dry_run,
        payload_json,
        args_json: tool_wire.args_json,
        target: tool_wire.target.as_ref().map(target_to_json),
        steps,
    }))
}

fn execute_tool_result(
    _context_wire: RuntimeBridgeContextWire,
    tool_wire: RuntimeToolInvocationWire,
    data: Value,
) -> Result<Value, BridgeError> {
    let invocation = to_tool_invocation(&tool_wire)?;
    let spec = default_tool_registry()
        .tool(invocation.tool.as_str())
        .ok_or_else(|| {
            BridgeError::validation(format!("bridge runtime 暂不支持 tool: {}", invocation.tool))
        })?;
    let args = tool_wire.args_json;

    match spec.name {
        "search_web" => {
            let query = read_required_string_field(&args, "query")?;
            let count = read_u64_field(&args, "count").unwrap_or(6).clamp(1, 10);
            let base = env::var("SEARXNG_BASE_URL")
                .unwrap_or_else(|_| "http://127.0.0.1:8889".into())
                .trim()
                .trim_end_matches('/')
                .to_string();
            let token = env::var("SEARXNG_API_TOKEN")
                .unwrap_or_default()
                .trim()
                .to_string();
            let url = format!(
                "{base}/search?q={}&format=json&language=zhh-CN&categories=general&safesearch=1",
                urlencoding::encode(&query)
            );
            let payload = fetch_searxng_json(&url, &token)?;
            let results = payload
                .get("results")
                .and_then(Value::as_array)
                .cloned()
                .unwrap_or_default()
                .into_iter()
                .filter_map(|row| {
                    let title = read_string_value(&row, "title")?;
                    let url = read_string_value(&row, "url")?;
                    if !(url.starts_with("http://") || url.starts_with("https://")) {
                        return None;
                    }
                    Some(json!({
                        "title": title,
                        "url": url,
                        "snippet": read_string_value(&row, "content").or_else(|| read_string_value(&row, "snippet")),
                        "engine": read_string_value(&row, "engine"),
                    }))
                })
                .take(count as usize)
                .collect::<Vec<Value>>();
            return Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "query": query,
                "results": results,
            }));
        }
        "image_read" => {
            let input = merge_tool_input(&args, &data);
            return Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "found": input.get("asset").is_some(),
                "asset": input.get("asset").cloned().unwrap_or(Value::Null),
                "ocrStatus": input.get("ocrStatus").cloned().unwrap_or(Value::Null),
                "ocrText": input.get("ocrText").cloned().unwrap_or(Value::Null),
                "hasOcrText": input.get("hasOcrText").cloned().unwrap_or(Value::Null),
                "note": input.get("note").cloned().unwrap_or(Value::Null),
            }));
        }
        "slash_run" => {
            let input = merge_tool_input(&args, &data);
            let parsed = parse_slash_command_payload(&input)?;
            return Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "parsed": parsed,
            }));
        }
        "onlyoffice_session_resolve" => {
            let input: OnlyOfficeSessionResolveInput = parse_tool_input(&args, &data)?;
            let result = resolve_session(input);
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "locator": result.locator,
                "session": result.session,
            }))
        }
        "onlyoffice_sign" => {
            let input = merge_tool_input(&args, &data);
            let config = input
                .get("config")
                .cloned()
                .ok_or_else(|| BridgeError::validation("onlyoffice_sign 缺少 config"))?;
            let secret = input.get("secret").and_then(Value::as_str).unwrap_or("");
            let tokens = sign_config(&config, secret).map_err(BridgeError::validation)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "token": tokens.token,
                "documentToken": tokens.document_token,
                "editorConfigToken": tokens.editor_config_token,
            }))
        }
        "onlyoffice_prepare_proxy" => {
            let input: OnlyOfficeProxyPreparationInput = parse_tool_input(&args, &data)?;
            let result = prepare_proxy_request(input).map_err(BridgeError::validation)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "targetUrl": result.target_url,
                "forwardHeaders": result.forward_headers,
            }))
        }
        "onlyoffice_prepare_callback" => {
            let input: OnlyOfficeCallbackPreparationInput = parse_tool_input(&args, &data)?;
            let result = prepare_callback(input).map_err(BridgeError::validation)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "shouldWrite": result.should_write,
                "downloadUrl": result.download_url,
                "idempotencyKey": result.idempotency_key,
                "locator": result.locator,
                "session": result.session,
            }))
        }
        "onlyoffice_prepare_forcesave" => {
            let input: OnlyOfficeForcesavePreparationInput = parse_tool_input(&args, &data)?;
            let requests = prepare_forcesave(input).map_err(BridgeError::validation)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "requests": requests,
            }))
        }
        "bridge_request_get" => normalize_bridge_observability_result(&data, "bridge.request.get"),
        "bridge_trace_get" => normalize_bridge_observability_result(&data, "bridge.trace.get"),
        "bridge_command_get" => normalize_bridge_observability_result(&data, "bridge.command.get"),
        "event_replay" => execute_event_replay(&args, &data),
        "index_rebuild" => execute_index_rebuild(&args, &data),
        "doc_get" => {
            let blocks = normalize_blocks_from_value(&data);
            let max_blocks = read_u64_field(&args, "maxBlocks").unwrap_or(80);
            let summaries = walk_block_summaries(&blocks, max_blocks as usize);
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "totalTopLevelBlocks": blocks.len(),
                "blocks": summaries,
            }))
        }
        "doc_find" => {
            let query = read_required_string_field(&args, "query")?;
            let max_results = read_u64_field(&args, "maxResults").unwrap_or(8);
            let blocks = normalize_blocks_from_value(&data);
            let summaries = walk_block_summaries(&blocks, 400);
            let needle = query.to_lowercase();
            let results = summaries
                .into_iter()
                .filter(|summary| summary.text.to_lowercase().contains(&needle))
                .take(max_results as usize)
                .collect::<Vec<RuntimeBlockSummary>>();
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "query": query,
                "results": results,
            }))
        }
        "docs_search" => {
            let payload: DocsSearchToolPayload = parse_tool_input(&args, &data)?;
            let normalized_query = payload.query.trim().to_string();
            if normalized_query.is_empty() {
                return Ok(json!({
                    "ok": true,
                    "source": infer_tool_source(&data),
                    "query": normalized_query,
                    "results": [],
                    "enqueueAssetIds": [],
                }));
            }
            let limit = payload.limit.unwrap_or(12).clamp(1, 30) as usize;
            let requested_workspace = payload
                .workspace_id
                .as_ref()
                .map(|value| value.trim())
                .filter(|value| !value.is_empty())
                .map(str::to_string);
            let mut enqueue_asset_ids = Vec::<String>::new();
            let mut results = Vec::<Value>::new();
            for dataset in payload.datasets {
                if let Some(workspace_id) = requested_workspace.as_ref() {
                    if dataset.workspace_id != *workspace_id {
                        continue;
                    }
                }
                let evaluation = evaluate_search_documents(
                    &SearchDocumentsRequest {
                        query: normalized_query.clone(),
                        workspace_id: dataset.workspace_id.clone(),
                        page_id: payload.page_id.clone(),
                        limit,
                        title_only: payload.title_only.unwrap_or(false),
                        exact: payload.exact.unwrap_or(false),
                        include_ocr: payload.include_ocr.unwrap_or(false),
                        time_range: payload.time_range.clone().unwrap_or_else(|| "any".into()),
                        time_field: payload
                            .time_field
                            .clone()
                            .unwrap_or_else(|| "updated".into()),
                        custom_range_from: payload.custom_range_from.clone(),
                        custom_range_to: payload.custom_range_to.clone(),
                    },
                    &SearchDocumentsDataset {
                        documents: dataset.documents,
                        mindmaps: dataset.mindmaps,
                        tables: dataset.tables,
                        table_rows: dataset.table_rows,
                        assets: dataset.assets,
                    },
                );
                enqueue_asset_ids.extend(evaluation.enqueue_asset_ids);
                for item in evaluation.results {
                    results.push(json!({
                        "id": item.id,
                        "title": item.title,
                        "snippet": item.snippet,
                        "updatedAt": item.updated_at,
                        "createdAt": item.created_at,
                        "matchField": item.match_field,
                        "hasOcr": item.has_ocr,
                        "publicPath": item.public_path,
                        "score": item.score,
                        "workspaceId": dataset.workspace_id,
                    }));
                }
            }
            results.sort_by(|left, right| {
                let left_score = left.get("score").and_then(Value::as_f64).unwrap_or(0.0);
                let right_score = right.get("score").and_then(Value::as_f64).unwrap_or(0.0);
                right_score
                    .partial_cmp(&left_score)
                    .unwrap_or(std::cmp::Ordering::Equal)
                    .then_with(|| {
                        let left_updated =
                            left.get("updatedAt").and_then(Value::as_str).unwrap_or("");
                        let right_updated =
                            right.get("updatedAt").and_then(Value::as_str).unwrap_or("");
                        right_updated.cmp(left_updated)
                    })
            });
            results.truncate(limit);
            enqueue_asset_ids.sort();
            enqueue_asset_ids.dedup();
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "query": normalized_query,
                "results": results,
                "enqueueAssetIds": enqueue_asset_ids,
                "includeDeleted": payload.include_deleted.unwrap_or(false),
            }))
        }
        "docs_read" => {
            let payload: DocsReadToolPayload = parse_tool_input(&args, &data)?;
            let max_chars = payload.max_chars.unwrap_or(2500).clamp(200, 20_000) as usize;
            let raw_text = payload.raw_text.unwrap_or_default();
            let raw_text_length = payload
                .raw_text_length
                .unwrap_or_else(|| raw_text.chars().count() as u64);
            let trimmed = trim_text_for_docs_read(&raw_text, max_chars);
            let mut result = json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "documentId": payload.document_id,
                "title": payload.title.unwrap_or_else(|| "".into()),
                "workspaceId": payload.workspace_id.unwrap_or_else(|| "".into()),
                "parentId": payload.parent_id,
                "updatedAt": payload.updated_at,
                "rawTextLength": raw_text_length,
                "rawText": trimmed,
            });
            if payload.include_content.unwrap_or(false) {
                if let Some(map) = result.as_object_mut() {
                    map.insert("content".into(), payload.content.unwrap_or(Value::Null));
                }
            }
            Ok(result)
        }
        "doc_insert_blocks" => {
            let blocks = normalize_blocks_from_value(&data);
            let specs = parse_insert_specs(&args)?;
            let after_block_id = read_optional_string_field(&args, "afterBlockId");
            let before_block_id = read_optional_string_field(&args, "beforeBlockId");
            let result = apply_insert_blocks(blocks, after_block_id, before_block_id, specs)?;
            let editor_command_source = if result.editor_commands.is_empty() {
                "legacy_snapshot_fallback"
            } else {
                "rust_editor_command"
            };
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "inserted": result.inserted,
                "editorCommandSource": editor_command_source,
                "editorCommands": result.editor_commands,
                "data": result.blocks,
            }))
        }
        "doc_replace_range" => {
            let blocks = normalize_blocks_from_value(&data);
            let block_id = read_required_string_field(&args, "blockId")?;
            let text = read_required_string_field(&args, "text")?;
            let mode =
                read_optional_string_field(&args, "mode").unwrap_or_else(|| "replace".into());
            let editor_commands = build_replace_editor_commands(&blocks, &block_id, &text, &mode);
            let result = apply_replace_range(blocks, &block_id, &text, &mode)?;
            let editor_command_source = if editor_commands.is_empty() {
                "legacy_snapshot_fallback"
            } else {
                "rust_editor_command"
            };
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "blockId": block_id,
                "mode": mode,
                "editorCommandSource": editor_command_source,
                "editorCommands": editor_commands,
                "data": result,
            }))
        }
        "mindmap_get" => {
            let tree = normalize_mindmap_from_value(&data)?;
            let max_nodes = read_u64_field(&args, "maxNodes").unwrap_or(120);
            let nodes = walk_mindmap_summaries(&tree, max_nodes as usize);
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "documentId": resolve_document_id(&args, tool_wire.target.as_ref())?,
                "mindmapId": resolve_mindmap_id(&args, tool_wire.target.as_ref())?,
                "nodes": nodes,
            }))
        }
        "mindmap_get_subtree" => {
            let tree = normalize_mindmap_from_value(&data)?;
            let uid = read_required_string_field(&args, "uid")?;
            let depth = read_u64_field(&args, "depth").unwrap_or(2) as usize;
            let max_nodes = read_u64_field(&args, "maxNodes").unwrap_or(60) as usize;
            let node = find_mindmap_node_by_uid(&tree, &uid)
                .ok_or_else(|| BridgeError::validation(format!("未找到 uid={uid}")))?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "uid": uid,
                "nodes": summarize_mindmap_subtree(node, depth, max_nodes),
            }))
        }
        "mindmap_put" => {
            let tree = parse_mindmap_tree_from_args(&args, "data")?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "data": tree,
            }))
        }
        "mindmap_apply_ops" => {
            let mut tree = normalize_mindmap_from_value(&data)?;
            let ops = parse_mindmap_ops(&args)?;
            let result = apply_mindmap_ops(&mut tree, &ops)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "applied": result.applied,
                "errors": result.errors,
                "data": tree,
                "meta": {
                    "reason": read_optional_string_field(&args, "reason"),
                },
            }))
        }
        "mindmap_expand_node" => {
            let merged = merge_tool_input(&args, &data);
            let mut tree = normalize_mindmap_from_value(&data)?;
            let target_uid = read_required_string_field(&merged, "targetUid")?;
            let instruction =
                read_optional_string_field(&merged, "instruction").unwrap_or_default();
            let mut ops = if let Some(ops_value) = merged.get("ops").cloned() {
                serde_json::from_value::<Vec<MindmapOp>>(ops_value).map_err(|error| {
                    BridgeError::validation(format!(
                        "mindmap_expand_node ops 反序列化失败: {error}"
                    ))
                })?
            } else {
                Vec::new()
            };
            if ops.is_empty() {
                let search_results = merged
                    .get("searchResults")
                    .and_then(Value::as_array)
                    .cloned()
                    .unwrap_or_default();
                for item in search_results {
                    let title = item
                        .as_object()
                        .and_then(|map| map.get("title"))
                        .and_then(Value::as_str)
                        .unwrap_or("")
                        .trim()
                        .to_string();
                    let url = item
                        .as_object()
                        .and_then(|map| map.get("url"))
                        .and_then(Value::as_str)
                        .unwrap_or("")
                        .trim()
                        .to_string();
                    let snippet = item
                        .as_object()
                        .and_then(|map| map.get("snippet"))
                        .and_then(Value::as_str)
                        .unwrap_or("")
                        .trim()
                        .to_string();
                    if title.is_empty() || url.is_empty() {
                        continue;
                    }
                    ops.push(MindmapOp::AddChild {
                        parent_uid: target_uid.clone(),
                        node: MindmapNodeInput {
                            uid: None,
                            text: title,
                            hyperlink: Some(url.clone()),
                            note: None,
                            refs: Some(vec![MindmapNodeRef {
                                kind: "url".into(),
                                asset_id: None,
                                file_url: Some(url),
                                page: None,
                                slide: None,
                                title: None,
                                snippet: if snippet.is_empty() {
                                    None
                                } else {
                                    Some(snippet)
                                },
                            }]),
                        },
                    });
                }
            }
            if ops.is_empty() {
                let base = instruction.chars().take(12).collect::<String>();
                let label = if base.trim().is_empty() {
                    "补完".to_string()
                } else {
                    base.trim().to_string()
                };
                for index in 1..=3 {
                    ops.push(MindmapOp::AddChild {
                        parent_uid: target_uid.clone(),
                        node: MindmapNodeInput {
                            uid: None,
                            text: format!("{label}（待核验）{index}"),
                            hyperlink: None,
                            note: None,
                            refs: None,
                        },
                    });
                }
            }
            let result = apply_mindmap_ops(&mut tree, &ops)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "applied": result.applied,
                "errors": result.errors,
                "ops": ops,
                "data": tree,
            }))
        }
        "mindmap_empty_trash" => {
            let payload: MindmapEmptyTrashToolPayload = parse_tool_input(&args, &data)?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "workspaceId": payload.workspace_id,
                "removed": 0,
            }))
        }
        "mindmap_outline_to_mindmap" => {
            let payload: MindmapOutlineToolPayload = parse_tool_input(&args, &data)?;
            let outline = parse_mindmap_outline_items(&json!({
                "outline": payload.outline,
            }))?;
            let tree = build_mindmap_outline_tree(
                &payload.root_title,
                &outline,
                &payload.page_link_pattern,
            )?;
            Ok(json!({
                "ok": true,
                "source": infer_tool_source(&data),
                "documentId": payload.document_id,
                "mindmapId": payload.mindmap_id,
                "data": tree,
                "meta": {
                    "title": payload.root_title,
                    "outlineCount": outline.len(),
                },
            }))
        }
        other => Err(BridgeError::validation(format!(
            "bridge runtime 暂不支持 tool 执行: {other}"
        ))),
    }
}

fn fetch_searxng_json(url: &str, token: &str) -> Result<Value, BridgeError> {
    let client = reqwest::blocking::Client::builder()
        .timeout(std::time::Duration::from_secs(20))
        .build()
        .map_err(|error| BridgeError::transport(format!("SearxNG 客户端创建失败: {error}")))?;
    let mut request = client.get(url);
    if !token.is_empty() {
        request = request.header("Authorization", format!("Bearer {token}"));
    }
    let response = request
        .send()
        .map_err(|error| BridgeError::transport(format!("SearxNG 请求失败: {error}")))?;
    let status = response.status();
    let text = response
        .text()
        .map_err(|error| BridgeError::transport(format!("SearxNG 响应读取失败: {error}")))?;
    if !status.is_success() {
        return Err(BridgeError::transport(format!(
            "SearxNG 请求失败：{} {}",
            status.as_u16(),
            status.canonical_reason().unwrap_or("unknown")
        )));
    }
    serde_json::from_str(&text)
        .map_err(|error| BridgeError::transport(format!("SearxNG JSON 解析失败: {error}")))
}

fn to_tool_invocation(
    tool_wire: &RuntimeToolInvocationWire,
) -> Result<ToolInvocation, BridgeError> {
    Ok(ToolInvocation {
        tool: tool_wire.tool.clone(),
        kind: parse_invocation_kind(tool_wire.kind.as_str())?,
        mode: parse_tool_mode(tool_wire.mode.as_deref()).unwrap_or(ToolExecutionMode::Plan),
        args_json: serde_json::to_string(&tool_wire.args_json).map_err(|error| {
            BridgeError::validation(format!("tool args_json 序列化失败: {error}"))
        })?,
    })
}

fn parse_invocation_kind(value: &str) -> Result<InvocationKind, BridgeError> {
    match value {
        "command" => Ok(InvocationKind::Command),
        "query" => Ok(InvocationKind::Query),
        "job" => Ok(InvocationKind::Job),
        other => Err(BridgeError::validation(format!(
            "未知 tool invocation kind: {other}"
        ))),
    }
}

fn parse_tool_mode(value: Option<&str>) -> Result<ToolExecutionMode, BridgeError> {
    match value.unwrap_or("plan") {
        "plan" => Ok(ToolExecutionMode::Plan),
        "result" => Ok(ToolExecutionMode::Result),
        "explain-plan" => Ok(ToolExecutionMode::ExplainPlan),
        other => Err(BridgeError::validation(format!(
            "未知 tool execution mode: {other}"
        ))),
    }
}

fn build_tool_payload_json(
    context_wire: &RuntimeBridgeContextWire,
    invocation: &ToolInvocation,
) -> String {
    json!({
        "kind": "tool",
        "tool": invocation.tool,
        "invocation_kind": invocation_kind_label(&invocation.kind),
        "mode": core_protocol::tool_mode_label(&invocation.mode),
        "request_id": context_wire.request_id,
        "trace_id": context_wire.trace_id,
        "workspace_id": context_wire.workspace_id,
        "actor": {
            "type": context_wire.actor.actor_type,
            "id": context_wire.actor.actor_id,
            "session_id": context_wire.actor.session_id,
        },
        "source": {
            "channel": context_wire.source.channel,
            "client": context_wire.source.client,
        },
        "validate_only": context_wire.validate_only,
        "dry_run": context_wire.dry_run,
    })
    .to_string()
}

fn build_tool_plan_steps(
    context: &BridgeContext,
    tool_wire: &RuntimeToolInvocationWire,
    invocation: &ToolInvocation,
) -> Result<Vec<RuntimeToolPlanStep>, BridgeError> {
    if invocation.tool == "search_web" {
        return Ok(vec![RuntimeToolPlanStep {
            kind: "external".into(),
            name: "search_web".into(),
            function_name: None,
            description: "在 Rust runtime 内直接请求 SearxNG 并归一化搜索结果".into(),
            args_json: tool_wire.args_json.clone(),
        }]);
    }

    if invocation.tool == "image_read" {
        return Ok(vec![RuntimeToolPlanStep {
            kind: "transport".into(),
            name: "media_assets.resolve".into(),
            function_name: None,
            description: "通过 transport 读取附件/媒体行，再由 Rust runtime 统一归一化 OCR 结果"
                .into(),
            args_json: tool_wire.args_json.clone(),
        }]);
    }

    if invocation.tool == "slash_run" {
        return Ok(vec![RuntimeToolPlanStep {
            kind: "transform".into(),
            name: "slash_run".into(),
            function_name: None,
            description:
                "在 Rust runtime 内解析斜杠命令并生成统一执行语义，TS 只保留最小 transport 写壳"
                    .into(),
            args_json: tool_wire.args_json.clone(),
        }]);
    }

    if invocation.tool == "docs_search" {
        return Ok(vec![
            RuntimeToolPlanStep {
                kind: "transport".into(),
                name: "docs_search.dataset".into(),
                function_name: None,
                description: "通过 transport 拉取文档、导图、表格与 OCR 搜索数据集，再交由 Rust runtime 统一排序。".into(),
                args_json: tool_wire.args_json.clone(),
            },
            RuntimeToolPlanStep {
                kind: "transform".into(),
                name: "docs_search".into(),
                function_name: None,
                description: "在 Rust runtime 内复用统一搜索评估器，输出跨页文档搜索结果。".into(),
                args_json: tool_wire.args_json.clone(),
            },
        ]);
    }

    if invocation.tool == "docs_read" {
        return Ok(vec![
            RuntimeToolPlanStep {
                kind: "transport".into(),
                name: "docs_read.document".into(),
                function_name: None,
                description: "通过 transport 读取目标文档 meta/content，再交由 Rust runtime 统一裁剪正文与返回结构。".into(),
                args_json: tool_wire.args_json.clone(),
            },
            RuntimeToolPlanStep {
                kind: "transform".into(),
                name: "docs_read".into(),
                function_name: None,
                description: "在 Rust runtime 内标准化跨页文档读取结果。".into(),
                args_json: tool_wire.args_json.clone(),
            },
        ]);
    }

    if invocation.tool.starts_with("onlyoffice_") {
        let description = match invocation.tool.as_str() {
            "onlyoffice_session_resolve" => {
                "解析 OnlyOffice 的附件、页面与用户上下文，生成稳定 session/asset 边界"
            }
            "onlyoffice_sign" => {
                "在 Rust adapter 内为 OnlyOffice config/document/editorConfig 生成 JWT 签名"
            }
            "onlyoffice_prepare_proxy" => {
                "在 Rust adapter 内校验 proxy 目标、重写回源地址并生成上游请求头"
            }
            "onlyoffice_prepare_callback" => {
                "在 Rust adapter 内解析 callback 状态、下载地址和 session 边界"
            }
            "onlyoffice_prepare_forcesave" => {
                "在 Rust adapter 内生成 forcesave 请求序列与 token 负载"
            }
            _ => "执行 OnlyOffice 对象边界适配",
        };
        let mut steps = vec![RuntimeToolPlanStep {
            kind: "transform".into(),
            name: invocation.tool.clone(),
            function_name: None,
            description: description.into(),
            args_json: tool_wire.args_json.clone(),
        }];
        if invocation.tool == "onlyoffice_prepare_callback" {
            steps.push(RuntimeToolPlanStep {
                kind: "write".into(),
                name: "media.assets.replace_storage".into(),
                function_name: Some("mediaAssets:replaceStorageFromUpload".into()),
                description:
                    "callback 下载并上传文件后，会继续通过统一 bridge 命令写回附件 storage 绑定"
                        .into(),
                args_json: json!({
                    "assetId": read_string_value(&tool_wire.args_json, "assetId"),
                    "documentId": read_string_value(&tool_wire.args_json, "documentId"),
                }),
            });
        }
        return Ok(steps);
    }

    if invocation.tool.starts_with("bridge_") {
        let (query_name, description) = match invocation.tool.as_str() {
            "bridge_request_get" => (
                "bridge.request.get",
                "按 request_id 回查统一 command log 与 domain event 视图",
            ),
            "bridge_trace_get" => (
                "bridge.trace.get",
                "按 trace_id 回查统一 command log 与 domain event 视图",
            ),
            "bridge_command_get" => (
                "bridge.command.get",
                "按 command_id 回查统一 command log 与 domain event 视图",
            ),
            other => {
                return Err(BridgeError::validation(format!("未知观测工具: {other}")));
            }
        };
        let query = QueryEnvelope {
            name: query_name.into(),
            payload: tool_wire.args_json.clone(),
        };
        let request = build_query_request(context, &query)?;
        return Ok(vec![RuntimeToolPlanStep {
            kind: "query".into(),
            name: query_name.into(),
            function_name: Some(request.function_name),
            description: description.into(),
            args_json: tool_wire.args_json.clone(),
        }]);
    }

    if matches!(invocation.tool.as_str(), "event_replay" | "index_rebuild") {
        let description = if invocation.tool == "event_replay" {
            "在 Rust runtime 内按事件流回放统一观测结果，并返回回放后的 cursor 摘要"
        } else {
            "在 Rust runtime 内基于事件流重建全文索引批次与 cursor 摘要"
        };
        return Ok(vec![RuntimeToolPlanStep {
            kind: "job".into(),
            name: invocation.tool.clone(),
            function_name: None,
            description: description.into(),
            args_json: tool_wire.args_json.clone(),
        }]);
    }

    if invocation.tool.starts_with("mindmap_") {
        if invocation.tool == "mindmap_empty_trash" {
            return Ok(vec![RuntimeToolPlanStep {
                kind: "validate".into(),
                name: "mindmap_empty_trash".into(),
                function_name: None,
                description: "校验清空导图回收站所需的工作区对象边界".into(),
                args_json: tool_wire.args_json.clone(),
            }]);
        }
        if invocation.tool == "mindmap_outline_to_mindmap" {
            return Ok(vec![RuntimeToolPlanStep {
                kind: "transform".into(),
                name: "mindmap_outline_to_mindmap".into(),
                function_name: None,
                description: "在 Rust runtime 内把结构化大纲转换成思维导图树".into(),
                args_json: tool_wire.args_json.clone(),
            }]);
        }
        if invocation.tool == "mindmap_expand_node" {
            return Ok(vec![RuntimeToolPlanStep {
                kind: "transform".into(),
                name: "mindmap_expand_node".into(),
                function_name: None,
                description: "在 Rust runtime 内归一化补完候选并应用到当前导图".into(),
                args_json: tool_wire.args_json.clone(),
            }]);
        }
        let document_id = resolve_document_id(&tool_wire.args_json, tool_wire.target.as_ref())?;
        let workspace_id =
            resolve_workspace_id(context, &tool_wire.args_json, tool_wire.target.as_ref());
        let mindmap_id = resolve_mindmap_id(&tool_wire.args_json, tool_wire.target.as_ref())?;
        let mindmap_query = QueryEnvelope {
            name: "mindmaps.get".into(),
            payload: GetMindmap {
                document_id: document_id.clone(),
                mindmap_id: mindmap_id.clone(),
                workspace_id,
            },
        };
        let mindmap_request = build_query_request(context, &mindmap_query)?;
        let mut steps = vec![RuntimeToolPlanStep {
            kind: "query".into(),
            name: "mindmaps.get".into(),
            function_name: Some(mindmap_request.function_name),
            description: "读取当前思维导图树，供工具执行使用".into(),
            args_json: json!({
                "docId": document_id,
                "mindmapId": mindmap_id,
            }),
        }];
        if invocation.tool == "mindmap_put" {
            steps.push(RuntimeToolPlanStep {
                kind: "write".into(),
                name: "mindmaps.put".into(),
                function_name: Some("mindmaps:put".into()),
                description: "用完整思维导图树覆盖当前导图".into(),
                args_json: tool_wire.args_json.clone(),
            });
        } else if invocation.tool == "mindmap_apply_ops" {
            steps.push(RuntimeToolPlanStep {
                kind: "transform".into(),
                name: "mindmap_apply_ops".into(),
                function_name: None,
                description: "在 Rust runtime 内应用 MindmapOp 列表并返回新树".into(),
                args_json: tool_wire.args_json.clone(),
            });
        } else if invocation.tool == "mindmap_get_subtree" {
            steps.push(RuntimeToolPlanStep {
                kind: "inspect".into(),
                name: "mindmap_get_subtree".into(),
                function_name: None,
                description: "在 Rust runtime 内裁剪指定 uid 的子树摘要".into(),
                args_json: tool_wire.args_json.clone(),
            });
        } else if invocation.mode == ToolExecutionMode::ExplainPlan {
            steps.push(RuntimeToolPlanStep {
                kind: "inspect".into(),
                name: invocation.tool.clone(),
                function_name: None,
                description: "解释该导图工具将如何读取并整理当前树结构".into(),
                args_json: tool_wire.args_json.clone(),
            });
        }
        return Ok(steps);
    }

    let document_id = resolve_document_id(&tool_wire.args_json, tool_wire.target.as_ref())?;
    let workspace_id =
        resolve_workspace_id(context, &tool_wire.args_json, tool_wire.target.as_ref());
    let content_query = QueryEnvelope {
        name: "documents.content.get".into(),
        payload: core_protocol::GetPageContent {
            page_id: document_id.clone(),
            workspace_id: workspace_id.clone(),
        },
    };
    let content_request = build_query_request(context, &content_query)?;
    let mut steps = vec![RuntimeToolPlanStep {
        kind: "query".into(),
        name: "documents.content.get".into(),
        function_name: Some(content_request.function_name),
        description: "读取当前文档快照，供工具执行使用".into(),
        args_json: json!({
            "id": document_id,
        }),
    }];

    if invocation.tool == "doc_insert_blocks" {
        steps.push(RuntimeToolPlanStep {
            kind: "transform".into(),
            name: "doc_insert_blocks".into(),
            function_name: None,
            description: "在 Rust runtime 内按 before/after block 位置插入块快照".into(),
            args_json: tool_wire.args_json.clone(),
        });
    } else if invocation.tool == "doc_replace_range" {
        steps.push(RuntimeToolPlanStep {
            kind: "transform".into(),
            name: "doc_replace_range".into(),
            function_name: None,
            description: "在 Rust runtime 内替换目标块文本并返回新快照".into(),
            args_json: tool_wire.args_json.clone(),
        });
    } else if invocation.mode == ToolExecutionMode::ExplainPlan {
        steps.push(RuntimeToolPlanStep {
            kind: "inspect".into(),
            name: invocation.tool.clone(),
            function_name: None,
            description: "解释该工具将如何读取并整理当前文档快照".into(),
            args_json: tool_wire.args_json.clone(),
        });
    }

    Ok(steps)
}

fn target_to_json(target: &RuntimeTargetWire) -> Value {
    json!({
        "workspaceId": target.workspace_id,
        "pageId": target.page_id,
        "blockId": target.block_id,
    })
}

fn resolve_document_id(
    args: &Value,
    target: Option<&RuntimeTargetWire>,
) -> Result<String, BridgeError> {
    if let Some(value) = read_string_value(args, "documentId") {
        return Ok(value);
    }
    if let Some(value) = read_string_value(args, "pageId") {
        return Ok(value);
    }
    if let Some(target) = target {
        if let Some(value) = target.page_id.clone() {
            if !value.trim().is_empty() {
                return Ok(value);
            }
        }
    }
    Err(BridgeError::validation(
        "tool 缺少 documentId/pageId 或 target.pageId",
    ))
}

fn resolve_mindmap_id(
    args: &Value,
    target: Option<&RuntimeTargetWire>,
) -> Result<String, BridgeError> {
    if let Some(value) = read_string_value(args, "mindmapId") {
        return Ok(value);
    }
    if let Some(value) = read_string_value(args, "attachmentId") {
        return Ok(value);
    }
    if let Some(target) = target {
        if let Some(value) = target.block_id.clone() {
            if !value.trim().is_empty() {
                return Ok(value);
            }
        }
    }
    Err(BridgeError::validation(
        "tool 缺少 mindmapId/attachmentId 或 target.blockId",
    ))
}

fn resolve_workspace_id(
    context: &BridgeContext,
    args: &Value,
    target: Option<&RuntimeTargetWire>,
) -> Option<String> {
    read_string_value(args, "workspaceId")
        .or_else(|| target.and_then(|value| value.workspace_id.clone()))
        .or_else(|| context.workspace_id.clone())
}

fn infer_tool_source(data: &Value) -> String {
    data.as_object()
        .and_then(|map| map.get("source"))
        .and_then(Value::as_str)
        .filter(|value| !value.trim().is_empty())
        .unwrap_or("runtime")
        .to_string()
}

fn merge_tool_input(args: &Value, data: &Value) -> Value {
    let mut merged = serde_json::Map::new();
    if let Some(map) = data.as_object() {
        for (key, value) in map {
            merged.insert(key.clone(), value.clone());
        }
    }
    if let Some(map) = args.as_object() {
        for (key, value) in map {
            merged.insert(key.clone(), value.clone());
        }
    }
    Value::Object(merged)
}

fn parse_slash_command_payload(args: &Value) -> Result<Value, BridgeError> {
    let raw_text = read_string_value(args, "text").unwrap_or_default();
    let command = read_string_value(args, "command").unwrap_or_default();
    let params = args
        .as_object()
        .and_then(|map| map.get("params"))
        .cloned()
        .unwrap_or(Value::Null);

    if raw_text.starts_with('/') {
        let segments = raw_text
            .split_whitespace()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .collect::<Vec<&str>>();
        let head = segments.first().copied().unwrap_or_default();
        let rest = raw_text[head.len()..].trim();

        if matches!(head, "/new" | "/new-doc" | "/newdoc") {
            if rest.is_empty() {
                return Err(BridgeError::validation("用法：/new <标题>"));
            }
            return Ok(json!({
                "ok": true,
                "command": "new_doc",
                "params": {
                    "title": rest,
                    "parentId": Value::Null,
                    "workspaceId": Value::Null,
                }
            }));
        }

        if matches!(head, "/rename" | "/rename-doc" | "/renamedoc") {
            let document_id = segments.get(1).copied().unwrap_or_default();
            let title = segments
                .iter()
                .skip(2)
                .copied()
                .collect::<Vec<&str>>()
                .join(" ")
                .trim()
                .to_string();
            if document_id.is_empty() || title.is_empty() {
                return Err(BridgeError::validation(
                    "用法：/rename <documentId> <新标题>",
                ));
            }
            return Ok(json!({
                "ok": true,
                "command": "rename_doc",
                "params": {
                    "documentId": document_id,
                    "title": title,
                }
            }));
        }

        return Err(BridgeError::validation(format!("未知命令：{head}")));
    }

    if command == "new_doc" {
        let title = params
            .get("title")
            .and_then(Value::as_str)
            .map(str::trim)
            .unwrap_or_default();
        if title.is_empty() {
            return Err(BridgeError::validation("缺少标题"));
        }
        let parent_id = params
            .get("parentId")
            .and_then(Value::as_str)
            .filter(|value| !value.trim().is_empty());
        let workspace_id = params
            .get("workspaceId")
            .and_then(Value::as_str)
            .filter(|value| !value.trim().is_empty());
        return Ok(json!({
            "ok": true,
            "command": "new_doc",
            "params": {
                "title": title,
                "parentId": parent_id,
                "workspaceId": workspace_id,
            }
        }));
    }

    if command == "rename_doc" {
        let document_id = params
            .get("documentId")
            .and_then(Value::as_str)
            .map(str::trim)
            .unwrap_or_default();
        let title = params
            .get("title")
            .and_then(Value::as_str)
            .map(str::trim)
            .unwrap_or_default();
        if document_id.is_empty() || title.is_empty() {
            return Err(BridgeError::validation("缺少 documentId 或 title"));
        }
        return Ok(json!({
            "ok": true,
            "command": "rename_doc",
            "params": {
                "documentId": document_id,
                "title": title,
            }
        }));
    }

    Err(BridgeError::validation("缺少 text（以 / 开头）或 command"))
}

fn normalize_bridge_observability_result(
    data: &Value,
    query_name: &str,
) -> Result<Value, BridgeError> {
    let scope_key = match query_name {
        "bridge.request.get" => "request_id",
        "bridge.trace.get" => "trace_id",
        "bridge.command.get" => "command_id",
        "bridge.workspace.overview" => "workspace_id",
        other => {
            return Err(BridgeError::validation(format!(
                "未知观测查询类型: {other}"
            )));
        }
    };
    let scope_value = read_string_value(data, scope_key)
        .or_else(|| {
            data.as_object()
                .and_then(|map| {
                    map.get(match scope_key {
                        "request_id" => "requestId",
                        "trace_id" => "traceId",
                        "workspace_id" => "workspaceId",
                        _ => "commandId",
                    })
                })
                .and_then(Value::as_str)
                .map(str::to_string)
        })
        .unwrap_or_default();
    let command_logs = sort_bridge_entries_by_time(
        data.as_object()
            .and_then(|map| map.get("command_logs").or_else(|| map.get("commandLogs")))
            .and_then(Value::as_array)
            .cloned()
            .unwrap_or_default(),
    );
    let domain_events = sort_bridge_entries_by_time(
        data.as_object()
            .and_then(|map| map.get("domain_events").or_else(|| map.get("domainEvents")))
            .and_then(Value::as_array)
            .cloned()
            .unwrap_or_default(),
    );
    let mut payload = serde_json::Map::new();
    payload.insert(scope_key.to_string(), Value::String(scope_value));
    payload.insert("command_logs".into(), Value::Array(command_logs.clone()));
    payload.insert("domain_events".into(), Value::Array(domain_events.clone()));
    payload.insert(
        "counts".into(),
        json!({
            "command_logs": command_logs.len(),
            "domain_events": domain_events.len(),
        }),
    );
    payload.insert(
        "generated_at".into(),
        data.as_object()
            .and_then(|map| map.get("generated_at").or_else(|| map.get("generatedAt")))
            .cloned()
            .unwrap_or(Value::Null),
    );
    if query_name == "bridge.workspace.overview" {
        let filters = data
            .as_object()
            .and_then(|map| map.get("filters"))
            .cloned()
            .unwrap_or(Value::Null);
        let next_cursor = data
            .as_object()
            .and_then(|map| map.get("next_cursor").or_else(|| map.get("nextCursor")))
            .cloned()
            .unwrap_or(Value::Null);
        let has_more = data
            .as_object()
            .and_then(|map| map.get("has_more").or_else(|| map.get("hasMore")))
            .cloned()
            .unwrap_or(Value::Bool(false));
        payload.insert("filters".into(), filters);
        payload.insert("next_cursor".into(), next_cursor);
        payload.insert("has_more".into(), has_more);
    }
    Ok(Value::Object(payload))
}

fn sort_bridge_entries_by_time(mut entries: Vec<Value>) -> Vec<Value> {
    entries.sort_by(|left, right| bridge_entry_time(right).cmp(&bridge_entry_time(left)));
    entries
}

fn bridge_entry_time(entry: &Value) -> String {
    read_string_value(entry, "created_at")
        .or_else(|| read_string_value(entry, "createdAt"))
        .or_else(|| read_string_value(entry, "finished_at"))
        .or_else(|| read_string_value(entry, "finishedAt"))
        .unwrap_or_default()
}

fn execute_event_replay(args: &Value, data: &Value) -> Result<Value, BridgeError> {
    let workspace_id = read_required_string_field(args, "workspaceId")?;
    let cursor = build_replay_cursor(args, &workspace_id);
    let events = parse_domain_events_from_value(data)?;
    let projector = MinimalWorkspaceProjector;
    let result = rebuild_from_events(&projector, &events, &cursor);
    Ok(json!({
        "ok": true,
        "workspaceId": workspace_id,
        "canReplay": can_rebuild_from_events(&cursor),
        "replayedEventIds": result.batches.iter().map(|batch| batch.source_event_id.clone()).collect::<Vec<String>>(),
        "batchCount": result.batches.len(),
        "cursor": index_cursor_to_json(&result.cursor),
    }))
}

fn execute_index_rebuild(args: &Value, data: &Value) -> Result<Value, BridgeError> {
    let workspace_id = read_required_string_field(args, "workspaceId")?;
    let cursor = build_replay_cursor(args, &workspace_id);
    let events = parse_domain_events_from_value(data)?;
    let projector = MinimalWorkspaceProjector;
    let result = rebuild_from_events(&projector, &events, &cursor);
    Ok(json!({
        "ok": true,
        "workspaceId": workspace_id,
        "indexedObjectKinds": index_fts::supported_index_objects().into_iter().map(|kind| format!("{kind:?}")).collect::<Vec<String>>(),
        "cursor": index_cursor_to_json(&result.cursor),
        "batchCount": result.batches.len(),
        "documentCount": result.batches.iter().map(|batch| batch.documents.len()).sum::<usize>(),
        "batches": result.batches.iter().map(|batch| {
            json!({
                "workspaceId": batch.workspace_id,
                "sourceEventId": batch.source_event_id,
                "documents": batch.documents.iter().map(|doc| {
                    json!({
                        "workspaceId": doc.workspace_id,
                        "entityKind": format!("{:?}", doc.entity_kind),
                        "entityId": doc.entity_id,
                        "parentId": doc.parent_id,
                        "title": doc.title,
                        "sourceEventId": doc.source_event_id,
                        "revision": doc.revision,
                    })
                }).collect::<Vec<Value>>(),
            })
        }).collect::<Vec<Value>>(),
    }))
}

fn build_replay_cursor(args: &Value, workspace_id: &str) -> IndexCursor {
    IndexCursor {
        workspace_id: workspace_id.to_string(),
        last_processed_event_id: read_optional_string_field(args, "lastProcessedEventId")
            .unwrap_or_else(|| "evt_bootstrap".into()),
        last_processed_at: read_optional_string_field(args, "lastProcessedAt")
            .unwrap_or_else(|| "1970-01-01T00:00:00Z".into()),
    }
}

fn parse_domain_events_from_value(data: &Value) -> Result<Vec<DomainEventRecord>, BridgeError> {
    let rows = if let Some(events) = data
        .as_object()
        .and_then(|map| map.get("events"))
        .and_then(Value::as_array)
    {
        events.clone()
    } else if let Some(events) = data.as_array() {
        events.clone()
    } else if let Some(events) = data
        .as_object()
        .and_then(|map| map.get("domain_events").or_else(|| map.get("domainEvents")))
        .and_then(Value::as_array)
    {
        events.clone()
    } else {
        Vec::new()
    };
    rows.into_iter()
        .map(|row| parse_domain_event(&row))
        .collect()
}

fn parse_domain_event(row: &Value) -> Result<DomainEventRecord, BridgeError> {
    let event_id = read_required_string_field(row, "event_id")
        .or_else(|_| read_required_string_field(row, "id"))?;
    let workspace_id = read_required_string_field(row, "workspace_id")
        .or_else(|_| read_required_string_field(row, "workspaceId"))?;
    let aggregate_type = read_required_string_field(row, "aggregate_type")
        .or_else(|_| read_required_string_field(row, "aggregateType"))?;
    let aggregate_id = read_required_string_field(row, "aggregate_id")
        .or_else(|_| read_required_string_field(row, "aggregateId"))?;
    let event_type = read_required_string_field(row, "event_type")
        .or_else(|_| read_required_string_field(row, "eventType"))?;
    let event_version = read_u64_field(row, "event_version")
        .or_else(|| read_u64_field(row, "eventVersion"))
        .unwrap_or(1) as u32;
    let payload = row
        .as_object()
        .and_then(|map| map.get("payload"))
        .cloned()
        .unwrap_or_else(|| row.get("payload_json").cloned().unwrap_or(Value::Null));
    let payload_json = if payload.is_string() {
        payload.as_str().unwrap_or("").to_string()
    } else {
        serde_json::to_string(&payload).map_err(|error| {
            BridgeError::transport(format!("领域事件 payload 序列化失败: {error}"))
        })?
    };
    let created_at = read_required_string_field(row, "created_at")
        .or_else(|_| read_required_string_field(row, "createdAt"))?;
    let command_log_id = read_required_string_field(row, "command_log_id")
        .or_else(|_| read_required_string_field(row, "commandLogId"))?;
    let actor_type = read_required_string_field(row, "actor_type")
        .or_else(|_| read_required_string_field(row, "actorType"))?;
    let trace_id = read_required_string_field(row, "trace_id")
        .or_else(|_| read_required_string_field(row, "traceId"))?;
    let request_id = read_required_string_field(row, "request_id")
        .or_else(|_| read_required_string_field(row, "requestId"))?;
    let command_id = read_required_string_field(row, "command_id")
        .or_else(|_| read_required_string_field(row, "commandId"))?;

    Ok(DomainEventRecord {
        event_id,
        workspace_id,
        aggregate_type,
        aggregate_id,
        event_type,
        event_version,
        payload_json,
        command_log_id,
        actor_type,
        created_at: Timestamp::new(created_at),
        status: event_log::EventStatus::Committed,
        trace_id,
        request_id,
        command_id,
    })
}

fn index_cursor_to_json(cursor: &IndexCursor) -> Value {
    json!({
        "workspaceId": cursor.workspace_id,
        "lastProcessedEventId": cursor.last_processed_event_id,
        "lastProcessedAt": cursor.last_processed_at,
    })
}

fn parse_tool_input<T>(args: &Value, data: &Value) -> Result<T, BridgeError>
where
    T: DeserializeOwned,
{
    serde_json::from_value(merge_tool_input(args, data))
        .map_err(|error| BridgeError::validation(format!("tool 输入反序列化失败: {error}")))
}

fn normalize_blocks_from_value(data: &Value) -> Vec<Value> {
    if let Some(array) = data.as_array() {
        return array.clone();
    }
    if let Some(map) = data.as_object() {
        if let Some(array) = map.get("blocks").and_then(Value::as_array) {
            return array.clone();
        }
        if let Some(content) = map.get("content") {
            return normalize_blocks_from_value(content);
        }
    }
    vec![]
}

fn read_trimmed_string_from_map(
    map: &serde_json::Map<String, Value>,
    keys: &[&str],
) -> Option<String> {
    for key in keys {
        let value = map
            .get(*key)
            .and_then(Value::as_str)
            .map(str::trim)
            .unwrap_or("");
        if !value.is_empty() {
            return Some(value.to_string());
        }
    }
    None
}

fn read_trimmed_string_field(value: &Value, keys: &[&str]) -> Option<String> {
    value
        .as_object()
        .and_then(|map| read_trimmed_string_from_map(map, keys))
}

fn normalize_page_subtree_projection_id(document_id: &str) -> String {
    format!("kernel_projection:page_tree:{document_id}")
}

fn pick_first_text(values: &[Option<String>]) -> String {
    for value in values {
        if let Some(value) = value
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
        {
            return value.to_string();
        }
    }
    String::new()
}

fn normalize_snippet(value: &str) -> String {
    value
        .split_whitespace()
        .collect::<Vec<&str>>()
        .join(" ")
        .chars()
        .take(220)
        .collect()
}

fn clamp_heading_level(value: Option<u64>) -> u32 {
    let level = value.unwrap_or(1);
    level.clamp(1, 5) as u32
}

fn get_page_block_children(value: Option<&Value>) -> Vec<Value> {
    value.and_then(Value::as_array).cloned().unwrap_or_default()
}

fn get_inline_text(value: &Value) -> String {
    if let Some(text) = value.as_str() {
        return text.to_string();
    }
    let Some(nodes) = value.as_array() else {
        return String::new();
    };
    let mut text = String::new();
    for node in nodes {
        if let Some(fragment) = node.as_str() {
            text.push_str(fragment);
            continue;
        }
        let Some(map) = node.as_object() else {
            continue;
        };
        if map.get("type").and_then(Value::as_str) == Some("link") {
            if let Some(content) = map.get("content") {
                text.push_str(&get_inline_text(content));
            }
            continue;
        }
        if let Some(fragment) = map.get("text").and_then(Value::as_str) {
            text.push_str(fragment);
        }
    }
    text
}

fn get_block_snippet(block: &Value) -> String {
    let inline_text = block
        .as_object()
        .and_then(|map| map.get("content"))
        .map(get_inline_text)
        .map(|value| normalize_snippet(&value))
        .unwrap_or_default();
    if !inline_text.is_empty() {
        return inline_text;
    }
    let props = block
        .as_object()
        .and_then(|map| map.get("props"))
        .and_then(Value::as_object);
    let fallback = props.map(|props| {
        pick_first_text(&[
            read_trimmed_string_from_map(props, &["title"]),
            read_trimmed_string_from_map(props, &["caption"]),
            read_trimmed_string_from_map(props, &["summary"]),
            read_trimmed_string_from_map(props, &["fileName", "file_name"]),
            read_trimmed_string_from_map(props, &["name"]),
            read_trimmed_string_from_map(props, &["alt"]),
            read_trimmed_string_from_map(props, &["status"]),
        ])
    });
    normalize_snippet(fallback.as_deref().unwrap_or_default())
}

fn get_block_display_title(block: &Value, snippet: &str) -> Option<String> {
    let block_type = read_trimmed_string_field(block, &["type"]);
    let props = block
        .as_object()
        .and_then(|map| map.get("props"))
        .and_then(Value::as_object);
    let title = props.and_then(|props| read_trimmed_string_from_map(props, &["title"]));
    let caption = props.and_then(|props| read_trimmed_string_from_map(props, &["caption"]));
    let file_name =
        props.and_then(|props| read_trimmed_string_from_map(props, &["fileName", "file_name"]));
    match block_type.as_deref() {
        Some("heading") => Some(if snippet.is_empty() {
            "未命名标题".into()
        } else {
            snippet.into()
        }),
        Some("pageReference") => Some(pick_first_text(&[
            title,
            Some(snippet.into()),
            Some("页面引用".into()),
        ])),
        Some("blockReference") => Some(pick_first_text(&[
            title,
            Some(snippet.into()),
            Some("块引用".into()),
        ])),
        Some("onlineTable") => Some(pick_first_text(&[
            title,
            Some(snippet.into()),
            Some("在线表格".into()),
        ])),
        Some("mindmap") => Some(pick_first_text(&[
            title,
            Some(snippet.into()),
            Some("思维导图".into()),
        ])),
        Some("media") => Some(pick_first_text(&[
            caption,
            file_name,
            Some(snippet.into()),
            Some("附件".into()),
        ])),
        Some("codeBlock") => Some(if snippet.is_empty() {
            "代码块".into()
        } else {
            snippet.into()
        }),
        Some("advancedTodo") => Some(if snippet.is_empty() {
            "任务".into()
        } else {
            snippet.into()
        }),
        Some("quote") => Some(if snippet.is_empty() {
            "引用".into()
        } else {
            snippet.into()
        }),
        _ => {
            if snippet.is_empty() {
                None
            } else {
                Some(snippet.into())
            }
        }
    }
}

fn get_document_read_node_type(block_type: Option<&str>) -> DocumentReadNodeType {
    match block_type {
        Some("heading") => DocumentReadNodeType::Section,
        Some("blockReference") | Some("pageReference") => DocumentReadNodeType::ReferenceAnchor,
        Some("mindmap") => DocumentReadNodeType::Mindmap,
        _ => DocumentReadNodeType::ContentNode,
    }
}

fn get_document_read_evidence_kind(block_type: Option<&str>) -> DocumentReadEvidenceKind {
    match block_type {
        Some("heading") => DocumentReadEvidenceKind::Heading,
        Some("paragraph") => DocumentReadEvidenceKind::Paragraph,
        Some("bulletListItem") | Some("numberedListItem") | Some("checkListItem") => {
            DocumentReadEvidenceKind::List
        }
        Some("advancedTodo") => DocumentReadEvidenceKind::Todo,
        Some("quote") => DocumentReadEvidenceKind::Quote,
        Some("codeBlock") => DocumentReadEvidenceKind::Code,
        Some("media") => DocumentReadEvidenceKind::Media,
        Some("pageReference") | Some("blockReference") => DocumentReadEvidenceKind::Reference,
        Some("onlineTable") => DocumentReadEvidenceKind::Table,
        Some("mindmap") => DocumentReadEvidenceKind::Mindmap,
        _ => DocumentReadEvidenceKind::Text,
    }
}

fn build_document_page_subtree(
    document_id: &str,
    title: Option<&str>,
    content: &Value,
) -> DocumentReadPageSubtree {
    let root_node_id = document_id.trim().to_string();
    let blocks = normalize_blocks_from_value(content);
    let root_title = title
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .unwrap_or("无标题")
        .to_string();
    let root_node = DocumentReadNode {
        id: root_node_id.clone(),
        parent_node_id: None,
        node_type: DocumentReadNodeType::Page,
        block_id: None,
        anchor_block_id: None,
        depth: 0,
        metadata: DocumentReadNodeMeta {
            title: Some(root_title.clone()),
            text_snippet: None,
            block_type: Some("page".into()),
            heading_level: None,
            numbering: None,
            child_count: blocks.len() as u32,
            order: 0,
            path: vec![root_node_id.clone()],
        },
    };

    let mut nodes = vec![root_node.clone()];
    let mut outline = Vec::<DocumentReadOutlineEntry>::new();
    let mut evidence = Vec::<DocumentReadEvidenceItem>::new();
    let mut heading_counters = [0u32; 5];
    let mut heading_stack = Vec::<(u32, String)>::new();
    let mut order = 0u32;
    let mut max_depth = 0u32;

    fn walk_blocks(
        items: &[Value],
        parent_block_node_id: Option<&str>,
        depth: u32,
        path: &[usize],
        root_node_id: &str,
        nodes: &mut Vec<DocumentReadNode>,
        outline: &mut Vec<DocumentReadOutlineEntry>,
        evidence: &mut Vec<DocumentReadEvidenceItem>,
        heading_counters: &mut [u32; 5],
        heading_stack: &mut Vec<(u32, String)>,
        order: &mut u32,
        max_depth: &mut u32,
    ) {
        for (index, block) in items.iter().enumerate() {
            let block_id = read_trimmed_string_field(block, &["id"]);
            let mut auto_path = path
                .iter()
                .map(|value| value.to_string())
                .collect::<Vec<String>>();
            auto_path.push(index.to_string());
            let node_id = block_id
                .as_ref()
                .map(|value| format!("block:{value}"))
                .unwrap_or_else(|| format!("block:auto:{}", auto_path.join(".")));
            let snippet = get_block_snippet(block);
            let block_type = read_trimmed_string_field(block, &["type"]);
            let heading_level = if block_type.as_deref() == Some("heading") {
                let level = block
                    .as_object()
                    .and_then(|map| map.get("props"))
                    .and_then(Value::as_object)
                    .and_then(|props| props.get("level"))
                    .and_then(Value::as_u64);
                Some(clamp_heading_level(level))
            } else {
                None
            };
            let mut parent_node_id = parent_block_node_id
                .map(str::to_string)
                .or_else(|| heading_stack.last().map(|(_, node_id)| node_id.clone()))
                .unwrap_or_else(|| root_node_id.to_string());
            let mut numbering = None::<String>;

            if let Some(level) = heading_level {
                while heading_stack
                    .last()
                    .map(|(current_level, _)| *current_level >= level)
                    .unwrap_or(false)
                {
                    heading_stack.pop();
                }
                parent_node_id = heading_stack
                    .last()
                    .map(|(_, node_id)| node_id.clone())
                    .or_else(|| parent_block_node_id.map(str::to_string))
                    .unwrap_or_else(|| root_node_id.to_string());
                let index = (level - 1) as usize;
                heading_counters[index] += 1;
                for counter in heading_counters.iter_mut().skip(index + 1) {
                    *counter = 0;
                }
                numbering = Some(
                    heading_counters
                        .iter()
                        .take(level as usize)
                        .copied()
                        .filter(|value| *value > 0)
                        .map(|value| value.to_string())
                        .collect::<Vec<String>>()
                        .join("."),
                );
            }

            *order += 1;
            let children =
                get_page_block_children(block.as_object().and_then(|map| map.get("children")));
            let node = DocumentReadNode {
                id: node_id.clone(),
                parent_node_id: Some(parent_node_id),
                node_type: get_document_read_node_type(block_type.as_deref()),
                block_id: block_id.clone(),
                anchor_block_id: block_id.clone(),
                depth: depth + 1,
                metadata: DocumentReadNodeMeta {
                    title: get_block_display_title(block, &snippet),
                    text_snippet: if snippet.is_empty() {
                        None
                    } else {
                        Some(snippet.clone())
                    },
                    block_type: block_type.clone(),
                    heading_level,
                    numbering: numbering.clone(),
                    child_count: children.len() as u32,
                    order: *order,
                    path: std::iter::once(root_node_id.to_string())
                        .chain(path.iter().map(|value| value.to_string()))
                        .chain(std::iter::once(index.to_string()))
                        .collect(),
                },
            };

            nodes.push(node.clone());
            *max_depth = (*max_depth).max(node.depth);

            if let Some(level) = heading_level {
                outline.push(DocumentReadOutlineEntry {
                    id: block_id.clone().unwrap_or_else(|| node.id.clone()),
                    node_id: node.id.clone(),
                    anchor_block_id: block_id.clone(),
                    title: node
                        .metadata
                        .title
                        .clone()
                        .unwrap_or_else(|| "未命名标题".into()),
                    level,
                    numbering: numbering.unwrap_or_default(),
                });
                heading_stack.push((level, node.id.clone()));
            }

            if !snippet.is_empty() {
                evidence.push(DocumentReadEvidenceItem {
                    id: format!("evidence:{}", node.id),
                    node_id: node.id.clone(),
                    block_id: block_id.clone(),
                    kind: get_document_read_evidence_kind(block_type.as_deref()),
                    snippet,
                });
            }

            if !children.is_empty() {
                let mut next_path = path.to_vec();
                next_path.push(index);
                walk_blocks(
                    &children,
                    Some(node.id.as_str()),
                    depth + 1,
                    &next_path,
                    root_node_id,
                    nodes,
                    outline,
                    evidence,
                    heading_counters,
                    heading_stack,
                    order,
                    max_depth,
                );
            }
        }
    }

    walk_blocks(
        &blocks,
        None,
        0,
        &[],
        &root_node_id,
        &mut nodes,
        &mut outline,
        &mut evidence,
        &mut heading_counters,
        &mut heading_stack,
        &mut order,
        &mut max_depth,
    );

    if !root_title.is_empty() {
        evidence.insert(
            0,
            DocumentReadEvidenceItem {
                id: format!("evidence:{root_node_id}"),
                node_id: root_node_id.clone(),
                block_id: None,
                kind: DocumentReadEvidenceKind::Page,
                snippet: root_title,
            },
        );
    }

    let block_count = nodes.len().saturating_sub(1) as u32;
    let heading_count = outline.len() as u32;
    let evidence_count = evidence.len() as u32;

    DocumentReadPageSubtree {
        projection_id: normalize_page_subtree_projection_id(&root_node_id),
        projection: "page_tree".into(),
        root_node_id: root_node_id.clone(),
        root_node,
        subtree: DocumentReadSubtree {
            root_node_id: root_node_id.clone(),
            nodes,
        },
        outline,
        evidence: evidence.clone(),
        stats: DocumentReadStats {
            block_count,
            heading_count,
            evidence_count,
            max_depth,
        },
    }
}

fn build_document_content_result(
    data: &Value,
    document_id: &str,
    _workspace_id: Option<&str>,
) -> Result<DocumentContentResult, BridgeError> {
    if data.is_null() {
        return Err(BridgeError::not_found(
            "documents.content.get 未返回文档内容",
        ));
    }
    let content = data
        .as_object()
        .and_then(|map| map.get("content"))
        .cloned()
        .unwrap_or(Value::Null);
    let revision = data
        .as_object()
        .and_then(|map| map.get("revision"))
        .and_then(Value::as_u64)
        .unwrap_or(0);
    let conflict_detection_key =
        read_trimmed_string_field(data, &["conflict_detection_key", "conflictDetectionKey"])
            .unwrap_or_else(|| format!("{document_id}:{revision}"));
    let title = read_trimmed_string_field(data, &["title"]);
    let page_subtree = build_document_page_subtree(document_id, title.as_deref(), &content);

    Ok(DocumentContentResult {
        content,
        revision,
        conflict_detection_key,
        title,
        page_subtree,
    })
}

fn normalize_mindmap_from_value(data: &Value) -> Result<MindmapTreeNode, BridgeError> {
    if data.is_null() {
        return Ok(default_mindmap_tree());
    }
    let candidate = if let Some(map) = data.as_object() {
        if (map.contains_key("ok") || map.contains_key("meta") || map.contains_key("source"))
            && map.get("data").is_some()
        {
            map.get("data").cloned().unwrap_or(Value::Null)
        } else if map.get("data").is_some() && map.get("children").is_some() {
            data.clone()
        } else if let Some(nested) = map.get("mindmap") {
            nested.clone()
        } else if let Some(nested) = map.get("result") {
            nested.clone()
        } else {
            data.clone()
        }
    } else {
        data.clone()
    };
    let mut tree: MindmapTreeNode = serde_json::from_value(candidate)
        .map_err(|error| BridgeError::validation(format!("mindmap 数据反序列化失败: {error}")))?;
    ensure_mindmap_uids(&mut tree);
    Ok(tree)
}

fn walk_block_summaries(root_blocks: &[Value], max_nodes: usize) -> Vec<RuntimeBlockSummary> {
    let mut queue = VecDeque::new();
    for block in root_blocks {
        queue.push_back((block.clone(), 0usize));
    }
    let mut summaries = Vec::new();
    while let Some((block, depth)) = queue.pop_front() {
        if summaries.len() >= max_nodes {
            break;
        }
        let id = block
            .as_object()
            .and_then(|map| map.get("id"))
            .and_then(Value::as_str)
            .unwrap_or("")
            .trim()
            .to_string();
        let block_type = block
            .as_object()
            .and_then(|map| map.get("type"))
            .and_then(Value::as_str)
            .unwrap_or("unknown")
            .trim()
            .to_string();
        let children = block
            .as_object()
            .and_then(|map| map.get("children"))
            .and_then(Value::as_array)
            .cloned()
            .unwrap_or_default();
        if !id.is_empty() {
            summaries.push(RuntimeBlockSummary {
                id,
                block_type,
                text: extract_inline_text(&block),
                depth,
                child_count: children.len(),
            });
        }
        for child in children {
            queue.push_back((child, depth + 1));
        }
    }
    summaries
}

fn extract_inline_text(block: &Value) -> String {
    block
        .as_object()
        .and_then(|map| map.get("content"))
        .and_then(Value::as_array)
        .map(|nodes| {
            nodes
                .iter()
                .filter_map(|node| {
                    node.as_object()
                        .and_then(|map| map.get("text"))
                        .and_then(Value::as_str)
                })
                .collect::<String>()
                .trim()
                .to_string()
        })
        .unwrap_or_default()
}

fn parse_insert_specs(args: &Value) -> Result<Vec<RuntimeInsertSpec>, BridgeError> {
    let specs_raw = args
        .as_object()
        .and_then(|map| map.get("blocks"))
        .and_then(Value::as_array)
        .ok_or_else(|| BridgeError::validation("doc_insert_blocks 缺少 blocks"))?;

    if specs_raw.is_empty() {
        return Err(BridgeError::validation("doc_insert_blocks 缺少 blocks"));
    }
    if specs_raw.len() > 20 {
        return Err(BridgeError::validation(
            "doc_insert_blocks blocks 过多（最多 20）",
        ));
    }

    Ok(specs_raw
        .iter()
        .map(|spec| RuntimeInsertSpec {
            block_type: read_string_value(spec, "type")
                .filter(|value| value == "heading")
                .unwrap_or_else(|| "paragraph".into()),
            text: read_string_value(spec, "text").unwrap_or_default(),
            level: read_u64_field(spec, "level").unwrap_or(2),
        })
        .collect())
}

fn apply_insert_blocks(
    mut blocks: Vec<Value>,
    after_block_id: Option<String>,
    before_block_id: Option<String>,
    specs: Vec<RuntimeInsertSpec>,
) -> Result<InsertBlocksResult, BridgeError> {
    let created = specs
        .iter()
        .map(build_block_from_spec)
        .collect::<Vec<Value>>();
    let editor_commands = build_insert_editor_commands(
        &blocks,
        after_block_id.as_deref(),
        before_block_id.as_deref(),
        &created,
        &specs,
    );
    let inserted = created
        .iter()
        .filter_map(|block| read_string_value(block, "id"))
        .collect::<Vec<String>>();
    let target_id = before_block_id.clone().or(after_block_id.clone());

    match target_id {
        Some(target_id) => {
            let inserted_ok = insert_blocks_at_target(
                &mut blocks,
                &target_id,
                before_block_id.is_some(),
                &created,
            );
            if !inserted_ok {
                return Err(BridgeError::validation(format!(
                    "未找到 blockId：{target_id}"
                )));
            }
        }
        None => blocks.extend(created),
    }

    Ok(InsertBlocksResult {
        inserted,
        blocks,
        editor_commands,
    })
}

fn insert_blocks_at_target(
    blocks: &mut Vec<Value>,
    target_id: &str,
    before: bool,
    created: &[Value],
) -> bool {
    for index in 0..blocks.len() {
        if read_string_value(&blocks[index], "id").as_deref() == Some(target_id) {
            let insert_at = if before { index } else { index + 1 };
            blocks.splice(insert_at..insert_at, created.iter().cloned());
            return true;
        }
        if let Some(children) = blocks[index]
            .as_object_mut()
            .and_then(|map| map.get_mut("children"))
            .and_then(Value::as_array_mut)
        {
            if insert_blocks_at_target(children, target_id, before, created) {
                return true;
            }
        }
    }
    false
}

fn build_block_from_spec(spec: &RuntimeInsertSpec) -> Value {
    let id = format!(
        "rust_tool_block_{}",
        TOOL_BLOCK_COUNTER.fetch_add(1, Ordering::Relaxed)
    );
    let level = spec.level.clamp(1, 5);
    let props = if spec.block_type == "heading" {
        json!({ "level": level })
    } else {
        json!({})
    };
    json!({
        "id": id,
        "type": spec.block_type,
        "props": props,
        "content": [{"type":"text","text": spec.text}],
        "children": [],
    })
}

fn build_text_content_nodes(text: &str) -> Vec<ContentNode> {
    if text.trim().is_empty() {
        return vec![];
    }
    vec![ContentNode {
        payload: ContentNodePayload::Text {
            text: text.to_string(),
            marks: vec![],
        },
        attrs: BTreeMap::new(),
    }]
}

fn editor_block_type_from_spec(spec: &RuntimeInsertSpec) -> EditorBlockType {
    match spec.block_type.as_str() {
        "heading" => EditorBlockType::Heading,
        _ => EditorBlockType::Paragraph,
    }
}

fn build_editor_block_from_spec(spec: &RuntimeInsertSpec, block_id: &str) -> EditorBlock {
    let mut props = BlockProps::default();
    if matches!(editor_block_type_from_spec(spec), EditorBlockType::Heading) {
        props.heading_level = Some(spec.level.clamp(1, 5) as u8);
    }
    EditorBlock {
        block_id: block_id.to_string(),
        block_type: editor_block_type_from_spec(spec),
        props,
        content_nodes: build_text_content_nodes(&spec.text),
        child_block_ids: vec![],
    }
}

fn collect_block_order(blocks: &[Value], order: &mut Vec<String>) {
    for block in blocks {
        if let Some(block_id) = read_string_value(block, "id") {
            order.push(block_id);
        }
        if let Some(children) = block
            .as_object()
            .and_then(|map| map.get("children"))
            .and_then(Value::as_array)
        {
            collect_block_order(children, order);
        }
    }
}

fn resolve_insert_anchor(
    blocks: &[Value],
    after_block_id: Option<&str>,
    before_block_id: Option<&str>,
) -> Option<String> {
    let mut order = Vec::new();
    collect_block_order(blocks, &mut order);
    if let Some(after_block_id) = after_block_id {
        if order.iter().any(|item| item == after_block_id) {
            return Some(after_block_id.to_string());
        }
        return None;
    }
    if let Some(before_block_id) = before_block_id {
        let index = order.iter().position(|item| item == before_block_id)?;
        if index == 0 {
            return None;
        }
        return order.get(index - 1).cloned();
    }
    order.last().cloned()
}

fn build_insert_editor_commands(
    blocks: &[Value],
    after_block_id: Option<&str>,
    before_block_id: Option<&str>,
    created: &[Value],
    specs: &[RuntimeInsertSpec],
) -> Vec<EditorCommand> {
    let mut anchor = resolve_insert_anchor(blocks, after_block_id, before_block_id);
    if anchor.is_none() {
        return vec![];
    }
    let mut commands = Vec::new();
    for (created_block, spec) in created.iter().zip(specs.iter()) {
        let Some(block_id) = read_string_value(created_block, "id") else {
            continue;
        };
        let Some(after_block_id) = anchor.clone() else {
            break;
        };
        commands.push(EditorCommand::InsertBlockAfter(EditorInsertBlockAfter {
            after_block_id,
            block: build_editor_block_from_spec(spec, &block_id),
        }));
        anchor = Some(block_id);
    }
    commands
}

fn find_block_text(blocks: &[Value], block_id: &str) -> Option<String> {
    for block in blocks {
        if read_string_value(block, "id").as_deref() == Some(block_id) {
            return Some(extract_inline_text(block));
        }
        if let Some(children) = block
            .as_object()
            .and_then(|map| map.get("children"))
            .and_then(Value::as_array)
        {
            if let Some(found) = find_block_text(children, block_id) {
                return Some(found);
            }
        }
    }
    None
}

fn build_replace_editor_commands(
    blocks: &[Value],
    block_id: &str,
    text: &str,
    mode: &str,
) -> Vec<EditorCommand> {
    let Some(previous) = find_block_text(blocks, block_id) else {
        return vec![];
    };
    let next_text = match mode {
        "append" => format!("{previous}{text}"),
        "prepend" => format!("{text}{previous}"),
        _ => text.to_string(),
    };
    vec![EditorCommand::ReplaceBlock(EditorReplaceBlock {
        block_id: block_id.to_string(),
        block_type: None,
        props: None,
        content_nodes: Some(build_text_content_nodes(&next_text)),
    })]
}

fn apply_replace_range(
    mut blocks: Vec<Value>,
    block_id: &str,
    text: &str,
    mode: &str,
) -> Result<Vec<Value>, BridgeError> {
    if !replace_block_text(&mut blocks, block_id, text, mode)? {
        return Err(BridgeError::validation(format!(
            "未找到 blockId：{block_id}"
        )));
    }
    Ok(blocks)
}

fn replace_block_text(
    blocks: &mut Vec<Value>,
    block_id: &str,
    text: &str,
    mode: &str,
) -> Result<bool, BridgeError> {
    for block in blocks.iter_mut() {
        if read_string_value(block, "id").as_deref() == Some(block_id) {
            let previous = extract_inline_text(block);
            let next_text = match mode {
                "append" => format!("{previous}{text}"),
                "prepend" => format!("{text}{previous}"),
                _ => text.to_string(),
            };
            let object = block
                .as_object_mut()
                .ok_or_else(|| BridgeError::validation(format!("block 数据异常：{block_id}")))?;
            object.insert("content".into(), json!([{"type":"text","text": next_text}]));
            return Ok(true);
        }
        if let Some(children) = block
            .as_object_mut()
            .and_then(|map| map.get_mut("children"))
            .and_then(Value::as_array_mut)
        {
            if replace_block_text(children, block_id, text, mode)? {
                return Ok(true);
            }
        }
    }
    Ok(false)
}

fn read_required_string_field(value: &Value, field: &str) -> Result<String, BridgeError> {
    read_string_value(value, field)
        .ok_or_else(|| BridgeError::validation(format!("tool 缺少 {field}")))
}

fn read_optional_string_field(value: &Value, field: &str) -> Option<String> {
    read_string_value(value, field)
}

fn read_string_value(value: &Value, field: &str) -> Option<String> {
    value
        .as_object()
        .and_then(|map| map.get(field))
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string)
}

fn read_u64_field(value: &Value, field: &str) -> Option<u64> {
    value
        .as_object()
        .and_then(|map| map.get(field))
        .and_then(Value::as_u64)
}

fn next_mindmap_uid() -> String {
    format!(
        "rust_mindmap_uid_{}",
        MINDMAP_UID_COUNTER.fetch_add(1, Ordering::Relaxed)
    )
}

fn default_mindmap_tree() -> MindmapTreeNode {
    let mut root = MindmapTreeNode {
        data: MindmapNodeData {
            uid: None,
            text: Some("中心主题".into()),
            hyperlink: None,
            note: None,
            refs: None,
            extra: BTreeMap::new(),
        },
        children: vec![],
        extra: BTreeMap::new(),
    };
    ensure_mindmap_uids(&mut root);
    root
}

fn ensure_mindmap_uids(node: &mut MindmapTreeNode) {
    if node
        .data
        .uid
        .as_ref()
        .map(|value| value.trim().is_empty())
        .unwrap_or(true)
    {
        node.data.uid = Some(next_mindmap_uid());
    }
    let text = node.data.text.clone().unwrap_or_else(|| "新节点".into());
    node.data.text = Some(text);
    for child in &mut node.children {
        ensure_mindmap_uids(child);
    }
}

fn strip_html_tags(value: &str) -> String {
    let mut result = String::with_capacity(value.len());
    let mut inside_tag = false;
    for ch in value.chars() {
        match ch {
            '<' => inside_tag = true,
            '>' => inside_tag = false,
            _ if !inside_tag => result.push(ch),
            _ => {}
        }
    }
    result.trim().to_string()
}

fn trim_text_for_docs_read(value: &str, max_chars: usize) -> String {
    let taken = value.chars().take(max_chars).collect::<String>();
    if value.chars().count() > max_chars {
        format!("{taken}…")
    } else {
        taken
    }
}

fn walk_mindmap_summaries(root: &MindmapTreeNode, max_nodes: usize) -> Vec<RuntimeMindmapSummary> {
    let mut list = Vec::new();
    let mut queue = VecDeque::from([(root, None::<String>, 0usize)]);
    while let Some((node, parent_uid, depth)) = queue.pop_front() {
        if list.len() >= max_nodes {
            break;
        }
        let uid = node.data.uid.clone().unwrap_or_default();
        let text = strip_html_tags(node.data.text.as_deref().unwrap_or(""));
        list.push(RuntimeMindmapSummary {
            uid: uid.clone(),
            text,
            parent_uid,
            depth,
            child_count: node.children.len(),
        });
        for child in &node.children {
            queue.push_back((child, Some(uid.clone()), depth + 1));
        }
    }
    list
}

fn summarize_mindmap_subtree(
    root: &MindmapTreeNode,
    depth_limit: usize,
    max_nodes: usize,
) -> Vec<RuntimeMindmapSubtreeSummary> {
    let mut list = Vec::new();
    let mut queue = VecDeque::from([(root, 0usize)]);
    while let Some((node, depth)) = queue.pop_front() {
        if list.len() >= max_nodes {
            break;
        }
        list.push(RuntimeMindmapSubtreeSummary {
            uid: node.data.uid.clone().unwrap_or_default(),
            text: strip_html_tags(node.data.text.as_deref().unwrap_or("")),
            depth,
            child_count: node.children.len(),
        });
        if depth < depth_limit {
            for child in &node.children {
                queue.push_back((child, depth + 1));
            }
        }
    }
    list
}

fn find_mindmap_node_by_uid<'a>(
    node: &'a MindmapTreeNode,
    uid: &str,
) -> Option<&'a MindmapTreeNode> {
    if node.data.uid.as_deref() == Some(uid) {
        return Some(node);
    }
    for child in &node.children {
        if let Some(found) = find_mindmap_node_by_uid(child, uid) {
            return Some(found);
        }
    }
    None
}

fn parse_mindmap_tree_from_args(args: &Value, field: &str) -> Result<MindmapTreeNode, BridgeError> {
    let value = args
        .as_object()
        .and_then(|map| map.get(field))
        .cloned()
        .ok_or_else(|| BridgeError::validation(format!("tool 缺少 {field}")))?;
    let mut tree: MindmapTreeNode = serde_json::from_value(value).map_err(|error| {
        BridgeError::validation(format!("{field} 不是合法的思维导图树: {error}"))
    })?;
    ensure_mindmap_uids(&mut tree);
    Ok(tree)
}

fn parse_mindmap_ops(args: &Value) -> Result<Vec<MindmapOp>, BridgeError> {
    let ops_value = args
        .as_object()
        .and_then(|map| map.get("ops"))
        .cloned()
        .ok_or_else(|| BridgeError::validation("mindmap_apply_ops 缺少 ops"))?;
    let ops: Vec<MindmapOp> = serde_json::from_value(ops_value)
        .map_err(|error| BridgeError::validation(format!("ops 反序列化失败: {error}")))?;
    if ops.is_empty() {
        return Err(BridgeError::validation("mindmap_apply_ops 缺少 ops"));
    }
    if ops.len() > 80 {
        return Err(BridgeError::validation(
            "mindmap_apply_ops ops 过多（最多 80）",
        ));
    }
    Ok(ops)
}

fn parse_mindmap_outline_items(
    args: &Value,
) -> Result<Vec<MindmapOutlineItemPayload>, BridgeError> {
    let outline_value = args
        .as_object()
        .and_then(|map| map.get("outline"))
        .cloned()
        .ok_or_else(|| BridgeError::validation("mindmap_outline_to_mindmap 缺少 outline"))?;
    let outline: Vec<MindmapOutlineItemPayload> = serde_json::from_value(outline_value)
        .map_err(|error| BridgeError::validation(format!("outline 反序列化失败: {error}")))?;
    if outline.is_empty() {
        return Err(BridgeError::validation(
            "mindmap_outline_to_mindmap 缺少 outline",
        ));
    }
    if outline.len() > 600 {
        return Err(BridgeError::validation(
            "mindmap_outline_to_mindmap outline 过多（最多 600）",
        ));
    }
    Ok(outline)
}

#[derive(Debug)]
struct MindmapOpsResult {
    applied: usize,
    errors: Vec<String>,
}

fn apply_mindmap_ops(
    root: &mut MindmapTreeNode,
    ops: &[MindmapOp],
) -> Result<MindmapOpsResult, BridgeError> {
    ensure_mindmap_uids(root);
    let mut applied = 0usize;
    let mut errors = Vec::new();
    for op in ops {
        let result = match op {
            MindmapOp::AddChild { parent_uid, node } => add_mindmap_child(root, parent_uid, node),
            MindmapOp::AddSiblingAfter { target_uid, node } => {
                add_mindmap_sibling_after(root, target_uid, node)
            }
            MindmapOp::UpdateText { uid, text } => update_mindmap_text(root, uid, text),
            MindmapOp::SetHyperlink { uid, hyperlink } => {
                set_mindmap_hyperlink(root, uid, hyperlink.clone())
            }
            MindmapOp::SetRefs { uid, refs } => set_mindmap_refs(root, uid, refs.clone()),
            MindmapOp::AppendNote { uid, markdown } => append_mindmap_note(root, uid, markdown),
            MindmapOp::DeleteNode { uid } => delete_mindmap_node(root, uid),
        };
        match result {
            Ok(true) => applied += 1,
            Ok(false) => errors.push(format!(
                "未找到可操作节点: {}",
                describe_mindmap_op_target(op)
            )),
            Err(error) => errors.push(error),
        }
    }
    ensure_mindmap_uids(root);
    Ok(MindmapOpsResult { applied, errors })
}

fn describe_mindmap_op_target(op: &MindmapOp) -> String {
    match op {
        MindmapOp::AddChild { parent_uid, .. } => format!("parentUid={parent_uid}"),
        MindmapOp::AddSiblingAfter { target_uid, .. } => format!("targetUid={target_uid}"),
        MindmapOp::UpdateText { uid, .. }
        | MindmapOp::SetHyperlink { uid, .. }
        | MindmapOp::SetRefs { uid, .. }
        | MindmapOp::AppendNote { uid, .. }
        | MindmapOp::DeleteNode { uid } => format!("uid={uid}"),
    }
}

fn mindmap_node_mut_by_path<'a>(
    node: &'a mut MindmapTreeNode,
    path: &[usize],
) -> Option<&'a mut MindmapTreeNode> {
    if path.is_empty() {
        return Some(node);
    }
    let (first, rest) = path.split_first()?;
    let child = node.children.get_mut(*first)?;
    mindmap_node_mut_by_path(child, rest)
}

fn build_mindmap_outline_tree(
    root_title: &str,
    outline: &[MindmapOutlineItemPayload],
    page_link_pattern: &str,
) -> Result<MindmapTreeNode, BridgeError> {
    if !page_link_pattern.contains("{page}") {
        return Err(BridgeError::validation(
            "mindmap_outline_to_mindmap 的 pageLinkPattern 必须包含 {page}",
        ));
    }

    let mut root = default_mindmap_tree();
    root.data.text = Some(root_title.trim().to_string());

    let mut latest_paths: Vec<Vec<usize>> = vec![vec![]];
    for item in outline {
        let level = item.level.clamp(1, 6) as usize;
        let title = item.title.trim();
        if title.is_empty() {
            continue;
        }
        let page = item.page.max(1);
        let hyperlink = page_link_pattern.replace("{page}", &page.to_string());
        let node = MindmapTreeNode {
            data: MindmapNodeData {
                uid: Some(next_mindmap_uid()),
                text: Some(title.to_string()),
                hyperlink: Some(hyperlink.clone()),
                note: None,
                refs: Some(vec![MindmapNodeRef {
                    kind: "pdf".into(),
                    asset_id: None,
                    file_url: Some(hyperlink),
                    page: Some(page),
                    slide: None,
                    title: Some(title.to_string()),
                    snippet: None,
                }]),
                extra: BTreeMap::new(),
            },
            children: vec![],
            extra: BTreeMap::new(),
        };

        if latest_paths.len() > level + 1 {
            latest_paths.truncate(level + 1);
        }
        while latest_paths.len() <= level {
            latest_paths.push(vec![]);
        }

        let parent_path = if level == 1 {
            vec![]
        } else {
            latest_paths.get(level - 1).cloned().unwrap_or_default()
        };
        let parent = mindmap_node_mut_by_path(&mut root, &parent_path)
            .ok_or_else(|| BridgeError::validation("mindmap_outline_to_mindmap 树路径异常"))?;
        parent.children.push(node);
        let next_index = parent.children.len().saturating_sub(1);
        let mut node_path = parent_path;
        node_path.push(next_index);
        latest_paths[level] = node_path;
    }

    ensure_mindmap_uids(&mut root);
    Ok(root)
}

fn build_mindmap_child(node: &MindmapNodeInput) -> MindmapTreeNode {
    let mut child = MindmapTreeNode {
        data: MindmapNodeData {
            uid: node.uid.clone(),
            text: Some(node.text.clone()),
            hyperlink: node.hyperlink.clone(),
            note: node.note.clone(),
            refs: node.refs.clone(),
            extra: BTreeMap::new(),
        },
        children: vec![],
        extra: BTreeMap::new(),
    };
    ensure_mindmap_uids(&mut child);
    child
}

fn add_mindmap_child(
    root: &mut MindmapTreeNode,
    parent_uid: &str,
    node: &MindmapNodeInput,
) -> Result<bool, String> {
    if let Some(parent) = find_mindmap_node_mut(root, parent_uid) {
        parent.children.push(build_mindmap_child(node));
        return Ok(true);
    }
    Ok(false)
}

fn add_mindmap_sibling_after(
    root: &mut MindmapTreeNode,
    target_uid: &str,
    node: &MindmapNodeInput,
) -> Result<bool, String> {
    insert_mindmap_sibling_after(&mut root.children, target_uid, node)
}

fn insert_mindmap_sibling_after(
    nodes: &mut Vec<MindmapTreeNode>,
    target_uid: &str,
    node: &MindmapNodeInput,
) -> Result<bool, String> {
    for index in 0..nodes.len() {
        if nodes[index].data.uid.as_deref() == Some(target_uid) {
            nodes.insert(index + 1, build_mindmap_child(node));
            return Ok(true);
        }
        if insert_mindmap_sibling_after(&mut nodes[index].children, target_uid, node)? {
            return Ok(true);
        }
    }
    Ok(false)
}

fn update_mindmap_text(root: &mut MindmapTreeNode, uid: &str, text: &str) -> Result<bool, String> {
    if let Some(node) = find_mindmap_node_mut(root, uid) {
        node.data.text = Some(text.to_string());
        return Ok(true);
    }
    Ok(false)
}

fn set_mindmap_hyperlink(
    root: &mut MindmapTreeNode,
    uid: &str,
    hyperlink: Option<String>,
) -> Result<bool, String> {
    if let Some(node) = find_mindmap_node_mut(root, uid) {
        node.data.hyperlink = hyperlink;
        return Ok(true);
    }
    Ok(false)
}

fn set_mindmap_refs(
    root: &mut MindmapTreeNode,
    uid: &str,
    refs: Vec<MindmapNodeRef>,
) -> Result<bool, String> {
    if let Some(node) = find_mindmap_node_mut(root, uid) {
        node.data.refs = Some(refs);
        return Ok(true);
    }
    Ok(false)
}

fn append_mindmap_note(
    root: &mut MindmapTreeNode,
    uid: &str,
    markdown: &str,
) -> Result<bool, String> {
    if let Some(node) = find_mindmap_node_mut(root, uid) {
        let next = if let Some(current) = node.data.note.as_ref() {
            if current.trim().is_empty() {
                markdown.to_string()
            } else {
                format!("{current}\n\n{markdown}")
            }
        } else {
            markdown.to_string()
        };
        node.data.note = Some(next);
        return Ok(true);
    }
    Ok(false)
}

fn delete_mindmap_node(root: &mut MindmapTreeNode, uid: &str) -> Result<bool, String> {
    if root.data.uid.as_deref() == Some(uid) {
        return Err("deleteNode: 不能删除根节点".into());
    }
    Ok(delete_mindmap_node_in_children(&mut root.children, uid))
}

fn delete_mindmap_node_in_children(nodes: &mut Vec<MindmapTreeNode>, uid: &str) -> bool {
    if let Some(index) = nodes
        .iter()
        .position(|node| node.data.uid.as_deref() == Some(uid))
    {
        nodes.remove(index);
        return true;
    }
    for child in nodes.iter_mut() {
        if delete_mindmap_node_in_children(&mut child.children, uid) {
            return true;
        }
    }
    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 integer_field(value: &Value, key: &str) -> Option<i64> {
    record_field(value, key).and_then(|field| {
        field
            .as_i64()
            .or_else(|| field.as_f64().map(|number| number as i64))
    })
}

fn is_pdf_asset(file_name: &str, mime_type: &str) -> bool {
    let lowered_mime = mime_type.trim().to_ascii_lowercase();
    let lowered_name = file_name.trim().to_ascii_lowercase();
    lowered_mime.contains("pdf") || lowered_name.ends_with(".pdf")
}

fn is_book_asset(file_name: &str, mime_type: &str) -> bool {
    let lowered_mime = mime_type.trim().to_ascii_lowercase();
    let lowered_name = file_name.trim().to_ascii_lowercase();
    lowered_mime.contains("epub")
        || lowered_mime.contains("ebook")
        || [".epub", ".mobi", ".azw3", ".azw", ".fb2"]
            .iter()
            .any(|suffix| lowered_name.ends_with(suffix))
}

fn classify_asset_kind(
    asset_type: &str,
    file_name: &str,
    mime_type: &str,
) -> KernelProjectionAssetKind {
    let lowered_type = asset_type.trim().to_ascii_lowercase();
    let lowered_mime = mime_type.trim().to_ascii_lowercase();
    if lowered_type == "mindmap" {
        return KernelProjectionAssetKind::Mindmap;
    }
    if lowered_type == "luckysheet" {
        return KernelProjectionAssetKind::Table;
    }
    if is_pdf_asset(file_name, mime_type) {
        return KernelProjectionAssetKind::Pdf;
    }
    if is_book_asset(file_name, mime_type) {
        return KernelProjectionAssetKind::Book;
    }
    if lowered_mime.starts_with("image/") {
        return KernelProjectionAssetKind::Image;
    }
    if lowered_mime.starts_with("video/") {
        return KernelProjectionAssetKind::Video;
    }
    if lowered_mime.starts_with("audio/") {
        return KernelProjectionAssetKind::Audio;
    }
    if lowered_type == "file" || lowered_type.is_empty() {
        return KernelProjectionAssetKind::File;
    }
    KernelProjectionAssetKind::Unknown
}

fn classify_asset_resource_kind(
    asset_kind: &KernelProjectionAssetKind,
) -> KernelProjectionResourceKind {
    match asset_kind {
        KernelProjectionAssetKind::Mindmap => KernelProjectionResourceKind::Mindmap,
        KernelProjectionAssetKind::Table => KernelProjectionResourceKind::Table,
        KernelProjectionAssetKind::Book => KernelProjectionResourceKind::Book,
        KernelProjectionAssetKind::Pdf => KernelProjectionResourceKind::Pdf,
        _ => KernelProjectionResourceKind::Asset,
    }
}

fn classify_asset_node_type(asset_kind: &KernelProjectionAssetKind) -> KernelNodeType {
    match asset_kind {
        KernelProjectionAssetKind::Mindmap => KernelNodeType::Mindmap,
        KernelProjectionAssetKind::Table => KernelNodeType::Table,
        KernelProjectionAssetKind::Book => KernelNodeType::Book,
        KernelProjectionAssetKind::Pdf => KernelNodeType::Pdf,
        _ => KernelNodeType::Asset,
    }
}

fn classify_asset_icon_hint(asset_kind: &KernelProjectionAssetKind) -> &'static str {
    match asset_kind {
        KernelProjectionAssetKind::Mindmap => "mindmap",
        KernelProjectionAssetKind::Table => "table",
        KernelProjectionAssetKind::Book => "book",
        KernelProjectionAssetKind::Pdf => "pdf",
        KernelProjectionAssetKind::Image => "image",
        KernelProjectionAssetKind::Video => "video",
        KernelProjectionAssetKind::Audio => "audio",
        _ => "file",
    }
}

fn build_projection_row_id(
    projection: &KernelProjectionKind,
    row_kind: &KernelProjectionRowKind,
    node_id: &str,
) -> String {
    match projection {
        KernelProjectionKind::FileTree => match row_kind {
            KernelProjectionRowKind::Document => format!("doc:{node_id}"),
            KernelProjectionRowKind::Index => format!("index:{node_id}"),
            KernelProjectionRowKind::Asset => format!("asset:{node_id}"),
            KernelProjectionRowKind::AssetFolder => format!("asset-folder:{node_id}"),
        },
        _ => format!("page:{node_id}"),
    }
}

fn build_document_projection_capabilities(child_count: u32) -> Vec<KernelProjectionCapability> {
    let mut capabilities = vec![
        KernelProjectionCapability::Open,
        KernelProjectionCapability::Drag,
        KernelProjectionCapability::Drop,
        KernelProjectionCapability::Select,
        KernelProjectionCapability::CreateChild,
        KernelProjectionCapability::Rename,
        KernelProjectionCapability::Archive,
        KernelProjectionCapability::Restore,
        KernelProjectionCapability::ContextMenu,
        KernelProjectionCapability::Reorder,
    ];
    if child_count > 0 {
        capabilities.insert(0, KernelProjectionCapability::Expand);
    }
    capabilities
}

fn build_asset_folder_projection_capabilities(child_count: u32) -> Vec<KernelProjectionCapability> {
    let mut capabilities = vec![
        KernelProjectionCapability::OpenAsset,
        KernelProjectionCapability::Select,
        KernelProjectionCapability::ContextMenu,
    ];
    if child_count > 0 {
        capabilities.insert(0, KernelProjectionCapability::Expand);
    }
    capabilities
}

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));
    }
    if let Some(sort_order) =
        integer_field(value, "sort_order").or_else(|| integer_field(value, "sortOrder"))
    {
        extra.insert("sortOrder".into(), json!(sort_order));
    }

    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 kernel_node_sort_order(node: &KernelNode) -> i64 {
    node.metadata
        .extra
        .get("sortOrder")
        .and_then(Value::as_i64)
        .unwrap_or(0)
}

fn file_tree_leaf_capabilities(open_asset: bool) -> Vec<KernelProjectionCapability> {
    let mut capabilities = vec![
        KernelProjectionCapability::Select,
        KernelProjectionCapability::ContextMenu,
    ];
    if open_asset {
        capabilities.insert(0, KernelProjectionCapability::OpenAsset);
    } else {
        capabilities.insert(0, KernelProjectionCapability::Open);
    }
    capabilities
}

fn make_projection_edge(
    parent_node_id: &str,
    child_node_id: &str,
    workspace_id: Option<&str>,
) -> KernelEdge {
    KernelEdge {
        id: format!("edge_parent_of_{}_{}", parent_node_id, child_node_id),
        edge_type: KernelEdgeType::ParentOf,
        workspace_id: workspace_id.map(ToOwned::to_owned),
        from_node_id: parent_node_id.to_string(),
        to_node_id: child_node_id.to_string(),
        metadata: BTreeMap::new(),
        audit: KernelAuditStamp::default(),
    }
}

fn make_projection_resource_meta(
    resource_kind: KernelProjectionResourceKind,
    document_id: Option<String>,
    asset_id: Option<String>,
    workspace_id: Option<String>,
    asset_kind: Option<KernelProjectionAssetKind>,
    icon_hint: &str,
    row_kind: &str,
) -> KernelProjectionResourceMeta {
    let mut extra = BTreeMap::new();
    extra.insert("rowKind".into(), json!(row_kind));
    KernelProjectionResourceMeta {
        resource_kind: Some(resource_kind),
        document_id,
        asset_id,
        workspace_id,
        asset_kind,
        icon_hint: Some(icon_hint.into()),
        extra,
    }
}

#[derive(Debug, Clone)]
struct NormalizedFileTreeAsset {
    id: String,
    document_id: String,
    workspace_id: Option<String>,
    title: String,
    resource_kind: KernelProjectionResourceKind,
    asset_kind: KernelProjectionAssetKind,
    icon_hint: &'static str,
}

fn infer_file_tree_asset_shape(
    asset_type: &str,
    file_name: &str,
    mime_type: &str,
) -> (
    KernelProjectionResourceKind,
    KernelProjectionAssetKind,
    &'static str,
) {
    let asset_kind = classify_asset_kind(asset_type, file_name, mime_type);
    (
        classify_asset_resource_kind(&asset_kind),
        asset_kind.clone(),
        classify_asset_icon_hint(&asset_kind),
    )
}

fn normalize_file_tree_asset(value: &Value) -> Option<NormalizedFileTreeAsset> {
    let id = string_field(value, "id")?;
    let document_id =
        string_field(value, "document_id").or_else(|| string_field(value, "documentId"))?;
    let file_name = string_field(value, "file_name")
        .or_else(|| string_field(value, "title"))
        .unwrap_or_else(|| "附件".into());
    let mime_type = string_field(value, "mime_type").unwrap_or_default();
    let asset_type = string_field(value, "asset_type").unwrap_or_else(|| "file".into());
    let (resource_kind, asset_kind, icon_hint) =
        infer_file_tree_asset_shape(&asset_type, &file_name, &mime_type);

    Some(NormalizedFileTreeAsset {
        id,
        document_id,
        workspace_id: string_field(value, "workspace_id")
            .or_else(|| string_field(value, "workspaceId")),
        title: file_name,
        resource_kind,
        asset_kind,
        icon_hint,
    })
}

fn build_file_tree_assets(
    data: &Value,
) -> (
    BTreeMap<String, Vec<NormalizedFileTreeAsset>>,
    BTreeMap<String, NormalizedFileTreeAsset>,
    BTreeMap<String, Vec<String>>,
) {
    let mut assets_by_doc = BTreeMap::<String, Vec<NormalizedFileTreeAsset>>::new();
    let mut asset_by_id = BTreeMap::<String, NormalizedFileTreeAsset>::new();
    let mut seen = BTreeMap::<String, bool>::new();

    for key in ["media_assets", "mindmap_assets", "table_assets"] {
        for value in data
            .get(key)
            .and_then(Value::as_array)
            .cloned()
            .unwrap_or_default()
        {
            let Some(asset) = normalize_file_tree_asset(&value) else {
                continue;
            };
            if seen.insert(asset.id.clone(), true).is_some() {
                continue;
            }
            assets_by_doc
                .entry(asset.document_id.clone())
                .or_default()
                .push(asset.clone());
            asset_by_id.insert(asset.id.clone(), asset);
        }
    }

    let child_map = data
        .get("mindmap_asset_children")
        .and_then(Value::as_object)
        .map(|object| {
            object
                .iter()
                .map(|(asset_id, children)| {
                    let child_ids = children
                        .as_array()
                        .cloned()
                        .unwrap_or_default()
                        .into_iter()
                        .filter_map(|value| value.as_str().map(ToOwned::to_owned))
                        .collect::<Vec<_>>();
                    (asset_id.clone(), child_ids)
                })
                .collect::<BTreeMap<_, _>>()
        })
        .unwrap_or_default();

    (assets_by_doc, asset_by_id, child_map)
}

fn build_file_tree_projection_result(
    subtree: &KernelSubtreeResult,
    _workspace_id: Option<&str>,
    root_node_id: Option<&str>,
    data: &Value,
) -> KernelProjectionResult {
    let (assets_by_doc, asset_by_id, child_assets_by_parent) = build_file_tree_assets(data);
    let page_ids = subtree
        .nodes
        .iter()
        .map(|node| (node.id.clone(), node.clone()))
        .collect::<BTreeMap<_, _>>();
    let mut child_pages = BTreeMap::<String, Vec<String>>::new();
    for node in &subtree.nodes {
        if let Some(parent_id) = node.parent_id.as_ref() {
            child_pages
                .entry(parent_id.clone())
                .or_default()
                .push(node.id.clone());
        }
    }
    for ids in child_pages.values_mut() {
        ids.sort_by_key(|node_id| {
            page_ids
                .get(node_id)
                .map(kernel_node_sort_order)
                .unwrap_or_default()
        });
    }

    let mut roots = if let Some(root_node_id) = root_node_id {
        vec![root_node_id.to_string()]
    } else {
        subtree
            .nodes
            .iter()
            .filter(|node| {
                node.parent_id
                    .as_ref()
                    .map(|parent_id| !page_ids.contains_key(parent_id))
                    .unwrap_or(true)
            })
            .map(|node| node.id.clone())
            .collect::<Vec<_>>()
    };
    roots.sort_by_key(|node_id| {
        page_ids
            .get(node_id)
            .map(kernel_node_sort_order)
            .unwrap_or_default()
    });

    let mut items = Vec::<KernelProjectionItem>::new();
    let mut edges = subtree.edges.clone();

    fn walk_page(
        page_id: &str,
        depth: u32,
        page_ids: &BTreeMap<String, KernelNode>,
        child_pages: &BTreeMap<String, Vec<String>>,
        assets_by_doc: &BTreeMap<String, Vec<NormalizedFileTreeAsset>>,
        asset_by_id: &BTreeMap<String, NormalizedFileTreeAsset>,
        child_assets_by_parent: &BTreeMap<String, Vec<String>>,
        items: &mut Vec<KernelProjectionItem>,
        edges: &mut Vec<KernelEdge>,
    ) {
        let Some(node) = page_ids.get(page_id) else {
            return;
        };
        let direct_child_pages = child_pages.get(page_id).cloned().unwrap_or_default();
        let doc_assets = assets_by_doc.get(page_id).cloned().unwrap_or_default();
        let mut nested_child_asset_ids = BTreeMap::<String, bool>::new();
        for asset in &doc_assets {
            if let Some(child_ids) = child_assets_by_parent.get(&asset.id) {
                for child_id in child_ids {
                    nested_child_asset_ids.insert(child_id.clone(), true);
                }
            }
        }
        let direct_asset_count = doc_assets
            .iter()
            .filter(|asset| !nested_child_asset_ids.contains_key(&asset.id))
            .count() as u32;
        let doc_child_count = 1 + direct_asset_count + direct_child_pages.len() as u32;
        let parent_node_id = if depth == 0 {
            None
        } else {
            node.parent_id.clone()
        };
        let workspace_id = node.workspace_id.clone();
        items.push(KernelProjectionItem {
            row_id: build_projection_row_id(
                &KernelProjectionKind::FileTree,
                &KernelProjectionRowKind::Document,
                &node.id,
            ),
            row_kind: KernelProjectionRowKind::Document,
            node_id: node.id.clone(),
            parent_node_id,
            node_type: KernelNodeType::Page,
            projection_kind: KernelProjectionKind::FileTree,
            title: node.metadata.title.clone(),
            depth,
            position: Some(kernel_node_sort_order(node)),
            child_count: doc_child_count,
            expandable: doc_child_count > 0,
            expanded_by_default: true,
            capabilities: build_document_projection_capabilities(doc_child_count),
            resource_meta: Some(make_projection_resource_meta(
                KernelProjectionResourceKind::Document,
                Some(node.id.clone()),
                None,
                workspace_id.clone(),
                None,
                "page",
                "document",
            )),
            icon_hint: Some("page".into()),
        });

        let index_node_id = format!("index:{}", node.id);
        items.push(KernelProjectionItem {
            row_id: build_projection_row_id(
                &KernelProjectionKind::FileTree,
                &KernelProjectionRowKind::Index,
                &node.id,
            ),
            row_kind: KernelProjectionRowKind::Index,
            node_id: index_node_id.clone(),
            parent_node_id: Some(node.id.clone()),
            node_type: KernelNodeType::IndexNode,
            projection_kind: KernelProjectionKind::FileTree,
            title: Some("index.md".into()),
            depth: depth + 1,
            position: Some(0),
            child_count: 0,
            expandable: false,
            expanded_by_default: false,
            capabilities: file_tree_leaf_capabilities(false),
            resource_meta: Some(make_projection_resource_meta(
                KernelProjectionResourceKind::Index,
                Some(node.id.clone()),
                None,
                workspace_id.clone(),
                None,
                "index",
                "index",
            )),
            icon_hint: Some("index".into()),
        });
        edges.push(make_projection_edge(
            &node.id,
            &index_node_id,
            workspace_id.as_deref(),
        ));

        let mut asset_position = 1i64;
        for asset in doc_assets {
            if nested_child_asset_ids.contains_key(&asset.id) {
                continue;
            }

            if let Some(child_ids) = child_assets_by_parent
                .get(&asset.id)
                .filter(|ids| !ids.is_empty())
            {
                let folder_node_id = format!("asset-folder:{}", asset.id);
                items.push(KernelProjectionItem {
                    row_id: build_projection_row_id(
                        &KernelProjectionKind::FileTree,
                        &KernelProjectionRowKind::AssetFolder,
                        &asset.id,
                    ),
                    row_kind: KernelProjectionRowKind::AssetFolder,
                    node_id: folder_node_id.clone(),
                    parent_node_id: Some(node.id.clone()),
                    node_type: KernelNodeType::Mindmap,
                    projection_kind: KernelProjectionKind::FileTree,
                    title: Some(asset.title.trim_end_matches(".json").to_string()),
                    depth: depth + 1,
                    position: Some(asset_position),
                    child_count: child_ids.len() as u32,
                    expandable: true,
                    expanded_by_default: false,
                    capabilities: build_asset_folder_projection_capabilities(child_ids.len() as u32),
                    resource_meta: Some(make_projection_resource_meta(
                        asset.resource_kind.clone(),
                        Some(asset.document_id.clone()),
                        Some(asset.id.clone()),
                        asset.workspace_id.clone(),
                        Some(KernelProjectionAssetKind::Mindmap),
                        "mindmap",
                        "asset_folder",
                    )),
                    icon_hint: Some("mindmap".into()),
                });
                edges.push(make_projection_edge(
                    &node.id,
                    &folder_node_id,
                    workspace_id.as_deref(),
                ));

                for (child_index, child_id) in child_ids.iter().enumerate() {
                    let Some(child_asset) = asset_by_id.get(child_id) else {
                        continue;
                    };
                    let child_node_id = format!("asset:{}", child_asset.id);
                    items.push(KernelProjectionItem {
                        row_id: build_projection_row_id(
                            &KernelProjectionKind::FileTree,
                            &KernelProjectionRowKind::Asset,
                            &child_asset.id,
                        ),
                        row_kind: KernelProjectionRowKind::Asset,
                        node_id: child_node_id.clone(),
                        parent_node_id: Some(folder_node_id.clone()),
                        node_type: KernelNodeType::Asset,
                        projection_kind: KernelProjectionKind::FileTree,
                        title: Some(child_asset.title.clone()),
                        depth: depth + 2,
                        position: Some(child_index as i64),
                        child_count: 0,
                        expandable: false,
                        expanded_by_default: false,
                        capabilities: file_tree_leaf_capabilities(true),
                        resource_meta: Some(make_projection_resource_meta(
                            child_asset.resource_kind.clone(),
                            Some(child_asset.document_id.clone()),
                            Some(child_asset.id.clone()),
                            child_asset.workspace_id.clone(),
                            Some(child_asset.asset_kind.clone()),
                            child_asset.icon_hint,
                            "asset",
                        )),
                        icon_hint: Some(child_asset.icon_hint.into()),
                    });
                    edges.push(make_projection_edge(
                        &folder_node_id,
                        &child_node_id,
                        workspace_id.as_deref(),
                    ));
                }
                asset_position += 1;
                continue;
            }

            let asset_node_id = format!("asset:{}", asset.id);
            items.push(KernelProjectionItem {
                row_id: build_projection_row_id(
                    &KernelProjectionKind::FileTree,
                    &KernelProjectionRowKind::Asset,
                    &asset.id,
                ),
                row_kind: KernelProjectionRowKind::Asset,
                node_id: asset_node_id.clone(),
                parent_node_id: Some(node.id.clone()),
                node_type: classify_asset_node_type(&asset.asset_kind),
                projection_kind: KernelProjectionKind::FileTree,
                title: Some(asset.title.clone()),
                depth: depth + 1,
                position: Some(asset_position),
                child_count: 0,
                expandable: false,
                expanded_by_default: false,
                capabilities: file_tree_leaf_capabilities(true),
                resource_meta: Some(make_projection_resource_meta(
                    asset.resource_kind.clone(),
                    Some(asset.document_id.clone()),
                    Some(asset.id.clone()),
                    asset.workspace_id.clone(),
                    Some(asset.asset_kind.clone()),
                    asset.icon_hint,
                    "asset",
                )),
                icon_hint: Some(asset.icon_hint.into()),
            });
            edges.push(make_projection_edge(
                &node.id,
                &asset_node_id,
                workspace_id.as_deref(),
            ));
            asset_position += 1;
        }

        for child_page_id in direct_child_pages {
            walk_page(
                &child_page_id,
                depth + 1,
                page_ids,
                child_pages,
                assets_by_doc,
                asset_by_id,
                child_assets_by_parent,
                items,
                edges,
            );
        }
    }

    for root_id in roots {
        walk_page(
            &root_id,
            0,
            &page_ids,
            &child_pages,
            &assets_by_doc,
            &asset_by_id,
            &child_assets_by_parent,
            &mut items,
            &mut edges,
        );
    }

    KernelProjectionResult {
        projection_id: format!(
            "kernel_projection:file_tree:{}",
            root_node_id.unwrap_or("root")
        ),
        projection: KernelProjectionKind::FileTree,
        root_node_id: root_node_id.map(ToOwned::to_owned),
        items,
        edges,
    }
}

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,
        }
    };
    if projection == KernelProjectionKind::FileTree {
        return Ok(build_file_tree_projection_result(
            &subtree,
            workspace_id,
            root_node_id,
            data,
        ));
    }
    let items = subtree
        .nodes
        .iter()
        .map(|node| {
            let child_count = subtree
                .edges
                .iter()
                .filter(|edge| {
                    edge.from_node_id == node.id && edge.edge_type == KernelEdgeType::ParentOf
                })
                .count() as u32;
            KernelProjectionItem {
                row_id: build_projection_row_id(
                    &projection,
                    &KernelProjectionRowKind::Document,
                    &node.id,
                ),
                row_kind: KernelProjectionRowKind::Document,
                node_id: node.id.clone(),
                parent_node_id: node.parent_id.clone(),
                node_type: node.node_type.clone(),
                projection_kind: projection.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,
                expandable: child_count > 0,
                expanded_by_default: true,
                capabilities: build_document_projection_capabilities(child_count),
                resource_meta: Some(KernelProjectionResourceMeta {
                    resource_kind: Some(KernelProjectionResourceKind::Document),
                    document_id: Some(node.id.clone()),
                    asset_id: None,
                    workspace_id: node.workspace_id.clone(),
                    asset_kind: None,
                    icon_hint: Some("page".into()),
                    extra: BTreeMap::new(),
                }),
                icon_hint: Some("page".into()),
            }
        })
        .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,
) -> Option<&'a mut MindmapTreeNode> {
    if node.data.uid.as_deref() == Some(uid) {
        return Some(node);
    }
    for child in &mut node.children {
        if let Some(found) = find_mindmap_node_mut(child, uid) {
            return Some(found);
        }
    }
    None
}

fn execute_query_result(
    _context_wire: RuntimeBridgeContextWire,
    query_wire: RuntimeQueryEnvelopeWire,
    data: Value,
) -> Result<Value, BridgeError> {
    match query_wire.name.as_str() {
        "documents.content.get" => {
            let payload: DocumentContentQueryPayload = parse_payload(query_wire.payload)?;
            let result = build_document_content_result(
                &data,
                &payload.document_id,
                payload.workspace_id.as_deref(),
            )?;
            serde_json::to_value(result).map_err(|error| {
                BridgeError::transport(format!("documents.content.get result 序列化失败: {error}"))
            })
        }
        "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| {
                BridgeError::transport(format!("mindmaps.get result 序列化失败: {error}"))
            })
        }
        "search.documents" => {
            let payload: SearchDocumentsQueryPayload = parse_payload(query_wire.payload)?;
            let dataset: SearchDocumentsDataset =
                serde_json::from_value(data).map_err(|error| {
                    BridgeError::validation(format!(
                        "search.documents dataset 反序列化失败: {error}"
                    ))
                })?;
            let result = evaluate_search_documents(
                &SearchDocumentsRequest {
                    query: payload.query,
                    workspace_id: payload.workspace_id,
                    page_id: payload.page_id,
                    limit: payload.limit.unwrap_or(30) as usize,
                    title_only: payload.title_only.unwrap_or(false),
                    exact: payload.exact.unwrap_or(false),
                    include_ocr: payload.include_ocr.unwrap_or(false),
                    time_range: payload.time_range.unwrap_or_else(|| "any".into()),
                    time_field: payload.time_field.unwrap_or_else(|| "updated".into()),
                    custom_range_from: payload.custom_range_from,
                    custom_range_to: payload.custom_range_to,
                },
                &dataset,
            );
            serde_json::to_value(SearchDocumentsEvaluation {
                enqueue_asset_ids: result.enqueue_asset_ids,
                results: result.results,
            })
            .map_err(|error| {
                BridgeError::transport(format!("search.documents result 序列化失败: {error}"))
            })
        }
        "search.recent" => {
            let rows = data
                .as_object()
                .and_then(|map| map.get("recents"))
                .and_then(Value::as_array)
                .cloned()
                .unwrap_or_default();
            let items = rows
                .into_iter()
                .filter_map(|row| {
                    let document_id = row.get("documentId")?.as_str()?.trim();
                    if document_id.is_empty() {
                        return None;
                    }
                    Some(json!({ "documentId": document_id }))
                })
                .collect::<Vec<Value>>();
            Ok(json!({ "items": items }))
        }
        "bridge.request.get"
        | "bridge.trace.get"
        | "bridge.command.get"
        | "bridge.workspace.overview" => {
            normalize_bridge_observability_result(&data, query_wire.name.as_str())
        }
        other => Err(BridgeError::validation(format!(
            "bridge runtime 暂不支持 query 执行: {other}"
        ))),
    }
}

fn execute_command(
    context_wire: RuntimeBridgeContextWire,
    command_wire: RuntimeCommandEnvelopeWire,
) -> 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 {
                name: "blocks.patch".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: PatchBlock {
                    page_id: payload.document_id.clone(),
                    block_id: payload.block_id.clone(),
                    workspace_id: payload.workspace_id.clone(),
                    revision: None,
                    block_snapshot_json: serde_json::to_string(&payload.next_block).map_err(
                        |error| {
                            BridgeError::validation(format!(
                                "blocks.patch block snapshot 序列化失败: {error}"
                            ))
                        },
                    )?,
                    conflict_detection_key: None,
                },
                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: json!({
                    "id": payload.document_id,
                    "blockId": payload.block_id,
                    "nextBlock": payload.next_block,
                }),
            }))
        }
        "blocks.move" => {
            let payload: BlockMoveCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "blocks.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: MoveBlock {
                    block_id: payload.block_id.clone(),
                    new_parent_block_id: Some(payload.target_document_id.clone()),
                    new_page_id: Some(payload.target_document_id.clone()),
                    prev_block_id: None,
                },
                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: json!({
                    "id": payload.block_id,
                    "sourceDocumentId": payload.source_document_id,
                    "targetDocumentId": payload.target_document_id,
                }),
            }))
        }
        "blocks.embed" => {
            let payload: BlockEmbedCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "blocks.embed".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: EmbedBlock {
                    source_document_id: payload.source_document_id.clone(),
                    source_block_id: payload.block_id.clone(),
                    target_document_id: payload.target_document_id.clone(),
                    target_block_id: payload.target_block_id.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: json!({
                    "sourceDocumentId": payload.source_document_id,
                    "blockId": payload.block_id,
                    "targetDocumentId": payload.target_document_id,
                    "targetBlockId": payload.target_block_id,
                }),
            }))
        }
        "documents.title.update" | "tree.node.rename" | "page.head.updateTitle" => {
            let payload: DocumentTitleCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command_name = match command_wire.name.as_str() {
                "tree.node.rename" => "tree.node.rename",
                "page.head.updateTitle" => "page.head.updateTitle",
                _ => "documents.title.update",
            };
            let command = CommandEnvelope {
                name: command_name.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: core_protocol::UpdatePageTitle {
                    page_id: payload.document_id.clone(),
                    title: payload.title.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: json!({
                    "id": payload.document_id,
                    "title": payload.title,
                }),
            }))
        }
        "documents.save" | "page.body.save" => {
            let payload: DocumentSaveCommandPayload = parse_payload(command_wire.payload.clone())?;
            let editor_document = normalize_save_editor_document(&payload)?;
            let canonical_content = legacy_content_from_editor_document(&editor_document);
            let command = CommandEnvelope {
                name: command_wire.name.clone(),
                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: core_protocol::SavePageContent {
                    page_id: payload.document_id.clone(),
                    workspace_id: payload.workspace_id.clone(),
                    revision: payload.revision,
                    content_json: serde_json::to_string(&canonical_content).map_err(|error| {
                        BridgeError::validation(format!(
                            "{} content 序列化失败: {error}",
                            command_wire.name
                        ))
                    })?,
                    conflict_detection_key: payload.conflict_detection_key.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: json!({
                    "id": payload.document_id,
                    "content": canonical_content,
                    "editorDocument": editor_document,
                    "tiptapDocument": payload.tiptap_document,
                    "expectedRevision": payload.revision,
                    "conflictDetectionKey": payload.conflict_detection_key,
                }),
            }))
        }
        "documents.embed" => {
            let payload: DocumentEmbedCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.embed".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: core_protocol::SavePageContent {
                    page_id: payload.document_id.clone(),
                    workspace_id: payload.workspace_id.clone(),
                    revision: payload.revision,
                    content_json: serde_json::to_string(&payload.content).map_err(|error| {
                        BridgeError::validation(format!(
                            "documents.embed content 序列化失败: {error}"
                        ))
                    })?,
                    conflict_detection_key: payload.conflict_detection_key.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: json!({
                    "id": payload.document_id,
                    "content": payload.content,
                    "expectedRevision": payload.revision,
                    "conflictDetectionKey": payload.conflict_detection_key,
                    "sourceDocumentId": payload.source_document_id,
                    "targetDocumentId": payload.target_document_id,
                    "anchorBlockId": payload.anchor_block_id,
                }),
            }))
        }
        "mindmaps.put" => {
            let payload: MindmapPutCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "mindmaps.put".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: PutMindmap {
                    document_id: payload.document_id.clone(),
                    mindmap_id: payload.mindmap_id.clone(),
                    data_json: serde_json::to_string(&payload.data).map_err(|error| {
                        BridgeError::validation(format!("mindmaps.put data 序列化失败: {error}"))
                    })?,
                    create_only: payload.create_only.unwrap_or(false),
                },
                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: json!({
                    "docId": payload.document_id,
                    "mindmapId": payload.mindmap_id,
                    "data": payload.data,
                    "createOnly": payload.create_only.unwrap_or(false),
                }),
            }))
        }
        "mindmaps.delete" => {
            let payload: MindmapDeleteCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "mindmaps.delete".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: payload.mindmap_id.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: json!({
                    "docId": payload.document_id,
                    "mindmapId": payload.mindmap_id,
                }),
            }))
        }
        "mindmaps.restore" => {
            let payload: MindmapRestoreCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "mindmaps.restore".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: payload.mindmap_id.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: json!({
                    "docId": payload.document_id,
                    "mindmapId": payload.mindmap_id,
                }),
            }))
        }
        "mindmaps.purge" => {
            let payload: MindmapPurgeCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "mindmaps.purge".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: payload.mindmap_id.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: json!({
                    "docId": payload.document_id,
                    "mindmapId": payload.mindmap_id,
                }),
            }))
        }
        "mindmaps.emptyTrashByWorkspace" => {
            let payload: MindmapEmptyTrashCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "mindmaps.emptyTrashByWorkspace".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: payload.workspace_id.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: json!({
                    "workspaceId": payload.workspace_id,
                }),
            }))
        }
        "documents.create" | "tree.node.create" => {
            let payload: DocumentCreateCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command_name = if command_wire.name == "tree.node.create" {
                "tree.node.create"
            } else {
                "documents.create"
            };
            let command = CommandEnvelope {
                name: command_name.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: payload.content.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: json!({
                    "id": payload.document_id,
                    "workspaceId": payload.workspace_id,
                    "parentId": payload.parent_id,
                    "title": payload.title,
                    "accessScope": payload.access_scope,
                    "content": payload.content,
                }),
            }))
        }
        "documents.move" | "tree.subtree.move" => {
            let payload: DocumentMoveCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command_name = if command_wire.name == "tree.subtree.move" {
                "tree.subtree.move"
            } else {
                "documents.move"
            };
            let command = CommandEnvelope {
                name: command_name.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: 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: json!({
                    "id": payload.document_id,
                    "parentId": payload.parent_id,
                    "sortOrder": payload.sort_order,
                }),
            }))
        }
        "documents.delete" => {
            let payload: DocumentDeleteCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.delete".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: payload.document_id.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: json!({
                    "id": payload.document_id,
                }),
            }))
        }
        "documents.restore" => {
            let payload: DocumentRestoreCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.restore".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: payload.document_id.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: json!({
                    "id": payload.document_id,
                }),
            }))
        }
        "documents.duplicate" => {
            let payload: DocumentDuplicateCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.duplicate".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: payload.source_document_id.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: json!({
                    "sourceId": payload.source_document_id,
                    "newId": payload.new_document_id,
                    "title": payload.title,
                }),
            }))
        }
        "documents.template" => {
            let payload: DocumentTemplateCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.template".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: payload.is_template,
                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: json!({
                    "id": payload.document_id,
                    "isTemplate": payload.is_template,
                }),
            }))
        }
        "documents.emptyTrashByWorkspace" => {
            let payload: DocumentEmptyTrashCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.emptyTrashByWorkspace".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: payload.workspace_id.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: json!({
                    "workspaceId": payload.workspace_id,
                }),
            }))
        }
        "documents.purge" => {
            let payload: DocumentPurgeCommandPayload = parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.purge".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: payload.document_id.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: json!({
                    "id": payload.document_id,
                }),
            }))
        }
        "documents.copy_tree" => {
            let payload: DocumentCopyTreeCommandPayload =
                parse_payload(command_wire.payload.clone())?;
            let command = CommandEnvelope {
                name: "documents.copy_tree".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: payload.items.len(),
                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: json!({
                    "items": payload.items.iter().map(|item| json!({
                        "documentId": item.document_id,
                        "recursive": item.recursive,
                    })).collect::<Vec<Value>>(),
                    "targetParentId": payload.target_parent_id,
                }),
            }))
        }
        other => Err(BridgeError::validation(format!(
            "bridge runtime 暂不支持 command: {other}"
        ))),
    }
}

fn parse_payload<T>(value: Value) -> Result<T, BridgeError>
where
    T: DeserializeOwned,
{
    serde_json::from_value(value).map_err(|error| {
        BridgeError::validation(format!("bridge runtime payload 反序列化失败: {error}"))
    })
}

fn normalize_editor_block_type_for_save(raw_type: &str) -> EditorBlockType {
    match raw_type {
        "heading" => EditorBlockType::Heading,
        "bullet_list_item" | "bullet_list" | "bullet-list" => EditorBlockType::BulletListItem,
        "numbered_list_item" | "ordered_list" | "ordered-list" => EditorBlockType::NumberedListItem,
        "todo" | "task" => EditorBlockType::Todo,
        "quote" | "blockquote" => EditorBlockType::Quote,
        "code" | "code_block" | "code-block" => EditorBlockType::CodeBlock,
        "page_reference" => EditorBlockType::PageReference,
        "block_reference" => EditorBlockType::BlockReference,
        _ => EditorBlockType::Paragraph,
    }
}

fn normalize_editor_block_from_legacy(block: &Value, index: usize) -> EditorBlock {
    let block_id = read_trimmed_string_field(block, &["blockId", "id"])
        .unwrap_or_else(|| format!("block-{}", index + 1));
    let raw_type = read_trimmed_string_field(block, &["blockType", "type"])
        .unwrap_or_else(|| "paragraph".into())
        .to_lowercase();
    let mut props = BlockProps::default();
    if matches!(
        normalize_editor_block_type_for_save(&raw_type),
        EditorBlockType::Heading
    ) {
        props.heading_level = block
            .get("props")
            .and_then(Value::as_object)
            .and_then(|map| map.get("level").or_else(|| map.get("headingLevel")))
            .and_then(Value::as_u64)
            .and_then(|value| u8::try_from(value).ok())
            .map(|value| value.clamp(1, 6));
    }
    if matches!(
        normalize_editor_block_type_for_save(&raw_type),
        EditorBlockType::Todo
    ) {
        props.checked = block
            .get("props")
            .and_then(Value::as_object)
            .and_then(|map| map.get("checked"))
            .and_then(Value::as_bool);
    }
    if matches!(
        normalize_editor_block_type_for_save(&raw_type),
        EditorBlockType::CodeBlock
    ) {
        props.language = block
            .get("props")
            .and_then(Value::as_object)
            .and_then(|map| map.get("language"))
            .and_then(Value::as_str)
            .map(ToOwned::to_owned);
    }
    let text = read_trimmed_string_field(block, &["content"])
        .filter(|value| !value.is_empty())
        .unwrap_or_else(|| extract_inline_text(block));

    EditorBlock {
        block_id,
        block_type: normalize_editor_block_type_for_save(&raw_type),
        props,
        content_nodes: build_text_content_nodes(&text),
        child_block_ids: vec![],
    }
}

fn editor_document_from_legacy_content(document_id: &str, content: &Value) -> EditorBlockDocument {
    let blocks = normalize_blocks_from_value(content)
        .iter()
        .enumerate()
        .map(|(index, block)| normalize_editor_block_from_legacy(block, index))
        .collect::<Vec<EditorBlock>>();
    let root_block_ids = blocks
        .iter()
        .map(|block| block.block_id.clone())
        .collect::<Vec<String>>();
    EditorBlockDocument {
        document_id: document_id.to_string(),
        root_block_ids,
        blocks,
    }
}

fn normalize_save_editor_document(
    payload: &DocumentSaveCommandPayload,
) -> Result<EditorBlockDocument, BridgeError> {
    if let Some(editor_document) = payload.editor_document.clone() {
        if let Ok(mut parsed) = serde_json::from_value::<EditorBlockDocument>(editor_document) {
            if parsed.document_id.trim().is_empty() {
                parsed.document_id = payload.document_id.clone();
            }
            if parsed.root_block_ids.is_empty() {
                parsed.root_block_ids = parsed
                    .blocks
                    .iter()
                    .map(|block| block.block_id.clone())
                    .collect();
            }
            return Ok(parsed);
        }
    }
    if let Some(tiptap_document) = payload.tiptap_document.clone() {
        let parsed = serde_json::from_value::<TiptapNode>(tiptap_document).map_err(|error| {
            BridgeError::validation(format!("documents.save tiptapDocument 非法: {error}"))
        })?;
        return EditorBlockDocumentTiptapBridge::from_tiptap_doc(
            payload.document_id.clone(),
            &parsed,
        )
        .map_err(|error| {
            BridgeError::validation(format!(
                "documents.save tiptap -> editorDocument 失败: {error:?}"
            ))
        });
    }
    Ok(editor_document_from_legacy_content(
        &payload.document_id,
        &payload.content,
    ))
}

fn legacy_props_from_editor_block(block: &EditorBlock) -> Option<Value> {
    match block.block_type {
        EditorBlockType::Heading => Some(json!({
            "level": block.props.heading_level.unwrap_or(1),
        })),
        EditorBlockType::Todo => Some(json!({
            "checked": block.props.checked.unwrap_or(false),
        })),
        EditorBlockType::CodeBlock => Some(json!({
            "language": block.props.language,
        })),
        _ => None,
    }
}

fn legacy_text_from_editor_block(block: &EditorBlock) -> String {
    block
        .content_nodes
        .iter()
        .filter_map(|node| match &node.payload {
            ContentNodePayload::Text { text, .. } => Some(text.as_str()),
            ContentNodePayload::HardBreak => Some("\n"),
            ContentNodePayload::ReferenceToken { token } => token.label.as_deref(),
        })
        .collect::<String>()
}

fn legacy_content_from_editor_document(document: &EditorBlockDocument) -> Value {
    let mut ordered = Vec::<&EditorBlock>::new();
    let mut seen = std::collections::BTreeSet::<String>::new();
    for root_block_id in &document.root_block_ids {
        if let Some(block) = document
            .blocks
            .iter()
            .find(|block| &block.block_id == root_block_id)
        {
            seen.insert(block.block_id.clone());
            ordered.push(block);
        }
    }
    for block in &document.blocks {
        if seen.insert(block.block_id.clone()) {
            ordered.push(block);
        }
    }

    Value::Array(
        ordered
            .into_iter()
            .map(|block| {
                json!({
                    "id": block.block_id,
                    "type": block.block_type,
                    "props": legacy_props_from_editor_block(block),
                    "content": legacy_text_from_editor_block(block),
                })
            })
            .collect(),
    )
}

fn to_bridge_context(context: RuntimeBridgeContextWire) -> BridgeContext {
    BridgeContext {
        deployment_id: context.deployment_id,
        project_id: context.project_id,
        workspace_id: context.workspace_id,
        request_id: context.request_id,
        trace_id: context.trace_id,
        actor_type: context.actor.actor_type,
        actor_id: context.actor.actor_id,
        session_id: context.actor.session_id,
        tenant_id: context.tenant_id,
        auth_token: context.auth_token,
        source_channel: context.source.channel,
        source_client: context.source.client,
        idempotency_key: context.idempotency_key,
        validate_only: context.validate_only,
        dry_run: context.dry_run,
    }
}

fn to_actor_payload(actor: &RuntimeActorWire) -> ActorPayload {
    ActorPayload {
        actor_type: actor.actor_type.clone(),
        actor_id: actor.actor_id.clone(),
        session_id: actor.session_id.clone(),
    }
}

fn to_source_payload(source: &RuntimeSourceWire) -> SourcePayload {
    SourcePayload {
        channel: source.channel.clone(),
        client: source.client.clone(),
    }
}

fn to_target_ref(target: Option<&RuntimeTargetWire>) -> Option<TargetRef> {
    target.map(|target| TargetRef {
        workspace_id: target.workspace_id.clone(),
        page_id: target.page_id.clone(),
        block_id: target.block_id.clone(),
    })
}

fn bridge_error_kind_to_wire(kind: &BridgeErrorKind) -> &'static str {
    match kind {
        BridgeErrorKind::Validation => "validation",
        BridgeErrorKind::Unauthorized => "unauthorized",
        BridgeErrorKind::Conflict => "conflict",
        BridgeErrorKind::NotFound => "not_found",
        BridgeErrorKind::Transport => "transport",
        BridgeErrorKind::Rejected => "rejected",
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn demo_context() -> RuntimeBridgeContextWire {
        RuntimeBridgeContextWire {
            deployment_id: Some("dep_1".into()),
            project_id: Some("proj_1".into()),
            workspace_id: Some("ws_1".into()),
            request_id: "req_1".into(),
            trace_id: "trace_1".into(),
            actor: RuntimeActorWire {
                actor_type: "user".into(),
                actor_id: "user_1".into(),
                session_id: Some("sess_1".into()),
            },
            source: RuntimeSourceWire {
                channel: "next-route".into(),
                client: "wolai-frontend".into(),
            },
            tenant_id: None,
            auth_token: None,
            idempotency_key: Some("idem_1".into()),
            validate_only: false,
            dry_run: false,
        }
    }

    #[test]
    fn blocks_get_query_plan_maps_to_blocks_get_by_id() {
        let plan = execute_runtime_input(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "blocks.get".into(),
                payload: json!({
                    "blockId": "block_1",
                    "workspaceId": "ws_1",
                }),
            },
            data: None,
        })
        .expect("query plan should build");

        match plan {
            RuntimeExecutionPlan::Query(plan) => {
                assert_eq!(plan.function_name, "blocks:getById");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "id": "block_1",
                        "workspaceId": "ws_1",
                    })
                );
            }
            RuntimeExecutionPlan::Command(_) => panic!("expected query plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected query plan"),
        }
    }

    #[test]
    fn blocks_patch_command_plan_maps_to_documents_update_content() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "blocks.patch".into(),
                command_id: "cmd_patch_1".into(),
                idempotency_key: Some("idem_patch".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("block_1".into()),
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                    "blockId": "block_1",
                    "nextBlock": {
                        "id": "block_1",
                        "type": "paragraph",
                    },
                }),
                reason: Some("替换块快照".into()),
                refs: vec![],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("command plan should build");

        match plan {
            RuntimeExecutionPlan::Command(plan) => {
                assert_eq!(plan.function_name, "documents:updateContent");
                assert_eq!(plan.command_name, "blocks.patch");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "id": "doc_1",
                        "blockId": "block_1",
                        "nextBlock": {
                            "id": "block_1",
                            "type": "paragraph",
                        },
                    })
                );
            }
            RuntimeExecutionPlan::Query(_) => panic!("expected command plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected command plan"),
        }
    }

    #[test]
    fn sidebar_dataset_query_plan_maps_to_sidebar_dataset_list() {
        let plan = execute_runtime_input(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "sidebar.dataset.list".into(),
                payload: json!({
                    "workspaceId": "ws_1",
                }),
            },
            data: None,
        })
        .expect("query plan should build");

        match plan {
            RuntimeExecutionPlan::Query(plan) => {
                assert_eq!(plan.function_name, "sidebar:datasetList");
                assert_eq!(plan.args_json, json!({ "workspaceId": "ws_1" }));
            }
            RuntimeExecutionPlan::Command(_) => panic!("expected query plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected query plan"),
        }
    }

    #[test]
    fn search_documents_query_plan_maps_to_search_documents() {
        let plan = execute_runtime_input(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "search.documents".into(),
                payload: json!({
                    "query": "rust",
                    "workspaceId": "ws_1",
                    "pageId": "page_1",
                    "limit": 20,
                    "titleOnly": false,
                    "exact": false,
                    "includeOcr": true,
                    "timeRange": "any",
                    "timeField": "updated",
                    "customRangeFrom": null,
                    "customRangeTo": null,
                }),
            },
            data: None,
        })
        .expect("query plan should build");

        match plan {
            RuntimeExecutionPlan::Query(plan) => {
                assert_eq!(plan.function_name, "search:documents");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "query": "rust",
                        "workspaceId": "ws_1",
                        "pageId": "page_1",
                        "limit": 20,
                        "cursor": null,
                        "titleOnly": false,
                        "exact": false,
                        "includeOcr": true,
                        "timeRange": "any",
                        "timeField": "updated",
                        "customRangeFrom": null,
                        "customRangeTo": null,
                    })
                );
            }
            RuntimeExecutionPlan::Command(_) => panic!("expected query plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected query plan"),
        }
    }

    #[test]
    fn search_documents_query_executes_in_rust_runtime() {
        let result = execute_runtime_query(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "search.documents".into(),
                payload: json!({
                    "query": "rust",
                    "workspaceId": "ws_1",
                    "pageId": null,
                    "limit": 10,
                    "titleOnly": false,
                    "exact": false,
                    "includeOcr": true,
                    "timeRange": "any",
                    "timeField": "updated",
                    "customRangeFrom": null,
                    "customRangeTo": null,
                }),
            },
            data: Some(json!({
                "documents": [
                    {
                        "id": "page_1",
                        "workspaceId": "ws_1",
                        "title": "Rust Notes",
                        "rawText": "这里有 rust 搜索内容",
                        "createdAt": "2026-04-15T00:00:00Z",
                        "updatedAt": "2026-04-15T01:00:00Z"
                    }
                ],
                "mindmaps": [],
                "tables": [],
                "tableRows": [],
                "assets": []
            })),
        })
        .expect("query result should build");

        assert_eq!(
            result,
            json!({
                "enqueueAssetIds": [],
                "results": [
                    {
                        "id": "page_1",
                        "title": "Rust Notes",
                        "snippet": "<mark>Rust</mark> Notes",
                        "updatedAt": "2026-04-15T01:00:00Z",
                        "createdAt": "2026-04-15T00:00:00Z",
                        "matchField": "title",
                        "hasOcr": false,
                        "publicPath": "/documents/page_1",
                        "nodeId": "page_1",
                        "subtreeRootId": "page_1",
                        "evidence": [
                            {
                                "kind": "title",
                                "nodeId": "page_1",
                                "snippet": "<mark>Rust</mark> Notes"
                            }
                        ],
                        "score": 3.0
                    }
                ]
            })
        );
    }

    #[test]
    fn documents_content_query_executes_into_page_subtree_projection() {
        let result = execute_runtime_query(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "documents.content.get".into(),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                }),
            },
            data: Some(json!({
                "title": "服务端标题",
                "content": [
                    {
                        "id": "heading_1",
                        "type": "heading",
                        "props": { "level": 1 },
                        "content": [{ "type": "text", "text": "章节一" }]
                    }
                ],
                "revision": 5,
                "conflict_detection_key": "doc_1:5"
            })),
        })
        .expect("query result should build");

        assert_eq!(result["revision"], json!(5));
        assert_eq!(result["pageSubtree"]["rootNodeId"], json!("doc_1"));
        assert_eq!(
            result["pageSubtree"]["outline"][0]["title"],
            json!("章节一")
        );
        assert_eq!(result["pageSubtree"]["stats"]["headingCount"], json!(1));
    }

    #[test]
    fn search_recent_query_executes_in_rust_runtime() {
        let result = execute_runtime_query(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "search.recent".into(),
                payload: json!({
                    "workspaceId": "ws_1",
                    "limit": 10,
                    "cursor": null,
                }),
            },
            data: Some(json!({
                "recents": [
                    {
                        "documentId": "page_1",
                        "workspaceId": "ws_1",
                        "lastAccessedAt": "2026-04-15T02:00:00Z"
                    }
                ]
            })),
        })
        .expect("query result should build");

        assert_eq!(
            result,
            json!({
                "items": [
                    {
                        "documentId": "page_1"
                    }
                ]
            })
        );
    }

    #[test]
    fn mindmaps_get_query_plan_maps_to_mindmaps_get() {
        let plan = execute_runtime_input(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "mindmaps.get".into(),
                payload: json!({
                    "documentId": "doc_1",
                    "mindmapId": "mind_1",
                    "workspaceId": "ws_1",
                }),
            },
            data: None,
        })
        .expect("mindmap query plan should build");

        match plan {
            RuntimeExecutionPlan::Query(plan) => {
                assert_eq!(plan.function_name, "mindmaps:get");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "docId": "doc_1",
                        "mindmapId": "mind_1",
                    })
                );
            }
            _ => panic!("expected query plan"),
        }
    }

    #[test]
    fn mindmaps_put_command_plan_maps_to_mindmaps_put() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "mindmaps.put".into(),
                command_id: "cmd_mindmap_put_1".into(),
                idempotency_key: Some("idem_mindmap_put".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("mind_1".into()),
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "mindmapId": "mind_1",
                    "data": {
                        "data": {"text": "中心主题"},
                        "children": [],
                    },
                    "createOnly": true,
                }),
                reason: Some("保存导图".into()),
                refs: vec!["task-032".into()],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("mindmap command plan should build");

        match plan {
            RuntimeExecutionPlan::Command(plan) => {
                assert_eq!(plan.function_name, "mindmaps:put");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "docId": "doc_1",
                        "mindmapId": "mind_1",
                        "data": {
                            "data": {"text": "中心主题"},
                            "children": [],
                        },
                        "createOnly": true,
                    })
                );
            }
            _ => panic!("expected command plan"),
        }
    }

    #[test]
    fn docs_search_tool_plan_uses_transport_and_transform_steps() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "docs_search".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "query": "rust",
                }),
                target: None,
                reason: Some("跨页搜索".into()),
                refs: vec!["task-050".into()],
            },
            data: None,
        })
        .expect("docs_search plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "docs_search");
                assert_eq!(plan.toolset_id, "toolset.docs_read");
                assert_eq!(plan.steps.len(), 2);
                assert_eq!(plan.steps[0].name, "docs_search.dataset");
                assert_eq!(plan.steps[1].name, "docs_search");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn docs_search_tool_result_uses_rust_search_evaluation() {
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "docs_search".into(),
                kind: "query".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "query": "rust",
                    "limit": 5,
                }),
                target: None,
                reason: Some("跨页搜索".into()),
                refs: vec!["task-050".into()],
            },
            data: Some(json!({
                "source": "convex",
                "datasets": [
                    {
                        "workspaceId": "ws_1",
                        "documents": [
                            {
                                "id": "page_1",
                                "workspaceId": "ws_1",
                                "title": "Rust 文档",
                                "rawText": "这里记录 rust runtime 收口",
                                "createdAt": "2026-04-15T00:00:00Z",
                                "updatedAt": "2026-04-16T00:00:00Z"
                            }
                        ],
                        "mindmaps": [],
                        "tables": [],
                        "tableRows": [],
                        "assets": []
                    }
                ]
            })),
        })
        .expect("docs_search result should build");

        assert_eq!(result.get("query").and_then(Value::as_str), Some("rust"));
        assert_eq!(
            result
                .get("results")
                .and_then(Value::as_array)
                .map(Vec::len),
            Some(1)
        );
        assert_eq!(result["results"][0]["id"], json!("page_1"));
    }

    #[test]
    fn docs_read_tool_result_trims_text_and_keeps_content_optional() {
        let long_text = "a".repeat(260);
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "docs_read".into(),
                kind: "query".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "documentId": "page_1",
                    "maxChars": 200,
                    "includeContent": true,
                }),
                target: None,
                reason: Some("读取文档".into()),
                refs: vec!["task-050".into()],
            },
            data: Some(json!({
                "source": "convex",
                "title": "示例页面",
                "workspaceId": "ws_1",
                "parentId": "parent_1",
                "updatedAt": "2026-04-16T00:00:00Z",
                "rawText": long_text,
                "rawTextLength": 260,
                "content": {"blocks": [{"id": "b1"}]}
            })),
        })
        .expect("docs_read result should build");

        assert_eq!(result["documentId"], json!("page_1"));
        let trimmed = result["rawText"]
            .as_str()
            .expect("rawText should be string");
        assert_eq!(trimmed.chars().count(), 201);
        assert!(trimmed.ends_with('…'));
        assert_eq!(result["rawTextLength"], json!(260));
        assert_eq!(result["content"]["blocks"][0]["id"], json!("b1"));
    }

    #[test]
    fn doc_get_tool_plan_uses_documents_content_query() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "doc_get".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "maxBlocks": 20,
                }),
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: None,
                }),
                reason: Some("查看页面结构".into()),
                refs: vec!["task-031".into()],
            },
            data: None,
        })
        .expect("tool plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "doc_get");
                assert_eq!(plan.invocation_kind, "query");
                assert_eq!(plan.execution_mode, "plan");
                assert_eq!(plan.toolset_id, "toolset.doc_read");
                assert_eq!(plan.steps[0].name, "documents.content.get");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn search_web_tool_plan_executes_inside_rust_runtime() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "search_web".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "query": "onlyoffice rust",
                    "count": 6,
                }),
                target: None,
                reason: Some("查询资料".into()),
                refs: vec!["task-040".into()],
            },
            data: None,
        })
        .expect("search_web plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "search_web");
                assert_eq!(plan.toolset_id, "toolset.readonly");
                assert_eq!(plan.steps.len(), 1);
                assert_eq!(plan.steps[0].kind, "external");
                assert_eq!(plan.steps[0].name, "search_web");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn image_read_tool_plan_uses_transport_step() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "image_read".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "assetId": "asset_1",
                }),
                target: None,
                reason: Some("读取 OCR".into()),
                refs: vec!["task-040".into()],
            },
            data: None,
        })
        .expect("image_read plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "image_read");
                assert_eq!(plan.toolset_id, "toolset.media_read");
                assert_eq!(plan.steps.len(), 1);
                assert_eq!(plan.steps[0].kind, "transport");
                assert_eq!(plan.steps[0].name, "media_assets.resolve");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn slash_run_tool_plan_builds_without_ts_fallback() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "slash_run".into(),
                kind: "command".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "text": "/new 新页面",
                }),
                target: None,
                reason: Some("执行斜杠命令".into()),
                refs: vec!["task-040".into()],
            },
            data: None,
        })
        .expect("slash_run plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "slash_run");
                assert_eq!(plan.toolset_id, "toolset.slash_write");
                assert_eq!(plan.steps.len(), 1);
                assert_eq!(plan.steps[0].kind, "transform");
                assert_eq!(plan.steps[0].name, "slash_run");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn slash_run_tool_result_parses_new_command_inside_rust() {
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "slash_run".into(),
                kind: "command".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "text": "/new 新页面",
                }),
                target: None,
                reason: Some("执行斜杠命令".into()),
                refs: vec!["task-040".into()],
            },
            data: Some(json!({
                "source": "ai-agent-route"
            })),
        })
        .expect("slash_run result should build");

        assert_eq!(result.get("ok"), Some(&json!(true)));
        assert_eq!(result.get("source"), Some(&json!("ai-agent-route")));
        assert_eq!(result.pointer("/parsed/command"), Some(&json!("new_doc")));
        assert_eq!(
            result.pointer("/parsed/params/title"),
            Some(&json!("新页面"))
        );
    }

    #[test]
    fn doc_replace_range_tool_executes_in_rust_runtime() {
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "doc_replace_range".into(),
                kind: "command".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "blockId": "block_1",
                    "text": "新的正文",
                    "mode": "replace",
                }),
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("block_1".into()),
                }),
                reason: Some("改写段落".into()),
                refs: vec!["task-031".into()],
            },
            data: Some(json!({
                "source": "client",
                "blocks": [
                    {
                        "id": "block_1",
                        "type": "paragraph",
                        "content": [{"type":"text","text":"旧内容"}],
                        "children": []
                    }
                ]
            })),
        })
        .expect("tool result should build");

        assert_eq!(
            result,
            json!({
                "ok": true,
                "source": "client",
                "blockId": "block_1",
                "mode": "replace",
                "editorCommandSource": "rust_editor_command",
                "editorCommands": [
                    {
                        "kind": "replace_block",
                        "blockId": "block_1",
                        "blockType": null,
                        "props": null,
                        "contentNodes": [
                            {
                                "payload": {
                                    "type": "text",
                                    "text": "新的正文",
                                    "marks": []
                                },
                                "attrs": {}
                            }
                        ]
                    }
                ],
                "data": [
                    {
                        "id": "block_1",
                        "type": "paragraph",
                        "content": [{"type":"text","text":"新的正文"}],
                        "children": []
                    }
                ]
            })
        );
    }

    #[test]
    fn doc_insert_blocks_tool_emits_editor_commands() {
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "doc_insert_blocks".into(),
                kind: "command".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "afterBlockId": "block_1",
                    "blocks": [
                        {
                            "type": "heading",
                            "level": 2,
                            "text": "Rust 命令插入标题"
                        }
                    ]
                }),
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("block_1".into()),
                }),
                reason: Some("在块后插入标题".into()),
                refs: vec!["task-054".into()],
            },
            data: Some(json!({
                "source": "client",
                "blocks": [
                    {
                        "id": "block_1",
                        "type": "paragraph",
                        "content": [{"type":"text","text":"旧内容"}],
                        "children": []
                    }
                ]
            })),
        })
        .expect("tool result should build");

        assert_eq!(result.get("ok"), Some(&json!(true)));
        assert_eq!(
            result.get("editorCommandSource"),
            Some(&json!("rust_editor_command"))
        );
        assert_eq!(
            result.pointer("/editorCommands/0/kind"),
            Some(&json!("insert_block_after"))
        );
        assert_eq!(
            result.pointer("/editorCommands/0/afterBlockId"),
            Some(&json!("block_1"))
        );
        assert_eq!(
            result.pointer("/editorCommands/0/block/blockType"),
            Some(&json!("heading"))
        );
        assert_eq!(
            result.pointer("/editorCommands/0/block/props/headingLevel"),
            Some(&json!(2))
        );
        assert_eq!(
            result.pointer("/editorCommands/0/block/contentNodes/0/payload/text"),
            Some(&json!("Rust 命令插入标题"))
        );
    }

    #[test]
    fn documents_save_command_plan_normalizes_tiptap_into_rust_truth() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "documents.save".into(),
                command_id: "cmd_save_1".into(),
                idempotency_key: Some("idem_save".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: None,
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                    "revision": 4,
                    "content": [],
                    "tiptapDocument": {
                        "type": "doc",
                        "content": [
                            {
                                "type": "paragraph",
                                "attrs": {
                                    "blockId": "p_1"
                                },
                                "content": [
                                    {
                                        "type": "text",
                                        "text": "来自 Rust tiptap 归一化"
                                    }
                                ]
                            }
                        ]
                    },
                    "conflictDetectionKey": "doc_1:4"
                }),
                reason: Some("保存正文".into()),
                refs: vec!["task-055".into()],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("documents.save plan should build");

        let RuntimeExecutionPlan::Command(plan) = plan else {
            panic!("expected command plan");
        };

        assert_eq!(plan.function_name, "documents:updateContent");
        assert_eq!(plan.args_json.get("id"), Some(&json!("doc_1")));
        assert_eq!(
            plan.args_json.pointer("/editorDocument/rootBlockIds/0"),
            Some(&json!("p_1"))
        );
        assert_eq!(
            plan.args_json.pointer("/editorDocument/blocks/0/blockType"),
            Some(&json!("paragraph"))
        );
        assert_eq!(plan.args_json.pointer("/content/0/id"), Some(&json!("p_1")));
        assert_eq!(
            plan.args_json.pointer("/content/0/content"),
            Some(&json!("来自 Rust tiptap 归一化"))
        );
    }

    #[test]
    fn documents_save_command_plan_falls_back_from_invalid_editor_document_to_tiptap() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "documents.save".into(),
                command_id: "cmd_save_2".into(),
                idempotency_key: Some("idem_save_2".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: None,
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                    "revision": 6,
                    "editorDocument": {
                        "documentId": "doc_1",
                        "rootBlockIds": ["legacy_block_1"],
                        "blocks": [
                            {
                                "blockId": "legacy_block_1",
                                "blockType": "paragraph",
                                "contentNodes": [
                                    {
                                        "attrs": {}
                                    }
                                ],
                                "childBlockIds": []
                            }
                        ]
                    },
                    "tiptapDocument": {
                        "type": "doc",
                        "content": [
                            {
                                "type": "paragraph",
                                "attrs": {
                                    "blockId": "p_1"
                                },
                                "content": [
                                    {
                                        "type": "text",
                                        "text": "来自 tiptap 回退"
                                    }
                                ]
                            }
                        ]
                    },
                    "content": [
                        {
                            "id": "content_block_1",
                            "type": "paragraph",
                            "content": "来自 content 回退"
                        }
                    ],
                    "conflictDetectionKey": "doc_1:6"
                }),
                reason: Some("保存正文".into()),
                refs: vec!["task-save-fallback".into()],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("documents.save plan should build");

        let RuntimeExecutionPlan::Command(plan) = plan else {
            panic!("expected command plan");
        };

        assert_eq!(plan.function_name, "documents:updateContent");
        assert_eq!(plan.args_json.pointer("/content/0/id"), Some(&json!("p_1")));
        assert_eq!(
            plan.args_json.pointer("/content/0/content"),
            Some(&json!("来自 tiptap 回退"))
        );
        assert_eq!(
            plan.args_json.pointer("/editorDocument/rootBlockIds/0"),
            Some(&json!("p_1"))
        );
        assert_eq!(
            plan.args_json.pointer("/editorDocument/blocks/0/blockId"),
            Some(&json!("p_1"))
        );
    }

    #[test]
    fn documents_save_command_plan_prefers_valid_editor_document_over_other_sources() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "documents.save".into(),
                command_id: "cmd_save_3".into(),
                idempotency_key: Some("idem_save_3".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: None,
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                    "revision": 7,
                    "editorDocument": {
                        "documentId": "doc_1",
                        "rootBlockIds": ["editor_block_1"],
                        "blocks": [
                            {
                                "blockId": "editor_block_1",
                                "blockType": "paragraph",
                                "contentNodes": [
                                    {
                                        "payload": {
                                            "type": "text",
                                            "text": "来自 editorDocument",
                                            "marks": []
                                        },
                                        "attrs": {}
                                    }
                                ],
                                "childBlockIds": []
                            }
                        ]
                    },
                    "tiptapDocument": {
                        "type": "doc",
                        "content": [
                            {
                                "type": "paragraph",
                                "attrs": {
                                    "blockId": "p_1"
                                },
                                "content": [
                                    {
                                        "type": "text",
                                        "text": "来自 tiptap"
                                    }
                                ]
                            }
                        ]
                    },
                    "content": [
                        {
                            "id": "content_block_1",
                            "type": "paragraph",
                            "content": "来自 content"
                        }
                    ],
                    "conflictDetectionKey": "doc_1:7"
                }),
                reason: Some("保存正文".into()),
                refs: vec!["task-save-prefer-editor".into()],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("documents.save plan should build");

        let RuntimeExecutionPlan::Command(plan) = plan else {
            panic!("expected command plan");
        };

        assert_eq!(plan.function_name, "documents:updateContent");
        assert_eq!(
            plan.args_json.pointer("/content/0/content"),
            Some(&json!("来自 editorDocument"))
        );
        assert_eq!(
            plan.args_json.pointer("/editorDocument/blocks/0/contentNodes/0/payload/text"),
            Some(&json!("来自 editorDocument"))
        );
        assert_eq!(
            plan.args_json.pointer("/content/0/id"),
            Some(&json!("editor_block_1"))
        );
    }

    #[test]
    fn documents_save_command_plan_supports_legacy_content_only_payloads() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "page.body.save".into(),
                command_id: "cmd_save_4".into(),
                idempotency_key: Some("idem_save_4".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: None,
                }),
                payload: json!({
                    "documentId": "doc_1",
                    "workspaceId": "ws_1",
                    "revision": 8,
                    "content": [
                        {
                            "id": "legacy_content_1",
                            "type": "paragraph",
                            "content": "来自旧式 content-only 保存"
                        }
                    ],
                    "conflictDetectionKey": "doc_1:8"
                }),
                reason: Some("保存正文".into()),
                refs: vec!["task-save-content-only".into()],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("page.body.save plan should build");

        let RuntimeExecutionPlan::Command(plan) = plan else {
            panic!("expected command plan");
        };

        assert_eq!(plan.function_name, "documents:updateContent");
        assert_eq!(plan.args_json.pointer("/content/0/id"), Some(&json!("legacy_content_1")));
        assert_eq!(
            plan.args_json.pointer("/content/0/content"),
            Some(&json!("来自旧式 content-only 保存"))
        );
        assert_eq!(
            plan.args_json.pointer("/editorDocument/rootBlockIds/0"),
            Some(&json!("legacy_content_1"))
        );
    }

    #[test]
    fn blocks_move_command_plan_maps_to_blocks_move() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "blocks.move".into(),
                command_id: "cmd_move_1".into(),
                idempotency_key: Some("idem_move".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("block_1".into()),
                }),
                payload: json!({
                    "sourceDocumentId": "doc_1",
                    "blockId": "block_1",
                    "targetDocumentId": "doc_2",
                }),
                reason: Some("移动块".into()),
                refs: vec![],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("command plan should build");

        match plan {
            RuntimeExecutionPlan::Command(plan) => {
                assert_eq!(plan.function_name, "blocks:move");
                assert_eq!(plan.command_name, "blocks.move");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "id": "block_1",
                        "sourceDocumentId": "doc_1",
                        "targetDocumentId": "doc_2",
                    })
                );
            }
            RuntimeExecutionPlan::Query(_) => panic!("expected command plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected command plan"),
        }
    }

    #[test]
    fn blocks_embed_command_plan_maps_to_blocks_embed() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "blocks.embed".into(),
                command_id: "cmd_embed_1".into(),
                idempotency_key: Some("idem_embed".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_2".into()),
                    block_id: Some("block_2".into()),
                }),
                payload: json!({
                    "sourceDocumentId": "doc_1",
                    "blockId": "block_1",
                    "targetDocumentId": "doc_2",
                    "targetBlockId": "anchor_1",
                }),
                reason: Some("嵌入块".into()),
                refs: vec![],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("command plan should build");

        match plan {
            RuntimeExecutionPlan::Command(plan) => {
                assert_eq!(plan.function_name, "blocks:insert");
                assert_eq!(plan.command_name, "blocks.embed");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "sourceDocumentId": "doc_1",
                        "blockId": "block_1",
                        "targetDocumentId": "doc_2",
                        "targetBlockId": "anchor_1",
                    })
                );
            }
            RuntimeExecutionPlan::Query(_) => panic!("expected command plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected command plan"),
        }
    }

    #[test]
    fn documents_embed_command_plan_maps_to_documents_update_content() {
        let plan = execute_runtime_input(RuntimeInput::Command {
            context: demo_context(),
            command: RuntimeCommandEnvelopeWire {
                name: "documents.embed".into(),
                command_id: "cmd_doc_embed_1".into(),
                idempotency_key: Some("idem_doc_embed".into()),
                actor: RuntimeActorWire {
                    actor_type: "user".into(),
                    actor_id: "user_1".into(),
                    session_id: Some("sess_1".into()),
                },
                source: RuntimeSourceWire {
                    channel: "next-route".into(),
                    client: "wolai-frontend".into(),
                },
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_2".into()),
                    block_id: None,
                }),
                payload: json!({
                    "documentId": "doc_2",
                    "workspaceId": "ws_1",
                    "revision": 5,
                    "content": [{ "id": "block_1", "type": "pageReference" }],
                    "conflictDetectionKey": "conflict_5",
                    "sourceDocumentId": "doc_1",
                    "targetDocumentId": "doc_2",
                    "anchorBlockId": "anchor_1",
                }),
                reason: Some("嵌入页面".into()),
                refs: vec![],
                dry_run: false,
                validate_only: false,
            },
        })
        .expect("command plan should build");

        match plan {
            RuntimeExecutionPlan::Command(plan) => {
                assert_eq!(plan.function_name, "documents:updateContent");
                assert_eq!(plan.command_name, "documents.embed");
                assert_eq!(
                    plan.args_json,
                    json!({
                        "id": "doc_2",
                        "content": [{ "id": "block_1", "type": "pageReference" }],
                        "expectedRevision": 5,
                        "conflictDetectionKey": "conflict_5",
                        "sourceDocumentId": "doc_1",
                        "targetDocumentId": "doc_2",
                        "anchorBlockId": "anchor_1",
                    })
                );
            }
            RuntimeExecutionPlan::Query(_) => panic!("expected command plan"),
            RuntimeExecutionPlan::Tool(_) => panic!("expected command plan"),
        }
    }

    #[test]
    fn mindmap_get_tool_plan_uses_mindmaps_get_query() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "mindmap_get".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "documentId": "doc_1",
                    "mindmapId": "mind_1",
                    "maxNodes": 20,
                }),
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("mind_1".into()),
                }),
                reason: Some("查看导图摘要".into()),
                refs: vec!["task-032".into()],
            },
            data: None,
        })
        .expect("tool plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "mindmap_get");
                assert_eq!(plan.toolset_id, "toolset.mindmap_read");
                assert_eq!(plan.steps[0].name, "mindmaps.get");
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[test]
    fn mindmap_apply_ops_executes_in_rust_runtime() {
        let result = execute_runtime_query(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "mindmap_apply_ops".into(),
                kind: "command".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "documentId": "doc_1",
                    "mindmapId": "mind_1",
                    "ops": [
                        {
                            "op": "addChild",
                            "parentUid": "root_1",
                            "node": {
                                "text": "新分支"
                            }
                        }
                    ],
                }),
                target: Some(RuntimeTargetWire {
                    workspace_id: Some("ws_1".into()),
                    page_id: Some("doc_1".into()),
                    block_id: Some("mind_1".into()),
                }),
                reason: Some("补充分支".into()),
                refs: vec!["task-032".into()],
            },
            data: Some(json!({
                "source": "convex",
                "data": {
                    "data": {
                        "uid": "root_1",
                        "text": "中心主题"
                    },
                    "children": []
                }
            })),
        })
        .expect("mindmap tool result should build");

        assert_eq!(result.get("ok"), Some(&json!(true)));
        assert_eq!(result.get("source"), Some(&json!("convex")));
        assert_eq!(result.get("applied"), Some(&json!(1)));
        assert_eq!(result.get("errors"), Some(&json!([])));
        assert_eq!(result.pointer("/data/data/uid"), Some(&json!("root_1")));
        assert_eq!(result.pointer("/data/data/text"), Some(&json!("中心主题")));
        assert_eq!(
            result.pointer("/data/children/0/data/text"),
            Some(&json!("新分支"))
        );
        let generated_uid = result
            .pointer("/data/children/0/data/uid")
            .and_then(Value::as_str)
            .expect("mindmap child uid should exist");
        assert!(
            generated_uid.starts_with("rust_mindmap_uid_"),
            "unexpected generated uid: {generated_uid}"
        );
        assert_eq!(result.pointer("/meta/reason"), Some(&Value::Null));
    }

    #[test]
    fn bridge_request_query_plan_maps_to_bridge_logs() {
        let plan = execute_runtime_input(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "bridge.request.get".into(),
                payload: json!({
                    "workspaceId": "ws_1",
                    "requestId": "req_lookup_1",
                    "commandId": "cmd_lookup_1",
                }),
            },
            data: None,
        })
        .expect("bridge request query plan should build");

        match plan {
            RuntimeExecutionPlan::Query(plan) => {
                assert_eq!(plan.function_name, "bridgeLogs:listByRequest");
                assert_eq!(plan.args_json["workspaceId"], json!("ws_1"));
                assert_eq!(plan.args_json["requestId"], json!("req_lookup_1"));
                assert_eq!(plan.args_json["commandId"], json!("cmd_lookup_1"));
            }
            _ => panic!("expected query plan"),
        }
    }

    #[test]
    fn bridge_trace_tool_plan_uses_observe_toolset() {
        let plan = execute_runtime_input(RuntimeInput::Tool {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "bridge_trace_get".into(),
                kind: "query".into(),
                mode: Some("plan".into()),
                args_json: json!({
                    "workspaceId": "ws_1",
                    "traceId": "trace_lookup_1",
                }),
                target: None,
                reason: Some("排查链路".into()),
                refs: vec!["task-034".into()],
            },
            data: None,
        })
        .expect("bridge trace tool plan should build");

        match plan {
            RuntimeExecutionPlan::Tool(plan) => {
                assert_eq!(plan.tool_name, "bridge_trace_get");
                assert_eq!(plan.toolset_id, "toolset.observe_read");
                assert_eq!(plan.steps[0].name, "bridge.trace.get");
                assert_eq!(
                    plan.steps[0].function_name.as_deref(),
                    Some("bridgeLogs:listByTrace")
                );
            }
            _ => panic!("expected tool plan"),
        }
    }

    #[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]["rowId"], json!("page:page_root"));
        assert_eq!(result["items"][0]["rowKind"], json!("document"));
        assert_eq!(result["items"][0]["nodeId"], json!("page_root"));
        assert_eq!(result["items"][0]["position"], json!(0));
        assert_eq!(result["items"][0]["projectionKind"], json!("sidebar_tree"));
        assert_eq!(
            result["items"][0]["resourceMeta"]["resourceKind"],
            json!("document")
        );
        assert!(result["items"][0]["capabilities"]
            .as_array()
            .map(|caps| caps.contains(&json!("create-child")))
            .unwrap_or(false));
        assert_eq!(result["items"][1]["parentNodeId"], json!("page_root"));
        assert_eq!(result["items"][1]["position"], json!(1));
    }

    #[test]
    fn kernel_project_view_query_executes_into_file_tree_projection_contract() {
        let result = execute_runtime_query(RuntimeInput::Query {
            context: demo_context(),
            query: RuntimeQueryEnvelopeWire {
                name: "kernel.project_view".into(),
                payload: json!({
                    "projection": "file_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": 2,
                        "is_starred": false,
                        "is_template": false,
                        "created_at": "2026-04-16T00:00:00Z",
                        "updated_at": "2026-04-16T00:00:00Z"
                    }
                ],
                "media_assets": [
                    {
                        "id": "asset_img_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_root",
                        "asset_type": "file",
                        "file_name": "cover.png",
                        "mime_type": "image/png",
                        "storage_path": "cover.png"
                    },
                    {
                        "id": "asset_pdf_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_child",
                        "asset_type": "file",
                        "file_name": "notes.pdf",
                        "mime_type": "application/pdf",
                        "storage_path": "notes.pdf"
                    },
                    {
                        "id": "asset_book_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_child",
                        "asset_type": "file",
                        "file_name": "guide.epub",
                        "mime_type": "application/epub+zip",
                        "storage_path": "guide.epub"
                    },
                    {
                        "id": "asset_ref_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_root",
                        "asset_type": "file",
                        "file_name": "diagram-source.png",
                        "mime_type": "image/png",
                        "storage_path": "diagram-source.png"
                    }
                ],
                "mindmap_assets": [
                    {
                        "id": "mind_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_root",
                        "asset_type": "mindmap",
                        "file_name": "roadmap.json",
                        "mime_type": "application/json",
                        "storage_path": "roadmap.json"
                    }
                ],
                "table_assets": [
                    {
                        "id": "table_1",
                        "workspace_id": "ws_1",
                        "document_id": "page_root",
                        "asset_type": "luckysheet",
                        "file_name": "budget.luckysheet",
                        "mime_type": "application/json",
                        "storage_path": "budget.luckysheet"
                    }
                ],
                "mindmap_asset_children": {
                    "mind_1": ["asset_ref_1"]
                }
            })),
        })
        .expect("file_tree projection result should build");

        let items = result["items"].as_array().expect("items should be array");
        let item_by_row_id = items
            .iter()
            .filter_map(|item| {
                item.get("rowId")
                    .and_then(Value::as_str)
                    .map(|row_id| (row_id.to_string(), item))
            })
            .collect::<std::collections::BTreeMap<_, _>>();

        assert_eq!(result["projection"], json!("file_tree"));
        assert_eq!(
            result["projectionId"],
            json!("kernel_projection:file_tree:page_root")
        );
        assert_eq!(
            item_by_row_id["doc:page_root"]["rowKind"],
            json!("document")
        );
        assert_eq!(
            item_by_row_id["doc:page_root"]["resourceMeta"]["resourceKind"],
            json!("document")
        );
        assert_eq!(item_by_row_id["index:page_root"]["rowKind"], json!("index"));
        assert_eq!(
            item_by_row_id["index:page_root"]["resourceMeta"]["resourceKind"],
            json!("index")
        );
        assert_eq!(
            item_by_row_id["asset:asset_img_1"]["resourceMeta"]["assetKind"],
            json!("image")
        );
        assert_eq!(
            item_by_row_id["asset:asset_img_1"]["resourceMeta"]["resourceKind"],
            json!("asset")
        );
        assert_eq!(
            item_by_row_id["asset-folder:mind_1"]["rowKind"],
            json!("asset_folder")
        );
        assert_eq!(
            item_by_row_id["asset-folder:mind_1"]["resourceMeta"]["resourceKind"],
            json!("mindmap")
        );
        assert_eq!(
            item_by_row_id["asset-folder:mind_1"]["resourceMeta"]["assetKind"],
            json!("mindmap")
        );
        assert_eq!(
            item_by_row_id["asset:asset_ref_1"]["parentNodeId"],
            json!("asset-folder:mind_1")
        );
        assert_eq!(
            item_by_row_id["asset:asset_ref_1"]["capabilities"][0],
            json!("open-asset")
        );
        assert_eq!(
            item_by_row_id["asset:table_1"]["resourceMeta"]["resourceKind"],
            json!("table")
        );
        assert_eq!(
            item_by_row_id["asset:asset_pdf_1"]["resourceMeta"]["resourceKind"],
            json!("pdf")
        );
        assert_eq!(
            item_by_row_id["asset:asset_book_1"]["resourceMeta"]["resourceKind"],
            json!("book")
        );
    }

    #[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 {
            context: demo_context(),
            tool: RuntimeToolInvocationWire {
                tool: "index_rebuild".into(),
                kind: "job".into(),
                mode: Some("result".into()),
                args_json: json!({
                    "workspaceId": "ws_1",
                    "lastProcessedEventId": "evt_0",
                    "lastProcessedAt": "2026-04-15T00:00:00Z",
                }),
                target: None,
                reason: Some("重建搜索索引".into()),
                refs: vec!["task-034".into()],
            },
            data: Some(json!({
                "events": [
                    {
                        "id": "evt_1",
                        "workspace_id": "ws_1",
                        "command_id": "cmd_1",
                        "command_log_id": "clog_1",
                        "aggregate_type": "page",
                        "aggregate_id": "page_1",
                        "event_type": "page.created",
                        "event_version": 1,
                        "actor_type": "user",
                        "payload": {"title": "第一篇"},
                        "request_id": "req_1",
                        "trace_id": "trace_1",
                        "created_at": "2026-04-15T01:00:00Z"
                    }
                ]
            })),
        })
        .expect("index rebuild tool result should build");

        assert_eq!(result.get("ok"), Some(&json!(true)));
        assert_eq!(result.get("workspaceId"), Some(&json!("ws_1")));
        assert_eq!(result.get("batchCount"), Some(&json!(1)));
        assert_eq!(result.get("documentCount"), Some(&json!(1)));
        assert_eq!(
            result.pointer("/cursor/lastProcessedEventId"),
            Some(&json!("evt_1"))
        );
    }
}
