Files
Agent-Squad/squad/intent.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

150 lines
5.3 KiB
Go

package squad
import (
"context"
"fmt"
)
// IntentRejection 守卫拦截:拒绝该次运行并给出引导("识别即拒绝"语义)。
type IntentRejection struct {
// Reason 稳定拒因(进入运行结果与事件文本)。
Reason string
// Guidance 给用户的引导文案(进入运行结果错误详情)。
Guidance string
}
// IntentDecision 意图闸门的分流决策;nil 表示不干预(完全走原路径)。
type IntentDecision struct {
// Reject 非空时拒绝该次运行(守卫拦截)。
Reject *IntentRejection
// BlueprintKey 非空时定向使用该蓝图模板(等价显式 TaskKey;不存在的蓝图 fail-closed)。
BlueprintKey string
// IntentID / Handler / Metadata 透传识别元数据,供运行审计与日志。
IntentID string
Handler string
Metadata map[string]string
}
// IntentGate 可选意图闸门:在 Run 入口对目标文本做一次确定性识别。
// 入口语义:澄清/确认后的续跑不再过闸;显式 TaskKey 优先于意图定向(引擎保证)。
// 实现必须无副作用、可并发调用;系统错误以 error 返回(引擎 fail-closed 标记运行失败)。
// 宿主实现见 squadintent 适配器;本接口不依赖 agent-intent。
type IntentGate interface {
Decide(ctx context.Context, goal Goal, opts RunOptions) (*IntentDecision, error)
}
// ---- 能力目录(目录驱动的编排)----
// IntentSlotView 目录中的槽位视图:规划者/领导据此知道缺什么信息、建议怎么问。
type IntentSlotView struct {
Key string `json:"key"`
Required bool `json:"required,omitempty"`
Label string `json:"label,omitempty"`
Question string `json:"question,omitempty"`
Enum []string `json:"enum,omitempty"`
Format string `json:"format,omitempty"`
}
// IntentSpecView 目录中的单条可调用能力。
type IntentSpecView struct {
ID string `json:"id"`
Description string `json:"description,omitempty"`
Risk string `json:"risk,omitempty"`
RequiresConfirm bool `json:"requires_confirm,omitempty"`
Handler string `json:"handler,omitempty"`
Slots []IntentSlotView `json:"slots,omitempty"`
RequiredSlots []string `json:"required_slots,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// IntentCatalog 意图能力目录:squadintent 从识别快照渲染的数据视图,
// squad 只消费不构造(不 import agent-intent)。Markdown 注入规划者/领导提示词;
// Specs 用于计划的确定性校验(意图存在、必填槽位齐、枚举合法)。
type IntentCatalog struct {
Revision int64
Markdown string
Specs map[string]IntentSpecView
}
// intentCatalogMd nil 安全的目录文本(未启用时为空串)。
func (c *IntentCatalog) intentCatalogMd() string {
if c == nil {
return ""
}
return c.Markdown
}
// Get 按 id 查目录能力。
func (c *IntentCatalog) Get(id string) (IntentSpecView, bool) {
if c == nil {
return IntentSpecView{}, false
}
v, ok := c.Specs[id]
return v, ok
}
// IntentStep 系统功能步:不经 BackendResolver/智能体,由宿主 IntentRunner 直接执行。
type IntentStep struct {
IntentID string
Slots map[string]string
Title string
WorkDir string
Targets []string
}
// IntentRunner 系统功能执行器(squadintent 提供实现:查 spec.Handler 并调宿主处理函数)。
type IntentRunner interface {
Run(ctx context.Context, step IntentStep) (*ExecResult, error)
}
// DefaultIntentRole 系统功能步的默认角色 key(无后端语义,仅满足编制覆盖)。
const DefaultIntentRole = "intent"
// DefaultIntentRoleSpec 返回系统功能步的默认角色(组合根在启用意图目录时注册)。
func DefaultIntentRoleSpec() RoleSpec {
return RoleSpec{Role: DefaultIntentRole, Name: "系统能力", Backend: "stub", Keywords: []string{"系统功能"}}
}
// validateIntentSteps 计划落盘前的确定性校验:意图步必须命中目录、必填槽位齐、枚举合法。
// fail-closed:任一不通过即整计划拒绝(与未知 backend 拒绝同构)。
func validateIntentSteps(plan *SquadPlan, catalog *IntentCatalog) error {
if plan == nil {
return nil
}
for _, step := range plan.Workflow.Steps {
if step.IntentID == "" {
continue
}
if catalog == nil {
return fmt.Errorf("intent 步骤 %s:引擎未装配意图目录(IntentCatalog),无法执行系统功能步", step.ID)
}
view, ok := catalog.Get(step.IntentID)
if !ok {
return fmt.Errorf("intent 步骤 %s:目录中不存在意图 %s", step.ID, step.IntentID)
}
for _, sl := range view.Slots {
v, given := step.IntentSlots[sl.Key]
if sl.Required && (!given || v == "") {
q := sl.Question
if q == "" {
q = "请提供 " + sl.Key
}
return fmt.Errorf("intent 步骤 %s:意图 %s 缺必填槽位 %s(澄清建议:%s)", step.ID, step.IntentID, sl.Key, q)
}
if given && len(sl.Enum) > 0 && !containsString(sl.Enum, v) {
return fmt.Errorf("intent 步骤 %s:槽位 %s 的值 %q 不在枚举 %v 内", step.ID, sl.Key, v, sl.Enum)
}
}
}
return nil
}
func containsString(list []string, s string) bool {
for _, v := range list {
if v == s {
return true
}
}
return false
}