dsh-moli
Verifieddsh-moli · v1.1.19 · MIT · Web UI
DeepSeek Harness tools for the local Moli headless browser: rendered fetches, markdown, semantic trees, screenshots, and PDFs.
Install
dsh plugin add dsh-moli Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-moli
两个工具,让 DeepSeek Harness 的 agent 能驱动真正的无头浏览器。 它们封装 Moli——一个专为 AI agent 打造的 Rust 无头浏览器——所以 agent 能看到 那些"必须跑完 JavaScript 才存在"的页面内容,也能把它们拍成图。
English | 中文
它做什么,什么时候需要它
普通的 HTTP 抓取拿回的是服务器发来的 HTML。对文章、文档、API 来说这足够了,DSH 的 web_fetch 已经覆盖
这种场景。但它拿不到任何需要浏览器构建出来的内容:
- 单页应用(SPA)——客户端渲染的新闻站、仪表盘、文档站。响应只是一个空壳,内容要等脚本跑完才出现。
- 登录与同意墙——必须先有会话或 cookie 才肯显示内容的页面。
- 图——想看一眼页面长什么样(截图),或想存档(PDF)。
- 页面深处的一个值——价格、计数、标题,用选择器直接取,而不是把整篇文档拉回来。
- 只用 7% 的篇幅读完一个列表——
selector只返回匹配到的容器,实测在一个真实信息流上是 1,779 字符, 而同一页面走 markdown 是 139,338。如果你正准备抓一个列表页再自己筛,先看 Usage 里的selector一节。
针对这些场景,本插件给 agent 两个工具:
| 工具 | 作用 |
|---|---|
moli_fetch |
渲染 URL 并返回 Markdown / HTML / 语义树 / 带 HTTP 状态的 JSON;用 selector 只取一个容器;执行 JS 表达式取值;写出 PNG 截图或 PDF |
moli_probe |
报告引擎是否安装、什么版本、HTTPS 是否可用;没装时还能替你装上 |
判断标准很简单:agent 需要的是"浏览器眼里的页面"吗? 如果普通抓取已经能拿到你要的文字,web_fetch
更轻更快。
agent ──moli_fetch──▶ dsh-moli 插件 ──启动──▶ moli.exe
▲ │
│ 渲染页面、
│ 执行其中 JavaScript
│ │
└──markdown / json / png / pdf◀┘
安装
两条路,装的是同一个插件,按你的网络情况选。
路线 1 —— 从 npm 装(推荐)。 经 npm registry 解析,所以在 GitHub 慢或不通的网络里照样能装,DSH 的 安装对话框还能回退到国内镜像:
dsh plugin --profile web add dsh-moli
路线 2 —— 直接从仓库装。 直接跟踪源码,需要 GitHub 可达:
dsh plugin --profile web add github:27painter/dsh-moli
[!IMPORTANT] npm 包只有插件,不含 Moli 引擎。
npm tarball 是 26 kB:八个 JavaScript 与文本文件。
moli.exe是另外的东西——约 101 MB 的 Rust 编译 产物,由 Moli 项目按自己的节奏构建和发布。为什么不能打包进来:
- 体积。 引擎约为插件的 4,000 倍。每次安装、每次插件更新都要顺带搬运一个浏览器。
- 归属。 Moli 是
lexmount/moli,版权属于它自己的 MIT 署名。把它家的二进制经由本包再分发,等于 把别人的产物当自己的发布。- 平台。 引擎按操作系统与架构分别发布——Windows/Linux/macOS × x64/arm64。打包进来等于把某一个 构建方式钉死给所有用户。
- 版本。 插件要能配合用户手上任意 Moli 版本工作,包括插件发布之后才出现的 Moli 新版本。 打包会把两者的版本绑死。
引擎同样没有 npm 包。 registry 上的
moli和moli-cli属于无关项目,@lexmount/moli并不 存在。Moli 以预编译二进制形式发布在 GitHub Releases——不要靠猜包名去装引擎。
装好之后的两种情形
上面两条下载路线装的是同一个东西。区别在于你的机器上有没有 Moli——这决定你是就此完事,还是还差一步。
情形 A —— 已经装了 Moli:不用再做别的
插件自己会找到 moli.exe,两个工具立即可用。重启 DSH 然后确认:
moli_probe
如果报出来的版本正常,就结束了。
情形 B —— 还没装 Moli:把引擎也拉下来
在你装上引擎之前,moli_fetch 会一直报"引擎缺失"。用下面任一种装上:
moli_probe action=install # 让插件替你拉
# 或者自己跑 Moli 官方的安装器
powershell -ExecutionPolicy ByPass -c "irm https://github.com/lexmount/moli/releases/latest/download/moli-installer.ps1 | iex"
然后用 moli_probe 确认并抓一个页面:
moli_probe
moli_fetch url=https://example.com dump=markdown
获取 Moli 引擎
moli_probe 会报告引擎是否在场。如果不在,三种方式任选:
1. 让插件替你装——多路径下载,并报告实际执行字节的 SHA256。整个过程异步(主机照常服务),配置的
proxy 会同时传给下载路由与安装器内部的引擎下载:
moli_probe action=install
2. 官方一行安装(Moli 自己的文档命令):
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://github.com/lexmount/moli/releases/latest/download/moli-installer.ps1 | iex"
# Linux / macOS
curl --proto '=https' --tlsv1.2 -fsSL https://github.com/lexmount/moli/releases/latest/download/moli-installer.sh | sh
3. 直接下二进制(不想跑安装器的话):
# 从 releases 页挑与你架构匹配的资产
curl.exe -L -o moli.zip https://github.com/lexmount/moli/releases/latest/download/moli-x86_64-pc-windows-msvc.zip
Expand-Archive moli.zip -DestinationPath "$env:LOCALAPPDATA\Moli" -Force
这些 releases/latest/download/... 链接始终指向最新发布,所以不用钉版本号也不会失效。
装完若位置不常规,把插件指过去:
config:
exePath: 'C:\path\to\moli.exe'
校验下载
Moli 的 README 没有记录任何校验和或签名,安装器也不发布校验和文件。存在的是 GitHub 自己给每个资产的
sha256 摘要,可以从 release API 读到:
(irm https://api.github.com/repos/lexmount/moli/releases/latest).assets | Select-Object name, digest
v1.1.17 下这个命令会给出(节选):
moli-installer.ps1 2,020 sha256:e002f7a6c174d78ae6ed7289aca9f5d3a62ab0b1f6e588606a80c0cb5d37964d
moli-x86_64-pc-windows-msvc.zip 43.8 MB sha256:5247309837b6eaf6e73bfa18f2a8d8d90473b9e2fe5cfcaaef57d7eee9b074c4
与你下载的文件比对:
(Get-FileHash .\moli-installer.ps1 -Algorithm SHA256).Hash.ToLower()
插件遵循同样的纪律:moli_probe action=install 会报告它实际执行的那份字节的 SHA256,而
expectedSha256 能把这份报告变成强制校验门。它不会编造一个可信默认值——因为摘要只有在与发布页自己
的值比对时才有意义。
这是验证过的性质,不是声明:用插件自己的 downloadInstaller 下载 v1.1.17 的安装器,复现出的正是
GitHub 公布的那个摘要 e002f7a6…964d,从磁盘重新哈希也对得上。
装进 desktop profile
dsh plugin --profile desktop … 会被拒绝——desktop profile 归 Electron 应用所有。直接改它的
package.json,或用应用内插件页(侧边栏 →插件→添加插件),那条路径同样走 pnpm。
profile 插件需要两半都齐:依赖边(dependencies)+ dsh.profile.bundles 条目。缺一个 loader 就看不到
它。从本地目录安装的完整四步是:
- 把本目录放到任意位置,例如
D:\dsh_plugins\dsh-moli。 - 在该 profile 的
package.json中加入两项:dependencies:"dsh-moli": "link:D:/dsh_plugins/dsh-moli"dsh.profile.bundles:"dsh-moli"
- 在 profile 目录执行
pnpm install(生成node_modules/dsh-moli)。 - 重启 DSH。
无法解析的 bundle 会被静默跳过,只在 stderr 留一行 skipping profile bundle …。工具迟迟不出现时先找
这行。
用法
moli_fetch 参数:url(必填)、dump、writeTo、selector、eval、scroll、wait、
waitSelector、waitScript、waitResponseUrl、waitResponseJson、delayMs、timeoutMs、
maxChars、headers、userAgent、layout、scrollbars、noJs、noCss、resources、
withFrames、insecureTls、failOnHttpError、profileDir、cookieFile、proxy。
moli_fetch url=https://example.com dump=markdown # 省 token 的正文
moli_fetch url=https://example.com dump=semantic_tree_text # 带节点 id 的结构
moli_fetch url=https://example.com eval="document.title" # 取一个值
moli_fetch url=https://example.com selector=".video-card" # 只取一个容器,不要整页
moli_fetch url=https://example.com dump=screenshot writeTo=/绝对路径/a.png
moli_fetch url=https://example.com dump=pdf writeTo=/绝对路径/a.pdf
moli_fetch url=https://example.com dump=markdown scroll=800 # 先触发懒加载再抓取
截图与 PDF 必须给 writeTo;字节是原样落盘的,不会打印到输出里。
selector —— 只买条目,不买整页
这是本插件读取页面最便宜的方式,通常也是你真正想要的那个。
列表页里如果只要条目,正文大部分都是噪声。selector 返回 CSS 选择器匹配到的元素文本,所以你只为你要的
那部分付费,而不是为整篇文档。同一页面、同一时刻实测:
| 调用 | 结果 |
|---|---|
dump=markdown |
139,338 字符 |
selector=".bili-video-card" |
1,779 字符,匹配 47 个 |
少读 78 倍——而且省的不只是成本。整页拿回来会带着导航、页脚、cookie 横幅和推荐流;选择器的结果就是你 要的那 47 行,之后不用再筛。
moli_fetch url=https://example.com/feed selector=".video-card"
moli_fetch url=https://example.com/feed selector="h2" scroll=2000 # 先滚再取
moli_fetch url="https://www.bing.com/search?q=deepseek+harness" selector=".b_algo"
最后一条值得单独说:搜索引擎的结果页也只是一个页面,所以一次抓取加一个选择器就能读,既不需要搜索 API,
也没有按次计费。这也是本插件只暴露 fetch 的原因——搜索是一次选择器调用,不是另一个工具。各家引擎对
客户端的拦截程度不同,一个拒绝请求就换一个。
所有匹配项以换行拼接,并且结果会报告匹配到多少个:"matched 48 element(s)" 是"读全了"和"只读到一部分" 的区别,也是你判断选择器太宽或太窄的唯一信号。
两种情况刻意做成错误而不是空答案,因为引擎分不清"没匹配到"和"页面没有文本"——表达式返回 "" 时是
exit 0 加一个空行:
- 没有任何匹配 ——
ok: false,"no element matches"。页面上不存在的选择器几乎总是拼写错误或页面还没加载。 - 匹配到了但没有文本 ——
ok: false,说明匹配到了但都没文本,这通常会发生在图标或图片容器上。需要属性就 用eval。
selector 不能与 dump 或 eval 同传:选择器返回一个容器的文本、dump 返回整页、而一次只能返回一个表达式
值——所以其中一个只能被静默忽略,而这正是这个插件一路在修的那类失败。
scroll —— 拿到"只有滚动后才出现"的内容
引擎会序列化整个文档,所以普通抓取已经包含 DOM 里的一切。它拿不到的是尚未存在的内容:首次
scroll 事件才加载的列表、进入视口才触发的 IntersectionObserver、靠近视口才换上的图片。scroll 会在
抓取前置入一次滚动,让这些处理器先跑起来。
moli_fetch url=https://example.com/feed dump=markdown scroll=600
moli_fetch url=https://example.com/feed dump=markdown scroll=bottom
moli_fetch url=https://example.com/feed dump=markdown scroll=-400 # 往回滚
在一个"滚动处理器填充某个槽位"的页面上实测:不带 scroll 时 dump 是 STILL-EMPTY,带
scroll=1000 时是 LAZY-CONTENT-1000。它同样会改变截图,所以带滚动的 dump=screenshot 拍到的是页面的
那一段。
scroll 是副作用,因此既可组合也可单独使用:
moli_fetch url=... dump=markdown scroll=600 # 先滚动,再 dump 页面
moli_fetch url=... eval="document.title" scroll=600 # 先滚动,再取一个值
moli_fetch url=... scroll=600 # 只滚动,你仍然拿到页面
最后一种写法值得单独说明,因为它曾经是坏的。 滚动在引擎自身的"抓取前求值"里执行,而在没有 dump 时,
引擎打印的就是这个求值的返回值——所以早期版本返回滚动位置,导致这一调用回的是 600 而不是页面,而同样的
调用不加 scroll 会返回 1989 个字符的 HTML。现在滚动不报告任何东西,它只是发生。
eval 与 dump 同用 —— 表达式值会被丢弃
这是引擎写明并有意的行为,予以保留——用表达式在抓取前改变页面是真实用法。不能接受的是它看起来像一个
干净答案:eval='JSON.stringify({probe:1})' 配 dump=markdown 会返回 900 个字符的 Markdown、报
ok: true、什么都不说。现在结果会带 evalDiscarded: true 和说明,而且渲染输出把警告放在最前面,不会
被正文埋掉。
要拿表达式的值,就只传 eval、不要传 dump。
failOnHttpError —— 别再把错误页当正文读
默认情况下 404 和普通页面一样被返回,于是抓取失败看起来是成功的。加上 failOnHttpError 后,最终状态码
400 及以上会变成 ok: false 并报出状态码——而且保留响应体,因为错误页往往正是你需要读的东西。
moli_fetch url=https://example.com/nope dump=markdown failOnHttpError=true
| 默认 | failOnHttpError: true |
|
|---|---|---|
| 一个 404 | ok: true,错误页作为正文 |
ok: false、status: 404,响应体仍然返回 |
| 404 的截图 | 当普通图片写出 | 仍然写出,但报告为失败 |
默认关闭,因此现有调用行为不变。JSON dump 本来就能自己报出状态码;markdown 与 HTML 不能,这个开关补的正是 这个缺口。
等待页面真正取回的东西
wait: "networkidle" 加 delayMs 是在猜列表要多久才到。下面这几个等的是请求本身:
# 等待承载数据的那次响应
moli_fetch url=https://example.com/feed dump=markdown waitResponseUrl="/api/feed"
# URL 不够有特征时,按载荷内容等
moli_fetch url=https://example.com/feed dump=markdown waitResponseJson="data.list.0.id=42"
# 让页面用自己的方式表示"就绪"
moli_fetch url=https://example.com/app dump=markdown waitScript="window.appReady === true"
waitResponseJson 接收 PATH=VALUE:PATH 定位字段,VALUE 按字面比较。当固定延时不是浪费时间就是读得太
早时,用这几个。
与 Moli 官方 agent skill 的关系
Moli 仓库自带官方 agent skill:moli-webfetch、
moli-websearch、
moli-cdp-server,其 README 的快速开始
就是让 agent 去安装它们。如果你的 agent 能加载 skill,最省事的路就是直接用官方那套——上游维护,而且额外覆盖
网页搜索与 CDP 服务。
本插件的存在是因为它是另一种取舍,不是替代品:
- 常驻工具 schema,而非按需 skill。 工具在 Host 启动时注册,模型每回合都能看到,不需要发现步骤。
- 针对 DSH 的失败处理。 下面那三个坑(沙箱管道捕获、二进制被 UTF-16 破坏、
link:插件的裸包名解析) 以及沙箱/吊销/代理三类失败分类,都在这里解决了——因为它们本质是"DSH on Windows"的问题。 - 面更窄。 只封装了
fetch;官方 skill 还覆盖网页搜索和 CDP 服务,本插件没有。
两者可以共存:想要上游覆盖就装 skill,想要能力始终对模型可见就留这个插件。
三个设计要点
- 输出走文件描述符,不走管道。 DSH 的 Windows 沙箱禁止子进程打开继承的命名管道,
spawnSync(..., { stdio: 'pipe' })会得到status=null与 0 字节。本项目始终把 stdout/stderr 重定向到临时文件再读回。 - 二进制按原始字节落盘。 PowerShell 的
>会把原生程序输出按 UTF-16 重编码(每字节后插00),截图因此损坏。这里直接写字节,PNG 签名保持89504e470d0a1a0a。 - 一个可选的外部依赖。 profile 插件按磁盘真实路径加载(
link:依赖仍是符号链接目标),任何裸包名 导入(例如@deepseek-ai/schemastery)都会解析到插件目录而非 profile 的node_modules。因此该导入 是受保护的:没有 schemastery(link:安装)时设置界面不出现、插件退化为纯补丁层配置,而moli_fetch与moli_probe照常注册;有它(普通 npm 安装会自动带上)时设置页面可用。
平台支持
支持 Windows、Linux、macOS(x64 / arm64)。
Linux 与 macOS 支持由 @nd002723 贡献,见 PR #1:POSIX 标准位置的引擎探测、
sh安装器路径, 以及把 PowerShell 相关路径按平台门控。
| 平台 | 状态 |
|---|---|
| Windows | 由维护者验证 |
| Linux | 由贡献者验证,非维护者:Ubuntu 24.04 LTS、Node v24.21.0、Moli 1.1.17——含异步安装路径、代理注入、以及 strace 记录的全链路抓取 |
| macOS | 已实现,尚未在真机验证 |
- Windows —— 引擎在官方安装位置查找(
%LOCALAPPDATA%\Moli\bin\moli.exe与%USERPROFILE%\.local\bin\moli.exe), 并以%LOCALAPPDATA%\Moli递归扫描兜底;moli_probe action=install走官方 PowerShell 安装器。 - Linux / macOS —— 引擎在标准位置(
~/.local/bin/moli、~/.local/share/moli/bin/moli、~/.local/share/moli/moli、~/.moli/bin/moli)与$PATH中查找;moli_probe action=install通过sh执行官方moli-installer.sh。
安装绝不阻塞 DSH 主机。 安装器的下载与执行都以异步 spawn 进行,超时先 SIGTERM 再 SIGKILL,
被中断的回合会立刻终止子进程——Moli 安装器干活期间主机照常服务。用 strace -e trace=connect
对真实安装抓包,记录的每次连接都走配置的代理端口、没有任何直连上游主机;fixture 安装器也确认
代理进入了子进程的环境。
配置的 proxy 同时喂两个阶段:插件自己的下载路由(curl --proxy,以及 PowerShell Invoke-WebRequest
的 -Proxy),以及官方安装器内部的引擎下载——Windows 上通过 DefaultWebProxy prelude(PowerShell
的 Invoke-WebRequest 不读代理环境变量),POSIX 上通过安装器 curl 会读取的标准 HTTPS_PROXY/
HTTP_PROXY/ALL_PROXY 变量。带凭据的代理会显式设置凭据。
在 Linux 上,moli_probe 无需干预就找到了 ~/.local/bin/moli;moli_fetch 端到端渲染了一个地区限制的
电商站——经由本地正向代理、并带上 Netscape 格式的 cookieFile——返回 500+ KB 的页面,最终 URL 带上了
cookie 驱动的查询参数。那次实测同时覆盖了代理下载路由与引擎探测路径。
配置
在 profile 的补丁层里覆盖 config,全部可选。也可以在图 Moli 设置 界面里编辑(见下方「设置页入口」)。
- insert:
- id: dsh-moli
name: dsh-moli
config:
exePath: 'C:\Users\me\AppData\Local\Moli\bin\moli.exe' # 默认自动探测
defaultTimeoutMs: 30000 # 单次抓取就绪超时
maxChars: 200000 # 内联文本截断阈值
proxy: 'http://127.0.0.1:7890' # 转给本地正向代理(moli、下载、安装器内部引擎下载都走它)
insecureTls: true # 见下方 TLS 说明
上面配置里的 proxy 是默认值;moli_fetch 也能接收 proxy 参数,因此单次调用可以走另一个代理而不必改
profile。无论生效的是哪个代理,loopback 地址一律绕过它。
未设置 exePath 时探测顺序:exePath → 环境变量 MOLI_BIN → %LOCALAPPDATA%\Moli\bin\moli.exe
→ %USERPROFILE%\.local\bin\moli.exe,随后还会在 %LOCALAPPDATA%\Moli 内递归查找 moli.exe,
所以装在带版本号的子目录里也能找到。
设置页入口
插件自带一个设置界面,挂在插件管理页的对应插件条目上(plugins.bundle.config 槽位,由浏览器端
client.js 注册)。两种方式进入:
- 插件管理页:打开 DSH Web 界面的插件管理页,找到 dsh-moli 条目,其配置区即是本设置界面; 引擎状态、安装/卸载按钮、以及下方所有配置字段都在这里编辑。
- 直接改 profile:按上面的 YAML 改
cordis.patch.yml同样生效,两边是同一份config,保存即同步。
设置界面跟随 DSH 当前语言(内置 zh / en,英语兜底),布尔字段(如 insecureTls)以下拉选择
原始值(true / false)显示,不做本地化翻译。
引擎生命周期(设置页的「引擎」行)
设置界面顶部有一条引擎状态行,自动检测已安装的引擎,并可一键安装/卸载,无需动 profile:
- 状态 —
engineState为installed(同时给出engineVersion)、missing,或一段简短错误文本。 - 安装 — 点「安装引擎」后表单写入
engineAction: "install",主机异步下载并运行官方安装器, 完成后通过engineState/engineVersion回写结果。 - 卸载 — 「卸载引擎」写入
engineAction: "uninstall"。Windows 上只删除安装器所属的%LOCALAPPDATA%\Moli目录树;没有该目录树时不触碰任何文件。POSIX 上删除定位到的二进制, 并清理随之变空的安装目录。
engineAction / engineState / engineVersion 三个字段属于动态 Config schema(engineAction 是
none / install / uninstall 三选一),设计上由设置界面驱动;日常改配置请优先用上面的四个字段。
已知限制
- macOS 未经验证,Linux 是由贡献者而非维护者验证的。 维护者是一名没有 macOS / Linux 电脑的 大学生;Linux 由 @nd002723 在 Ubuntu 24.04 上独立验证通过(见 平台支持),而 macOS 从未在真机运行过。任一平台出问题,都请带上报错信息开 issue——那是这些路径能被打通的唯一途径。
- 不包含 Moli 本体。 需自行安装,或让
moli_probe action=install替你拉取。 - 只封装了
fetch。moli serve(CDP/WebDriver 服务器)与moli import(会话导入)尚未做成工具。 - 抓取不支持单次取消。 引擎对抓取是同步执行的,被中断的回合无法中途终止;已在进行的调用会跑完并
写出产物。
moli_probe action=install是例外——它是异步的,被中断的回合会立刻终止子进程。 - HTTP 错误码只在
dump: json下可见。 那是唯一携带状态字段的格式;用markdown或html时错误页会作为内容返回。 writeTo调用被串行化。 它们声明为不可并行,因此两次产物写入不会交错。insecureTls默认为true。 在过滤代理后面,证书吊销响应者常常不可达,Schannel 于是在 GitHub 这类站点上报CRYPT_E_REVOCATION_OFFLINE (0x80092013)。默认传 moli 的--insecure-disable-tls-host-verification;在吊销检查可用的网络里改成insecureTls: false, 或按次传insecureTls: false。- 与沙箱的交互。 DSH 默认文件沙箱下子进程是低完整性,Schannel 拿不到客户端凭证,HTTPS 会报
SEC_E_NO_CREDENTIALS。纯 HTTP 仍可用,且失败会被结构化上报(sandboxTlsBlock: true)并给出 重试方式;该次调用带sandbox_permissions: "danger-full-access"即可通过。 - 部分网络下主机可达性会波动。 开发机上观察到的现象:同一时刻,基于 curl 的客户端(moli)访问
github.com超时,而gitover HTTPS 与系统浏览器几秒内就能通;api.github.com正常而github.com不通。所以超时可能是暂时性的——重试,或设置proxy。正因裸超时含义不唯一,它被 上报为possiblyUnreachable而不当作定论。安装动作的多路径回退与上面的手动命令正是为这种情况准备的。
验证情况
verify/ 下有八个可直接对工作区运行的测试:
config-verify.mjs检查设置面与引擎生命周期:动态Configschema(每个已声明字段、engineAction为none/install/uninstall三选一)、apply同时接受普通配置与动态.get()引用、settings/document-updated接线、uninstallMoli(平台感知:POSIX 删除定位到的二进制,Windows 只删除安装器所属的%LOCALAPPDATA%\Moli目录树)、以及 client.js 的形状(plugins.bundle.config注册、zh/en 词典、true/false下拉显示原始值)。schema-verify.mjs用注册表自己的校验器检查工具定义,并带有断言"两种非法 schema 形状仍会被拒"的对照组。install-verify.mjs用本地回环 HTTP 服务器驱动安装链路:路由顺序、被提供字节的 SHA256、校验不匹配时 拒绝执行、匹配时真正执行、以及全部路径失败的诊断。linux-verify.mjs(POSIX 主机)断言代理辅助函数,然后对真实安装验证异步改造的两个保证:安装器子进程 确实通过环境收到代理(fixture 安装器捕获HTTPS_PROXY),以及慢安装器运行期间主机事件循环保持活跃—— 这正是异步路径的验收标准。win-verify.mjs(Windows 主机)跑同样的纯函数断言,然后在真实powershell.exe里执行DefaultWebProxyprelude 并检查它设置的代理对象与凭据,练习 Windows 下载路由,并端到端跑installMoli。empty-verify.mjs用三个编译好的桩引擎断言:退出码 0 且无输出属于"成功但内容为空",而不是错误;非零退出 仍然失败;stderr 有抱怨而 stdout 为空不会被误当成空页面。report-verify.mjs守住一份使用体验报告里的发现:eval与dump同用会丢弃表达式值(已在参数描述里 写明)、表达式抛异常时被正确归类而不是甩锅给"安装有问题"、截断同时保留头尾、proxy是可按调用传入的参数、 以及moli_probe会说明它的httpsOk描述的是哪个进程。scroll-verify.mjs在无引擎时校验表达式构造(像素值、负数、top、bottom、无效输入被拒、调用方的eval值被保留),有引擎时抓取一个"滚动处理器填充槽位"的页面,断言带scroll时内容出现、不带时只有占位符—— 断言的是输出的性质,而不是内部调用。selector-verify.mjs覆盖"按容器取值"最容易出现的两个静默错答案:选择器无匹配时报错而不是空内容、匹配多个 元素时报告数量并拼接。它还断言选择器是作为数据被引号包裹而不是拼进表达式、selector与dump/eval同传会被拒绝(而不是静默忽略其中一个)、以及它能与scroll组合。http-verify.mjs直接断言"参数 → 引擎选项"的映射,因为参数没送到引擎和功能不生效,从外面看是一样的。 随后抓一个 404,验证failOnHttpError会报告失败同时保留响应体、健康页面不受影响、以及错误页的截图仍然被 写为合法 PNG 而不是被当文本解码。它还覆盖那两个"静默给错答案"的情形:eval配dump会带evalDiscarded标记且警告渲染在最前,scroll单独用返回的是文档而不是滚动偏移。端到端部分需要一个真实 服务器的 HTTP 错误:本沙箱内无法访问 loopback——curl.exe连127.0.0.1会超时,而 Node 宿主进程 可以——所以它用example.com下的一个路径,网络不可用时跳过。
测试前置条件
插件有一个可选的运行期依赖——@deepseek-ai/schemastery,
由动态 Config schema(注册设置命名空间的那份)使用。凡是 import 插件本体的套件(config-verify、
schema-verify、empty-verify、report-verify、http-verify)都需要它能从工作区解析,所以先跑
npm install。
schema-verify 额外需要从 npm 全局安装的 @deepseek-ai/dsh 里 import 注册表自己的校验器
(@deepseek-ai/dsh-tools);report-verify 与引擎类套件需要真实 Moli 引擎,empty-verify 需要
C 编译器来构建它的桩引擎。在 Windows 上,@deepseek-ai/schemastery 可以通过目录 junction 指到
npm 全局 DSH 树来解析(与套件解析 dsh-tools 的方式相同)。
.github/workflows/verify.yml 在 Linux、Windows、macOS 上跑 schema-verify、empty-verify、
scroll-verify 与 http-verify(无需引擎、无需网络),在 Linux 与 Windows 上尝试依赖引擎的几套;
若 runner 上没有引擎,则报告为跳过而不是失败。
report-verify.mjs 故意不放进 CI:其中三项会抓 example.com,还有一项抓一个具体的论坛帖子,因此它
需要能上外网——如果这台机器的流量走代理,还需要那个代理可用。有网络时它是有用的,但一个因为网站改版就变
红的测试套件,比没有套件更糟,所以它只作为本机检查保留。如果你跑它时发现第 1–3 节通过而第 4–6 节失败,
先怀疑网络,再怀疑代码。
此外,lib/moli.js 与工具层在 Windows 上对 Moli 1.1.16 做了端到端验证:注册、probe、Markdown 与 JSON
dump、eval(含针对 -t 参数位置的紧超时回归防护)、PNG 与 PDF 产物签名、截断、全部拒绝路径(二元 dump
缺 writeTo、非法 dump、非法 wait、目标目录不存在)、失败分类(不可达主机、HTTP 状态、沙箱 TLS 阻断、
吊销阻断)以及并发声明。被阻断主机的分类器用真实运行捕获的 stderr 文本做纯函数测试。
运行时也确认过:走本地代理抓取 JavaScript 渲染页面(GitHub 搜索)返回 14,560 字符 Markdown,其中包含
30 处真实结果匹配。另有一个装了本插件的一次性 profile,组合其配置时既无 JsonSchemaError 也无被跳过的
bundle。
版本说明:1.1.11 与 1.1.12 已废弃
请使用 1.1.13 或更高版本。 这两个版本之间夹着一次"交互式浏览器"实验——moli_session、第二个
Chromium 后端、原始 CDP 逃生舱、按调用的 CDP 端点、持久化浏览器 profile——约 2300 行机制,远远超出了这个
插件的定位。它已被移除,代码回到 1.1.10 的形态,只保留一处聚焦的新增。
那次工作里唯一有价值的发现保留在 /lib/moli.js,并以 scroll 参数的形式出现在上文。它
本来需要的其他一切,moli fetch 自带的 --eval 早就有了——所以那次实验没有换来任何"一个参数换不来的
东西"。
关于这两个版本本身:npm 只允许在很短的时间窗口内撤回发布,且无法单独撤回某一个版本,因此 1.1.11 与
1.1.12 仍然可安装,但已被标记为废弃并指向这里。npm install dsh-moli 会安装到 1.1.13。
许可
MIT,见 LICENSE。
Moli 本体也是 MIT(Copyright (c) 2026 Moli contributors);本插件是独立的集成项目,与 Moli 项目无从属 或背书关系。