File size: 4,161 Bytes
857a91b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Package matrixshell installs and runs the real MatrixShell CLI
// (github.com/agent-matrix/MatrixShell) inside a dedicated Python virtual
// environment on the host, and executes commands in a sandbox working directory
// with the venv on PATH. No simulation: install, version and command output are
// all real.
package matrixshell

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/agent-matrix/matrix-runtime/internal/pyenv"
)

// RepoSpec is the pip/uv install spec for MatrixShell (not on PyPI yet).
const RepoSpec = "git+https://github.com/agent-matrix/MatrixShell.git@master"

// ErrBlocked is returned when a command is refused by the safety denylist.
var ErrBlocked = errors.New("refused by safety denylist")

// hard denylist — destructive operations refused even on explicit confirm.
var denyList = []*regexp.Regexp{
	regexp.MustCompile(`(?i)\bmkfs\b`), regexp.MustCompile(`(?i)\bdd\s+if=`),
	regexp.MustCompile(`(?i)\bfdisk\b`), regexp.MustCompile(`(?i)\bdiskpart\b`),
	regexp.MustCompile(`(?i)\bshutdown\b`), regexp.MustCompile(`(?i)\breboot\b`),
	regexp.MustCompile(`(?i)\bmkswap\b`), regexp.MustCompile(`:\(\)\s*\{`),
	regexp.MustCompile(`(?i)\brm\s+-rf\s+(/|~|\$HOME)(\s|$)`),
	regexp.MustCompile(`>\s*/dev/sd`),
}

// Denied reports whether a command is blocked by the safety denylist.
func Denied(command string) bool {
	for _, re := range denyList {
		if re.MatchString(command) {
			return true
		}
	}
	return false
}

// VenvDir is the MatrixShell sandbox venv directory under the data dir.
func VenvDir(dataDir string) string { return filepath.Join(dataDir, "venvs", "matrixshell") }

// SandboxDir is the working directory commands run in.
func SandboxDir(dataDir string) string { return filepath.Join(dataDir, "sandbox", "matrixshell") }

func env(dataDir string) *pyenv.Env { return &pyenv.Env{Dir: VenvDir(dataDir)} }

// Status describes the local MatrixShell installation.
type Status struct {
	Installed bool   `json:"installed"`
	Version   string `json:"version"`
	Venv      string `json:"venv"`
	Python    string `json:"python"`
	Sandbox   string `json:"sandbox"`
	Spec      string `json:"spec"`
}

// GetStatus inspects the sandbox venv and reports whether matrixsh is installed.
func GetStatus(ctx context.Context, dataDir string) Status {
	e := env(dataDir)
	st := Status{Venv: e.Dir, Sandbox: SandboxDir(dataDir), Spec: RepoSpec}
	if !e.Exists() {
		return st
	}
	st.Python = e.Python()
	if e.HasBin("matrixsh") {
		res, _ := e.Shell(ctx, SandboxDir(dataDir), `python -c "import importlib.metadata as m; print(m.version('matrixsh'))"`)
		if res.ExitCode == 0 {
			st.Installed = true
			st.Version = strings.TrimSpace(res.Stdout)
		}
	}
	return st
}

// Install creates the sandbox venv (if needed) and installs MatrixShell from
// git, streaming real output to emit. Returns the resulting status.
func Install(ctx context.Context, dataDir string, emit func(string)) (Status, error) {
	dir := VenvDir(dataDir)
	emit("creating python sandbox venv: " + dir)
	e, err := pyenv.Ensure(ctx, dir, emit)
	if err != nil {
		return Status{}, err
	}
	emit("installing MatrixShell (" + RepoSpec + ") — this is a real pip install")
	if err := e.Pip(ctx, emit, RepoSpec); err != nil {
		return Status{}, err
	}
	if err := os.MkdirAll(SandboxDir(dataDir), 0o755); err != nil {
		return Status{}, err
	}
	st := GetStatus(ctx, dataDir)
	if !st.Installed {
		return st, errors.New("matrixsh installed but its version could not be resolved")
	}
	emit("matrixsh " + st.Version + " ready in the sandbox")
	return st, nil
}

// Exec runs a command inside the sandbox venv (with matrixsh/python on PATH),
// in the sandbox working directory. Destructive commands are refused.
func Exec(ctx context.Context, dataDir, command string) (pyenv.Result, error) {
	command = strings.TrimSpace(command)
	if command == "" {
		return pyenv.Result{}, errors.New("empty command")
	}
	if Denied(command) {
		return pyenv.Result{}, ErrBlocked
	}
	e := env(dataDir)
	if !e.Exists() {
		return pyenv.Result{}, errors.New("MatrixShell sandbox is not installed yet")
	}
	return e.Shell(ctx, SandboxDir(dataDir), command)
}