dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
为 DSH 挂出完整的浏览器工具面(33 个 browser_* 工具):把 ctx.browser 接缝接到自管的 headless Chromium(经 127.0.0.1 单例 CDP 守护进程,带 Bearer token 鉴权、请求路径白名单与 WebSocket 地址改写,不需要 root / GUI 库 / Electron);另带一个网页面板——可直接点、滚、打字的实时页面,操作/控制台/网络日志与一键清理,窗口外观(边框色、透明度、玻璃效果)与几何(贴合画面比例去黑边、顶部留白、默认居中)可自定义,页面分辨率可调(默认 1920×1080)——收起后的胶囊可拖到任意位置(自由位置),切回「网页」自动续帧不用再点「开始」,实时画面「最大边」默认 1920——外加两份随包技能(browser、browser-runtime),由插件经 DSH 内置技能提供者提供,技能中心显示「系统内置」,默认不往 $DSH_HOME/skills 落盘。
给 DSH 一个能跑在无 root、无 GUI 容器里的浏览器。 一个包、一条 dsh plugin add 装完即用:单例 CDP 守护进程(用户态 chrome-headless-shell,只绑回环、带 Bearer token 门)+ 完整的 33 个 browser_* 工具面 + ctx.browser 接缝的 cdp-daemon provider + 一个网页浮动窗口(实时画面可点可打字)+ 两份随包全局技能。不需要 Electron、不需要 GUI 库、不需要 root、没有构建步骤。
English TL;DR — A single-package browser backend for DSH. One tarball ships a singleton CDP daemon (user-space
chrome-headless-shell, bound to loopback behind a Bearer-token proxy), the full 33-toolbrowser_*surface, thectx.browserseam providercdp-daemon, an in-page floating panel (live interactive view + logs + appearance settings) and two bundled global skills. No root, no GUI libraries, no Electron, no build step.dsh plugin --profile web add dsh-browser-service@latest # then restart DSH
一处入口,一个窗口。 装完插件、重启一次 DSH,页面左上角就多出一枚小胶囊(贴哪个角、偏多少都能改,也可以直接拖到任意位置)。点开就是浏览器窗口:左边一竖排入口(网页 / 操作 / 控制台 / 网络 / 设置),中间是能点、能滚、能打字的实时画面,右下角能拖宽窄,标题栏能拖、双击能最大化。
| 实时画面(默认入口):能点、能滚、能打字 | 在真 DSH 界面里:居中、贴合画面比例、没有黑边 |
|---|---|
![]() | ![]() |
| 设置区:分辨率、顶部留白、胶囊偏移、窗口外观 | 网络日志:手动清理 + 自动清理上限 | 收起后的胶囊:位置与外观都跟随设置 |
|---|---|---|
![]() | ![]() | ![]() |
取帧曾经漏发 maxh,被服务端当成 0 夹到下限 240,于是 1920×1080 的页面被等比压进 max×240 的小框、再被界面放大。0.8.2 起:缺字段沿用当前值、最大边同时封顶宽高、大屏页面按原尺寸下发。

