dsh-plugin-development について
DeepSeek Harness のプラグイン開発に向けた、調査、設計、実装、テスト、配布のガイドです。対象バージョンの API 確認と、host・client の実行面の分離を重視しています。
主な機能
- 01対象バージョンの API 確認
- 02host と client の実行面の選択
- 03Bundle、ツール、サービスの実装
- 04リソースのライフサイクルと永続化管理
- 05ビルド、テスト、インストールの検証
活用シーン
- 01Harness の host プラグイン開発
- 02Web UI 付き client プラグインの開発
- 03プラグインのビルドや導入の問題調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nanmicoder/ dsh-agent-teams --skill dsh-plugin-developmentインストール後、Claude Code で/dsh-plugin-developmentを入力して読み込まれたか確認します。
詳細
DSH プラグイン開発
対象バージョン:本文の主な内容は 2026-08-13 時点の歴史的なテンプレートを維持しており、現在の Harness API を表すものではありません。AgentTeams リポジトリ内でアップグレードを行う場合は、まずプロジェクトルートの
skills/README.mdを読み、プロジェクト内のdsh-upgrade-audit、plugin-upgrade、plugin-testを使用してください。この skill を単独でインストールする場合、これらの関連ファイルは必要ありません。引き続き §2 に従って根拠を調査してください。下記のdsh-client-runtime、conversationEventsなどの旧インターフェースは、対象の正確な tag と npm の成果物を使って再確認する必要があります。Alpha.2 以降にそのまま使用しないでください。
これは正式版を目指すための実行チェックリストです。まず実行面を判断し、公式テンプレートを選択して実装し、実際の組み合わせとユーザーのインストール経路で必ず検証してください。特定のプロジェクトで偶然採用されている実装を、フレームワークの契約と見なさないでください。
1. 開始前
pwd、git rev-parse --show-toplevel、git status --short --branchを使って、プロジェクトとユーザーの変更を確認します。package.json、cordis.patch.yml、tsconfig*.json、ビルド設定、関連するsrc/とテストを読みます。- ユーザーの変更を上書きせず、ユーザーが明示的に除外した profile、ポート、インスタンスを操作しません。
- 必要最小限の実行面を判断します。
- ツール、system prompt、HTTP、永続化、provider:host。
- slot、Conversation Node、ブラウザーの状態、オーバーレイ:client。
- host の機能と Web での可視化が必要:host + client。
- Web の要件がない場合:
dsh.clientを宣言せず、client bundle もビルドしません。
- プラグインの唯一の責務、依存する service、追加する設定行、永続化の owner、ユーザーが確認できる検証面を記録してから、コーディングを開始します。
2. 根拠と公式リファレンス
2.1 根拠の調査順
動作が不確かな場合は、推測せず、次の順に根拠を調査します。
- 現在のプロジェクトと、インストール済みの
node_modules/@deepseek-ai/*にあるpackage.json、exports、types、README。 - 環境から明示的に提供された DeepSeek Harness checkout。読み取り専用で分析し、変更しません。
- 公式リポジトリをクローンして調査(§2.3 を参照)。
- それでも情報が不足する場合は、現在の正式版の exports/types を境界として、安全に失敗する最小限の実装を選び、前提を明記します。
ローカルマシンの絶対パスをハードコードしたり、許可されていない非公開リポジトリの内容にアクセスしたり、内容を伝えたりしないでください。
2.2 公式テンプレートの選択
Harness checkout が提供されている場合(環境から提供されたもの、または §2.3 に従ってクローンしたもの)、プラグインの形態に応じて、次のテンプレートを優先して読みます。パスは checkout のルートを基準とします。
| 目的 | 主な参考資料 | 学ぶポイント |
|---|---|---|
| Host Service / HTTP | packages/host/webserver | Service、static Config、Service.init、route disposer、接続のクリーンアップ |
| 最小限の client プラグイン | packages/client/ui-message-feedback | inject、apply、locale、セッションごとの controller、slot の登録とクリーンアップ |
| Slot / Conversation Node | packages/client/ui-conversation + packages/client/ui-slots | SlotMap、slot の kind/scope、children の割り当て、keyed node renderer |
| Bundle の階層化 | packages/bundle/base + packages/bundle/web-app | トップレベルの patch 配列、行 id の上書き、config 全体の置き換え、読み込み順 |
| シンプルな永続化 backend | packages/storage/storage-json | register → disposer → close、明示的な root、並行オープンの制御 |
| クラッシュセーフなログ | packages/session/session-persistence-jsonl | アトミックな公開、fsync、並行時の no-clobber、torn-tail の処理 |
| ツールプラグイン | packages/fs/tool-fs | defineTool、schema、render、オプションの機能のマウント |
| Client テスト | packages/test-support/client-runtime | jsdom、SlotTestRuntime、mount/dispose、fake service |
複雑なプラグインは根拠の補強にのみ使用し、最初のテンプレートにはしません。読み取り専用の調査を委任する場合、プロンプトでファイル、行範囲、契約、最小限の提案を提示するよう求めてください。
2.3 公式リポジトリによる補完調査
公式リポジトリ https://github.com/deepseek-ai/deepseek-harness は、引用可能な公開の MIT ライセンスの根拠資料です。調査では対象バージョン、対応する tag/commit、npm の公開状況を明確にしてください。デフォルトブランチをユーザーのインストールバージョンと同一視しないでください。補完調査が必要な場合は、次の手順に従います。
-
一時ディレクトリを選びます。ユーザーまたは環境から提供されたディレクトリを使います。例:
SCRATCH="$(mktemp -d)"。ローカルマシンの絶対パスをハードコードしないでください。 -
既存の checkout を再利用します。remote、HEAD、作業ツリーの状態を確認し、
git show <exact-tag>:<path>などの読み取り専用の方法で対象バージョンを調べます。既存の checkout をリセットしないでください。バージョンのオブジェクトを補う必要がある場合は、明示した tag を fetch し、既存の変更を保持します。 -
checkout がない場合は一時ディレクトリにクローンします(読み取り専用の調査なので、
pnpm installは不要です)。その後、正確な tag/commit を指定してファイルを読み取ります。git clone --filter=blob:none https://github.com/deepseek-ai/deepseek-harness.git "$SCRATCH/dsh-official" -
公式の
deepseek-ai/deepseek-harnessのみをクローンしてください。許可されていない非公開リポジトリの内容にアクセスしたり、内容を伝えたりしないでください。クローンした内容も読み取り専用で分析し、変更しません。
クローン後は、次の場所を確認します。
- まずルートの
AGENTS.md(CLAUDE.mdはそのシンボリックリンク)を読みます。リポジトリの構成、コマンド、規約をまとめて確認できる、公式の agent 向け入口です。 - 次に
packages/README.mdのグループ表を使って、対象パッケージがどのpackages/<group>/<pkg>にあるかを確認します。 - §2.2 のテンプレート表に従って、対応するパッケージの
README.mdとsrc/を読みます。調査結果にはファイルと行範囲を示してください。
発展時の補完調査:§2.2 の歴史的なテンプレートパスはインデックスにすぎません。すべて対象バージョンの実際のコードと公開成果物を優先してください。パスや名前が変わっている場合は、対象バージョンの packages/README.md で新しい場所を特定し、修正内容を報告してください。対象の tag、解決後の commit、実際にインストールされている依存関係のバージョンを記録してください。GitHub に tag があることは、npm で公開済みであることを意味せず、本プロジェクトでそのバージョンの受け入れ検証が済んでいることも意味しません。
3. Bundle、Profile、package の契約
3.1 2 つの概念
- Bundle は作者が配布するパッケージです。
package.json.dsh.bundle.patchが設定レイヤーを指します。 - Profile はユーザーが実行する組み合わせです。
$DSH_HOME/profiles/<name>/package.json.dsh.profile.bundlesに、bundle の順序付きリストが保存されます。 - プラグイン作者は bundle を作成し、
dsh pluginは profile を作成・管理します。ユーザーの profile manifest を手書きしないでください。
3.2 最小の両面 package
{
"name": "dsh-my-plugin",
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
"./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
"./cordis.patch.yml": "./cordis.patch.yml",
"./package.json": "./package.json"
},
"files": ["lib", "cordis.patch.yml", "README.md"], // 目录或显式清单均可;官方仓库常用显式文件清单
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"platform": "web",
"inject": ["@deepseek-ai/dsh-client-runtime"]
}
}
}
ルール:
- Host のみのパッケージでは、
./clientとdsh.clientを削除します。 - Client パッケージには、
dsh.client.platform: "web"と実在するexports["./client"]の両方が必要です。 dsh.client.injectは、図とともに配信される情報用メタデータです(事前チェック表示 / HMR diff 用)。client fiber の有効化順序は決定しません。事前取得はdsh.client.immediatelyによって行われ、実際の依存関係の待機は client bundle がエクスポートするexport const inject(§5.1)によって行われます。両者は互いに代替できません。dsh.client.immediatelyは、起動に不可欠なエントリーポイントにのみ使用する、任意の事前取得マーカーです。通常のサードパーティープラグインでは、デフォルトで有効にしないでください。- 現在の正式なフィールドは
dsh.clientです。歴史的な互換フィールドは、対象の正式なデプロイ先で引き続き明確に読み取られる場合にのみ追加します。 - exports、
files、Git / 公開成果物は一貫していなければなりません。どのエントリーポイントも、存在しないファイルを指してはなりません。 - DSH、Cordis、React などの共有ランタイムは、runtime identity の複製を避けるため、優先的に peer として宣言します。バージョン範囲は対象の正式版の package metadata から確認します。
3.3 Patch 层
cordis.patch.yml 必须是顶层数组:
- insert:
- id: my-plugin
name: dsh-my-plugin
config: {}
注意:
id是配置树中稳定的行身份;name是 Node 可解析的包名或导出路径。- 后层按
id覆盖前层;目标行的config是整段替换,不是深合并,因此覆盖时要重述所需键。 - 生效顺序是 profile bundles → profile
cordis.patch.yml→$DSH_HOME/cordis.patch.yml→ 命令行--patch;后者获胜。 - 包没有
dsh.bundle时只会成为普通依赖,不会自动成为 profile 层。
4. Host 面实现
4.1 函数插件
普通插件通常导出:
export const name = 'my-plugin'
export const inject = ['tools']
export interface Config { enabled: boolean }
export const Config = z.object({ enabled: z.boolean().default(true) })
export function apply(ctx: Context, config: Config): void {}
z从@deepseek-ai/schemastery导入(不是 zod);static Config = Config引用导出的 schema,与官方内联的static Config: z<Config> = z.object({...})等价。inject是必需 service;未满足时 fiber 保持 pending,框架会在服务就绪后激活,不要用轮询模拟依赖注入。- Config 默认值放 schema;任何部署可能需要改变的值都应成为配置,而不是源码常量。
- 可选 service 用
ctx.get()判断或ctx.inject([...], childCtx => ...)惰性挂载;不要在apply()中抢跑兄弟 provider。
4.2 Service 插件
当插件提供稳定 service 时,参考 host/webserver:
export class MyService extends Service {
static Config = Config
constructor(ctx: Context, config: Config) {
super(ctx, 'myService')
}
async [Service.init](): Promise<void> {}
}
- 构造器声明 service key;异步启动放在
Service.init。 - 初始化失败应让 fiber 失败并由启动方报告,不要吞掉组合错误。
- 注册方法返回 disposer;拥有资源的一方负责关闭资源。
4.3 Effect 所有权
所有长生命周期资源必须归当前 fiber:
- route、listener、watcher、timer、React root、DOM、socket、临时 service 都必须可清理。
- 用
ctx.on()或ctx.effect(() => disposer, label)。 - disposer 顺序通常是:停止外部入口/注销 registry → 等待或取消在途工作 → 关闭资源。
- 需要服务后绑定时,用“立即尝试 + service 事件/
ctx.inject重试 + 幂等 guard”,不要重复注册。
4.4 工具
使用 ctx.tools.register(defineTool(...)):
description写清何时调用、必要前置条件、失败语义和副作用。parameters与output.schema都用@deepseek-ai/dsh-tools的 value-schema DSL(编译后是受支持的 JSON Schema 子集):parameters是隐式开放对象根、必填用属性内联required: true;output.schema声明 canonical 返回值并在注册时被assertSupportedJsonSchema强制校验。二者是同一 DSL 的两个面,不是两套语言。output.render给模型稳定、紧凑、可判定的文本。- 从
exec.agent获取当前会话、工作区和 owner,不从全局进程状态猜。 - 异步工作观察或转发
exec.signal;写操作要有幂等、锁或冲突策略。
4.5 HTTP
- 注入当前正式版 Web server service,并用结构化最小接口降低耦合。
- 路由通过
ctx.effect(() => ctx.webServer.register({ kind: 'exact' | 'prefix', path, handler }))注册;重复 (kind, path) 会抛错。 - 状态接口显式设置缓存策略:敏感或实时快照优先
Cache-Control: no-store,可重验证资源使用no-cache;静态资源使用明确白名单和正确 content type。 - path decode、请求体解析和 handler rejection 都要转成明确 4xx/5xx,不能成为未处理 rejection。
- exact route、最长 prefix、fallback 的所有权不能冲突;未知插件资源返回 404,不落入 SPA fallback。
- 涉及权限或本机能力时采用最小暴露、回环/信任边界和方法白名单。
4.6 持久化与并发
先判断应复用正式版 storage/session persistence service,还是插件拥有独立介质。无论哪种:
- 路径配置显式指定;不要用
process.cwd()默认值散落用户数据。 - 状态按 workspace、session、owner 或业务 id 建立清晰隔离维度。
- 同一资源的读改写串行化;并发创建采用 no-clobber 语义。
- 人可读 JSON 要用同目录临时文件 + fsync + 原子发布;追加日志要处理 torn tail。并发创建用
link()+unlink()的 no-clobber 协议,勿用rename()静默覆盖。 - Registry backend 的清理顺序是 unregister 再 close。
- 恢复与 HMR 不能假设创建事件会重放;需要时显式扫描和回填已有对象。
5. Client 面实现
5.1 最小入口
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
export const inject = ['slots']
export function apply(ctx: ClientContext): void {}
- 类型贡献使用 type-only import 拉入 Context/SlotMap merge。
- client 注册、controller、listener、style 和 DOM 都必须随 client fiber dispose。
- per-session 状态按
SessionId分桶;连接重置时只重同步已经读过的对象。
5.2 Slot 四步契约
- 声明:从提供 slot 的官方包拉入类型;自定义 owner 才通过 module augmentation 扩展
SlotMap。 - 认领:父 entry 的
children表声明子 slot;声明即占有渲染权,不要争抢别人的 seat。 - 注册:owner 与贡献者的激活顺序不保证,使用
ctx.slots.inject(key, () => ctx.slots.register({ name, children?, store?, locale?, inject?, ...kind 参数 }, Component))等待声明;children同时是子 slot 的认领表(认领即占有渲染权)。kind 参数:keyed 必填key、list 必填id(可加order/label)、chain 必填select;single/keyed/list 可加priority做 cell 隐藏(同 cell 同 priority 会抛错)。向未声明 slot 直接 register 会抛错。 - 渲染:owner 使用
renderSlot/renderSlotChain;贡献者不 import owner 的实现组件。
选择接缝时先检查当前正式版类型。常见会话 UI 接缝包括:conversation.session.header.actions/.utilities、conversation.view、conversation.chat.node、conversation.chat.commandview、conversation.chat.assistant-actions、conversation.chat.turnTail、conversation.input.dock、conversation.composer.dock、conversation.composer.bar、conversation.input.left/conversation.input.right/conversation.input.plan/conversation.input.model。全局浮层用 shell.overlay(list/root),不要碰 root 单槽。不要仅凭旧文档写 slot 名,以当前正式版 ui-conversation/src/client/contract/slots.ts 的 SlotMap 为准。
5.3 Conversation Node
Conversation Node は「イベントの折り畳み+キー付きスロットレンダラー」の組み合わせです。
- 共有イベント型を定義し、session event map にマージします。
uiConversationをinjectに配置し、ctx.uiConversation.events.register(definition)を呼び出します。matchでイベントを選択します。startでノード状態を作成します。updateで seq に基づき決定的に折り畳みます。buildViewNodeで安定した view node を生成します。
ChatNodeDataMap/ノード kind 型をマージします。conversation.chat.nodeに同じ key のレンダラーを登録します。
レッドライン:
- 同じイベントシーケンスを再生した場合、必ず同じノードになる必要があります。時刻、乱数、現在のディスク状態を読み込んではいけません。
matchは安定した業務 ID とstart|updateの役割を返します。ノードエンジンは現在のセッション内でconversationContextKey(kind, businessId)を使って重複排除します。セッションをまたいで永続化するキャッシュでは、別途 owner session を key に含めてください。エンジンの契約と混同してはいけません。- イベントは業務 owner セッションに書き込みます。共有 host/client イベントファイルは type-only とし、可能であれば実行時 import をゼロにして、2 つの tsconfig の Context augmentation が相互に汚染されるのを防ぎます。
- ディスク/サーバーのスナップショットはリアルタイム UI の真実として使用できます。イベントストリームは会話への投影、監査、決定的な履歴に使用します。両者の役割を混同しないでください。
5.4 Portal によるフォールバック
意味的に適切な slot が使える場合は、fixed portal を使わないでください。アプリ全体のオーバーレイには、まず shell.overlay(list/root、entry が pointer events を明示的に有効にするまではクリック透過)を登録します。グローバルな slot がどうしてもない場合に限り、body portal を使います。
- React root、host DOM、window listener、グローバル属性にはすべて disposer を用意します。
- session list に追従し、現在の owner でフィルターします。ナビゲーション時には即座に閉じます。
- ワイド画面ではメイン列の幅を譲り、狭い画面では overlay に戻します。安定した
data-*のみを使用し、ハッシュ class に依存しないでください。 - 初回表示時に復元された既存のアクティビティはバッジのみ表示し、最初のリクエストの応答後に自動展開して大きなレイアウト移動が起きないようにします。安定後に発生した新しいアクティビティは自動展開します。
- パネルの高さはコンテナー/ビューポートの一部に制限し、コンテンツ領域内でスクロールさせます。狭い画面では個別に上限を設定します。
- ポーリングには
no-store、in-flight guard、応答形式の検証、アンマウント保護を使用します。失敗時には最後に成功したスナップショットを保持します。 - キーボード、
:focus-visible、aria-*、Escape、reduced motion に対応します。hover/focus はプレビューのみとし、click で固定状態にします。
6. TypeScript と Client のビルド
6.1 2 つの tsc program
Host と client では 2 つの program を使用します。ファイル名はプロジェクトのレイアウトに合わせて選べます。公式リポジトリでは、tsconfig.host.json と tsconfig.client.json の 2 つの集約 program を使って、それぞれ host/client のチェックを行います。host では packages/client/*/src/** と *.client.* テストを除外します。client の集約には、各 client パッケージの CSS module 宣言、client テスト、ビルドスクリプトを含めます。共有 leaf は project references を介して含め、各 packages/client/* パッケージでも、パッケージ内の型チェック用に個別の composite tsconfig を管理します。JSX には .tsx と react-jsx を使用します。相対 TS import は、emitted JS に正しく書き換えられる必要があります。
これにより、同名の Context service に対する host session と browser runtime の declaration merge の競合を防げます。
6.2 Client バンドル
現行の正式版 Harness の client tsdown helper または検証済みテンプレートを優先して再利用し、loader プロトコルを独自に実装しないでください。生成物はビルド時に自動で次のようにラップされる必要があります。
window.__ModuleLoader__.load({ id, factory: (require) => { /* bundle */ } })
ビルドでは以下を維持する必要があります。
- host/client の両方の生成物を共存させること(client build で host の出力を消去しないこと)。
- sourcemap。
- CSS Modules のコンパイルと
style[data-plugin]の挿入。 - emitted
lib/からsrc/リソースを見つけるためのパスフォールバック。 - client bundle purity gate。
6.3 Client import の純度
ブラウザーモジュール表には、正式版プラットフォームの seed モジュールと明示的な例外のみを記載します。ルールは次のとおりです。
- プラットフォームモジュールは、正式版
packages/client/web/src/platform.tsと公式 client ビルド設定を基準にします。React、Cordis、slots、web-react、primitives、attachment、schema-form などはモジュール表で提供されます。 @deepseek-ai/dsh-client-runtime/clientは、公式ビルド設定で明示的に一時的な例外として指定されています。通常のプラットフォームモジュールではありません。runtime 値を任意に import できる許可として一般化しないでください。- 型のみの import は消去されるため、パッケージをまたいで型定義を取り込めます。
- wire types、生成された remote codec、または明示的に vendored された純粋なライブラリは、公式テンプレートで許可されている場合に限り inline にできます。
- その他のプラグインをまたぐ値の import は禁止です。連携には Cordis service/remote/slot を使用してください。そうしない場合、ビルド時の純度ゲートまたは実行時の require が失敗します。
7. 配布、インストール、生効の境界
7.1 インストール
dsh plugin --profile <name> <args...> は profile ディレクトリ内の pnpm 転送レイヤーです。成功後、インストール状態と dsh.bundle に基づいて bundle リストを照合します。そのため、npm、パス、tarball、Git に対応しています。
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:<owner>/<repo>
GitHub での配布に npm の公開は必要ありませんが、ビルド戦略を 1 つ選択する必要があります(Git で取得されるのはソースであり、ビルド成果物ではありません)。
- 公式の推奨:自己完結型の
prepare(公式 turtle-ui モード)を提供します。pnpm ≥10 では、Git 依存関係のビルドスクリプトはデフォルトでブロックされます。ユーザーは profile のpnpm-workspace.yamlでallowBuildsを明示的に設定した後、addを再実行する必要があります。これはサードパーティのコードを実行するため、commit を固定し、レビュー済みのリポジトリのみを信頼してください。 - 代替案(対話なしのインストール):exports が参照する完全かつ最新の
lib/を Git にコミットします。ユーザーは依存関係のスクリプトを実行する必要はありませんが、公式には推奨されない方法です。
README には、新規の profile で検証済みの推奨コマンドのみを記載してください。インストール後、対象の profile を再起動します。
7.2 HMR と再起動
- client HMR には、
tsdown --watchなどのビルド watcher が継続的にlib/client.jsを書き換える必要があります。host HMR はファイル変更の stat 検知のみを行い、rev/SSE を通じて browser fiber の dispose/reload をトリガーします。 - bundle の内容の変更のみ client HMR を利用できます。package manifest、exports、プラグインセット、profile bundles、host コードの変更には再起動が必要です。
- watcher を使わずに通常の build を実行した後は、既存の DSH ページを再読み込みします。
- DSH GUI の代わりに独立した Vite server を起動しないでください。Web shell は host が挿入する
window.__DSH_BOOT__に依存します。
8. 検証マトリックス
8.1 ベースライン
pnpm typecheck
pnpm build
pnpm test # package.json 声明时运行
pnpm verify # package.json 声明时运行
git diff --check
まず package.json.scripts を読み込み、すべてのリポジトリに同名の集約スクリプトがあると想定しないでください。公式 Harness では check:ci/check:all と複数の verify-* gate を使用します。サードパーティのプラグインでは verify を独自に定義できます。プロジェクトレベルの verify/check では、少なくとも以下をカバーしてください。
- 純粋な業務ルールと状態遷移。
- 一時ディレクトリでのファイルの往復処理、ロック、アーカイブ/復元。
- client で個別にテストできる投影/折り畳みの純粋関数。
- canonical Skill とミラーの整合性(プロジェクトにミラーがある場合)。
8.2 Host 与真实组合
- 单元测试覆盖 schema、service、失败和 disposer。
- 有 registry/backend 接口时使用共享 contract suite。
- 不只手搓
ctx.plugin():至少一个测试通过真实 Loader/patch 组合启动,断言用户可见表面。 - 先用
dsh plugin --profile <scratch> add <pkg>创建非内置 scratch profile,再执行dsh --profile <scratch> --dump-config,确认 bundle 层、行 id、name、config 和注入顺序;内置web/headlessprofile 可由 launcher 初始化。另有--dump-default-config:只打印 bundle 层、跳过用户层与--patch,可作坏cordis.patch.yml时的恢复诊断。 - 真实任务使用
dsh --profile headless "一个小而可判定的任务";不要发明dsh run子命令。
8.3 Client
- client 测试使用 jsdom lane;通过 SlotTestRuntime 或最小 fake services mount 插件。
- 断言 slot 注册、渲染、session 隔离、connection reset、dispose 后 registry/DOM/style/controller 均清理。
- 每个 registry 贡献至少有一个 HMR/dispose 安全测试。
- GUI 使用独立 web profile 和真实浏览器,验证名册、路由、交互、刷新、宽窄屏、滚动、焦点和 reduced motion。
8.4 从零安装与 Git 分发
- 使用全新临时
DSH_HOME/profile。 - 按 README 的精确命令安装。
- 断言 profile dependency 与
dsh.profile.bundles。 - 断言所有 exports、host/client bundle、patch 和静态资源存在。
--dump-config必须出现插件层。- 启动后检查 host route、client roster 和真实 UI。
仓库仍私有时,可把待发布内容复制到临时 Git repo 并提交,再通过 git+file://... 安装;这能验证“Git 获取的内容”而不是当前 checkout 的未提交文件。前提:git 在 PATH、目录是已提交的真实 Git 仓库;若包声明了 prepare,还需在 profile 的 pnpm-workspace.yaml 加 allowBuilds(与 §7.1 相同门禁)。只删除本任务创建的精确临时目录。
9. 完成标准
完成前逐项确认:
- 运行面最小,manifest、exports、patch 与产物一致。
- 必需 inject 和可选 service 边界清楚;pending/failed 状态可诊断。
- route、registry、timer、watcher、DOM、React root 和存储均可清理。
- Conversation Node 可确定性重放,owner 与去重维度正确。
- client import 未越过模块表,host/client 类型隔离。
- 持久化有并发与崩溃语义,不依赖偶然 cwd。
- typecheck、build、verify、真实组合、从零安装和需要的 GUI 验证通过。
- README 安装命令与实际分发形态一致。
- 未执行未经授权的 commit、push、发布或 visibility 变更。