Add the activity capability seam: ctx.activities is a pure observation
plane where long-running work (background bash/pwsh, workflow runs) opens a
handle, appends transient output into a byte-offset ring buffer, and settles
exactly once. Nothing model-visible changes: reads are non-consuming and
lossy-by-design (absolute UTF-8 offsets survive eviction and report gaps),
correlation carries {callId, jobId}, and visibility is owner-fenced like jobs.
The seam is strictly optional. Producers tap it through ctx.get('activities')
and contain every observation failure, so an unregistered or broken registry
never affects the underlying job (proven by broken-registry tests).
Ship the full path in one change:
- @deepseek-ai/dsh-activity: Service Definition, pumpActivityOutput pull
substrate, invariant companion; dsh-activity-local: ring-buffer provider
with retention config and owner cleanup.
- shell: ShellProcess.observed exposes non-consuming stdout/stderr readers;
tool-bash/tool-pwsh mirror background jobs into activities.
- tool-workflow: phase/log/agent lines mirrored as a workflow activity.
- api-activity-controller: Typert Remote control (roster baseline+buckets)
and observe (opened/output/status frames, coalesced and byte-capped)
streams plus the client feed service with reference-counted observation.
- ui-activity: session-header action rendering live output through
TerminalBlock, which now shows output while a command is still running.
Docs: activity subsystem page, architecture row, regenerated catalogs, and
the Agent Note (2026-08-24-activity-observation-seam), all bilingual. A
keyless web e2e replays a real run_in_background stream to ARIA goldens.
@deepseek-ai/dsh-bash-local
English | 中文
Local Service Provider for the @deepseek-ai/dsh-shell executor seam over the @deepseek-ai/dsh-subprocess service: LocalBashExecutor spawns bash -c <command> per call as a managed process group through ctx.subprocess, and owns everything bash-shaped — command defaulting and caps, timeout/cancel classification, the model-friendly terminal environment, and the model-facing stdout/stderr merge for background reads. Group mechanics (bounded spill-backed output, credential scrub, kill escalation, disposal) are the subprocess service's.
The package root exports the default and named LocalBashExecutor plugin plus its Config.
Config
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: /path/to/workspace # default: process.cwd()
timeoutMs: 120000 # default foreground timeout
maxTimeoutMs: 600000 # cap for per-call overrides
maxOutputBytes: 64000 # per-stream in-memory cap; overflow spills to disk
maxSpillBytes: 67108864 # per-stream full-output spill cap
graceMs: 3000 # kill escalation and post-exit pipe-drain grace
Behavior
- Spawn per call, no shell state — every call is a fresh non-login
bash -cwith no rc files. - The composition entry is a layer, not the last word — when a settings provider is composed, this executor registers the capability's
bashnamespace with the entry above as its base, so a user section insettings.yamllayers over it and the next command runs with the new budgets. Values the schema cannot judge (positive and finite, thegraceMstimer bound) are refused at the write, leaving the running executor on its last good section; without a provider, or after one detaches, the composition entry is what runs. - Configured budgets over managed groups —
resolve()fillsworkdir/timeoutMs/stdoutMaxBytesfrom config, and every spawn hands the service explicit byte caps, spill cap, andgraceMs. The grace must be positive, finite, and no greater thanMAX_TIMER_DELAY_MS, so Node can represent it with one timer. Process-group kills, post-exit pipe draining, tail retention, and bounded spill files aredsh-subprocess-localmechanics. A foregroundShellExecRequest.stdoutMaxBytescan raise stdout's capture budget for one trusted caller; stderr and background runs still usemaxOutputBytes. - Timeout and cancel classification —
run()fuses its config-clamped timeout with the caller's signal through one deadline; only the executor's own timeout reportstimedOut, an upstream cancel reportsaborted, and a self-signaled command reports neither (timeout-library Agent Note). - Model-friendly terminal env —
NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=catprevents pagers and ANSI color from garbling results. These values merge as ordinary env under the service's credential scrub andDSH_*channel rules; an explicit caller entry still wins. See the stdin/env Agent Note and managed environment Agent Note. - Background processes —
start()returns a liveShellProcesshandle immediately with no timeout, andreadOutput()merges offset-based stdout/stderr reads into one consuming delta, placing stderr under a[stderr]marker when present. A running process belongs to the subprocess service, survives executor reloads, and is killed and joined on service disposal. Job ids, ownership, polling, and notices belong to the genericctx.jobsruntime, which the tool layer registers the handle with.
Model Experience
Indirectly, through dsh-tool-bash, which renders this executor's bounded stdout/stderr tails, background-process deltas, spill-file paths, and infrastructure failures.
KV Cache effect
No direct invalidation; the named consumer owns any request-prefix changes.
Known Limitations and Deferred Work
- Unconfined by itself — this executor always runs commands with the harness process's authority; deployments needing confinement compose
dsh-bash-sandbox, while per-call allow/deny/ask policy belongs ontools/pre-execute. - No persistent shell or PTY — every call starts a fresh non-login
bash -c; cwd-only persistence and interactive terminal sessions remain deferred until a real workflow requires them. - POSIX-only — the
bashbinary is hardcoded, and the underlying service's group semantics are POSIX; Windows is unsupported. - A background spawn-failure note is single-delivery — the subprocess service buffers no output for a process that never ran, so the executor injects
spawn failed: …into exactly onereadOutput()delta; a reader that discards that delta cannot recover it.
Scrub-heuristic and spill-retention caveats live with dsh-subprocess-local, which owns those mechanics.