executor

package
v0.0.0-...-b8fbad1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

This file implements the fs_read/fs_write/fs_list/fs_delete/fs_stat executors. See docs/specs/backend.md Section 3.3.

This file implements the input_key, input_mouse_click, input_mouse_move, and input_type executors via xdotool -- the pragmatic X11 implementation noted in docs/specs/backend.md Section 19 ("via xdotool or the XTest extension"). This is the `input` capability: a higher-risk area, independently toggle-able from shell/screenshot/filesystem/process/ sysinfo, requiring mandatory elicitation on every single action at the server layer (internal/mcp/tools/input.go) -- this file only performs the already-confirmed action.

This file implements the process_list/process_info/process_signal executors by reading /proc directly (Linux). See docs/specs/backend.md Section 3.4.

This file implements the screenshot_capture and screenshot_watch executors. See docs/specs/backend.md Section 3.2.

This file implements the Wayland screenshot capture backend for CaptureScreenshot (screenshot.go), via the grim CLI tool -- the pragmatic alternative to a full pipewire/xdg-desktop-portal D-Bus integration noted in docs/specs/backend.md Section 19. grim itself talks to the compositor's wlr-screencopy (or equivalent) protocol; on compositors/portals that require an interactive permission grant per capture (e.g. GNOME on Wayland without a wlroots-style protocol), grim may fail or hang waiting on a portal dialog the agent has no way to answer -- see ErrWaylandCaptureUnavailable.

Package executor implements the desktop agent's tool executors: the code that actually spawns commands, allocates PTYs, reads the filesystem, etc. This file implements shell_exec. See docs/specs/backend.md Section 3.1.1 and Section 2.2.

This file implements the PTY-backed interactive shell session executor (shell_session_start/write/close). See docs/specs/backend.md Section 3.1.2-3.1.4.

This file implements the sysinfo_get executor. See docs/specs/backend.md Section 3.5.1. Every gather* function degrades gracefully -- an unreadable subsystem yields a nil/zero-value result rather than an error, so one broken section never fails the whole call.

Index

Constants

View Source
const (
	// DefaultFSReadLimit is used when FSReadInput.Limit is unset (Section
	// 3.3.1: "default: 1048576 = 1MB").
	DefaultFSReadLimit = 1 << 20
	// FSChunkThreshold: file content at or above this size is streamed as
	// FrameFileContent binary chunks rather than inlined in the JSON
	// result (Section 3.3.1: "for large files the agent sends content as
	// binary file content frames").
	FSChunkThreshold = 256 * 1024
	// FSStreamChunkSize is the size of each streamed FrameFileContent chunk.
	FSStreamChunkSize = 64 * 1024

	// DefaultFSListMaxDepth/DefaultFSListLimit are fs_list's defaults
	// (Section 3.3.3).
	DefaultFSListMaxDepth = 3
	DefaultFSListLimit    = 1000

	// DefaultFSFileMode is used when FSWriteInput.FileMode is unset
	// (Section 3.3.2: "default: 0644").
	DefaultFSFileMode = 0o644
)
View Source
const (
	DefaultScreenshotDisplay = ":0"
	DefaultScreenshotQuality = 6
	DefaultWatchIntervalMs   = 2000
	MinWatchIntervalMs       = 500
	DefaultWatchMaxFrames    = 30
	MaxWatchMaxFrames        = 120
	DefaultWatchDurationSecs = 60
	MaxWatchDurationSecs     = 300
)
View Source
const (
	// DefaultShellTimeout is used when ShellExecInput.Timeout is nil.
	DefaultShellTimeout = 30 * time.Second
	// MaxShellTimeout bounds ShellExecInput.Timeout regardless of what the
	// client requests.
	MaxShellTimeout = 300 * time.Second
)
View Source
const (
	// DefaultShellSessionShell is used when neither ShellSessionStartInput.Shell
	// nor $SHELL is set.
	DefaultShellSessionShell = "/bin/bash"

	// DefaultShellSessionIdleTimeout is how long shell_session_write waits
	// for the PTY to go quiet before returning its accumulated output.
	DefaultShellSessionIdleTimeout = 2 * time.Second
	// DefaultShellSessionReadTimeout bounds shell_session_write overall,
	// even if the PTY never goes idle.
	DefaultShellSessionReadTimeout = 30 * time.Second
	// DefaultShellSessionCloseGrace is how long shell_session_close waits
	// after SIGTERM before escalating to SIGKILL.
	DefaultShellSessionCloseGrace = 5 * time.Second

	// DefaultMaxShellSessions is the default RC_MAX_SHELL_SESSIONS cap,
	// enforced by callers (typically per MCP session) via
	// ShellSessionManager.Count.
	DefaultMaxShellSessions = 5
)

