はじめに
対象読者
目次
前提:OpenClawは外向き接続(Outbound)である
チャネルごとの接続パターン
パターン1: 外向きWebSocket接続
Discord
Slack
WhatsApp
パターン2: 外向きHTTPポーリング
パターン3: 外向き接続 + ローカルプロセス連携
パターン4: インバウンドWebhook(例外的)
接続パターンのまとめ
Gateway内部の通信
チャネルからGatewayへのハンドオフ
エージェントの呼び出し
Embeddedモード(デフォルト)
RPCモード
なぜbashを実行できるのか
シンプルな答え:Node.jsプロセスの権限
コマンド実行の実装
macOSの権限(TCC)
まとめ
参考リンク
この記事が参考になったら
https://s3.ap-northeast-1.amazonaws.com/hanzochang.com/_v2/1771224917403_u5s8m7akgsk.png

OpenClaw 通信・接続の仕組み - なぜDiscordのメッセージでローカルのbashが動くのか

OpenClawはなぜDiscordやSlackからローカルのbashを操作できるのか?外向きWebSocket接続の仕組み、Gateway内部のメッセージフロー、Node.jsプロセスの権限継承まで、ソースコードを追いながら通信経路を解説します。

公開日2026.02.14

更新日2026.02.14

AIOpenClawWebSocket

はじめに

OpenClawは、ローカルマシン上で動くパーソナルAIアシスタントのOSSです。Discord、Slack、Telegram、WhatsAppなど38以上のメッセージングプラットフォームを1つのAIが横断的に対応します。

OpenClawの構造は5つのレイヤーで構成されています。


Generated Image A2a19313 9b57 4e25 8fa5 35f75d23a79a A2a19313 9b57 4e25 8fa5 35f75d23a79a
  1. Layer 1: Input Sources — Discord、Slackなど各メッセンジャーとの接続
  2. Layer 2: WebSocket Gateway — ローカルで動作するWebSocketサーバー。メッセージのルーティングやセッション管理
  3. Layer 3: Agent Runtime — LLMにプロンプトを送り、ツール(bash、ファイル操作など)を実行するエージェント本体
  4. Layer 4: LLM Providers — Anthropic、OpenAI、Geminiなど複数のLLMプロバイダーへの接続
  5. Layer 5: Output & Storage — セッション永続化、メモリ/RAG、音声合成、ネイティブアプリなどの出力系

この記事では、Layer 1 → Layer 2 → Layer 3 の通信経路に焦点を当て、以下の疑問にソースコードを追いながら答えます。

  • DiscordやSlackのメッセージが、なぜローカルマシンに届くのか
  • なぜファイル操作やbashコマンドの実行が可能なのか

対象読者

  • 「外部サービス→ローカル実行」の通信設計を理解したい方
  • AIエージェントのネットワーク構成に興味がある方

目次

前提:OpenClawは外向き接続(Outbound)である

DiscordやSlackからローカルマシンに「直接アクセス」しているわけではありません。OpenClawのほぼすべてのチャネルは、ローカルマシンから外部サーバーへの「外向き接続(Outbound)」で成り立っています。


スクリーンショット 2026 02 16 16.25.28

つまり、外部から自分のマシンへのインバウンド接続は存在しません。NAT配下やファイアウォールの内側でも動作します。

チャネルごとの接続パターン

パターン1: 外向きWebSocket接続

Discord、Slack、WhatsAppはこのパターンです。

Discord

discord.jsライブラリが、Discord公式のGateway API(wss://gateway.discord.gg)へ外向きWebSocket接続を張ります。

import { Client, GatewayIntentBits } from 'discord.js'

// discord.js が wss://gateway.discord.gg へ outbound WebSocket を張る
const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
})

client.on('messageCreate', async (message) => {
  // Discord → OpenClaw auto-reply パイプラインへハンドオフ
  const msgContext = normalizeToMsgContext(message)
  await autoReplyPipeline.handle(msgContext)
})

// ローカル → Discord サーバーへの outbound 接続
client.login(botToken)

Source: extensions/discord/src/monitor.ts

Discordサーバーからの「プッシュ」に見えますが、実際にはOpenClaw側が張ったWebSocketコネクション上でイベントを受信しているだけです。

Slack

@slack/boltのSocket Modeを使用します。これもSlack側のAPIサーバーへの外向きWebSocket接続です。

import { App } from '@slack/bolt'

// Socket Mode: ローカル → Slack API へ outbound WebSocket を張る
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true, // HTTPサーバーではなくWebSocket接続を使う
})

