Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

プラグイン

ZeroClaw のプラグインシステムを使用すると、コアバイナリに手を加えることなくエージェントに機能を追加できます。このページでは技術的な決定について説明します。プラグインが何で構成されているのか、なぜ WebAssembly なのか、そしてホストが信頼できないコンポーネントをどのように隔離しているのかを解説します。その下にあるガイドでは、各種類のプラグインの構築方法を、下に進むにつれてより技術的な内容へと掘り下げながら順を追って説明します。

Markdown のみの スキルバンドル はプラグインではありませんが、同じマニフェスト、署名、インストール機構を経由します。そのページは Skills のドキュメントにあります。

オペレーターの視点でのディスカバリー、署名ポリシー、および設定については、プラグインの仕組み を参照してください。規範的な契約リファレンスについては、プラグインプロトコル を参照してください。

WebAssembly を選ぶ理由

プラグインは、API キー、会話履歴、シェルアクセスを保持するプロセス内で任意のサードパーティコードを実行します。分離境界は助言的なものではなく、実質的なものでなければなりません。ZeroClaw は wasmtime 上の WASI Component Model を使用します。動的ライブラリ方式やサブプロセス方式では同時に満たせない4つの特性を提供するからです:

  1. ケイパビリティベースのサンドボックス。 WebAssembly コンポーネントにはアンビエント権限がありません。ホストがそのケイパビリティをリンカーに明示的に接続しない限り、ファイル、ソケット、環境変数を開くことはできません。ZeroClaw のホストは、ファイルシステムのプリオープンもネットワークもない WASI コンテキストで各プラグインストアを構築します(crates/zeroclaw-plugins/src/component.rs の PluginState)。プラグインが到達できるのは、その world が宣言するホストインポートの集合と、マニフェストのパーミッションが追加するものだけであり、それ以外には何もありません。
  2. 実行のメータリング。 エンジンは燃料メータリングを有効にしてビルドされており、呼び出しごとに新しい燃料予算と、待機中のホスト処理を含む実時間の期限が設定されます。無限ループや無限待機を行うプラグインは失敗し、エージェントをハングさせることはできません。メモリ、テーブル、インスタンスの上限はストアリミッターによって適用されます。5つすべての上限はオペレーター設定(plugins.limits.*)から取得され、ゼロ以外であることが検証されます。また、これらの上限なしではストアを構築できないため、どの読み込み経路でもサンドボックス化されていないプラグインが生成されることはありません。
  3. 型付きの言語非依存 ABI。 ホストとプラグイン間の契約は Rust API ではなく、WIT インターフェースファイルのセット(ZeroClaw リポジトリ内の wit/v0/)です。ホストは wasmtime の bindgen! を用いてそれらのファイルからバインディングを生成し、プラグインは Rust の wit-bindgen、または wasm32-wasip2 コンポーネントにコンパイルできる任意の言語の同等のツールを用いて、ミラーイメージのゲストバインディングを生成します。レコード、バリアント、result、option 型は、型を保持したまま境界を越えます。
  4. 組み込みと同一の動作。 各プラグイン種別は、ファーストパーティ実装が使用する同じ Rust トレイトに適合されます。ツールプラグインは Tool (wasm_tool.rs) に、チャネルプラグインは Channel (wasm_channel.rs) に、メモリプラグインは Memory (wasm_memory.rs) になります。エージェントループ、アトリビューション、レシート、セキュリティポリシーは違いを認識しません。

ピース

ディスク上のプラグインは、マニフェストとコンパイル済みコンポーネントを保持するディレクトリです:

~/.zeroclaw/plugins/
└── my-plugin/
    ├── manifest.toml     # identity, capabilities, permissions, signature
    └── my-plugin.wasm    # wasm32-wasip2 component

マニフェストは直交する 2 つの事柄を宣言します。

  • Capabilities: プラグインが 何であるか。tool、channel、memory、observer、skill のいずれか1つ以上(crates/zeroclaw-plugins/src/lib.rs の PluginCapability 列挙型)。各 WASM 機能は、コンポーネントがエクスポートしなければならない WIT world を選択します。skill 機能は例外で、コードではなくインストール機構に乗る markdown の skill bundle を示すものであり、コンポーネントは不要です。
  • 権限: プラグインのコードが到達できるホストサービスを指定します。同じファイル内の PluginPermission 列挙型を参照してください。現在、config_read(ツールおよびチャネルアダプターは、スキーマに基づいて実体化・検証された自身の公開設定を受け取り、認可されたサービス呼び出しにおいてスキーマで指定されたシークレットを解決できます)、http_client、state_read、state_write には動作上の効果があります。HTTP 権限は、送信用の wasi:http を実装するアダプターに必要な付与です。ツールとチャネルはこのインターフェースを有効にしますが、メモリでは意図的に、まだ有効になりません。状態権限は、正確なパッケージ、ケイパビリティ、バインディングによって所有される暗号化された永続状態を制御します。config_read はマニフェストの config_schema と組み合わせて指定する必要があり、どちらか一方のみの指定は拒否されます。ファイルシステム権限とメモリアクセス権限はスキーマで受け入れられますが、まだホスト関数によって裏付けられていないため、宣言しても何も付与されません。

世界

wit/v0/ は WASM 機能ごとに 1 つの world を定義します。すべての world はホストの logging インターフェイスをインポートし、その log-record イベントはホスト呼び出し元のスパン属性を保持しながら構造化ログに記録されます。また、plugin-info(自己申告の名前とバージョン)とそのプライマリインターフェイスをエクスポートします。

Worldエクスポートストアのライフサイクル
tool-plugintool: name, description, parameters-schema, executeexecuteごとに新しいストアを作成します。インポートはsecretsと永続的なstateにスコープされます。
channel-pluginchannel: configure、send、poll-message、ほか23個の機能ゲート付きメソッドウォームストアは非同期ミューテックスで保護され、呼び出しごとに再補充されます。インポートされるスコープは config、secrets、永続的な state、およびホストから供給される inbound です。
memory-pluginmemory: store、recall、get、forget、および11個の機能ゲート付きメソッド非同期ミューテックスの背後にあるウォームストア。呼び出しごとに補充されます

チャネルワールドとメモリワールドは、capability flags を使用します。これは、ホストがロード時に一度だけ読み取るビットマスクです(get-channel-capabilities / get-memory-capabilities)。設定されていないフラグについては、ホストは Rust トレイトのデフォルトを使用し、プラグインのエクスポートを呼び出すことはありません。これにより、WIT コントラクトは追加的な状態を保ちます。新しいオプションのメソッドは、新しいフラグと新しい関数の追加であり、破壊的変更となることはありません。

プラグインがここでまだ読み込めるか確認しています

wit/v0 は実験的な仕様のため、ホストと乖離したベンダリング済みコピーに対してビルドされたプラグインは、コンパイルは通るもののインスタンス化に失敗します。zeroclaw plugin install はこうしたプラグインのインストールを拒否しますが、そのゲートは、ゲートの導入前にインストールされたプラグイン、--no-verify を付けてインストールされたプラグイン、ホストのアップグレード後もそのまま残されたプラグインには効果がありません。zeroclaw plugin info <name> は、デーモンが起動時に実行するのと同じインスタンス化チェックを常に実行し、判定結果、wasmtime の完全な原因チェーン、再ビルドのヒントを表示します。プラグインが読み込めない場合は非ゼロで終了するため、スクリプトで分岐処理を行えます。zeroclaw plugin list --verify は、インストールされているすべてのパッケージに対してチェックを実行し、各行に判定結果を注記します。失敗した場合は、原因の最初の行を注記します。通常の zeroclaw plugin list は変更されておらず、ディレクトリの読み取りのみの コストのままです。検証では各コンポーネントのコンパイルとインスタンス化を行うためです。スキルバンドルにはコンポーネントが含まれないため、失敗ではなく対象外として報告されます。

インスタンス化されるコンポーネントを持つツールプラグインの場合、両コマンドは、ランタイムがツールを登録する前に行うツール設定のチェックも適用します。plugin removeはパッケージの[[plugins.entries]]行とその値を残すため、再インストール時に、マニフェストが受け付けない値に遭遇することがあります。新しいパッケージがconfig_schemaを宣言していない場合は、どのような値であってもすべて該当します。ランタイムはそのようなツールをスキップするため、判定結果は、ツールが読み込まれないというものになり、ランタイムの理由と値を保持している行も併せて示されます。

不完全なインストールからの復旧

