use serde_json::{json, Value}; pub const MANIFEST_SCHEMA_VERSION: &str = "mnote.hermes_tool_manifest.v1"; pub const TOOL_SCHEMA_VERSION: &str = "mnote.hermes_tool.v1"; pub fn manifest() -> Value { json!({ "schemaVersion": MANIFEST_SCHEMA_VERSION, "plugin": { "name": "mnote", "description": "mnote 页面、树、artifact 与 edge 工具", "runtimeOwner": "mnote-web", "writeOwner": "rust-runtime-kernel" }, "tools": [ doc_fetch_tool(), doc_find_tool(), block_fetch_tool(), doc_plan_update_tool(), block_replace_tool(), block_insert_after_tool(), block_delete_tool(), block_move_after_tool(), doc_apply_block_ops_tool(), doc_markdown_edit_tool(), page_get_tool(), page_save_tool(), available_tool("mnote.page.update_title", "更新当前页面标题", ["page.write"]), available_tool("mnote.page.update_options", "更新当前页面设置", ["page.write"]), mindmap_fetch_tool(), mindmap_apply_ops_tool(), office_fetch_summary_tool(), office_propose_changes_tool(), available_tool("mnote.artifact.create_summary", "为当前页面创建或更新 AI Summary", ["artifact.write"]), available_tool("mnote.artifact.create_ai_note", "基于当前页面创建新的 AI Note", ["artifact.write"]) ] }) } fn base_identity_properties() -> Value { json!({ "workspaceId": { "type": "string" }, "documentId": { "type": "string" }, "sessionId": { "type": "string" }, "runId": { "type": "string" }, "toolCallId": { "type": "string" }, "traceId": { "type": "string" }, "actorId": { "type": "string" }, "actorType": { "type": "string" } }) } fn doc_fetch_tool() -> Value { let mut properties = base_identity_properties(); if let Value::Object(map) = &mut properties { map.insert( "scope".into(), json!({ "type": "string", "default": "full" }), ); map.insert( "detail".into(), json!({ "type": "string", "default": "with_ids" }), ); map.insert( "format".into(), json!({ "type": "string", "enum": ["json", "markdown", "text", "page_xml"], "default": "json" }), ); map.insert("blockId".into(), json!({ "type": "string" })); map.insert( "selectedBlockIds".into(), json!({ "type": "array", "items": { "type": "string" } }), ); map.insert( "allowedTargetBlockIds".into(), json!({ "type": "array", "items": { "type": "string" } }), ); map.insert("query".into(), json!({ "type": "string" })); map.insert( "maxBlocks".into(), json!({ "type": "integer", "default": 120 }), ); } json!({ "name": "mnote.doc.fetch", "description": "读取当前页面的 canonical block projection,支持 full/outline/block/keyword 范围", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["page.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId"], "properties": properties } }) } fn doc_find_tool() -> Value { let mut properties = base_identity_properties(); if let Value::Object(map) = &mut properties { map.insert("query".into(), json!({ "type": "string" })); map.insert( "match".into(), json!({ "type": "string", "default": "text" }), ); map.insert("limit".into(), json!({ "type": "integer", "default": 20 })); } json!({ "name": "mnote.doc.find", "description": "在 Page Aggregate block projection 中按文本、类型或 blockId 查找块", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["page.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "query"], "properties": properties } }) } fn block_fetch_tool() -> Value { let mut properties = base_identity_properties(); if let Value::Object(map) = &mut properties { map.insert("blockId".into(), json!({ "type": "string" })); map.insert( "includeChildren".into(), json!({ "type": "boolean", "default": true }), ); map.insert( "contextBefore".into(), json!({ "type": "integer", "default": 1 }), ); map.insert( "contextAfter".into(), json!({ "type": "integer", "default": 1 }), ); map.insert( "format".into(), json!({ "type": "string", "enum": ["json", "markdown", "text", "page_xml"], "default": "json" }), ); } json!({ "name": "mnote.block.fetch", "description": "读取单个块及同父级上下文", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["block.read", "page.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "blockId"], "properties": properties } }) } fn doc_plan_update_tool() -> Value { write_tool( "mnote.doc.plan_update", "生成页面块更新 dry-run 计划,不直接写入", ["page.write", "block.write"], json!({ "command": { "type": "string" }, "blockId": { "type": "string" }, "anchorBlockId": { "type": "string" }, "content": { "type": ["string", "object", "array"] }, "query": { "type": "string" }, "revision": { "type": ["number", "string"] }, "conflictDetectionKey": { "type": "string" } }), [], ) } fn block_replace_tool() -> Value { write_tool( "mnote.block.replace", "兼容块写工具:替换指定块内容;本地 Markdown 普通编辑优先使用 agent 原生 patch/diff,必要时再走 Rust page.body.write 兼容链路", ["block.write", "page.write"], json!({ "blockId": { "type": "string" }, "content": { "type": ["string", "object", "array"] }, "revision": { "type": ["number", "string"] }, "conflictDetectionKey": { "type": "string" }, "blockRevisionRef": { "type": "string" }, "allowedTargetBlockIds": { "type": "array", "items": { "type": "string" } } }), [ "blockId", "content", "revision", "conflictDetectionKey", "blockRevisionRef", ], ) } fn block_insert_after_tool() -> Value { let mut tool = write_tool( "mnote.block.insert_after", "在指定块后插入新块;真实写入走 Rust page.body.save 链路", ["block.write", "page.write"], json!({ "anchorBlockId": { "type": "string" }, "content": { "type": ["string", "object", "array"] }, "block": { "type": "object" }, "blocks": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": ["string", "object", "array"] } }, "revision": { "type": ["number", "string"] }, "conflictDetectionKey": { "type": "string" }, "anchorRevisionRef": { "type": "string" }, "allowedTargetBlockIds": { "type": "array", "items": { "type": "string" } } }), [ "anchorBlockId", "revision", "conflictDetectionKey", "anchorRevisionRef", ], ); if let Some(schema) = tool.get_mut("inputSchema").and_then(Value::as_object_mut) { schema.insert( "anyOf".into(), json!([ { "required": ["content"] }, { "required": ["block"] }, { "required": ["blocks"] } ]), ); } tool } fn block_move_after_tool() -> Value { write_tool( "mnote.block.move_after", "受限同父级普通叶子块移动;真实写入走 Rust page.body.save 链路", ["block.write", "page.write"], json!({ "blockId": { "type": "string" }, "anchorBlockId": { "type": "string" }, "revision": { "type": ["number", "string"] }, "conflictDetectionKey": { "type": "string" }, "blockRevisionRef": { "type": "string" }, "anchorRevisionRef": { "type": "string" }, "allowedTargetBlockIds": { "type": "array", "items": { "type": "string" } } }), [ "blockId", "anchorBlockId", "revision", "conflictDetectionKey", "blockRevisionRef", "anchorRevisionRef", ], ) } fn block_delete_tool() -> Value { write_tool( "mnote.block.delete", "删除指定无子块普通块;真实写入走 Rust page.body.save 链路", ["block.write", "page.write"], json!({ "blockId": { "type": "string" }, "revision": { "type": ["number", "string"] }, "conflictDetectionKey": { "type": "string" }, "blockRevisionRef": { "type": "string" }, "allowedTargetBlockIds": { "type": "array", "items": { "type": "string" } } }), [ "blockId", "revision", "conflictDetectionKey", "blockRevisionRef", ], ) } fn doc_apply_block_ops_tool() -> Value { write_tool( "mnote.doc.apply_block_ops", "一次性应用多个块级操作;Rust 侧统一读取最新 projection、生成 canonical content 并一次保存,适合页面 AI 小段落增删改移动快路径", ["block.write", "page.write"], json!({ "operations": { "type": "array", "items": { "type": "object", "properties": { "op": { "type": "string" }, "blockId": { "type": "string" }, "anchorBlockId": { "type": "string" }, "matchText": { "type": "string" }, "anchorText": { "type": "string" }, "afterText": { "type": "string" }, "allowedTargetBlockIds": { "type": "array", "items": { "type": "string" } }, "content": { "type": ["string", "object", "array"] } } } } }), ["operations"], ) } fn write_tool( name: &str, description: &str, scope: impl IntoIterator, extra_properties: Value, extra_required: impl IntoIterator, ) -> Value { let mut properties = base_identity_properties(); if let Value::Object(map) = &mut properties { map.insert("dryRun".into(), json!({ "type": "boolean" })); map.insert("idempotencyKey".into(), json!({ "type": "string" })); if let Value::Object(extra) = extra_properties { for (key, value) in extra { map.insert(key, value); } } } let mut required = vec![ "workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "dryRun", "idempotencyKey", ]; required.extend(extra_required); json!({ "name": name, "description": description, "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": scope.into_iter().collect::>(), "status": "available", "annotations": tool_annotations(false, false, false, false), "inputSchema": { "type": "object", "required": required, "properties": properties } }) } fn page_get_tool() -> Value { json!({ "name": "mnote.page.get", "description": "读取当前页面 Page Aggregate 摘要", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["page.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId"], "properties": { "workspaceId": { "type": "string" }, "documentId": { "type": "string" }, "sessionId": { "type": "string" }, "runId": { "type": "string" }, "toolCallId": { "type": "string" }, "traceId": { "type": "string" }, "actorId": { "type": "string" }, "actorType": { "type": "string" }, "includeBody": { "type": "boolean", "default": true }, "includeOptions": { "type": "boolean", "default": true }, "includeBlocks": { "type": "boolean", "default": true } } } }) } fn page_save_tool() -> Value { json!({ "name": "mnote.page.save", "description": "粗粒度兼容兜底:保存当前页面正文;本地 Markdown 普通编辑优先使用 agent 原生 patch/diff,只有整页覆盖/追加且其它工具无法表达时使用", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["page.write"], "status": "available", "annotations": tool_annotations(false, true, false, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "content", "dryRun", "idempotencyKey"], "properties": { "workspaceId": { "type": "string" }, "documentId": { "type": "string" }, "sessionId": { "type": "string" }, "runId": { "type": "string" }, "toolCallId": { "type": "string" }, "traceId": { "type": "string" }, "actorId": { "type": "string" }, "actorType": { "type": "string" }, "content": { "description": "要写入的正文块数组、{blocks:[...]}、TipTap content 数组或纯文本", "type": ["array", "object", "string"] }, "mode": { "type": "string", "enum": ["replace", "append", "prepend"], "default": "replace" }, "dryRun": { "type": "boolean" }, "idempotencyKey": { "type": "string" } } } }) } fn available_tool( name: &str, description: &str, scope: impl IntoIterator, ) -> Value { json!({ "name": name, "description": description, "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": scope.into_iter().collect::>(), "status": "available", "annotations": tool_annotations(false, false, false, false) }) } fn doc_markdown_edit_tool() -> Value { let mut tool = write_tool( "mnote.doc.markdown_edit", "兼容 / 远端代理 fallback:通过文本级搜索替换编辑 markdown 内容。local-first 本地 workspace 默认优先让 agent 原生 patch/diff 直接编辑授权文件;仅在需要 MNote 兼容工具、远端代理或结构校验时使用。", ["block.write", "page.write"], json!({ "operations": { "type": "array", "description": "搜索替换操作列表", "items": { "type": "object", "properties": { "search": { "type": "string", "description": "要搜索的原文片段" }, "replace": { "type": "string", "description": "替换后的新文本" } }, "required": ["search", "replace"] } }, "full_content": { "type": "string", "description": "完整修改后的 markdown 文本(替代 operations)" } }), [], ); if let Some(schema) = tool.get_mut("inputSchema").and_then(Value::as_object_mut) { schema.insert( "anyOf".into(), json!([ { "required": ["operations"] }, { "required": ["full_content"] } ]), ); } tool } fn resource_identity_properties(resource_id_name: &str) -> Value { let mut properties = base_identity_properties(); if let Value::Object(map) = &mut properties { map.insert("rootUri".into(), json!({ "type": "string" })); map.insert("sourceKind".into(), json!({ "type": "string" })); map.insert(resource_id_name.into(), json!({ "type": "string" })); map.insert("resourcePath".into(), json!({ "type": "string" })); map.insert( "aiAccessScope".into(), json!({ "type": "object", "properties": { "permissionLevel": { "type": "string" }, "allowedResourceIds": { "type": "array", "items": { "type": "string" } }, "allowedRoots": { "type": "array" } } }), ); } properties } fn mindmap_fetch_tool() -> Value { let mut properties = resource_identity_properties("mindmapId"); if let Value::Object(map) = &mut properties { map.insert( "scope".into(), json!({ "type": "string", "enum": ["tree", "subtree", "markdown_summary"], "default": "tree" }), ); map.insert("nodeId".into(), json!({ "type": "string" })); } json!({ "name": "mnote.mindmap.fetch", "description": "读取授权 root 内的 mindmap resource 结构或 markdown summary", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["resource.read", "mindmap.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "mindmapId", "sessionId", "runId", "toolCallId", "traceId", "actorId"], "properties": properties } }) } fn mindmap_apply_ops_tool() -> Value { write_tool( "mnote.mindmap.apply_ops", "对授权 mindmap resource 生成或执行结构操作;本地 workspace 默认建议 agent 直接编辑授权文件,必要时才走该工具", ["resource.write", "mindmap.write"], json!({ "mindmapId": { "type": "string" }, "rootUri": { "type": "string" }, "sourceKind": { "type": "string" }, "resourcePath": { "type": "string" }, "expectedRevision": { "type": ["number", "string"] }, "ops": { "type": "array", "items": { "type": "object" } }, "aiAccessScope": { "type": "object" } }), ["mindmapId", "ops"], ) } fn office_fetch_summary_tool() -> Value { let mut properties = resource_identity_properties("assetId"); if let Value::Object(map) = &mut properties { map.insert( "extractMode".into(), json!({ "type": "string", "enum": ["text", "outline", "metadata"], "default": "metadata" }), ); } json!({ "name": "mnote.office.fetch_summary", "description": "读取授权 Office resource 的元数据和轻量文本摘要;不直接写二进制文件", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["resource.read", "office.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "assetId", "sessionId", "runId", "toolCallId", "traceId", "actorId"], "properties": properties } }) } fn office_propose_changes_tool() -> Value { let mut properties = resource_identity_properties("assetId"); if let Value::Object(map) = &mut properties { map.insert("instructions".into(), json!({ "type": "string" })); map.insert( "basisRevision".into(), json!({ "type": ["number", "string"] }), ); } json!({ "name": "mnote.office.propose_changes", "description": "基于授权 Office resource 生成修改建议;真实写入仍由 OnlyOffice / officecli 完成", "schemaVersion": TOOL_SCHEMA_VERSION, "capabilityScope": ["resource.read", "office.read"], "status": "available", "annotations": tool_annotations(true, false, true, false), "inputSchema": { "type": "object", "required": ["workspaceId", "documentId", "assetId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "instructions"], "properties": properties } }) } fn tool_annotations( readonly: bool, destructive: bool, idempotent: bool, requires_approval: bool, ) -> Value { json!({ "readonly": readonly, "destructive": destructive, "idempotent": idempotent, "readOnly": readonly, "requiresWritePermission": !readonly, "requiresApproval": requires_approval, "approvalMode": if requires_approval { "review" } else { "yolo" }, "runtimeOwner": "mnote-web", "writeOwner": "rust-runtime-kernel", "selectionEffect": if readonly { "preserve" } else { "may_change" } }) }