Skip to content

チャネル設定

源码版本v2026.6.11

責務

チャネル設定 (channel config) は openclaw.jsonchannels.* フィールドの解析とマッチング層です。3 件事を管轄します:ユーザが書いた JSON を構造化型に解析,チャネル id でアカウント設定(account config)をマッチ,token / botToken / webhookSecret のような機密フィールドをハードコード文字列から secret reference(env / file / exec)に昇格。

「どのチャネルを起動すべきか」の決定は行いません——それは チャネル登録表config-presence.ts と協力して行います。ここは「ユーザが何を書いたか、正しく書けたか、どのキーでどのアカウント設定にマッチしたか」だけを担当します。

設計動機

4 つの現実的理由。

第一に,22 個のチャネルフィールドはそれぞれ異なります。Telegram は botToken + webhookUrl + pollingStallThresholdMs,Slack は botToken + appToken + socketMode,WhatsApp は authDir,Discord は guilds + channels。1 つの ChannelsConfig 型に詰め込むと数千行のユニオン型になり,1 チャネル変えるたびにコア型をいじる必要があります。

第二に,複数アカウント。Telegram は複数 bot,Slack は複数 workspace をぶら下げられ,各アカウントが独立設定です。accounts: Record<string, AccountConfig> + defaultAccount: string の統一パターンが必要で,各チャネルフィールドは account 層に下に押し下げられます。