zeroclaw plugin install は、プラグインディレクトリ内の一意な隠し作業領域でパッケージを構築し、公開まで オペレーティングシステムのリース を保持します。manifest、コンポーネント、および存在する場合は skills/ ツリーの書き込みがすべて完了した後にのみ、既存のものを置き換えることなくパッケージを最終位置へ移動します。プラットフォームまたはファイルシステムがアトミックな置き換えなし移動を提供できない場合(Linux、Android、macOS 以外の FreeBSD などの Unix システム、置き換えなしをサポートしない NFS マウント、Linux 3.15 より前のバージョン)、インストールは中止され、ステージングされたコピーを削除したうえでその旨を通知します。その環境では再試行しても同じように失敗します。書き込みの失敗や中断が発生しても、最終名に部分的なパッケージが残ることはありません。検出処理は隠し作業領域を無視し、後続のインストールは別プロセスのステージを置き換えるのではなく、新しいステージングを使用します。

サポートされていない移動は、同じサポートされていないファイルシステム上で再試行する理由にはなりません。クリーンアップも失敗した場合、install は失敗したクリーンアップの場所を報告し、その失敗と安全な移動のサポートを先に解決する必要があることを示します。その場所は、部分的なクリアまたは空ディレクトリの削除の失敗時にはパッケージディレクトリであり、パッケージが存在しない、またはすでに削除されている場合にはトランザクションエントリです。このメッセージは、元のペイロードが無傷であるとは主張しません。クリーンアップのチェックでは、続行する前に、置き換えられたまたは削除された plugins ルートまたは調整ロックを名前空間の拒否として扱います。報告された非表示のトランザクションは、この失敗した install に属しており、後続の install をブロックすることはありません。その名前のパッケージが存在しない間は plugin remove はこれを掃除しないため、install が実行されていないときに、その特定のトランザクションを手動で確認して削除できます。

以前のビルドでは、最終ディレクトリへ直接書き込んでいました。ディレクトリを作成してからmanifest.tomlを最初に書き込むため、インストールが中断されると、空のディレクトリ、途中で切れたmanifest.toml、またはコンポーネントのないmanifest.tomlが残る可能性がありました。検出処理はこのようなディレクトリを読み飛ばすため、インストール済みのパッケージではありません。それでもその名前は占有されたままでした。zeroclaw plugin installは、パッケージ名で既に存在するものを決して上書きせず、インストールを拒否したうえで、復旧方法としてzeroclaw plugin remove <name>を案内します。

ホストが読み込んだパッケージの場合、plugin remove は、そのパッケージの読み込み元となったディレクトリを削除します。このディレクトリの名前がパッケージ名と一致するとは限りません(例:手動で移動した後など)。削除では、まず非公開のリカバリトランザクション内でそのディレクトリを確保し、ホストが読み込んだマニフェストとコンポーネントがまだ含まれていることを確認したうえで、その確保を通じて削除します。パッケージ名と同じ名前にあるだけのディレクトリは削除されません。ホストが読み込んだ後にディスク上で置き換えられたパッケージは元に戻され、削除は拒否されます。リカバリと同様に、これにも上記で説明したアトミックな非置換移動が必要であり、プラットフォームまたはファイルシステムがそれを提供できない場合は削除を拒否します。

plugin remove が、ディレクトリを確保したものの削除前に中断された場合、または読み込み後に変更されたディレクトリを元に戻せなかった場合(保持されたファイルを報告する)、確保したものはパッケージ名のみを記録する隠しリカバリートランザクションに残ります。次の plugin remove <name> は、それを読み込み元のディレクトリではなく <plugins_dir>/<name> に戻します。<name> に別のものが存在する限り、処理を拒否します。完了させるには、<name> にあるものを脇に移動し、zeroclaw plugin remove <name> を2回実行します。1回目の実行でパッケージは <name> に戻され、ホストがそこから読み込んでいないため、そのまま残ります。2回目の実行で削除されます。その後、必要であれば、もう一方のディレクトリを元に戻してください。削除がすでに開始されていた場合は、代わりに次の remove が完了させます。

ホストが読み込んでいない名前の場合、plugin remove は、選択されたディレクトリ世代を非表示のリカバリートランザクションで確保し、保持したその内容に同じ受け入れチェックを適用します。その世代を削除するのは、次の 2 つの場合に限られます:

  • ディレクトリは空です。
  • manifest.toml を持ち、その内容自体が原因で install と discovery が適用する受理チェックに失敗する。具体的には、切り詰められたマニフェストや解析できないマニフェスト、コンポーネントの欠落、宣言された wasm_sha256 と一致しないコンポーネント、またはこのホストが記述どおりには受理できないパッケージ(受理サイズ上限を超えるコンポーネント、コンパイルできない config_schema、不完全なスキルバンドルなど)が該当する。また、install が書き込むことのないものを一切含んではならない。install はマニフェストの名前の下にパッケージを書き込み、まずマニフェストを書いた後に、wasm_path にあるコンポーネントのみを、経路上のフォルダを伴うファイルとして書き込む。スキルプラグインの場合は、ファイルとサブフォルダからなる skills/ フォルダも書き込む。したがって、解析できないマニフェストの隣には何も存在してはならず、解析できるマニフェストの隣には、そのマニフェストが与える名前の下で、これらのエントリのみが存在しうる。

リカバリでは、リースプロトコルによって作成された、放棄されたことが確実なステージも削除されます。アクティブなステージと、曖昧なレガシーの .<name>.installing-<pid> ディレクトリは保持され、コマンドはそのパスを報告します。ファイル名に含まれる PID は、放棄されたことを証明するものではありません。保持されたこれらのステージは、不完全な最終パッケージがリカバリされた後の新規インストールを妨げません。最終パッケージが存在しない場合、plugin remove は、以前の plugin remove が開始した削除を完了させる場合を除き、ステージングディレクトリを一掃しません。

それ以外の占有者は拒否され、コマンドはその理由を表示します:名前に一致するシンボリックリンクまたはファイル、manifest.toml を含まずファイルのみを含むディレクトリ、インストールが決して書き込まないファイルを含むディレクトリ(例:コンポーネントがまだビルドされていないプラグインのソースチェックアウトや、ファイルマネージャーが追加した .DS_Store)、マニフェストが別のパッケージを指定しているディレクトリ、検査またはリストできないディレクトリ、読み込み済みパッケージの読み込み元となったディレクトリ、admission が受け入れたパッケージ、このホストが署名ポリシーにより拒否するパッケージ(署名なし、信頼されていない発行者によるもの、無効な署名を持つもの、または strict モードで署名付きの wasm_sha256 を持たないもの)。admission はコンポーネントを読み込む前に署名を検証するため、strict モードでは、署名のないパッケージから取り残された manifest.toml、または署名が検証不能になるように編集された manifest.toml は、コンポーネントが欠落していても署名を理由に拒否されます。

拒否された claim 済みパッケージは、空のディレクトリを含め、同時に存在する占有物を置き換えることなく復元されます。リカバリーは、プラットフォームまたはファイルシステムがアトミックな no-replace 移動を提供できない場合、claim または復元を拒否します。復元が完了できない場合、そのバイト列は報告された非表示の場所に残ります。元の名前が変更されていないとは限りません。plugin remove を再試行すると、受付条件を再確認する前に、保持されているリカバリートランザクションが復元されます。plugin remove が開始していた削除は代わりに完了されます。不完全と判断したパッケージの削除を一度開始すると、再試行によってその削除が完了します。再試行する前に、報告された移動先の競合またはファイルシステムエラーを解決してください。隠しトランザクションが残存物に見えるというだけの理由で削除しないでください。

実行モデル

ホスト(crates/zeroclaw-plugins/src/component.rs)は、プロセス用に1つの非同期 wasmtime::Engine を保持します。読み込みはバックエンドに依存します。Cranelift JIT を使用したビルドでは、読み込み時に .wasm をコンパイルします。ランタイム専用ビルドでは、プリコンパイル済みの .cwasm をデシリアライズします。各プラグインのインスタンス化では、以下が行われます。

  • サンドボックス化された WASI コンテキスト、リソーステーブル、オプションの HTTP コンテキスト、および燃料バジェットを保持する Store。
  • そのワールド、付与された権限、アダプターサポートが呼び出す import だけを正確に備えた Linker は、常に logging、ツールとチャネルには secrets、チャネルには config と inbound を使用し、マニフェストが http_client を許可している場合に限り、ツールおよびチャネルのアダプターには wasi:http を使用します。Memory は HTTP コンテキストも HTTP リンカーも作成しません。各アダプターはインスタンス化時にコンテキストとリンカーを相互検証します(ensure_http_coherent)。

