Skip to content

✨ sctl Browser 第 5 期原始 CDP - #7

Merged
CodFrm merged 11 commits into
mainfrom
feat/raw-cdp
Oct 9, 2026
Merged

CodFrm merged 11 commits into
mainfrom
feat/raw-cdp

Conversation

@CodFrm

@CodFrm CodFrm commented Oct 9, 2026

Copy link
Copy Markdown
Member

概要

sctl Browser 第 5 期:原始 CDP。

  • 单条原始命令:AI agent 和脚本可以经命令行或 MCP,向标签页发送一条原始 Chrome DevTools Protocol 命令。
  • CDP 端点:
    • sctl 给出一个端点,真实的 Playwright chromium.connectOverCDP() 与 Puppeteer connect() 可以直接连上用户正在用的浏览器。
    • 浏览器不需要以 --remote-debugging-port 重启。

spec:docs/specs/2026-10-08-raw-cdp.md

  • 修订 f47608b:设置下载行为的请求成功但不起作用。Playwright 连接时一定会发这条请求,报错会让 connectOverCDP 失败。
  • 修订 f47608b 另外要求扩展列出能附加的标签页及目标 ID,并给出真实的 User-Agent。

改动

  • sctl cdp send <Method> [--params JSON],MCP cdp_send
    • 走 sctl 自己的调试会话,附加、空闲断开、串行规则都与第 3 期页面命令相同。
    • 返回 Chrome 的原始结果,外加 tabId 与 contentTrust。
    • 以下 7 条命令会破坏 sctl 自身的状态,直接拒绝:Page/Runtime/Network/Log.disable、Emulation.setFocusEmulationEnabled、Target.setAutoAttach、Target.detachFromTarget。其余命令原样发送。
    • 帮助与 MCP 描述写明 Fetch.enable、Debugger.pause 的后果和恢复办法:Fetch.disable、Debugger.resume 或 page detach。
    • 弹框打开时照常发送,可以用来发 Page.handleJavaScriptDialog。
  • sctl cdp endpoint|status|close,MCP cdp_endpoint/cdp_close
    • 地址:
      • 每个浏览器一个端点,给出 http://<daemon>/cdp/<密钥>(Playwright)和 ws://…/devtools/browser/<id>(Puppeteer)两种地址。
      • 密钥是 128 位随机数,只存在内存里。
      • 地址在以下情况失效:close、daemon 退出、连续 60 分钟没有客户端、browsers forget(浏览器离线时也生效)。
    • 安全:拒绝错误密钥(404),拒绝 Host 不是回环地址或 IP 的请求,拒绝任何带 Origin 的请求(403)。同时只允许一个客户端,第二个返回 409。
    • 连接时:sctl 交出这个浏览器的标签页,即结束录制、先关已知的弹框、再断开自己附加的标签页。
    • 连接期间:page/debug/cdp send 返回新错误码 ENDPOINT_CONNECTED(退出码 3)。不操作页面的命令照常执行。
    • 断开时:先关弹框,再断开端点附加的标签页。标签页(包括客户端新开的)都保留,sctl 的命令恢复可用。
  • 浏览器级模拟(新包 internal/daemon/cdpendpoint)
    • 为什么要模拟:chrome.debugger 只能附加标签页,所以 Browser.* 与 Target.* 由 sctl 模拟。
    • 模拟的命令:
      • Browser.getVersion 返回真实 UA。
      • setDownloadBehavior 回答成功但不起作用。
      • Browser.close 只断开客户端,不关闭浏览器。
      • 目标的发现、自动附加、新建、关闭、激活、附加、分离,以及标签页增删改对应的目标事件。
    • 焦点模拟:端点附加的每个标签页都开启。真机探针里,Puppeteer 不开焦点模拟时在后台标签页上超时。
    • 转发:页面会话与跨进程 iframe 子会话的命令按序原样转发,事件原样转回。
    • 不支持的功能:新的浏览器上下文、权限、窗口尺寸、证书错误、Service Worker,返回点名该功能的 CDP 错误。
  • bridge:支持多个监听者。忘记离线实例时也通知监听者。
  • 扩展:新增 internal 方法 debugger.targets、userAgent、open、close、own,以及通知 debugger.tabCreated、tabUpdated、tabRemoved。端点占用的标签页不受 10 分钟兜底断开影响,与 daemon 断开时清除。
  • 协议:新增错误码 ENDPOINT_CONNECTED,只对浏览器扩展这一对端生成。给 ScriptCat 的生成 TS 逐字节不变。没有新增浏览器权限,最低版本仍为 Chrome 125。
  • 文档:AGENTS、architecture、protocol、threat-model、mcp 和两份 README(含 Playwright/Puppeteer 连接示例与不支持列表)。

验证

自动化检查(交付树 249a5b6)

  • go build、go vet、go test ./... -race -count=1、golangci-lint(0 issues)、make protocol-check 全部 exit 0。
  • 扩展的 lint、format:check、typecheck、test(629/629)、build 全部 exit 0。
  • 端点测试按探针里 Playwright 与 Puppeteer 真实的握手序列回放,用假扩展完成。不提交直连真浏览器的测试。

