Files

description, kind
description kind
The model-facing bash tool for users and maintainers choosing, configuring, or debugging one-shot command execution, background jobs, and sandbox escalation. package-reference

@deepseek-ai/dsh-tool-bash

English | 中文

Summary

dsh-tool-bash runs Bash commands and returns stdout, stderr, and exit markers. Each call uses a fresh shell; cwd, variables, and functions do not persist. With a job registry composed, every command is a job from its start: run_in_background returns the id at once, a foreground command that outlives its timeout returns the same id, and job_output/job_kill collect and stop it. Commands receive the managed DSH_* environment; sandbox denials can be retried once with wider sandbox_permissions, a justification, and user approval. Nonzero exits are results for the agent to interpret. Mount an executor such as dsh-bash-local or dsh-bash-sandbox with dsh-shell-env.

Table of Contents


Use this package

Load this plugin in any composition where the agent should run bash commands: it registers the bash tool once an executor provider and the dsh-shell-env registry are mounted, and stays pending until the tools, shell, systemPrompt, and shellEnv services exist.

Minimal configuration

The common path is an executor provider, the environment registry, and this tool; add the job runtime when the agent may run commands in the background.

- name: '@deepseek-ai/dsh-bash-local'
- name: '@deepseek-ai/dsh-shell-env'
- name: '@deepseek-ai/dsh-tool-bash'

# Optional: background jobs
- name: '@deepseek-ai/dsh-jobs-local'
- name: '@deepseek-ai/dsh-tool-jobs'

The config fields govern the background surface.

Field Default Meaning
enableRunInBackground true Expose run_in_background while a job registry is composed; when false, forced background calls are rejected
promoteOnTimeout true Keep a foreground command that reaches its timeout running as its background job instead of killing it

The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc; the generated tool catalog carries the full argument schema.

Running a command

The tool executes bash -c <command> and returns the combined output. Commands run in a fresh shell every call, so state never persists — pass workdir instead of cd. A non-zero exit is reported as [exit code: N] for the agent to interpret, not surfaced as a tool error. A description in active voice (5–10 words) labels the call in the UI; timeoutMs overrides the executor's default and cap. Output beyond the executor's stream caps is truncated to its tail, with the full output saved to a spill file whose path is reported.

Running long commands in the background

Passing run_in_background: true admits a job and returns its id immediately; confinement preparation may still be pending, and no background execution timeout applies. Output is empty until the process is available. Job cancellation aborts preparation and stops any process that arrives afterward; startup failure settles the admitted job as failed. The agent reads its output with job_output (non-blocking unless wait: true), lists jobs with job_list, and stops it with job_kill; a finished job notifies the owning agent in-session. Background support needs the generic job runtime (dsh-jobs-local) and its control tools (dsh-tool-jobs) mounted. The background job hands the run's non-consuming observed readers to the job registry as pull sources; the registry pumps them into the job's output ring at its own cadence (pumpPollMs on dsh-jobs-local), so the Web client streams live output and the model's job_output reads consume the same bytes through a separate cursor. A reader that throws is logged once and its stream stops; the job runs on to its own settlement. The ring is a best-effort live preview: stdout and stderr are copied per poll round, so writes the two streams made inside one poll window appear stdout first rather than in write order.

Foreground commands as jobs

