dsh-web-search-pro-plus
增强型、可持久化的扩展网页搜索插件 for DeepSeek Harness(DSH)。
一个 DSH bundle 插件,把多引擎网页搜索、持久化缓存、抓取与证据管线打包成模型可调用的能力。常驻上下文只有 web_index / web_call 两个工具(原 11 个 web_* 工具已并入 20 个动作,见工具面)。路由控制面借鉴 Agent-Reach 的后端探测、顺序选择和失败冷却思路,核心逻辑为本项目原生 TypeScript 实现。
本项目是 anweat/dsh-web-search-pro 的派生版本:搜索核心与上游一致,针对 dsh-v0.2.1-rc.2 调整 peer 范围,并修复了在 Windows 上运行测试的路径与清理问题。浏览器相关能力在本宿主版本上不可用(原因见下)。上游版权与归属见 NOTICE。
兼容与发布通道
| 插件发布通道 | DSH 基线 | 兼容承诺 |
|---|---|---|
0.2.1-r1(本仓库) |
只支持 dsh-v0.2.1-rc.2 这一条线(peer >=0.2.1-rc.2 <0.2.2-0) |
派生版本;774 项单元测试(745 pass / 29 skip / 0 fail)、94 项 bench 测试、pnpm run test:peers 两种解析模式检查、类型检查、构建与客户端 bundle 检查均通过。浏览器能力不可用 |
支持范围:DSH >=0.2.1-rc.2 <0.2.2-0。这是唯一验证过的宿主基线;更早的 0.2.0 线与 0.2.1 的前序预发布都会被拒绝。
浏览器能力不可用:上游配套的 @anweat/dsh-browser 0.2.0 自身 peer 范围是 >=0.2.0-rc.2 <0.2.1-0,在 0.2.1-rc.2 宿主上无法安装,本派生也不 fork 它。因此:
- 不要在本 profile 里安装或升级
@anweat/dsh-browser; read.snapshot、read.fetch mode=playwright、浏览器平台与 OpenCLI 平台会返回CAPABILITY_UNAVAILABLE;read.fetch mode=auto只走 HTTP/CLI 后端,不尝试浏览器渲染;- 其余搜索、抓取(HTTP)、证据管线、缓存、历史功能照常工作。
peer 范围的写法:
- 宿主的组装期校验用的是
semver.satisfies(host, range, { includePrerelease: true });真正决定装机成败的是 npm/pnpm 的默认 semver:预发布版本只有在某个比较符自带同号(同 major.minor.patch)预发布时才被判为满足。>=0.2.1-rc.2自带0.2.1的预发布,是承重的,必须保留;把下界写成别的版本线会过宿主校验、却让dsh plugin add报 ERESOLVE。 - 上界是
<0.2.2-0而不是<0.2.2:后者放行0.2.2-alpha.1这类预发布。收口后,若 0.2.2 真出现破坏,会在组装期直接报is incompatible with dsh 0.2.2并点名。 - 范围只表示「测过哪些」,不等于承诺不破坏。
pnpm run test:peers(已并入pnpm verify)用两种解析模式逐个断言受管 peer:0.2.1-rc.2必须通过,0.1.7-rc.2、0.2.0-rc.2、0.2.1-rc.1、0.2.2-alpha.1必须被拒绝;并自带--selftest已知行为自校验。
安装
本仓库尚未发布到 npm,直接从 GitHub 或本地目录安装:
# GitHub(推荐):
dsh plugin --profile web add github:AlbertZJ/dsh-web-search-pro-plus
# 或本地目录(先在仓库内 pnpm install && pnpm build):
dsh plugin --profile web add ../dsh-web-search-pro-plus
# 重启(web profile 关闭了 HMR):
dsh --profile web
> 本插件必须是 profile 的直接依赖:DSH 只激活直接依赖的 bundle layer。标准 profile 可能设置 autoInstallPeers: false,不要依赖 peer 自动补齐。
> 若你的 harness 是本地源码 checkout,版本号可能有出入——用 dsh plugin --profile web add ./,并在 profile 的 pnpm-workspace.yaml 里对齐依赖后重装。
> 安装/更新带客户端面板的插件版本后需要完整停止并重启 Web profile,仅刷新网页不会重新扫描 client.js。
从上游版本迁移
本包名是 dsh-web-search-pro-plus,与上游 dsh-web-search-pro 并存,不是原地升级。迁移时先移除上游包,再安装本包;web-search-pro: 配置段与已存储数据(历史、页面、规则、证据、账本)保持兼容:
dsh plugin --profile web remove dsh-web-search-pro
dsh plugin --profile web add github:AlbertZJ/dsh-web-search-pro-plus
dsh --profile web
> 不要安装 @anweat/dsh-browser:它的 peer 范围不包含 0.2.1-rc.2,会让安装失败;即使装上了,本插件也不会驱动它。浏览器相关动作会返回上面的 CAPABILITY_UNAVAILABLE。
> 破坏性变更(工具面):旧的 web_search_pro、web_fetch_pro 等 11 个工具名不再注册,也没有兼容包装。旧会话里对它们的调用会失败;把调用改成对应动作即可,对照表见旧工具到新动作。模型在 web_call 里写旧工具名会得到新动作名和翻译后的参数,web_index() 根目录也列出同一张对照。已存储的数据(历史、页面、规则、证据、账本)与 ctx.web provider 不受影响。其他不兼容变化与新功能见 CHANGELOG.md。
升级完成后需要完整停止并重新启动 Web profile;仅刷新网页不会重新扫描插件的 client.js。随后依次检查:
browser_call({action:"runtime.status"}):确认 OpenCLI、playwright | patchright运行时、automationMode与usagePolicy符合预期。web_call({action:"sources.status"}):确认搜索、CLI、Agent Reach 与浏览器后端是否 ready(浏览器应显示ready,不是legacy (unsupported))。- 打开
插件 → 已安装中两个 bundle 各自的详情页,确认配置表单都已加载;浏览器表单负责自由度、运行时、OpenCLI 与调用缓冲。
> automationMode 和防止过度调用的 usagePolicy 都属于 dsh-browser,升级不会自动改写现有配置。生产 profile 建议保留 standard;unrestricted 只用于隔离的自动化测试 profile,并且仍受并发、突发、页数/深度和 429/503 退避保护。
若 Clash/TUN 使用 fake-IP DNS,原生 HTTP 后端可能看到 198.18.0.0/15、fdfe:dcba:9876::/64 或 2001:2::/48(报错信息会直接提示 resolved to proxy fake-IP …)。可在可视化面板的高级设置中启用 allowProxyFakeIp;默认关闭。该开关只信任这些代理网段的 DNS 解析结果,字面 fake-IP URL、localhost 和其他私网地址仍会被 SSRF 防护拒绝。
快速使用与适用情形
安装并重启后,直接在 DSH 会话里要求模型调用工具即可:
请先用 web_call 的 sources.status 检查后端,然后用 search.run 搜索
"DeepSeek Harness community feedback",指定 exa、fresh=true、返回 8 条来源。
开箱默认值(不改任何设置):默认关闭 Jev(evidence.jevMode: off,证据评分用本地规则,不调用任何模型,也不需要任何模型 Key);只有配置了博查 Key(bochaApiKey / BOCHA_SEARCH_API_KEY 或凭据引用)才会使用博查,没配置时博查不在引擎列表里,也不会被自动规划选中。默认引擎是 seam、exa、ddg、bing、jina(Exa 可走无 Key 的 MCP 回退,ddg / bing 免 Key)。Defaults out of the box: Jev is off by default (evidence.jevMode: off; evidence scoring uses the local rules and no model is called, no model key is needed); Bocha is used only when you configure a key (bochaApiKey / BOCHA_SEARCH_API_KEY / a credentials ref) — without one it is not in the engine list and never planned.
模型通常不需要先翻目录:web_index() 根目录直接给出搜索与读取两个最常用调用,随包的 skill dsh-web-search-pro 带有证据模式的工作示例;没有 skill 服务时根目录附一段精简指南。
| 情形 | 推荐入口 | 说明 |
|---|---|---|
| 日常网页搜索 | search.run |
默认按配置顺序回退;需要强制刷新时传 fresh=true |
| 语义研究、社区观点 | search.run + engines=exa |
有 API Key 时走原生 Exa API;只有 Exa MCP 连接时自动经 mcporter 回退 |
| 已知 URL 的批量正文 | read.contents |
直接调用 Exa /contents,必须配置 EXA_API_KEY |
| GitHub/B站/Reddit 等平台 | search.run + platform=… |
Reddit 等 OpenCLI 平台需要 Chrome 扩展在线;中文受限站点使用 AuthProfile |
| 登录后页面或私有论坛 | browserBindings + AuthProfile |
Cookie 保存在本地 storageState,按域名授权,默认只读 |
| 页面改版、懒加载 | platformRules 或 RulePack |
优先改选择器;需要等待/点击/滚动时再使用有界 RulePack |
| 模型生成多步页面操作 | browser_call → automation.run_recipe |
只读步骤直接运行;页面交互按 dsh-browser 的 automationMode 决定拒绝/审批/直通 |
| 外部模型生成油猴脚本 | script.validate → script.run_userscript |
强制 @match、@grant none、禁用 @require;仅 unrestricted 跳过审批 |
| 有限泛爬取 | crawl.crawl |
匿名、默认同源;调用参数不能突破浏览器插件的页数/深度预算 |
| OpenCLI 站点适配器或浏览器桥 | opencli.status → opencli.catalog → opencli.run |
先发现精确 adapter;仅 unrestricted 跳过通用 argv 审批 |
平台来源
每个平台(GitHub、GitHub 代码 / Issues、B站、YouTube、V2EX、arXiv、PubMed、小红书、Twitter / X、Reddit、Instagram、Facebook、RSS、知乎、微博、豆瓣、贴吧、抖音、快手,以及你的 customPlatforms)都是来源注册表里 kind: platform 的 provider:有描述符(语言、任务类型、站点域名、所需浏览器能力 / 登录 / CLI / Token)和适配器,与网页引擎走同一条路径(探测、冷却、并发合并、shapeSources、重试说明、历史)。每个平台是一个 provider,内部是一条有序的后端链(独立 CLI → 独立 OpenCLI → dsh-browser 的 OpenCLI / 搜索页),顺序可用 platformBackends 改,详见下面的 CLI 适配。sources.status 在同一张列表里按维度报告它们的就绪状态(浏览器安装、已绑定登录、设置开关);目录条目用 provider 指向同名 id。
search.run platform=zhihu query=…只搜这个平台,历史类型仍为platform;engines=zhihu在经典搜索里同样可用。url(RSS)、authProfile、rulePack随platform传,browserBindings补全后两者。- 证据模式:
platform可与task/profile/needs/constraints同用——该平台成为 S1 / S2 的显式来源,之后门限、读取前几页(有 Browser 时自动升级)、评分、证据包照常。 - 平台不可用(缺浏览器 / 登录 / CLI / Token、被设置关闭、冷却中)时返回
CAPABILITY_UNAVAILABLE,写明缺什么,并附目录里的安装说明;不会悄悄改搜网页引擎。传allowFallback=true才改搜该 profile 的网页引擎,证据包的notes会写明。平台已运行但没有结果时返回空列表和平台自己的提示(多半是缺登录),不冷却。 - 自动规划:平台从不被自动选入第一轮;只有任务带硬
site约束且命中某平台域名(如zhihu.com)并且该平台就绪时,它才排第一,并保留一个网页引擎做回退(同一上游家族只取一个,其余留给第二轮)。平台没就绪就沿用网页计划并在notes说明。search.recommend对experience类任务可以推荐已就绪的平台(仍至多 3 个)。 - 自定义平台随设置热更新:新增、修改、删除都会同步注册表;与已有 provider 同名的键不会覆盖内置来源,
sources.status的notes会列出被拒绝的键。
先运行 sources.status 判断后端是否 ready(要不要用哪个来源,直接问 search.recommend)。指定单一引擎时失败会原样返回;不指定时才会按 engines 顺序自动回退。所有引擎都返回空结果或不可用(没有运行时错误)时,search.run 返回空结果和说明,不再报错。
两种用法
同一套证据管线有两个入口。模型是否走哪一个,取决于它看到哪些工具;第二种能在模型“顺手用内置 web_search”时仍然让证据管线生效。
(a)直接调用 web_call search.run。 模型加载 skill dsh-web-search-pro(或按常驻提示)调用 search.run,传 task / profile 得到证据包(见下节)。常驻提示和 skill 描述都写明:做网页研究时优先用它,而不是 web_search + web_fetch,因为它只返回过滤后的证据,占用的上下文少得多;读页用 read.fetch(长页用 offset 续读)。
(b)让宿主内置的 web_search / web_fetch 经过本插件。 宿主自带这两个工具,模型常常直接用它们(实测 DeepSeek 在自然任务里就是这样,绕过了 web_index)。把本插件注册并选中为 ctx.web 的 provider 后:
web_search的返回内容 = 本插件的证据包(与search.run同一渲染:resultId、缺口Gaps、“覆盖是启发式的”提示,末尾一行说明可用web_call history.expand/read.fetch展开),sources仍是来源列表;truncated只在确有来源被截掉时为真。查询即任务(task = query,needs = [query],profile 按规则推断,推断不出为general,预算为默认值)。- 证据运行有自己的期限(
provider.deadlineMs,默认 25 秒;到点返回PARTIAL的已有结果)。超时、管线报错都退回今天的普通来源列表,不会让内置工具失败;只有调用方取消会原样抛出。为避免递归,这条路径不会再调用ctx.web引擎(seam)。 web_fetch沿用同一套抓取管线,上限是fetchDefaultChars的两倍;被截断时truncated为真,并在正文末尾追加一行[Continue with web_call read.fetch url=… offset=N]。provider.evidence: off让web_search回到只返回来源列表(与旧行为完全一致)。
怎么选中(宿主的规则,不是本插件的)。 @deepseek-ai/dsh-web 只在两种情况下使用某个 provider:web 条目里写明了它的 id(searchProvider / fetchProvider,环境变量 DSH_WEB_SEARCH_PROVIDER / DSH_WEB_FETCH_PROVIDER 与它们是同一个字段,不是另一条优先级链),或它是唯一可用的已注册 provider。没写 id 又有两个可用 provider 时,每次 web_search 都会以 WEB_PROVIDER_AMBIGUOUS 失败。所以 registerProvider 默认是 false:只注册、不选中是无害的,但默认注册会在没有写 id 的部署里让内置工具直接报错,本插件不会悄悄接管宿主默认 provider。
settings.yaml的web-search-pro:段打开注册(启动时生效,改完重启):
web-search-pro:
registerProvider: true
providerId: web-search-pro # 默认值;与下面的 id 必须一致
provider:
evidence: auto # auto(默认)| off
deadlineMs: 25000
- 在 profile 的
cordis.patch.yml($DSH_HOME/profiles//cordis.patch.yml)里把web条目指向它:
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: web-search-pro
fetchProvider: web-search-pro # 只想换搜索、保留宿主抓取时,写宿主抓取 provider 的 id(宿主源码里内置的是 `http`,以所用宿主版本为准),不要省略
或者只在启动环境里设 DSH_WEB_SEARCH_PROVIDER=web-search-pro(需要时再加 DSH_WEB_FETCH_PROVIDER)。配置里的值与环境变量同时存在时以配置为准。不要只注册而不写 fetchProvider:本插件会同时注册搜索与抓取两个 provider,若宿主的 web 条目没有固定抓取 provider,宿主自带的 http 与本插件的会同时可用,web_fetch 会报 WEB_PROVIDER_AMBIGUOUS。
- 重启后用
web_call sources.status检查:ctx.web route一行会写明是否已注册、web_search/web_fetch当前是否选中本插件、宿主固定的是哪个 id(未选中时还给出上面的配置提示)。宿主不暴露 provider 状态查询接口,所以本插件读的是web运行时已合并好的 id(读不到时只看环境变量,并在状态行里注明)。
和其他“联网调研”skill 并存。 用户自己安装的 skill(例如 agent-reach)也可能在描述里声称负责联网调研;本插件的 skill 描述只陈述自己做什么(中英文触发词:联网搜索 / 查证 / 调研 / 读取网页,web research / look up / verify / read a page),不声称对其他 skill 的优先权,也无法阻止模型选别的 skill。(b)正是为这种情形准备的:不管模型加载了哪个 skill,只要它最终调用内置 web_search / web_fetch,走的就是本插件的证据管线。
证据包模式(search.run 传 task 或 profile)
传 task(一句话目标)或 profile(docs_code / news_fact / academic / experience / compare / general)时,search.run 不返回结果列表,而是按 profile 选择来源(docs_code:ddg/bing/github;academic:arxiv/pubmed/ddg;experience:ddg/bing/v2ex;news_fact:ddg/bing;compare:ddg/bing/github;general:配置的 engines;显式 engines 优先),读取前 4 个保留候选的页面,分块并按每个需求评分,在字符预算(默认 6000,budget 可调)内挑出摘录,并列出未被满足的需求(gaps)。可选参数:needs(; 分隔或 JSON 数组)、constraints(JSON 数组 {kind,value,strength};strength 缺省为 soft,hard 只在确定违反时才丢弃候选)。输出新增 resultId、evidence、coveredNeeds、gaps、partial 等可选字段,原有 sources 仍在;超过总时限(timeoutMs + 30 秒)时返回 partial: true 的已有结果。history.expand 传 evidenceId 可读回摘录所在块及其前后块(至多 4000 字符)。不传 task / profile 时行为和输出与以前完全相同。
相关度门(S4):需求与候选标题/摘要语言不同(如中文需求对英文结果)时,门限判断改用跨语言对齐(需求、query、目标与实体/必含词里的拉丁词一并参与匹配),同语言保持原有的词法规则;若门限之后保留的候选少于 3 个而更多候选存在,则按融合排序补回最靠前的、未违反硬约束的候选,标为“低相关”(sources[].lowConfidence / evidence[].lowConfidence,渲染为 (low relevance),stats.lowConfidence 计数,并在 notes 说明),不会因为相关度启发式而返回空包。
有界第二轮(S8):第一轮之后若有关键需求(critical)没有被满足(gaps 里原因不是 budget),且轮数、查询数、时间都还有余量,管线最多再补搜一轮:以“需求文本 + 任务里的关键实体(entity / must_term / version 约束与 query 里的标识符)”为查询,按各 provider 编译(站点约束等照常下推),优先用第一轮没用过的 provider,其次复用已回答的;只读取新候选里最好的至多 2 页,只对缺口需求评分,再与第一轮结果合并(按 URL 去重)重新挑选。单任务查询总数(一次 provider 调用算一次,GitHub 的放宽重试合并算一次;允许第二轮时第一轮至多用 maxQueries-1 个 provider,留一次给第二轮,显式 engines 不裁剪)不超过 evidence.maxQueries(默认 4),轮数不超过 evidence.maxRounds(默认 2,设 1 即关闭);剩余时间不足 15 秒或预算已用完时跳过并在 notes 说明。证据包 stats.rounds / stats.queries 给出实际轮数与查询数。
块评分默认用本地词法规则。可选的博查 Jev 评分(付费,需环境变量或凭据引用 BOCHA_JEV_API_KEY)由 evidence 配置控制:jevMode: off(默认,不调用)、shadow(规则决定,Jev 评分只记录到存储 evidence_runs.pack_json 供对照)、control(且 scorer: jev 时由 Jev 决定;任何 Jev 失败都回退到规则并在 notes 里说明)、hybrid(规则评分全部块,Jev 只重评需求语言与块语言不一致的 (需求, 块) 对;hybridBorderline: true 时再加规则评分为 1 的边界对;Jev 失败保留规则评分;与 scorer 无关);maxJevQuestions(默认 64)限制每次搜索发送的 (需求, 块) 问题数。规则评分本身对中文需求与英文块做了跨语言对齐(query / 需求 / 约束里的英文词和标识符并入匹配词),默认即生效。发给 Jev 的只有一句话目标、需求文字和页面块文本。
Jev 提示词(rubric):可配置、有版本 / Judge prompts: configurable and versioned
中文 发给 Jev 的问题措辞、评分等级和长度上限是带版本的 rubric(内置 score.support,另有 gate.relevance、gate.constraint 备用)。默认值与离线评测(bench/rubrics/*.v1.json)逐字相同。要调整,在设置文件 evidence.rubrics 里按 rubric id 写覆盖项,无需改代码:
evidence:
jevMode: hybrid
rubrics:
score.support:
version: v2 # 必填;内容有改动就必须换新版本号(不能是 v1)
instructions: | # 只能用 {task} {need} {candidate};必须含 {need} 和 {candidate}(≤2000 字符)
文本块本身是否直接陈述了需求所问的答案,而不只是提到该主题?
需求:{need}
文本块:{candidate}
criteria: [无关, 只提到主题, 部分回答, 直接回答且含证据] # 2–10 级,从低到高;不是 4 级时分数按比例换算到 0..3
maxStateChars: 200 # 20–2000,任务描述上限
maxCandidateChars: 1200 # 100–8000,每个文本块上限
- 校验与回退:含未知变量、缺必需变量、等级数不在 2–10、长度越界、未换版本等,整条覆盖被忽略,改用内置版本;原因出现在
sources.status的evidence.diagnostics和证据包的notes(仅启用 Jev 时)。 - 版本规则:改了措辞、等级或长度就换新
version(如 v2、v3)。每次 Jev 评分都记录id@version#内容哈希:证据包stats.jev.rubric、shadow 日志(evidence_runs.pack_json的shadow.rubric)、证据行(evidence_blocks.rubric);离线评测的缓存键同样包含 id、版本和全文,所以改提示词不会复用旧分数。 - 查看:
sources.status的evidence.rubrics列出每个 rubric 当前生效的版本、是否被覆盖。 - 恢复默认:删除对应的
evidence.rubrics.条目即可(设置页暂不提供该项的编辑界面,只读设置文件)。 - 先离线对照再上线:
node --experimental-transform-types bench/src/eval-pack.ts --rubric-file bench/rubrics/variants/score.support.v2-example.json,在同一份冻结数据上评估候选版本;不带--allow-jev N时不会发出任何请求(用法见bench/src/eval-pack.ts文件头注释)。 - 不让线上模型改提示词:rubric 只能由人通过设置文件修改;插件和任何线上模型都不会自动改写或上线新版本。
English The question wording, grade levels and length caps sent to Jev are versioned rubrics (built in: score.support, plus gate.relevance and gate.constraint for future use). Defaults are byte-identical to the offline-evaluated wording. Override one under evidence.rubrics. in settings.yaml with its own version, optional instructions (variables {task} {need} {candidate} only; {need} and {candidate} required), criteria (2-10 levels, lowest first; other than 4 levels are rescaled to 0..3), maxStateChars, maxCandidateChars. An invalid override (unknown variable, bad level count, out-of-range length, changed content under the old version label) is ignored and the built-in is used; the reason shows in sources.status (evidence.diagnostics) and the pack notes. Bump version whenever the content changes. Every Jev result records id@version#hash (pack stats.jev.rubric, shadow log, evidence_blocks.rubric), and the offline judge-cache keys include id, version and full text, so a changed prompt never reuses old scores. Restore defaults by deleting the evidence.rubrics. entry (there is no settings-page editor for it yet). Compare a candidate offline first with bench/src/eval-pack.ts --rubric-file. Online models must never rewrite or roll out rubrics by themselves: they change only through the settings file.
评分模型 provider 与用量上限 / Judge providers and usage caps
中文 S6 的模型评分分成「协议 × provider」两层,换模型只改配置:
- 协议:
systemone(Jev 兼容的POST /v1/systemone:博查 Jev、其他 Jev 部署、本地 Laya sidecar);rerank(Jina / Cohere 风格的{model, query, documents, top_n}→results[{index, relevance_score}],也适用于暴露同形状接口的本地 bge / Qwen reranker);llm(OpenAI 兼容 chat completions,温度 0、严格 JSON 校验,仅供参考/实验,默认关闭,需evidence.judge.allowLlm: true)。systemone与llm用score.supportrubric;rerank以需求文本作 query。 - 内置 provider:
bocha-jev(默认;https://jev.bocha.cn,bocha-jev-v1,密钥BOCHA_JEV_API_KEY,请求与旧版逐字节相同)、typesafe-jev(占位预设,须自行填baseUrl/model,未验证)、laya-local(http://127.0.0.1:8765,无密钥;实验 r1 里在当前提示词下接近随机,用前必须自己校准)、jina-rerank、cohere-rerank(未验证,从未对真实服务调用过)。 - 自定义 provider(
jevMode/judge.mode决定怎么用;judge.mode是jevMode的中性写法,两者都设时前者优先,且control不再需要scorer: jev):
evidence:
judge:
mode: hybrid # off | shadow | control | hybrid
provider: my-reranker # 默认 bocha-jev
providers:
my-reranker:
protocol: rerank
baseUrl: http://127.0.0.1:8080/v1 # 非本机必须 https;路径可用 path 覆盖(默认 /rerank)
model: bge-reranker-v2-m3
keyRef: MY_RERANK_KEY # 可选:凭据引用或环境变量名,永远不写密钥本身
limits: { maxDocumentsPerRequest: 50, blockChars: 1000 }
calibration: # rerank 必填,见下
version: v1
points: [[0.1, 0], [0.4, 1], [0.7, 2], [0.9, 3]]
bocha-jev: # 与预设同名 = 覆盖预设的个别字段
limits: { maxQuestionsPerRequest: 16 }
budget:
perSearchInputTokens: 60000 # 默认
dailyInputTokens: 1000000 # 默认
timezone: Asia/Shanghai # 日界线时区,默认系统时区
providers: { laya-local: { dailyInputTokens: 200000 } } # 按 provider 覆盖,取更严格者
- 校准是硬要求:reranker 的相关度分数不是等级,不同模型/语言差异很大。
calibration.points是[原始分, 等级0..3]的单调分段线性映射(原始分严格递增、等级不降;区间外取端点),版本号和内容哈希随结果记录;没有校准的 rerank provider 不会被使用(规则评分继续,并在notes说明)。不同 provider 的分数从不混用,离线缓存也按 provider+模型分开(rerank 缓存的是原始分,换校准无需重新请求)。systemone也可选填calibration(Laya 建议)。先用shadow观察,再考虑hybrid/control。 - 用量账本与上限:每次模型调用前按保守估算预留 token、调用后按服务返回的
usage结算(服务不返回则按估算记账并标estimated;价格未声明时金额为空,不是 0;price可选声明)。预留是原子的(SQLiteusage_ledger表,跨搜索、跨进程,重启不清零,未结算的预留继续占用额度)。超过单次搜索或当日上限时跳过模型阶段、回退规则评分,notes出现 “model budget exceeded”。sources.status的evidence.provider/evidence.usage显示当前 provider 是否可用、今日用量与上限。注意默认单次上限 60000 输入 token 小于maxJevQuestions: 64全量发送的估算量,较大的 hybrid 搜索可能提前停在上限处。 - 离线评测:
bench/src/eval-pack.ts --provider [--providers-file providers.json]、bench/src/run-judges.ts --provider;文件格式同evidence.judge.providers。
English S6 model scoring is split into protocol x provider; switching models is configuration. Protocols: systemone (Jev-compatible API: Bocha Jev, other Jev deployments, the local Laya sidecar), rerank (Jina/Cohere-style query-documents API, also local bge/Qwen servers) and an opt-in llm (OpenAI-compatible chat, temperature 0, strict JSON; off unless evidence.judge.allowLlm). Presets: bocha-jev (default, requests byte-identical to before), typesafe-jev (placeholder, unverified), laya-local (no key; near random with the current prompts, calibrate first), jina-rerank / cohere-rerank (unverified, never called live). Add your own under evidence.judge.providers (same-id entries override a preset's fields); evidence.judge.mode is the neutral name of jevMode. A reranker's relevance score is not a grade: calibration.points (monotone piecewise-linear [raw, grade 0..3]) is mandatory, versioned and recorded with results; provider scores are never mixed and caches are per provider+model. Every model call is reserved before and settled after in a persisted ledger (actual tokens from the API, otherwise a flagged estimate; unknown price = null, never 0), under evidence.budget caps (default 60k input tokens per search, 1M per day, per-provider overrides, day boundary in timezone). Over a cap the model stage is skipped and rule grades are used ("model budget exceeded"). sources.status shows the provider and today's usage. Offline: eval-pack.ts / run-judges.ts --provider.
覆盖判定(S8,可选,默认关闭) 规则覆盖是词法判断,分辨不出“文档里没有”。evidence.coverage: { mode: off | shadow | control, provider?, thresholds?: { weak, covered } }:对规则声称已覆盖的每个需求,向 systemone 模型(默认 bocha-jev)问一道 cover.sufficient noul 题——“这些摘录本身是否已明确给出答案”(一次请求,经用量账本与上限)。shadow 只把原始概率记入 stats.coverage,覆盖不变;control:概率低于 weak 的需求变成缺口 weak_support (judge)(关键需求可触发既有的有界第二轮,不增加轮数),介于 weak 与 covered 之间的保持覆盖并标 (judge unsure)。只有显式设置才启用,不随 jevMode 自动开启;失败、超限、超时、缺 Key、缺阈值时保留规则覆盖并在 notes 说明。阈值只对校准过的 provider + rubric 版本有效(内置 bocha-jev + cover.sufficient@v1:weak 0.0512,covered 0.313,取自 v1 calibration 划分);换 provider、模型或 rubric 版本须自己配 thresholds。离线评测见 bench/README.md “覆盖判定评测”,数字见开发计划 M9:增益有限,所以默认保持关闭,建议先用 shadow 积累数据。
English Optional S8 coverage judge (default off). evidence.coverage: { mode: off | shadow | control, provider?, thresholds?: { weak, covered } } asks a systemone model (default bocha-jev) one cover.sufficient noul question per need the rules claim covered ("do these excerpts by themselves state the answer?"), metered by the usage ledger. shadow records raw probabilities in stats.coverage and changes nothing; control turns probabilities below weak into weak_support (judge) gaps (a critical one may start the existing bounded second round) and marks the band up to covered as (judge unsure). Only its own setting turns it on; any failure, cap, timeout, missing key or missing thresholds keeps the rule coverage and says so in notes. Thresholds are only valid for the provider + rubric version they were calibrated for (shipped: bocha-jev + cover.sufficient@v1).
工具面(索引 → 调用)
常驻上下文只有两个工具,常驻文本(系统提示 + 工具名、描述、参数)约 800 字符(原 11 个工具约 9300 字符):
| 工具 | 作用 |
|---|---|
web_index({group?, action?, query?}) |
渐进披露目录:无参数列出能力组、两个最常用调用、旧名对照;group 列出该组动作;action 给出完整参数;query 按关键词检索(至多 8 条) |
web_call({action, args}) |
执行一个动作,返回统一信封 {ok, action, result \| error{code,message,hint,schema?}, truncation?};参数校验失败(INVALID_ARGS)时附带该动作的精简 schema,一次即可改对;未知动作会给出相近动作名 |
使用指引放在随包提供的 skill dsh-web-search-pro(证据模式优先、先读 gaps 再下结论、search.recommend 只用 1–2 个来源、read.fetch 的 offset 续读、约束、预算、浏览器交接;参考文件含来源与 Key 配置、评分 provider 与 rubric、排障)。skill 通过可选的 ctx.skills 注册,不写入必需的 inject;宿主没有 skill 服务时,web_index() 根目录附一段精简指南兜底。系统提示只保留一行入口(有 dsh-browser 时再加一行指向 skill dsh-browser / browser_index)。
动作
| 组 | 动作 | 作用 |
|---|---|---|
search |
run |
多引擎搜索 + RRF 融合 + 内存/SQLite 双层缓存 + 历史;传 task / profile 得证据包;传 platform 搜单个平台(GitHub/B站/YouTube/V2EX/小红书/Twitter/Reddit/IG/FB/RSS + 知乎/微博/豆瓣/贴吧/抖音/快手,登录态走 Playwright;RSS 用 url 传 feed、query 可选过滤;平台与网页引擎走同一条注册表执行路径,可与 task/profile 同用得到证据包,见“平台来源”) |
recommend |
按任务推荐至多 3 个来源(已就绪优先,其余给出缺失条件),不发搜索请求 | |
read |
fetch |
可读化抓取(Jina → HTTP+规则抽取 → Playwright 兜底)+ 快照缓存与 offset 续读;auto 按质量升级:每次结果先判为 content / shell / js_shell / login_wall / captcha / error,只有 shell、js_shell、login_wall 且 dsh-browser 就绪时才升级到 Playwright(captcha 与错误页不会,短而有实质内容的事实页不算空壳),取质量最好的一次并在 attempts 里记录各后端结果;不会自动安装任何东西;显式 mode 不升级,只复用同后端缓存 |
contents |
原生 Exa /contents 批量正文抓取(1-100 URL,需 Exa Key) |
|
snapshot |
Playwright HTML + 文本落盘;screenshot=false 时不生成 PNG(需要 dsh-browser) |
|
history |
list / replay / expand / export / delete |
持久历史:过滤列出 / 按 id 回放 / 读回证据摘录及前后块 / 导出 JSON / 删除一条查询 |
sources |
status |
无副作用后端探测、失败/冷却诊断与 CLI 状态(Twitter 项同时检查 twitter 命令、设置开关和凭据环境变量,note 说明缺什么) |
deps / install |
检测 / 安装搜索后端的外部依赖(bili/yt-dlp/twitter/agent-reach/mcporter;每项探测的是后端真正执行的命令);浏览器依赖由 dsh-browser 管理 | |
rules |
list / upsert / remove / import / export |
持久化按站提取规则(脚本猫式);export 会写出可再导入的版本化 JSON rule pack |
cache |
clear / stats |
按时间/引擎清缓存 / 存储统计 |
每个动作的参数与输出 schema 是封闭的(未声明字段不会出现,除必需字段外都是可选字段)。
旧工具到新动作
旧的 web_* 工具名已不存在,也没有兼容包装;模型调用旧名会得到新动作名与翻译后的参数。映射表在 src/actions/legacy.ts,测试逐项验证每个旧参数仍可达。
| 旧工具(参数) | 新动作 |
|---|---|
web_search_pro |
search.run(query、task、profile、needs、constraints、budget、engines、count、fresh、multi、Exa 选项原样保留) |
web_platform_search |
search.run,传 platform(另有 url、authProfile、rulePack;现在也支持 fresh) |
web_fetch_pro |
read.fetch |
web_exa_contents |
read.contents |
web_snapshot |
read.snapshot |
web_history(过滤) |
history.list |
web_history replay= |
history.replay {id} |
web_history action=expand evidenceId= |
history.expand {evidenceId} |
web_history export=true |
history.export(同样的过滤参数) |
web_cache_clear(olderThanDays / engine) |
cache.clear |
web_cache_clear queryId= |
history.delete {id} |
web_rule action=list\|upsert\|remove\|import\|export |
rules.list / rules.upsert / rules.remove / rules.import / rules.export |
web_search_stats |
cache.stats |
web_backend_status(默认) |
sources.status |
web_backend_status action=recommend |
search.recommend |
web_deps(默认 / action=check) |
sources.deps |
web_deps action=install |
sources.install |
迁移说明:
- 没有 dsh-browser 的会话里,
web_*旧名的调用会报“工具不存在”(宿主层错误,插件无法拦截);新会话的模型通过系统提示、skill 或web_index()找到新名字。 - 用户在宿主里对旧工具名设置的“总是允许”规则不再生效;
web_index无副作用,web_call的审批由本插件按动作判断(见下)。 web_history的replay/export/action=expand不再与列表过滤混在一次调用里,每个动作只做一件事。- 输出路径变化:
search.recommend直接返回推荐本身(不再包在recommend字段里);sources.install返回install结果(不再带空的backends)。
审批、并发与超时(按动作)
- 审批:
sources.install(安装外部命令)、cache.clear、history.delete、rules.upsert/rules.remove/rules.import(改本地存储)会请用户确认;其余动作直通。原先这些规则按工具名写在 dsh-browser 的tools/pre-execute钩子里,而web_call对它是不透明的,所以规则现在由本插件解析web_call的动作后施加,并在 dsh-browser 存在时读取它的automationMode:read-only拒绝;安装在unrestricted直通;本地写入在standard询问、autonomous/unrestricted直通。没有 dsh-browser 时模式未知,按standard处理(询问)——这比旧行为(无 dsh-browser 时完全不审批)更严格。 - 并发:
web_call的并发安全性由参数里的动作决定:只读动作可并行,修改状态的动作独占(sources.install现在也独占,旧的web_deps因为一个标志同时覆盖 check 与 install 而全部可并行)。 - 超时:每个动作保持旧工具的上限(搜索与快照
timeoutMs+ 60 秒,读取与 Exa 正文 + 30 秒,依赖检测/安装 + 180 秒,历史与状态等 10–20 秒);web_call的宿主上限取其中最长者,各动作自己的截止时间到期后返回DEADLINE(修改状态的动作会说明结果未知)。调用方的取消信号会传给执行器。
工具面形态
toolSurface: indexed | flat(设置文件顶层,启动时生效,默认 indexed)。flat 把同一份动作注册表投影成每个动作一个工具,名字为 web_<组>_<动作>(如 web_search_run、web_read_fetch),只用于对照与调试,不是旧工具的兼容层,常驻体积回到全部注入的量级。两种形态共用同一个分发函数,信封、错误码与审批完全一致。
输出预算(所有出口)
每个把正文交给模型的出口都有默认上限;超出部分留在本地存储,按 ID 或偏移续读,不会因为 UI 折叠而仍把全文送进上下文。
| 出口 | 默认预算 | 配置项 | 超出时 |
|---|---|---|---|
search.run(证据包) |
摘录总计 6000 字符,每 URL 至多 2~4 块 | 参数 budget(至多 30000) |
溢出的需求列在 gaps;history.expand 传 evidenceId 读回摘录所在块及前后块 |
read.fetch |
20000 字符 | fetchDefaultChars(1000–500000);参数 maxChars |
输出 truncated、nextOffset、totalChars,并提示 “more: call read.fetch with offset=N”;offset 从已存的页面快照续读,命中缓存时不重新抓取 |
read.contents |
每 URL 8000、全部 URL 合计 30000 字符;总量不足时较短的文本原样保留、剩余额度均分给较长的 | exaContentsPerUrlChars、exaContentsTotalChars |
每条结果带 truncated、totalChars,并给出 read.fetch offset 的续读提示 |
ctx.web 抓取 Provider(内置 web_fetch) |
fetchDefaultChars 的两倍(默认 40000);WebFetchRequest 没有大小参数 |
fetchDefaultChars |
truncated 如实反映是否被截断,正文末尾追加 [Continue with web_call read.fetch url=… offset=N] |
ctx.web 搜索 Provider(内置 web_search,provider.evidence: auto) |
证据包同上(默认摘录 6000 字符);来源条数取宿主给的 maxResults(1–20) |
provider.evidence、provider.deadlineMs |
期限到点返回 PARTIAL 的已有证据包;失败退回普通来源列表;truncated 仅在确有来源被截掉时为真 |
read.snapshot 文本、history.replay 的页面文本 |
fetchDefaultChars |
fetchDefaultChars |
文末带截断标记;全文仍在存储里,用 read.fetch url=… offset=N 读取 |
search.run(普通列表与平台搜索) |
每条摘要 500 字符,条数由 count 限制 |
searchMaxResults |
摘要截断 |
抓取时页面至少读取并存储 100000 字符(更大的 offset + maxChars 会读更多,上限 500000),所以续读来自 SQLite 快照;超过已存部分的 offset 会自动用更大的上限重新读取。
浏览器脚本与自动化分层
> 本派生版本中不可用:@anweat/dsh-browser 0.2.0 的 peer 范围不包含 0.2.1-rc.2,本仓库不 fork 它,也不做浏览器适配。下面的内容保留自上游,仅作能力说明;相关动作会返回 CAPABILITY_UNAVAILABLE(sources.status 显示浏览器未就绪)。
@anweat/dsh-browser ^0.2.0 提供三类脚本入口:
- 内置只读脚本:
article-clean、links、jsonld、forms,适合稳定抽取;先用script.catalog(browser_call)查看。 - Recipe:最多 25 步的结构化 Playwright 操作,支持 wait/click/fill/type/press/select/check/hover/scroll/extract/assert/screenshot;交互步骤由自动化模式决定审批。
- 外部 UserScript:适合外部模型生成站点专项逻辑。先
script.validate查看 SHA-256、域名范围与能力提示,再script.run_userscript(均经browser_call);它在页面主世界运行,并非安全沙箱。
工具自由度由 dsh-browser 的 automationMode 控制:read-only 隐藏或拒绝页面及 Web Search Pro 写操作;standard(默认)对交互、写 Recipe、外部脚本、OpenCLI、缓存/规则变更和安装操作审批;autonomous 直通页面交互、写 Recipe 以及本地缓存/规则变更,但安装、外部脚本和通用 OpenCLI 仍审批;unrestricted 为隔离测试 profile 提供无审批运行。所有模式仍保留域名、参数、大小和步骤上限校验,并始终应用 dsh-browser 的调用缓冲、退避与爬取预算。
OpenCLI 用于已有站点 adapter 或复用 Chrome 登录会话。推荐顺序是 opencli.catalog 查精确 adapter → network/extract → DOM 操作;先运行 opencli.status。opencli.run 接受 argv 数组而非 shell 字符串,可覆盖 adapter、显式 session 的 browser state/find/get/click/fill/type/select/keys/wait/extract/network 等命令;仅 unrestricted 跳过审批。
更完整的 AuthProfile、脚本元数据与 OpenCLI 示例见 LOGIN.md。
配置
三层,越靠前越日常:
- DSH 可视化面板:打开
设置 → 插件 → 插件配置 → Web Search Pro(各分组见下节“设置面板”)。修改先保留为本地草稿,点击“保存”后写入settings.yaml并热更新,支持放弃修改和逐字段恢复部署值。
- Exa、Jina、GitHub、博查与各需 Key 来源的密钥通过 DSH Credentials 写入,面板只显示“已配置/未配置”,不会把明文密钥读回浏览器,也不会把密钥值写进
settings.yaml。 platformRules、customPlatforms、browserBindings、Playwright、自定义评分 provider 等使用 JSON 对象编辑器;格式、数值范围或服务端校验不通过时会阻止保存,并在字段下方给出与服务端一致的错误信息。- 浏览器工具的审批自由度由
dsh-browser.automationMode管辖,调用缓冲由dsh-browser.usagePolicy管辖;用browser_call的runtime.status查看当前状态。Web Search Pro 面板只管理搜索插件自己的后端开关,不会绕过浏览器插件的审批或资源策略。 - 更新带客户端面板的插件版本后需要重启 Web profile,让 DSH 客户端模块扫描器重新装载
client.js。
$DSH_HOME/settings.yaml→web-search-pro:段(热重载,改完即生效):
web-search-pro:
toolSurface: indexed # indexed(默认,只常驻 web_index / web_call)| flat(每个动作一个工具,仅对照与调试;启动时生效)
exaApiKeyEnv: EXA_API_KEY # 推荐:运行环境或凭据服务,不把密钥写入配置
jinaApiKeyEnv: JINA_API_KEY
bochaApiKeyEnv: BOCHA_SEARCH_API_KEY # 博查 Key 的凭据 / 环境变量名;缺省再试 BOCHA_JEV_API_KEY
# keyedSources: # 付费来源(可选,见“付费 / 需 Key 的来源”):tavily / brave / linkup / serper / metaso / zhipu / baidu-qianfan
# tavily: { apiKeyEnv: TAVILY_API_KEY } # 或 apiKey(字面量,不推荐)/ baseUrl;没有 Key 的来源保持不可用
engines: [ddg, bing, exa, seam, jina]
parallelEngines: false
# registerProvider: false # true = 把本插件注册为 ctx.web provider(还需在 web 条目里选中,见“两种用法”)
# provider: { evidence: auto, deadlineMs: 25000 } # 内置 web_search 经本插件时是否返回证据包(auto | off)与其期限
evidence: # 证据包模式的块评分;默认完全不调用 Jev
scorer: rule # rule | jev
jevMode: off # off | shadow | control | hybrid
hybridBorderline: false # 仅 hybrid:同时重评规则边界对
maxJevQuestions: 64
maxRounds: 2 # 证据包补搜轮数上限;1 = 关闭第二轮
maxQueries: 4 # 单任务搜索查询总数(含第一轮;一次 provider 调用算一次)
autoProviders: true # 按任务语言提前就绪的来源(你配置了 Key 的来源,英文无 Key 时的 Exa);false = 只用 profile 表
# sourcePolicy: default # default | anonymous-only(自动规划绝不用需要 Key / 账号 / 登录的来源,见“来源策略”)
# rubrics: ... # 可选:覆盖 Jev 提示词,见下文“Jev 提示词(rubric)”
# judge: ... # 可选:选择/自定义模型 provider,见下文“评分模型 provider 与用量上限”
# budget: ... # 可选:模型输入 token 上限(单次搜索 / 每日),默认 60000 / 1000000
# sources: # 来源偏好与请求额度(全部可选,默认什么都不设)
# priority: [bocha, exa] # 就绪且适合任务语言 / profile 的来源按此顺序排最前
# disabled: [bing] # 自动规划与 engines 列表都不用
# budget: { bocha: { total: 1000, daily: 50 } } # 请求上限;用完被跳过并回退,不报错
ttlSeconds: 3600
searchMaxResults: 8
browserBindings:
zhihu:
authProfile: china-community
rulePack: zhihu-enhanced
- cordis.yml
config:(部署级默认值,见cordis.patch.yml)。 - 环境变量 / 凭据:
$EXA_API_KEY、$JINA_API_KEY、$BOCHA_SEARCH_API_KEY(或$BOCHA_JEV_API_KEY)(exaApiKeyEnv/jinaApiKeyEnv/bochaApiKeyEnv引用)。这些引用名与密钥都可以在面板“来源”分组里设置。
设置面板
面板按以下分组折叠显示(中英文随 DSH 语言;搜索、凭据、运行时默认展开)。嵌套选项(evidence.*、provider.*、keyedSources.*)由面板整体写回各自的顶层字段,不认识的同级键原样保留。
| 分组 | 内容 |
|---|---|
| 搜索策略 | 默认引擎顺序、默认结果数、并行融合 |
| 网络 | allowProxyFakeIp(Clash/TUN fake-IP 症状说明)、timeoutMs |
| 工具面 | toolSurface:indexed(默认)/ flat(仅调试),启动时生效 |
| 内置 web 工具路由 | registerProvider、providerId、provider.evidence(auto / off)、provider.deadlineMs,以及选中本插件所需的 profile patch 片段(随 providerId 变化) |
| 证据管线 | evidence.autoProviders、maxRounds、maxQueries,fetchDefaultChars、exaContentsPerUrlChars / TotalChars。minKeep 与证据包字符预算没有配置项(内置默认 / search.run 的 budget 参数) |
| 评分模型 | evidence.judge.mode(off / shadow / control / hybrid,同时写旧键 jevMode / scorer 保持一致)、hybridBorderline、provider 下拉(内置预设 + 自定义 id)、maxJevQuestions、allowLlm;覆盖判定 evidence.coverage 的模式、provider、阈值;用量上限 evidence.budget(单次 / 每日 / 时区 / 按 provider);自定义 provider JSON(与服务端同一套校验,拒绝在定义里放密钥,只放 keyRef 名) |
| 提示词(rubric) | 列出内置 rubric 与生效版本;逐个覆盖(版本、模板、等级、字符上限),按 rubrics.ts 的规则校验(未知变量、必需变量、等级数、版本必须不同于内置);“恢复默认”删除覆盖 |
| 来源 | 来源策略(evidence.sourcePolicy、sources.priority、sources.disabled)、博查(Key 引用名、接口地址、长摘要)、七个需 Key 的来源(Key 引用名、写入式密钥、接口地址)、每个来源的请求额度(sources.budget..total / daily,已用 / 剩余见 web_call sources.status)、searxngUrl、openalexMailto,以及匿名来源参考表;各来源的实时就绪状态由 web_call sources.status 显示 |
| 服务凭据 / 运行时与后端 / 高级 | Exa、Jina、GitHub 凭据引用与密钥;CLI、OpenCLI、Agent Reach、Playwright、平台后端顺序 platformBackends、自定义 CLI 适配 cliAdapters(JSON,校验与服务端共用);缓存、排序加权、平台规则、自定义平台、浏览器绑定 |
校验与服务端共用同一批纯函数(pipeline/rubrics-spec.ts、judges/providers-spec.ts、judges/calibration-spec.ts、coverage.ts、budget-spec.ts),它们不引入 Node 模块,客户端 bundle 仍只依赖宿主模块表里的少数模块(pnpm run test:client-bundle 检查)。
CLI 适配 / CLI adapters
中文 平台不再几乎只靠 dsh-browser 内置的 OpenCLI:本插件有一层声明式的 CLI 适配(src/cli/)。一个适配规格 CliAdapterSpec 写明:命令名(bins,包名可与命令名不同,记在 packageNote)、探测契约(probe:--version、最低版本、--help 里必须出现的子命令 / 参数)、搜索命令(search.argv 是数组,只有 {query}、{count} 两个占位符,永不经过 shell)、输出格式(json / ndjson / yaml / text)与字段映射、emptyWhen / notLoggedInPatterns、环境变量白名单(env.required / passthrough,只写名字)、超时与输出上限。执行器按 UTF-8 解码,超时 / 取消 / 输出超限都有结构化错误:CLI_NOT_FOUND、CLI_CONTRACT_MISMATCH、CLI_NOT_LOGGED_IN(提示“请自行运行 login”)、ENGINE_EMPTY、CLI_FAILED;错误文字里不会出现环境变量的值。
只读保证(内置和用户自定义规格同样适用):规格必须声明 allowedSubcommands,argv 里用到的子命令都得在其中,且不得命中全局拒绝表(login、logout、post、comment、delete*、like、follow、favorite、upvote、save、subscribe、publish、ask、article、pin、setup、init、install、update 等);会读取浏览器 Cookie 或把凭据放进参数的 flag(--cookie-source、--cookies-from-browser、--token、--password……)一律拒绝。插件永不运行任何登录命令,也不代用户登录:需要登录的 CLI 由你自己运行它的 login;没有已保存登录的 xhs / rdt / zhihu 不会被运行(它们在没有登录时会自己去读浏览器 Cookie)。子进程只拿到最小环境(PATH、HOME、代理、证书,加规格点名的变量),不继承你整个环境。
探测只在本地:找命令、跑 --version 和规格里的 --help,检查版本与契约,缓存 60 秒;不搜索、不登录。同名但契约不同的程序(如 PyPI 上另一个 bili-cli)报 incompatible 并写明原因,不会被当成可用。sources.deps 显示每个 CLI 的 installation(missing / detected / incompatible)、版本、适配验证程度;sources.status 的每个平台带 chain:每条后端的顺序、状态、被跳过的原因。
| 内置规格 | 命令 | 已验证版本(2026-10-06) | 验证程度 |
|---|---|---|---|
bili(bilibili) |
bili |
0.6.2 | live:真实只读搜索并解析 |
gh(github / github-issues / github-code 的回退) |
gh |
2.101.0 | live(仓库搜索);issues / code 按帮助页字段,未运行 |
omnireach(wechat 与多源 omnireach) |
omnireach |
0.19.0-alpha | live(--on wechat);多源命令未运行 |
yt-dlp(youtube) |
yt-dlp |
2026.08.19 | contract-only:帮助与版本,未运行搜索 |
twitter |
twitter |
0.8.5 | contract-only:需要你的 Token,未运行搜索 |
xhs(xiaohongshu) |
xhs |
0.6.4 | contract-only:帮助 + 已装包源码里的输出形状;搜索需要你的登录 |
zhihu |
zhihu(PyPI pyzhihu-cli) |
0.2.4 | contract-only(同上) |
rdt(reddit) |
rdt |
0.4.2 | contract-only(同上) |
独立 opencli(按站点) |
opencli |
1.8.8 | contract-only:只用 opencli list -f json 里有只读 search 命令的站点,输出用 -f json |
wx-search-cli(wechat 第二后端) |
wx-search-cli |
0.1.0 | contract-only:帮助与版本已在真实安装上探测;输出形状按上游 README / 源码,未运行搜索 |
tanso |
tanso |
2.0.2 | docs-only:按上游 README,未安装;只选 bocha_web 与 zhihu_search |
目录里另有只列条目、没有适配的 douyin-cli(它的 README 没写搜索记录的字段)。contract-only 的字段映射来自工具自己的帮助 / 源码,并有标成 “constructed” 的夹具测试;它们在真实登录后的输出上还没被确认。
默认后端链(platformBackends 可改,未列出的平台用默认顺序):
| 平台 | 默认链 |
|---|---|
| xiaohongshu | opencli(独立)→ xhs → browser-opencli(dsh-browser) |
rdt → opencli → browser-opencli |
|
| zhihu | zhihu → browser-search(dsh-browser 搜索页) |
twitter → opencli → browser-opencli |
|
| bilibili / youtube | bili / yt-dlp |
| github、github-issues、github-code | rest(现有 REST)→ gh |
| wechat(公众号,新) | omnireach → wx-search-cli |
| omnireach(多源,不自动选入) | omnireach |
| instagram / facebook | browser-opencli;weibo、douban、tieba、douyin、kuaishou:browser-search |
链上缺失、不兼容或未登录的后端被跳过并记下原因;只有整条链都不可用时平台才不可用,错误会列出每条后端缺什么、该安装或运行什么。M0 冷却、并发合并、输出整形、证据模式与显式 platform 的语义不变。
启用一个 CLI:按 sources.deps 给出的命令安装(sources.install 需要你的审批),再自己运行它的登录命令(xhs login、rdt login、zhihu login、gh auth login;twitter 用环境变量 TWITTER_AUTH_TOKEN / TWITTER_CT0)。想优先用它,就改链序:
web-search-pro:
platformBackends:
xiaohongshu: [xhs, opencli, browser-opencli]
cliAdapters: # 自定义只读适配(同样的校验),注册为平台 custom-cli:mytool
mytool:
bins: [mytool]
packageNote: 'mytool (npm i -g mytool)'
platforms: [mytool]
probe: { versionArgs: ['--version'], helpArgs: ['search', '--help'], mustContain: ['--json'] }
allowedSubcommands: [search]
search:
argv: [search, '{query}', '--limit', '{count}', '--json']
maxCount: 10
output: { format: json, itemsPath: data, fields: { url: url, title: title, snippet: summary } }
env: {}
needsLogin: false
timeoutMs: 30000
maxOutputBytes: 1048576
无效的 cliAdapters 条目被忽略,原因出现在 sources.status 的 notes;设置面板里两项都有 JSON 编辑器,校验与服务端是同一批函数。