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

+113
View File
@@ -0,0 +1,113 @@
package cdp
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/coder/websocket"
)
// tinyPNG 是 1x1 红色 PNG 的 base64(截图测试用)。
const tinyPNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
// fakeCDPHandler 处理一条 CDP 命令并回响应;navigate 先发事件再回响应,
// 用于验证"先订阅后触发"的事件时序。
func fakeCDPHandler(t *testing.T, title string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil)
if err != nil {
return
}
defer c.Close(websocket.StatusInternalError, "")
ctx := r.Context()
for {
_, data, err := c.Read(ctx)
if err != nil {
return
}
var req struct {
ID int64 `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
if err := json.Unmarshal(data, &req); err != nil {
continue
}
reply := func(v map[string]any) {
b, _ := json.Marshal(map[string]any{"id": req.ID, "result": v})
_ = c.Write(ctx, websocket.MessageText, b)
}
switch req.Method {
case "Page.enable":
reply(map[string]any{})
case "Page.navigate":
// 事件先于响应到达:验证 Subscribe 必须先行
ev, _ := json.Marshal(map[string]any{"method": "Page.loadEventFired", "params": map[string]any{}})
_ = c.Write(ctx, websocket.MessageText, ev)
reply(map[string]any{"frameId": "f1"})
case "Runtime.evaluate":
reply(map[string]any{"result": map[string]any{"type": "string", "value": title}})
case "Page.captureScreenshot":
reply(map[string]any{"data": tinyPNG})
default:
b, _ := json.Marshal(map[string]any{"id": req.ID, "error": map[string]any{"code": -32601, "message": "unknown method"}})
_ = c.Write(ctx, websocket.MessageText, b)
}
}
}
}
// TestConnCallAndEventTiming:事件先到时 Call 仍能配对响应、Subscribe 先行能等到事件。
func TestConnCallAndEventTiming(t *testing.T) {
srv := httptest.NewServer(fakeCDPHandler(t, "Fake Page"))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/devtools/page/t1"
ctx := context.Background()
conn, err := Dial(ctx, wsURL)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
loaded := conn.Subscribe("Page.loadEventFired")
if _, err := conn.Call(ctx, "Page.enable", nil); err != nil {
t.Fatal(err)
}
if _, err := conn.Call(ctx, "Page.navigate", map[string]any{"url": "https://example.com"}); err != nil {
t.Fatalf("navigate: %v", err)
}
// 事件在响应前已发出:订阅先行则必然能等到
if _, err := loaded.Wait(ctx, 2*time.Second); err != nil {
t.Fatalf("event wait: %v", err)
}
title, err := evaluateString(ctx, conn, "document.title")
if err != nil || title != "Fake Page" {
t.Fatalf("evaluate: %q %v", title, err)
}
// 服务端错误响应 → Go error
if _, err := conn.Call(ctx, "NoSuch.Method", nil); err == nil || !strings.Contains(err.Error(), "unknown method") {
t.Fatalf("expected rpc error, got %v", err)
}
}
// TestConnClosed:连接断开后 Call 立即失败。
func TestConnClosed(t *testing.T) {
srv := httptest.NewServer(fakeCDPHandler(t, "x"))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/devtools/page/t1"
ctx := context.Background()
conn, err := Dial(ctx, wsURL)
if err != nil {
t.Fatal(err)
}
conn.Close()
if _, err := conn.Call(ctx, "Page.enable", nil); err == nil {
t.Fatal("call on closed connection should fail")
}
}