AI Agent 连不上浏览器了:CDP 远程控制从 502 到全链路打通

本文最后更新于 2026年7月20日 晚上

“贾维斯的浏览器打不开了。”

报错是 502。CDP 协议连不上 Edge,所有远程浏览器操作全挂。一个 502 背后,藏着五个独立的坑——每个都能让排查停在某一步。这篇文章记录从 502 到 YouTube 截图成功、六个脚本落地、技能审查通过的全过程。

前置条件

  • Linux 主机(Hermes Agent)通过 SSH 连接 Windows 机器(JARVIS)
  • Windows 上运行 Edge 浏览器,开启 CDP 远程调试
  • SSH 账号 + PsExec64(用于在桌面会话启动 GUI 程序)

502 的真因:Edge 压根没开 CDP

502 不是网络问题。curl CDP 端口返回 502,是因为 Edge 根本没带 CDP 参数启动——它就是个普通浏览器进程。

Chromium 内核开启 CDP 调试需要两个必选参数:

1
msedge.exe --remote-debugging-port=9888 --user-data-dir="%USERPROFILE%\edge-cdp-profile"
  • --remote-debugging-port:指定 CDP 端口
  • --user-data-dir必选。不加这个参数,CDP 端口静默不启动,不报错不提示

JARVIS 桌面上有个 Edge 快捷方式,参数里已经写好了这两个值。但 SSH 登录后处于 Session 0,看不到桌面快捷方式,也无法直接启动 GUI。

解决:SSH → PsExec → Edge 三段式启动

Session 0 无法启动图形界面程序。Start-Process msedge 通过 SSH 执行后,Edge 进程确实出现了,但窗口不显示,CDP 端口也不开。

必须用 PsExec64 在桌面会话(Session 2)启动:

1
2
3
# Linux 端通过 SSH 调用
sshpass -p "$PASS" ssh user@windows-host \
"C:\Users\<USERNAME>\PsExec64.exe -i 2 -d \"C:\...\msedge.exe --remote-debugging-port=9888 --user-data-dir=%USERPROFILE%\edge-cdp-profile\""
  • -i 2:在 Session 2(桌面会话)启动
  • -d:不等待程序返回

验证

1
2
curl http://<JARVIS_IP>:9889/json/version
# {"Browser":"Edg/150.0.4078.83","Protocol-Version":"1.3",...}

到这里,CDP 通了。

端口迁移:9222 → 9888/9889 双端口

旧端口 9222 是 Chrome CDP 默认端口,容易被扫描。迁移到 9888/9889 双端口架构:

1
2
3
Edge → 127.0.0.1:9888(CDP 只绑 localhost)
netsh portproxy → 0.0.0.0:9889 → 127.0.0.1:9888
外部访问 → http://<IP>:9889

为什么需要 portproxy?因为 Chromium 从 2023 年起不再支持 --remote-debugging-address=0.0.0.0,CDP 端口只能绑 localhost。portproxy 是 Windows 自带的端口转发,不装额外软件。

1
2
3
4
5
6
7
# 在 Windows 上执行(管理员权限)
netsh interface portproxy add v4tov4 \
listenport=9889 listenaddress=0.0.0.0 \
connectport=9888 connectaddress=127.0.0.1

# 验证
netsh interface portproxy show v4tov4

Pitfall:迁移端口后,JARVIS 上的 hermes_mcp_server.py 里的 CDP_PORT 也需要同步改。改完需要重启 MCP Server 进程。

六个脚本落地

把整个 CDP 生命周期固化成脚本,不依赖 skill.md 里的文字指令:

脚本 功能
init.sh 首次配置(IP/SSH/端口/profile 路径写入 config.yaml)
launch_jarvis_cdp.sh SSH→PsExec 启动 CDP Edge
health_check_jarvis_cdp.sh 健康检测(支持 –quiet 供脚本调用)
maximize_jarvis_cdp.sh 窗口最大化
tab_clean.sh 随机关闭多余标签页(模拟人类行为)
close_jarvis_cdp.sh 关闭 CDP Edge

所有运维脚本从 ~/.hermes/.cdp-browser-control/config.yaml 读取连接参数,不硬编码 IP 和密码。

三个 Bug 和一个 Edge 怪癖

tab_clean.sh:三个 Bug 叠加

标签页清理脚本第一版有三个 bug:

Bug 1 — Python API 名错误urllib.request.request 不存在,应该是 urllib.request。低级错误,但脚本照着写的时候没测。

