DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-playwright

d

dsh-playwright

Manifest valid

A plugin for the DeepSeek Harness, enabling browser-use via playwright

hasBundlePatch

playwright-browser

Release License: MIT

DSH (DeepSeek Harness) 浏览器自动化插件:给智能体提供一套 browser_* 模型工具,用 Playwright 驱动 Chromium 真实操作网页——打开页面、点击、填表、抓取 DOM、截图。

兼容性:对 dsh 0.2.0-rc.2 实测通过——browser_* 工具注册与实际驱动、内置 skill、bundle 装配都在该版本上跑过。

从 dsh 0.2.0-rc.2 起多了一道插件兼容闸门:dsh 会读插件的 peerDependencies,凡是声明了 @deepseek-ai/dsh / @deepseek-ai/dsh-* 且 semver 不满足当前运行时的插件行,会被直接拒绝加载(插件管理里报 incompatible-version),需要 dsh plugin allow-version <包>@<版本> --dsh-version <版本> --accept-risk 才放行。本插件据此声明了 @deepseek-ai/dsh: ^0.2.0-rc.2(覆盖整个 0.2.x 线),并同步写了 engines.dsh。升级 dsh 到 0.3 及以上时请同步更新本插件,否则会被闸门拦下。

该 peer 标记为 optional:dsh 给每个 profile 写了 autoInstallPeers: false,运行时的 dsh 由安装目录提供、不由 profile 安装,标成 optional 只是避免 pnpm 报"未满足的 peer"——闸门读的是 peerDependencies 本身,不受 peerDependenciesMeta 影响。

  • 宿主进程内直接 require('playwright-core'),自建模式下无需外部桥服务或端口
  • 浏览器按需懒启动,插件卸载时自动关闭
  • 可选挂载已经打开的浏览器(CDP attach):直接接管你正在用的 Chrome/Edge,登录态、Cookie、扩展、已开标签页全都在(见下文)
  • 只依赖 playwright-core,无其他运行时依赖
  • 中断即取消:转发 exec.signal——goto/click/fill/type/press/innerText/screenshot 把取消信号直接交给 Playwright(在途操作真的被中止),Playwright 无法取消的调用(connectOverCDP、launch、evaluate、waitForTimeout …)也保证立刻结算,不会把整轮对话吊在那里
  • 失败就是失败:出错时直接抛,由 dsh 渲染成带 isError 的规范失败结果(正文是 Error: …),不再拼成"看起来成功"的字符串——重试/纠错类策略和会话日志都能看见
  • 截图能"看":browser_screenshot 把 PNG 交给 profile 的 attachment 服务,以图片块与路径一起内联;支持图片输入的模型直接看到画面(不支持时 dsh 自动换成占位文本,仍可用返回的路径调 read_image)
  • 工具描述/参数文档/输出文案支持中英双语(PW_LANG=en 切换,默认中文)— English README

功能一览

工具作用
browser_open打开 URL,返回最终地址与页面标题
browser_status查询浏览器/当前页面状态(是否挂载、端点、URL、标题、标签页数)
browser_attach运行时挂载/切换到一个已在运行的浏览器(无需重启 DSH)
browser_tabs列出/切换/关闭已打开的标签页(挂载模式下的"接手"入口)
browser_click点击元素(CSS 或 text= 选择器)
browser_type逐字输入(可设 delay 模拟真人)
browser_fill快速填充输入框
browser_press按键(Enter / Tab / Escape …)
browser_wait等待若干毫秒
browser_extract抓取页面或指定元素的文本
browser_html抓取页面或指定元素的 HTML
browser_eval在页面上下文执行 JS 表达式(诊断 DOM 等)
browser_screenshot截图保存为 PNG 并返回绝对路径;支持图片输入的模型同时看到画面本身
browser_close关闭浏览器释放资源(挂载模式下只断开连接)

安装到 DSH profile

在任意目录执行一条命令(dsh 自行定位 profile 目录,首次使用会自动初始化):

# 示例:装进 web profile
dsh plugin --profile web add git+https://github.com/whklwhkl/dsh-playwright.git