With a job registry composed, a foreground command is registered with ctx.jobs at its start and the call waits on that job: the command is listed, streams through job.list and job.follow, and can be stopped from the Web task list for as long as it runs. A command that finishes within the timeout returns the ordinary foreground result and its job record leaves the registry with it, so the model never sees an id. A command that outlives the timeout keeps running as the job it already was, and the call returns [still running after <timeoutMs>ms; moved to background job <id>] plus the job hand-off guidance, seeded with one consuming read of the output so far — job_output continues exactly after it. A kill from outside the call (the human stopping the job) settles the foreground result with [stopped: <reason>] ahead of the signal marker, so the model reads the reason instead of a command failure; cancelling the call itself kills the job. Registration is best-effort: promoteOnTimeout: false, a missing job registry, or a registry that refuses the job at its start (the owner's job limit, no controller) run the command under the executor's deadline kill instead, and the timeoutMs parameter description advertises the hand-over only when it holds.

Sandboxed execution and escalation

When the mounted executor confines commands (for example dsh-bash-sandbox), a blocked file operation is reported as [sandbox: file access denied under <mode> mode] — a policy denial, not a command failure. The model may then retry the exact same command once in the same turn with sandbox_permissions (the narrowest wider mode that suffices) and a one-sentence justification; the approval prompt raised by that retry is how the user consents. Request wider access only after a real denial; a rejected escalation is final for that command. Repeating the current mode runs without approval, while a narrower target fails before execution. Without sandbox_permissions, justification may be omitted, empty, or whitespace-only; a non-empty reason without a mode is rejected. Repeating the effective mode also permits an omitted or blank reason. A different requested mode requires a non-empty reason; widening still requires approval.

What can go wrong

A composition with no executor provider never activates the tool. Background calls without the job runtime fail with background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs, and sandbox_permissions without a sandboxing executor fails with sandbox_permissions is not available in this composition (no sandboxing executor to escalate). enableRunInBackground: false removes the parameter and rejects a forced background call at execution time.


Understand the implementation

Implementation internals — click to expand

This section explains the design decisions behind the tool and points at the code that realizes them; the observable behavior is fully covered in Use this package.

Design philosophy

  • Model-facing consumer of the shell seam. The tool is the Consumer role of the bash capability: it registers the bash schema, renders results, and resolves per-call policy, while the executor seam owns process mechanics.
  • Request from named args only. The tool never exposes stdin, env, or stdoutMaxBytes; it builds each request from command/workdir/timeout/signal fields plus the registry-collected dshEnv, so model-supplied keys cannot replace managed values.
  • Non-zero exits are reported, not errored. Only infrastructure failures (spawn errors, aborts) surface as tool errors; the model interprets exit codes and markers.
  • Every command belongs to the job runtime when one is composed. A call registers its process handle with ctx.jobs as it starts, whether the model asked for the background or the tool waits on it; ids, ownership, completion notices, and disposal are the runtime's, and this tool only maps bash exit and sandbox facts into job output. Without a registry the tool is foreground-only, and it swaps between the two registrations as the registry comes and goes.

Source map

File Role
src/index.ts Plugin entry: tool registration, prompt section, arg validation, escalation, request assembly
src/background.ts Own asynchronous shell preparation, map process settlement onto job outcomes, and render a ring read as a process read
src/render.ts Model-facing result text: streams, markers, truncation notices
— No runtime invariant companion is published; the environment registry validates ownership and collected values at each mutation/read; it publishes no independent snapshot that a companion could cross-check.

Request resolution

The tool resolves the workdir before ctx.shell.resolve() runs: an explicit relative workdir is resolved against the session cwd, and a sandbox policy's canonical workspace root wins so confinement and launch use the same identity. Sandbox policy resolves per call through ctx.sandboxPolicy; an escalation request goes through ctx.approval before anything executes, and the tool fails at load if the executor confines but no policy service is mounted.

Rendering story

The result text is stdout, then a marked [stderr] section, then conditional markers: truncation notice, sandbox denial (plus the same-turn escalation hint when the composition advertises escalation), timeout, signal, and exit code — each on its own line. The exit marker doubles as the UI card's exit-status pill: the shared parseExitStatus from dsh-shell consumes it from the output body, so replay shows the pill without duplicating the marker.


Further Exploration

Read these pages when the package-level contract is not enough. They move from the shell family to the executor seam, the job runtime, and the decision notes behind the behavior.


Model Experience

System prompt

What the model sees

Every request in this plugin's registration scope contains the bash guidance below at first-party order 1000. The policy owner contributes current sandbox state through its cache-safe runtime context rather than changing this section. Scoped tool restrictions can hide the schema without removing this independently registered section.

Bash guidance
Check the [exit code: N] marker on every bash result; investigate failures before moving on.

Token effect

Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches.

KV Cache effect

Prefix-stable while the registration scope and prompt text are unchanged. Plugin activation or disposal may invalidate reuse from this prompt section; sandbox mode switches do not.

Tool schemas

What the model sees

The model sees the generated bash schema. run_in_background appears only when this producer enables it and a job registry is composed; sandbox_permissions and justification appear only when the mounted executor advertises sandboxing; the justification asks the model to use the language of the current user request. Agent-scoped tool restrictions can remove the definition for that agent.

Token effect

Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields and its conditional description paragraph.

KV Cache effect

Prefix-stable while visibility, background support, and executor sandbox capabilities are unchanged. A restriction, config change, or executor change may invalidate reuse from the first changed tool definition.

Foreground result

What the model sees

The renderer emits the data-dependent stdout tail, then optional [stderr] and the stderr tail. With no output it emits exactly (no output). Conditional lines are exactly [output truncated; full output: <path-or-(unavailable)>], [sandbox: file access denied under <mode> mode], [timed out after <timeoutMs>ms], [stopped: <reason>], [killed by signal: <signal>], and [exit code: <exitCode>]; the sandbox escalation and runner-failure lines are quoted in dsh-bash-sandbox.

Token effect

Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Background job context and results

What the model sees

Start returns exactly started background job <jobId>. This producer supplies incremental process output, optional [some output was dropped from memory; full output: <paths-or-(unavailable)>], sandbox facts, and terminal detail such as exit code: <exitCode> or signal: <signal> to the generic job runtime. dsh-tool-jobs owns the visible status line, completion notice, listing, and cancellation response.

Token effect

The start acknowledgement is small and retained; collected output is data-dependent and bounded by the executor's stream buffers. Consuming reads do not repeat prior output.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Tool errors

What the model sees

Validation and policy failures are normalized as Error: <message>. This package's stable messages are invalid command: expected a non-empty string, invalid description: expected a non-empty string, invalid timeoutMs: expected a positive number, got <value>, the escalation pairing failures, run_in_background is disabled for this deployment (enableRunInBackground: false), background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs, sandbox_permissions is not available in this composition (no sandboxing executor to escalate), the approval availability/rejection/cancellation variants, and tool call aborted.

Token effect

Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

These limits define when the tool is a poor fit or needs special care. They are current package constraints, not a task backlog.

  • Replay exit pills parse from result text — output whose final line happens to be exactly [exit code: N] / [killed by signal: …] shows a wrong pill on session replay and loses that line from the card body, because the parse treats it as the marker it consumes; a display-only known residual.
  • The bash tool opts out of timeout-policy budgets — it keeps the executor-owned BASH_TIMEOUT path, per the tool-call timeout-policy Agent Note.
  • Background processes have no executor timeout — callers must use job_kill, or rely on owner/service disposal, when work no longer matters; a foreground command registered as a job has none either, since its timeout bounds only the wait.
  • The job list shows every foreground command while it runs — a settled one leaves with its result, but the Web task list does not yet mark which running rows a tool call is still waiting on.

Dev Note

Working context for maintainers — click to expand

None.