Files
Agent-Squad/adapters/intent/catalog.go
T
weichenandCursor a182d7662b Move cross-library squad code into adapter modules
Why: the squad core has to compile without agent-native or agent-intent. Ports stay in the core; adapters and the CLI own the concrete clients.
Evidence: GOWORK=off go test -count=1 ./squad/ ./store/ ./textextract/ ./schema/ passed, and the intent, sqlite, native, and cmd modules passed their own go test.
Impact: core go.mod keeps error-code-system only. Nested gitlinks stay. Callers register the native backend through agent-squad/adapters/native.
Refs: D1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 13:40:58 +00:00

116 lines
3.7 KiB
Go

package squadintent
import (
"context"
"fmt"
"sort"
"strings"
"agent-squad/squad"
)
// Catalog 把识别快照渲染为 squad 的能力目录数据视图(规划者提示词 + 计划校验用)。
// 闸门未启用(nil Gate)时返回 nil。
func (g *Gate) Catalog() *squad.IntentCatalog {
snap := g.Snapshot()
if g == nil || snap == nil {
return nil
}
catalog := &squad.IntentCatalog{
Revision: snap.Revision(),
Markdown: snap.CatalogMarkdown(),
Specs: make(map[string]squad.IntentSpecView, len(snap.SpecIDs())),
}
for _, cs := range 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) (*squad.ExecResult, error) {
if r == nil || r.gate == nil || r.gate.Snapshot() == nil {
return nil, fmt.Errorf("intent runner: gate not configured")
}
spec, ok := r.gate.Snapshot().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) *squad.ExecResult {
patches := make(map[string][]byte, len(step.Targets))
for _, t := range step.Targets {
patches[t] = []byte(text)
}
return &squad.ExecResult{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()
}