# 参考: dsh-TUI v0.15.0 が DSH 本体とどう接続しているか(読み取り専用調査) 出典: 読み取り専用調査 / 対象 dsh-TUI v0.15.0 / 作成日 2026-10-18 対象: `$HOME//dsh-TUI`(v0.15.0, TypeScript/Ink, bin/dsh-tui.js) 作業: ファイル変更なし。`workflow` 未使用(読み取り+grep のみ)。 --- ## 1. 起動経路 **結論: 二重構造。既定は「TUI が DSH を in-process で取り込む」、条件付きで「`dsh --profile dsh-tui` 子プロセス」。DSH のコードを書き換えるのではなく、DSH の boot 手順を TUI 側で“複製”する。** 1. **グローバル副本 → profile 副本へ委譲**(同一ファイルが二役) - `bin/dsh-tui.js:544-549` が `$DSH_HOME/profiles/dsh-tui/node_modules/@deepseek-harness-tui/dsh-tui` を自分の位置と比較(`runningInsideProfile`)。 - グローバル側なら `bin/dsh-tui.js:1478-1497` で profile 内の同 `bin/dsh-tui.js` を `spawn(process.execPath, ...)` で起動して丸投げ(`DSH_TUI_NO_DELEGATE=1` で無効化)。 - 委譲できない/未初期化なら `dsh plugin --profile dsh-tui add @` を自走(`bin/dsh-tui.js:1321-1322`)。 2. **profile 副本 = 本番ロジック**(`bin/dsh-tui.js:1498-1680`) - `dsh --version` を非同期プローブ(`:1240`, `:1501`)、profile 版数整合チェック(`:1523`)、`--resume` / ワークスペース引数の横取り(`:1528-1646`)。 - **分岐の核心は `bin/dsh-tui.js:1660-1678`** - `hostEntry = /lib/types/dsh-adapter/host-entry.js` - `hostEntryEnabled = DSH_TUI_HOST_ENTRY !== '0' && existsSync(hostEntry)` - `hostArgs.length === 0`(= `--profile`/`--patch`/`--dump-config`/`-V` 等を付けていない)なら **`startEntrySession`**(`:828-836`)で `node ` を起動 ← **既定** - それ以外は **`startDshSession`**(`:796-808`)で `spawn('dsh', ['--profile', 'dsh-tui', ...args])` ← 従来経路 3. **in-process 経路の中身** `src/dsh-adapter/host-entry.ts` - `:23-32` 起動 → `hostProfile()` で profile 名、`configuredBackend()` で patch の backend 行を読み、`delegatesToProfile()` が真(= `!!js` 式で動的決定)なら `delegateToDsh()` へ。 - `delegateToDsh()` `:41-79` … `spawn('dsh', ['--profile', profile, '--', ...])`。**ここが in-process の逃げ道**。 - `runInEntry()` `:114-135` … `loadHostDsh()` → `prepareHostRoot()` で **DSH の Cordis ルートを乗っ取る**。失敗時は `:126-134` で `dsh --profile` にフォールバック。 - `:136-143` で `./index.js`(`Config`)と `./plugin.js`(`apply`)を動的 import。 - `:145` ホストの Cordis `Context` を再利用、`:149-155` で `cordis.patch.yml` の `dsh-tui` 行 Config を env から再構築、`:169` `apply(ctx, config, ctx, { deferBackendOpen, profile, entryKernel, exitSeam, entrySlot })` を直接呼ぶ ← **TUI は Cordis プラグインとして直接 apply される**。 - `:184-194` `root.compose()` で profile(cordis.yml)をマウント。このとき同 profile 内の `dsh-tui` 行も Loader に載るが、`:162` の `createEntrySlot()`(`src/dsh-adapter/entry-slot.ts:32,37-41` の `globalThis[Symbol.for(...)]`)経由で `plugin.ts:2688` の `attachDsh` に合流し、**二重描画しない**。 4. **DSH をどう取り込むか** `src/dsh-adapter/host-dsh.ts` - `findHostDsh()` `:79-105` … PATH 上の `dsh` を realpath で辿り `@deepseek-ai/dsh` の package dir を特定(link / shim / beside / volta)。 - `loadHostDsh()` `:198-247` … `createRequire(/package.json)` を作り、**ホスト側の絶対パスを `import(pathToFileURL(require.resolve(spec)))` で動的 import**。契約は `:228-233` で「モジュール存在+エクスポート名存在」を検査、欠ければ点名して throw。 - `prepareHostRoot()` `:283-410` … `loadLayeredEnv` → `prepareProfile(name, true, undefined)` → `createRuntimeResolution` → `installProxyFromEnvironment` → `new host.Context()` → `ctx.baseUrl` / `ctx.provide('dshHomePath')` → `ctx.plugin(host.Loader)` → `appBoot.PluginPackages`(モジュール解決の乗っ取り)→ `provideCmdline`。 - `mountAndAudit()` `:350-386` … `mountRootInclude(ctx, rootConfig, patches)` → `loader.await()` → `auditStartupEntries()` → `appReady.commit()`。失敗は `writeStartupReport()`(`:434-470`)で `$DSH_HOME/logs/startup-*.log` に保存。 **「パッチを当てるか」への回答:** 宣言的な **profile 合成パッチ(cordis.patch.yml)はある**が、DSH の JS を書き換える仕組みではない。一方で実質的な“モンキーパッチ”が 2 箇所ある: - `src/dsh-adapter/fresh-agent.ts:30-60` … `session-persistence-jsonl` サービスの `create` メソッドを差し替え(`:41 source.create = tapped`)。 - `src/dsh-adapter/host-dsh.ts:302-304` … `internal/update` イベントに `{ global: true, prepend: true }` で割り込み。 - さらに `src/dsh-adapter/channel/model-selection.ts:56-95` は上流 `installModelSelection` の**再実装**(後述)。 --- ## 2. DSH との境界 - **境界ルール(明文化)** `ADAPTER.md:9` … 「`@deepseek-ai/*` は `src/dsh-adapter/` 内でのみ import」。`:18-20` で `native.dsh` と文字列リテラルも同様に制限。 - **静的な値 import(実測)**: `src/dsh-adapter/` 配下から `@deepseek-ai/*` への値 import が多数。代表例: - `src/dsh-adapter/backend/session.ts:12-14` (`dsh-agent`, `dsh-llm`, `dsh-session`) - `src/dsh-adapter/channel/model-selection.ts:1-3` (`cordis`, `dsh-agent`, `dsh-llm`) - `src/dsh-adapter/plugin.ts:3` (`dsh-session`)、`backend/session.ts:14 createUserMessage` - `src/dsh-adapter/host-dsh.ts:15-20` は **`import type` のみ**(契約) - `import type` は src 全体で 160 箇所。 - **実行時 import の解決根拠**: 値 import は Node の通常解決に乗る。`bin/dsh-tui.js:88-91` が「`$DSH_HOME/profiles/node_modules` モジュール回退リンク(上流 dsh の healProfilesModuleFallback)」に言及。加えて in-process 経路では `host-dsh.ts:329-331` の `PluginPackages` でホスト側解決を先に設置する(`host-contract.ts:110` の deviation `hijack-before-tui`)。 - **ホスト契約は単一ファイル** `src/dsh-adapter/host-contract.ts` - `:9` `HOST_PACKAGE = '@deepseek-ai/dsh'`(依存ではなく PATH から探す) - `:31-46` `HOST_MODULES` = 8 モジュール + 要求エクスポート名: `@deepseek-ai/cordis`(Context) / `dsh-app-boot`(loadLayeredEnv, createRuntimeResolution, PluginPackages, installFailLoud, readProfilePatches, mountRootInclude, auditStartupEntries, StartupError, getDshRuntimeVersion) / `cordis-plugin-loader`(default) / `dsh-home-paths`(dshHomePath, resolveDshHome) / `dsh-launch-environment`(DSH_LAUNCH_ENVIRONMENT_KEY) / `dsh-cmdline`(provideCmdline) / `dsh/profile-boot`(prepareProfile, INSTALL_ANCHOR, PROFILE_ROOT_FILENAME) / `dsh-http-proxy`(installProxyFromEnvironment) - `:53-59` `HOST_TYPE_PACKAGES`(`import type` のみ・optional peer)、`:69-71` `ADAPTER_TYPE_PACKAGES`(`dsh-plugin-manager`) - **イベント(受信)— TUI が購読する DSH イベント** | イベント | 宣言元 | TUI 側の購読 | |---|---|---| | `session/event` | `@deepseek-ai/dsh-session/lib/types/index.d.ts:64` | `src/dsh-adapter/backend/session.ts:106`、`channel/binding-events.ts:112`、`plugin.ts:1966` | | `agent/assistant-stream` | `@deepseek-ai/dsh-agent/lib/types/runtime-types.d.ts:366-369`(payload `{agent, frame: AssistantStreamFrame}`、`:107-137` が start/chunk/end) | `src/dsh-adapter/channel/binding-events.ts:115-117` | | `approval/request` | `@deepseek-ai/dsh-user-approval/lib/types/types.d.ts:81`(waterfall) | `src/dsh-adapter/plugin.ts:1956-1957` | | `agent/inbox/claimed` / `agent/inbox/discarded` | `dsh-agent` | `src/dsh-adapter/backend/session.ts:226-227` | | `agent/status` / `agent/created` / `agent/disposed` | `dsh-agent` | `channel/agent-view-projection.ts:119-121`, `binding-events.ts:127` | | `subagent/start` / `subagent/end` | 上流 | `channel/binding-events.ts:121-126` | | `system-prompt/assemble` / `agent/request` / `agent/pre-step` | `dsh-agent`, `dsh-system-prompt` | `channel/model-selection.ts:56,66,80` | | `commands/change` / `skills/change` | `dsh-commands`, `dsh-skill` | `channel/skill-catalog.ts:198-199` | | `llm/stream` | `dsh-llm` | `channel/compaction.ts:134` | | `internal/update`, `internal/service` | cordis | `host-dsh.ts:302`, `channel/cordis-host.ts:40` | - **コマンド(送信)— DSH サービスメソッド呼び出し** - `ctx.agents` … `create` / `resume` / `get`(`plugin.ts:2974`, `:2837`, `channel/session-resume.ts:483`, `session-tree-actions.ts:49`, `session-rewind.ts:53`, `session-fork.ts:33`, `background-action.ts:54`, `session-mount-heartbeat.ts:59`, `plugin.ts:3101`) - `ctx.get('llm')` … `listModels(provider)`(`channel/model-switch.ts:43-45`, `plugin.ts:2952-2954`) - `ctx.get('approval')` … `request()`(`approvals.ts:3-4` の doc、`plugin.ts:1955`) - `ctx.agents.get(id).inbox.remove(MessageId(...))` … `backend/session.ts:180`(キャンセル時のキュー取り消し) - `agent.send/steer/followup/inject/cancel` … 後述 - **TUI 自身のプロトコル層 `tui-profile/`(DSH の API ではない)** - `tui-profile/protocols/tui-channel.js:3-6` … `TUI_CHANNEL = { apiVersion: 'tui.dsh/v1alpha1', kind: 'Channel' }` - `:8` `TUI_CHANNEL_WIRE_REVISION = 6` - `:10-30` `TUI_CHANNEL_FEATURES` 20 個(commands, credentials, diagnostics, files, models, modes, presets, presentation, provider-setup, scenes, session-history, session-input, session-lifecycle, session-state, settings, skills, subagents, trace, workspaces + …) - `tui-profile/protocols/tui-channel.d.ts:42-46` … **操作型は 4 つだけ**: `open` / `subscribe` / `invoke` / `close` - `tui-profile/protocols/tui-channel.d.ts:18-23` `TuiChannelSnapshot`(wireRevision, channelId, version, state) - `tui-profile/protocols/profile-definitions.js:8-11` `DECISION_EVENTS`(`tui.dsh/v1alpha1` `DecisionEvents`)、`:64-68` `profileDefinitions = [decisionEvents, tuiChannel, backend]` - **これは plugin↔TUI 間の capability protocol**(`tui-profile/notes/0007-tui-channel.md`, `0008-tui-channel-http.md` が仕様)。DSH 本体との境界ではない。実装は `src/adapter/upstream/channel-driver.ts:36,111` / `src/adapter/channel/provider.ts:284` / `src/adapter/kernel/replay.ts:405-406`。HTTP transport 既定は `127.0.0.1:10721`(notes 0008)。 - `package.json:36-40` の `imports` で `#tui-profile/*` → `./tui-profile/protocols/*.js` に束ねている。 - `@dsh-std/*` は git submodule `vendor/dsh-std`(`.gitmodules`)で、これも DSH 本家ではなく **TUI 側の共通プロトコル実装**。 --- ## 3. 公開面だけを使う最小構成(5 点) | # | 機能 | DSH 側 API(モジュール / 型 / 関数) | dsh-TUI 側の実装箇所 | |---|---|---|---| | 1 | **セッション生成** | `ctx.agents.create(options: CreateAgentOptions): Promise` — `@deepseek-ai/dsh-agent`(`AgentRegistry`、`lib/types/index.d.ts:198,278`。`CreateAgentOptions` は同 `:44-80`)。再開は `ctx.agents.resume(options: ResumeAgentOptions)`(`:286`)、既存取得は `ctx.agents.get(id: SessionId): Agent \| undefined`(`:343`) | `src/dsh-adapter/plugin.ts:2974`(boot 時 create)/ `:2837`(resume 時の get)/ `:2715-2719`(`/new`)/ `src/dsh-adapter/channel/session-resume.ts:483` / `session-tree-actions.ts:123` / `session-rewind.ts:98` / `session-fork.ts:75` / `background-action.ts:54`。ラッパは `src/dsh-adapter/fresh-agent.ts:186-204` | | 2 | **メッセージ送信** | `agent.followup(message: UserMessage): void`(新ターン)/ `agent.steer(message: UserMessage): void`(次ステップ)/ `agent.send(message, target, wakeup)` / `agent.inject(message)` — `@deepseek-ai/dsh-agent` `lib/types/runtime-types.d.ts:186,192,200,209`。`UserMessage` は `@deepseek-ai/dsh-llm` の `createUserMessage({content, source})` | `src/dsh-adapter/backend/session.ts:278-291`(`submit()` が `placement` で `steer`/`followup` に振り分け、`now` は `agent.cancel({kind:'user'})` → `followup`)。上流の中立抽象は `src/agent/session.ts:65-81`(`AgentSession.submit(input, placement)`)、入力経路は `src/dsh-adapter/channel/input-delivery.ts:241` | | 3 | **ストリーミング受信** | ① `ctx.on('session/event', (session, event: SessionEvent) => …)` — `@deepseek-ai/dsh-session` `lib/types/index.d.ts:64`/② `ctx.on('agent/assistant-stream', ({agent, frame: AssistantStreamFrame}) => …)` — `@deepseek-ai/dsh-agent` `lib/types/runtime-types.d.ts:366-369`、`AssistantStreamFrame` は `:107-137`(`start`/`chunk`(StreamChunk)/`end`) | ① `src/dsh-adapter/backend/session.ts:106-110` で tap、② `src/dsh-adapter/channel/binding-events.ts:115-117`。翻訳は `src/dsh-adapter/backend/translate.ts:1-28`(DSH の `SessionEvent`/`AssistantStreamFrame` → `AgentEvent`)、投影は `src/channel/projection.ts`(72KB) | | 4 | **ツール承認 (approval)** | サービス `ctx.approval.request(req: ApprovalRequest): Promise` — `@deepseek-ai/dsh-user-approval` `lib/types/index.d.ts:135`。`ApprovalRequest` は同 `:70-88`、`ApprovalOutcome = 'allowed-once' \| 'rejected' \| 'cancelled' \| 'unavailable'`(`lib/types/types.d.ts:26`)。waterfall イベント `approval/request` は `lib/types/types.d.ts:81`(無応答なら **fail-closed で `unavailable`**) | `src/dsh-adapter/plugin.ts:1941-1970`。`:1956-1957` で `dshCtx.on('approval/request', (req, next) => approvalStore.park(req).catch(() => next()))`、`:1966` で `session/event` を store に流し込んで source badge を更新、`:1967` で teardown 時 `settleAll('cancelled')`。store 本体は `src/dsh-adapter/approvals.ts`(`:401 park()`、`:500 settleAll()`、`:29` で `ApprovalOutcome`/`ApprovalRequest` を型 import) | | 5 | **モデル選択** | 型 `ModelSelectionRef { current?: ModelSelection; assembled?: ModelSelection }` と関数 `installModelSelection(agentCtx: Context, selection: ModelSelectionRef): () => void` — `@deepseek-ai/dsh-agent/model-selection`(`lib/types/model-selection.d.ts:16-30,49`、実装 `lib/types/model-selection.js:45-94`)。モデル一覧は `ctx.get('llm').listModels(provider)` | **⚠ dsh-TUI は上流 `installModelSelection` を呼ばず、同等ロジックを自前実装している**: `src/dsh-adapter/channel/model-selection.ts:9-101`(`:56 system-prompt/assemble`, `:66 agent/request`, `:80 agent/pre-step` の 3 本を自前登録、`:79` のコメントが「Match upstream installModelSelection's admission-time provenance」)。切替 UI は `channel/model-switch.ts:31-60`(`:58 deps.selection.current = {provider, model}`)、一覧は `channel/model-actions.ts` | --- ## 4. 回避策の実態と壊れやすい箇所 ### 4-a. 各ファイルの役割 - **`cordis.patch.yml`(495 行)** … profile 合成への**宣言的介入**。`patch-surface.snapshot.json` によれば **inserts 19 / configOverrides 6 / disabled 22+1**。 - inserts 例: `cordis.patch.yml:303` `dsh-tui-plugin-host`, `:398` `dsh-tui-auth`, `:407-408` `dsh-tui` 本体行, `:491` `working-activity`。 - config overrides: `:19` `system-prompt`, `:43` `llm-deepseek`, `:53` `agent-loop`, `:177` `sandbox-policy`, `:184` `approval`, `:192` `session-persistence-jsonl`。 - disabled: `:71-170` で dsh-base の 22 行(tool-* / skill-filesystem / plan-mode / compaction-basic …)を落とす。 - `package.json:580` `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` がこのファイルを profile に登録する目印(`install.sh` のコメントも同旨)。 - **`vendor/`** … 3 つとも**DSH 本体ではなく TUI 自身の依存隔離**。 - `vendor/dsh-std` … **git submodule**(`.gitmodules` → `github.com/T-Auto/dsh-std.git`)。`@dsh-std/core|connection|manifest|presentation|command|storage|messages` を提供。`pnpm-workspace.yaml:2-8` で workspace 登録。TUI 独自プロトコルの土台。 - `vendor/sqlite-island` … `0.1.1-rc.2` の peer 閉包を隔離する private wrapper(`vendor/sqlite-island/package.json` の description / `pnpm-workspace.yaml:63-79`)。**`scripts/migrate-sessions-to-jsonl.mts:40` だけが消費、出荷されない**。 - `vendor/mathjax-tex-svg` … MathJax を esbuild でバンドル(`src/math/renderer.ts:152` が動的 import、`scripts/build-vendor.mjs:57-58`)。 - **`host-replica.snapshot.json`** … `hostVersion: 0.2.0-rc.2` 上で、`host-contract.ts:89-98` の `HOST_REPLICAS` 8 シンボルが載っている **上流 lib ファイルの整ファイル sha256**。値は `process-shutdown` 系 5 つが同一ハッシュ、`boot` / `startup-report` / `run-cli` が別ハッシュ。`ADAPTER.md:116-118` が「信号は粗い/差分は `HOST_DEVIATIONS` に記録」と明記。 - **`patch-surface.snapshot.json`** … `cordis.patch.yml` の介入面のスナップショット。`inserts` 19 / `configOverrides` 6 / `webAppComparisons`(公式 dsh-web-app patch との差分をバージョン別に比較、`webAppDisablesBeyondTui` に `tool-plugin-manager`, `workflow-ptc`, `hmr` など)。`ADAPTER.md:148-152` が `pnpm run verify:patch-surface` のゲートを説明。 - **`install.sh`(32 行)** … `dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui` の 1 発インストール。**DSH ソーススナップショットも workspace link も不要**(`:10-11` コメント)。`$DSH_HOME/profiles/node_modules` のモジュール回退は上流 dsh 側の `healProfilesModuleFallback` に依存(`bin/dsh-tui.js:88-91` のコメント)。 ### 4-b. DSH 更新で壊れる箇所(具体列挙) 1. **`HOST_MODULES` の 8 モジュール × エクスポート名**(`host-contract.ts:31-46`)。1 つでも消える/改名されると `loadHostDsh()`(`host-dsh.ts:228-233`)が throw → 入口は `dsh --profile` にフォールバック(`host-entry.ts:130-134`)。in-process 経路が丸ごと失われる。 2. **`HOST_REPLICAS` 8 シンボルの再実装**(`host-contract.ts:89-98` ↔ `host-dsh.ts:283-410`, `:434-470`, `:473-494`, `:497-520`)。`dsh-app-boot` の `boot` と `@deepseek-ai/dsh` の `runProfile`/`composeProfile`/`runCli`/`createProcessShutdown`/`createAppReady`/`reportStartupFailure` を**手で写している**。上流の起動順・proxy 政策・fail-loud・readiness が変わると無言で挙動がずれる。`host-replica.snapshot.json` のハッシュは「そのファイルが変わった」ことしか教えない(`ADAPTER.md:116-118`)。 3. **`HOST_DEVIATIONS` 9 件**(`host-contract.ts:101-111`)… `no-interrupt`, `exit-seam`, `fail-loud-removable`, `startup-report-always`, `startup-report-no-terminal`, `no-overlays`, `defer-root-guard`, `report-exclude-network`, `hijack-before-tui`。全て「上流と意図的に違う」点で、上流が変われば前提が崩れる。 4. **`cordis.patch.yml` の `!!js` プローブ** … 公式行の id/name を実行時に走査して自己 disable する。例: `:220` `[...ctx.loader.entries()].some(entry => entry.options.id === 'storage' && entry.options.name === '@deepseek-ai/dsh-storage' && !entry.disabled)`、`:224`, `:230`, `:240`, `:246`, `:260`, `:323`。**上流が行 id か package 名を変えると probe が黙って false になり、行が二重挿入されるか、逆に不要な disable が起きる。** 5. **`cordis.patch.yml:20-35` のバージョン分岐 `!!js`** … `dsh-system-prompt` の version を `require.resolve` して `persona` / `personaPrefix` を切り替える。`ADAPTER.md:144-146` も「0.1.7 で旧 roster/code-runtime 行を disable、registry 経由で bundle 登録」と記述。 6. **`patch-surface.snapshot.json` のゲート** … `ADAPTER.md:148-152` の通り、上流発版で patch 面が変われば CI が先に落ちる(=人間の追随が必須)。 7. **`UPSTREAM_VALIDATED_VERSIONS` 17 行**(`contract.ts:40-58`)と **`UPSTREAM_BLESSED_PACKAGES`**(`:70-…`)、**peer 範囲**(`package.json:270-…`、40 パッケージ分の `peerDependenciesMeta`)。`ADAPTER.md:92-97,161-163` が「`contract.ts` が唯一真源、peer/CI の SHA/検証スクリプトの定数はその鏡で同時に直す」と明記。契約外は起動時 drift 警告(`contract.ts:278-306`)。 8. **`src/dsh-adapter/compat/`(7 ファイル / 1,812 行)** … まさに版差吸収層。例 `compat/messages.ts:3`「0.1.7 puts tool output on the message; older logs wrap it in one block」、`compat/shell.ts:1-5`(`execute(resolve(spec)).result()` 優先、旧 host は `run`)、`compat/settings.ts:1-8`(新 host は Config volatile、旧 host は scope 登録)、`compat/sessionLog.ts` は **1,304 行**。 9. **`src/dsh-adapter/fresh-agent.ts:30-60` の `session-persistence-jsonl.create` 差し替え** … `writer.id` / `append` / `flush` / `close` という**非公開形状**に依存。`:17-19` で `JsonlPersistence` をローカル宣言している=公開型ではない。 10. **`src/dsh-adapter/channel/model-selection.ts:56-95`** … 上流 `installModelSelection`(`node_modules/@deepseek-ai/dsh-agent/lib/types/model-selection.js:45-94`)と**ほぼ同一のロジック+通知文言を複製**(`model-selection.js:18` の `[model changed: …]` と `model-selection.ts:91` が同一文字列)。上流が通知や `agent/request` の扱いを変えても dsh-TUI 側は追随しない。 11. **`@deepseek-ai/*` の値 import が src 全体に散在** … 40 近いパッケージが `src/dsh-adapter/**` から直接 import される。解決は `$DSH_HOME/profiles/node_modules` 回退リンク(`bin/dsh-tui.js:88-91`)か `PluginPackages` 乗っ取り(`host-dsh.ts:329-331`)頼み。**profile の node_modules が壊れる/新規インストール直後は解決に失敗し得る**(`host-dsh.ts:276-282` がその旨を明記)。 12. **`bin/dsh-tui.js:1660` の `lib/types/dsh-adapter/host-entry.js`** … コンパイル出力のパスと配置に依存(`existsSync` が false なら無言で `dsh --profile` 経路へ)。 13. **`src/dsh-adapter/entry-slot.ts:32` の `Symbol.for('@deepseek-harness-tui/dsh-tui:host-entry')` + `globalThis`** … 入口と profile 行が**別コピーのパッケージ**だと出会えない。`src/dsh-adapter/index.ts:24-32` が同種の事故(patch と plugin 本体のコピー齟齬で boot deadlock)を明記。 14. **`src/dsh-adapter/index.ts:465` の `apply` は 2 引数、実体は `plugin.ts:237` の 4 引数** … 入口(`host-entry.ts:137`)は `./plugin.js` から `apply` を取る。入口と profile 行で**別々の `apply` を踏む**二重構造で、`entry-slot` 経由で調停している。 --- ## 5. コード量の実感 計測(`find … | xargs wc -l`、`.d.ts` 除外): | ディレクトリ | ファイル数 | 行数 | |---|---:|---:| | `src/` 全体(生成物含む) | 1,784 | 230,275 | | `src/` 全体(`*/generated/*` 除外) | 909 | 220,157 | | `src/dsh-adapter/`(DSH 境界+実装) | 233 | **58,122** | | `src/components/` | 191 | 41,116 | | `src/ink/`(Ink 相当の自前実装) | 124 | 33,755 | | `src/backends/`(claude/codex 等) | 973 | 28,909 | | `src/screens/` | 20 | 18,364 | | `src/adapter/`(ports/kernel/upstream/standard/spec) | 88 | 13,486 | | `src/` 直下ファイル | 58 | 16,112 | | `src/channel/`(中立投影) | 19 | 4,864 | | `src/utils/` | 34 | 4,150 | | `src/terminal-utils/` | 14 | 3,390 | | `src/native-ts/yoga-layout/` | 2 | 3,296 | | `src/agent/`(中立ドメイン) | 10 | 1,562 | | `src/backends/shared/` | 9 | 1,055 | | `src/sessions/` | 4 | 847 | | `src/trajectory/` | 5 | 843 | | `src/settings/` | 1 | 741 | | `src/math/` | 3 | 552 | | `src/hooks/` | 4 | 154 | | `src/bootstrap/` | 1 | 12 | - 巨大単一ファイル: `src/i18n.ts` 2,949 行 / `src/update.ts` 2,456 行 / `src/theme.ts` 1,503 行 / `src/tips.ts` 952 行 / `src/sessionMounts.ts` 788 行。 - `src/dsh-adapter/` の内訳で重いのは `channel/`(68 ファイル 13,555 行、うち `channel/core/` 11 ファイル 3,454 行)、`trajectory/`(8 ファイル 2,994 行)、`sessions/`(9 ファイル 1,843 行)、`oauth/`(10 ファイル 1,762 行)、`compat/`(1,812 行)、`migrate/`(26 ファイル 3,312 行)。 - 参考: `tui-profile/` は 98 ファイル、JS は 1,210 行のみ。`scripts/` は 602 エントリ(検証スクリプト群)。 ### 「DSH の公開面だけで書く場合」の当たり(**推測**) **必須(残る)** - `src/dsh-adapter/` のうち **接続コアだけ**: `host-contract.ts`(111) + `host-dsh.ts`(520) + `host-entry.ts`(216) + `contract.ts`(359) + `plugin.ts` の DSH 接続部 + `backend/session.ts`(857) + `backend/translate.ts` + `channel/model-selection.ts`(102) + `approvals.ts`(557) + `channel/input-delivery.ts`(442) + `fresh-agent.ts`(208) + `channel/core/sessions.ts`(320) + `channel/session-resume.ts`。**推測で 5,000〜8,000 行規模**(`src/dsh-adapter/` 58,122 行の 1〜2 割)。 - `src/agent/`(1,562 行)… 中立ドメイン型。DSH のイベント語彙を UI から隔離する要。 - `src/channel/projection.ts`(72KB)+ `src/channel/`(4,864 行)… イベント→画面行の変換。 - UI 層(`ink/` 33,755 + `components/` 41,116 + `screens/` 18,364 ≒ 93,235 行)… ここが本体。DSH 公開面だけでも削れない。 **不要になる(推測)** - `src/backends/claude/`(30 ファイル 5,917 + 1,542 + 1,940)と `src/backends/codex/`(11 + 3 + 1 + 100 + 4 + 16 + 8 ファイル、生成プロトコル 875 ファイル含む)… DSH 一本なら不要。マルチバックエンド抽象(`src/agent/backend.ts` 224 + `backend-manifest.ts` 153 + `capabilities.ts` 413)も縮退可能。ただし `src/backends/codex/protocol/generated` は `package.json:107` で出荷除外済みなので、実出荷コードの削減量は見た目のファイル数ほど大きくない。 - `src/dsh-adapter/migrate/`(26 ファイル 3,312 行)… claude-code/codex/zcode/omp/grok-build からの移行。DSH 単独なら不要。 - `src/dsh-adapter/compat/`(1,812 行)… **単一の DSH 版に固定できるなら**大幅に削れる。ただし版を跨ぐなら削れない。 - `src/dsh-adapter/oauth/`(10 ファイル 1,762 行)、`trajectory/`(2,994 行)、`sessions/`(1,843 行)、`usage`/`pricing` 系 … 製品要件次第。 - `src/dsh-adapter/host-dsh.ts` + `host-entry.ts`(736 行)… **`dsh --profile dsh-tui` 経路だけに統一すれば不要**。代わりに「自分の入口を持てない(dsh CLI 経由でしか起動できない)」「profile 経由なので TUI 側で boot を制御できない」制約を受け入れることになる。**これが最大の取捨**(`bin/dsh-tui.js:1660-1678` の分岐を捨てる判断)。 - `src/dsh-adapter/plugin-host.ts` / `extensions.ts` / `panels.ts` / `scenes.ts` / `settings-sections.ts` / `command-trees.ts` / `workspaces.ts`(`package.json:47-102` の公開サブパス群)… サードパーティ plugin を載せないなら不要。`src/adapter/`(13,486 行、特に `standard/` 1,763 + `upstream/` 3,127 + `ports/` 3,971)の大半も plugin 相互運用のためで、載せないなら削れる。 - `src/dsh-adapter/lite-profile.ts` … 非 DSH カーネル専用。 **注意:** 上記は行数とモジュール依存からの**推測**であり、実際の削減可否は各ファイルの DSH 依存を 1 本ずつ切って tsc を通すまで確定しない。特に `plugin.ts`(`src/dsh-adapter/` 直下で最大級)は「plugin ホスト」「承認」「プリセット」「セッション起動」「設定」「entry 連携」が 1 ファイルに同居しており、**分割してからでないと削減量が見積もれない**。 --- ## 不明点リスト 1. **`cordis.patch.yml` の disabled 行の正確な数** … `patch-surface.snapshot.json` は「inserts 19 / configOverrides 6」を列挙するが、`ADAPTER.md:127` の「disabled overrides: 23 行(22 行恒常 + `command-goal` 条件付き)」は**スナップショット JSON にフィールドが無い**。`cordis.patch.yml` 内の `disabled: true` を数えれば出るが、`!!js` 条件付き disable(`:104`, `:220`, `:224`, `:230`, `:240`, `:246`, `:260`, `:323`)の実行時 true/false は静的に決まらない。**不明**。 2. **`lib/` 配下の実測行数** … `package.json` の `files` に出荷物として `lib` があり、`bin/dsh-tui.js:1660` が `lib/types/dsh-adapter/host-entry.js` を参照するが、本調査は `src/` のみ計測。`lib/` は `tsc -p tsconfig.json` の出力(`package.json` の `compile` スクリプト)なので src とほぼ同比率と**推測**されるが、未計測。 3. **`dsh-tui-webserver` / `dsh-tui-auth` の実装本体** … `cordis.patch.yml:398` に `dsh-tui-auth` 行があるが、対応するソース(`src/dsh-adapter/oauth/` か別パッケージか)を特定できていない。`ADAPTER.md:134-141` は「`dsh-tui-webserver` が webserver パッケージとホストアカウントサービス行を探す」と記すのみ。**不明**。 4. **`host-replica.snapshot.json` のハッシュが指す具体的ファイル名** … スナップショットは `replicas: { id: sha256 }` のみで、対象 lib ファイルのパスを持たない(`host-contract.ts:76-79` に「`lib/*.js` を検索する」とあるが、解決は `scripts/verify-host-contract.ts` 側)。**どのファイルのハッシュかは未確認**。 5. **`DSH_TUI_HOST_ENTRY=0` の実運用頻度** … コード上の分岐は確認したが、既定が in-process である以外に、どちらの経路が実際に常用されているか(README 等の記述)は未確認。 6. **`src/backends/codex/protocol/generated` の 875 ファイル** … 出荷除外(`package.json:107`)は確認したが、`pnpm compile` がこれを毎回生成するのか、リポジトリにコミット済みなのかは未確認(`src/dsh-adapter/backends.generated.ts` は `ADAPTER.md:73-84` により**コミット必須**と明記されているが、codex protocol 側は言及なし)。 7. **`tui-profile/` が DSH 本体からどう読まれるか** … `package.json:36-40` の `imports` と `files` の `tui-profile/protocols` は確認したが、`vendor/dsh-std` の `@dsh-std/core` が `ProtocolCatalog` をどこで構築するか(`src/adapter/standard/tui-extension.ts:42 buildAdmissionCatalog`)の実行時タイミングは未検証。 8. **`vendor/dsh-std` submodule のコミット SHA** … `.gitmodules` で URL は確認したが `git submodule status` は未実行(読み取り専用の範囲で実行可能だが今回は未実施)。**不明**。 9. **`src/dsh-adapter/index.ts:465` の `apply` が 2 引数である理由** … `plugin.ts:237` が 4 引数であることは確認したが、入口が `./plugin.js` から直接 `apply` を取る一方で `./index.js` にも 2 引数の `apply` を残す設計意図(DSH Loader からの呼び出し面を保つため、と**推測**)は一次資料で確認できていない。