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>
78 lines
1.9 KiB
Go
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
|
|
}
|