Files
Agent-Squad/squadintent/catalog.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

116 lines
3.7 KiB
Go

package squadintent
import (
"context"
"fmt"
"sort"
"strings"
"agent-native/exec"
"agent-squad/squad"
)
// Catalog 把识别快照渲染为 squad 的能力目录数据视图(规划者提示词 + 计划校验用)。
// 闸门未启用(nil Gate)时返回 nil。
func (g *Gate) Catalog() *squad.IntentCatalog {
if g == nil || g.snap == nil {
return nil
}
catalog := &squad.IntentCatalog{
Revision: g.snap.Revision(),
Markdown: g.snap.CatalogMarkdown(),
Specs: make(map[string]squad.IntentSpecView, len(g.snap.SpecIDs())),
}
for _, cs := range g.snap.Catalog() {
view := squad.IntentSpecView{
ID: cs.ID,
Description: cs.Description,
Risk: cs.Risk,
RequiresConfirm: cs.RequiresConfirm,
Handler: cs.Handler,
Metadata: cs.Metadata,
}
for _, sl := range cs.Slots {
view.Slots = append(view.Slots, squad.IntentSlotView{
Key: sl.Key, Required: sl.Required, Label: sl.Label,
Question: sl.Question, Enum: sl.Enum, Format: sl.Format,
})
if sl.Required {
view.RequiredSlots = append(view.RequiredSlots, sl.Key)
}
}
catalog.Specs[cs.ID] = view
}
return catalog
}
// IntentHandler 宿主注册的系统功能处理函数:返回写入步骤 targets 的正文。
type IntentHandler func(ctx context.Context, step squad.IntentStep) (string, error)
// Runner 系统功能执行器:按 spec.Handler 查宿主处理函数并调用;
// echo=true 时未注册的 handler 以物化文本回退(开发/演示用,正文明确声明未真实执行)。
type Runner struct {
gate *Gate
handlers map[string]IntentHandler
echo bool
}
// Runner 基于闸门快照构建执行器。handlers 以 spec.Handler 为键。
func (g *Gate) Runner(handlers map[string]IntentHandler) *Runner {
return &Runner{gate: g, handlers: handlers}
}
// EchoRunner 演示执行器:所有调用物化为文本(AGENT_SQUAD_INTENT_ECHO 场景)。
func (g *Gate) EchoRunner() *Runner {
return &Runner{gate: g, echo: true}
}
// Run 实现 squad.IntentRunner。未知意图/未知 handler fail-closed(不静默降级)。
func (r *Runner) Run(ctx context.Context, step squad.IntentStep) (*exec.Result, error) {
if r == nil || r.gate == nil || r.gate.snap == nil {
return nil, fmt.Errorf("intent runner: gate not configured")
}
spec, ok := r.gate.snap.SpecByID(step.IntentID)
if !ok {
return nil, fmt.Errorf("intent runner: snapshot 中不存在意图 %s", step.IntentID)
}
handlerKey := spec.Handler
h, registered := r.handlers[handlerKey]
if !registered {
if !r.echo {
return nil, fmt.Errorf("intent runner: handler %q 未注册(fail-closed,不静默降级)", handlerKey)
}
text := echoText(step, handlerKey, false)
return textResult(step, text), nil
}
text, err := h(ctx, step)
if err != nil {
return nil, err
}
return textResult(step, text), nil
}
func textResult(step squad.IntentStep, text string) *exec.Result {
patches := make(map[string][]byte, len(step.Targets))
for _, t := range step.Targets {
patches[t] = []byte(text)
}
return &exec.Result{Success: true, Patches: patches, Logs: "intent:" + step.IntentID}
}
func echoText(step squad.IntentStep, handlerKey string, _ bool) string {
var b strings.Builder
b.WriteString("# 系统功能调用(EchoRunner 物化,未真实执行)\n\n")
fmt.Fprintf(&b, "- intent_id: %s\n- handler: %s\n- title: %s\n", step.IntentID, handlerKey, step.Title)
keys := make([]string, 0, len(step.Slots))
for k := range step.Slots {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(&b, "- slot %s = %s\n", k, step.Slots[k])
}
b.WriteString("\n宿主应用注册真实 handler 后,此文件将是系统功能的真实输出。\n")
return b.String()
}