feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、 builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、 编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。 产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt) 留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
commit
d5dac5a6fc
140 files changed
+26497
No files matched your search
@@ -0,0 +1,98 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"git.gopher.ink/mirrors2/AutoZero/llm"
|
||||
)
|
||||
|
||||
// 回合收尾钩子挂在 Run 的唯一出口 finishTurn 上。这条钩子是"工作区安全网"的
|
||||
// 落点(进展沉淀、checkpoint 留档):入口各写各的就会漏一端——历史上
|
||||
// recordProgress 就只有 webChat 与 cron 调过,终端一直没做。
|
||||
// 所以这里钉三件事:成功一次、失败也一次、子 agent 不重复触发。
|
||||
|
||||
func TestOnTurnEndFiresOncePerRun(t *testing.T) {
|
||||
a, _ := newTestAgent(t)
|
||||
var got []error
|
||||
a.Opt.OnTurnEnd = func(r TurnReport) { got = append(got, r.Err) }
|
||||
a.Provider = &turnEndProvider{reply: "做完了"}
|
||||
|
||||
if err := a.Run(context.Background(), "把这件事做完"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 1 || got[0] != nil {
|
||||
t.Errorf("收尾钩子 = %v, want 恰好一次且 err 为 nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 失败的一轮同样要回调(宿主自己决定不做事),但拿到的必须是那一轮的 error,
|
||||
// 不是 nil——留档逻辑靠这个区分"做到一半崩了"。
|
||||
func TestOnTurnEndReportsFailure(t *testing.T) {
|
||||
a, _ := newTestAgent(t)
|
||||
boom := errors.New("gateway exploded")
|
||||
var got []error
|
||||
a.Opt.OnTurnEnd = func(r TurnReport) { got = append(got, r.Err) }
|
||||
a.Provider = &turnEndProvider{err: boom}
|
||||
|
||||
if err := a.Run(context.Background(), "办不成"); err == nil {
|
||||
t.Fatal("该失败")
|
||||
}
|
||||
if len(got) != 1 || !errors.Is(got[0], boom) {
|
||||
t.Errorf("收尾钩子拿到 %v, want [%v]", got, boom)
|
||||
}
|
||||
}
|
||||
|
||||
// 子 agent 不触发:它的会话是 ephemeral,留档与进展归父级那一次回合,
|
||||
// 否则一次委派就在工作区里多打一个 commit。
|
||||
func TestOnTurnEndSkipsSubAgent(t *testing.T) {
|
||||
a, _ := newTestAgent(t)
|
||||
calls := 0
|
||||
a.Opt.OnTurnEnd = func(TurnReport) { calls++ }
|
||||
a.Provider = &turnEndProvider{reply: "子过程结束"}
|
||||
a.Depth = 1
|
||||
a.Opt.Stderr = io.Discard
|
||||
|
||||
if err := a.Run(context.Background(), "子任务"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Errorf("子 agent 触发了收尾钩子 %d 次,want 0", calls)
|
||||
}
|
||||
}
|
||||
|
||||
// Notify 是宿主侧那句话的入口:没接就安静地什么都不做(不能因为没人收就 panic)。
|
||||
func TestNotifyWithoutHost(t *testing.T) {
|
||||
a, _ := newTestAgent(t)
|
||||
a.Notify("没人听", false)
|
||||
|
||||
var got []string
|
||||
a.Opt.OnNotice = func(text string, failed bool) {
|
||||
if failed {
|
||||
text += "!"
|
||||
}
|
||||
got = append(got, text)
|
||||
}
|
||||
a.Notify("留档 a1b2c3d", false)
|
||||
a.Notify("压缩没压下去", true)
|
||||
if len(got) != 2 || got[0] != "留档 a1b2c3d" || got[1] != "压缩没压下去!" {
|
||||
t.Errorf("OnNotice 收到 %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// turnEndProvider 是一问一答的假网关:不产工具调用,所以 Run 一步就收尾。
|
||||
// (包内已有的 stubProvider 没有失败路径,这里要能按用例造错误。)
|
||||
type turnEndProvider struct {
|
||||
reply string
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *turnEndProvider) Stream(context.Context, string, []llm.Message, []llm.ToolDef,
|
||||
llm.StreamOptions, func(llm.StreamDelta)) (llm.Message, error) {
|
||||
if p.err != nil {
|
||||
return llm.Message{}, p.err
|
||||
}
|
||||
return llm.Message{Role: llm.RoleAssistant, Content: p.reply}, nil
|
||||
}
|
||||
Reference in new issue
Block a user