| 现状问题 | 本项目的做法 |
|---|---|
DSH 内置 dsh-builtin-browser 的 provider 是自托管 Electron:没有 Electron 二进制、没有 GUI 库、seccomp 下 No usable sandbox! | 用系统已有的 chrome-headless-shell,--no-sandbox --disable-dev-shm-usage,库用用户态目录,不需要 root |
| 每个消费方各起一个浏览器(内存重复) | 一个守护进程,多客户端通过 CDP 连入,会话用隔离 BrowserContext 分开 |
| CDP 端口若对外监听 = 把浏览器(含 cookie)交给主机上任意进程 | 内核只绑 127.0.0.1:<内部随机端口>,对外只暴露本机回环代理(要求 Bearer token,只放行读元数据与 /devtools);代理顺带精确统计连接数 |
dsh-univer-office 只接受「浏览器可执行文件路径」,无法连远端 CDP | 同一个内核外面套一层包装脚本当 browserExecutablePath(同一份二进制与库,不共享进程) |
# 一条命令(推荐;可重复执行)
dsh plugin --profile web add dsh-browser-service@latest
# 钉版本 / 离线分发(同一个包):Release 资产或本地 tarball
# dsh plugin --profile web add https://github.com/lyp88997/dsh-browser-service/releases/download/v0.8.2/dsh-browser-service-0.8.2.tgz
# dsh plugin --profile web add ./dsh-browser-service-0.8.2.tgz
# 然后重启 DSH(插件在 boot 时 import,热重载不可靠),再校验组合:
dsh --profile web --dump-config | grep -E 'browserProvider|# == dsh-browser-service|not found'
期望看到 browserProvider: cdp-daemon、# == dsh-browser-service 层;browser-electron / playwright-browser 两条守卫行会打印 not found(只装本包时这两个 id 不存在,无害)。
装完这一个包就有什么
browser_* 工具(id: tool-browser):工具面与接缝来自本包依赖 dsh-builtin-browser,经本包 ./browser / ./tool-browser 转出口暴露(plugin/shims/)。cdp-daemon provider:接到自建守护进程,首次用浏览器时用本包自带的 bin/browsersvc.mjs start 自动拉起。browser、browser-runtime,由插件注册的内置技能提供者提供,技能中心里显示「系统内置」。三条不要做的事
dsh-builtin-browser:它若同时是组合包,browser/tool-browser 两个 id 会被插两次、33 个工具重名挂两遍。已装过就先 dsh plugin --profile <name> remove dsh-builtin-browser。dsh-browser-cdp 当裸包名(v0.4.0 之前子包的名字):npm 上的 dsh-browser-cdp 是别人的同名包(drscrewdriver,0.17.4),写了就装到别人家。本包自 2026-09-27 起以 dsh-browser-service 发布,当前版本 0.8.1。profiles/<name>/cordis.patch.yml 的 insert 行会重复(写法见 docs/profile-patch.browser-service.yml,只在改源码时临时用)。组合(plugin/cordis.patch.yml)自动做四件事:插入接缝 browser(选 cdp-daemon)、插入 tool-browser、插入 browser-cdp provider、关掉内置 browser-electron 与 dsh-playwright-browser(后者自带 10 个与内置同名的 browser_* 工具,两个 provider 的工具面不能共存)。
不需要配置 autoStartCommand:端点不通时插件默认用本包自带的 bin/browsersvc.mjs start 拉起一次(plugin/lib/provider.js 的 defaultAutoStartCommand())。想换端口/内核,再在 profile 的 cordis.patch.yml 里覆盖(patch 是整行替换 config,要重述 cdpUrl):
- id: browser-cdp
config:
cdpUrl: http://127.0.0.1:9333
autoStartCommand: node /home/you/dsh-browser-service/bin/browsersvc.mjs start --port=9333
browsersvc 默认空闲 15 分钟自杀(--idle-ms,上限 24 小时);插件自启时默认用 5 分钟(配置键 idleMs)。最后一个会话关闭后插件会主动断开 CDP 连接(P1),守护进程才可能真的空闲退出;下一次调用浏览器会自动把它拉回来(F25,见 CHANGELOG)。
⚠️ 改完必须重启 DSH(插件在 boot 时 import,热重载不可靠):运行中的完整 web profile 上
patchReload: live会静默回滚(进程 stdout 归 docker,看不到报错),干净进程里 boot 完全正常。
# 升级(@latest;钉版本就用 Release 资产 URL 或本地 tgz)
dsh plugin --profile web add dsh-browser-service@latest
# 卸载(依赖与层一起清)
dsh plugin --profile web remove dsh-browser-service
# 顺手停掉守护进程(可选)
node "$(dsh --profile web --dump-config >/dev/null 2>&1; echo ~/.dsh/profiles/web/node_modules/dsh-browser-service)/bin/browsersvc.mjs" stop
版本兼容:插件与 browsersvc 必须同版本升级。v0.3.0 起公开端口要求 Authorization: Bearer <token>,旧插件对新守护进程会在 401 上失败。升级顺序:先换插件(重启 DSH),再 browsersvc restart。
@latest在「已装版本仍满足依赖范围」时不会升级;要确保换版本就用 Release 资产 URL 或本地 tgz 钉住。
从 0.3.x 升到 0.4.x 及以上:交付物从「工具包 + 插件子包」变成一个包,旧装法留下的两个包要清掉:
dsh plugin --profile web remove dsh-browser-cdp # 旧子包名(本地 tarball 装的话)
dsh plugin --profile web remove dsh-builtin-browser # 现在由本包依赖提供
dsh plugin --profile web add dsh-browser-service@latest
装上之后首次调用浏览器时插件会自动拉起守护进程,所以这一步通常可以跳过;想手动起、或要固定端口/内核时:
# 1) 看本机能用哪个内核(会依次看环境变量、常见落点、PATH)
node bin/browsersvc.mjs detect
# 2) 起服务(默认端口 9333,root 默认 $DSH_HOME/browser-service)
node bin/browsersvc.mjs start --port=9333 --idle-ms=900000
# 3) 验证
node bin/browsersvc.mjs status
TOKEN=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.DSH_HOME+"/browser-service/service.json","utf8")).token')
curl -s -H "Authorization: Bearer $TOKEN" 127.0.0.1:9333/json/version
# 4) 停
node bin/browsersvc.mjs stop
本机(DSH 容器)实测可用的一行:
node bin/browsersvc.mjs start \
--wrapper=/home/node/DSH/.browser/chromium-wrapper.sh \
--port=9333 --idle-ms=900000
--wrapper 指向的包装脚本负责注入 LD_LIBRARY_PATH(用户态 43 个包)、FONTCONFIG_FILE(中文渲染)、并追加 --no-sandbox --disable-dev-shm-usage。没有包装脚本时,用 --kernel=/path/to/chrome-headless-shell 直接指定内核,但必须保证系统库与字体可用(否则截图纯白、中文变方块)。
dsh-univer-office 用- id: univer
config:
browserExecutablePath: /home/node/DSH/.browser/chromium-wrapper.sh
保持现状即可:univer 用同一份二进制与库,自己起临时实例(它没有 connectOverCDP 能力,分析见 docs/design-notes.md)。
browsersvc)| 命令 | 说明 |
|---|---|
detect | 打印候选内核与已解析配置 |
start | 后台拉起守护进程并等到 CDP 就绪(已运行且健康则直接返回) |
stop | 先停 supervisor,再兜底清理浏览器进程与状态文件(动手前校验 pid 身份,--force 跳过) |
status | 打印状态;健康退出码 0,未运行 1(便于脚本判断) |
restart | stop + start |
run | 前台运行(start 内部用它做后台进程) |
logs [--lines=60] | 打印日志尾部 |
ops [--lines=20] [--json] | 最近的操作(工具动作、耗时、成败、会话/标签、错误原因)——数据来自 <root>/ops.jsonl |
console [--lines=40] [--json] | 页面控制台输出(log/error/warning + pageerror)——<root>/console.jsonl |
network [--lines=40] [--json] | 网络请求(method/url/状态码/耗时,不记头与体)——<root>/network.jsonl |
har [--session=s1] [--out=file] | 会话关闭后落盘的 HAR(<root>/har/,只留最近 10 份),--out 复制出去 |
cookies [--url=…] [--json|--export=file|--import=file] | cookie + localStorage 导出/注入到文件(--export 0600 且拒绝覆盖;--import 需有活会话) |
skills [--install] [--uninstall] [--force] [--dir=…] [--json] | 随包技能:不带 --install/--uninstall 只报每个文件的状态;--install 落盘(可选,默认 $DSH_HOME/skills);--uninstall 撤掉落盘那份——见「随包技能」 |
参数:--root --port(0 = 自动择取)--idle-ms --kernel --wrapper --user-data-dir --max-restarts --start-timeout --internal-port-base,对应环境变量 DSH_BROWSER_SVC_ROOT / DSH_BROWSER_SVC_PORT / DSH_BROWSER_SVC_IDLE_MS / DSH_BROWSER_CHROME / DSH_BROWSER_WRAPPER,也可写进 $ROOT/config.json。优先级:CLI > 环境变量 > config.json > 自动探测。skills 另有 --dir / --force / --json。
browser_*)工具面与内置 dsh-builtin-browser 完全一致(同一份 tool-browser 模块,经本包转出口挂出),所以 browser_* 的语义、参数与 target {by: css|text|xpath} 定位方式都沿用内置文档。按用途分组:
打开与导航
| 工具 | 说明 |
|---|---|
browser_open | 打开 URL(只接受 http(s);首次调用会按需自启守护进程) |
browser_wait | 等到页面就绪:load complete,可等期望 URL 或某个 CSS 选择器出现 |
browser_back / browser_forward | 历史后退 / 前进(无历史时是 no-op) |
browser_refresh | 重新加载当前页 |
browser_challenge | 检测人机校验(Cloudflare / reCAPTCHA / hCaptcha / Turnstile)是否挡住当前页 |
读取页面
| 工具 | 说明 |
|---|---|
browser_a11y | 读无障碍树(语义角色 / 名称 / 状态,穿透同源 iframe 与 shadow root)——理解页面结构的首选 |
browser_snapshot | 可交互元素快照(带编号,供视觉/文本驱动) |
browser_content | 取页面内容:html / markdown / txt / json,可按选择器限定范围 |
browser_scrape | 列表页结构化提取:给容器选择器 + 字段映射(sel@attr,a@href 取绝对地址)。item 必须是容器:字段选择器是在 item 内部查找的,所以 item: img + img@src 取不到值,要写 item: body |
browser_screenshot | 截屏(PNG/JPEG、fullPage、等比缩小、可落盘) |
browser_get_value | 读单个输入/文本域/下拉/可编辑区的当前值 |
操作与表单
| 工具 | 说明 |
|---|---|
browser_click | 点击:语义定位(css/text/xpath)或视口坐标(配 browser_screenshot 用) |
browser_type | 向聚焦元素插入文本(browser_key 发 Enter/Tab/方向键等) |
browser_set_value | 设值(原生 setter + input 事件,React/Vue 受控组件也生效) |
browser_fill | 一次填一张表单(fields[] 支持 selector / name / label / placeholder,可提交) |
browser_check | 勾选/取消 checkbox、radio |
browser_select | <select> 选一项(按 value / 可见文本 / 序号) |
browser_clear | 清空输入框,或取消勾选 |
browser_scroll | 滚动(像素增量 / 滚到某元素 / 到顶到底) |
browser_execute | 在页面里执行 JS(传表达式或函数,可带参数)——驱动页面的主力 |
browser_key | 发送单个命名按键 |
标签页与会话
| 工具 | 说明 |
|---|---|
browser_list_tabs | 列出标签页(id / url / title / 是否活动) |
browser_switch_tab | 按 id 切换标签页 |
browser_close_tab | 按 id 关标签页(关活动页会激活下一个) |
browser_reset | 关掉全部标签页,开一个空白页重来 |
browser_session | 查看本任务的浏览器会话(id 与标签页) |
browser_reset_session | 重置本任务的浏览器会话(进程坏了/连不上时的兜底) |
文件、凭据与调试
| 工具 | 说明 |
|---|---|
browser_download | 下载 URL 到本地文件(保留会话 cookie 与登录态) |
browser_auth | 导出/恢复 cookies(登录态可保存到私有文件、重启后恢复) |
browser_history | 列出本会话已执行的操作(navigate/execute/click/type,含成功与错误) |
browser_replay | 按历史序号重放某一步 |
browser_restrict | 限制允许的浏览器动作(软护栏,防误点误导航) |
同一个包还带一个网页浮动窗口(收起时是页面上的小胶囊,点开是卡片)。它不新增任何工具,也不改 33 个 browser_* 的行为。左侧一竖排入口:网页 / 操作 / 控制台 / 网络 / 设置。
按住标题栏拖动、拖右下角手柄缩放、双击标题栏或 ⤢ 最大化/还原、收起 回到小胶囊(默认左上角,设置里可换到任意一个角或「自由」位置,并可用水平/垂直偏移把它挪开几个像素;胶囊本身也能直接拖到任意位置,松手即记住);位置与大小记在浏览器本地(localStorage,设置里可关)。窗口被拉出视口或浏览器窗口缩放时,会自动收回可见范围;胶囊同理——屏幕变小时按当前视口夹回屏内,但只夹不改存,视口放回去它自己就回到原处。
设置页第一段「窗口外观」,三项:
#rrggbb 形状,别的一律回落主题色(这些值会写进 CSS 变量,所以先卡一道)。color-mix(in srgb, 主题底色 N%, transparent),所以底色仍随暗/亮主题走,只改实心程度,不会把界面调成怪色。backdrop-filter: blur(10px) saturate(1.15),默认)/ 液态玻璃(blur(18px) saturate(1.65) 再加一道斜向高光与内圈描边光)。纯 CSS 近似,不做真折射;老浏览器上退化成半透明。窗口和收起后的胶囊用的是同一份外观;窗口「里面」也在同一份外观里——标题栏、左侧入口竖排、底栏、列表吸顶条、保存栏都按同一个不透明度透出去,选了自定义边框色时这些分区的分隔线也用同色调一层浅的(45%)。「跟随主题」/「无边框」时内部分隔线保持宿主原本的细线,无边框 只去掉最外圈那一条。
为什么专门写这一条:只把最外壳做成透明、而里面仍是实心主题底色时,看起来「只有边缘那条线在变」——这是真实反馈过的现象,修法就是把内部分区也接到同一份分层底色变量上。
改任何一项都会立刻先按新值显示(方便预览),但只有点顶部吸顶栏的**「保存」**才写进浏览器本地并长期保留:
● 有未保存的改动(窗口外观已先按新值显示),同时左侧「设置」入口和收起后的胶囊上有一个小圆点提醒;✓ 已保存:窗口外观立即生效,刷新页面也保留。;保存失败:这个浏览器不让写本地存储(无痕模式或站点策略),改动只在本次打开有效。,草稿不会被丢掉;直接把浏览器当前标签的画面流到卡片里,你能看到的那个页面就是真页面——在窗口里点按钮、滚轮滚动、键盘输入、地址栏回车跳转,都会被原样打进真浏览器;对话里助手的调用与你在窗口里的操作作用在同一个页面上。
maxTabs 默认 5,助手用 browser_open / browser_switch_tab 控制),窗口只显示 session.active 指向的那一个——也就是最近被打开/切换/操作的标签;面板目前没有切换标签的按钮,要换标签得让助手切(live.json 也暂不回标签清单)。plugin/lib/liveview.mjs:Page.startScreencast(JPEG,默认质量 70;「最大边」随请求下发、客户端默认 1920——0.8.3 起与默认视口同长边,0.8.2 及以前是 1280,服务端缺省回退仍 1280;质量默认值在 0.8.2 从 85 下调到 70)经我们的回环代理下发,只在画面变化时发帧(静止时零流量,实测一帧约 6.8 KB);操作走 Input.dispatchMouseEvent/insertText/dispatchKeyEvent/mouseWheel。画质与最大边由客户端按当前设置随取帧请求发下去(?quality=&max=&maxh=,客户端把「最大边」同时当作宽与高的上限,即长边上限;缺字段沿用当前值、越界由服务端夹到安全范围),改完下一次取帧即生效。
max × maxh 的框里」,只给 max 不给 maxh 会让大屏页面被压扁变小,画面看着就糊(0.8.1 的 P7 修掉的真缺陷:缺 maxh 时高度上限被算成下限 240)。origin/referer 与 Host 同源),方法白名单 405。若你从别的机器直连 DSH Web,日志入口照常、实时窗口会被拦。DELETE 停流;宿主半边 30 s 没收到取帧也自动停(setInterval 已 unref)。「操作 / 控制台 / 网络」三个入口的数据就是上面三个 JSONL 的尾巴(?lines= 默认 40,上限 200),所以 CLI 与窗口看到的是同一份真相;轮询间隔按设置(默认 2.5 s),页面不可见时停轮询,卡片上有「暂停/继续」「清理」与「收起」。
POST /browser-service/logs 的 clear,同样的回环/同源/方法三道闸);trim),只在面板打开着的时候检查;已自动清理:网络 12000 → 500 / 清理网络日志不可用:宿主没有这条路由(HTTP 404)——宿主半边是旧版,重启 DSH 后生效),不会再出现「设了没反应」。没写协议会自动补 https://(与服务端 normalizeUrl 同规则);一个浏览器会话都没有时会自己开一个再跳(以前这条路径只会静默失败);宿主拒绝的原因会显示在地址栏正下方,不再是「点了没反应」。
POST /browser-service/viewport 真改浏览器视口,已打开的页面会按新尺寸重排、之后新建的会话也用它,服务端夹在 640×360 ~ 3840×2160 之间;回执是 ✓ 已生效 W×H(已应用到 N 个页面)左上/右上/左下/右下/自由(0.8.3 起)五档 + 水平/垂直偏移(贴角档 0–400 px,默认 15/48,按选中的角生效);选 自由 时这两行改名水平坐标/垂直坐标(按屏幕坐标存,上限跟着当前视口),也可以直接把胶囊拖到任意位置——松手自动切到「自由」并立即记住,鼠标按住拖动期间是抓手光标,原地单击(位移 ≤ 4px)仍然是展开面板zBase、窗口 = zBase + 1)'auto' 或 0–200px,默认 'auto'——窗口顶部让开宿主顶栏的高度;「自动」=实测宿主贴顶的固定/粘性条(量不到按 48),也可手填覆盖)0.8.2 把默认参数调成了:画面质量 70、打开即取帧 关、浏览器窗口分辨率 1920×1080、背景不透明度 40% + 毛玻璃(宿主的
viewportWidth/viewportHeight默认也同步改成 1920×1080),同批还做了窗口几何三项:贴合画面比例(默认开,去黑边)、顶部留白(默认自动,窗口让开宿主顶栏)、没记过位置时摆正中。既有浏览器里已存过的设置不受影响——默认值只在「从没存过这一项」时生效;想回到默认就在设置页点「恢复默认」再点「保存」。
0.8.3 又调了两处:「最大边」默认 1280 → 1920(与默认视口 1920×1080 同长边,画面不再先缩后放);取帧开关记住状态——点过一次「开始」后,切到别的入口再切回会自动续上,手动「停帧」后回来仍是停的。
控制台与网络的录制开关属于插件配置(captureConsole/captureNetwork),改插件配置要重启 DSH。
宿主半边 plugin/lib/panel.js 在 ctx.webServer 上挂六条 exact 路由(与面板同源):
| 路由 | 方法 | 作用 |
|---|---|---|
/browser-service/panel.json | GET(HEAD 也允许) | 只读数据:日志尾巴 + 服务信息块(no-store,载荷不含本机绝对路径,下载目录只回目录名) |
/browser-service/live.jpg?since=N[&quality=&max=&maxh=] | GET | 实时帧(长轮询,最多等 1.5 s,只回比 since 新的帧,没有新帧回 204) |
/browser-service/live.json | GET | 地址栏/标题/推流状态与生效中的取帧参数(每秒同步) |
/browser-service/live | POST / DELETE | 转发操作(down/up/move/wheel/text/key/reload/goto)与停流 |
/browser-service/logs | POST | {action:'clear'|'trim', kind, keep} 清空/裁剪观测日志 |
/browser-service/viewport | POST | {width,height} → provider.setViewport(),回 {ok,viewport,applied} |
live / logs / viewport 是写路由,走同一套三道闸:只认回环地址(非本机 403)、方法白名单(405)、写路由同源校验。没有 webServer 的宿主不会挂这些路由,插件照常工作。
客户端半边 plugin/client.js 手写、零构建,走 DSH 的 window.__ModuleLoader__ 协议,只 require('react')(平台种子表内的包),样式全内联,不引第三方 UI 库。窗口几何、设置规整与外观换算都写成纯函数(clampRect / normalizeSettings / appearanceStyle / borderOf / pillAnchor / withScheme),并由 verify-bundle.mjs 的客户端探针真跑一遍断言。
半边归属决定生效方式:改 plugin/client.js(界面、外观、保存按钮)刷新页面即可(Ctrl+F5);改宿主半边(plugin/lib/panel.js、plugin/lib/liveview.mjs、plugin/lib/provider.js、src/*、plugin/lib/index.js)必须重启 DSH。窗口第一次在页面里出现也需要重启一次(客户端插件在启动时收集)。
包里带两份全局技能(skills/,共 7 个文件),装完插件就在技能中心里以系统内置的身份出现,之后任何会话都能用:
| 技能 | 讲什么 | 文件 |
|---|---|---|
browser | 怎么用:33 工具速查、实测的 target 语法、标准工作流、截图验证法、陷阱表 | skills/browser/SKILL.md |
browser-runtime | 怎么修:browsersvc CLI 速查、安装/升级、状态文件与凭据门、排障表、非 root 重建用户态 Chromium 运行时 | skills/browser-runtime/SKILL.md + 同目录 scripts/ 下的 5 个文件(browse.mjs、chromium-wrapper.sh、fonts.conf、setup-libs.sh、verify-cjk.mjs) |
身份怎么来的(v0.8.2 起):插件用 DSH 的技能提供者接口注册它们——ctx.skills.registerProvider(),候选带 source:'bundled' 与 rank:600(dsh-skill 导出的 BUNDLED_SKILL_RANK),和 dsh-univer-office 的 univer-skills 是同一形态,技能中心据此显示成「系统内置」。技能正文是现读 skills/<name>/SKILL.md(改文件不用重启 DSH),配置项 registerSkills(默认 true)可关。
默认不再落盘,以及为什么:v0.8.1 走的是另一条路——把文件写进技能根目录,因为当时还没发现提供者接口。但 DSH 里同一层的技能按 rank 升序、小的先赢:磁盘那份是用户级($DSH_HOME/skills,rank 400),会盖住插件注册的内置那份(rank 600),技能中心里就显示成「用户级技能」。所以 v0.8.2 起默认只走提供者,落盘改成可选(syncSkills 默认 false),只在「DSH 以外的工具也要读这些文件」时才打开。已经落过盘的:跑一次 browsersvc skills --uninstall 就退回内置身份(重启 DSH 后生效)。
落盘同步机制(可选;src/skills.mjs,CLI 与插件共用同一份实现):
$DSH_HOME/skills(DSH 的用户级技能根;DSH 未设 DSH_HOME 时回落 ~/.dsh/skills)。可用配置 skillsDir 改,或 CLI 的 --dir= 指定。.dsh-browser-service.skills.json 记着「哪些文件是本包写的、当时是什么哈希」。于是逐个文件只有四种结果:
--force。--uninstall 只删台账里属于本包、且内容与本包写的那份逐字节相同的文件(你改过的保留并说明原因),撤净后连台账一起删、空目录也清掉。syncSkills: true 时):plugin/lib/index.js 的 apply() 里执行,失败只记一行日志,绝不影响 provider。browsersvc skills # 列每个文件的状态(缺失/已最新/可升级/你改过/非本包)+ 待处理数 + 身份提示
browsersvc skills --install # 可选:落盘(装 + 升级本包自己的旧版)
browsersvc skills --install --force # 连「你改过 / 非本包」的同名文件一起覆盖
browsersvc skills --uninstall # 退回「系统内置」身份:撤掉落盘那份(不动你改过的)
browsersvc skills --dir=/path/skills --json # 换目标目录 / 给脚本消费
装完提示「技能下次会话即可用(重启 DSH 最稳)」;落盘与不落盘,技能正文都来自 skills/,只有技能中心里的身份不同。
配置写在 profile patch 的 browser-cdp 行 config: 下(patch 整行替换 config,覆盖时要重述该行需要的每个键)。
| 键 | 默认 | 说明 |
|---|---|---|
providerId | cdp-daemon | seam 里注册的 provider 名,要和 patch 里 browser.browserProvider 一致 |
cdpUrl | http://127.0.0.1:9333 | browsersvc 的公开(回环)端点 |
connectTimeoutMs | 10000 | 连 CDP / 首次用浏览器时等守护进程起来的超时 |
actionTimeoutMs | 30000 | 单次元素操作(点击/填写/求值)预算 |
navigationTimeoutMs | 30000 | 导航/刷新预算 |
lookupTimeoutMs | 5000 | 元素查找与 scrape 的等待预算 |
snapshotMaxElements | 200 | browser_snapshot 返回的元素上限 |
contentMaxChars | 200000 | browser_content 截断长度 |
captureConsole | true | 是否把页面控制台(console.* / pageerror)记进 <root>/console.jsonl |
captureNetwork | true | 是否把网络请求记进 <root>/network.jsonl,并按会话录 HAR(关掉就不录 HAR) |
maxTabs | 5 | 单个会话允许的最大标签页数(夹在 1..50)。超过时 browser_open {newTab:true} 报 BROWSER_TAB_LIMIT——每个标签页是一个独立渲染进程,实测约 +93 MB |
viewportWidth / viewportHeight | 1920 / 1080(0.8.2 起;0.8.1 及以前 1440 / 900) | 新页面视口(坐标点击的空间)。面板设置里的「浏览器窗口分辨率」可运行时改这一项 |
idleMs | 300000(5 分钟) | 本包自启守护进程时的空闲回收窗口(夹在 1000..86400000)。最后一个会话关闭后插件主动断开连接,守护进程再空闲这么久就退出、把内存还给系统。仅在使用默认 autoStartCommand 时生效 |
autoStartCommand | 空 = 用本包自带的 bin/browsersvc.mjs start | 可选:首次用浏览器时执行的命令;换端口/内核才需要填(自己填的话,idleMs 不会自动带上,要自己写 --idle-ms) |
autoStartTimeoutMs | 60000 | autoStartCommand 的执行超时 |
cdpToken | 空 | 一般不用填:留空时自动读 <DSH_BROWSER_SVC_ROOT 或 $DSH_HOME/browser-service>/service.json 里的 token(每次 attach 重读,守护进程重启换 token 也能跟上)。只有指向自建/非 browsersvc 的 CDP 端点时才需要显式给 |
downloadDir | 系统 Downloads 目录 | 截图/下载的 savePath 必须落在该目录内。不配置时与内置 provider 同语义:XDG_DOWNLOAD_DIR(存在才用)→ 家目录下存在的 Downloads/下载/下載 → ~/Downloads(首次写入时建出来)。要存进工作区/别处就显式填一个目录 |
skillsDir | 空 = $DSH_HOME/skills | 可选的落盘同步目标技能根目录(仅在 syncSkills 打开时用) |
syncSkills | false(0.8.2 起;0.8.1 是 true) | 插件启动时是否把随包 skills/ 也落盘进 skillsDir。默认关:落盘是用户级技能(rank 400),会盖住插件注册的内置那份(rank 600,技能中心显示「系统内置」)。开启带归属台账,你改过的文件不会被覆盖 |
registerSkills | true | 是否把随包技能注册成 DSH 的内置技能提供者(source:'bundled'、rank 600,与 dsh-univer-office 同形态),技能中心里显示「系统内置」。关掉就只剩手动落盘那条路 |
这些键的权威定义在 plugin/lib/index.js 的 Config(schemastery schema)——改默认值必须同时改这里和本表。
v0.3.0 起公开端口要求
Authorization: Bearer <token>(token 由browsersvc生成,落在 0600 的service.json)。插件自动读取它,无需改配置;但browsersvc与插件必须一起升级——旧插件 + 新守护进程会在 401 上失败(见「升级与卸载」)。
| 数据 | 位置 | 说明 |
|---|---|---|
| 操作日志 | <root>/ops.jsonl | 每次工具动作:seq/action/params/ok/error/ms,由 provider 的通用追踪器(traced() 返回 Proxy)自动覆盖所有公共方法,以后新增方法也不会漏 |
| 页面控制台 | <root>/console.jsonl | console.* 与 pageerror,文本截 500 字 |
| 网络 | <root>/network.jsonl | request/response/requestfailed 两阶段,同请求记耗时,不记头与体 |
| HAR | <root>/har/<时间戳>-<会话>.har | 按会话录(playwright-core 的 recordHar),只留最近 10 份 |
| 守护进程状态 | <root>/service.json(0600) | pid / 端口 / 内核 / 版本 / 本次启动的访问 token;同 uid 可读,故 0600 |
| 守护进程日志 | <root>/*.log(0600) | browsersvc logs 读它 |
环形上限:ops / console 各 1 MiB、network 2 MiB,超限保尾部一半;日志文件一律 0600;任何落盘 IO 异常都被吞掉并返回 false——观测出事不能把浏览器调用拖下水。<root> 默认 $DSH_HOME/browser-service。
| 项 | 要求 |
|---|---|
| DSH | 支持 dsh plugin / dsh.bundle 的版本(本机在 0.1.5-rc.3 实测) |
| Node.js | ≥ 22.19(package.json 的 engines;守护进程只用 Node 内置模块,零第三方运行时依赖) |
| 操作系统 | Linux x86_64。本项目在 Debian 12 容器(uid 1000、cgroup 只读、/dev/shm 64M、无 Xvfb)实测;macOS / Windows 未测试 |
| 浏览器内核 | 系统已有的 chrome-headless-shell / Chromium / Chrome,或用户态目录里的 chrome-headless-shell + 包装脚本(注入 LD_LIBRARY_PATH 与 FONTCONFIG_FILE,见 bin/browsersvc.mjs detect) |
| 权限 | 不需要 root;不需要 GUI 库,不需要 Xvfb。容器里 NoNewPrivs=1 + seccomp 时 Chromium 沙箱起不来,因此固定加 --no-sandbox --disable-dev-shm-usage |
| 网络 | 只监听 127.0.0.1(默认公开端口 9333,内核用内部端口);不依赖外网 |
| 磁盘 | 一个 profile 里的包体量 ≈ 依赖(playwright-core 约 14M,只做 CDP 客户端、不下载浏览器);运行时数据在 `$DSH_HOME/brow |