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 は変更されておらず、ディレクトリの読み取りのみの コストのままです。検証では各コンポーネントのコンパイルとインスタンス化を行うためです。スキルバンドルにはコンポーネントが含まれないため、失敗ではなく対象外として報告されます。

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

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>を案内します。

For a name the host has not loaded, plugin remove claims the selected directory generation in a hidden recovery transaction and applies the same admission checks to those held contents. It deletes that generation in two cases only: ホストが読み込んでいない名前については、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トレイトを実装しますランタイムはこれをまだ設定可能なバックエンドとして構築しません
observersop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。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 を要求してはなりません。

ツールコンポーネントとチャネルコンポーネントは、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の宣言と許可

A plugin’s network reach consists of two separate facts, and only one of them is under your control:

Translation to ja:

プラグインのネットワーク到達範囲は、互いに独立した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 の判断はコマンドラインで明示します。詳しくは チャネルインスタンスのバインド を参照してください):

  • For a row it creates, it seeds the declaration into egress_hosts, prints each destination it granted, and prints the zeroclaw config set command that edits the grant later.

    Wait, I need to correct that: the source is English and needs translating to Japanese. Here is the translation:

    作成する行については、宣言を egress_hosts に初期設定し、付与した各宛先を出力し、後から許可を編集するための zeroclaw config set コマンドを出力します。

  • 既存の行(アップグレード、再インストール、または自分で作成した行)の場合、何も変更されません。代わりに差分を表示します。マニフェストで宣言されているが行で許可されていない宛先、行で許可されているがマニフェストではもう宣言されていない宛先、そして追加を適用する正確なコマンドです。そのため、パッケージの更新によって自身のネットワーク到達範囲が広がることはなく、差分は意図的に適用する必要があります。

  • サポートされていない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は、同じ比較を1インスタンスずつ繰り返す常設の診断です。対象は、インストール済みツールプラグインのうちhttp_clientを保持するものすべてのデフォルトツールバインディングと、パッケージがhttp_client、websocket_client、socket_clientのいずれかを保持するバインド済みチャネルエイリアスすべてです。各インスタンスについて、宣言された宛先のうち行が許可していないもの(それらへの要求は拒否されます)を1行で示し、そのギャップを解消するコマンドも併記します。行がまだないインスタンスには、そのコマンドが許可付きの行を作成します。[channels.plugin.ops-team]のように手書きされ、設定のエイリアス文法で拒否されるエイリアスを持つ、行がまだないチャネルインスタンスには、文法の理由と、代わりにリネームすべきテーブルを示します。行を作成するコマンドは設定全体を検証するため、そのエイリアスを拒否してしまうからです。チャネルインスタンスは、エグレス権限が管理するすべてのトランスポートを対象とします。ソケットやWebSocketのみで通信するチャネル(IRCやMQTTなど)が報告されないままになることを防ぐためです。ツールの行は、出荷時のhttp_clientルールをそのまま保持します。逆方向、つまり許可されているが宣言されていない場合は指摘しません。宣言に一致しない許可は指摘事項ではなく正規の経路です。宛先がデプロイ設定で決まるプラグイン(セルフホストの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つのコマンドが付与を利用可能かどうかについて食い違うことはありません。

A channel package with no bound alias has no instance yet, so plugin list reports no channel row for it: there is no row to compare against, and install does not invent one.

Wait—this string is already in English and I’m asked to translate to ja. Translating:

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

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

チャネルパッケージは、[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回目の実行では、設定ファイルはバイト単位で同一のまま残ります。対応する行のないバインディング、またはバインディングのない行を検出した実行は、もう一方に触れることなく、欠けている側のみを作成します。

After binding, the command reports whether the instance can start, reading each answer from the source the runtime uses: バインド後、コマンドはインスタンスを起動できるかどうかを報告します。その際、各回答はランタイムが使用するソースから読み取ります:

  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からインストール

A plugin host を含むビルドでは、CLI Quickstart の任意項目 Plugins からツールプラグインをインストールすることもできます。手順は Quickstart → Plugins を参照してください。Quickstart は zeroclaw plugin install と同じダウンロード、審査、ロード確認、公開およびシード投入のトランザクションを実行します。上記のルールは引き続き適用されます。

  • 宣言と付与の違い。 Quickstart では、マニフェストの [egress] 宣言が表示されますが、これ自体では何も付与されません。マニフェストが http_client を要求し、接続先を宣言しているパッケージについて行を作成する場合、作成される行に初期値として設定される唯一の宣言に対して、新しい行がそれらの接続先を付与するかどうかを選択できます。パッケージが新規かどうか、行なしで既にインストール済みだったかどうかは問いません。この質問は、ネットワークアクセスなしでインストールする場合から始まります。その場合 egress_hosts は空のままになり、チャネルインスタンスに対して plugin bind --egress none が行うように、後で接続先を付与するコマンドが表示されます。既にインストールされたパッケージをスキップする場合、その行は存在しないままになり、空の付与で行を作成するコマンドが表示された後、改行して、宣言された接続先を付与するコマンドが表示されます。Quickstart が欠落している行を作成するために表示するコマンドは、決してネットワークアクセスを付与しません。付与は常に独立したコマンドです。読み込みチェックに失敗したプラグインに対して Quickstart が表示する plugin install --no-verify コマンドは、いつものとおり、プラグイン CLI のインストールであり、作成する行に、マニフェストが http_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 のログ行です。