收口 MNote P0 P1 P2 审查尾项

- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
This commit is contained in:
lix-2026
2026-06-01 09:29:12 +08:00
parent 49a0545148
commit 1882db7681
143 changed files with 29810 additions and 3228 deletions
@@ -41,6 +41,68 @@ const SKILLS: &[MnoteSkill] = &[
tool_names: &["mnote.context.snapshot", "mnote.context.resolve_target"],
content: include_str!("../../../../../skills/mnote-local-file/SKILL.md"),
},
MnoteSkill {
id: "mnote-onlyoffice-live",
title: "MNote ONLYOFFICE live bridge",
description: "Operate the currently open ONLYOFFICE editor session for Word, Excel, and PPT.",
agent_ids: &["hermes", "reasonix"],
read_only: false,
requires_context_refs: &["onlyoffice"],
tool_names: &[
"mnote.onlyoffice.session.current",
"mnote.onlyoffice.capabilities",
"mnote.onlyoffice.selection.get",
"mnote.onlyoffice.document.insert_text",
"mnote.onlyoffice.document.replace_selection",
"mnote.onlyoffice.document.insert_html",
"mnote.onlyoffice.document.export",
"mnote.onlyoffice.document.search_replace",
"mnote.onlyoffice.document.insert_table",
"mnote.onlyoffice.document.get_comments",
"mnote.onlyoffice.document.add_comment",
"mnote.onlyoffice.sheet.get_sheets",
"mnote.onlyoffice.sheet.add_sheet",
"mnote.onlyoffice.sheet.rename_sheet",
"mnote.onlyoffice.sheet.get_range",
"mnote.onlyoffice.sheet.get_range_values",
"mnote.onlyoffice.sheet.get_values",
"mnote.onlyoffice.sheet.set_value",
"mnote.onlyoffice.sheet.set_formula",
"mnote.onlyoffice.sheet.batch_set_values",
"mnote.onlyoffice.sheet.set_range_values",
"mnote.onlyoffice.sheet.format_range",
"mnote.onlyoffice.sheet.set_dimensions",
"mnote.onlyoffice.sheet.sort_range",
"mnote.onlyoffice.sheet.add_chart",
"mnote.onlyoffice.presentation.get_slides",
"mnote.onlyoffice.presentation.get_slide_texts",
"mnote.onlyoffice.presentation.get_shapes",
"mnote.onlyoffice.presentation.add_text_slide",
"mnote.onlyoffice.presentation.replace_text",
"mnote.onlyoffice.presentation.set_shape_text",
"mnote.onlyoffice.presentation.delete_slide",
"mnote.onlyoffice.presentation.add_table",
"mnote.onlyoffice.presentation.clear_slide",
"mnote.onlyoffice.presentation.add_shape",
],
content: include_str!("../../../../../skills/mnote-onlyoffice-live/SKILL.md"),
},
MnoteSkill {
id: "mnote-mindmap",
title: "MNote mindmap editing",
description: "Read, update, summarize, or create MNote mindmap resources, including generating a new mindmap from PDF or document outlines.",
agent_ids: &["hermes", "reasonix"],
read_only: false,
requires_context_refs: &["current_page", "file", "folder", "resource"],
tool_names: &[
"mnote.context.snapshot",
"mnote.context.resolve_target",
"mnote.mindmap.fetch",
"mnote.mindmap.apply_ops",
"mnote.mindmap.create_from_outline",
],
content: include_str!("../../../../../skills/mnote-mindmap/SKILL.md"),
},
MnoteSkill {
id: "mnote-chat-only",
title: "MNote chat only",
@@ -146,4 +208,108 @@ mod tests {
assert!(find_skill("mnote-local-file", Some("chat_only")).is_none());
assert!(find_skill("missing", Some("reasonix")).is_none());
}
#[test]
fn skill_registry_exposes_onlyoffice_live_skill_to_agents() {
let reasonix_skills = skill_summaries_for_agent(Some("reasonix"));
let skill = reasonix_skills
.iter()
.find(|skill| skill["id"] == "mnote-onlyoffice-live")
.expect("reasonix should see live ONLYOFFICE skill");
assert_eq!(skill["readOnly"], false);
assert_eq!(
skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.onlyoffice.session.current"),
true
);
assert_eq!(
skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.onlyoffice.sheet.batch_set_values"),
true
);
assert_eq!(
skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.onlyoffice.sheet.add_chart"),
true
);
assert_eq!(
skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.onlyoffice.presentation.add_shape"),
true
);
assert_eq!(
skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.onlyoffice.presentation.replace_text"),
true
);
}
#[test]
fn skill_registry_exposes_mindmap_skill_to_agents() {
let reasonix_skills = skill_summaries_for_agent(Some("reasonix"));
let skill = reasonix_skills
.iter()
.find(|skill| skill["id"] == "mnote-mindmap")
.expect("reasonix should see mindmap skill");
assert_eq!(skill["readOnly"], false);
assert!(skill["requiresContextRefs"]
.as_array()
.expect("context refs")
.iter()
.any(|value| value == "resource"));
assert!(skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.mindmap.create_from_outline"));
}
#[tokio::test]
async fn skill_read_returns_mindmap_skill_content() {
let context = RequestContext::from_http_parts(
&axum::http::Method::POST,
&"/api/hermes/tools/execute".parse().expect("uri"),
&axum::http::HeaderMap::new(),
);
let input: ToolCallInput = serde_json::from_value(json!({
"toolName": "mnote.skill.read",
"args": {
"skillId": "mnote-mindmap",
"agentId": "reasonix"
}
}))
.expect("input");
let payload = skill_read(&context, &input).await.expect("skill read");
assert_eq!(payload["skill"]["id"], "mnote-mindmap");
assert!(payload["content"]
.as_str()
.unwrap_or_default()
.contains("mnote.mindmap.create_from_outline"));
let content = payload["content"].as_str().unwrap_or_default();
assert!(content.contains("Mindmap outline format"));
assert!(content.contains("Use concise keywords or short phrases"));
assert!(content.contains("Avoid long paragraphs"));
assert!(payload["tools"]
.as_array()
.expect("tools")
.iter()
.any(|tool| tool == "mnote.mindmap.create_from_outline"));
}
}