PowerShell executor and pwsh tool
PowerShell 执行器与 pwsh 工具
The harness spoke one shell dialect on every platform: `bash`. Windows hosts could run it only through WSL or Git-Bash shims, and the shipped `dsh-bash-local` executor is POSIX-only (`bash` hardcoded, process-group semantics POSIX). The Windows roadmap — defaulting hosts to `pwsh`, later pwsh TUI/GUI rendering — had no execution foundation: there was no PowerShell implementation of the bash executor seam and no model
English
Problem
The harness spoke one shell dialect on every platform: bash. Windows hosts could run it only through WSL or Git-Bash shims, and the shipped dsh-bash-local executor is POSIX-only (bash hardcoded, process-group semantics POSIX). The Windows roadmap — defaulting hosts to pwsh, later pwsh TUI/GUI rendering — had no execution foundation: there was no PowerShell implementation of the bash executor seam and no model-facing tool that taught the PowerShell dialect. The bash tool is also larger than a Windows-first profile strictly needs — the persistent-PTY twin in particular is bash-shaped surface the pwsh tool still does not carry. The original minimal profile also left out background jobs and sandbox escalation: background arrived with the parity decision, and the sandbox surface (denial rendering plus sandbox_permissions escalation) arrived with the Windows ACL sandbox decision — the minimal tool was sized for the danger-full-access Windows posture, and that premise ended when the sandbox PR re-enabled confinement and approval on Windows.
Decision
Two new packages under packages/shell/:
@deepseek-ai/dsh-pwsh-local— a local implementation of thectx.shellexecutor seam overctx.subprocess, mirroringdsh-bash-localcall-for-call:resolve()defaults and caps from config,run()fuses the config-clamped timeout with the caller's signal through one deadline,start()returns a consuming background handle whose processes belong to the subprocess service. The command string rides as ONE argv element topwsh -NoLogo -NoProfile -NonInteractive -Command, so PowerShell parses it and no shell-quoting layer exists. Executable resolution (resolvePwshPath) is a pure function of(configured, env, platform): explicit config first, then Windows probes PowerShell 7's install, PATH entries (quotes stripped), and Windows PowerShell 5.1, else a barepwshvia PATH.@deepseek-ai/dsh-tool-pwsh— the model-facing tool overctx.shell, PowerShell-dialect by contract, mirroringdsh-tool-bashcall-for-call: foreground andrun_in_backgroundexecution through the generic job runtime, managedDSH_*environment through the shareddsh-shell-envregistry, the bash marker/truncation rendering story (a clean exit produces no marker), and — since the Windows ACL sandbox decision — the sandbox denial rendering andsandbox_permissionsescalation surface, plus the Windows-specific ConstrainedLanguage and named-pipe contracts in the tool description. The parity decision supersedes this note's minimal-profile tool description.
Windows vitest coverage is deliberately NOT part of this change: the repo's Windows CI lane owns build/static gates, and unit coverage runs on Linux, where both packages' suites run against a real pwsh (preinstalled on the GitHub-hosted runners) or self-skip when absent. The vitest windowsUnsupportedPackages exclusion narrows from packages/shell/* to the bash-requiring packages so the pwsh suites can also run natively on Windows dev machines.
The roadmap beyond this decision — defaulting Windows hosts to pwsh (bash off), and pwsh TUI/GUI rendering — is recorded separately as the Windows pwsh default decision.
Alternatives considered
Extend dsh-bash-local with a pwsh mode. Rejected: the executor's identity is the shell it spawns; a second dialect inside one package doubles its config surface (shell switches) and its test matrix, and the two dialects' quirks (signal facts on Windows, quoting domains) belong to their own packages' documentation.
Extend dsh-tool-bash with a dialect parameter. Rejected: the model-visible contract is the dialect itself (paths, variables, exit facts differ), so a dialect parameter would either churn the schema conditionally or force one tool to teach two dialects; the separate twin keeps the model contract honest — and carries the shared surfaces (background, sandbox, rendering) by mirroring rather than by sharing an implementation.
Wire the pwsh tool into the shipped CLI compositions now. Rejected: mounting tool-pwsh + pwsh-local in base.cordis.yml would change the shipped roster before the Windows-default decision lands; this change ships the capability and its wiring points (apps/cli dependencies, tsconfig projects) without switching any default.
Consequences
- The bash executor seam gains a second, Windows-native implementation with an identical request/spec contract, so model-facing consumers beyond
tool-pwsh(hooks bridges, in-process plugins) can run PowerShell without dialect shims. tool-pwshis the model-visible Windows-first shell tool: behaviorally interchangeable with the bash tool for foreground, background, and sandboxed work — including the same-turnsandbox_permissionsescalation throughctx.approval— with prompt guidance that states the marker contract, the sandbox denial/escalation vocabulary, and the ConstrainedLanguage and named-pipe boundaries precisely.- Windows semantics differ where the platform differs: forced termination reports exit 1 with no signal (so
signal/killedstatus facts are POSIX-only), and PowerShell writes CRLF, which tests normalize. - The CLI gains two workspace dependencies and two tsconfig projects without mounting either plugin — the composition decision stays with the Windows-default proposal.
中文
问题
harness 在每个平台只说一种 shell 方言:bash。Windows 主机只能通过 WSL 或 Git-Bash 垫片运行它,而交付的 dsh-bash-local 执行器仅限 POSIX(硬编码 bash,进程组语义是 POSIX 的)。Windows 路线图——让主机默认 pwsh,之后再做 pwsh TUI/GUI 渲染——没有执行基础:既没有 bash 执行器 seam 的 PowerShell 实现,也没有教模型 PowerShell 方言的面向模型工具。bash 工具也大于 Windows 优先画像的严格所需——尤其持久 PTY 孪生是 pwsh 工具至今仍不背负的 bash 形状表面。最初的最小画像也没有后台任务与沙箱升级:后台随 parity 决策 到来,沙箱面(拒绝渲染加 sandbox_permissions 升级)随 Windows ACL sandbox 决策 到来——最小工具当初按 danger-full-access 的 Windows 姿态裁剪,这一前提在 sandbox PR(Pull Request)于 Windows 上重新启用隔离与审批时终结。
决策
在 packages/shell/ 下新增两个包:
@deepseek-ai/dsh-pwsh-local——ctx.shell执行器 seam 的本地实现,基于ctx.subprocess,逐调用镜像dsh-bash-local:resolve()从配置默认化并设上限,run()通过一个 deadline 融合配置夹取的超时与调用方信号,start()返回消费式后台句柄,其进程归属于 subprocess 服务。命令字符串作为单个 argv 参数传给pwsh -NoLogo -NoProfile -NonInteractive -Command,由 PowerShell 解析,不存在 shell 引号层。可执行文件解析(resolvePwshPath)是(configured, env, platform)的纯函数:先显式配置,再在 Windows 上探测 PowerShell 7 安装位置、PATH 条目(剥离引号)与 Windows PowerShell 5.1,否则返回裸命令名pwsh,交由进程启动时按 PATH 解析。@deepseek-ai/dsh-tool-pwsh—— 基于ctx.shell的面向模型工具,约定是 PowerShell 方言,逐调用镜像dsh-tool-bash:经通用任务运行时执行前台与run_in_background,经共享dsh-shell-env注册表管理DSH_*环境,bash 的 marker/截断渲染机制(干净退出不产生 marker),以及——自 Windows ACL sandbox 决策以来——沙箱拒绝渲染与sandbox_permissions升级面,外加工具描述中的 Windows 专属 ConstrainedLanguage 与命名管道约定。parity 决策取代了本 Agent Note 的最小画像工具描述。
Windows vitest 覆盖率刻意不属本次改动:仓库的 Windows CI 通道负责构建/静态门禁,单元覆盖在 Linux 上运行,两个包的套件在那里以真实 pwsh 运行(GitHub 托管 runner 预装)或缺失时自行跳过。vitest 的 windowsUnsupportedPackages 排除从 packages/shell/* 收窄为真正需要 bash 的包,使 pwsh 套件也能在 Windows 开发机上原生运行。
本决策之后的路线图——让 Windows 主机默认 pwsh(关闭 bash)与 pwsh TUI/GUI 渲染——已另行记录为 Windows 默认 pwsh 决策。
备选方案
给 dsh-bash-local 增加 pwsh 模式。 否决:执行器的身份就是它 spawn 的 shell;在一个包内塞第二种方言会翻倍配置面(shell 开关)与测试矩阵,且两种方言的怪癖(Windows 上的信号信息、引号域)应各自归入自己包的文档。
给 dsh-tool-bash 增加方言参数。 否决:模型可见约定本身就是方言(路径、变量、退出事实都不同),因此方言参数要么让 schema 随条件变化,要么逼一个工具教两种方言;独立的孪生让模型约定保持诚实——并以镜像而非共享实现的方式携带共享表面(后台、沙箱、渲染)。
现在就接入交付的 CLI(命令行界面)组合。 否决:在 Windows 默认决策落地前把 tool-pwsh + pwsh-local 挂进 base.cordis.yml 会改变交付清单;本改动交付能力与接线点(apps/cli 依赖、tsconfig 工程),不切换任何默认。
后果
- bash 执行器 seam 有了第二个、Windows 原生的实现,请求/规范约定一致,因此
tool-pwsh之外的面向模型消费方(钩子桥、进程内插件)无需方言垫片即可运行 PowerShell。 tool-pwsh是模型可见的 Windows 优先 shell 工具:在前台、后台与沙箱化工作上与 bash 工具行为可互换——包括经ctx.approval的同轮次sandbox_permissions升级——提示词指导精确陈述 marker 约定、沙箱拒绝/升级词汇,以及 ConstrainedLanguage 与命名管道边界。- Windows 语义在平台差异处不同:强制终止报告退出码 1 且无信号(因此
signal/killed状态信息仅限 POSIX),PowerShell 输出 CRLF,测试做归一化。 - CLI 增加两个 workspace 依赖与两个 tsconfig 工程,但不挂载任一插件——组合决策留给 Windows 默认提案。