pwsh tool bash parity
pwsh 工具与 bash 对齐
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. The model-visible contract drifted from the implementation: the descri
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. The model-visible contract drifted 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.shellEnv had no effect on pwsh calls.
Decision
dsh-tool-pwsh now mirrors dsh-tool-bash call-for-call, 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 job runtime exactly like the bash tool: preflight, owner registration,job_output/job_killcontrol, and the same outcome mapping.pwsh-local's already-mirroredstart()handle backs it.- The
DSH_*environment is shared, not duplicated:ShellEnvRegistrymoved out ofdsh-tool-bashinto a new tool-independent@deepseek-ai/dsh-shell-envpackage (ctx.shellEnv+ built-ins + the session-persistence contributor), and both shell tools inject it. Contributors apply to pwsh calls exactly as they do to bash calls; shared environment ownership therefore sits outside either model-facing shell tool. - 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: persistent PTY shells (backends are Linux/macOS-only; ConPTY is roadmap work). Sandbox escalation shipped later with the Windows ACL sandbox decision — the pwsh tool now carries the sandbox denial rendering and the same-turn
sandbox_permissionsescalation surface, plus the Windows ConstrainedLanguage contract in its description. 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: 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 ShellExecutor, 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 shell-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, background, and sandboxed shell work (the sandbox surface arrived with the Windows ACL sandbox decision), 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-shell-envis a new shipped package;dsh-tool-bash'sdshHomeconfig moved there, so compositions mounting the shell tools must also mountshell-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.
中文
问题
首个 Windows 原生基础交付的 dsh-tool-pwsh 是刻意最小的画像——仅前台(每次调用都启动新进程;无持久 PTY 会话)、受管环境只有三个硬编码 DSH_* 键、以及一个未声明就偏离 bash 工具的 marker 故事(「恒打 [exit code: N]」)。模型可见约定曾与实现脱节:描述承诺了渲染器从未执行的 spill 路径报告,README 宣称了不存在的导出与工具未做的渲染,工具自己的测试还钉死了有损行为。最小画像还让 DSH_* contributor seam 因缺席而重复:向 ctx.shellEnv 贡献环境事实的插件对 pwsh 调用毫无作用。
决策
dsh-tool-pwsh 现在逐调用镜像 dsh-tool-bash,其模型可见文本精确描述这一行为:
- 渲染行为与 bash 完全一致:stdout、带标记的
[stderr]段、带 spill 路径的截断通知、空体渲染(no output)、退出 marker 仅限非零退出——干净退出不产生 marker。描述与tool:pwsh提示词部分精确陈述这一点(「Non-zero exits are reported as[exit code: N]markers」),刻意不复制 bash 提示词中与其自身渲染矛盾的「every result」措辞。 run_in_background经通用任务运行时接线,与 bash 工具完全一致:预检、owner 注册、job_output/job_kill控制与相同的结果映射。其背后是pwsh-local早已镜像好的start()句柄。DSH_*环境共享而非复制:ShellEnvRegistry从dsh-tool-bash迁入新的工具无关包@deepseek-ai/dsh-shell-env(ctx.shellEnv+ 内置事实 + 会话持久化贡献方),两个 shell 工具都注入它。contributor 对 pwsh 调用与 bash 调用一视同仁;因此,共享环境的所有权不属于任何一个面向模型的 shell 工具。- Windows 现实在 bash 无对应处钉死:每条命令都在 UTF-8 输出前置代码下运行,使 Windows PowerShell 5.1 兜底无法经 UTF-8 解码的 collector 破坏非 ASCII 输出;提示词说明 Windows 强制终止以 exit 1 结算,不产生 signal marker。
- 范围外,不变:持久 PTY shell(后端仅限 Linux/macOS;ConPTY 属路线图)。沙箱升级随 Windows ACL 沙箱决策 稍后交付——pwsh 工具现在携带沙箱拒绝渲染与同轮次
sandbox_permissions升级面,外加其描述中的 Windows ConstrainedLanguage 约定。带退出 pill 的 pwsh 专属 terminal 卡已随 pwsh UI 呈现与 bash 对齐 决策另行交付。
备选方案
保留最小画像,只修声明。 否决:从 bash 复制的文本约定在缺少对应实现时会漂移;最小工具加准确声明仍让 pwsh 调用没有后台执行、没有 contributor 对等、并留下一个必须永远重新辩护的偏离 marker 故事。
在加载时拒绝不匹配的执行器方言。 合并前尝试过并撤回:在 ShellExecutor 上加 ShellDialect 标记(bash | powershell),两个 shell 工具在挂载的执行器说另一种方言时抛错。它迫使每个执行器实现——包括每个测试与示例的 fake——都要声明 dialect,为一道仓内及合理部署中都没有目标可拦的护栏(交付组合总是把 tool-pwsh 配 dsh-pwsh-local、tool-bash 配 dsh-bash-local)给每个 shell 工具测试添噪。配对约定改由各工具 README 记录。
提取完全共享的工具实现基座(抽象 shell 方言,两个薄叶子)。 考虑后推迟:shell-env 提取与结构镜像(render.ts/background.ts 孪生)是它要立足的基础;在出现第三种方言或持久 PTY 孪生、让抽象的形态可观察之前,不做完整基座。
后果
- bash 与 pwsh 工具在前台、后台与沙箱化 shell 工作上行为可互换(沙箱面随 Windows ACL 沙箱决策到来),pwsh 的提示词/描述句每句都有渲染器背书——reviewer 的「拿代码 grep 对证」检查通过。
- 对齐也反向发生过一次:pwsh 工具的结构化前台中止(
HarnessError('tool call aborted', TOOL_ABORTED),name 为AbortError)被回移到 bash 工具,取代其无码的Error('command aborted')——这是模型可见/入日志的变更,由两侧的精确形状测试与 cancel-tool-calls fixture(测试前置数据)钉住。 @deepseek-ai/dsh-shell-env成为新的交付包;dsh-tool-bash的dshHome配置迁往那里,因此挂载 shell 工具的组合也必须挂载shell-env(主干组合包已如此)。- Windows 专属语义(CRLF 归一化、强制终止 exit-1/signal-null、仅 POSIX 的自信号)一如既往由测试钉住。
- pwsh 工具的逐文件覆盖率门禁由可脚本化的 fake 执行器套件(
tests/tools.spec.ts)承担;真实 pwsh 的集成与 Loader 组合套件在无pwsh的宿主自跳过,与 bash 套件的分工一致。 - 路线图提案的 parity 阶段已交付;terminal 卡呈现阶段随 pwsh UI 呈现与 bash 对齐 决策交付(TUI 本身已移除),剩余阶段是 Windows 默认组合。