Semantic resolutions beyond line merges: - The bash seam keeps resolveMode + the bash/resolve-mode waterfall on master's task-free BashExecutor (run/start/resolve only; BashProcess handles); tool-bash consults it at its stamping site and escalation baseline on master's render/background split, with a waterfall test on the recording executor. - dsh-mode's BASH_FAMILY narrows to ['bash']: bash_output/bash_kill are replaced by the kind-generic task_output/task_kill, which span every task kind and only observe or stop work, so the access cap withholds only the starter it can reason about. - The plan-mode snapshot suite adopts master's pin grammar (tool-schema sidecars; the expectedHeaderSnapshots extension is gone — the exit transition deltas, and entering-before-turn-1 needs no second snapshot); modes-advertise joins the plan header class (no-model, so membership is vacuous). Fixtures re-recorded on the acp-demo bin; the replay overlay gains the passthrough sandbox runner. - examples/plan-acp-agent rewires to @deepseek-ai/dsh-acp-demo and drops its tool-bash entry (the spine bundle now composes it); dsh-stdio (the renamed stdio-chat home) keeps its /mode command and gains the dsh-mode peer edge; the acp bridge keeps the modes surface beside master's permission presets. - mode README gains the Model Experience / Known Limitations sections the new README gates require; AGENTS.md ceiling 1370 → 1440 for the kept mode/ layout line and Agent efficiency section.
@deepseek-ai/dsh-bash
The bash executor seam: an abstract BashExecutor service (ctx.bash) defining WHAT a bash backend does — run foreground commands and start background processes — without saying HOW. Task ids, ownership, collection, cancellation, and notices belong to the generic ctx.tasks runtime.
This package is the interface quarter of the bash capability, split so each concern can evolve (and be swapped) independently:
| Package | Role |
|---|---|
@deepseek-ai/dsh-bash (this) |
the interface: abstract service + vocabulary types |
@deepseek-ai/dsh-bash-local |
an implementation: local subprocesses |
@deepseek-ai/dsh-bash-sandbox |
an implementation: dsh-bash-local's mechanics with every spawn confined via ctx.sandbox, denials reported as result facts |
@deepseek-ai/dsh-tool-bash |
the model-facing tool schemas over ctx.bash |
The split mirrors the LLM seam (LlmService/LlmAdapter) and the agent-tool survey: pi hides execution behind a BashOperations interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. dsh-bash-sandbox is exactly that swap in action — a sandboxing executor behind the same interface; the consumer detects its sandboxMode capability and adds escalation fields without importing the implementation. A containerized or remote executor slots in the same way.
Service API (ctx.bash)
| Member | Semantics |
|---|---|
run(spec) |
Foreground execution. Resolves when the command finishes. Rejects only for infrastructure failures (unusable workdir, missing shell, pre-aborted signal); nonzero exits, timeout kills, and abort kills resolve with a descriptive BashRunResult. |
start(spec) |
Background execution. Returns a task-free BashProcess handle immediately; no timeout applies. The caller may adapt it into ctx.tasks. |
sandboxMode |
The capability fact for the tool layer: the default mode a SANDBOXING executor confines under (undefined in the base class — "this executor does not sandbox"). dsh-tool-bash reads it at registration to advertise the escalation fields only when the composition honors them. |
resolveMode(session) |
The per-call sandbox-mode resolution: the session's standing override falling back to the executor default, dispatched through the bash/resolve-mode waterfall so policy plugins narrow it per call (dsh-mode's access cap is the shipped listener). Returns undefined — without consulting the waterfall — for a never-confining executor. dsh-tool-bash stamps the result onto each request; a freshly-approved escalation grant outranks it. |
BashProcess.readOutput() |
Incremental output read — consecutive reads never re-deliver. Reads that lost data to buffer bounds flag lossy and point at full-stream spill files. |
BashProcess.kill() |
Kill the process group. Returns false when it already finished. |
Implementations subclass BashExecutor and implement the abstract methods. Disposal must kill every running process and await its exit — see the HMR-safety tests.
Vocabulary
BashExecRequest (command, workdir?, timeoutMs?, signal?, stdin?, env?, sandboxMode?) resolves to BashExecSpec (command, workdir, timeoutMs, signal?, stdin?, env?, sandboxMode) before execution. sandboxMode is optional on the request and required-but-nullable on the resolved spec: it carries an approved one-shot escalation or the session's standing override; a sandboxing executor stamps its configured default when absent, while a non-sandboxing executor carries the field and confines nothing.
The seam also owns the per-session mode override vocabulary: the log-only 'bash/sandbox-mode' session event, the pure effectiveSandboxMode(events) fold, the setSandboxMode(session, mode) write path, and THE read path resolveMode(session) above, which folds the override and runs the bash/resolve-mode waterfall around it. SANDBOX_MODES is the narrowest→widest ladder; the ordering is part of the contract (the escalation widening check and a mode's access clamp compare by index). run() returns BashRunResult; start() returns BashProcess, whose incremental read and kill methods are adapted by dsh-tool-bash into a generic task registration. A sandboxing executor stamps BashSandboxInfo on foreground results and settled process handles. See src/types.ts and core-data-structures/bash.md.
stdin and env are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload on stdin and its CLAUDE_PROJECT_DIR/CLAUDE_PLUGIN_ROOT env. The model-facing dsh-tool-bash tool does not expose them as parameters — a model already has equivalent power through shell syntax (FOO=bar cmd, a heredoc), so they would be redundant tool params. This is not a security boundary: the implementation's credential scrub (not these fields) is what keeps the harness's ambient secrets out of a spawned command. They are plain optionals on the resolved spec; a missing value means "none". See the bash-stdin-env RFC.
Model Experience
Indirectly, through dsh-tool-bash, which turns executor output and sandbox facts into guidance and retained tool-result tokens.
Known Limitations and Deferred Work
- No interactive-input vocabulary —
stdinis written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept. - Foreground timeouts are always executor-owned — a caller-owned-deadline mode on the seam is explicitly deferred by the tool-call timeout-policy RFC.