feat(canvas): 云端 Agent - 完成策略分层、节点能力注册与可解释审批改造

This commit is contained in:
ddCat
2026-09-14 17:58:21 +08:00
parent 4b0e9d8965
commit 01486872c4
65 changed files with 5347 additions and 918 deletions

View File

@@ -21,7 +21,82 @@ func RegisterAgentRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
ok(c, gin.H{"version": 2, "permissionModes": []string{"read_only", "request_approval", "auto"}, "contextScopes": []string{"canvas"}, "skills": true, "writeTools": true, "billing": "fixed_request", "maxHistoryPairs": 8, "maxSteps": 8, "tools": []string{"canvas_list_node_types", "canvas_get_state", "skill_read_file", "task_get", "model_list", "canvas_apply_ops", "generate_media"}})
capabilities := service.CloudAgentCapabilitySetInfo()
ok(c, gin.H{"version": 2, "permissionModes": []string{"read_only", "request_approval", "auto"}, "contextScopes": []string{"canvas"}, "skills": true, "writeTools": true, "billing": "fixed_request", "maxHistoryPairs": 8, "maxSteps": 8, "tools": service.CloudAgentSupportedToolNames(), "capabilitySetVersion": capabilities.Version, "capabilitySetHash": capabilities.Hash, "nodeTypes": capabilities.Nodes})
})
// Profiles are durable preference data, not an authorization surface. The
// service validates scope ownership and the compiler injects the effective
// layers only after the code-level policy has been fixed.
r.GET("/agent/profile", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
scope := strings.TrimSpace(c.Query("scope"))
projectID := strings.TrimSpace(c.Query("projectId"))
canvasID := strings.TrimSpace(c.Query("canvasId"))
if scope == "" {
switch {
case canvasID != "":
scope = "canvas"
case projectID != "":
scope = "project"
default:
scope = "user"
}
}
switch scope {
case "user":
if projectID != "" || canvasID != "" {
fail(c, http.StatusBadRequest, errors.New("用户偏好不能带项目或画布 ID"))
return
}
case "project":
if projectID == "" || canvasID != "" {
fail(c, http.StatusBadRequest, errors.New("项目偏好需要 projectId,且不能带 canvasId"))
return
}
case "canvas":
if canvasID == "" {
fail(c, http.StatusBadRequest, errors.New("画布偏好需要 canvasId"))
return
}
default:
fail(c, http.StatusBadRequest, errors.New("无效的 Agent 偏好作用域"))
return
}
view, err := svc.CloudAgentProfileForScope(user.ID, projectID, canvasID)
if err != nil {
failService(c, err)
return
}
ok(c, view)
})
r.PATCH("/agent/profile", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 64<<10)
var req service.AgentProfileRequest
decoder := json.NewDecoder(c.Request.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&req); err != nil {
fail(c, http.StatusBadRequest, err)
return
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
fail(c, http.StatusBadRequest, errors.New("请求必须只包含一个 JSON 对象"))
return
}
view, err := svc.UpdateCloudAgentProfile(user.ID, req)
if err != nil {
failService(c, err)
return
}
ok(c, view)
})
create := func(c *gin.Context) {
user, err := currentUser(c, svc)