返回 CodeWhale
INTEGRATIONS_DSH.md
根目录 / docs / INTEGRATIONS_DSH.md
1 # DeepSeek Harness connected through Codewhale
2
3 `codewhale integrations dsh …` connects a user's **existing** official DeepSeek
4 Harness installation (`dsh`, npm `@deepseek-ai/dsh`) to their Codewhale setup.
5 DSH stays an integrated harness surface. Codewhale remains the owner of Fleet
6 configuration, provider/model selection, permissions, credentials, and
7 lifecycle authority; DSH is not a second Fleet scheduler and never an
8 authority bypass.
9
10 ## Version support
11
12 This launcher integration was last verified against `dsh 0.1.0-rc.6` in August
13 2026 (the integration landed 2026-08-15; the last live check recorded below is
14 2026-08-17). It has not been re-qualified since, and nothing newer is verified:
15 the pinned import fixture's `0.1.7-alpha.2` (commit `00102833`) is a static
16 import reference only, not a launcher qualification. DSH is a developer
17 preview that warns of compatibility-breaking changes.
18
19 What the code enforces (`crates/tui/src/integrations/dsh/detect.rs`): an older
20 `dsh`, or any `dsh` that does not advertise `--patch`, is `incompatible` and
21 refused. A newer `dsh` that parses and advertises `--patch` is reported as
22 `stale-version`: a connected profile still launches, unverified. The version is
23 read as semver, so current prerelease tags such as `0.1.7-alpha.2` parse and
24 order correctly (`alpha` < `beta` < `rc` < the bare release, within a core
25 version): `0.1.7-alpha.2` is newer than the verified `0.1.0-rc.6`, reported as
26 `stale-version`, and never presented as verified. Only text that is not a
27 semver version at all (for example `nightly` or `0.1`) is reported as
28 `offline`, which is refused.
29
30 This is an external-launcher integration: it runs the user's installed `dsh`
31 with a Codewhale-written overlay. It is not [`/plugin import dsh`](PLUGIN_AUTHORING.md#deepseek-harness-dsh),
32 which statically converts the MCP and skill rows of a DSH bundle package into a
33 native Codewhale plugin without running DSH or any plugin code. It is also not
34 the experimental TypeScript extension host ([EXTENSIONS.md](EXTENSIONS.md),
35 [design](design/TS_EXTENSION_HOST.md)), which runs DSH TypeScript plugin code
36 for tools behind the off-by-default `extension_host` feature.
37
38 ## What is (and is not) connected
39
40 Codewhale uses only DSH's documented seams:
41
42 | Seam | How Codewhale uses it |
43 | --- | --- |
44 | `dsh --version` / `dsh --help` | read-only detection (never initializes a profile) |
45 | `$DSH_HOME` (or `~/.dsh`) | read-only inventory: profile names, `settings.yaml` top-level namespaces, whether `.credentials.yaml` exists and is `0600`. Values are never read. |
46 | `--patch <file>` overlay | Codewhale writes **one** overlay under its own home and passes it at launch |
47 | `DSH_PERMISSION_MODE` env | mirrors the Codewhale permission posture |
48 | `--profile web` / `--profile headless` | the two shipped DSH profiles; DSH initializes them itself on first launch (its own documented behavior) |
49
50 Codewhale writes **only** under `$CODEWHALE_HOME/integrations/dsh/` (plus,
51 with the opt-in plugin path below, whatever `dsh plugin` itself writes into
52 the dedicated `codewhale` DSH profile):
53
54 - `codewhale.patch.yml` — the overlay. Identity only: provider route, model,
55 base URL, and (native DeepSeek route) `reasoningEffort`. For every
56 non-native route it declares a `codewhale-<provider>` route on DSH's
57 `llm-pi-ai` adapter, naming that route's own wire dialect under `api:`
58 (`openai-completions`, `openai-responses`, or `anthropic-messages`) and
59 `apiKeyEnv` naming the provider's canonical environment variable — the
60 *name*, never the value. Keyless local routes (loopback Ollama / LM Studio
61 / vLLM / SGLang) carry no credential reference.
62 - `receipt.json` — the current connection record plus an append-only history
63 of `connect` / `update` / `disable` / `enable` / `remove` events with the
64 overlay SHA-256, dsh version, `$DSH_HOME`, mapped identity, permission mode,
65 and timestamps (see `docs/RECEIPTS.md`). Every event is also appended to
66 `$CODEWHALE_HOME/audit.log`.
67 - `bundle/` — only after `install-bundle`; see below. The Codewhale palette
68 (skin) and the ambient ocean scene live here, in the bundle's client half —
69 no stylesheet is exported.
70
71 Codewhale **never**:
72
73 - copies, prints, or embeds API keys, OAuth documents, environment secrets,
74 prompts, or filesystem contents (a `--api-key`/keyring credential Codewhale
75 itself materialized into the process is stripped from the launched child;
76 a key the user exported in their own shell is left alone);
77 - writes to `$DSH_HOME` (settings, credentials, profiles, sessions);
78 - edits installed `@deepseek-ai/dsh` package files;
79 - switches to a cloud model or broadens permissions silently. Codewhale
80 `read-only` → DSH `read-only`; anything else → `workspace-write`;
81 `danger-full-access` only with `--allow-full-access` **and** a Codewhale
82 full-access posture (`sandbox_mode = "danger-full-access"` / yolo).
83
84 ## States
85
86 | State | Meaning | Launch |
87 | --- | --- | --- |
88 | `not-installed` | `dsh` not on `PATH` | refused |
89 | `offline` | `dsh` exists but `--version` failed or printed text that is not a semver version | refused |
90 | `incompatible` | older than 0.1.0-rc.6 or no `--patch` | refused |
91 | `detected` | usable dsh, no Codewhale overlay | refused (`connect` first) |
92 | `connected` | overlay matches the current Codewhale route | allowed |
93 | `stale-config` | route changed, overlay edited outside Codewhale, or missing | refused (`update`) |
94 | `stale-version` | connected, but dsh is newer than verified | allowed, unverified |
95 | `disabled` | overlay kept, launches refused | refused (`enable`) |
96
97 `status`, `plan`, `/setup tools` (Tools and MCP step) and `codewhale doctor`
98 are side-effect free.
99
100 ## Commands
101
102 ```bash
103 codewhale integrations dsh status [--json]
104 codewhale integrations dsh plan [--profile web|headless] [--allow-full-access] [--skin] [--json]
105 codewhale integrations dsh connect [--profile web|headless] [--allow-full-access] [--skin] [--yes]
106 codewhale integrations dsh update [--profile …] [--allow-full-access] [--skin true|false] [--ocean true|false] [--yes]
107 codewhale integrations dsh launch [--profile web|headless] [--dry-run] [-- <dsh app args>]
108 codewhale integrations dsh disable
109 codewhale integrations dsh enable
110 codewhale integrations dsh remove [--yes]
111 codewhale integrations dsh install-bundle [--app web|headless] [--yes]
112 codewhale integrations dsh remove-bundle [--yes]
113 ```
114
115 `connect`, `update`, and `remove` print the exact plan (files, identity,
116 permission mode, disclosures, and the overlay text) and require confirmation
117 (`--yes` when stdin is not a terminal). `launch` runs
118 `DSH_PERMISSION_MODE=<mode> dsh --profile <p> --patch <overlay> …` in the
119 Codewhale workspace with the user's own `$DSH_HOME`, so their credentials,
120 sessions, and profiles remain theirs.
121
122 ### Disclosures the plan makes
123
124 - DSH layers the user's `settings.yaml` sections (`agent-default-model`,
125 `llm-deepseek`, `llm-pi-ai`) over the overlay per field. If those sections
126 exist, DSH's saved selection can shadow the pinned identity until it is
127 cleared in DSH; `status`/`plan` list them.
128 - Reasoning tiers are mapped only for the native DeepSeek route
129 (`off|high|max`); hand-declared routes send no effort parameter.
130 - Wire dialects are carried, never approximated: a Chat Completions route
131 declares `api: openai-completions`, an OpenAI Responses route (e.g. the
132 default `deepseek/deepseek-v4-flash`) declares `api: openai-responses`,
133 and an Anthropic Messages route declares `api: anthropic-messages`. This
134 follows the installed adapters' own declarations (verified against
135 `@deepseek-ai/dsh@0.1.0-rc.6`): `@deepseek-ai/dsh-llm-deepseek` — the
136 `deepseek-official` route — speaks chat completions only (its single wire
137 call posts to `<baseURL>/chat/completions`, with no protocol switch),
138 while `@deepseek-ai/dsh-llm-pi-ai`'s hand-declared route schema accepts
139 exactly `openai-completions | openai-responses | anthropic-messages` for
140 `api:`. So DeepSeek chat routes ride the native adapter (with reasoning
141 tiers), and every other dialect — including DeepSeek's own
142 Responses-dialect models — rides a hand-declared `codewhale-*` pi-ai
143 route in its own dialect.
144 - What is refused: base URLs that embed credentials (userinfo or
145 query/fragment material) are never copied into the overlay; `plan` fails
146 naming the current `provider/model` and the reason, and `status` shows
147 carry-ability for the current route before `plan` is ever run.
148
149 ## The DSH plugin path (`install-bundle`)
150
151 `--patch` is Codewhale's default because it needs nothing but the launcher.
152 The **documented DSH plugin mechanism** is available as an explicit opt-in:
153
154 ```bash
155 codewhale integrations dsh install-bundle [--app web|headless] [--yes]
156 codewhale integrations dsh remove-bundle [--yes]
157 ```
158
159 `install-bundle` requires an existing connection and `pnpm` on `PATH` (dsh
160 shells out to it); without pnpm the status reads
161 `plugin path: not available: pnpm missing …` and the command refuses. It:
162
163 1. materializes an npm-shaped bundle package under
164 `$CODEWHALE_HOME/integrations/dsh/bundle/` — `package.json`
165 (`codewhale-dsh-bundle`, private, MIT, version
166 `<codewhale version>+dsh.<patch sha12>`, `"dsh": {"bundle": {"patch":
167 "./cordis.patch.yml"}}`), `cordis.patch.yml` (the identity overlay,
168 plus one trailing skin insert row when the skin is on — see below),
169 `README.md`, `NOTICE.md` (DSH MIT notice retained), and, with the skin
170 on, `lib/index.js` + `lib/client.js` (the palette plugin, with the ocean
171 scene spliced in unless `--ocean false`);
172 2. runs the documented `dsh plugin --profile codewhale add <path>` twice: first
173 for DSH's own shipped app bundle (`@deepseek-ai/dsh-web-app` or
174 `dsh-headless`, linked from the installed launcher so the profile can boot;
175 no network), then for the Codewhale bundle so its rows patch last. DSH
176 creates the **dedicated** profile `$DSH_HOME/profiles/codewhale`
177 (`package.json` with `link:` dependencies, `pnpm-lock.yaml`,
178 `node_modules` links). The user's `web`/`headless` profiles are never
179 touched;
180 3. records an `install_bundle` receipt (profile dir, bundle dir, package
181 version, patch SHA-256, app bundle source, pnpm version, SHA-256 digest of
182 the `dsh plugin` output — the output text itself is not stored).
183
184 Afterwards `dsh --profile codewhale` alone carries the identity (verified with
185 `dsh --profile codewhale --dump-config`), and `launch` prefers that profile
186 without `--patch`; `launch --profile web|headless` still uses the overlay.
187 Because the profile dependency is a `link:` to the Codewhale-owned directory,
188 `update` regenerates `cordis.patch.yml` (and the skin files) in place — no
189 pnpm run. Stale detection covers the bundle: a modified or missing bundle
190 patch, a bundle that no longer matches the overlay, a `lib/client.js` that
191 is missing, modified, present while the receipt says the skin is off, or
192 carrying/lacking the ocean scene against the receipt's `ocean` decision, or
193 a profile manifest that stopped listing `codewhale-dsh-bundle` all report
194 `stale-config`.
195
196 `remove-bundle` runs `dsh plugin --profile codewhale remove
197 codewhale-dsh-bundle` and deletes only the Codewhale-owned bundle files. The
198 profile directory itself (and the app bundle link dsh recorded there) is
199 DSH-owned and is left in place; the receipt says so. `remove` refuses while a
200 bundle is installed.
201
202 ## Skin (bundle profile, `overrideTokens`)
203
204 DSH 0.1.0-rc.6 has one documented token-level theming seam:
205 `ThemeService.overrideTokens(source, tokens)` in
206 `@deepseek-ai/dsh-client-ui-theme`, which stacks a partial `--dsw-alias-*`
207 layer over the active theme (per-token, later layers win) and returns a
208 disposer. That is the mechanism the Codewhale skin uses. It is **applied only
209 through the bundle profile** (`dsh --profile codewhale`); the `--patch`
210 overlay never carries skin code, so `launch --profile web|headless` stays
211 overlay-only and stock-themed.
212
213 `install-bundle` turns the skin **on by default**. With the skin on, the
214 bundle is a dual-face DSH plugin:
215
216 - `package.json` gains `"dsh": {"client": {"platform": "web", "immediately":
217 true, "inject": ["@deepseek-ai/dsh-client-ui-theme"]}}` and
218 `"exports": {".": …, "./client": …, "./package.json": …}` (Node exports maps are exhaustive; the loader imports the bare name and dsh-client-modules resolves `<name>/package.json`);
219 - `lib/index.js` is a no-op Node cordis entry (so the row mounts) and
220 `lib/client.js` is a plain `window.__ModuleLoader__.load({ id, factory })`
221 script whose factory calls
222 `ctx.theme.overrideTokens("codewhale-dsh-bundle", TOKENS)` inside
223 `ctx.effect` and returns the disposer (`inject: ["theme"]` defers it until
224 the theme service exists);
225 - `cordis.patch.yml` ends with
226 `- insert: [{ id: codewhale-skin, name: codewhale-dsh-bundle }]` after the
227 identity rows.
228
229 `TOKENS` is a bounded map of `--dsw-alias-*` names (backgrounds, borders,
230 brand, buttons, labels, error/success/warn states, code blocks, scrollbar,
231 toast, tooltip) onto light/dark values rendered from the TUI's real palette
232 (`crates/palette/src`, Blue Stage dark and light) — palette constants
233 only, no user data or environment. The receipt records `skin: true|false`
234 and `skin_sha256` (SHA-256 of the rendered `TOKENS` JSON); `package.json`
235 carries the same hash under `codewhale.skin_sha256`.
236
237 ### Whale Brothers / Codewhale identity
238
239 The skin mounts a small plugin-owned lockup in the top-right corner that says
240 `WHALE BROTHERS`, `CODEWHALE`, and `× DEEPSEEK HARNESS`. It is additive: it
241 registers through DSH's frame-wide `shell.overlay` slot and does not replace or
242 rewrite DeepSeek Harness branding or controls. The lockup uses the active skin
243 tokens, ignores pointer input, collapses to a compact whale mark below 760 px,
244 and is removed with the client plugin.
245 `package.json` records the generated fragment as `codewhale.brand_sha256`.
246
247 ### Ocean scene (whales and glyph fish)
248
249 With the skin on, `lib/client.js` also carries an ambient ocean: a
250 full-viewport `<canvas>` (`position: fixed; inset: 0; z-index: -1;
251 pointer-events: none`, painted below `#root` and above the body background)
252 with a visible depth gradient, one near and one far whale silhouette (blunt
253 head, low dorsal hump, long pectoral flipper, horizontal fluke flexing ±10°)
254 gliding slowly across on a gentle sine, biased to the lower half and the top
255 edge so they never cross the composer card, an occasional short spout of
256 bubbles from the head, a small school of Codewhale glyph fish (`><>` /
257 `><o>` in the code font, flocking-lite behind a wandering leader) and faint
258 rising bubbles. The
259 palette is the skin's own (`surface_bg`, `accent_primary`, `text_body`,
260 `text_dim` for light and dark); the scene follows DSH's `theme/change` event
261 so it flips with the app.
262
263 To let the canvas show through, the client re-issues two background tokens
264 as translucent rgba over the opaque table while the scene is on:
265 `--dsw-alias-bg-base` (α 0.42; the frame and the centre column both paint
266 it) and `--dsw-specific-sidebar-fill` (α 0.78, keeping navigation distinct).
267 Panels,
268 composer, code blocks and every other layer stay opaque. Verified live on
269 dsh 0.1.0-rc.6 in both schemes: no console errors, frames differ, and text
270 stays legible (see `docs/design/assets/dsh-ocean-{light,dark}.png`).
271
272 Budget: `requestAnimationFrame` capped at ~30 fps, paused while
273 `document.hidden`, one static frame under `prefers-reduced-motion: reduce`,
274 device-pixel-ratio aware, no per-frame allocations (typed arrays reused).
275 The scene ships inside `client.js` because dsh-client-modules serves exactly
276 one file per client plugin (`/plugins/<id>/client.js`); there is no
277 `lib/scene.js`. `package.json` records `codewhale.ocean` and
278 `codewhale.ocean_scene_sha256`; the receipt records `ocean: true|false`.
279
280 Off switches, smallest first: in the browser `localStorage["codewhale.ocean"]
281 = "off"` (or body class `codewhale-ocean-off`) skips both the canvas and the
282 translucent tokens on that machine; `window.__codewhaleOcean.stop()` /
283 `.start()` / `.setIntensity(0..1)` are exposed for the console; and
284 `codewhale integrations dsh update --ocean false` regenerates `client.js`
285 without the scene (default on; a bare `update` keeps the previous choice;
286 `--skin false` implies no scene).
287
288 Escape hatch: `codewhale integrations dsh update --skin false` regenerates
289 the bundle without the client half and without the insert row (no pnpm run;
290 the `link:` dependency picks the files up in place); `update --skin true`
291 turns it back on, and a bare `update` keeps the previous choice.
292 `install-bundle` itself takes no `--skin` flag. `connect --skin` / `plan
293 --skin` record the same decision ahead of a later bundle install and write
294 no extra files. `remove-bundle` deletes the client half with the rest of the
295 Codewhale-owned bundle files, and the `overrideTokens` layer is disposed
296 with the plugin, so stock DSH theming returns.
297
298 The 0.9.8 `--skin` CSS/preview export (`codewhale-dsh-skin.css`,
299 `codewhale-dsh-skin-preview.html`) is gone: `dsh-client-ui-layout` writes
300 the alias tokens as inline `body.style` properties, so any stylesheet rule
301 lost to them by construction. `connect`/`update` delete those leftover files
302 if present.
303
304 ## Removal
305
306 `remove` deletes only the overlay (and any 0.9.8 skin/preview leftovers)
307 under `$CODEWHALE_HOME/integrations/dsh/`, appends a `remove` receipt, and
308 never touches `$DSH_HOME` or the installed package. DSH keeps working exactly as
309 before the connection.
310
311 ## Attribution
312
313 DeepSeek Harness is © 2026 DeepSeek, MIT licensed; the integration invokes the
314 installed launcher and does not redistribute it. This is not native Codewhale
315 functionality: every surface labels it "DeepSeek Harness connected through
316 Codewhale".
317
317 lines MARKDOWN