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

+137
View File
@@ -0,0 +1,137 @@
package cdp
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"testing"
)
// fakeBrowserEnv 构造完整的假调试端点:/json/list、/json/new(PUT)、
// 每个 target 的 ws 端点(CDP 协议)。
func fakeBrowserEnv(t *testing.T) (*Browser, *httptest.Server) {
t.Helper()
var mu sync.Mutex
targets := []Target{{
ID: "t1", Title: "Blank", URL: "about:blank", Type: "page",
WebSocketDebuggerURL: "", // srv.URL 已知后再填
}}
var srv *httptest.Server
wsHandler := fakeCDPHandler(t, "Fake Page")
mux := http.NewServeMux()
mux.HandleFunc("/json/list", func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
_ = json.NewEncoder(w).Encode(targets)
})
mux.HandleFunc("/json/new", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
mu.Lock()
targets = append(targets, Target{ID: "t2", Title: "New", URL: r.URL.Query().Get("url"), Type: "page"})
nt := targets[len(targets)-1]
mu.Unlock()
_ = json.NewEncoder(w).Encode(nt)
})
mux.HandleFunc("/devtools/page/t1", wsHandler)
mux.HandleFunc("/devtools/page/t2", wsHandler)
srv = httptest.NewServer(mux)
mu.Lock()
targets[0].WebSocketDebuggerURL = "ws" + strings.TrimPrefix(srv.URL, "http") + "/devtools/page/t1"
mu.Unlock()
br := &Browser{DebugBase: srv.URL}
t.Cleanup(func() { srv.Close(); br.Close() })
return br, srv
}
func TestBrowserToolEndToEnd(t *testing.T) {
br, _ := fakeBrowserEnv(t)
tool := browserToolWith(br)
ctx := context.Background()
// tabs
res, err := tool.Run(ctx, `{"action":"tabs"}`)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(res.Text, "t1 | Blank | about:blank") {
t.Fatalf("tabs: %q", res.Text)
}
// open:navigate + loadEventFired + 读取标题
res, err = tool.Run(ctx, `{"action":"open","url":"https://example.com/x"}`)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(res.Text, "opened https://example.com/x") || !strings.Contains(res.Text, "Fake Page") {
t.Fatalf("open: %q", res.Text)
}
// js:Runtime.evaluate 结果 + 错误路径
res, err = tool.Run(ctx, `{"action":"js","javascript":"document.title"}`)
if err != nil {
t.Fatal(err)
}
if res.Text != "Fake Page" {
t.Fatalf("js: %q", res.Text)
}
// screenshot:PNG 落盘并作为 Image 返回
res, err = tool.Run(ctx, `{"action":"screenshot"}`)
if err != nil {
t.Fatal(err)
}
if len(res.Images) != 1 {
t.Fatalf("screenshot images: %v", res.Images)
}
data, err := os.ReadFile(res.Images[0].Path)
if err != nil {
t.Fatal(err)
}
if string(data[:4]) != "\x89PNG" {
t.Fatalf("not a png: %x", data[:4])
}
os.Remove(res.Images[0].Path)
// 参数校验
if _, err := tool.Run(ctx, `{"action":"open"}`); err == nil {
t.Fatal("open without url should fail")
}
if _, err := tool.Run(ctx, `{"action":"js"}`); err == nil {
t.Fatal("js without javascript should fail")
}
if _, err := tool.Run(ctx, `{"action":"bogus"}`); err == nil {
t.Fatal("unknown action should fail")
}
}
// TestNewTabPUT:无 page target 时经 PUT /json/new 新开 tab。
func TestNewTabPUT(t *testing.T) {
br, srv := fakeBrowserEnv(t)
// 清空 targets:强制走 NewTab
_ = srv
ctx := context.Background()
tg, err := br.NewTab(ctx, "https://example.com/new")
if err != nil {
t.Fatal(err)
}
if tg.ID != "t2" || tg.URL != "https://example.com/new" {
t.Fatalf("new tab: %+v", tg)
}
}
// TestFindBrowserExplicit:显式指定不存在的二进制给出可操作错误。
func TestFindBrowserExplicit(t *testing.T) {
if _, err := findBrowser("/no/such/browser"); err == nil || !strings.Contains(err.Error(), "AUTOGO_BROWSER_BIN") {
t.Fatalf("expected explicit-bin error, got %v", err)
}
}