メインコンテンツへスキップ

Documentation Index

Fetch the complete documentation index at: https://docs2.openclaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

チャンネルターンカーネルは、正規化されたプラットフォームイベントをエージェントターンに変換する共有インバウンド状態機械です。チャンネルPluginはプラットフォームの事実情報と配信コールバックを提供します。Core はオーケストレーション、つまり ingest、classify、preflight、resolve、authorize、assemble、record、dispatch、finalize を所有します。 Pluginがインバウンドメッセージのホットパス上にある場合にこれを使用します。メッセージ以外のイベント(スラッシュコマンド、モーダル、ボタン操作、ライフサイクルイベント、リアクション、音声状態)はPluginローカルに保ちます。カーネルが所有するのは、エージェントのテキストターンになり得るイベントのみです。
カーネルには、注入されたPluginランタイムを通じて runtime.channel.turn.* として到達します。Pluginランタイム型は openclaw/plugin-sdk/core からエクスポートされるため、サードパーティのネイティブPluginは、バンドル済みチャンネルPluginと同じ方法でこれらのエントリポイントを使用できます。

共有カーネルが必要な理由

チャンネルPluginは同じインバウンドフローを繰り返します。正規化、ルーティング、ゲート、コンテキストの構築、セッションメタデータの記録、エージェントターンのディスパッチ、配信状態の確定です。共有カーネルがないと、メンションゲート、ツール専用の可視返信、セッションメタデータ、保留中履歴、ディスパッチ確定への変更をチャンネルごとに適用する必要があります。 カーネルは、4つの概念を意図的に分離します。
  • ConversationFacts: メッセージの発生元
  • RouteFacts: どのエージェントとセッションが処理すべきか
  • ReplyPlanFacts: 可視返信の送信先
  • MessageFacts: エージェントが参照すべき本文と補足コンテキスト
Slack のDM、Telegram のトピック、Matrix のスレッド、Feishu のトピックセッションは、実際にはすべてこれらを区別します。これらを1つの識別子として扱うと、時間の経過とともにずれが生じます。

ステージライフサイクル

カーネルは、チャンネルに関係なく同じ固定パイプラインを実行します。
  1. ingest — アダプターが生のプラットフォームイベントを NormalizedTurnInput に変換する
  2. classify — アダプターが、このイベントでエージェントターンを開始できるかどうかを宣言する
  3. preflight — アダプターが重複排除、自己エコー、ハイドレーション、デバウンス、復号、部分的な事実情報の事前入力を行う
  4. resolve — アダプターが完全に組み立てられたターン(ルート、返信計画、メッセージ、配信)を返す
  5. authorize — 組み立てられた事実情報にDM、グループ、メンション、コマンドポリシーを適用する
  6. assemblebuildContext を介して事実情報から FinalizedMsgContext を構築する
  7. record — インバウンドセッションメタデータと最後のルートを永続化する
  8. dispatch — バッファリングされたブロックディスパッチャーを通じてエージェントターンを実行する
  9. finalize — ディスパッチエラー時でもアダプターの onFinalize を実行する
log コールバックが指定されている場合、各ステージは構造化ログイベントを出力します。可観測性を参照してください。

受け入れ種別

カーネルは、ターンがゲートされた場合に例外を投げません。ChannelTurnAdmission を返します。
種別場合
dispatchターンが受け入れられます。エージェントターンが実行され、可視返信パスが使われます。
observeOnlyターンはエンドツーエンドで実行されますが、配信アダプターは可視のものを送信しません。ブロードキャスト監視エージェントやその他の受動的なマルチエージェントフローに使用します。
handledプラットフォームイベントがローカルで消費されました(ライフサイクル、リアクション、ボタン、モーダル)。カーネルはディスパッチをスキップします。
dropスキップパスです。任意で recordHistory: true にすると、将来のメンションにコンテキストを持たせるため、メッセージを保留中のグループ履歴に保持します。
受け入れは classify(イベントクラスがターンを開始できないと示した場合)、preflight(重複排除、自己エコー、履歴記録を伴うメンション欠落)、または resolveTurn 自体から発生します。