app.message(async ({ message, say }) => {
  // Slack → OpenClaw auto-reply パイプラインへハンドオフ
  const msgContext = normalizeToMsgContext(message)
  await autoReplyPipeline.handle(msgContext)
})

// ローカル → Slack API への outbound 接続を開始
await app.start()

Source: extensions/slack/src/monitor.ts

WhatsApp

@whiskeysockets/baileysライブラリが、WhatsApp Webのプロトコルを使用してWhatsAppサーバーへ外向き接続します。初回接続時にQRコードを読み取る必要があります。

import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys'

// セッション情報をファイルに永続化(2回目以降はQR不要)
const { state, saveCreds } = await useMultiFileAuthState('./auth')

// ローカル → WhatsApp サーバーへ outbound WebSocket を張る
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)

// 初回接続時はQRコードをターミナルに表示
sock.ev.on('connection.update', ({ qr }) => {
  if (qr) printQRCode(qr)
})

sock.ev.on('messages.upsert', async ({ messages }) => {
  // WhatsApp → OpenClaw auto-reply パイプラインへハンドオフ
  const msgContext = normalizeToMsgContext(messages[0])
  await autoReplyPipeline.handle(msgContext)
})

Source: extensions/whatsapp/src/monitor.ts

パターン2: 外向きHTTPポーリング

Telegramはこのパターンです。

import { Bot } from 'grammy'

// Bot Token で Telegram Bot API に接続
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN)

bot.on('message:text', async (ctx) => {
  // Telegram → OpenClaw auto-reply パイプラインへハンドオフ
  const msgContext = normalizeToMsgContext(ctx.message)
  await autoReplyPipeline.handle(msgContext)
})

// long polling 開始: ローカル → Telegram API へ getUpdates を送り続ける
bot.start()

Source: extensions/telegram/src/monitor.ts

Long pollingなので、ローカルからTelegram APIへHTTPリクエストを送り続け、新着があればレスポンスとして返ってくる仕組みです。

パターン3: 外向き接続 + ローカルプロセス連携

Signal は signal-cli というローカルバイナリ経由で動作します。

// signal-cli が別プロセスで Signal サーバーと通信(outbound)
// OpenClaw は signal-cli の REST API(localhost:8080)経由でやり取り

const SIGNAL_CLI_API = 'http://localhost:8080/api/v1'

// signal-cli の receive エンドポイントをポーリング
const pollMessages = async () => {
  const res = await fetch(`${SIGNAL_CLI_API}/receive/${phoneNumber}`)
  const messages = await res.json()

  for (const msg of messages) {
    const msgContext = normalizeToMsgContext(msg)
    await autoReplyPipeline.handle(msgContext)
  }
}

setInterval(pollMessages, 1000)

Source: extensions/signal/src/monitor.ts

パターン4: インバウンドWebhook(例外的)

Google ChatやMicrosoft Teamsはこのパターンです。これらのプラットフォームはWebhookでメッセージを配信するため、外部からOpenClawへのHTTPアクセスが必要です。


Generated Image E955d787 F9ec 4bf8 9523 03bc9335f957 E955d787 F9ec 4bf8 9523 03bc9335f957

この場合はTailscale ServeまたはFunnelを使って、ローカルのGatewayをインターネットから到達可能にする必要があります。

import { execSync } from 'child_process'

// Gateway は localhost のみにバインド(外部から直接アクセス不可)
const GATEWAY_HOST = '127.0.0.1'
const GATEWAY_PORT = 18789

// Tailscale Serve: tailnet 内の他デバイスからアクセス可能にする
// tailscale serve --bg https+insecure://127.0.0.1:18789
execSync(`tailscale serve --bg https+insecure://${GATEWAY_HOST}:${GATEWAY_PORT}`)

// Tailscale Funnel: インターネット全体に公開(Webhook 受信用)
// tailscale funnel --bg https+insecure://127.0.0.1:18789
execSync(`tailscale funnel --bg https+insecure://${GATEWAY_HOST}:${GATEWAY_PORT}`)

// Google Chat / Teams の Webhook URL:
// https://your-node.tailnet.ts.net/googlechat

Source: src/gateway/server-tailscale.ts

💡 ほとんどのチャネルはインバウンド不要

Discord、Slack、Telegram、WhatsApp、Signalなど主要チャネルはすべて外向き接続なので、ポートフォワーディングやパブリックIPは不要です。Webhook必須のGoogle ChatやTeamsを使わない限り、完全にNAT内で動作します。

接続パターンのまとめ

