为 dsh 提供基于 PTY 的持久化交互式 shell 后端,支持 bash 和 pwsh 方言,通过 sandboxPolicy 实现安全隔离和就绪检测
- 语言
- TypeScript
- License
- MIT
- 分支
- master
安装
$ dsh plugin --profile web add npm:@deepseek-ai/dsh-terminal-bash在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
English | 中文
Persistent shell backend for ctx.terminals over ctx.subprocess.spawnTerminal. It starts an interactive shell under the shared ctx.sandboxPolicy, retains bounded line-oriented output, and detects readiness while the subprocess provider owns PTY allocation, environment scrubbing, foreground process groups, signalling, and complete terminal-session cleanup. The same PTY backend therefore composes with local or remote execution-world providers.
Plugin (terminal-bash)
The plugin injects pty, sandboxPolicy, and subprocess, then registers the configured backend type (shell). danger-full-access starts the shell directly without requiring a sandbox provider; confined modes require a same-world ctx.sandbox and wrap the exact shell argv through it, failing before spawn when none is mounted. At spawn, one ctx.sandboxPolicy.resolve({ session }) call supplies both the effective mode and the session workspace root; the same root is the default shell cwd when the caller omits one. A change to a different effective mode is rejected before its sandbox/mode event commits while that owner has an open PTY or a spawn in progress; the fence is attached to the exact owner and therefore outlives a provider reload that retains existing sessions. Wait for creation to settle and close the sessions before changing modes, so a terminal opened with wider access cannot survive a downgrade.
shellDialect selects the shell stack (bash default, pwsh): it picks the default shellPath/shellArgs (bash --noprofile --norc -i; pwsh -NoLogo -NoProfile through the shared dsh-pwsh-local resolver) and the startup contract. The bash dialect installs its prompt through the environment (PS1 plus an OSC 133;D;-terminated PROMPT_COMMAND). pwsh cannot install a prompt from the environment, so the backend writes a prompt function through the session and waits until the controlled prompt is actually visible — looping over follow-up sends because the pwsh banner-to-prompt gap can outlast the silence bound — while its environment drops the bash-only markers and adds NO_COLOR. That first send also prefixes the shared dsh-pwsh-local encoding preamble, pinning [Console]::OutputEncoding and $OutputEncoding to UTF-8 before anything runs: the session decode path reads PTY bytes as UTF-8, and an un-pinned console writes its host code page for non-ASCII output. Both dialects emit the same BEL-terminated OSC marker, so the readiness machinery and consumers are dialect-agnostic.
Readiness combines a foreground-verified private bash prompt marker, provider-reported foreground stdin-wait facts, silence fallback, and absolute timeout. A marker is not ready until the printable tail after the latest owned marker exactly equals the controlled PS1, including when the OSC marker and prompt are split across data callbacks; echoed input or output following an earlier prompt therefore cannot settle the current send. The controlled PROMPT_COMMAND re-asserts that PS1 before every prompt, so an in-shell prompt override cannot degrade later sends to silence readiness. Prompt and silence evidence collected before the provider write, including while pre-write foreground inspection is pending, is discarded at the write boundary. When bash prints the marker before the terminal provider publishes its return to the foreground process group, polling retains the candidate for handoffGraceMs past the ordinary silence bound so a coincident handoff can win. An interactive child that inherits PROMPT_COMMAND therefore cannot suppress inferred-idle readiness until the absolute timeout. Unknown foreground state is never a positive exact-idle signal. A foreground group's stdin wait that existed before a send is likewise not post-write readiness: the same group must be observed outside that wait before a later wait can settle the send, while a changed foreground group is new evidence. During unpublished startup, a fallback requires observed output; zero-output silence cannot publish an empty session, and timeout rejects the spawn. Cancellation closes the unpublished shell and rejects with the caller's exact abort reason; TerminalBackendCleanupError separately preserves a cleanup failure. The caller's signal is forwarded for terminal allocation and readiness initialization; after publication the handle owns its lifetime. Incomplete terminal-control sequences are bounded by maxReadBytes and discarded through their terminator after crossing that limit; malformed UTF-8 terminal output uses replacement characters, and a trailing carriage return is carried across callbacks so split CRLF becomes one newline.
Send cancellation marks queued input as canceled before asking the terminal handle to signal the current foreground process group with a real SIGINT; if asynchronous pre-write inspection later settles, it cannot execute that input. If a provider write is already in flight, signalling waits for it to settle; a rejected write sends no signal. The canceled send retains its slot until the write and foreground signalling settle, so a successor cannot receive either late bytes or that signal. A provider write or signal that never settles therefore retains the slot indefinitely; closing the session (terminal_close) is the recovery. The absolute deadline remains armed while cancellation waits. A signal failure is a terminal transport failure and rejects the active send. Cancellation never emulates interruption by writing \x03, so raw-mode programs remain cancellable. Close rejects new public signals, stops readiness polling, and awaits the handle's provider-owned complete-session termination before settling the active send as session_exit.
Model Experience
Current file policy and indirect consumer
What the model sees
The policy owner contributes capability-neutral sandbox:policy context. Through @deepseek-ai/dsh-tool-terminal or another PTY consumer, the model may also receive bounded MOTD, send deltas, scrollback pages, readiness reasons, and cleanup errors.
Token effect
The current-policy clause is present while this backend is mounted. Retained PTY scrollback is not placed in model history until a consumer returns bounded output.
KV Cache effect
A standing-policy change appends an owner-rendered superseding runtime-context snapshot after retained history; consumer results remain append-only.
Known Limitations and Deferred Work
- Line-oriented output is normalized; full-screen alternate-buffer interaction is unsupported.
- Exact stdin-wait detection depends on the mounted subprocess provider; providers that cannot prove it use prompt-marker and silence/timeout readiness. Windows is such a provider: the shell pid is the pseudo foreground group and there is no exact stdin-wait tier, so a marker-less child settles on the silence bound.
- The pwsh bootstrap writes through
[Console]::(the UTF-8 encoding pin and the prompt function), which the Windows ACL sandbox's read-only mode (ConstrainedLanguage) may deny. The shell can still settle through the controlled printable prompt and silence tier, but marker readiness is unavailable and non-ASCII output may follow the host code page. - Cleanup guarantees are those of
SubprocessTerminalHandle; provider-specific gaps belong to that implementation's contract rather than this PTY consumer. - Sessions do not survive harness process exit.