エントリポイント

ランタイムは、アダプターがチャンネルに合ったレベルでオプトインできるように、3つの推奨エントリポイントを公開します。
runtime.channel.turn.run(...)             // adapter-driven full pipeline
runtime.channel.turn.runAssembled(...)    // already-built context + delivery adapter
runtime.channel.turn.runPrepared(...)     // channel owns dispatch; kernel runs record + finalize
runtime.channel.turn.buildContext(...)    // pure facts to FinalizedMsgContext mapping
Plugin SDK 互換性のために、2つの古いランタイムヘルパーも引き続き利用できます。
runtime.channel.turn.runResolved(...)      // deprecated compatibility alias; prefer run
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer runAssembled

run

チャンネルがインバウンドフローを ChannelTurnAdapter<TRaw> として表現できる場合に使用します。アダプターには、ingest、任意の classify、任意の preflight、必須の resolveTurn、任意の onFinalize のコールバックがあります。
await runtime.channel.turn.run({
  channel: "tlon",
  accountId,
  raw: platformEvent,
  adapter: {
    ingest(raw) {
      return {
        id: raw.messageId,
        timestamp: raw.timestamp,
        rawText: raw.body,
        textForAgent: raw.body,
      };
    },
    classify(input) {
      return { kind: "message", canStartAgentTurn: input.rawText.length > 0 };
    },
    async preflight(input, eventClass) {
      if (await isDuplicate(input.id)) {
        return { admission: { kind: "drop", reason: "dedupe" } };
      }
      return {};
    },
    resolveTurn(input) {
      return buildAssembledTurn(input);
    },
    onFinalize(result) {
      clearPendingGroupHistory(result);
    },
  },
});
run は、チャンネルのアダプターロジックが小さく、フックを通じてライフサイクルを所有する利点がある場合に適した形です。

runAssembled

チャンネルがすでにルーティングを解決し、FinalizedMsgContext を構築済みで、 共有の記録、返信パイプライン、ディスパッチ、finalize 順序だけが必要な場合に使用します。これは、単純なバンドル済みインバウンドパスに推奨される形で、 そうしない場合は createChannelMessageReplyPipeline(...)runPrepared(...) の定型処理を繰り返すことになります。
await runtime.channel.turn.runAssembled({
  cfg,
  channel: "irc",
  accountId,
  agentId: route.agentId,
  routeSessionKey: route.sessionKey,
  storePath,
  ctxPayload,
  recordInboundSession: runtime.channel.session.recordInboundSession,
  dispatchReplyWithBufferedBlockDispatcher:
    runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
  delivery: {
    deliver: async (payload) => {
      await sendPlatformReply(payload);
    },
    onError: (err, info) => {
      runtime.error?.(`reply ${info.kind} failed: ${String(err)}`);
    },
  },
});
チャンネル所有のディスパッチ動作が、最終ペイロード配信と、任意の入力中表示、返信オプション、永続的配信、またはエラーログだけである場合は、runPrepared ではなく runAssembled を選択します。

runPrepared

チャンネルに、プレビュー、リトライ、編集、またはスレッドブートストラップを備えた複雑なローカルディスパッチャーがあり、チャンネル所有のままにする必要がある場合に使用します。カーネルはそれでも、ディスパッチ前にインバウンドセッションを記録し、統一された DispatchedChannelTurnResult を公開します。
const { dispatchResult } = await runtime.channel.turn.runPrepared({
  channel: "matrix",
  accountId,
  routeSessionKey,
  storePath,
  ctxPayload,
  recordInboundSession,
  record: {
    onRecordError,
    updateLastRoute,
  },
  onPreDispatchFailure: async (err) => {
    await stopStatusReactions();
  },
  runDispatch: async () => {
    return await runMatrixOwnedDispatcher();
  },
});
リッチなチャンネル(Matrix、Mattermost、Microsoft Teams、Feishu、QQ Bot)は、ディスパッチャーがカーネルに学習させるべきではないプラットフォーム固有の動作をオーケストレーションするため、runPrepared を使用します。

