type: reference status: accepted last-reviewed: 2026-09-07 relates-to:
- FND-001
- ADR-003
- crates/zeroclaw-plugins
プラグインプロトコル
この文書は、ZeroClawのプラグインホストとWASMプラグインコンポーネント間のプロトコルを定義します。
プラグインとは
プラグインは、コアバイナリには同梱されていない機能を追加するために ZeroClaw が実行時に読み込む、自己完結型の WebAssembly コンポーネントです。~/.zeroclaw/plugins/ 配下の専用ディレクトリに配置され、名前と提供内容を宣言するマニフェストと一緒に置かれます。ZeroClaw は起動時にそれを検出し、検証し、エクスポートされた関数を実行中のエージェントに接続して、組み込み機能と同じように動作させます。ツールプラグインはモデルに対して単なる呼び出し可能なツールとして表示され(WasmTool はネイティブツールと同じ Tool トレイトを実装します)、チャネルプラグインはメッセージングチャネルとして、メモリプラグインはストレージバックエンドとして動作します。
プラグインは、PluginCapability(crates/zeroclaw-plugins/src/lib.rs)で定義された機能のうち1つ以上を提供できます。呼び出し可能なツール、メッセージングチャネル、メモリバックエンド、オブザーバビリティバックエンド、またはマークダウンスキルのバンドルです。スキルのケースは特別で、WASMを一切同梱せず、マークダウンの skills/ ディレクトリのみを提供するため、コンパイル済みコンポーネントを省略する唯一の機能です。
なぜ作るのか
- フォークせずに拡張。 ZeroClaw のソースツリーを変更したりリリースを待ったりすることなく、ツールやチャネルを追加できます。プラグインはあなたのもので、インストールディレクトリから読み込まれます。
- ネイティブな動作。 読み込まれたプラグインは二級のアドオンではありません。ブリッジは組み込みが使用するのと同じランタイムトレイトを実装しているため、プラグインツールはモデルに提供され、帰属され、ファーストパーティのものとまったく同じように呼び出されます。
- 言語の選択。 契約は WIT と WASI Component Model であり、Rust API ではありません。
wasm32-wasip2コンポーネントにコンパイルできる任意の言語で world を実装できます。以下の実践ガイドは、現時点で最もサポートが充実したパスであるため Rust ですが、境界そのものは言語非依存です。 - デフォルトでサンドボックス化されています。 ホストは各プラグインを、ファイルシステムのプリオープンもアンビエントなネットワークもない WASI コンテキストに読み込みます。プラグインがホストに密かに到達することはできず、そのワールドに接続されたホスト関数だけを、それ以上は一切なしで利用できます。送信 HTTP、生ソケット、WebSocket は、開くことができるネットワーク面であり、それぞれ 2 段階で制御されます。マニフェストの許可(
http_client、socket_client、websocket_clientのいずれか)は、アダプターがその_面_をリンクするかどうかを選択し、オペレーターのインスタンスごとのplugins.entries.<key>.egress_hosts許可は、3 つすべてで共有される_到達範囲_を選択します。ツールおよびチャネルのアダプターは各面をリンクしますが、メモリは現在それらを提供していません。egress 許可がない場合、到達範囲は全拒否となるため、リンクされた面だけでは何も送信されません。 - 検証可能なプロビナンス。 マニフェストはEd25519で署名でき、オペレーターはプラグインがロードされる前に信頼できるパブリッシャーからの署名を要求できます。
プラグインでできないこと(現時点)
これらは現在のホストの実際の制限であり、スタイル上の好みではありません。存在しない機能を前提に設計する前に、これらを把握しておいてください。
logging、型付き設定、インスタンス単位のシークレットと永続状態、http_client、およびホスト経由の受信処理が接続済みです。 マニフェストで宣言できる権限のうち、config_readはプラグイン自身のスキーマ検証済みの公開設定を公開します。ツールまたはチャネルのスキーマは、公開設定から除外され、認可されたサービス呼び出し内で解決されるシークレットを指定できます。state_readとstate_writeは、正確に承認されたインスタンスが所有する、暗号化され比較交換(CAS)による状態へのアクセスを制御します。http_clientの付与は送信wasi:httpに必要ですが、それだけでは十分ではありません。capability アダプタがそのホストサーフェスをリンクしている必要があります(ツールとチャネルはリンクします。メモリは、ネットワーク境界にコンポーネントレベルのテストカバレッジが整うまで、意図的に HTTP を含みません)。さらに、オペレーターがegress_hostsで宛先を許可する必要があります。この付与がない場合、パケットが送出される前、または名前解決が行われる前に、すべての宛先が拒否されます。socket_clientとwebsocket_clientは、socketsとwebsocketのインポートを同じ条件でリンクします。つまり、同じ付与、アドレスクラスのルール、インスタンスごとの接続バジェットが適用されます。付与はホストを指定するもので、ポートは指定しません。socket_clientを使うと、許可されたホストには任意のポートで生の TCP 接続が可能になるため、プラグインがどのプロトコルで通信することも許容するホストだけを付与してください。ファイルシステムおよびメモリアクセスの権限は、マニフェストスキーマでは引き続き受け付けられますが、無効です。対応するホスト関数はまだリンカーに登録されていないためです。以下の「Permissions」と「Host imports」を参照してください。- ホストのネットワークやファイルシステムへの暗黙のアクセスなし。 WASIコンテキストにはプリオープンもアンビエントなネットワークもないため、プラグインはアンビエントなWASI経由で生のソケットを開いたりホストのファイルを読んだりすることはできません。
http_clientの権限を持つツールまたはチャネルプラグインには送信用のwasi:httpが与えられ、socket_clientまたはwebsocket_clientを持つものには、ホストが仲介するTCP/TLS/STARTTLSまたはWebSocket接続が与えられます。これらはすべて、オペレーターの送信許可(egress grant)に列挙された宛先にのみ到達できます。メモリプラグインは現在、これらのいずれも与えられません。どのプラグインも待ち受け(listen)はできません。受信トラフィックを受け取る必要があるチャネルプラグインは、自分でリスナーを開くことはありません。代わりにホストがリスナーを実行し、inboundインポートを通じてメッセージを供給します。プラグインはそれをpoll-messageエクスポートからドレインします。 - 32ビット境界。 ターゲットは
wasm32-wasip2です。ゲストメモリは32ビットアドレス空間であり、コンポーネントABIはホストのワードサイズに関係なくオフセットを32ビットとしてlowerします。大きな値(たとえばチャネル添付の生バイト)は値渡しで境界を越えます。これがアップストリームのツールチェーンの制約であり、このリポジトリで切り替え可能なフラグではない理由については、32ビットアドレス空間のセクションを参照してください。 - ツールプラグインごとに1つのツール。
tool-pluginワールドは、1つの名前とスキーマを持つ単一のtoolインターフェイスをエクスポートします。複数のツールを公開する必要があるプラグインは、複数のコンポーネント、または別のワールドを提供します。 - 実験的で未凍結のコントラクト。
wit/v0にはまだ.frozenマーカーがないため、最初の安定リリース前にインターフェイスが変更される可能性があります。バージョンをピン留めし、WIT のバンプ時には再コンパイルが必要になることを想定してください。
アーキテクチャ
ZeroClaw プラグインは、wit/v0/ 配下の WIT インターフェースによって定義され、wasmtime を直接使用して(crates/zeroclaw-plugins)ホストされる WebAssembly コンポーネントです。プラグインは WASI Preview 2 コンポーネント(wasm32-wasip2)にコンパイルされ、そのコンポーネントはプラグインワールド(tool-plugin、channel-plugin、memory-plugin)のいずれかをエクスポートし、wit/v0/ でそのワールドによって宣言されたホストインターフェースをインポートします。
ホストは crates/zeroclaw-plugins/src/component.rs にあります。非同期対応の wasmtime::Engine を 1 つ保持し、wit/v0 から wasmtime::component::bindgen! を使用してワールドバインディングを生成し、サンドボックス化された WASI p2 サーフェスを各ワールドのリンカーに接続します。ストアごとのホスト状態(PluginState)には、preopen もネットワークもなしで構築された WasiCtx に加えて、WASI が必要とする ResourceTable、ホストが発行したスコープ、および型付きの稼働中サービスハンドルが保持されます。すべてのワールドは logging をインポートします。tool は secrets を、channel は config、secrets、inbound をインポートします。付与された http_client 権限により、さらに wasi:http がアタッチされ、リンクされます。このサーフェスに対する正規の契約は、ワールド宣言と認可されたスコープです(Host imports を参照)。
3つのworldブリッジは、各WIT worldをランタイムのネイティブなトレイトにマッピングします:
| World | ブリッジモジュール | ランタイムサーフェス |
|---|---|---|
tool-plugin | runtime.rs, wasm_tool.rs | zeroclaw_api::tool::Tool |
channel-plugin | wasm_channel.rs | チャネルトレイト |
memory-plugin | wasm_memory.rs | メモリバックエンドトレイト |
ツールプラグインは呼び出しごとに新しいストアを使用します(ステートレス)。チャネルプラグインとメモリプラグインは、プラグインのライフタイム全体にわたって、非同期ミューテックスで保護されたウォームストアを保持します。
ツールプラグインは端から端まで検出・登録されます。ランタイムは channel_plugin_details() のツール版に相当する処理を走査し、各プラグインについて WasmTool を構築します。
チャネルのホストアダプター(WasmChannel。ホスト管理の送信ポリシーの背後にある、権限付与で制御される wasi:http インターフェース、利用箇所で設定を提供する config サービス、ホストから供給される inbound キューを含む)は完成しており、実コンポーネントを用いた送信テストでカバーされています。また PluginHost::channel_plugin_details() は、登録対象となる wasm ベースのチャネルプラグインを公開しています。
ランタイムは、明示的に宣言された [channels.plugin.<alias>] バインディングを解決し、その WasmChannel を構築して、設定されたエイリアスから登録するようになりました。エイリアスを考慮した構築とランタイム設定の解決は #10146 で導入されました。チャネルは、後述する汎用的なゲートウェイ所有の POST ウェブフックイングレスにオプトインすることもできます。ベンダートンネル、ポーリングクライアント、その他のトランスポートは今後の対応項目として残っています。
メモリブリッジ(WasmMemory)も、一段階手前で同様の状況にあります。アダプターは memory-plugin ワールドに対して Memory トレイトを完全に実装していますが、ホストはまだ channel_plugin_details() に対応するメモリ版を公開しておらず、ランタイムも設定可能なバックエンドとして WasmMemory を構築していません。
プラグイン構造
プラグインは以下を含むディレクトリです:
my-plugin/
manifest.toml # Plugin metadata and permissions
plugin.wasm # Compiled WASM module (optional for skill-only plugins)
プラグインは~/.zeroclaw/plugins/から検出されます(設定ファイルのplugins.plugins_dirで設定可能)。
レジストリの検索とインストール
ローカルプラグインのインストールパスは、インストール済みプラグインの信頼できる唯一の情報源であり続けます。レジストリは、コマンド実行時にプラグインアーカイブを検出してダウンロードするために使用される JSON インデックスに過ぎません:
zeroclaw plugin search calendar
zeroclaw plugin install team-calendar
zeroclaw plugin install team-calendar@0.2.0
zeroclaw plugin search calendar --registry https://example.invalid/registry.json
zeroclaw plugin install team-calendar --registry https://example.invalid/registry.json
zeroclaw plugin search はレジストリのメタデータを取得し、クエリをプラグイン名と説明に対して照合します。プラグインコードのインストール、有効化、実行は行いません。
取得したインデックスはキャッシュされています。zeroclaw plugin list はそのキャッシュを読み取り、ホストが承認したインストール済みレコードと並べてパッケージの行を1件表示します。その際、レジストリのメタデータを取得したり、コードがインストール済みであることの証拠として扱ったりはしません。
zeroclaw plugin install <name> は、レジストリから名前を解決し、選択された zip アーカイブをダウンロードし、省略可能な SHA-256 ダイジェストを検証し、アーカイブを安全に展開します。その後、展開されたプラグインディレクトリを、ローカルインストールと同じ admission(受け入れ審査)処理に渡します。Admission(PluginHost::admit_source)は、マニフェストを解析して署名を検証し、その構造と設定を検証し、既にインストールされている名前を拒否し、コンポーネントを一度だけ読み込みます。パスはパッケージ内に留まる必要があり、シンボリックリンクを含んではなりません。読み込みは 64 MiB に制限され、ファイルのメタデータからサイズが上限を超えると判定された場合は拒否されます。省略可能な wasm_sha256 ダイジェストも検証されます。その後 CLI は、受け入れ済みのバイト列に対してインストール時のロード検査を実行します。これはデーモンが起動時に行うのと同じインスタンス化であり、そのため、ドリフトした WIT に対してビルドされたコンポーネントは、後で黙ってスキップされるのではなく、インストール時に完全な診断情報とともに失敗します。plugin install --no-verify はロード検査を完全にスキップし(何もコンパイルされない)、注記を表示します。ただし、受け入れ審査はバイパスしません。WASM バックエンドなしでビルドされたホストは、インスタンス化の対象がないため、検査を行わずにインストールします。インストール(PluginHost::install_admitted)は、受け入れ済みのマニフェストのバイト列を永続化し、受け入れ済みのコンポーネントのバイト列を書き込みます。これにより、検証された内容はデーモンがロードする内容とバイト単位で完全に一致します。ローカルパスからのインストールも同じ手順に従います:
バージョンが固定されていない場合、ZeroClaw はレジストリインデックス内で最後に一致するエントリを選択するため、レジストリの公開者は重複する名前の順序を意図的に指定する必要があります。
zeroclaw plugin install ./my-plugin
zeroclaw plugin install ./my-plugin/manifest.toml
zeroclaw plugin update は、同じ審査プロセス(PluginHost::admit_update)を経て、インストール済みパッケージを置き換えます。ただし ID の判定は逆方向で、指定されたパッケージがインストール済みであることと、ソースがその名前を持つことが必要です。CLI は、新しいマニフェストが追加する権限を --allow が承認しない限り拒否します。また、同じ読み込みチェックを実行し、それを省略する手段はありません。PluginHost::update_admitted は、置き換え後のパッケージをステージングディレクトリで構築してから、インストール済みパッケージの場所にリネームします。運用者向けの仕様は Updating a plugin にあります。
zeroclaw plugin update team-calendar
zeroclaw plugin update team-calendar@0.2.0 --registry https://example.invalid/registry.json
zeroclaw plugin update --all
zeroclaw plugin update my-plugin --from ./my-plugin
デフォルトのレジストリ URL は次のとおりです。
https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw-plugins/main/registry.json
プライベートまたはステージング用のレジストリの場合は、コマンドごとに --registry <url> を使用するか、ZEROCLAW_PLUGIN_REGISTRY_URL を設定してください。
レジストリのエントリはこの形式を使用します:
{
プラグイン: [
{
"name": team-calendar,
version: 0.8.6,
"description": チームカレンダーで会議をスケジュールする,
author: サンプル チーム,
機能: [tool],
「url」: https://example.invalid/team-calendar-0.2.0.zip,
sha256: sha256:<zipの16進ダイジェスト>
}
]
}
アーカイブには、ルートレベルの manifest.toml、または manifest.toml を含む 1 つのネストされたプラグインディレクトリのいずれかが含まれている必要があります。トラバーサルパス、絶対パス、Windows ドライブプレフィックス付きパス、または複数のマニフェストを含むアーカイブは、インストール前に拒否されます。ダウンロードはストリーミング中に上限が設けられているため、Content-Length のないサーバーが ZeroClaw に過大なアーカイブをバッファリングさせることはできません。展開にも上限が設けられているため、圧縮されたアーカイブが一時インストール領域で無制限に展開されることはありません。
レジストリへのリクエストには時間制限があります。レジストリまたはアーカイブホストへの接続は最長10秒、レスポンスはヘッダー受信前およびボディの読み取り間で、データが届かない状態が最長30秒までとします。インデックスの取得は30秒以内に完了する必要があり、アーカイブのダウンロードは30分以内に完了する必要があります。ホストが応答しなくなった場合、zeroclaw plugin search または zeroclaw plugin install は待機し続けるのではなく失敗します。一方、データを送り続けている遅いアーカイブのダウンロードは、30分の上限によってのみ制限されます。
検索は認証なしの探索です。インストールがセキュリティ境界となります。レジストリからのインストールは、設定されたプラグイン署名ポリシーと信頼された発行者キーを使用します。これは PluginHost::install を通じたローカルプラグインのインストールと同じです。
スキルのみのプラグインレイアウト(Markdownバンドル)
skill のみを機能として提供するプラグインは、skills/ ディレクトリ配下に agentskills.io 形式でスキルを同梱し、wasm_path を省略します:
my-toolkit/
manifest.toml # declares the skill capability, no wasm_path
README.md # optional bundle-level overview
skills/
design-review/
SKILL.md
scripts/
references/
code-review/
SKILL.md
data-analysis/
SKILL.md
references/
各 SKILL.md には、name フィールドと description フィールドを含む YAML フロントマターを記述する必要があります。いずれかが欠落しているスキルを含むバンドルは、最初の呼び出し時ではなく検出時にランタイムによって拒否されます。スキルは、ユーザーが作成したスキルとの衝突やバンドル間の衝突を回避するため、plugin:<plugin-name>/<skill-name> 形式(例: plugin:my-toolkit/design-review)のプラグイン名前空間付き ID で登録されます。
マニフェスト形式
マニフェストとは、プラグインディレクトリ内にある manifest.toml という名前のファイルです。そのフィールドは crates/zeroclaw-plugins/src/lib.rs 内の PluginManifest の serde サーフェスであり、これが信頼できる情報源となります。
| フィールド | 必須 | 意味 |
|---|---|---|
name | yes | 一意の正規パッケージスラッグであり、派生する各インスタンス設定キーのパッケージ部分です。これはそれ自体がオペレーター設定キーではありません。1~128文字の小文字 ASCII 文字を使用し、先頭と末尾は [a-z0-9] とし、その間には [a-z0-9._-] のみを使用します。検出時に無効な名前や重複した名前は拒否されます。 |
version | yes | バージョン文字列(例:0.1.0)。 |
description | いいえ | zeroclaw plugin list によって表示される人間が読める説明。 |
author | いいえ | 作者名または組織。 |
wasm_path | WASM 機能について | コンポーネントのファイル名(プラグインディレクトリからの相対パス)。唯一の capability が skill の場合を除き必須。指定されたファイルが存在しない場合、検出はプラグインをスキップします。 |
wasm_sha256 | |in strict mode for WASM capabilities WASM機能の厳格モードで| |-| | |Hexadecimal SHA-256 of the exact component bytes. When present it is always enforced; strict signature policy requires it so the signed manifest binds the admitted executable. |
Wait — that’s the source text itself, and I shouldn’t return it verbatim. Here is the Japanese translation:
コンポーネントのバイト列そのものに対する16進数表記のSHA-256ハッシュ。指定されている場合は常に強制されます。厳格な署名ポリシーでは、署名済みマニフェストが許可された実行ファイルに紐付けられるよう、これが必須です。|
|capabilities|はい、空ではありません|プラグインの種類: tool、channel、memory、observer、skill のいずれか(PluginCapability、snake_case でシリアライズ)。|
|provides|いいえ|channel プラグインで、コンパイル済みのチャネルのドロップイン代替となるもの。ミラーする snake_case の設定 ID は telegram、gmail_push です。ホストは、有効なエージェントがルーティングする、設定済みかつ有効な [channels.<id>.<alias>] ごとに論理インスタンスを1つだけ受け入れるため、正規のチャネル設定が、これらの設定の唯一の保管場所であり続けます。config_read が必要です。plugin ファミリーは予約されており拒否されます。本ビルドで定義されていない ID は拒否されます。同じ ID を主張する2つのインストール済みパッケージは、両方とも拒否されます。省略時(デフォルト)は、[channels.plugin.<alias>] から設定される新規チャネルを意味します。ミラーは受け入れられますが、まだ構築されていません。アクティベーション計画については crates/zeroclaw-runtime/src/plugin_runtime.rs を参照してください。|
|permissions|いいえ|コードが到達しうるホストサービス: http_client、config_read、state_read、state_write、file_read、file_write、memory_read、memory_write(PluginPermission)。HTTP、config、および正確なインスタンス単位の state の権限は、ツール/チャネルアダプターに対して適用されます。ファイルシステムおよびメモリの権限は無効のままです。config_read を宣言するには config_schema が必要です。|
|config_schema|config_read と完全に一致して|このプラグインのプライベート設定用の Draft 2020-12 JSON Schema。canonical manifest バイト列に含まれるため、マニフェストの署名時に対象となります。ルートは properties マップと additionalProperties = false を持つオブジェクトでなければなりません。すべてのトップレベルプロパティは、直接または、ローカルの JSON Pointer を介して、明示的にサポートされる型を1つ持たなければなりません: string、boolean、integer、number、array、または object。ツールおよびチャネルの利用側は、トップレベルの文字列プロパティに直接 x-secret = true を設定することができます。これにより、そのプロパティは公開設定から削除され、スコープ付きの secrets.get ホストインポートを通じて公開されます。secret プロパティ名は、1~128 バイトの移植性のある ASCII 名であり、英字、数字、_、-、. のみを含みます。ツールは公開設定を __config の下で受け取り、execute の実行中にシークレットを読み取ることができます。チャネルは configure および運用時の呼び出し中に、config.get を通じて現在の公開オブジェクトを読み取り、secrets.get を通じてシークレットを読み取ります。インスタンス化および静的メタデータ検出の間は、両方のインポートは利用できません。ネストされた、false の、または非ブール値の secret マーカー、および文字列以外の secret プロパティは拒否されます。config_read のないスキーマ、またはスキーマのない config_read は拒否されます。|
|signature|いいえ|正規化されたマニフェストバイト列に対する Base64url Ed25519 署名。配布用に署名する際に設定されます。|
|publisher_key|いいえ|署名者の16進数エンコードされたEd25519公開鍵。|
|エグレス|いいえ|任意の [egress] テーブル。hosts リストを保持し、このプラグインが必要とする接続先を宣言します。指定できるのは完全一致のホスト(api.example.com)か、明示的なサフィックスパターン(*.example.com)で、後者はサブドメインには一致しますが apex(ドメイン自身)には一致しません。このテーブルは正規化されたマニフェストのバイト列の一部であるため、署名時に保護対象となり、変更するには再署名が必要です。不正な文法は、検出時およびインストール時にマニフェスト全体を拒否します。省略された状態、空の状態、「何も宣言しない」状態は同一として扱われます。|
コードが実際に使用する権限のみを宣言してください。宣言されていない権限はコンポーネントが到達できないホストサーフェスであり、不要に宣言された権限は自ら求めた攻撃対象領域であり、プラグインをレビューする人にとっての監査負担です。
[egress] は宣言であり、権限の付与ではありません。マニフェストによってネットワークへの到達性が与えられることはありません。ホストが適用する許可リストは、インスタンス自身の zpi1_… 行に対するオペレーターの plugins.entries[].egress_hosts であるため、独自の [egress] テーブルを書いた未署名のコンポーネントであっても、どこにも到達できません。宣言が果たす役割はインストール時の手続きです。zeroclaw plugin install はそれを、同コマンドが作成する行に設定し、何が付与されたかを表示します。既存の行を広げることは決してありません。詳しくは Declaring and granting egress を参照してください。
オペレーター値は plugins.entries 内では文字列のまま保持され、永続化時には、ホスト所有のパッケージ、ケイパビリティ、バインディングの識別情報から導出されたバージョン付きの zpi1_… 文字列をキーとして暗号化されます(インストール時に出力され、デフォルトのツールバインディングのフルインスタンスキーにシードされます)。文字列はそのまま保存され、ブール値と数値には JSON スカラーのテキストが使用され、配列とオブジェクトには JSON テキストが使用されます。ゲストコードが実行される前に、ホストはそれらの文字列をパッケージスキーマの型に実体化し、ツールアダプターとチャンネルアダプターについてオブジェクト全体を検証します。シークレットではないツールプロパティで __config が構成され、チャンネルは config.get を介してシークレットではないオブジェクトを取得します。x-secret = true とマークされたプロパティは両方の公開サーフェスから省略され、認可されたサービスフレーム内で secrets.get("property") を介してのみ利用できます。1 回の呼び出し中のチャンネルの公開読み取りとシークレット読み取りは単一の正規リビジョンを共有し、ホストは呼び出しの終了時にその実体化されたビューを破棄します。準拠するチャンネルプラグインは、利用する各時点でその両方を 必ず 解決し、ウォームなゲスト状態に設定や資格情報の値を保持してはなりません。ゲストに平文を返すと、ホストは悪意のあるコードに対して値を保持しないことを強制できなくなります。config_read が要求されたものの実効的に許可されなかった場合、ホストは空のオブジェクトを検証します。そのため、必須プロパティを持つスキーマは、必須設定なしで起動するのではなくフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config を省略し、チャンネルの設定/シークレットインポートは access-denied を返します。認可されたフレーム外の呼び出し、解決失敗、ホスト呼び出し予算の枯渇は unavailable を返します。
機能
capabilities は crates/zeroclaw-plugins/src/lib.rs で定義された PluginCapability 値の空でないリストです(snake_case としてシリアライズされます)。各値は、プラグインがエクスポートする WIT ワールド(tool、channel、memory)を選択するか、可観測性バックエンド(observer)を指定するか、Markdown のみのスキルバンドル(skill)としてマークします。正規のセットについては enum を参照してください。これが信頼できる情報源であり、このページでは再掲しません。
マニフェストは少なくとも1つのケイパビリティを宣言する必要があります。wasm_path は、唯一のケイパビリティが skill であるプラグインを除くすべてのケイパビリティに必須です。そのプラグインは WASM ペイロードを持たず、有効な skills/ バンドルを省略すると検出時に拒否されます(host.rs の validate_manifest_shape)。
パーミッション
permissions は PluginPermission 値のリストであり、こちらも crates/zeroclaw-plugins/src/lib.rs で定義されています。正規のセットについては enum を参照してください。
宣言された権限と実際に強制される権限の差に注意してください。現在のコンポーネントホストでは、config_read、http_client、state_read、state_write が実際に動作に影響します。config_read を要求するには config_schema が必要で、権限なしにそのスキーマだけを宣言することも拒否されます。ツールまたはチャネルのコンポーネントを使用する前に、ホストは有効な権限(effective grant)を解決し、プラグインのオペレーター値を型付き JSON にマテリアライズして、オブジェクト全体を検証します。runtime.rs は呼び出し元が指定した __config を取り除き、検証済みの非シークレット値をツール呼び出しに注入します。x-secret: true が付いた、トップレベルの直接の文字列プロパティは公開設定から省かれ、ホストスコープの secrets インポートを通じて読み取られます。ツールは execute の間にそのサービスを受け取ります。チャネルは configure および運用呼び出しの間に、config.get で公開設定を、secrets.get でシークレットを受け取ります。インスタンス化と静的メタデータの検出は引き続き利用できません。
http_client は必要な権限であって、完全な認可判断ではありません。ケイパビリティアダプターは HTTP コンテキストを構築し wasi:http をリンクする必要があり、さらにホストが管理する egress ポリシーがその宛先を許可している必要があります。ツールとチャネルのアダプターは、権限の検証後にこのサーフェスをリンクします。メモリアダプターは意図的にリンクしないため、メモリスコープに http_client を付与してもネットワークサーフェスは増えません。
ツールとチャネルでは、オペレーターが plugins.entries.<key>.egress_hosts の下で宛先を列挙するまで、このサーフェスは何にも到達しません。ポリシーはそれ以外のすべてのホストを拒否します。付与されたホストが loopback、private、link-local のアドレスに解決される場合、オペレーターが plugins.entries.<key>.egress_allow_private にもそのホストを列挙しない限り拒否されます。この例外は、すでに付与された宛先に対してアドレスクラスを緩和するだけで、宛先を新たに付与することはありません(オペレーター側については プラグイン を参照してください)。
state 権限は、ツールおよびチャネルのサービスフレームにおいて state.get と state.put/delete のインポートを、それぞれ独立してゲートします。パッケージ、ケイパビリティ、バインディングは許可されたホストスコープからのみ供給され、ゲストは名前空間を指定しません。
残りのバリアント(file_read、file_write、memory_read、memory_write)はマニフェストスキーマで受け入れられますが、まだホストインポートに接続されていません。これらを宣言しても、それだけでは何も付与されません。これらは、将来これらをゲートするホスト関数のために名前を予約しているものです(以下のホストインポートを参照してください)。
WIT インターフェース
プラグインコントラクトは wit/v0/ にある WIT ファイルの集合であり、パッケージは zeroclaw:plugin@0.1.0 です。すべての項目はパッケージが安定化するまで @unstable(feature = plugins-wit-v0) でゲートされています。互換性ルールについては wit/VERSIONING.md を参照してください。以下のインターフェースは概要把握のために要約したものです。正確なシグネチャについては .wit ファイルが正式なものとなります。
ワールド
wit/v0/ は3つの world を定義しており、それらは component.rs 内の bindgen! によってバインドされる。いずれも logging(ホスト)を import し、plugin-info と主要インターフェースを export する:tool-plugin は tool を、channel-plugin は channel を、memory-plugin は memory を export する。tool はさらに secrets と state を import し、channel は config、secrets、state、inbound を import する。tool と channel は、それぞれの機能フラグ(plugins-wit-v0-sockets、plugins-wit-v0-websocket)の背後で sockets と websocket も import する。ホストは対応する grant を保持するインスタンスに対してのみそれらをリンクするため、grant なしでいずれかを import するコンポーネントはインスタンス化に失敗する。各 world の必須(デフォルトなし)の export は、対応する .wit ファイル内の world のドキュメントコメントに列挙されている。
tool インターフェース
wit/v0/tool.wit は単一ツールのインターフェースを定義します。ホストは読み込み時に name、description、parameters-schema を一度だけ呼び出し、その後呼び出しごとに execute をディスパッチします:
record tool-result {
success: bool,
output: string,
error: option<string>,
}
name: func() -> string;
description: func() -> string;
parameters-schema: func() -> json-string;
execute: func(args: json-string) -> result<tool-result, string>;
parameters-schema はツール呼び出しのために LLM に提示される JSON Schema 文字列を返します。execute はそのスキーマに一致する JSON エンコードされた引数を受け取り、tool-result またはエラー文字列を返します。json-string は wit/v0/types.wit の string 型エイリアスです。呼び出し側は有効な JSON を生成し、受信側はそれを解析します。
channelおよびmemoryインターフェース
wit/v0/channel.wit と wit/v0/memory.wit は、ケイパビリティでゲートされたサーフェスを定義します。ホストはロード時に get-channel-capabilities / get-memory-capabilities を一度呼び出し、設定されていない各フラグについては、プラグインを呼び出す代わりに Rust トレイトのデフォルトを使用します。プラグインは依然としてすべての関数をエクスポートしなければなりません(文書化されたデフォルト値を返すスタブで十分です)。ホストは単に、フラグが存在しない関数を決して呼び出さないだけです。設定されていない各フラグが解決されるデフォルトは、*-capabilities フラグの隣の WIT にインラインで文書化されており、これがフラグセットとそのデフォルトの両方に対する信頼できる情報源です。
機能フラグ
オプションのメソッドは flags channel-capabilities および flags memory-capabilities を通じてアドバタイズされます。フラグはビットマスクであるため、新しいオプションのメソッドは新しい @since 関数とペアにして、破壊的変更なしに vN/ パッケージに追加できます。フラグ、関数、フィールド、またはバリアントケースの削除や名前変更は破壊的変更であり、新しい vN+1/ ディレクトリが必要です。
ホストのインポート
ホスト関数はプラグインがインポートし、ランタイムが提供します。すべてのワールドのリンカーは、add_wasi と並べて component.rs にリンクされる logging(component_logging.rs のホスト実装経由)を接続します。ツールとチャネルは、インスタンス単位のスコープを持つ secrets サービスをリンクします。チャネルはさらに、型付き公開オブジェクト用の config と、poll-message からドレインするホスト供給のメッセージキュー用の inbound をインポートします。ツールとチャネルのアダプターは、許可されたスコープが http_client を付与した後にのみ、送信用の wasi:http をリンクします(component.rs の PluginStoreSpec::with_granted_http と add_wasi_http)。両者はホスト所有の到達性ポリシー(PluginStoreSpec::with_egress_policy)も組み合わせるため、ゲストが Wasmtime の無制限なデフォルトフックを取得することはありません。Memory はコンテキストとリンカーのサーフェスの両方を与えません。ファイルシステムとメモリアクセスの権限は無効なままです。それらをゲートするホスト関数はまだリンカーに接続されていないためです。プラグインのアンビエント権限は、WASI コンテキスト(プリオープンなし、アンビエントなネットワークなし)に、その付与とアダプターのオプトインが組み合わさって有効にする正確なホストインポートのみを加えたものです。
ZeroClaw 所有のインポートは、ホストによってディスパッチされるサービスフレームごとに、固定の安全予算を共有します。基準となる上限は、crates/zeroclaw-plugins/src/component.rs にある MAX_HOST_CALLS_PER_FRAME です。上限に達すると、ロギングは何もしなくなり、受信ポーリングは空の結果を返し、公開設定またはシークレットの読み取りは unavailable を返します。新しいフレームで予算がリセットされます。この上限は固定されたホストポリシーであり、オペレーター設定を重複させたものではありません。
inbound
wit/v0/inbound.wit は channel-plugin ワールドによってインポートされます。チャネルプラグインは独自のリスナーを持たずに実行されるため、ホストがリスナー(Webhook サーバー、ベンダートンネル、ポーリングクライアント)を実行し、受信した各メッセージをキューに入れます。プラグインは inbound-poll を呼び出すことで poll-message エクスポートからキューをドレインし、バッチでドレインするために inbound-pending を使用できます。
inbound-poll: func() -> option<host-inbound-message>;
inbound-pending: func() -> u32;
ホスト側はチャネルごとに InboundQueue を保持します。WasmChannel::inbound はクローンをリスナータスクに渡すため、エンキューされたトラフィックはプラグインのドレインから参照できます。
Channelのwebhook受信
チャネルコンポーネントは、返す channel-capabilities に webhook-ingress を追加し、webhook-path と parse-webhook を実装できます。その後ホストは /plugin/<path> に GET/POST ルートをマウントします。パスは設定ではなく実行時の申告(クレーム)であり、ASCII の英字、数字、ハイフン、アンダースコアを 1〜64 文字含む必要があります。申告が空、不正、または重複している場合、チャネルインスタンスは拒否されます。デーモンがルートマップを置き換える前にすべての申告が解決されるため、マニフェストの走査順序によって勝者が選ばれることはなく、ルートの再構築に失敗しても部分的な世代が公開されることはありません。公開には世代リース(generation lease)が伴います。終了処理中の古いチャネルスーパーバイザーが、新しいルートセットを消去することはできません。
Webhook の受信には、コンポーネントの有効な config_read 権限が必要です。リクエストごとに、ゲートウェイは HTTP メソッド、生のクエリ、小文字の UTF-8 ヘッダー、正確なボディのバイト列を含む型付きの webhook-request を、使い捨ての設定済みストアの中で parse-webhook に渡します。ゲストはその呼び出しの中で、現在のスコープ付き設定とシークレットを解決し、プラットフォームの真正性を検証したうえで、webhook-response.messages、webhook-response.reply、または型付きの unauthorized / bad-request による拒否を返します。HTTP リクエストがキャンセルされたりタイムアウトしたりした場合、その使い捨てストアは破棄されます。ポーリングやアウトバウンドのチャネル呼び出しで使用されるウォームストアが取り残されることはありません。
検証済みのチャレンジはreply(body)を返します。ゲートウェイは、送信者の認可、冪等性、エージェントへの配信を行わずに、HTTP 200とそのテキストで応答します。ホストは、コンポーネント境界とHTTP境界の両方で、最大4096 UTF-8バイトまでを受け付けます。超過した場合は、固定の502 invalid webhook responseを返します。メソッドとクエリは、リクエストのフィールドから取得され、予約済みヘッダーからは取得されません。メッセージのチャンネル名には、応答制御の意味はありません。HEADおよびその他のサポートされていないメソッドは、コンポーネントに到達する前に、Allow: GET, POSTを伴う405を返します。
The unauthenticated edge remains host-governed: の日本語訳として、認証されていないエッジは引き続きホストによって管理されます:
- 信頼された転送情報に対応する正規のWebhookレートリミッターは、ルート検索の前に実行されます。リクエストボディにはゲートウェイの64 KiB上限が適用され、各ルートには64リクエストのキューがあり、解析と配信の合計に10秒の期限があります。
- パブリックなレスポンスは固定されています。成功は
200、ゲスト認証およびペイロード拒否は不透明な401と400、ホスト、コンポーネント、または下流の障害は不透明な503、タイムアウトは504、キューまたはレート上限の飽和は429です。ゲストおよび Wasmtime の診断情報は有界かつ帰属が明確な形で、ホストログに残ります。ゲストの診断情報に認証情報や生のシークレットが含まれることは決してあってはなりません。 - ゲスト認証の後、ホストは認可された
plugin.<alias>ID を付与し、そのチャネルについて[peer_groups.*].external_peersをその場で解決します。プラグイン送信者のマッチングは完全一致です("*"は明示的な全許可エントリとして引き続き有効です)。拒否された送信者は冪等性キーを予約せず、チャネルキューにも到達しません。poll-messageが返すメッセージにも同じライブかつデフォルト拒否のゲートが適用されるため、すべてのプラグイン受信ブリッジがホストの単一のポリシーを共有します。 - Non-empty guest message IDは、ルートから導出された名前空間内で重複排除されます。処理中の重複は、所有している配信がコミットまたはロールバックするまで待機します。世代スコープの所有トークンにより、古い所有者が差し替え後の予約を削除することを防ぎます。配信が失敗またはキャンセルされた場合はロールバックされるため、プロバイダーのリトライが新しい所有者になれます。
これは、未凍結の実験的なwit/v0チャネルワールドを変更します。Webhookを提供しないものを含め、すべてのチャネルコンポーネントは、対象ホストが提供するWITに対して再ビルドし、文書化された機能ゲート付きスタブをエクスポートする必要があります。凍結バージョンの互換性ウィンドウは、まだ適用されません。
logging
wit/v0/logging.wit は3つのワールドすべてにインポートされます。プラグインは log-record を呼び出して、構造化されたイベントをホストに返します:
log-record: func(level: log-level, event: plugin-event);
呼び出しは fire-and-forget 方式です。何も返さず、ホスト(component_logging.rs)がすべてのエラーを吸収するため、ログ書き込みの失敗によってプラグインの実行がクラッシュすることはありません。配信は非同期です。import はレコードを容量上限のあるホスト側キューに渡し、専用スレッドがそこから取り出して処理する間もブロックせずに戻ります。そのため、処理の遅い、またはハングしたログコンシューマーによって、ゲストエクスポートが plugins.limits.call_timeout_ms を超えてブロックされることはありません。処理を後回しにしてもイベントの意味は変わりません。各レコードはゲストの呼び出し箇所でその時点で有効なホストスパンを捕捉し、そのスコープ内で書き込まれるため、エージェント/チャネル/ツールの帰属情報と終端ラベルは、インラインで出力した場合と一致します。この上限は実際のメモリ上限です。イベントフィールドは長さに上限のない文字列であり、ゲストの max_memory_mb 上限の対象外であるホストメモリにコピーされるためです。ゲストが制御するバイト数が 64 KiB を超えるレコードは切り詰めずに破棄され、キューに入ったレコードは合計 8 MiB の固定バイト予算を使用し、その予算はレコードが書き込まれた後にのみ解放されます。キューが満杯である場合、レコードが上限を超える場合、または予算を使い果たした場合は、いずれも最新のレコードが破棄されます。ドレインスレッドは各書き込み後とアイドル状態からの起床時に累積ドロップ数を報告するため、拒否されたレコードの後に受け入れられたレコードが一つも続かない場合でも、損失を観測できます。plugin-action と plugin-outcome は zeroclaw-log にある閉じた Action / EventOutcome 分類体系を反映します。エスケープハッチとなるバリアントは意図的に用意していません。wasi:logging を直接呼び出さないでください。直接呼び出すとプラグインイベントのフォーマットが一貫しなくなり、zeroclaw_log のすべての出力先に届かなくなります。
config
wit/v0/config.wit は channel world によってインポートされます。現在のスキーマ検証済みで、秘密情報を含まないオブジェクトを JSON として返します。
get: func() -> result<json-string, config-error>;
このオブジェクトは config_schema で宣言された型を保持します。x-secret: true とマークされたプロパティは省略されます。このサービスは configure 中および運用チャネルのエクスポート中に利用できます。許可されたインスタンスに有効な config_read 権限がない場合は access-denied を返します。コンポーネントの初期化中または静的メタデータの検出中の呼び出し、リゾルバーまたは検証の失敗、ホスト呼び出し予算の枯渇時には、内部の詳細を公開せずに unavailable を返します。
config.get は読み込み時のスナップショットではなく、使用時点でのアクセスです。準拠するチャネルプラグインは、設定を使用する各操作で 必ず これを呼び出し、その戻り値のオブジェクトをウォームなゲスト状態に保持してはなりません。これはプラグインの準拠要件です。JSON を信頼されたゲストコードに返した後、ホストは悪意のあるコンポーネントによるそのコピーを防止できません。
secrets
wit/v0/secrets.wit は tool world と channel world によってインポートされます。ゲストが提供するのはトップレベルのプロパティ名のみです:
get: func(name: string) -> result<string, secret-error>;
ホストは、承認済みの PluginInstanceScope から、パッケージ、capability、バインディング、有効な権限付与を導出します。これらはいずれもゲストからの入力ではありません。読み取りが許可されるのは、マニフェストスキーマで x-secret: true と指定された、トップレベルの文字列プロパティのみです。ツールは、ホストが execute をディスパッチしている間にこれらを読み取れます。チャネルは、configure と、send、poll、webhook 解析、health、capability でゲートされたアクションなどの運用呼び出しの間に読み取れます。コンポーネントの初期化と静的メタデータのエクスポートは、設定を解決せずに unavailable を返します。1 つのチャネルサービスフレーム内では、すべての config.get と secrets.get が、解決済みの同一の正規化された設定リビジョンを使用します。そのフレームは、すべての終了経路で破棄されます。したがって、準拠したプラグインは、同一バインディングに対する公開値とシークレットのローテーションを、次の操作で同時に観測します。access-denied、not-found、unavailable は、リゾルバーやスキーマの詳細を意図的に一切明かしません。読み取りに成功すると、信頼されたゲストに平文の値が返されます。このサービスは、公開値の注入やインスタンス間の横断的な選択を防ぎますが、プラグインコードから値を隠し続けるエグレスプロキシではありません。準拠したチャネルプラグインは、必ず使用箇所ごとにシークレットを解決し、ウォーム状態に 2 つ目のコピーを保持してはなりません。平文を返した後の非保持をホストが強制することはできません。
シークレットのプロパティ名は、ステートキーと同じ移植性のあるプラグインローカル文法を使用します。すなわち、英字、数字、_、-、. のみを含む 1~128 バイトの ASCII です。URI、パス、名前空間、制御文字、非 ASCII の構文は、マニフェストの受け入れ時に拒否されます。
state
wit/v0/state.wit は tool および channel のワールドからインポートされます。正確に許可されたパッケージ、ケイパビリティ、バインディングによって所有される、暗号化された永続バイト値を提供します:
get: func(key: string) -> result<option<state-entry>, state-error>;
put: func(key: string, value: list<u8>, expected-revision: option<u64>)
-> result<u64, state-error>;
delete: func(key: string, expected-revision: u64) -> result<_, state-error>;
state_read は get を許可し、state_write は put と delete を許可します。put での none は、キーが存在しないことを条件とする compare-and-swap です。一方、some(revision) は現在のリビジョンと完全に一致している必要があります。書き込みは新しいリビジョンを返します。リビジョンは不透明な値であり、インスタンス内で再利用されることはありません。そのため、削除して再作成されたキーは古い compare-and-swap の条件を満たせません。キーには上記の移植可能なプラグインローカルの文法を使用し、別のインスタンスを選択することはできません。状態が利用できるのはツール実行中とチャネルサービスフレーム中のみです。静的呼び出し、ホスト呼び出しの予算超過、ストレージやキーの失敗、整合性の失敗は、クローズドエラーを返します。インスタンスごとに固定されたエントリ数、値、合計サイズの上限を超えた場合は、一部だけをコミットせず quota-exceeded を返します。
The runtime stores one authenticated envelope per value in data/plugin-state.db, encrypted with the install’s existing .secret_key. The database contains only keyed blind indexes and enc2: ciphertext. Missing or replaced install keys fail closed and are never silently regenerated while durable rows exist. Back up the database and .secret_key together.
sockets
Host仲介によるアウトバウンドTCP。connect は host、port、mode、および任意の TLS プロファイルを受け取り、connection リソースを返します:
plaintext: 生のTCP。運用者がホストに許可した場所であればどこからでも到達可能です。平文のための別個の例外はありません(ADR-014)。direct-tls:connectが返る前に TLS が確立されます。start-tls: 接続は交渉フェーズで開始され、このフェーズではsend-negotiationとreceive-negotiationのみ動作します。upgrade-tlsはストリームをその場でTLSに確定させます。失敗時はストリームが閉じられ、平文での再試行は行われません。
ホストは宛先を一度だけ名前解決し、検証済みのアドレスにのみ接続します。インスタンスの接続リースは、リソースが破棄されるまで保持します。TLS は、リクエストで TLS プロファイルが指定されていない限り、プラグインの HTTPS と同じルート証明書に対して検証します。receive はブロックせず、バッファに何もない場合は idle を返します。失敗は型付きの socket-error のケースとして返されます。
websocket
ホスト経由の送信用 WebSocket。connect は平文(ws)または TLS(wss)の WebSocket URL、追加ヘッダー、提案するサブプロトコル、任意の TLS プロファイルを受け取り、send、receive、close、negotiated-subprotocol を持つ connection リソースを返す。DNS、宛先の決定、TLS、アップグレードハンドシェイク、有界キューはホストが管理する。ハンドシェイクに属するヘッダー(Host、Connection、Upgrade、Sec-WebSocket-*)は拒否される。receive はブロックしない。終端イベントの closed または failed が取り出された後は、none ではなく closed エラーを返す。リソースを破棄すると、ソケットが閉じられ、接続リースが解放される。
TLSプロファイル
sockets または websocket のリクエストでは、オペレーターがインスタンス上で設定した TLS プロファイル([[plugins.entries.tls_profiles]])を指定できます。プロファイルは、信頼する認証局と、相互TLS用のクライアント証明書(任意)を選択します。これらは、インスタンスの x-secret プロパティから読み込まれます。プロファイルが宛先を許可することはありません。リクエストはまず egress_hosts を通過する必要があり、さらにプロファイルがそのホストを対象に含んでいる必要があります。平文接続で TLS プロファイルを指定することは、無効なリクエストです。プロファイルが参照するプロパティはホスト専用であり、secrets.get はそれらに対して access-denied を返します。そのため、プラグインは認証に使用する秘密鍵を保持することはありません。
ホストは、各接続のTLS構成を、その接続を開くサービスフレームの構成リビジョンから、接続が開かれる時点で構築し、その構成は接続が所有します。フレームの一時的な素材は、一致する正規の構成入力から導出された認可とのみ組み合わされます。ポリシー、プロファイル参照、または素材が、これらのビュー間で変化した場合(DNSの解決中を含む)、リビジョンを組み合わせるのではなく、接続のオープンはフェイルクローズ(失敗として閉じる)されます。比較対象には、インスタンスの構成エントリ全体(そのgrant、TLSプロファイル、およびすべての構成値)と、デプロイメントのNAT64プレフィックスおよび接続数上限が含まれます。そのため、フレームの実行中にこれらが編集されると、カスタムCAまたはクライアントIDを使用する後続の接続は、そのフレームにおいて失敗します。後続のフレームでは、ローテーション後の素材が解決されます。既に開いている接続は変更されません。エグレスのgrantは各リクエストごとに引き続き検査されるため、削除されたプロファイルまたはgrantは次の接続を拒否します。システムルートは例外です。システムルートを保持するプロファイル、およびプロファイルを持たない接続は、HTTPSが信頼するルートプラグインを使用します。このプラグインは、プロセスごとに一度だけ読み込まれます。既に開いている接続は、切断またはクローズされるまで、ハンドシェイクで提示したIDを維持します。接続を破棄する場合、または接続をまだ開いている途中の呼び出しをキャンセルする場合、構成は接続のリースとともに解放されます。
プラグインごとの設定(__config と config.get)
権限: config_read
プラグインはプロセス環境変数を読み取りません。マニフェストでは config_read と Draft 2020-12 の config_schema を組み合わせる必要があり、どちらか一方しかないマニフェストは無効です。スキーマのルートは、properties マップを持ち、additionalProperties = false であるオブジェクトでなければなりません。各トップレベルプロパティでは、直接、またはパッケージローカルの JSON Pointer を介して、string、boolean、integer、number、array、object のいずれかを宣言しなければなりません。ツールおよびチャネルの利用側は、直接のトップレベル文字列プロパティに x-secret: true を設定できます。入れ子になったマーカー、false のマーカー、または boolean 以外のマーカー、および文字列以外のシークレットプロパティは拒否されます。未知のキー、形式不正なエンコーディング、または制約違反があると、値がゲストコードに渡される前にインスタンスが拒否されます。
オペレーターの正規の plugins.entries.<instance-key>.config 値は、メモリ内ではシークレットとしてマークされた文字列マップのままとなり、永続化時には暗号化されます。ホストは、パッケージ、ケイパビリティ、バインディングの完全な識別情報からバージョン付きの zpi1_… エントリキーを導出します。これにより、異なるパッケージやケイパビリティの環境で、main のようなエイリアスを安全に再利用できます。承認済みパッケージのマニフェストによってスキーマが選択されます。string 値はそのまま保存されます。boolean、integer、number の値には "true"、"4"、"0.5" のような JSON スカラー文字列が使用されます。array と object の値には '["urgent","ops"]' や '{"region":"us-east"}' のような JSON テキストが使用されます。ホストは、使用するたびに完全なデータを型付き JSON として実体化して検証し、その後、すべてのプロパティを正確に 1 回だけ振り分けます。ツールの場合、非シークレット値は予約済みの __config キーの下に注入されます。
{
"prompt": 夕焼け,
__config: {
"retry_limit": 4,
"有効": true,
"ラベル": ["緊急", "ops"]
}
}
スキーマでシークレットとして指定されている場合、省略された api_key は secrets.get("api_key") によって明示的に読み取られます。公開セクションを注入する前に、runtime.rs は呼び出し元から提供された __config をすべて削除するため、セクションを偽装することはできません。1 つの execute フレーム内でのツールへの公開設定の注入とシークレットの読み取りは、解決済みのライブ設定リビジョンを共有します。フレームは成功、エラー、トラップ、パニック、またはキャンセル時に破棄されます。チャネルの configure エクスポートには config パラメーターがありません。公開オブジェクトには config.get を、シークレットプロパティには secrets.get を呼び出します。設定を使用する後続の各オペレーション用エクスポートも同様です。1 回の呼び出し内で両方のインポートが、1 つの解決済みリビジョンを共有します。ホストは成功、エラー、トラップ、パニック、またはキャンセル時に、そのマテリアライズドビューを破棄します。したがって、準拠するゲストが使用時点で両方を解決する場合、同じ論理バインディング内での公開設定と認証情報の変更は、次の操作で同時に利用可能になります。
マニフェストが config_read を要求していても、ホストがそれを実質的に許可していない場合、解決処理では空のオブジェクトに置き換え、ゲストコードの実行前にそのオブジェクトを検証します。必須フィールドを持つスキーマは、構築時にフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config を省略します。一方、チャネルの config.get と secrets.get は access-denied を返します。プラグインから見えるのは常に自身のセクションだけです。
チャネルの静的メタデータエクスポートはどちらの構成サービスも呼び出せず、ロード時に一度だけ読み込まれます。そのため、ボット/アカウントのアイデンティティ、または構成から導出される機能、セルフハンドル、メンション、複数メッセージの遅延を変更するには、チャネルのライフサイクルを再構築する必要があります。一方、同じ論理バインディングに対する通常のパブリック構成の変更や認証情報のローテーションでは、その必要はありません。現在の構成コンシューマーは Tool とチャネルです。メモリワールドにはまだ構成のインポート機能がないため、その ABI とランタイム配線が導入されるまで、メモリプラグインは config_read を要求してはなりません。
宣言されたegress([egress])
対象: 転送権限(http_client)を要求するすべてのプラグイン
A manifest may carry an optional [egress] table naming the destinations the plugin declares it needs:
Wait, I should translate it. Here is the translation:
マニフェストには、プラグインが必要とする接続先を宣言するための、省略可能な [egress] テーブルを含めることができます:
[egress]
hosts = ["api.example.com", "*.cdn.example.com"]
厳格な egress 文法を使用します。エントリは完全一致のホスト名、または明示的な *.suffix パターンのいずれかで指定し、全許可形式はありません。文法that が無効なエントリがあると、検出時とインストール時にマニフェスト全体が拒否されます。このテーブルは正規マニフェストのバイト列の一部であるため、署名済みマニフェストはテーブルも対象に含みます。テーブルを変更するには再署名が必要です。
これは意図の表明であり、決して付与ではありません。ホストが強制する許可リストは、オペレーターのplugins.entries.<instance-key>.egress_hostsであり、インスタンスのプライベートなconfigマップを保持しているzpi1_…行と同じ行にあります。これはストアにスナップショットとして保存されるのではなく、リクエストごとにライブ設定から解決されます。オペレーターによる付与なしに独自の[egress]テーブルを同梱したコンポーネントは、どこにも到達できません。本リリースでは、そのチェックは付与のみによって決定されます。宣言はリクエスト時にさらに付与と交差されることはないため、ホストを宣言してもそのホストが付与されることはなく、オペレーターが設定した付与の範囲を制限することもありません。両者の交差をとる処理は、#8850の下で後続のロールアウト作業として行われる予定です。
宣言がもたらすのは、インストール時の手続きです。zeroclaw plugin install は、作成する [[plugins.entries]] の行に宣言された接続先を初期値として設定します。そのため、接続先を宣言しているプラグインを初めてインストールする場合、オペレーターがホストを手入力しなくても動作し、設定された値は出力されます。既存の行を拡張することはありません。宣言が増えたプラグインをアップグレードすると、差分と正確な zeroclaw config set コマンドが出力され、オペレーターがそれを適用します。zeroclaw plugin update は、作成する行に対しても一切初期値を設定しません。同じ差分を出力します。zeroclaw plugin list は、同じ不足を継続的な診断として報告します。コードが実際に接続する接続先を宣言してください。また、宣言が増えることは、アップグレードのたびにすべてのオペレーターが承認すべき事項として扱ってください。
チャネルインスタンスの行は、オペレーターがそのエイリアスをバインドしたときに作成されます。方法は zeroclaw plugin bind <package> --channel-alias <alias> または zeroclaw plugin install <source> --channel-alias <alias> です。宣言は、明示的な --egress declared を指定した場合にのみ、その行にシードされます。--egress none を指定すると、代わりに空の権限付与でバインドされます。どちらのフラグも指定せずに行を作成するバインドは拒否され、宣言済みのホストが一覧表示されます。チャネルインスタンスの場合、宣言は http_client だけでなく、websocket_client や socket_client も対象になります。
reinstall 実行時に、パッケージ名をキーとする、型付き設定に対応していない古い行が見つかった場合、install は派生行 zpi1_… を作成する前に処理を中止し、plugin list と同じ順序の更新手順を表示します。パッケージのインストールはロールバックされ、古いプライベート設定と両方の egress リストは、オペレーターがベータ版の設定を更新して再試行するまで変更されません。
Plugin の送信先がデプロイ設定(セルフホスト Gitea や LAN 内の Nextcloud など)である場合、ここには何も宣言しないでください。オペレーターはその許可をインスタンスの行に直接設定します。また、何も宣言していないマニフェストは、送信先を失ったものとして報告されることはありません。
WASI コンポーネントホスト
ホスト(crates/zeroclaw-plugins/src/component.rs)は、単一の非同期 wasmtime::Engine に対してコンポーネントをコンパイルおよびインスタンス化します。.wasm ファイルの読み込み方法は、ビルドの実行バックエンドによって異なります。
plugins-wasm-cranelift: JIT バックエンドが存在するため、load_componentは読み込み時にComponent::from_file経由で.wasmコンポーネントをコンパイルします。- JIT バックエンドなし (
plugins-wasm-pulleyまたはランタイムのみ): バイナリにコンパイラがないため、load_componentはComponent::deserialize_file経由でファイルを直接デシリアライズし、一致する wasmtime によって生成された事前コンパイル済みの.cwasmとして扱います。不一致のアーティファクトはデシリアライズのバージョンチェックによって拒否されます。
両方のバックエンド機能は plugins-wasmtime を取り込みます。ロードパスはビルドに cranelift コンパイラが含まれているかどうかによって決まり、pulley によってではありません。
呼び出しごとの実行制限
すべてのゲストエクスポートは、ホストがストアに適用する呼び出しごとのリソース制限の下で実行されます。エンジンはフューエルメータリングを有効にし、暴走したコンポーネントや悪意のあるコンポーネントがホストをハングさせるのではなくトラップするよう、各呼び出しに新しいフューエル予算を割り当てます。ホストは、wasi:http などの非同期ホストインポートの待機時間も含め、エクスポート全体のフューチャーに対してウォールクロックのデッドラインも適用します。フューエルによる定期的な yield により、中断されないゲスト計算がそのタイマーの処理を妨げることはありません。また、ゲストから到達可能なホストインポートがエグゼキューターをブロックすることもありません(ログレコードは有界キューに渡され、専用のホストスレッドによって書き込まれるため)。そのため、ホスト処理の実行中もデッドラインを監視できます。StoreLimits の上限は、線形メモリ、テーブル要素、インスタンス数を制限します。ツールワールドには実行ごとに新しいストアが用意されます。ウォームチャネルおよびメモリーストアには各呼び出しの前にフューエルが再補給されるため、長時間稼働するプラグインは存続期間中に予算を使い果たすのではなく、新しい予算を得られます。
5つの上限はオペレーターが調整可能で、すべての値は非ゼロであることが検証されます: plugins.limits.call_fuel(デフォルト 1,000,000,000 命令単位)、plugins.limits.call_timeout_ms(デフォルト 30,000 ミリ秒)、plugins.limits.max_memory_mb(デフォルト 256)、plugins.limits.max_table_elements(デフォルト 100,000)、plugins.limits.max_instances(デフォルト 64)。ストアは明示的な上限を指定しないと構築できないため、どのロード経路でもサンドボックス化されていないプラグインを作成することはできません。ゲストの wasi:http リクエストオプションは呼び出しを早く終了させることはできますが、ホストのデッドラインを延長することはできません。中断されたウォームストアは再開されることがなく、チャネルもメモリバックエンドも、Wasmtime内で呼び出しが失敗したストアを再利用しません。ゲストのトラップ、燃料の枯渇、またはホストインポートの失敗の後、Wasmtimeはそのストアへの以降のすべての呼び出しを拒否し、結果を取り出せなかった場合はそのエクスポートのpost-returnクリーンアップがスキップされます。チャネルは次の呼び出し時に、ホストが所有する入力からインスタンスを再構築します。トラップやデッドラインの超過が、承認・選択プロンプト以外で、再構築の予算(同時に3回、その後は5分に1回)を超えた場合は待機します。メモリインスタンスは所有者が再構築するまで利用できないままとなり、プラグインに到達する以降の呼び出しは、デッドライン超過時と同じエラーで失敗します。メモリワールドはプラグインに自身のインスタンス外のストレージを与えないため、再構築されたメモリインスタンスは空の状態から始まります。正規のフィールドとデフォルト値は設定リファレンスにあります。
32ビットアドレス空間 (wasip2 は wasm32)
プラグインのターゲットは wasm32-wasip2 で、ホストエンジンは wasm_memory64 なしで燃料計測を有効化して(Config::consume_fuel(true))ビルドされています。プラグインの境界は固定された 32 ビット形式であり、それには明確に述べておくべき影響があります:
- ゲストのアドレス空間は32ビットです。 プラグインはwasm32の線形メモリ内で実行されます。大きな値は値渡しで境界を越えます。チャネルプラグインの
media-attachmentは完全なバイト列をlist<u8>として保持し、wit/v0/channel.witではこれが数メガバイトになる可能性があり、リソースハンドルモデルは将来のリビジョンに残すと既に記されています。その32ビット空間内で、ホストはplugins.limits.max_memory_mb(デフォルト256)による明示的なストアごとのメモリ上限を適用するため、ゲストはwasm32アドレス空間とそのZeroClaw設定の上限のうち小さい方で制限されます。 - コンポーネントABIは、ホストのワードサイズに関係なくオフセットを32ビットとして下げます。 64ビットホスト上でも、カノニカルABIにおけるリストと文字列のオフセットは
i32です。memory64はゲストの線形メモリアドレッシングを広げますが、コンポーネントモデルのカノニカルABIを広げるものではないため、有効にしてもWITレベルのフィールドが64ビットになることはありません。 - バインド対象となる64ビットのwasip2ターゲットは存在しません。
wasm32-wasip2は現在のrustcおよびLLVMにおける唯一のWASI Preview 2ターゲットです。プラグインを64ビットのp2コンポーネントにコンパイルできないため、エンジンがmemory64を有効にしてもホストがロードするものはありません。
これはアップストリームのツールチェーンによる制約であり、このリポジトリのフラグで解除できるホスト側の制限ではありません。64ビットのp2ターゲットとより広いコンポーネントABIがアップストリームに導入されれば、bindgen!のシームはそれらに対して再生成され、フィールド幅はwit/VERSIONING.mdのウィンドウ配下のWITで見直されます。それまでは、プラグイン境界は構造上32ビットであるものとして扱ってください。
署名
プラグインマニフェストには Ed25519 署名を含めることができます(crates/zeroclaw-plugins/src/signature.rs)。署名は、正規化されたマニフェストのバイト列(ルートにある正確な signature および publisher_key エントリだけを削除した、解析済みの TOML)に対して base64url エンコードされます。パブリッシャーの公開鍵は 16 進数でエンコードされます。これらの名前を持つネストされたスキーマプロパティは署名の対象に残ります。ホストは plugins.security.signature_mode で指定された 3 つのモードのいずれかを適用します:
| モード | 未署名のプラグイン | 信頼されていない、または無効な署名 |
|---|---|---|
strict | 却下されました | 却下されました |
permissive | 警告付きで読み込まれました | 警告付きで読み込まれました |
disabled | loaded(読み込み済み) | 未チェック |
検証は検出時とインストール時の両方で実行されます。検出時は、ポリシーに違反したプラグインをホスト全体を中断せずにスキップします。インストール時はエラーを返します。
Rustでプラグインを書く
プラグインは、コンポーネントモデルをターゲットとする cdylib クレートです。ホストが使用するのと同じ wit/v0 パッケージからゲストバインディングを生成し、エクスポートされた world を実装して、wasm32-wasip2 にコンパイルします。空のクレートからインストール済みプラグインまでの完全な手順解説については、プラグインガイド を参照してください。以下の注記では、ビルドとインストールの仕組みについて説明します。
ビルド
sh
# WASI Preview 2 ターゲットをインストールする(一度だけ)
rustup target add wasm32-wasip2
# コンポーネントをビルドする
cargo build --target wasm32-wasip2 --release
出力コンポーネントは target/wasm32-wasip2/release/<crate_name>.wasm にあります。これを manifest.toml と同じ場所にコピーしてください。JITバックエンドを持たないランタイム専用のホストビルドでは、対応するwasmtimeでコンポーネントを.cwasmにプリコンパイルし、それを代わりに配布してください。そのようなホストは、読み込み時にコンパイルするのではなくデシリアライズを行うためです。
ホスト側のツールプラグインテストは公開済みアーティファクトに依存しません。crates/zeroclaw-plugins/tests/fixtures/tool-fixture はテスト時にソースからビルドされるリポジトリ内のコンポーネントであり、reference_plugin.rs と reference_plugin_e2e.rs は、デーモンが実行するものと同じ PluginHost、config_schema、および設定解決パスを通じてこれを実行します。フィクスチャをビルドできない場合、これらのテストは失敗します。
インストール
sh
# プラグインディレクトリにコピー
zeroclaw plugin install /path/to/my-plugin/
# または手動で
cp -r my-plugin/ ~/.zeroclaw/plugins/my-plugin/
設定
オペレーターの値は、現在は汎用の文字列マップ型ストレージを通じて入力されます。TOML の [[plugins.entries]] を編集するか、インスタンスのエントリが存在する場合は zeroclaw config set を使用します。ツールをインストールすると、そのデフォルトバインディング用のエントリが作成されます。チャネルのキーは設定済みのエイリアスに依存するため、チャネルインスタンスのエントリは、そのエイリアスがバインドされたときに作成されます。これは zeroclaw plugin bind <package> --channel-alias <alias> または zeroclaw plugin install <source> --channel-alias <alias> によって行われます。設定済みのエイリアスからチャネルの型付き設定を解決する、エイリアスを考慮した構築処理は #10146 で導入されました。zeroclaw plugin info <package> は、移行や後の編集のために、パッケージが所有するすべてのキーを出力します。これには、ツールのバインディングのキーと、インストールされたバージョンがインスタンスの状態を所有し、そのエイリアスでインスタンスを指定できる場合には、バインドされたエイリアスごとのキーが含まれます。そのようなバインド済みエイリアスごとに、インスタンスの準備状態も表示されます。キー行の有無にかかわらず、必須キーが設定済みか未設定か、設定とegressの判定結果、そしてランタイムがそのインスタンスを受け入れるかどうかが含まれます。スキーマ駆動のフォームとインラインのフィールドヘルプは、まだ実装されていません。現在のサーフェスは次のとおりです:
- CLI は
list、search、install、bind、remove、info、migrateを使ってプラグインのライフサイクルを管理します。zeroclaw config setは個々のプラグインの生の値を書き込むだけで、プラグインのスキーマは解釈しません。 - zerocode は ZeroClaw の静的なプラグインホスト設定を編集できますが、
config_schemaからプラグインごとのフィールドを生成する機能にはまだ対応していません。 - Web ゲートウェイはプラグインに対して読み取り専用です。
GET /api/pluginsは、読み込まれているプラグインとシステムが有効かどうかを報告します。 - ホストはパッケージを受け入れる際に
config_schemaを検証し、ゲストで使用する前にもオペレーターの値を再度検証して実体化します。 - マニフェストスキーマは、プラグイン作成者にとって、ゲスト境界における唯一の型および検証の契約です。サポートされるすべてのキーと制約をそこで定義し、ホストのランタイム設定構造体でその契約を重複して定義しないでください。ゲストコードは、ホストで検証済みの JSON をネイティブの型付き構造体にデシリアライズする必要があります。
静的な設定スキーマは、動的なプラグインごとのエディターではなく、汎用的な保存およびシークレット指定用のパスを提供します。crates/zeroclaw-config/src/schema.rs にあるプラグイン設定型には、#[prefix = "plugins"]、#[prefix = "plugins.entries"]、および #[prefix = "plugins.security"] が付与されており、Configurable derive によって、プレフィックス付きの各フィールドが汎用的な設定パスに変換されます。シークレットフィールド(プラグインエントリの config マップには #[secret] が付与されています)は、隣接する .secret_key の下で保存時に暗号化されます。ホスト設定の正規フィールド、デフォルト値、および signature_mode の値は Config リファレンス に記載されています。そのスキーマが信頼できる唯一の情報源であり、各プラグインマニフェストがプライベート設定の構造に関する信頼できる唯一の情報源です。
ビルド機能
プラグインホストはコンパイル時のオプトインです。ワークスペースの Cargo.toml にあるバイナリレベルの機能によって、プラグインをそもそも組み込むかどうか、およびどの実行バックエンドを同梱するかを選択します。
plugins-wasmは、プラグインホストとそのランタイム統合をバイナリに取り込む包括的な機能です。以下のすべてのバックエンド機能はこれを暗黙的に有効にするため、任意の実行バックエンド(例:--features plugins-wasm-cranelift)を有効にすると、常にプラグインホストとその CLI インターフェイスも含まれます。バックエンドのみのビルドが、pluginサブコマンドを含まないバイナリを黙って生成することはありません。この包括機能だけを有効にした場合はplugins-wasm-runtime-onlyと同等です。JIT はないため、事前コンパイル済みの.cwasmコンポーネントのみがロードされます。plugins-wasm-runtime-onlyは最小かつ起動が最速です。JIT がないため、コンポーネントはプリコンパイルされた.cwasmからデシリアライズされます。plugins-wasm-craneliftは Cranelift コンパイラを追加するため、.wasmコンポーネントは読み込み時にコンパイルされます。コンパイラを含む唯一の機能であり、プラグインレジストリが公開する.wasmコンポーネントを読み込めるのも、この機能だけです。plugins-wasm-pulleyは wasmtime の Pulley インタプリタ向けにコンパイルされます。ZeroClaw は Pulley をコンパイルターゲットとして選択することはないため、ネイティブの Cranelift バックエンドを持つホストでは、この機能は何も変えません。単独で使用した場合はplugins-wasm-runtime-onlyと同じ動作となり、plugins-wasm-craneliftと組み合わせた場合はネイティブコードをコンパイルします。ネイティブバックエンドを持たないホスト(32 ビット ARM など)では、wasmtime が単独で Pulley をターゲットとします。
これらは wasmtime を接続する zeroclaw-plugins クレートのフィーチャ(plugins-wasmtime、plugins-wasm-cranelift、plugins-wasm-pulley)に委譲します。ロードパスは、WASI Component Host で説明されているように、Cranelift コンパイラがビルドに含まれているかどうかによって決まります。正式な説明については、ワークスペースの Cargo.toml にあるフィーチャのコメントを参照してください。
プラグインのサポートはリリース成果物ごとに提供されます
標準の配布用フィーチャーセット(cargo generate features --selection dist。ワークスペースの Cargo.toml にあるレジストリから、ターゲットごとに解決される)は、64ビットのデスクトップおよびサーバー向けの7つのターゲットでは plugins-wasm-cranelift を含み、32ビットARMおよび実験的なAndroidターゲットでは除外しています。ホストを担う成果物はどれか、そしてそれを何が証明しているのか:
| アーティファクト | プラグインホスト | 承認済みパッケージ | Proof |
|---|---|---|---|
| CLIアーカイブ:x86_64 および aarch64 Linux(glibc および musl)、aarch64 および x86_64 macOS、x86_64 Windows | Cranelift コンパイラ、読み込み時にコンパイルされる .wasm | wit/v0 のリリースに対してビルドされた .wasm コンポーネント | リリースでは、publish および docker ジョブの実行前に、各アーカイブ内のプラグインをそのプラットフォーム上で実行します(plugin-smoke ジョブ)。 |
| ARMv7およびarm(ARMv6)のLinux向けCLIアーカイブ | none: plugin はコマンドではありません | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | レジストリのターゲットごとのポリシー。xtask テストによって固定されています。 |
| aarch64-linux-android 向け CLI アーカイブ(実験的) | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | 翻訳する英語の文字列が含まれていないようです。翻訳したい文字列を送ってください。 |
GHCR の latest、バージョン指定、debian イメージ | Cranelift: x86_64 と aarch64 の Linux glibc 向けアーカイブを再パッケージしています | .wasm | archive smoke を継承します。イメージ内では何も実行されません。 |
GHCR dist バリアント、AUR パッケージ、Nix flake パッケージ、setup.bat によるソースビルド | Cranelift: 生成された配布リストからソースをビルドする | .wasm | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 |
GHCR の all-features イメージ | Cranelift、Pulley、runtime-only がすべてコンパイル時に組み込まれています | .wasm | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 |
GHCR の minimal および default-features イメージ | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | |
| デスクトップ向けバンドル (dmg、deb、AppImage、msi、setup exe) | Cranelift: 標準のサイドカービルでは、ターゲット向けに生成された dist 機能を使用します(macOS では embedded-web も併用)。 | .wasm | scripts/desktop/prepare-kernel.shでソースの接続を行う。デスクトップバンドルにはプラグインのスモークゲートがない。 |
cargo install zeroclaw と install.sh のソースビルド | デフォルトではなし。--features plugins-wasm-cranelift で有効化します。 | .wasm を opt-in で | |
| Homebrew core | formula所有、このリポジトリの外部 |
ホストを持つすべてのアーティファクトに適用される制限:
-
wit/v0は不安定です。プラグインは、それを実行するリリースに同梱された WIT に対してビルドする必要があります(wit/VERSIONING.md)。マニフェストには host や WIT のバージョン情報が含まれないため、不一致はインストール時のロードチェックで検出されます。 -
コンポーネントのコンパイルには、書き込み後に実行可能になるメモリが必要です。これを禁止する環境(
MemoryDenyWriteExecute=yesを指定した systemd ユニットや、SELinux のexecmem拒否など)では、バイナリの他の部分は通常どおり動作しても、すべてのプラグインの読み込みが拒否されます。 -
公式レジストリのパッケージは現在署名されていないため、
signature_mode = "strict"の場合はいずれもインストールできません。 -
毎回のツール呼び出しでコンポーネントが再コンパイルされます。コンパイルキャッシュはありません。
-
ランタイムのデフォルトでは無効のままです。インストールされたツールまたはスキルパッケージは、オペレーターが
plugins.enabledとplugins.auto_discoverの両方を設定するまで何も寄与しません。両方が設定されると、ツールレジストリのビルドのたびに、許可された各ツールコンポーネントがコンパイルおよびインスタンス化され、パッケージで宣言された権限と egress ポリシーの下で、名前、説明、パラメータスキーマが照会されます。承認が必要になるのはexecuteのみです。チャネルパッケージは、
plugins.enabledが設定され、オペレーターが[channels.plugin.<alias>]で宣言し、有効なエージェントがplugin.<alias>をそのチャネルの一覧に含めている場合に実行されます。ネイティブチャネルをprovides経由でミラーするパッケージの場合、[channels.<provides>.<alias>]の下でのアクティベーションは記録されますが、まだ構築はされません。各プラグインのツール呼び出しには、エージェントのリスクプロファイルで事前承認されている場合(
auto_approve(*ワイルドカードを含む)またはlevel = "full")を除き、オペレーターの承認が必要です。ただし、同じ CLI セッションまたはチャネルのターン内で、そのツールに対して以前に Always と回答していた場合は除きます。always_askに含まれるツールは毎回確認を求めます。
中断されたインストールからの復旧
zeroclaw plugin remove <name> は、未ロードで空の、または構造的に不完全なパッケージディレクトリを回復できます。対象となるのは、インストールが書き込むものだけを含むディレクトリです。具体的には、そのパッケージ名を記載したマニフェスト、wasm_path にあるコンポーネントファイルとそこに至るまでのフォルダ、そしてスキルプラグインの場合はフォルダとファイルからなる skills/ ツリーです。署名ポリシーによる拒否、正常なパッケージ、ロード済みのエイリアス、シンボリックリンク、内容を検査できないもの、それ以外のものを含むディレクトリは、引き続き保護されます。回復処理は、受け入れと削除の前に単一のディレクトリ世代を確保します。古い判定を使ってパッケージ名の位置にある置き換え後のディレクトリを再帰的に削除することは決してありません。
インストールと復旧は、plugins ルート配下にある永続的で非表示の OS ロックを介して調整されます。インストール、更新、削除、および中断された更新によって退避されたものの一覧表示のみが、そのルートとそのロックを開きます。そのため、デーモンが保持しているものなど、プラグインの検出のみを行うホストは plugins ディレクトリ内に何も保持しません。Windows では、開かれたルートは、そのホストが解放するまで plugins ディレクトリとその祖先ディレクトリの名前変更を阻止します。60 秒以内にロックを取得できないプロセスは処理を中止し、ロックファイルの名前を表示します。そのファイルを読み取れるアカウントはいずれもロックを保持できます。オペレーティングシステムまたはファイルシステムがファイルロックを提供していない場合、インストール、更新、削除は拒否されます。新しいステージングディレクトリは、所有の証拠として PID を使うのではなく、一意の世代番号と OS が保持するリースを使用します。復旧処理は、実際の最終パッケージを復旧している間、または以前の削除で削除対象と判断されたものの削除を完了している間に限り、放棄されたプロトコルステージをクリーンアップします。放棄されたことの証拠がない、PID のみを持つレガシーステージは保持され、そのパスが表示されます。これらが新しいインストールで新しいステージングを使用することを妨げることはありません。
拒否された要求済みパッケージは、並行して存在する占有者を上書きすることなく復元されます。復元を完了できない場合、エラーは保持されている非表示のトランザクション場所を示します。そのディレクトリを保持し、報告された競合を解決してから plugin remove を再試行してください。再試行では、通常の受け入れポリシーを適用する前にパッケージが復元されます。削除が開始された要求には印が付けられ、再試行では復元ではなくその削除を完了させます。非表示のトランザクションは、その名前だけを根拠に、正常なパッケージを削除してよいことの証拠とみなされることは決してありません。ファイルシステムの名前空間の変更およびサポートされていないロックは、安全のため復旧を拒否します。パッケージの移動には、空のディレクトリを含め、既存のすべての移動先を拒否するアトミックなリネームが必要です。プラットフォームまたはファイルシステムがこれを提供できない場合(FreeBSD などその他の Unix プラットフォーム、置換なし(no-replace)のサポートがない NFS マウント、または 3.15 より前の Linux)、インストールおよび復旧は移動を拒否します。要求に失敗した場合、最終エントリは移動されないままになります。この方法で拒否されたインストールは、ステージングされたコピーを削除し、移動がサポートされていないと表示します。それ以外の公開の失敗および復元の失敗では、パッケージは非表示のトランザクション内に保持され、そのパスを報告します。単純な置換リネームは使用されません。