
OpenClaw 通信・接続の仕組み - なぜDiscordのメッセージでローカルのbashが動くのか
OpenClawはなぜDiscordやSlackからローカルのbashを操作できるのか?外向きWebSocket接続の仕組み、Gateway内部のメッセージフロー、Node.jsプロセスの権限継承まで、ソースコードを追いながら通信経路を解説します。
公開日2026.02.14
更新日2026.02.14
はじめに
OpenClawは、ローカルマシン上で動くパーソナルAIアシスタントのOSSです。Discord、Slack、Telegram、WhatsAppなど38以上のメッセージングプラットフォームを1つのAIが横断的に対応します。
OpenClawの構造は5つのレイヤーで構成されています。

- Layer 1: Input Sources — Discord、Slackなど各メッセンジャーとの接続
- Layer 2: WebSocket Gateway — ローカルで動作するWebSocketサーバー。メッセージのルーティングやセッション管理
- Layer 3: Agent Runtime — LLMにプロンプトを送り、ツール(bash、ファイル操作など)を実行するエージェント本体
- Layer 4: LLM Providers — Anthropic、OpenAI、Geminiなど複数のLLMプロバイダーへの接続
- Layer 5: Output & Storage — セッション永続化、メモリ/RAG、音声合成、ネイティブアプリなどの出力系
この記事では、Layer 1 → Layer 2 → Layer 3 の通信経路に焦点を当て、以下の疑問にソースコードを追いながら答えます。
- DiscordやSlackのメッセージが、なぜローカルマシンに届くのか
- なぜファイル操作やbashコマンドの実行が可能なのか
対象読者
- 「外部サービス→ローカル実行」の通信設計を理解したい方
- AIエージェントのネットワーク構成に興味がある方
目次
目次
前提:OpenClawは外向き接続(Outbound)である
DiscordやSlackからローカルマシンに「直接アクセス」しているわけではありません。OpenClawのほぼすべてのチャネルは、ローカルマシンから外部サーバーへの「外向き接続(Outbound)」で成り立っています。

つまり、外部から自分のマシンへのインバウンド接続は存在しません。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
@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アクセスが必要です。

この場合は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/googlechatSource: 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パイプラインにメッセージを渡します。この受け渡しはプロセス内の関数呼び出しです。

重要なのは、チャネルプラグインと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プロセスとして動作します。このプロセスはあなたのユーザー権限で実行されているため、あなたができることはすべてできます。

コマンド実行の実装
エージェントが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が判断したツール呼び出しは、ユーザー権限でそのまま実行される。これが利便性の源泉であると同時にリスクでもある
通信経路を理解すると、次に気になるのは「この仕組みはどこが攻撃されうるのか」という点です。実際の攻撃事例や防御機構についてはセキュリティモデルの記事でも触れています。
参考リンク
- OpenClaw GitHub リポジトリ
- OpenClaw アーキテクチャ徹底解剖 — 5レイヤー構成の全体像
- OpenClawのセキュリティモデル — 攻撃事例と防御機構
この記事が参考になったら
この記事が役に立ったと思いましたら、よろしければフォローをお願いします。
AIエージェントと外部サービスの連携基盤の構築に興味がありましたら、お気軽にご連絡ください。
株式会社hanzochangお問い合わせフォーム最新の記事

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

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

OpenClawの仕組み アーキテクチャ解剖 - ソースコードで読む5レイヤー設計
OpenClaw(旧Moltbot/Clawdbot)のソースコードを徹底解読。Input Sources・WebSocket Gateway・Agent Runtime・LLM Providers・Output & Storageの5レイヤー構成、ChannelPluginインターフェース、エージェントループ、スキルシステムの設計思想を解説します。
![[並列作業] ClaudeCodeに声でリマインダさせて、待ち時間放置時間をゼロに!](/_next/image?url=https%3A%2F%2Fs3.ap-northeast-1.amazonaws.com%2Fhanzochang.com%2F_v2%2F1770701250089_eaz028bl2ee.png&w=3840&q=75&dpl=dpl_BtBZuFBVgbWiVY8kSTnA52T6Bt1u)
[並列作業] ClaudeCodeに声でリマインダさせて、待ち時間放置時間をゼロに!
Claude Codeが完了や質問待ちになったら音声で喋って教えてくれる。放置が続けば繰り返しリマインド、応答したら自動停止。セッション終了後のリマインダ残留問題の対策と、ミュート切り替え機能も解説。プロンプト付き。
![[並列作業] ClaudeCodeの進捗を「音+案件名」で通知!プロンプト付き!](/_next/image?url=https%3A%2F%2Fs3.ap-northeast-1.amazonaws.com%2Fhanzochang.com%2F_v2%2F1770700583435_vn59k4qd8b.png&w=3840&q=75&dpl=dpl_BtBZuFBVgbWiVY8kSTnA52T6Bt1u)
[並列作業] ClaudeCodeの進捗を「音+案件名」で通知!プロンプト付き!
Claude Codeを複数プロジェクトで並列に走らせていると、どこが終わったか見失いがち。Hooksとsayコマンドで「効果音+案件名の読み上げ」を設定すれば、画面を見なくても進捗が分かります。コピペで設定できるプロンプト付き。

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