buildContext

事実情報のバンドルを FinalizedMsgContext にマッピングする純粋関数です。チャンネルがパイプラインの一部を手作業で構築しつつ、一貫したコンテキスト形状を求める場合に使用します。
const ctxPayload = runtime.channel.turn.buildContext({
  channel: "googlechat",
  accountId,
  messageId,
  timestamp,
  from,
  sender,
  conversation,
  route,
  reply,
  message,
  access,
  media,
  supplemental,
});
buildContext は、run 用のターンを組み立てるときに resolveTurn コールバック内でも有用です。
dispatchInboundReplyWithBase などの非推奨 SDK ヘルパーは、引き続き組み立て済みターンヘルパーを経由してブリッジします。新しいPluginコードでは run または runPrepared を使用してください。

事実情報の型

カーネルがアダプターから受け取る事実情報は、プラットフォームに依存しません。プラットフォームオブジェクトをこれらの形に変換してから、カーネルに渡してください。

NormalizedTurnInput

フィールド目的
id重複排除とログに使用する安定したメッセージID
timestamp任意のエポックミリ秒
rawTextプラットフォームから受信した本文
textForAgentエージェント向けの任意のクリーン済み本文(メンション除去、入力中トリム)
textForCommands/command 解析に使用する任意の本文
raw元のオブジェクトを必要とするアダプターコールバック向けの任意のパススルー参照

ChannelEventClass

フィールド目的
kindmessagecommandinteractionreactionlifecycleunknown
canStartAgentTurnfalse の場合、カーネルは { kind: "handled" } を返します
requiresImmediateAckディスパッチ前に ACK が必要なアダプター向けのヒント

SenderFacts

フィールド目的
id安定したプラットフォーム送信者ID
name表示名
usernamename と異なる場合のハンドル
tagDiscord 形式の識別子またはプラットフォームタグ
rolesメンバーロールの許可リスト照合に使用するロールID
isBot送信者が既知のボットである場合は true(カーネルはドロップに使用)
isSelf送信者が設定済みエージェント自身である場合は true
displayLabelエンベロープテキスト用に事前レンダリングされたラベル

ConversationFacts

フィールド目的
kinddirectgroup、または channel
idルーティングに使用する会話ID
labelエンベロープ用の人間向けラベル
spaceId任意の外側スペース識別子(Slack ワークスペース、Matrix ホームサーバー)
parentIdこれがスレッドである場合の外側会話ID
threadIdこのメッセージがスレッド内にある場合のスレッドID
nativeChannelIdルーティングIDと異なる場合のプラットフォームネイティブなチャンネルID
routePeerresolveAgentRoute ルックアップに使用するピア

RouteFacts

フィールド目的
agentIdこのターンを処理するべきエージェント
accountId任意の上書き(複数アカウントのチャンネル)
routeSessionKeyルーティングに使われるセッションキー
dispatchSessionKeyルートキーと異なる場合にディスパッチで使われるセッションキー
persistedSessionKey永続化されたセッションメタデータに書き込まれるセッションキー
parentSessionKey分岐/スレッド化されたセッションの親
modelParentSessionKey分岐セッションのモデル側の親
mainSessionKey直接会話用のメインDMオーナーピン
createIfMissing欠落しているセッション行をレコード段階で作成できるようにする

ReplyPlanFacts

