Files
Agent-Squad/squad/blueprint.go
T
weichen b6ff0ab55f
ci / vet-and-test (push) Failing after 2s
ci / docker-checkout-hostgateway (push) Failing after 2s
Add catalog-driven orchestration: intent catalog injection
Details (original message):
Add catalog-driven orchestration: intent catalog injection, deterministic plan validation, and system-function steps.
2026-09-29 16:37:46 +00:00

102 lines
2.3 KiB
Go

package squad
import (
"fmt"
"sort"
"strings"
"sync"
)
// BlueprintRegistry 任务模板注册中心:空表起步。
// 命中模板的 Goal 走基线工作流,未命中走规划器自主拆解。
type BlueprintRegistry struct {
mu sync.RWMutex
bps map[string]Blueprint
// OnChange 可选钩子:注册/删除后回调(store 层用于落盘)。
OnChange func(bp Blueprint)
}
// NewBlueprintRegistry 创建空注册表。
func NewBlueprintRegistry() *BlueprintRegistry {
return &BlueprintRegistry{bps: make(map[string]Blueprint)}
}
// Register 注册或更新模板。
func (r *BlueprintRegistry) Register(bp Blueprint) error {
if strings.TrimSpace(bp.Key) == "" {
return fmt.Errorf("blueprint key cannot be empty")
}
if len(bp.Steps) == 0 {
return fmt.Errorf("blueprint %s has no steps", bp.Key)
}
for i := range bp.Steps {
if bp.Steps[i].ID == "" {
bp.Steps[i].ID = fmt.Sprintf("step_%02d", i+1)
}
if bp.Steps[i].Targets == nil {
bp.Steps[i].Targets = []string{"output.md"}
}
}
r.mu.Lock()
r.bps[bp.Key] = bp
r.mu.Unlock()
if r.OnChange != nil {
r.OnChange(bp)
}
return nil
}
// Get 按 Key 获取模板。
func (r *BlueprintRegistry) Get(key string) (Blueprint, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
bp, ok := r.bps[key]
return bp, ok
}
// List 列出全部模板(按 Key 排序)。
func (r *BlueprintRegistry) List() []Blueprint {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]Blueprint, 0, len(r.bps))
for _, bp := range r.bps {
out = append(out, bp)
}
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
return out
}
// Remove 删除模板。
func (r *BlueprintRegistry) Remove(key string) bool {
r.mu.Lock()
_, ok := r.bps[key]
if ok {
delete(r.bps, key)
}
r.mu.Unlock()
return ok
}
// Match 按目标文本匹配模板:命中 Match 关键词数最多者;无命中返回 false。
// 显式 taskKey 非空时直接按 Key 精确查找。
func (r *BlueprintRegistry) Match(goalText, taskKey string) (Blueprint, bool) {
if taskKey != "" {
return r.Get(taskKey)
}
q := strings.ToLower(goalText)
var best Blueprint
bestScore := 0
for _, bp := range r.List() {
score := 0
for _, kw := range bp.Match {
if kw != "" && strings.Contains(q, strings.ToLower(kw)) {
score++
}
}
if score > bestScore {
best, bestScore = bp, score
}
}
return best, bestScore > 0
}