← 返回列表
⚠ 装前注意
dsh-browser-live · 看得见的 Agent 浏览器DSH 标准插件
基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/15 · 已提供中文文档
看得见的 Agent 浏览器(自研 DSH 插件):18 个 browser_* 工具直接驱动本机 Chrome/Edge(CDP + 拟人鼠标轨迹),实时观察窗支持内嵌面板与独立网页两态、可键鼠接管、可配代理与额外启动参数;设置页开着时浮球沉到遮罩底下、面板自动收进独立网页,不压内容;登录态持久化在 $DSH_HOME/dsh-browser-live。
综合分
29.3
GitHub 分
29.3
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Raylen-berry/dsh-browser-live未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
信任档位:已验证本站已于 1 天前真实安装成功
- 是什么
- dsh 原生插件 · browser
- 装得上吗
- 本站已真实安装成功(非静态推断)
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 10 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
🟢实装验证通过· 2026/9/25
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/21(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-browser-live(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
未发布到 npm registry,仅可从源码安装
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/22 21:39:16
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-tools用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成dsh-browser-live · 看得见的 Agent 浏览器(DSH 标准插件)
让 agent 驱动本机真实 Chrome/Edge/Brave(CDP 直连,无 vendored 运行时、无裸 npm 依赖),
并在右下角开一块实时观察窗:agent 在网页上干什么,你全程看得见,还能直接接管鼠标键盘。
v0.5.0 起还可选接管你自己的日常浏览器(装一个 MV3 扩展,带你的登录态/代理,P0 只读)。
设计灵感来自 dsh-ego-browser(MIT)。本插件是自研实现:
不携带 ego-lite 运行时、零裸 import,与 link: 本地安装工作流完全兼容(同 dsh-bg-atelier / dsh-cache-control 约定)。
它解决什么
DSH 内置的 web_search/web_fetch 只能“读”;凡是必须真浏览器的活(登录态、动态渲染、表单、验证码、
需真人会话的站点)就干不了。装上本插件后,agent 获得 21 个 browser_ 工具,能真正在页面里
点击、输入、提交、滚动、取快照;你在观察窗里实时看到每一步,卡在验证码时点进面板亲手代打,
完事再把鼠标还给 agent。
组成
| 文件 | 职责 |
|---|---|
| index.js | Host(ESM):拉起/接管 Chrome → CDP;注册 21 个 browser_ 工具;/bl/ 观察窗后端(SSE 帧流 + 输入回传 + 设置/代理 + 下载取回 + /bl/view 独立网页 + /bl/bridge 桥状态) |
| bridge.js | 用户浏览器桥(P0):127.0.0.1 WS 服务端 + token 握手 + 与 Cdp 同形的 send/on/alive/close |
| extension/ | Chrome MV3 扩展(P0):service worker 当“反向 CDP 客户端”(chrome.debugger),弹窗逐站点授权;见 extension/README.md |
| client.js | Client 单文件:侧栏 🌐 按钮(无 slots 环境退化为自建浮球;与 bg-atelier 宝珠叠列共存)+ 观察窗(实时帧、标签条、agent 动作条、鼠标键盘接管、FPS/画质、下载取回;内嵌面板可拖动贴边,或切成独立网页) |
| cordis.patch.yml | bundle 装载声明(行 id == 包名,客户端模块扫描按 manifest name 匹配) |
| package.json | dsh.bundle.patch + dsh.client(platform web,注入 runtime/ui-slots) |
安装(本机 link,与 dsh-bg-atelier 同套路)
dsh plugin --profile web add link:D:\DeepSeek\dsh-plugins\dsh-browser-live
装完重启一次 DSH Desktop。设置 → 插件 里可开关;侧栏底部出现 🌐 观察窗按钮。
发布前检查(CI 与本地同一条命令)
push / PR 都会跑 .github/workflows/ci.yml,它做两件事:npm ci(只装 devDependencies)→ npm test。
本地跑的就是同一条命令:
npm install # 只装 devDependencies(就一个 ws);CI 用 npm ci
npm test # = node tools/run-all.mjs
node tools/run-all.mjs --list # 只看清单:跑哪些、以及哪些被排除、为什么
npm run audit:check # 只校验留痕哈希链(= node tools/verify-audit-chain.mjs)
出网边界:只有 npm ci / npm install 那一步出网(按 package-lock.json 装 devDependencies)。
npm test 本身不出网 —— 不做真实下载、不调真实模型、不起真浏览器、不读本机 DSH 安装目录。
tools/run-all.mjs 把 8 项语法门禁(node --check)和 14 套离线测试都跑完再汇总(原来的 npm test 是 && 串,
第一套一失败后面的就不跑了),任一套非 0 退出 ⇒ npm test 退出码 1 ⇒ CI 变红。
CI 用 Node 22 / 24 两档矩阵、windows-latest。
文档口径(本轮定):这里不写“某套件通过多少条”。 逐套件的通过数只能跑出来 —— 而其中
verify-host / verify-page-fns / verify-web-tools 三套根本不在 CI 门禁里(要起真浏览器),
把它们写进文档就没人能核对,已经陈旧过不止一次(具体是哪些数、涨到多少,见 CHANGELOG)。
现在只留不跑就能静态核对的清单数字(门禁几条、离线几套、工具几个、技能正文几节,
都由 tools/verify-manifest.mjs 的 D 段当场比对,写错就红);
逐套件条数一律以 npm test 的输出为准(要清单用 node tools/run-all.mjs --list)。
为什么没有 Node 20(本轮实测纠正):扩展代码 extension/background.js 里的裸
new WebSocket(...)、以及“没有 ws 包就退回 globalThis.WebSocket”那条路,都依赖全局 WebSocket,
而 Node 20 没有这个全局对象(实测 node20 globalThis.WebSocket = undefined,node22/24 = function)。
于是 verify-extension / verify-browsers / verify-result-cap 三套在 Node 20 上必红
(干净环境实测:语法门禁全过、离线套件只有一部分通过,verify-result-cap 因缺 WebSocket 大面积失败;
那次的具体条数见 CHANGELOG)。
所以矩阵写 22/24,并在 package.json 里声明 engines.node >= 22 —— 把真实下限写进 manifest,而不是把红藏起来。
干净环境实测(DSH_HOME / APPDATA / LOCALAPPDATA / USERPROFILE 全指空目录,独立下载的 node):
| 套件 | Node 22 | Node 24 |
| --- | --- | --- |
| tools/verify-audit.mjs | 通过 | 通过 |
| tools/verify-audit-chain.mjs | 通过 | 通过 |
| tools/verify-audit-redact.mjs | 通过 | 通过 |
| tools/verify-extension-v2.mjs | 通过 | 通过 |
| tools/verify-launcher.mjs | 通过 | 通过 |
| tools/verify-manifest.mjs | 通过 | 通过 |
| tools/verify-bridge.mjs | 通过 | 通过 |
| tools/verify-bridge-v2.mjs | 通过 | 通过 |
| tools/verify-extension.mjs | 通过 | 通过 |
| tools/verify-browsers.mjs | 通过 | 通过 |
| tools/verify-result-cap.mjs | 通过 | 通过 |
| tools/verify-launch.mjs | 通过 | 通过 |
| tools/verify-shots-recycle.mjs | 通过 | 通过 |
合计:8 项语法门禁 + 14 套离线测试全部通过,两档均 npm test 退出码 0;逐套件的通过条数以 npm test 输出为准。
未纳入 CI 的套件(原因同时写在 tools/run-all.mjs 的 EXCLUDED 里):
- verify-host.mjs —— 会真的拉起浏览器:browser_open{gui:true} 在有 Chromium 的机器上断言的是“启动成功”,
CI runner 自带 Edge ⇒ 会弹真窗口、走真 CDP。
- verify-page-fns.mjs / verify-web-tools.mjs —— 会拉无头 Chromium(Edge/Chrome)做 CDP 实测,
测试期不许起真浏览器、不许做真实下载。
v0.12.1 之前这 6 套(bridge / bridge-v2 / extension / browsers / result-cap / launch)是因为
缺 npm ws 包被排除的;现在 ws 进了 devDependencies,CI 装依赖那一步就把它装好了。
换台机器:可迁移性与必须手动的步骤
给后续在任何一台机器上接手的人或 agent —— 本插件有一部分能力拿不到就是拿不到,必须人手动做,
别在换了机器之后以为“克隆+重启就完事”。起因:用户 2026-09-12 反馈
“工作电脑上传,回家发现可用性很差,一定需要手动操作,比如到浏览器安装本地插件”。
分两档能力,先认清你要哪一档:
| 能力 | 需要什么 | 能自动吗 |
|---|---|---|
| 插件自带实例(独立窗口 + 独立 profile,免逐站点授权) | 只要插件装好 + 重启 | ✅ 全自动,browser_open 直接开 |
| 接管你的日常浏览器(要你的登录态:后台/飞书/公司系统) | 必须装浏览器扩展(MV3,本仓库 extension/ 里)+ 粘 token + 逐站点授权 | ❌ 扩展安装与授权只能在浏览器里点 |
B 档的四步手工操作(agent 只能把现场备好,点不了)
1. 打开扩展管理页:Chrome chrome://extensions / Edge edge://extensions → 开开发者模式
2. 加载解压缩的扩展 → 选中本仓库的 extension/ 目录
3. 点扩展图标打开弹窗 → 粘贴 token → 「连接」
- token 在哪:$DSH_HOME/dsh-browser-live/bridge.json 的 token 字段
- 省事做法:让 agent 调 browser_ext_setup —— 它会置 userBridge=true、把 token 放进剪贴板、
打开扩展页、并在文件管理器里打开 extension/ 目录;人只剩“开发者模式 + 加载 + 粘贴 + 连接”这四下
4. 在扩展弹窗里逐站点授权:每个要操作的站点点「允许」(默认只读);要真点击/打字/上传,
还得打开「允许操作」(allowInput),否则 agent 只能看不能动
其余换机事实(都不随仓库走)
- 状态与凭据全在 $DSH_HOME/dsh-browser-live/:settings.json(含 userBridge / headless /
浏览器路径 / 代理等)、bridge.json(端口 + token)、chrome-profile/(登录态)、
downloads/、shots/、audit/(留痕)。换机器后 userBridge 是空的 ⇒ 桥不会自己开,
要重跑一次 browser_ext_setup 或手动置 userBridge: true。
- 扩展是 MV3 本地加载的,不是商店包 ⇒ 换机器/换浏览器/换 profile 都要重装一遍(第 1–3 步)。
扩展掉线时 host 会自动回退到“插件自带实例”这一档,所以“接管失败”往往表现为换了个后端而不是报错。
- 浏览器探测:本机装了 Chrome/Edge/Brave 任一即可;headless=true 时无头也能全通,但画面是虚拟的
(观察窗看的是渲染结果)。首选浏览器起不来会自动降级到下一个候选,并补试兼容参数 --in-process-gpu。
- 浏览器起不来怎么办(v0.12.0 起自动处理,这里给手动兜底):症状是工具报
「10 秒内没响应 CDP 端口」,而机器上其实有一堆 msedge.exe/chrome.exe 占着插件的
chrome-profile- —— 它们让新实例被“转交”、不开调试端口。原因见 CHANGELOG 0.12.0 §7。
一条 PowerShell 清干净(只匹配本插件数据目录,不碰你自己的浏览器;不用重启 DSH):
Get-CimInstance Win32_Process -Filter "Name='chrome.exe' or Name='msedge.exe' or Name='brave.exe'" |
Where-Object { $_.CommandLine -and $_.CommandLine.ToLower().Contains(($env:APPDATA + '\dsh-desktop\harness\dsh-browser-live\chrome-profile').ToLower()) } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
之后再 browser_open 即可。v0.12.0 起 browser_close 会等 profile 真正释放(最多 6s)、
启动前与启动失败时都会自动清理这类残留,并记一条 🧹 … 动作提示(不静默)。
- 留痕(v0.9.0 起):所有 browser_ 调用与页面自身跳转都追加到
$DSH_HOME/dsh-browser-live/audit/YYYY-MM-DD.jsonl(append-only + 哈希链)。
node tools/verify-audit-chain.mjs 验链;node tools/verify-audit.mjs 离线自检(不碰真实留痕目录)。
- 已知宿主坑(三个插件共有):部分 DSH Desktop 版本启动时的 installGeneration 迁移会把
link: 挂载的插件重新 stage,并把绝对路径当相对路径拼接 ⇒ ENOENT、迁移 defer、
插件可能不加载。本机靠应用 bundle 的本地补丁(KEEP_IN_SHARED_TREE)绕过,该补丁不在本仓库;
识别:启动日志 migration deferred / could not stage。DSH 每次升级都会覆盖它,升级后要重跑。
设置导出/导入(换机器一键搬配置,v0.9.0 新增)
node tools/settings.mjs export --out D:\bl-settings.json # 旧机器
node tools/settings.mjs import D:\bl-settings.json --yes # 新机器(覆盖前自动备份 settings.json.bak-)
show 看当前值;不带 --yes 演练。会搬 fps / 质量 / 无头 / 窗口尺寸 / 代理 / 观察窗形态 /
拟人轨迹与速度 / userBridge / backendMode / userDefault / 端口,并主动提示两件换机后必须人做的事:
userBridge=true 说明还要人工装扩展 + 重跑 browser_ext_setup;chromePath 指向的路径在新机器上可能不存在(空了会自动探测)。
不含:浏览器扩展、bridge.json 的 token、chrome-profile/ 登录态、audit/ 留痕 —— 这四样本就该每台机器各自一份。
换机后自查
node tools/verify-audit.mjs # 期望 PASS(离线自检;条数以 npm test 输出为准)
node tools/verify-result-cap.mjs # 期望 0 failed(离线,不起真浏览器)
node tools/verify-host.mjs # 期望 0 failed(v0.10.0 起按"有/无真 Chromium"分两支断言;不在 npm test 门禁里)
node tools/verify-audit-chain.mjs # 期望 0 = 链完整(还没留痕时会跳过并返回 0)
npm test # 全套;其中 verify-page-fns / verify-web-tools 会真起一个无头浏览器
工具一览(agent 侧)
| 工具 | 说明 |
|---|---|
| browser_open | 启动/接管浏览器(懒启动),可选 url / newTab / use |
| browser_ext_setup | 一键备好"接管你日常浏览器"的现场:开桥(userBridge=true)→ token 进剪贴板 → 打开目标浏览器的扩展页 → 资源管理器里打开 extension/ 目录,并把步骤列出。kind 选 edge/chrome,open:false 只返回路径与 token |
| browser_navigate | 导航并等加载完成 |
| browser_snapshot | 结构化快照:可见交互元素清单(跨快照稳定的 ref、role、文本、坐标、状态标记)+ 正文节选。重名元素带 ctx 消歧;页面上有人机验证时结果里会带 challenge。点击前先拿 ref |
| browser_click | ref / CSS selector / 坐标三选一,真实鼠标事件(右键、双击可选);默认走拟人贝塞尔轨迹(instant:true 可瞬移)。用 ref/selector 时先做可操作性检查(隐藏/零尺寸/disabled/pointer-events/视口外/被遮挡),失败返回确定性原因而不是静默点空(force:true 可跳过) |
| browser_move | 拟人移动鼠标(触发 :hover/下拉/tooltip);hold 按住左键、instant 瞬移;同样带遮挡/可见性检查 |
| browser_type | 聚焦输入框(自动全选便于替换)+ 插入文本,可回车;输入后回读字段值确认真的写进去了(password 只回长度,不回显) |
| browser_read | 读当前页的结构化内容(v0.10.0):自动识别正文主体、输出 Markdown(标题/列表/表格/代码块/链接)、附元信息与链接清单;offset/limit 是段落感知的,长文续读不会把句子劈开 |
| browser_scrape | 把重复结构抓成行数据(v0.10.0):item + fields("a@href" 取链接并绝对化),不用写 JS |
| browser_search | 在浏览器里真搜(v0.10.0):Bing/百度/DDG → 结构化结果;被反爬拦会自动换引擎并如实回报原因 |
| browser_upload | 本机文件塞进 (不弹系统对话框);ref/selector 可指上传按钮/拖拽区容器,自动解析其中隐藏 input(Meta Ads 等 React 自定义上传组件适用);触发 change 事件 |
| browser_press | 按键/组合键:Enter、ctrl+a、alt+ArrowLeft… |
| browser_scroll | 滚轮方向+像素 |
| browser_wait | 等文本出现 / 选择器命中 / URL 片段 / 纯等待,带超时 |
| browser_eval | 页内 JS 表达式(returnByValue + await 可选) |
| browser_text | 正文分块提取(纯字符切片;要结构就用 browser_read) |
| browser_screenshot | 可视区/整页 PNG 落盘,路径可直接交给读图工具 |
| browser_tabs | 标签页 list/new/select/close |
| browser_history | 前进/后退/刷新(可强刷) |
| browser_downloads | 下载目录清单(观察窗里也能一键取回;用户浏览器那档仍只列插件实例的目录) |
| browser_close | 关浏览器(profile 保留,登录态不丢);用户浏览器那档只断开调试器,不关你的浏览器 |
每个工具都带一个 use 参数(v0.7.0)——'plugin' / 'chrome' / 'edge' / 'user' / 'auto',
用来在同一个会话里指定这次调用落在哪台浏览器上(详见「接管你自己的浏览器」一节)。
不写 use = 沿用上一次调用用的那台。
所有工具串行互斥(一次只跑一个);首次调用自动拉起浏览器并弹观察窗。
配置:哪些是"可配/百分比",哪些是刻意固定的
原则:凡是"尺度 / 地址 / 顺序 / 上限"这类会因机器、显示器、网络、站点而异的量,都不该写死在代码里。
(这条是用户明确要求的,也确实是我先前的毛病:搜索引擎写死三个名字、面板尺寸带硬上限 560px、
搜索冷却写死 30s。)
可配($DSH_HOME/dsh-browser-live/settings.json,改完即时生效;node tools/settings.mjs show 看全部生效值)
| 设置 | 默认 | 说明 |
|---|---|---|
| panelWidthPct / panelHeightPct / panelWidePct | 42 / 52 / 66 | 观察窗面板的宽 / 高 / 宽屏宽 —— 都是视口百分比 |
| windowSize | 1440,900 | 浏览器窗口尺寸:像素或百分比(80%,85% = 屏幕工作区的 80%×85%) |
| search.engines / search.order | 空 | 自定义搜索引擎({url:"…%s", item, link, title, text})与 auto 的尝试顺序 |
| searchCooldownMs / searchBudgetMs | 30000 / 20000 | 引擎冷却时长 / 单次搜索总预算 |
| fps / quality | 2 / 60 | 观察窗帧率与画质 |
| headless / chromePath / extraArgs / proxy | — | 浏览器怎么起、走不走代理 |
| liveView | panel | 内嵌面板 / 独立网页(/bl/view,可丢副屏、F11 全屏) |
| humanize / humanSpeed | true / 1 | 拟人鼠标轨迹开关与速度倍率 |
| backendMode / userDefault / userBridge / bridgePort | auto / '' / false / 9760 | 默认用哪档浏览器、桥的端口 |
| maxTabsWarn | 12 | 标签页开太多的提醒阈值 |
每次调用还能单独给(不改设置):browser_search 的 engine / engineSpec、browser_read 的
limit / offset / linkLimit / selector、browser_scrape 的 limit、所有工具的 use。
刻意固定(写死是有理由的,不是漏改)
- 浏览器候选(Edge → Chrome → Brave):这几款是"你真的在用、且允许被驱动"的;路径可用 chromePath 覆盖。
- 人机验证特征库:属事实性数据;识别出来是"停下问人",不该做成可配置开关。
- 安全阀:工具串行互斥、快照元素上限(140)、单次结果上限(整个结果 24k 字符,其中正文单次
最多 20k —— 抓 300 行 / 搜 50 条这类额度也一样受它约束)—— 防止一次调用把上下文打爆的护栏,
需要更多就用 offset/limit 分段取。超限时是在序列化之前裁剪:结果仍是合法 JSON,且带着
truncated / truncation.originalChars(原始长度)/ truncation.nextOffset(下次续读的 offset)
与 next 提示 —— 绝不会把 JSON 拦腰切断,也不会静默丢内容。详见下面「单次结果上限」一节。
- 面板的可读下限:min-width:min(280px,90vw) —— 纯百分比在极窄窗口会把面板压到不可用;
这个下限本身也随视口缩(90vw),不是固定像素。
单次结果上限:声明与实际必须一致(超限在序列化前裁剪)
数字(只有这三个,别处引用它们)
| 量 | 值 | 含义 |
|---|---|---|
| MAX_TEXT_CHARS | 20000 | 正文类字段(text / value)的单次上限 —— 也就是 browser_text / browser_read 的 limit 声明上限 |
| MAX_RESULT_CHARS | 24000 | 整个结果的硬上限(正文 + 元信息 + 链接清单……全算进去) |
| 其它额度 | 抓 300 行 / 搜 50 条 / 快照 140 元素 | 行数类额度照旧,但它们产出的结果同样受 24000 约束 |
为什么要有这一节:v0.12.0 之前,browser_text / browser_read 声明“limit 上限 40000”,
而外层安全阀切的是序列化之后的字符串(safeJson 里 slice(0, 24000))。两者一叠加就是坏结果:
3 万字的页面上,调用方拿到的是被拦腰切断的串(JSON.parse 直接抛),而且 offset / charsStart 这些
续读信息正好落在切口之后 —— “内容长、请续读”实际表现成“结果坏了、也没法续读”。
现在怎么走
- 声明 = 实测:limit 上限就是 MAX_TEXT_CHARS(20000),三个地方用的是同一个数
(工具 schema 说明、index.js 的 clamp、注入到页面里的 READ_FN 的 clamp —— 页面内函数读不到模块常量,
所以 tools/verify-result-cap.mjs 里有一条断言专门钉住那个字面量)。
- 在序列化之前裁剪:正文按剩余预算(24000 减去元信息/链接等包装开销)先裁到段落边界;正文裁完还超,
就从尾部整条丢“成片行数据”(链接清单、抓取行),丢了几条也记账。返回值永远是合法 JSON。
- 截断显式记账(不再静默丢,也不再靠一句“……(截断)”):
{
"text": "…(已保留的正文)",
"truncated": true,
"next": "还有内容:用 offset=20000 续读",
"truncation": {
"truncated": true, "limit": 24000,
"field": "text", "originalChars": 40000, "keptChars": 20000,
"originalResultChars": 45210,
"nextOffset": 20000,
"fields": [{ "field": "links", "original": 100, "kept": 12 }],
"note": "结果超过单次上限:已在序列化前裁剪(不是把 JSON 切断)。…"
},
"linksDropped": 88
}
- 续读:照 truncation.nextOffset(或 next 里的 offset)接着读即可,分页不重不漏 ——
3 万字材料两次取完,拼起来与原文逐字一致。
- 短结果一个字都没变:没超限就原样返回、不加任何记账字段(tools/verify-result-cap.mjs 里
有“防修过头”断言钉住这两条)。
验证:node tools/verify-result-cap.mjs(离线、不起真浏览器)—— 真 apply() + 真桥 +
真扩展 + 一个 Worker 假浏览器(长页模式把工具真正生成的表达式放进 node:vm 真跑):
解析/限额/记账/续读链/短结果/声明一致,以及 browser_scrape、browser_search、browser_snapshot
这三处同类组合(它们没声明 40000,但同样会撑过 24000,走的是同一道收口)。npm test 已包含它。
网页理解与搜索(v0.10.0):读得懂、抓得到、搜得了
v0.10.0 从 7 个同类 MIT 插件里蒸馏出三个工具,没有新增任何 npm 依赖(全部靠注入脚本 + 现有 CDP 通道):
| 工具 | 它解决什么 | 关键实现 |
|---|---|---|
| browser_read | 读一篇文章/一个页面 | 四级正文降级(多 article ≥200 字 → role=main → main → 最大文本块)、噪音剥离、Markdown 输出、段落感知截断、meta/og/JSON-LD 元信息、链接清单、标题大纲 |
| browser_scrape | 把列表/表格/搜索结果抓成行数据 | item + fields("h3 a@href" 取链接并绝对化、@html、@text),命中不到时自适应重试 |
| browser_search | 在浏览器里真搜(引擎无关) | 打开“搜索 URL 模板” → 用 {item,link,title,text} 抽取 → 跳转链处理 → 四态空结果分诊 → 冷却换下一个 |
搜索引擎是“机制 + 数据”,不是一份菜单(v0.11.0):可迁移的技术是上面那套流程本身,与具体
是哪个引擎无关。所以:
- 预设只有三个(bing / baidu / ddg),它们只是“开箱即用的默认值”,不是“可用清单”;
- 要接任何别的引擎(Google、Sogou、公司内网站内搜索、某个文档站的搜索框)不用改代码、不用配置、
不用重启 —— 调用时自带即可:
browser_search({ query: '关键词', engineSpec: {
url: 'https://某个站点/search?q=%s', // %s 或 {q} = 搜索词(缺占位符会明确报错并给示例)
item: 'li.result', link: 'h3 a', title: 'h3', text: 'p.summary', label: '显示名',
}})
- 想长期复用就写进 $DSH_HOME/dsh-browser-live/settings.json:
"search": { "engines": { "我的站内搜索": { "url": "…%s", "item": "…", "link": "…", "title": "…", "text": "…" } }, "order": ["我的站内搜索"] }
—— 设置按调用读盘,改完下次调用即生效;order 就是 engine:"auto" 时的尝试顺序。
- 引擎名写错时,报错会告诉你怎么自带(而不是甩给你三个名字)。
为什么“在浏览器里搜”而不是发 HTTP 请求:用的是你自己浏览器已授权的身份,比服务端抓取更少遇到
反爬。空结果会按病因分诊(四态,处置完全不同):blocked(被反爬拦了 → 换引擎 + 冷却,默认 30s 可配)、
not-loaded(页面没加载完 → 等一下重试)、layout-changed(item 选择器没命中 → 改 item)、
filtered-out(命中了条目但字段全没通过 → 改 link/title/text)。最后一态是实测逼出来的:
百度那次 hits=8 却没有结果,原来被笼统报成“引擎改版”,把人引到错误的排查方向。
为什么不做成“自带行动循环的 agent”:循环是 DSH agent 自己的事。这里只把“看得懂”和“抓得到”补上,
再用一份调用方案告诉 agent 怎么组合。所以:
- skills/browser-automation/SKILL.md = 完整调用方案(14 节:决策表 → 浏览器档位 → 观察/行动循环 →
ref 与状态标记 → 故障处置表 → 读/抓/搜的用法 → 人机验证纪律 → 反模式清单 → 4 个现成剧本 → 验收口径)。
- DSH 的技能发现不会自动扫插件包里的 skills/,所以 index.js 自己调 skills.register 注册
(POST /bl/skills/reload 可免重启重扫新增技能)。技能真正常驻的只有 name + description,
正文在 agent 调 skill 时才进上下文 —— 21 个工具不该配一份常驻长文。
- 人机验证、被遮挡、ref 失效这些“卡住”的情形,都做成工具返回值里的一等公民
(challenge / flags.covered-by / 确定性错误原因),而不是让 agent 自己猜。
验证:node tools/verify-page-fns.mjs(真起无头 Chromium,本地 fixture 页面;它不在 npm test
门禁里,自带被起的浏览器);技能注册与重扫路由的断言在 node tools/verify-host.mjs 里。两套的条数
都以各自命令的输出为准(要清单见 node tools/run-all.mjs --list)。
拟人轨迹(v0.3.0)
agent 的鼠标移动(browser_click / browser_move)默认不再是“瞬移出现”:
三次贝塞尔弧线 + 两端慢中间快的缓动 + 随进度衰减的正弦抖动 + 偶发微停顿,
事件仍从 CDP 输入管线注入(isTrusted === true,与真鼠标同层)。
- 设置项:humanize(默认 true,关掉回到瞬移)、humanSpeed(0.3~4,默认 1,约 0.15~0.45s/中程)。
- 单点距离
收口点只有一个:所有工具都在 index.js 的注册循环里被 withAudit() 包了一层
(不是逐个工具加埋点 —— 那样必然会漏,新增工具时还会再漏)。
powershell
node tools/verify-audit-chain.mjs # 校验今天的留痕链是否完整(等价 npm run audit:check)
node tools/verify-audit-chain.mjs
链尾以文件为准,不信进程内存(v0.12.0 修):原来只在“跨天”时读一次文件尾,
同一天里默认内存里的 prev 是对的 —— 但同一天里可能有第二个写入者。2026-09-14 真踩到:
插件被重新 apply 后旧实例的 AUDIT 仍在写,两条线各自记链尾,第 16 行接回了第 14 行 ⇒ 链断,
看起来和“有人删了一行”完全一样(哈希链最不该误报的就是这个)。现在每次写之前读一次
文件末尾(16KB)取链尾,prev 是文件的事实;apply() 开头也会先收回上一个实例
(注册项与定时器,不动浏览器进程/登录态)。
那一处历史断点按“只追加不重写”保留原样(重写就等于毁掉证据),并在文件里用一条
type:"note" 记录写清了诊断与修法(kind:"chain-break-diagnosis")。所以:
npm run audit:check 现在仍会报这一处;第二天换文件即恢复全绿。
代码测试(npm test)不再包含“实时链检查” —— 它检查的是你的数据、不是代码,
混在一起会让“改了代码跑测试”因为历史数据而失败。
能力边界(别把它当保险箱):追加写 + 哈希链能查出“删了一行 / 改了一行”——
tools/verify-audit.mjs 里就有这两条断链断言。但日志和 agent 在同一台机器、同一个用户下,
拥有完整写权限的人可以把整条链重算一遍,因此它挡的是“顺手抹掉一两步”,不是防篡改。
真要不可抹,得把这条 JSONL 实时送到 agent 够不到的地方(另一个进程 / 另一台机器)。
另外,“关掉 agent 自己开的页面”这个权限本来就不在 agent 手里:它由浏览器右上角扩展弹窗里的
「允许关闭 agent 自己打开的标签页」开关控制(extension/popup.js + background.js 的
allowCloseOwn 判定,默认开;关掉之后连它自己开的也关不了),你手动开的页面任何情况下都不在范围内。
前置条件
- Node ≥ 22(DSH Desktop 自带);宿主 ≥ 0.1.2(defineTool 解析带 lite 兜底,装不上也只降级不崩)。
- 本机任一 Chromium 系浏览器:Chrome / Edge / Brave(自动探测;探测失败在设置里填 chromePath)。
- 无头服务器也可用:设置 headless=true 后工具全通,只是画面是虚拟的。
- 调试提示:若你在受限沙箱终端里跑本插件的 e2e 脚本,Chrome 会因命名管道被拒而
FATAL mojo platform_channel (0x5) 自杀——表现为 CDP 连上后立刻 1006 断开。
这不是插件问题:宿主内正常运行不受影响;离线验证请用允许命名管道的会话跑
node 03-调试临时\bl-e2e.mjs。
接管你自己的浏览器(v0.5.0 起;v0.7.0 起 Chrome / Edge 可同时接入)
默认后端是插件自拉的隔离实例(独立 profile,登录态是插件自己的)。
想要 agent 用你日常那个浏览器的登录态/代理/扩展,走扩展路线:
host(index.js) ──WS──> 扩展(extension/, MV3,Chrome 和 Edge 各装一份)
└── chrome.debugger ──> 各自真实的标签页
Chrome 与 Edge 各装一份扩展,两条连接可以同时挂在桥上;每次工具调用用 use 指定用哪台。
会话状态(标签页列表、当前标签、CDP 会话)按浏览器分开存,不会串台;
sessionId 一律带 : 前缀,桥靠这个前缀把请求和事件路由到正确的那条连接
(协议见 docs/MULTI-BROWSER.md)。
为什么必须装扩展、不能直接 CDP attach 你的浏览器:Chrome/Edge 136+ 在默认 user-data-dir 上
直接忽略 --remote-debugging-port / --remote-debugging-pipe(安全加固,CVE-2025-4051/4052)。
本机 Chrome 153、Edge 152 都早就过了这道线,chrome.debugger 是唯一像样的路。
开启:设置页「浏览器观察窗 → 用户浏览器」点成「桥已开启」(面板显示端口 + token,可一键复制),
然后按 extension/README.md 在 Chrome 和 Edge 里各加载一次扩展并粘 token。
同一个 token 两边通用;桥只绑 127.0.0.1,握手要 token,升级请求只认 chrome-extension:// 的 Origin
(网页里的 ws://127.0.0.1 连不进来)。设置页会列出当前已接入的浏览器,并可指定
use:"user" 默认用哪台(直接写 use:"edge" / use:"chrome" 永远优先于这个默认值)。
日常用法("免登录页 / 要登录的页"分工):默认档 auto 下,免登录的网页一律走插件自带实例
(不受逐站点授权限制、可 newTab 新开页);只有确实要你的登录态时才切到你的浏览器:
1. 在那台浏览器的扩展弹窗里给该站点点「允许」(整批页面可点「允许当前所有标签页」;
要真点击/打字再打开「允许操作」);
2. browser_open { url, use: "edge" } → agent 在 Edge 当前标签页里导航/操作。v0.3.2 扩展起它也能
newTab 新开页(开到你指定的 http/https,弹窗「允许 agent 新开标签页」控制,默认开);
新开的页记在 agent 名下,所以可被它自己关,你原有的标签页一个字都不动;
3. 完事 browser_open { use: "plugin" } 切回独立窗口,后面的免登录活继续不受授权限制。
use 取值:plugin(自带实例)/ chrome / edge(你的浏览器,必须已接入)/ user(settings.userDefault 那台)/
auto(桥连着就用你的浏览器)。不写 use = 沿用上一次调用用的那台,冷启动默认 plugin;
显式指定的浏览器没接入时会明确报错并列出当前已接入的 —— 绝不静默换到另一台去操作。
browser_open 的返回值里 browser 字段告诉你当前实际用的是哪个(插件自带实例 / 你的 Edge),
connected 列出当前已接入的浏览器,hint 直接给出下一步该传什么。
三层开关(都在扩展弹窗里,逐层收紧;两台浏览器各自一套):
| 开关 | 默认 | 作用 |
| --- | --- | --- |
| 逐站点「允许」 | 无授权 | 没点过的站点,url/标题对 agent 一律打码,调试器也附加不上 |
| 「允许当前所有标签页」 | — | 一次性把该浏览器里所有 http(s) 标签页的 origin 并入白名单(不打开「允许所有网站」) |
| 「撤销全部授权」 | — | 清空白名单(有二次确认) |
| 允许操作(v0.6.0 / P1) | 关 | 打开后才放行 Input. 与 DOM.setFileInputFiles(真点击/打字/按键/滚轮/上传) |
| 允许所有网站 | 关 | 高风险:放弃逐站点确认 |
只读能力(永远可用):browser_snapshot / browser_text / browser_screenshot /
browser_tabs list / browser_navigate / browser_wait。观察窗照样直播(帧也是经扩展回的
Page.captureScreenshot),/bl/view 与内嵌面板会亮红标:只读时「🔴 正在读取你的 Edge」,
打开「允许操作」后变成「🔴 正在操作你的 Edge(可点击/打字)」。
P1 操作能力(需打开「允许操作」):browser_click / browser_move / browser_type /
browser_press / browser_scroll / browser_upload —— 事件经 chrome.debugger 注入
(isTrusted:true,与真人同层),拟人鼠标轨迹照旧生效。
实机测出来的三条硬约束(v0.6.0 已处理,但你必须知道):
1. 输入只送到「可见 + 窗口有焦点」的标签页。后台标签、失焦窗口下 Chrome 会静默丢弃
Input.dispatchMouseEvent(键盘偶尔能过,所以看起来“半好”)。扩展在发任何输入前会
chrome.tabs.update({active:true}) + chrome.windows.update({focused:true}),
等窗口真的拿到焦点后再等 300ms 才发事件 —— 也就是说 agent 动手时会把 Chrome 拉到前台。
Windows 的前台锁定可能拒绝被抢焦点(任务栏闪烁而不切换),这时需要你手动点一下 Chrome 窗口。
只读命令(截图 / 取文本 / 快照)不会抢你的前台标签。
2. disabled 的表单控件收不到鼠标事件(Chrome 行为,不是接管失败):真点击一个 disabled
输入框会“没反应”。实测番茄渠道商后台「新增推广链」里的「书籍ID + 搜书」搜索框就是
disabled:true(要先选「选择包」等前置项),点它没反应是页面状态;同对话框里可用的
「书籍ID」表单项真点击/真打字正常。
3. 坐标必须一次到位:元素若在测量后被页面重排(Element UI 的对话框滚动),点击会落空。
用 instant:true 可把“测量→下发”的间隔压到一个往返,成功率高得多。
仍然被拒(扩展侧硬拒,会以错误结果返回,不会静默):
| 被拒 | 原因 |
| --- | --- |
| browser_tabs new/close | new 需扩展 v0.3.2+ 且弹窗「允许 agent 新开标签页」开着(自带实例档无条件允许);close 只关 agent 自己开的页(Target.closeTarget,你手动开的一律拒绝) |
| browser_downloads | 列的仍是插件实例的 downloads/ 目录,看不到你 Chrome 自己的下载(接 chrome.downloads 属 P2) |
| 未授权站点 | 逐 origin 授权;没授权时连调试器都附加不上,开了「允许操作」也进不去 |
| 改网络 / 模拟器 / 改属性 | Network.setExtraHTTPHeaders、Emulation.、DOM.setAttributeValue 不在接管范围 |
兜底:扩展掉线 → 下一次工具调用自动回退插件实例(行为与 v0.4.3 完全一致);
browser_close 在用户浏览器模式下只拆调试器,绝不关你的浏览器;
扩展侧会话失效(SW 重启 / DevTools 抢走调试器)时 host 会自动清 session 重试一次。
已知缺口:Runtime.evaluate 在扩展白名单里(snapshot/text 靠它取正文),所以扩展拦得住
“合成输入”,拦不住页面内 JS 自己点按钮 —— 也就是说 browser_eval 理论上仍能代打。
真正的护栏是逐站点授权 + 「允许操作」开关 + Chrome 那条无法隐藏的「正在调试此浏览器」横幅。
收窄 evaluate(改成固定脚本下发)仍是待办。
已知边界(v0.5.0)
- 观察窗单实例(整个宿主一个浏览器会话,不做多会话隔离);多 agent 并发浏览请串行使用
(多个会话共用同一受控 Chrome 时会互相抢标签页,这是设计如此,不是 bug)。
- 默认后端仍是插件自拉实例;接管你日常浏览器是 v0.5.0 的扩展路线(P0 只读,见上),
P1(输入接管)/ P2(审批联动、evaluate 收窄)未做。
- Page.captureScreenshot 取帧(非 screencast 事件流),高 FPS 下 CPU 开销线性上涨,默认 2fps。
- browser_upload 支持 (含隐藏 input、React 自定义组件的 label 包裹);纯 HTML5 拖拽(无 input)与跨 origin iframe 内交互未做。
- 拟人轨迹 = 页面侧事件流仿真;系统光标位置不动,比对 screenX 与 OS 光标高阶风控理论上可辨(极少数场景)。
- agent 靠 DOM 快照决策,“看不懂” Canvas 图表/图片内容——视觉闭环留 v1(DOM 为主、截图+视觉模型为辅)。
投放/运营场景模板(开箱即用)
把下面这些当 prompt 直接发给带本插件的 agent 即可;它们都遵循“只读→半自动→人工终审”的稳妥节奏。
- 竞对情报(零风险,先跑这个)
用 browser_open 打开 Meta 广告资料库,搜 "GoodNovel",snapshot 后把近 7 天投放的
文案钩子 / 素材形式 / 起量日期 / 落地页 URL 抽成表格存成 csv。
公开数据、无登录墙、无风控。抽出的结构化数据可直接喂给 LLM 产素材变体。
- 落地页巡检(真浏览器 + 真地区才有价值)
依次用 --window-size=390,844(iPhone 视口)打开这几条落地页,每张存图并检查:
付费按钮是否存在、首章文案有无截断、深链是否 404、像素有无发起请求。
需要查地区差异时,在设置页「代理服务器」填 http://(或 socks5://…)——
它直接翻成 Chrome 的 --proxy-server,等价于以前往 extraArgs 里手写参数,下次拉起浏览器生效。
- 半自动发布(人机协作,别全自动点 Publish)
在 Ads Manager 建好 Ad Set(预算/国家/兴趣词/落地页),素材用 browser_upload 传入,
填完文案后停在 Publish 前,打开观察窗等我人工确认。
配合已装的 dsh-approval-gate:可配成 browser_click 默认放行、唯独命中 Publish 转人工审批。
风控纪律:主账户只走“前端 UI 自动化”(等价真人点击);browser_eval 打内部 GraphQL 取 JSON
属于灰色地带,仅限只读且用独立测试账户隔离——“能拿到数据” ≠ “应该这么做”。Google Ads 对此更敏感。
版本与变更记录
- v0.14.0:收起成一条 = 暂停画面流(用户 2026-09-14 的省资源项)。
原先 setMin() 只切 CSS 类,画面流照旧在跑(仍在拉 JPEG 帧、仍在设 img.src,解码也照做),
只有整块面板被 ✕ 收走才关流。现在加 applyStreamState():want = S.open && !S.min,
Collapse and the stream stops; expand and it immediately resumes; because the host /bl/stream is "zero overhead with no viewers," disconnecting truly stops the screenshots
(just change it on the client side; no need to touch the host or restart). Health determination adds paused: the collapsed state is a gray solid light +
"Paused (collapsing)," and will not be misreported as the yellow light "reconnecting." Measured: after collapsing for 4.5 seconds, the frame bytes did not change by a single character;
after expanding, the frame bytes changed and the light returned to green. Regression verify-panel-health 21 ⇒ 30 items.
- v0.13.0: Screen health — a green light only means "the screen is updating"; a stopped stream shows a yellow light (user feedback 2026-09-14).
Previously, the green light turned on as soon as a frame was received, but when the stream stopped, the status did not update: es.onerror was purely a comment, and pollState's light check only ran in
the if (!S.es) branch ⇒ while the stream object still existed, that green dot would never be corrected, and the user would mistake the last frame for a live screen.
Now "the browser is running" (host /bl/state.alive) and "the screen is updating" (the time of the most recent SSE frame, threshold 3.5s)
are split into two facts: the determination is the pure function computeHealth(), and the green light is lit only by the frame handler.
When yellow, the top says "Reconnecting" (if no frame has ever been received, it says "No screen received yet"), and an
"⏸ Screen stopped · Last updated X seconds ago" badge is overlaid at the upper middle of the screen; the collapsed-state title line also carries "· ⏸ Screen stopped Ns".
Regression: added tools/verify-panel-health.mjs (21 items: eight combinations of the pure function + two copy variants + 9 wiring assertions,
including "has not fallen back to the old criterion classList.toggle('on', !!st.alive)"), offline suite 12 ⇒ 13 suites.
- v0.12.0: Changed "scale" from hardcoded pixels to percentages, and cleaned up the remaining hardcoded items ——
①Observation window panel: width/height/wide-screen width changed to viewport percentages (panelWidthPct, etc., adjustable in settings, effective immediately).
The old form clamp(300px→560px) looked like a percentage, but the upper limit of 560px was a hard cap —— no matter how wide the window, it would not grow larger,
and on wide screens it actually became relatively smaller, so in use it was a "fixed size"; the height was even entirely determined by the screenshot aspect ratio, and a narrow, tall window would pierce through the viewport.
Now the stage is flex:1 + min-height:0, and the image is contain (copying the already-verified approach from the standalone page /bl/view, consistent in both places).
Measured: in a 1418×775 viewport, the panel is 597×405 = 42.1%×52.2% (597px already exceeds the old 560px cap).
②Browser window size supports percentages (windowSize:"80%,85%" = proportion of the screen work area): after startup, it is converted according to the page's
screen.avail and adjusted with Browser.setWindowBounds (if the screen cannot be measured, do nothing; do not guess) ——
no need to change settings when switching monitors or machines. ③Other hardcoded items: search cooldown and total budget, and panel percentages all went into settings;
settings export/import changed to directly reuse the plugin body's DEFAULT_SETTINGS (previously it was a hand-copied version + comment constraints, which would drift sooner or later).
④Also fixed a real bug: sanitizeSettings started from the default values, causing invalid values in a PUT to silently reset that field to the default
(for example, PUT windowSize:"八百块" would wipe out the user's configured 80%) —— now a PUT uses the current settings as the base, and invalid values are merely ignored.
README adds a section "Configuration: what can be configured/is a percentage, and what is deliberately fixed," clearly explaining "configure everything that should be configured, and give reasons for what is hardcoded."
- v0.11.0: Changed search from a "menu" back to a "mechanism," and fixed the root of bugs like "writing to a mirror" ——
①browser_search no longer hardcodes engines: SEARCH_ENGINES is downgraded to presets, and a new engineSpec is added (carrying
{url:'…%s', item, link, title, text} at call time; any search page/site search can be connected, no code changes, no configuration, no restart,
with end-to-end tests) and long-term configuration via settings.search={engines,order}; error messages were also changed to teach you how to bring your own engine.
②viewOf() was a one-way copy, causing all browser.xxx = v to write into thin air —— the affected surface found by a full-repo audit includes
browser_tabs{action:'select'} and clicking tabs in the observation window (reports ok but the page does not change), and the anthropomorphic mouse "tip" and viewport size
being reset on every call; now these fields are write-through accessors, and this class of error is syntactically impossible to make again.
③Empty-result triage adds a fourth state filtered-out (item matched but field selectors do not match), and fixes the priority bug of "if the page is short, first judge
that it has not finished loading" (matching an item itself proves that loading is complete); this also corrects a claim in v0.10.1 that I wrote but
did not implement.
④browser_read is no longer drowned by badges: image URLs >120 characters keep only alt and are accounted for (imagesOmitted).
- v0.10.1: Ran v0.10.0 against real websites, two fixes ——
①The existing bug "after opening a new tab, the current page is still the old page" (browser_open {newTab} / browser_tabs new both affected;
browser_search therefore extracted on the old page and falsely reported "the page has not finished loading"): Target.createTarget takes effect asynchronously +
refreshTabs resets a "selected not found in the list" back to tabs[0] + browser.selected is only a mirror of a session field,
three things stacked together; unified into openTabAndSelect() (wait until it actually appears in the list before switching), with a regression test pinning it down.
②"That Baidu link has never succeeded": the title link is baidu.com/link?url=, and all were mistakenly killed by the guard that "the site's own internal links do not count as results"
(which required ending with /link) —— 8 results, 0 returned, and it was even reported as "engine redesign";
now redirect links are retained and marked viaEngineRedirect, and the summary selector was supplemented according to actual measurement with [class=summary].
Two lessons: fixtures must copy the DOM actually measured, not my guesses (the first version of the Baidu fixture was guessed, so the tests were all green
and there were 0 results online); the selector table SEARCH_ENGINES was changed to export so tests can reference it directly, preventing "copying another version in the tests."
- v0.10.0: "Can understand, can capture, can search" + turning the calling scheme into a skill —— from 7 similar MIT plugins
(real equivalents found in the ecosystem index; all six paths of the 4 URLs the user gave were 404), distilled browser_read (four-level main-text fallback + noise stripping +
Markdown + 段落感知截断)、browser_scrape(item+fields 结构化抓取)、browser_search
(真实浏览器里搜 Bing/百度/DDG,跳转链解包 + 三态空结果判定 + 引擎冷却),零新增依赖;
同时修三处观察/动作层短板:ref 跨快照稳定(原来每次快照重排编号,"上次的 ref=7" 在页面重排后
会点到另一个元素)、快照内联状态并做交互恢复(React 的 onClick 是事件委托,DOM 上没有 onclick
属性,只按选择器收元素会漏掉一大片真按钮)、点/输入前做可操作性检查(含 elementFromPoint
遮挡检测 —— 参考实现两家都没有)与输入后回读校验;browser_snapshot 里内联人机验证识别
(Cloudflare/hCaptcha/reCAPTCHA/Turnstile/文本兜底),命中就停下问人。
调用方案落成 skills/browser-automation/SKILL.md(DSH 不会自动发现插件包里的 skills/,
由 index.js 调 skills.register 注册 + 免重启重扫路由)。新增 96 条真浏览器断言;
另修两处与本功能无关、但本机一直红的测试环境假设(改动前 HEAD 同样失败,已用 worktree 取信)。
- v0.8.3:「不动你正在看的页面」+「Chrome 起不来不是提权,是安全软件拦了 GPU 沙箱」 ——
① 接管你的浏览器时,"当前页未授权、目标站已授权"不再就地导航(那会把你正在读的页面换掉),
改成 chrome.tabs.create 新开一个标签页、附加到新标签页,你原来的页面一个字都不动;
扩展把真实 tabId 报回 host,host 据此纠正映射(否则列表显示"旧标签页已附加"、操作却落在新标签页上)。
② 更正 v0.8.2 的错误归因:Chrome 静默退出不是"DSH 以管理员运行"—— 本机用计划任务
(干净父进程、中完整性、脱离 DSH 作业对象)复测照样起不来,而加 --in-process-gpu 就正常;
真凶是火绒 HipsDaemon 拦掉 Chrome GPU 进程的沙箱初始化(Edge 因微软签名被放行)。
启动链因此改成"先常规试 → 失败补试 --in-process-gpu → 成功就记进 state.json 下次直接用",
并刻意不用 --no-sandbox(渲染器沙箱要留着),verify-launcher 有断言钉死这条底线。
- v0.8.2:「Chrome 起不来 = 整个插件不可用」+「中文用户名下浏览器根本拉不起来」 ——
启动链上两个只在真实机器暴露的坑。① VBS 独立启动器原来用 UTF-8 写、wscript 按 ANSI 读,
中文用户名(C:\Users\陈道云\…)被读成乱码路径 → 浏览器一个进程都不起,对外只报「未响应 CDP 端口」,
还在 C:\Users 下留下乱码目录;现在写 UTF-16LE + BOM。
② 首选浏览器起不来时不再全盘失败:findChromiumExes() 返回候选列表
(chromePath → 环境变量 → Chrome → Edge → Brave)逐个尝试并自动降级。
这条是实测逼出来的:DSH Desktop 以管理员身份运行时,Chrome 会因无法在提权父进程下初始化沙箱
而静默退出(加 --no-sandbox 才起,但默认不关沙箱),Edge 不受影响 —— 也就是说"装了个 Chrome"
反而可能让插件彻底不可用。同时:端口改成跳过"已有别的 CDP 在听"的(避免附加到错误的浏览器),
每个浏览器各自一个 profile 目录(chrome-profile-chrome / -msedge / …)。
新增回归测试 tools/verify-launcher.mjs(10 条)。
- v0.8.1:「我允许了目标站点,你却还在报未授权」 —— 用户浏览器档的判权用的是标签页当前 URL,
于是"允许了 github.com、前台却开着别的页"这种最常见用法必然被闸死。
现在 browser_open {use:"edge", url} / browser_navigate {url} 会把目标 URL 传进 Target.attachToTarget
的 intendedUrl,扩展在当前页未授权、目标页已授权时先导航、再附加(最多等 5s 落在已授权 origin)。
隐私底线不变:attach 永远在导航之后,agent 拿不到未授权页面的调试器。- v0.8.0:装扩展不再靠“看文档” —— 新增 browser_ext_setup,并把“桥没开”和“扩展没装”分开报。
- 背景:接管你的浏览器要三件事同时成立(桥开着 / 扩展装着 / token 粘对),而 v0.7.x 只把它们写成文字说明:
use:"edge" 在桥没开时也报“扩展还没连接”,把人引去装一个装了也连不上的扩展;
报错不给 extension/ 绝对路径、不说 token 在哪;面板在桥没开时显示空 token,步骤却照旧摆着。
- 新工具 browser_ext_setup:开桥(userBridge=true,即时生效)→ token 进剪贴板 →
打开目标浏览器的扩展页 → 资源管理器里打开本包 extension/ 目录,并把 5 步操作原样列出。
kind:'edge'|'chrome'(默认 settings.userDefault),open:false 只返回路径与 token、不动 UI。
- browser_open {use:"edge"} 的失败信息分两种情况:桥没开 → 指向 browser_ext_setup;
扩展没装 → 给出扩展页地址 + extension/ 绝对路径 + bridge.json 位置(token 在那)。
- 面板:桥没开时出现 「① 启用用户浏览器桥」 按钮(一键 PUT {userBridge:true} 并刷新状态),
安装步骤加序号,并注明“也可以直接交给 agent:调 browser_ext_setup”。
- /bl/bridge 的 hint 带上 extension/ 绝对路径;browser_open 在“一台都没接入”时提示该调谁。
- 边界(没变):「加载已解压缩的扩展程序」是原生文件选择框,无法自动化 —— 那一下永远得人点;
本版做的是把“点哪儿、选哪个目录、粘什么”全部摆到眼前,而不是绕过它。- v0.7.1(2026-09-11 修):真圆的方圆角豁免。DSH 主题自带全局规则
,:before,:after{corner-shape:var(--dsw-corner-shape)},而默认值是 superellipse(1.5);
新版 Chromium 支持 corner-shape 后,凡 border-radius:50% 的元素都会被画成方圆块
(用户报的是底图工坊的宝珠,本插件的 .bl-fab hover 底衬、.bl-dot、
独立网页 /bl/view 里的 .dot 同理)。宿主自己的圆形控件都写了 corner-shape:round 豁免,
本插件已照做(client.js 三处 + index.js 的 /bl/view 一处)。旧内核自动忽略该属性。
注意 index.js 是宿主侧文件:改完要重启桌面端,/bl/view 页面才会换新。
- v0.7.0:Chrome / Edge 同时可控,每次调用指定用哪台(use)
- 需求:日常两个浏览器(Chrome 153 / Edge 152)各有登录态,agent 要能操作 Edge 里已登录的页面,
而且要能在同一个会话里来回切,不必断开重连。
- 桥(bridge.js):单个连接槽(this.sock/this.ext)→ conns: Map,
每个 kind 一条连接、各自的 pending/seq/心跳(同 kind 重复连接保留最新,旧的以 4002 superseded 关掉)。
回包按 sessionId 的 : 前缀找连接再配对,找不到就忽略(不抛)。
kindOfBrowser 现在纯 kind 与完整 UA 两种形状都认(原先只认 UA,"opera" 这种族名会静默落进 unknown)。
- 扩展(extension/,v0.3.0):装载时按 UA 嗅探身份(Edg/→edge…)并在 hello 里上报;
所有跨 WS 边界的 sessionId 一律带 : 前缀(出口加、入口剥,内部仍用裸 id);
弹窗新增身份行 + 「允许当前所有标签页」(批量并入 origin,不打开 allowAll)+「撤销全部授权」。
- host(index.js):新增每浏览器一份会话(tabs / selected / cdp 分开存),
browser 单例退化为"当前活跃会话的视图",于是 19 个工具里上百处 browser.xxx 读法一行没改;
每个工具注入 use 参数(plugin/chrome/edge/user/auto,缺省沿用上一次);
/bl/state 与 /bl/bridge 新增 browsers[],观察窗红标带上浏览器名;settings.userDefault 决定 use:"user" 指哪台。
- 隐私/安全边界不变:仍是逐站点授权 + 「允许操作」开关 + Chrome/Edge 自带且无法隐藏的调试横幅。
多浏览器只扩大"能连几台",不放松任何一层。
- 行为修正(都是这次改造暴露出来的真问题):
· browser_close 之后 browser_open 接不回来(外层守卫抢在 browser_open 自己的 useSession/launch 之前判死);
· 没写 use 时若"上一次那台"已掉线,会一路报错 → 现在退回插件实例并记录一行提示;
显式写了 use:"edge" 而它掉线时仍然报错(明示意图不该被静默改写);
· use 取值校验用错了判定(isUserKind 把 firefox 当成"未接入的浏览器")→ 新增 isKnownKind;
· onBridgeStatus 的兜底分支曾把完整 UA 当 kind 塞进 Set(子代理 review 抓到)→ 改为读 browsers[].kind;
· backendMode='user' 时桥从 0→N 接入会把活跃会话切到你的浏览器(恢复 v0.6 的档位语义)。
- 验证:新增 tools/verify-browsers.mjs(35 项)——真 index.js + 真桥 + 真扩展,
两台"浏览器"各跑在一个 Worker 里(独立 realm = 独立 globalThis),断言"发给 Edge 的命令只打在
Edge 的标签页上""两台各自一份调试器会话""同 kind 重连只占一个槽""掉线不影响另一台"等串台类故障。
另有 tools/verify-bridge-v2.mjs(74 项)、tools/verify-extension-v2.mjs(146 项)。
五个套件合计 23 + 74 + 66 + 146 + 59 + 35 = 403 项全绿(v1 那两个套件也一并保留通过)。
- 记一笔踩过的坑(替身层面,但东西是真的):在同一个 realm 里加载两份扩展副本时,
模块级自由变量 chrome 指向同一个 globalThis,靠"副本开头重新赋值"隔离不了 ——
表现是 Chrome 扩展读到 Edge 的标签页,断言全绿但全是假的。两个 Worker 才是与"两个浏览器进程"同构的替身。
- 提醒:改 index.js / client.js 后要重启 DSH Desktop 才加载新代码与新的设置页控件。
- v0.6.2:把"免登录页走自带实例、授权后才切你的浏览器"这条默认路做成看得见的一档
- 背景:v0.6.1 已经有 backendMode='auto',但设置页只有「自带实例(推荐)/ 我的浏览器」两档,
文案也没写清"免登录页 vs 要登录的页"各走谁 —— 于是很容易被设成 user,
之后每一个免登录页面都撞逐站点授权(表现为"连 example.com 都开不了")。
- 设置页三档,标签直接写明用途:免登录用自带实例(推荐,=auto)/ 只用自带实例(=plugin)/ 只用我的浏览器(=user);
下面那行说明按档位分别讲清"免登录页去哪、要登录的页怎么办"。
- browser_open 的返回值新增 mode(当前档位)与 hint(下一步该 use:"plugin" 还是 use:"user"),
描述里也写明:auto/plugin 免授权、可 newTab;user 档逐站点授权、默认只读,
新开标签页自扩展 v0.3.2 起也支持(弹窗可控,仅 http/https)。
- PUT /bl/settings.json 改 backendMode 当场换后端(原来要等下一次工具调用才切);
桥/扩展还没连上时切 user 只存设置并回 backendWarn,等扩展接入后由 onBridgeStatus 自愈。
- tools/verify-host.mjs 跟着修:原来的用例假设“桥一连上就切 user”(v0.6.0 旧行为),在 auto 档下必然红;
改成先显式设 backendMode:'user' 再验用户浏览器那条链路,并新增两条用例
(auto 档下桥连着也不抢后端、PUT 切档当场生效)。47 项全绿。
- 提醒:改 index.js / client.js 后要重启 DSH Desktop 才加载新代码与新的设置页按钮(工具描述在插件装载时定型)。
- v0.6.4:能在插件实例里打开 DSH 自己的 GUI(browser_open {gui:true})
- 症状:在插件自带实例(独立 profile)里访问 http://127.0.0.1:/ 只会得到
dsh web authentication required; reopen the URL printed by dsh web.。
- 机制(@deepseek-ai/dsh-client-connection 的 authorizeIndex):GUI 根路径只认两样东西 ——
① URL 查询串里的一次性 launch token(?token=…,仅 GET /)会 303 落地并种一个按 Host 绑定的签名 cookie;
② 之后靠那个 cookie。插件 Chrome 是干净 profile,两样都没有 → 必然 401。
token 由进程生成、只在“DSH 启动时打印的 URL”里下发,凭据库(.credentials.yaml)只存 cookie 签名密钥,不存 URL。
- 改法:不绕过门禁,改用宿主自己的接口 —— 与 dsh-web-app 打印 dsh web URL 用的是同一个:
ctx.inject(['connection'], c => c.connection.authenticatedUrl('http://127.0.0.1:'))。
- 启动时取一次并缓存(拿不到只警告,不影响任何既有功能);
- 新增路由 POST /bl/gui {need:'gui-url'} → {ok,url};可带 base 换成 LAN 地址(同一套 token 规则重签);
- browser_open {gui:true} 用它打开 GUI;
- 取向:只认 POST + JSON(裸 GET 会被 / 这类跨站请求捎带上 token;POST+JSON 触发预检,
而本服务不返回 CORS 头,跨站读不到结果),且 token 不进入工具失败结果的错误文本(有测试钉住)。
- tools/verify-host.mjs 新增 6 项(400 / 405 / 正确 URL / LAN 重签 / gui:true 走到启动 / 不泄露 token),
套件 58 项全绿;为此把假 ctx 扩成 get/inject/effect 齐全,并给假 webServer 补上 port。
- 前提:宿主 web profile 里要有 connection 服务(本机 DSH Desktop 有;纯 headless 且没配它的场景拿不到 URL,
此时按提示改用“启动时打印的 dsh web URL”)。
- v0.6.3:让插件自拉实例真的有一个“能看见的窗口”(launchDetached)
- 症状(用户报回来的):免登录页在插件实例里打开一切正常(工具全返回 ok、观察窗有画面),
但屏幕上根本看不到那个浏览器窗口,只能靠观察窗看。user32 枚举证实:插件实例的 8 个 chrome
进程(同一 Windows 会话、headless=false、页面自报 outer 1440x900)没有任何一个持有窗口句柄,
而用户自己的 Chrome 有 —— 即窗口没在系统里注册,不是被最小化。
- 根因:spawn(exe, args, { detached: false }) 把 Chrome 拉成 DSH 进程树的子进程;在带作业对象/
受限桌面的宿主里,Chrome 的窗口不注册。detached: true 也不够(Windows 上子进程仍留在同一作业对象)。
- 改法:默认 launchDetached: true —— 写一个极小的 VBScript 启动器到数据目录
(launch-chrome-detached.vbs,WScript.Shell.Run(cmd, 1, False)),用 wscript 起 Chrome,
使它成为没有父作业对象的独立进程,从而拿到真实可见窗口。启动器写失败/起不来时自动回退
老的子进程 spawn,所以不会把浏览器整体打死。
- 顺手把“窗口是否可见”变成可观测事实:启动后用 CDP 的 Browser.getWindowForTarget +
Browser.getWindowBounds 自查(Chrome 自己报的,不依赖宿主能不能枚举 Win32 窗口),结论写进
观察窗动作条:🪟 ✓ 启动后自查:窗口存在且可见(windowId=…,state=normal,1440x900);
窗口尺寸小于设置值时自动 Browser.setWindowBounds 修正。
- browser_close / shutdown 在独立启动下只走 CDP Browser.close(proc 此时是启动器,杀它没用);
process.on('exit') 也不再对启动器做无意义 kill。
- 新增 tools/verify-launch.mjs(14 项):VBS 生成逐条断言(含空格的 Program Files 路径、
带空格的 --user-data-dir、参数含引号时的 "" 转义、Run 的可见样式=1/不等待)+ 假 CDP
(HTTP /json/list + 真 WS 帧)验证窗口自查的三种结局(可见 / 最小化 / 不可达)。
这条测试当场抓出一个真 bug:writeDetachedLauncher 假设数据目录已存在,全新安装首次启动会抛错
(已修:先 mkdirSync)。npm test 现含 4 个套件(23 + 65 + 47 + 14 = 149 项)。
- 局限(说清楚):沙箱终端里无法验证窗口真的出现(该环境禁止从 shell 拉起 Chrome:
Start-Process / cmd start / wscript 三条路径的 Chrome 都没起来、连 profile 目录都没被创建),
所以这条改动靠“启动后自查 + 你实际看一眼”来确认;不行就把设置里 launchDetached 设回 false
即回到老行为。
- v0.6.1:默认不再抢占你的日常浏览器(backendMode)
- 问题:只要桥连着,onBridgeStatus 就把后端切成“用户浏览器”,于是连不需要登录态的页面
也会撞上 P0 的逐站点授权(没在扩展里点过「允许」就直接报“站点未授权”)——
实际上插件自拉实例本来没有这层限制,白白绕远了。
- 新增设置 backendMode(设置页「用哪个浏览器」,三档):
- auto(默认)= 默认用插件自拉实例(独立窗口 + 独立 profile,不碰你的浏览器、不受逐站点授权限制);
只有确实需要“你的登录态”时,由 agent 显式 browser_open { use: "user" } 切到你的日常浏览器。
- plugin = 只用插件实例。
- user = 与 v0.6.0 旧行为一致:默认就在你的浏览器里操作。
- browser_open 新增 use 参数,返回值里带上实际用的是哪个浏览器(browser 字段)。
- 桥连着不再等于“必须用它”;切后端会重建标签页清单(两个浏览器的 tab 列表本来就不通用)。
- 迁移:默认值保持“自带实例”,因此升级后你原来的 user 行为会变。若你就是要 agent 一直在
你的浏览器里干活,去设置页点一下「我的浏览器」(或把 settings.json 的 backendMode 设为 "user")。
- v0.6.0:P1 —— 真输入接管(点击/打字/按键/滚轮/上传)
- 扩展新增「允许操作」开关(默认关、持久化):关着时 Input. 与 DOM.setFileInputFiles
一律拒绝并提示去弹窗打开;打开后经 chrome.debugger 注入(isTrusted:true),
拟人鼠标轨迹照旧生效。前提仍是逐站点授权 —— 没授权的站点连调试器都附加不上。
- 输入前自动把目标标签/窗口置前(chrome.tabs.update + chrome.windows.update,
并等焦点真正到手 + 300ms 稳定期):实测后台标签、失焦窗口下 Chrome 会静默丢弃鼠标事件,
键盘偶尔能过 —— 不置前就会出现“工具说 ok、页面没反应”。只读命令不抢前台。
- 开关状态实时回传 host:/bl/state、/bl/bridge、设置页、观察窗红标(只读→「正在读取」,
可操作→「正在操作你的日常浏览器(可点击/打字)」)全部同步。
- 弹窗新增「已授权 N 个站点 / 允许操作:开|关」状态行 + 操作成功的绿色提示 + 消息失败自动重试
(MV3 SW 休眠时第一条消息会丢,旧版把错误吞了,表现为“点了没反应”)。
- host 加固:撤销授权后清掉本地缓存的 url/标题(原来会把打码前的真实 URL 留在 /bl/state 里)、
监听扩展 detach 事件作废 sessionId、遇到「会话已失效」自动清 session 重试一次。
- 验证:verify-bridge 23 + verify-extension 65 + verify-host 43 = 131 项,npm test 全跑;
并在真实 Chrome + 番茄渠道商后台用真点击/真 ctrl+a/真打字跑通(事件全 isTrusted:true)。
- 仍未做:chrome.downloads 接管(browser_downloads 在用户浏览器模式下仍指向插件目录)、
收窄 Runtime.evaluate、与 approval-gate 联动。
- v0.5.0:接管你自己的浏览器(P0 只读)—— 扩展路线落地
- 新增 bridge.js(WS 桥:只绑 127.0.0.1、token 握手、Origin 只认 chrome-extension://、
心跳 + 单实例顶替、端口顺延、bridge.json 持久化 token)与 extension/(MV3 扩展:
service worker 当“反向 CDP 客户端”、chrome.debugger 转发、弹窗逐站点授权 + 红点角标)。
- host 侧零改调用面:browser.cdp 直接换成桥对象,17 个工具的 send/on/alive/close 语义不变;
桥断自动回退插件实例;browser_close 在用户浏览器模式下只拆调试器。
- P0 硬拒:Input.* / DOM.setFileInputFiles / Target.createTarget|closeTarget /
javascript: 与 file: 导航;未授权站点的 url/title 对 agent 打码。
- 设置页新增「用户浏览器」开关(即时生效)+ 端口/token 展示与复制;/bl/bridge 路由;
/bl/view 与 /bl/state 带 backend,用户浏览器模式下常驻红标。
- 验证:tools/verify-bridge.mjs(23 项)、tools/verify-extension.mjs(41 项,mock chrome 跑真扩展)、
tools/verify-host.mjs(29 项,真 index.js + 真桥 + 真扩展代码)。npm test 全跑。
- 未做(P1/P2):输入接管、下载事件、Runtime.evaluate 收窄、与 approval-gate 联动。
- v0.4.3:设置页开着时,内嵌面板自动收进独立网页 /bl/view
- 浮球(v0.4.2)沉底就够好看了,但那块 560px 的窗沉下去等于看不见 —— 所以面板换做法:设置页一开
就 foldPanelToStandalone()(收面板 + 弹独立页),两边各看各的。
- 离开设置页 unfoldPanelIfNeeded():独立页已被你关掉才还原面板;还开着就不动,避免同一画面
出现两份。window.open 被拦(非用户手势有可能)时不收,面板留着还能看。
- showPanel() 的判断从 S.liveView 扩成 S.liveView || S.settingsUi > 0:所以在设置页里
agent 冷启动浏览器时也是直接弹独立页,不会先摊一个面板在设置内容上。
- 验证:03-调试临时\verify-bl-liveview.mjs 19 项(真 React SSR + 假 DOM/假接口,用一个
立即执行 effect 的 React 垫片把"挂载/卸载设置页分区"变成可驱动的测试点),覆盖
"panelWanted → 面板摊开" → "打开设置页 → window.open('/bl/view') + 面板 display:none" →
"独立页被关 + 离开设置页 → 面板还原 flex" → "弹窗被拦 → 不收面板"。
同一次排查还发现测试桩自己踩的坑:S.liveView 只在 boot/loadCfg/put 三条路径刷新,
光改盘上副本不生效 —— 断言前要先借一次设置页挂载让它重读。
- v0.4.2:浮球让位从"消失"改成"沉底"(用户反馈 v0.4.1 隐藏的做法"效果还是很差")
- 不再 display:none。改成量出盖住浮球那层的 z-index、把浮球降到它 − 1,于是浮球和壁纸宝珠
一起被设置页那层半透明 + backdrop-filter 的遮罩糊掉:位置不动、轮廓还在,只是沉在下面。
- 层级不用猜也不用写死:overlayZAt() 在浮球中心 elementsFromPoint → 跳过我们自己的元素 →
只认覆盖 ≥1/4 视口的层 → 沿祖先链取最大数值 z-index。遮罩撤掉后 zIndex='' 回到样式表基值。
- 兜底球(无 slots 环境)的 position/z-index 从行内样式搬进 .bl-fab-fallback 类 —— 否则
还原时把 style.zIndex 置空会把基值一起清没,浮球会真沉到侧栏底下去(这是改的过程中发现的)。
- 量不到层级时退回 .bl-fab-ghost:opacity .3 + blur(2px) saturate(.7) + 一块自带
backdrop-filter 的小玻璃板(::before),仍然是"一层影",不会硬压在设置内容上。
- 验证:03-调试临时\verify-bl-liveview.mjs 16 项,含"沉底时 display 绝不能是 none"、
"z 变成 1999"、"量不到→ghost"、"关掉→全部还原";另用真 Chrome 跑
03-调试临时\bl-fab-sink-probe.html 验证机制本身:命中链 ["#fab","#scrim","html"] →
量到遮罩 2000 → 设 1999 后该点最上层变成 #scrim(浮球确实在磨砂之下),置空后回到 2147483049。
- v0.4.1:两个用户报回来的 bug
- 选了「独立网页」却掉回内嵌面板:根因是客户端 api() 返回的是 Response,而
fetchSettings() / loadCfg() 两处直接把它当设置对象读 —— j.liveView 恒为 undefined,
于是盘上明明存着 standalone,开机算出来仍是 panel:地球钮弹内嵌面板,设置页里
「内嵌面板」还一直被画成选中态。改成先 .json()(并带 cache:no-store),选中态在
cfg 未落地前用已知状态顶上、不再闪成面板;put 换形态时新增 applyLiveView()
当场交接(原来开着窗 → 立刻在两种形态间搬迁,不重复开窗)。
- 左下角 🌐 浮在设置页之上:叠列地球钮与兜底浮球都是 position:fixed + 近上限 z-index。
新增 fabShouldYield()/syncFabYield():设置页开着(以本插件 settings.section 挂载为信号)
或页面有 role=dialog/aria-modal 弹层时隐藏浮球,弹层关掉自动回来。
(这个"隐藏"在 v0.4.2 被"沉底"取代 —— 判断信号沿用,做法换了。)
- 验证:03-调试临时\verify-bl-liveview.mjs 11 项真 React SSR + 假 DOM/假接口,覆盖"存着
standalone 时设置页必须高亮独立网页"与"弹层开→浮球消失→弹层关→浮球回来";另用
03-调试临时\make-bl-buggy.mjs 复原旧写法跑同一测试,确认它会红(3 项 FAIL),
不是只会绿的摆设。
- v0.4.0:观察窗双形态(可拖动贴边的内嵌面板 + 独立网页 /bl/view,设置项 liveView)、
代理设置映射 Chrome --proxy-server、面板本会话被关后不再自动重开、与底图工坊宝珠叠列共存。
更早历史见 git log。
许可与致谢
- 本插件源码:MIT © 2026 Raylen-berry(作者 / 维护者)。
- 灵感来源(原作者):dsh-ego-browser,
MIT © Fisfzy and dsh-ego-browser contributors。其"把 agent 接进 DSH 的真浏览器 + 观察窗"
设计(含 ego-lite 运行时,© CitroLabs / ego-lite contributors,MIT)证明了这条路走得通。
- v0.10.0 蒸馏来源(均 MIT;页面侧算法全部按浏览器 DOM 独立重写,搜索引擎 URL/选择器与
验证码特征属事实性数据):dsh-read-url
(正文降级顺序 / 链接密度阈值 / 段落感知截断 / Markdown walker 细节 / JSON-LD 兜底)、
wqty123/dsh-browser(选择器@属性 抓取约定 /
人机验证特征集与判定顺序)、Lum1104/dsh-browser
(WeakMap 稳定编号 / 无障碍名字优先级 / 输入后回读 / 页面内容按不可信数据处理)、
Tencent/BrowserSkill(技能即调用方案的组织骨架)、
modsearch · dsh-free-search ·
dsh-web-search-pro(引擎 URL 与选择器 / 空结果三态判定 / 冷却)。
- 本仓库是独立自研的重实现:不含 ego-browser / ego-lite 的任何一行代码,不携带其运行时 vendored 源码。
v0.10.0 起为诚实起见,NOTICE 里明确声明了两类“不是纯概念借鉴”的东西:事实性数据
(引擎 URL、SERP 选择器、验证码特征)与按 DOM 重写的算法(页面侧函数),并附上各自的 MIT 许可全文。
- 上游未采用的部分也写清楚:dsh-read-url 的可选 @mozilla/readability 升级路径是 MPL-2.0,
本插件不引入任何 npm 依赖,所以那条路直接关闭(好消息是那些判定逻辑本就是它的自研零依赖部分)。
感谢 ego-browser 作者把“接进 DSH 的 agent 浏览器 + 观察窗”这条路趟通。
完整署名与上游声明见仓库根目录 NOTICE 文件。