Files
Agent-System-Backend/internal/server/admin_execenv.go
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

152 lines
3.8 KiB
Go

package server
import (
"io"
"net/http"
"os"
"path/filepath"
"time"
)
func (s *Server) adminExecEnv(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireAdmin(w, r); !ok {
return
}
if s.ExecEnv == nil {
writeJSON(w, http.StatusOK, map[string]any{"configured": false})
return
}
base, err := s.ExecEnv.Root.CurrentBase()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
reqs, err := s.ExecEnv.Store.ListExecEnvRequests("", 50)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
binds, err := s.ExecEnv.Store.ListExecEnvBindings()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
bin, _ := s.ExecEnv.Root.PythonBin()
ents, _ := os.ReadDir(s.ExecEnv.Root.LayersDir())
layers := make([]string, 0, len(ents))
for _, ent := range ents {
if ent.IsDir() {
layers = append(layers, ent.Name())
}
}
writeJSON(w, http.StatusOK, map[string]any{
"configured": true,
"root": s.ExecEnv.Root.Path,
"python": bin,
"base": base,
"layers": layers,
"requests": reqs,
"bindings": binds,
})
}
func (s *Server) adminExecEnvDecision(w http.ResponseWriter, r *http.Request) {
actor, ok := s.requireAdmin(w, r)
if !ok {
return
}
if s.ExecEnv == nil {
http.Error(w, "exec env is not configured", http.StatusConflict)
return
}
var body struct {
Allow bool `json:"allow"`
Isolate bool `json:"isolate"`
}
if err := decodeJSON(r, &body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
out, err := s.ExecEnv.Decide(r.Context(), r.PathValue("id"), actor, true, body.Allow, body.Isolate)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
writeJSON(w, http.StatusOK, out)
}
func (s *Server) adminExecEnvRebase(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireAdmin(w, r); !ok {
return
}
if s.ExecEnv == nil {
http.Error(w, "exec env is not configured", http.StatusConflict)
return
}
id, err := s.ExecEnv.Rebase(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
writeJSON(w, http.StatusOK, map[string]any{"base": id})
}
func (s *Server) adminExecEnvGC(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireAdmin(w, r); !ok {
return
}
if s.ExecEnv == nil {
http.Error(w, "exec env is not configured", http.StatusConflict)
return
}
removed, err := s.ExecEnv.GC(7 * 24 * time.Hour)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, http.StatusOK, map[string]any{"removed": removed})
}
func (s *Server) adminExecEnvWheel(w http.ResponseWriter, r *http.Request) {
actor, ok := s.requireAdmin(w, r)
if !ok {
return
}
if s.ExecEnv == nil {
http.Error(w, "exec env is not configured", http.StatusConflict)
return
}
if err := r.ParseMultipartForm(64 << 20); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, hdr, err := r.FormFile("wheel")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
dir := filepath.Join(s.ExecEnv.Root.Path, "uploads")
if err := os.MkdirAll(dir, 0o755); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
dst, err := os.CreateTemp(dir, "upload-*.whl")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(dst, file); err != nil {
dst.Close()
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
dst.Close()
out, err := s.ExecEnv.RequestPackages(r.Context(), r.FormValue("conversationId"), actor, r.FormValue("reason"), nil, dst.Name())
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
writeJSON(w, http.StatusOK, map[string]any{"file": hdr.Filename, "outcome": out})
}