DSH / Atlas
2026-08-05implementedfeature

pwsh UI presentation matches bash

pwsh UI presentation matches bash

The [pwsh tool bash parity decision](../../implemented/feature/2026-08-02-pwsh-tool-bash-parity.md) made `dsh-tool-pwsh` behaviorally interchangeable with `dsh-tool-bash` for execution, markers, and background jobs, but explicitly deferred the human-visible half: a completed pwsh foreground call presented as a generic `console`-fenced card while the bash tool's completed call presented as a terminal card with a parse

English

Problem

The pwsh tool bash parity decision made dsh-tool-pwsh behaviorally interchangeable with dsh-tool-bash for execution, markers, and background jobs, but explicitly deferred the human-visible half: a completed pwsh foreground call presented as a generic console-fenced card while the bash tool's completed call presented as a terminal card with a parsed exit-status pill. The roadmap that owned this gap (Windows defaults to pwsh) named "pwsh TUI/GUI rendering" as stage 2, but the TUI package was removed, leaving the Web surface as the only UI the gap affects.

Decision

dsh-tool-pwsh's presentResult now mirrors dsh-tool-bash's call-for-call: a completed foreground result is a terminal card whose output body is the marker-free rendered text and whose exit-status pill is the parsed exitCode/signal; background acknowledgements and isError results stay generic console-fenced cards; non-single-text-block results stay untouched (undefined).

The parse is shared, not duplicated: parseExitStatus/ParsedExitStatus moved from dsh-tool-bash's private render module into the @deepseek-ai/dsh-shell Service Definition package (exported from its index), and dsh-tool-bash's render.ts re-exports it so its source-plane consumers keep one import root. Both tools' renderers emit the same [exit code: N] / [killed by signal: X] markers, so one Service-Definition-owned inverse can never drift between the twins — the same "shared, not duplicated" shape the shell-env extraction used for the DSH_* registry.

The Web UI needs no per-tool code for the card itself: the client's terminal-card bridge maps any card: 'terminal' result view (terminal-card-model in dsh-client-ui-conversation), so the pwsh presenter change flows through the same rendering path bash already has. The collapsed tool row does get one client classification entry: classifyTool('pwsh') now yields the shell-family row (bash variant, its own Pwsh title) instead of the generic others "Tool call" row. A keyless browser lane (apps/web/tests/pwsh-terminal.e2e.ts) seeds a session whose pwsh call/result is presented by the real tool on replay — the api-proxy recomputes views from logged args/result content — and pins the terminal card golden, including the exit pill and the run-state dot.

Alternatives considered

Import parseExitStatus from @deepseek-ai/dsh-tool-bash/src/render.ts. Rejected: workspace imports stay external in the built bundles, so tool-pwsh would gain a hard runtime dependency on tool-bash in every consumer closure (including compositions that deliberately mount the pwsh twin without bash), and a sibling tool depending on its twin for one function inverts the package relationship. The seam move keeps the shared contract on a package both tools already depend on.

A new dedicated presentation package (e.g. @deepseek-ai/dsh-shell-present). Rejected: a new package costs manifests, module-graph/catalog regeneration, and README surface for a single pure function; @deepseek-ai/dsh-shell is already in both tools' closures and already owns the ShellRunResult facts the parse reconstructs.

Duplicate the parse into tool-pwsh's render module (a third twin). Rejected: copied text contracts drift without a shared implementation (pwsh tool bash parity); the parse and the marker emission must co-evolve in one place, and the parse is exactly the contract the UI pill depends on.

Consequences

  • A Windows composition using dsh-tool-pwsh now shows its shell calls exactly as bash calls look in the Web UI: cwd-headed terminal card, raw output, exit-status pill, run-state dot, and the red failure treatment on non-zero exits.
  • parseExitStatus becomes public contract surface on @deepseek-ai/dsh-shell; dsh-tool-bash/src/render.ts keeps re-exporting it, so no bash-tool consumer changes.
  • The roadmap's stage 2 shrinks: the TUI is removed (EOL), and the terminal-card counterpart now ships on the Web surface. The Windows default composition (stage 1) remains the outstanding stage.
  • Verification: dsh-shell owns the parse edge cases under the per-file coverage gate; tool-pwsh's presenter suite mirrors tool-bash's (clean/non-zero/signal/timeout round-trip, marker-like output, background/error generics, multi-block fallback); the client row-model suite pins the Pwsh shell-family row; the web pwsh-terminal lane is the assembled keyless scenario.

