ゲートウェイコア
責務
startGatewayServer は OpenClaw の常駐プロセスエントリです:ポートをバインド、プラグインを読み込み、RPC メソッドを登録、agent イベントを放送 (broadcast)。すべてのチャネルメッセージ、ACP リクエスト、Web クライアントがこのゲートウェイに接続し,ゲートウェイはリクエストを agent メインループに転送し,agent が生成したイベントを各クライアントに放送します。
ゲートウェイ自体は「思考」しません——受け・転送・放送だけを担当します:JSON-RPC リクエストを受け,対応する handler(多くは最終的に agent に落ちる)に転送,agent イベントを該当セッション (session) を購読した WebSocket 接続に放送。
設計動機
なぜゲートウェイを単独の常駐プロセスにするのか?OpenClaw は 22 個のチャネルに同時にぶら下がる必要があり,これらのチャネルは多くが長接続 (WebSocket/polling) を必要とします。毎回のメッセージで短命プロセスを立ち上げると,接続ハンドシェイクのコストと状態復元の複雑さが設計を圧潰します。常駐 gateway により,すべてのチャネル接続、すべての agent session、すべてのプラグイン状態が 1 プロセス内で共有され,レイヤ間連携(例:チャネル A のメッセージがトリガした agent のツール呼び出しで,ツール結果をチャネル B に配送)はプロセス内関数呼び出し一回で済みます。
主要ファイル
openclaw.mjs— ルートエントリ,Node バージョンチェック後にdist/entry.jsへ転送。entry.ts main:79-131—main()→runMainOrRootHelp振り分け。run-main gateway hot path:159-225—gateway runを直接動的 import。run-command .action:64-71— オプション解析後にrunGatewayCommandを呼ぶ。server.ts startGatewayServer:31-40— 薄いラッパー,server.impl を遅延読み込み。startGatewayServer 実装:551-580— 真の起動:旧プラグイン generation の清理、ネットワークランタイムの bootstrap。coreGatewayHandlers:269— コア RPC handler 登録表。createRequestGatewayMethodRegistry:598-630— リクエストごとに core+plugin+extra handler をマージ。handleGatewayRequest:638-660— JSON-RPC リクエストを認可して振り分け。createAgentEventHandler:299-320— agent イベントを WebSocket クライアントに放送。
データフロー
startGatewayServer の核心起動シーケンス(server.impl.ts:551):
export async function startGatewayServer(
port = 18789,
opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
normalizeStateDirEnv(process.env);
// 前回リスタートで残った旧プラグイン generation を清理し,ラウンド間の状態をきれいに保つ
const installRecords = loadInstalledPluginIndexInstallRecordsSync();
const removedGenerations = await cleanupRetiredManagedNpmInstallGenerations({
activeInstallPaths: Object.values(installRecords).flatMap((record) =>
record.installPath ? [record.installPath] : [],
),
onError: (error, projectRoot) =>
log.warn(`failed to clean retained npm generation ${projectRoot}: ${String(error)}`),
});
const { bootstrapGatewayNetworkRuntime } = await import("./server-network-runtime.js");
bootstrapGatewayNetworkRuntime();デフォルトポート 18789 に注意,さらに起動の第一件事は旧プラグイン npm generation の清理です——これは in-process restart のための設計:gateway はリスタート時に前回残ったプラグインリソースを回収し,リークを避けます。
リクエスト振り分け(handleGatewayRequest:638)は JSON-RPC で走ります:
/** Authorizes and dispatches one gateway JSON-RPC-style request. */
export async function handleGatewayRequest(
opts: GatewayRequestOptions & { extraHandlers?: GatewayRequestHandlers },
): Promise<void> {
const { req, respond, client, isWebchatConnect, context } = opts;
// 呼び出し側が添付した registry(そのメソッドを保持する場合)を優先し,
// さもなくば live plugin registry から再構築——起動スナップショット後に登録された
// プラグイン RPC メソッドも到達可能にする(#94127)handler registry はリクエストごとに再構築されます(createRequestGatewayMethodRegistry:598):core handler、現在アクティブなプラグイン handler、呼び出し側の追加 handler をマージ。この設計によりプラグインは RPC メソッドを hot 登録でき,ゲートウェイ再起動不要です。
agent イベント放送(createAgentEventHandler:299)には 3 つのプリミティブが注入されます:
export function createAgentEventHandler({
broadcast, // 該当セッションを購読する全接続に放送
broadcastToConnIds, // 指定接続に方向付き放送
nodeSendToSession, // ノードまたぎで該当 session に配送
agentRunSeq,
chatRunState,
resolveSessionKeyForRun,
...broadcast と broadcastToConnIds は createGatewayNodeSessionRuntime によって server.impl.ts:971 で注入され,ゲートウェイ全寿命に貫きます。
境界と失敗
- in-process restart:ゲートウェイリスタート時にまず旧プラグイン npm generation を清理し(
src/gateway/server.impl.ts:560) 失敗しても warn だけで起動をブロックしません——サービスを立ち上げられることの方が清理の完全性より重要。 - プラグイン hot 登録の handler 到達性:
handleGatewayRequestは呼び出し側スナップショットがメソッドを持たないとき live registry にフォールバックして再構築し,#94127 を修正——さもなくば起動スナップショット後に登録されたプラグイン RPC が「見えなく」なります。 - 状態ディレクトリ:起動時にまず
normalizeStateDirEnv(process.env)を行い,$OPENCLAW_STATE_DIRが正しく解決されることを保証,さもなくば設定と workspace パスがずれます。 - ネットワークランタイム:
bootstrapGatewayNetworkRuntime()はネットワーク層読み込み前に呼ばれ,順序を間違えると WebSocket チャネルが未就緒になります。
まとめ
ゲートウェイは受け・転送・播の 3 件事だけを行いますが,それぞれ「常駐 + マルチチャネル + hot プラグイン可能」のために設計されています:起動時にまず清理,handler はリクエストごと再構築,放送プリミティブは注入。真正に「考える」のは Agent メインループ,リクエストがどう認可・振り分けされるかは RPC メソッド表,イベントがどうクライアントに届くかは チャット放送 を参照。
公式資料:Gateway ドキュメント · README。