mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 19:23:42 +08:00
fix(skill): sync workspace when builtin skill is updated or version bumped
This commit is contained in:
parent
3940cfd1ce
commit
9d727c607a
@ -114,11 +114,19 @@ public class SkillController {
|
||||
return R.ok(skillRuntimeService.resolveAllSkillsStatus());
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新 active skills 缓存")
|
||||
@Operation(summary = "刷新 active skills 缓存,resync=true 时同步内置技能到 workspace")
|
||||
@PostMapping("/runtime/refresh")
|
||||
public R<Map<String, Object>> refreshRuntime() {
|
||||
public R<Map<String, Object>> refreshRuntime(
|
||||
@RequestParam(defaultValue = "false") boolean resync) {
|
||||
List<String> resynced = List.of();
|
||||
if (resync) {
|
||||
resynced = workspaceManager.syncBundledSkills();
|
||||
}
|
||||
List<ResolvedSkill> skills = skillRuntimeService.refreshActiveSkills();
|
||||
return R.ok(Map.of("count", skills.size(), "message", "Active skills refreshed"));
|
||||
return R.ok(Map.of(
|
||||
"count", skills.size(),
|
||||
"message", resync ? "Active skills refreshed with workspace resync" : "Active skills refreshed",
|
||||
"resynced", resynced));
|
||||
}
|
||||
|
||||
// ==================== Workspace API ====================
|
||||
|
||||
@ -153,6 +153,9 @@ public class SkillService {
|
||||
skillMapper.updateById(existing);
|
||||
log.info("Updated builtin skill (limited): {}", existing.getName());
|
||||
|
||||
// builtin skill 也同步 workspace SKILL.md
|
||||
syncSkillContentToWorkspace(existing);
|
||||
|
||||
// 刷新 runtime cache
|
||||
if (runtimeService != null) {
|
||||
runtimeService.refreshActiveSkills();
|
||||
|
||||
@ -13,7 +13,10 @@ import java.util.List;
|
||||
* Skill 工作区启动初始化
|
||||
* <p>
|
||||
* 1. 确保 workspace root 目录存在
|
||||
* 2. 将 classpath 下预置技能同步到 workspace(仅首次,不覆盖)
|
||||
* 2. 将 classpath 下预置技能同步到 workspace
|
||||
* - 首次:创建并同步
|
||||
* - 后续:比对 SKILL.md frontmatter 中的 version 字段,
|
||||
* bundled version 更高时归档旧版本并覆盖升级
|
||||
* <p>
|
||||
* Order(195) — 在 DatabaseBootstrapRunner(200) 之前执行。
|
||||
*
|
||||
|
||||
@ -346,11 +346,28 @@ public class SkillWorkspaceManager {
|
||||
|
||||
Path targetDir = resolveConventionPath(skillName);
|
||||
if (Files.exists(targetDir)) {
|
||||
log.debug("Bundled skill '{}' already exists at {}, skipping", skillName, targetDir);
|
||||
// 版本比对:classpath version > workspace version 时覆盖升级
|
||||
String bundledVersion = parseFrontmatterVersion(skillMdResource);
|
||||
Path workspaceMd = targetDir.resolve("SKILL.md");
|
||||
String workspaceVersion = Files.exists(workspaceMd)
|
||||
? parseFrontmatterVersionFromPath(workspaceMd) : null;
|
||||
|
||||
if (bundledVersion != null && isNewerVersion(bundledVersion, workspaceVersion)) {
|
||||
log.info("Bundled skill '{}' version {} > workspace version {}, upgrading",
|
||||
skillName, bundledVersion, workspaceVersion);
|
||||
archiveWorkspace(skillName);
|
||||
syncSingleBundledSkill(resolver, bundledPath, skillName, targetDir);
|
||||
synced.add(skillName);
|
||||
eventPublisher.publishEvent(
|
||||
new SkillWorkspaceEvent(skillName, SkillWorkspaceEvent.Type.CREATED, targetDir));
|
||||
} else {
|
||||
log.debug("Bundled skill '{}' workspace version is current (bundled={}, workspace={}), skipping",
|
||||
skillName, bundledVersion, workspaceVersion);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 同步该技能目录下的所有文件
|
||||
// 目录不存在:首次同步
|
||||
syncSingleBundledSkill(resolver, bundledPath, skillName, targetDir);
|
||||
synced.add(skillName);
|
||||
log.info("Synced bundled skill '{}' → {}", skillName, targetDir);
|
||||
@ -433,6 +450,74 @@ public class SkillWorkspaceManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 版本比对 ====================
|
||||
|
||||
private static final java.util.regex.Pattern VERSION_PATTERN =
|
||||
java.util.regex.Pattern.compile("^version:\\s*[\"']?([^\"'\\s]+)[\"']?", java.util.regex.Pattern.MULTILINE);
|
||||
|
||||
/**
|
||||
* 从 classpath Resource (SKILL.md) 的 frontmatter 中提取 version 字段
|
||||
*/
|
||||
private String parseFrontmatterVersion(Resource resource) {
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
String content = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
|
||||
return extractVersionFromContent(content);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件路径 (SKILL.md) 的 frontmatter 中提取 version 字段
|
||||
*/
|
||||
private String parseFrontmatterVersionFromPath(Path path) {
|
||||
try {
|
||||
String content = Files.readString(path);
|
||||
return extractVersionFromContent(content);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String extractVersionFromContent(String content) {
|
||||
// 只读 frontmatter 部分(--- 之间)
|
||||
if (!content.startsWith("---")) return null;
|
||||
int endIdx = content.indexOf("---", 3);
|
||||
if (endIdx < 0) return null;
|
||||
String frontmatter = content.substring(0, endIdx);
|
||||
var matcher = VERSION_PATTERN.matcher(frontmatter);
|
||||
return matcher.find() ? matcher.group(1) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 语义版本比较:newer > older 返回 true
|
||||
* <p>
|
||||
* 例:1.1.0 > 1.0.0, 2.0.0 > 1.9.9, older=null 视为 0.0.0
|
||||
*/
|
||||
static boolean isNewerVersion(String newer, String older) {
|
||||
if (newer == null) return false;
|
||||
if (older == null) return true;
|
||||
|
||||
String[] nParts = newer.split("\\.");
|
||||
String[] oParts = older.split("\\.");
|
||||
int len = Math.max(nParts.length, oParts.length);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int n = i < nParts.length ? parseSegment(nParts[i]) : 0;
|
||||
int o = i < oParts.length ? parseSegment(oParts[i]) : 0;
|
||||
if (n > o) return true;
|
||||
if (n < o) return false;
|
||||
}
|
||||
return false; // equal
|
||||
}
|
||||
|
||||
private static int parseSegment(String s) {
|
||||
try {
|
||||
return Integer.parseInt(s.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
private String sanitizeName(String name) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
name: sql_query
|
||||
version: "1.1.0"
|
||||
description: "当用户提出数据查询需求(如'查数'、'查一下订单量'、'有多少用户'、'帮我跑个SQL'等),使用数据源工具发现表结构,生成并执行只读 SQL 查询。"
|
||||
dependencies:
|
||||
tools:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user