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

+227
View File
@@ -0,0 +1,227 @@
package tools
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
// 路径围栏:read/write/edit/ls/grep 只允许访问允许根内的路径。
// 这是硬边界而非信任门控——`--yes` 也不能越过。
//
// 围栏是实例(Fence),不是进程级全局状态:每个会话按其工作区现场组装
// 自己的围栏(工作区目录 + 该工作区的额外授权 + spill/memory),A 工作区
// 导入的目录 B 工作区的会话看不到。包级的 Default() 围栏只服务默认工作区
// (进程 cwd)场景,历史 API(SetAllowedRoots 等)是它的薄封装。
//
// 每个根带访问模式:rw 可读写;ro 只读——read/ls/grep 放行,write/edit 拒绝。
// ro 的典型场景:导入别人的项目只做分析,防止模型改坏。
//
// bash 边界(有意为之,非遗漏):bash 不做逐路径校验,只在 cmd.Dir 绑定
// 工作区。ZCode 对持久 shell 有 bash-cwd-policy(命令结束后把逃逸的 cwd
// 重置回工作区),AutoGo 的 bash 是无状态的 `sh -c`,每次调用都在工作区
// 重新开始,cwd 不存在跨调用逃逸,该策略在此不适用。bash 仍可读写围栏外
// 文件——ro 挡的是文件类工具的误写,不是恶意进程,完整沙箱见 TODO P2。
// RootMode 是允许根的访问模式。
type RootMode string
const (
ModeRW RootMode = "rw"
ModeRO RootMode = "ro"
)
// allowedRoot 是围栏里的一个允许根。removable=false 是固有根
// (cwd/工作区目录/spill/memory),RemoveRoot 不允许摘掉它们。
type allowedRoot struct {
path string // 已解析(符号链接收敛)的绝对路径
mode RootMode
removable bool
}
// Fence 是一组允许根的实例。零值可用(空围栏 = 不限制,供测试)。
type Fence struct {
mu sync.RWMutex
roots []allowedRoot
}
// NewFence 创建空围栏。
func NewFence() *Fence { return &Fence{} }
var defaultFence = NewFence()
// Default 返回包级默认围栏(默认工作区 / 进程 cwd 场景用)。
func Default() *Fence { return defaultFence }
func normalizeRoot(r string) (string, bool) {
if r == "" {
return "", false
}
abs, err := filepath.Abs(expandHome(r))
if err != nil {
return "", false
}
// macOS 的 /var -> /private/var 等系统符号链接:根与路径必须
// 在同一(解析后的)坐标系里比较。
return evalPath(abs), true
}
// SetRoots 整体替换允许根(全部 rw、不可移除;传空 = 不限制,供测试使用)。
func (f *Fence) SetRoots(roots ...string) {
f.mu.Lock()
defer f.mu.Unlock()
f.roots = nil
for _, r := range roots {
if p, ok := normalizeRoot(r); ok {
f.roots = append(f.roots, allowedRoot{path: p, mode: ModeRW})
}
}
}
// AddRoot 追加一个允许根。要求 path 是已存在的目录;已存在时只更新模式。
// removable=false 标记固有根(工作区目录),不可被 RemoveRoot 摘除。
func (f *Fence) AddRoot(path string, mode RootMode, removable bool) error {
if mode != ModeRO {
mode = ModeRW
}
p, ok := normalizeRoot(path)
if !ok {
return fmt.Errorf("invalid path %q", path)
}
if st, err := os.Stat(p); err != nil || !st.IsDir() {
return fmt.Errorf("%s is not a directory", path)
}
f.mu.Lock()
defer f.mu.Unlock()
for i := range f.roots {
if f.roots[i].path == p {
f.roots[i].mode = mode
// 固有根不会被追加降级为可移除(SetRoots 设的 cwd/工作区目录等)。
if f.roots[i].removable {
f.roots[i].removable = removable
}
return nil
}
}
f.roots = append(f.roots, allowedRoot{path: p, mode: mode, removable: removable})
return nil
}
// RemoveRoot 移除一个可移除的允许根(固有根不可移除)。返回是否确实移除了。
func (f *Fence) RemoveRoot(path string) bool {
p, ok := normalizeRoot(path)
if !ok {
return false
}
f.mu.Lock()
defer f.mu.Unlock()
for i := range f.roots {
if f.roots[i].path == p {
if !f.roots[i].removable {
return false
}
f.roots = append(f.roots[:i], f.roots[i+1:]...)
return true
}
}
return false
}
// RootDetail 是允许根的展示信息(提示词注入、/dir list 用)。
type RootDetail struct {
Path string
Mode RootMode
Removable bool
}
// RootDetails 返回全部允许根(含模式)。
func (f *Fence) RootDetails() []RootDetail {
f.mu.RLock()
defer f.mu.RUnlock()
out := make([]RootDetail, 0, len(f.roots))
for _, r := range f.roots {
out = append(out, RootDetail{Path: r.path, Mode: r.mode, Removable: r.removable})
}
return out
}
// Roots 返回当前允许根路径列表(用于错误提示与展示)。
func (f *Fence) Roots() []string {
f.mu.RLock()
defer f.mu.RUnlock()
out := make([]string, 0, len(f.roots))
for _, r := range f.roots {
out = append(out, r.path)
}
return out
}
// Check 校验路径在允许根内(只读语义:ro 根放行)。
// 做尽力而为的符号链接解析(沿存在的祖先链向上解析,deepest-first),防止用软链越界。
func (f *Fence) Check(p string) error { return f.check(p, false) }
// CheckWrite 按读写语义校验:ro 根拒绝。
func (f *Fence) CheckWrite(p string) error { return f.check(p, true) }
func (f *Fence) check(p string, write bool) error {
abs, err := filepath.Abs(expandHome(p))
if err != nil {
return err
}
abs = evalPath(abs)
f.mu.RLock()
defer f.mu.RUnlock()
if len(f.roots) == 0 {
return nil
}
for _, r := range f.roots {
if abs == r.path || strings.HasPrefix(abs, r.path+string(filepath.Separator)) {
if write && r.mode == ModeRO {
return fmt.Errorf("path %q is inside read-only directory %q (imported as read-only)", p, r.path)
}
return nil
}
}
return fmt.Errorf("path %q is outside allowed roots %v (import one with /dir add)", p, f.roots)
}
// ---- 包级默认围栏的历史 API(默认工作区场景兼容保留)----
// SetAllowedRoots 整体替换默认围栏的允许根。
func SetAllowedRoots(roots ...string) { defaultFence.SetRoots(roots...) }
// AddAllowedRoot 向默认围栏追加可移除根(目录导入 /dir 命令)。
func AddAllowedRoot(path string, mode RootMode) error { return defaultFence.AddRoot(path, mode, true) }
// RemoveAllowedRoot 从默认围栏移除一个可移除根。
func RemoveAllowedRoot(path string) bool { return defaultFence.RemoveRoot(path) }
// AllowedRootDetails 返回默认围栏的全部允许根。
func AllowedRootDetails() []RootDetail { return defaultFence.RootDetails() }
// AllowedRoots 返回默认围栏的允许根路径列表。
func AllowedRoots() []string { return defaultFence.Roots() }
func checkPath(p string) error { return defaultFence.Check(p) }
func checkPathMode(p string, write bool) error { return defaultFence.check(p, write) }
// evalPath 尽力而为地把路径按真实文件系统解析(处理 /var -> /private/var
// 这类系统符号链接):从最深处向上找到第一个存在的祖先,解析后拼回尾部。
func evalPath(abs string) string {
var tail []string
cur := abs
for {
if resolved, err := filepath.EvalSymlinks(cur); err == nil {
return filepath.Join(append([]string{resolved}, tail...)...)
}
parent := filepath.Dir(cur)
if parent == cur {
return abs // 到根都没有存在的祖先:放弃解析
}
tail = append([]string{filepath.Base(cur)}, tail...)
cur = parent
}
}