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>
152 lines
3.8 KiB
Go
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})
|
|
}
|