Files
Agent-Native/adapters/intentgate/intentgate.go
T
weichenandCursor 17f2a4ecfa Close the native P1 gaps the audit listed for this batch
Why: token budget let the current tool batch run, compaction could split a tool pair or race an append, writes were not crash-atomic, and several MCP and session edges only existed as comments.
Evidence: GOWORK=off go test -count=1 -race ./runtime/ ./runtime/llm/ ./runtime/session/ ./runtime/tools/ ./runtime/mcp/ ./adapters/native/ ./adapters/intentgate/ passed, including the TestR2* cases for budget, replacement, cancel, compaction, SSE, UTF-8, schema, MCP retry, session terminate, poison reload, and registry reload.
Impact: over-budget tool calls are skipped and paired; Stop.Replacement is appended to the session; the intent gate can follow Registry.Reload.
Refs: N5, N6, N7, N8, N9, N11, N12, N15, N16, N17, N18, N19, N21, I9
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 13:05:58 +00:00

82 lines
3.0 KiB
Go

// Package intentgate 提供可选的意图闸门装饰器:在把 Prompt 交给被装饰的
// 执行后端之前,用 agent-intent 做一次确定性识别;分支为守卫/命中/预期命中时
// 交由宿主 Handler 解释,Ambiguous/Fallback 或 Handler 放弃时原样透传。
//
// 本包是 Agent-Native 仓内唯一 import agent-intent 的地方。不使用本包时,
// 本仓与 agent-intent 无任何编译关系;未 Wrap 或快照零意图时,被装饰后端的
// 行为与原来完全一致。
package intentgate
import (
"context"
"agent-intent/intent"
"agent-native/exec"
)
// Handler 解释"识别到意图"的执行请求。返回 handled=false 表示宿主放弃处理,
// 装饰器回退为透传原后端。合成结果的语义(拒绝、引导、定向执行)由宿主定义,
// 本包不发明结果语义。
type Handler func(ctx context.Context, req exec.Request, res intent.Result) (result *exec.Result, err error, handled bool)
type gated struct {
next exec.Backend
rec intent.Recognizer
snap *intent.Snapshot
handler Handler
}
// Wrap 返回带意图闸门的后端。next/rec/snap/handler 任一为 nil 时直接返回 next
// (等价于未启用,行为不变)。
func Wrap(next exec.Backend, rec intent.Recognizer, snap *intent.Snapshot, h Handler) exec.Backend {
if next == nil || rec == nil || snap == nil || h == nil {
return next
}
return &gated{next: next, rec: rec, snap: snap, handler: h}
}
// WrapRegistry 持有 Registry,每次执行读取当前快照。Reload 之后不用重建装饰器。
func WrapRegistry(next exec.Backend, rec intent.Recognizer, reg *intent.Registry, h Handler) exec.Backend {
if next == nil || rec == nil || reg == nil || h == nil {
return next
}
return &gatedRegistry{next: next, rec: rec, reg: reg, handler: h}
}
type gatedRegistry struct {
next exec.Backend
rec intent.Recognizer
reg *intent.Registry
handler Handler
}
func (g *gatedRegistry) Name() string { return g.next.Name() }
func (g *gatedRegistry) Execute(ctx context.Context, req exec.Request) (*exec.Result, error) {
return executeGated(ctx, g.next, g.rec, g.reg.Snapshot(), g.handler, req)
}
func (g *gated) Name() string { return g.next.Name() }
func (g *gated) Execute(ctx context.Context, req exec.Request) (*exec.Result, error) {
return executeGated(ctx, g.next, g.rec, g.snap, g.handler, req)
}
func executeGated(ctx context.Context, next exec.Backend, rec intent.Recognizer, snap *intent.Snapshot, handler Handler, req exec.Request) (*exec.Result, error) {
res, err := rec.Recognize(ctx, req.Prompt, snap)
if err != nil {
// 识别系统错误 fail-closed:不静默放行可能应拦截的内容,也不吞错。
return nil, err
}
switch res.Branch {
case intent.BranchGuardHit, intent.BranchMatched, intent.BranchExpectationHit:
default:
return next.Execute(ctx, req) // 无裁决:透传原后端
}
out, herr, handled := handler(ctx, req, res)
if !handled {
return next.Execute(ctx, req)
}
return out, herr
}