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 9d76fc30..a05b37c8 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 @@ -147,16 +147,16 @@ 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); + // already has the test-driven-development playbook). Resolution + // failures (slug missing in this workspace) are skipped with a + // warning; bind-service exceptions still propagate and roll back + // the @Transactional hire — see helper Javadoc. + applyDefaultSkillBindings(template, created); - // 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. + // 5. Pre-bind any standalone tools the template wants. Picker + // outage and unknown names are dropped with a warning; a + // setToolBindings exception still propagates (same contract as + // skills) — see helper Javadoc. applyDefaultToolBindings(template, created); return created; @@ -164,15 +164,39 @@ public class TemplateService { /** * 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). + * the agent's own workspace and pre-bind them. + * + *

Failure contract — read carefully. + *

+ * + *

Reads the workspace off the just-persisted {@link AgentEntity} + * rather than a separate parameter so the lookup and the validator + * inside {@code AgentBindingService.requireSameWorkspace} can never + * disagree on which workspace they're talking about. */ - private void applyDefaultSkillBindings(TemplateDTO template, AgentEntity created, Long workspaceId) { + private void applyDefaultSkillBindings(TemplateDTO template, AgentEntity created) { List slugs = template.getDefaultSkillSlugs(); if (slugs == null || slugs.isEmpty()) return; + // Mirror the fallback inside AgentBindingService.requireSameWorkspace: + // a null workspace_id on a row is treated as workspace 1, so the + // lookup needs to agree or we'd silently turn `eq(workspaceId, null)` + // into `IS NULL` and match nothing. + Long workspaceId = created.getWorkspaceId() == null ? 1L : created.getWorkspaceId(); + List resolvedIds = new ArrayList<>(); for (String slug : slugs) { if (slug == null || slug.isBlank()) continue; @@ -198,6 +222,11 @@ public class TemplateService { * {@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. + * + *

Failure contract mirrors {@link #applyDefaultSkillBindings}: + * picker outage and unknown names are dropped with a warning; an + * exception from {@code setToolBindings} itself still propagates and + * rolls back the hire. */ private void applyDefaultToolBindings(TemplateDTO template, AgentEntity created) { List names = template.getDefaultToolNames(); diff --git a/mateclaw-server/src/main/resources/templates/product-assistant.json b/mateclaw-server/src/main/resources/templates/product-assistant.json index c6d84e4a..74f96d0c 100644 --- a/mateclaw-server/src/main/resources/templates/product-assistant.json +++ b/mateclaw-server/src/main/resources/templates/product-assistant.json @@ -10,8 +10,7 @@ "maxIterations": 12, "defaultSkillSlugs": [ "ideation", - "make_plan", - "writing-plans" + "make_plan" ], "systemPrompt": "## Role\n产品助理\n\n## Goal\n把模糊需求理成可执行的 PRD\n\n## Backstory\n你做产品做久了,知道一句话需求背后通常藏着三个不一样的问题。所以你拿到任何描述,先把它翻译成\"用户是谁 + 他在什么场景下 + 他想达成什么 + 现在的痛点是什么\"。你写 PRD 不堆功能列表,会先讲清楚\"不做什么\"和\"成功长什么样\"。\n\n## Additional Instructions\n输出结构:1) 用户与场景;2) 目标与反目标(不做什么);3) 核心流程;4) 验收标准。一段话能讲清的不用列表,能列清的不用图。讲清楚\"为什么\"比讲清楚\"做什么\"更重要。\n", "workspaceFiles": [