dsh plugin 把 add 之后的参数原样转发给 profile 目录里的 pnpm,安装完成后自动把声明了 dsh.bundle 的依赖追加进 dsh.profile.bundles——无需手改 package.json。registry 包名、github:<user>/<repo>、本地路径等 pnpm 支持的安装源均可。

本地开发时指向 checkout 目录,加 link: 前缀以符号链接安装,改动后无需重新安装(重启 DSH 生效):

dsh plugin --profile web add link:/path/to/dsh-playwright

link: 装的是符号链接,插件自己的依赖不会跟着进 profile:先在这个 checkout 里跑一次 pnpm install(装 playwright-core),否则 dsh 加载插件时会报 Cannot find package 'playwright-core',browser_* 工具直接消失。仓库里那份 pnpm-workspace.yaml(autoInstallPeers: false)也是为此——dsh 给每个 profile 写的就是同一份设置,它阻止 pnpm 顺着 peerDependencies 把整个 @deepseek-ai/dsh 运行时树拉进这个仓库。

如果工具是"先能用、重装插件后消失",先重启 dsh:宿主插件加载失败不会被自动重试。

重启 DSH: bundle 列表在启动时读取,重启后 browser_* 工具对 profile 下所有会话自动可用。

旧版 dsh 没有 plugin 子命令时:手动把 playwright-browser 加入 profile package.json 的 dependencies,并追加进 dsh.profile.bundles,再在 profile 目录执行 pnpm install。

准备浏览器

playwright-core 不会自动下载浏览器,首次使用前需要准备 Chromium,二选一:

方式 A:让 playwright-core 自动下载(推荐,零配置)

# 任选其一(等价):
npx playwright-core install chromium
# 或安装完整版 playwright 借其下载器:
npm i -D playwright && npx playwright install chromium

下载的浏览器会进入系统标准缓存(macOS 为 ~/Library/Caches/ms-playwright,Linux 为 ~/.cache/ms-playwright),插件启动时自动发现。

方式 B:复用系统已有的 Chrome/Edge/Chromium(免下载)

给运行 DSH 的进程设置环境变量,指向任意现成浏览器可执行文件:

export PW_CHROMIUM_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
# Linux 示例:export PW_CHROMIUM_PATH="/usr/bin/google-chrome"

版本提示:自动发现依赖 playwright-core 与其期望的 Chromium build 号匹配(npx playwright-core install chromium 总是安装匹配版本)。用 PW_CHROMIUM_PATH 指向任意 Chromium 系浏览器则无版本要求。

方式 C:挂载已经打开的浏览器(复用登录态)

上面 A/B 两种方式都是让插件新起一个干净浏览器——没有登录态、没有扩展、没有你手动打开的标签页。若要让智能体直接在你正在使用的 Chrome 里干活,用挂载模式:

export PW_CDP_ENDPOINT=chrome        # 真实默认 profile,推荐
# 或 export PW_CDP_ENDPOINT=http://127.0.0.1:9222   # 指定调试端口
# 或 export PW_CDP_ENDPOINT=auto      # 先试 chrome,再试 9222

chrome 这个取法要求你在目标浏览器里开启一次远程调试开关(Chrome 136+ 的安全策略,默认关闭):

  1. 地址栏打开 chrome://inspect/#remote-debugging
  2. 勾选 Allow remote debugging for this browser instance

之后插件即可挂载你的默认 profile:Cookie、登录态、扩展、已打开的标签页全都在。

