diff --git a/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java b/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java index 04a3ce85..ac097c99 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java @@ -30,6 +30,25 @@ public class TemplateDTO { private String systemPrompt; private List workspaceFiles; + /** + * Skill slugs (matching {@code mate_skill.name}) to pre-bind to the newly + * hired agent. Resolved against the target workspace at apply time; any + * slug whose row is missing in that workspace is logged and skipped so a + * partially-installed environment can still hire the agent. Templates ship + * with classpath-stable slugs, not numeric IDs, because skill ids vary per + * install. + */ + private List defaultSkillSlugs; + + /** + * Tool names to pre-bind directly (bypassing the skill layer). Filtered + * against {@code AvailableToolService.listAvailable()} at apply time — + * names the picker can't resolve are dropped with a warning rather than + * aborting the hire. Use for capabilities that aren't owned by any skill, + * not for system-level tools that are already universally available. + */ + private List defaultToolNames; + @Data public static class WorkspaceFileTemplate { private String filename; diff --git a/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java b/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java index ae416515..9d76fc30 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java @@ -1,5 +1,6 @@ package vip.mate.agent.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -8,9 +9,14 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import vip.mate.agent.AgentService; +import vip.mate.agent.binding.service.AgentBindingService; import vip.mate.agent.model.AgentEntity; import vip.mate.agent.model.TemplateDTO; import vip.mate.exception.MateClawException; +import vip.mate.skill.model.SkillEntity; +import vip.mate.skill.repository.SkillMapper; +import vip.mate.tool.model.AvailableToolDTO; +import vip.mate.tool.service.AvailableToolService; import vip.mate.workspace.document.WorkspaceFileService; import java.io.IOException; @@ -18,6 +24,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -36,6 +43,9 @@ public class TemplateService { private final AgentService agentService; private final WorkspaceFileService workspaceFileService; private final ObjectMapper objectMapper; + private final AgentBindingService agentBindingService; + private final SkillMapper skillMapper; + private final AvailableToolService availableToolService; /** * 列出所有可用模板 @@ -135,9 +145,94 @@ public class TemplateService { } } + // 4. Pre-bind skills the template declares so a hired agent is + // usable out of the box ("数据分析师" already knows SQL, "代码审查员" + // already has the test-driven-development playbook). Slugs are + // resolved against the target workspace; missing skills are skipped + // with a warning so a partially-installed environment can still + // complete the hire. + applyDefaultSkillBindings(template, created, workspaceId); + + // 5. Pre-bind any standalone tools the template wants. Filtered + // against the picker so a deprecated / unavailable tool name in the + // template doesn't abort the hire — same forgiving stance as + // skills above. + applyDefaultToolBindings(template, created); + return created; } + /** + * Resolve {@link TemplateDTO#getDefaultSkillSlugs()} to skill ids inside + * {@code workspaceId} and pre-bind them on the freshly-created agent. + * Slugs whose row is missing in the workspace are logged and dropped — a + * template MUST be safe to apply even when some bundled skills haven't + * landed yet (offline upgrade, partial seed, custom workspace). + */ + private void applyDefaultSkillBindings(TemplateDTO template, AgentEntity created, Long workspaceId) { + List slugs = template.getDefaultSkillSlugs(); + if (slugs == null || slugs.isEmpty()) return; + + List resolvedIds = new ArrayList<>(); + for (String slug : slugs) { + if (slug == null || slug.isBlank()) continue; + SkillEntity skill = skillMapper.selectOne(new LambdaQueryWrapper() + .eq(SkillEntity::getName, slug.trim()) + .eq(SkillEntity::getWorkspaceId, workspaceId)); + if (skill == null) { + log.warn("[Template] template {} requested skill slug '{}' not found in workspace {}; skipping", + template.getId(), slug, workspaceId); + continue; + } + resolvedIds.add(skill.getId()); + } + if (resolvedIds.isEmpty()) return; + + agentBindingService.setSkillBindings(created.getId(), resolvedIds); + log.info("[Template] template {} pre-bound {} skill(s) on agent {}", + template.getId(), resolvedIds.size(), created.getId()); + } + + /** + * Pre-filter the template's tool names through the picker so + * {@link AgentBindingService#setToolBindings} sees only resolvable names + * — its own validation would otherwise abort the call on the first + * unknown name and leave the agent with no tool bindings at all. + */ + private void applyDefaultToolBindings(TemplateDTO template, AgentEntity created) { + List names = template.getDefaultToolNames(); + if (names == null || names.isEmpty()) return; + + Set bindable; + try { + bindable = availableToolService.listAvailable().stream() + .filter(AvailableToolDTO::isAvailable) + .map(AvailableToolDTO::getName) + .collect(Collectors.toSet()); + } catch (Exception e) { + log.warn("[Template] picker unavailable during template {} apply; skipping tool pre-bind: {}", + template.getId(), e.getMessage()); + return; + } + + List filtered = new ArrayList<>(); + for (String name : names) { + if (name == null || name.isBlank()) continue; + String trimmed = name.trim(); + if (bindable.contains(trimmed)) { + filtered.add(trimmed); + } else { + log.warn("[Template] template {} requested tool '{}' not currently bindable; skipping", + template.getId(), trimmed); + } + } + if (filtered.isEmpty()) return; + + agentBindingService.setToolBindings(created.getId(), filtered); + log.info("[Template] template {} pre-bound {} tool(s) on agent {}", + template.getId(), filtered.size(), created.getId()); + } + /** * True when the raw Accept-Language header best-matches a Chinese locale. * Implementation is intentionally simple — we only need to disambiguate diff --git a/mateclaw-server/src/main/resources/templates/code-reviewer.json b/mateclaw-server/src/main/resources/templates/code-reviewer.json index cca50445..78c467c5 100644 --- a/mateclaw-server/src/main/resources/templates/code-reviewer.json +++ b/mateclaw-server/src/main/resources/templates/code-reviewer.json @@ -8,6 +8,12 @@ "agentType": "react", "tags": "code,review,developer", "maxIterations": 10, + "defaultSkillSlugs": [ + "systematic-debugging", + "test-driven-development", + "requesting-code-review", + "subagent-driven-development" + ], "systemPrompt": "## Role\n代码审查员\n\n## Goal\n找到不该在 PR 里的东西\n\n## Backstory\n你是见过太多周五下午合并事故的资深审查员。你信奉一条:被合进 main 的代码,要么经得起半年后的回头看,要么不该进。你读代码先读改动的边界——它影响哪些调用方、哪些边缘情况、哪些隐藏假设。你直接但不刻薄,每一条意见都附上修法。\n\n## Additional Instructions\n审查清单:逻辑错误与边缘情况;安全漏洞;性能瓶颈;命名与可读性;错误处理完备性;测试覆盖盲区。先读完整段再下结论,不要只看 diff 的±号。\n", "workspaceFiles": [ { diff --git a/mateclaw-server/src/main/resources/templates/data-analyst.json b/mateclaw-server/src/main/resources/templates/data-analyst.json index 817accd2..52c81ebb 100644 --- a/mateclaw-server/src/main/resources/templates/data-analyst.json +++ b/mateclaw-server/src/main/resources/templates/data-analyst.json @@ -8,6 +8,10 @@ "agentType": "react", "tags": "data,analysis,sql", "maxIterations": 12, + "defaultSkillSlugs": [ + "sql_query", + "xlsx" + ], "systemPrompt": "## Role\n数据分析师\n\n## Goal\n把数据变成可执行的洞察\n\n## Backstory\n你在数据里待了十年。最大的体会是:80% 的烂分析栽在第一步——问题没问对。所以你拿到任何需求都先停一下,确认\"我们到底想知道什么\",再决定要拉哪张表。你写 SQL 简洁、加注释,不堆 CTE 炫技。出结论时永远带数据范围、口径定义和置信度。\n\n## Additional Instructions\n工作流程:1) 复述问题,确认理解;2) 列出关键指标与维度;3) 写查询并注明口径;4) 给一句话结论 + 一张关键图 + 三条建议。不要把表格堆给用户,要把判断给他。\n", "workspaceFiles": [ { diff --git a/mateclaw-server/src/main/resources/templates/product-assistant.json b/mateclaw-server/src/main/resources/templates/product-assistant.json index e78f7513..c6d84e4a 100644 --- a/mateclaw-server/src/main/resources/templates/product-assistant.json +++ b/mateclaw-server/src/main/resources/templates/product-assistant.json @@ -8,6 +8,11 @@ "agentType": "react", "tags": "product,prd,requirements", "maxIterations": 12, + "defaultSkillSlugs": [ + "ideation", + "make_plan", + "writing-plans" + ], "systemPrompt": "## Role\n产品助理\n\n## Goal\n把模糊需求理成可执行的 PRD\n\n## Backstory\n你做产品做久了,知道一句话需求背后通常藏着三个不一样的问题。所以你拿到任何描述,先把它翻译成\"用户是谁 + 他在什么场景下 + 他想达成什么 + 现在的痛点是什么\"。你写 PRD 不堆功能列表,会先讲清楚\"不做什么\"和\"成功长什么样\"。\n\n## Additional Instructions\n输出结构:1) 用户与场景;2) 目标与反目标(不做什么);3) 核心流程;4) 验收标准。一段话能讲清的不用列表,能列清的不用图。讲清楚\"为什么\"比讲清楚\"做什么\"更重要。\n", "workspaceFiles": [ { diff --git a/mateclaw-server/src/main/resources/templates/research-analyst.json b/mateclaw-server/src/main/resources/templates/research-analyst.json index ae7b7e0b..8e9575fc 100644 --- a/mateclaw-server/src/main/resources/templates/research-analyst.json +++ b/mateclaw-server/src/main/resources/templates/research-analyst.json @@ -8,6 +8,11 @@ "agentType": "plan_execute", "tags": "research,analysis,planning", "maxIterations": 20, + "defaultSkillSlugs": [ + "arxiv", + "news", + "x_intel" + ], "systemPrompt": "## Role\n研究分析员\n\n## Goal\n把信息整理成可下结论的判断\n\n## Backstory\n你像图书馆员一样固执——没有可信来源,你不下结论。你做研究的步骤是固定的:先把大问题拆成可独立查证的小问题,再分别取证,最后交叉对照。看到两个来源说反话,你不会偷偷选一个,会原样列出并标注分歧。\n\n## Additional Instructions\n研究流程:1) 拆解问题;2) 用网络搜索拿最新事实;3) 在 Wiki 知识库找已有分析;4) 多源交叉验证;5) 对每条结论标注信心等级。准确高于速度。证据不足时直接说\"我不知道\"。\n", "workspaceFiles": [ {