ツールの呼び出しは構造上ステートレスです。WasmTool::execute は新しいストアを作成し、呼び出しを実行してから破棄します。チャネルとメモリバックエンドは本質的にステートフルであるため、プラグインのライフタイムの間、ウォーム状態のストアを1つ保持します。ホストは各呼び出しの前にそのストアへ燃料を補充するため、長時間稼働するプラグインでも、燃料が呼び出しごとに消費されていくことはなく、毎回十分な予算を得られます。期限による中断が発生した場合は、部分的に巻き戻されたゲスト状態から再開するのではなく、ウォームストアを破棄します。チャネルとメモリバックエンドは、Wasmtime 内部で呼び出しが失敗したストアも破棄します。Wasmtime は、トラップ、燃料枯渇、またはホストインポートの失敗の後、そのストアへのそれ以降のすべての呼び出しを拒否します。また、変換できなかった結果は、エクスポートのクリーンアップをスキップさせます。チャネルは、次の呼び出し時にインスタンスを再作成します。失敗が再構築の予算を上回る場合は、待機します。メモリは、所有者が再構築するまで利用できないままとなり、その間にプラグインへ到達したすべての呼び出しは、同じ plugin instance is unavailable エラーで失敗します。メモリプラグインはエントリを自身のインスタンス内にのみ保持するため、再構築されたインスタンスは空の状態で開始します。認可されたチャネル呼び出しの間、config.get と secrets.get は、その認可されたインスタンスの正規化された設定について、最大1リビジョンをマテリアライズします。ホストは呼び出しの終了時にそのビューを破棄します。準拠するチャネルプラグインは、両方を使用箇所ごとに解決しなければならず、返された設定や平文のシークレットをウォームなゲスト状態に保持してはなりません。信頼されたゲストコードにデータを返した後の非保持は、ホストが強制することはできません。

境界は 32 ビットです。wasm32-wasip2 は Rust ツールチェーンが提供する唯一の WASI Preview 2 ターゲットであり、コンポーネント ABI はホストのワードサイズに関係なくオフセットを 32 ビットとして扱います。大きな値(チャネルアタッチメントのバイト列)は値渡しで受け渡されます。これが上流の制約である理由については、プロトコルページを参照してください。

現在の配線状態

エンドツーエンドで登録されているものと、ホスト完了だがまだ実行中のデーモンから到達できないものとの違いに注意してください:

