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:
Sakurasan committed 2026-10-26 00:54:46 +08:00
commit d5dac5a6fc
140 files changed
+26497

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
package agent
import (
"context"
"testing"
"git.gopher.ink/mirrors2/AutoZero/llm"
"git.gopher.ink/mirrors2/AutoZero/tools"
)
// TestSteerDeliveredAtStepBoundary:插话必须在工具结果回填之后、下一次模型调用
// 之前落成普通 user 消息;模型下一步就能看到它。
func TestSteerDeliveredAtStepBoundary(t *testing.T) {
calls := 0
prov := &scriptedProvider{fn: func(call int, _ string, _ []llm.Message, _ []llm.ToolDef) (llm.Message, error) {
calls++
if call == 1 {
return llm.Message{Role: llm.RoleAssistant, ToolCalls: []llm.ToolCall{
{ID: "t1", Name: "noop", Input: "{}"},
}}, nil
}
return llm.Message{Role: llm.RoleAssistant, Content: "done"}, nil
}}
noop := &tools.Tool{Name: "noop", Run: func(context.Context, string) (tools.Result, error) {
return tools.TextResult("ok"), nil
}}
a, sess := newTestAgent(t, noop)
a.Provider = prov
pending := []SteerMessage{{Text: "also fix the typo"}}
a.Opt.Steer = func() []SteerMessage {
out := pending
pending = nil
return out
}
var steered []SteerMessage
a.Opt.OnSteer = func(m SteerMessage) { steered = append(steered, m) }
if err := a.Run(context.Background(), "do it"); err != nil {
t.Fatal(err)
}
if calls != 2 {
t.Fatalf("model calls = %d, want 2", calls)
}
if len(steered) != 1 || steered[0].Text != "also fix the typo" {
t.Fatalf("OnSteer = %+v", steered)
}
steerAt, toolAt := -1, -1
for i, m := range sess.Messages {
if m.Role == llm.RoleUser && m.Content == "also fix the typo" {
steerAt = i
}
if m.Role == llm.RoleTool {
toolAt = i
}
}
if steerAt < 0 || toolAt < 0 || steerAt < toolAt {
t.Fatalf("steer must land after the tool result (tool=%d steer=%d): %+v", toolAt, steerAt, sess.Messages)
}
}
// TestSteerContinuesAnOtherwiseCompletedTurn:模型已经说完了,但宿主还有插话——
// 不能直接收尾,要把插话落盘再跑一步。这保证"回合结束才发"的旧排队语义也能
// 在同一回合内被消费,而不是等下一次用户手动发送。
func TestSteerContinuesAnOtherwiseCompletedTurn(t *testing.T) {
calls := 0
prov := &scriptedProvider{fn: func(call int, _ string, _ []llm.Message, _ []llm.ToolDef) (llm.Message, error) {
calls++
if call == 1 {
return llm.Message{Role: llm.RoleAssistant, Content: "first done"}, nil
}
return llm.Message{Role: llm.RoleAssistant, Content: "second done"}, nil
}}
a, sess := newTestAgent(t)
a.Provider = prov
pending := []SteerMessage{{Text: "one more thing"}}
a.Opt.Steer = func() []SteerMessage {
out := pending
pending = nil
return out
}
if err := a.Run(context.Background(), "start"); err != nil {
t.Fatal(err)
}
if calls != 2 {
t.Fatalf("model calls = %d, want 2 (the steer must not be dropped)", calls)
}
if len(sess.Messages) < 4 {
t.Fatalf("messages = %+v", sess.Messages)
}
last := sess.Messages[len(sess.Messages)-1]
prev := sess.Messages[len(sess.Messages)-2]
if prev.Role != llm.RoleUser || prev.Content != "one more thing" || last.Content != "second done" {
t.Fatalf("steer was not answered in the same turn: %+v", sess.Messages)
}
}