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,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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user