チャネル設定
責務
チャネル設定 (channel config) は openclaw.json の channels.* フィールドの解析とマッチング層です。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 で下流にどのレベルにヒットしたかを知らせます。
主要ファイル
channel-config.ts:60-165—resolveChannelEntryMatch/resolveChannelEntryMatchWithFallback,direct / parent / wildcard マッチ。zod-schema.channels-config.ts:52-69—ChannelsSchemaトップレベル,passthrough でプラグインフィールドを容忍。zod-schema.core.ts:33-90—SecretRefSchema/SecretInputSchema,3 種 secret source。zod-schema.providers-core.ts:249-409—TelegramAccountSchemaBase/TelegramConfigSchema,複数アカウント schema の例。SlackConfigSchema 位置:1054— Slack 複数アカウント schema。WhatsAppConfigSchema:238— WhatsApp はz.preprocessで旧設定に互換。types.channels.ts:127-146—ChannelsConfigインターフェース,typed フィールド + open-world index signature。plugins/config-schema.ts:27-49—AllowFromEntrySchema/buildCatchallMultiAccountChannelSchema公共ビルダー。config-presence.ts:47-66—hasMeaningfulChannelConfig/listExplicitlyDisabledChannelIdsForConfig。
データフロー
openclaw.json トップレベル channels フィールドは ChannelsSchema(zod-schema.channels-config.ts:52)を走ります:
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>;トップレベルは defaults と modelByChannel の 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 に走らせ,OpenWorldChannelConfig は ReturnType<typeof JSON.parse> で完全に開放しています。
Secret 参照は設定層の核心抽象(zod-schema.core.ts:83)です:
/** 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]);SecretRefSchema は discriminatedUnion("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 を取得します。
SecretInputSchema は z.union([z.string(), SecretRefSchema])——旧式インライン文字列も互換ですが,.register(sensitive) でフィールドを機密マーク(zod-schema.providers-core.ts:269)します:
botToken: SecretInputSchema.optional().register(sensitive),これで openclaw doctor / ログ出力時に botToken のリテラル値をマスキングします。
各チャネル自身の schema は共通フィールド + チャネル専用フィールドを組み合わせ,Telegram は最も典型的な複数アカウント例(zod-schema.providers-core.ts:405)です:
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)がこのパターンの汎用ヘルパーです:
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 に落ちます。各ステップで matchKey と matchSource を結果に書き込み,下流は applyChannelMatchMeta でこの 2 つのフィールドを最終 config オブジェクトにコピーし,「この設定がどの key からヒットしたか」の監査を容易にします。
normalizeKey はオプショナルな正規化関数で,例えば normalizeChannelSlug(channel-config.ts:47)は #general、General、GENERAL 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-levelbindings[]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 体系 を参照。