Files
mnote/rust/crates/core-protocol/src/lib.rs
T

367 lines
14 KiB
Rust
Raw Normal View History

2026-04-30 06:58:17 +08:00
pub mod ai;
2026-04-14 13:22:29 +08:00
pub mod command;
pub mod common;
pub mod editor;
2026-04-14 13:22:29 +08:00
pub mod governance;
pub mod kernel;
2026-04-15 20:01:12 +08:00
pub mod mindmap;
pub mod page_aggregate;
2026-04-14 13:22:29 +08:00
pub mod query;
pub mod search;
2026-04-14 13:22:29 +08:00
pub mod tool;
pub use ai::{
AiEvent, AiEventKind, AiRuntimeOwner, AiSession, AiStructuredWriteKind,
AiStructuredWriteResult, AiToolCall,
};
2026-04-14 13:22:29 +08:00
pub use command::{
2026-04-15 20:01:12 +08:00
CommandEnvelope, CopyTreeDocumentPages, CreateDocumentPage, CreatePage, CreateWorkspace,
DeleteBlock, DeleteDocumentPage, DuplicateDocumentPage, EmbedBlock, InsertBlock, MoveBlock,
MoveDocumentPage, PatchBlock, PatchPageBlock, PutMindmap, ReplaceMediaAssetStorage,
RestoreDocumentPage, SavePageContent, UpdateBlock, UpdatePageOptions, UpdatePageStats,
UpdatePageTitle,
2026-04-14 13:22:29 +08:00
};
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,
2026-05-08 00:41:03 +08:00
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::{
2026-04-15 20:01:12 +08:00
GetBlock, GetBridgeCommand, GetBridgeRequest, GetBridgeTrace, GetMindmap, GetPage,
GetPageContent, GetPageMeta, ListBridgeWorkspaceOverview, ListPageBlocks, ListSidebarDataset,
QueryEnvelope, SearchBlocks, SearchDocuments, SearchPages, SearchRecent,
2026-04-15 20:01:12 +08:00
};
pub use search::{
SearchHighlight, SearchProjectionOwner, SearchQuery, SearchResultProjection, SearchScope,
};
2026-04-15 20:01:12 +08:00
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,
2026-04-15 20:01:12 +08:00
ONLYOFFICE_TOOL_PREPARE_FORCESAVE, ONLYOFFICE_TOOL_PREPARE_PROXY,
ONLYOFFICE_TOOL_SESSION_RESOLVE, ONLYOFFICE_TOOL_SIGN, RECOVERY_TOOLSET_JOB,
REPLAY_TOOL_EVENTS,
};
2026-04-14 13:22:29 +08:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_envelope_keeps_validate_only_flag() {
let envelope = CommandEnvelope::<CreateWorkspace> {
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);
}
2026-04-15 20:01:12 +08:00
#[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",
2026-04-15 20:01:12 +08:00
"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"]);
2026-04-15 20:01:12 +08:00
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,
2026-04-15 20:01:12 +08:00
"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"
]
2026-04-15 20:01:12 +08:00
);
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 { .. }));
}
2026-04-14 13:22:29 +08:00
}