Variables

View Source
var ErrInputUnavailable = errors.New("executor: input injection unavailable (xdotool not found or action failed)")

ErrInputUnavailable is returned when xdotool is not installed or the injected action itself fails (e.g. no X11 display reachable).

View Source
var ErrNoDisplay = errors.New("executor: no display available")

ErrNoDisplay is returned when no X11 display is configured/reachable.

View Source
var ErrPathNotAllowed = errors.New("executor: path outside allowed roots")

ErrPathNotAllowed is returned when AGENT_FS_ALLOWED_ROOTS is configured and the requested path falls outside every configured root.

View Source
var ErrProcessNotFound = errors.New("executor: process not found")

ErrProcessNotFound is returned when the given PID has no /proc entry (or exited between being listed and being read).

View Source
var ErrSelfSignalRejected = errors.New("executor: refusing to signal the agent's own process")

ErrSelfSignalRejected is returned by SendProcessSignal when asked to signal the agent's own PID -- a hard reject per Section 3.4.3, even if the server forwarded it.

View Source
var ErrShellSessionNotFound = errors.New("executor: shell session not found")

ErrShellSessionNotFound is returned by ShellSessionManager methods when the given shellSessionId is unknown (never existed, or already closed and reaped).

View Source
var ErrWaylandCaptureUnavailable = fmt.Errorf("executor: wayland screenshot capture unavailable")

ErrWaylandCaptureUnavailable is returned when Wayland screenshot capture cannot proceed: grim is not installed, or the capture command itself failed (including a portal permission denial). Callers get this as a specific, immediate error rather than a hang -- grimCaptureTimeout bounds how long the external command may run.

Functions

func CaptureScreenshot

func CaptureScreenshot(display string, maxWidth int, quality int) (pngBytes []byte, width, height int, err error)

CaptureScreenshot captures the current display, encodes it as PNG, and downscales to maxWidth (preserving aspect ratio) if set and exceeded. quality maps to PNG compression level 0-9 the same way screenshot_ capture's input schema documents it (0 = no compression/fastest, 9 = best compression); values outside 0-9 fall back to DefaultScreenshotQuality. The tool surface (screenshot_capture/watch) is identical on X11 and Wayland -- CaptureScreenshot picks the backend at call time via DetectDisplayServer (Section 19). display is only meaningful for X11 (an explicit :N override); it is ignored under Wayland, which has no equivalent per-call display selector.

func FSWrite

func FSWrite(path string, content []byte, mode string, fileMode os.FileMode, createDirs bool) (bytesWritten int, absPath string, err error)

FSWrite writes content to path per mode ("overwrite" default, or "append") and fileMode, creating parent directories first iff createDirs.

func InputKey

func InputKey(key string) error

InputKey sends a keypress (or key combo, e.g. "ctrl+c") via `xdotool key`.

func InputMouseClick

func InputMouseClick(x, y int, button string) error

InputMouseClick moves to (x, y) and clicks button ("left"/"middle"/ "right", default "left") via `xdotool mousemove ... click`.

func InputMouseMove

func InputMouseMove(x, y int) error

InputMouseMove moves the mouse cursor to absolute coordinates (x, y) via `xdotool mousemove`.

func InputType

func InputType(text string) error

InputType types literal text via `xdotool type`. The "--" separator prevents text starting with "-" from being parsed as an xdotool flag.

func SendProcessSignal

func SendProcessSignal(pid int, signalName string) (resolvedSignal string, err error)

SendProcessSignal sends signalName (default SIGTERM if empty/unknown) to pid, refusing to target the agent's own process.

Types

type DisplayServer

type DisplayServer int

DisplayServer identifies which windowing system is active on the agent's desktop session (Section 19: "detect the active display server at runtime").

const (
	// DisplayServerNone means neither a Wayland nor an X11 session was
	// detected (e.g. a headless machine with no desktop session at all).
	DisplayServerNone DisplayServer = iota
	DisplayServerX11
	DisplayServerWayland
)

func DetectDisplayServer

func DetectDisplayServer() DisplayServer

