[**简体中文**](README.md) | 🌐 English
# dsh-thinking-language
A DeepSeek Harness plugin that lets you switch the language of the agent's
**thinking/reasoning process** (chain-of-thought). Supports Chinese (Simplified
and Traditional), English, Russian, French, German, Spanish, Arabic, Portuguese
and Japanese, or "auto" to follow the system locale.
## What it does
- **A settings page of its own** — Settings → **Thinking language** is a page
with a single row: the title and its hint on the left, one selector pill
(dropdown menu) on the right — the same shape as the built-in Language /
Permission rows.
- **Prompt injection** — when a language is selected the plugin injects a
system-prompt instruction telling the model to write its internal reasoning
in that language. With *Follow the system (auto)* the instruction uses the
system UI locale (Settings → General → Language; defaults to Chinese). The
instruction is evaluated per prompt assembly, so it applies to every **new
session**. Existing sessions keep the prompt they already composed.
The setting is stored in the standard user-settings document under the
`thinking-language` namespace, so it survives restarts.
> The final answer to the user is **not** affected: the setting only targets
> the model's internal thinking/chain-of-thought.
## Supported languages
`auto` (follow the system) · `zh-CN` 简体中文 · `zh-TW` 繁體中文 · `en` English ·
`ru` Русский · `fr` Français · `de` Deutsch · `es` Español · `ar` العربية ·
`pt` Português · `ja` 日本語
*Auto* matches the system language tag hierarchically: exact id, then region
variant, then language code. So `en-US` → English, `ja` → Japanese, `de-AT` →
German, and Chinese is decided by **script/region**: `zh`, `zh-Hans`, `zh-CN`
mean Simplified while `zh-Hant`, `zh-TW`, `zh-HK`, `zh-MO` mean Traditional.
An unrecognised locale falls back to English (matching the shell locale
plugin's own fallback).
## Install
Install into **the Harness home you actually run** — check `DSH_HOME` /
`DSH_PROFILE`, since the desktop app and the CLI do not share one by default
(adjust the profile name too if you use e.g. `desktop`):
**From GitHub (recommended):**
```bash
dsh plugin --profile web add github:qingmomo233/dsh-thinking-language
```
**Or from a local source checkout** (replace `` with your own local
path; pnpm resolves it inside the profile directory, so point it at the plugin
directory itself):
```bash
dsh plugin --profile web add
```
Then **restart the Harness** (fully quit and relaunch the desktop app). The
client module graph is written into `index.html` when the page is rendered, so a
page refresh does not deliver a newly added plugin to an already-open page.
The desktop app **owns** its home: `dsh --profile …` against it is refused with
`profile "desktop" is managed exclusively by the Electron application`, so
install from the app's own plugin market instead (it accepts only a
`github:owner/repo[#sha]` spec).
> **If it installs but does not show, look for `skipping profile bundle` in the
> log.** A leftover entry under the old package name (renamed from
> `dsh-plugin-thinking-language` to `dsh-thinking-language` in v1.1) is skipped —
> `dsh plugin --profile web remove dsh-plugin-thinking-language` first. A
> `peerDependencies` mismatch against the running dsh version is skipped too
> (`^0.1.0-rc.6` does **not** match 0.2.x); the log says
> `is incompatible with dsh …`.
### About the settings-exposure patch (very old harnesses only)
Since DSH 0.2.6 the host exposes **every** registered settings namespace to the
browser, and DSH 0.2.9 removed `dsh-host-apiproxy` entirely. Current builds
therefore need **no patch at all**.
Only DSH ≤ 0.2.3 (the `dsh-host-apiproxy@0.1.0-rc.5` generation) still keeps a
hard-coded `WEB_SETTINGS_NAMESPACES` allowlist; there a plugin-owned namespace
registers host-side but the browser gets `settings-not-exposed`, so the row
renders but never persists. The script detects the allowlist before touching
anything:
```bash
node scripts/patch-apiproxy.mjs # dry run: detect and report only
node scripts/patch-apiproxy.mjs --write # apply (writes a .dsh-thinking-language.bak backup first)
```
## Usage
1. Open **Settings** (gear icon) → **Thinking language** in the left nav.
2. Pick a language on that page (or choose *Follow the system (auto)* to use
the system UI locale).
3. Start a **new session** — its thinking process is written in the selected
language.
## Compatibility
The plugin degrades instead of failing when a service or dependency is absent:
| Situation | Behaviour |
| --- | --- |
| Harness without `systemPrompt` | the settings namespace keeps working |
| Harness without this settings channel (no `settings` service / registration refused) | one warning, the prompt surfaces keep working |
| Harness without a settings transport (`configForms` / `settingsScope`) | the page is skipped and reported once after boot |
| Harness without the `locale` service | the page uses its built-in copy instead of crashing |
| Harness without the platform UI primitives module | the page falls back to a native select and still shows |
| `describe()` unavailable or a different schema shape | the picker falls back to the bundled catalog |
| Hand-edited/corrupt settings section | treated as `auto`; nothing is thrown and user data is never rewritten |
| Unrecognised system language tag | falls back to English (matching the shell locale plugin) |
Both halves share one catalog: the picker's entries are derived from the
**host-registered settings schema** (each enum value carries its endonym, e.g.
`简体中文` — exactly what the picker showed before), the browser keeps only a
fallback copy, and `smoke-test.mjs` fails when the two drift apart.
## How it works
| Part | File | Role |
| --- | --- | --- |
| Pure core | `lib/languages.js` | catalog, system-locale tag matching, instruction/reminder copy (no cordis, no browser globals — directly unit-testable) |
| Host entry | `lib/index.js` | registers the `thinking-language` settings namespace and the `app:thinking-language` system-prompt section (order 85) |
| Browser bundle | `lib/client.js` | registers a dedicated **Thinking language** page into the `settings.section` slot and reads/writes the same namespace through the client settings scope |
| Bundle patch | `cordis.patch.yml` | inserts the plugin row into the composed profile tree |
| Tests | `smoke-test.mjs` | host registration, locale matrix, settings-handle shapes, browser-bundle drift and degradation paths, frozen UI copy |
```bash
pnpm install # installs the test dependencies (@deepseek-ai/cordis, @deepseek-ai/schemastery)
npm test # smoke-test.mjs + cordis-check.mjs
```