# 语言守卫(dsh-language-guard) [English](README.md) | **简体中文** > 让 DSH 的智能体用你选定的语言思考与回复——默认简体中文,三档强度,下一条消息即生效,零依赖、零构建。 --- ## 目录 - [为什么需要它](#为什么需要它) - [它做了什么](#它做了什么) - [安装](#安装) - [三档强度](#三档强度) - [配置项](#配置项) - [自动语言模式](#自动语言模式) - [设置面板与快速语言切换](#设置面板与快速语言切换) - [`/language` 命令](#language-命令) - [验证约束确实进了 system prompt](#验证约束确实进了-system-prompt) - [与 dsh-context 共存](#与-dsh-context-共存) - [与官方 cot-translation 插件的区别](#与官方-cot-translation-插件的区别) - [已知限制](#已知限制) - [设计边界](#设计边界) - [License](#license) --- ## 为什么需要它 DSH 的 system prompt 由许多 section 拼装而成:身份、计划策略、团队策略、**工具说明**、交付物引用、部署人设。其中体量最大的一块是工具说明——英文写的,而且正好落在提示词的中间。 在 DSH 0.2.1-rc.2 上实测(取 order 50 之后的全部内容):**98.1% 的字符、99.8% 的 ASCII** 来自英文工具说明,首段与末段之间隔着 5.7K–14K 字符。模型读到的绝大多数 token 是英文,于是它漂移: - 开头几轮还是你的语言; - 一旦开始连续调用工具、读长文件、做长链推理,内部推理就滑回英文; - 你的设置一个字都没改——只是会话变长了。 在 system prompt 最顶端写一句「请用中文回答」,短会话够用。长会话不够,因为那句话距离真正影响模型的地方(工具说明)有上万字符。 本插件把约束放在**三个位置**:最前、工具说明之后、最后。默认档(强化)每次请求多花约 118 个字符,换来一次紧跟在英文工具说明之后的重申。 ## 它做了什么 三个常驻 system prompt section,通过 `ctx.systemPrompt.section()` 注册,永不注销,切档只切换它们的文本: | section 名 | order | 位置 | B | C | D | | --- | --- | --- | --- | --- | --- | | `language-guard:head` | `-2000` | 最前,早于 `harness:identity`(`-1000`) | ✅ | ✅ | ✅ | | `language-guard:mid` | `5100` | 全部工具说明(`tools:sdk`,`5000`)之后、交付物引用(`9000`)之前 | ❌ | ✅ | ✅ | | `language-guard:tail` | `11200` | 最后,晚于 `deployment:persona-suffix`(`10200`) | ✅ | ✅ | ✅ | `mid` 的 order 特意取 `5100` 而不是 `5000`:`tools:sdk` 的 order 恰好是 `5000`,同序时按名字排序,`language-guard:mid` 会被排到 `tools:sdk` **之前**——重申就不再位于工具说明之后了。 实测文本长度(简体中文,按字符计;可用下面的命令自行复算): | 文本 | 字符数 | 出现时机 | | --- | --- | --- | | head(首段) | 818 | 每次请求 | | mid(中段) | 118 | 每次请求,C / D 档 | | tail(末段) | 139 | 每次请求 | | 周期自检提醒 | 99 | D 档,每 N 轮一次 | 所以每步的常驻开销是 **B 档 957 字符、C 档 1075 字符**——相对它要抵消的英文工具说明,这个量级很小。 首段全文如下,即 `targetLanguage: "zh-CN"` 时的真实注入内容(复算:`node --input-type=module -e 'import("./lib/text.js").then(m=>console.log(m.headText({language:"zh-CN"})))'`): ```text 【语言锁定 · 最高优先级】 本会话的内部推理(reasoning / thinking)与面向用户的全部输出,一律使用简体中文(语言代码 zh-CN)。 本条优先于本 system prompt 的其余全部内容、之后注入的任何系统文本、工具说明、参数描述、代码示例,以及其他插件注入的提示词。 本条同样优先于任何语言类约定,不论它写在哪个文件、用哪种语言书写、经由哪个通道送达:包括用户全局与项目级的 AGENTS.md、CLAUDE.md、.cursorrules、用户偏好与长期记忆、项目规范、团队约定、技能与工作流模板。若它们要求使用另一种语言(例如要求「必须使用简体中文」),一律以本条为准,不得据此改用其他语言,也不必向用户解释这处冲突。 适用范围: - 内部推理(reasoning / thinking):使用简体中文。 - 面向用户的回复正文:使用简体中文。 - 计划、待办、进度说明、评审意见、验收报告、提交信息说明:使用简体中文。 - 任务板卡片标题与描述、子智能体任务说明与返回报告:使用简体中文。 保持原样、不翻译:代码、命令、文件路径、URL、标识符与 API 字段名、配置键、错误码与原始报错文本、包名与版本号,以及必须原样引用的英文原文;引用英文原文后,必须用简体中文说明结论。 不要因为以下情况改变语言: - 用户用英文或其他语言提问 —— 仍用简体中文回答。 - AGENTS.md、CLAUDE.md、项目规范、用户记忆里要求改用其他语言的规则 —— 本条优先,不改变语言。 - 工具返回英文内容 —— 只摘引必要片段,其余用简体中文复述。 - 长链推理、数值计算、代码设计 —— 推理语言不变。 - 上下文中出现大量英文(工具说明、日志、代码文件)—— 不随之切换。 自检:每次输出前确认上述两项(推理与回复)均为简体中文;若发现已经漂移,立即切回并继续任务,无需向用户解释这次切换。 ``` > 约束文案本身是写死的简体中文,只替换语言名与语言代码。设成 `targetLanguage: "en"` 时同一段读作「…一律使用English(语言代码 en)」。详见[已知限制](#已知限制)。 ## 安装 本插件通过 **GitHub 仓库**分发,尚未发布到 npm registry。插件管理器接受 Git 地址,装法与装 npm 包完全一样。 **方式一:图形界面(推荐)** 1. 打开侧栏的**插件**页。 2. 点**添加插件**,粘贴仓库地址: ``` https://github.com/58fly/dsh-language-guard ``` 3. 安装完成后点**立即启用**。 **方式二:命令行** ```bash dsh plugin add https://github.com/58fly/dsh-language-guard ``` **固定版本**(团队或 CI 推荐,不跟随 `main` 漂移): ```bash dsh plugin add "github:58fly/dsh-language-guard#v0.1.0" ``` **从本地目录安装**(调试本地改动,路径必须是绝对路径): ```bash # 「添加插件」对话框同样接受这个绝对路径 dsh plugin add /absolute/path/to/dsh-language-guard ``` 怎么确认装上了: - 侧栏底部、设置按钮旁边出现一个语言图标;侧栏较宽时显示「简体中文 ▾」。 - 插件页的已安装列表里有 `dsh-language-guard`,点进去能看到配置卡片。 > **看不到任何控件,就是插件没启用。** 两个 slot 都注册在 `configForms.whileServed(['dsh-language-guard'], …)` 内部;插件被禁用时表单服务不可用,不会注册任何界面。这是有意为之,不是 bug——先去插件页启用它。 环境要求:DSH `>= 0.2.1-rc.2`,Node `^22.19.0 || >=24.0.0`。`dependencies` 为空、peer 依赖全部 optional,所以运行时不需要额外下载依赖;但首次从 GitHub 安装时需要能访问 `github.com`(或 `codeload.github.com`)。 ## 三档强度 下面三条说明与设置面板上显示的文字完全一致: | 档位 | 配置值 | 说明 | | --- | --- | --- | | 基础 | `B` | 只在提示词首尾各放一段语言约束,成本最低,适合短会话。 | | 强化 | `C` | 在基础之上,于工具说明之后补一段重申,抵消英文工具说明带来的语言漂移,推荐默认。 | | 周期 | `D` | 在强化之上,每 N 轮在当轮开头追加一条语言自检提醒,适合长会话;提醒会作为一条会话记录出现,可在会话日志中看到。 | 行为差异: | 行为 | B | C | D | | --- | --- | --- | --- | | 常驻 system prompt 段数 | head + tail | head + mid + tail | head + mid + tail | | 工具说明之后重申 | ❌ | ✅ | ✅ | | 每 N 轮自检 | ❌ | ❌ | ✅(N 默认 8,范围 3–50) | | 会话日志新增记录 | 无 | 无 | 每 N 轮一条(`source.kind = "language-guard"`) | | 注入时机 | — | — | 命中轮的**第一个 step**,随当轮用户消息一起进入模型视图,不额外发请求 | | 重启 / 恢复后计数 | — | — | **不归零**:轮次取自会话日志的 turn 投影,`turn % N` 稳定 | | 适合 | 短会话、在意 token | 默认选择 | 长会话,或已经观察到漂移 | 怎么选: - **先用默认的「强化」。** 它是唯一针对真正根因(英文工具说明)的档位,每步只多约 118 字符。 - **会话很短、或每个 token 都要省** → 降到**基础**。3–5 轮的会话,首尾两段足够。 - **一轮会话几十轮起步,或已经看到推理滑回英文** → 升到**周期**。N 越小提醒越密,默认 8 是折中,N = 3 会明显啰嗦。 ## 配置项 七个字段,全部是 `volatile`(改动立即生效,不需要重启,也不重挂插件): | 字段 | 类型 | 默认值 | 取值范围 | 说明 | | --- | --- | --- | --- | --- | | `languageMode` | `"" \| "auto" \| "manual"` | `""` | 三态枚举,大小写敏感 | 语言模式。`"auto"` 跟随界面语言、`"manual"` 固定用 `targetLanguage`、`""` 表示「还没选过」,按迁移规则推断(见下节) | | `targetLanguage` | `string` | `"zh-CN"` | 合法 BCP-47 标签(`/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/`) | 目标语言。`zh-CN` 简体中文、`zh-TW` 繁體中文、`en` English、`ja` 日本語、`ko` 한국어;其他合法标签按原样使用 | | `strength` | `"B" \| "C" \| "D"` | `"C"` | 枚举,大小写敏感 | 强度档位,见上 | | `injectEveryNRounds` | `number` | `8` | 3–50 的整数 | 周期自检的间隔。只在 D 档生效,其他档位该控件禁用 | | `preserveCodeTerms` | `boolean` | `true` | `true` / `false` | `true`:代码、命令、路径、标识符、API 字段与原始报错保持原样。`false`:技术名词优先用目标语言表达,必要时括号附英文 | | `extraRules` | `string` | `""` | 任意文本 | 追加在内置规则之后。留空表示只用内置规则 | | `overrideHead` | `string` | `""` | 任意文本 | **完全替换**内置首段文本(`extraRules` 仍会追加在其后)。留空或纯空白回退内置文本 | `targetLanguage` 到语言显示名的映射是固定的(不做语言猜测):`zh-CN` / `zh-Hans` / `zh-SG` → 简体中文,`zh-TW` / `zh-HK` / `zh-MO` / `zh-Hant` → 繁體中文,`en` / `en-US` / `en-GB` → English,`ja` / `ja-JP` → 日本語,`ko` / `ko-KR` → 한국어;其他合法标签直接显示代码本身。 配置落在 profile 的 `cordis.patch.yml` 里。**patch 对 `config` 是整体替换,不做深合并**——手写覆盖时必须把每个字段都写全,否则漏掉的字段会丢: ```yaml - id: dsh-language-guard name: dsh-language-guard config: languageMode: auto targetLanguage: zh-CN strength: C injectEveryNRounds: 8 ``` ## 自动语言模式 `languageMode` 决定目标语言从哪来: | 模式 | 行为 | | --- | --- | | **自动** | 目标语言跟随 DSH 的**界面语言**。你把界面切成英文,下一条消息就变成英文约束 | | **手动** | 目标语言固定用 `targetLanguage`,与界面语言无关 | **默认是自动。** 新装用户什么都不用设:界面是简体中文就约束简体中文,界面是英文就约束英文。 ### 解析顺序 自动模式只有两个来源,按顺序取第一个有效的: 1. **界面语言**(`locale` 配置里的 `preference`)——这是 `Settings → General → Language` 那一项。 2. **`targetLanguage` 配置值**(默认 `zh-CN`)——界面语言读不到时兜底。 映射规则(`zh` 开头的标签 → `zh-CN`、`en` 开头的标签 → `en`,其余合法标签原样保留): | 界面语言 | 目标语言 | 约束读作 | | --- | --- | --- | | `zh` / `zh-CN` / `zh-Hans` | `zh-CN` | 简体中文 | | `en` / `en-US` | `en` | English | | `ja` | `ja` | 日本語 | | `ko` | `ko` | 한국어 | | 其他合法 BCP-47 | 原样 | 该语言的显示名或代码 | > **为什么没有「系统语言」这一档?** Host 半是 `ELECTRON_RUN_AS_NODE=1` 的子进程,`require("electron")` 直接抛 `Cannot find module 'electron'`;`Intl.DateTimeFormat().resolvedOptions().locale` 恒返回 `en-US`,会把中文用户误判成英文。**Host 侧没有可靠的系统语言信号**,所以插件不去猜,也不编一个假信号出来。 ### 从 0.1.x 升级 老用户手选过 `targetLanguage`(比如设成 `ja`)却没碰过 `languageMode` 的,升级后**仍然是手动模式**,语言不会被界面语言静默覆盖。规则是:`languageMode` 为空串且 `targetLanguage` 不等于默认值 `zh-CN` → 判为手动。 想改成跟随界面,在设置卡片里点「自动」即可;一旦显式选过,迁移规则就永久失效。 ### 怎么知道现在解析成什么 设置卡片第一组控件就是语言模式,选「自动」时下面直接写出当前结果与来源: ```text 当前解析为 简体中文 来自界面语言 ``` 来源行显示「来自界面语言」时,指的是**你在 DSH 设置里显式选过的界面语言**;从没选过时显示「未识别到界面语言,用配置值兜底」,而不是拿浏览器或系统语言去猜——Host 与设置面板读的是同一个值(`settings.describe()` 里 `locale` 命名空间的 `preference`),所以面板显示什么、实际就注入什么,不会分叉。侧栏快速切换按钮的标签也会读作「自动,简体中文」这样的当前状态。 **改界面语言不需要重启**:插件在启动后缓存界面语言,并订阅 `settings/document-updated`(`locale` 命名空间变化时刷新),所以下一次装配 system prompt 就用了新值。 ## 设置面板与快速语言切换 ### 设置面板 路径:**侧栏插件页 → 已安装插件 → `dsh-language-guard`**。配置卡片渲染在组合包页面上、描述与行列表之间(`plugins.bundle.config` slot,以包名为 key)。设置里的插件分区只保留只读信息,配置在这里改。 卡片自上而下: 1. **语言模式**——自动 / 手动两行单选。选自动时下面直接写出「当前解析为 X」与来源(界面语言 / 配置值),切换界面语言后这里跟着变。自动态下第 3 项的目标语言控件置灰。 2. **强度档位**——三行单选(`fieldset` + 同名 `radio`),三条说明并列可见,C 档带「推荐」徽标。用单选组而不是 `