description: "The bash executor seam for developers and maintainers choosing, composing, or implementing command execution over ctx.shell."
English | 中文
dsh-shell defines the executor service (ctx.shell) that runs shell commands for the harness: foreground commands that resolve with bounded output when they finish, and background processes that return a handle immediately. Every shell executor in the repository — local Bash, sandboxed Bash, local PowerShell, sandboxed PowerShell — implements this one contract, so the model-facing bash and pwsh tools work unchanged over any of them. Callers pass a request and receive a fully-resolved spec with explicit defaults and caps before any command runs. The service itself never renders anything to a model; the shell tools own all model-visible output and sandbox guidance.
Use ctx.shell when an agent or an in-process plugin needs to run a shell command and read its output, or start a background process and poll it. It is the contract every shell executor and the model-facing bash/pwsh tools build on, so code written against it works over any executor implementation.
Call run with a resolved spec to execute a command in the foreground. The promise resolves when the command finishes: a nonzero exit, an executor timeout kill, or a caller abort kill is a result, never a rejection. run rejects only for infrastructure failures such as an unusable working directory or a missing shell. The result carries the exit code or signal, whether a timeout or an abort cut the run short, and the collected stdout/stderr with spill-file paths when a stream overflowed its budget.
const result = await ctx.shell.run(ctx.shell.resolve({ command: 'ls -la' }))
console.log(result.exitCode, result.stdout.text)
Call start with a resolved spec to launch a background process; it returns a handle immediately and no timeout applies. Read output incrementally with readOutput() — consecutive reads never repeat output, and lossy reads point at full-stream spill files. Terminate the provider-managed range with kill() (returns false once the direct command has finished) and await done for direct-command settlement. Job ids, ownership, polling, and notices belong to the generic ctx.jobs runtime, where the tool layer registers the handle.
Every execution starts from a ShellExecRequest with optional fields; the executor's resolve() turns it into a fully-resolved ShellExecSpec with explicit defaults and caps before anything runs. This request/spec split is the repository's template for explicit resolution at package boundaries: callers never rely on hidden defaults inside run or start. resolve() fills the working directory and timeout from the executor's configuration, caps per-call overrides, and carries optional inputs — stdin, ordinary env, and the trusted DSH_* snapshot — through verbatim.
The seam is not an executor: mount exactly one provider per composition, and the tools work unchanged. On POSIX, dsh-bash-local runs commands as fresh bash -c processes and dsh-bash-sandbox confines every command through the sandbox capability; on Windows, dsh-pwsh-local and dsh-pwsh-sandbox are the counterparts. The bash and pwsh tools advertise escalation fields only while a sandboxing executor is mounted. The smallest composition is the executor alone:
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: /path/to/workspace
Tool results end with a machine-readable exit marker — [exit code: N] or [killed by signal: X] — so the model can always tell how a command ended. The seam owns that marker format and the parseExitStatus helper that splits a rendered result back into its output body and structured exit status, keeping the bash and pwsh tools from drifting on it.
Read these pages when the seam contract is not enough. They move from the shared subsystem reference to the concrete executors and the model-facing tools.
bash -c processes, budgets, and deadlines.bash tool over this seam.Indirectly, through dsh-tool-bash, which turns executor output and sandbox facts into guidance and retained tool-result tokens.
No direct invalidation; the named consumer owns any request-prefix changes.
These limits define what the seam does not provide. They are current package constraints, not a roadmap.
stdin is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept.