feat: advance local-first workspace checklist
- add admin access-policy UI and local access control surfaces - add local markdown conflict resolution UI and smoke coverage - add ACP local agent changed-files audit scaffold and read-only write guard - document current P0-P2 checklist progress and verification evidence
This commit is contained in:
@@ -18,14 +18,17 @@ pub async fn page_get(
|
||||
WebError::bad_request_code("mnote_tool_bad_request", "mnote.page.get 缺少 documentId")
|
||||
.with_context(context)
|
||||
})?;
|
||||
crate::hermes_tools::doc::ensure_ai_scope_resource_allowed(context, input, &document_id)?;
|
||||
let workspace_id = input.effective_workspace_id();
|
||||
let source_kind = input.effective_source_kind();
|
||||
let root_uri = input.effective_root_uri();
|
||||
let aggregate = build_page_aggregate_snapshot(
|
||||
state,
|
||||
context,
|
||||
&document_id,
|
||||
workspace_id.as_deref(),
|
||||
None,
|
||||
None,
|
||||
source_kind.as_deref(),
|
||||
root_uri.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
let aggregate_value =
|
||||
@@ -83,6 +86,14 @@ fn ensure_write_contract(context: &RequestContext, input: &ToolCallInput) -> Res
|
||||
)
|
||||
.with_context(context));
|
||||
}
|
||||
if input.ai_access_scope_is_read_only() {
|
||||
return Err(WebError::new(
|
||||
axum::http::StatusCode::FORBIDDEN,
|
||||
"mnote_tool_ai_scope_write_forbidden",
|
||||
"当前 AI scope 是只读权限,禁止执行写入型 mnote tool",
|
||||
)
|
||||
.with_context(context));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -200,6 +211,74 @@ async fn page_command(
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
if input.effective_source_kind().as_deref() == Some("local_folder") {
|
||||
let root_uri = input.effective_root_uri().ok_or_else(|| {
|
||||
WebError::bad_request_code("local_folder_root_required", "缺少本地文件夹 rootUri")
|
||||
.with_context(context)
|
||||
})?;
|
||||
crate::routes::ensure_local_workspace_access(context, &root_uri)
|
||||
.map_err(|error| error.with_context(context))?;
|
||||
let local_result = match command_name {
|
||||
"page.body.save" => {
|
||||
let content = payload.get("content").cloned().unwrap_or(Value::Null);
|
||||
crate::routes::write_local_markdown_page_body(
|
||||
&core_protocol::PageBodyWriteRequest {
|
||||
document_id: document_id.clone(),
|
||||
workspace_id: workspace_id.clone().unwrap_or_default(),
|
||||
source_kind: core_protocol::WorkspaceSourceKind::LocalFolder,
|
||||
root_uri: root_uri.clone(),
|
||||
expected_file_version: input.arg_string("expectedFileVersion"),
|
||||
base_content_hash: input.arg_string("baseContentHash"),
|
||||
content_format: "editorBlocks".into(),
|
||||
content,
|
||||
editor_source: Some("mnote.page.save".into()),
|
||||
},
|
||||
)?
|
||||
}
|
||||
"page.head.updateTitle" => {
|
||||
let title = payload
|
||||
.get("title")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| {
|
||||
WebError::bad_request_code(
|
||||
"mnote_tool_bad_request",
|
||||
"mnote.page.update_title 缺少 title",
|
||||
)
|
||||
.with_context(context)
|
||||
})?;
|
||||
crate::routes::update_local_markdown_title(&root_uri, &document_id, title)?
|
||||
}
|
||||
"page.layout.updateOptions" => {
|
||||
let options = payload.get("options").cloned().ok_or_else(|| {
|
||||
WebError::bad_request_code(
|
||||
"mnote_tool_bad_request",
|
||||
"mnote.page.update_options 缺少 options",
|
||||
)
|
||||
.with_context(context)
|
||||
})?;
|
||||
crate::routes::update_local_page_options(&root_uri, &document_id, &options)?
|
||||
}
|
||||
_ => {
|
||||
return Err(WebError::bad_request_code(
|
||||
"mnote_tool_bad_request",
|
||||
format!("local source 暂不支持页面命令 {command_name}"),
|
||||
)
|
||||
.with_context(context));
|
||||
}
|
||||
};
|
||||
let mut result = json!({
|
||||
"dryRun": false,
|
||||
"source": "local_folder",
|
||||
"commandName": if command_name == "page.body.save" { "page.body.write" } else { command_name },
|
||||
"commandId": command_id,
|
||||
"documentId": document_id,
|
||||
"workspaceId": workspace_id,
|
||||
"result": local_result
|
||||
});
|
||||
merge_result_extra(&mut result, result_extra);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let command = RuntimeCommandEnvelopeWire {
|
||||
name: command_name.into(),
|
||||
command_id: command_id.clone(),
|
||||
|
||||
Reference in New Issue
Block a user