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:
lix-2026
2026-05-19 08:07:17 +08:00
parent a2cb1338c8
commit 68d321e297
36 changed files with 8643 additions and 235 deletions
@@ -1,7 +1,9 @@
use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::routes::local_folder_source::decode_local_id_segment;
use crate::routes::local_folder_source::{
decode_local_id_segment, ensure_local_workspace_read_access,
};
use axum::extract::{Extension, Query, State};
use axum::http::{HeaderMap, HeaderName, HeaderValue};
use axum::response::sse::{Event as SseEvent, KeepAlive, Sse};
@@ -9,7 +11,6 @@ use futures_util::stream;
use serde::Deserialize;
use serde_json::{json, Value};
use std::convert::Infallible;
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::broadcast::error::RecvError;
@@ -31,14 +32,8 @@ pub async fn local_folder_events(
),
WebError,
> {
let root = parse_file_root_uri(&query.root_uri)?;
let canonical_root = root.canonicalize().map_err(|error| {
WebError::bad_request_code(
"local_folder_unavailable",
format!("无法访问本地文件夹: {error}"),
)
.with_context(&context)
})?;
let canonical_root = ensure_local_workspace_read_access(&context, &query.root_uri)
.map_err(|error| error.with_context(&context))?;
let document_relative_path = query
.document_id
.as_deref()
@@ -105,17 +100,6 @@ pub async fn local_folder_events(
))
}
fn parse_file_root_uri(root_uri: &str) -> Result<PathBuf, WebError> {
let trimmed = root_uri.trim();
let Some(path) = trimmed.strip_prefix("file://") else {
return Err(WebError::bad_request_code(
"local_folder_root_invalid",
"本地文件夹 rootUri 必须是 file:// URI",
));
};
Ok(PathBuf::from(path))
}
fn local_markdown_relative_path_from_document_id(document_id: &str) -> Option<String> {
let trimmed = document_id.trim();
let encoded = trimmed.strip_prefix("local-md:")?;
@@ -156,10 +140,4 @@ mod tests {
Some("docs/README.md")
);
}
#[test]
fn parse_file_root_uri_requires_file_scheme() {
assert!(parse_file_root_uri("file:///tmp/example").is_ok());
assert!(parse_file_root_uri("/tmp/example").is_err());
}
}