チャネル接続方向プロトコルポート開放
Discord
Outbound
WebSocket
不要
Slack
Outbound
WebSocket (Socket Mode)
不要
WhatsApp
Outbound
WebSocket (Baileys)
不要
Telegram
Outbound
HTTP Long Polling
不要
Signal
Outbound
signal-cli → Signal
不要
iMessage
Local
BlueBubbles REST API
不要
Google Chat
Inbound
Webhook POST
必要
Teams
Inbound
Bot Framework
必要
Matrix
Outbound
HTTP Sync API
不要

Gateway内部の通信

チャネルからGatewayへのハンドオフ

外部プラットフォームからメッセージを受信したチャネルプラグインは、Gateway内部のauto-replyパイプラインにメッセージを渡します。この受け渡しはプロセス内の関数呼び出しです。


Generated Image 6f1002a8 De7d 423f 9ca8 C3f7218cc791 6f1002a8 De7d 423f 9ca8 C3f7218cc791

重要なのは、チャネルプラグインとGateway、そしてAgent Runtimeがすべて同一プロセス内で動作している点です。WebSocket経由のプロセス間通信ではなく、TypeScriptの関数呼び出しでメッセージが流れます。

エージェントの呼び出し

GatewayからAgentへのメッセージ受け渡しには2つのモードがあります。

Embeddedモード(デフォルト)

エージェントがGatewayと同一プロセス内で動作します。runEmbeddedPiAgent 関数が直接呼び出され、LLMとのやり取りもこのプロセス内で行われます。

RPCモード

エージェントが別プロセスとして起動され、WebSocket経由でGatewayと通信します。Dockerサンドボックスを使う場合はこのモードになります。

// Embeddedモード: Gateway プロセス内で直接エージェントを実行
const runEmbeddedPiAgent = async (msgContext: MsgContext) => {
  const messages = buildPrompt(msgContext)
  const response = await llm.chat(messages) // LLM API 呼び出し
  const toolCalls = response.toolCalls ?? []

  for (const call of toolCalls) {
    const result = await executeTool(call) // bash, fs 等のツール実行
    messages.push({ role: 'tool', content: result })
  }

  return await llm.chat(messages) // 最終応答
}

// RPCモード: 別プロセスのエージェントへ WebSocket でリレー
// const ws = new WebSocket('ws://localhost:18790/agent')
// ws.send(JSON.stringify(msgContext))

Source: src/agents/embedded-agent.ts

なぜbashを実行できるのか

シンプルな答え:Node.jsプロセスの権限

OpenClawのGatewayはNode.jsプロセスとして動作します。このプロセスはあなたのユーザー権限で実行されているため、あなたができることはすべてできます。


Generated Image 8ab14fcb Fa8c 497d Aad6 160fca93a4d2 8ab14fcb Fa8c 497d Aad6 160fca93a4d2 1

コマンド実行の実装

エージェントがbashコマンドを実行する際は、Node.jsの child_process.spawn または node-pty(擬似ターミナル)を使ってサブプロセスを起動します。

import { spawn } from 'child_process'
import * as pty from 'node-pty'

// execツール: 単発コマンド実行
const execTool = (command: string): Promise<string> => {
  return new Promise((resolve, reject) => {
    const proc = spawn('bash', ['-c', command])
    let output = ''
    proc.stdout.on('data', (data) => {
      output += data
    })
    proc.stderr.on('data', (data) => {
      output += data
    })
    proc.on('close', () => resolve(output))
  })
}

// bashツール: 対話的シェルセッション(node-pty で擬似ターミナル)
const bashSession = pty.spawn('bash', [], {
  name: 'xterm-256color',
  cols: 120,
  rows: 30,
})

// ファイル操作: Node.js の fs モジュールで直接読み書き
// import { readFile, writeFile } from 'fs/promises'

Source: src/agents/tools/

macOSの権限(TCC)

macOSのネイティブアプリ(メニューバーアプリ)を使う場合は、TCC(Transparency, Consent, and Control)による権限管理があります。

// ノードが公開するケイパビリティ
enum Permission {
  case appleScript      // Automation権限
  case notifications    // 通知
  case accessibility    // アクセシビリティ
  case screenRecording  // 画面録画
  case microphone       // マイク
  case speechRecognition // 音声認識
  case camera           // カメラ
  case location         // 位置情報
}

Source: apps/macos/Sources/OpenClawIPC/IPC.swift

ただしこれはmacOSアプリ経由の場合のみです。CLIで直接 node を起動した場合、Node.jsプロセスはTerminalアプリのTCC権限を継承します。Terminalにフルディスクアクセスを許可していれば、OpenClawも同等の権限を持ちます。

