// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.AI.Tools.Shell; /// /// Pluggable backend that runs shell commands on behalf of a tool. /// /// /// /// runs commands directly on the host (no /// isolation; approval-in-the-loop is the security boundary). /// runs them inside a container with resource /// limits, network isolation, and a non-root user. /// /// /// This is an abstract class rather than an interface so the surface can be /// extended in future versions (e.g., adding new lifecycle hooks) without /// breaking existing third-party implementations. Mirrors the Python /// ShellExecutor Protocol in /// agent_framework_tools.shell._executor_base. /// /// /// Lifetime: is invoked at most once per /// instance (idempotent); tears the executor down /// at the end of its life. There is no public Shutdown step — disposal is the /// teardown. /// /// /// Concurrency and session ownership. A single executor instance is /// intended to serve a single conversation / agent session — i.e., a single /// user. Stateless mode is safe to share across concurrent callers (each /// RunAsync spawns a fresh process or container, so there is no /// shared mutable state). Persistent mode is not shareable: a /// single long-lived shell process backs every call, it carries mutable /// state (working directory, exported variables, history, in-flight /// background jobs) that is visible to every subsequent command, and /// concurrent commands would interleave on its stdin/stdout. The framework /// does not isolate one caller's state from another's. Build one executor /// per session, treat it as owned by that session for its lifetime, and /// dispose it when the session ends. If you register an executor with a DI /// container, use a per-request / per-conversation scope, not a singleton. /// /// public abstract class ShellExecutor : IAsyncDisposable { /// /// Eagerly initialize the backend. Idempotent; subsequent calls are /// no-ops once the executor is started. For stateless executors this is /// typically a no-op (the default implementation returns /// ). /// /// Cancellation token. public virtual Task InitializeAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; /// /// Run a single command and return its result. Implementations are /// expected to apply the configured per-command timeout and surface it /// via + ExitCode = 124. /// /// The shell command to execute. /// Cancellation token. public abstract Task RunAsync(string command, CancellationToken cancellationToken = default); /// public abstract ValueTask DisposeAsync(); }