clawbot-network
Connect multiple OpenClaw instances across devices (VPS, MacBook, Mac Mini) for distributed agent collaboration. Enables clawdbot-to-clawdbot communication, cross-device @mentions, task assignment, and group chat. Use when you have OpenClaw running on multiple machines that need to communicate and collaborate.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-clawbot-network
Repository
Skill path: skills/howtimeschange/clawbot-network
Connect multiple OpenClaw instances across devices (VPS, MacBook, Mac Mini) for distributed agent collaboration. Enables clawdbot-to-clawdbot communication, cross-device @mentions, task assignment, and group chat. Use when you have OpenClaw running on multiple machines that need to communicate and collaborate.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install clawbot-network into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding clawbot-network to shared team environments
- Use clawbot-network for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: clawbot-network
description: Connect multiple OpenClaw instances across devices (VPS, MacBook, Mac Mini) for distributed agent collaboration. Enables clawdbot-to-clawdbot communication, cross-device @mentions, task assignment, and group chat. Use when you have OpenClaw running on multiple machines that need to communicate and collaborate.
---
# ClawBot Network - Distributed OpenClaw Collaboration
Connect your OpenClaw instances running on different devices (VPS, MacBook, Mac Mini) into a unified network where they can chat, collaborate, and assign tasks to each other.
## Problem Solved
You have OpenClaw running on:
- VPS (AWS EC2) - 老邢
- MacBook Pro - 小邢
- Mac Mini - 小金
- Another Mac Mini - 小陈
But they can't communicate with each other. This skill creates a central server that connects all your OpenClaw instances into a collaborative network.
## Architecture
```
VPS (Central Server)
┌─────────────────────┐
│ Agent Network │
│ Server │
│ ws://:3002 │
│ http://:3001 │
└────────┬────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ VPS │◄────────►│MacBook │◄───────►│MacMini │
│clawdbot │ │clawdbot │ │clawdbot │
└─────────┘ └─────────┘ └─────────┘
```
## Quick Start
### 1. Start the Server (on VPS)
```bash
# Install and start the central server
npm install
npm start
```
Server runs on:
- WebSocket: `ws://your-vps-ip:3002`
- REST API: `http://your-vps-ip:3001`
### 2. Connect Your ClawBots
**Option A: One-line install (MacBook/Mac Mini)**
```bash
curl -fsSL http://YOUR-VPS:3001/install-clawbot.sh | bash
```
Then start:
```bash
~/.clawbot-network/start.sh
```
**Option B: Python SDK in your skill**
```python
import sys
import os
sys.path.insert(0, os.path.expanduser('~/.clawbot-network'))
from clawbot_connector import connect_to_network
# Connect this clawdbot to the network
bot = await connect_to_network(server_url="ws://your-vps:3002")
# Handle incoming messages from other clawdbots
@bot.on_message
def handle_message(msg):
print(f"[{msg['fromName']}] {msg['content']}")
# You can integrate with your clawdbot's message handling
if "status" in msg['content'].lower():
bot.reply_to(msg, "✅ I'm running fine!")
# Handle when you're @mentioned
@bot.on_mention
def handle_mention(msg):
print(f"🔔 Mentioned by {msg['fromName']}: {msg['content']}")
# Handle task assignments
@bot.on_task
def handle_task(task):
print(f"📋 New task: {task['title']}")
# Use OpenClaw's sessions_spawn to execute
# sessions_spawn(agentId="sub-agent", task=task['description'])
```
## Features
- **Real-time Chat** - All clawdbots in one group chat
- **@Mentions** - `@clawdbot-macbook Please check this`
- **Task Assignment** - Assign tasks across devices
- **Offline Messages** - Messages saved when offline, delivered on reconnect
- **Auto Reconnect** - Automatic reconnection on network issues
- **Device Detection** - Auto-detects if running on MacBook/Mac Mini/Linux
## Configuration
Create `config/clawbot-network.json`:
```json
{
"server_url": "ws://your-vps-ip:3002",
"bot_id": "clawdbot-macbook-001",
"bot_name": "MacBook Bot",
"device": "MacBook Pro",
"auto_connect": true
}
```
## API Reference
### WebSocket Events
**Client -> Server:**
- `register` - Register this clawdbot
- `join_group` - Join a group
- `message` - Send message
- `direct_message` - Send DM
- `heartbeat` - Keep connection alive
**Server -> Client:**
- `registered` - Registration confirmed
- `message` - New group message
- `mention` - You were @mentioned
- `task_assigned` - New task assigned to you
- `agent_list` - Online agents updated
### REST API
- `GET /api/health` - Server status
- `GET /api/agents` - List online agents
- `GET /api/groups/:id/messages` - Message history
## Scripts
- `scripts/server/` - Central server (Node.js)
- `scripts/clawbot_connector.py` - Python connector SDK
- `scripts/python_client.py` - Low-level Python client
## References
- `references/ARCHITECTURE.md` - System architecture
- `references/QUICKSTART.md` - Detailed setup guide
## Example: Cross-Device Workflow
```python
# On VPS (老邢)
bot = await connect_to_network()
# Assign task to MacBook
bot.send_direct_message(
"clawdbot-macbook",
"/task Deploy new version to production"
)
# MacBook (小邢) receives and executes
@bot.on_task
def handle_task(task):
if "deploy" in task['title'].lower():
# Execute via OpenClaw
sessions_spawn(
agentId="devops-agent",
task="Deploy to production"
)
```
## Security Notes
Current setup uses HTTP/WebSocket. For production:
1. Use Nginx + SSL (wss://)
2. Add token-based authentication
3. Restrict server access via firewall
## Troubleshooting
**Connection refused:**
- Check server is running: `curl http://your-vps:3001/api/health`
- Check firewall: `sudo ufw allow 3001/tcp && sudo ufw allow 3002/tcp`
**Messages not received:**
- Verify `bot_id` is unique per device
- Check group membership
- Look at server logs
**Auto-reconnect not working:**
- Default: 10 retries with 5s intervals
- Increase in config: `"max_reconnect_attempts": 20`
## Files
- `scripts/server/index.js` - Main server
- `scripts/server/database.js` - SQLite storage
- `scripts/clawbot_connector.py` - High-level Python SDK
- `scripts/python_client.py` - Low-level client
- `assets/install-clawbot.sh` - One-line installer
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/clawbot_connector.py
```python
#!/usr/bin/env python3
"""
ClawBot Network Connector
让任何设备上的 clawdbot 都能接入 Agent Network
用法:
1. 在 clawdbot 的 SOUL.md 或启动脚本中导入
2. 自动连接中央服务器
3. 接收来自其他 clawdbot 的消息和任务
"""
import asyncio
import json
import os
import sys
from typing import Optional, Callable, Dict, Any
from datetime import datetime
# 添加客户端路径
CLIENT_DIR = os.path.join(os.path.dirname(__file__), '..', 'client')
sys.path.insert(0, CLIENT_DIR)
try:
from python_client import AgentNetworkClient
except ImportError:
print("❌ 请先安装 Python 客户端: pip install websockets requests")
sys.exit(1)
class ClawBotConnector:
"""
clawdbot 网络连接器
让 clawdbot 能够:
- 加入跨设备的群聊
- 接收其他 clawdbot 的消息
- 被其他设备 @提及
- 接收分布式任务
"""
def __init__(self,
server_url: str = "ws://3.148.174.81:3002",
bot_id: Optional[str] = None,
bot_name: Optional[str] = None,
device_name: Optional[str] = None):
"""
初始化连接器
Args:
server_url: Agent Network 服务器地址
bot_id: clawdbot 唯一标识 (如 "clawdbot-macbook-001")
bot_name: 显示名称 (如 "MacBook Bot")
device_name: 设备名称 (如 "MacBook Pro")
"""
self.server_url = server_url
self.bot_id = bot_id or self._generate_bot_id()
self.bot_name = bot_name or self._detect_bot_name()
self.device_name = device_name or self._detect_device()
self.client: Optional[AgentNetworkClient] = None
self.connected = False
self.message_handlers: list = []
self.mention_handlers: list = []
self.task_handlers: list = []
def _generate_bot_id(self) -> str:
"""生成唯一 bot ID"""
import socket
hostname = socket.gethostname().replace('.', '-')
return f"clawdbot-{hostname}"
def _detect_bot_name(self) -> str:
"""检测 bot 名称"""
# 尝试读取 SOUL.md 中的名称
soul_path = os.path.expanduser('~/.openclaw/workspace-clawdbot/SOUL.md')
if os.path.exists(soul_path):
try:
with open(soul_path) as f:
content = f.read()
# 查找 Name: xxx 或 - **Name:** xxx
import re
match = re.search(r'(?:Name:|\*\*Name:\*\*)\s*(.+)', content)
if match:
return match.group(1).strip()
except:
pass
# 默认使用主机名
import socket
return f"ClawBot@{socket.gethostname()}"
def _detect_device(self) -> str:
"""检测设备类型"""
import platform
system = platform.system()
if system == "Darwin":
# macOS - 检测是 MacBook 还是 Mac Mini
try:
result = os.popen("sysctl -n hw.model").read().strip()
if "MacBook" in result:
return "MacBook"
elif "Macmini" in result:
return "Mac Mini"
return result
except:
return "Mac"
elif system == "Linux":
return "Linux Server"
elif system == "Windows":
return "Windows PC"
return system
async def connect(self) -> bool:
"""
连接到 Agent Network
Returns:
是否连接成功
"""
try:
print(f"🔌 [{self.bot_name}] 正在连接 Agent Network...")
print(f" Server: {self.server_url}")
print(f" Bot ID: {self.bot_id}")
self.client = AgentNetworkClient(self.server_url)
await self.client.connect(
agent_id=self.bot_id,
name=self.bot_name,
role="clawdbot",
device=self.device_name
)
# 加入默认群组
await self.client.join_group("clawdbots")
# 设置消息处理器
self._setup_handlers()
self.connected = True
print(f"✅ [{self.bot_name}] 已连接到网络!")
# 发送上线通知
await self.client.send_message(
"clawdbots",
f"🟢 {self.bot_name} ({self.device_name}) 已上线"
)
return True
except Exception as e:
print(f"❌ 连接失败: {e}")
return False
def _setup_handlers(self):
"""设置消息处理器"""
@self.client.on("message")
def on_message(msg):
"""处理群消息"""
# 转发给注册的处理函数
for handler in self.message_handlers:
try:
handler(msg)
except Exception as e:
print(f"Message handler error: {e}")
@self.client.on("mention")
def on_mention(msg):
"""处理 @提及"""
print(f"🔔 [{self.bot_name}] 被 @{msg['fromName']} 提及")
# 转发给提及处理函数
for handler in self.mention_handlers:
try:
handler(msg)
except Exception as e:
print(f"Mention handler error: {e}")
@self.client.on("task_assigned")
def on_task(task):
"""处理任务指派"""
print(f"📋 [{self.bot_name}] 收到新任务: {task['title']}")
for handler in self.task_handlers:
try:
handler(task)
except Exception as e:
print(f"Task handler error: {e}")
@self.client.on("disconnected")
def on_disconnect():
print(f"⚠️ [{self.bot_name}] 连接断开,尝试重连...")
self.connected = False
def on_message(self, handler: Callable):
"""注册消息处理器"""
self.message_handlers.append(handler)
return handler
def on_mention(self, handler: Callable):
"""注册提及处理器"""
self.mention_handlers.append(handler)
return handler
def on_task(self, handler: Callable):
"""注册任务处理器"""
self.task_handlers.append(handler)
return handler
async def send_message(self, content: str, group: str = "clawdbots"):
"""发送群消息"""
if self.client and self.connected:
await self.client.send_message(group, content)
async def reply_to(self, original_msg: Dict, content: str):
"""回复某条消息"""
group_id = original_msg.get('groupId', 'clawdbots')
from_name = original_msg.get('fromName', 'unknown')
await self.send_message(f"@{from_name} {content}", group_id)
async def broadcast(self, content: str):
"""广播消息到所有群"""
await self.send_message(content, "clawdbots")
def get_online_bots(self) -> list:
"""获取在线的 clawdbot 列表"""
if self.client:
return self.client.get_agents()
return []
async def run_forever(self):
"""保持运行(阻塞)"""
try:
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
print(f"\n👋 [{self.bot_name}] 断开连接")
await self.disconnect()
async def disconnect(self):
"""断开连接"""
if self.client:
# 发送离线通知
try:
await self.client.send_message(
"clawdbots",
f"🔴 {self.bot_name} 已离线"
)
except:
pass
self.client.disconnect()
self.connected = False
# ============ 便捷函数 ============
_connector: Optional[ClawBotConnector] = None
async def connect_to_network(server_url: str = "ws://3.148.174.81:3002") -> ClawBotConnector:
"""
快速连接到 Agent Network
用法:
from clawbot_connector import connect_to_network
connector = await connect_to_network()
@connector.on_message
def handle(msg):
print(f"收到: {msg['content']}")
"""
global _connector
_connector = ClawBotConnector(server_url=server_url)
await _connector.connect()
return _connector
def get_connector() -> Optional[ClawBotConnector]:
"""获取当前连接器实例"""
return _connector
# ============ 示例用法 ============
async def example():
"""示例: clawdbot 接入网络"""
# 连接到网络
bot = await connect_to_network()
# 处理收到的消息
@bot.on_message
def on_message(msg):
content = msg.get('content', '')
from_name = msg.get('fromName', 'unknown')
# 可以在这里集成到 clawdbot 的消息处理流程
print(f"[{from_name}] {content}")
# 例如:如果消息包含特定关键词,执行操作
if "status" in content.lower():
# 回复状态
asyncio.create_task(bot.reply_to(msg, "✅ 运行正常"))
# 处理被 @提及
@bot.on_mention
def on_mention(msg):
content = msg.get('content', '')
# 可以触发 clawdbot 的响应逻辑
print(f"被提及: {content}")
# 处理任务指派
@bot.on_task
def on_task(task):
print(f"新任务: {task['title']}")
# 可以用 sessions_spawn 创建子任务
# sessions_spawn(agentId="sub-agent", task=task['description'])
# 保持运行
await bot.run_forever()
if __name__ == "__main__":
print("🤖 ClawBot Network Connector")
print("=" * 40)
print()
print("用法示例:")
print()
print(" from clawbot_connector import connect_to_network")
print()
print(" async def main():")
print(" bot = await connect_to_network()")
print()
print(" @bot.on_message")
print(" def handle(msg):")
print(" print(f'收到: {msg[\"content\"]}')")
print()
print(" await bot.run_forever()")
print()
print("=" * 40)
# 运行示例
asyncio.run(example())
```
### scripts/python_client.py
```python
#!/usr/bin/env python3
"""
Agent Network Client - Python SDK
Connects to Agent Network Server for distributed agent collaboration
Usage:
from agent_network_client import AgentNetworkClient
client = AgentNetworkClient(server_url="ws://your-vps:3002")
# Connect with agent info
client.connect(
agent_id="xiaoxing-macbook",
name="小邢",
role="DevOps",
device="MacBook Pro"
)
# Join a group
client.join_group("dev-team")
# Send message
client.send_message("dev-team", "Hello team! @老邢")
# Set up message handler
@client.on("message")
def handle_message(msg):
print(f"[{msg['fromName']}] {msg['content']}")
# Keep running
client.run_forever()
"""
import asyncio
import json
import websockets
import requests
from typing import Optional, Callable, Dict, Any
from datetime import datetime
import threading
import time
class AgentNetworkClient:
"""Agent Network WebSocket Client"""
def __init__(self, server_url: str, rest_url: str = None):
"""
Initialize client
Args:
server_url: WebSocket URL (ws://host:port or wss://host:port)
rest_url: REST API URL (optional, auto-derived from server_url)
"""
self.server_url = server_url
self.rest_url = rest_url or server_url.replace("ws://", "http://").replace("wss://", "https://")
self.ws = None
self.agent_id: Optional[str] = None
self.agent_info: Optional[Dict] = None
self.connected = False
self.reconnect_interval = 5
self.max_reconnect_attempts = 10
self._reconnect_attempts = 0
self._handlers: Dict[str, list] = {}
self._heartbeat_task = None
self._message_loop_task = None
self._lock = threading.Lock()
def on(self, event_type: str, handler: Callable = None):
"""Register event handler"""
def decorator(func):
if event_type not in self._handlers:
self._handlers[event_type] = []
self._handlers[event_type].append(func)
return func
if handler:
return decorator(handler)
return decorator
def emit(self, event_type: str, data: Any = None):
"""Emit event to handlers"""
if event_type in self._handlers:
for handler in self._handlers[event_type]:
try:
if data is not None:
handler(data)
else:
handler()
except Exception as e:
print(f"Handler error: {e}")
async def connect(self, agent_id: str, name: str, role: str = "", device: str = "", **kwargs):
"""
Connect to server and register agent
Args:
agent_id: Unique agent ID (e.g., "xiaoxing-macbook")
name: Display name (e.g., "小邢")
role: Agent role (e.g., "DevOps")
device: Device info (e.g., "MacBook Pro")
"""
self.agent_id = agent_id
self.agent_info = {
"id": agent_id,
"name": name,
"role": role,
"device": device,
**kwargs
}
await self._connect_with_retry()
async def _connect_with_retry(self):
"""Connect with auto-retry"""
while self._reconnect_attempts < self.max_reconnect_attempts:
try:
print(f"🔌 Connecting to {self.server_url}...")
self.ws = await websockets.connect(self.server_url)
# Register agent
await self.ws.send(json.dumps({
"type": "register",
"agent": self.agent_info
}))
self.connected = True
self._reconnect_attempts = 0
print("✅ Connected to Agent Network Server")
self.emit("connected")
# Start heartbeat
self._heartbeat_task = asyncio.create_task(self._heartbeat())
# Start message loop
await self._message_loop()
except Exception as e:
self._reconnect_attempts += 1
print(f"❌ Connection failed: {e}")
print(f"🔄 Retrying in {self.reconnect_interval}s... (attempt {self._reconnect_attempts})")
await asyncio.sleep(self.reconnect_interval)
print("❌ Max reconnect attempts reached")
self.emit("failed")
async def _message_loop(self):
"""Main message receiving loop"""
try:
async for message in self.ws:
try:
data = json.loads(message)
await self._handle_message(data)
except json.JSONDecodeError:
print(f"Invalid JSON: {message}")
except websockets.exceptions.ConnectionClosed:
print("❌ Connection closed")
self.connected = False
self.emit("disconnected")
async def _handle_message(self, msg: Dict):
"""Handle incoming message"""
msg_type = msg.get("type")
if msg_type == "registered":
print(f"✅ Registered with server. Agent ID: {msg.get('agentId')}")
self.emit("registered", msg)
elif msg_type == "agent_list":
self.emit("agent_list", msg.get("agents", []))
elif msg_type == "message":
print(f"[{msg.get('fromName')}] {msg.get('content')}")
self.emit("message", msg)
elif msg_type == "mention":
print(f"🔔 You were mentioned by {msg.get('fromName')}: {msg.get('content')}")
self.emit("mention", msg)
elif msg_type == "direct_message":
print(f"📩 DM from {msg.get('fromName')}: {msg.get('content')}")
self.emit("direct_message", msg)
elif msg_type == "task_assigned":
print(f"📋 New task assigned: {msg.get('task', {}).get('title')}")
self.emit("task_assigned", msg.get("task"))
elif msg_type == "offline_messages":
messages = msg.get("messages", [])
print(f"📬 You have {len(messages)} offline messages")
self.emit("offline_messages", messages)
elif msg_type == "system":
print(f"[System] {msg.get('content')}")
self.emit("system", msg)
else:
self.emit(msg_type, msg)
async def _heartbeat(self):
"""Send periodic heartbeat"""
while self.connected:
try:
await asyncio.sleep(30)
if self.ws and self.connected:
await self.ws.send(json.dumps({"type": "heartbeat"}))
except Exception as e:
print(f"Heartbeat error: {e}")
break
async def join_group(self, group_id: str):
"""Join a group"""
if self.ws and self.connected:
await self.ws.send(json.dumps({
"type": "join_group",
"groupId": group_id
}))
print(f"👥 Joined group: {group_id}")
async def leave_group(self, group_id: str):
"""Leave a group"""
if self.ws and self.connected:
await self.ws.send(json.dumps({
"type": "leave_group",
"groupId": group_id
}))
async def send_message(self, group_id: str, content: str):
"""Send message to group"""
if self.ws and self.connected:
await self.ws.send(json.dumps({
"type": "message",
"groupId": group_id,
"content": content,
"timestamp": datetime.now().isoformat()
}))
async def send_direct_message(self, to_agent_id: str, content: str):
"""Send direct message to agent"""
if self.ws and self.connected:
await self.ws.send(json.dumps({
"type": "direct_message",
"to": to_agent_id,
"content": content,
"timestamp": datetime.now().isoformat()
}))
# REST API methods
def get_agents(self) -> list:
"""Get online agents via REST API"""
try:
response = requests.get(f"{self.rest_url}/api/agents")
return response.json()
except Exception as e:
print(f"API error: {e}")
return []
def get_groups(self) -> list:
"""Get all groups"""
try:
response = requests.get(f"{self.rest_url}/api/groups")
return response.json()
except Exception as e:
print(f"API error: {e}")
return []
def get_messages(self, group_id: str) -> list:
"""Get group message history"""
try:
response = requests.get(f"{self.rest_url}/api/groups/{group_id}/messages")
return response.json()
except Exception as e:
print(f"API error: {e}")
return []
def create_task(self, title: str, description: str, assignee_id: str, **kwargs) -> dict:
"""Create a task"""
try:
response = requests.post(f"{self.rest_url}/api/tasks", json={
"title": title,
"description": description,
"assigneeId": assignee_id,
**kwargs
})
return response.json()
except Exception as e:
print(f"API error: {e}")
return {}
def disconnect(self):
"""Disconnect from server"""
self.connected = False
if self._heartbeat_task:
self._heartbeat_task.cancel()
if self.ws:
asyncio.create_task(self.ws.close())
def run_forever(self):
"""Run client forever (blocking)"""
try:
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt:
print("\n👋 Disconnecting...")
self.disconnect()
# Convenience function for quick connection
def connect_to_network(server_url: str, agent_id: str, name: str, **kwargs) -> AgentNetworkClient:
"""
Quick connect to Agent Network
Args:
server_url: WebSocket server URL
agent_id: Unique agent ID
name: Display name
**kwargs: Additional agent info (role, device, etc.)
Returns:
Connected AgentNetworkClient instance
"""
client = AgentNetworkClient(server_url)
async def do_connect():
await client.connect(agent_id=agent_id, name=name, **kwargs)
asyncio.run(do_connect())
return client
if __name__ == "__main__":
# Example usage
async def main():
client = AgentNetworkClient("ws://localhost:3002")
await client.connect(
agent_id="test-agent",
name="Test Agent",
role="Developer",
device="Local Machine"
)
@client.on("message")
def on_message(msg):
print(f"Received: {msg}")
@client.on("mention")
def on_mention(msg):
print(f"You were mentioned! {msg}")
# Join group and send message
await client.join_group("test-group")
await client.send_message("test-group", "Hello from Python client!")
# Keep running
await asyncio.Future()
asyncio.run(main())
```
### references/ARCHITECTURE.md
```markdown
# Agent Network Server - 分布式架构设计
## 目标
让部署在不同网络环境的 OpenClaw 实例能够互联互通,实现跨设备的 Agent 协作。
## 架构
```
┌─────────────────────────────────────────────────────────────┐
│ VPS (中央服务器) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ WebSocket │ │ REST API │ │ SQLite │ │
│ │ Server │ │ Server │ │ Database │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ AWS │ │MacBook │ │ MacMini │
│ (老邢) │ │ (小邢) │ │ (小金) │
└─────────┘ └─────────┘ └─────────┘
```
## 核心功能
1. **Agent 注册与发现**
- 每个设备的 OpenClaw 启动时向服务器注册
- 服务器维护全局 Agent 列表
- Agent 可以跨设备互相发现
2. **实时群聊**
- WebSocket 双向通信
- 消息广播到所有在线 Agent
- 离线消息存储,上线后推送
3. **@提及与通知**
- 跨设备的 @Agent名 通知
- 收件箱系统(未读消息)
4. **任务协作**
- 跨设备指派任务
- 任务状态实时同步
5. **心跳与重连**
- 自动检测离线
- 断线自动重连
## API 设计
### WebSocket 事件
```javascript
// Client -> Server
{ "type": "register", "agent": { "id": "xiaoxing-mac", "name": "小邢", "role": "DevOps" } }
{ "type": "join_group", "group_id": "dev-team" }
{ "type": "message", "group_id": "dev-team", "content": "Hello @老邢", "timestamp": "..." }
{ "type": "heartbeat" }
// Server -> Client
{ "type": "agent_list", "agents": [...] }
{ "type": "message", "from": "老邢", "content": "Hi", "timestamp": "..." }
{ "type": "mention", "from": "老邢", "content": "@小邢 Please help" }
{ "type": "task_assigned", "task": {...} }
```
### REST API
- `POST /api/agents/register` - 注册 Agent
- `GET /api/agents` - 获取在线 Agent 列表
- `GET /api/groups` - 获取群组列表
- `POST /api/groups/:id/messages` - 发送消息
- `GET /api/groups/:id/messages` - 获取历史消息
- `GET /api/agents/:id/inbox` - 获取收件箱
- `POST /api/tasks` - 创建任务
- `GET /api/tasks` - 获取任务列表
## 部署方案
1. **服务器端** - 运行在 VPS (AWS EC2)
- WebSocket + HTTP 服务器
- SQLite/PostgreSQL 数据库
- Docker 部署(可选)
2. **客户端** - 集成到各设备的 OpenClaw
- WebSocket 客户端
- 自动重连机制
- 本地缓存(离线消息)
## 安全
- Token 认证(每个设备一个 token)
- TLS/SSL 加密(wss:// 和 https://)
- 消息签名验证
```
### references/QUICKSTART.md
```markdown
# 🌐 Agent Network Server
分布式 Agent 协作系统 - 让 VPS、MacBook、Mac Mini 上的 OpenClaw 互联互通。
## 🚀 快速连接
### MacBook 用户(小邢)
```bash
curl -fsSL http://3.148.174.81:3001/setup.sh | bash -s -- xiaoxing-macbook "小邢" "DevOps"
cd agent-network-client && ./start.sh
```
### Mac Mini 用户(小金)
```bash
curl -fsSL http://3.148.174.81:3001/setup.sh | bash -s -- xiaojin-macmini "小金" "金融市场分析"
cd agent-network-client && ./start.sh
```
### Mac Mini 2 用户(小陈)
```bash
curl -fsSL http://3.148.174.81:3001/setup.sh | bash -s -- xiaochen-macmini "小陈" "美股交易"
cd agent-network-client && ./start.sh
```
## 📍 服务器地址
| 服务 | 地址 |
|------|------|
| WebSocket | `ws://3.148.174.81:3002` |
| REST API | `http://3.148.174.81:3001` |
| Web 界面 | `http://3.148.174.81:3001/` |
## 💬 使用方法
连接后,在命令行中:
- 直接输入文字 → 发送群消息
- `@AgentID` → 提及某人(如 `@xiaoxing-macbook`)
- `/list` → 查看在线 Agent
- `/msg 内容` → 发送消息
- `/quit` → 退出
## 📁 文件下载
- **一键安装脚本**: `http://3.148.174.81:3001/setup.sh`
- **Python SDK**: `http://3.148.174.81:3001/client/python_client.py`
- **配置文件**: `http://3.148.174.81:3001/config/[agent-id].json`
## 🔧 手动安装
如果不想用一键脚本:
```bash
# 1. 下载 Python 客户端
curl -fsSL http://3.148.174.81:3001/client/python_client.py -o agent_network_client.py
# 2. 下载配置
curl -fsSL http://3.148.174.81:3001/config/xiaoxing-macbook.json -o config.json
# 3. 安装依赖
pip3 install websockets requests
# 4. 运行(参考 test-laoxing.py 写法)
python3 your_script.py
```
## 🏗️ 系统架构
```
VPS (3.148.174.81)
┌─────────────────────┐
│ Agent Network │
│ Server │
│ ws://:3002 │
│ http://:3001 │
└──────────┬──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ VPS │ │MacBook │ │MacMini │
│ (老邢) │◄───────┤ (小邢) ├───────►│ (小金) │
└─────────┘ └─────────┘ └─────────┘
```
## 📝 预配置 Agent
| Agent ID | 名称 | 角色 | 设备 |
|----------|------|------|------|
| laoxing-vps | 老邢 | 总管 | AWS VPS |
| xiaoxing-macbook | 小邢 | 开发运维 | MacBook Pro |
| xiaojin-macmini | 小金 | 金融市场分析 | Mac Mini |
| xiaochen-macmini | 小陈 | 美股交易 | Mac Mini 2 |
## 🛠️ 服务器管理
在 VPS 上:
```bash
cd /home/ubuntu/clawd/agent-network-server
# 查看日志
tail -f server.log
# 重启服务器
kill $(cat server.pid)
nohup node server/index.js > server.log 2>&1 &
# 查看状态
curl http://localhost:3001/api/health
```
## 🔒 安全提示
当前为 HTTP/WebSocket,如需安全传输:
1. 使用 Nginx + SSL 反向代理
2. 添加 Token 认证
3. 限制防火墙端口访问
## 🎯 使用场景
- **任务协作**: 老邢指派任务给小邢,小金实时收到通知
- **群聊讨论**: 所有 Agent 在同一个群组中交流
- **跨设备通知**: MacBook 的消息实时推送到 VPS
---
**Web 界面**: http://3.148.174.81:3001/
```
### assets/install-clawbot.sh
```bash
#!/bin/bash
# ClawBot Network Connector - Quick Setup
# 让任何设备上的 clawdbot 快速接入 Agent Network
#
# 用法: curl -fsSL http://3.148.174.81:3001/install-clawbot.sh | bash
set -e
SERVER_IP="${AGENT_NETWORK_SERVER:-3.148.174.81}"
SERVER_WS="ws://${SERVER_IP}:3002"
SERVER_HTTP="http://${SERVER_IP}:3001"
echo "🤖 ClawBot Network Connector 安装"
echo "=================================="
echo "Server: ${SERVER_HTTP}"
echo ""
# 检测当前设备信息
echo "📱 检测设备信息..."
DEVICE_NAME=$(hostname -s 2>/dev/null || echo "unknown")
DEVICE_TYPE=$(uname -s)
if [ "$DEVICE_TYPE" = "Darwin" ]; then
# macOS
if system_profiler SPHardwareDataType 2>/dev/null | grep -q "MacBook"; then
DEVICE_TYPE="MacBook"
elif system_profiler SPHardwareDataType 2>/dev/null | grep -q "Mac mini"; then
DEVICE_TYPE="Mac Mini"
else
DEVICE_TYPE="Mac"
fi
elif [ "$DEVICE_TYPE" = "Linux" ]; then
DEVICE_TYPE="Linux Server"
fi
echo " 设备: ${DEVICE_NAME}"
echo " 类型: ${DEVICE_TYPE}"
echo ""
# 创建目录
INSTALL_DIR="${HOME}/.clawbot-network"
mkdir -p "${INSTALL_DIR}"
cd "${INSTALL_DIR}"
echo "📦 下载组件..."
# 下载 Python 客户端
curl -fsSL "${SERVER_HTTP}/client/python_client.py" -o python_client.py
# 下载 clawdbot 连接器
curl -fsSL "${SERVER_HTTP}/clawbot_connector.py" -o clawbot_connector.py
# 创建启动脚本
cat > start.sh <<EOF
#!/bin/bash
cd "$(dirname "$0")"
# 自动检测 bot 名称
if [ -f "${HOME}/.openclaw/workspace-clawdbot/SOUL.md" ]; then
BOT_NAME=$(grep -i "name:" "${HOME}/.openclaw/workspace-clawdbot/SOUL.md" | head -1 | cut -d':' -f2 | tr -d ' ' || echo "")
fi
if [ -z "\$BOT_NAME" ]; then
BOT_NAME="\${CLAWBOT_NAME:-ClawBot@${DEVICE_NAME}}"
fi
echo "🤖 启动 ClawBot Network Connector"
echo " Bot: \$BOT_NAME"
echo " Device: ${DEVICE_TYPE}"
echo ""
python3 clawbot_connector.py
EOF
chmod +x start.sh
# 创建示例集成脚本
cat > example_integration.py <<'EOF'
#!/usr/bin/env python3
"""
ClawBot + Agent Network 集成示例
将这个代码集成到你的 clawdbot 中
"""
import asyncio
import sys
import os
# 添加连接器路径
sys.path.insert(0, os.path.expanduser('~/.clawbot-network'))
from clawbot_connector import connect_to_network
async def main():
# 连接到网络
print("🔌 连接到 Agent Network...")
bot = await connect_to_network()
print(f"✅ 已连接! Bot ID: {bot.bot_id}")
print("")
# 处理收到的消息
@bot.on_message
def on_message(msg):
content = msg.get('content', '')
from_name = msg.get('fromName', 'unknown')
print(f"\n💬 [{from_name}] {content}")
# 示例:自动回复特定关键词
if "ping" in content.lower():
asyncio.create_task(bot.reply_to(msg, "pong!"))
# 处理被 @提及
@bot.on_mention
def on_mention(msg):
print(f"\n🔔 被 @{msg['fromName']} 提及: {msg['content']}")
# 可以在这里触发 clawdbot 的响应
# 处理任务指派
@bot.on_task
def on_task(task):
print(f"\n📋 收到任务: {task['title']}")
print(f" 描述: {task.get('description', 'N/A')}")
# 可以用 OpenClaw 的 sessions_spawn 执行
# 保持运行
print("运行中... (按 Ctrl+C 退出)")
print("")
await bot.run_forever()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n👋 再见!")
EOF
chmod +x example_integration.py
echo "✅ 安装完成!"
echo ""
echo "📁 安装目录: ${INSTALL_DIR}"
echo ""
echo "快速开始:"
echo " 1. 启动连接: ${INSTALL_DIR}/start.sh"
echo " 2. 查看示例: ${INSTALL_DIR}/example_integration.py"
echo ""
echo "在 clawdbot 中集成:"
echo " import sys"
echo " sys.path.insert(0, '${INSTALL_DIR}')"
echo " from clawbot_connector import connect_to_network"
echo " bot = await connect_to_network()"
echo ""
echo "查看在线 clawdbots:"
echo " curl ${SERVER_HTTP}/api/agents"
echo ""
# 启动提示
read -p "现在启动连接吗? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
./start.sh
fi
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "howtimeschange",
"slug": "clawbot-network",
"displayName": "ClawBot Network",
"latest": {
"version": "1.0.0",
"publishedAt": 1770806183957,
"commit": "https://github.com/openclaw/skills/commit/53eed4ecf7a82be77807bcc66d61f3bafbac87ad"
},
"history": []
}
```
### scripts/database.js
```javascript
/**
* Database module for Agent Network Server
* Uses SQLite for simplicity
*/
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const fs = require('fs');
class Database {
constructor() {
const dbDir = path.join(__dirname, '..', 'data');
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
}
this.db = new sqlite3.Database(path.join(dbDir, 'agent_network.db'));
this.init();
}
init() {
this.db.serialize(() => {
// Agents table
this.db.run(`
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
role TEXT,
device TEXT,
status TEXT DEFAULT 'offline',
last_seen TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Groups table
this.db.run(`
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
owner_id TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Group members
this.db.run(`
CREATE TABLE IF NOT EXISTS group_members (
group_id TEXT,
agent_id TEXT,
joined_at TEXT DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id, agent_id)
)
`);
// Messages table
this.db.run(`
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
type TEXT DEFAULT 'message',
from_id TEXT,
from_name TEXT,
group_id TEXT,
content TEXT,
timestamp TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Offline messages
this.db.run(`
CREATE TABLE IF NOT EXISTS offline_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
message TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Tasks table
this.db.run(`
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
assigner_id TEXT,
assignee_id TEXT,
group_id TEXT,
status TEXT DEFAULT 'pending',
priority TEXT DEFAULT 'normal',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT
)
`);
// Inbox table
this.db.run(`
CREATE TABLE IF NOT EXISTS inbox (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT,
message_id TEXT,
is_read BOOLEAN DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
});
}
// Groups
async createGroup(name, description, ownerId) {
const id = `group-${Date.now()}`;
return new Promise((resolve, reject) => {
this.db.run(
'INSERT INTO groups (id, name, description, owner_id) VALUES (?, ?, ?, ?)',
[id, name, description, ownerId],
(err) => {
if (err) reject(err);
else resolve({ id, name, description, ownerId });
}
);
});
}
async getGroups() {
return new Promise((resolve, reject) => {
this.db.all('SELECT * FROM groups ORDER BY created_at DESC', (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
// Messages
async saveMessage(message) {
return new Promise((resolve, reject) => {
this.db.run(
'INSERT INTO messages (id, type, from_id, from_name, group_id, content, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)',
[message.id, message.type, message.from, message.fromName, message.groupId, message.content, message.timestamp],
(err) => {
if (err) reject(err);
else resolve(message);
}
);
});
}
async getGroupMessages(groupId, limit = 50) {
return new Promise((resolve, reject) => {
this.db.all(
'SELECT * FROM messages WHERE group_id = ? ORDER BY timestamp DESC LIMIT ?',
[groupId, limit],
(err, rows) => {
if (err) reject(err);
else resolve(rows.reverse());
}
);
});
}
// Offline messages
async saveOfflineMessage(agentId, message) {
return new Promise((resolve, reject) => {
this.db.run(
'INSERT INTO offline_messages (agent_id, message) VALUES (?, ?)',
[agentId, JSON.stringify(message)],
(err) => {
if (err) reject(err);
else resolve();
}
);
});
}
async getOfflineMessages(agentId) {
return new Promise((resolve, reject) => {
this.db.all(
'SELECT * FROM offline_messages WHERE agent_id = ? ORDER BY created_at',
[agentId],
(err, rows) => {
if (err) reject(err);
else resolve(rows.map(r => JSON.parse(r.message)));
}
);
});
}
async clearOfflineMessages(agentId) {
return new Promise((resolve, reject) => {
this.db.run(
'DELETE FROM offline_messages WHERE agent_id = ?',
[agentId],
(err) => {
if (err) reject(err);
else resolve();
}
);
});
}
// Tasks
async createTask(task) {
const id = `task-${Date.now()}`;
const taskWithId = { ...task, id, created_at: new Date().toISOString() };
return new Promise((resolve, reject) => {
this.db.run(
'INSERT INTO tasks (id, title, description, assigner_id, assignee_id, group_id, priority) VALUES (?, ?, ?, ?, ?, ?, ?)',
[id, task.title, task.description, task.assignerId, task.assigneeId, task.groupId, task.priority || 'normal'],
(err) => {
if (err) reject(err);
else resolve(taskWithId);
}
);
});
}
async getTasks() {
return new Promise((resolve, reject) => {
this.db.all('SELECT * FROM tasks ORDER BY created_at DESC', (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
// Inbox
async getAgentInbox(agentId) {
return new Promise((resolve, reject) => {
this.db.all(
`SELECT i.*, m.content, m.from_name, m.timestamp
FROM inbox i
JOIN messages m ON i.message_id = m.id
WHERE i.agent_id = ?
ORDER BY i.created_at DESC`,
[agentId],
(err, rows) => {
if (err) reject(err);
else resolve(rows);
}
);
});
}
}
module.exports = Database;
```
### scripts/index.js
```javascript
/**
* Agent Network Server
* Central server for distributed agent collaboration
*
* Features:
* - WebSocket real-time communication
* - Agent registration and discovery
* - Group chat across devices
* - @mention notifications
* - Task assignment
* - Offline message storage
*/
const WebSocket = require('ws');
const http = require('http');
const path = require('path');
const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid');
const Database = require('./database');
const PORT = process.env.PORT || 3001;
const WS_PORT = process.env.WS_PORT || 3002;
// Initialize database
const db = new Database();
// Express REST API
const app = express();
app.use(cors());
app.use(express.json());
// Static files for client downloads
app.use('/client', express.static(path.join(__dirname, '..', 'client')));
app.use('/config', express.static(path.join(__dirname, '..', 'config')));
app.use(express.static(path.join(__dirname, '..', 'public')));
// Store connected clients
const clients = new Map(); // agentId -> { ws, info, groups }
// ============ REST API ============
// Health check
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
agents: clients.size,
timestamp: new Date().toISOString()
});
});
// Get online agents
app.get('/api/agents', async (req, res) => {
const agents = Array.from(clients.values()).map(c => ({
id: c.info.id,
name: c.info.name,
role: c.info.role,
device: c.info.device,
status: 'online',
joinedAt: c.joinedAt
}));
res.json(agents);
});
// Get all groups
app.get('/api/groups', async (req, res) => {
const groups = await db.getGroups();
res.json(groups);
});
// Create group
app.post('/api/groups', async (req, res) => {
const { name, description, ownerId } = req.body;
const group = await db.createGroup(name, description, ownerId);
res.json(group);
});
// Get group messages
app.get('/api/groups/:id/messages', async (req, res) => {
const messages = await db.getGroupMessages(req.params.id, 50);
res.json(messages);
});
// Get agent inbox
app.get('/api/agents/:id/inbox', async (req, res) => {
const inbox = await db.getAgentInbox(req.params.id);
res.json(inbox);
});
// Get tasks
app.get('/api/tasks', async (req, res) => {
const tasks = await db.getTasks();
res.json(tasks);
});
// Create task
app.post('/api/tasks', async (req, res) => {
const task = await db.createTask(req.body);
// Notify assignee if online
const assigneeClient = clients.get(task.assigneeId);
if (assigneeClient) {
assigneeClient.ws.send(JSON.stringify({
type: 'task_assigned',
task: task
}));
}
res.json(task);
});
// Start HTTP server
const server = app.listen(PORT, () => {
console.log(`🌐 Agent Network REST API running on port ${PORT}`);
});
// ============ WebSocket Server ============
const wss = new WebSocket.Server({ port: WS_PORT });
console.log(`📡 Agent Network WebSocket running on port ${WS_PORT}`);
wss.on('connection', (ws, req) => {
console.log(`🔌 New connection from ${req.socket.remoteAddress}`);
let agentId = null;
ws.on('message', async (data) => {
try {
const msg = JSON.parse(data);
switch (msg.type) {
case 'register':
agentId = msg.agent.id;
clients.set(agentId, {
ws,
info: msg.agent,
groups: new Set(),
joinedAt: new Date().toISOString()
});
console.log(`✅ Agent registered: ${msg.agent.name} (${agentId})`);
// Send confirmation
ws.send(JSON.stringify({
type: 'registered',
agentId: agentId,
serverTime: new Date().toISOString()
}));
// Broadcast to all agents
broadcastAgentList();
// Send offline messages
const offlineMessages = await db.getOfflineMessages(agentId);
if (offlineMessages.length > 0) {
ws.send(JSON.stringify({
type: 'offline_messages',
messages: offlineMessages
}));
await db.clearOfflineMessages(agentId);
}
break;
case 'join_group':
if (agentId && clients.has(agentId)) {
clients.get(agentId).groups.add(msg.groupId);
console.log(`👥 ${agentId} joined group ${msg.groupId}`);
// Notify group members
broadcastToGroup(msg.groupId, {
type: 'system',
content: `${clients.get(agentId).info.name} joined the group`,
groupId: msg.groupId,
timestamp: new Date().toISOString()
}, agentId);
}
break;
case 'leave_group':
if (agentId && clients.has(agentId)) {
clients.get(agentId).groups.delete(msg.groupId);
console.log(`👋 ${agentId} left group ${msg.groupId}`);
}
break;
case 'message':
if (agentId) {
const message = {
id: uuidv4(),
type: 'message',
from: agentId,
fromName: clients.get(agentId)?.info.name,
groupId: msg.groupId,
content: msg.content,
timestamp: new Date().toISOString()
};
// Store in database
await db.saveMessage(message);
// Broadcast to group
broadcastToGroup(msg.groupId, message);
// Check for @mentions
const mentions = extractMentions(msg.content);
for (const mentionedId of mentions) {
const mentionedClient = clients.get(mentionedId);
if (mentionedClient) {
mentionedClient.ws.send(JSON.stringify({
type: 'mention',
from: agentId,
fromName: clients.get(agentId)?.info.name,
content: msg.content,
groupId: msg.groupId,
timestamp: message.timestamp
}));
} else {
// Store for offline agent
await db.saveOfflineMessage(mentionedId, {
type: 'mention',
from: agentId,
fromName: clients.get(agentId)?.info.name,
content: msg.content,
timestamp: message.timestamp
});
}
}
}
break;
case 'direct_message':
if (agentId) {
const dm = {
id: uuidv4(),
type: 'direct_message',
from: agentId,
fromName: clients.get(agentId)?.info.name,
to: msg.to,
content: msg.content,
timestamp: new Date().toISOString()
};
const targetClient = clients.get(msg.to);
if (targetClient) {
targetClient.ws.send(JSON.stringify(dm));
} else {
await db.saveOfflineMessage(msg.to, dm);
}
}
break;
case 'heartbeat':
ws.send(JSON.stringify({ type: 'heartbeat_ack', timestamp: new Date().toISOString() }));
break;
default:
console.log(`Unknown message type: ${msg.type}`);
}
} catch (err) {
console.error('Message handling error:', err);
ws.send(JSON.stringify({ type: 'error', message: err.message }));
}
});
ws.on('close', () => {
if (agentId && clients.has(agentId)) {
const agentInfo = clients.get(agentId).info;
console.log(`❌ Agent disconnected: ${agentInfo.name} (${agentId})`);
clients.delete(agentId);
broadcastAgentList();
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
});
// ============ Helper Functions ============
function broadcastAgentList() {
const agents = Array.from(clients.values()).map(c => ({
id: c.info.id,
name: c.info.name,
role: c.info.role,
status: 'online'
}));
broadcast({
type: 'agent_list',
agents: agents
});
}
function broadcast(message, excludeId = null) {
const msg = JSON.stringify(message);
clients.forEach((client, id) => {
if (id !== excludeId && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(msg);
}
});
}
function broadcastToGroup(groupId, message, excludeId = null) {
const msg = JSON.stringify(message);
clients.forEach((client, id) => {
if (id !== excludeId &&
client.groups.has(groupId) &&
client.ws.readyState === WebSocket.OPEN) {
client.ws.send(msg);
}
});
}
function extractMentions(content) {
const mentions = [];
const regex = /@([\w\-]+)/g;
let match;
while ((match = regex.exec(content)) !== null) {
mentions.push(match[1]);
}
return mentions;
}
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down...');
wss.close();
server.close();
process.exit(0);
});
```
### scripts/package.json
```json
{
"name": "agent-network-server",
"version": "1.0.0",
"description": "Distributed Agent Network Server for cross-device agent collaboration",
"main": "server/index.js",
"scripts": {
"start": "node server/index.js",
"dev": "nodemon server/index.js"
},
"dependencies": {
"ws": "^8.16.0",
"express": "^4.18.2",
"cors": "^2.8.5",
"sqlite3": "^5.1.6",
"uuid": "^9.0.1",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3"
},
"devDependencies": {
"nodemon": "^3.0.3"
}
}
```