Files
Agent-System-Backend/internal/execenv/catalog.go
T
weichenandCursor b85a1e1ca3 Add immutable layered Python environments for the sandbox.
Why: The sandbox hides the host home directory and has no network, so pip install --user cannot supply python-docx, and the service PATH resolved python3 to a linuxbrew path that does not exist inside the sandbox.
Evidence: SELF go test of execenv, tools, store, app, server, runmgr, config, and cmd/agent-system on this worktree. CI has not run.
Impact: Sessions inherit a read-only base layer. Extra packages are requested through python_env, auto-approved only from the allowlist, and mounted per conversation. Schema 35 adds the request and binding tables.
Refs: .task/feature-layered-python-env.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 13:52:48 +00:00

78 lines
1.9 KiB
Go

package execenv
import (
"encoding/json"
"os"
)
// Catalog 是管理员可编辑的白名单。不在白名单里的包不会自动安装。
type Catalog struct {
MaxWheelBytes int64 `json:"maxWheelBytes"`
Allow []CatalogEntry `json:"allow"`
Deny []string `json:"deny"`
}
// CatalogEntry 的 Spec 为空表示任意版本;非空时是给人类看的范围说明,
// 自动批准只看包名是否在列,版本冲突另由基础层锁判定。
type CatalogEntry struct {
Name string `json:"name"`
Spec string `json:"spec,omitempty"`
Reason string `json:"reason,omitempty"`
}
// DefaultCatalog 是第一次创建环境根时落下的名单。之后以磁盘上的 catalog.json 为准。
func DefaultCatalog() Catalog {
return Catalog{
MaxWheelBytes: 80 << 20,
Allow: []CatalogEntry{
{Name: "python-docx", Reason: "Word 交付物"},
{Name: "openpyxl", Reason: "Excel"},
{Name: "python-pptx", Reason: "PPT"},
{Name: "lxml", Reason: "OOXML"},
{Name: "pillow", Reason: "图片"},
{Name: "numpy", Reason: "数值"},
{Name: "matplotlib", Reason: "图表"},
{Name: "pypdf", Reason: "PDF 读取"},
{Name: "reportlab", Reason: "PDF 生成"},
{Name: "markdown", Reason: "Markdown"},
{Name: "pdfplumber", Reason: "PDF 抽取"},
},
Deny: []string{"setuptools", "pip"},
}
}
func (r *Root) LoadCatalog() (Catalog, error) {
raw, err := os.ReadFile(r.CatalogPath())
if err != nil {
return Catalog{}, err
}
var c Catalog
if err := json.Unmarshal(raw, &c); err != nil {
return Catalog{}, err
}
if c.MaxWheelBytes <= 0 {
c.MaxWheelBytes = 80 << 20
}
return c, nil
}
func (c Catalog) allowed(name string) bool {
n := normName(name)
for _, e := range c.Allow {
if normName(e.Name) == n {
return true
}
}
return false
}
func (c Catalog) denied(name string) bool {
n := normName(name)
for _, d := range c.Deny {
if normName(d) == n {
return true
}
}
return false
}