DetectDisplayServer inspects the standard environment variables a desktop session sets to determine which display server is active. WAYLAND_DISPLAY takes precedence when both are set (common under Xwayland compatibility, where DISPLAY is also present) -- the native Wayland capture path is preferred whenever the session actually is Wayland.

type FSDeleteResult

type FSDeleteResult struct {
	ItemsRemoved int
}

FSDeleteResult is the outcome of FSDelete.

func FSDelete

func FSDelete(path string, recursive bool) (FSDeleteResult, error)

FSDelete removes path. A non-empty directory requires recursive=true; a file or empty directory is removed regardless.

type FSListResult

type FSListResult struct {
	Entries    []listEntry
	Truncated  bool
	TotalCount int
}

FSListResult is the outcome of FSList.

func FSList

func FSList(path string, recursive bool, maxDepth int, showHidden bool, limit int) (FSListResult, error)

FSList lists path's contents, optionally recursively up to maxDepth, including dotfiles iff showHidden, capped at limit entries returned (though TotalCount reflects how many were actually found).

type FSReadResult

type FSReadResult struct {
	Content   []byte
	Encoding  string // "utf8" | "base64"
	Size      int64
	Truncated bool
}

FSReadResult is the outcome of FSRead.

func FSRead

func FSRead(path string, offset, limit int64, encoding string) (FSReadResult, error)

FSRead reads up to limit bytes from path starting at offset. If the resulting content isn't valid UTF-8 and encoding wasn't explicitly "base64", it falls back to "base64" per Section 3.3.1.

type FSStatResult

type FSStatResult struct {
	Name       string
	Path       string
	Type       string
	Size       int64
	Mode       string
	ModTime    string
	Owner      string
	Group      string
	LinkTarget string
}

FSStatResult is the outcome of FSStat.

func FSStat

func FSStat(path string, followSymlinks bool) (FSStatResult, error)

FSStat returns metadata for path, following symlinks iff followSymlinks.

type ProcessInfo

type ProcessInfo struct {
	PID       int
	PPID      int
	Name      string
	Cmdline   string
	Exe       string
	Cwd       string
	User      string
	State     string
	Threads   int
	CPUPct    float64
	MemPct    float64
	MemRSSKB  int64
	MemVMSKB  int64
	StartTime time.Time
	FDs       int
	Environ   map[string]string
}

ProcessInfo is the full set of fields FSStat-style callers may want; process_list only surfaces a subset (see ProcessListEntry).

func GetProcessInfo

func GetProcessInfo(pid int) (ProcessInfo, error)

GetProcessInfo returns full details for pid, or ErrProcessNotFound.

func ListProcesses

func ListProcesses(filter, userFilter, sortBy string, limit int) (procs []ProcessInfo, totalCount int, err error)

ListProcesses returns processes matching filter (substring of Name) and userFilter (exact match), sorted by sortBy ("pid" default | "cpu" | "memory" | "name"), capped at limit (default 100). totalCount is the number of processes that matched the filters, before the limit is applied.

type ShellExecResult

type ShellExecResult struct {
	Stdout     string
	Stderr     string
	ExitCode   int
	Killed     bool
	DurationMs int64
}

ShellExecResult is the outcome of one Exec call.

func Exec

func Exec(ctx context.Context, input types.ShellExecInput, onChunk StreamFunc) (ShellExecResult, error)

Exec spawns "/bin/sh -c <command>" per input, streaming combined stdout/stderr chunks to onChunk (if non-nil) as they arrive, and returns once the command exits, the timeout elapses (SIGKILL, Killed=true), or ctx is cancelled (also SIGKILL, Killed=true).

A command that fails to be found by /bin/sh itself (e.g. "nosuchcmd") is not a Go-level start error: /bin/sh reports it on stderr and exits 127, which Exec surfaces as ExitCode 127, not an error.

type ShellSession

type ShellSession struct {
	ID       string
	PID      int
	Shell    string
	ClientID string
	// contains filtered or unexported fields
}

ShellSession is one PTY-backed interactive shell running on the agent.

func (*ShellSession) StreamUntilIdleOrTimeout

func (s *ShellSession) StreamUntilIdleOrTimeout(ctx context.Context, onChunk StreamFunc, idleTimeout, readTimeout time.Duration) (output string, exited bool, exitCode int)

StreamUntilIdleOrTimeout streams PTY output via onChunk (per the 200ms/4KB cadence) until the PTY has been quiet for idleTimeout, readTimeout elapses, or the shell process exits -- whichever comes first. It returns the output accumulated since the previous call (or session start), whether the process has exited, and its exit code if so.

