Merge pull request 'Add the explicit spec.action declaration (I1 WP2)' (#7) from feature/agent-stack-i1 into main
This commit was merged in pull request #7.
This commit is contained in:
+5
-2
@@ -57,9 +57,12 @@ type Command struct {
|
||||
Target string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
// actionOf 从候选推导动作引用:显式 blueprint 键优先于 Handler,
|
||||
// 都没有时回落 auto(宿主裁决)。
|
||||
// actionOf 从候选推导动作引用:spec.action 显式声明优先,
|
||||
// 其后是 blueprint 键、Handler,都没有时回落 auto(宿主裁决)。
|
||||
func actionOf(c Candidate) ActionRef {
|
||||
if c.Action != nil {
|
||||
return *c.Action
|
||||
}
|
||||
if bp := strings.TrimSpace(c.Metadata["blueprint"]); bp != "" {
|
||||
return ActionRef{Kind: ActionBlueprint, ID: bp}
|
||||
}
|
||||
|
||||
@@ -162,3 +162,44 @@ type listExpectation struct{ ids []string }
|
||||
func (s listExpectation) Pending(ctx context.Context) *PendingExpectation {
|
||||
return &PendingExpectation{IntentIDs: s.ids}
|
||||
}
|
||||
|
||||
// TestSpecActionDeclaredWins:spec.action 显式声明在 ActionRef 推导中胜出(WP2)。
|
||||
func TestSpecActionDeclaredWins(t *testing.T) {
|
||||
b := NewRegistryBuilder(Options{})
|
||||
if err := b.Register(IntentSpec{
|
||||
ID: "report.explicit", Domain: "report", Version: 1,
|
||||
Matcher: MatchSpec{IncludesAny: []string{"显式动作"}},
|
||||
Action: &ActionRef{Kind: ActionWorkflow, ID: "wf-report"},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reg, err := b.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res := recognize(t, reg.Snapshot(), "显式动作")
|
||||
if len(res.Commands) != 1 || res.Commands[0].Kind != CommandStart {
|
||||
t.Fatalf("commands = %+v", res.Commands)
|
||||
}
|
||||
if got := res.Commands[0].Action; got.Kind != ActionWorkflow || got.ID != "wf-report" {
|
||||
t.Fatalf("action = %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpecActionValidation:action.kind 非法与 id 为空都被拒绝。
|
||||
func TestSpecActionValidation(t *testing.T) {
|
||||
bad := IntentSpec{
|
||||
ID: "report.bad", Domain: "report", Version: 1,
|
||||
Matcher: MatchSpec{IncludesAny: []string{"x"}},
|
||||
Action: &ActionRef{Kind: "bogus", ID: "y"},
|
||||
}
|
||||
b := NewRegistryBuilder(Options{})
|
||||
if err := b.Register(bad); err == nil {
|
||||
t.Fatal("bogus action kind accepted")
|
||||
}
|
||||
bad.Action = &ActionRef{Kind: ActionCommand, ID: ""}
|
||||
b2 := NewRegistryBuilder(Options{})
|
||||
if err := b2.Register(bad); err == nil {
|
||||
t.Fatal("empty action id accepted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +420,10 @@ func (h specHit) candidate(input string, withSlots bool) Candidate {
|
||||
if c.Risk == "" {
|
||||
c.Risk = RiskLow
|
||||
}
|
||||
if cs.spec.Action != nil {
|
||||
ref := *cs.spec.Action
|
||||
c.Action = &ref
|
||||
}
|
||||
if len(cs.spec.Metadata) > 0 {
|
||||
c.Metadata = make(map[string]string, len(cs.spec.Metadata))
|
||||
for k, v := range cs.spec.Metadata {
|
||||
|
||||
@@ -35,6 +35,9 @@ type Candidate struct {
|
||||
// InvalidSlots 捕获到但未通过确定性校验的槽位(key→原因,如枚举不合法)。
|
||||
// 必填且非法只进此处(不重复进 Missing),宿主可区分"没给"与"给错了"。
|
||||
InvalidSlots map[string]string
|
||||
// Action 是 spec 的显式动作声明(I1 WP2),随候选透传;nil 表示未声明,
|
||||
// ActionRef 推导回落 blueprint/Handler/auto 顺序。
|
||||
Action *ActionRef
|
||||
// Reason 命中解释:命中词与模式,供宿主审计与 intentctl explain 复用。
|
||||
Reason string
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ type IntentSpec struct {
|
||||
RequiresConfirm bool `json:"requires_confirm,omitempty"`
|
||||
Handler string `json:"handler,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
// Action 为显式动作声明(I1 WP2 四键统一的首选来源):
|
||||
// 声明后优先于 Handler 与 Metadata["blueprint"] 被 ActionRef 采用。
|
||||
// kind 必须是 command/workflow/blueprint/handler 之一。
|
||||
Action *ActionRef `json:"action,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -131,6 +135,16 @@ func validateSpec(spec IntentSpec, lim limits, scoped map[string]SlotValidator)
|
||||
default:
|
||||
return newError(InvalidSpec, "risk 只能是 low/medium/high 或留空", "field", "risk", "risk", spec.Risk)
|
||||
}
|
||||
if spec.Action != nil {
|
||||
switch spec.Action.Kind {
|
||||
case ActionCommand, ActionWorkflow, ActionBlueprint, ActionHandler:
|
||||
default:
|
||||
return newError(InvalidSpec, "action.kind 必须是 command/workflow/blueprint/handler", "field", "action.kind", "action.kind", spec.Action.Kind)
|
||||
}
|
||||
if strings.TrimSpace(spec.Action.ID) == "" {
|
||||
return newError(InvalidSpec, "action.id 为空", "field", "action.id", "id", spec.ID)
|
||||
}
|
||||
}
|
||||
|
||||
m := spec.Matcher
|
||||
for _, terms := range []struct {
|
||||
|
||||
Reference in New Issue
Block a user