CH 18 · 最初のプラグイン:hello-plugin
この章の目標
CH 17 では他人のプラグインをインストールする方法を学びました。この章から自作を始めます —— 目標は最小限:hello-plugin を書き、実際に dsh にロードさせる。ツールは書かない、界面も触らない、検証することは 1 つだけ:自分の書いたプラグインが dsh に発見され、ロードされ、実行される。このステップが通れば、CH 19 から CH 23 のおもしろい部分(ツール、フック、UI、公開)はすべてその上に育ちます。
まず心構え:プラグインは神秘的なものではありません。CH 08 で「すべてはプラグイン」と述べましたが、逆は:dsh に 1 つ能力を増やしたいなら、apply 関数を export する小さなモジュールを書く。それがプラグイン全体の骨格です。
実操作:hello-plugin を書く
任意の作業ディレクトリで、ディレクトリを作ります(まずそのディレクトリへ cd、それから実行):
New-Item -ItemType Directory -Path "hello-plugin\src" -Force次に hello-plugin\src\hello-plugin.js を作成し、次のように書きます:
export const name = 'hello-plugin'
export function apply(ctx) {
console.log('[hello-plugin] plugin loaded!')
}コアロジックはたったの 2 行:プラグインがロードされたとき、[hello-plugin] plugin loaded! を表示。これが表示できれば「自分のコードが dsh に実行された」ことの証明 —— これが最初のマイルストーン。
ここでは公式の TS 例ではなく JS を使います:グローバルインストールした dsh には tsx ランタイムが入っておらず、直接
.tsをロードするとエラーになります;.jsならビルド不要、依存ゼロ、5 分で動きます。複雑なプラグインを書くときに TypeScript とビルドチェーンを持ち込みます(CH 20 で展開)。
dsh にロードさせる
ファイルがあるだけでは、dsh はロードを知りません。dsh に「追加でこのプラグインをロードせよ」と伝える「オーバーレイ」が必要です。hello-plugin\cordis.yml を作ります(下の name のパスは例、自分のディレクトリに置き換え、下記の注意参照):
- insert:
- id: hello
name: 'file:///E:/software-workspace/DeepSeek%20harness%20demo/hello-plugin/src/hello-plugin.js'3 つの注意:
nameはfile://で始まる完全な URL である必要があり、E:\...やE:/...とは書けません。Windows では、dsh のモジュールローダはfile:///E:/...の形のみ受け付けます;ドライブレターのパスを直接書くとOnly URLs with a scheme in: file, data, and node are supportedと報告されます。- パス内のスペースは
%20にエンコードする必要があります。たとえばディレクトリがmy work spaceなら、my%20work%20spaceと書きます。 - これは絶対パスです。 patch ファイルは設定のみを提供し、モジュール解決のルートは依然として profile ディレクトリなので、ローカルプラグインはフルパスで書く必要があります。
headless で 1 回走らせ素早く検証
Web UI は触らず、headless を使って素早くプラグインが実際にロードされたか検証します:
cd your-workspace-directory
dsh --profile headless --patch "./hello-plugin/cordis.yml" "Just reply: hi"出力に次の 2 行が出ます:
[hello-plugin] plugin loaded!
hi最初の行は起動時にあなたのプラグインが表示したもので、2 行目はタスク完了後のモデルの返答です。[hello-plugin] plugin loaded! が見えれば、最初のプラグインは動いています。
次に Web UI にロードする
headless で検証できますが、プラグインは Web UI で使うものです。まず実行中の dsh web を停止します(そうでないとポート占有)、次に patch を付けて起動:
dsh web --patch "./hello-plugin/cordis.yml"http://127.0.0.1:3080 を開くと、dsh を起動したターミナルにも [hello-plugin] plugin loaded! が表示されます。今のところ hello-plugin に UI 効果はありません、その唯一の「アウトプット」はこのログ行ですが、これで CH 08 で話したツリーのメンバーと一緒に web プラグインツリーに入ったことが証明されます。

