pub mod ai; pub mod command; pub mod common; pub mod editor; pub mod governance; pub mod kernel; pub mod mindmap; pub mod page_aggregate; pub mod query; pub mod search; pub mod tool; pub use ai::{ AiEvent, AiEventKind, AiRuntimeOwner, AiSession, AiStructuredWriteKind, AiStructuredWriteResult, AiToolCall, }; pub use command::{ CommandEnvelope, CopyTreeDocumentPages, CreateDocumentPage, CreatePage, CreateWorkspace, DeleteBlock, DeleteDocumentPage, DuplicateDocumentPage, EmbedBlock, InsertBlock, MoveBlock, MoveDocumentPage, PatchBlock, PatchPageBlock, PutMindmap, ReplaceMediaAssetStorage, RestoreDocumentPage, SavePageContent, UpdateBlock, UpdatePageOptions, UpdatePageStats, UpdatePageTitle, }; pub use common::{ ActorPayload, AffectedObject, ErrorDetail, ErrorPayload, OkPayload, RequestMeta, ResponseMeta, SourcePayload, TargetRef, }; pub use editor::{ BlockProps, ContentNode, ContentNodePayload, EditorAttachReferenceToken, EditorBlock, EditorBlockDocument, EditorBlockType, EditorCommand, EditorCommandCatalog, EditorCommandDescriptor, EditorCommandKind, EditorDeleteBlock, EditorDetachReferenceToken, EditorIndentBlock, EditorInsertBlockAfter, EditorKernelCommandMapping, EditorMergeWithPrevious, EditorMoveBlock, EditorOutdentBlock, EditorReplaceBlock, EditorSplitBlock, EditorToggleHeadingCollapse, MarkdownExportBoundary, MarkdownExportMode, MarkdownExportOptions, MarkdownExportRequest, MarkdownExportResult, MarkdownFlavor, MarkdownImportBoundary, MarkdownImportMode, MarkdownImportOptions, MarkdownImportRequest, MarkdownImportResult, ReferenceToken, ReferenceTokenKind, ReferenceTokenStrategy, TextMark, }; pub use kernel::{ DocumentContentResult, DocumentReadEvidenceItem, DocumentReadEvidenceKind, DocumentReadNode, DocumentReadNodeMeta, DocumentReadNodeType, DocumentReadOutlineEntry, DocumentReadPageSubtree, DocumentReadStats, DocumentReadSubtree, 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, WorkspaceSource, WorkspaceSourceCapability, WorkspaceSourceKind, }; pub use mindmap::{ MindmapCommand, MindmapNodeData, MindmapNodeInput, MindmapNodeRef, MindmapOp, MindmapProjection, MindmapProjectionEdge, MindmapProjectionNode, MindmapProjectionOwner, MindmapTreeNode, }; pub use page_aggregate::{ PageAggregateProjection, PageAggregateSource, PageBody, PageHead, PageIdentity, PageLayout, PageOptions, PagePermissions, PageStats, PageTree, }; pub use query::{ GetBlock, GetBridgeCommand, GetBridgeRequest, GetBridgeTrace, GetMindmap, GetPage, GetPageContent, GetPageMeta, ListBridgeWorkspaceOverview, ListPageBlocks, ListSidebarDataset, QueryEnvelope, SearchBlocks, SearchDocuments, SearchPages, SearchRecent, }; pub use search::{ SearchHighlight, SearchProjectionOwner, SearchQuery, SearchResultProjection, SearchScope, }; pub use tool::{ default_tool_registry, invocation_kind_label, tool_effect_label, tool_mode_label, InvocationKind, ToolEffect, ToolExecutionMode, ToolInvocation, ToolRegistry, ToolSetSpec, ToolSpec, BRIDGE_TOOL_COMMAND_GET, BRIDGE_TOOL_REQUEST_GET, BRIDGE_TOOL_TRACE_GET, DOCS_TOOLSET_READ, DOCS_TOOL_READ, DOCS_TOOL_SEARCH, DOC_TOOLSET_READ, DOC_TOOLSET_WRITE, DOC_TOOL_FIND, DOC_TOOL_GET, DOC_TOOL_INSERT_BLOCKS, DOC_TOOL_REPLACE_RANGE, INDEX_TOOL_REBUILD, MINDMAP_TOOLSET_READ, MINDMAP_TOOLSET_WRITE, MINDMAP_TOOL_APPLY_OPS, MINDMAP_TOOL_EMPTY_TRASH, MINDMAP_TOOL_EXPAND_NODE, MINDMAP_TOOL_GET, MINDMAP_TOOL_GET_SUBTREE, MINDMAP_TOOL_OUTLINE_TO_MINDMAP, MINDMAP_TOOL_PUT, OBSERVE_TOOLSET_READ, ONLYOFFICE_TOOLSET_SERVICE, ONLYOFFICE_TOOL_PREPARE_CALLBACK, ONLYOFFICE_TOOL_PREPARE_FORCESAVE, ONLYOFFICE_TOOL_PREPARE_PROXY, ONLYOFFICE_TOOL_SESSION_RESOLVE, ONLYOFFICE_TOOL_SIGN, RECOVERY_TOOLSET_JOB, REPLAY_TOOL_EVENTS, }; #[cfg(test)] mod tests { use super::*; #[test] fn command_envelope_keeps_validate_only_flag() { let envelope = CommandEnvelope:: { name: "create_workspace".into(), command_id: "cmd_1".into(), idempotency_key: Some("idem_1".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "cli".into(), client: "mnote-cli".into(), }, target: None, payload: CreateWorkspace { name: "demo".into(), slug: Some("demo".into()), }, reason: Some("初始化工作区".into()), refs: vec![], dry_run: false, validate_only: true, }; assert!(envelope.validate_only); } #[test] fn default_tool_registry_exposes_doc_tools() { let registry = default_tool_registry(); assert_eq!( registry.tool_names(), vec![ "search_web", "doc_get", "doc_find", "docs_search", "docs_read", "image_read", "doc_insert_blocks", "doc_replace_range", "slash_run", "mindmap_get", "mindmap_get_subtree", "mindmap_put", "mindmap_apply_ops", "mindmap_expand_node", "mindmap_empty_trash", "mindmap_outline_to_mindmap", "bridge_request_get", "bridge_trace_get", "bridge_command_get", "event_replay", "index_rebuild", "onlyoffice_session_resolve", "onlyoffice_sign", "onlyoffice_prepare_proxy", "onlyoffice_prepare_callback", "onlyoffice_prepare_forcesave", ] ); assert_eq!( registry .tool("search_web") .expect("search_web should exist") .toolset_id, "toolset.readonly" ); assert_eq!( registry .tool("image_read") .expect("image_read should exist") .toolset_id, "toolset.media_read" ); assert_eq!( registry .tool("slash_run") .expect("slash_run should exist") .toolset_id, "toolset.slash_write" ); let readonly = registry .toolset("toolset.readonly") .expect("readonly toolset should exist"); assert!(!readonly.write_toolset); assert_eq!(readonly.tool_names, &["search_web"]); let media = registry .toolset("toolset.media_read") .expect("media toolset should exist"); assert!(!media.write_toolset); assert_eq!(media.tool_names, &["image_read"]); let slash = registry .toolset("toolset.slash_write") .expect("slash toolset should exist"); assert!(slash.write_toolset); assert_eq!(slash.tool_names, &["slash_run"]); let docs_read = registry .toolset("toolset.docs_read") .expect("docs_read toolset should exist"); assert!(!docs_read.write_toolset); assert_eq!(docs_read.tool_names, &["docs_search", "docs_read"]); let doc_write = registry .toolset("toolset.doc_write") .expect("doc_write toolset should exist"); assert!(doc_write.write_toolset); assert_eq!( doc_write.tool_names, &["doc_insert_blocks", "doc_replace_range"] ); assert_eq!( registry .tool("doc_get") .expect("doc_get should exist") .toolset_id, "toolset.doc_read" ); let mindmap_write = registry .toolset("toolset.mindmap_write") .expect("mindmap_write toolset should exist"); assert!(mindmap_write.write_toolset); assert_eq!( mindmap_write.tool_names, &[ "mindmap_put", "mindmap_apply_ops", "mindmap_expand_node", "mindmap_empty_trash", "mindmap_outline_to_mindmap", ] ); let onlyoffice_service = registry .toolset("toolset.onlyoffice_service") .expect("onlyoffice service toolset should exist"); assert!(onlyoffice_service.write_toolset); assert_eq!( onlyoffice_service.tool_names, &[ "onlyoffice_session_resolve", "onlyoffice_sign", "onlyoffice_prepare_proxy", "onlyoffice_prepare_callback", "onlyoffice_prepare_forcesave", ] ); let observe = registry .toolset("toolset.observe_read") .expect("observe toolset should exist"); assert!(!observe.write_toolset); assert_eq!( observe.tool_names, &[ "bridge_request_get", "bridge_trace_get", "bridge_command_get" ] ); let recovery = registry .toolset("toolset.recovery_job") .expect("recovery toolset should exist"); assert!(recovery.write_toolset); assert_eq!(recovery.tool_names, &["event_replay", "index_rebuild"]); } #[test] fn page_aggregate_projection_contract_covers_kernel_owned_fields() { let projection = PageAggregateProjection { schema: "mnote.page_aggregate.v1".into(), projection_version: 1, source: PageAggregateSource::KernelProjection, page_id: "page_1".into(), parent_id: Some("root".into()), title: "Rust 页面".into(), path: vec!["root".into(), "page_1".into()], sidebar_tree_membership: vec!["workspace-sidebar".into()], body_ref: Some("page_1:body".into()), layout_options: serde_json::json!({"wideLayout": true}), updated_at: Some("2026-04-29T00:00:00Z".into()), identity: page_aggregate::PageIdentity { document_id: "page_1".into(), workspace_id: "ws_demo".into(), }, head: page_aggregate::PageHead { title: "Rust 页面".into(), updated_at: serde_json::json!("2026-04-29T00:00:00Z"), permissions: page_aggregate::PagePermissions { read_only: false, disable_download: false, disable_copy: false, }, }, layout: page_aggregate::PageLayout { page_options: page_aggregate::PageOptions::default(), }, body: page_aggregate::PageBody { content: serde_json::json!([]), revision: serde_json::json!(7), conflict_detection_key: serde_json::json!("page_1:7"), }, tree: page_aggregate::PageTree { page_subtree: serde_json::json!({"rootNodeId": "page_1"}), }, stats: page_aggregate::PageStats::default(), }; assert_eq!(projection.source, PageAggregateSource::KernelProjection); assert_eq!(projection.projection_version, 1); assert_eq!(projection.page_id, "page_1"); assert_eq!(projection.identity.document_id, "page_1"); } #[test] fn search_projection_contract_marks_owner_and_scope() { let result = SearchResultProjection { id: "page_1".into(), title: "Rust 搜索".into(), snippet: "Rust Web".into(), updated_at: Some("2026-04-29T00:00:00Z".into()), created_at: None, match_field: "title".into(), has_ocr: false, public_path: "/documents/page_1".into(), node_id: Some("page_1".into()), subtree_root_id: Some("page_1".into()), evidence: vec![], score: 3.0, projection_owner: SearchProjectionOwner::RustKernel, }; let query = SearchQuery { query: "Rust".into(), workspace_id: "ws_demo".into(), scope: SearchScope::Workspace, page_id: None, limit: 20, }; assert_eq!(query.scope, SearchScope::Workspace); assert_eq!(result.projection_owner, SearchProjectionOwner::RustKernel); } #[test] fn ai_protocol_contract_exposes_structured_write_result() { let result = AiStructuredWriteResult { ok: true, write_kind: AiStructuredWriteKind::SummaryNode, command_name: "tree.node.create".into(), target_id: Some("summary_1".into()), revision: Some(2), }; assert_eq!(result.write_kind, AiStructuredWriteKind::SummaryNode); assert_eq!(result.command_name, "tree.node.create"); } #[test] fn mindmap_projection_and_command_contract_cover_kernel_truth() { let projection = MindmapProjection { schema: "mnote.mindmap_projection.v1".into(), map_id: "mind_1".into(), root_node: "root".into(), nodes: vec![], edges: vec![], layout_hints: serde_json::json!({"layout": "right"}), revision: 1, owner: MindmapProjectionOwner::RustKernel, }; let command = MindmapCommand::RenameNode { map_id: "mind_1".into(), node_id: "root".into(), title: "新标题".into(), }; assert_eq!(projection.owner, MindmapProjectionOwner::RustKernel); assert!(matches!(command, MindmapCommand::RenameNode { .. })); } }