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

+186
View File
@@ -0,0 +1,186 @@
package cdp
import (
"os"
"path/filepath"
"strings"
"testing"
)
// writeFile 建一个带内容的文件(自动建父目录)。
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
}
// TestCheckProfileWarnings:三个最常见的坑都要被明确讲出来。
//
// 这三条正是"配了 profile 却用不上登录态"的真实原因:
// 1. 指到了 profile 本体(Default/ 里面)而不是 user-data-dir;
// 2. 指到了 Chrome 默认目录(Chrome 136+ 禁止在其上开远程调试,且日常 Chrome 占着);
// 3. 那份 profile 正被一个运行中的 Chrome 占用。
func TestCheckProfileWarnings(t *testing.T) {
t.Run("目录不存在", func(t *testing.T) {
warns := CheckProfile(filepath.Join(t.TempDir(), "nope"))
if len(warns) != 1 || !strings.Contains(warns[0], "不存在") {
t.Fatalf("应提示目录不存在,实际 %v", warns)
}
})
t.Run("指向 profile 本体", func(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "Cookies"), "x")
warns := CheckProfile(dir)
if !hasSubstr(warns, "父目录") {
t.Fatalf("应提示 --user-data-dir 要指向父目录,实际 %v", warns)
}
})
t.Run("指向 Chrome 默认目录", func(t *testing.T) {
cands := defaultUserDataDirCandidates()
if len(cands) == 0 {
t.Skip("当前平台没有已知的默认目录")
}
// 直接对候选路径做判断(纯字符串逻辑,不需要它真的存在)。
if !IsChromeDefaultDir(cands[0]) {
t.Fatalf("IsChromeDefaultDir(%q) 应为 true", cands[0])
}
if IsChromeDefaultDir(filepath.Join(t.TempDir(), "somecopy")) {
t.Error("非默认目录不该被判为默认目录")
}
})
t.Run("被运行中的 Chrome 占用", func(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "Local State"), "{}")
writeFile(t, filepath.Join(dir, "SingletonLock"), "")
warns := CheckProfile(dir)
if !hasSubstr(warns, "SingletonLock") {
t.Fatalf("应提示 profile 被占用,实际 %v", warns)
}
})
}
func hasSubstr(list []string, sub string) bool {
for _, s := range list {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// TestCopyProfile:复制要**保留登录态与扩展、跳过缓存、不带单实例锁**。
func TestCopyProfile(t *testing.T) {
src := t.TempDir()
dst := filepath.Join(t.TempDir(), "copy")
// 要保留的:登录态与扩展。
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "Default", "Cookies"), "cookies-db")
writeFile(t, filepath.Join(src, "Default", "Preferences"), "{}")
writeFile(t, filepath.Join(src, "Default", "Extensions", "abc", "manifest.json"), "{}")
// 要跳过的:缓存(体积大且与登录态无关)。
writeFile(t, filepath.Join(src, "Default", "Cache", "big.bin"), strings.Repeat("x", 4096))
writeFile(t, filepath.Join(src, "Default", "Code Cache", "big.bin"), strings.Repeat("y", 4096))
writeFile(t, filepath.Join(src, "Default", "Service Worker", "s.bin"), "z")
n, err := CopyProfile(src, dst)
if err != nil {
t.Fatalf("复制失败: %v", err)
}
if n == 0 {
t.Error("复制字节数为 0")
}
for _, want := range []string{
filepath.Join("Default", "Cookies"),
filepath.Join("Default", "Preferences"),
filepath.Join("Default", "Extensions", "abc", "manifest.json"),
"Local State",
} {
if _, err := os.Stat(filepath.Join(dst, want)); err != nil {
t.Errorf("应保留 %s:%v", want, err)
}
}
for _, skip := range []string{
filepath.Join("Default", "Cache"),
filepath.Join("Default", "Code Cache"),
filepath.Join("Default", "Service Worker"),
} {
if _, err := os.Stat(filepath.Join(dst, skip)); err == nil {
t.Errorf("缓存目录应被跳过:%s", skip)
}
}
if !LooksLikeBrowserProfile(dst) {
t.Error("复制结果应当被识别为浏览器 profile")
}
}
// TestCopyProfileRefusesRunningChrome:源被占用(SingletonLock)时必须拒绝——
// 运行中复制的 profile 可能不一致,而且锁文件带过去会让 Chrome 犯迷糊。
func TestCopyProfileRefusesRunningChrome(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "SingletonLock"), "")
if _, err := CopyProfile(src, filepath.Join(t.TempDir(), "copy")); err == nil {
t.Fatal("源被占用时应拒绝复制")
} else if !strings.Contains(err.Error(), "退出 Chrome") {
t.Errorf("错误应提示先退出 Chrome:%v", err)
}
}
// TestCopyProfileNoSingletonInDst:目标里不该出现单实例锁,否则 Chrome 会以为另有一个实例在跑。
func TestCopyProfileNoSingletonInDst(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "Default", "Cookies"), "c")
dst := filepath.Join(t.TempDir(), "copy")
if _, err := CopyProfile(src, dst); err != nil {
t.Fatal(err)
}
for _, f := range []string{"SingletonLock", "SingletonSocket", "SingletonCookie"} {
if _, err := os.Lstat(filepath.Join(dst, f)); err == nil {
t.Errorf("目标不该有 %s", f)
}
}
}
// TestCopyProfileRejectsProfileBodyAsSource:源指向 profile 本体时拒绝并说清该指哪。
func TestCopyProfileRejectsProfileBodyAsSource(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Cookies"), "x")
if _, err := CopyProfile(src, filepath.Join(t.TempDir(), "copy")); err == nil {
t.Fatal("源是 profile 本体时应拒绝")
}
}
// TestPersistentProfileDir:默认目录必须是**固定**路径。
//
// 旧的实现用 os.MkdirTemp——每次都给出一个新目录,于是每次进程启动都像一台从没
// 登录过的新机器(登录一次也留不住)。这里断言的关键性质就是"两次调用同一个路径"。
func TestPersistentProfileDir(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
dir, err := PersistentProfileDir()
if err != nil {
t.Fatal(err)
}
want := filepath.Join(home, ".autogo", "browser-profile")
if dir != want {
t.Errorf("默认 profile = %q, want %q", dir, want)
}
again, err := PersistentProfileDir()
if err != nil {
t.Fatal(err)
}
if again != dir {
t.Errorf("两次调用应返回同一路径(临时目录才会每次不同):%q vs %q", dir, again)
}
}