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

+117
View File
@@ -0,0 +1,117 @@
// Package spill 把超长的工具结果落盘,并返回一个模型可用 read 工具取回的路径。
// 动机:视图层为省上下文会截断超长 tool result,模型因此拿不回原文;落盘后
// 截断提示里带上路径,模型需要时能读回全文。
package spill
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// maxAge 是落盘文件的保留时长,超过即在 Prune 时删除(防无限增长)。
const maxAge = 7 * 24 * time.Hour
// Dir 返回落盘目录(~/.autogo/results)。与 sessions/images/logs 同惯例;
// 取不到 HOME 时退回相对目录。
func Dir() string {
home, err := os.UserHomeDir()
if err != nil || home == "" {
return filepath.Join(".autogo", "results")
}
return filepath.Join(home, ".autogo", "results")
}
// Save 把 text 写入 dir 并返回文件路径。
// 文件名优先用消毒后的 callID(同一工具调用幂等、可读),为空时退回内容哈希。
// 已存在同路径文件则直接复用,不重复写。
func Save(dir, callID, text string) (string, error) {
if dir == "" {
return "", fmt.Errorf("spill: empty dir")
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", err
}
name := sanitize(callID)
if name == "" {
sum := sha256.Sum256([]byte(text))
name = "sha-" + hex.EncodeToString(sum[:])[:16]
}
path := filepath.Join(dir, name+".txt")
if _, err := os.Stat(path); err == nil {
return path, nil // 幂等:内容已落盘
}
// tmp + rename 原子写,避免读到半截文件。
tmp, err := os.CreateTemp(dir, name+".*.tmp")
if err != nil {
return "", err
}
tmpName := tmp.Name()
if _, err := tmp.WriteString(text); err != nil {
tmp.Close()
os.Remove(tmpName)
return "", err
}
if err := tmp.Close(); err != nil {
os.Remove(tmpName)
return "", err
}
if err := os.Rename(tmpName, path); err != nil {
os.Remove(tmpName)
return "", err
}
return path, nil
}
// Prune 删除 dir 下超过 maxAge 的文件;目录不存在时静默返回。
func Prune(dir string) {
entries, err := os.ReadDir(dir)
if err != nil {
return
}
cutoff := time.Now().Add(-maxAge)
for _, e := range entries {
if e.IsDir() {
continue
}
info, err := e.Info()
if err != nil || info.ModTime().After(cutoff) {
continue
}
os.Remove(filepath.Join(dir, e.Name()))
}
}
// sanitize 把 callID 收敛为安全的文件名片段:只留字母数字与 . _ -,
// 其余替换为 _,并丢弃 . / .. 等特殊值(防路径穿越)。
func sanitize(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
var b strings.Builder
for _, r := range s {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9',
r == '_', r == '-':
b.WriteRune(r)
case r == '.':
b.WriteRune('_') // 不留 ".",杜绝 ".." 之类
default:
b.WriteRune('_')
}
}
out := b.String()
if out == "" {
return ""
}
// 限长,避免超长 callID 造成文件名过长。
if len(out) > 64 {
out = out[:64]
}
return out
}