機能ホストアダプターランタイムの接続
toolWasmTool登録がエンドツーエンドで完了しました。検出されたツールプラグインがエージェントのツールセットに表示されます
skillmarkdown ローダーエンドツーエンドで登録済み。スキルは plugin:<plugin>/<skill> の名前空間で読み込まれます
channelWasmChannel、完全かつユニットテスト網羅済みエイリアス側での構築と実行時設定の解決が取り込まれました(#10146)。各トランスポートからチャネルの inbound キューへデータを流し込むベンダーごとのホストリスナーは、後続対応となります
memoryWasmMemory、完全なMemoryトレイトを実装しますランタイムはこれをまだ設定可能なバックエンドとして構築しません
observerなしPluginCapability::Observer は予約済みです。WIT world やアダプターはまだ存在しません

設定

静的なプラグインホスト設定では、他のすべてと同じスキーマミラーを使用します。インスタンスごとの値は現在、汎用のTOMLまたは zeroclaw config set を使用します。プラグインマニフェストスキーマは、まだzerocode形式やゲートウェイ形式としてレンダリングされていません。手動で編集する場合は注意してください。セクション内の構文ミス(たとえば、[[plugins.entries]] とすべき箇所で [plugins.entries] とするなど)により、現在は [plugins] セクション全体のデシリアライズに失敗し、警告なしでデフォルト値にフォールバックします。その結果、読み戻すと plugins.enabled = false になります(issue #8636で追跡中)。一般的な操作:

# システムをオンにする
zeroclaw config set plugins.enabled true

# 実行時に自動検出されたツールおよびスキルのプラグインを読み込む(デフォルト: false)
zeroclaw config set plugins.auto_discover true

# プラグインが検出される場所(デフォルト: ~/.zeroclaw/plugins)
zeroclaw config set plugins.plugins_dir /srv/zeroclaw/plugins

# 署名ポリシー: disabled | permissive | strict
zeroclaw config set plugins.security.signature_mode strict

# 呼び出しごとのサンドボックス制限
zeroclaw config set plugins.limits.call_fuel 1000000000
zeroclaw config set plugins.limits.call_timeout_ms 30000
zeroclaw config set plugins.limits.max_memory_mb 256

plugins.limits.max_connections_per_instance(デフォルトは16)は、1つの論理プラグインインスタンスが同時に保持できる送信接続の数の上限を定めます。この上限はインスタンス単位であり、呼び出し単位ではありません。レスポンスは、ゲストがボディを読み切るか、レスポンスを破棄するまで接続を保持するため、1回の呼び出し内で16個のレスポンスを生かしたままにするツールは、17本目の接続を開くことはできません。各レスポンスを順次読み切るか破棄していくシーケンシャルなリクエストであれば、この上限に近づくことはありません。上限に達すると、ゲストには送信拒否ではなく、wasi:http自身のconnection-limit-reachedエラーが返されます。宛先への許可は与えられており、ホストは自身がカウントしているリソースについて報告しているためです。

plugins.enabled = true はプラグインホストを有効にしますが、自動検出されたツールおよびスキルの機能が読み込まれるのは、plugins.auto_discover = true も設定されている場合だけです。そのフラグはデフォルトで false(フェイルクローズ)なので、enabled = true だけでは、[channels.plugin.<alias>] の下で宣言したチャンネルが利用できるだけで、プラグインのツールやスキルは利用できません。つまり、ツールまたはスキルのパッケージは list と info を正常に実行できても、実行時には何も提供しないことがあります。明示的なチャンネルバインディングは自動検出ではなくオペレーターが名前を指定するものなので、auto_discover は必要ありません。このフラグが制御するのは、自動検出されたツールとスキルだけです。

インスタンスごとの設定は plugins.entries 配下に保存されます。キーは、ホストが管理するパッケージ、ケイパビリティ、バインディングの識別情報から導出された、バージョン付きの zpi1_… 文字列です。インストール時には、パッケージのデフォルトのツールバインディング用のキーが表示され、そのキーの行が初期化されます。チャンネルインスタンスのバインディングは、作成されたパッケージ名のバインディングではなく、設定されたエイリアスそのものです。そのため、チャンネルのキーはエイリアスがバインドされて初めて存在します。zeroclaw plugin bind <package> --channel-alias <alias> または zeroclaw plugin install <source> --channel-alias <alias> を実行すると、[channels.plugin.<alias>] のバインディングが作成され、そのインスタンスの行が初期化されます(チャンネルインスタンスのバインド を参照)。デーモンは、明示的に宣言された [channels.plugin.<alias>] インスタンスの型付き設定を、同じキーを通じて解決します。これは #10146 で導入されたランタイムの経路です。zeroclaw plugin info <package> は、パッケージが所有するすべてのキーを表示します。これには、ツールバインディングのキーに加え、インストールされたバージョンがインスタンス状態を持ち、そのインスタンスをエイリアスで指定できる場合には、バインドされたエイリアスごとのキーも含まれます。指定可能なインスタンスのバインド済みエイリアスの準備状態レポートも、どちらの場合でも同様に得られます。完全な識別情報を含むキーにより、異なるパッケージやケイパビリティの世界が、資格情報を共有することなく main のようなエイリアスを安全に再利用できます。

正規の運用者向け値は、secret とマークされた文字列マップであり、保存時は暗号化されたまま(enc2:…)です。config_read を要求するプラグインは、config_schema でマップの単一の型契約を宣言します。これは閉じた Draft 2020-12 オブジェクトであり、そのトップレベルのプロパティは string、boolean、integer、number、array、object のいずれかを明示的に使用します。ツールまたはチャンネルのコンシューマは、トップレベルの文字列プロパティに x-secret = true を設定できます。ホストは、オブジェクト全体を用いてこれを検証し、公開設定から取り除いたうえで、許可されたインスタンスの secrets.get インポートを通じてのみ利用可能にします。ツールは execute 中に secret を読み取れます。チャンネルは configure および運用時の呼び出し中に、config.get で公開設定を、secrets.get で secret を取得します。有効な config_read の権限がない場合、どちらのインポートも access-denied を返します。インスタンス化、静的メタデータの検出、解決の失敗、ホスト呼び出しのバジェット超過は unavailable を返します。

値は、文字列はそのまま保存し、真偽値と数値は JSON のスカラーテキストとして、配列とオブジェクトは JSON テキストとして保存します。ホストは、結果の型付きオブジェクトを生成して検証してから、ツールまたはチャンネルのゲストコードで使用します。不明な値、形式が不正な値、範囲外の値は、プラグインに渡される前に失敗します。

メモリプラグインにはまだ設定インポートがないため、その ABI が追加されるまでは config_read を要求してはなりません。

インスタンスの config マップ内のすべての値は、スキーマの内容にかかわらず保存時に暗号化されます。zeroclaw config set plugins.entries.<key>.config.<name> は、x-secret の値に対してマスク入力を求め、--no-interactive が指定されていない限り、コマンドラインからその値を受け付けません。インストール済みパッケージが x-secret を指定せずに宣言しているプロパティは、コマンドラインで指定された値を受け取り、指定がない場合はエコー表示ありで入力を求めます。config set がインストール済みパッケージのスキーマと照合できない名前(例えば、パッケージがインストールされていない行など)は、シークレットとして扱われます。

ツールコンポーネントとチャネルコンポーネントは、state インポートに対して state_read および/または state_write を要求できます。許可されたパッケージ、ケイパビリティ、バインディングの名前空間は、ゲストではなくホストが提供します。ポータブルな論理キーは、そのインスタンス内で認証済みのバイト値を指し示します。読み取りはリビジョンを返し、作成、置換、削除の各操作では正確な compare-and-swap リビジョンを使用します。永続行は、data/plugin-state.db 内のキー付きブラインドインデックスの背後に enc2: 暗号文として保存され、インストール時の .secret_key を使用します。固定のクォータ、およびキー、ストレージ、整合性に関する障害はいずれもフェイルクローズします。

状態はインスタンスの識別情報(パッケージ名、capability、バインディング)に紐づき、これは設定エントリが使うのと同じ識別情報です。公開者には紐づきません。zeroclaw plugin update はどちらも保持し、zeroclaw plugin remove も同様です。そのため、アップグレードは更新でも、削除後の再インストールでも状態を維持します。また、後から同じ名前で別のパッケージをインストールすると、そのインスタンスの状態と設定済みのシークレットを引き継ぎます。削除時には、そのパッケージを指す [channels.plugin.<alias>] のバインディングもすべて保持されるため、新しく入ったパッケージもそれらのエイリアスとその行に紐づけられます。plugin remove は、残されたバインディングと、まだ egress を許可している保持済みの各行を出力し、何を削除すべきかを示します。インストールされたバージョンがチャネルを提供する場合は、バインドされたすべてのエイリアスのキーの下にある行を、そのバージョンが所有するその他のものとともに削除します。インストールされたバージョンがインスタンス状態を所有する場合は、デフォルトのツールバインディングの行も削除します。無関係なプラグインを削除済みプラグインの名前でインストールする前に、[[plugins.entries]] の行とそれらのバインディングを削除してください。また、その状態は新しいパッケージから読み取り可能であるものとして扱ってください。

1.0 より前のプラグイン作成者は明示的に移行する必要があります。config_read を要求しながら config_schema を指定しないマニフェストは、今後検出されません。現在の値に一致する閉じたスキーマを追加し、文字列マップではなく型付きJSONをデシリアライズするようツール/チャネルのゲストを更新して、スキーマも署名の対象に含まれるため再ビルドして再署名します。ホスト統合では、所有する設定マップの代わりに、PluginConfigResolver をラップする PluginHostServices が注入されます。認可された各ツールまたはチャネルのフレームでは、スコープにバインドされた ResolvedPluginConfig を最大1つ実体化し、フレーム内のすべての設定読み取りでそのビューを使用して、フレーム終了時に破棄します。チャネルは使用時点で config.get と secrets.get を呼び出すため、同じ論理バインディング内で公開設定と認証情報のローテーションが行われた場合、次の操作では1つのリビジョンとして見えます。静的な識別情報とケイパビリティのエクスポートはロード時に一度だけ読み取られます。ボット/アカウントの識別情報やその他の静的メタデータを変更するには、チャネルのライフサイクルを再構築する必要があります。型付き設定への移行は、手順を追ったガイドであり、互換性シムなしでこの強制を提供するというリリース判断も含まれています。

これは厳格な 1.0 以前のキー形式です。パッケージ名またはバインディング名だけで命名されたレガシーエントリは参照されません。既存のツールパッケージの場合は、zeroclaw plugin info <package> を実行して完全なインスタンスキーを取得し、古いエントリの名前をそのキーに変更して、設定を保存してください。新規にツールをインストールすると、自動的に設定されます。

実効権限は、マニフェストの要求とは別にチェックされます。config_read が拒否されると、ホストは空のオブジェクトを検証します。必須プロパティがあると、起動はフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config キーを省略し、チャネル設定/シークレットのインポートは access-denied を返します。ホストの正式なフィールド一覧とデフォルト値については、Config リファレンスを参照してください。zeroclaw config list には、現在保存されている値が表示されます。

Egressの宣言と許可

プラグインのネットワーク到達範囲は 2 つの別個の事実から成り、そのうちあなたが管理するのは一方だけです:

  • 宣言とは、マニフェストの [egress] テーブル(hosts = [...])のことで、作成者がプラグインに必要だと宣言する接続先を指定します。これはマニフェストの正規バイト列の一部であるため、署名されたマニフェストはこれを対象に含みます。ただし、宣言によって何かが許可されるわけではありません。独自の [egress] テーブルを書き込んだ未署名のコンポーネントも、依然としてどこにも到達できません。
  • grantは、インスタンス自身のzpi1_…行にあるplugins.entries.<instance-key>.egress_hostsです。その隣には、より狭い範囲を対象とするegress_allow_privateの例外設定があります。これはホストが強制するリストで、リクエストごとにライブ設定から読み込まれるため、編集はプラグインを再起動しなくても適用されます。

On this release the grant alone governs. The host checks a destination against egress_hosts and does not additionally require it to appear in the manifest, so a declaration neither grants a host nor bounds a grant you authored. The declaration’s job here is to seed and to diff, described below. Narrowing effective reach to the intersection of declaration and grant is later rollout work tracked in #8850.

egress_hosts が空の場合がデフォルトで、ネットワークへの到達を一切許可しないことを意味します。http_client などのトランスポート権限はサーフェスへのアクセスを付与し、このフィールドは到達先を付与します。エントリは完全一致のホスト(api.example.com、10.0.0.5)または明示的なサフィックスパターン(*.example.com)です。サフィックスパターンはサブドメインにマッチしますが、apex(素のドメイン)にはマッチしないため、必要な場合は apex を別途記載してください。エントリにポートは含まれません。ホストを付与すると、そのホスト上のすべてのポートが許可されます。* で「どこでも」を表す指定はありません。

egress_hosts は、暗号化された config マップの中のキーではなく、その兄弟として平文で配置されています。許可リストは運用者が監査する対象であるため、監査対象のファイル内で読める状態を保ち、その隣にあるシークレットは enc2:… のまま暗号化されています。

install と list の機能

zeroclaw plugin install は、ユーザーが何も入力しなくても、パッケージのデフォルトのツールバインディングについて両側が照合される唯一のタイミングです(チャネルインスタンスの行は、そのエイリアスをバインドする際に作成され、grant の判断はコマンドラインで明示します。詳しくは チャネルインスタンスのバインド を参照してください):

  • 作成する行について、宣言をegress_hostsにシードし、許可した各宣言先を表示し、後で許可を編集するzeroclaw config setコマンドを表示します。宣言は、マニフェストがそれを利用できるトランスポートを要求している場合にのみ有効とみなされます。そのトランスポートはhttp_client、websocket_client、socket_clientのいずれかです。いずれも要求されていない宣言はシードされません。後のバージョンでトランスポートが追加された場合は既存の行が対象になりますが、その行が拡張されることはありません。
  • 既存の行(アップグレード、再インストール、または自分で作成した行)の場合、何も変更されません。代わりに差分を表示します。マニフェストで宣言されているが行で許可されていない宛先、行で許可されているがマニフェストではもう宣言されていない宛先、そして追加を適用する正確なコマンドです。そのため、パッケージの更新によって自身のネットワーク到達範囲が広がることはなく、差分は意図的に適用する必要があります。
  • サポートされていないpre-1.0のパッケージ名行を検出した再インストールは、正規の行を作成する前に拒否され、plugin list と同じ順序付きの更新手順を表示します。plugin list と同様に、デプロイ全体にかかわる拒否(不正な security.nat64_prefixes、plugins.limits.max_connections_per_instance がゼロ)は、単独で一度だけ報告され、修正されるまで有効にならない行の手順は表示しません。失敗したインストールは、何かを通知する前にロールバックされ、古い config、egress_hosts、egress_allow_private の値は、ベータ設定を更新して再試行するまで変更されないままです。

表示されるコマンドには、既存の許可設定と宣言内容の和集合が含まれています。zeroclaw config set はリストに追記するのではなく置き換えるため、このようにしています。表示どおりに実行すると、宣言された宛先が追加され、ご自身で記述したホストが削除されることはありません。

出力されるすべてのコマンドは、計算元となった設定ディレクトリを示す zeroclaw --config-dir '<dir>' で始まります。--config-dir(およびそれが設定する ZEROCLAW_CONFIG_DIR)は、渡されたプロセスにのみ影響するため、zeroclaw --config-dir /srv/a plugin list からコピーしたコマンドは、そうしなければシェルがデフォルトで解決する設定に対して実行されてしまいます。zpi1_… の行キーはパッケージ、機能、バインディングを示しますが、プロファイルは示しません。そのため、このコマンドは別のプロファイルの許可リストを、このプロファイルから計算したリストで置き換えてしまいます。ディレクトリ、ホストリスト、およびコマンドが持つその他すべての引数(スキーマのプロパティを指定する設定パス、パッケージ、エイリアス)は、それぞれ1つのリテラル引数としてクォートされているため、マニフェストで宣言された内容がシェルによって展開または置換されることはありません。出力されたとおりにコマンドを貼り付けてください。

唯一の例外は、送信先を宣言していないパッケージへの許可です。この場合、行の egress_hosts に値のない config set --no-interactive コマンドとして出力されます。デプロイで使用するホストを、カンマ区切りの1つの値として末尾に追加してください。出力どおりに実行すると、値がないため拒否され、何も書き込まれません。

出力された plugin bind コマンドも同様に完全な形です。エイリアスを自分で選ぶ必要がある場合、コマンドにはその位置に ALIAS という語が含まれます。シェルはこの語をそのまま渡し、エイリアスは小文字でなければならないため、コマンドは無効なエイリアスとして拒否し、選んだエイリアスに置き換えるまで何も書き込みません。

インスタンスの行を作成する際に送信(egress)の判断が必要な場合は、2つのコマンドが出力されます。最初は --egress none で終わるもの、次は --egress declared で終わるものです。どちらか決めた方を実行してください。両方を一度に貼り付けると、2つ目のコマンドは最初のコマンドが作成した行を見つけてしまうため、インスタンスはネットワークに到達できない状態のままになります。既存の行が拡張されることはないためです。

引用符は、コマンドが出力されたプラットフォームのシェルに従います。Linux と macOS では POSIX のシングルクォート形式(sh、bash、zsh、fish)を使い、値に含まれる引用符は '\'' と書きます。Windows では、ZeroClaw は cmd.exe と PowerShell のどちらを使っているか判別できないため、両方がそのまま受け付ける形式、つまり各値をダブルクォートで囲んだ形式を出力します。これは、値に両方のシェルがダブルクォート内で展開する文字が含まれていなければ、どちらのシェルにも正しく貼り付けられます。スペースを含む通常のホストリストやプロファイルパスは、常にこの条件を満たします。値が条件を満たさない場合("、%、!、$、バッククォートを含む、またはバックスラッシュで終わる場合。悪意のあるマニフェストが宣言しそうな $(id).example.com もこの例です)、行全体の代わりに、マーカー # PowerShell only, cmd.exe cannot pass this value literally: に続けて PowerShell 形式のコマンドを出力します。PowerShell 形式では、値に含まれる引用符は '' と二重にします。この行をまるごと貼り付けても、どちらのシェルでも何も実行されません(cmd.exe は # を実行できず、PowerShell はそれをコメントとして読み取ります)。マーカーの後のコマンドだけを PowerShell にコピーして実行してください。cmd.exe には、そのような値をそのまま渡せる引用方法がありません(%name% はダブルクォート内でも展開され、シングルクォートは通常の文字として扱われます)。そのため、cmd.exe にはこの種の値を決して信頼して渡しません。

zeroclaw plugin list は、インストール済みプラグインのうち http_client、websocket_client、socket_client のいずれかを保持するものすべてについて、同じ比較を常設の診断として1インスタンスずつ繰り返します。ツールプラグインのデフォルトのツールバインディングはパッケージ名として出力され、バインドされた各チャネルエイリアスは package (plugin.<alias>) として出力されます。各インスタンスについて、そのプラグインが宣言しているものの行で許可されていない宛先を1行で示します。そこへのリクエストは拒否されるためです。併せて、そのギャップを解消するコマンドも示します。まだ行が存在しないインスタンスの場合、そのコマンドは許可付きの行を作成します。

設定エイリアス文法が拒否するエイリアスを持ち、まだ行が存在しないチャネルインスタンス(手書きの [channels.plugin.ops-team] など)については、文法による理由と、代わりにリネームすべきテーブルを示します。行を作成するコマンドは設定全体を検証するため、そのエイリアスを拒否してしまうからです。

すべてのインスタンスについて、送信(egress)権限が管理するすべてのトランスポートを対象にします。ソケットやWebSocketのみで通信するプラグイン(IRCやMQTTなど)が報告されないまま残ることを防ぐためです。逆方向は指摘しません。宣言に対応しない許可は一級の経路であり、指摘事項ではありません。宛先がデプロイ設定で決まるプラグイン(セルフホストのGiteaやLAN上のNextcloudなど)は、そのホストを作者が宣言できないため、ユーザー側で宣言を作成します。

インスタンスの行がまだ 1.0 より前のキー(zpi1_… キーではなくパッケージ名)を保持している場合、ランタイムはグラントを zpi1_… キーのみで解決するため、plugin list は上記のリネームを必ず表示します。行が付与している内容は有効にならず、リネームされるまでリクエストは拒否されます。行のグラントがマニフェストで宣言されているすべてをすでにカバーしている場合、リネームだけが必要な手順です。現在の正しいリストを置き換えるだけになるため、zeroclaw config set は提示されません。宣言が行に付与されていない宛先をまだ指定している場合、リネームが手順 1、グラントコマンドが手順 2 となります。このコマンドは行の既存のグラントを引き継ぐため、お客様が設定した内容は何も取り消されません。ドットで区切られた plugins.entries.<key>.… パスは、ライブ設定にすでに存在する行しか解決されないため、リネーム前にグラントコマンドを実行すると Unknown property で失敗します。表示された手順を表示された順序で適用すれば機能します。plugin list はこの状態を診断するだけで、設定ファイルを自分で編集することはありません。この状態になりうるのはデフォルトのツールバインディングの行だけです。チャネルインスタンスは 1.0 より前のキー形式より後に導入されたため、リネームの対象としてチャネルの行が報告されることはありません。

plugin list は、ランタイムがリクエスト時に使用するものと同じポリシーコンストラクタで行を判定するため、その判定結果はランタイムの判定と一致します。ランタイムがその行を拒否する場合(*.com のような単一ラベルのワイルドカード、前後に空白を含むエントリ、許可された任意のホストがカバーしていない egress_allow_private の例外指定)、設定ローダーは警告を出すだけですが、ランタイムは許可リスト全体を拒否し、すべてのリクエストを拒否します。plugin list はランタイムの理由を表示し、ランタイムが受け入れるエントリのみから構成された付与コマンドを表示します。1.0未満の行では、このコマンドに名称変更が反映されます。ランタイムが拒否する行については、名称変更だけが提示されることはありません。表示されるコマンドが置き換えるのは egress_hosts のみです。修正後も私的な例外指定が拒否される場合、レポートはその旨を示し、その行の egress_allow_private のパスを挙げます。これは手作業での修正が必要です。行そのものに関係しない拒否理由、つまり不正な security.nat64_prefixes やゼロの plugins.limits.max_connections_per_instance は、すべてのプラグインのポリシーを同様に拒否します。plugin list はこれを一度だけ報告し、その2つのパスを挙げ、修正されるまでプラグインごとの行は表示せず、プラグインごとの付与修復を提示することは決してありません。plugin install と plugin bind は、既存の行に対して同じ判定を同じ文言で報告するため、3つのコマンドが付与を利用可能かどうかについて食い違うことはありません。

エイリアスがバインドされていないチャネルパッケージにはまだインスタンスがないため、plugin list はそのパッケージのチャネル行を報告しません。比較対象となる行は存在せず、install が行を作り出すこともありません。

チャネルインスタンスのバインド

チャネルパッケージは、[channels.plugin.<alias>] テーブルでその名前が指定されるまでインスタンスが存在しません。エイリアスは利用者が自由に選べるため、パッケージをインストールしただけではチャネルインスタンスの行は作成されません。エイリアスを紐付ける手順によって初めて作成されます:

zeroclaw plugin bind acme.chat --channel-alias operations --egress declared

plugin bind は、インストール済みのパッケージに対して動作します。また、2つ目のエイリアスを追加する方法であり、部分的なバインディングを修復する方法でもあります。zeroclaw plugin install <source> --channel-alias <alias> は、インストールの一部として同じ手順を実行します。さらに、同じ --egress フラグを受け付けますが、install では --channel-alias と一緒に指定した場合にのみ受け付けられます。このフラグが制御するのはチャンネルインスタンスの行だけです。同じパッケージのツール行は、これまでどおり install 自身のルールによってシードされます。ここでの拒否はすべて、パッケージが公開される前に発生します。ツール行、バインディング、チャンネル行は1回の保存でまとめて書き込まれ、それらの書き込み中に失敗が起きた場合はパッケージがロールバックされます。--channel-alias を指定せずにチャンネルパッケージをインストールした場合は、従来どおり公開されます。plugin remove の後に残るなどして、すでにバインディングがそのパッケージを参照している場合は、後述する各エイリアスの準備状況レポートを表示します。そうでない場合は、実行すべき plugin bind コマンドを表示します。その際、選択したエイリアスの代わりに ALIAS を使用します。マニフェストがチャンネルから到達可能な宛先を宣言している場合は、それらの宛先を列挙し、エグレス判定ごとに1回ずつそのコマンドを表示します。最初は --egress none で終わり、次に --egress declared で終わるものです。この手順は決してプロンプトを表示しません。すべての判定はフラグで指定します。

コマンドは次の場合に実行を拒否し、何も書き込みません:

  • 設定ファイルが古いスキーマバージョンです。zeroclaw config set には要件があるため、先に zeroclaw config migrate を実行してください。保存すると、まだ古い形式のセクションに現在のバージョンが書き込まれてしまうためです。
  • パッケージはチャンネルを提供していません;
  • 別名は、1〜63文字の小文字の英字・数字、および単一のアンダースコアで構成され、先頭と末尾は英字または数字である必要があります;
  • the エイリアスは既に別のパッケージにバインドされており、その拒否理由に記載されています。別のバインディングのpackageは書き換えられることはありません。
  • 設定ファイル、またはその [plugins]、[channels]、[channels.plugin]、[channels.plugin.<alias>] セクションが不正な形式のため読み込まれていません。このコマンドは、ローダーが読み込まなかったセクションを上書きすることは決してないためです。

それ以外の場合は、package を用い、既定値の enabled = true を記述したバインディングを作成します。ただし、パッケージ名が完全に一致する既存のバインディングがあれば、enabled = false を含めてそのまま保持します。続いて、インスタンスの [[plugins.entries]] 行がまだ存在せず、かつインスタンスがホストによって管理される状態を保持する必要がある場合には、その行を作成します。保持が必要な状態とは、config_schema、宣言された接続先、または egress authority が管理するトランスポート(http_client、websocket_client、socket_client のいずれか)です。

Creating that row from a declaration the instance can use takes a decision. When the manifest declares destinations and the package holds a governed transport:

  • --egress declared は宣言内容を egress_hosts に書き込み、各宛先を、後で権限付与を編集するコマンドとともに表示します。これは、ツール行に対して install が出力するものと同じ表示です。
  • --egress none を指定すると空の許可設定で行が作成されるため、インスタンスはネットワークに到達できなくなります。また、宣言された宛先と、後からそれらを許可するためのコマンドが表示されます。
  • フラグをどちらも指定しない場合、コマンドは実行を拒否し、宣言されている出力先を一覧表示したうえで、両方のフラグの名前を示します。何も書き込まれません。

そのような宣言がない場合は判断の必要はありません。行は空の権限付与で作成されます。パッケージが管理対象のトランスポートを保持しているのに宛先を宣言していない場合は、コマンドが、行の egress_hosts に値を入れていない config set --no-interactive コマンドを出力します。デプロイで使用するホストを、カンマ区切りの1つの値として入力して補完してください。出力された状態のままでは拒否され、何も書き込まれません。

このフラグが適用されるのは、コマンドが作成する行のみです。既に存在する行(以前の実行で作成されたもの、ユーザーが書き込んだもの、または削除されたパッケージが残したもの)は決して拡張されません。コマンドは、install が既存の行に対して表示するのと同じ差分を表示し、--egress を渡した場合は、それが適用されなかったことを注記します。したがって、常に --egress declared を渡すスクリプトでは、権限を拡大することはできません。各エイリアスの行はマニフェストのみから初期化され、他のエイリアスの行やパッケージのツール行からコピーされることは決してありません。また、egress_allow_private が書き込まれることも決してありません。同じ引数での2回目の実行では、設定ファイルはバイト単位で同一のまま残ります。対応する行のないバインディング、またはバインディングのない行を検出した実行は、もう一方に触れることなく、欠けている側のみを作成します。

バインド後、コマンドはインスタンスが起動できるかどうかを報告します。各回答は、ランタイムが使用するソースから読み取ります:

  1. インスタンスが保持する権限で、そのマニフェストが要求するものです。ここに表示されるのは確認用であり、決定を行うものではありません。決定するのは egress allowlist の付与です。
  2. スキーマで required として指定されている各キーについて、設定済みか未設定かを表示します。未設定のキーには、そのキーを設定する config set plugins.entries.<key>.config.<name> コマンドを併記します。シークレットキーのコマンドは、値をエコーせずに入力を求めるプロンプトを表示します。ポータブルなキー文法の外にあるプロパティ名にはコマンドを表示せず、レポートでは設定ファイル内で編集すべき行を示します。行がまだ存在しないインスタンス(手書きのバインディングなど)には、キーごとのコマンドの代わりに、その行を作成する plugin bind コマンドを表示します。config set は既存の行しか解決しないためです。マニフェストで行が使用できる送信先が宣言されている場合、レポートはそれらを明示し、送信判断(egress decision)ごとに 1 回だけそのコマンドを表示します。最初は --egress none で終わるもの、次に --egress declared で終わるものを表示し、そのうち決定したほうを実行できるようにします。[channels.plugin.ops-team] のように、plugin bind が拒否する手書きのバインディングのエイリアスについては、エイリアス文法による理由と、名前を変更すべきテーブルを表示します。値は一切表示されません。
  3. ランタイムの設定リゾルバーによる、その行に対する判定です。リゾルバーが報告する場合は、失敗するスキーマのパスまたはプロパティを示し、値は決して示しません。plugin remove によって残された行で、再インストールされたマニフェストが config_schema を宣言していない場合も含まれます。その場合、ランタイムはその行のあらゆる値を拒否し、レポートはキーで行を指定したうえで、適切な対処法を示します。インスタンスが他の状態を所有していない場合は、行を削除します。行がインスタンスの egress grant を保持している場合(マニフェストが宛先を宣言している、またはガバナンス対象のトランスポートを要求している場合)は、config マップ配下の値だけを削除し、行は残します。
  4. 行が存在するインスタンスについては、plugin list が使う表現で egress のギャップを示します。まだ行がないインスタンスには、ここではギャップの行を表示しません。項目2の plugin bind コマンドが、あなたの egress の決定を使って行を作成します。一方、plugin list は、行が許可済みの状態で作成される config patch による修復を表示します。plugin bind と plugin install --channel-alias は、そのコマンドで行の許可状態をすでに報告しているため、ギャップを省略します。
  5. アクティベーション計画そのものの判定であり、ランタイムが最初に満たされていないと判断した前提条件を示します。順に次を確認します。plugins.enabled、バインディングの enabled、channels に plugin.<alias> を含む有効なエージェント、パッケージがインストールされてチャネルを提供していること、そして plugins.max_active_instances の下に空きスロットがあることです。プラグインシステムが無効な場合は、それを有効にするコマンドが併せて表示されます。設定のエイリアス文法で受け付けられるエイリアスを持つ無効なバインディングも同様です。それ以外のエイリアス、たとえば手書きの [channels.plugin."a.b"] には、コマンドは表示されません。レポートでは、手動で enabled = true を設定すべきテーブルが示されます。このような点を含むエイリアスでは、config set を使うと別のバインディングが作成されてしまうためです。所有者が存在しない場合は、コマンドを示さず説明のみを表示します。エージェントの channels リストに対して config set を実行すると、リスト全体が置き換えられてしまうためです。すべての前提条件が満たされると、計画はそのインスタンスを受け入れ、コンポーネントが読み込まれる場合は次のデーモン起動またはリロード時に開始されます。ランタイムがアクティベーション計画を構築できない場合は、レポートにその旨とランタイムのエラーが表示されます。
  6. 実行中のデーモンは、再起動またはリロードの後にのみインスタンスを起動します。

この儀式が plugins.enabled の値を切り替えることはなく、エージェントの channels を編集することもありません。どちらも引き続きユーザーが決定します。バインドが成功した場合、インスタンスがまだ起動できない状態でも終了コードは0になります。終了コードが0以外になるのは、拒否された場合か書き込みに失敗した場合のみです。zeroclaw plugin info <package> は、バインド済みの各エイリアスのキーの後に同じレポートを出力します。チャンネルパッケージにバインド済みのエイリアスがない場合は、install が表示するのと同じ plugin bind のヒントを出力します。

制御文字を含むなど、どのプラグインインスタンスからも名前で指定できないエイリアスのバインディングは、設定の検証で警告が出るだけで、同じパッケージの他のインスタンスの報告を妨げません。plugin list、plugin info、plugin install、plugin remove は、そのバインディングをスキップしたことを伝える1行を出力します。この行ではエイリアスをエスケープして表示し、名前を変更または削除すべき [channels.plugin.<alias>] テーブルを示します。

Quickstartからインストール

プラグインホストを含むビルドでは、CLI Quickstart の任意の Plugins 行からツールプラグインをインストールすることもできます。手順は クイックスタート → プラグイン で説明しています。Quickstart は zeroclaw plugin install と同じダウンロード、受け入れ、ロードチェック、公開とシードのトランザクションを実行し、上記のルールも引き続き適用されます:

  • 宣言と付与の違い。 クイックスタートでは、マニフェストの [egress] 宣言を表示しますが、それ自体では何も付与されません。ネットワークトランスポートを要求し接続先を宣言しているパッケージの行を作成する場合、作成される行に設定される唯一の宣言は、その接続先を付与するかどうかをユーザーが選択できるというものです。これは、パッケージが新規であるか、行なしで既にインストールされていたかに関係ありません。この質問は、ネットワークアクセスなしでインストールする場合に始まります。その場合 egress_hosts は空のままとなり、宣言された接続先を後で付与するコマンドを表示します。チャネルインスタンスに対する plugin bind --egress none と同様です。既にインストールされているパッケージをスキップする場合は、その行は作成されず、空の付与で行を作成するコマンドを表示した後、宣言された接続先を付与するコマンドを別の行に表示します。クイックスタートが欠落した行を作成するために表示するコマンドは、ネットワークアクセスを決して付与しません。付与は常に独立したコマンドです。ロードチェックに失敗したプラグインに対してクイックスタートが表示する plugin install --no-verify コマンドは、プラグインCLIのインストールです。これは、作成する行に対して、マニフェストが http_client、websocket_client、または socket_client に対して宣言した接続先を設定します。
  • 既存の行は拡張されない。 インスタンスの [[plugins.entries]] 行がすでに存在する場合、Quickstart は egress_hosts、egress_allow_private、および設定をそのまま残す。すでにインストール済みのパッケージをダウンロード、アップグレード、再設定することは決してなく、そのパッケージの行が存在しない場合にのみ行を作成する。
  • アクティベーションには同意が必要です。 Quickstart は、確認なしに plugins.enabled や plugins.auto_discover を有効にすることはありません。確認を行う前に、それらの設定によって有効化されるすべてのツール、スキル、チャネルプラグインを、以前にインストールされたものも含めて一覧表示します。この一覧が、今回の実行でインストールまたは保持したプラグインを超える場合、確認の既定値は「いいえ」になります。不足行をスキップしたプラグイン、または Quickstart がセットアップを完了できなかったプラグインは、この一覧に含まれません。一覧は、デーモンが読み込む際と同じようにプラグインディレクトリから取得されます。そのディレクトリを読み取れない場合も、確認の既定値は「いいえ」になります。

Quickstart は plugin install よりも次の3点で厳格です。レジストリのエントリにアーカイブの sha256 ダイジェストがない場合は、検証できないアーカイブをインストールせず、拒否します。設定ファイルの [plugins] セクションを読み取れない間は、何もインストールしません。一方 plugin install は、パッケージはインストールしたうえで、そのパッケージの [[plugins.entries]] 行を作成できなかったという警告を出すだけです。さらに、plugins.security.signature_mode の値にかかわらず、パッケージの概要には、マニフェストの署名が plugins.security.trusted_publisher_keys に対して検証できるかどうかが表示されます。署名が検証できないパッケージは、デフォルトが no の確認に yes と答えた場合にのみインストールされます。ただし、受け入れ時に何を拒否するかは、引き続き signature_mode が決めます。

プラグインの更新

zeroclaw plugin update replaces an installed package with another version of the same package and keeps everything that belongs to its instances:

レジストリが各名前付きプラグインに対して選択するバージョン
zeroclaw plugin update my-plugin other-plugin

# 正確なバージョン、インストール済みのものより新しいもの、または古いもの
zeroclaw plugin update my-plugin@0.2.0

每つのインストール済みプラグイン
zeroclaw plugin update --all

# ローカルのパッケージディレクトリ(例: 再ビルドしたプラグイン)
zeroclaw plugin update my-plugin --from ./my-plugin/

--registry <url> は plugin install と同様にレジストリを選択します。update はインストール済みのパッケージのみを置き換えます。未知の名前の場合は plugin install への案内を伴って失敗し、インストール済みのパッケージに対して plugin install を実行した場合は plugin update への案内を伴って失敗します。パッケージは名前のみで照合されるため、--all はディレクトリからインストールしたものを含め、インストール済みのすべてのパッケージについてレジストリに問い合わせます。ローカルビルドを実行する場合は、プラグインを明示的に名前で指定してください。update コマンドは、レジストリのライフサイクルの一部であり、#7432 (R3) で追跡されています。

バージョンの選択。 バージョン指定のない名前では plugin install が選ぶバージョン、つまりレジストリがその名前について最後に列挙したエントリが使われ、name@version では指定したエントリそのものが使われます。ZeroClaw はバージョンを並べ替えて選ぶことをしないため、選択されたバージョンがインストール済みのものと異なる場合は、古いバージョンであってもインストールされます。両方がセマンティックバージョンで、新しいほうが古い場合は、結果の行でアップデートとは報告せず、その旨を示します。以前使っていたバージョンを指定することが、元のバージョンに戻す方法です。選択されたバージョンがインストール済みのものと同じ場合は、プラグインは最新であると報告され、何もダウンロードされません。--from パッケージは、同じバージョンであっても常にインストール済みのものを置き換えるため、バージョンを上げなくても再ビルドしたプラグインを再インストールできます。

検証。 置き換えは、インストール済みのパッケージまたは設定が変更される前に、新規インストールが行うチェックに合格する必要があります。これには、レジストリエントリがダイジェストを記載している場合のアーカイブダイジェスト、マニフェストの名前とバージョンのレジストリエントリとの照合、署名ポリシー、ペイロードダイジェスト、設定スキーマ、そしてplugins.limitsを使用したこのホストのWIT worldに対するロードチェックが含まれます。updateには--no-verifyはありません。ロードできないパッケージをインストールするには、プラグインを削除してから--no-verifyを付けてインストールしてください。

追加された権限: ホストは、プラグインのマニフェストが要求するすべての権限を付与します。そのため、新しいマニフェストが、インストール済みバージョンにない権限や機能を要求する場合、新しい provides チャネルを宣言する場合、またはインストール済みバージョンと異なる公開者キーを指定する場合(インストール済みバージョンがキーを指定していた場合、またはキーを指定していなかった場合も含む)は、コンポーネントのコンパイル前に更新が拒否されます。キーが公開者を特定するのは plugins.security.signature_mode が strict の場合のみで、この場合はキーが検証されます。permissive および disabled モードでは、キーは未検証の主張にすぎず、インストール済みバージョンがキーを指定していなかった場合に新たに現れたキーは変更として扱われません。拒否時には、新バージョンが追加する内容と、確認済みのバージョンに固定された、その追加内容だけを正確に受け入れるコマンドが表示されます。

'my-plugin' 0.2.0 requests authority the installed version does not have:
  + permission:http_client
Nothing was changed. To accept it and update, run: zeroclaw --config-dir '/home/user/.zeroclaw' plugin update 'my-plugin@0.2.0' --allow-token 'permission:http_client'

--allow-token は1つのフラグにつき完全な項目を1つ受け取り、繰り返し指定できます: permission:<name>、capability:<name>、provides:<id>、または publisher:<key>(キー名のないパッケージの場合は publisher:unsigned)。--allow は同じ項目をカンマ区切りのリストとして受け取ります。表示されるコマンドは --allow-token を使用するため、provides ID のようにカンマを含む項目もそのまま完全な形でコマンドに渡されます。どちらのフラグも一度に1つのプラグインにのみ適用されます。レジストリの更新の場合、そのプラグインは name@version として固定する必要があり、これにより承認するのは確認済みのバージョンになります。もう1つの方法は、プラグインを削除して新しいバージョンをインストールし直すことです。表示されるコマンド内のすべての値は、Declaring and granting egress に記載されたルールに従ってシェル向けに引用符で囲まれています。

アップデートで保持されるもの。 インスタンスの識別子はパッケージ名、capability、binding で決まり、バージョンでは決まりません。そのため、プラグインの [[plugins.entries]] 行、その設定値とシークレット、egress_hosts、egress_allow_private、TLS プロファイル、永続状態はそのまま引き継がれます。plugin install の egress ルールもそのまま適用されます。アップデートによって権限が拡大されることはなく、インスタンスが存在しなかったために新たに作成される行であっても同様です。アップデートは、新しい宣言と現在の権限の差分を表示し、それを適用するためのコマンドも併せて表示します。新バージョンの設定スキーマが、インスタンスに既にある設定を拒否する場合(例えば新たに必須になった設定がある場合)、アップデートは完了し、そのインスタンス名と問題を表示します。そのインスタンスは、設定が新バージョンに適合するまで読み込まれません。設定行が(パッケージ名にちなんだ)1.0 以前のキーをまだ使用しているパッケージは、その行の名前が変更されるまでアップデートされません。この場合の拒否では、plugin install と同じガイダンスが表示されます。

結果。 各プラグインには結果が1つ割り当てられます。更新済み、最新の状態、スキップ(--all 指定時にレジストリに掲載されていないパッケージ、または中断された更新によって退避されたままのパッケージ。後者は退避済みとして記載されます)、承認が必要、または未更新(理由と引き続きインストールされているバージョンを併記)のいずれかです。1つのプラグインが失敗しても、他のプラグインの処理は続行されます。いずれかのプラグインが未更新または承認が必要だった場合、コマンドは非ゼロで終了するため、スクリプトはその結果に基づいて分岐できます。

置換と復旧。 更新は、plugin install および plugin remove と同じ隠しロックを保持している間にのみプラグインディレクトリを変更するため、プロセスをまたいでも、これらの変更が重なることはありません。新しいパッケージは最初に検証され、その後 install が行うのと同様に、隠しステージングトランザクションへ全体が書き込まれます。その後にようやく、更新は既存のインストール済みディレクトリを独自の隠しトランザクションへ確保(claim)し、そのディレクトリが依然として更新の準備時に対象としたパッケージであること、すなわち同じマニフェストとコンポーネントを持つことを確認します。次に、新しいパッケージを何も上書きせずに配置先へ移動し、この移動が失敗した場合は、以前のパッケージを元に戻します。確保より前に失敗した場合、インストール済みパッケージは変更されません。更新開始後にディスク上で変更されたパッケージは元に戻され、更新は拒否されます。確保後、新しいパッケージを配置する前にプロセスが停止した場合、以前のパッケージはその隠しトランザクション内に残ります。検出処理はこれを無視し、そのパッケージに対するオペレーティングシステムのリースはプロセスの終了とともに解放されます。その後、plugin list は進行中のインストール、更新、または復旧の完了を待ってから、そのパッケージが退避された(displaced)状態であることを表示します。次の zeroclaw plugin update <name> は、他の処理より前に、レジストリへの問い合わせも含めて、まずそのパッケージを元に戻します。そのため、復旧はオフラインでも機能します。その後、更新は通常どおり進みます。ディレクトリからインストールしたパッケージの場合は、そのディレクトリを指定して --from を付けてください。付けないと、同名のレジストリパッケージで置き換えられる可能性があります。--all はそのコマンドにのみ作用します。したがって、退避されたパッケージは、バッチ実行中に更新によって退避されたものであっても、名前を指定しない限り元に戻されることはありません。置き換えられた旧バージョンは、削除される前にそのトランザクション内で削除対象としてマークされるため、削除できなかったものが元に戻されることはありません。警告にはその場所が示され、そのプラグインの次回の更新で削除されます。マークすら失敗した場合は、警告に示されたディレクトリを手動で削除してください。プラグインが削除された後では、zeroclaw plugin update <name> がそのコピーを元に戻してしまうためです。新しいパッケージを配置した後、旧バージョンをマークする前にプロセスが停止した場合も同様です。その旧バージョンは、そのプラグインの次回の更新で削除されます。ただし plugin remove の後では、plugin list が退避されたものとして表示し、名前を指定した更新によって元に戻されます。

ファイルは、plugin install の場合と同様に、リネームの前にディスクへフラッシュされていません。そのため、アップデート直後に電源が落ちると、新しいパッケージが不完全な状態で残る可能性があります。レジストリが選択するバージョンはすでに損傷したパッケージに割り当てられているため、再度アップデートしても修復されません。zeroclaw plugin info <name> でプラグインが読み込めないと表示された場合は、zeroclaw plugin remove <name> を実行してから再インストールしてください。設定の行と権限付与(grants)は削除後も保持されます。プラグインが一覧に表示されなくなった場合は、plugins_dir 配下にあるそのプラグインのディレクトリを手動で削除してから、再インストールしてください。

実行中のデーモン。 アップデートはインストール済みパッケージのみを変更し、実行中のデーモンには一切通知しません。アップデート後にデーモンが構築するツールセットは新しいバージョンを読み込みます。一方、チャネルインスタンスは、デーモンが再起動またはチャネルをリロードするまで、読み込み済みのコンポーネントを保持し続けます。すべてのインスタンスを新しいバージョンに移行するには、デーモンを再起動してください。入れ替えのまさにその瞬間に構築されたツールセットは、プラグインを取りこぼす可能性があります。また、マニフェストが wasm_sha256 を束縛していないパッケージの場合、古いマニフェストと新しいコンポーネントを組み合わせてしまうことがあります。次に構築されるツールセットは一貫した状態になります。

信頼境界が実際にどこにあるか

サンドボックスは読み込まれたプラグインが実行できる操作を制限します。署名ポリシーはそもそも何が読み込まれるかを制限します。どちらもオペレーターの判断によるもので、これらは組み合わせて機能します。

  • plugins.enabled が false(デフォルト)の場合、プラグインコードは一切実行されません。
  • plugins.auto_discover が false(デフォルト)の場合、自動検出されたツールおよびスキルの機能は読み込まれません。plugins.enabled = true だけでは、[channels.plugin.<alias>] 配下で宣言したチャネルのみが有効になります。ツールとスキルを読み込むには、auto_discover = true も必要です。
  • 署名 strict: マニフェストに信頼済みセットの鍵による有効な Ed25519 署名を持つコンポーネントのみが読み込まれます。
  • 読み込み済みプラグイン: fuel、メモリ上限、no-preopen WASI、およびパーミッションゲート付きインポートセットによって制限されています。

サンドボックスが制約しないのは、モデルが呼び出すことを選択したツールの意味的な挙動です。http_client 権限を持ち、ツールアダプターの HTTP サーフェスを使用するツールは、モデルが渡したものを、そのコードが決めた任意の場所に送信できます。署名ポリシーが存在するのは、「どのコードを読み込むか」が最も重要な判断だからです。意識して決めてください。

プラグインHTTPSの証明書信頼

HTTPS を使用するプラグインのリクエストは、同梱の webpki ルートプログラムと、このマシンが既に信頼しているルートに対して検証されます。そのため、エンタープライズ MDM ルートやプライベート PKI など、ローカルにインストールされた CA に連鎖する証明書を持つエンドポイントは、プロバイダーへのリクエストで既に動作しているのと同じように、プラグインでも動作します。検証自体は変更されていません。証明書チェーンの構築とホスト名の照合は引き続き有効であり、ゲストがアクセスできる宛先は egress ポリシーによって引き続き決定されます。

Sockets と WebSocket 接続は、同じ基盤から確立されます。プライベート CA の背後にあるサービスへ到達する必要がある、またはクライアント証明書を提示する必要があるプラグインは、オペレーターがそのインスタンスに設定した TLS プロファイルの名前を指定します:

[[plugins.entries]]
name = "zpi1_…"                       # インスタンスキー
egress_hosts = ["imap.corp.example.com"]

[[plugins.entries.tls_profiles]]
name = "corp"
hosts = ["imap.corp.example.com"]     # egress_hosts に含まれている必要があります
system_roots = false                  # 下記の CA のみを信頼します
custom_ca_secret = "corp_ca"          # プラグインの設定スキーマにある x-secret プロパティ(PEM 形式の素材を保持)
client_certificate_secret = "cert"    # 同上
client_private_key_secret = "key"

プロファイルが選択するのは証明書のみです。その hosts の各ホストは egress_hosts によって許可されている必要があり、これは設定検証で確認されます。また、プロファイルを指定したリクエストは、通常の許可も引き続き通過する必要があります。証明書の実体はインスタンスの暗号化された設定に保持され、プロファイルのフィールドはそのプロパティ名にすぎません。接続を構築するのはホストであり、プラグインには読み取れません。secrets.get はプロファイルで指定されたプロパティをすべて拒否するため、クライアント秘密鍵がゲストに渡ることはありません。

それらのルート証明書は、プロセスごとに一度だけ読み込まれます。同じパスの証明書ファイルを書き換えても、オペレーティングシステムの証明書ストアを変更しても、実行中のデーモンには反映されません。プラグインの HTTPS でその変更を有効にするには、デーモンを再起動してください。読み込みが不運にも失敗した場合も同様です。ストアが一時的に読み取れなかったマシンでは、プロセスが再起動されるまでバンドル済みの信頼設定のみが使われます。どちらが起きたかを示すのは plugin_egress_trust_anchors のログ行です。