中文

Problem

pwsh 工具与 bash 对齐决策dsh-tool-pwsh 在执行、marker 与后台任务上行为可互换,但明确推迟了面向人类的一半:完成的 pwsh 前台调用呈现为通用 console 围栏卡片,而 bash 工具的完成调用呈现为带解析退出状态 pill 的 terminal 卡。负责解决此缺口的路线图(Windows 默认改用 pwsh)把「pwsh TUI/GUI 渲染」列为阶段 2,但 TUI 包已被移除,使 Web 表面成为该缺口唯一影响的 UI。

Decision

dsh-tool-pwshpresentResult 现在逐调用镜像 dsh-tool-bash:完成的前台结果是 terminal 卡,输出正文为去 marker 的渲染文本,退出状态 pill 为解析出的 exitCode/signal;后台 ack 与 isError 结果保持通用 console 围栏卡片;非单一文本块结果保持不变(undefined)。

解析是共享而非复制:parseExitStatus/ParsedExitStatusdsh-tool-bash 的私有 render 模块迁入 @deepseek-ai/dsh-shell Service Definition 包(由其 index 导出),dsh-tool-bashrender.ts 再导出它,使源平面消费方保持单一导入根。两个工具的渲染器发出相同的 [exit code: N] / [killed by signal: X] marker,因此一个由 Service Definition 拥有的逆解析永远不会在孪生之间漂移——与 shell-env 抽取 处理 DSH_* 注册表时相同的「共享而非复制」形态。

Web UI 的卡片本身不需要任何按工具编写的代码:客户端的 terminal 卡桥接(dsh-client-ui-conversationterminal-card-model)映射任意 card: 'terminal' 结果视图,因此 pwsh presenter 变更直接流经 bash 已有的同一渲染路径。折叠的工具行有一处客户端分类条目:classifyTool('pwsh') 现在归入 shell 家族行(bash variant,自有 Pwsh 标题),而非通用的 others「Tool call」行。一条 keyless 浏览器通道(apps/web/tests/pwsh-terminal.e2e.ts)预置一个会话,其 pwsh 调用/结果在回放时由真实工具呈现(api-proxy 从已记录的 args/result 内容重新计算视图),并钉住 terminal 卡 golden,包括退出 pill 与运行状态点。

Alternatives considered

@deepseek-ai/dsh-tool-bash/src/render.ts 导入 parseExitStatus 否决:workspace 导入在构建产物中保持外部引用,因此 tool-pwsh 会在每个消费方闭包中新增对 tool-bash 的硬运行时依赖(包括刻意只挂 pwsh 孪生、不挂 bash 的组合),且兄弟工具为一个函数而依赖其孪生会颠倒包间关系。seam 迁移把共享约定放在两个工具本就依赖的包上。

新建专用呈现包(如 @deepseek-ai/dsh-shell-present)。 否决:为一个纯函数新建包要付出 manifest(元数据清单)、module-graph/目录再生成与 README 内容的成本;@deepseek-ai/dsh-shell 已在两个工具的闭包中,且已拥有该解析重建的 ShellRunResult 事实。

把解析复制进 tool-pwsh 的 render 模块(第三个孪生)。 否决:复制的文本约定缺少共享实现就会漂移(pwsh 工具与 bash 对齐);解析与 marker 发出必须在同一处共同演化,而解析恰恰是 UI pill 依赖的约定。

Consequences

  • 使用 dsh-tool-pwsh 的 Windows 组合现在在 Web UI 中显示的 shell 调用与 bash 调用完全一致:cwd 头的 terminal 卡、原始输出、退出状态 pill、运行状态点,以及非零退出时的红色失败处理。
  • parseExitStatus 成为 @deepseek-ai/dsh-shell 公开约定的一部分;dsh-tool-bash/src/render.ts 继续再导出它,bash 工具消费方零改动。
  • 路线图阶段 2 收窄:TUI 已移除(EOL),对应的 terminal 卡现已在 Web 表面交付。Windows 默认组合(阶段 1)仍是未完成的阶段。
  • 验证:dsh-shell 在逐文件覆盖率门禁下拥有解析边界用例;tool-pwsh 的 presenter 套件镜像 tool-bash 的(干净/非零/信号/超时往返、形似 marker 的输出、后台/错误通用卡片、多块回退);客户端行模型套件钉住 Pwsh shell 家族行;web pwsh-terminal 通道是组装后的 keyless 场景。