ターミナル出力をみてください:最初の行は dsh 起動時に出力されたプラグインロードログ、次の 2 行は Web UI の準備情報。
アンロード時の自動クリーンアップ
ctx に登録するものすべて(イベントリスナー、ツール、タイマー)は、プラグインがアンロードされるときにフレームワークによって自動でクリーンアップされます;手動で removeListener や clearInterval を呼ぶ必要はありません。手動解放が必要なリソース(ネットワーク接続など)がある場合、ctx.effect() を使ってフレームワークにクリーンアップ方法を伝えます:
export function apply(ctx) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
// 返された関数はプラグインがアンロードされたときに走る
return () => clearInterval(timer)
})
}effect が返したクリーンアップ関数は、プラグインがアンロードされる瞬間に呼ばれます —— これは dsh がリソースライフサイクルを管理する標準的な方法です。
依存の宣言:inject
プラグインが他の能力(例:tools、llm)を必要とする場合、inject を宣言し、フレームワークがあなたのプラグインをロードする前に依存の準備を確実にします:
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx) {
// ここでは ctx.tools の利用が保証されている
ctx.tools.register(/* ... */)
}inject は Cordis における「サービス依存」のエントリポイントです。今はとりあえず慣れておき、ツールプラグインを書く CH 20 で正式に使います。
プラグインの 3 形態
apply 関数が最も一般的な形ですが、プラグインには 3 つの書き方があります(CH 19 でそれぞれ詳しく、ここでは概観):
| 形態 | 見た目 | 使いどき |
|---|---|---|
| 関数 | export function apply(ctx) {} | デフォルトの選択、本チュートリアルはこちら |
| オブジェクト | export default { name, apply(ctx) {} } | 静的メタデータも一緒に持たせたいとき |
| クラス | export default class extends Service {} | 他のプラグインにサービスを提供したいとき(CH 19 で展開) |
今は 1 行だけ覚えてください:関数形態が 90% のニーズを解決、「あなたのプラグインが他のプラグインから依存される必要がある」ときだけサービス形態に。
dsh にやらせる:1 つのプロンプトで完結
ここまでのステップは手動でなぞったもので、すべて学びました。でも dsh 自身は Agent —— プラグインを書く仕事もこなせるし、「自分で書いて自分で検証」もできます。
Web UI の入力欄にそのままこのプロンプトを送ってください:
現在の作業ディレクトリで、dsh がロードできる最小限の hello-plugin プラグインを書いてください。まず公式の dsh プラグイン開発ドキュメントを読み、プラグインの書き方とロード方法を把握し、それから公式仕様に従って実装、実際にロードされたか検証し、最後に結果とファイルの場所を教えてください。技術的な詳細を何も伝える必要はありません —— dsh は自分で公式プラグイン開発ドキュメントを読み、どう書くか、どうロードするか、どう検証するかを自分で決めます。作業を眺め、それからファイルを開いて何を書いたか確認すれば OK。これはまさしく「すべてはプラグイン」の延長:プラグインを書く仕事も、プラグインから組み立てられた Agent に任せられる。

私が実際に走らせたところ、右上に参照した資料と生成されたファイル(package.json、index.js、cordis.patch.yml)が表示されています。書き込み完了後、右側のファイルパネルには作業ディレクトリの新しい hello-plugin ディレクトリが直接表示 —— まさに CH 17 でインストールしたサイドバープラグインが輝く場面、ファイルマネージャに切り替えなくても何を書いたか確認できます。
よくある落とし穴
| 問題 | 何が起きているか | 対処 |
|---|---|---|
Only URLs with a scheme in: file... と報告される | Windows でパスがドライブレター形式で書かれている | name を file:///E:/... のような完全 URL に変更 |
| ファイル/モジュールが見つからないと報告 | パスのスペースがエンコードされていない | スペースを %20 として書く |
| patch と再起動後に反応なし? | プラグインパスまたは yml の typo | id と name のスペルを確認、dsh --profile web --patch ./hello-plugin/cordis.yml --dump-config でプラグインが設定ツリーにあるか確認 |
.ts を直接ロードしてエラー? | グローバル dsh には tsx ランタイムが入っていない | まずビルド不要の .js を使う、TS が必要になったらまず .js にビルドしてからロード |
| ポート占有で起動できない? | 前の dsh web がまだ動いている | 古いプロセスを先に停止、それから起動 |
この章で学んだこと
下の項目を自力で達成できれば合格です:
- [ ] プラグインの最小形態を言える:
apply(ctx)関数を export するモジュール - [ ] hello-plugin を作成し、
cordis.ymlでfile://URL を使って宣言できる - [ ]
dsh --profile headless --patch ...を使って素早くプラグインがロードされたか検証できる - [ ]
--patchを付けて web を起動し、プラグインを Web UI のプラグインツリーに持ち込める - [ ]
ctx.effect()がリソースクリーンアップを行い、injectがサービス依存を宣言することを知っており、プラグインの 3 形態(関数 / オブジェクト / クラス)を知っている