审查

  • spec 核对(1197fba):浏览器离线时 cdp status/close 仍能查看并撤销点名浏览器的端点;补威胁模型与 AGENTS。
  • 代码审查(249a5b6)修了 4 个问题,并删了一处死代码:
    • 附加准备期间被断开时,attachToTarget 改为返回错误。
    • 经主机名连到 daemon 时,不再给出必被 Host 检查拒绝的地址。
    • 扩展的端点标记在两种情况下会遗留,现在都会清除。
    • 删掉只被测试用到的 Browser.InstanceID。

真机验证

  • 环境:
    • 隔离的临时配置,不使用日常浏览器配置。
    • 同时加载本分支的 sctl Browser 与 ScriptCat,连临时 daemon。
    • 真实的 playwright-core 1.64 与 puppeteer-core 25 只经端点连接。
  • 配置:Chrome for Testing 125 headless、Chromium 153 有界面、Chrome for Testing 125 有界面。共 315 项检查,0 失败。
  • 成立的要点:
    • 两种客户端都在后台标签页上完成了导航、执行脚本、填写与点击、截图、跨源 iframe、console 与请求事件、弹框、请求拦截、popup、新建与关闭页面。
    • browser.close() 不关浏览器,标签页(包括客户端新开的)保留,断开后 sctl 命令恢复。
    • 连接前的录制被结束,已知弹框被关闭;连接期间 page/debug/cdp send 返回 ENDPOINT_CONNECTED,tabs list 与 ScriptCat 照常。
    • 第二个客户端返回 409;错误密钥 404;Host: evil.example 与带 Origin 的请求 403。
    • 断开时有未处理弹框、客户端进程被杀、sctl cdp close:三种情况都正确收回。
    • daemon 退出与 Chrome 退出时客户端收到 1001;daemon 重启后旧地址 404;离线 forget 后地址 404。
    • Cookie 读取在 125 上被 Chrome 拒绝(Permission denied),在 153 上可以。
    • cdp send 的校验、拒绝列表、Chrome 错误、TIMEOUT、PAYLOAD_TOO_LARGE、弹框期间发送,以及 Debugger.pause/resume 恢复都成立;MCP 三个工具都成立。

未在真机上观察(均有单元测试覆盖)

  • 60 分钟无客户端失效(没有真等)。
  • 调试提示条的显示与消失:验证终端没有 macOS 屏幕录制权限。
  • 下载事件不送达:为避免写入真实的 ~/Downloads,没有点下载。
  • 浏览器离线时创建端点的错误码。
  • 与页面命令排队,以及默认 10 秒超时。
  • 窗口尺寸、证书错误、Service Worker 的不支持错误。
  • 超过 4 MiB 的事件。

已知限制

  • 单个超过 4 MiB 的事件会被扩展丢弃,端点客户端收不到。
  • 客户端连着时,sctl 自己的页面命令不可用,这是 spec 设计决策 4:每个标签页只有一个使用方。
  • 同一会话上的命令按应答或 20 ms 先到者放行下一条,因为 bridge 没有「已写给扩展」的信号;这样弹框与请求拦截不会死锁。

CodFrm added 11 commits October 9, 2026 14:27
…DP 失败),只是收不到下载事件;扩展另需列出可附加标签页与目标 ID、给出真实 User-Agent
…WS 地址、Host/Origin/密钥检查与单客户端、连上先交出 sctl 标签页并标记端点所有、断开先关弹框再断开且保留标签页后收回、close/60 分钟/忘记/daemon 退出失效;/control/cdp/*、sctl cdp endpoint|status|close、MCP cdp_endpoint/cdp_close 及文档
…vior 空操作、close 只断开、Target 发现/自动附加/getTargets/getTargetInfo/上下文/新建/关闭/激活/附加/分离),附加的标签页先开焦点模拟,页面与子会话命令按序转发、事件按会话转回、标签页增删改转成目标事件,不支持的功能与扩展错误映射为 CDP 错误;按 Playwright/Puppeteer 真实握手回放测试;mcp.md 与两份 README 补连接示例与限制
… 改述端点现在的行为(浏览器级命令由 sctl 模拟、会话命令转发、不支持的功能返回点名功能的 CDP 错误)
…dge.ResolvePairedBrowser,未点名时仍只选在线实例),protocol.md 改述;威胁模型补原始 cdp send 的范围与 contentTrust,AGENTS.md 无审批例外补 sctl cdp,architecture.md 补 Browser 用到的 own 与 tabs.activate
…经主机名连到 daemon 时 cdp endpoint/status 回答 INVALID_REQUEST 而不是给出必被 Host 检查拒绝的地址;扩展 debugger.open 找不到目标时清除标记、与 daemon 断开时清除全部端点标记;删除只被测试用到的 Browser.InstanceID;protocol.md 同步
@CodFrm
CodFrm merged commit 5d10c7a into main Oct 9, 2026
4 checks passed
@CodFrm
CodFrm deleted the feat/raw-cdp branch October 9, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant