feat(page-ai): add skill context and agent profile policy

This commit is contained in:
lix-2026
2026-05-29 21:57:29 +08:00
parent 631205ba2f
commit 49a0545148
18 changed files with 3478 additions and 259 deletions
@@ -2,7 +2,9 @@ use super::hermes_client;
use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::hermes_tools::{artifact, block, doc, manifest, page, resource, ToolCallInput};
use crate::hermes_tools::{
artifact, block, context_tools, doc, manifest, page, resource, skill, ToolCallInput,
};
use axum::extract::{Extension, Query, State};
use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
use axum::Json;
@@ -347,6 +349,14 @@ pub(crate) async fn execute_mnote_tool_call(
return Ok(cached);
}
let result = match input.tool_name.as_str() {
"mnote.skill.read" => skill::skill_read(&context, &input).await,
"mnote.context.snapshot" => context_tools::context_snapshot(&state, &context, &input).await,
"mnote.context.read_current_page" => {
context_tools::read_current_page(&state, &context, &input).await
}
"mnote.context.resolve_target" => {
context_tools::resolve_target(&state, &context, &input).await
}
"mnote.doc.fetch" => doc::doc_fetch(&state, &context, &input).await,
"mnote.doc.find" => doc::doc_find(&state, &context, &input).await,
"mnote.doc.plan_update" => doc::plan_update(&state, &context, &input).await,
@@ -538,6 +548,10 @@ fn is_read_tool(tool_name: &str) -> bool {
matches!(
tool_name,
"mnote.page.get"
| "mnote.skill.read"
| "mnote.context.snapshot"
| "mnote.context.read_current_page"
| "mnote.context.resolve_target"
| "mnote.doc.fetch"
| "mnote.doc.find"
| "mnote.block.fetch"
@@ -1359,6 +1373,80 @@ mod tests {
let _ = fs::remove_dir_all(&root);
}
#[tokio::test]
async fn hermes_tools_context_read_current_page_reads_local_markdown() {
let root = std::env::temp_dir().join(format!(
"mnote-context-read-current-page-{}",
std::process::id()
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).expect("root");
fs::write(
root.join("README.md"),
"# 当前页\n\n来自 mnote.context.read_current_page 的正文。\n",
)
.expect("markdown");
let root_uri = format!("file://{}", root.display());
crate::routes::local_folder_source::initialize_local_workspace_for_actor(
"user_1", &root_uri,
)
.expect("workspace");
let response = app()
.oneshot(
Request::builder()
.method("POST")
.uri("/api/hermes/tools/mnote/call")
.header("content-type", "application/json")
.header("x-mnote-actor-id", "user_1")
.header("x-mnote-actor-type", "user")
.body(Body::from(
json!({
"toolName": "mnote.context.read_current_page",
"workspaceId": "local-ws-context",
"documentId": "local-md:README.md",
"sourceKind": "local_folder",
"rootUri": root_uri,
"profile": "reasonix",
"actorId": "user_1",
"sessionId": "sess_context_read",
"runId": "run_context_read",
"toolCallId": "call_context_read",
"traceId": "trace_context_read",
"args": {
"format": "markdown",
"contextRefs": [{ "kind": "current_page" }],
"aiAccessScope": {
"permissionLevel": "read_write",
"allowedRoots": [{ "rootUri": root_uri, "permission": "write" }],
"allowedResourceIds": ["local-md:README.md"]
}
}
})
.to_string(),
))
.expect("request"),
)
.await
.expect("response");
let status = response.status();
let body = to_bytes(response.into_body(), usize::MAX)
.await
.expect("body");
let payload: Value = serde_json::from_slice(&body).expect("json");
assert_eq!(status, StatusCode::OK, "{payload}");
assert_eq!(payload["ok"], true);
assert_eq!(payload["result"]["ok"], true);
assert!(
payload["result"]["content"]
.as_str()
.unwrap_or_default()
.contains("mnote.context.read_current_page"),
"{payload}"
);
let _ = fs::remove_dir_all(&root);
}
#[tokio::test]
async fn hermes_tools_mindmap_fetch_reads_authorized_local_resource() {
let root = std::env::temp_dir().join(format!(