Files
Agent-System-Backend/internal/route/route.go
T
weichen 517ae236b3
ci / vet-and-test (pull_request) Successful in 42s
Project the command types through the intent gate surface
Why: The boundary linter allows agent-intent imports only in the
adapter layer; route and runmgr consume aliases from intentgate
instead of importing the ecosystem library directly.

Evidence: lint-boundaries passes below; backend suite green.

Impact: intentgate re-exports Command/ActionRef and the kind
constants; route drops the agent-intent import.
2026-10-07 07:58:05 +00:00

115 lines
3.9 KiB
Go
Raw 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 route 是 Backend 的唯一意图 Router(I1 WP3)。
// 它把识别器产出的命令列表统一裁决为路由决策,收编原散落在
// server(chat 内联分支)与 runmgr(HITL 自由输入转轨)的两套逻辑。
//
// 优先级明文(提案 §6 要求 I1 写明):
// - auto/handler:命令总线 > 工作流 > 普通回合(与现网行为一致)。
// - 显式 command/workflow/blueprint:按声明执行;声明目标在当前环境
// 不可解析(命令不存在、工作流未注册、小队未启用)时不静默错配——
// 回落普通回合并把原因写进 Reason。
// - PromptTrigger(技能触发)是提示词装配、不参与路由决胜;
// 意图产出 Start/Clarify 类命令时路由优先。
//
// 当前版本的已知边界(如实记录,不静默):
// - squad 决策需要 runmgr 的 per-run entry 覆盖才能真正执行(独立设计项);
// 本版本调用方按普通回合处理并保留 Decision 供审计。
// - SetSlot/Cancel/Handoff 命令由库层产出,但 Backend 尚无会话槽位状态机
// 与语义层,Router v1 不消费它们(expectation_hit 在 intentgate 已被折叠为
// fallback,Start 只来自 matched)。
package route
import (
"agent-system-backend/internal/intentgate"
core "agent-system-core"
)
// Kind 是路由决策类型。
type Kind string
const (
KindCommand Kind = "command"
KindWorkflow Kind = "workflow"
KindSquad Kind = "squad"
KindTurn Kind = "turn"
)
// Decision 是一次路由裁决。
type Decision struct {
Kind Kind
CommandID string
Workflow core.WorkflowDef
Blueprint string
Slots map[string]string
Missing []intentgate.SlotQuestion
Reason string // 回落 turn 的原因(显式声明不可解析时)
StartFound bool // 是否存在 Start 命令(runmgr 转轨判定用)
}
// Env 是路由裁决的环境解析器(由调用方注入;nil 视为不可解析)。
type Env struct {
HasCommand func(id string) bool
WorkflowFor func(intentID string) (core.WorkflowDef, bool)
SquadReady bool
}
// Decide 裁决一次识别结果。规则:
// 1. 非 matched(fallback/guard/ambiguous 经闸门折叠后)→ turn。
// 2. 找到首个 Start 命令;没有时按 TopID 走 auto(兼容兜底,与现网一致)。
// 3. 按 ActionRef.Kind 解析:command→命令总线;workflow→工作流;
// blueprint→squad 决策;handler/auto→命令总线 > 工作流 > turn。
func Decide(res intentgate.MatchResult, env Env) Decision {
d := Decision{Kind: KindTurn, Slots: res.Slots, Missing: res.Missing}
if res.Branch != "matched" {
return d
}
var start *intentgate.Command
for i := range res.Commands {
if res.Commands[i].Kind == intentgate.CommandStart {
start = &res.Commands[i]
break
}
}
action := intentgate.ActionRef{Kind: intentgate.ActionAuto, ID: res.ID}
if start != nil {
d.StartFound = true
action = start.Action
if len(start.Slots) > 0 {
d.Slots = start.Slots
}
}
switch action.Kind {
case intentgate.ActionCommand:
if env.HasCommand != nil && env.HasCommand(action.ID) {
d.Kind, d.CommandID = KindCommand, action.ID
return d
}
d.Reason = "声明的命令不存在:" + action.ID
case intentgate.ActionWorkflow:
if env.WorkflowFor != nil {
if def, ok := env.WorkflowFor(action.ID); ok {
d.Kind, d.Workflow = KindWorkflow, def
return d
}
}
d.Reason = "声明的工作流未注册:" + action.ID
case intentgate.ActionBlueprint:
if env.SquadReady {
d.Kind, d.Blueprint = KindSquad, action.ID
return d
}
d.Reason = "声明的小队未启用:" + action.ID
default: // auto / handler
if env.HasCommand != nil && env.HasCommand(action.ID) {
d.Kind, d.CommandID = KindCommand, action.ID
return d
}
if env.WorkflowFor != nil {
if def, ok := env.WorkflowFor(action.ID); ok {
d.Kind, d.Workflow = KindWorkflow, def
return d
}
}
}
return d
}