まとめ

OpenClawの通信経路を追った結果、以下のことが分かりました。

  • 主要チャネル(Discord、Slack、Telegram、WhatsApp)はすべてローカルから外部への外向き接続で成り立っている。ポート開放やパブリックIPは不要
  • Gateway、Agent Runtime、ツール実行はデフォルトで同一Node.jsプロセス内で動作する。bash実行はNode.jsの child_process によるもの
  • LLMが判断したツール呼び出しは、ユーザー権限でそのまま実行される。これが利便性の源泉であると同時にリスクでもある

通信経路を理解すると、次に気になるのは「この仕組みはどこが攻撃されうるのか」という点です。実際の攻撃事例や防御機構についてはセキュリティモデルの記事でも触れています。

参考リンク

この記事が参考になったら

この記事が役に立ったと思いましたら、よろしければフォローをお願いします。

X (Twitter)Xをフォローする

AIエージェントと外部サービスの連携基盤の構築に興味がありましたら、お気軽にご連絡ください。

株式会社hanzochangお問い合わせフォーム
picture
hanzochang - 半澤勇大
慶應義塾大学卒業後、Webプランナーとして勤務。 ナショナルクライアントのキャンペーンサイトの企画・演出を担当。 その後開発会社に創業メンバーとして参加。 Fintech案件や大手企業のDXプロジェクトに関わり、その後個人事業主として独立し、 2023年にWeb3に特化した開発会社として法人化しました。 現在はWeb3アプリ開発を中心にAI開発フローの整備を行っています。
また、趣味で2017年ごろより匿名アカウントでCryptoの調査等を行い、 ブロックチェーンメディアやSNSでビットコイン論文等の図解等を発信していました。
X (Twitter)

最新の記事

FDE(Forward Deployed Engineering)とは - 中小企業のAI導入で活きる場面

FDE(Forward Deployed Engineering)とは - 中小企業のAI導入で活きる場面

FDE(Forward Deployed Engineering)はPalantirが体系化した顧客現場型エンジニアリング手法です。中小企業のAI導入においてFDEが活きる具体的な場面と、従来のSIer・コンサル・SaaSとの違いを解説します。

OpenClaw セキュリティと攻撃の実態 - CVE-2026-25253・プロンプトインジェクション・ClawHub

OpenClaw セキュリティと攻撃の実態 - CVE-2026-25253・プロンプトインジェクション・ClawHub

OpenClaw(旧Moltbot)への実際の攻撃事例を解説。CVE-2026-25253(1-Click RCE)、CVE-2026-24763(Docker脱出)、Zenity間接プロンプトインジェクション、ClawHubサプライチェーン攻撃と、ソースコードレベルの防御機構を詳述します。

OpenClawの仕組み アーキテクチャ解剖 - ソースコードで読む5レイヤー設計

OpenClawの仕組み アーキテクチャ解剖 - ソースコードで読む5レイヤー設計

OpenClaw(旧Moltbot/Clawdbot)のソースコードを徹底解読。Input Sources・WebSocket Gateway・Agent Runtime・LLM Providers・Output & Storageの5レイヤー構成、ChannelPluginインターフェース、エージェントループ、スキルシステムの設計思想を解説します。

[並列作業] ClaudeCodeに声でリマインダさせて、待ち時間放置時間をゼロに!

[並列作業] ClaudeCodeに声でリマインダさせて、待ち時間放置時間をゼロに!

Claude Codeが完了や質問待ちになったら音声で喋って教えてくれる。放置が続けば繰り返しリマインド、応答したら自動停止。セッション終了後のリマインダ残留問題の対策と、ミュート切り替え機能も解説。プロンプト付き。

[並列作業] ClaudeCodeの進捗を「音+案件名」で通知!プロンプト付き!

[並列作業] ClaudeCodeの進捗を「音+案件名」で通知!プロンプト付き!

Claude Codeを複数プロジェクトで並列に走らせていると、どこが終わったか見失いがち。Hooksとsayコマンドで「効果音+案件名の読み上げ」を設定すれば、画面を見なくても進捗が分かります。コピペで設定できるプロンプト付き。

MCP Apps入門 - Skill付き!AIチャット内にオリジナルUIを埋め込む

MCP Apps入門 - Skill付き!AIチャット内にオリジナルUIを埋め込む

Model Context Protocol Appsを使ってAIクライアント内にHTML UIを埋め込む方法を解説します。テキストだけでは伝えきれない色やチャートのようなビジュアル情報をインタラクティブに表示する技術を学べます。