⚠️ 每次新建 CDP 连接,Chrome 都会弹一次「要允许远程调试吗?」(实测 Chrome 153,这是 chrome://inspect 开关的激进安全策略,无法配置成"记住授权")。在你点「允许」之前,WebSocket 升级握手一直被静默挂起,所以表现就像"连不上/无缘无故超时"。 因此:第一次挂载请留意 Chrome 窗口,点一次「允许」;之后插件会复用这一条连接,不再弹框(browser_attach 同一端点、browser_tabs、browser_open 等都不会重新建连)。browser_close/插件重载会断开,下次挂载需要再点一次。

想改用端口方式(http://127.0.0.1:9222)时注意:Chrome 136 起 --remote-debugging-port 对默认数据目录不再生效,必须同时指定一个专属目录,并且该 Chrome 需先完全退出:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-debug"

这条路子完全不弹授权框(不走 chrome://inspect 开关),/json/version 也正常,代价是那个专属目录是新的:需要在那里登录一次你要用的站点。适合无人值守/长期自动化;想直接用现有登录态就用上面的 channel 名 chrome。

两种取法不能混用(实测):用 chrome://inspect 开关启动的浏览器只提供 WebSocket 端点,不响应 HTTP 发现接口(http://127.0.0.1:9222/json/version 返回 404),所以对这类实例只能写 channel 名 chrome;反过来,用 --remote-debugging-port 起的实例两种写法都行。 插件现在自己读 DevToolsActivePort(第 1 行端口 + 第 2 行 GUID 路径)来解析 chrome,不再走 Playwright 内置的 channel 解析——后者只取端口、丢掉 GUID 路径,在 Chrome 153 上必然连不上。

不想重启 DSH 也能挂载: 直接让智能体调用 browser_attach(也可以自己说"挂上我的 Chrome")。它接受与 PW_CDP_ENDPOINT 相同的写法,并且:

调用作用
browser_attach缺省端点:已挂载就保持当前端点,否则用 PW_CDP_ENDPOINT(未设置则 auto)
browser_attach({ endpoint: "chrome" })挂到该端点(channel 名 / http://… / ws://… / auto / 逗号分隔回退)
browser_attach({ page: "portal" })不换浏览器,只按 URL/标题子串换要接手的标签页;命中不到会报错
browser_attach({ endpoint: "launch" })不再挂载,切回插件自建的干净浏览器(忽略环境变量)

挂载模式的行为约定:

  • browser_status 不会主动建立连接(一条新连接 = 一次授权弹窗,只读查询不值得让用户点框);未挂载时它会告诉你去调 browser_attach;
  • 同一端点上重复 browser_attach 会复用现有连接(只按 page 子串重选标签页),不会断开重连、不会又弹一个授权框;
  • browser_tabs 列出全部标签页(* = 当前操作页),传 index 或 url 子串即可切换(会置前),加 close:true 关闭该标签页;
  • 取标题这类"顺手带的信息"都有往返预算:某个标签的渲染进程卡死(死循环、Chrome 的「页面无响应」、DevTools 里断点暂停)时,它会被标成 (无响应),整次调用仍在约 PW_RTT_TIMEOUT 毫秒内返回,而不是挂住整轮对话;
  • browser_open 在当前跟踪的标签页里导航——别拿它去覆盖你不想丢的页面,可以先 browser_tabs 选一个;
  • browser_close 只断开连接,绝不关闭你的浏览器(自建实例模式下才是真的关闭);
  • PW_CDP_PAGE 可按 URL/标题子串固定要接手的标签页;
  • 挂载失败时会打印诊断:端口是否在监听、/json/version 的 HTTP 状态、DevToolsActivePort 内容;并且区分「WS 握手就没完成」(= 在等你点「允许远程调试」)与「握手完成了但初始化没结束」(= 某个标签的渲染进程不响应,报错里会列出各标签帮你定位);
  • 连接预算由 PW_CDP_TIMEOUT 控制(默认 90 秒,留够看到弹窗并点一下的时间);端口无人监听时仍然毫秒级快速失败,不会白等;
  • 该模式下 PW_HEADLESS、PW_CHROMIUM_PATH 无效(浏览器是你自己起的)。

与 webclaw3 的区别:webclaw3 走 Chrome 扩展桥接 + 本地服务,本插件走 Playwright 原生 CDP 挂载,不需要装扩展;代价是 CDP 看不到 chrome:// 等特权页面(扩展桥接可以)。

配置(环境变量)

变量默认说明
PW_LANGzh设为 en 切换工具描述与输出为英文
PW_CHROMIUM_PATH自动发现复用指定浏览器可执行文件
PW_HEADLESStrue设为 false 弹出可见窗口(挂载模式下无效)
PW_SHOT_DIR插件目录下 shots/截图保存目录
PW_CDP_ENDPOINT未设置设置后进入挂载模式:chrome / msedge 等 channel 名、http://127.0.0.1:9222、ws://…,逗号分隔可做回退,auto = 先 chrome 再 9222
PW_CDP_PAGE未设置挂载时按 URL 或标题子串挑选要接手的标签页
PW_CDP_TIMEOUT90000挂载连接预算毫秒。它同时是"等你在授权框上点允许"的窗口;端口无人监听时仍毫秒级快速失败
PW_RTT_TIMEOUT1500单次页面往返(取标题、置前)的预算毫秒。渲染进程卡死时靠它快速失败并标记 (无响应),而不是把整次调用挂死

内置 skill:playwright-browser-tips

bundle 同时携带一个 playwright-browser-tips skill,正文是一张站点地图:各站点在自动化下的实测行为与对策(搜索类任务默认 Bing),加上通用恢复手法。中英双语跟随 PW_LANG;模型在 browser_* 工具失败或自动化搜索/登录流程时按需加载,用户也可以直接输入 /playwright-browser-tips 调用。

地图欢迎共建——人和 agent 都可以按 SITE-MAP-SPEC.md 的规范提交条目(只改 assets/site-map.json),提交前运行 node scripts/validate-site-map.js 并把输出贴进 PR。框架文本(含反自动化边界:验证码一律由用户人工完成)由代码持有,不随地图数据变化。

skill 需要带 skill 注册表的 profile——web、headless、acp、sdk-app 等基于 dsh-base 的 profile 均满足。同名项目或用户目录 skill 优先级更高,可本地覆盖插件内置版本。

使用示例(对智能体说的话)

  • "用浏览器打开 https://example.com,抓取正文给我"
  • "打开百度,搜索「playwright」,把第一条结果标题告诉我"
  • "打开这个页面 https://…,点击「登录」,截个图"
  • "挂上我的 Chrome,把当前标签页的正文抓下来"(智能体调用 browser_attach,或设 PW_CDP_ENDPOINT)
  • "看看我浏览器里都开了哪些标签页,切到那个 XXX 页面然后点登录"(同上)

故障排查

现象处理
Executable doesn't exist ... ms-playwright浏览器未下载,执行 npx playwright-core install chromium
下载 Chromium 时连接中断/超时(代理环境常见)大文件经代理易被中断;改用方式 B 的 PW_CHROMIUM_PATH 指向系统 Chrome,免下载
Could not connect to chrome: DevToolsActivePort file not found目标浏览器没开远程调试:去 chrome://inspect/#remote-debugging 勾选允许,或改用 PW_CDP_ENDPOINT=http://127.0.0.1:9222 并以专属 --user-data-dir 启动 Chrome
http://127.0.0.1:9222 报 Unexpected status 404 ... /json/version/该端口上的浏览器是用 chrome://inspect 开关开的:它不提供 HTTP 发现接口,改用 channel 名 chrome(或 browser_attach({endpoint:"chrome"}))
connect ECONNREFUSED 127.0.0.1:9222目标浏览器没起来或端口不对:确认它带 --remote-debugging-port=9222 启动,且 curl http://127.0.0.1:9222/json/version 有返回
CDP 挂载失败(已尝试 N 个端点)端点不对或浏览器没开调试;报错里会逐个列出失败原因 + 端口是否在监听 + /json/version 状态 + DevToolsActivePort 内容,不用手工摸排
挂载报 Timeout ... exceeded,同时提示"端口在监听但 WS 握手一直没完成"Chrome 在等你点授权框:切到 Chrome 窗口点「允许远程调试」的「允许」,再调一次 browser_attach。框没看到就翻一下其他窗口/最小化的 Chrome;点得慢就把 PW_CDP_TIMEOUT 调大
挂载报 Timeout ... exceeded,提示是"WS 握手已经完成,但初始化一直没结束"不是授权框问题:浏览器里有标签的渲染进程不响应(挂载要等所有已存在的标签页初始化完)。报错里会列出当前标签,关掉那个卡住的(Chrome 里常显示"页面无响应")再重试;想快速失败可调小 PW_CDP_TIMEOUT
同上,而且你刚经历过电脑意外关机 / Chrome 异常退出后重开同一条原因:Chrome 恢复会话时留下的那些挂起标签一直没真正加载,也算"没初始化完"(实测踩过)。把那些标签关掉(或先 browser_attach({endpoint:"launch"}) 用自建实例绕开),或者干脆清掉 PW_CDP_ENDPOINT 不挂载
browser_tabs 某一行的标题是 (无响应)那个标签的渲染进程卡死了(死循环、Chrome 的"页面无响应"、DevTools 里断点暂停)。它的标题和页面操作都取不到,别再驱动它;在浏览器里关掉它即可恢复。若整行是 (无标题) 则只是页面本身没有标题
browser_tabs / browser_status 卡住不返回0.4.0 及更早版本会把整次调用挂在无响应标签的标题上。0.4.1 起有往返预算(PW_RTT_TIMEOUT,默认 1500ms),最坏也只是标成 (无响应)
每调一次 browser_attach 都弹一次授权框说明中间断开过(browser_close、插件重载、DSH 重启)。同一端点上重复 browser_attach 现在会复用连接;想彻底摆脱弹框就改用专属 --user-data-dir 的端口方式(见方式 C)
挂载模式下 browser_open 覆盖了我正在看的页面正常现象——它导航的是"当前跟踪的标签页";先 browser_tabs 切到目标页,或用 PW_CDP_PAGE 固定
net::ERR_CONNECTION_CLOSED目标站点网络问题或反爬,换个站点/稍后重试
站点弹验证码(如百度滑块)、headless 下输入框不可见反自动化机制,非插件问题;实测 Bing 全流程可用,可优先换 Bing,或设 PW_HEADLESS=false 用有头模式
元素"not visible"页面改版或选择器过时,用 browser_eval 检查 DOM 再选选择器
截图只返回路径、没有画面三种原因:profile 没挂 attachment 服务(dsh-base 系 profile 都挂了)、图片超出部署上限、或当前模型不接受图片输入。后两种不影响使用——路径照常返回,可用 read_image 查看
browser_open 报 net::ERR_* 后,再开任何页面都报 ... is interrupted by another navigation to chrome-error://...0.5.0 之前的老问题:失败的导航会在那个标签里挂起一条错误页导航,把该标签的后续导航全部堵死(reload 也救不回来)。0.5.0 起 browser_open 检测到这类失败会自动换掉那个 page(自建实例关掉重开;挂载模式改为新开一个标签,不关你的标签)
失败结果的正文从 错误:… 变成了 Error: …0.5.0 起错误直接抛给 dsh,由它渲染成规范失败结果(isError):前缀由 dsh 决定,因此是英文。工具是否失败看 isError,不要匹配文案

本地开发 / 快速自测

# 不启动 DSH,直接验证模块加载与工具注册:
node --input-type=module -e "
import { apply } from './lib/index.js'
const tools = []
apply({ tools: { register: (d) => tools.push(d) }, effect: () => () => {} })
console.log(tools.map((t) => t.name).join('\n'))
"

# 挂载模式冒烟测试(另开一个终端,先按"方式 C"起好可调试的 Chrome):
# 不设 PW_CDP_ENDPOINT 也行——browser_attach 可以运行时指定端点
node --input-type=module -e "
const { apply } = await import('./lib/index.js')
const m = new Map()
apply({ tools: { register: (d) => m.set(d.name, d) }, effect: () => () => {} })
const call = (n, a = {}) => m.get('browser_' + n).execute(a)
console.log(await call('attach', { endpoint: 'http://127.0.0.1:9222' }))  // 挂载
console.log(await call('tabs'))                                          // 列出现有标签页
console.log(await call('attach', { endpoint: 'launch' }))                // 切回自建实例
console.log(await call('open', { url: 'https://example.com' }))
console.log(await call('close'))                                         // 关闭自建实例
"

社区与支持

本插件是 DeepSeek Harness(DSH)生态插件,按官方社区支持指南添加了 dsh-plugin topic 以便被发现。

License

MIT

Comments

Loading…