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

+121
View File
@@ -0,0 +1,121 @@
package tools
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHTMLToText(t *testing.T) {
in := `<!DOCTYPE html><html><head><title> Doc
Title </title><meta charset="utf-8"><style>body{color:red}</style></head>
<body><h1> Hello </h1><script>alert("x")</script>
<p>a &amp; b<br>c</p><div>tail&nbsp;text</div><span>inline</span></body></html>`
out := htmlToText([]byte(in))
if !strings.Contains(out, "title: Doc Title") {
t.Fatalf("title not extracted: %q", out)
}
if strings.Contains(out, "alert") || strings.Contains(out, "color:red") {
t.Fatalf("script/style leaked: %q", out)
}
if !strings.Contains(out, "a & b") {
t.Fatalf("entity not decoded: %q", out)
}
// 块级元素分行:Hello 与 a & b 应在不同行。
if !strings.Contains(out, "Hello\na & b") {
t.Fatalf("block separation wrong: %q", out)
}
// &nbsp; 由解析器解码为普通空格,行内文本不粘连;div 为块级元素,前后换行。
if !strings.Contains(out, "tail text\ninline") {
t.Fatalf("inline text mangled: %q", out)
}
}
func TestHTMLToTextEmpty(t *testing.T) {
if out := htmlToText([]byte("<html><head></head><body></body></html>")); out != "" {
t.Fatalf("expected empty, got %q", out)
}
}
func TestFetchTool(t *testing.T) {
SetAllowedRoots() // 不限制(fetch 不走路径围栏,显式声明意图)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/page":
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("<html><title>ok</title><body><p>hello fetch</p></body></html>"))
case "/json":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"a":1}`))
case "/gone":
w.WriteHeader(http.StatusNotFound)
case "/binary":
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write([]byte{0x89, 'P', 'N', 'G'})
case "/ua":
if ua := r.Header.Get("User-Agent"); !strings.Contains(ua, "Chrome") {
w.WriteHeader(http.StatusTeapot)
_, _ = w.Write([]byte("ua=" + ua))
return
}
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte("chrome impersonated"))
default:
w.WriteHeader(http.StatusTeapot)
}
}))
defer srv.Close()
call := func(url string) (string, error) {
f := Fetch()
res, err := f.Run(context.Background(), `{"url":"`+url+`"}`)
if err != nil {
return "", err
}
return res.Text, nil
}
// HTML 转文本。
out, err := call(srv.URL + "/page")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out, "title: ok") || !strings.Contains(out, "hello fetch") {
t.Fatalf("unexpected page result: %q", out)
}
// JSON 原样返回。
out, err = call(srv.URL + "/json")
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(out) != `{"a":1}` {
t.Fatalf("unexpected json result: %q", out)
}
// 非 2xx 报错。
if _, err = call(srv.URL + "/gone"); err == nil || !strings.Contains(err.Error(), "404") {
t.Fatalf("expected 404 error, got %v", err)
}
// 二进制内容报错并引导换工具。
if _, err = call(srv.URL + "/binary"); err == nil || !strings.Contains(err.Error(), "browser") {
t.Fatalf("expected binary guidance error, got %v", err)
}
// 指纹仿真生效:默认 Chrome UA。
out, err = call(srv.URL + "/ua")
if err != nil {
t.Fatal(err)
}
if out != "chrome impersonated" {
t.Fatalf("unexpected ua result: %q", out)
}
// 非法 URL。
if _, err = call("ftp://x"); err == nil {
t.Fatal("expected invalid url error")
}
}