使用 dsh-bash-encoding 修复 DeepSeek Harness 的 bash 中文乱码

前言

DeepSeek Harness(简称 DSH)是 DeepSeek 开源的智能体运行时,官方仓库把核心理念写成「Everything is a Plugin」(一切皆插件)。bash 工具是其中最常用的能力之一:模型下发一条命令,宿主把 stdout / stderr 收上来,再交给后续推理。

在 Linux 或 macOS 上,这条链路大多按 UTF-8 工作,问题不明显。到了 Windows + WSL 组合里,情况就不一样了。DSH 跑在 Windows 侧,bash 经 wsl.exe 执行;当 WSL 仍是 NAT 网络、同时 HTTP_PROXY / HTTPS_PROXY 指向 localhost 时,启动器会在 stderr 上打一条 UTF-16LE 编码的代理警告。DSH 核心的 subprocess 层对所有输出做 Buffer.toString('utf8'),原始字节在这一步丢掉,警告变成不可恢复的乱码。更麻烦的是,这条警告经常和命令自己的 UTF-8 输出挤在同一条管道里,按某一种编码通解,会把另一段也解坏。

社区维护者 lhh010 做了 dsh-bash-encoding,专门替换 ctx.bash 执行器:自己 spawn、自己收集原始 Buffer、检测后再解码。它被收录在社区插件目录的「工具与能力」分类下。需要先说明:这个目录站点(deepseek-harness-plugin.com)是独立社区索引,和 DeepSeek / 幻方没有官方从属关系,安装前仍要以仓库源码和许可证为准。

这是什么

dsh-bash-encoding 是一个宿主侧 DSH 插件,npm 包名为 @dsh-external/dsh-bash-encoding,当前版本 0.1.0,许可证 BSD-3-Clause,主要语言 TypeScript。GitHub 仓库为 lhh010/dsh-bash-encoding,截至 2026-08-18 星标 7。

它解决的问题很具体:自动识别 bash 输出里的 UTF-16LE / UTF-8 / GBK(以及 GB18030、UTF-16BE、带 BOM 的变体),把 Web、TUI、hooks 桥和后台任务里看到的中文从乱码还原成可读文本。它不改 DSH 的 subprocess 核心服务,而是绕过那一层有损解码——任何包在 ctx.subprocess 之上的包装都修不了这个问题,因为拿到的已经是乱码文本。

仓库 README 写明兼容 DSH snapshot0808(snapshots/20260808T121140Z)、snapshot0809(snapshots/20260809T140917Z),以及 npm 发版 @deepseek-ai/dsh@0.0.1-rc.1。这些版本号以仓库说明为准;换 snapshot 前仍建议对照当前 DSH 的 bash 缝合线是否还叫 ctx.bash。官方文档里这条缝合线会拆到 dsh-bash-localdsh-bash-sandbox 等包,本插件替换的是其中的 ctx.bash 实现。

编码检测怎么做

插件把执行器换成 EncodingBashExecutor:子进程由插件自己 spawn,stdout / stderr 先按原始字节收集,再交给 src/decode-core.ts 里的流式分段解码。检测顺序在 README 里写得很清楚:

  1. 纯 ASCII 快速路径:没有高字节就按 UTF-8 处理,避免把 STDERRwsl: 这类字母误判成 UTF-16。
  2. BOM:UTF-8(EF BB BF)、UTF-16LE(FF FE)、UTF-16BE(FE FF)。
  3. UTF-16 段检测:按 chunk 分段。用 NUL 奇偶位锚定 ASCII 子段,用 CJK 高字节优势锚定纯中文段;连续 4 个可打印 ASCII code unit 会断段(用来区分 STDERR 和「个」「片」这类字),遇到 UTF-8 三字节签名则硬断。
  4. 严格 UTF-8(fatal decoder)通过,则定为 UTF-8。
  5. 再试 GBK,然后 GB18030(对应 Windows 中文 OEM 代码页 936 / 54936)。
  6. 最后 Latin-1 兜底,保证解码不会失败。

同一条管道里混着 WSL 的 UTF-16LE 警告和命令自己的 UTF-8 输出,是这个检测链要处理的难点。仓库给出的对比大致是这样。

修复前(核心 exec 按 UTF-8 解 UTF-16LE):

w s l: �hKm0R localhost �NtM�nFO*g\��P0R WSL0NAT !j_N�v WSL \rN/e c localhost �Nt

修复后:

wsl: 检测到 localhost 代理配置,但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理。

GBK 工具的输出、8KB 以上跨 chunk 的长 UTF-16 流,以及 STDERR: 这种 ASCII 前缀后跟中文的形态,README 里都有对照示例。仓库提供 26 个测试用例,覆盖解码内核和真实 spawn 的退出码、stdin、超时、后台任务、失败路径,用下面这条命令跑:

pnpm test

作者在 npm 发版基线上的实测记录是 25/26 通过,唯一失败被写成「本机 WSL localhost 代理警告混入 stderr 的环境噪音」,解码行为本身仍被标记为正确。

安装与启用

社区目录页给出的安装命令是:

dsh plugin add github:lhh010/dsh-bash-encoding

目录页同时提示:如需可复现安装,应固定 commit 哈希。当前 main 分支 HEAD 为 30ecc056cbdade90292ff8ad72a2cd8324fe863f(提交于 2026-08-13),可以写成:

