diff --git a/backend/internal/app/cloud_agent_contract_test.go b/backend/internal/app/cloud_agent_contract_test.go index 51e22506c..dcf46105b 100644 --- a/backend/internal/app/cloud_agent_contract_test.go +++ b/backend/internal/app/cloud_agent_contract_test.go @@ -215,16 +215,28 @@ func TestCloudAgentAnnotationRenderFeedsControlledTransientReference(t *testing. } func TestCloudAgentPolicyPublishesSkillManifestWithoutInliningSkillBody(t *testing.T) { - skill := cloudAgentSkill{ID: "skill-1", Name: "任务技能", Version: "v1", Hash: agentProfileHash("skill"), Instruction: "PRIVATE_SKILL_BODY", Files: map[string]string{"references/a.md": "A"}} + skill := cloudAgentSkill{ID: "skill-1", Name: "任务技能", Description: "当用户要写短剧剧本时调用", Version: "v1", Hash: agentProfileHash("skill"), Instruction: "PRIVATE_SKILL_BODY", Files: map[string]string{"references/a.md": "A"}} text, _, err := compileCloudAgentPolicies(agentTestRequest(), []cloudAgentSkill{skill}, "", cloudAgentProfileSnapshot{Revision: agentProfileRevision(nil), Hash: agentProfileHash("")}) if err != nil { t.Fatal(err) } - if strings.Contains(text, skill.Instruction) || !strings.Contains(text, `"entryPath":"SKILL.md"`) || !strings.Contains(text, `"files":["SKILL.md","references/a.md"]`) { + if strings.Contains(text, skill.Instruction) || !strings.Contains(text, `"entryPath":"SKILL.md"`) || !strings.Contains(text, `"files":["SKILL.md","references/a.md"]`) || !strings.Contains(text, `"description":"当用户要写短剧剧本时调用"`) { t.Fatalf("compiled policy did not publish a safe on-demand skill manifest: %s", text) } } +func TestCloudAgentPolicyTruncatesOversizedSkillDescription(t *testing.T) { + long := strings.Repeat("描", 600) + skill := cloudAgentSkill{ID: "skill-2", Name: "长描述技能", Description: long, Version: "v1", Hash: agentProfileHash("skill2")} + text, _, err := compileCloudAgentPolicies(agentTestRequest(), []cloudAgentSkill{skill}, "", cloudAgentProfileSnapshot{Revision: agentProfileRevision(nil), Hash: agentProfileHash("")}) + if err != nil { + t.Fatal(err) + } + if strings.Contains(text, long) || !strings.Contains(text, strings.Repeat("描", 500)+"…") { + t.Fatalf("oversized skill description was not capped at 500 runes: %s", text) + } +} + func TestCloudAgentPolicyPublishesCapabilityRoutingGuide(t *testing.T) { text, _, err := compileCloudAgentPolicies(agentTestRequest(), nil, "", cloudAgentProfileSnapshot{Revision: agentProfileRevision(nil), Hash: agentProfileHash("")}) if err != nil { diff --git a/backend/internal/app/cloud_agent_policy.go b/backend/internal/app/cloud_agent_policy.go index 7feb41f75..f89ba8729 100644 --- a/backend/internal/app/cloud_agent_policy.go +++ b/backend/internal/app/cloud_agent_policy.go @@ -84,6 +84,19 @@ func cloudAgentCapabilityGuide() string { return b.String() } +// cloudAgentSkillManifestDescription bounds the system prompt: the description +// is author-supplied public metadata, so it is trimmed and capped before it +// enters the compiled policy. +func cloudAgentSkillManifestDescription(description string) string { + const maxRunes = 500 + trimmed := strings.TrimSpace(description) + if utf8.RuneCountInString(trimmed) <= maxRunes { + return trimmed + } + runes := []rune(trimmed) + return strings.TrimSpace(string(runes[:maxRunes])) + "…" +} + func compileCloudAgentPolicies(req CloudAgentRequest, skills []cloudAgentSkill, canvasSummary string, profile cloudAgentProfileSnapshot, anchors ...cloudAgentCreativeAnchor) (string, cloudAgentPolicySnapshot, error) { system, media, err := prompts.LoadAgentPolicies() if err != nil { @@ -121,7 +134,7 @@ func compileCloudAgentPolicies(req CloudAgentRequest, skills []cloudAgentSkill, } manifests := make([]map[string]any, 0, len(skills)) for _, skill := range skills { - manifests = append(manifests, map[string]any{"skillId": skill.ID, "name": skill.Name, "version": skill.Version, "hash": skill.Hash, "entryPath": cloudAgentSkillEntryPath, "files": cloudAgentSkillPaths(skill)}) + manifests = append(manifests, map[string]any{"skillId": skill.ID, "name": skill.Name, "description": cloudAgentSkillManifestDescription(skill.Description), "version": skill.Version, "hash": skill.Hash, "entryPath": cloudAgentSkillEntryPath, "files": cloudAgentSkillPaths(skill)}) } context["skills"] = manifests layers := make([]map[string]any, 0, len(profile.Layers)) diff --git a/backend/internal/app/cloud_agent_tools.go b/backend/internal/app/cloud_agent_tools.go index 742038745..5455c3915 100644 --- a/backend/internal/app/cloud_agent_tools.go +++ b/backend/internal/app/cloud_agent_tools.go @@ -22,6 +22,7 @@ import ( type cloudAgentSkill struct { ID string `json:"id"` Name string `json:"name"` + Description string `json:"description,omitempty"` Version string `json:"version"` Hash string `json:"hash"` Instruction string `json:"instruction,omitempty"` @@ -60,7 +61,9 @@ func (s *Service) cloudAgentSkills(userID string, ids []string) ([]cloudAgentSki } // Skill content is loaded only after the model explicitly calls // skill_read_file; keep the run context to stable metadata and paths. - snapshot := cloudAgentSkill{ID: id, Name: skill.SkillName, Version: skill.VersionID, Hash: skill.ContentHash, Files: map[string]string{cloudAgentSkillEntryPath: ""}} + // The description is public metadata (market listing) and lets the + // model route between activated skills without reading any body. + snapshot := cloudAgentSkill{ID: id, Name: skill.SkillName, Description: skill.Description, Version: skill.VersionID, Hash: skill.ContentHash, Files: map[string]string{cloudAgentSkillEntryPath: ""}} files, err := s.SkillPackageFiles(userID, id) if err != nil { return nil, err