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

+105
View File
@@ -0,0 +1,105 @@
package session
import (
"os"
"path/filepath"
"strings"
"testing"
"git.gopher.ink/mirrors2/AutoZero/llm"
)
// TestNewNameSkipsExisting:时间戳撞上已有文件时依次加 -2/-3,绝不指向别人的会话。
// 不依赖时钟(直接喂固定 ts),所以不会因跨过一秒而变哑。
func TestNewNameSkipsExisting(t *testing.T) {
dir := t.TempDir()
ts := "20260101-000000"
if got := newName(dir, ts); got != filepath.Join(dir, ts+".jsonl") {
t.Fatalf("干净目录里应直接用基础名,得到 %q", got)
}
writeFile(t, filepath.Join(dir, ts+".jsonl"))
if got := newName(dir, ts); got != filepath.Join(dir, ts+"-2.jsonl") {
t.Fatalf("基础名被占时应给 -2,得到 %q", got)
}
writeFile(t, filepath.Join(dir, ts+"-2.jsonl"))
if got := newName(dir, ts); got != filepath.Join(dir, ts+"-3.jsonl") {
t.Fatalf("前两个都被占时应给 -3,得到 %q", got)
}
}
// TestNewDoesNotRecoverOldSession:连续两次新建必须拿到不同名字,第二份还是空的。
// 撞名的真实后果:/session new 后立刻发消息(文件写下内容),同一秒内再新建,
// 前端拿 LoadOrCreate 打开同名文件,"新建"就变成了把旧会话整份恢复回来。
func TestNewDoesNotRecoverOldSession(t *testing.T) {
t.Setenv("HOME", t.TempDir())
first, err := New()
if err != nil {
t.Fatalf("New: %v", err)
}
if err := first.Append(llm.Message{Role: llm.RoleUser, Content: "history"}); err != nil {
t.Fatalf("append: %v", err)
}
second, err := New()
if err != nil {
t.Fatalf("New: %v", err)
}
if second.Path == first.Path {
t.Fatalf("两次新建拿到同一个名字 %s", second.Path)
}
if len(second.Messages) != 0 {
t.Fatalf("新会话应为空,得到 %d 条", len(second.Messages))
}
if _, err := os.Stat(second.Path); !os.IsNotExist(err) {
t.Fatalf("新会话文件不该在这一步出现(err=%v)", err)
}
// 经渠道/前端实际会走的路径再打开一次,仍然是空的
byName := strings.TrimSuffix(filepath.Base(second.Path), ".jsonl")
again, err := LoadOrCreate(byName)
if err != nil {
t.Fatalf("LoadOrCreate: %v", err)
}
if len(again.Messages) != 0 {
t.Fatalf("按名字打开新会话却拿到 %d 条消息", len(again.Messages))
}
// 第一份的历史不受影响
reloaded, err := Load(first.Path)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(reloaded.Messages) != 1 || reloaded.Messages[0].Content != "history" {
t.Fatalf("旧会话被改动: %+v", reloaded.Messages)
}
}
// TestNewNameSkipsUnwrittenIssued:两份新会话都还没落盘时也不能同名。
// 会话文件要到第一条消息才写出来,所以"盘上没有"不等于"没人用"。
func TestNewNameSkipsUnwrittenIssued(t *testing.T) {
dir := t.TempDir()
ts := "20260202-120000"
first := newName(dir, ts)
second := newName(dir, ts)
if first == second {
t.Fatalf("两次取名拿到同一个名字 %s(第一份还没落盘,Stat 看不见)", first)
}
if strings.TrimSuffix(filepath.Base(second), ".jsonl") != ts+"-2" {
t.Fatalf("第二个名字 = %q,want %s-2.jsonl", second, ts)
}
// 已发出的名字不许被复用
if got := newName(dir, ts); got == first || got == second {
t.Fatalf("复用了已发出的名字: %q", got)
}
}
func writeFile(t *testing.T, path string) {
t.Helper()
if err := os.WriteFile(path, []byte("{}\n"), 0o644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}