Files
weichen cb398dadc6
ci / vet-and-test (pull_request) Successful in 24s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Enable NFKC folding in the squad intent gate and bump the intent pin
Why: Both intent hosts should fold compatibility-equivalent input
(backend intentgate already does); the nested pin carries the I1
command model the gate depends on.

Evidence: adapters/intent suite ok; all five squad modules green.

Impact: NFKC on in NewFromOverlay; nested agent-intent at 2554b50.
2026-10-07 07:42:52 +00:00

138 lines
4.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package squadintent 把 agent-intent 的识别结果适配为 SquadEngine 的意图闸门。
//
// 本包是 Agent-Squad 仓内唯一 import agent-intent 的地方;squad/server/store 核心
// 不感知意图。未提供任何意图来源时 New 返回 nil 闸门(= 未启用),引擎行为与
// 未接入时完全一致;来源存在但配置非法时返回错误(调用方 fail-closed 拒绝启动)。
//
// 分支映射:
//
// GuardHit → Reject(引导文案取 Metadata["guidance"])
// Matched/ExpectationHit 且 Metadata["blueprint"] 非空 → 蓝图定向(等价显式 TaskKey)
// Matched 但无 blueprint 映射 / Ambiguous / Fallback → nil(不干预,走原路径)
// 识别系统错误 → error(引擎标记运行失败,fail-closed)
package squadintent
import (
"context"
"io/fs"
"agent-intent/intent"
"agent-squad/squad"
)
// Options 闸门构造选项。
type Options struct {
// IntentOptions 传给 agent-intent:GuardDomains/ProtectedDomains/上限等(Log 可选)。
IntentOptions intent.Options
// GuardSpecs 宿主内置守卫 spec,应位于 ProtectedDomains 声明的域内(overlay 不可覆盖)。
GuardSpecs []intent.IntentSpec
// FS 与 Roots 是 JSON overlay 来源;两者为空则只用 GuardSpecs。
FS fs.FS
Roots []string
}
// NewFromOverlay 从 JSON overlay 构造闸门,并把 guard 域标成受保护域。
// 命令行用它,这样命令本身不必 import agent-intent。
// NFKC 开启:全角/兼容等价输入与词条同形折叠(与 backend intentgate 一致)。
func NewFromOverlay(ctx context.Context, fsys fs.FS, roots ...string) (*Gate, error) {
return New(ctx, Options{
IntentOptions: intent.Options{
GuardDomains: []string{"guard"},
ProtectedDomains: []string{"guard"},
NFKC: true,
},
FS: fsys,
Roots: roots,
})
}
// New 构造闸门。没有任何意图来源时返回 (nil, nil)——引擎拿到 nil IntentGate,
// 行为与原实现一致(T0 行为解耦)。
func New(ctx context.Context, o Options) (*Gate, error) {
if len(o.GuardSpecs) == 0 && (o.FS == nil || len(o.Roots) == 0) {
return nil, nil
}
b := intent.NewRegistryBuilder(o.IntentOptions)
if len(o.GuardSpecs) > 0 {
if err := b.Register(o.GuardSpecs...); err != nil {
return nil, err
}
}
var (
reg *intent.Registry
err error
)
if o.FS != nil {
reg, err = b.BuildFS(ctx, o.FS, o.Roots...)
} else {
reg, err = b.Build()
}
if err != nil {
return nil, err
}
return &Gate{rec: intent.NewRecognizer(o.IntentOptions, nil, nil), reg: reg}, nil
}
// Gate 实现 squad.IntentGate。快照从 Registry 现读,Reload 后下一次 Decide 即生效。
type Gate struct {
rec intent.Recognizer
reg *intent.Registry
}
// Snapshot 暴露当前识别快照(诊断/审计用)。
func (g *Gate) Snapshot() *intent.Snapshot {
if g == nil || g.reg == nil {
return nil
}
return g.reg.Snapshot()
}
// Reload 重读配置来源。失败时保留旧快照。
func (g *Gate) Reload(ctx context.Context) (*intent.Snapshot, error) {
if g == nil || g.reg == nil {
return nil, nil
}
return g.reg.Reload(ctx)
}
// Decide 实现 squad.IntentGate。同输入 + 同快照结果确定;无副作用。
func (g *Gate) Decide(ctx context.Context, goal squad.Goal, opts squad.RunOptions) (*squad.IntentDecision, error) {
if g == nil || g.Snapshot() == nil {
return nil, nil
}
if opts.TaskKey != "" {
return nil, nil // 显式 TaskKey 优先(引擎侧同样保证,双保险)
}
res, err := g.rec.Recognize(ctx, goal.Text, g.Snapshot())
if err != nil {
return nil, err
}
switch res.Branch {
case intent.BranchGuardHit:
if res.Top == nil {
return nil, nil
}
return &squad.IntentDecision{
Reject: &squad.IntentRejection{
Reason: "命中守卫意图 " + res.Top.ID,
Guidance: res.Top.Metadata["guidance"],
},
IntentID: res.Top.ID,
Handler: res.Top.Handler,
Metadata: res.Top.Metadata,
}, nil
case intent.BranchMatched, intent.BranchExpectationHit:
if res.Top == nil || res.Top.Metadata["blueprint"] == "" {
return nil, nil // 命中但无蓝图映射:不干预
}
return &squad.IntentDecision{
BlueprintKey: res.Top.Metadata["blueprint"],
IntentID: res.Top.ID,
Handler: res.Top.Handler,
Metadata: res.Top.Metadata,
}, nil
default:
return nil, nil // Ambiguous / Fallback:交给原路径
}
}