本文档详细描述 VCPToolBox 的分布式网络架构,包括 WebSocket 协议、节点注册、分布式工具执行、文件传输机制以及 ChromeBridge 集成。
VCP 分布式架构采用星型网络拓扑,由一个主服务器 (VCPToolBox) 和多个分布式节点 (VCPDistributedServer) 组成。
┌─────────────────────────┐
│ VCP 主服务器 │
│ (WebSocketServer) │
│ │
│ - 路由与调度 │
│ - 插件管理 │
│ - 文件获取服务 │
└───────────┬─────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 分布式节点 A │ │ 分布式节点 B │ │ ChromeObserver │
│ (GPU 服务器) │ │ (文件服务器) │ │ (浏览器插件) │
└───────────────┘ └───────────────┘ └───────────────┘
| 组件 | 文件 | 职责 |
|---|---|---|
| WebSocket 服务器 | WebSocketServer.js |
连接管理、消息路由、工具调度 |
| 文件获取服务 | FileFetcherServer.js |
跨节点文件传输、缓存管理 |
| 插件管理器 | Plugin.js |
本地/远程插件注册与执行 |
VCP 支持 6 种客户端类型,通过 WebSocket URL 路径区分:
路径格式: /VCPlog/VCP_Key=<key>
用途: 普通日志客户端,用于接收服务器广播的日志消息。
存储位置: clients Map
// 连接确认消息
{ type: 'connection_ack', message: 'WebSocket connection successful for VCPLog.' }路径格式: /vcpinfo/VCP_Key=<key>
用途: 信息通道客户端,用于接收系统状态、插件状态等推送信息。
存储位置: clients Map
专用广播函数: broadcastVCPInfo(data)
// 连接确认消息
{ type: 'connection_ack', message: 'WebSocket connection successful for VCPInfo.' }路径格式: /vcp-distributed-server/VCP_Key=<key>
用途: 分布式服务器节点,用于注册远程工具并执行主服务器下发的工具调用请求。
存储位置:
distributedServersMap - 键为serverId,值为{ ws, tools, ips, serverName }distributedServerIPsMap - 存储 IP 信息
生成的 serverId 格式: dist-<clientId>
// 连接时初始化
distributedServers.set(serverId, {
ws, // WebSocket 连接
tools: [], // 已注册的工具列表
ips: {} // IP 信息
});路径格式: /vcp-chrome-observer/VCP_Key=<key>
用途: Chrome 浏览器扩展的观察者端,持续上报页面状态、接收并执行控制命令。
存储位置: chromeObserverClients Map
关联模块:
ChromeBridge(优先)ChromeObserver(回退)
// 连接时自动调用模块处理
const chromeBridgeModule = pluginManager.getServiceModule('ChromeBridge');
if (chromeBridgeModule && typeof chromeBridgeModule.handleNewClient === 'function') {
chromeBridgeModule.handleNewClient(ws);
}路径格式: /vcp-chrome-control/VCP_Key=<key>
用途: Chrome 浏览器扩展的控制端,用于发送控制命令并接收执行结果。
存储位置: chromeControlClients Map
等待机制: waitingControlClients Map - 存储等待页面信息更新的客户端
// 典型消息流程
// 1. Control 发送命令
{ type: 'command', data: { requestId, command, wait_for_page_info } }
// 2. Observer 转发结果
{ type: 'command_result', data: { requestId, status, message/error } }路径格式: /vcp-admin-panel/VCP_Key=<key>
用途: Web 管理面板客户端,用于实时接收系统状态更新。
存储位置: adminPanelClients Map
专用广播函数: broadcastToAdminPanel(data)
客户端 主服务器
│ │
│──── HTTP Upgrade Request ─────►│
│ (带 VCP_Key 的 URL) │
│ │
│◄─── 握手成功/拒绝 ────────────│
│ │
│◄─── connection_ack ────────────│ (部分客户端)
│ │
// URL 路径正则匹配
const pathRegex = /^\/<client-type>\/VCP_Key=(.+)$/;
// 密钥验证
if (serverConfig.vcpKey && connectionKey === serverConfig.vcpKey) {
isAuthenticated = true;
} else {
// 认证失败,销毁连接
socket.destroy();
return;
}function generateClientId() {
return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
}
// 示例: "m5x2k9a-3h7j2n4"分布式节点连接后,发送 register_tools 消息注册其提供的工具:
// 节点发送
{
type: 'register_tools',
data: {
tools: [
{
name: 'video_generator',
description: '生成视频',
// ... 其他工具元数据
}
]
}
}服务端处理:
case 'register_tools':
const serverEntry = distributedServers.get(serverId);
if (serverEntry && message.data && Array.isArray(message.data.tools)) {
// 过滤内部工具
const externalTools = message.data.tools.filter(
t => t.name !== 'internal_request_file'
);
// 注册到插件管理器
pluginManager.registerDistributedTools(serverId, externalTools);
// 更新服务器记录
serverEntry.tools = externalTools.map(t => t.name);
distributedServers.set(serverId, serverEntry);
}
break;分布式节点上报其网络信息,用于文件溯源:
// 节点发送
{
type: 'report_ip',
data: {
localIPs: ['192.168.1.100', '10.0.0.5'],
publicIP: '203.0.113.50',
serverName: 'GPU-Server-01'
}
}服务端处理:
case 'report_ip':
const serverInfo = distributedServers.get(serverId);
if (serverInfo && message.data) {
const ipData = {
localIPs: message.data.localIPs || [],
publicIP: message.data.publicIP || null,
serverName: message.data.serverName || serverId
};
// 存储 IP 映射
distributedServerIPs.set(serverId, ipData);
// 同步名称到连接记录
serverInfo.serverName = ipData.serverName;
distributedServers.set(serverId, serverInfo);
}
break;分布式节点可以推送静态占位符更新:
// 节点发送
{
type: 'update_static_placeholders',
data: {
serverName: 'Remote-Server',
placeholders: {
'{{RemoteWeather}}': '晴天 25°C',
'{{RemoteStatus}}': '运行中'
}
}
}主服务器 分布式节点
│ │
│── execute_tool ───────────►│
│ { requestId, │
│ toolName, │
│ toolArgs } │
│ │
│◄── tool_result ────────────│
│ { requestId, │
│ status, │
│ result/error } │
│ │
// 主服务器发送
{
type: 'execute_tool',
data: {
requestId: 'm5x2k9a-3h7j2n4',
toolName: 'video_generator',
toolArgs: {
prompt: '生成一段日落视频',
duration: 10
}
}
}// 分布式节点返回 - 成功
{
type: 'tool_result',
data: {
requestId: 'm5x2k9a-3h7j2n4',
status: 'success',
result: {
videoUrl: 'http://...',
duration: 10
}
}
}
// 分布式节点返回 - 失败
{
type: 'tool_result',
data: {
requestId: 'm5x2k9a-3h7j2n4',
status: 'error',
error: 'GPU 内存不足'
}
}async function executeDistributedTool(serverIdOrName, toolName, toolArgs, timeout) {
// 获取超时配置
const plugin = pluginManager.getPlugin(toolName);
const defaultTimeout = plugin?.communication?.timeout || 60000;
const effectiveTimeout = timeout ?? defaultTimeout;
// 支持通过 ID 或名称查找服务器
let server = distributedServers.get(serverIdOrName);
if (!server) {
for (const srv of distributedServers.values()) {
if (srv.serverName === serverIdOrName) {
server = srv;
break;
}
}
}
if (!server || server.ws.readyState !== WebSocket.OPEN) {
throw new Error(`Distributed server ${serverIdOrName} is not connected or ready.`);
}
const requestId = generateClientId();
const payload = {
type: 'execute_tool',
data: { requestId, toolName, toolArgs }
};
return new Promise((resolve, reject) => {
// 设置超时
const timeoutId = setTimeout(() => {
pendingToolRequests.delete(requestId);
reject(new Error(`Request timed out after ${effectiveTimeout / 1000}s.`));
}, effectiveTimeout);
// 注册待处理请求
pendingToolRequests.set(requestId, { resolve, reject, timeout: timeoutId });
// 发送请求
server.ws.send(JSON.stringify(payload));
});
}case 'tool_result':
const pending = pendingToolRequests.get(message.data.requestId);
if (pending) {
clearTimeout(pending.timeout);
if (message.data.status === 'success') {
pending.resolve(message.data.result);
} else {
pending.reject(new Error(message.data.error || 'Distributed tool execution failed.'));
}
pendingToolRequests.delete(message.data.requestId);
}
break;FileFetcherServer.js 提供跨节点文件获取能力,实现透明远程文件访问。
插件请求 主服务器 分布式节点
│ │ │
│── file://... ───────►│ │
│ │ │
│ │── internal_request_file ──►│
│ │ { fileUrl } │
│ │ │
│ │◄── Base64 Data ───────────│
│ │ │
│◄── Buffer/MIME ──────│ │
│ │ │
async function fetchFile(fileUrl, requestIp) {
// 1. 快速循环检测
const now = Date.now();
if (recentRequests.has(fileUrl)) {
const lastRequestTime = recentRequests.get(fileUrl);
if (now - lastRequestTime < REQ_CACHE_EXPIRATION_MS) {
throw new Error('检测到重复请求,防止无限循环');
}
}
recentRequests.set(fileUrl, now);
// 2. 检查本地缓存
const cacheKey = crypto.createHash('sha256').update(fileUrl).digest('hex');
const cachedFilePath = path.join(CACHE_DIR, cacheKey + extension);
try {
const buffer = await fs.readFile(cachedFilePath);
return { buffer, mimeType };
} catch (e) {
// 缓存未命中,继续远程获取
}
// 3. 查找来源服务器
const serverId = webSocketServer.findServerByIp(requestIp);
if (!serverId) {
throw new Error(`未找到 IP [${requestIp}] 对应的分布式服务器`);
}
// 4. 请求远程文件
const result = await webSocketServer.executeDistributedTool(
serverId,
'internal_request_file',
{ fileUrl: fileUrl },
60000
);
if (result && result.status === 'success' && result.fileData) {
const buffer = Buffer.from(result.fileData, 'base64');
// 5. 写入缓存
await fs.writeFile(cachedFilePath, buffer);
return { buffer, mimeType: result.mimeType };
}
}这是分布式节点上的内置工具,用于响应文件请求:
// 节点处理 internal_request_file
{
type: 'execute_tool',
data: {
requestId: 'xxx',
toolName: 'internal_request_file',
toolArgs: { fileUrl: 'file:///path/to/file.mp4' }
}
}
// 节点响应
{
type: 'tool_result',
data: {
requestId: 'xxx',
status: 'success',
fileData: '<Base64>', // 文件内容
mimeType: 'video/mp4' // MIME 类型
}
}function findServerByIp(ip) {
for (const [serverId, ipInfo] of distributedServerIPs.entries()) {
if (ipInfo.publicIP === ip ||
(ipInfo.localIPs && ipInfo.localIPs.includes(ip))) {
return ipInfo.serverName || serverId;
}
}
return null;
}| 缓存类型 | 位置 | 过期时间 | 用途 |
|---|---|---|---|
| 文件缓存 | .file_cache/ |
永久 | 存储获取的文件 |
| 失败缓存 | failedFetchCache |
30秒 | 防止重复失败请求 |
| 请求缓存 | recentRequests |
5秒 | 防止循环请求 |
ChromeBridge 采用观察者-控制者双角色模式:
┌─────────────────┐ ┌─────────────────┐
│ ChromeObserver │ │ ChromeControl │
│ (持久连接) │ │ (临时连接) │
└────────┬────────┘ └────────┬────────┘
│ │
│ ┌──────────────┐ │
└────────►│ 主服务器 │◄────────────┘
│ WebSocketServer │
└──────────────┘
- 持续上报页面信息 (
pageInfoUpdate) - 接收并执行控制命令
- 返回命令执行结果
消息类型:
// 心跳
{ type: 'heartbeat' }
// 响应: { type: 'heartbeat_ack', timestamp: Date.now() }
// 页面信息更新
{ type: 'pageInfoUpdate', data: { markdown: '...' } }
// 命令结果
{ type: 'command_result', data: { requestId, status, message/error, sourceClientId } }- 发送控制命令
- 接收命令执行结果
- 可选等待页面信息更新
消息类型:
// 发送命令
{
type: 'command',
data: {
requestId: 'xxx',
command: 'click',
selector: '#submit-btn',
wait_for_page_info: true // 可选:等待页面更新
}
}// Control → Observer
if (parsedMessage.type === 'command') {
const observerClient = Array.from(chromeObserverClients.values())[0];
if (observerClient) {
// 附加源客户端 ID
parsedMessage.data.sourceClientId = ws.clientId;
// 如果需要等待页面信息,注册等待
if (parsedMessage.data.wait_for_page_info) {
waitingControlClients.set(ws.clientId, parsedMessage.data.requestId);
}
observerClient.send(JSON.stringify(parsedMessage));
}
}
// Observer → Control (结果返回)
if (parsedMessage.type === 'command_result' && parsedMessage.data.sourceClientId) {
const sourceClientId = parsedMessage.data.sourceClientId;
sendMessageToClient(sourceClientId, resultForClient);
}
// Observer → Control (页面信息转发)
if (parsedMessage.type === 'pageInfoUpdate') {
waitingControlClients.forEach((requestId, clientId) => {
sendMessageToClient(clientId, {
type: 'page_info_update',
data: { requestId, markdown: parsedMessage.data.markdown }
});
waitingControlClients.delete(clientId);
});
}ChromeObserver 客户端需要定期发送心跳:
// 客户端发送
{ type: 'heartbeat' }
// 服务端响应
{ type: 'heartbeat_ack', timestamp: 1707849600000 }ws.on('close', () => {
if (ws.clientType === 'DistributedServer') {
// 注销所有分布式工具
pluginManager.unregisterAllDistributedTools(ws.serverId);
distributedServers.delete(ws.serverId);
distributedServerIPs.delete(ws.serverId);
} else if (ws.clientType === 'ChromeObserver') {
chromeObserverClients.delete(ws.clientId);
} else if (ws.clientType === 'ChromeControl') {
chromeControlClients.delete(ws.clientId);
waitingControlClients.delete(ws.clientId);
} else if (ws.clientType === 'AdminPanel') {
adminPanelClients.delete(ws.clientId);
} else {
clients.delete(ws.clientId);
}
});ws.on('error', (error) => {
console.error(`[WebSocketServer] Error with client ${ws.clientId}:`, error);
if (ws.clientId) clients.delete(ws.clientId);
});| 类型 | 方向 | 发送者 | 说明 |
|---|---|---|---|
connection_ack |
S→C | 服务器 | 连接确认 |
heartbeat |
C→S | Observer | 心跳请求 |
heartbeat_ack |
S→C | 服务器 | 心跳响应 |
register_tools |
C→S | Distributed | 注册工具 |
report_ip |
C→S | Distributed | 上报 IP |
update_static_placeholders |
C→S | Distributed | 更新占位符 |
execute_tool |
S→C | 服务器 | 执行工具请求 |
tool_result |
C→S | Distributed | 工具执行结果 |
command |
C→S | ChromeControl | 浏览器命令 |
command_result |
C→S | ChromeObserver | 命令结果 |
pageInfoUpdate |
C→S | ChromeObserver | 页面信息更新 |
page_info_update |
S→C | 服务器 | 转发页面信息 |
{
type: '<message_type>',
data: {
// 消息特定数据
}
}// 通用广播
broadcast(data, targetClientType = null);
// VCPInfo 专用广播
broadcastVCPInfo(data);
// AdminPanel 专用广播
broadcastToAdminPanel(data);
// 发送给特定客户端
sendMessageToClient(clientId, data);if (serverConfig.vcpKey && connectionKey === serverConfig.vcpKey) {
isAuthenticated = true;
} else {
writeLog(`${clientType} connection denied. Invalid or missing VCP_Key.`);
socket.destroy();
return;
}const timeoutId = setTimeout(() => {
pendingToolRequests.delete(requestId);
reject(new Error(`Request to distributed tool ${toolName} timed out after ${effectiveTimeout / 1000}s.`));
}, effectiveTimeout);if (!server || server.ws.readyState !== WebSocket.OPEN) {
throw new Error(`Distributed server ${serverIdOrName} is not connected or ready.`);
}// 30秒内防止重复失败请求
const cachedFailure = failedFetchCache.get(fileUrl);
if (cachedFailure && (Date.now() - cachedFailure.timestamp < CACHE_EXPIRATION_MS)) {
throw new Error(`文件获取在短时间内已失败: ${cachedFailure.error}`);
}// 5秒内重复请求视为潜在循环
if (recentRequests.has(fileUrl)) {
const lastRequestTime = recentRequests.get(fileUrl);
if (now - lastRequestTime < REQ_CACHE_EXPIRATION_MS) {
throw new Error('检测到对同一文件的重复请求,防止无限循环');
}
}// 客户端存储
const clients = new Map(); // clientId → WebSocket
const distributedServers = new Map(); // serverId → { ws, tools, ips, serverName }
const chromeControlClients = new Map(); // clientId → WebSocket
const chromeObserverClients = new Map(); // clientId → WebSocket
const adminPanelClients = new Map(); // clientId → WebSocket
const pendingToolRequests = new Map(); // requestId → { resolve, reject, timeout }
const distributedServerIPs = new Map(); // serverId → { localIPs, publicIP, serverName }
const waitingControlClients = new Map(); // clientId → requestIdconst vcpLogPathRegex = /^\/VCPlog\/VCP_Key=(.+)$/;
const vcpInfoPathRegex = /^\/vcpinfo\/VCP_Key=(.+)$/;
const distServerPathRegex = /^\/vcp-distributed-server\/VCP_Key=(.+)$/;
const chromeControlPathRegex = /^\/vcp-chrome-control\/VCP_Key=(.+)$/;
const chromeObserverPathRegex = /^\/vcp-chrome-observer\/VCP_Key=(.+)$/;
const adminPanelPathRegex = /^\/vcp-admin-panel\/VCP_Key=(.+)$/;module.exports = {
initialize, // 初始化 WebSocket 服务器
setPluginManager, // 设置插件管理器引用
broadcast, // 广播消息
broadcastVCPInfo, // 广播给 VCPInfo 客户端
broadcastToAdminPanel, // 广播给管理面板
sendMessageToClient, // 发送给特定客户端
executeDistributedTool, // 执行分布式工具
findServerByIp, // 根据 IP 查找服务器
shutdown // 关闭服务器
};文档版本: 1.0
最后更新: 2026-02-13
相关文件: WebSocketServer.js, FileFetcherServer.js, Plugin.js