shellsession

package
v0.40.2 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package shellsession manages one persistent PTY-backed shell per chat session, rooted at the session's workspace root and outliving individual commands. Output lives in a bounded scrollback ring with monotonically increasing offsets; the agent never receives it streamed and must read scrollback explicitly. Run submits exactly one line, gated by HITL.

Index

Constants

View Source
const (
	ToolsProviderName = "shell_session"
	ToolRun           = "shell_session_run"
	ToolRead          = "shell_session_read"
)

Tool names: shell_session is one ToolsRepo exposing two function tools — Run gated by HITL (default policy approve), Read ungated (reference-only).

Variables

View Source
var ErrNoSession = errors.New("shellsession: no live shell for this session")

ErrNoSession is returned by Write for a session with no live shell, distinct from a spawn failure: the caller addressed something that is not there.

Functions

func NewTools

func NewTools(mgr Manager) taskengine.ToolsRepo

NewTools returns the shell_session ToolsRepo, registered in the engine's LocalTools map under ToolsProviderName like local_shell/local_fs so it is HITL-wrapped and reachable only when shell tooling is on.

func WithSpawn added in v0.38.0

func WithSpawn(ctx context.Context, cwd, shell string) context.Context

WithSpawn attaches a per-shell cwd and/or shell to ctx, honoured by the next shell created for that session; empty strings fall back to CwdResolver/Config.Shell, and the cwd is still validated against the workspace allowlist.

Types

type Chunk

type Chunk struct {
	Offset int64
	Data   string
	Reset  bool
}

Chunk is one batch of terminal output delivered to a subscriber: Offset is where Data begins in scrollback, and Reset marks a snapshot (initial or after PTY restart) the consumer should replace rather than append.

type Config

type Config struct {
	// CwdResolver returns the workspace root a new shell should be rooted at, given the tool/request context (which carries the session id); required.
	CwdResolver func(ctx context.Context) string
	// Workspace is the operator's workspace-root allowlist enforced against CwdResolver's output and the only source of the default root; nil means no allowlist, so an absolute cwd is taken as given.
	Workspace *vfs.Factory
	// Shell overrides the shell executable; empty picks a platform default.
	Shell string
	// ScrollbackBytes bounds retained output per shell (default 64 KiB).
	ScrollbackBytes int
	// IdleTimeout kills inactive shells (default 15m; <=0 disables reaping).
	IdleTimeout time.Duration
	// ScrubEnv, when set, maps the parent environment to the one a spawned shell inherits, so serve's own secrets never reach an agent-reachable PTY; nil inherits the full environment.
	ScrubEnv func([]string) []string
	// Interactive spawns shells for a HUMAN at a real terminal (ECHO on, shell draws its own prompt); the default (false) is the agent-facing posture — echo off, prompt suppressed — since output is scrollback for a model to read.
	Interactive bool
	// OnExit, when set, is invoked once per shell when it terminates, from a dedicated goroutine, for every cause (process exit, Kill, idle reap, Shutdown); total, never called twice for the same shell.
	OnExit func(sessionID string)
}

Config configures a Manager; zero values fall back to sane defaults.

type Manager

type Manager interface {
	// Run ensures a shell exists for sessionID (rooted via the cwd resolver against ctx, used only at creation time) and submits one line to it.
	Run(ctx context.Context, sessionID, line string) (RunResult, error)
	// Open ensures a shell exists for sessionID without submitting anything, so an interactive client can attach before the first keystroke; idempotent, an already-live shell is returned as-is.
	Open(ctx context.Context, sessionID string) error
	// Write feeds raw bytes to sessionID's shell stdin VERBATIM (unlike Run, no newline or line discipline, since the bytes are a human's keystrokes); never creates a shell — an unknown session is ErrNoSession.
	Write(sessionID string, data []byte) error
	// Read returns scrollback for sessionID (bytes since `since` when since >= 0, otherwise the last `tailBytes`); never creates a shell.
	Read(sessionID string, since int64, tailBytes int) ReadResult
	// Resize records the terminal geometry for sessionID and applies it to the live shell if any; total (unknown session, reaped shell, or non-positive dimension are no-ops), and remembered even with no live shell so the next one is born at it.
	Resize(sessionID string, rows, cols int)
	// Subscribe registers fn for live output of sessionID, invoked from a dedicated goroutine so a slow consumer cannot stall the PTY; the current scrollback is delivered immediately as a Reset chunk.
	Subscribe(sessionID string, fn func(Chunk)) (cancel func())
	// Kill terminates and forgets sessionID's shell (session close/delete).
	Kill(sessionID string)
	// Shutdown kills every shell and stops the reaper.
	Shutdown()
}

Manager owns the process-global set of per-session shells; all methods are safe for concurrent use and key on the internal chat-session id.

func NewManager

func NewManager(cfg Config) Manager

NewManager builds a Manager and starts its idle reaper.

type ReadResult

type ReadResult struct {
	Content    string
	FromOffset int64
	NextOffset int64
	Exists     bool
}

ReadResult is a scrollback slice: the content, the offset it starts at, and the current end marker to hand to the next read.

type ReadResultJSON

type ReadResultJSON struct {
	Content    string `json:"content"`
	FromOffset int64  `json:"from_offset"`
	NextOffset int64  `json:"next_offset"`
	Exists     bool   `json:"exists"`
	Note       string `json:"note,omitempty"`
}

ReadResultJSON is the structured result of a scrollback read.

type RunResult

type RunResult struct {
	Offset   int64
	Snapshot string
	Started  bool // a new shell was created for this run
}

RunResult is what Run returns after submitting a line: the scrollback end marker and a best-effort snapshot of the output captured within the initial window (empty when the command is still running silently).

type RunResultJSON

type RunResultJSON struct {
	Offset  int64  `json:"offset"`
	Output  string `json:"output"`
	Started bool   `json:"started_new_shell,omitempty"`
	Note    string `json:"note,omitempty"`
}

RunResultJSON is the structured result the agent receives from a run: a marker and the initial output snapshot, followed up via shell_session_read with the returned offset.

Jump to

Keyboard shortcuts

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