Files
clawgo/docs/node-p2p-e2e.md

6.8 KiB
Raw Blame History

Node P2P E2E

这份文档用于验证 gateway.nodes.p2p 的两条真实数据面:

  • websocket_tunnel
  • webrtc

目标不是单元测试,而是两台公网机器上的真实联通性验证。

验证目标

验证通过需要同时满足:

  1. 两台远端 node 都能成功注册到同一个 gateway
  2. websocket_tunnel 模式下,远端 node 任务可成功完成
  3. webrtc 模式下,远端 node 任务可成功完成
  4. webrtc 模式下,/webui/api/nodesp2p.active_sessions 大于 0
  5. Dashboard / Subagents 能看到 node P2P 会话状态和最近调度路径

前置条件

  • 一台 gateway 机器
  • 两台远端 node 机器
  • 三台机器都能运行 clawgo
  • 远端机器有 python3
  • gateway 机器对外开放 WebUI / node registry 端口

推荐:

  • 先验证 websocket_tunnel
  • 再切到 webrtc
  • webrtc 至少配置一个可用的 stun_servers

测试思路

为了排除 HTTP relay 误判,建议让目标 node 的 endpoint 故意写成只对目标 node 本机有效的地址,例如:

http://127.0.0.1:<port>

这样如果任务仍能完成,就说明请求不是靠 gateway 直接 HTTP relay 打过去的,而是走了 node P2P 通道。

建议配置

1. websocket_tunnel

{
  "gateway": {
    "host": "0.0.0.0",
    "port": 18790,
    "token": "YOUR_GATEWAY_TOKEN",
    "nodes": {
      "p2p": {
        "enabled": true,
        "transport": "websocket_tunnel",
        "stun_servers": [],
        "ice_servers": []
      }
    }
  }
}

2. webrtc

{
  "gateway": {
    "host": "0.0.0.0",
    "port": 18790,
    "token": "YOUR_GATEWAY_TOKEN",
    "nodes": {
      "p2p": {
        "enabled": true,
        "transport": "webrtc",
        "stun_servers": ["stun:stun.l.google.com:19302"],
        "ice_servers": []
      }
    }
  }
}

最小 node endpoint

在每台远端 node 上启动一个最小 HTTP 服务,用于返回固定结果:

#!/usr/bin/env python3
import json
import os
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer

PORT = int(os.environ.get("PORT", "19081"))
LABEL = os.environ.get("NODE_LABEL", socket.gethostname())

class H(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        pass

    def do_POST(self):
        length = int(self.headers.get("Content-Length", "0") or 0)
        raw = self.rfile.read(length) if length else b"{}"
        try:
            req = json.loads(raw.decode("utf-8") or "{}")
        except Exception:
            req = {}
        action = req.get("action") or self.path.strip("/")
        payload = {
            "handler": LABEL,
            "hostname": socket.gethostname(),
            "path": self.path,
            "echo": req,
        }
        if action == "agent_task":
            payload["result"] = f"agent_task from {LABEL}"
        else:
            payload["result"] = f"{action} from {LABEL}"
        body = json.dumps({
            "ok": True,
            "code": "ok",
            "node": LABEL,
            "action": action,
            "payload": payload,
        }).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

HTTPServer(("0.0.0.0", PORT), H).serve_forever()

注册远端 node

在每台 node 上执行:

clawgo node register \
  --gateway http://<gateway-host>:18790 \
  --token YOUR_GATEWAY_TOKEN \
  --id <node-id> \
  --name <node-name> \
  --endpoint http://127.0.0.1:<endpoint-port> \
  --actions run,agent_task \
  --models gpt-4o-mini \
  --capabilities run,invoke,model \
  --watch \
  --heartbeat-sec 10

验证注册成功:

curl -s -H 'Authorization: Bearer YOUR_GATEWAY_TOKEN' \
  http://<gateway-host>:18790/webui/api/nodes

预期:

  • 远端 node 出现在 nodes
  • online = true
  • 主拓扑中出现 node.<id>.main

建议的任务验证方式

不要通过普通聊天 prompt 让模型“自己决定是否调用 nodes 工具”作为主判据。
更稳定的方式是直接调用 subagent runtime把任务派给远端 node branch

curl -s \
  -H 'Authorization: Bearer YOUR_GATEWAY_TOKEN' \
  -H 'Content-Type: application/json' \
  http://<gateway-host>:18790/webui/api/subagents_runtime \
  -d '{
    "action": "dispatch_and_wait",
    "agent_id": "node.<node-id>.main",
    "task": "Return exactly the string NODE_P2P_OK",
    "wait_timeout_sec": 30
  }'

预期:

  • ok = true
  • result.reply.status = completed
  • result.reply.result 含远端 endpoint 返回内容

websocket_tunnel 判定

websocket_tunnel 模式下,上面的任务应能成功完成。

如果目标 node 的 endpoint 配成了 127.0.0.1:<port>,且任务仍成功,则说明:

  • 不是 gateway 直接 HTTP relay 到远端公网地址
  • 实际请求已经通过 node websocket 隧道送达目标 node

webrtc 判定

切到 webrtc 配置后,重复同样的 dispatch_and_wait

随后查看:

curl -s -H 'Authorization: Bearer YOUR_GATEWAY_TOKEN' \
  http://<gateway-host>:18790/webui/api/nodes

预期 p2p 段包含:

  • transport = "webrtc"
  • active_sessions > 0
  • nodes[].status = "open"
  • nodes[].last_ready_at 非空

这表示 WebRTC DataChannel 已经真正建立,而不只是 signaling 被触发。

WebUI 判定

验证页面:

  • Dashboard
    • 能看到 Node P2P 会话明细
    • 能看到最近节点调度记录,包括 used_transportfallback_from
  • Subagents
    • 远端 node branch 的卡片/tooltip 能显示:
      • P2P transport
      • session status
      • retry count
      • last ready
      • last error

常见问题

1. gateway 端口上已经有旧实例

现象:

  • 新配置明明改了,但 /webui/api/version/webui/api/nodes 仍表现出旧行为

处理:

  • 先确认端口上实际监听的是哪一个 clawgo 进程
  • 再启动测试实例

2. chat 路由干扰 node 工具验证

现象:

  • 普通聊天请求被 router 或 skill 行为分流
  • 没有真正命中 nodes 数据面

处理:

  • 直接用 /webui/api/subagents_runtimedispatch_and_wait
  • 让任务明确走 node.<id>.main

3. webrtc 一直停在 connecting

优先检查:

  • stun_servers 是否可达
  • 两端机器是否允许 UDP 出站
  • 是否需要 turn: / turns: 服务器

4. 任务成功但 UI 没显示会话

优先检查:

  • 是否真的运行在 webrtc 配置下
  • /webui/api/nodes 返回的 p2p 是否含 active_sessions
  • 前端是否已经更新到包含 node P2P runtime 展示的版本

回归建议

每次改动以下模块后,至少回归一次本流程:

  • pkg/nodes/webrtc.go
  • pkg/nodes/transport.go
  • pkg/agent/loop.go
  • pkg/api/server.go
  • cmd/clawgo/cmd_node.go
  • cmd/clawgo/cmd_gateway.go