Skip to content

Cœur de passerelle

源码版本v2026.6.11

Responsabilités

startGatewayServer est l'entrée du processus persistant d'OpenClaw: binder le port, charger les plugins, enregistrer les méthodes RPC, diffuser les événements de l'agent. Tous les messages des canaux, requêtes ACP, et clients Web se connectent à cette passerelle, qui les forward à la boucle principale de l'agent et diffuse les événements de l'agent à tous les clients.

La passerelle elle-même ne « pense » pas — elle ne fait que recevoir, forwarder, diffuser: recevoir une requête JSON-RPC, la forwarder au handler correspondant (beaucoup atterrissent in fine sur l'agent), et diffuser les événements de l'agent aux WebSocket qui ont souscrit à la session.

Motivation de conception

Pourquoi ériger la passerelle en processus persistant distinct? Parce qu'OpenClaw doit être branché simultanément sur 22 canaux, dont la plupart nécessitent des connexions longues (WebSocket/polling). Si chaque message déclenchait un processus court, le coût de handshake et la complexité de restauration d'état écraseraient la conception. Une passerelle persistante permet à toutes les connexions de canaux, toutes les sessions d'agent, et tout l'état des plugins de partager un seul processus; la coordination inter-couche (par exemple: un message du canal A déclenche un agent qui appelle un outil, dont le résultat doit être livré au canal B) n'est plus qu'un appel de fonction intra-processus.

Fichiers clés

Flux de données

La séquence de démarrage centrale de startGatewayServer (server.impl.ts:551):

typescript
export async function startGatewayServer(
  port = 18789,
  opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
  normalizeStateDirEnv(process.env);
  // Nettoie l'ancienne génération de plugins laissée par le redémarrage précédent, pour un état propre entre deux runs
  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();

Notez le port par défaut 18789 et le fait que la première chose exécutée est le nettoyage de l'ancienne génération de plugins npm — c'est pour les redémarrages in-process: la passerelle doit d'abord récupérer les ressources de la génération précédente pour éviter les fuites.

La répartition des requêtes (handleGatewayRequest:638) en JSON-RPC:

typescript
/** 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;
  // Privilégier le registry attaché par l'appelant (s'il possède la méthode),
  // sinon reconstruire depuis le live plugin registry pour que les méthodes RPC
  // des plugins enregistrés après le snapshot de démarrage restent atteignables (#94127)

Le handler registry est reconstruit à chaque requête (createRequestGatewayMethodRegistry:598): on fusionne core handlers, plugin handlers actifs, et extra handlers fournis par l'appelant. Ce design permet aux plugins d'enregistrer chaud (hot register) des méthodes RPC sans redémarrer la passerelle.

La diffusion des événements agent (createAgentEventHandler:299) reçoit trois primitives injectées:

typescript
export function createAgentEventHandler({
  broadcast,              // Diffuser à toutes les connexions abonnées à cette session
  broadcastToConnIds,     // Diffusion ciblée à des connexions précises
  nodeSendToSession,      // Livraison inter-nœud vers une session
  agentRunSeq,
  chatRunState,
  resolveSessionKeyForRun,
  ...

broadcast et broadcastToConnIds sont injectés par createGatewayNodeSessionRuntime dans server.impl.ts:971, pendant toute la vie de la passerelle.

Limites et modes d'échec

  • Redémarrage in-process: au redémarrage de la passerelle, on nettoie d'abord l'ancienne génération de plugins npm (src/gateway/server.impl.ts:560); si le nettoyage échoue, on log un warn sans bloquer le démarrage — pouvoir lever le service passe avant un nettoyage complet.
  • Accessibilité des handlers en hot register: handleGatewayRequest repli sur le live registry si le snapshot de l'appelant ne possède pas la méthode, fix de #94127 — sinon les RPC de plugins enregistrés après le snapshot de démarrage seraient « invisibles ».
  • Répertoire d'état: le démarrage appelle normalizeStateDirEnv(process.env) pour s'assurer que $OPENCLAW_STATE_DIR est résolu correctement, sinon les chemins de config et de workspace se désynchronisent.
  • Runtime réseau: bootstrapGatewayNetworkRuntime() est appelé avant de charger la couche réseau; un ordre inverse laisserait les canaux WebSocket non prêts.

Résumé

La passerelle ne fait que recevoir, forwarder, diffuser, mais chaque tâche est conçue pour « persistant + multi-canaux + hot-swappable »: nettoyage au démarrage, handler registry recréé par requête, primitives de diffusion injectées. Ce qui « pense » vraiment est la Boucle principale de l'agent; pour l'autorisation et la répartition des requêtes, voir Table de méthodes RPC; pour la livraison des événements aux clients, voir Diffusion du chat.

Pour comparer avec la documentation officielle: Gateway docs · README.