フィールド目的
toコンテキスト To に書き込まれる論理返信先
originatingTo発生元のコンテキストターゲット(OriginatingTo
nativeChannelId配信用のプラットフォームネイティブなチャンネルID
replyTargetto と異なる場合の最終的な表示返信先
deliveryTarget下位レベルの配信上書き
replyToId引用/アンカーされたメッセージID
replyToIdFullプラットフォームが両方を持つ場合の完全形式の引用ID
messageThreadId配信時のスレッドID
threadParentIdスレッドの親メッセージID
sourceReplyDeliveryModethreadreplychanneldirect、または none

AccessFacts

AccessFacts は承認段階に必要な真偽値を保持します。ID照合はチャンネル内にとどまります。カーネルは結果のみを使用します。
フィールド目的
dmDMの許可/ペアリング/拒否の判定と allowFrom リスト
groupグループポリシー、ルート許可、送信者許可、許可リスト、メンション要件
commands設定済みオーソライザー全体でのコマンド承認
mentionsメンション検出が可能か、およびエージェントがメンションされたか

MessageFacts

フィールド目的
body最終的なエンベロープ本文(整形済み)
rawBody生の受信本文
bodyForAgentエージェントが見る本文
commandBodyコマンド解析に使われる本文
envelopeFromエンベロープ用に事前レンダリングされた送信者ラベル
senderLabelレンダリング済み送信者の任意の上書き
previewログ用の短い編集済みプレビュー
inboundHistoryチャンネルがバッファを保持する場合の最近の受信履歴エントリ

SupplementalContextFacts

補足コンテキストは、引用、転送、スレッドのブートストラップコンテキストを扱います。カーネルは設定済みの contextVisibility ポリシーを適用します。チャンネルアダプターはファクトと senderAllowed フラグのみを提供するため、チャンネル横断ポリシーは一貫したままです。

InboundMediaFacts

メディアはファクトとして表現されます。プラットフォームのダウンロード、認証、SSRFポリシー、CDNルール、復号はチャンネルローカルにとどまります。カーネルはファクトを MediaPathMediaUrlMediaTypeMediaPathsMediaUrlsMediaTypesMediaTranscribedIndexes にマッピングします。

アダプター契約

完全な run では、アダプターの形は次のとおりです。
type ChannelTurnAdapter<TRaw> = {
  ingest(raw: TRaw): Promise<NormalizedTurnInput | null> | NormalizedTurnInput | null;
  classify?(input: NormalizedTurnInput): Promise<ChannelEventClass> | ChannelEventClass;
  preflight?(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
  ): Promise<PreflightFacts | ChannelTurnAdmission | null | undefined>;
  resolveTurn(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
    preflight: PreflightFacts,
  ): Promise<ChannelTurnResolved> | ChannelTurnResolved;
  onFinalize?(result: ChannelTurnResult): Promise<void> | void;
};
resolveTurnChannelTurnResolved を返します。これは任意のアドミッション種別を持つ AssembledChannelTurn です。{ admission: { kind: "observeOnly" } } を返すと、表示される出力を生成せずにターンを実行します。アダプターは引き続き配信コールバックを所有しますが、そのターンでは no-op になります。 onFinalize はディスパッチエラーを含むすべての結果で実行されます。保留中のグループ履歴のクリア、ackリアクションの削除、ステータスインジケーターの停止、ローカル状態のフラッシュに使用します。

配信アダプター

カーネルはプラットフォームを直接呼び出しません。チャンネルは ChannelTurnDeliveryAdapter をカーネルに渡します。
type ChannelTurnDeliveryAdapter = {
  deliver(payload: ReplyPayload, info: ChannelDeliveryInfo): Promise<ChannelDeliveryResult | void>;
  onError?(err: unknown, info: { kind: string }): void;
  durable?: false | DurableInboundReplyDeliveryOptions;
};

type ChannelDeliveryResult = {
  messageIds?: string[];
  receipt?: MessageReceipt;
  threadId?: string;
  replyToId?: string;
  visibleReplySent?: boolean;
};
deliver はバッファされた返信チャンクごとに1回呼び出されます。メッセージライフサイクル移行中、組み立て済みチャンネルターンの配信はデフォルトでチャンネル所有です。durable フィールドが省略されている場合、カーネルは deliver を直接呼び出す必要があり、汎用アウトバウンド配信経由でルーティングしてはいけません。チャンネルが監査され、返信/スレッドターゲット、メディア処理、送信済みメッセージ/自己エコーキャッシュ、ステータスクリーンアップ、返却されるメッセージIDを含め、汎用送信パスが従来の配信動作を保持することが証明された後にのみ durable を設定します。durable: false は「チャンネル所有のコールバックを使う」ための互換表記として残りますが、未移行のチャンネルが追加する必要はありません。チャンネルがプラットフォームメッセージIDを持っている場合は返し、ディスパッチャーがスレッドアンカーを保持し、後続チャンクを編集できるようにします。新しい配信パスでは receipt も返すべきです。これにより、リカバリー、プレビューの確定、重複抑制を messageIds から移行できます。観察のみのターンでは、{ visibleReplySent: false } を返すか、createNoopChannelTurnDeliveryAdapter() を使用します。 完全にチャンネル所有のディスパッチャーで runPrepared を使うチャンネルには ChannelTurnDeliveryAdapter がありません。それらのディスパッチャーはデフォルトでは durable ではありません。完全なターゲット、リプレイセーフなアダプター、receipt 契約、チャンネルの副作用フックを備えた新しい送信コンテキストに明示的にオプトインするまでは、直接配信パスを維持するべきです。 recordInboundSessionAndDispatchReplydispatchInboundReplyWithBase、直接DMヘルパーなどの公開互換ヘルパーは、移行中も動作を保持する必要があります。呼び出し元所有の deliver または reply コールバックより前に、汎用 durable 配信を呼び出してはいけません。

レコードオプション

レコード段階は recordInboundSession をラップします。ほとんどのチャンネルはデフォルトを使用できます。record で上書きします。
record: {
  groupResolution,
  createIfMissing: true,
  updateLastRoute,
  onRecordError: (err) => log.warn("record failed", err),
  trackSessionMetaTask: (task) => pendingTasks.push(task),
}
ディスパッチャーはレコード段階を待ちます。record が例外を投げた場合、カーネルは onPreDispatchFailurerunPrepared に指定されている場合)を実行して再スローします。

