fix: hide disabled model-invocation skills from briefs (#5311)

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Multica Eve
2026-07-13 12:37:40 +08:00
committed by GitHub
parent f686901bd6
commit 57ecdef38b
4 changed files with 285 additions and 8 deletions

View File

@@ -668,10 +668,11 @@ func renderIssueContext(provider string, ctx TaskContextForEnv) string {
b.WriteString("## Quick Start\n\n")
fmt.Fprintf(&b, "Run `multica issue get %s --output json` to fetch the full issue details.\n\n", ctx.IssueID)
if len(ctx.AgentSkills) > 0 {
skills := modelVisibleSkills(ctx.AgentSkills)
if len(skills) > 0 {
b.WriteString("## Agent Skills\n\n")
b.WriteString("The following skills are available to you:\n\n")
for _, skill := range ctx.AgentSkills {
for _, skill := range skills {
fmt.Fprintf(&b, "- **%s**\n", skill.Name)
}
b.WriteString("\n")
@@ -692,9 +693,10 @@ func renderQuickCreateContext(ctx TaskContextForEnv) string {
b.WriteString("> ")
b.WriteString(ctx.QuickCreatePrompt)
b.WriteString("\n\n")
if len(ctx.AgentSkills) > 0 {
skills := modelVisibleSkills(ctx.AgentSkills)
if len(skills) > 0 {
b.WriteString("## Agent Skills\n\n")
for _, skill := range ctx.AgentSkills {
for _, skill := range skills {
fmt.Fprintf(&b, "- **%s**\n", skill.Name)
}
b.WriteString("\n")
@@ -731,10 +733,11 @@ func renderAutopilotContext(ctx TaskContextForEnv) string {
b.WriteString("\n\n")
}
if len(ctx.AgentSkills) > 0 {
skills := modelVisibleSkills(ctx.AgentSkills)
if len(skills) > 0 {
b.WriteString("## Agent Skills\n\n")
b.WriteString("The following skills are available to you:\n\n")
for _, skill := range ctx.AgentSkills {
for _, skill := range skills {
fmt.Fprintf(&b, "- **%s**\n", skill.Name)
}
b.WriteString("\n")

View File

@@ -421,7 +421,8 @@ func writeSubIssueCreation(b *strings.Builder) {
// writeSkills emits the Skills section listing skill names + descriptions.
func writeSkills(b *strings.Builder, provider string, ctx TaskContextForEnv) {
if len(ctx.AgentSkills) == 0 {
skills := modelVisibleSkills(ctx.AgentSkills)
if len(skills) == 0 {
return
}
b.WriteString("## Skills\n\n")
@@ -435,7 +436,7 @@ func writeSkills(b *strings.Builder, provider string, ctx TaskContextForEnv) {
default:
b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n")
}
for _, skill := range ctx.AgentSkills {
for _, skill := range skills {
if desc := strings.TrimSpace(skill.Description); desc != "" {
fmt.Fprintf(b, "- **%s** — %s\n", skill.Name, desc)
} else {

View File

@@ -0,0 +1,47 @@
package execenv
import (
"strings"
"gopkg.in/yaml.v3"
)
func modelVisibleSkills(skills []SkillContextForEnv) []SkillContextForEnv {
if len(skills) == 0 {
return nil
}
visible := make([]SkillContextForEnv, 0, len(skills))
for _, skill := range skills {
if skillModelInvocationVisible(skill) {
visible = append(visible, skill)
}
}
return visible
}
func skillModelInvocationVisible(skill SkillContextForEnv) bool {
return !skillDisablesModelInvocation(skill.Content)
}
func skillDisablesModelInvocation(content string) bool {
fmBody, _, ok := frontmatterParts(content)
if !ok || strings.TrimSpace(fmBody) == "" {
return false
}
var data map[string]any
if err := yaml.Unmarshal([]byte(fmBody), &data); err != nil {
return false
}
value, ok := data["disable-model-invocation"]
if !ok {
return false
}
switch v := value.(type) {
case bool:
return v
case string:
return strings.EqualFold(strings.TrimSpace(v), "true")
default:
return false
}
}

View File

@@ -0,0 +1,226 @@
package execenv
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestSkillModelInvocationVisibility(t *testing.T) {
t.Parallel()
cases := []struct {
name string
content string
want bool
}{
{
name: "no frontmatter is visible",
content: "body",
want: true,
},
{
name: "disable true is hidden",
content: `---
name: hidden
disable-model-invocation: true
---
body`,
want: false,
},
{
name: "disable quoted true is hidden",
content: `---
name: hidden
disable-model-invocation: "true"
---
body`,
want: false,
},
{
name: "disable false is visible",
content: `---
name: visible
disable-model-invocation: false
---
body`,
want: true,
},
{
name: "user invocable false is still visible",
content: `---
name: visible
user-invocable: false
---
body`,
want: true,
},
{
name: "invalid frontmatter is visible",
content: `---
name: visible
description: bad: value
disable-model-invocation: true
---
body`,
want: true,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := skillModelInvocationVisible(SkillContextForEnv{Content: tc.content})
if got != tc.want {
t.Errorf("skillModelInvocationVisible = %v, want %v", got, tc.want)
}
})
}
}
func TestRenderedSkillListsHideDisableModelInvocationSkills(t *testing.T) {
t.Parallel()
ctx := TaskContextForEnv{
IssueID: "issue-1",
QuickCreatePrompt: "create something",
AutopilotRunID: "run-1",
AgentName: "Eve",
AgentID: "eve-1",
AgentSkills: []SkillContextForEnv{
{
Name: "Visible Skill",
Description: "visible description",
Content: `---
name: visible-skill
---
Visible body.`,
},
{
Name: "Hidden Skill",
Description: "hidden description",
Content: `---
name: hidden-skill
disable-model-invocation: true
---
Hidden body.`,
},
},
}
rendered := map[string]string{
"runtime config": buildMetaSkillContent("codex", TaskContextForEnv{
IssueID: ctx.IssueID,
AgentName: ctx.AgentName,
AgentID: ctx.AgentID,
AgentSkills: ctx.AgentSkills,
}),
"issue context": renderIssueContext("codex", TaskContextForEnv{
IssueID: ctx.IssueID,
AgentSkills: ctx.AgentSkills,
}),
"quick create": renderQuickCreateContext(TaskContextForEnv{
QuickCreatePrompt: ctx.QuickCreatePrompt,
AgentSkills: ctx.AgentSkills,
}),
"autopilot": renderAutopilotContext(TaskContextForEnv{
AutopilotRunID: ctx.AutopilotRunID,
AgentSkills: ctx.AgentSkills,
}),
}
for name, out := range rendered {
if !strings.Contains(out, "Visible Skill") {
t.Errorf("%s missing visible skill:\n%s", name, out)
}
if strings.Contains(out, "Hidden Skill") || strings.Contains(out, "hidden description") {
t.Errorf("%s advertised disable-model-invocation skill:\n%s", name, out)
}
}
}
func TestRenderedSkillListsOmitSectionWhenOnlyDisabledSkills(t *testing.T) {
t.Parallel()
ctx := TaskContextForEnv{
IssueID: "issue-1",
AgentSkills: []SkillContextForEnv{
{
Name: "Hidden Skill",
Content: `---
name: hidden-skill
disable-model-invocation: true
---
Hidden body.`,
},
},
}
runtimeConfig := buildMetaSkillContent("codex", ctx)
if strings.Contains(runtimeConfig, "## Skills") {
t.Errorf("runtime config should omit Skills section when every skill disables model invocation:\n%s", runtimeConfig)
}
issueContext := renderIssueContext("codex", ctx)
if strings.Contains(issueContext, "## Agent Skills") {
t.Errorf("issue context should omit Agent Skills section when every skill disables model invocation:\n%s", issueContext)
}
}
func TestWriteContextFilesHydratesDisableModelInvocationSkillButDoesNotAdvertiseIt(t *testing.T) {
t.Parallel()
dir := t.TempDir()
ctx := TaskContextForEnv{
IssueID: "issue-1",
AgentSkills: []SkillContextForEnv{
{
Name: "Visible Skill",
Content: `---
name: visible-skill
---
Visible body.`,
},
{
Name: "Hidden Skill",
Content: `---
name: hidden-skill
disable-model-invocation: true
---
Hidden body.`,
},
},
}
if err := writeContextFiles(dir, "", ctx, nil); err != nil {
t.Fatalf("writeContextFiles failed: %v", err)
}
issueContext, err := os.ReadFile(filepath.Join(dir, ".agent_context", "issue_context.md"))
if err != nil {
t.Fatalf("read issue_context.md: %v", err)
}
if strings.Contains(string(issueContext), "Hidden Skill") {
t.Fatalf("issue_context.md advertised hidden skill:\n%s", string(issueContext))
}
hiddenSkill, err := os.ReadFile(filepath.Join(dir, ".agent_context", "skills", "hidden-skill", "SKILL.md"))
if err != nil {
t.Fatalf("disabled skill should still be hydrated on disk: %v", err)
}
if !strings.Contains(string(hiddenSkill), "disable-model-invocation: true") {
t.Errorf("hidden skill frontmatter should be preserved on disk:\n%s", string(hiddenSkill))
}
}