func (*ShellSession) Write

func (s *ShellSession) Write(input string) (int, error)

Write sends input to sess's PTY. It returns the number of bytes written.

type ShellSessionManager

type ShellSessionManager struct {
	// contains filtered or unexported fields
}

ShellSessionManager tracks all PTY sessions currently open on this agent, keyed by shellSessionId.

func NewShellSessionManager

func NewShellSessionManager() *ShellSessionManager

NewShellSessionManager constructs an empty ShellSessionManager.

func (*ShellSessionManager) Close

func (m *ShellSessionManager) Close(id string, signal string, grace time.Duration) (exitCode int, finalOutput string, err error)

Close terminates sess: sends signal (default SIGTERM), waits up to grace for the process to exit, then escalates to SIGKILL. It kills the whole process group (see the process-tree kill note on Start) so children the shell forked don't outlive it and keep the pty open.

func (*ShellSessionManager) CloseAll

func (m *ShellSessionManager) CloseAll()

CloseAll terminates every open session (SIGTERM with a short grace, then SIGKILL via Close's escalation) and releases their PTYs. Used when the agent's reconnect grace period lapses (Section 2.1).

func (*ShellSessionManager) Count

func (m *ShellSessionManager) Count() int

Count returns the number of currently open (not-yet-closed) sessions.

func (*ShellSessionManager) Get

Get returns the session for id, or ErrShellSessionNotFound.

func (*ShellSessionManager) Start

Start allocates a PTY, spawns input.Shell (or $SHELL / DefaultShellSessionShell), and registers the resulting session under a freshly minted shellSessionId.

type StreamFunc

type StreamFunc func(chunk []byte)

StreamFunc is called with each combined stdout/stderr chunk as the command runs, per the streaming cadence above. It must not block for long, since it is called synchronously from the pipe-reading goroutines.

type SysinfoCPU

type SysinfoCPU struct {
	Model                         string
	Cores, Threads                int
	UsagePct                      float64
	LoadAvg1, LoadAvg5, LoadAvg15 float64
}

type SysinfoDisk

type SysinfoDisk struct {
	Mount, Device, FSType        string
	TotalKB, UsedKB, AvailableKB int64
	UsagePct                     float64
}

type SysinfoMemory

type SysinfoMemory struct {
	TotalKB, UsedKB, AvailableKB, SwapTotalKB, SwapUsedKB int64
	UsagePct                                              float64
}

type SysinfoNetworkIface

type SysinfoNetworkIface struct {
	Name, IPv4, IPv6, MAC, State string
}

type SysinfoOS

type SysinfoOS struct{ Name, Version, Kernel, Arch string }

type SysinfoResult

type SysinfoResult struct {
	Hostname string
	OS       *SysinfoOS
	Uptime   *SysinfoUptime
	CPU      *SysinfoCPU
	Memory   *SysinfoMemory
	Disk     []SysinfoDisk
	Network  []SysinfoNetworkIface
}

SysinfoResult mirrors types.SysinfoGetOutput without importing the mcp types package's JSON tags, so this file stays agent-only.

func GatherSysinfo

func GatherSysinfo(sections []string) SysinfoResult

GatherSysinfo collects the requested sections (case-insensitive; "all" or an empty slice means every section).

type SysinfoUptime

type SysinfoUptime struct {
	Seconds int64
	Human   string
}

type WatchFrame

type WatchFrame struct {
	PNG   []byte
	Index int // 0-based frame index
}

WatchFrame is delivered to onFrame for each successfully captured frame.

type WatchOptions

type WatchOptions struct {
	Display      string
	MaxWidth     int
	Quality      int
	IntervalMs   int
	MaxFrames    int
	DurationSecs int
}

WatchOptions configures a WatchScreenshots run.

type WatchResult

type WatchResult struct {
	FramesCaptured int
	DurationMs     int64
	StoppedReason  string // "maxFrames" | "duration" | "cancelled" | "error"
}

WatchResult is the terminal outcome of a WatchScreenshots run.

func WatchScreenshots

func WatchScreenshots(ctx context.Context, opts WatchOptions, onFrame func(WatchFrame)) (WatchResult, error)

WatchScreenshots captures screenshots at opts.IntervalMs, invoking onFrame for each one, until opts.MaxFrames frames have been captured, opts.DurationSecs elapses, or ctx is cancelled -- whichever comes first. A capture error stops the watch early (StoppedReason "error") rather than looping forever against a broken display.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL