Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

チャンネルプラグインの作成

チャンネルプラグインはメッセージングプラットフォーム統合です。エージェントの応答をプラットフォームに配信し、プラットフォームのメッセージをエージェントに提示します。チャンネルは長寿命でステートフルであり、ランタイムと27個の関数からなるサーフェスを通じてやり取りし、そのうち必須なのは5個だけであるため、最も複雑なプラグインの種類です。

このガイドは、tool プラグインをビルド済みで、crate のセットアップ、__config ルール、ロギング、およびインストールを理解していることを前提としています。本ガイドは wit/v0/channel.wit とホストアダプター(crates/zeroclaw-plugins/src/wasm_channel.rs)に対して検証されています。

接続状態。 チャネルプラグインは、実行中のデーモンによって構築されます。[channels.plugin.<alias>] を介してバインドされたインストール済みパッケージは、起動時に取り込まれ、ネイティブチャネルとまったく同じように監視されます。以下の チャネルプラグインを有効化する を参照してください。

ライフサイクル

チャネルプラグインのランタイム形状は、ツールのものと3つの基本的な点で異なり、それぞれがコード内の設計上の決定を左右します:

  1. プラグインの存続期間全体で 1 つのウォームストア。 ホストはコンポーネントを 1 回だけインスタンス化し(WasmChannel::from_wasm)、非同期ミューテックスの背後でストアを保持します。コンポーネントは呼び出し間でゲスト所有のプロトコル状態を保持できますが、オペレーター設定はホストが所有します。準拠するプラグインは、設定を必要とするすべての操作で config.getsecrets.get必ず呼び出す必要があり、その結果をウォームなゲスト状態にコピーしてはなりません。ホストは各呼び出し後にマテリアライズしたビューを破棄しますが、悪意のあるゲストコードが返された JSON や平文を保持するのを阻止することはできません。ストアには呼び出しごとに燃料が再補給されます(component.rs 内の call_channel!)。そのため、長期間存続するチャネルには、存続期間を通じて燃料を消費し続けるのではなく、呼び出しごとに新しい燃料予算が与えられます。
  2. 設定は使用時点で要求されます。 ホストは、引数なしの configure エクスポートをロード時に、他のエクスポートに先立って正確に 1 回呼び出します。マニフェストの config_schema に対して検証された型付きの公開 JSON オブジェクトを取得するには config.get を呼び出します。x-secret = true とマークされたプロパティは省略され、secrets.get を介して読み取る必要があります。configure 内、またはそれ以降の任意の運用エクスポート内での公開設定とシークレットの読み取りは、単一の解決済み設定リビジョンを共有します。そのため、同一バインディングの公開設定と資格情報のローテーションは、次の操作で同時に確認できます。インスタンス化中および静的ディスカバリ中の呼び出しは、設定を解決せずに unavailable を返します。静的ディスカバリには nameplugin-infoget-channel-capabilitiesself-handleself-addressed-mentionmulti-message-delay-ms が含まれます。ボット/アカウントの識別情報やその他の静的メタデータを変更するには、チャネルのライフサイクルを再構築する必要があります。
  3. あなたはリッスンしません。ホストがあなたにフィードします。 WASI コンテキストにはネットワークリスナー機能がありません。インバウンドトラフィックは、インポートされた inbound インターフェースを通じてあなたに届きます。ホストが実際のリスナー(Webhook サーバー、ベンダートンネル、ポーリングクライアント)を実行し、受信した各メッセージを InboundQueue にエンキューし、あなたの poll-message エクスポートが inbound-poll を呼び出してそれをドレインします。必要に応じて inbound-pending でバッチドレインします。

必須エクスポート

5つの関数にはRustトレイトのデフォルトがなく、実際に動作する必要があります(world channel-plugin doc、channel.wit):

エクスポート契約
name人間が読めるチャネル名。
configureロード時の初期化を完了します。引数はなく、現在の1つのリビジョンに対して config.getsecrets.get を呼び出します。エラー文字列が返されると、ロードに失敗します。
sendsend-message(コンテンツ、受信者、オプションの件名/スレッド/添付ファイル)をプラットフォームに配信します。
poll-messageノンブロッキング: 次の受信メッセージまたは none を即座に返します。決してブロックしないでください。ホストの poll bridge がペーシングを処理します。
get-channel-capabilitiesロード時に一度だけ呼び出されます。実際に実装しているオプションメソッドのビットマスクを返します。

ポールブリッジについては注記が必要です。ホストはポールからプッシュへのループ(wasm_channel.rs内のlisten)を実行し、キューが空の間は50msから500msへの指数バックオフでpoll-messageを呼び出し、トラフィックがあればリセットします。poll-messageがトラップした場合、ホストはチャネルをポール異常としてマークし、ログを記録してバックオフします。ポールがトラップし続けるプラグインは、独自のhealth-checkをエクスポートしていなくてもhealth_checkを通じて異常を報告します。したがってpoll-messageでのトラップは可視化されますが致命的ではなく、チャネルが役に立たなくなります。シンプルに保ちましょう。キューを排出し、変換し、返してください。

機能フラグ: 22個のオプションメソッド

インターフェースの他のすべてが channel-capabilities フラグによってゲートされています。パターン(メモリワールドと同一):

  • ホストは読み込み時にフラグを一度だけ読み取ります。
  • すべての unset フラグについて、ホストは Rust のトレイトのデフォルトを使用し、あなたのエクスポートを呼び出すことはありません。
  • すべての関数を引き続きエクスポートする必要があります。文書化されたデフォルト値を返すスタブはコンパイルされますが、呼び出されることはありません。

フラグごとのデフォルト値は、信頼できる情報源である channel.wit 内のフラグ宣言のすぐ隣にインラインで記載されています。要約すると、グループは次のとおりです。

グループフラグ実装によって得られるもの
ヘルスhealth-checkプラットフォームの到達可能性を報告します。ホストアダプターによるポーリングの正常性と組み合わせて使用されます。
ID情報self-handleself-addressed-mentiondrop-self-messageセルフループ保護(ランタイムがボット自身のメッセージを破棄する)と、チャンネルごとのシステムプロンプトにおける正しい @メンション形式。ホストは読み込み時に self-handleself-addressed-mention をキャッシュします。これらは一度だけ読み取られます。
入力start-typing، stop-typingエージェントが考えている間にインジケーターを構成しています。
下書きsupports-draft-updatessend-draftupdate-draftupdate-draft-progressfinalize-draftcancel-draftプログレッシブなメッセージ編集: ランタイムは、完了を待つのではなく、編集可能なプラットフォームメッセージにレスポンスをストリーミングします。6つすべてをまとめて実装するか、まったく実装しないかのいずれかにしてください。
マルチメッセージストリーミングsupports-multi-message-streamingmulti-message-delay-ms段落ごとの配信と、最小メッセージ間遅延(デフォルト 800ms、ロード時にキャッシュ)。
モデレーションadd-reactionremove-reactionpin-messageunpin-messageredact-message絵文字リアクション、ピン留め、メッセージ削除。
インタラクションrequest-approvalrequest-choicesupports-free-form-askプラットフォーム上でネイティブに表示される、ツール呼び出しの承認プロンプトと選択式の質問。

必須の5つに加えてhealth-checkから始め、プラットフォームがサポートするグループを追加していきます。実装していないフラグを公開することは、それを省略するよりも悪影響を及ぼします。ホストはあなたのエクスポートを呼び出し、その応答を信頼するからです。

承認画面

request-approval は最も深いレベルの統合ポイントです。ランタイムはコンパクトな approval-request(ツール名、引数の概要、オプションの生の JSON 引数)を提示し、チャネルはプラットフォームが許可する方法(ボタン、リアクション、返信規約)でそれをレンダリングします。返す approval-response バリアントがセキュリティ機構を駆動します。

  • approve: この呼び出しを1回実行する
  • deny: 拒否する
  • always-approve: 実行し、そのツールをセッションスコープの許可リストに追加します
  • deny-with-edit(string): 拒否するが、編集された置換引数を提供する

プロンプトを表示できない場合は none を返します。呼び出し元は自動拒否にフォールバックします。フェイルクローズします。

インバウンドメッセージの形状

プラットフォームイベントを inbound-message レコードへ忠実に変換します。ランタイムのスレッド処理ロジックはプラットフォームのペイロードフィールドを基準とし、ルーティングIDはホストが発行したエンドポイント(channel.witwasm_channel.rs 内の from_wit_inbound)のみから取得されます:

  • idsendercontent:基本項目。reply-target は応答の送信先(チャンネル ID、チャット ID、メールアドレス)です。
  • channelchannel-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 サーフェスであり、これが信頼できる情報源となります。

フィールド必須意味
nameyes一意の正規パッケージスラッグであり、派生する各インスタンス設定キーのパッケージ部分です。これはそれ自体がオペレーター設定キーではありません。1~128文字の小文字 ASCII 文字を使用し、先頭と末尾は [a-z0-9] とし、その間には [a-z0-9._-] のみを使用します。検出時に無効な名前や重複した名前は拒否されます。
versionyesバージョン文字列(例:0.1.0)。
descriptionいいえzeroclaw plugin list によって表示される人間が読める説明。
authorいいえ作者名または組織。
wasm_pathWASM 機能についてコンポーネントのファイル名(プラグインディレクトリからの相対パス)。唯一の capability が skill の場合を除き必須。指定されたファイルが存在しない場合、検出はプラグインをスキップします。
capabilitiesはい、空ではありませんプラグインの種類: toolchannelmemoryobserverskill のいずれか(PluginCapability、snake_case でシリアライズ)。
permissionsいいえコードがアクセスできるホストサービス: http_clientconfig_readfile_readfile_writememory_readmemory_writePluginPermission)。現在強制されるのは最初の2つのみで、残りは受け付けられますが機能しません。config_read の宣言には config_schema が必要で、現在これを提供するのはツール/チャネルアダプターのみです。
config_schemaconfig_read と完全に一致してこのプラグインのプライベート設定用の Draft 2020-12 JSON Schema を作成します。これは正規マニフェストのバイト列に含まれるため、マニフェストへの署名によって保護されます。ルートは properties マップを持ち、additionalProperties = false であるオブジェクトでなければなりません。トップレベルのすべてのプロパティには、直接またはローカル JSON Pointer を介して、サポート対象の型を 1 つ明示しなければなりません。型は stringbooleanintegernumberarray、または object です。ツールおよびチャネルの利用側は、トップレベルの文字列プロパティに直接 x-secret = true を設定して、そのプロパティを公開設定から削除し、スコープ付きの secrets.get ホストインポートを介して公開できます。ツールは __config 配下で公開設定を受け取り、execute 中にシークレットを読み取れます。チャネルは、configure および運用呼び出し中に、config.get を通じて現在の公開オブジェクトを読み取り、secrets.get を通じてシークレットを読み取ります。両方のインポートは、インスタンス化および静的メタデータ検出中は利用できません。ネストされたシークレットマーカー、false のシークレットマーカー、または boolean でないシークレットマーカー、および文字列以外のシークレットプロパティは拒否されます。config_read のないスキーマ、またはスキーマのない config_read は拒否されます。
signatureいいえ正規化されたマニフェストバイト列に対する Base64url Ed25519 署名。配布用に署名する際に設定されます。
publisher_keyいいえ署名者の16進数エンコードされたEd25519公開鍵。

コードが実際に使用する権限のみを宣言してください。宣言されていない権限はコンポーネントが到達できないホストサーフェスであり、不要に宣言された権限は自ら求めた攻撃対象領域であり、プラグインをレビューする人にとっての監査負担です。

オペレーター値は 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 を実装しますが、その許可が検証された後にのみリンクします。両方が揃っていなければ、send はプラットフォームへのネットワークパスを持ちません。

config_readChannelConfig が使用するスキーマに対応付ける:

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.getapi_base を含む型付き JSON を返し、api_token は省略します。api_tokensecrets.get を介してのみ利用できます。両方が必須であるため、config_read を付与しない場合、ゲストコードが実行される前にフェイルクローズし、必須の設定なしでチャネルを開始することはありません。各チャネルインスタンスは、フルパッケージ、その channel ケイパビリティ、およびバインディング識別情報から導出された plugins.entries キーを選択し、この1つのパッケージ所有スキーマを再利用します。したがって、異なるパッケージにある同一のエイリアスは分離されたままになります。install および info コマンドは、設定済みのチャネルエイリアスを所有していないため、このキーを作成できません。これらの自動的な表示およびシード動作はツール専用なので、チャネルインスタンスのエントリは手動で書き込まれます。

config.getsecrets.get は、それらを使用する各操作の中で呼び出してください。ホストはその呼び出しについて最大1つの正規リビジョンを解決し、その後はそのビューを破棄します。同じ論理バインディング内の公開設定と資格情報のローテーションは、デーモンの再読み込みやチャネルの再構築を行わなくても、次の操作で同時に反映されます。ボット/アカウントの識別情報、公開する機能、自身のハンドル、メンション、またはその他のロード時メタデータを変更する場合は、チャネルのライフサイクルを再構築する必要があります。これは、これらのエクスポートが静的ディスカバリー時に一度だけ読み取られるためです。

空のオブジェクトが有効なオプションのスキーマでは、実効的な config_read 権限付与を拒否されたインスタンスでもロードできますが、config.getsecrets.getaccess-denied を返します。いずれのインポートも、インスタンス化または静的検出中、リゾルバーまたは検証の失敗後、あるいは共有ホスト呼び出し予算を使い果たした場合に unavailable を返します。secrets.get はさらに、存在しない名前、または x-secret = true とマークされていない名前に対して not-found を返します。

チャネルプラグインの有効化

インストールされたパッケージは、オペレーターが論理チャネルインスタンスにバインドするまで何もしません。バインドで指定するのはパッケージ名だけで、それ以外は指定しません。エイリアスがインスタンスの識別子です。

[plugins]
enabled = true

[channels.plugin.operations]
package = "acme.chat"
enabled = true

[agents.support]
channels = ["plugin.operations"]

エイリアスは通常のチャンネル参照になるため、plugin.operationstelegram.main とまったく同じようにルーティング、監視、再起動、アドレス指定されます。2 つのエイリアスで 1 つのパッケージを指定できますが、それぞれが独自のインスタンス、独自のストア、独自の plugins.entries キーを持つため、状態は共有されません。

インスタンスは、以下の条件をすべて満たした場合にのみ受理されます。各条件は意図的なフェイルクローズのゲートであり、1つでも満たさない宣言は中途半端に開始されるのではなく、無効になります。

  • plugins.enabled は true です。
  • 宣言の enabled は true です。
  • 指定されたパッケージがインストールされており、そのマニフェストで channel ケイパビリティが宣言されています。
  • 一部の有効化されたエージェントは、channels に plugin.<alias> を指定しています。参照されていないバインディングでは、配信先がないままリスナーが実行されることになります。

受け入れ判定はゲストコードが実行される前に行われます。パッケージホストがすでに検証したマニフェストに基づいて判定されるため、コンポーネントが破損しているパッケージも、正常なパッケージと同じように計画され、拒否されます。受け入れ判定を通過したパッケージがその後の構築に失敗した場合は、ログに記録されてスキップされます。そのため、1つの壊れたプラグインが原因で、デーモンが他のチャネルを起動できなくなることはありません。

plugins.max_active_instances は、すべての機能で受け入れられる論理インスタンス数に上限を設けます。明示的なチャネルバインディングは自動検出されたツールやスキルよりも優先されるため、プラグインディレクトリがいっぱいでも、オペレーターが手動で設定したチャネルが置き換えられることはありません。

同じ許可済みセットが、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 で事前コンパイルし、.cwasmwasm_path アーティファクトとして同梱してください。バージョンが一致しないアーティファクトは、wasmtime のデシリアライズチェックによって拒否され、サイレントに誤ロードされることはありません。

これらのコマンドには、プラグインホストをコンパイルして組み込んだバイナリが必要です。 インストーラーが配布するビルド済みリリースバイナリは plugins-wasm 機能なしでビルドされているため、そこでは 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 listzeroclaw 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::newPluginHostServices にラップし、WasmChannel::from_wasm を介してコンポーネントを読み込み、それが公開する InboundQueue ハンドルにエンキューして、poll-message がメッセージを取り出して変換することをアサートします。これは本番デーモンが実行するのと同じコードパスであり、これに合格することは、ライブホストなしで得られる配布前の最も強力なシグナルです。

次へ