This commit is contained in:
OpenClaw-User 2026-03-14 17:11:16 +08:00
commit f5145d34fc
3 changed files with 47 additions and 5 deletions

View file

@ -25,6 +25,7 @@ type ContextBuilder struct {
memory *MemoryStore
toolDiscoveryBM25 bool
toolDiscoveryRegex bool
skillsFilter []string
// Cache for system prompt to avoid rebuilding on every call.
// This fixes issue #607: repeated reprocessing of the entire context.
@ -51,6 +52,13 @@ func (cb *ContextBuilder) WithToolDiscovery(useBM25, useRegex bool) *ContextBuil
return cb
}
// WithSkillsFilter restricts which skills appear in the system prompt to the
// named list. An empty or nil filter means all available skills are included.
func (cb *ContextBuilder) WithSkillsFilter(filter []string) *ContextBuilder {
cb.skillsFilter = filter
return cb
}
func getGlobalConfigDir() string {
if home := os.Getenv("PICOCLAW_HOME"); home != "" {
return home
@ -141,7 +149,7 @@ func (cb *ContextBuilder) BuildSystemPrompt() string {
}
// Skills - show summary, AI can read full content with read_file tool
skillsSummary := cb.skillsLoader.BuildSkillsSummary()
skillsSummary := cb.skillsLoader.BuildSkillsSummaryFiltered(cb.skillsFilter)
if skillsSummary != "" {
parts = append(parts, fmt.Sprintf(`# Skills
@ -718,16 +726,27 @@ func (cb *ContextBuilder) AddAssistantMessage(
return messages
}
// GetSkillsInfo returns information about loaded skills.
// GetSkillsInfo returns information about loaded skills, respecting any active filter.
func (cb *ContextBuilder) GetSkillsInfo() map[string]any {
allSkills := cb.skillsLoader.ListSkills()
var allowed map[string]bool
if len(cb.skillsFilter) > 0 {
allowed = make(map[string]bool, len(cb.skillsFilter))
for _, n := range cb.skillsFilter {
allowed[n] = true
}
}
skillNames := make([]string, 0, len(allSkills))
for _, s := range allSkills {
skillNames = append(skillNames, s.Name)
if allowed == nil || allowed[s.Name] {
skillNames = append(skillNames, s.Name)
}
}
return map[string]any{
"total": len(allSkills),
"available": len(allSkills),
"available": len(skillNames),
"names": skillNames,
}
}

View file

@ -117,6 +117,8 @@ func NewAgentInstance(
skillsFilter = agentCfg.Skills
}
contextBuilder.WithSkillsFilter(skillsFilter)
maxIter := defaults.MaxToolIterations
if maxIter == 0 {
maxIter = 20

View file

@ -192,19 +192,36 @@ func (sl *SkillsLoader) LoadSkillsForContext(skillNames []string) string {
}
func (sl *SkillsLoader) BuildSkillsSummary() string {
return sl.BuildSkillsSummaryFiltered(nil)
}
// BuildSkillsSummaryFiltered builds the XML skills summary, restricted to
// allowedNames if non-empty. An empty/nil allowedNames includes all skills.
func (sl *SkillsLoader) BuildSkillsSummaryFiltered(allowedNames []string) string {
allSkills := sl.ListSkills()
if len(allSkills) == 0 {
return ""
}
var allowed map[string]bool
if len(allowedNames) > 0 {
allowed = make(map[string]bool, len(allowedNames))
for _, n := range allowedNames {
allowed[n] = true
}
}
var lines []string
lines = append(lines, "<skills>")
for _, s := range allSkills {
if allowed != nil && !allowed[s.Name] {
continue
}
escapedName := escapeXML(s.Name)
escapedDesc := escapeXML(s.Description)
escapedPath := escapeXML(s.Path)
lines = append(lines, fmt.Sprintf(" <skill>"))
lines = append(lines, " <skill>")
lines = append(lines, fmt.Sprintf(" <name>%s</name>", escapedName))
lines = append(lines, fmt.Sprintf(" <description>%s</description>", escapedDesc))
lines = append(lines, fmt.Sprintf(" <location>%s</location>", escapedPath))
@ -213,6 +230,10 @@ func (sl *SkillsLoader) BuildSkillsSummary() string {
}
lines = append(lines, "</skills>")
if len(lines) == 2 {
// Only open/close tags — filter excluded everything
return ""
}
return strings.Join(lines, "\n")
}