第三に,secret をハードコードしないbotToken: "123456:ABC..." を openclaw.json に書くとファイル漏洩時に token が泄れます。SecretInputSchema = z.union([z.string(), SecretRefSchema]) でユーザは旧式インライン文字列も書けるし,{source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN"} のような構造化参照も書けます。

第四に,設定マッチは direct / parent / wildcard をサポート。同じチャネルに複数の設定がある可能性があります:具体的なアカウント id 1 つ,親チャネル 1 つ,ワイルドカード 1 つ。解析時に優先度でフォールバックし,matchSource で下流にどのレベルにヒットしたかを知らせます。

主要ファイル

データフロー

openclaw.json トップレベル channels フィールドは ChannelsSchema(zod-schema.channels-config.ts:52)を走ります:

typescript
export const ChannelsSchema: z.ZodType<ChannelsConfig | undefined> = z
  .object({
    defaults: z
      .object({
        groupPolicy: GroupPolicySchema.optional(),
        contextVisibility: ContextVisibilityModeSchema.optional(),
        heartbeat: ChannelHeartbeatVisibilitySchema,
        botLoopProtection: ChannelBotLoopProtectionSchema.optional(),
      })
      .strict()
      .optional(),
    modelByChannel: ChannelModelByChannelSchema,
  })
  .passthrough() // Allow extension channel configs (nostr, matrix, zalo, etc.)
  .superRefine((value, ctx) => {
    addLegacyChannelAcpBindingIssues(value, ctx);
  })
  .optional() as z.ZodType<ChannelsConfig | undefined>;

トップレベルは defaultsmodelByChannel の 2 つの strict フィールドだけで,残りはすべて passthrough——channels.telegram / channels.slack / channels.nostr / channels.zalo などのキーはトップレベル schema ではフィールド級検証をしない。フィールド検証は各チャネル自身の TelegramConfigSchema / SlackConfigSchema / WhatsAppConfigSchema に下に置かれます。.superRefine は legacy bindings.acp フィールド(廃止済み)だけをスキャンし,見つければエラーにしてユーザにトップレベル bindings[] への移行を促します。

なぜ passthrough を使うのか?プラグインチャネルは open-world だからです——types.channels.ts(types.channels.ts:127)の ChannelsConfig インターフェースは 10 個のコアチャネル(discord / googlechat / imessage / irc / msteams / signal / slack / telegram / whatsapp 等)を typed フィールドにし,残りはすべて [key: string]: OpenWorldChannelConfig に走らせ,OpenWorldChannelConfigReturnType<typeof JSON.parse> で完全に開放しています。

Secret 参照は設定層の核心抽象(zod-schema.core.ts:83)です:

typescript
/** Config-level secret reference schema shared by model/provider/plugin credential fields. */
export const SecretRefSchema = z.discriminatedUnion("source", [
  EnvSecretRefSchema,
  FileSecretRefSchema,
  ExecSecretRefSchema,
]);

/** Accepts either legacy inline secret strings or structured secret references. */
export const SecretInputSchema = z.union([z.string(), SecretRefSchema]);

SecretRefSchemadiscriminatedUnion("source", ...) で,3 種 source です:env は環境変数から読み,id/^[A-Z][A-Z0-9_]{0,127}$/ にマッチ(例:TELEGRAM_BOT_TOKEN)。file はファイルから読み,id は JSON pointer(例:/providers/openai/apiKey または単値モードの value)。exec は外部コマンドを呼んで secret を取得します。

SecretInputSchemaz.union([z.string(), SecretRefSchema])——旧式インライン文字列も互換ですが,.register(sensitive) でフィールドを機密マーク(zod-schema.providers-core.ts:269)します:

typescript
botToken: SecretInputSchema.optional().register(sensitive),

これで openclaw doctor / ログ出力時に botToken のリテラル値をマスキングします。

各チャネル自身の schema は共通フィールド + チャネル専用フィールドを組み合わせ,Telegram は最も典型的な複数アカウント例(zod-schema.providers-core.ts:405)です:

typescript
export const TelegramConfigSchema = TelegramAccountSchemaBase.extend({
  accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(),
  defaultAccount: z.string().optional(),
}).superRefine((value, ctx) => {
  requireOpenAllowFrom({
    // ...
  });
});

TelegramAccountSchemaBase はチャネル級フィールド集合で,TelegramConfigSchema はその上に accounts 複数アカウントコンテナと defaultAccount を加えます——buildCatchallMultiAccountChannelSchema(plugins/config-schema.ts:42)がこのパターンの汎用ヘルパーです:

typescript
export function buildCatchallMultiAccountChannelSchema<T extends ExtendableZodObject>(
  accountSchema: T,
): T {
  return accountSchema.extend({
    accounts: z.object({}).catchall(accountSchema).optional(),
    defaultAccount: z.string().optional(),
  }) as T;
}

catchall(accountSchema)accounts 下の任意の key を accountSchema で検証——新アカウント追加時に schema を変更する必要がありません。

設定マッチは resolveChannelEntryMatchWithFallback(channel-config.ts:83)で,核心は優先度によるフォールバック——direct ヒットは直接 { matchKey, matchSource: "direct" } を返します。ヒットしなければ normalized direct(normalizeKey で key を正規化してマッチ)に走り,さらに parent,normalized parent,最後に wildcard に落ちます。各ステップで matchKeymatchSource を結果に書き込み,下流は applyChannelMatchMeta でこの 2 つのフィールドを最終 config オブジェクトにコピーし,「この設定がどの key からヒットしたか」の監査を容易にします。

normalizeKey はオプショナルな正規化関数で,例えば normalizeChannelSlug(channel-config.ts:47)は #generalGeneralGENERAL ROOM をすべて general に正規化します——ユーザが設定内で channel 名を書くとき精密マッチを強要しません。

hasMeaningfulChannelConfig は「内容を設定した」と「単に enabled=false」を区別(config-presence.ts:47)します:Object.keys(value).some((key) => key !== "enabled") をチェック——enabled 単独は「設定あり」とは見なさず,運用意図だけです。これで openclaw status が「明示的無効」のチャネルを「設定済み未起動」と誤報しません。

設定解析とマッチング全体のフロー:

境界と失敗

  • passthrough でプラグインフィールドを容忍:ChannelsSchema.passthrough()(zod-schema.channels-config.ts:65)は channels.nostr / channels.matrix のようなプラグインフィールドをトップレベルでエラーにせず,検証もしない。検証は各チャネル plugin 自身の ChannelConfigSchema に下に置かれ,schema 未登録のチャネルは unvalidated です。
  • legacy ACP bindings はエラー:addLegacyChannelAcpBindingIssues(zod-schema.channels-config.ts:20)は bindings.acp を再帰スキャンし,発見すれば ctx.addIssue で「Legacy channel-local ACP bindings were removed; use top-level bindings[] entries」を提示します。旧設定のアップグレードはここでブロックされます。
  • secret ref 校験は厳格:env 系 id/^[A-Z][A-Z0-9_]{0,127}$/ 必須(zod-schema.core.ts:44)で,file 系 id は絶対 JSON pointer 必須。形式不正は直接エラー,黙ってリテラル値に戻りません。
  • allowlist には allowFrom が必須:requireOpenAllowFrom(zod-schema.providers-core.ts:409)は dmPolicy: "allowlist" のとき allowFrom が非空を強制し,さもなくば safeParse 失敗。「ホワイトリストを開いたつもりが誰でも DM 可能」を防ぎます。
  • accounts は record ではなく catchall:z.object({}).catchall(accountSchema)(plugins/config-schema.ts:46)は z.record() より一層多く——「既知フィールド」と「動的アカウント key」を区別でき,将来 defaultAccount のような予約フィールドを追加してもアカウント id と衝突しません。
  • enabled=false は configured ではない:hasMeaningfulChannelConfig(config-presence.ts:47)は enabled を明示的に除外し,setup フローが「明示的無効」を「設定済みだが未起動」と誤認するのを防ぎます。
  • WhatsApp は preprocess を走る:WhatsAppConfigSchema = z.preprocess(...)(zod-schema.providers-whatsapp.ts:238)は解析前にまず旧設定を移行し,superRefine ではありません——mutate-before-validate は歴史互換に重要ですが,デバッグ時には safeParse が受け取る data が移行後のものであることに注意してください。

まとめ

チャネル設定層は 22 個のチャネルの各フィールド差を ChannelsSchema.passthrough() + 各チャネル ConfigSchema の 2 段階で吸収します——トップレベルは公共フィールドと legacy 検出だけを管轄し,フィールド級検証は下に委譲。複数アカウントパターンは buildCatchallMultiAccountChannelSchema で統一,secret は SecretInputSchema で 3 ソース(env / file / exec)がリテラル値と互換,マッチは direct / normalized / parent / wildcard の 5 段階フォールバックで matchSource を書き下流の監査に使わせます。

チャネル登録表 から plugin を取得し,その config.listAccountIds / config.resolveAccount を呼んでこれらの設定を構造化 account オブジェクトに変換する方法は チャネルアダプタ を参照。openclaw.json の全体読み込みと検証フローは openclaw.json 設定,すべての zod schema の組織方式は zod schema 体系 を参照。