elevation: replace Elevator interface with x/elevate plugin script

Remove the elevation package and Elevator interface entirely. The
execute server now invokes x/elevate directly, with detection reduced
to a stat check. Decl() loses the elevator parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-18 20:50:20 +02:00
parent e09a6c1250
commit 56c299d29b
4 changed files with 32 additions and 89 deletions

View File

@ -1,28 +0,0 @@
package elevation
import (
"context"
"io"
"os/exec"
)
// Elevator runs commands with elevated privileges outside the sandbox.
// Returns the exit code; a non-zero exit code is not itself an error.
type Elevator interface {
Run(ctx context.Context, cmd, dir string, stdout, stderr io.Writer) (int, error)
Name() string
}
// Detect returns the best available Elevator, or nil if none is configured.
func Detect() Elevator {
if sp := newSuperpowers(); sp != nil {
return sp
}
return nil
}
// Available reports whether a named elevator binary is present in PATH.
func Available(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}

View File

@ -1,44 +0,0 @@
package elevation
import (
"context"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"ollie/pkg/paths"
)
type superpowersElevator struct {
script string
}
func newSuperpowers() *superpowersElevator {
if _, err := exec.LookPath("superpowers"); err != nil {
return nil
}
script := filepath.Join(paths.CfgDir(), "tools", "elevate_superpowers")
if _, err := os.Stat(script); err != nil {
return nil
}
return &superpowersElevator{script: script}
}
func (s *superpowersElevator) Name() string { return "superpowers" }
func (s *superpowersElevator) Run(ctx context.Context, cmd, dir string, stdout, stderr io.Writer) (int, error) {
c := exec.CommandContext(ctx, "superpowers", "run-session", "--", "python3", s.script, cmd)
c.Dir = dir
c.Stdout = stdout
c.Stderr = stderr
if err := c.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return exitErr.ExitCode(), nil
}
return -1, err
}
return 0, nil
}

View File

@ -4,9 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
@ -14,7 +16,6 @@ import (
"time"
"ollie/internal/sandbox"
"ollie/pkg/elevation"
"ollie/pkg/tools"
)
@ -24,9 +25,6 @@ type Server struct {
// Returns true to allow, false to deny. Trusted tools bypass this check.
Confirm func(string) bool
// Elevator, if set, is used to run steps with elevated:true outside the sandbox.
Elevator elevation.Elevator
// trusted is the set of tool names that bypass Confirm.
trustedMu sync.RWMutex
trusted map[string]bool
@ -47,12 +45,10 @@ type Server struct {
blockedUntil time.Time
}
// Decl returns a factory for an execute Server with the given working directory and elevator.
func Decl(cwd string, el elevation.Elevator) func() tools.Server {
// Decl returns a factory for an execute Server with the given working directory.
func Decl(cwd string) func() tools.Server {
return func() tools.Server {
s := New(cwd)
s.Elevator = el
return s
return New(cwd)
}
}
@ -96,7 +92,7 @@ Examples:
"language": {"type": "string", "description": "Language interpreter (default: bash). Ignored when tool or parallel is set."},
"tool": {"type": "string", "description": "Named tool script from the tools directory. Discover available tools: grep -iA2 'keyword' $OLLIE/t/idx"},
"args": {"type": "array", "items": {"type": "string"}, "description": "Arguments for the tool script."},
"elevated": {"type": "boolean", "description": "Run this step outside the sandbox via the elevation backend (e.g. superpowers). Only bash code is supported. Omit or false for normal sandboxed execution."},
"elevated": {"type": "boolean", "description": "Run this step outside the sandbox via the elevation backend. Only bash code is supported. Omit or false for normal sandboxed execution."},
"parallel": {
"type": "array",
"description": "Fan-out: steps run concurrently, outputs concatenated in submission order, result fed to next stage. All parallel steps should produce the same output schema so the concatenated result is coherent.",
@ -183,18 +179,23 @@ func (e *Server) SetEnv(key, value string) {
e.envMu.Unlock()
}
// executeElevated runs cmd as a bash command via the configured Elevator.
// executeElevated runs cmd outside the sandbox via x/elevate.
// Returns (output, error); a non-zero exit code is treated as an error.
func (e *Server) executeElevated(ctx context.Context, cmd, dir string, timeout int) (string, error) {
if e.Elevator == nil {
return "", fmt.Errorf("elevation not available: no backend configured")
script := filepath.Join(PluginsPath(), "elevate")
if _, err := os.Stat(script); err != nil {
return "", fmt.Errorf("elevation not available: x/elevate not found")
}
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
defer cancel()
c := exec.CommandContext(ctx, script, "--", cmd)
c.Dir = dir
var outBuf, errBuf bytes.Buffer
lw := &limitedWriter{w: &outBuf, limit: 10 * 1024 * 1024}
code, err := e.Elevator.Run(ctx, cmd, dir, lw, &errBuf)
c.Stdout = lw
c.Stderr = &errBuf
err := c.Run()
combined := outBuf.String()
if errBuf.Len() > 0 {
combined += errBuf.String()
@ -206,11 +207,12 @@ func (e *Server) executeElevated(ctx context.Context, cmd, dir string, timeout i
return combined, fmt.Errorf("execution timeout after %d seconds", timeout)
}
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return combined, fmt.Errorf("elevated execution failed (exit %d)\nOutput: %s", exitErr.ExitCode(), combined)
}
return combined, fmt.Errorf("elevated execution failed: %v", err)
}
if code != 0 {
return combined, fmt.Errorf("elevated execution failed (exit %d)\nOutput: %s", code, combined)
}
return combined, nil
}

View File

@ -22,6 +22,19 @@ func ToolsPath() string {
return paths.CfgDir() + "/tools"
}
// PluginsPath returns the directory for server-invoked plugin scripts.
// Resolved in order: first entry of OLLIE_PLUGINS_PATH (colon-separated),
// then ~/.config/ollie/scripts/x.
func PluginsPath() string {
if p := os.Getenv("OLLIE_PLUGINS_PATH"); p != "" {
if i := strings.Index(p, ":"); i >= 0 {
p = p[:i]
}
return p
}
return paths.CfgDir() + "/scripts/x"
}
// detectLanguage infers the script language from the shebang line.
// Returns "python3", "perl", "awk", "sed", "ed", or "bash".
func detectLanguage(code string) string {