Why: complete agents delegate; the kernel must not nest executions itself (orchestration stays with the host per the agent-execution standard), so the missing piece is a port plus a tool face. Evidence: go vet ./... clean; go test ./... 14 packages ok. Cases: nil factory registers nothing, full delegation field round-trip (goal/inputs/write_scope/non_goals/done_criteria/deliverables) with sorted file list in the result text, sub-agent failure and factory error propagation, schema-required goal rejection, pre-canceled delegations never starting, and adapter end-to-end wiring through Options.Subagent. Impact: new runtime/subagent package (Request/Result/Factory); the delegation message set matches the agent-execution delegation clause (sub-tasks derive no extra permissions; budget fields are host-set); native.Options gains Subagent. Refs: plan B8; standards/agent-execution.md delegation section.
87 lines
3.0 KiB
Go
87 lines
3.0 KiB
Go
package subagent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"agent-native/runtime/tools"
|
|
)
|
|
|
|
func TestRegisterNilFactoryNoop(t *testing.T) {
|
|
reg := tools.NewRegistry(t.TempDir())
|
|
Register(reg, nil)
|
|
for _, d := range reg.Decls() {
|
|
if d.Name == "delegate_task" {
|
|
t.Fatal("nil factory must not register delegate_task")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDelegateToolRoundTrip(t *testing.T) {
|
|
var got Request
|
|
reg := tools.NewRegistry(t.TempDir())
|
|
Register(reg, func(ctx context.Context, req Request) (Result, error) {
|
|
got = req
|
|
return Result{
|
|
Success: true,
|
|
Text: "调研完成:结论 A。",
|
|
Files: map[string][]byte{"notes.md": []byte("n"), "data/table.csv": []byte("c")},
|
|
}, nil
|
|
})
|
|
res := reg.Execute(context.Background(), "delegate_task", json.RawMessage(`{
|
|
"goal":"调研主题 X",
|
|
"inputs":"docs/v2 规范",
|
|
"write_scope":"research/",
|
|
"non_goals":"不做实现",
|
|
"done_criteria":"产出三来源对照表",
|
|
"deliverables":["notes.md","data/table.csv"]
|
|
}`))
|
|
if res.IsError {
|
|
t.Fatal(res.Content)
|
|
}
|
|
if got.Goal != "调研主题 X" || got.WriteScope != "research/" || got.DoneCriteria != "产出三来源对照表" {
|
|
t.Fatalf("delegation fields lost: %+v", got)
|
|
}
|
|
if !strings.Contains(res.Content, "[sub-agent succeeded]") ||
|
|
!strings.Contains(res.Content, "调研完成") ||
|
|
!strings.Contains(res.Content, "files: data/table.csv, notes.md") {
|
|
t.Fatalf("result text wrong:\n%s", res.Content)
|
|
}
|
|
}
|
|
|
|
func TestDelegateToolFailureAndValidation(t *testing.T) {
|
|
reg := tools.NewRegistry(t.TempDir())
|
|
Register(reg, func(ctx context.Context, req Request) (Result, error) {
|
|
return Result{Success: false, Err: "子任务超出预算"}, nil
|
|
})
|
|
res := reg.Execute(context.Background(), "delegate_task", json.RawMessage(`{"goal":"g","done_criteria":"d"}`))
|
|
if !res.IsError || !strings.Contains(res.Content, "[sub-agent failed]") || !strings.Contains(res.Content, "子任务超出预算") {
|
|
t.Fatalf("failure must propagate, got %+v", res)
|
|
}
|
|
// goal 必填(schema 校验拦截)。
|
|
res = reg.Execute(context.Background(), "delegate_task", json.RawMessage(`{"done_criteria":"d"}`))
|
|
if !res.IsError || !strings.Contains(res.Content, "required argument goal") {
|
|
t.Fatalf("missing goal must fail schema validation, got %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestDelegateToolFactoryErrorAndCancel(t *testing.T) {
|
|
reg := tools.NewRegistry(t.TempDir())
|
|
Register(reg, func(ctx context.Context, req Request) (Result, error) {
|
|
return Result{}, context.Canceled
|
|
})
|
|
res := reg.Execute(context.Background(), "delegate_task", json.RawMessage(`{"goal":"g","done_criteria":"d"}`))
|
|
if !res.IsError || !strings.Contains(res.Content, "sub-agent failed") {
|
|
t.Fatalf("factory error must surface, got %+v", res)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
res = reg.Execute(ctx, "delegate_task", json.RawMessage(`{"goal":"g","done_criteria":"d"}`))
|
|
if !res.IsError || !strings.Contains(res.Content, "canceled before start") {
|
|
t.Fatalf("pre-canceled delegation must not start, got %+v", res)
|
|
}
|
|
}
|