// Package scratch 给 Agent 一个能正大光明写临时文件的地方。 // // 为什么需要它:文件类工具(read/write/edit)受路径围栏约束,允许根只有 // 工作区 + spill + memory,/tmp 不在其中。实测会话里模型按习惯把中间产物写进 // /tmp,回来 read 被围栏拦下("outside allowed roots"),于是改走 // `bash: cat /tmp/…`——73 条命令里 23 条这么绕。bash 不做逐路径校验是**有意** // 的设计(见 tools/guard.go 头注释),所以该修的不是给 bash 补沙箱,而是别把 // 诚实的那条路堵上:给一个正式 scratch 根,并把它写进提示词。 package scratch import ( "os" "path/filepath" "time" ) // maxAge 比 spill 的落盘结果更短命:临时产物没人回头读。 const maxAge = 3 * 24 * time.Hour // Dir 返回 scratch 目录(~/.autogo/scratch),不存在则创建。 // 围栏的 AddRoot 要求目录已存在,所以这里必须建出来。 func Dir() string { dir := filepath.Join(".autogo", "scratch") if home, err := os.UserHomeDir(); err == nil && home != "" { dir = filepath.Join(home, ".autogo", "scratch") } // 建不出来就不拦:后面文件工具会给出可见的错误。 _ = os.MkdirAll(dir, 0o700) // 0700:里面可能躺着调试用的中间数据 return dir } // Prune 删除超期的临时文件(启动时与 spill.Prune 一起跑)。 func Prune(dir string) { entries, err := os.ReadDir(dir) if err != nil { return } for _, e := range entries { if e.IsDir() { continue } info, err := e.Info() if err != nil || info.ModTime().Before(time.Now().Add(-maxAge)) { _ = os.Remove(filepath.Join(dir, e.Name())) } } }