Details (original message): Add catalog-driven orchestration: intent catalog injection, deterministic plan validation, and system-function steps.
123 lines
4.1 KiB
Go
123 lines
4.1 KiB
Go
package squadintent
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
"testing/fstest"
|
||
|
||
"agent-intent/intent"
|
||
"agent-squad/squad"
|
||
)
|
||
|
||
// catalogFS:带槽位(label/question/enum)的可调用能力 + 守卫。
|
||
func catalogFS() fstest.MapFS {
|
||
return fstest.MapFS{"intents.json": &fstest.MapFile{Data: []byte(`{
|
||
"intents": [
|
||
{"id": "guard.config_change", "domain": "guard", "version": 1,
|
||
"matcher": {"includes_any": ["系统配置"]}},
|
||
{"id": "demo.run.status_query", "domain": "demo", "version": 1,
|
||
"description": "查询运行进度",
|
||
"matcher": {"includes_any": ["进度"]},
|
||
"handler": "taskrun.query_status",
|
||
"slots": [
|
||
{"key": "run_id", "required": true, "pattern": "(run-\\d+)",
|
||
"label": "运行编号", "question": "要查询哪个运行?请提供 run 编号"}
|
||
]}
|
||
]
|
||
}`)}}
|
||
}
|
||
|
||
func newCatalogGate(t *testing.T) *Gate {
|
||
t.Helper()
|
||
g, err := New(context.Background(), Options{
|
||
IntentOptions: intent.Options{GuardDomains: []string{"guard"}, ProtectedDomains: []string{"guard"}},
|
||
FS: catalogFS(),
|
||
Roots: []string{"."},
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return g
|
||
}
|
||
|
||
func TestGateCatalogMapsSpecsAndSlots(t *testing.T) {
|
||
g := newCatalogGate(t)
|
||
c := g.Catalog()
|
||
if c == nil || len(c.Specs) != 1 {
|
||
t.Fatalf("catalog specs: %+v", c)
|
||
}
|
||
view, ok := c.Get("demo.run.status_query")
|
||
if !ok || view.Handler != "taskrun.query_status" || len(view.RequiredSlots) != 1 || view.RequiredSlots[0] != "run_id" {
|
||
t.Fatalf("view: %+v", view)
|
||
}
|
||
if len(view.Slots) != 1 || view.Slots[0].Question == "" || view.Slots[0].Label != "运行编号" {
|
||
t.Fatalf("slot view: %+v", view.Slots)
|
||
}
|
||
if _, ok := c.Get("guard.config_change"); ok {
|
||
t.Fatal("guard spec must not appear in catalog")
|
||
}
|
||
if !strings.Contains(c.Markdown, "提问建议:要查询哪个运行") {
|
||
t.Fatalf("markdown missing question: %s", c.Markdown)
|
||
}
|
||
}
|
||
|
||
func TestRunnerDispatchesToHandlerAndFailsClosed(t *testing.T) {
|
||
g := newCatalogGate(t)
|
||
var got squad.IntentStep
|
||
runner := g.Runner(map[string]IntentHandler{
|
||
"taskrun.query_status": func(ctx context.Context, step squad.IntentStep) (string, error) {
|
||
got = step
|
||
return "# 进度\n\nrun-42 已完成,正文满足最小长度要求用于质检演示。\n", nil
|
||
},
|
||
})
|
||
res, err := runner.Run(context.Background(), squad.IntentStep{
|
||
IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-42"},
|
||
Targets: []string{"output.md"},
|
||
})
|
||
if err != nil || res == nil || !res.Success {
|
||
t.Fatalf("run: %+v %v", res, err)
|
||
}
|
||
if got.Slots["run_id"] != "run-42" || string(res.Patches["output.md"]) == "" {
|
||
t.Fatalf("handler dispatch: %+v", got)
|
||
}
|
||
|
||
// 未知意图与未注册 handler:fail-closed(无 handler 的 runner 拒绝执行)。
|
||
if _, err := runner.Run(context.Background(), squad.IntentStep{IntentID: "demo.nope"}); err == nil {
|
||
t.Fatal("expected unknown intent error")
|
||
}
|
||
bare := g.Runner(nil)
|
||
if _, err := bare.Run(context.Background(), squad.IntentStep{IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-1"}}); err == nil {
|
||
t.Fatal("expected unregistered handler error")
|
||
} else if !strings.Contains(err.Error(), "fail-closed") {
|
||
t.Fatalf("error: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestEchoRunnerMaterializesCall(t *testing.T) {
|
||
g := newCatalogGate(t)
|
||
res, err := g.EchoRunner().Run(context.Background(), squad.IntentStep{
|
||
IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-7"},
|
||
Targets: []string{"output.md"}, Title: "查询进度",
|
||
})
|
||
if err != nil || !res.Success {
|
||
t.Fatalf("echo run: %+v %v", res, err)
|
||
}
|
||
text := string(res.Patches["output.md"])
|
||
for _, want := range []string{"EchoRunner", "demo.run.status_query", "run-7", "未真实执行"} {
|
||
if !strings.Contains(text, want) {
|
||
t.Fatalf("echo text missing %q: %s", want, text)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestNilGateCatalogAndRunner(t *testing.T) {
|
||
var g *Gate
|
||
if g.Catalog() != nil {
|
||
t.Fatal("nil gate catalog must be nil")
|
||
}
|
||
if _, err := g.EchoRunner().Run(context.Background(), squad.IntentStep{IntentID: "x"}); err == nil {
|
||
t.Fatal("nil gate runner must fail-closed")
|
||
}
|
||
}
|