mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(skill): prevent directory collision for non-ASCII skill names (#255)
Skills with non-ASCII (e.g. Chinese) names collapsed to underscores in the workspace path, so two such skills resolved to the same directory and overwrote each other. Preserve Unicode letters/digits when sanitizing the path so distinct names map to distinct directories. Also write SKILL.md with CREATE_NEW to avoid a TOCTOU race on concurrent uploads, and mount the skills workspace as a named Docker volume so packages survive container restarts. Closes #254
This commit is contained in:
parent
f238959856
commit
00b87a4325
@ -109,7 +109,9 @@ services:
|
||||
- "1455:1455"
|
||||
volumes:
|
||||
- server_data:/app/data
|
||||
- skills_data:/root/.mateclaw/skills
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
server_data:
|
||||
skills_data:
|
||||
|
||||
@ -47,10 +47,33 @@ public class SkillWorkspaceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按约定解析 skill 工作区路径:{root}/{skillName}/
|
||||
* 按约定解析 skill 工作区路径:{root}/{sanitizedName}-{hash}/
|
||||
* 路径完全由 skillName 决定,不依赖文件系统状态,保证确定性:
|
||||
* 同一 skillName 始终返回同一路径,不同 skillName 不会碰撞。
|
||||
*/
|
||||
public Path resolveConventionPath(String skillName) {
|
||||
return getWorkspaceRoot().resolve(sanitizeName(skillName));
|
||||
String base = sanitizeNameForFs(skillName);
|
||||
String hash = Integer.toHexString(skillName.hashCode());
|
||||
return getWorkspaceRoot().resolve(base + "-" + hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理文件系统路径不安全字符,保留 Unicode 字母及数字的可读性,
|
||||
* 仅移除真正有问题的字符(路径分隔符、控制字符等)。
|
||||
*/
|
||||
private String sanitizeNameForFs(String name) {
|
||||
if (name == null || name.isBlank()) {
|
||||
return "unnamed";
|
||||
}
|
||||
// 第一步:移除路径分隔符和控制字符
|
||||
String cleaned = name.replaceAll("[/\\\\:*?\"<>|\\x00-\\x1F]", "-");
|
||||
// 第二步:保留 Unicode 字母和数字,其他替换为下划线
|
||||
cleaned = cleaned.replaceAll("[^\\p{L}\\p{N}_\\-.\\s]", "_");
|
||||
// 第三步:折叠连续分隔符
|
||||
cleaned = cleaned.replaceAll("[_\\s]+", "_").replaceAll("[-_]+", "_");
|
||||
// 第四步:去掉首尾分隔符
|
||||
cleaned = cleaned.replaceAll("^-|-$", "");
|
||||
return cleaned.isEmpty() ? "unnamed" : cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,11 +139,18 @@ public class SkillWorkspaceManager {
|
||||
Files.createDirectories(workspaceDir.resolve("scripts"));
|
||||
|
||||
Path skillMd = workspaceDir.resolve("SKILL.md");
|
||||
if (overwrite || !Files.exists(skillMd)) {
|
||||
String content = (initialContent != null && !initialContent.isBlank())
|
||||
? initialContent
|
||||
: buildDefaultSkillMd(skillName);
|
||||
String content = (initialContent != null && !initialContent.isBlank())
|
||||
? initialContent
|
||||
: buildDefaultSkillMd(skillName);
|
||||
if (overwrite) {
|
||||
Files.writeString(skillMd, content);
|
||||
} else {
|
||||
// 原子创建,避免并发上传时的 TOCTOU 竞态
|
||||
try {
|
||||
Files.writeString(skillMd, content, StandardOpenOption.CREATE_NEW);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
// 另一线程已创建,跳过写入
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Initialized skill workspace: {} (overwrite={})", workspaceDir, overwrite);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user