A completed pwsh foreground call now presents as the bash tool's terminal card with the parsed exit-status pill instead of a generic console fence, and the collapsed row classifies as the shell family (Pwsh title). The marker-to-exit-status parse moves from dsh-tool-bash's private render module into the @deepseek-ai/dsh-bash seam so both shell tools share one inverse of the marker contract (the bash-env precedent). The Web UI needs no per-tool code: the terminal-card bridge maps any card:'terminal' view. Coverage: dsh-bash owns the parse edge cases; the tool-pwsh presenter suite mirrors tool-bash's; the client row-model suite pins the Pwsh shell row; the new keyless pwsh-terminal web lane seeds an authored session, presents it through the real tool on replay, and pins the card golden. TUI is out of scope: the TUI package was removed (ed30088adb), so the Web surface is the only UI the gap affected; the roadmap proposal's stage 2 is updated accordingly.
5.6 KiB
Agent Note: pwsh tool bash parity
Status: implemented
English | 中文
Problem
The first Windows-native foundation shipped dsh-tool-pwsh as a deliberately minimal profile — foreground only (a fresh process per call; no persistent PTY session), no managed-environment parity beyond three hardcoded DSH_* keys, and a marker story ("always [exit code: N]") that diverged from the bash tool's rendering without being declared. Review of that change found the model-visible contract drifting from the implementation: the description promised spill-path reporting the renderer never performed, the README claimed exports that did not exist and rendering the tool did not do, and the tool's own tests pinned the lossy behavior. The minimal profile also left the DSH_* contributor seam duplicated-by-absence: plugins contributing environment facts to ctx.bashEnv had no effect on pwsh calls.
Decision
dsh-tool-pwsh now mirrors dsh-tool-bash call-for-call, minus the sandbox surface, and its model-visible text describes exactly that behavior:
- Rendering adopts the bash story verbatim: stdout, a marked
[stderr]section, truncation notices with spill paths,(no output)for an empty body, and exit markers only for non-zero exits — a clean exit produces no marker. The description and thetool:pwshprompt section state this precisely ("Non-zero exits are reported as[exit code: N]markers"), deliberately not copying the bash prompt's "every result" phrasing, which its own renderer contradicts. run_in_backgroundis wired through the generic task runtime exactly like the bash tool: preflight, owner registration,task_output/task_killcontrol, and the same outcome mapping.pwsh-local's already-mirroredstart()handle backs it.- The
DSH_*environment is shared, not duplicated:BashEnvRegistrymoved out ofdsh-tool-bashinto a new tool-independent@deepseek-ai/dsh-bash-envpackage (ctx.bashEnv+ built-ins + the session-persistence contributor), and both shell tools inject it. Contributors apply to pwsh calls exactly as they do to bash calls, resolving the bash tool'sFIXME(bash-env-ownership). - Windows reality is pinned where bash has no analog: every command runs under a UTF-8 output preamble so the Windows PowerShell 5.1 fallback cannot garble non-ASCII output through the UTF-8-decoding collector, and the prompts teach that Windows forced termination settles as exit 1 without a signal marker.
- Out of scope, unchanged: sandbox escalation (waits for a Windows-confining executor) and persistent PTY shells (backends are Linux/macOS-only; ConPTY is roadmap work). The pwsh-specific terminal card with an exit pill shipped separately in the pwsh UI presentation matches bash decision.
Alternatives considered
Keep the minimal profile and fix only the claims. Rejected: the review's core finding was that text contracts copied from bash drift without the corresponding implementation; a minimal tool plus accurate claims still leaves pwsh calls without background execution, without contributor parity, and with a divergent marker story that must be re-justified forever.
Reject a mismatched executor dialect at load. Attempted and reverted before merge: a ShellDialect marker (bash | powershell) on BashExecutor, with both shell tools throwing when the mounted executor speaks another shell. It forced every executor implementation — including each test and example fake — to declare a dialect, adding noise to every shell-tool test for a guard with no in-repo or plausible deployment to catch (shipped compositions always pair tool-pwsh with dsh-pwsh-local and tool-bash with dsh-bash-local). The pairing contract stays documented in each tool's README instead.
Extract a fully shared tool implementation base (abstract shell dialect, two thin leaves). Considered and deferred: the bash-env extraction and the structural mirror (render.ts/background.ts twins) are the foundation it would rest on; a full base waits until a third dialect or the persistent-PTY twin makes the abstraction's shape observable.
Consequences
- The bash and pwsh tools are now behaviorally interchangeable for foreground and background shell work (minus sandbox), and the pwsh prompt/description sentences are each backed by the renderer — the reviewer's grep-against-code check passes.
- Parity ran BOTH ways once: the pwsh tool's structured foreground abort (
HarnessError('tool call aborted', TOOL_ABORTED)with nameAbortError) was backported to the bash tool, replacing its uncodedError('command aborted')— a model-visible/logged change pinned by exact-shape tests on both sides and by the cancel-tool-calls fixture. @deepseek-ai/dsh-bash-envis a new shipped package;dsh-tool-bash'sdshHomeconfig moved there, so compositions mounting the shell tools must also mountbash-env(the spine bundles do).- Windows-only semantics (CRLF normalization, forced-termination exit-1/signal-null, POSIX-only self-signal) remain pinned by tests as before.
- The pwsh tool's per-file coverage gate rides on the scriptable fake-executor suite (
tests/tools.spec.ts); the real-pwsh integration and Loader-composition suites self-skip wherepwshis absent, mirroring the bash suites' division of labor. - The roadmap proposal's parity stage is delivered; the terminal-card presentation stage shipped in the pwsh UI presentation matches bash decision (the TUI itself was removed), leaving the Windows default composition as the remaining stage.