新增AGC首页自定义工作目录

新增用户目录创建命令与首页接线

保持普通用户权限并避免ACL和UAC耦合

补充里程碑与实施计划
This commit is contained in:
2026-09-15 14:38:04 +08:00
parent 8ae7ed0128
commit fa2a7caa95
5 changed files with 109 additions and 2 deletions
@@ -811,6 +811,41 @@ pub(crate) async fn pick_local_project_directory(
Ok(Some(path.to_string_lossy().into_owned()))
}
/// Create a user-requested child directory using the current user's normal
/// filesystem rights. This deliberately does not inspect or rewrite ACLs and
/// never attempts elevation.
#[tauri::command]
pub(crate) fn create_local_project_directory(
parent_path: String,
directory_name: String,
) -> Result<String, String> {
let parent = Path::new(parent_path.trim());
if parent.as_os_str().is_empty() || !parent.is_absolute() {
return Err("父目录必须是绝对路径".to_string());
}
if project_path_has_control_chars(parent) {
return Err("父目录不能包含控制字符".to_string());
}
if !parent.is_dir() {
return Err("父目录不存在或不是文件夹".to_string());
}
let name = directory_name.trim();
if name.is_empty() || name == "." || name == ".." || name.chars().any(|c| c == '/' || c == '\\')
{
return Err("目录名称无效".to_string());
}
if name.chars().any(|c| c.is_control()) {
return Err("目录名称不能包含控制字符".to_string());
}
let target = parent.join(name);
fs::create_dir(&target).map_err(|error| match error.kind() {
std::io::ErrorKind::AlreadyExists => "目录已存在".to_string(),
std::io::ErrorKind::PermissionDenied => "没有权限在此位置创建目录".to_string(),
_ => format!("创建目录失败:{error}"),
})?;
Ok(target.to_string_lossy().into_owned())
}
#[tauri::command]
pub(crate) async fn pick_local_file(app: tauri::AppHandle) -> Result<Option<String>, String> {
let (sender, receiver) = tokio::sync::oneshot::channel();
@@ -2636,6 +2636,7 @@ fn main() {
is_local_project_directory_non_empty,
inspect_local_project_directory,
pick_local_project_directory,
create_local_project_directory,
rename_local_game_project,
suggest_automatic_project_name,
polish_local_project_prompt,
@@ -895,14 +895,26 @@ export function useHomeProjectCreation({
setProjectAction('creating');
setStatus('正在选择新项目文件夹');
try {
const selectedPath = await invoke<string | null>(
const parentPath = await invoke<string | null>(
'pick_local_project_directory',
projectPath.trim() ? { initialPath: projectPath.trim() } : undefined,
);
if (!selectedPath) {
if (!parentPath) {
setStatus('已取消');
return;
}
const directoryName = window.prompt('请输入新目录名称');
if (!directoryName?.trim()) {
setStatus('已取消');
return;
}
const selectedPath = await invoke<string>(
'create_local_project_directory',
{
parentPath,
directoryName: directoryName.trim(),
},
);
setProjectPath(selectedPath);
projectActionRef.current = null;
setProjectAction(null);
@@ -0,0 +1,26 @@
# 【实施计划】AGC 首页自定义工作目录
Version: 1
Status: active
Date: 2026-09-15
Parent Spec: 【里程碑】AGC首页自定义工作目录-2026-09-15
## 顺序
1. 定位现有首页创建与 Tauri 命令注册。
2. 增加目录选择/创建/检查的最小 native command 与前端适配。
3. 接入首页创建状态,保留现有 HomeCreationOperation。
4. 补 Rust/前端定向测试,执行编码和差异检查。
## 验证
- AGC web typecheck
- 相关 Tauri cargo test
- AGC appSurface 定向测试
- npm run check:encoding
- git diff --check
## 风险与回滚
- 风险:Windows 权限检查误触发 ACL/UAC;只使用普通文件 API,不调用安全描述符或提升权限。
- 回滚:移除新增 command/UI 适配,保留既有自动建项路径。
@@ -0,0 +1,33 @@
# 【里程碑】AGC 首页自定义工作目录
Version: 1
Status: active
Date: 2026-09-15
Parent Spec: AGC 客户端 AI 游戏创作 App 实施计划
## 范围
- 首页创建入口支持选择已有目录或创建子目录。
- 目录校验包含存在性、目录类型、可读写性和空目录判断。
- 校验通过后复用现有首页建项流程。
- 外部进程在校验后写入目录导致非空,不纳入本里程碑处理。
## 权限边界
- 不修改 ACL,不请求管理员权限,不通过提升权限探测目录。
- 可写性以当前用户在目标目录执行无害临时文件创建/删除为准;失败返回普通错误。
- 选择目录使用系统目录选择器,创建目录使用当前用户可写的父目录。
## 验收
- 选择空目录可继续建项。
- 选择非空目录被阻止并可重新选择。
- 创建目录成功后自动选中并可继续建项。
- 不存在、非目录、不可读写均有稳定错误状态。
- 不触发 UAC,不出现 ACL 权限栈错误泄漏。
## 不做
- 不合并非空目录内容。
- 不监控校验后目录变化。
- 不新增项目格式或公开 API。