チャンネルプラグインの作成
チャネルプラグインは、メッセージングプラットフォームとの連携機能です。エージェントの応答をプラットフォームへ配信し、プラットフォームのメッセージをエージェントに提示します。チャネルは長時間稼働し、状態を保持し、29個の関数からなるインターフェースを介してランタイムとやり取りするため、プラグインの種類の中で最も複雑です。ただし必須となるのは、そのうち6個のみです。
このガイドは、tool プラグインをビルド済みで、crate のセットアップ、__config ルール、ロギング、およびインストールを理解していることを前提としています。本ガイドは wit/v0/channel.wit とホストアダプター(crates/zeroclaw-plugins/src/wasm_channel.rs)に対して検証されています。
接続状態。 チャネルプラグインは、実行中のデーモンによって構築されます。
[channels.plugin.<alias>]を介してバインドされたインストール済みパッケージは、起動時に取り込まれ、ネイティブチャネルとまったく同じように監視されます。以下の チャネルプラグインを有効化する を参照してください。
ライフサイクル
チャネルプラグインのランタイム形状は、ツールのものと3つの基本的な点で異なり、それぞれがコード内の設計上の決定を左右します:
- プラグインの存続期間全体で 1 つのウォームストア。 ホストはコンポーネントを 1 回だけインスタンス化し(
WasmChannel::from_wasm)、非同期ミューテックスの背後でストアを保持します。コンポーネントは呼び出し間でゲスト所有のプロトコル状態を保持できますが、オペレーター設定はホストが所有します。準拠するプラグインは、設定を必要とするすべての操作でconfig.getとsecrets.getを必ず呼び出す必要があり、その結果をウォームなゲスト状態にコピーしてはなりません。ホストは各呼び出し後にマテリアライズしたビューを破棄しますが、悪意のあるゲストコードが返された JSON や平文を保持するのを阻止することはできません。ストアには呼び出しごとに燃料が再補給されます(component.rs内のcall_channel!)。そのため、長期間存続するチャネルには、存続期間を通じて燃料を消費し続けるのではなく、呼び出しごとに新しい燃料予算が与えられます。 - 設定は使用時点で要求されます。 ホストは、引数なしの
configureエクスポートをロード時に、他のエクスポートに先立って正確に 1 回呼び出します。マニフェストのconfig_schemaに対して検証された型付きの公開 JSON オブジェクトを取得するにはconfig.getを呼び出します。x-secret = true とマークされたプロパティは省略され、secrets.getを介して読み取る必要があります。configure内、またはそれ以降の任意の運用エクスポート内での公開設定とシークレットの読み取りは、単一の解決済み設定リビジョンを共有します。そのため、同一バインディングの公開設定と資格情報のローテーションは、次の操作で同時に確認できます。インスタンス化中および静的ディスカバリ中の呼び出しは、設定を解決せずにunavailableを返します。静的ディスカバリにはname、plugin-info、get-channel-capabilities、self-handle、self-addressed-mention、multi-message-delay-msが含まれます。ボット/アカウントの識別情報やその他の静的メタデータを変更するには、チャネルのライフサイクルを再構築する必要があります。 - あなたはリッスンしません。ホストがあなたにフィードします。 WASI コンテキストにはネットワークリスナー機能がありません。インバウンドトラフィックは、インポートされた
inboundインターフェースを通じてあなたに届きます。ホストが実際のリスナー(Webhook サーバー、ベンダートンネル、ポーリングクライアント)を実行し、受信した各メッセージをInboundQueueにエンキューし、あなたのpoll-messageエクスポートがinbound-pollを呼び出してそれをドレインします。必要に応じてinbound-pendingでバッチドレインします。
必須エクスポート
5つの関数にはRustトレイトのデフォルトがなく、実際に動作する必要があります(world channel-plugin doc、channel.wit):
| エクスポート | 契約 |
|---|---|
name | 人間が読めるチャネル名。 |
configure | ロード時の初期化を完了します。引数はなく、現在の1つのリビジョンに対して config.get と secrets.get を呼び出します。エラー文字列が返されると、ロードに失敗します。 |
send | send-message(コンテンツ、受信者、オプションの件名/スレッド/添付ファイル)をプラットフォームに配信します。 |
poll-message | ノンブロッキング: 次の受信メッセージまたは none を即座に返します。決してブロックしないでください。ホストの poll bridge がペーシングを処理します。 |
get-channel-capabilities | ロード時に一度だけ呼び出されます。実際に実装しているオプションメソッドのビットマスクを返します。 |
ポールブリッジについて補足します。ホストはポーリングからプッシュへのループ(wasm_channel.rs の listen)を実行しており、キューが空の間は50msから500msまで指数バックオフしながら poll-message を呼び出します。トラフィックがあるとバックオフはリセットされます。poll-message がトラップした場合、ホストはそれをログに記録してバックオフし、health-check を自分でエクスポートしていなくても、ゲートウェイの /health スナップショットでチャネルを異常として報告します。トラップするとインスタンスはどのエクスポートも実行できなくなるため、ホストはそれを破棄し、次の呼び出しで新しいインスタンスを構築して configure を再度実行します。トラップしたポーリングがキューから取り出していたメッセージは失われますが、後続のメッセージは引き続き届きます。トラップした場合や呼び出しの期限を超えた場合、どのエクスポートも同じ方法で置き換えられます。一方、エクスポートが返したエラー文字列はインスタンスをそのまま残します。ゲートウェイ型プラグインの再構築は再接続を伴うため、ホストは再構築回数に上限を設けています。失敗の平均発生間隔が5分に1回以下であれば直ちに再構築し、3回分の連続実行を許容します。それを超えると、予算が回復するまで待機し、その間の呼び出しはプラグインを実行せずに失敗させます。人の回答を待つ request-approval と request-choice での期限超過は、この予算には算入されません。大きなアップロードのように、期限より長い時間を常に必要とするエクスポートには、より大きい plugins.limits.call_timeout_ms が必要です。シンプルに保ちましょう。キューを空にし、変換し、返す。
機能フラグ: 23 個のオプションメソッド
インターフェースの他のすべてが channel-capabilities フラグによってゲートされています。パターン(メモリワールドと同一):
- ホストは読み込み時にフラグを一度だけ読み取ります。
- すべての unset フラグについて、ホストは Rust のトレイトのデフォルトを使用し、あなたのエクスポートを呼び出すことはありません。
- すべての関数を引き続きエクスポートする必要があります。文書化されたデフォルト値を返すスタブはコンパイルされますが、呼び出されることはありません。
フラグごとのデフォルト値は、信頼できる情報源である channel.wit 内のフラグ宣言のすぐ隣にインラインで記載されています。要約すると、グループは次のとおりです。
| グループ | フラグ | 実装によって得られるもの |
|---|---|---|
| ヘルス | health-check | |Report whether the channel works. The host asks about every 30 seconds while listening and shows the answer, combined with poll health, in /health (see Health checks). |
Translation to Japanese:
チャネルが動作しているかどうかを報告します。リッスン中、ホストは約30秒ごとに問い合わせを行い、その結果をポーリングの状態とあわせて /health に表示します(ヘルスチェックを参照)。|
|ID情報|self-handle、self-addressed-mention、drop-self-message|セルフループ保護(ランタイムがボット自身のメッセージを破棄する)と、チャンネルごとのシステムプロンプトにおける正しい @メンション形式。ホストは読み込み時に self-handle と self-addressed-mention をキャッシュします。これらは一度だけ読み取られます。|
|入力|start-typing، stop-typing|エージェントが考えている間にインジケーターを構成しています。|
|下書き|supports-draft-updates、send-draft、update-draft、update-draft-progress、finalize-draft、cancel-draft|プログレッシブなメッセージ編集: ランタイムは、完了を待つのではなく、編集可能なプラットフォームメッセージにレスポンスをストリーミングします。6つすべてをまとめて実装するか、まったく実装しないかのいずれかにしてください。|
|マルチメッセージストリーミング|supports-multi-message-streaming、multi-message-delay-ms|段落ごとの配信と、最小メッセージ間遅延(デフォルト 800ms、ロード時にキャッシュ)。|
|モデレーション|add-reaction、remove-reaction、pin-message、unpin-message、redact-message|絵文字リアクション、ピン留め、メッセージ削除。|
|インタラクション|request-approval、request-choice、supports-free-form-ask|プラットフォーム上でネイティブに表示される、ツール呼び出しの承認プロンプトと選択式の質問。|
|Webhookの受信口|webhook-ingress|受信Webhookを処理します。ゲートウェイはwebhook-pathセグメントを/plugin/<segment>の下にマウントし、各リクエストをparse-webhookに渡します。parse-webhookはプラットフォームの真正性を検証し、メッセージまたはチャレンジへの応答を返します。両方のエクスポートをあわせて実装してください。|
必須の6つに加えてhealth-checkから始め、プラットフォームがサポートするグループを順次追加してください。実装していないフラグを広告するのは、省略するよりも悪い対応です。ホストはexportを呼び出し、その応答を信頼するためです。
ヘルスチェック
チャネルがリッスンしている間、ホストは各ポーリングの合間に、実行中のインスタンスへ health-check を呼び出します。最初のポーリングの直後と、その後は約30秒ごとです。その応答は、ゲートウェイの /health スナップショットにおけるチャネルのコンポーネント channel:plugin.<alias> になります:
startingは最初の回答が得られるまで表示されます。最初の回答がfalseの場合も、次のチェックまではstartingと表示されるため、ハンドシェイクを完了中のプラグインが失敗として報告されることはありません。ok最新の回答がtrueである間はerrorとなる条件:最新の回答がfalseの場合、チェックがトラップされた場合または呼び出し期限を過ぎた場合、poll-messageがトラップされた場合、間隔 2 回分と呼び出し期限 2 回分を合わせた時間(既定の 30 秒の期限では 2 分)を過ぎても回答が届かない場合。
zeroclaw channel doctor も、新しく設定されたインスタンスに対して health-check を1回呼び出します。
health-check は繰り返し実行されるため、プラグインがすでに保持している状態、つまり接続が確立されているかどうか、またはプラットフォームとの直前のやり取りがどうだったかに基づいて応答してください。health-check 内のネットワークリクエストは毎回のチェックで実行され、次のポーリングを遅らせます。ネットワーク経由で認証情報を検証する場合は、直前の結果を保持し、独自のスケジュールで更新してください。トラップしたり呼び出しの期限を過ぎたりしたチェックはインスタンスを破棄され、ホストは configure で再構築します。そのため、ゲートウェイ型のプラグインは接続を失います。このような失敗のたびに、ホストは次のチェックまでの待機時間を2倍にし、最大10分まで延ばします。また、ホストは、もう1回失敗すると再構築が待機状態に入る場合にはチェックを延期します。そのため、失敗したチェック単独で再構築の待機が発生することはありません。
承認画面
request-approval は最も深いレベルの統合ポイントです。ランタイムはコンパクトな approval-request(ツール名、引数の概要、オプションの生の JSON 引数)を提示し、チャネルはプラットフォームが許可する方法(ボタン、リアクション、返信規約)でそれをレンダリングします。返す approval-response バリアントがセキュリティ機構を駆動します。
approve: この呼び出しを1回実行するdeny: 拒否するalways-approve: 実行し、そのツールをセッションスコープの許可リストに追加しますdeny-with-edit(string): 拒否するが、編集された置換引数を提供する
プロンプトを表示できない場合は none を返します。呼び出し元は自動拒否にフォールバックします。フェイルクローズします。
インバウンドメッセージの形状
プラットフォームイベントを inbound-message レコードへ忠実に変換します。ランタイムのスレッド処理ロジックはプラットフォームのペイロードフィールドを基準とし、ルーティングIDはホストが発行したエンドポイント(channel.wit、wasm_channel.rs 内の from_wit_inbound)のみから取得されます:
id、sender、content:基本項目。reply-targetは応答の送信先(チャンネル ID、チャット ID、メールアドレス)です。channelとchannel-aliasは v0 レコードに保持されているレガシーヒントです。ホストはルーティングにおいてこれら両方を無視し、許可されたチャンネルタイプと設定済みバインディングをスタンプするため、プラグインが別のオーナーやセッションを選択することはできません。thread-tsはスレッド返信用にプラットフォームのスレッド識別子を保持します。subjectはメールのスレッド化のために存在します。interruption-scope-idは、中断・キャンセルのためにメッセージをグループ化します。トップレベルのメッセージではnoneのままにしてください。attachmentsは境界を越えて完全な生バイト列を伝送します(media-attachment: ファイル名、バイト列、オプションのMIMEタイプ)。ボイスメモは数メガバイトを値渡しで越えることになります。これは32ビット境界における文書化されたコストであり、リソースハンドルモデルは将来のWIT改訂へ明示的に先送りされています。
送信側では、send-message は同じフィールドを反映します。Rust の SendMessage のキャンセレーショントークンは、ホスト側の概念でありプラグイン内では意味を持たないため、意図的に WIT レコードから省略されています。
スケルトン
プラットフォームごとの翻訳を省略した構造は、実際の作業内容です:
#![allow(unused)]
fn main() {
#[cfg(target_family = wasm)]
mod component {
wit_bindgen::generate!({
path: wit/v0,
world: channel-plugin,
features: [plugins-wit-v0],
});
use exports::zeroclaw::plugin::channel::{
ApprovalRequest, ApprovalResponse, ChannelCapabilities,
Guest as Channel, InboundMessage, SendMessage,
};
use exports::zeroclaw::plugin::plugin_info::Guest as PluginInfo;
use zeroclaw::plugin::config::get as config_get;
use zeroclaw::plugin::inbound::inbound_poll;
use zeroclaw::plugin::secrets::get as secret_get;
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct ChannelConfig {
api_base: String,
}
fn current_config() -> Result<ChannelConfig, String> {
let json = config_get().map_err(|_| パブリック設定は利用できません.to_string())?;
serde_json::from_str(&json).map_err(|e| format!(無効な設定 JSON: {e}))
}
fn current_api_token() -> Result<String, String> {
secret_get("api_token").map_err(|_| api_token は利用できません.to_string())
}
fn current_inputs() -> Result<(ChannelConfig, String), String> {
// このエクスポート内の両方のインポートは、解決済みの同一の正規リビジョンを共有します。
Ok((current_config()?, current_api_token()?))
}
struct MyChannel;
impl Channel for MyChannel {
fn name() -> String {
my-platform.to_string()
}
fn configure() -> Result<(), String> {
let (config, api_token) = current_inputs()?;
validate_configuration(&config.api_base, &api_token)
}
fn send(message: SendMessage) -> Result<(), String> {
let (config, api_token) = current_inputs()?;
// wasi:http を介した外向きプラットフォーム配信
// (マニフェストで http_client 権限が必要)。この呼び出しの値から
// リクエストを構築し、2つ目のコピーを決して保持しない。
send_to_platform(&config.api_base, &api_token, message)
}
fn poll_message() -> Option<InboundMessage> {
// ホスト供給キューをドレインして変換する。
inbound_poll().map(translate_inbound)
}
fn get_channel_capabilities() -> ChannelCapabilities {
ChannelCapabilities::HEALTH_CHECK
}
fn health_check() -> bool {
current_inputs().is_ok()
}
// その他のすべてのメソッド: WIT で文書化されたデフォルトを返すスタブ。
// ホストはフラグが未設定の間、それらを決して呼び出さない。
// ...
}
export!(MyChannel);
}
}
current_inputs は使用時点で意図的に呼び出されます。ホストは両方のインポートを、この許可済みパッケージ、channel 機能、およびエイリアスにバインドします。1つのエクスポート内の読み取りは、解決済み設定の同じリビジョンを共有しますが、次のエクスポートでは、同じバインディングの公開設定と認証情報のローテーションを確認できます。ChannelConfig は呼び出しごとの型付きビューであり、トークンとともに破棄されます。thread_local 設定や認証情報キャッシュを追加しないでください。
マニフェストと権限
マニフェストとは、プラグインディレクトリ内にある 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 に channel が含まれ、ほぼ確実に config_read(認証情報なしではどのプラットフォームも動作しないため)と http_client の両方が含まれます。チャンネルアダプターは送信方向の wasi:http を実装していますが、そのグラントが検証された後にのみリンクします。また、グラントだけでは依然として何にも到達できません。ホストがegressポリシーを管理しており、そのポリシーはデフォルト拒否です。オペレーターは、send がネットワーク経路を持つ前に、そのチャンネルインスタンスについて、プラットフォームのAPIホストを plugins.entries.<key>.egress_hosts に登録する必要があります。このリストに含まれない宛先は、パケットが送出される前に拒否されます。また、許可されたホストがループバック、プライベート、またはリンクローカルのアドレスに解決される場合も、egress_allow_private にも登録されていない限り拒否されます。マニフェストの [egress] テーブルに、プラットフォームのAPIホストを宣言してください。オペレーターが zeroclaw plugin bind <package> --channel-alias <alias> --egress declared でインスタンスをバインドすると、その宣言が新しいインスタンスの egress_hosts に書き込まれ、出力されます。代わりに --egress none を指定すると、ネットワーク到達性なしでバインドされます。チャンネルインスタンスの場合、管理対象のトランスポート(http_client、websocket_client、socket_client のいずれか)であれば、宣言が有効になります。そのため、ソケットのみで通信するチャンネルも同じ方法で初期設定されます。セルフホスト型サーバーのように、デプロイメント設定に属するホストは宣言すべきではありません。代わりに、そのホストはオペレーターが許可することをREADMEに記載してください。
config_read を ChannelConfig が使用するスキーマに対応付ける:
name = "my-platform"
version = "0.1.0"
wasm_path = "my_platform.wasm"
capabilities = ["channel"]
permissions = ["config_read", "http_client"]
[config_schema]
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
additionalProperties = false
required = ["api_base", "api_token"]
[config_schema.properties.api_base]
type = "string"
minLength = 1
[config_schema.properties.api_token]
type = "string"
minLength = 1
x-secret = true
ホストは両方のプロパティを1つのオブジェクトとして検証します。config.get は api_base を含む型付きの JSON を返し、api_token は省略します。api_token は secrets.get を通じてのみ利用できます。両方とも必須であるため、config_read を付与しない場合は、必須の設定なしでチャネルを起動するのではなく、ゲストコードの実行前にフェイルクローズドで失敗します。各チャネルインスタンスは、パッケージが所有するこの単一のスキーマを再利用しつつ、完全なパッケージ名、channel ケイパビリティ、バインディング識別子から導出される plugins.entries のキーを選択します。そのため、異なるパッケージ内で同一のエイリアスを使用しても互いに分離されたままになります。オペレーターがエイリアスをバインドすると、キーが存在するようになります。zeroclaw plugin bind はそのインスタンスのエントリを作成し、zeroclaw plugin bind と zeroclaw plugin info の両方が、このスキーマの各 required キーを設定済みか未設定かとして一覧表示します。未設定のキーには zeroclaw config set コマンドが併せて表示されます。シークレットキーのコマンドは、エコーなしで値の入力を求めます。いかなる値も出力されません。コマンドが表示されるのは、移植可能なキーの文法(ASCII の英字、数字、_、-、. からなる 1~128 文字)に従うプロパティ名の場合に限られます。それ以外の名前の場合は、代わりに編集すべき設定ファイルの行が報告されます。そのため、必須プロパティ名はこの文法の範囲内に収めてください。手書きされたバインディングのように、エントリがまだ存在しない場合、plugin info はキーの一覧の代わりに、そのエントリを作成する plugin bind コマンドを出力します。マニフェストで送信先が宣言されている場合は、egress の判断ごとに1回出力されます。config set は既存のエントリのみを解決するためです。
config.get と secrets.get は、それらを使用する各操作の中で呼び出してください。ホストはその呼び出しについて最大1つの正規リビジョンを解決し、その後はそのビューを破棄します。同じ論理バインディング内の公開設定と資格情報のローテーションは、デーモンの再読み込みやチャネルの再構築を行わなくても、次の操作で同時に反映されます。ボット/アカウントの識別情報、公開する機能、自身のハンドル、メンション、またはその他のロード時メタデータを変更する場合は、チャネルのライフサイクルを再構築する必要があります。これは、これらのエクスポートが静的ディスカバリー時に一度だけ読み取られるためです。
空のオブジェクトが有効なオプションのスキーマでは、実効的な config_read 権限付与を拒否されたインスタンスでもロードできますが、config.get と secrets.get は access-denied を返します。いずれのインポートも、インスタンス化または静的検出中、リゾルバーまたは検証の失敗後、あるいは共有ホスト呼び出し予算を使い果たした場合に unavailable を返します。secrets.get はさらに、存在しない名前、または x-secret = true とマークされていない名前に対して not-found を返します。
チャネルプラグインの有効化
An installed package does nothing until an operator binds it to a logical channel instance. The binding names the package and nothing else; the alias is the instance’s identity. The operator path is zeroclaw plugin bind, which writes the binding, seeds the instance’s config entry, and reports what the instance still needs before it can start:
zeroclaw plugin bind acme.chat --channel-alias operations --egress declared
--egress declared は、マニフェストで宣言された接続先への通信を許可し、--egress none はネットワーク到達性を持たない状態でバインドします。このフラグが意味を持つのは、コマンドがインスタンスの設定エントリを作成するときだけです。その場合、マニフェストが接続先を宣言し、パッケージがそれらに到達できるトランスポート(http_client、websocket_client、socket_client のいずれか)を保持しているなら、フラグは必須です。zeroclaw plugin install <source> --channel-alias operations はインストール中に同じ手順を実行します。その場合、フラグが制御するのはチャネルインスタンスのエントリだけで、同じパッケージのツールエントリは、インストール時に通常どおりシードされます。チャネルインスタンスのバインドで詳しく説明しています。
In config.toml、エージェントにバインドおよびルーティングされたインスタンスは次のようになります:
[plugins]
enabled = true
[channels.plugin.operations]
package = "acme.chat"
enabled = true
[agents.support]
channels = ["plugin.operations"]
plugin bind は [channels.plugin.operations] テーブルと、インスタンスの [[plugins.entries]] 行のみを書き込みます。新しいバインディングは package と既定の enabled = true を付けて書き込まれます。コマンドが自ら行わず、オペレーターが明示的に判断する必要がある手順は2つあります。1つ目はプラグインシステムの有効化(plugins.enabled)、2つ目は所有権の設定、つまり有効なエージェントの channels リスト(agents.<agent>.channels)への plugin.operations の追加です。コマンドが出力する準備状況レポート(zeroclaw plugin info でも再表示されます)は、まだ満たされていない最初の前提条件を示し、それを満たすコマンドがあればそのコマンドも示します。
エイリアスは通常のチャンネル参照になるため、plugin.operations は telegram.main とまったく同じようにルーティング、監視、再起動、アドレス指定されます。2 つのエイリアスで 1 つのパッケージを指定できますが、それぞれが独自のインスタンス、独自のストア、独自の plugins.entries キーを持つため、状態は共有されません。
インスタンスは、以下の条件をすべて満たした場合にのみ受理されます。各条件は意図的なフェイルクローズのゲートであり、1つでも満たさない宣言は中途半端に開始されるのではなく、無効になります。
plugins.enabledは true です。- 宣言の
enabledは true です。 - 指定されたパッケージがインストールされており、そのマニフェストで
channelケイパビリティが宣言されています。 - 一部の有効化されたエージェントは、channels に
plugin.<alias>を指定しています。参照されていないバインディングでは、配信先がないままリスナーが実行されることになります。
受け入れ判定はゲストコードが実行される前に行われます。パッケージホストがすでに検証したマニフェストに基づいて判定されるため、コンポーネントが破損しているパッケージも、正常なパッケージと同じように計画され、拒否されます。受け入れ判定を通過したパッケージがその後の構築に失敗した場合は、ログに記録されてスキップされます。そのため、1つの壊れたプラグインが原因で、デーモンが他のチャネルを起動できなくなることはありません。
plugins.max_active_instances は、すべての機能で受け入れられる論理インスタンス数に上限を設けます。明示的なチャネルバインディングは自動検出されたツールやスキルよりも優先されるため、プラグインディレクトリがいっぱいでも、オペレーターが手動で設定したチャネルが置き換えられることはありません。
zeroclaw plugin bind と zeroclaw plugin info は、ルールを再記述するのではなく、アクティベーションプランにこの判定を問い合わせます。そのため、報告される最初の未達のゲート(上限を含む)は、同じ設定とインストール済みパッケージに対してデーモンが適用するものと同じです。ready の判定は、インスタンスが受理されたことを意味するものであり、コンポーネントが読み込まれることを意味するものではありません。plugin info は、その読み込みチェックとともに、これを別途報告します。
同じ許可済みセットが、3つすべてのローダー、つまりチャネルローダー、ツールレジストリ、プラグインスキルローダーを動かします。したがって上限は、機能ごとではなく、1つの共有枠です。チャネルとツールの両方を提供するパッケージは実際に2つのスロットを消費し、上限を超えるツールやスキルはそもそも構築されません。許可判定は、現在の設定とインストール済みパッケージだけから決まる純粋関数です。カウンターを保持しないため、エージェントごと、CLI実行ごと、デリゲートごと、SOP実行ごとに再構築されるツールレジストリは、それぞれ同じセットを再導出し、長時間稼働するデーモンの存続期間中に上限を使い果たすことはありません。
ツールとスキルのインスタンスは 自動検出 されるため、plugins.auto_discover が true の場合にのみ登録されます。明示的な [channels.plugin.<alias>] 宣言にはこれは必要ありません。plugins.enabled = true かつ auto_discover = false の場合、宣言したチャンネルバインディングだけが取得され、それ以外は何もありません。
plugins.max_pluginsからの移行。 旧キーは一度も適用されず、plugins.max_active_instancesに置き換えられました。両者は数える対象が異なります。旧キーはインストール済みパッケージを数え、新しいキーは受け入れられた論理インスタンスを数えるため、チャンネルとツールの両方を提供するパッケージは2つ分を消費します。単位が異なるため、既存のmax_pluginsの値は引き継がれません。無視され、新しいキーにはデフォルト値が使用されます。デフォルト以外の上限に依存していた場合は、max_active_instancesを明示的に設定してください。
まだ接続されていないもの
Plugin チャネルは、同期的なチャネルマップの公開面がすでに構築された後に、非同期で構築されます。したがって、チャネルをアドレス指定する tools は、まだプラグインチャネルを対象にできません。監視下のリスナーを介した受信ポーリングと送信配信には影響せず、欠けているのはツール側のアドレス指定だけです。
ビルドとインストール
WASI Preview 2 ターゲットを一度インストールしてから、コンポーネントをビルドします:
rustup target add wasm32-wasip2
cargo build --release --target wasm32-wasip2
コンポーネントは target/wasm32-wasip2/release/<crate_name>.wasm に生成されます(クレート名のハイフンはアンダースコアに変換されます)。プラグインディレクトリを構成する際は、マニフェストの wasm_path で宣言した名前に変更してください。
[!IMPORTANT] コンパイル済みの
.wasmおよび.cwasmファイルはバイナリ成果物であり、多くの場合それぞれ数メガバイトになります。Git LFS を使わずに Git のソースツリーへコミットしないでください。リビルドのたびに素のブロブとしてコミットされると、リポジトリの履歴が永久的に肥大化し、git diffやレビューツールがそれらの処理でつまずきます。これらは他のビルド出力と同様に扱ってください。target/と*.wasm/*.cwasmを.gitignoreに追加し、代わりにリリース成果物やプラグインレジストリのアーカイブを通じて配布してください。どうしても成果物をツリー内に置く必要がある場合は、最初のコミットの前に LFS でパターンを追跡してください(git lfs track "*.wasm")。
ターゲットホストがランタイム専用ビルド(JITバックエンドがコンパイルに含まれていない)の場合、ロード時に .wasm をコンパイルできません。代わりに事前コンパイル済みの .cwasm をデシリアライズします。ホストのバージョンと一致する wasmtime CLI で事前コンパイルし、.cwasm を wasm_path アーティファクトとして同梱してください。バージョンが一致しないアーティファクトは、wasmtime のデシリアライズチェックによって拒否され、サイレントに誤ロードされることはありません。
これらのコマンドには、プラグインホストがコンパイルに含まれたバイナリが必要です。 64ビットのデスクトップ・サーバー向けターゲット(glibc または musl の x86_64 および aarch64 Linux、Apple silicon と Intel の macOS、x86_64 の Windows)のリリースアーカイブにはこれが含まれており、各リリースは公開前に、それぞれのアーカイブからプラグインを1つ実行します。32ビット ARM のアーカイブ2種と実験的な Android アーカイブには含まれておらず、Cargo のデフォルト設定によるソースビルド(
cargo install zeroclaw、install.sh --source、または features なしのcargo build)にも含まれていません。これらの場合、zeroclaw plugin ...は認識されないサブコマンドとなり、インストール済みのプラグインは検出されません。そのような環境では、プラグイン実行バックエンドを有効にしてソースからビルドしてください(例えばcargo build --release --features plugins-wasm-cranelift)。アーティファクトごとのプラグイン対応状況は、プロトコルのページに記載されています。
各プラグインは、プラグインディレクトリ(デフォルト ~/.zeroclaw/plugins/、plugins.plugins_dir を通じて解決)の独自のサブディレクトリに配置され、マニフェストとマニフェストの wasm_path に一致する名前のコンポーネントを保持します:
~/.zeroclaw/plugins/
└── my-plugin/
├── manifest.toml
└── my-plugin.wasm
ローカルディレクトリからインストール(これはマニフェストの形状を検証し、何かをコピーする前に署名ポリシーを実行します):
zeroclaw plugin install ./my-plugin/
プラグインシステムを有効にして検出を確認する:
zeroclaw config set plugins.enabled true
zeroclaw plugin list
zeroclaw plugin info my-plugin
zeroclaw plugin list と zeroclaw plugin info は、パッケージがインストールされ、検出可能であることを確認しますが、検出は有効化を意味しません。plugins.enabled = true によってプラグインホストが有効になります。自動検出されたツールおよびスキルの機能は、plugins.auto_discover = true も設定されている場合にのみ実行時に読み込まれ、このフラグはデフォルトでは false です(フェイルクローズド):
zeroclaw config set plugins.auto_discover true
したがって、plugins.enabled = true だけでは、[channels.plugin.<alias>] の下で宣言したチャンネルのみが有効になり、プラグインのツールやスキルは有効になりません。ツールまたはスキルのパッケージが zeroclaw plugin list に表示されていても、実行時には何も提供しないことがあります。明示的なチャンネルバインディングは自動検出ではなくオペレーターが名前を指定するものなので、auto_discover は不要です。このフラグが制御するのは、自動検出されたツールとスキルだけです。
zeroclaw plugin list にないプラグインは検出時にスキップされました: 起動ログでスキップ警告を確認してください(形式が不正なマニフェスト、wasm_path ファイルの欠落、または署名ポリシーによる拒否)。
ホストコントラクトに対するテスト
ホストアダプターと構成リゾルバーのテストは実行可能な仕様であり、型付きマテリアライゼーションとスキーマ検証、使用箇所での公開スコープとシークレットスコープ、同一リビジョンでの一貫したローテーション、拒否された権限付与、静的ディスカバリの拒否、インバウンドキューのハンドオフ、ケイパビリティでゲートされたディスパッチ、およびポーリングヘルスの計上をカバーしています。
独自のコンポーネントをそれらの正確なセマンティクスで実行するには、実際のホストアダプター経由でインスタンス化する統合テストを作成してください。zeroclaw-plugins は crates.io に公開されていないため、対象のホストに一致するタグに固定した git 開発依存として取得してください:
cargo add --dev zeroclaw-plugins \
--git https://github.com/zeroclaw-labs/zeroclaw --tag <host-version> \
--no-default-features --features plugins-wasm-cranelift
テストは続いて、マニフェストとテスト用オペレーター値に基づく PluginConfigResolver::new を PluginHostServices にラップし、WasmChannel::from_wasm を介してコンポーネントを読み込み、それが公開する InboundQueue ハンドルにエンキューして、poll-message がメッセージを取り出して変換することをアサートします。これは本番デーモンが実行するのと同じコードパスであり、これに合格することは、ライブホストなしで得られる配布前の最も強力なシグナルです。
次へ
- メモリプラグインの作成: エージェント帰属セマンティクスを備えた、もう一方のウォームストアの世界。
- プラグインの配布(署名とレジストリへの公開について)。