# DSH 統合面の調査ノート (2026-10-18) 対象: DeepSeek Harness (dsh) v0.2.0-rc.2 に「独自 TUI フロントエンド」を載せる方法の事実確認。 本書は **検証済みの事実のみ**を記載する。推測は『推測』と明示する。 ## 0. 調査環境 | 項目 | 値 | 根拠 | |---|---|---| | Node | v22.22.1 (`/usr/bin/node`, libnode.so.127 リンク) | `node -v`, `ldd /usr/bin/node` | | Node の TS サポート | **無し** (`node_use_amaro=false`) | `node -p "process.config.variables.node_use_amaro"` → `false`、`node foo.ts` が SyntaxError | | pnpm / npm | 11.28.5 / 9.2.0 | `pnpm -v`, `npm -v` | | DSH 本体 | `@deepseek-ai/dsh` / 内部パッケージ **205個** | `$HOME/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/` | | DSH パッケージ版 | **0.2.0-rc.2** | 各 `package.json` の `version` | | 参考 TUI | `@deepseek-harness-tui/dsh-tui` v0.15.0 | `$HOME//dsh-TUI/package.json` | > 副作用: `workflow` ツールは本環境で使用不可(Node に Amaro が無いため orchestration script の TS 実行に失敗)。 > 並列 `subagent` で代替した。 ## 1. DSH の構成単位は「プロファイル = バンドルの合成」 DSH の実行単位はプロファイル。プロファイルは `package.json` の `dsh.profile.bundles` に **Cordis プラグイン木(バンドル)を並べたもの**で、`cordis.patch.yml` が設定上書き層になる。 `$HOME/.dsh/profiles/headless/package.json`: ```json { "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless" ] } } } ``` `$HOME/.dsh/profiles/dsh-tui/package.json`: ```json { "dependencies": { "@deepseek-harness-tui/dsh-tui": "file:.../deepseek-harness-tui-dsh-tui-0.15.0-20261016b.tgz" }, "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-harness-tui/dsh-tui" ] } } } ``` **結論(事実): dsh-TUI は DSH の「パッチ」ではなく、DSH が公式に用意したバンドル機構に載る 1 バンドルである。** つまり「独自 TUI をバンドルとして作る」こと自体は公式の拡張点である。 バンドルは `package.json` の `dsh.bundle.patch` で自前の `cordis.patch.yml` を宣言する (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-headless` で確認)。 ## 1.5 バンドル自作の公式契約(`package.json.dsh`) 出典: `$HOME/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-package-manifest/lib/types/types.d.ts`(全90行) パッケージ: `@deepseek-ai/dsh-package-manifest` — "Shared type declarations for package.json.dsh configuration fields" ```ts interface DshManifest { manifestVersion?: 1; bundle?: DshBundleManifest; // { patch: string | string[] } ← 自前 cordis.patch.yml への相対パス profile?: DshProfileManifest; // { bundles?: string[] } ← プロファイル側の層順 client?: DshClientManifest; // { platform, inject?, immediately?, external? } ← Web クライアント用 } interface DshPackageManifest { name; version; description?; icon?; private?; dependencies?; peerDependencies?; engines?: { dsh?: string /* SemVer range */, node?: string, npm?: string, [k: string]: string|undefined }; dsh?: DshManifest; } ``` **事実: 第三者が TUI バンドルを自作するのに必要な宣言は `name`/`version` + `dsh.bundle.patch` のみ。** `dsh.client` はブラウザ(Web コンシューマが `platform: 'web'` を選ぶ)向けの宣言であり、 TUI バンドルには不要。`engines.dsh` で DSH 互換 SemVer を宣言できる(「宣言的で、強制はリーダ次第」と明記)。 ## 1.6 base バンドルが供給する「対話の受け皿」 `@deepseek-ai/dsh-base/cordis.patch.yml`(529行、挿入行 id は約80個)を確認。 TUI が担うべき対話系サービスは **すでに base 側にマウントされている**: | 挿入 id | 実装パッケージ | 設定(base 既定) | |---|---|---| | `user-questions` | `@deepseek-ai/dsh-user-questions` | 設定なし(=描画側はサーフェスが担う) | | `approval` | `@deepseek-ai/dsh-user-approval` | `policy: (DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'` | | `permission` | `@deepseek-ai/dsh-permission-presets` | presets: `read-only` / `workspace-write` / `danger-full-access` × `{sandbox, approval}` | | `settings`, `config-editor` | `@deepseek-ai/dsh-settings`, `dsh-config-editor` | `disabled: !!js "!ctx.get('profileContext')"` ← **プロファイル対応ランチャが無いと無効** | その他の主要サービス: `agent`, `session`, `llm`, `llm-pi-ai`, `commands`, `goal`, `plan-mode`, `skills`, `subagent`, `jobs`, `spill`, `compaction`, `token-meter`, `sandbox`, `fs-observation-policy`, `tool-*` 群。 **含意**: 承認とユーザー質問は「base がサービスとして持ち、UI が無い」状態。 つまり TUI バンドルの仕事は**この2つを含む対話面の描画と入力の提供**であり、 これは A(SDK 型)では原理的に担えない領域(§3 参照)。ユーザーの選択(B)と整合する。 ## 1.7 参考実装 dsh-TUI の自己宣言(実測) `$HOME//dsh-TUI/package.json` を JSON として読み出した結果: ``` name: @deepseek-harness-tui/dsh-tui version: 0.15.0 bin: { "dsh-tui": "./bin/dsh-tui.js", "dst": "./bin/dsh-tui.js" } engines: { "node": "^22.19 || >=24" } ← DSH 互換の engines.dsh は宣言していない dsh: { "bundle": { "patch": "./cordis.patch.yml" } } ← §1.5 の契約そのもの dependencies: 39 peerDependencies: 40 ``` **事実**: dsh-TUI が DSH 本体に対して宣言しているのは `dsh.bundle.patch` の1行のみ。 DSH 側の各パッケージは **peerDependencies 40件**として「消費側プロファイルが供給するもの」とされている。 これが「DSH の公開エクスポートだけを消費する」という主張の実装上の意味である。 ## 2. DSH が公開している「フロントエンド用」プロファイル束 `@deepseek-ai/` 配下に、サーフェス(UI/接続面)ごとのバンドルが公式に存在する。 すべて v0.2.0-rc.2、`dsh.bundle.patch` 持ち。上流は `github.com/deepseek-ai/deepseek-harness`。 | パッケージ | 役割 (package.json description より引用) | |---|---| | `dsh-base` | The shared dsh core as a profile bundle | | `dsh-headless` | The dsh one-shot bundle: direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layer | | `dsh-web-app` | The dsh browser-surface bundle(`dsh --profile web`) | | `dsh-sdk-app` | The dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-base | | `dsh-sdk-minimal` | The standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, JSONL sessions | | `dsh-acp-app` | The dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-base | 関連ランタイム/ライブラリ: - `dsh-sdk-protocol` — ワイヤプロトコル定義(純ライブラリ) - `dsh-sdk-jsonrpc-server` — stdio JSON-RPC サーバプラグイン - `dsh-acp` — "Automation-only Agent Client Protocol server for driving DeepSeek Harness agents over JSON-RPC stdio" - `dsh-client-connection` — "Authenticated RPC transport and generation lifecycle" - `dsh-api-gateway` — "Typert Remote Host dispatcher and Client API endpoint" ## 3. SDK プロトコルの全容(重要な制約) 出典: `$HOME/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-sdk-protocol/lib/types/types.d.ts` トランスポート: **newline-delimited JSON-RPC 2.0 over stdio**。`serverInfo.name` は ワイヤ安定名 `deepseek-harness-sdk-runtime`。 ### クライアント → サーバ(request は 3 つのみ) | method | params | result | |---|---|---| | `initialize` | `cwd`, `provider`, `model`, `reasoningEffort?`, `maxTokens?` | `serverInfo{name,version}` | | `session/prompt` | `sessionId`, `contentBlocks`(text + インライン画像 base64) | `messageId`(inbox 受付のみ) | | `shutdown` | なし | `{}` | ### サーバ → クライアント(notification は 4 つのみ) | method | payload | |---|---| | `session.event` | `sessionId` + **セッションログの完全な `SessionEvent` エンベロープ**(ランタイム内の全セッション対象) | | `session.status` | `sessionId` + `'idle' \| 'running'` | | `subagent.started` | `parentSessionId`, `childSessionId` | | `subagent.finished` | `provider`, `agentId`, `parentSessionId`, `childSessionId`, `status`, `stopReason`, `lastAssistantMessage?` | ### この設計から導かれる事実上の制約 - **承認(approval)要求のチャネルが無い** — ツール実行の可否をクライアントに問い合わせる request/notification が存在しない。 権限はランタイム側のポリシー(sandbox / permission preset)で解決される。 - **`ask_user_question` のチャネルが無い** — `dsh-tool-ask-user` を TUI から対話的に答えられない。 - **モデル/プロバイダは `initialize` でプロセス単位に固定** — セッション途中の `/model` 切替は不可(変更は再起動)。 - `dsh-sdk-jsonrpc-server` の Known Limitations(README より逐語): - "The wire has no per-session close or prompt-cancel method" - "There is no per-prompt result" - "stdout purity is deployment-enforced"(stdout に logger を載せると JSON-RPC が壊れる) - "Automatic adapter mounting is DeepSeek-specific" - `dsh-sdk-app` の Known Limitations: "Configuration changes require restart"(HMR 無効)。 - `dsh-acp` の Known Limitations: "**No transcript replay or interactive extensions** — session deletion, fork, `session/load`, modes, commands, plans, terminals, client filesystem operations, and elicitation remain outside this automation surface." → **ACP は TUI の土台としては不足**。 > 補足: TypeScript クライアント `@deepseek-ai/dsh-sdk-client` が上流に存在する(`dsh-sdk-protocol` の > README が言及)が、本環境には**未インストール**(`find` で該当ディレクトリ無し)。Python SDK も > リポジトリ `python/` にあると記載されている。 ## 4. dsh-TUI の主張との整合 dsh-TUI README: "Zero core changes: it consumes only DSH's public exports, and is moving from a mounted plugin to an app with its own entry and composition root, where DSH loads in process as the first-party backend." → バンドル機構(§1)を使う限り「DSH 本体のソース改変」は不要、という主張と整合する。 ただし dsh-TUI リポジトリには `cordis.patch.yml`, `vendor/`, `host-replica.snapshot.json`, `patch-surface.snapshot.json` が存在し、**上流の版に追随するための整合管理**を行っている(詳細は subagent 調査で補完)。 ## 5. 未解決(追加調査が必要) - [ ] DSH 本体の `dsh --help` に出るプロファイル/フラグの全量(subagent 調査中) - [ ] バンドルを自作する場合に必要な最小の Cordis プラグイン契約(どの id を挿すと何が生えるか) - [ ] In-process バンドル経路での承認/質問 UI の受け方(`dsh-user-approval`, `dsh-user-questions` の公開面) - [ ] `@deepseek-ai/dsh-sdk-client` の npm 公開状況 - [ ] 公式ドキュメント(github.com/deepseek-ai/deepseek-harness の docs)における "surface 追加" 手順の有無 ## 6. 出典(ローカル実体) - `$HOME/.dsh/profiles/{headless,dsh-tui}/package.json`, `cordis.patch.yml` - `$HOME/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-*/README.md`, `package.json` - `$HOME/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-sdk-protocol/lib/types/types.d.ts` --- # 追記: DSH 本体の公開面(並列調査 subagent #1, 2026-10-18) ## 7. ランチャ(`@deepseek-ai/dsh` 0.2.0-rc.2)の実測 `dsh --help` 実行結果(exit=0、書込なし)。書式は `dsh [--profile] [options] [app-args...]` / `dsh plugin --profile `。 | フラグ | 意味 | |---|---| | `--profile ` | `$DSH_HOME/profiles` 配下のプロファイルを起動 | | `--from-default-profile ` | 出荷テンプレートから新規カスタムプロファイルを作る | | `--patch ` | プロファイル層の後に重ねる追加 patch(繰り返し可) | | `--dump-config` | 合成済みプロファイル木を出力して終了 | | `--dump-config-schema` | マウントせず profile entries/patches の JSON Schema を出力 | | `--dump-default-config` | ユーザー層と `--patch` 抜きの木を出力 | | `-V, --version` | `0.2.0-rc.2` | - ランチャは `.helpOption(false).allowUnknownOption().passThroughOptions()` で **未知トークン以降を app へ素通し**する(`dsh/lib/bin.js:10-24` のコメント、`:105` の commander 定義)。 → `dsh tui --resume ` の `--resume` は TUI アプリ側のフラグ。 - **出荷テンプレートは `acp` / `web` / `headless` / `sdk` / `sdk-minimal` の5つのみ** (`dsh-app-boot/lib/index.js:529-535`)。**`tui` というテンプレートは存在しない**(help の `dsh tui ...` は例示)。 それ以外の名前は `DEFAULT_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base"]`(同 `:543`)。 - **`@deepseek-ai/dsh` パッケージに `"."` export が無い**(`./profile-boot` / `./lib/*` / `./package.json` のみ)。 → `import "@deepseek-ai/dsh"` は**不可**。プログラム入口は `@deepseek-ai/dsh/profile-boot` (`runProfile` / `prepareProfile` / `initializeProfileFromDefault` 等)。 - `src/*` サブパス export は多数のパッケージにあるが **`src/` は npm 同梱外** → インストール済み環境では解決不能。 ## 8. 第三者バンドルの導入経路(審査なし) - `dsh plugin --profile add ` は **pnpm への素通し**(`dsh/lib/bin.js:115-126`)。 許可リスト・審査は無い(`desktop` プロファイル名のみ予約で拒否: `bin.js:35-37`)。 - プロファイル bundle 未導入時は `cannot resolve profile bundle ...; run 'dsh plugin --profile install'`(`dsh-app-boot/lib/index.js:901-908`)。 - **互換性 NG のバンドルは例外を投げず `skippedBundles` に黙って積まれる**(同 `:920-957`)。 明示許可は `dsh plugin --profile X allow-version --dsh-version --accept-risk`。 - 実在する第三者バンドルの前例: `@deepseek-harness-tui/dsh-tui` 0.15.0、 `dsh-working-activity` 0.5.1(非 scoped、`dsh.bundle.patch` と `dsh.client.platform="web"` の両方を宣言)。 - `profiles/dsh-tui/node_modules/` に `@deepseek-ai` は**存在しない**(peer 40件はホスト側解決に委ねる)。 ## 9. 「プラットフォーム非依存の UI 契約」は公開されていない - `dsh.client`(`DshClientManifest.platform`)の消費者は **`web` のみ**。 コメント逐語: "Client platform identifier; the Web consumer selects `web`"。 - その消費者(`dsh-client-modules` / `dsh-client-ui-*`)は**本環境に1つもインストールされていない** (`find ... -name "*client-ui*"` → 0件)。 - 未解決: `@deepseek-ai/dsh` の `dependencies` に `dsh-client-ui-cordis` と `dsh-client-ui-agent-preset` が 宣言されているのにディスク上に存在しない(prune / 配布欠落 / optional のいずれかは**不明**)。 **結論(subagent #1 の判定・本書の見解と一致)**: 「DSH 本体を改造せず独自フロントエンドを実装する」ことは**条件付きで可能**。 ただしそれは **Host 側 Cordis バンドルとして DSH と同一プロセスに載る方式に限られる**。 プラットフォーム非依存の UI 契約は `web` 以外に公開されておらず、SDK/ACP は承認・質問チャネルを欠く。 ## 10. 参考実装が負っている追随コスト(定量的な裏取り) `$HOME//dsh-TUI/ADAPTER.md:123-146` の Patch Surface 内訳: **公式行への disabled override 23行 / config override 6行 / insert 19行**。 `cordis.patch.yml` は 25KB、`patch-surface.snapshot.json` / `host-replica.snapshot.json` と `verify:patch-surface` スクリプトで上流の行集合の変化を検出する運用。 → 「本体改造は不要」だが「本体非依存ではない」。 --- ## 11. 重要な反証: 参考実装は `ink` パッケージを使っていない **当初の選択肢提示(「TypeScript + Ink = 導入コストゼロ」)は誤りだった。** 実測で否定された。 | 事実 | 根拠 | |---|---| | `src/ink/` は **124ファイル / 33,755行** の自前レンダラ | `find src/ink -type f \| wc -l` / `xargs wc -l` | | `ink` は dependencies / peerDependencies / devDependencies の**どこにも無い** | `package.json` を JSON として走査 | | React 依存は `react ^19.2.0` と `react-reconciler ^0.34.0` のみ | 同上 | | `import ... from 'ink'` はソース中に**1件も無い**(コメント内の言及1件のみ) | `grep -rn "from 'ink'" src/` | | Yoga レイアウトを **TS 移植で同梱**(2ファイル / 3,296行) | `src/native-ts/yoga-layout` | | 自前ドキュメントが「**移植的 Ink renderer**」と明記 | `docs/architecture.md:42`, `docs/architecture.en.md:42` | | 「**敏感基础设施**(sensitive infrastructure)」と位置づけ | `docs/contributing.md:137`, `docs/contributing.en.md:181,233` | 自前レンダラの主要ファイル: `src/ink/ink.tsx` **3,108行**, `src/ink/dom.ts` 759行, ほか `focus.ts` / `hit-test.ts` / `frame.ts` / `geometry-trace.ts` / `input-suppression.ts` / `clearTerminal.ts` / `bidi.ts` / `flush-tick.ts` / `devtools.ts`。 React 側の回避策も実装済み: - `src/force-production-react.ts` 逐語コメント: *"the dev build records one performance.measure() per render into an unbounded buffer — **the 4 GB long-session OOM (b1e06d8)**"* → `process.env.NODE_ENV ??= 'production'` - `src/jsx-runtime.ts` 逐語コメント: 自前 `react/jsx-runtime` を再輸出。プラグインが古い React を バンドルすると `Symbol.for('react.element')` を出し、**このアプリの reconciler はそれを受け付けず初回描画で死ぬ** (受理するのは React 19 の `Symbol.for('react.transitional.element')` のみ) **含意**: 「TS + Ink」を選ぶ場合、**バニラ Ink で足りる範囲**と、参考実装が 37k 行を費やした範囲 (端末プロトコル・差分描画・マウス選択・ヒットテスト・スクロールの sticky-bottom アンカリング・長セッション性能)を **スコープとして明示的に分離**しなければならない。ここを曖昧にしたまま要件を積むと必ず破綻する。