dsh-browser-cdp — 看得见的智能体浏览器CDP 接入
42.9/100
综合分
20
GitHub 星标
7,118
npm 周下载
—/5
用户评分
数据截至 2026/10/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-browser-cdpnpm 源 · dsh-browser-cdp 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · browser
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-browser-cdp @ 1.7.2
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/27 23:40:22
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 135 处,展示前 79 条)
- 读取本地文件lib/index.js:191
return existsSync(b.exePath) && existsSync(join(b.userDataDir, "Local State")); - 发起网络请求lib/index.js:276
const r = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(3e3) }); - 读取本地文件lib/index.js:364
if (!existsSync(src)) return null; - 写入本地文件lib/index.js:367
await mkdir(dir, { recursive: true }); - 读取本地文件lib/index.js:373
const markers = countEncryptionMarkers(await readFile(src)); - 删除本地文件lib/index.js:376
for (const old of entries.slice(0, Math.max(0, entries.length - 3))) await rm(join(backupRoot, old), { - 读取本地文件lib/index.js:394
if (countEncryptionMarkers(await readFile(src)) > 0) return false; - 读取本地文件lib/index.js:425
const profiles = profilesFromLocalState(await readFile(join(browser.userDataDir, "Local State"), "utf8"), browser.userDataDir); - 删除本地文件lib/index.js:504
await rm(linkDir, { force: true }); - 删除本地文件lib/index.js:510
await rm(portFile, { force: true }).catch(() => null); - 读取本地文件lib/index.js:548
port = parseDevToolsActivePort(await readFile(portFile, "utf8")); - 删除本地文件lib/index.js:676
await rm(linkDir, { force: true }); - 发起网络请求lib/index.js:787
const response = await (options.fetchVersion ?? ((url, init) => fetch(url, { signal: init.signal })))(target, { signal }); - 发起网络请求lib/index.js:1919
const r = await fetch(`http://127.0.0.1:${port}${path}`, { signal: AbortSignal.timeout(4e3) }); - 发起网络请求lib/index.js:1929
const r = await fetch(`http://127.0.0.1:${port}${path}`, { - 写入本地文件lib/index.js:3195
await mkdir(dir, { recursive: true }); - 写入本地文件lib/index.js:3201
await writeFile(target, block, "utf8"); - 写入本地文件lib/index.js:4172
await mkdir(platformRoot, { recursive: true }); - 写入本地文件lib/index.js:4176
await mkdir(tempRoot, { recursive: true }); - 写入本地文件lib/index.js:4202
if (manifest.archiveType === "gzip") await pipeline(createReadStream(archivePath), createGunzip(), createWriteStream(extractedPath, { mode: 493 })); - 删除本地文件lib/index.js:4223
await rm(archivePath, { force: true }); - 删除本地文件lib/index.js:4236
if (hadExisting) await rm(backup, { - 发起网络请求lib/index.js:4792
fetch: (...args) => globalThis.fetch(...args), - 发起网络请求lib/index.js:4834
const response = await deps.fetch(url, { - 发起网络请求lib/client.js:718
return fetch(path, { - 发起网络请求lib/client.js:985
fetch(VIDEO_ROUTE + "?generation=" + encodeURIComponent(generation), { signal: abort.signal }).then(function(res) { - 发起网络请求lib/client.js:1274
return fetch(path, { - 发起网络请求lib/client.js:2804
fetch(INPUT_ROUTE$1, { - 发起网络请求lib/client.js:3046
await fetch(EGO_CLOSE_ROUTE, { - 发起网络请求lib/client.js:3221
fetch("/api/bcdp/raise", { - 读取本地文件lib/launcher-BadIUjAx.js:9
exists: (path) => existsSync(path), - 读取本地文件lib/launcher-BadIUjAx.js:10
readFile: (path) => readFileSync(path, "utf8"), - 写入本地文件lib/launcher-BadIUjAx.js:12
mkdirSync(join(path, ".."), { recursive: true }); - 写入本地文件lib/launcher-BadIUjAx.js:13
writeFileSync(path, data, "utf8"); - 删除本地文件lib/launcher-BadIUjAx.js:17
rmSync(path); - 执行外部命令固定命令lib/launcher-BadIUjAx.js:31
spawn("taskkill", [ - 执行外部命令来自配置/变量lib/launcher-BadIUjAx.js:45
const child = spawn(bin, args, { - 发起网络请求lib/launcher-BadIUjAx.js:57
return { ok: (await fetch(url)).ok }; - 执行外部命令固定命令lib/launcher-BadIUjAx.js:366
spawn("wsl.exe", [ - 读取本地文件bin/cdp-cast-worker.mjs:4733
if (candidate !== "ffmpeg") await access(candidate, process.platform === "win32" ? constants.F_OK : constants.X_OK); - 发起网络请求bin/cdp-cast-worker.mjs:6622
const response = await fetch(`http://127.0.0.1:${state.port}/json/version`, { signal: AbortSignal.timeout(1500) }); - 发起网络请求bin/cdp-cast-worker.mjs:6639
const response = await fetch(`http://127.0.0.1:${active.port}/json/list`, { signal: AbortSignal.timeout(1500) }); - 执行外部命令固定命令bin/cdp-cast-worker.mjs:6736
const output = execFileSync("powershell.exe", [ - 执行外部命令固定命令bin/cdp-cast-worker.mjs:6746
execFileSync("taskkill", [ - 执行外部命令固定命令bin/cdp-cast-worker.mjs:6757
const output = execFileSync("ps", ["-eo", "pid=,ppid=,args="], { - 删除本地文件bin/cdp-cast-worker.mjs:6785
rmSync(CAST_STATE_FILE, { force: true }); - 写入本地文件bin/cdp-cast-worker.mjs:7068
mkdirSync(STATE_DIR, { recursive: true }); - 写入本地文件bin/cdp-cast-worker.mjs:7069
writeFileSync(CAST_STATE_FILE, JSON.stringify({ - 删除本地文件bin/cdp-cast-worker.mjs:7084
if (JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(CAST_STATE_FILE, "utf8"))).pid === process.pid) rmSync(CAST_STATE_FILE, { for - 读取本地文件runtime/ego-linux/src/agent-identity.mjs:36
if (existsSync(pin)) { - 读取本地文件runtime/ego-linux/src/agent-identity.mjs:38
const name = readFileSync(pin, "utf8").trim(); - 读取本地文件runtime/ego-linux/src/chrome.mjs:145
await access(path, constants.F_OK); - 读取本地文件runtime/ego-linux/src/chrome.mjs:154
await access(path, constants.F_OK); - 执行外部命令来自配置/变量runtime/ego-linux/src/chrome.mjs:181
const child = spawn(cmd, [name], { stdio: ["ignore", "pipe", "ignore"] }); - 发起网络请求runtime/ego-linux/src/chrome.mjs:198
const response = await fetch(`http://127.0.0.1:${port}/json/version`, { - 读取本地文件runtime/ego-linux/src/chrome.mjs:211
return JSON.parse(await readFile(BROWSER_STATE_FILE, "utf8")); - 写入本地文件runtime/ego-linux/src/chrome.mjs:218
await mkdir(STATE_DIR, { recursive: true }); - 写入本地文件runtime/ego-linux/src/chrome.mjs:219
await writeFile(BROWSER_STATE_FILE, JSON.stringify(state, null, 2)); - 读取本地文件runtime/ego-linux/src/chrome.mjs:238
const [line] = (await readFile(path, "utf8")).split("\n"); - 读取本地文件runtime/ego-linux/src/chrome.mjs:269
const prefs = JSON.parse(await readFile(path, "utf8")); - 写入本地文件runtime/ego-linux/src/chrome.mjs:287
if (changed) await writeFile(path, JSON.stringify(prefs)); - 读取本地文件runtime/ego-linux/src/chrome.mjs:316
const prefs = JSON.parse(await readFile(path, "utf8")); - 写入本地文件runtime/ego-linux/src/chrome.mjs:319
await writeFile(path, JSON.stringify(prefs)); - 删除本地文件runtime/ego-linux/src/chrome.mjs:430
rm(join(profileDir, name), { force: true }), - 执行外部命令来自配置/变量runtime/ego-linux/src/chrome.mjs:543
const child = spawn( - 执行外部命令来自配置/变量runtime/ego-linux/src/chrome.mjs:608
const child = spawn(binary, args, { - 写入本地文件runtime/ego-linux/src/chrome.mjs:645
await mkdir(PROFILE_DIR, { recursive: true }); - 删除本地文件runtime/ego-linux/src/chrome.mjs:703
await rm(join(PROFILE_DIR, PORT_FILE), { force: true }); - 删除本地文件runtime/ego-linux/src/chrome.mjs:854
await rm(BROWSER_STATE_FILE, { force: true }); - 执行外部命令来自配置/变量runtime/ego-linux/src/desktop.mjs:51
const child = spawn(command, args, { stdio: "ignore" }); - 写入本地文件runtime/ego-linux/src/desktop.mjs:60
await mkdir(applications, { recursive: true }); - 写入本地文件runtime/ego-linux/src/desktop.mjs:61
await mkdir(icons, { recursive: true }); - 写入本地文件runtime/ego-linux/src/desktop.mjs:64
await copyFile(fileURLToPath(ICON_SOURCE), iconPath); - 写入本地文件runtime/ego-linux/src/desktop.mjs:67
await writeFile(entryPath, desktopEntry(fileURLToPath(LAUNCHER)), { mode: 0o755 }); - 发起网络请求runtime/ego-linux/src/tabs.mjs:19
const response = await fetch(`http://127.0.0.1:${port}/json/list`, { - 读取本地文件runtime/ego-linux/src/task-spaces.mjs:76
const parsed = JSON.parse(await readFile(TASK_SPACE_FILE, "utf8")); - 写入本地文件runtime/ego-linux/src/task-spaces.mjs:84
await mkdir(STATE_DIR, { recursive: true }); - 写入本地文件runtime/ego-linux/src/task-spaces.mjs:85
await writeFile(TASK_SPACE_FILE, JSON.stringify(state, null, 2)); - 读取本地文件runtime/ego-linux/src/task-spaces.mjs:387
const state = JSON.parse(readFileSync(TASK_SPACE_FILE, "utf8"));
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cosmokit@deepseek-ai/dsh-tools@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-store@deepseek-ai/dsh-client-ui-settings-plugins@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-settings给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 实时视频与截图回传
- 登录态导入
展开完整介绍(130 字)↓
DSH 浏览器插件(原 dsh-ego-browser):让 Agent 通过 CDP 接入真实 Chrome —— 远程目标序列与激活、实时视频/截图回传、登录导入。Remote-CDP agent browser for DeepSeek Harness.
最近更新
在 GitHub 查看全部 →v0.18.02026-10-22
· v0.18.0
· 新增
· render worker captureWithinBudget 实现(0.2.0 线修复真机阻断):bin/cdp-render-worker.mjs 的 capture 此前调用一个从未定义的函数(真机链路 capture 必挂,且被门控探针掩盖)。现实现 JPEG 质量阶梯(72 起步、步长 12、下限 24,超预算如实上报 overBudget)+ scale.maxWidth 几何降采样 + PNG/无预算单发路径;data 以 base64 回传供 JEV 判定链消费。真机实测:Chrome/153 实连,预算一次达标(attempt 1, q=72)。
· client 半 noImplicitAny 开启:tsconfig.client.json 升 noImplicitAny: true,401 处 implicit-any 全部显式化(scripts/fix-implicit-any.mjs 位置精确 codemod + 手工收尾);类型注解零运行时影响,client-input.test.ts 源码正则同步。
· 门控探针真机启用:remote CDP 探针(4/4)与 render 探针(1/1)对本机真实 Chrome/153 实跑通过——discovery→attach→inspect→input→binding 回环 + 截图预算链路。cli-probe 与 login-import e2e 绑 vendored Linux 运行时,维持门控(本机 N/A)。
· 变更
· peerDependencies 换代(替换式):六项 @deepseek-ai/dsh-client-locale / dsh-client-store / dsh-client-ui-settings-plugins / dsh-client-ui-slots / dsh-settings / dsh-tools 由 >=0.1.7-rc.1 =0.2.0-rc.1 =0.2.0-rc.1 =0.2.0-rc.1 <0.2.1-0,typecheck 据此解析 0.2.0 宿主类型——「全绿」证明的是 0.2.0 兼容而非 0.1.7 残留。
· 零代码依据:0.2.0 的 composer/forkSession 为向后兼容签名扩展,本插件经 ctx.get('conversation').input 门面投递选区(不调 submit()、不 override 契约),不在影响面内。
· README 徽章/版本兼容矩阵/适配说明改写为 0.2.0 口径。
· 验证
· 双 tsconfig typecheck + build + vitest(656 passed / 11 skipped)针对 dsh-tools@0.2.0-rc.1 全绿;npm ls 无 invalid / peer 冲突。
· 其他
· dsh-plugin.json 版本 0.17.0 → 0.18.0(该字段此前落后于 package.json,本版一并拉齐)。
· 依赖树由 npmmirror 全量重建,package-lock.json 重新生成提交。
---
_Release backfilled on 2026-10-22 from git tag + CHANGELOG history._
v1.2.22026-10-22
· v1.2.2
· hosts: 追加 0.2.1-rc.2 声明面枚举(34/34 @deepseek-ai/* 宿主 peer 逐包 npm 实测 rc.2 在版;本插件不触 ctx.web provider 与 conversation.input.model.label 槽,代码零改动,纯元数据发版;农场 rc.2 格挂账待命)
⚠️ 0.18.10 已被 registry 烧号:npmjs 受理后长期停留在 staged 状态从未提交(读路径 404、重发 EPUBLISHCONFLICT: previously staged),版本号永久不可安装——安装请用 0.18.11(内容 = 0.18.10 全部 + dock 注册诊断 __dshBrowserCdpDeco + locale 补齐 + 点选卡片正则放宽)。0.18.5 同前例。
---
_Release backfilled on 2026-10-22 from git tag + CHANGELOG history._
v1.2.32026-10-22
· v1.2.3
修复(1.2.3):issue #1 + #2 — login_import 的 schema 字段、导入错误类型标注、family-host 的 uSES 崩溃兜底
_这个版本没找到 CHANGELOG 段落,说明由 tag 提交信息自动生成。_
---
_2026-10-22 依据 git tag 和 CHANGELOG 历史回填的发布记录。_
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-browser-cdp — 看得见的智能体浏览器(CDP 接入)
= 0.12.2 (optional)">
= 22">
仓库:github.com/drscrewdriver/dsh-browser-cdp|版本历史见 CHANGELOG.md
版本兼容矩阵
| 依赖 | 最低版本 | 推荐版本 | 说明 |
| DSH (DeepSeek Harness) | 0.1.7-rc.1 | 0.1.7-rc.1 ~ 0.2.1-rc.4(精确枚举) | 精确枚举 8 个 rc 版本,engines 与全部 dsh peer 同串(无区间/caret):0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4;宿主对插件 API 向后兼容(0.2.0 的 composer/forkSession 为签名扩展,插件仅作调用方,免改)。声明式设置面自 0.1.7 引入(Config .volatile() 字段自动生成设置表单),0.2.0/0.2.1 延续该形态。
展开完整文档(README 剩余 1 节)
dsh-browser-cdp — 看得见的智能体浏览器(CDP 接入)
= 0.12.2 (optional)">
= 22">
仓库:github.com/drscrewdriver/dsh-browser-cdp|版本历史见 CHANGELOG.md
版本兼容矩阵
| 依赖 | 最低版本 | 推荐版本 | 说明 |
| DSH (DeepSeek Harness) | 0.1.7-rc.1 | 0.1.7-rc.1 ~ 0.2.1-rc.4(精确枚举) | 精确枚举 8 个 rc 版本,engines 与全部 dsh peer 同串(无区间/caret):0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4;宿主对插件 API 向后兼容(0.2.0 的 composer/forkSession 为签名扩展,插件仅作调用方,免改)。声明式设置面自 0.1.7 引入(Config .volatile() 字段自动生成设置表单),0.2.0/0.2.1 延续该形态。DSH 0.1.2-rc.1 ~ 0.1.5-rc.3 请使用 0.17.x 版本线,0.1.0-rc.x / 0.1.1-rc.x 请使用 v0.8.0 及更早版本 |
| dsh-better-sidebar | 0.12.2(可选) | ≥ 0.17.1 | 未安装时自动回退浮动观察球;< 0.12.2 可运行但外部链接拦截(urlTarget)静默降级 |
| Node.js | 22 | — | harness 环境自带 |
DSH 全版本适配说明:本版本(1.2.1 起;1.2.2 起追加 0.2.1-rc.2)面向 DSH 0.1.7-rc.1 ~ 0.2.1-rc.4(精确枚举 8 个 rc):相对 0.1.7 线(v0.17.x)零代码改动,纯依赖/元数据换代——0.2.0 宿主对插件 API 向后兼容,本插件经 ctx.get('conversation').input 门面投递选区(不调 submit()、不 override),不在 0.2.0 变更影响面内。声明式设置面自 0.1.7 引入:插件不再注册任何设置节(ctx.settings 注册类 API 已被宿主删除),改为在 Config schema 上以 .volatile() 标记可配置字段,由宿主设置页自动生成表单;volatile 字段变更通过 loader/volatile-update 事件热生效,无需重载插件。核心宿主 API(defineTool、ctx.tools.register、ctx.subprocess.spawn、ctx.webServer.register、ctx.inject、ModuleLoader CJS factory、cordis.patch.yml)沿用 0.1.2 以来形态。DSH 0.1.2-rc.1 ~ 0.1.5-rc.3 请使用 0.17.x 版本线。
dsh-better-sidebar 适配说明:本插件通过 ctx.betterSidebar 服务(try-catch 防御性获取)注册侧边栏 Tab 并监听外部链接。关键 API 引入版本:
| API | 本插件用法 | better-sidebar 引入版本 |
| registerTab() / openTab() / ctx.betterSidebar | Tab 注册 + 打开 | v0.9.0+ |
| TabDescriptor.single | 单实例 Tab | v0.9.0+ |
| TabDescriptor.urlTarget | 外部链接拦截 | v0.12.2+(低于此版本链接拦截静默失效) |
DSH 版本支持详情:v0.8.2 → v0.8.3 主要变更:合并 6 个社区 PR(root/xvfb/macOS headless 适配、rc.1 兼容、Windows 稳定性),修复无认证 /api/bcdp/ 路由安全漏洞、无 dsh-better-sidebar 宿主 client 启动失败(#29)、Windows 冷启动回归(#22 引入的 Xvfb 误判),并修复 gateway 设置白名单缺 runtimeArgs/chromeArgs。适配点:client 运行时改名(@deepseek-ai/dsh-client-store)、client 模块注册 id 与装载行名按声明包名、dsh.client.inject 仅声明真实模块图行、webServer 以嵌套注入交付(可选服务),并同步侧边栏 Tab(dsh-better-sidebar)模式。
侧边栏支持(dsh-better-sidebar):宿主装了 dsh-better-sidebar(推荐 ≥ v0.12.2)时,实时观察窗注册为侧边栏原生 Tab——「Agent 浏览器」出现在侧边栏「+」菜单里,点击即打开,并随侧边栏抽屉固定展示;agent 首次调用 bcdp_ 工具时自动打开该 Tab(v0.8.5 起按调用会话作用域打开,多会话不再弹错位置)。没装 dsh-better-sidebar 时自动回退为右下角浮动观察球(#dsh-ego-fab)模式。两种形态共用同一套 SSE 实时推流 / 点击 / 输入 / 下载捕获能力。观察窗还提供一个「弹出窗口」按钮:无头(headless)运行的 agent 浏览器可一键替换为同 Profile 的有头窗口(标签页保留),方便手动接管。
登录态导入(v0.8.5 新增):工具 bcdp_login_import,把你日常 Chrome/Edge/Brave 里的登录 cookie 按域名复制进 agent 浏览器(真实二进制无头启动 + CDP 透传读取,兼容 Chrome 127+ 的 App-Bound Encryption,不做离线解密;源浏览器运行中可选择优雅关闭后导入,窗口下次启动自动恢复)。cookie 值不出现在任何日志与输出中;导入前自动备份源 cookie 库,异常清空自动还原。配合默认的磁盘持久化 Profile,导入的登录态跨重启永久保留。
一款 CDP 浏览器代理:内置 ego 兼容运行时(供 AI Agent 用的 Chromium,来源与许可逐文件见 THIRD_PARTY_NOTICES.md)接入 DeepSeek Harness,以 34 个结构化 bcdp_ 工具驱动浏览器,并配一套实时观察前端口——agent 后台操作网页时,你能像看直播一样看到它正在浏览的每个页面,还能直接操作它。
一点私藏的独特之处(self-observation):agent 用的就是这一个 Chromium——连它操作 DSH 自身(管理会话、任务看板、调设置)时,观察窗也实时显示、你能随时接手。不只是"看得见 agent 在网页上干活",连 agent 操作 DSH 界面本身都是全程可见、可掌控的。
开箱即用:插件包内置 ego 兼容运行时(runtime/,第三方 MIT,来源见 THIRD_PARTY_NOTICES.md)——无需额外浏览器、无需手动构建,--no-sandbox wrapper 随包自带,root / Docker / 无显示器一键跑。
我们的真正优势(不是口号,是能对照代码和竞品核实的能力)
同为把 ego 浏览器接进 DSH 的方案,市面上已有同类插件只做了 3 个工具——一个 run 脚本、一个 help 指南、一个 status 体检,浏览器仍是后台黑盒。本插件走的是另一条路:把黑盒打开,并且一上来就把"看"和"控"的能力做到位。
| 能力 | 本插件(本仓库) | 同类插件 |
| 结构化工具数 | 34 个,职责单一、可确定性调用 | 3 个(run/help/status) |
| 实时观察窗(CDP JPEG / FFmpeg H.264 双后端 + 标签条 + 历史抽屉) | ✅ 有 | ❌ 无 |
| 监控窗鼠标直接操作真实浏览器(点击/拖拽/滚动回传 CDP) | ✅ 有 | ❌ 无 |
| 观察窗点选元素 → 捕获并引用进对话输入框([CDP-PICKS] 结构化块,人工指认目标) | ✅ 有 | ❌ 无 |
| worker 单实例守卫 + 崩溃/重复自愈 | ✅ 有 | ❌ 无 |
| 下载捕获 bcdp_download / 人机验证检测 bcdp_captcha/bcdp_page_info | ✅ 有 | ❌ 无 |
| 平台自适应(Linux/macOS/Windows 自动探测 + root/无头/--no-sandbox 兜底) | ✅ 全平台 | 仅 Windows 预览宿主,需手动配 |
| 登录态落盘持久化 bcdp_auth_flush | ✅ 有 | ⚠️ 仅文档级说明 |
关键差异三条:
- 看得到:别家是"跑完告诉你结果"的黑盒;我们实时推流,你看着 agent 操作,卡在验证码/走岔立刻发现。
- 控得住:别家只读;我们监控窗直接驱动同一个 agent 浏览器,需要时你亲手接管(缩放/拖拽/点击),不必打断 agent 重来。
- 指得准:你在观察窗里点选页面元素,插件捕获其 backendNodeId/tag/id 与语义描述,以 [CDP-PICKS] 结构化块并入对话输入框草稿(同页多元素自动合并编号)——agent 拿到的是精确目标,不用猜"你说的按钮是哪个"。
以上对比基于公开可见的可核实事实:本仓库代码(bin/cdp-cast-worker.mjs 实时推流 + CDP 输入回传、lib/index.js 34 个注册工具、lib/cast-server.js host 桥接、lib/client.js 的 deliverPickToConversation 点选引用)与同类插件公开可见的源码/README。此文档不含对任何他人的贬低——我们只陈述自己多实现并验证了哪些能力。
相对内置运行时本体(一个无头 CLI),我们多做了这些(都可对照本仓库代码核实):
| 能力 | 说明(对应代码) |
| 观察窗前端口 | 内置运行时本体是无头 CLI(只有 heredoc 脚本 + 文本输出);我们在其上加了 SSE 实时推流 + 标签条 + 历史抽屉 + 监控窗鼠标直操 + 点选元素引用进输入框(bin/cdp-cast-worker.mjs、lib/cast-server.js、lib/client.js 的 createPickControl/deliverPickToConversation),让"看"、"控"、"指"成为一等能力 |
| 开箱即用 + 跨平台自足 | resolveEgoEnv 自动探测 Chrome/Edge/Brave,内置 --no-sandbox wrapper,root / Docker / 无显示器免配置(lib/index.js);不必先装一个 GUI 宿主 |
| 健壮性层 | 冷启动自动重试(只重试 CDP 瞬态,不吞真错)、worker 单实例守卫 + 崩溃自动重启、插件卸载 fire-and-forget 不阻塞宿主退出、前端帧缓存上限(withWarmupRetry / makeEnsureWorker / frameCache) |
| 运维型工具 | bcdp_doctor(环境体检)、bcdp_captcha(人机验证探测)、bcdp_auth_flush(登录落盘)、bcdp_login_import(系统浏览器登录态导入)、bcdp_http(浏览器上下文请求)等,是原生 CLI helper 没有的一层 |
| self-observation | 智能体操作 DSH 自身界面时同样实时可见、可接手 |
我们不声称媲美原生 macOS App 的内核级快照或多窗口体验;本仓库解决的是「把同一套浏览器能力带进 DSH + Linux/WSL + 看得见」这件事。
它解决什么问题
通用浏览器不是为智能体设计的,而 Web 上大量交互(登录态、验证码、动态渲染、表单、需真人会话的站点)只有真浏览器能面对——这正是 ego 浏览器 「让智能体用你已登录的浏览器,而不打扰你」 的设计初衷。
本插件把它接进 DSH,并把最痛的一点——你看不见智能体在干什么、也插不上手——用一套观察窗解决:
🌐 小球一点看直播;🟦 标签条切换/关闭;🕘 历史抽屉回看;🔍 缩放拖拽;🖱️ 监控窗直接接管真实浏览器。一句话:让智能体在浏览器里干活,你在旁边既看得见、又随时能接手。
几个常见的上手场景
- 文献 / 数据抓取:让智能体登录知网 / 谷歌学术翻页收集,你在观察窗看着它滚动、点下一页、下载 PDF,中途卡住立刻能发现。
- 表单与登录:智能体填表到一半,观察窗弹出验证码——你直接接管把验证码点了,再交还给智能体继续。
- QA / 冒烟测试:让智能体在自己产品上点一圈,观察窗等于一台「会说话的录屏」,顺手还能回看历史轨迹。
- 看智能体操作 DSH 自身(self-observation):智能体在管理会话 / 调设置时,观察窗同样全程可见、可接手。
前置条件
| 要求 | 说明 |
| Node ≥ 22 | harness 环境自带 |
| 任意 Chrome / Chromium / Brave / Edge | 自动发现,或 EGO_LINUX_CHROME 指定;root 下用自带 wrapper |
| DSH + dshx | 插件装载机制 |
| 带图形界面的 DSH Web(观察窗) | headless 会话仍可用 bcdp_ 工具,仅无观察窗 |
安装
方式一:GitHub 直装(推荐)
dsh plugin --profile web add github:drscrewdriver/dsh-browser-cdp
也可锁定到某个 commit / tag:
dsh plugin --profile web add github:drscrewdriver/dsh-browser-cdp#v0.9.0
github: 安装由 pnpm 经 codeload.github.com 拉取仓库 tarball,无需向 npm registry 发包;直接使用仓库中预构建入库的 lib/(files 仅含 lib/、bin/、runtime/、cordis.patch.yml、dsh-plugin.json),因此不触发任何构建脚本,也不需要 devDependencies。host 入口 lib/index.js、客户端 lib/client.js、worker bin/cdp-cast-worker.mjs 均随仓库分发。
方式二:本地 tarball / git URL
dshx install dsh-browser-cdp <dsh-browser-cdp.tgz> # tarball 或 git URL 均可 dshx list # 应显示:[on] dsh-browser-cdp
包名迁移说明:本插件包名是 dsh-browser-cdp(曾用名 dsh-ego-browser、别名 @dsh-external/ego-browser)。DSH Desktop 2.0.5 起增加了「profile 依赖名 == 包实际 name」的一致性校验,若 profile 仍用旧名引用,启动会挂进恢复模式。升级后请把 profile 的 package.json 依赖键 和 dsh.profile.bundles 条目两处都改为 dsh-browser-cdp:
- "dsh-ego-browser": "<旧 github 来源>",
+ "dsh-browser-cdp": "github:drscrewdriver/dsh-browser-cdp",
- "dsh-ego-browser",
+ "dsh-browser-cdp",
观察窗设置中可选 captureBackend=auto|cdp|ffmpeg(默认 auto,当前解析为 CDP)、画质档位、CDP FPS/JPEG 质量/最大宽度,以及 FFmpeg FPS/最大宽度/码率/编码器/自定义路径。插件先检测自定义路径、系统 PATH 和托管缓存;GitHub 下载可用 githubMirror 替换 https://github.com,例如 https://gh-proxy.com/github.com。FFmpeg 码率范围为 500-20000 kbps,低/平衡/高档默认 2000/4000/8000 kbps。
无需宿主侧任何配置:resolveEgoEnv 自动探测 root / 无显示器并兜底。观察窗 host 路由(/api/bcdp/spaces 等)仅在有 HTTP server 时注册,headless 是安全 no-op。
连接方式:CDP 端点 / 本机 ego CLI
连接目标直接在设置里增删改(0.18.1 起,与 0.17.x 线同源):设置卡有两处入口——「起子插件设置」family 区的本插件 tab(装了 dsh-thinking-levels 时),以及插件详情页的设置卡(plugins.bundle.config,不装 thinking-levels 时的独立入口);两处渲染同一份组件。每行支持:启用开关、激活、标签/端点/CLI 路径就位编辑、逐行探测、上移/下移、删除(删激活行会同时清空激活,绝不顺延);底部内联表单直接添加 CDP 端点或本机 ego CLI(第二条本机 CLI 会被拒绝)。写回落在活动 profile 的 cordis.patch.yml,跨重启保留。0.18.1 之前,连接序列只能在 profile 的 cordis.patch.yml links 中手工维护。
设置面板里的连接列表是有序的——顺序即优先级,被激活的那一项驱动每一次 bcdp_ 调用。列表元素有两种类型:
| 类型 | 它做什么 | 约束 |
| CDP 端点 | 把一个已开调试端口的浏览器地址交给内置运行时(注入 EGO_LINUX_CDP_URL),运行时附着上去 | 可多条 |
| 本机 ego CLI | 由本机 ego CLI 自己驱动它本机的 ego 浏览器——不注入任何端点 | 仅限本机,全局至多一条 |
为什么需要第二种:注入用的 EGO_LINUX_CDP_URL 是内置运行时(Linux 线)的私有契约,macOS 上用 ego lite app 附带的那个原生 ego-browser 根本不读它。所以在只有"CDP 端点"这一种类型时,macOS 用户无法用上自己那个已登录的 ego——只能连远端端口,或让内置运行时另起一个 stock Chromium。本机 CLI 类型补的就是这条路。
解析顺序(cliPath 留空时):显式路径 → PATH 上的 ego-browser → macOS app 包内 helper(/Applications/ego lite.app/…/Helpers/ego-browser)→ 插件内置运行时。
两个默认值值得知道:
- useSdkPath 默认关闭:关闭时用 CLI 自带的 harness(配套的那套);打开后,探测与每一次工具执行都会注入 --sdk-path <插件内置 harness>。这一点很关键:只在探测时带、执行时不带,脚本首行就会崩 ReferenceError: taskSpaces is not defined(1.2.4 修复的 P0)。若 CLI 不认这个旗标,探测会记一条提示并自动重试一次不带它;此时只有 CLI 自带的 harness 确实暴露 taskSpaces / browser 门面才算就绪,否则显式判失败并给出缺失的门面名——不再假阳性。
- 就绪探测是 nodejs 通道上的 facade 探针(与干活同一条通道):只检查注入后的 taskSpaces / browser / page 全局,不启动也不等待浏览器,实测约 300 ms;bcdp_status 走同一判据。
双 API 面共存风险
本插件通过注入 SDK 使用 taskSpaces.(插件侧 API),而 ego-browser skill 脚本使用原生 taskSpace()(app 侧 API)。二者驱动的是同一个 ego,task space 也是共享的:同时用插件工具和该 skill 脚本操作同一空间,可能互相抢控制权。建议二选一,以插件为准;确需跑该 skill 脚本时,先停用本插件的 ego-cli link。
与其它开关的关系:
- cdpMode:auto 按激活项(两种类型都行);remote 只接受 CDP 端点,激活项是本机 CLI 时会显式报 mode-kind-mismatch;local 走受管本地启动器。
- remoteEnabled(远端总开关)只管远端 CDP,不影响本机 CLI 连接。
- allowLocalFallback / localHeadless / localUserDataDir 只作用于 CDP 端点的回落与受管启动。
- allowProbeColdStart(默认关):就绪探测绝不拉起 ego 浏览器——--status 证明浏览器已在跑才做 heredoc 验证,否则跳过,浏览器推迟到首个真实 bcdp_ 调用才启动(插件挂载/改设置不再弹浏览器窗口)。打开 = 挂载探测预先验证 SDK harness(可能当场弹出浏览器窗口)。
bcdp_doctor 会报告当前序列类型计数、CLI 实际解析到的路径与形态,以及(若同机装了旧版 dsh-ego-browser)一条共存提示。
Lightpanda 后端(可选引擎,beta)
Lightpanda 是面向 AI 的无头文本渲染浏览器(AGPL-3.0,本插件仅作 CDP 客户端使用)。它对插件来说就是一个普通 CDP 端点:设置面板添加并激活后,probe 会从 /json/version 的 Browser: Lightpanda/… 自动识别引擎,badge 与 bcdp_doctor 显示 engine,能力降级自动生效(无需任何配置)。
分系统安装
| 系统 | 方式 |
| Linux(建议本机直装测试) | curl -fsSL https://pkg.lightpanda.io/install.sh \| bash(装入 ~/.local/bin,确保 PATH 含它)→ lightpanda serve --host 127.0.0.1 --port 9222 |
| macOS(建议本机直装测试) | 同上一键脚本(aarch64)→ 同 serve 命令 |
| Windows(内置方案 = WSL2,无原生版) | 管理员 PowerShell wsl --install → 重启 → wsl --install -d Ubuntu → WSL 内同 Linux 一键脚本 → WSL 内 lightpanda serve …;WSL2 自动把端口映射到 Windows localhost(二进制只存在于 WSL 内,宿主侧没有文件,属预期) |
| Docker(备选) | docker run -d --name lightpanda -p 127.0.0.1:9222:9222 lightpanda/browser:nightly |
装好后在本插件设置面板添加 CDP 端点 http://127.0.0.1:9222 并激活即可。可选:export LIGHTPANDA_DISABLE_TELEMETRY=true 关遥测。
能力降级矩阵(识别后自动生效)
| 能力 | Lightpanda 上的行为 |
| 导航 / JS / 点击 / 填表 / 键鼠 / 快照(AX 树)/ HTTP / 登录导入 / 等待族 | 无损可用 |
| bcdp_screenshot | PNG 文本级渲染(无像素引擎,视觉保真低于 Chrome;runtime 本就发 png,零改动兼容) |
| jev 视觉链(bcdp_jev_frame 等) | 投影自动改发 PNG;输出带 engine / rendering: text 元数据 |
| 观察窗实时画面 | 关闭——无 screencast 且无窗口,worker 不启动采集循环,面板显示降级提示 |
| 面板点选(inspect 路径) | 关闭——无 Overlay 域 / bindingCalled 事件,返回 engine-unsupported(i18n);坐标回退路径保留 |
| bcdp_download | 关闭——无 Page 下载事件,调用即返回结构化错误(不会挂起超时) |
| bcdp_auth_flush | 返回 degraded: true——引擎无 Storage.flushCookies,cookie 仅随引擎进程存活 |
识别失败的端点(engine: unknown)保持全能力——误判只应「少给」,不应「错给」。
工具清单(34 个,前缀 bcdp_,完整索引见 bcdp_help)
| 类别 | 工具 |
| 任务空间 | bcdp_space(open / close / list / handoff / takeover) bcdp_status |
| 页面读取 | bcdp_snapshot(语义树) bcdp_page_info bcdp_read_element |
| 导航/标签/等待 | bcdp_navigate(复用 tab / reload) bcdp_tab(list/current/new/switch/close) bcdp_wait(duration / selector / url / response / load / function / request / event) |
| 交互 | bcdp_click bcdp_fill bcdp_hover bcdp_drag bcdp_select bcdp_check bcdp_key(type/insert) bcdp_scroll |
| 执行/调试 | bcdp_js(页面求值) bcdp_cdp(原始 CDP) bcdp_script(多步脚本;verbatim:true = 原 bcdp_cli 的裸 heredoc) |
| 输出 | bcdp_screenshot bcdp_download bcdp_upload |
| 会话/安全 | bcdp_auth_flush(登录落盘) bcdp_login_import(跨浏览器登录态导入) bcdp_captcha bcdp_dialog |
| 元工具 | bcdp_help bcdp_doctor bcdp_http |
| 评审(JEV/Laya,外接判定服务可选) | bcdp_jev_status bcdp_jev_frame bcdp_jev_ask bcdp_jev_run bcdp_jev_attempt |
1.5.0 迁移表(旧名 → 新调用)
wait / space / escape 三族已按 op 合并,旧工具名移除(不保留别名——别名同样吃 schema 预算,等于没省)。其余工具名不变。
| 旧调用 | 新调用 |
| bcdp_wait {ms} | bcdp_wait {op:"duration", ms} |
| bcdp_wait_for_selector {selector, state, timeout} | bcdp_wait {op:"selector", selector, state, timeout} |
| bcdp_wait_for_url {pattern, timeout} | bcdp_wait {op:"url", pattern, timeout} |
| bcdp_wait_for_response {url, body, timeout} | bcdp_wait {op:"response", url, body, timeout} |
| bcdp_wait_page {op:"load"\|"function"\|"request"\|"event", …} | bcdp_wait 同名 op(op 取值不变,直接搬家) |
| bcdp_space_open {name} | bcdp_space {op:"open", name} |
| bcdp_space_close {name, keep} | bcdp_space {op:"close", name, keep} |
| bcdp_cli {script} | bcdp_script {script, verbatim:true} |
op 合并后各 op 的必需参数写在工具描述里;缺参会返回带 op 名的 reason(如 bcdp_wait: selector is required for op=selector),不会静默降级。
工具注入分级(懒注册 / 分级 / 锁定)
34 个工具全量注册时,每个会话每一轮都要为它们的 schema 付费(挂载级实测 description + parameters 序列化 = 24,631 字符 ≈ 7.1K token,1.7.0;1.5.0 曾测为 22,224,增量主要来自分级文档与工具描述互斥引导)。而不碰浏览器的会话一个也用不上。插件因此把「谁能出现在模型面前」收成一处策略(src/toolTiers.ts),分八批注入。
8 批 ≠ 8 个开关。批次是注入时机,开关是用户面(§9.3「开关与批次解耦」)。当前 4 个开关管 8 批:toolBrowse 管 act+observe+wait 三批,按波次放行;toolExtended 管 power+session;toolEscape、toolJudge 各管一批。所以「toolBrowse = 19 个」指的是并集上限,实际第一波只放 13 个。
| 批次 | 成员 | schema | 何时出现 |
| core 常驻(1) | bcdp_status | 401 | 永远在场,不可封印——它是 lazy 唯一的解锁入口 |
| act 动手(13) | space / navigate / click / fill / key / hover / scroll / select / check / drag / tab / help / doctor | 8,762 | lazy 下首次活动放第一波 |
| observe 观察(5) | screenshot / page_info / read_element / js / snapshot | 3,109 | 第二次活动 |
| wait 等待(1) | bcdp_wait(duration / selector / url / response / load / function / request / event) | 1,976 | 第三次活动 |
| power 重量级(5) | raw CDP / http / upload / download / dialog | 3,692 | 首次活动(同 toolExtended) |
| session 登录态(3) | captcha / auth_flush / login_import | 1,864 | 首次活动(同 toolExtended) |
| escape 逃生舱(1) | bcdp_script(verbatim:true = 原 cli 的裸 heredoc) | 1,484 | 首次活动(1.7.0 起不再吃活动闸豁免,见下) |
| judge 评审链(5) | bcdp_jev_ | 3,343 | 除解锁外,还要求配了 JEV 或 Laya Key |
| 合计 | 34 | 24,631 | — |
表内 schema 与合计均为 1.7.0 挂载级实测(tests/tier-mount.test.ts 的度量用例打印),八批求和 = 24,631,与全量档逐字对齐。
设置项在插件设置页最上方的「工具封印」分块(全部 volatile,翻完当场生效,不重启插件)——一个开关管一组批次,形状照 dsh-prime-memory 的「工具封印」:
- toolInjection lazy(默认:只常驻 bcdp_status——它是唯一的解锁入口,其余首次 bcdp_ 调用后按开关注入)/ always(挂载即全量,历史行为)。
- toolBrowse(act + observe + wait = 19)/ toolExtended(power + session = 8)/ toolEscape(script = 1)/ toolJudge(5):开 = 开放注册,关 = 该批从模型工具列表移除。四组各自独立,封一组不波及别组;core 级不给开关(封了就没了活动入口)。
1.7.0 起 escape 不再吃活动闸的例外:bcdp_script 仍是唯一能在一次调用里干完任意多步任务的入口(page / browser / taskSpaces / fetch / cdp 全预置),但它不再常驻——先调一次常驻的 bcdp_status 即可拿到它。豁免的代价是它单独占 ≈1,700 字符/轮(比收窄后的整个常驻面还大四轮),而 lazy 会话里模型本来就要先调 status。如果你需要「冷启动直接写脚本」的旧行为,设 toolInjection = always(或留在 1.6.x)。
行为要点:
- 封印 = 反注册(工具从模型的工具列表里消失),不是执行期拒绝。解锁信号挂在 bcdp_ 调用的公共出口上,工具体不知道分级存在;bcdp_status / bcdp_help / bcdp_doctor 自身也算活动信号(读了指南就得有工具可用)——但后两者已属 act 批,冷启动下只能靠 bcdp_status 触发。
- 闲置自动收回:lazy + idleTimeoutMin > 0 时,空闲回收把后台浏览器停掉的同一刻收回扩展面;下次活动重新铺开。always 模式不受影响。
- legacyEgoToolNames 的 ego_ 别名跟随其 bcdp_ 本体的级别。
- 当前注入面写在 bcdp_doctor 报告首行(例:tool injection: mode=lazy browse=open extended=open escape=open judge=open judgeKey=none active=yes → live 28 / sealed 6,即一次 bcdp_status + 一次 doctor 之后的面),不用猜。注意 doctor 本身已属 act 批,要先调 bcdp_status 才能拿到它;冷启动时模型面上只有 bcdp_status(live 1 / sealed 33)。
- 默认档下不碰浏览器的会话只有 1 个工具在场(bcdp_status = 401 字符 ≈115 tok,较 1.5.0 的 4,978 字符 −91.9%);它是解锁入口,调用即按批次铺开其余工具。要回到历史行为把 toolInjection 改成 always(34 件全在场需再配一个评审 Key)。
- 各档实测注入面(挂载级口径,1.7.0;序列化工具的 name + description + parameters 求和,token 按 3.48 字符/token 折算):默认 1 个 / 401 字符 ≈115 tok → 首次活动(act 批 + power + session + escape)23 个 / 16,203 ≈4,660 tok → 第 2 波(+observe)与第 3 波(+wait)后 29 个 / 21,288 ≈6,120 tok → 全量(always + 评审 Key)34 个 / 24,631 ≈7,080 tok。这些数字由 tests/tier-mount.test.ts 的度量用例当场打印,不靠估算。
- 五个字段都带 schema .default():volatile 字段的表单描述符直接拿 schema 默认值,不写默认会出现「面板显示 A、运行时用 B」。
取舍提醒:窄工具(一个动作一个工具)是模型写对参数、少重试的护栏。1.5.0 只合并了 wait / space / escape 三族同源冗余(41 → 34,省 1,869 字符 schema):五个 wait 共用同一 preamble 与 timeout 语义、三个 space 本就是同一个 op 工具的碎片、cli/script 是同一 heredoc 运行时。代价是这三族的旧工具名不再存在(见上方迁移表);交互原子(click/fill/key…)不合并,因为它们是模型写对参数的护栏。
任务空间命名与并发隔离(1.6.0)
默认就是会话隔离:不传 space 的调用落在本会话自己的任务空间 dsh-agent-<会话id前8位>,两个会话永远不会共用或抢占同一个浏览上下文。只有显式写 space: 'default'(或 bcdp_space op=open name='default')才会用那个跨会话共享的具名槽。
| 你怎么写 | 落到哪个空间 |
| 省略 space(默认,defaultSpace 未设 = :auto) | dsh-agent-<会话id前8位>,每会话独立 |
| space: 'default' / op=open name='default' | 共享具名槽 default(此后该会话固定用它) |
| defaultSpace: 'default'(配置项改成具名) | 所有会话默认都用 default(退回旧行为,全局生效) |
| 无 session 的调用(__global__) | default,保证向后兼容 |
- defaultSpace(A):volatile 标量,留空/缺失折叠为 :auto(1.6.0 起,此前折叠为 default),热更新不重注册。注意区分两个概念:配置值 cfg.defaultSpace(schema 里那个,可以是 :auto 或具名)与运行时当前会话活值 cfg.activeSpace(命令实际用的 useSpace(...) 入参);前者是设置,后者随会话与显式选择切换。
- :auto 派生(C)::auto 在两种模式下都派生 dsh-agent-<sid8> —— 空间命名与 isolateSpaces(那个只管 Profile 落盘还是内存沙箱)是两条独立的轴。会话 id 会先剥掉 session- 前缀再取 8 位:真实 DSH id 形如 session-52adc205-…,直接 slice(0,8) 会得到 session-,让所有会话撞成同一个空间——这正是隔离失效的经典坑,已用 sessionSpaceToken() 修掉并有回归测试。
- 会话级隔离(B):活动空间追踪器按 sessionId 键控成一张槽位表(Map<string,Slot>,无 session 的调用落 __global__ 槽保持向后兼容)。每个 bcdp_ 调用一进来就 setCurrentSession(callingSessionId(exec)),所以并发多会话各开各的空间互不漂移——A 会话重启空间不波及 B。
- 不要再手动建编号空间:不需要 #4/#5 这类手工命名,隔离已经由会话派生自动完成;需要共享就走显式 default。
跨调用稳定定位(D1)
bcdp_snapshot 现在带稳定 locator:输出里每个可交互节点除了 [ref=N] 还附 loc=css:… / loc=role:… / loc=href:…。跨调用请优先用 loc= 而非 @N——@N 每次快照重新编号且其 ref 映射只活在取快照的那个子进程里,而 loc= 稳定、能跨子进程命中(bcdp_click / bcdp_fill 直接吃 loc=...)。这是插件层解法,未改内置运行时 SDK 的 ref 编号生成。
navigate 卡死自愈(F)
bcdp_navigate 包了一层 watchdog:当一次导航因页面主线程挂死而 eval 超时 / no active tab 时,先开一个空白 tab 再关掉挂死那个(顺序关键:先关会触发末-tab 守卫留下死 tab),然后重试一次,结果附 recoveredFrom(正常路径为 null)。重试硬上限一次,二次仍卡如实返回,不用反复换 tab 掩盖真错误。
观察窗怎么用
右下角 🌐 常驻小球 → 点开:
- 主画面:agent 当前页面实况;点击/拖动/滚轮直接操作页面,Ctrl+滚轮缩放视图、Ctrl+拖动平移,双击复位。点击画面后可直接键盘输入,支持中文 IME、粘贴、Tab/Enter/方向键及 Ctrl/Cmd 快捷键。
- 选择元素(点选引用):点一下工具条进入点选模式,再点实时画面里的任意元素——插件捕获其 backendNodeId/tag/id 与语义描述,以 [CDP-PICKS] 结构化块并入对话输入框草稿;同一页面的多次点选自动合并进同一块并依次编号,agent 按编号精确取用。浮动观察球与侧边栏 Tab 均可用。
- 标签页条:顶部横排,点选切换,× 关闭。
- 历史抽屉(🕘):按时间回看访问轨迹。
- 操作时下方网址行就地显示提示,2 秒后恢复。
- 面板关闭、sidebar Tab 隐藏或组件卸载后,1.5 秒宽限结束即停止画面生产。仅把 DSH 窗口切到后台不会停止串流,避免回前台时反复重建 WGC/FFmpeg;异常关闭由 120 秒 worker lease 超时兜底。
画面后端
- cdp:Page.startScreencast JPEG,默认 20 FPS。每个源帧立即按 Chrome 提供的帧 ID ACK,只保留最新待发帧;仅捕获当前观看标签,静态页恢复截图默认 3 秒一次。
- ffmpeg:Windows 以 gfxcapture(hwnd) 直接采集目标 Chrome 窗口的 D3D11 surface;其他平台使用显示来源 crop。随后编码 H.264 fragmented MP4 → HTTP 二进制 chunk → MediaSource ,不经过 Base64/SSE。
- auto:默认选择 CDP,不检测成功也不会自动下载 FFmpeg。FFmpeg 安装并通过能力检查后才可选择;已保存的 FFmpeg 后端若后来失效,本次观察会回退 CDP 并展示原因。
- Windows FFmpeg 必须包含 gfxcapture。插件按 browser PID、target title 和 CDP window bounds 匹配 HWND;窗口移动或被遮挡时仍捕获目标页面,并且不允许回退到桌面录制。若 target 是同一 Chrome 窗口里的后台 tab,会明确报错而不是展示当前可见 tab 或抢用户焦点。macOS 首次使用需要「屏幕录制」权限;X11 需要 Chromium 与 FFmpeg 共享 DISPLAY;Wayland 缺少 Portal/PipeWire 输入时会提示切回 CDP。
托管 FFmpeg 安装到 ~/.dsh/cache/ego-browser/ffmpeg/,不会写入插件目录。Windows/Linux 使用固定 BtbN release tag;macOS 使用固定的 ffmpeg-static GitHub release 资产(其 Intel/Apple Silicon 二进制分别来源于 Evermeet/OSXExperts)。所有下载均固定资源 SHA-256,只提取 FFmpeg 主程序,不安装 ffprobe 或 ffplay。Windows/Linux 解包使用系统 tar;缺少时会在下载前明确报错。
登录态说明:多任务空间 Cookie 相互隔离,请在对应空间内登录。重启 DSH 后运行期登录态被清空(Chrome 运行期 Cookie 仅优雅关闭时落盘),需重登——扫码很快。
工作原理
- 工具层:每个工具把参数拼成 JS 脚本,经 ctx.subprocess 用 ego-browser nodejs 喂给 stdin 运行,宿主经 CDP 驱动共享 Chromium。结果以 @@DSH_RESULT@@ 哨兵行解析。所有 bcdp_ 经进程内互斥锁串行化,错误统一归一。
- 观察窗:lib/client.js 管理 watcher lease、JPEG 与 MSE ;lib/cast-server.js 代理元数据 SSE、watch API 和带背压的二进制视频;worker 中 CaptureManager 保证同时只有一个活动后端和一个当前 target。CDP 控制面(标签、viewport、输入、验证码)独立于画面后端。
JEV 风格管道:让 LLM 把控浏览器循环(阶段 10)
把「一帧 ≡ 截图 + 编号 DOM + 意图 + 操作进度」这个帧契约,连同「判定只回编号、绝不给选择器」的判定接缝,落地成一套可跑、可测、可归档的管道。判定由外部的 Laya / JEV 判定服务承担(见下),循环由本插件驱动。
四个工具(宿主侧 defineTool,判定请求从插件进程发 HTTP,不经过 agent 会话):
| 工具 | 干什么 | 何时用 |
| bcdp_jev_status | 先跑这个:判定链可用性 + 配置体检,不发任何请求 | 怀疑配置/链路问题时第一步 |
| bcdp_jev_frame | 采一帧(截图 + 编号候选 + 意图),看判定器会看到什么 | 调试帧内容、核对候选编号 |
| bcdp_jev_ask | 组装请求体;dryRun 默认 true,可指定 round=control\|chapter\|pick\|evaluate 逐级看,确认无误再发 | 想先看清楚判定请求再发 |
| bcdp_jev_run | 跑整条循环,返回逐步 trace(每步的候选数 / top / 预算 / 命中) | 真要让它动手 |
默认链路是 laya → rule。JEV 目前无法注册,所以默认不写它;将来可用时把 jev 加回 judgePrefer 跳序并填 jevUrl 即可。bcdp_jev_status 在缺 key 时会主动打印本地起法(ENGINE=laya … uvicorn laya_api.main:app,端口 8000 而非 7789,ALLOW_DEV_LOGIN=true 建 key)。不可用的跳跳过不调用(laya 强制鉴权无匿名分支,缺 key 必然 401),每一跳的跳过与失败都进 trace,绝不静默回落;refuse 是结果不是异常。
三级缩小(让 LLM 把控进程,而不是一次猜全部):
1. control —— 要不要动手(固定 5 项:pick_button / sleep / next / prev / done);
2. chapter —— 哪个章节(按 AX 容器聚簇,从 childIds 父链挑最近的结构化祖先,如 form#1 / form#2;单章节时此轮跳过);
3. pick —— 章节内选哪个编号 + 一个风险度 score。
分章节不是装饰:阈值按候选数分桶,把 20 选 1 拆成「几选 1 × 几选 1」会让两轮都落进更严的桶(top≥0.5 且 top−second≥0.15),比一次性 20 选 1(top≥0.6)更可控。判定器只回编号,坐标/选择器由执行层每次重新量(DOM.getBoxModel + 点击前 DOM.getNodeForLocation 命中复核),帧里的矩形绝不作为点击依据。
判定上下文是隔离的:只允许 INTENT / PROGRESS / FRAME / HISTORY 四段,绝不带 agent 会话 session 前缀;多出任何一段都在组装时直接抛错(不靠约定,靠断言)。PROGRESS(步数 / 各章节尝试与结果 / 真正成功过的动作 / 剩余预算)由循环自己机械生成,不让模型写——模型写的进度是第二难发现的幻觉通道。
评估开关 jevEvaluate(默认开,设置面板可关):每一步执行后,判定器再判一遍进度是 inprogress / done / fail。fail 或「未验真的 done」不猜下一步,而是升级为 escalate——带一组有序的 RecoveryOption(如先 reload 刷新,因为陈旧渲染会藏住已写入的确认),交回 LLM 决定恢复还是停手。
循环终止态:done(对照 successCriteria 自验)/ blocked(预算拦)/ exhausted(预算耗尽,带 exhaustedKind)/ stuck(同锚同动作连续 3 次无变化)/ unavailable(无判定器)/ error / escalate。
现状诚实记录:本阶段已证明协议、阈值、终止、分章节、组装、隔离正确(bcdp_jev_ 单测覆盖),但尚未在真实浏览器上端到端跑过(T10.22 待做);判定准确率未经实测(阈值分桶是标定用的,不是选得对的证明)。存档的被操作元素会保留 class 等定位特征(只剥离检视器外壳 token),供 escalate 时回传给 LLM 恢复。
开发
源码在 src/(TypeScript),构建产物在 lib/(host + client bundle)与 bin/cdp-cast-worker.mjs(worker bundle)。
pnpm typecheck # tsc 类型门禁(tsconfig.json 主 + tsconfig.client.json 客户端)
pnpm test # vitest 单元测试
pnpm run build # tsdown 三 bundle:lib/index.js + lib/client.js + bin/cdp-cast-worker.mjs
直接改 src/(src/index.ts 工具层、src/client/index.ts 前端、src/worker/cdp-cast-worker.ts worker)。新工具在 registerActionTools 里按 t({...}) 加,并在 bcdp_help 索引(src/help.ts)补一条,跑 pnpm typecheck && pnpm test && pnpm run build。lib/ 与 bin/cdp-cast-worker.mjs 是构建产物(预构建入库),不要手改。
node_modules/ 仅含指向 DSH checkout 的符号链接(编译期类型解析);运行时由 harness 解析 @deepseek-ai/dsh-tools。
已知限制(诚实说明)
- Windows:插件层已做 v0.4.0 适配;内置运行时的宿主对 Windows 仍以社区适配为主,复杂多步流程稳定性可能弱于 macOS。
- FFmpeg 平台捕获:Windows 已使用 gfxcapture(HWND),需要包含该 filter 的新构建;PATH 中的旧 FFmpeg 会被跳过并提示下载兼容版本。Linux 使用 x11grab、macOS 使用 avfoundation display crop;macOS ScreenCaptureKit、Wayland Portal helper 属后续增强。
- 安装环境:本仓库的 DSH peer 包不全在公共 npm registry。普通 pnpm install 可能在解析 @deepseek-ai/ peer 时失败;DSH profile 安装应提供这些 peer。CDP 不依赖 FFmpeg,也不会在插件安装阶段下载二进制。
- 快照质量:Linux 用 CDP DOMSnapshot 重建语义树,非 macOS 内核级,复杂 iframe/画布场景可能降级。
- 宿主可靠性(Linux):未合并的社区 PR,跨 CLI 调用间可能丢 tab/空间状态;插件已内置防御,简单流程稳定,复杂流程可能需重试。
- 登录态持久化:Chrome 运行期 Cookie 仅优雅关闭时落盘,强杀重启需重登。
- 输出 schema 为宽松 additionalProperties: true,客户端以实际返回值为准。
许可与署名
插件本体 MIT。内置运行时嵌入第三方 MIT 代码;可选下载的 FFmpeg 构建涉及 GPL-3.0-or-later 义务。使用或再分发前请阅读构建来源的许可证与源码获取信息,来源与署名详见 THIRD_PARTY_NOTICES.md。
供应链与权限说明
为目录收录与审查提供的确定性事实:
- 运行文件:lib/(构建产物,由 npm run build 从 src/ TypeScript 以 tsdown 确定性生成)、bin/(worker 与 ffmpeg-probe 的可执行入口脚本)、cordis.patch.yml(装配层)、dsh-plugin.json(manifest)。.map 仅为调试用 sourcemap,不参与运行,已声明排除。
- 原生/可执行工件:runtime/ 内置 ego 兼容运行时(第三方 MIT,来源与逐文件清单见 THIRD_PARTY_NOTICES.md)——它是本插件的核心功能(自带受管 Chrome/CDP 宿主),属有意携带的可执行工件,非构建副产物。runtime/PATCHES.md 记录对内置运行时的全部本地补丁。
- 依赖:运行时依赖仅 @deepseek-ai/schemastery(由 DSH 宿主提供对等实现);peer 依赖全部为 @deepseek-ai/dsh- 宿主服务。客户端 bundle 的外部模块由宿主模块表解析,不携带 npm 运行时依赖。
- 外部服务:无遥测、无外部 API 调用。唯一的网络行为是可选的 FFmpeg 安装器按用户指令从 GitHub(或用户配置的镜像)下载构建件,来源校验与许可义务见 THIRD_PARTY_NOTICES.md。
- 失败边界:宿主无 webServer(TUI/headless)时 watch 路由安全跳过;worker 启动失败时 watch 路由返回 ok:false 的 JSON 而非挂起;浏览器进程随宿主 teardown 一并终止(--stop fire-and-forget,不阻塞宿主退出)。
- 权限:manifest permissions 为空——工具集的文件读写限定在 ego 自管的空间目录与用户工作区,网络访问走受管的智能体浏览器,不经过宿主进程。
友链
同为 DeepSeek Harness 插件生态的作品,互相安利:预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
drscrewdriver/dsh-browser-cdp
dsh-browser-cdp — 看得见的智能体浏览器CDP 接入
相关插件 · 浏览器
智能体插件框架deepseek-ai/deepseek-harness
DeepSeek Harnessdsh是由 DeepSeek AI 开发的开源 agent harness智能体框架。
★ 266,913已汉化DSH
71.8
多设备 Agent 看板YaoApp/yao
✨ 所有 Agent 和工作区集中管理,覆盖你的每一台设备。任务看板追踪进度,桌面端、移动端、浏览器、API…
★ 8,147已汉化DSH
71.2
可扩展侧边栏工作台omdsh-dev/DSH-better-sidebar
DSH 的双工作台:原生右侧栏 + 自绘底部面板,并把 ctx.betterSidebar 服务开放给所有插件
★ 4,761已汉化DSH
71.1
浏览器自动化桥Tencent/BrowserSkill
你的 Agent。你的浏览器。连接,就能协作。
★ 9,848已汉化DSH
71
AI 小说创作工作台EthanYoQ/AI-Novel-Writer
AI 小说作家 / AI Novel Writer
★ 1,591已汉化DSH
70.7
智能体浏览器引擎platonai/Browser4
DeepSeek Harness 集成
★ 1,171已汉化DSH
70.4