Bug 2 — Shell 变量注入 Python 字符串:用 f-string 把 SSH 密码直接拼进 Python 代码字符串。密码含 @ 符号还能跑,但遇到特殊字符会炸。改为用环境变量传参。

Bug 3 — CDP 消息 ID 冲突:关闭多个标签页时用了 100 + i 作为消息 ID。但 CDP 浏览器自身的事件消息也带 ID,会和手动设置的 ID 冲突。改为单调递增分配唯一 ID。

窗口最大化:Edge CDP 的状态机限制

写窗口最大化脚本时,自然思路是:

1
2
3
# 直接设为 maximized
await ws.send(json.dumps({'method': 'Browser.setWindowBounds',
'params': {'windowId': wid, 'bounds': {'windowState': 'maximized'}}}))

Edge 返回错误:

1
{"code": -32000, "message": "To maximize a minimized or fullscreen window, restore it to normal state first."}

窗口当前是 minimized 状态,Edge 不允许从 minimized 直接跳 maximized。必须两步:

1
2
3
4
5
6
# 第一步:restore 到 normal
await ws.send(json.dumps({'method': 'Browser.setWindowBounds',
'params': {'windowId': wid, 'bounds': {'windowState': 'normal'}}}))
# 第二步:maximize
await ws.send(json.dumps({'method': 'Browser.setWindowBounds',
'params': {'windowId': wid, 'bounds': {'windowState': 'maximized'}}}))

另一个坑Browser.getWindowForTarget 在 browser-level WebSocket 上调用会返回 "No web contents in the target"。必须在 page-level WebSocket 上调用才行。CDP 文档没标这个区别。

技能优化大师审查

脚本和 SKILL.md 写完后,跑了一轮技能优化大师审查。五维标杆诊断发现:

  • 死引用:SKILL.md 引用 waf-bypass-via-cdp.md,实际文件名是 cdp-data-proxy-waf-bypass.md
  • 幽灵脚本cdp_search.pyverify_all_platforms.py 在 scripts/ 目录里存在,但 SKILL.md 完全没提——agent 不知道它们能干什么
  • 4 个 references 文件未被索引:10 个文件只列了 6 个
  • 缺反例清单:只有”应该怎么做”,没有”不要怎么做”
  • CDP 示例用了未定义变量<IP>:<PORT> 没说从哪读

全部修完,SKILL.md 从 v2.0.0 升到 v2.2.0,10/10 references 零死引用。

验证:YouTube + Google

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 健康检测
bash health_check_jarvis_cdp.sh
# ✅ CDP 存活 | Edge | 标签页: 1

# 导航到 Google + 截图(Python CDP WebSocket)
# ✅ 截图保存: /tmp/google_screenshot.jpg (30008 bytes)
# 页面标题: Google

# 导航到 YouTube + 截图
# ✅ 截图保存: /tmp/youtube_screenshot.jpg (10741 bytes)
# 页面标题: YouTube

# 窗口最大化
bash maximize_jarvis_cdp.sh
# 当前状态: minimized → 最终状态: maximized ✅

# 关闭
bash close_jarvis_cdp.sh
# All msedge killed ✅ CDP 已关闭

全链路:检测 → 启动 → 导航 → 截图 → 最大化 → 标签清理 → 关闭,每个环节都有脚本,每个脚本都实测过。

教训

  1. 脚本固化优于文档指令:CDP 连接涉及 SSH + PsExec + 端口 + profile 路径 + 防火墙五个变量,写成文档让人照着做 vs 写成一个 launch.sh,后者不会漏步骤。
  2. 先测再写文档:tab_clean.sh 三个 bug 都是因为”照着之前的代码改”没实测。写完脚本第一件事是跑一遍。
  3. Edge CDP ≠ Chrome CDP:窗口状态机限制、getWindowForTarget 的 page/browser WS 区别,这些在标准 CDP 文档里找不到。只有踩了才知道。

参考文献

  1. Chrome DevTools Protocol — Browser domain
  2. PsExec - Sysinternals
  3. Microsoft Edge 远程调试
  4. Chromium 修改:–remote-debugging-address 已废弃

AI Agent 连不上浏览器了:CDP 远程控制从 502 到全链路打通
https://normdist.com/2026/07/20/ND-20260720-002-cdp-browser-control-debug/
作者
小瑞
发布于
2026年7月20日
许可协议