fix(agent): 画布助手 - 避免未支持节点阻断整张画布

This commit is contained in:
ddCat
2026-09-15 09:27:00 +08:00
parent 18d4bcbca6
commit 40aca20f4d
5 changed files with 63 additions and 4 deletions

View File

@@ -462,10 +462,13 @@ func cloudAgentCanvasSummary(canvas *model.CanvasProject) (string, error) {
break
}
descriptor, known := cloudAgentNodeCapabilityForType(node.Type)
if !known {
return "", BadAuthRequest("画布包含当前 Agent 不支持的节点类型")
}
item := map[string]any{"id": truncateRunes(node.ID, 100), "type": truncateRunes(node.Type, 40), "title": truncateRunes(node.Title, 300)}
if !known {
item["agentSupported"] = false
item["agentUnsupportedReason"] = "仅展示基础信息;当前 Agent 不支持操作此类型节点"
nodes = append(nodes, item)
continue
}
projected, err := cloudAgentProjectNodeFields(map[string]any{"title": node.Title}, node.Metadata, descriptor, descriptor.SummaryFields, 600, false, 0)
if err != nil {
return "", err

View File

@@ -86,7 +86,12 @@ func cloudAgentCanvasState(repo *repository.Repository, userID string, doc map[s
}
capability, known := cloudAgentNodeCapabilityForType(stringValue(node["type"]))
if !known {
return nil, BadAuthRequest("画布包含当前 Agent 不支持的节点类型")
// Read visibility is not permission to mutate or use a node as a media reference.
item["agentSupported"] = false
item["agentUnsupportedReason"] = "仅展示基础信息;当前 Agent 不支持操作此类型节点"
nodes = append(nodes, item)
included[id] = true
continue
}
fields := capability.SummaryFields
if len(ids) > 0 {

View File

@@ -1,13 +1,58 @@
package app
import (
"encoding/json"
"reflect"
"strings"
"testing"
"infinite-canvas/backend/internal/canvas/capability"
"infinite-canvas/backend/internal/model"
)
func TestCloudAgentMixedCanvasReadsUnsupportedNodesWithoutGrantingCapabilities(t *testing.T) {
nodes := []map[string]any{{"id": "text", "type": "text", "metadata": map[string]any{"content": "readable"}}}
for _, kind := range []string{"ai-art-critique", "config", "drawing", "future-plugin"} {
nodes = append(nodes, map[string]any{"id": kind, "type": kind, "title": "插件节点", "position": map[string]any{"x": 10.0, "y": 20.0}, "metadata": map[string]any{"content": "PRIVATE_SENTINEL", "apiKey": "PRIVATE_SENTINEL"}})
if _, supported := cloudAgentNodeCapabilityForType(kind); supported {
t.Fatalf("unsupported type gained write capability: %s", kind)
}
if err := validateCreationOps([]CreationCanvasOp{{Type: "add_node", ID: "new", NodeType: kind}}); err == nil {
t.Fatalf("unsupported creation accepted: %s", kind)
}
if _, _, err := cloudAgentReferenceDescriptor(nodes[len(nodes)-1]); err == nil {
t.Fatalf("unsupported media reference accepted: %s", kind)
}
if err := validateCloudAgentConnection(nodes, kind, "text"); err == nil {
t.Fatalf("unsupported connection accepted: %s", kind)
}
}
doc := map[string]any{"nodes": nodes, "connections": []map[string]any{{"id": "edge", "fromNodeId": "drawing", "toNodeId": "text"}}}
raw, _ := json.Marshal(doc)
summary, err := cloudAgentCanvasSummary(&model.CanvasProject{PayloadJSON: string(raw)})
if err != nil || strings.Contains(summary, "PRIVATE_SENTINEL") || !strings.Contains(summary, `"agentSupported":false`) {
t.Fatalf("mixed summary failed or leaked metadata: %v", err)
}
for _, ids := range [][]string{nil, {"drawing", "config"}} {
view, err := cloudAgentCanvasState(nil, "user", doc, 0, ids, 0)
if err != nil {
t.Fatal(err)
}
result := view.(map[string]any)
encoded, _ := json.Marshal(result)
if strings.Contains(string(encoded), "PRIVATE_SENTINEL") || result["snapshotHash"] != cloudAgentCanvasHash(doc) || len(result["connections"].([]any)) != 1 {
t.Fatal("unsafe projection or lost snapshot/connection")
}
want := len(nodes)
if ids != nil {
want = len(ids)
}
if len(result["nodes"].([]any)) != want {
t.Fatal("nodes silently omitted")
}
}
}
func TestCloudAgentToolsFollowCanvasCapabilityRegistry(t *testing.T) {
req := agentTestRequest()
req.PermissionMode = "auto"