dsh plugin add github:lhh010/dsh-bash-encoding#30ecc056cbdade90292ff8ad72a2cd8324fe863f

插件以当前 dsh 进程的权限运行,安装时可能执行代码。安装前请检查源代码仓库和许可证。

同一 context 里只能有一个 ctx.bash 实现。接入后需要在 profile 的 cordis.yml(或 cordis.patch.yml)里替换原来的 bash 条目,@deepseek-ai/dsh-bash-local@deepseek-ai/dsh-bash-sandbox 二选一被本插件替代,不能并存。仓库给出的配置如下:

- id: bash
  name: '@dsh-external/dsh-bash-encoding'
  config:
    cwd: null            # 默认工作目录(默认 process.cwd())
    timeoutMs: 120000    # 前台命令默认超时
    maxTimeoutMs: 600000 # 单次超时上限
    maxOutputBytes: 65536  # 每流输出上限(超限截断并标记 lossy)
    graceMs: 3000        # SIGTERM→SIGKILL 宽限期

各项含义按 README 原文:

  • cwd:默认工作目录,缺省为 process.cwd()
  • timeoutMs:前台命令默认超时。
  • maxTimeoutMs:单次超时上限。
  • maxOutputBytes:每条流的输出上限,超限截断并标记 lossy
  • graceMs:先发 SIGTERM,再等这么久才 SIGKILL。

如果走 cordis.patch.yml,README 特别指出:patch 的 name 字段只做校验、不能替换插件。正确做法是先把原 bash 条目设为 disabled: true,再 insert 本插件。改完后重启 dsh web 生效。bash 工具、后台任务、hooks 桥的输出都会经过编码检测。

仓库另外写了一种本地 link 的接入方式(DSH 要求 Node ^22.19 || >=24):

cd /path/to/dsh-bash-encoding && pnpm install && pnpm build
cd "${DSH_HOME:-$HOME/.dsh}/profiles/web"
pnpm add -w link:/path/to/dsh-bash-encoding

npm install 时还有一个版本号坑:peerDependencies.cordis 声明为 ^4.0.0-rc.7,而 DSH npm 发版把内置 cordis 按 0.0.1-rc.? 统一预发布号发出去,可能报 ERESOLVE。README 的处理是加 --legacy-peer-deps。经 dsh plugin 或 pnpm 安装会自动处理,运行不受影响。

适用场景与注意事项

适合谁:在 Windows 上跑 DSH、bash 走 WSL,并且经常看到中文乱码的人。典型触发条件同时满足下面三条:

  • 操作系统是 Windows,DSH 在 Windows 侧,bash 经 WSL 执行。
  • WSL 网络仍是 NAT(%UserProfile%\.wslconfig 未设置 networkingMode=mirrored)。
  • 环境变量 HTTP_PROXY / HTTPS_PROXY 指向 localhost

附带能修好的还有:GBK 中文工具、UTF-16 输出,以及警告和命令输出混在同一管道的情况。

不适合、或者明确做不到的,仓库写得很直接:

  1. Windows 原生(无 WSL)profile 默认停用。 平台层已经插入 pwsh-sandboxSandboxPwshExecutor),同样会注册 ctx.bash;两边一起开会启动失败(service bash has been registered)。本插件用 bash -c spawn,面向 POSIX bash 栈,本来也不适用 pwsh 栈。这是 profile 配置层的停用,不是代码弃用。
  2. read 工具读 GBK / UTF-16 文件 不在当前范围,README 标成路线图 v2。
  3. node-pty 交互终端tool-pty / dsh-web-terminal)修不了:node-pty 内部已经按 UTF-8 有损解码,插件拿不到原始字节。
  4. 不改 subprocess 核心层,避免替换基础服务。
  5. 超过 maxOutputBytes 时保留头部并标记 lossy,不做 spill 文件。
  6. 继承环境里 DSH_* 前缀、以及名称含 KEY / PASSWORD / SECRET / TOKEN 的变量会被清掉,托管变量经 dshEnv 显式传入,这点和 DSH 官方 bash 缝合线的 hygiene 对齐。

如果只是想少看那条 WSL 代理警告,也可以从环境侧处理:把 .wslconfignetworkingMode 改成 mirrored,或设置 WSL_UTF8=1。这是和插件互补的做法,不是替代关系。

沙箱方面,插件会在运行时探测 ctx.sandbox / ctx.sandboxPolicy,非 full-access 走 confine 路径,sandboxMode 惰性读取。它没有客户端 bundle,不受 snapshot0809 客户端插件机制(dshClient 声明 / ClientPackageCompositionError)影响。

小结

dsh-bash-encoding 做的事情很窄:把 bash 输出的原始字节留下来,按 UTF-16LE / UTF-8 / GBK 等编码解开,专门对付 Windows + WSL 下那类「每次命令都乱码」的情况。它不是官方内置插件,星标也不高,但对卡在这条链路上的人,比在模型侧猜测乱码更直接。安装前检查源码和 BSD-3-Clause 许可证,需要可复现环境时固定 commit。

目录页:https://deepseek-harness-plugin.com/zh-CN/plugins/dsh-bash-encoding/

GitHub:https://github.com/lhh010/dsh-bash-encoding

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