Files
mnote/rust/crates/mnote-web/src/hermes_tools/manifest.rs
T
lix-2026 a2cb1338c8 chore: 保存当前架构收口与 bug 修复快照
归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
2026-05-18 17:01:35 +08:00

499 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"]),
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",
"替换指定块内容;真实写入走 Rust page.body.save 链路",
["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<Item = &'static str>,
extra_properties: Value,
extra_required: impl IntoIterator<Item = &'static str>,
) -> 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::<Vec<_>>(),
"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"],
"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": "保存当前页面正文;replace 覆盖正文,append/prepend 会先读取当前 Page Aggregate 后合成完整正文再保存",
"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<Item = &'static str>,
) -> Value {
json!({
"name": name,
"description": description,
"schemaVersion": TOOL_SCHEMA_VERSION,
"capabilityScope": scope.into_iter().collect::<Vec<_>>(),
"status": "available",
"annotations": tool_annotations(false, false, false, false)
})
}
fn doc_markdown_edit_tool() -> Value {
let mut tool = write_tool(
"mnote.doc.markdown_edit",
"通过文本级搜索替换编辑 markdown 内容(AI 编辑主路径)。在线 Convex 文档和本地 .md 文件共用,不需要 blockId。",
["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 tool_annotations(
readonly: bool,
destructive: bool,
idempotent: bool,
requires_approval: bool,
) -> Value {
json!({
"readonly": readonly,
"destructive": destructive,
"idempotent": idempotent,
"requiresApproval": requires_approval,
"approvalMode": if requires_approval { "review" } else { "yolo" },
"runtimeOwner": "mnote-web",
"writeOwner": "rust-runtime-kernel",
"selectionEffect": if readonly { "preserve" } else { "may_change" }
})
}