pub struct ExecContext {Show 27 fields
pub backend: Arc<dyn KernelBackend>,
pub scope: Scope,
pub cwd: PathBuf,
pub prev_cwd: Option<PathBuf>,
pub stdin: Option<Vec<u8>>,
pub stdin_data: Option<Value>,
pub stdin_data_rx: Option<Receiver<Option<Value>>>,
pub pipe_stdin: Option<PipeReader>,
pub pipe_stdout: Option<PipeWriter>,
pub tool_schemas: Arc<[ToolSchema]>,
pub tools: Option<Arc<ToolRegistry>>,
pub job_manager: Option<Arc<JobManager>>,
pub stderr: Option<StderrStream>,
pub pipeline_position: PipelinePosition,
pub interactive: bool,
pub kill_children_on_parent_death: bool,
pub aliases: HashMap<String, String>,
pub ignore_config: IgnoreConfig,
pub output_limit: OutputLimitConfig,
pub allow_external_commands: bool,
pub trash_backend: Option<Arc<dyn TrashBackend>>,
pub dispatcher: Option<Arc<dyn CommandDispatcher>>,
pub cancel: CancellationToken,
pub output_format: Option<OutputFormat>,
pub vfs_budget: Option<Arc<ByteBudget>>,
pub watchdog: Option<Arc<Watchdog>>,
pub overlay_handle: Option<Arc<OverlayHandle>>,
}Expand description
Execution context passed to tools.
Provides access to the backend (for file operations and tool dispatch), scope, and other kernel state.
Fields§
§backend: Arc<dyn KernelBackend>Kernel backend for I/O operations.
This is the preferred way to access filesystem operations.
Use backend.read(), backend.write(), etc.
scope: ScopeVariable scope.
cwd: PathBufCurrent working directory (VFS path).
prev_cwd: Option<PathBuf>Previous working directory (for cd -).
stdin: Option<Vec<u8>>Standard input for the tool (from a redirect, heredoc, here-string, or
ExecuteOptions::stdin). Bytes-typed (GH #176) so a < binfile
redirect over non-UTF-8 content reaches a byte-aware builtin intact
instead of erroring at redirect setup; a text-only builtin still
refuses it loudly when it calls read_stdin_to_text.
stdin_data: Option<Value>Structured data from pipeline (pre-parsed JSON from previous command). Tools can check this before parsing stdin to avoid redundant JSON parsing.
stdin_data_rx: Option<Receiver<Option<Value>>>Sideband receiver for the previous stage’s structured .data, set by the
concurrent pipeline runner. Resolved lazily via Self::resolve_stdin
AFTER the pipe is drained — never pre-read — so a streaming upstream that
only sends its data after writing the pipe can’t deadlock a consumer that
awaits it. Non-Clone, so it’s moved on resolve.
pipe_stdin: Option<PipeReader>Streaming pipe input (set when this command is in a concurrent pipeline).
pipe_stdout: Option<PipeWriter>Streaming pipe output (set when this command is in a concurrent pipeline).
tool_schemas: Arc<[ToolSchema]>Tool schemas for help command.
Arc<[…]> rather than Vec: the full builtin schema catalog (~70
entries, each with its own Vecs and Strings) is snapshotted into a
fresh ExecContext at every command dispatch and pipeline/fork child. As
a Vec that was a deep clone of the whole catalog per command; as an
Arc<[…]> it’s a refcount bump (GH #48, item 8). Immutable after the
kernel seeds it, so a shared slice is the right shape.
tools: Option<Arc<ToolRegistry>>Tool registry reference (for tools that need to inspect available tools).
job_manager: Option<Arc<JobManager>>Job manager for background jobs (optional).
stderr: Option<StderrStream>Kernel stderr stream for real-time error output from pipeline stages.
When set, pipeline stages write stderr here instead of buffering in
ExecResult.err. This allows stderr from all stages to stream to
the terminal (or other sink) concurrently, matching bash behavior.
pipeline_position: PipelinePositionPosition of this command within a pipeline (for stdio decisions).
interactive: boolWhether we’re running in interactive (REPL) mode.
kill_children_on_parent_death: boolArm PR_SET_PDEATHSIG(SIGKILL) on external commands spawned from this
context, so a hard-killed kaish process cannot orphan them.
Seeded from KernelConfig::kill_children_on_parent_death — read that
field for the tradeoff and the macOS gap. It lives here, not on the
Kernel, because both external-command spawn sites (Kernel:: try_execute_external and dispatch.rs’s BackendDispatcher) reach an
ExecContext and only one of them reaches a Kernel; one home keeps
the two pre_exec blocks from drifting.
false for a stand-alone ExecContext built outside a kernel, which
is the pre-existing behavior.
aliases: HashMap<String, String>Command aliases (name → expansion string).
ignore_config: IgnoreConfigIgnore file configuration for file-walking tools.
output_limit: OutputLimitConfigOutput size limit configuration for agent safety.
allow_external_commands: boolWhether external command execution is allowed.
When false, external commands (PATH lookup, exec, spawn) are blocked.
Only kaish builtins and backend-registered tools (MCP) are available.
trash_backend: Option<Arc<dyn TrashBackend>>Trash backend for safe file deletion.
Always present when the kernel creates the context (even if set -o trash
is off — the backend exists so kaish-trash list/restore/empty work
regardless of the trash flag).
dispatcher: Option<Arc<dyn CommandDispatcher>>Command dispatcher for re-dispatching through the full resolution chain.
When set (via Kernel::into_arc()), builtins like timeout can dispatch
inner commands through the full chain (user tools → builtins → .kai scripts
→ external commands) instead of being limited to backend.call_tool().
None when the Kernel was not wrapped via into_arc().
cancel: CancellationTokenCancellation token for this execution path.
Populated by the kernel at execute entry, then propagated through pipeline
stages, foreground forks (scatter workers, concurrent pipeline stages,
$(...) cmdsubs), and into spawned external children. When the token
fires, externals receive SIGTERM/SIGKILL via the wait_or_kill helper.
Default for stand-alone ExecContext constructors is a fresh, never-fired
token so non-kernel test contexts behave as before.
output_format: Option<OutputFormat>Per-execution output format override set by a builtin’s GlobalFlags
flatten (e.g. --json). The dispatcher reads this after tool.execute()
returns and applies the format via apply_output_format.
Builtins set this via GlobalFlags::apply(ctx); external commands
don’t touch it.
vfs_budget: Option<Arc<ByteBudget>>Shared VFS memory budget for this kernel’s MemoryFs mounts.
Arc-cloned from the owning Kernel (or its fork parent) so all
concurrent execution paths draw from the same pool. None means
unbounded. Populated by Kernel::assemble and forwarded through
child_for_pipeline / fork_inner so background jobs and scatter
workers see the same cap as foreground execution.
watchdog: Option<Arc<Watchdog>>The per-execute timeout watchdog, when a script timeout is in effect.
Populated by the kernel at execute entry (alongside cancel) and
shared through child_for_pipeline so forks and pipeline stages can
acquire patient holds against the same script clock. None when no
timeout is configured — ToolCtx::patient then returns an inert guard.
overlay_handle: Option<Arc<OverlayHandle>>Active overlay handle when the kernel was constructed with overlay: true.
Arc-cloned so forks and pipeline stages share the same transaction.
None when no overlay is active (most kernels).
Implementations§
Source§impl ExecContext
impl ExecContext
Sourcepub const STREAM_CHUNK_SIZE: u64
pub const STREAM_CHUNK_SIZE: u64
Default chunk size for forward file scans. Bounds the memory a scan-oriented builtin holds at once, independent of file size.
Sourcepub fn new(vfs: Arc<VfsRouter>) -> Self
pub fn new(vfs: Arc<VfsRouter>) -> Self
Create a new execution context with a VFS (uses LocalBackend without tools).
This constructor is for backward compatibility and tests that don’t need tool dispatch.
For full tool support, use with_vfs_and_tools.
Sourcepub fn with_vfs_and_tools(vfs: Arc<VfsRouter>, tools: Arc<ToolRegistry>) -> Self
pub fn with_vfs_and_tools(vfs: Arc<VfsRouter>, tools: Arc<ToolRegistry>) -> Self
Create a new execution context with VFS and tool registry.
This is the preferred constructor for full kaish operation where tools need to be dispatched through the backend.
Sourcepub fn with_backend(backend: Arc<dyn KernelBackend>) -> Self
pub fn with_backend(backend: Arc<dyn KernelBackend>) -> Self
Create a new execution context with a custom backend.
Sourcepub fn with_vfs_tools_and_scope(
vfs: Arc<VfsRouter>,
tools: Arc<ToolRegistry>,
scope: Scope,
) -> Self
pub fn with_vfs_tools_and_scope( vfs: Arc<VfsRouter>, tools: Arc<ToolRegistry>, scope: Scope, ) -> Self
Create a context with VFS, tools, and a specific scope.
Sourcepub fn with_scope(vfs: Arc<VfsRouter>, scope: Scope) -> Self
pub fn with_scope(vfs: Arc<VfsRouter>, scope: Scope) -> Self
Create a context with a specific scope (uses LocalBackend without tools).
For tests that don’t need tool dispatch. For full tool support,
use with_vfs_tools_and_scope.
Sourcepub fn with_backend_and_scope(
backend: Arc<dyn KernelBackend>,
scope: Scope,
) -> Self
pub fn with_backend_and_scope( backend: Arc<dyn KernelBackend>, scope: Scope, ) -> Self
Create a context with a custom backend and scope.
Sourcepub fn set_tool_schemas(&mut self, schemas: Vec<ToolSchema>)
pub fn set_tool_schemas(&mut self, schemas: Vec<ToolSchema>)
Set the available tool schemas (for help command).
Takes a Vec for caller convenience and converts to the shared
Arc<[…]> the field stores (see the field docs; GH #48).
Sourcepub fn set_tools(&mut self, tools: Arc<ToolRegistry>)
pub fn set_tools(&mut self, tools: Arc<ToolRegistry>)
Set the tool registry reference.
Sourcepub fn set_job_manager(&mut self, manager: Arc<JobManager>)
pub fn set_job_manager(&mut self, manager: Arc<JobManager>)
Set the job manager for background job tracking.
Sourcepub fn set_trash_backend(&mut self, backend: Arc<dyn TrashBackend>)
pub fn set_trash_backend(&mut self, backend: Arc<dyn TrashBackend>)
Set the trash backend.
Sourcepub fn set_stdin(&mut self, stdin: impl Into<Vec<u8>>)
pub fn set_stdin(&mut self, stdin: impl Into<Vec<u8>>)
Set stdin for this execution.
An explicit stdin buffer (< file, heredoc, here-string, or a pipeline
hand-off) supersedes any inherited lazy pipe_stdin. Since read_stdin_*
prefers pipe_stdin, clear it here so redirect precedence holds — a
< file must beat a frontend-seeded piped stdin. Accepts anything
Into<Vec<u8>> — a String/&str (heredocs, here-strings, most
callers) or a raw Vec<u8> (a < binfile redirect, GH #176) both work.
Sourcepub fn take_stdin(&mut self) -> Option<Vec<u8>>
pub fn take_stdin(&mut self) -> Option<Vec<u8>>
Get stdin, consuming it.
Sourcepub fn set_stdin_with_data(&mut self, text: String, data: Option<Value>)
pub fn set_stdin_with_data(&mut self, text: String, data: Option<Value>)
Set both text stdin and structured data.
Use this when passing output through a pipeline where the previous
command produced structured data (e.g., JSON from MCP tools). The text
side is always a genuine String here (structured-data hand-off is a
JSON-producing pipeline stage, never binary).
Sourcepub fn take_stdin_data(&mut self) -> Option<Value>
pub fn take_stdin_data(&mut self) -> Option<Value>
Take structured data if available, consuming it.
Tools can use this to avoid re-parsing JSON that was already parsed by a previous command in the pipeline.
Sourcepub async fn resolve_stdin(&mut self) -> Result<(Option<Value>, String), String>
pub async fn resolve_stdin(&mut self) -> Result<(Option<Value>, String), String>
Resolve stdin for a builtin that can consume either structured .data
or raw text from the previous pipeline stage (jq, scatter, …). Returns
(Some(data), _) when the upstream produced structured data, else
(None, text).
Ordering matters and is the whole point: the pipe is drained to text
FIRST, which runs the upstream producer to completion (it can’t be parked
on pipe backpressure), and only THEN is the structured-data sideband
awaited — by which point the producer has definitely sent it (it sends
before writing/closing its pipe). A streaming upstream that emits a lot
of text before sending its (absent) data therefore can’t deadlock us, and
a fast structured producer (seq) is no longer lost to a startup race
that a one-shot try_recv used to drop on the floor.
Sourcepub fn resolve_path(&self, path: &str) -> PathBuf
pub fn resolve_path(&self, path: &str) -> PathBuf
Resolve a path relative to cwd, normalizing . and .. components.
Sourcepub fn set_cwd(&mut self, path: PathBuf)
pub fn set_cwd(&mut self, path: PathBuf)
Change the current working directory.
Saves the old directory for cd - support.
Sourcepub fn get_prev_cwd(&self) -> Option<&PathBuf>
pub fn get_prev_cwd(&self) -> Option<&PathBuf>
Get the previous working directory (for cd -).
Sourcepub async fn read_stdin_to_text(&mut self) -> Result<Option<String>, String>
pub async fn read_stdin_to_text(&mut self) -> Result<Option<String>, String>
Read stdin as text, erroring on non-UTF-8 instead of silently
lossy-decoding it (which corrupts binary with U+FFFD).
The strict counterpart to Self::read_stdin_to_bytes, for text-only
builtins (grep, sed, awk, cut, sort, jq, …): a binary stream
is a loud error, not a mangle. Returns Ok(None) when there is no stdin
at all. The Err is a ready-to-use message; callers prefix their name.
See docs/binary-data.md.
Sourcepub async fn read_stdin_to_bytes(&mut self) -> Result<Option<Vec<u8>>, String>
pub async fn read_stdin_to_bytes(&mut self) -> Result<Option<Vec<u8>>, String>
Read all of stdin as raw bytes, preserving binary intact.
The byte-clean counterpart to Self::read_stdin_to_text, for
binary-aware builtins (base64, xxd, checksum, wc -c, cmp, …).
Returns Ok(None) when there is no stdin at all (no pipe and no
buffer); an empty pipe yields Ok(Some(vec![])). The buffered source is
already bytes-typed (GH #176), so this is a plain move, never a
re-encode. See docs/binary-data.md.
Anything an earlier Self::read_stdin_line left behind comes first,
then the rest of the pipe — read x; cat gives cat everything after
the line read took, in order, and nothing twice.
A failed pipe read is Err, never Ok(None). “The pipe broke” and
“there was no stdin” are different facts, and collapsing them hands the
builtin a short read dressed up as empty input — wc would report 0
lines and exit 0 on a stream that died halfway, and the bytes already
read would go with it. Self::read_stdin_line propagates this same
error from this same reader; the two now agree. The Err is a
ready-to-use message; callers prefix their name.
Sourcepub async fn read_stdin_line(&mut self) -> Result<Option<String>, String>
pub async fn read_stdin_line(&mut self) -> Result<Option<String>, String>
Read one line from stdin, leaving the rest for the next reader.
This is the stream-shaped counterpart to Self::read_stdin_to_bytes:
it takes a single line and keeps everything after it, so read x; read y
binds two lines and read x; cat hands cat the remainder. Draining to
EOF for one line would discard the rest of the stream — there is no way
to put it back once a pipe has been read.
The trailing newline is stripped, and a final line without one is still
a line. Returns Ok(None) at end of input — no line left, which is a
fact the caller reports, not an empty binding. Err on non-UTF-8, with
the same message shape as Self::read_stdin_to_text.
Sourcepub fn child_for_pipeline(&self) -> Self
pub fn child_for_pipeline(&self) -> Self
Create a child context for a pipeline stage.
Shares backend, tools, job_manager, aliases, cwd, and scope but has independent stdin/stdout pipes.
Sourcepub async fn build_ignore_filter(&self, root: &Path) -> Option<IgnoreFilter>
pub async fn build_ignore_filter(&self, root: &Path) -> Option<IgnoreFilter>
Build an IgnoreFilter from the current ignore configuration.
Returns None if no filtering is configured.
Sourcepub async fn snapshot_overwrites(
&mut self,
command: &str,
targets: &[(String, bool)],
) -> Result<GateExpectations, ExecResult>
pub async fn snapshot_overwrites( &mut self, command: &str, targets: &[(String, bool)], ) -> Result<GateExpectations, ExecResult>
Snapshot a batch of truncating overwrites into the trash, the way rm
snapshots deletes — so tee/patch/sed -i can’t clobber a file
under set -o trash without leaving a recoverable prior copy.
Each target is (display_path, is_append). A path that doesn’t exist
yet or is an append has nothing to lose and passes. For an existing
file under set -o trash, the prior content is copied to trash first
(via trash_bytes) so it’s recoverable; the file is left in place for
the caller to overwrite. With trash off, every target passes: the
kernel does not decide whether an overwrite is allowed.
Ok(snapshots) means every snapshot is done and the caller may write
all targets; snapshots maps each trash-snapshotted target’s resolved
path to its prior bytes, so a byte-oriented caller can pass them as the
expected to overwrite_checked for a binary-safe compare-and-swap.
Err(result) is what the caller must return verbatim — a trash failure
is an error, never a fall-through to a destructive overwrite.
Sourcepub async fn expand_glob(&self, pattern: &str) -> Result<Vec<PathBuf>, String>
pub async fn expand_glob(&self, pattern: &str) -> Result<Vec<PathBuf>, String>
Expand a glob pattern to matching file paths.
Returns the matched paths (absolute). Used by builtins that accept glob patterns in their path arguments (ls, cat, head, tail, wc, etc.).
Sourcepub async fn expand_paths(
&self,
positional: &[Value],
) -> Result<Vec<String>, String>
pub async fn expand_paths( &self, positional: &[Value], ) -> Result<Vec<String>, String>
Expand positional arguments, resolving glob patterns to relative paths.
Used by file-processing builtins (cat, head, tail, wc) that accept glob patterns in their path arguments. Non-string values are converted to strings (matching shell conventions).
A Value::Bytes operand goes LOUD (GH #93 item 1), and Value::Json
(list/record), Value::Bool, and Value::Null operands go LOUD too
(GH #121) — none is silently dropped by a catch-all anymore. Every
caller here falls back to reading stdin (or a generic “missing path”
error) when the path list comes back empty, so a structured, bool, or
null path used to vanish into a wrong data source instead of erroring.
The match is exhaustive over all 7 Value variants on purpose: a
future new variant fails to compile here until handled, rather than
silently falling through a wildcard arm.
Sourcepub async fn read_file_chunked<F>(
&self,
path: &Path,
chunk_size: u64,
f: F,
) -> BackendResult<()>
pub async fn read_file_chunked<F>( &self, path: &Path, chunk_size: u64, f: F, ) -> BackendResult<()>
Stream a file’s bytes forward in chunk_size slices, handing each
non-empty chunk to f.
Reads are issued as positional read_range requests, so backends slice
without materialising the whole file (LocalFs seeks; MemoryFs/OverlayFs
slice their stored bytes). The loop terminates on the first empty chunk,
which every backend returns once the offset reaches EOF. f returns a
ControlFlow: Break stops the loop early
(e.g. a consumer that has detected binary content and will discard the
rest), so we don’t keep reading a file the caller is done with. This is
the shared engine for scan-oriented builtins (wc, checksum, grep)
that walk a file front-to-back and must not hold it all in memory.
Trait Implementations§
Source§impl ToolCtx for ExecContext
The kernel’s full execution context satisfies the trimmed portable
ToolCtx contract that out-of-tree tools see.
impl ToolCtx for ExecContext
The kernel’s full execution context satisfies the trimmed portable
ToolCtx contract that out-of-tree tools see.
Trusted in-tree builtins recover the concrete ExecContext (job control,
pipes, dispatcher) through
ToolCtx::as_any_mut.
Source§fn backend(&self) -> &Arc<dyn KernelBackend> ⓘ
fn backend(&self) -> &Arc<dyn KernelBackend> ⓘ
Source§fn resolve_path(&self, path: &str) -> PathBuf
fn resolve_path(&self, path: &str) -> PathBuf
.
and .. lexically. Never touches the real filesystem.Source§fn var(&self, name: &str) -> Option<Value>
fn var(&self, name: &str) -> Option<Value>
Source§fn set_output_format(&mut self, format: OutputFormat)
fn set_output_format(&mut self, format: OutputFormat)
--json). Read moreAuto Trait Implementations§
impl !RefUnwindSafe for ExecContext
impl !UnwindSafe for ExecContext
impl Freeze for ExecContext
impl Send for ExecContext
impl Sync for ExecContext
impl Unpin for ExecContext
impl UnsafeUnpin for ExecContext
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T, S> SpanWrap<S> for Twhere
S: WrappingSpan<T>,
impl<T, S> SpanWrap<S> for Twhere
S: WrappingSpan<T>,
Source§fn with_span(self, span: S) -> <S as WrappingSpan<Self>>::Spanned
fn with_span(self, span: S) -> <S as WrappingSpan<Self>>::Spanned
WrappingSpan::make_wrapped to wrap an AST node in a span.