Improve local evidence search and AI capabilities
This commit is contained in:
@@ -3,8 +3,8 @@ use crate::app::AppState;
|
||||
use crate::context::RequestContext;
|
||||
use crate::error::WebError;
|
||||
use crate::hermes_tools::{
|
||||
artifact, block, context_tools, doc, evidence, manifest, onlyoffice_live, page, resource,
|
||||
skill, ToolCallInput,
|
||||
artifact, block, context_tools, doc, evidence, index, manifest, onlyoffice_live, page,
|
||||
resource, skill, ToolCallInput,
|
||||
};
|
||||
use axum::extract::{Extension, Query, State};
|
||||
use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
|
||||
@@ -365,6 +365,11 @@ pub(crate) async fn execute_mnote_tool_call(
|
||||
"mnote.evidence.search" => evidence::evidence_search(&state, &context, &input).await,
|
||||
"mnote.evidence.read" => evidence::evidence_read(&state, &context, &input).await,
|
||||
"mnote.evidence.open" => evidence::evidence_open(&state, &context, &input).await,
|
||||
"mnote.index.status" => index::index_status(&state, &context, &input).await,
|
||||
"mnote.index.refresh" => index::index_refresh(&state, &context, &input).await,
|
||||
"mnote.index.update_settings" => {
|
||||
index::index_update_settings(&state, &context, &input).await
|
||||
}
|
||||
"mnote.doc.plan_update" => doc::plan_update(&state, &context, &input).await,
|
||||
"mnote.block.fetch" => block::block_fetch(&state, &context, &input).await,
|
||||
"mnote.block.replace" => block::block_replace(&state, &context, &input).await,
|
||||
@@ -704,6 +709,8 @@ fn is_read_tool(tool_name: &str) -> bool {
|
||||
| "mnote.evidence.search"
|
||||
| "mnote.evidence.read"
|
||||
| "mnote.evidence.open"
|
||||
| "mnote.index.status"
|
||||
| "mnote.index.refresh"
|
||||
| "mnote.block.fetch"
|
||||
| "mnote.mindmap.fetch"
|
||||
| "mnote.office.fetch_summary"
|
||||
@@ -1578,6 +1585,70 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_manifest_exposes_mnote_capability_packs() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/hermes/tools/mnote/manifest")
|
||||
.header("x-mnote-actor-id", "user_1")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: Value = serde_json::from_slice(&body).expect("json");
|
||||
let manifest = &payload["manifest"];
|
||||
let capabilities = manifest["capabilities"].as_array().expect("capabilities");
|
||||
assert!(capabilities
|
||||
.iter()
|
||||
.any(|capability| capability["id"] == "mnote-local-index"));
|
||||
assert!(capabilities
|
||||
.iter()
|
||||
.any(|capability| capability["id"] == "mnote-onlyoffice-live"));
|
||||
|
||||
let tools = manifest["tools"].as_array().expect("tools");
|
||||
let index_status = tools
|
||||
.iter()
|
||||
.find(|tool| tool["name"] == "mnote.index.status")
|
||||
.expect("index status tool");
|
||||
assert!(index_status["capabilityIds"]
|
||||
.as_array()
|
||||
.expect("index capability ids")
|
||||
.iter()
|
||||
.any(|id| id == "mnote-local-index"));
|
||||
|
||||
let onlyoffice_batch_set = tools
|
||||
.iter()
|
||||
.find(|tool| tool["name"] == "mnote.onlyoffice.sheet.batch_set_values")
|
||||
.expect("onlyoffice batch set tool");
|
||||
assert_eq!(
|
||||
onlyoffice_batch_set["capabilityId"],
|
||||
"mnote-onlyoffice-live"
|
||||
);
|
||||
assert!(onlyoffice_batch_set["capabilityIds"]
|
||||
.as_array()
|
||||
.expect("onlyoffice capability ids")
|
||||
.iter()
|
||||
.any(|id| id == "mnote-onlyoffice-live"));
|
||||
|
||||
let context_snapshot = tools
|
||||
.iter()
|
||||
.find(|tool| tool["name"] == "mnote.context.snapshot")
|
||||
.expect("context snapshot tool");
|
||||
assert!(
|
||||
context_snapshot["capabilityIds"]
|
||||
.as_array()
|
||||
.expect("context capability ids")
|
||||
.len()
|
||||
> 1
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_onlyoffice_session_current_reads_registered_bridge_session() {
|
||||
let app = app();
|
||||
@@ -5386,6 +5457,21 @@ mod tests {
|
||||
)
|
||||
.expect("markdown");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
crate::routes::local_search_index::write_local_index_settings(
|
||||
&root,
|
||||
&[String::from(".")],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("settings");
|
||||
crate::routes::local_search_index::refresh_local_search_index(
|
||||
&root,
|
||||
&root_uri,
|
||||
"local-ws-docs-search",
|
||||
)
|
||||
.expect("refresh");
|
||||
|
||||
let response = app()
|
||||
.oneshot(
|
||||
@@ -5437,6 +5523,12 @@ mod tests {
|
||||
result["source"]["ownerDocumentPath"].as_str(),
|
||||
Some("README.md")
|
||||
);
|
||||
assert!(result["citationMarkdown"]
|
||||
.as_str()
|
||||
.is_some_and(|value| value.contains("](/documents/")));
|
||||
assert!(result["citationUrl"]
|
||||
.as_str()
|
||||
.is_some_and(|value| value.contains("resourceTab=")));
|
||||
let evidence_id = result["evidenceId"].as_str().expect("evidence id");
|
||||
assert_eq!(
|
||||
payload["result"]["evidenceIds"][0].as_str(),
|
||||
@@ -5480,6 +5572,112 @@ mod tests {
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_local_index_update_and_status_manage_scope() {
|
||||
let root = std::env::temp_dir().join(format!("mnote-index-tool-{}", std::process::id()));
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
fs::create_dir_all(root.join(".mnote")).expect("metadata");
|
||||
fs::create_dir_all(root.join("docs")).expect("docs");
|
||||
fs::write(
|
||||
root.join(".mnote").join("workspace.json"),
|
||||
r#"{"workspaceId":"local-ws-index-tool","ownerId":"user_1","createdAt":"2026-06-04T00:00:00Z","capabilities":["local_files","search"]}"#,
|
||||
)
|
||||
.expect("manifest");
|
||||
fs::write(
|
||||
root.join("docs").join("indexed.md"),
|
||||
"# Indexed\n\nindex-tool-token\n",
|
||||
)
|
||||
.expect("markdown");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
let app = app();
|
||||
|
||||
let update_response = app
|
||||
.clone()
|
||||
.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.index.update_settings",
|
||||
"workspaceId": "local-ws-index-tool",
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": root_uri,
|
||||
"sessionId": "sess_index_tool",
|
||||
"runId": "run_index_tool",
|
||||
"toolCallId": "call_index_update",
|
||||
"traceId": "trace_index_tool",
|
||||
"dryRun": false,
|
||||
"idempotencyKey": "idem_index_tool_update",
|
||||
"args": {
|
||||
"includePaths": ["docs"],
|
||||
"scheduleMode": "manual",
|
||||
"scheduleTime": "02:00",
|
||||
"runOnChange": false
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("update request"),
|
||||
)
|
||||
.await
|
||||
.expect("update response");
|
||||
assert_eq!(update_response.status(), StatusCode::OK);
|
||||
let update_body = to_bytes(update_response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("update body");
|
||||
let update_payload: Value = serde_json::from_slice(&update_body).expect("update json");
|
||||
assert_eq!(
|
||||
update_payload["result"]["settings"]["includePaths"][0],
|
||||
"docs"
|
||||
);
|
||||
assert_eq!(update_payload["result"]["index"]["documentCount"], 1);
|
||||
assert!(root.join(".mnote/index/search-index.json").exists());
|
||||
assert!(root.join(".mnote/index/evidence.sqlite").exists());
|
||||
|
||||
let status_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.index.status",
|
||||
"workspaceId": "local-ws-index-tool",
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": root_uri,
|
||||
"sessionId": "sess_index_tool",
|
||||
"runId": "run_index_tool",
|
||||
"toolCallId": "call_index_status",
|
||||
"traceId": "trace_index_tool",
|
||||
"args": {}
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("status request"),
|
||||
)
|
||||
.await
|
||||
.expect("status response");
|
||||
assert_eq!(status_response.status(), StatusCode::OK);
|
||||
let status_body = to_bytes(status_response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("status body");
|
||||
let status_payload: Value = serde_json::from_slice(&status_body).expect("status json");
|
||||
assert_eq!(
|
||||
status_payload["result"]["result"]["settings"]["includePaths"][0],
|
||||
"docs"
|
||||
);
|
||||
assert_eq!(status_payload["audit"]["effect"], "read");
|
||||
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hermes_tools_legacy_docs_read_returns_document_with_locator() {
|
||||
let root =
|
||||
|
||||
Reference in New Issue
Block a user