Improve local evidence search and AI capabilities

This commit is contained in:
lix-2026
2026-06-05 23:00:53 +08:00
parent a1dcfc9f76
commit 4dbd9a978b
52 changed files with 6415 additions and 527 deletions
+113 -30
View File
@@ -5,10 +5,11 @@ use axum::http::StatusCode;
use serde_json::{json, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MnoteSkill {
pub struct MnoteCapabilityPack {
pub id: &'static str,
pub title: &'static str,
pub description: &'static str,
pub category: &'static str,
pub agent_ids: &'static [&'static str],
pub read_only: bool,
pub requires_context_refs: &'static [&'static str],
@@ -16,11 +17,14 @@ pub struct MnoteSkill {
pub content: &'static str,
}
const SKILLS: &[MnoteSkill] = &[
MnoteSkill {
pub type MnoteSkill = MnoteCapabilityPack;
const CAPABILITY_PACKS: &[MnoteCapabilityPack] = &[
MnoteCapabilityPack {
id: "mnote-current-page",
title: "MNote current page",
description: "Read the current MNote Markdown page only when the task needs page content.",
title: "当前页读取",
description: "仅在任务需要当前 MNote Markdown 页面内容时读取当前页。",
category: "mnote",
agent_ids: &["hermes", "reasonix"],
read_only: true,
requires_context_refs: &["current_page"],
@@ -31,12 +35,13 @@ const SKILLS: &[MnoteSkill] = &[
],
content: include_str!("../../../../../skills/mnote-current-page/SKILL.md"),
},
MnoteSkill {
id: "mnote-document-evidence",
title: "MNote document evidence",
description: "Search local documents and resources with clickable evidence locators.",
MnoteCapabilityPack {
id: "mnote-local-index",
title: "本地索引与证据检索",
description: "检索本地文档证据,并管理本地索引范围、刷新和删除。",
category: "knowledge",
agent_ids: &["hermes", "reasonix"],
read_only: true,
read_only: false,
requires_context_refs: &["folder"],
tool_names: &[
"mnote.context.snapshot",
@@ -44,23 +49,28 @@ const SKILLS: &[MnoteSkill] = &[
"mnote.evidence.search",
"mnote.evidence.read",
"mnote.evidence.open",
"mnote.index.status",
"mnote.index.refresh",
"mnote.index.update_settings",
],
content: include_str!("../../../../../skills/mnote-document-evidence/SKILL.md"),
content: include_str!("../../../../../skills/mnote-local-index/SKILL.md"),
},
MnoteSkill {
MnoteCapabilityPack {
id: "mnote-local-file",
title: "MNote local file editing",
description: "Read and patch local Markdown files inside MNote allowed roots.",
title: "本地文件编辑",
description: "在 MNote 授权目录内读取和修改本地 Markdown 文件。",
category: "file",
agent_ids: &["hermes", "reasonix"],
read_only: false,
requires_context_refs: &["current_page", "file", "folder"],
tool_names: &["mnote.context.snapshot", "mnote.context.resolve_target"],
content: include_str!("../../../../../skills/mnote-local-file/SKILL.md"),
},
MnoteSkill {
MnoteCapabilityPack {
id: "mnote-onlyoffice-live",
title: "MNote ONLYOFFICE live bridge",
description: "Operate the currently open ONLYOFFICE editor session for Word, Excel, and PPT.",
title: "ONLYOFFICE 实时编辑",
description: "操作当前已打开的 ONLYOFFICE WordExcel、PPT 编辑会话。",
category: "office",
agent_ids: &["hermes", "reasonix"],
read_only: false,
requires_context_refs: &["onlyoffice"],
@@ -103,10 +113,11 @@ const SKILLS: &[MnoteSkill] = &[
],
content: include_str!("../../../../../skills/mnote-onlyoffice-live/SKILL.md"),
},
MnoteSkill {
MnoteCapabilityPack {
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.",
title: "思维导图",
description: "读取、更新、总结或创建 MNote 思维导图资源。",
category: "resource",
agent_ids: &["hermes", "reasonix"],
read_only: false,
requires_context_refs: &["current_page", "file", "folder", "resource"],
@@ -119,10 +130,11 @@ const SKILLS: &[MnoteSkill] = &[
],
content: include_str!("../../../../../skills/mnote-mindmap/SKILL.md"),
},
MnoteSkill {
MnoteCapabilityPack {
id: "mnote-chat-only",
title: "MNote chat only",
description: "Reply conversationally without reading or writing MNote page/file context.",
title: "纯聊天",
description: "只进行对话回复,不读取或写入 MNote 页面、文件上下文。",
category: "chat",
agent_ids: &["chat_only", "hermes", "reasonix"],
read_only: true,
requires_context_refs: &[],
@@ -132,20 +144,75 @@ const SKILLS: &[MnoteSkill] = &[
];
pub fn skill_summaries_for_agent(agent_id: Option<&str>) -> Vec<Value> {
SKILLS
capability_summaries_for_agent(agent_id)
}
pub fn capability_summaries_for_agent(agent_id: Option<&str>) -> Vec<Value> {
CAPABILITY_PACKS
.iter()
.filter(|skill| skill_matches_agent(skill, agent_id))
.map(skill_summary)
.collect()
}
pub fn public_capability_packs() -> &'static [MnoteCapabilityPack] {
CAPABILITY_PACKS
}
pub fn find_skill(skill_id: &str, agent_id: Option<&str>) -> Option<&'static MnoteSkill> {
let requested = skill_id.trim();
SKILLS
find_capability_pack(skill_id, agent_id)
}
pub fn find_capability_pack(
capability_id: &str,
agent_id: Option<&str>,
) -> Option<&'static MnoteCapabilityPack> {
let requested = canonical_skill_id(capability_id.trim());
CAPABILITY_PACKS
.iter()
.find(|skill| skill.id == requested && skill_matches_agent(skill, agent_id))
}
pub fn capability_ids_for_tool(tool_name: &str) -> Vec<&'static str> {
let name = tool_name.trim();
if name.is_empty() {
return Vec::new();
}
CAPABILITY_PACKS
.iter()
.filter(|pack| pack.tool_names.iter().any(|tool| *tool == name))
.map(|pack| pack.id)
.collect()
}
pub fn manifest_capabilities() -> Vec<Value> {
CAPABILITY_PACKS
.iter()
.map(|pack| {
json!({
"id": pack.id,
"title": pack.title,
"description": pack.description,
"category": pack.category,
"agentIds": pack.agent_ids,
"readOnly": pack.read_only,
"requiresContextRefs": pack.requires_context_refs,
"skillId": pack.id,
"toolNames": pack.tool_names,
"uiKind": if pack.category == "chat" { "chat" } else { "ai_capability" },
"public": true
})
})
.collect()
}
fn canonical_skill_id(skill_id: &str) -> &str {
match skill_id {
"mnote-document-evidence" => "mnote-local-index",
other => other,
}
}
pub async fn skill_read(
context: &RequestContext,
input: &ToolCallInput,
@@ -196,6 +263,7 @@ fn skill_summary(skill: &MnoteSkill) -> Value {
"id": skill.id,
"title": skill.title,
"description": skill.description,
"category": skill.category,
"agentIds": skill.agent_ids,
"readOnly": skill.read_only,
"requiresContextRefs": skill.requires_context_refs,
@@ -296,18 +364,33 @@ mod tests {
}
#[test]
fn skill_registry_exposes_document_evidence_skill_to_agents() {
fn skill_registry_exposes_local_index_skill_to_agents() {
let hermes_skills = skill_summaries_for_agent(Some("hermes"));
let skill = hermes_skills
.iter()
.find(|skill| skill["id"] == "mnote-document-evidence")
.expect("hermes should see document evidence skill");
assert_eq!(skill["readOnly"], true);
.find(|skill| skill["id"] == "mnote-local-index")
.expect("hermes should see local index skill");
assert_eq!(skill["readOnly"], false);
assert!(skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.evidence.search"));
assert!(skill["toolNames"]
.as_array()
.expect("tool names")
.iter()
.any(|name| name == "mnote.index.update_settings"));
assert!(!hermes_skills
.iter()
.any(|skill| skill["id"] == "mnote-document-evidence"));
}
#[test]
fn skill_read_keeps_document_evidence_compat_alias() {
let skill = find_skill("mnote-document-evidence", Some("reasonix"))
.expect("compat alias should resolve");
assert_eq!(skill.id, "mnote-local-index");
}
#[tokio::test]