feat: 完善 Agent 标准交互与安全运行时

- 接入 AG-UI 运行投影、Turn 时间线和审批隔离

- 增加 Agent Skill 冻结绑定与运行时消费闭环

- 增加受控工作区、内置工具和私有 Artifact 生命周期
This commit is contained in:
2026-08-19 22:13:41 +08:00
parent 91d66e636d
commit 4e8640dcaf
241 changed files with 24382 additions and 2777 deletions

View File

@@ -26,6 +26,8 @@ import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;
import java.math.BigInteger;
import java.util.Objects;
@@ -46,6 +48,7 @@ public class AgentDependencyAccessService {
private final AgentCategoryService agentCategoryService;
private final CategoryPermissionService categoryPermissionService;
private final ResourceAccessService resourceAccessService;
private final SkillService skillService;
/**
* 创建 Agent 依赖资源校验服务。
@@ -60,6 +63,7 @@ public class AgentDependencyAccessService {
* @param agentCategoryService Agent 分类服务
* @param categoryPermissionService 分类权限服务
* @param resourceAccessService 资源权限服务
* @param skillService Skill 服务
*/
public AgentDependencyAccessService(ModelService modelService,
WorkflowService workflowService,
@@ -70,7 +74,8 @@ public class AgentDependencyAccessService {
DocumentCollectionService documentCollectionService,
AgentCategoryService agentCategoryService,
CategoryPermissionService categoryPermissionService,
ResourceAccessService resourceAccessService) {
ResourceAccessService resourceAccessService,
SkillService skillService) {
this.modelService = modelService;
this.workflowService = workflowService;
this.pluginItemService = pluginItemService;
@@ -81,6 +86,7 @@ public class AgentDependencyAccessService {
this.agentCategoryService = agentCategoryService;
this.categoryPermissionService = categoryPermissionService;
this.resourceAccessService = resourceAccessService;
this.skillService = skillService;
}
/**
@@ -135,6 +141,17 @@ public class AgentDependencyAccessService {
* @return 插件工具
*/
public PluginItem requirePluginItem(Agent agent, BigInteger pluginItemId) {
return requirePluginResource(agent, pluginItemId).pluginItem();
}
/**
* 校验并锁定插件工具及其父插件,返回同一事务中的完整调用资源。
*
* @param agent Agent
* @param pluginItemId 插件工具 ID
* @return 插件项与父插件
*/
public PluginResource requirePluginResource(Agent agent, BigInteger pluginItemId) {
PluginItem current = pluginItemService.getById(pluginItemId);
if (current == null || current.getPluginId() == null) {
throw new BusinessException("绑定插件不存在");
@@ -153,7 +170,7 @@ public class AgentDependencyAccessService {
}
assertSameTenant(agent, plugin.getTenantId(), "无权限绑定该插件");
pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限绑定该插件");
return pluginItem;
return new PluginResource(pluginItem, plugin);
}
/**
@@ -174,6 +191,30 @@ public class AgentDependencyAccessService {
return mcp;
}
/**
* 校验并锁定 Agent 可使用的已发布 Skill。
*
* <p>Skill 发布阶段已经完成底层 Tool 权限与 MCP 清单检测。Agent 保存阶段只消费冻结快照,
* 避免在数据库事务中执行外部 MCP I/O快照内容及组合 hash 由运行投影器继续校验。</p>
*
* @param agent Agent
* @param skillId Skill ID
* @return 已发布 Skill
*/
public Skill requireSkill(Agent agent, BigInteger skillId) {
Skill skill = skillService.getOne(QueryWrapper.create()
.eq(Skill::getId, skillId)
.forUpdate());
if (skill == null || PublishStatus.from(skill.getPublishStatus()) != PublishStatus.PUBLISHED
|| skill.getPublishedSnapshotJson() == null || skill.getPublishedSnapshotJson().isEmpty()) {
throw new BusinessException("绑定 Skill 不存在或未发布");
}
assertSameTenant(agent, skill.getTenantId(), "无权限绑定该 Skill");
resourceAccessService.assertAccess(
CategoryResourceType.SKILL, skill, ResourceAction.USE, "无权限绑定该 Skill");
return skill;
}
/**
* 校验并锁定知识库。
*
@@ -233,4 +274,13 @@ public class AgentDependencyAccessService {
throw new BusinessException(message);
}
}
/**
* 插件运行依赖聚合。
*
* @param pluginItem 插件工具
* @param plugin 父插件调用配置
*/
public record PluginResource(PluginItem pluginItem, Plugin plugin) {
}
}

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.security.AgentVisibilityQueryHelper;
import tech.easyflow.agent.vo.AgentOptionView;
@@ -28,8 +29,12 @@ import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -54,9 +59,11 @@ public class AgentOptionQueryService {
private final PluginItemService pluginItemService;
private final PluginVisibilityService pluginVisibilityService;
private final McpService mcpService;
private final SkillService skillService;
private final AgentVisibilityQueryHelper agentVisibilityQueryHelper;
private final ResourceAccessService resourceAccessService;
private final ObjectMapper objectMapper;
private CategoryPermissionService categoryPermissionService;
/**
* 创建 Agent 安全选项查询服务。
@@ -69,6 +76,7 @@ public class AgentOptionQueryService {
* @param pluginItemService 插件工具服务
* @param pluginVisibilityService 插件可见性服务
* @param mcpService MCP 服务
* @param skillService Skill 服务
* @param agentVisibilityQueryHelper Agent 可见性查询助手
* @param resourceAccessService 资源访问服务
* @param objectMapper JSON 映射器
@@ -81,6 +89,7 @@ public class AgentOptionQueryService {
PluginItemService pluginItemService,
PluginVisibilityService pluginVisibilityService,
McpService mcpService,
SkillService skillService,
AgentVisibilityQueryHelper agentVisibilityQueryHelper,
ResourceAccessService resourceAccessService,
ObjectMapper objectMapper) {
@@ -92,6 +101,7 @@ public class AgentOptionQueryService {
this.pluginItemService = pluginItemService;
this.pluginVisibilityService = pluginVisibilityService;
this.mcpService = mcpService;
this.skillService = skillService;
this.agentVisibilityQueryHelper = agentVisibilityQueryHelper;
this.resourceAccessService = resourceAccessService;
this.objectMapper = objectMapper;
@@ -133,12 +143,113 @@ public class AgentOptionQueryService {
return new AgentResourceOptionsView(
listModelOptions(account),
listKnowledgeOptions(account),
listSkillOptions(account),
listWorkflowOptions(account),
listPluginToolOptions(account),
listMcpOptions(account)
listMcpOptions(account),
new AgentResourceOptionsView.Capabilities(
categoryPermissionService != null && categoryPermissionService.isSuperAdmin(account))
);
}
/**
* 注入平台超级管理员判定服务。
*
* @param categoryPermissionService 分类权限服务
*/
@Autowired
public void setCategoryPermissionService(CategoryPermissionService categoryPermissionService) {
this.categoryPermissionService = categoryPermissionService;
}
/**
* 查询当前账号可使用的已发布 Skill 安全选项。
*
* @param account 当前登录账号
* @return Skill 选项
*/
private List<AgentResourceOptionsView.SkillOption> listSkillOptions(LoginAccount account) {
return skillService.list(QueryWrapper.create()
.eq(Skill::getTenantId, account.getTenantId())
.eq(Skill::getPublishStatus, PublishStatus.PUBLISHED.getCode())
.orderBy(Skill::getModified, false)
.orderBy(Skill::getDisplayName, true))
.stream()
.filter(skill -> resourceAccessService.canAccess(
CategoryResourceType.SKILL, skill, ResourceAction.USE))
.map(this::toSkillOption)
.toList();
}
/**
* 将 Skill 发布数据投影为不含正文、资源内容和连接配置的选择项。
*
* @param skill Skill
* @return 安全选择项
*/
private AgentResourceOptionsView.SkillOption toSkillOption(Skill skill) {
Map<String, Object> publishedSnapshot = skill.getPublishedSnapshotJson();
List<?> resources = listValue(publishedSnapshot, "resources");
int textCount = 0;
int binaryCount = 0;
long textBytes = utf8Length(textValue(publishedSnapshot, "skillContent"));
for (Object raw : resources) {
if (!(raw instanceof Map<?, ?> resource)) {
continue;
}
if (Boolean.TRUE.equals(resource.get("text"))) {
textCount++;
textBytes = Math.addExact(textBytes,
utf8Length(resource.get("textContent") == null
? null : String.valueOf(resource.get("textContent"))));
} else {
binaryCount++;
}
}
int toolCount = 0;
for (Object raw : listValue(skill.getPublishedToolBindingsJson(), "bindings")) {
if (raw instanceof Map<?, ?> binding && binding.get("toolCount") instanceof Number number) {
toolCount += Math.max(0, number.intValue());
}
}
return new AgentResourceOptionsView.SkillOption(
skill.getId(),
publishedText(publishedSnapshot, "displayName", skill.getDisplayName()),
publishedText(publishedSnapshot, "description", skill.getDescription()),
publishedText(publishedSnapshot, "visibilityScope", skill.getVisibilityScope()),
skill.getSnapshotHash(), toolCount, textBytes, textCount, binaryCount);
}
/**
* 读取发布快照中的展示字段,旧快照缺少字段时兼容当前行。
*
* @param snapshot 发布内容快照
* @param key 字段名
* @param legacyFallback 历史快照回退值
* @return 冻结展示值
*/
private String publishedText(Map<String, Object> snapshot, String key, String legacyFallback) {
if (snapshot == null || !snapshot.containsKey(key)) {
return legacyFallback;
}
Object value = snapshot.get(key);
return value == null ? null : String.valueOf(value);
}
private String textValue(Map<String, Object> source, String key) {
Object value = source == null ? null : source.get(key);
return value == null ? null : String.valueOf(value);
}
private long utf8Length(String value) {
return value == null ? 0L : value.getBytes(StandardCharsets.UTF_8).length;
}
private List<?> listValue(Map<String, Object> source, String key) {
Object value = source == null ? null : source.get(key);
return value instanceof List<?> list ? list : List.of();
}
/**
* 查询当前账号可用于 Agent 会话的知识库安全选项。
*
@@ -228,7 +339,6 @@ public class AgentOptionQueryService {
return workflowService.list(QueryWrapper.create()
.eq(Workflow::getTenantId, account.getTenantId())
.eq(Workflow::getPublishStatus, PublishStatus.PUBLISHED.getCode())
.eq(Workflow::getStatus, 1)
.orderBy(Workflow::getModified, false))
.stream()
.filter(item -> resourceAccessService.canAccess(

View File

@@ -2,8 +2,12 @@ package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
/**
@@ -35,6 +39,26 @@ public interface AgentService extends IService<Agent> {
*/
Agent updateDraft(Agent agent);
/**
* 在一个事务中保存 Agent 草稿及发生变化的资源绑定。
*
* @param agent Agent 草稿
* @param toolBindings 工具绑定
* @param replaceToolBindings 是否替换工具绑定
* @param knowledgeBindings 知识库绑定
* @param replaceKnowledgeBindings 是否替换知识库绑定
* @param skillBindings Skill 绑定
* @param replaceSkillBindings 是否替换 Skill 绑定
* @return 保存后的 Agent 与本次替换的绑定
*/
Agent saveDraftGraph(Agent agent,
List<AgentToolBinding> toolBindings,
boolean replaceToolBindings,
List<AgentKnowledgeBinding> knowledgeBindings,
boolean replaceKnowledgeBindings,
List<AgentSkillBinding> skillBindings,
boolean replaceSkillBindings);
/**
* 更新 Agent 的可见范围。
*

View File

@@ -0,0 +1,38 @@
package tech.easyflow.agent.service;
import com.mybatisflex.core.service.IService;
import tech.easyflow.agent.entity.AgentSkillBinding;
import java.math.BigInteger;
import java.util.List;
/**
* Agent Skill 绑定服务。
*/
public interface AgentSkillBindingService extends IService<AgentSkillBinding> {
/**
* 原子替换 Agent 的全部 Skill 绑定。
*
* @param agentId Agent ID
* @param bindings Skill 引用列表
* @return 规范化后的脱敏绑定摘要
*/
List<AgentSkillBinding> replaceBindings(BigInteger agentId, List<AgentSkillBinding> bindings);
/**
* 查询 Agent 的 Skill 草稿绑定。
*
* @param agentId Agent ID
* @return 稳定排序的绑定
*/
List<AgentSkillBinding> listBindings(BigInteger agentId);
/**
* 查询 Agent 的 Skill 脱敏绑定摘要。
*
* @param agentId Agent ID
* @return 稳定排序的绑定摘要
*/
List<AgentSkillBinding> listSummaries(BigInteger agentId);
}

View File

@@ -0,0 +1,142 @@
package tech.easyflow.agent.service.impl;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 比较 Agent 绑定的可持久化业务字段,忽略 ID、审计字段与展示摘要。
*/
final class AgentBindingSemanticComparator {
private static final String DEFAULT_RETRIEVAL_MODE = "HYBRID";
private AgentBindingSemanticComparator() {
}
/**
* 判断工具绑定整组替换是否会产生业务变化。
*
* @param current 当前持久化绑定
* @param requested 客户端请求绑定
* @return 业务字段完全一致时返回 {@code true}
*/
static boolean sameTools(List<AgentToolBinding> current, List<AgentToolBinding> requested) {
List<AgentToolBinding> left = safeList(current);
List<AgentToolBinding> right = safeList(requested);
if (left.size() != right.size()) {
return false;
}
for (int index = 0; index < left.size(); index++) {
AgentToolBinding persisted = left.get(index);
AgentToolBinding incoming = right.get(index);
if (persisted == null || incoming == null
|| !Objects.equals(normalizeToolType(persisted.getToolType()), normalizeToolType(incoming.getToolType()))
|| !Objects.equals(persisted.getTargetId(), incoming.getTargetId())
|| !Objects.equals(text(persisted.getToolName()), text(incoming.getToolName()))
|| !Objects.equals(enabled(persisted.getEnabled()), enabled(incoming.getEnabled()))
|| !Objects.equals(Boolean.TRUE.equals(persisted.getHitlEnabled()),
Boolean.TRUE.equals(incoming.getHitlEnabled()))
|| !Objects.equals(map(persisted.getHitlConfigJson()), map(incoming.getHitlConfigJson()))
|| !Objects.equals(map(persisted.getOptionsJson()), map(incoming.getOptionsJson()))
|| !Objects.equals(persisted.getSortNo(), sortNo(incoming.getSortNo(), index))) {
return false;
}
}
return true;
}
/**
* 判断知识库绑定整组替换是否会产生业务变化。
*
* @param current 当前持久化绑定
* @param requested 客户端请求绑定
* @return 业务字段完全一致时返回 {@code true}
*/
static boolean sameKnowledges(List<AgentKnowledgeBinding> current,
List<AgentKnowledgeBinding> requested) {
List<AgentKnowledgeBinding> left = safeList(current);
List<AgentKnowledgeBinding> right = safeList(requested);
if (left.size() != right.size()) {
return false;
}
for (int index = 0; index < left.size(); index++) {
AgentKnowledgeBinding persisted = left.get(index);
AgentKnowledgeBinding incoming = right.get(index);
if (persisted == null || incoming == null
|| !Objects.equals(persisted.getKnowledgeId(), incoming.getKnowledgeId())
|| !Objects.equals(retrievalMode(persisted.getRetrievalMode()),
retrievalMode(incoming.getRetrievalMode()))
|| !Objects.equals(enabled(persisted.getEnabled()), enabled(incoming.getEnabled()))
|| !Objects.equals(map(persisted.getOptionsJson()), map(incoming.getOptionsJson()))
|| !Objects.equals(persisted.getSortNo(), sortNo(incoming.getSortNo(), index))) {
return false;
}
}
return true;
}
/**
* 判断 Skill 绑定顺序是否发生变化。
*
* @param current 当前持久化绑定
* @param requested 客户端请求绑定
* @return Skill ID 与稳定顺序完全一致时返回 {@code true}
*/
static boolean sameSkills(List<AgentSkillBinding> current, List<AgentSkillBinding> requested) {
List<AgentSkillBinding> left = safeList(current);
List<AgentSkillBinding> right = safeList(requested);
if (left.size() != right.size()) {
return false;
}
for (int index = 0; index < left.size(); index++) {
AgentSkillBinding persisted = left.get(index);
AgentSkillBinding incoming = right.get(index);
if (persisted == null || incoming == null
|| !Objects.equals(persisted.getSkillId(), incoming.getSkillId())
|| !Objects.equals(persisted.getSortNo(), index)) {
return false;
}
}
return true;
}
private static String normalizeToolType(String value) {
try {
return AgentToolType.from(value).name();
} catch (RuntimeException ignored) {
return value;
}
}
private static String retrievalMode(String value) {
return value == null || value.isBlank()
? DEFAULT_RETRIEVAL_MODE : value.trim().toUpperCase(java.util.Locale.ROOT);
}
private static String text(String value) {
return value == null ? "" : value;
}
private static Boolean enabled(Boolean value) {
return value == null || value;
}
private static Integer sortNo(Integer value, int index) {
return value == null ? index : value;
}
private static Map<String, Object> map(Map<String, Object> value) {
return value == null ? Collections.emptyMap() : value;
}
private static <T> List<T> safeList(List<T> value) {
return value == null ? Collections.emptyList() : value;
}
}

View File

@@ -57,6 +57,10 @@ public class AgentKnowledgeBindingServiceImpl extends ServiceImpl<AgentKnowledge
Agent agent = requireAgentForUpdate(agentId);
resourceAccessService.assertAccess(
CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
List<AgentKnowledgeBinding> current = listAll(agentId);
if (AgentBindingSemanticComparator.sameKnowledges(current, bindings)) {
return enabledBindings(current);
}
validateBindings(agent, bindings);
remove(QueryWrapper.create().where("agent_id = ?", agentId));
if (bindings == null || bindings.isEmpty()) {
@@ -66,7 +70,7 @@ public class AgentKnowledgeBindingServiceImpl extends ServiceImpl<AgentKnowledge
applyBindingDefaults(agent, bindings.get(i), i);
}
saveBatch(bindings);
return listEnabled(agentId);
return enabledBindings(bindings);
});
}
@@ -81,6 +85,30 @@ public class AgentKnowledgeBindingServiceImpl extends ServiceImpl<AgentKnowledge
.orderBy("sort_no asc, id asc"));
}
/**
* 查询 Agent 的全部知识库绑定,用于整组语义比较。
*
* @param agentId Agent ID
* @return 稳定排序的全部绑定
*/
private List<AgentKnowledgeBinding> listAll(BigInteger agentId) {
return list(QueryWrapper.create()
.where("agent_id = ?", agentId)
.orderBy("sort_no asc, id asc"));
}
/**
* 从已加载或已写入的绑定中筛选启用项,避免替换后再次查询。
*
* @param bindings 知识库绑定
* @return 启用绑定
*/
private List<AgentKnowledgeBinding> enabledBindings(List<AgentKnowledgeBinding> bindings) {
return bindings.stream()
.filter(binding -> binding != null && binding.getEnabled() != Boolean.FALSE)
.toList();
}
/**
* 锁定并加载待修改的 Agent。
*

View File

@@ -255,7 +255,29 @@ public class AgentResourceBindingProviderImpl implements AgentResourceBindingPro
AgentToolType toolType,
BigInteger resourceId) {
return snapshotListContains(snapshot, "toolBindings", resourceId, toolType.name())
|| snapshotListContains(snapshot, "toolSummaries", resourceId, toolType.name());
|| snapshotListContains(snapshot, "toolSummaries", resourceId, toolType.name())
|| nestedSkillBindingsContain(snapshot, toolType, resourceId);
}
private boolean nestedSkillBindingsContain(Map<String, Object> snapshot,
AgentToolType toolType,
BigInteger resourceId) {
Object rawBindings = snapshot == null ? null : snapshot.get("skillBindings");
if (!(rawBindings instanceof List<?> bindings)) {
return false;
}
for (Object raw : bindings) {
if (!(raw instanceof Map<?, ?> binding)
|| !(binding.get("resourceSnapshot") instanceof Map<?, ?> resourceSnapshot)) {
continue;
}
Object rawTools = resourceSnapshot.get("toolBindings");
if (rawTools instanceof List<?> tools
&& tools.stream().anyMatch(item -> matchesResourceBinding(item, resourceId, toolType.name()))) {
return true;
}
}
return false;
}
/**

View File

@@ -7,18 +7,26 @@ import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.config.AgentInteractionConfigSupport;
import tech.easyflow.agent.config.AgentBuiltinToolsConfigResolver;
import tech.easyflow.agent.config.AgentBuiltinToolsConfig;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.runtime.AgentRuntimeCompiler;
import tech.easyflow.agent.service.AgentDependencyAccessService;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.agent.service.AgentSkillBindingService;
import tech.easyflow.agent.runtime.skill.AgentSkillRuntimeProjector;
import tech.easyflow.agent.support.AgentBindingLockExecutor;
import tech.easyflow.ai.entity.*;
import tech.easyflow.ai.easyagentsflow.repository.AgentWorkflowSnapshotFactory;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.mcp.McpConnectionSnapshotFactory;
import tech.easyflow.ai.plugin.PluginConnectionSnapshotFactory;
import tech.easyflow.ai.service.*;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
@@ -26,7 +34,9 @@ import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.entity.SysLog;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.system.service.SysLogService;
import javax.annotation.Resource;
import java.math.BigInteger;
@@ -43,12 +53,15 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
private static final TypeReference<List<AgentToolBinding>> TOOL_BINDING_LIST_TYPE = new TypeReference<>() {};
private static final TypeReference<List<AgentKnowledgeBinding>> KNOWLEDGE_BINDING_LIST_TYPE = new TypeReference<>() {};
private static final TypeReference<List<AgentSkillBinding>> SKILL_BINDING_LIST_TYPE = new TypeReference<>() {};
@Resource
private AgentToolBindingService agentToolBindingService;
@Resource
private AgentKnowledgeBindingService agentKnowledgeBindingService;
@Resource
private AgentSkillBindingService agentSkillBindingService;
@Resource
private ModelService modelService;
@Resource
private WorkflowService workflowService;
@@ -57,6 +70,10 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
@Resource
private McpService mcpService;
@Resource
private McpConnectionSnapshotFactory mcpConnectionSnapshotFactory;
@Resource
private PluginConnectionSnapshotFactory pluginConnectionSnapshotFactory;
@Resource
private DocumentCollectionService documentCollectionService;
@Resource
private ResourceAccessService resourceAccessService;
@@ -68,6 +85,14 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
private AgentBindingLockExecutor agentBindingLockExecutor;
@Resource
private AgentRuntimeCompiler agentRuntimeCompiler;
@Resource
private AgentSkillRuntimeProjector agentSkillRuntimeProjector;
@Resource
private AgentWorkflowSnapshotFactory agentWorkflowSnapshotFactory;
@Resource
private AgentBuiltinToolsConfigResolver agentBuiltinToolsConfigResolver;
@Resource
private SysLogService sysLogService;
/**
* {@inheritDoc}
@@ -76,8 +101,11 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
public Agent getDetail(BigInteger id) {
Agent agent = requireAgent(id);
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.READ, "无权限查看该 Agent");
agent.setExecutionConfigJson(
agentBuiltinToolsConfigResolver.normalizeForDraftRead(agent.getExecutionConfigJson()));
agent.setToolBindings(agentToolBindingService.listEnabled(id));
agent.setKnowledgeBindings(agentKnowledgeBindingService.listEnabled(id));
agent.setSkillBindings(agentSkillBindingService.listSummaries(id));
return agent;
}
@@ -88,9 +116,15 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
@Transactional(rollbackFor = Exception.class)
public Agent saveDraft(Agent agent) {
applyDraftDefaults(agent);
validateDraft(agent);
validateDraft(agent, null);
boolean shellApprovalDisabled = agentBuiltinToolsConfigResolver
.isShellApprovalDisableTransition(agent.getExecutionConfigJson(), null);
save(agent);
return getDetail(agent.getId());
if (shellApprovalDisabled) {
recordShellApprovalDisabled(agent.getId(), "saveDraft",
AgentBuiltinToolsConfig.newAgentDefaults().shell());
}
return agent;
}
/**
@@ -107,13 +141,48 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
resourceAccessService.assertAccess(
CategoryResourceType.AGENT, existing, ResourceAction.MANAGE, "无权限管理该 Agent");
agent.setTenantId(existing.getTenantId());
validateDraft(agent);
Map<String, Object> existingExecutionConfig = existing.getExecutionConfigJson();
validateDraft(agent, existingExecutionConfig);
boolean shellApprovalDisabled = agentBuiltinToolsConfigResolver
.isShellApprovalDisableTransition(agent.getExecutionConfigJson(), existingExecutionConfig);
AgentBuiltinToolsConfig.ToolSwitch previousShell = agentBuiltinToolsConfigResolver
.resolveDraftRuntime(existingExecutionConfig).shell();
applyDraftUpdate(existing, agent);
updateById(existing);
return getDetail(existing.getId());
if (shellApprovalDisabled) {
recordShellApprovalDisabled(existing.getId(), "updateDraft", previousShell);
}
return existing;
});
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Agent saveDraftGraph(Agent agent,
List<AgentToolBinding> toolBindings,
boolean replaceToolBindings,
List<AgentKnowledgeBinding> knowledgeBindings,
boolean replaceKnowledgeBindings,
List<AgentSkillBinding> skillBindings,
boolean replaceSkillBindings) {
Agent saved = agent != null && agent.getId() != null
? updateDraft(agent) : saveDraft(agent);
BigInteger agentId = saved.getId();
if (replaceToolBindings) {
saved.setToolBindings(agentToolBindingService.replaceBindings(agentId, toolBindings));
}
if (replaceKnowledgeBindings) {
saved.setKnowledgeBindings(agentKnowledgeBindingService.replaceBindings(agentId, knowledgeBindings));
}
if (replaceSkillBindings) {
saved.setSkillBindings(agentSkillBindingService.replaceBindings(agentId, skillBindings));
}
return saved;
}
/**
* {@inheritDoc}
*/
@@ -174,7 +243,10 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
CategoryResourceType.AGENT, detail, ResourceAction.MANAGE, "无权限管理该 Agent");
detail.setToolBindings(agentToolBindingService.listEnabled(agentId));
detail.setKnowledgeBindings(agentKnowledgeBindingService.listEnabled(agentId));
validateDraft(detail);
detail.setSkillBindings(agentSkillBindingService.listBindings(agentId));
validateDraft(detail, detail.getExecutionConfigJson());
List<AgentSkillBinding> projectedSkillBindings =
agentSkillRuntimeProjector.projectCurrentBindings(detail, detail.getSkillBindings());
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("id", detail.getId());
snapshot.put("tenantId", detail.getTenantId());
@@ -194,12 +266,15 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
snapshot.put("visibilityScope", detail.getVisibilityScope());
snapshot.put("toolBindings", snapshotToolBindings(detail, detail.getToolBindings()));
snapshot.put("knowledgeBindings", snapshotKnowledgeBindings(detail, detail.getKnowledgeBindings()));
snapshot.put("skillBindings", projectedSkillBindings);
snapshot.put("basicSummary", basicSummary(detail));
snapshot.put("modelSummary", modelSummary(detail.getModelId()));
snapshot.put("parameterSummary", parameterSummary(detail));
snapshot.put("promptSummary", promptSummary(detail));
snapshot.put("toolSummaries", toolSummaries(detail.getToolBindings()));
snapshot.put("knowledgeSummaries", knowledgeSummaries(detail.getKnowledgeBindings()));
snapshot.put("skillSummaries", projectedSkillBindings.stream()
.map(AgentSkillBinding::getResourceSummary).toList());
snapshot.put("snapshotAt", new Date());
// 发布前完整编译一次,提前暴露工具运行名冲突和运行定义错误。
agentRuntimeCompiler.compile(fromSnapshot(snapshot));
@@ -222,10 +297,14 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
agent.setModelId(toBigInteger(snapshot.get("modelId")));
agent.setCategoryId(toBigInteger(snapshot.get("categoryId")));
agent.setPublishStatus(PublishStatus.PUBLISHED.getCode());
agent.setExecutionConfigJson(
agentBuiltinToolsConfigResolver.normalizeForPublishedRuntime(agent.getExecutionConfigJson()));
agent.setInteractionConfigJson(AgentInteractionConfigSupport.normalize(agent.getInteractionConfigJson()));
agent.setPublishedSnapshotJson(snapshot);
agent.setToolBindings(objectMapper.convertValue(snapshot.get("toolBindings"), TOOL_BINDING_LIST_TYPE));
agent.setKnowledgeBindings(objectMapper.convertValue(snapshot.get("knowledgeBindings"), KNOWLEDGE_BINDING_LIST_TYPE));
agent.setSkillBindings(snapshot.get("skillBindings") == null ? List.of()
: objectMapper.convertValue(snapshot.get("skillBindings"), SKILL_BINDING_LIST_TYPE));
return agent;
}
@@ -253,7 +332,7 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
return agent;
}
private void validateDraft(Agent agent) {
private void validateDraft(Agent agent, Map<String, Object> existingExecutionConfig) {
if (agent == null) {
throw new BusinessException("Agent 不能为空");
}
@@ -264,7 +343,9 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
agentDependencyAccessService.validateCategory(agent);
agent.setVisibilityScope(VisibilityScope.fromOrDefault(agent.getVisibilityScope(), VisibilityScope.PRIVATE).name());
agent.setInteractionConfigJson(AgentInteractionConfigSupport.normalize(agent.getInteractionConfigJson()));
agent.setExecutionConfigJson(normalizeExecutionConfig(agent.getExecutionConfigJson()));
Map<String, Object> executionConfig = normalizeExecutionConfig(agent.getExecutionConfigJson());
agent.setExecutionConfigJson(agentBuiltinToolsConfigResolver.normalizeForDraftSave(
executionConfig, existingExecutionConfig, requireCurrentLoginAccount()));
}
/**
@@ -356,6 +437,33 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
existing.setModifiedBy(account.getId());
}
/**
* 持久化 Shell 审批关闭这一高风险配置变更的专用审计记录。
*
* @param agentId Agent ID
* @param actionMethod 触发变更的服务方法
* @param previousShell 变更前 Shell 配置
*/
private void recordShellApprovalDisabled(BigInteger agentId,
String actionMethod,
AgentBuiltinToolsConfig.ToolSwitch previousShell) {
LoginAccount account = requireCurrentLoginAccount();
SysLog log = new SysLog();
log.setAccountId(account.getId());
log.setActionName("关闭 Agent Shell 调用审批");
log.setActionType("SECURITY_CONFIG_CHANGE");
log.setActionClass(AgentServiceImpl.class.getName());
log.setActionMethod(actionMethod);
log.setActionUrl("/api/v1/agent/" + ("saveDraft".equals(actionMethod) ? "save" : "update"));
log.setActionBody("{\"agentId\":\"" + agentId
+ "\",\"setting\":\"shell\",\"before\":{\"enabled\":"
+ previousShell.enabled() + ",\"approvalRequired\":" + previousShell.approvalRequired()
+ "},\"after\":{\"enabled\":true,\"approvalRequired\":false}}");
log.setStatus(1);
log.setCreated(new Date());
sysLogService.save(log);
}
private Map<String, Object> modelSummary(BigInteger modelId) {
Model model = modelService.getModelInstance(modelId);
Map<String, Object> summary = new LinkedHashMap<>();
@@ -432,14 +540,19 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
private Map<String, Object> toolResourceSnapshot(Agent agent, AgentToolBinding binding) {
if ("WORKFLOW".equalsIgnoreCase(binding.getToolType())) {
Workflow workflow = agentDependencyAccessService.requireWorkflow(agent, binding.getTargetId());
return objectMapper.convertValue(workflow, new TypeReference<Map<String, Object>>() {});
return agentWorkflowSnapshotFactory.snapshot(workflow);
}
if ("PLUGIN".equalsIgnoreCase(binding.getToolType())) {
PluginItem pluginItem = agentDependencyAccessService.requirePluginItem(agent, binding.getTargetId());
return objectMapper.convertValue(pluginItem, new TypeReference<Map<String, Object>>() {});
AgentDependencyAccessService.PluginResource resource =
agentDependencyAccessService.requirePluginResource(agent, binding.getTargetId());
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("pluginItem", objectMapper.convertValue(
resource.pluginItem(), new TypeReference<Map<String, Object>>() {}));
snapshot.put("plugin", pluginConnectionSnapshotFactory.snapshot(resource.plugin()));
return snapshot;
}
Mcp mcp = agentDependencyAccessService.requireMcp(agent, binding.getTargetId());
return objectMapper.convertValue(mcp, new TypeReference<Map<String, Object>>() {});
return mcpConnectionSnapshotFactory.snapshot(mcp);
}
private List<AgentKnowledgeBinding> snapshotKnowledgeBindings(

View File

@@ -0,0 +1,233 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.mapper.AgentSkillBindingMapper;
import tech.easyflow.agent.runtime.skill.AgentSkillRuntimeProjector;
import tech.easyflow.agent.service.AgentSkillBindingService;
import tech.easyflow.agent.support.AgentBindingLockExecutor;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Agent Skill 绑定服务实现。
*/
@Service
public class AgentSkillBindingServiceImpl
extends ServiceImpl<AgentSkillBindingMapper, AgentSkillBinding>
implements AgentSkillBindingService {
private final AgentMapper agentMapper;
private final AgentBindingLockExecutor bindingLockExecutor;
private final AgentSkillRuntimeProjector runtimeProjector;
private final SkillService skillService;
private final ResourceAccessService resourceAccessService;
/**
* 创建 Agent Skill 绑定服务。
*
* @param agentMapper Agent Mapper
* @param bindingLockExecutor Agent 绑定锁执行器
* @param runtimeProjector Skill 运行投影器
* @param skillService Skill 服务
* @param resourceAccessService 资源权限服务
*/
public AgentSkillBindingServiceImpl(AgentMapper agentMapper,
AgentBindingLockExecutor bindingLockExecutor,
AgentSkillRuntimeProjector runtimeProjector,
SkillService skillService,
ResourceAccessService resourceAccessService) {
this.agentMapper = agentMapper;
this.bindingLockExecutor = bindingLockExecutor;
this.runtimeProjector = runtimeProjector;
this.skillService = skillService;
this.resourceAccessService = resourceAccessService;
}
/** {@inheritDoc} */
@Override
@Transactional(rollbackFor = Exception.class)
public List<AgentSkillBinding> replaceBindings(BigInteger agentId,
List<AgentSkillBinding> bindings) {
return bindingLockExecutor.execute(agentId, () -> {
Agent agent = requireAgentForUpdate(agentId);
resourceAccessService.assertAccess(
CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
List<AgentSkillBinding> current = listBindings(agentId);
if (AgentBindingSemanticComparator.sameSkills(current, bindings)) {
return listSummaries(agentId);
}
List<AgentSkillBinding> normalized = normalize(agent, bindings);
// 在删除旧绑定前完成权限、包完整性、Tool 与 8 MiB 预算校验,失败时保留旧组。
List<AgentSkillBinding> projected = runtimeProjector.projectCurrentBindings(agent, normalized);
Map<BigInteger, Map<String, Object>> summaries = new HashMap<>();
for (AgentSkillBinding binding : projected) {
summaries.put(binding.getSkillId(), binding.getResourceSummary());
}
normalized.forEach(binding -> binding.setResourceSummary(summaries.get(binding.getSkillId())));
remove(QueryWrapper.create()
.eq(AgentSkillBinding::getTenantId, agent.getTenantId())
.eq(AgentSkillBinding::getAgentId, agentId));
if (!normalized.isEmpty()) {
saveBatch(normalized);
}
return normalized;
});
}
/** {@inheritDoc} */
@Override
public List<AgentSkillBinding> listBindings(BigInteger agentId) {
if (agentId == null) {
return Collections.emptyList();
}
return list(QueryWrapper.create()
.eq(AgentSkillBinding::getAgentId, agentId)
.orderBy(AgentSkillBinding::getSortNo, true)
.orderBy(AgentSkillBinding::getId, true));
}
/** {@inheritDoc} */
@Override
public List<AgentSkillBinding> listSummaries(BigInteger agentId) {
List<AgentSkillBinding> bindings = listBindings(agentId);
Agent agent = agentMapper.selectOneById(agentId);
Map<BigInteger, String> publishedHashes = publishedRuntimeHashes(agent);
for (AgentSkillBinding binding : bindings) {
Skill skill = skillService.getById(binding.getSkillId());
if (skill == null) {
binding.setResourceSummary(Map.of(
"skillId", binding.getSkillId(),
"displayName", "已失效技能",
"available", false));
continue;
}
binding.setResourceSummary(runtimeProjector.currentSummary(
skill, publishedHashes.get(binding.getSkillId())));
binding.getResourceSummary().put("available", true);
}
return bindings;
}
/**
* 规范客户端绑定并写入服务端归属、排序和审计字段。
*
* @param agent Agent
* @param bindings 客户端绑定
* @return 规范绑定
*/
private List<AgentSkillBinding> normalize(Agent agent, List<AgentSkillBinding> bindings) {
if (bindings == null || bindings.isEmpty()) {
return List.of();
}
if (bindings.size() > AgentSkillRuntimeProjector.MAX_SKILL_COUNT) {
throw new BusinessException(409, 4092, "单个 Agent 最多可绑定 20 个 Skill");
}
Set<BigInteger> unique = new HashSet<>();
List<AgentSkillBinding> result = new ArrayList<>();
LoginAccount account = requireAccount();
Date now = new Date();
for (int index = 0; index < bindings.size(); index++) {
AgentSkillBinding source = bindings.get(index);
if (source == null || source.getSkillId() == null) {
throw new BusinessException("Agent Skill 绑定参数不完整");
}
if (!unique.add(source.getSkillId())) {
throw new BusinessException(409, 4092, "同一 Skill 不能重复绑定");
}
AgentSkillBinding binding = new AgentSkillBinding();
binding.setTenantId(agent.getTenantId());
binding.setAgentId(agent.getId());
binding.setSkillId(source.getSkillId());
binding.setSortNo(index);
binding.setCreated(now);
binding.setCreatedBy(account.getId());
binding.setModified(now);
binding.setModifiedBy(account.getId());
result.add(binding);
}
return result;
}
/**
* 查询并锁定 Agent。
*
* @param agentId Agent ID
* @return Agent
*/
private Agent requireAgentForUpdate(BigInteger agentId) {
if (agentId == null) {
throw new BusinessException("Agent ID 不能为空");
}
Agent agent = agentMapper.selectOneByQuery(QueryWrapper.create()
.eq(Agent::getId, agentId)
.forUpdate());
if (agent == null) {
throw new BusinessException(404, 404, "Agent 不存在");
}
return agent;
}
/**
* 获取 Agent 当前线上冻结的 Skill 组合 hash。
*
* @param agentId Agent ID
* @param skillId Skill ID
* @return 组合 hash 或 null
*/
private Map<BigInteger, String> publishedRuntimeHashes(Agent agent) {
Map<BigInteger, String> hashes = new HashMap<>();
Map<String, Object> snapshot = agent == null ? null : agent.getPublishedSnapshotJson();
Object rawBindings = snapshot == null ? null : snapshot.get("skillBindings");
if (!(rawBindings instanceof List<?> items)) {
return hashes;
}
for (Object raw : items) {
if (!(raw instanceof Map<?, ?> item) || item.get("skillId") == null) {
continue;
}
Object resource = item.get("resourceSnapshot");
if (resource instanceof Map<?, ?> resourceMap) {
Object hash = resourceMap.get("skillRuntimeSnapshotHash");
if (hash != null) {
hashes.put(new BigInteger(String.valueOf(item.get("skillId"))), String.valueOf(hash));
}
}
}
return hashes;
}
/**
* 获取当前登录账号。
*
* @return 登录账号
*/
private LoginAccount requireAccount() {
LoginAccount account = SaTokenUtil.getLoginAccount();
if (account == null || account.getId() == null) {
throw new BusinessException(401, 401, "当前登录状态失效,请重新登录后再试");
}
return account;
}
}

View File

@@ -0,0 +1,69 @@
package tech.easyflow.agent.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentSkillBindingService;
import tech.easyflow.skill.service.SkillReferenceProvider;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Agent 草稿和有效发布快照中的 Skill 引用提供者。
*/
@Component
public class AgentSkillReferenceProvider implements SkillReferenceProvider {
private final AgentService agentService;
private final AgentSkillBindingService bindingService;
/**
* 创建引用提供者。
*
* @param agentService Agent 服务
* @param bindingService Agent Skill 绑定服务
*/
public AgentSkillReferenceProvider(AgentService agentService,
AgentSkillBindingService bindingService) {
this.agentService = agentService;
this.bindingService = bindingService;
}
/** {@inheritDoc} */
@Override
public List<String> listReferences(BigInteger skillId) {
Set<BigInteger> ids = new LinkedHashSet<>();
for (AgentSkillBinding binding : bindingService.list(QueryWrapper.create()
.eq(AgentSkillBinding::getSkillId, skillId))) {
ids.add(binding.getAgentId());
}
for (Agent agent : agentService.list(QueryWrapper.create()
.select(Agent::getId, Agent::getPublishedSnapshotJson)
.isNotNull(Agent::getPublishedSnapshotJson))) {
if (containsSkill(agent.getPublishedSnapshotJson(), skillId)) {
ids.add(agent.getId());
}
}
List<String> result = new ArrayList<>();
for (Agent agent : agentService.listByIds(ids)) {
result.add("智能体“" + (agent.getName() == null ? "未命名智能体" : agent.getName()) + "");
}
return result;
}
private boolean containsSkill(Map<String, Object> snapshot, BigInteger skillId) {
Object raw = snapshot == null ? null : snapshot.get("skillBindings");
if (!(raw instanceof List<?> bindings)) {
return false;
}
return bindings.stream().anyMatch(item -> item instanceof Map<?, ?> binding
&& skillId.toString().equals(String.valueOf(binding.get("skillId"))));
}
}

View File

@@ -55,6 +55,10 @@ public class AgentToolBindingServiceImpl extends ServiceImpl<AgentToolBindingMap
Agent agent = requireAgentForUpdate(agentId);
resourceAccessService.assertAccess(
CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
List<AgentToolBinding> current = listAll(agentId);
if (AgentBindingSemanticComparator.sameTools(current, bindings)) {
return enabledBindings(current);
}
validateBindings(agent, bindings);
remove(QueryWrapper.create().where("agent_id = ?", agentId));
if (bindings == null || bindings.isEmpty()) {
@@ -64,7 +68,7 @@ public class AgentToolBindingServiceImpl extends ServiceImpl<AgentToolBindingMap
applyBindingDefaults(agent, bindings.get(i), i);
}
saveBatch(bindings);
return listEnabled(agentId);
return enabledBindings(bindings);
});
}
@@ -79,6 +83,30 @@ public class AgentToolBindingServiceImpl extends ServiceImpl<AgentToolBindingMap
.orderBy("sort_no asc, id asc"));
}
/**
* 查询 Agent 的全部工具绑定,用于整组语义比较。
*
* @param agentId Agent ID
* @return 稳定排序的全部绑定
*/
private List<AgentToolBinding> listAll(BigInteger agentId) {
return list(QueryWrapper.create()
.where("agent_id = ?", agentId)
.orderBy("sort_no asc, id asc"));
}
/**
* 从已加载或已写入的绑定中筛选启用项,避免替换后再次查询。
*
* @param bindings 工具绑定
* @return 启用绑定
*/
private List<AgentToolBinding> enabledBindings(List<AgentToolBinding> bindings) {
return bindings.stream()
.filter(binding -> binding != null && binding.getEnabled() != Boolean.FALSE)
.toList();
}
/**
* 锁定并加载待修改的 Agent。
*