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)

View File

@@ -29,7 +29,44 @@ paths:
summary: 读取云端 Agent 能力
responses:
"200":
description: 信封 data 包含 version、permissionModes、contextScopes、skills、writeTools、billing、maxHistoryPairs
description: 信封 data 包含 version、permissionModes、contextScopes、skills、writeTools、billing、maxHistoryPairs、capabilitySetVersion、capabilitySetHash、nodeTypes、tools
/agent/profile:
get:
tags: [agent]
summary: 读取生效的用户、项目、画布 Agent 偏好层
parameters:
- in: query
name: scope
schema:
type: string
enum: [user, project, canvas]
- in: query
name: projectId
schema:
type: string
- in: query
name: canvasId
schema:
type: string
responses:
"200":
description: 信封 data 为合并 revision、hash 和按 user/project/canvas 顺序排列的 layers;每层含独立数字 revision
"403":
description: 项目或画布不属于当前用户
patch:
tags: [agent]
summary: 使用单层 revision CAS 更新 Agent 偏好
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentProfileRequest"
responses:
"200":
description: 返回更新后的生效偏好视图
"409":
description: 偏好 revision 已变化,须重新读取后保存
/agent/runs:
post:
tags: [agent]
@@ -314,6 +351,25 @@ components:
in: cookie
name: open_ai_canvas_session
schemas:
AgentProfileRequest:
type: object
additionalProperties: false
required: [scope, content, revision]
properties:
scope:
type: string
enum: [user, project, canvas]
projectId:
type: string
canvasId:
type: string
content:
type: string
description: 常驻偏好文档,最多 12000 个 Unicode 字符;不能授权节点、工具、预算或绕过审批
revision:
type: integer
minimum: 0
description: 待更新层的数字 revision;首次创建为 0
CloudAgentRequest:
type: object
additionalProperties: false
@@ -336,6 +392,13 @@ components:
permissionMode:
type: string
enum: [read_only, request_approval, auto]
reasoningMode:
type: string
enum: [off, auto, deep]
description: 本轮推理策略;供应商是否支持由后端校验
profileRevision:
type: string
description: GET /agent/profile 的生效合并 revision;变化时拒绝创建新轮次
skillIds:
type: array
maxItems: 8