可観測性

log コールバックが指定されている場合、各段階は構造化イベントを発行します。
await runtime.channel.turn.run({
  channel: "twitch",
  accountId,
  raw,
  adapter,
  log: (event) => {
    runtime.log?.debug?.(`turn.${event.stage}:${event.event}`, {
      channel: event.channel,
      accountId: event.accountId,
      messageId: event.messageId,
      sessionKey: event.sessionKey,
      admission: event.admission,
      reason: event.reason,
    });
  },
});
ログに記録される段階: ingestclassifypreflightresolveauthorizeassemblerecorddispatchfinalize。生の本文をログに記録するのは避け、短い編集済みプレビューには MessageFacts.preview を使用します。

チャンネルローカルにとどまるもの

カーネルはオーケストレーションを所有します。チャンネルは引き続き次を所有します。
  • プラットフォームトランスポート(Gateway、REST、websocket、polling、webhooks)
  • ID解決と表示名照合
  • ネイティブコマンド、スラッシュコマンド、オートコンプリート、モーダル、ボタン、音声状態
  • カード、モーダル、adaptive-cardのレンダリング
  • メディア認証、CDNルール、暗号化メディア、文字起こし
  • 編集、リアクション、リダクション、プレゼンスAPI
  • バックフィルとプラットフォーム側の履歴取得
  • プラットフォーム固有の検証を必要とするペアリングフロー
2つのチャンネルがこれらのいずれかについて同じヘルパーを必要とし始めた場合は、カーネルに押し込むのではなく、共有SDKヘルパーを抽出します。

安定性

runtime.channel.turn.* は公開Pluginランタイムサーフェスの一部です。ファクト型(SenderFactsConversationFactsRouteFactsReplyPlanFactsAccessFactsMessageFactsSupplementalContextFactsInboundMediaFacts)とアドミッション形状(ChannelTurnAdmissionChannelEventClass)は、openclaw/plugin-sdk/corePluginRuntime から到達できます。 後方互換性ルールが適用されます。新しいファクトフィールドは追加的であり、アドミッション種別はリネームされず、エントリーポイント名は安定したままです。非追加的な変更を必要とする新しいチャンネル要件は、Plugin SDK移行プロセスを経る必要があります。

関連