| 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 |