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