# dsh-plugin-tavily [English](README.md) | 中文 基于 [Tavily](https://tavily.com) 的 **web 搜索提供方插件**,用于 [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness)。定位是**面向进阶用户的专业版 Tavily 搜索插件**:完整暴露 Tavily 请求参数,支持 WebUI 可视化调参与配置文件双模式。 它把 `tavily` 搜索提供方注册进 harness 的 `ctx.web` seam,让内置的 `web_search` 工具通过 Tavily 联网搜索;同时提供一张 **设置卡片**(`设置 → 插件 → 网页搜索`),在图形界面里粘贴 API Key、调节高级参数并测试连接。一次安装,两个半部。 ## 功能 - **即装即用(无需手动配提供方)**:安装本插件即通过其 `cordis.patch.yml` **自动把 `web_search` 提供方选为 Tavily**。卡片里粘一个 Key 就能搜,无需改 yaml 或设 `DSH_WEB_SEARCH_PROVIDER`。 - **Tavily/DeepSeek 引擎开关**:GUI 开关让 `web_search` 由 Tavily(默认;无 Key 走 keyless)应答,或回退到**官方 DeepSeek**——无需卸载。这是真正切换提供方的开关,不是改配置。 - **服务端连通探针**:`POST /api/tavily-probe` 让卡片能测试**已保存的 Key**(浏览器读不回已存密钥),无 Key 时走 keyless。 - **状态指示器**:`GET /api/tavily-status` 用**已保存的 Key** 读取 Tavily `GET /usage`(不消耗搜索额度),卡片常驻显示 ✅ 正常 / ⚠️ 额度不足 / ✗ API 错误(或「未配置 Key」)徽标,由宿主机刷新、无需重新输入密钥。 - **GUI 完整专业参数**:API Key、API Base URL、`maxResults`、`searchDepth`(basic/advanced/fast/ultra-fast)、`topic`、`includeAnswer`、`includeRawContent`、`timeout`、`days`、`chunksPerSource`、`timeRange`、`startDate`/`endDate`、`includeImages`、`includeDomains`/`excludeDomains`、`country` 全部可在卡片编辑;高级参数收进默认折叠的 `
` 面板,普通用户不会被大量选项吓到。 - **参数预设模板**:一键暂存一组高级参数——**深度研究**(advanced 深度、更多结果、返回原始内容)、**快速摘要**(basic 深度、少量结果、详细摘要)、**新闻实时**(news 主题、day 时间窗),点「保存」生效;被配置文件覆盖的字段自动跳过。 - **配置文件优先**:`cordis.patch.yml` > WebUI > 代码默认值。yaml 显式设置的字段在卡片上置灰并显示「该参数已被配置文件覆盖」,WebUI 无法覆盖。 - **API 连通测试**:基础设置区提供独立「测试API连接」按钮,直接用当前填写的 key/baseUrl 发起轻量搜索并展示成功/报错信息,报错已**分类**(Key 无效 / 余额不足 / 限流 / 服务宕机 / 超时 / 网络)并给出对应解决文案。已保存的密钥因安全设计无法被浏览器读回,测试已配置密钥时需要重新输入一次(不会重复保存)。 - **用量与成本面板**:卡片实时显示当前设置的每次搜索积分/token 预估,并提供「检查用量」按钮读取 Tavily `GET /usage`(剩余额度、搜索用量、套餐)。提供方另有宿主侧 `usage()` 方法可在已存密钥可用时读取同一数据。 - **页面抓取**:基于 Tavily Extract 的 fetch 提供方(`tavily-extract`)从 URL 读取整页内容并返回干净的 text/html —— 选择一次后,URL 检索即由 Tavily 应答。 - **可选 Firecrawl 抓取**:另一个 fetch 提供方(`firecrawl`)通过 Firecrawl `POST /scrape`(markdown、正文为主)抓取页面,适合 Tavily 提取质量差的页面。它有独立 id 与独立凭据(默认引用 `FIRECRAWL_API_KEY`);搜索始终走 Tavily,Firecrawl 只负责 URL 检索。未选择时保持惰性。 - **持久化结果缓存**:`cacheFile` 把 TTL/LRU 搜索缓存持久化到 JSON 文件(重启不丢;支持 `~/`,相对路径按工作目录解析)。防抖、尽力而为——磁盘故障绝不阻断搜索。默认关闭。 - **限流重试与缓存**:收到 429 后按 `retry-after` 做有界退避重试;可选 TTL 缓存让相同查询直接命中以节省额度——缓存带 **LRU 上限**(默认 200 条),且默认对**时效敏感查询**(news/finance 主题或任意时间窗口)完全跳过,「现在」类问题绝不命中陈旧快照。 - **精简调试日志**:可选 `debug` 开关,每次搜索/抓取输出一行可读日志(查询节选、深度、积分、缓存状态、耗时、分类错误)——绝不记录密钥或原始响应体。 - **多密钥轮换 & 故障转移**:`apiKeyRefs` 列出额外凭据引用(只存引用名,密钥仍在凭据库/环境变量中),与字面量 `apiKey`、`apiKeyEnv` 组成轮换环。搜索在 Key 级故障(429 / Key 无效 / 余额不足)时自动切到环中下一把 Key;连续失败 3 次的 Key 进入 60 秒冷却,保证健康 Key 接管。 - **脚注式引用(证据溯源)**:`citeFormat: footnote` 在生成摘要后追加编号来源块(`Sources:\n[1] 标题 — url…`),模型可按编号引用来源;`plain`(默认)仅返回摘要原文。 - **自动兜底引擎**:`fallbackEngine: deepseek` 在 Tavily 出现服务侧故障(超时 / 网络 / 5xx)时改由官方 DeepSeek 搜索应答;Key 级故障(429/401)不触发兜底——那是凭据问题,不是宕机。 - **凭据优先的密钥解析**:每次搜索按 字面量 `apiKey` → 凭据服务(`apiKeyEnv`)→ `process.env[apiKeyEnv]` 的顺序解析。 ## 安装 ```sh dsh plugin --profile web add "github:1624318455/dsh-plugin-tavily#main" ``` 开发期间可用本地路径安装: ```sh dsh plugin --profile web add "file:/绝对路径/dsh-plugin-tavily" ``` 插件的 `cordis.patch.yml` 会把 `web.config.searchProvider` 设为 `tavily`,即**自动选 Tavily 为提供方**(无需手动改配置)。 ## 启用 1. **安装并重启 dsh**。插件已替你设置 `web.config.searchProvider: tavily`,无需手动选择提供方。 2. **设置 Tavily API key**(可选)。打开 `设置 → 插件 → 网页搜索`,展开 **网页搜索(Tavily)** 卡片,把密钥粘贴进 **API Key** 输入框。没有 Key 时 Tavily 走 **keyless**(免费、限流),有 Key 走账号档。在 **网页搜索引擎** 开关里选 `Tavily`(默认)或 `官方 DeepSeek`。 3. 照常使用 `web_search`。面向模型的工具不变,只是后端换成 Tavily(或你切到的 DeepSeek)。 > 若日后你想在 yaml 里手动覆盖提供方,对应行是: ```yaml # ~/.dsh/profiles/web/cordis.patch.yml - id: web config: searchProvider: tavily ``` ### 启用抓取(Extract)提供方(可选) 插件同时注册一个基于 Tavily Extract 的 **fetch** 提供方(`tavily-extract`),用于从 URL 读取整页内容。默认不启用,需像搜索提供方一样选择: ```sh export DSH_WEB_FETCH_PROVIDER=tavily-extract ``` 或在 `cordis.patch.yml` 中: ```yaml - id: web config: searchProvider: tavily fetchProvider: tavily-extract ``` 如需改用可选的 **Firecrawl** 页面抓取(需要自己的 Key,默认凭据引用 `FIRECRAWL_API_KEY`): ```sh export DSH_WEB_FETCH_PROVIDER=firecrawl ``` 或把上面同一行的 `fetchProvider` 改为 `firecrawl`。未配置 Firecrawl Key 时,抓取会报 `WEB_PROVIDER_CREDENTIAL_MISSING`,其余时间保持惰性。 ### 验证后端确实是 Tavily `web_search` 工具的输出 schema 与提供方无关 —— 模型看不到提供方名称,且 API key 刻意存放在环境变量之外,所以"查环境变量"是错误探测方式。要确认当前后端: - **提供方选择** —— `~/.dsh/profiles/web/cordis.patch.yml` 中有 `web` 行且 `searchProvider: tavily`。 - **插件已加载** —— `~/.dsh/settings.yaml` 含 `web-search-tavily` 配置节(只有插件的 `installSettingsSection` 会写入它)。 - **凭据在位** —— `TAVILY_API_KEY` 存在于凭据存储(`~/.dsh/.credentials.yaml`),不在环境变量中。 - **结果特征** —— Tavily 结果在 `content` 中携带生成式 answer 摘要;内置 DeepSeek provider 不产生该字段。 ### 故障排查:仍然看到「没有 DeepSeek API key」报错 本插件现已**自动选 Tavily**(`web.searchProvider: tavily`),新装即由 Tavily 应答 `web_search`,正常使用不该再出现此错。若仍看到 DeepSeek key 报错: - **你把引擎切到了「官方 DeepSeek」却没配 DeepSeek key**。把卡片的「网页搜索引擎」切回 `Tavily`(或配置 DeepSeek key)。 - **你在 yaml 里覆盖了提供方**。确保没有更靠后的 `web` patch 行把 `searchProvider` 指回 `deepseek`(插件自己的行已选 `tavily`)。 - **这是 agent/assistant 环境**。聊天应用自己的 `web_search` 是另一套 `web` seam、并未安装本插件——它始终用默认 DeepSeek 后端,与你的 Tavily 安装无关。 ## 🖥️ 图形界面使用(推荐普通用户) 打开 `设置 → 插件 → 网页搜索`,展开 **网页搜索(Tavily)** 卡片。 - **基础设置(默认展开)**: - **状态指示器** —— 常驻徽标(✅ 正常 / ⚠️ 额度不足 / ✗ API 错误 / 未配置 Key),由宿主机用**已保存的 Key** 检查(`GET /api/tavily-status` 读取 `GET /usage`,不消耗搜索额度);「刷新」按钮强制重查(自动检查限每分钟一次)。 - **网页搜索引擎** —— `Tavily`(默认;无 Key 走 keyless)或 `官方 DeepSeek`。这是真正的提供方开关;插件已被自动选为提供方。 - **API Key** —— 粘贴你的 Tavily 密钥。密钥经凭据服务写入,绝不进入设置文件。 - **API Base URL** —— 留空使用 `https://api.tavily.com`;可填代理/自定义接口地址。 - **参数预设** —— 一键应用「深度研究 / 快速摘要 / 新闻实时」:多个高级字段被同时暂存(被配置文件覆盖的字段跳过),点「保存」生效。 - **测试API连接** —— 验证当前输入的 key/baseUrl;失败会分类提示(Key 无效 / 余额不足 / 限流 / 服务宕机 / 超时 / 网络)并附解决建议。测试会消耗一次 Tavily 搜索额度。如果已配置密钥但输入框为空,会提示重新输入一次(浏览器无法读取已保存的密钥)。 - **预估成本** —— 实时显示当前深度/结果数/片段数对应的预估积分与大致 token 量。 - **检查用量** —— 用当前输入的 key 读取 Tavily `GET /usage`,展示剩余额度、搜索用量与套餐;已保存的密钥与测试一样需重新输入一次。 - **高级搜索参数(`🔧 高级 Tavily 请求参数`,默认收起)**: - **最大结果数** —— 单次搜索返回网页结果数量(1–20,默认 5)。 - **搜索深度** —— `basic`(均衡)、`advanced`(2 积分,深度)、`fast`、`ultra-fast`(1 积分,最低延迟)。 - **搜索主题** —— `general`、`news` 或 `finance`。 - **生成摘要答案** —— `true`/`basic`(快速)或 `advanced`(详细)。 - **返回网页原始内容** —— `false`、`markdown` 或 `text`;开启会大幅增加上下文 token 消耗。 - **每个来源的片段数** —— 每个来源返回的相关片段数(1–3)。 - **时间范围** —— 时效预设(`day`/`week`/`month`/`year`/`d`/`w`/`m`/`y`)。 - **开始日期 / 结束日期** —— 精确的 `YYYY-MM-DD` 发布窗口。 - **包含图片 / 图片描述 / 包含网站图标** —— 请求更丰富的结果元数据。 - **包含域名 / 排除域名** —— 站点白/黑名单。 - **国家加权** —— 偏向某一国家(general 主题)。 - **限流重试次数** —— 收到 429 后的额外重试次数(0–5);等待遵循 `retry-after` 并做有界退避。 - **缓存时长(秒)** —— 缓存相同查询以节省额度;0 表示关闭(0–3600)。 - **缓存条目上限** —— 缓存条目的 LRU 上限(1–10000,默认 200);超出后淘汰最旧条目。 - **时效性查询跳过缓存** —— 开启(默认)时,news/finance 主题或带时间窗口的搜索完全绕过缓存。 - **调试日志** —— 每次搜索/抓取输出一行精简日志(查询节选、积分、缓存状态、耗时、错误);绝不记录密钥或原始响应。 - **引用格式** —— `plain`(仅摘要,默认)或 `footnote`(追加 `[1] 标题 — url…` 编号来源块,模型可按编号引用)。 - **兜底引擎** —— `none`(默认)或 `DeepSeek(自动兜底)`:Tavily 服务侧故障(超时/网络/5xx)时改由官方 DeepSeek 应答。 - **请求超时(毫秒)** —— 默认 30000。 - **时间窗口(天)** —— 可选,用于 news/finance 的时效过滤。 每个控件都有简短注释和默认值 placeholder。修改后点 **保存** 即时生效,无需重启服务。 > 如果某个字段显示「该参数已被配置文件覆盖,请修改 yaml」,说明它被 `cordis.patch.yml` 钉住,WebUI 故意不允许覆盖。 ## ⚙️ 配置文件进阶用法(面向开发者) 配置文件即 profile 的 `cordis.patch.yml`(`~/.dsh/profiles/web/cordis.patch.yml`)。在 `web-search-tavily` 行加一个 `config` 块即可设置任意键: ```yaml - id: web-search-tavily name: '@dsh-external/dsh-plugin-tavily' config: searchDepth: advanced topic: news maxResults: 8 includeRawContent: false timeout: 20000 engine: tavily citeFormat: footnote # 摘要后追加编号引用块 [1] 标题 — url fallbackEngine: deepseek # Tavily 服务侧宕机时由 DeepSeek 兜底 apiKeyRefs: # 多密钥轮换环(只放凭据引用名) - TAVILY_API_KEY_1 - TAVILY_API_KEY_2 ``` ### 优先级 ``` cordis.patch.yml 配置 > WebUI 面板保存值 > 代码内置默认值 ``` - yaml `config` 中出现的字段,卡片对应控件会置灰并显示配置覆盖提示。 - yaml 未设置的字段,使用 WebUI 保存的值。 - 两者都没有时,使用代码内置默认值。 ### 配置键一览 | 配置键 | 默认值 | 含义 | GUI 可编辑 | |---|---|---|---| | `apiKey` | (未设) | Tavily API 密钥字面量;建议用凭据服务 | 密钥输入框(走凭据) | | `apiKeyEnv` | `TAVILY_API_KEY` | 凭据引用(环境变量名),每次搜索时解析 | 仅配置 | | `apiKeyRefs` | `[]` | 多密钥轮换环的额外凭据引用(仅引用名;密钥在凭据库/env) | 仅配置 | | `baseURL` | `https://api.tavily.com` | 端点基址,追加 `/search` | ✓ | | `maxResults` | `5` | 单次搜索默认结果数(1–20) | ✓ | | `searchDepth` | `basic` | `basic`/`advanced`/`fast`/`ultra-fast` | ✓ | | `topic` | `general` | `general`、`news` 或 `finance` | ✓ | | `includeAnswer` | `true` | 生成式答案:`true`/`basic`(快速)或 `advanced`(详细) | ✓ | | `includeRawContent` | `false` | 原始内容:`false`、`markdown` 或 `text`(耗上下文) | ✓ | | `chunksPerSource` | `3` | 每个来源的片段数(1–3) | ✓ | | `timeRange` | (未设) | 时效预设:`day`/`week`/`month`/`year`/`d`/`w`/`m`/`y` | ✓ | | `timeout` | `30000` | 请求超时(毫秒) | ✓ | | `engine` | `tavily` | 应答 web_search 的引擎:`tavily`(无 Key 走 keyless)或 `deepseek` | ✓ | | `citeFormat` | `plain` | 答案/来源排版:`plain` 或 `footnote`(编号引用块) | ✓ | | `fallbackEngine` | `none` | Tavily 服务侧故障(超时/网络/5xx)时改为 `deepseek` 应答 | ✓ | | `days` | (未设) | 时效窗口(天),用于 news/finance | ✓ | | `retryMaxAttempts` | `2` | 收到 429 后的额外重试(0–5) | ✓ | | `cacheTtlSeconds` | `0` | 查询缓存时长(秒),0 关闭 | ✓ | | `cacheMaxEntries` | `200` | 缓存条目的 LRU 上限(1–10000) | ✓ | | `cacheBypassFresh` | `true` | 时效敏感查询(news/finance 或带时间窗口)跳过缓存 | ✓ | | `cacheFile` | (未设) | 把结果缓存持久化到的 JSON 文件(重启不丢);未设/空则关闭 | 仅配置 | | `debug` | `false` | 精简的每次搜索调试日志(绝不记录密钥/原始响应) | ✓ | | `firecrawlBaseURL` | `https://api.firecrawl.dev/v1` | Firecrawl fetch 提供方接口地址(追加 `/scrape`) | 仅配置 | | `firecrawlApiKey` | (未设) | Firecrawl 字面量 Key;优先用凭据引用 | 仅配置 | | `firecrawlApiKeyEnv` | `FIRECRAWL_API_KEY` | Firecrawl 每次抓取解析的凭据引用 | 仅配置 | | `startDate` | (未设) | 只返回该 `YYYY-MM-DD` 之后的结果 | ✓ | | `endDate` | (未设) | 只返回该 `YYYY-MM-DD` 之前的结果 | ✓ | | `includeImages` | `false` | 收集查询相关及来源图片 | ✓ | | `includeImageDescriptions` | `false` | 为每张图片附带描述 | ✓ | | `includeFavicon` | `false` | 返回每个结果的 favicon URL | ✓ | | `includeDomains` | `[]` | 只包含这些域名(白名单) | ✓ | | `excludeDomains` | `[]` | 排除这些域名(黑名单) | ✓ | | `country` | (未设) | 偏向某一国家(general 主题) | ✓ | | `numResults` | `5` | **已废弃**:`maxResults` 的旧别名 | 否(请用 `maxResults`) | `apiKeyEnv` 保持「仅配置」:它属于高级接线细节。GUI 保存的值落在 `~/.dsh/settings.yaml` 的 `web-search-tavily` 段。设置改动即时生效 —— 提供方每次操作都会重读配置段,无需重启或重新注册。 ## 平台说明(Web GUI 卡片可见性) Web GUI 只有在 apiproxy 白名单(`@deepseek-ai/dsh-host-apiproxy` 的 `WEB_SETTINGS_NAMESPACES`)内的设置段才会下发给浏览器。截至 `0.1.0-rc.6`,该列表为硬编码,且"让插件自行暴露其配置"的机制尚未落地,因此第三方插件的卡片即使宿主侧已注册也会被过滤。要让 **网页搜索(Tavily)** 卡片渲染出来,请在已安装副本的白名单数组中加入该命名空间并重启 dsh: ```js // ~/.dsh/profiles/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.js // 在 WEB_SETTINGS_NAMESPACES 数组中加入: "web-search-deepseek", "web-search-tavily", // ← 添加这一行 ``` 提供方及全部功能无需此补丁即可工作,只是 GUI 卡片被隐藏。`pnpm install --force` 与 harness 升级都会覆盖此补丁,重装依赖后需重新应用。 **用内置脚本应用补丁**(幂等;`--check` 只检测不写入): ```sh node scripts/patch-apiproxy.mjs --check # 仅报告是否需要补丁 node scripts/patch-apiproxy.mjs # 修补所有已安装 profile node scripts/patch-apiproxy.mjs --profile web # 只修补指定 profile ``` > **已保存密钥的服务端测试**:本插件注册了宿主侧探针 `POST /api/tavily-probe`,可在服务端用已存 Key 测试 Tavily 连通性(无 Key 走 keyless);`TavilySearchProvider.connectivityTest()` / `probe()` / `usage()` / `status()` 也提供了编程用的服务端路径,`GET /api/tavily-status` 为卡片状态指示器供数。浏览器侧因安全设计读不回已存密钥,故「测试API连接」按钮仍要求已配置密钥时重输一次。 ## 映射 Tavily 的扁平 `results[]` 映射为规范化的 `WebSearchSource`:`url` ← `url`、`title` ← `title`、`snippet` ← 非空 `content`(无内容的条目被丢弃)、`publishedAt` ← `published_date`(news/finance 主题)。Tavily 生成式 `answer`(`includeAnswer` 开启时)成为结果 `content`。请求的 `maxResults` 优先于配置默认值,作为 Tavily `max_results` 发送;最终上限由 seam 强制执行。完整专业参数集被转发:`search_depth`(basic/advanced/fast/ultra-fast)、`chunks_per_source`、`topic`、`time_range`、`start_date`/`end_date`、`days`、`include_answer`(布尔或 `basic`/`advanced`)、`include_raw_content`(布尔或 `markdown`/`text`)、`include_images`、`include_image_descriptions`、`include_favicon`、`include_domains`/`exclude_domains`、`country`。注意:`include_images`/`include_favicon` 会发送给 Tavily,但当前 seam 的 `WebSearchSource` 尚无图片/favicon 字段,无法在规范化结果中呈现;暴露它们是为了让请求能带上这些参数。失败以 seam 的 `WebError` 呈现(`WEB_PROVIDER_ERROR` / `WEB_ABORTED`);请求超时报为 `WEB_PROVIDER_ERROR`。 ## 路线图(规划中) 产品分析中确认的高置信后续项: - ✅ **用量/成本面板** —— 卡片展示 `GET /usage` + 实时积分/token 预估(已实现)。 - ✅ **429 重试 + 短时缓存** —— `retry-after` 感知退避 + 可选 TTL 缓存(已实现)。 - ✅ **Extract 提取能力** —— 已在现有 fetch seam 上注册基于 Tavily Extract 的 `WebFetchProvider`(已实现)。 - ✅ **apiproxy 白名单摩擦** —— 提供幂等的 `scripts/patch-apiproxy.mjs`(已实现)。 - ✅ **状态指示器** —— `GET /api/tavily-status`(已存密钥,不消耗搜索额度)+ 卡片徽标与刷新(已实现)。 - ✅ **错误分类** —— 连通/用量失败分类为 Key 无效 / 余额不足 / 限流 / 服务宕机 / 超时 / 网络,并附分场景 UI 文案(已实现)。 - ✅ **缓存加固** —— LRU 上限(`cacheMaxEntries`)+ 时效性查询绕行(`cacheBypassFresh`)(已实现)。 - ✅ **参数预设** —— 卡片一键暂存「深度研究 / 快速摘要 / 新闻实时」(已实现)。 - ✅ **精简调试日志** —— 可选 `debug` 开关,每次搜索/抓取输出一行可读日志(已实现)。 - ✅ **多密钥轮换 & 故障转移** —— `apiKeyRefs` 凭据引用环,429/Key 无效/余额不足自动轮换,3 次失败进冷却(已实现)。 - ✅ **脚注式引用(证据溯源)** —— `citeFormat: footnote` 在摘要后追加编号来源块(已实现)。 - ✅ **自动兜底引擎** —— `fallbackEngine: deepseek` 在超时/网络/5xx 时自动兜底(已实现)。 - ✅ **可选 Firecrawl 抓取** —— `firecrawl` fetch 提供方(`/scrape`、markdown、独立凭据引用),未选择时惰性(已实现)。 - ✅ **持久化结果缓存** —— `cacheFile` JSON 落盘、重启不丢,防抖且尽力而为(已实现)。 已暂缓(刻意为之,见优化评审):**搜索后批量 extract(top-N 页面)** 会改变结果语义(抓取文本必须注入 `content`)、按 URL 消耗 extract 额度,并把搜索与整页抓取隐式耦合——如需可另立可选开关再议。 ## 开发 ```sh pnpm install pnpm run build # tsdown → lib/index.mjs(宿主端)+ lib/client.cjs(浏览器端,均提交入库) pnpm run typecheck # tsc --noEmit pnpm run test:contract # slot 契约漂移守卫(不需联网) node tests/decode-check.mjs # schema 往返校验(不需联网) pnpm test # 真实 API 冒烟:需要 TAVILY_API_KEY node tests/profile-boot-smoke.mjs # 安装→服务连通冒烟:需要 PLUGIN_TGZ ``` `settings.plugin.item` 插槽契约在不同 DSH 发布版之间发生过漂移——`0.1.0-rc.6` 声明为 **list** 插槽(注册要求 `id`),而 `0.1.1-rc.x` 声明为 **keyed**(注册要求 `key`)。卡片注册**同时携带** `key` 与 `id`(外加 list 侧 `order`),因此同一份注册可被所有已发布运行时接受。`pnpm run test:contract` 会解析产物 `lib/client.cjs`,把其中的注册形状喂给真实 `SlotCore` 在两种声明形态下验证;CI 矩阵(`.github/workflows/ci.yml`)还会针对已发布的 `dsh-client-ui-slots` 核心(`0.1.0-rc.6` / `0.1.0-rc.8` / `0.1.1-rc.2`)重跑,并按发布版(`0.1.0-rc.6` / `0.1.1-rc.2`)各启动一次真实 dsh profile 验证实际服务的 bundle。 **发布完全自动化且经 CI 门禁**:改 `package.json` 版本号并推送到 `main`,`Release` 工作流(`.github/workflows/release.yml`)会先等待该提交的 CI 全绿,随后自动打 `v<版本>` tag 并发布 GitHub Release(自动生成变更记录 + 打包好的 tarball 附件)。节奏由 SemVer 决定:**patch** 修 bug(尤其是被 issue 锚定的修复——有人在等,如本次 settings.plugin.item 修复的 v0.6.1)、**minor** 一批功能落地、**major** 破坏性变更。`#main` 安装路径本来就会拿到每个合入的提交;tag 是"推荐版本"的不可变快照。 `lib/` 提交入库,插件安装时无需构建步骤(无 `prepare` 脚本,不需要 pnpm 构建脚本白名单)。`@deepseek-ai/*` 的 seam 与框架包**外部化** —— 由 harness 在运行时提供,声明为 `peerDependencies`。浏览器 bundle(`lib/client.cjs`)是 CJS 模块加载器工厂:只 `require()` 客户端模块表中的平台包,插件自身卡片代码内联其中,安装时无需额外解析。`@deepseek-ai/dsh-base` 只是 devDependency,供冒烟测试解析 harness 运行时闭包。 ## 许可证 MIT