# dsh-web-recorder

<p align="center">
  <a href="https://www.npmjs.com/package/dsh-web-recorder"><img alt="npm" src="https://img.shields.io/npm/v/dsh-web-recorder?label=npm&color=cb3837"></a>
  <a href="https://www.npmjs.com/package/dsh-web-recorder"><img alt="downloads" src="https://img.shields.io/npm/dm/dsh-web-recorder?label=downloads&color=brightgreen"></a>
  <a href="https://github.com/kakajun/dsh-web-recorder/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/kakajun/dsh-web-recorder/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://github.com/kakajun/dsh-web-recorder/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/kakajun/dsh-web-recorder?style=social"></a>
  <a href="https://github.com/kakajun/dsh-web-recorder/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/kakajun/dsh-web-recorder?label=License&color=yellow"></a>
  <a href="https://dshfind.com/en/plugins/kakajun/dsh-web-recorder?ref=badge"><img alt="dshfind" src="https://dshfind.com/api/badge/kakajun/dsh-web-recorder"></a>
</p>

**语言：简体中文 · [English](README_EN.md)**

> **宿主版本要求**：本插件适配 `@deepseek-ai/dsh` **`^0.2.1-rc.2`**（依赖其 `@deepseek-ai/dsh-tools@0.2.1-rc.2`），即 profile 的 `package.json` 需满足 `"@deepseek-ai/dsh": "^0.2.1-rc.2"`。

![dsh-web-recorder 封面](assets/podcast-cover.png)

网页操作录制器插件：面向「用户手动操作浏览器，插件在后台录制」的场景。它可以 attach 到已打开的浏览器窗口（如带调试端口的 Playwright MCP 浏览器，**这个是可选的**），也可以自己启动一个有头浏览器窗口（默认本机 Edge）；用户在窗口里正常点网页，插件在后台记录每次点击 / 输入 / 表单提交和每个网络请求 / 响应 / 失败，停止后生成 Markdown 摘要报告。

## 设计初衷

要让 agent 直接通过接口对接某个业务平台（例如 EIP 类系统），前提是先搞清楚三件事：平台暴露了哪些接口、每个业务步骤对应调用哪个、参数和响应是什么格式。传统做法是查接口文档或逆向前端代码，但文档往往缺失或过时，而且就算有了接口清单，也很难把「用户在页面上的业务操作」和「背后触发的接口调用」一一对上。

本插件换一种思路：**不逆向，只留操作依据**。开发者先让记录仪就位（`recorder_start`），再让目标业务流程在一个受监控的有头浏览器窗口里完整跑一遍即可——操作可以由真人手动完成，也可以交给 playwright MCP / browser-use 等浏览器自动化工具按步骤驱动——之后不需要再翻文档，因为这次操作留下的原始数据已经回答了上面三个问题：

- **UI 事件线**：每一步点击 / 输入 / 表单提交发生在何时、落在哪个元素、填了什么值；
- **网络事件线**：每一步操作触发了哪些 `xhr`/`fetch` 请求——method、URL、请求头、请求体、响应体、失败原因。

两类事件按时间交错实时写入 `events.jsonl`，两条线天然对齐：任何一个业务步骤，都能在原始数据里找到「它调了哪个接口、参数怎么填、返回了什么」。停止后生成的 `report.md` 再把这套操作时间线与请求明细整理成便于阅读的摘要。

得到这些原始依据后，交给大模型做分析：归纳出完整的「业务流程 × 接口调用序列 × 数据契约」，再沉淀为 skill——把流程拆成步骤，注明每步该调哪个接口、请求 / 响应长什么样、出错怎么办——让 agent 之后照着 skill 直接走接口完成任务，整个过程不再依赖人工演示。

一句话概括：**页面操作一遍 = 留下可分析的操作依据 = 大模型据此制作接口级 skill 的原料**。录制本身只是采集，分析和沉淀交给模型完成。

## 效果预览

下面是一次真实录制的产物截图（`tests/smoke.mjs` 冒烟测试在一个本地页面执行「输入关键词 → 查询 → 去第二页」的完整流程）。

**① `report.md` 摘要**——统计概览 + 操作时间线 + 网络请求明细，把「业务步骤」和「接口调用」整理在同一张表里：

![report.md 摘要](assets/screenshot-1.png)

**② `events.jsonl` 原始事件流**——每一行一个事件（导航 / 点击 / 输入 / 请求 / 响应…），按发生时间交错实时落盘，是分析时最完整的操作依据：

![events.jsonl 原始事件流](assets/screenshot-2.png)

两张图对照着看：一次「输入关键词并查询」的操作，在 `events.jsonl` 里留下了对应的 `POST /api/query` 请求与 `200` 响应事件，在 `report.md` 里则汇总成一行请求明细——这正是设计初衷里「页面操作一遍，留下可分析的操作依据」的直观体现。

## 🚀 安装

**前置**：已装好 DSH（`dsh web` 能正常运行，`@deepseek-ai/dsh` 需为 `^0.2.1-rc.2`），Node.js ≥ 18、pnpm ≥ 8。

```sh
dsh plugin --profile web add dsh-web-recorder@latest
```

装完**硬刷新浏览器**（Cmd/Ctrl+Shift+R）即可在 DSH 会话工具列表里看到 `recorder_*` 工具（DSH 对 client 改动热加载，无需重启；仅 host 半更新时需要重启）。

**让 DSH 自己装**——把下面这段提示词发给任意一个 DSH 会话：

```text
帮我安装 dsh-web-recorder 插件（网页操作录制器），步骤：
1. 执行 dsh plugin --profile web add dsh-web-recorder@latest
2. 完成后提醒我硬刷新浏览器（Cmd/Ctrl+Shift+R）
```

<details>
<summary><b>更新</b></summary>

```sh
dsh plugin --profile web add dsh-web-recorder@latest
```

更新完**硬刷新浏览器**（Cmd/Ctrl+Shift+R）即可生效。

</details>

<details>
<summary><b>常见问题</b></summary>

| 现象 | 原因与解决 |
|---|---|
| 报 `Ignored build scripts` | pnpm 拦截了构建脚本。在 profile 目录（`~/.dsh/profiles/web`）跑 `pnpm approve-builds --all`。 |
| 报「找不到 profile 目录」 | 先跑一次 `dsh web`，让它初始化 `~/.dsh/profiles/web`。 |
| 提示 `dsh: command not found` | 先安装 DSH；或直接用 `npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-web-recorder@latest`。 |

</details>

<details>
<summary><b>从源码安装 / 开发（可选）</b></summary>

调试本地改动时，把依赖指向本地克隆并自行构建：

```text
1. git clone https://github.com/kakajun/dsh-web-recorder.git ~/Code/dsh-web-recorder
   cd ~/Code/dsh-web-recorder && pnpm install && pnpm build
2. ~/.dsh/profiles/web/package.json 的 dependencies 写 "dsh-web-recorder": "link:<克隆目录绝对路径>"
3. 在 ~/.dsh/profiles/web 执行 pnpm install
4. 硬刷新浏览器（Cmd/Ctrl+Shift+R）即可生效
```

更新：`git pull && pnpm install && pnpm build` → 硬刷新浏览器即可。

</details>

## 注册的工具

| 工具                   | 说明                                                                    |
| ---------------------- | ----------------------------------------------------------------------- |
| `recorder_start(url?, waitSeconds?)` | 开始录制：优先 attach 到已有浏览器窗口（`cdpUrl` / 自动发现**本机任何开了调试端口的 Chromium**，Playwright MCP 优先），否则启动有头浏览器并可导航到起始 URL。自启窗口**自带 CDP 调试端口**并在返回里给出 `cdpUrl`，驱动方可直接连上来接管这个窗口。`waitSeconds` 可让录制先等 N 秒再记录（跳过登录 / 初始化）；事件实时落盘 `events.jsonl`。返回时会把本次录制登记为后台任务并返回 `jobId` |
| `recorder_stop()`      | 停止录制：生成 `report.md` 摘要报告；attach 模式**只断开录制连接、不关闭外部浏览器**，插件自启的窗口才关闭；幂等       |
| `recorder_status()`    | 查询状态：是否在录制、事件分类计数、产物目录、本次录制窗口的 CDP 地址、`jobId`、上一次收尾结果 |

用户直接关掉浏览器窗口会自动收尾（`reason: browser-closed`），已录数据不丢。

**录制结束会主动把模型接回来**：`recorder_start` 同时把本次录制登记为宿主的后台任务（`ctx.jobs`，`<jobId>` 形如 `recorder-1`，归属发起调用的 agent）。用户操作完直接关掉浏览器（或调用 `recorder_stop`）时任务结算，模型在会话内收到完成通知——空闲时被唤醒开新的一轮，繁忙时注入下一步——随后即可用 `job_output` 取回产物路径与统计并接着总结，不必轮询 `recorder_status`。宿主未加载后台任务能力（`dsh-jobs` / `dsh-tool-jobs`）时自动退化为原行为（无 `jobId`，仍需 `recorder_stop` 或用户一句话把结果交给模型）。

## 使用方法:如何命中本插件

### 什么时候会命中（触发场景）

插件的 3 个工具会随插件启用出现在 DSH 会话的工具列表里，「命中」发生在模型做工具选择时——依据是工具名与描述。当用户任务属于「查清某个网页业务流程背后调用了哪些接口、参数/响应长什么样，并据此生成接口级流程说明或 skill」时，模型应主动调用 `recorder_*`。这类任务通常要分步完成：先打开目标页面开始录制，再由驱动方（真人或自动化工具）在页面上完成实际操作，最后基于录制结果分析沉淀。典型任务表述：

- "帮我打开 `http://目标地址`，开始录制"
- "打开 XX 页面，我操作一遍，你录下来再分析接口调用"
- "我在页面上点『查询』时发了什么请求？帮我录下来看看"
- "打开 `http://目标地址`，先等我 30 秒，然后再开始录"

如果只是回答不涉及真实页面操作的问题（例如直接查文档就能答），模型通常不会命中本插件。

### 工具如何可用（加载）

本仓库即 DSH 插件：入口 `lib/index.js`（源码 `src/index.ts`），经 `package.json` 的 `dsh.bundle.patch` 指向仓库根 `cordis.patch.yml` 并入宿主 profile（其中 `id`/`name` 均为 `dsh-web-recorder`）；`@deepseek-ai/*` 由宿主按 `peerDependencies` 提供。插件启用后工具即进入模型工具列表，无需额外声明即可被选择。

### 职责划分：本插件只「记录」，「打开页面 / 操作」交给驱动方

本插件**不直接操作页面**——工具只有 `recorder_start / recorder_stop / recorder_status`，职责是「旁观并记录」：它启动一个受监控的有头浏览器窗口，把窗口里发生的每一步 UI 操作与每个网络请求原样落盘。真正「打开 XXXX 页面并按步骤操作」的是**浏览器驱动方**，可以是：

1. **真人**：在插件弹出的窗口里手动点击 / 输入 / 跳转；
2. **playwright MCP（可选）**：模型通过它打开页面、点击、填表，把目标流程一步步执行出来；
3. **其他 browser-use 类工具（可选）**：同样作为"手"来驱动浏览器执行流程。

**playwright MCP 不是必需依赖**：没有装它、没有 browser-use，插件照样能录——真人在弹出的窗口里手动操作一遍即可，这也是最省事的方式。MCP / browser-use 的价值只在于把「打开页面 + 按步骤操作」自动化，属于可选项。

模型（agent）在会话里做编排：先让记录仪就位，再指挥驱动方执行流程，最后收尾并读取产物分析。

### 推荐流程：打开 X 页面 → 录制步骤（有 / 没有 playwright MCP 都适用）

1. **（可选，有 MCP 时推荐）驱动方先把页面打开**：让 playwright MCP 打开目标页面；只要 MCP 浏览器以 `--remote-debugging-port` 启动（见下文「协同前提」），`recorder_start` 会自动发现它的 CDP 端口并 **attach 到这个已打开的窗口**，无需新开浏览器，MCP 也能继续在这个窗口上操作。**没有 playwright MCP 就跳过本步**，直接进第 2 步，由真人在插件弹出的窗口里操作
2. **记录仪就位**：`recorder_start()`——attach 到已有窗口，或弹出受监控浏览器并打开起始页，从此刻起窗口内一切操作与网络请求都开始实时落盘 `events.jsonl`（注意：从这一刻起发生的页面加载也会被录进来，见下文「初始化请求噪音」）
3. **驱动方执行流程**：让 playwright MCP / browser-use（或真人）**在同一个受监控窗口里**逐步完成目标流程——打开各页面、填写表单、点击提交、翻页……每步操作的 UI 事件和它触发的接口调用都会被同时录下
4. **（可选）查进度**：`recorder_status()`——查看是否在录制、事件分类计数、产物目录、`jobId`
5. **收尾**：`recorder_stop()`——生成 `report.md` 摘要报告（attach 模式只断开 CDP 连接，不关闭外部浏览器；插件自启的窗口会关闭；用户直接关窗口也会自动收尾）。录完可以什么都不做直接关窗口：后台任务会结算并通知模型
6. **分析沉淀**：模型收到后台任务完成通知后读取 `report.md` + `events.jsonl`，归纳「流程步骤 × 接口调用 × 请求/响应契约」，进一步沉淀为 skill 或接口对接文档

### 零配置接入：不必再手改 MCP 配置

以前要让「浏览器自动化工具」和录制器共用同一个窗口，得先给 playwright MCP 加 `--remote-debugging-port`（要写一份 `playwright-mcp.config.json`）。现在这步可以省掉了：

**① 谁开了调试端口就接管谁**——自动发现的范围已从「只认 Playwright MCP」扩展为「本机任何带 `--remote-debugging-port` 的 Chromium」：`chrome --remote-debugging-port=9222` 手工起的浏览器、chrome-devtools MCP 的、其他工具开的，都能被自动 attach。端口写成 `0`（让 Chrome 自选）时，会去读该 user-data-dir 下的 `DevToolsActivePort` 拿实际端口。Playwright MCP 的浏览器优先级最高。

**② 插件自己开的窗口也能被连**——自启窗口现在固定带 `--remote-debugging-port=0`，`recorder_start` 的返回里会带上它的 CDP 地址（`cdpUrl`）。要让驱动方在这个窗口里继续操作，直接连这个地址即可：

| 驱动方 | 怎么连 |
| --- | --- |
| playwright MCP | 启动 MCP 时加 `--cdp-endpoint=<cdpUrl>` |
| browser-use | `BrowserSession(cdp_url=<cdpUrl>)` |
| 自己的 playwright-core 脚本 | `chromium.connectOverCDP(cdpUrl)` |

于是「先让 MCP 打开页面、再起录」不再是唯一路径——**反过来**也成立：录制器先开窗口（`recorder_start`），把 `cdpUrl` 交给驱动方连上来操作，同样录得到。

**③ 登录态只付一次**——自启窗口使用**持久化 profile**（默认 `~/.dsh-web-recorder/browser-profile`，可用 Config 的 `userDataDir` 改）。目标站登录一次，之后每次录制都直接是已登录状态，顺带省掉「登录跳转」那批噪音请求。持久 profile 被残留进程占用而启动失败时会自动改用临时 profile，不影响录制。

> attach 模式下 `recorder_stop` **只断开录制连接，不会关闭外部浏览器**（MCP 会话与用户手上那个窗口都照常可用）；这一条在 `pnpm smoke:cdp` / `pnpm smoke:mcp` 里作为断言校验。

### 协同前提（重要）

- **playwright MCP 是可选的，不是前置依赖**：没装它、也没装 browser-use，插件照样能录——真人在插件弹出的窗口里手动操作一遍即可（这也是最常见的用法）。MCP / browser-use 只是把「打开页面 + 按步骤操作」自动化的可选项。
- **唯一「不能在 playwright MCP 已打开的浏览器上继续操作」的场景**：MCP 的浏览器**没有**带 `--remote-debugging-port`。此时录制器无法 attach 上去，只能走「接管」回退——记下 MCP 浏览器当前页面 URL 后**把 MCP 浏览器进程关掉**，再用插件自启窗口打开同一页面；此后 MCP 与原来那个浏览器的连接已经断了，无法再驱动它（MCP 若重新开一个窗口，那个新窗口同样不在录制范围内）。想让 MCP 与录制器共存有两条路：让 **MCP 浏览器带调试端口**（或用显式 `cdpUrl` 指向它），录制器 attach 过去；或者**反过来**——接管回退后插件自启的窗口自带 CDP 端口（返回里的 `cdpUrl`），让 MCP 以 `--cdp-endpoint=<cdpUrl>` 连到这个新窗口继续操作。
- 录制范围是**插件 attach / 启动的那个浏览器窗口**（含其新标签页与 iframe）；驱动方（真人或自动化工具）的操作必须发生在**这个窗口里**才会被录到。
- **与 playwright MCP 共用同一个浏览器窗口**：让 MCP 启动浏览器时带上远程调试端口，记录仪即可自动 attach。给 `@playwright/mcp` 传一个配置文件（`--config=path/to/playwright-mcp.config.json`），内容为：
  ```json
  {
    "browser": {
      "launchOptions": {
        "args": ["--remote-debugging-port=0"]
      }
    }
  }
  ```
  端口为 `0` 时 Chrome 自动挑选空闲端口并写入其 user-data-dir 下的 `DevToolsActivePort` 文件，`recorder_start` 会自动读取并完成 attach（Windows 上通过进程命令行里的 `ms-playwright-mcp` user-data-dir 定位）。未配置调试端口时记录仪无法 attach，会回退到接管模式：记录 MCP 浏览器当前页面 URL 后关闭它，再用插件自启窗口打开同一页面。
- 也可以在插件 Config 中显式设置 `cdpUrl`（如 `http://127.0.0.1:9222`）指定 attach 目标，优先级高于自动发现；此时 `recorder_stop` 只会断开 CDP 连接，不会关闭外部浏览器进程。

### 前置条件与注意

- 本机需装有浏览器：默认用 Edge（`channel: msedge`），可配 `chrome` 或 `executablePath`；插件不下载浏览器
- **不强制要求 playwright MCP**：驱动方可以是真人（在插件弹出的窗口里操作），也可以是 playwright MCP / browser-use 等自动化工具；两者都没有时，插件自己打开窗口，由真人操作即可
- 配置 `cdpUrl` 或 MCP 浏览器带 `--remote-debugging-port` 时，记录仪优先通过 Chrome DevTools Protocol attach 到已有浏览器实例；attach 失败会回退到接管/新开窗口
- CDP attach 模式下，`recorder_stop` 不会关闭外部浏览器进程，仅断开录制连接（`pnpm smoke:cdp` / `pnpm smoke:mcp` 会校验进程与页面都还在）；普通模式（未 attach）下结束会关闭插件自启的窗口
- `events.jsonl` 含请求头/请求体/响应体等完整数据，按敏感数据处理，勿外发

### 初始化请求噪音：直接打开页面会多录一批接口（已实测确认）

录制从 `recorder_start` 那一刻开始，**凡是开始之后发生的页面加载，其请求都在录制范围内**。所以 `recorder_start(url)` 直接打开页面（或录制开始后再由驱动方导航）时，页面自身的初始化请求会被完整录进来——登录态校验、字典/枚举、菜单、配置、用户信息、埋点上报等等。

实测（本地页面加载时发 2 个初始化接口 `/api/init`、`/api/config`，点击按钮才发业务接口 `/api/query`）：

| 方式 | 初始化接口事件 | 业务接口事件 |
| --- | --- | --- |
| `recorder_start(url)` 直接打开页面 | 2（`/api/init` + `/api/config`） | 点击后同样会录到 |
| 页面先加载完成再 attach 录制 | 0 | 1（`/api/query`） |

差异来自「页面加载发生在录制开始之前还是之后」，与页面本身无关。

**影响**：这些初始化接口通常与目标业务流程无关，会稀释有效信息——尤其分析「点『查询』发了什么请求」时，需要额外分辨哪些是页面固有的、哪些是操作触发的。

**怎么减少这类噪音**：

1. **首选 attach 模式**：让驱动方（playwright MCP 带 `--remote-debugging-port`，或显式 `cdpUrl`）先把目标页面打开、等它加载稳定，再 `recorder_start()`（**不传 url**）——录制从「页面已就绪」开始，初始化接口不计入。
2. **需要登录/跳转才能到达目标页时**：起录时直接传 `waitSeconds`（见下一节），让插件先等 N 秒再开始记录；已经录了的话，按时间把开头的加载段裁掉。
3. **录完后剔除**：`events.jsonl` 里紧跟在 `navigate` 事件之后、且没有对应 UI 事件（click / change / submit）的请求，基本都是初始化噪音。
4. **取基线**：操作前先 `recorder_status()` 记下 `eventCount`，之后只看 `seq` 大于该基线的事件，即可跳过开头那批加载请求。
5. **分析时以操作时间线为准**：`report.md` 把 UI 事件与请求按时间交错列出，能对应上某次点击/输入的请求才是真正的业务流程接口。

### 等待期：`recorder_start` 的 `waitSeconds`（起录即跳过登录 / 初始化）

对「目标站要登录」或「首页加载会刷一堆初始化接口」的场景，不用事后裁剪——起录时直接让插件先等一会儿：

```text
recorder_start({ url: "https://目标地址/login", waitSeconds: 10 })
```

- **语义只有一个**：开始录制后**先等 10 秒再开始记录**，等价于「把前 10 秒从录制结果里剔除」。等待期内发生的点击 / 输入 / 导航 / 请求一律不落盘，等待期结束后才正常记录。
- **计时起点**：`recorder_start` 被调用的时刻。浏览器启动与起始页导航都算在这段时间里，所以「登录 → 跳首页 → 首页初始化」这一整段都在被跳过的范围内。
- **整组丢弃**：请求若发起于等待期内，它的响应 / 失败也一并丢弃，不会出现「只有响应没有请求」的半条记录。
- **可观测**：`recorder_status()` 的 `waitRemainingSec` 告诉你还要等多久才开始记录（> 0 时此刻的操作不会被记录），`skippedEvents` 是等待期内已丢弃的事件数；`recorder_stop()` 的结果与 `report.md` 头部同样会写「等待 10s 后开始记录: 等待期内 N 个事件已丢弃」。
- **不传或传 0 = 立即开始记录**（默认，与旧行为一致）。

## 录制内容

- **UI 事件**：点击（选择器 + 元素文本 + 坐标）、输入/选择变更（name + 值，密码框只记录动作不落值）、表单提交、页面导航。通过 `context.exposeBinding` + `addInitScript` 捕获阶段监听实现，覆盖新标签页与 iframe。
- **网络事件**：默认只录 `xhr`/`fetch` 接口调用（`requestResourceTypes` 可调）——每个请求的 method/URL/resourceType/请求头/请求体，响应的状态码与响应体（截断），失败请求的错误原因。可选记录 console 消息。
- **产物**：每次录制在 `<outputDir>/rec-<时间戳>/` 下生成 `events.jsonl`（完整数据，逐行实时写入）和 `report.md`（操作时间线 + 请求明细表 + 失败请求摘要）。

## Config 字段

| 字段                    | 默认值                                                          | 说明                                                                                                                     |
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `channel`               | `msedge`                                                        | playwright-core 浏览器渠道（`msedge`/`chrome`），使用本机已装浏览器，不下载 Chromium                                     |
| `executablePath`        | `''`                                                            | 自定义浏览器可执行文件路径，非空时覆盖 `channel`                                                                         |
| `cdpUrl`                | `''`                                                            | CDP 调试地址（如 `http://127.0.0.1:9222`），非空时优先 attach 到已有浏览器；留空时自动发现带 CDP 端口的 Playwright MCP 浏览器；都失败则回退到接管/新开窗口 |
| `userDataDir`           | `''`                                                            | 自启窗口使用的浏览器 profile 目录；空则默认 `~/.dsh-web-recorder/browser-profile`。持久化 profile 让登录态跨次录制保留（目标站只需登录一次），并让插件窗口自身暴露 CDP 端口供驱动方连接 |
| `outputDir`             | `''`                                                            | 产物输出根目录；空则默认当前工作目录（用户正在操作的文件夹，harness 会话 cwd）下 `reports/recorder` |
| `captureResponseBodies` | `true`                                                          | 是否抓取 xhr/fetch 响应体                                                                                                |
| `maxBodyBytes`          | `16384`                                                         | 单个请求体/响应体最大记录字节数，超出截断                                                                                |
| `recordConsole`         | `false`                                                         | 是否记录 console 消息                                                                                                    |
| `requestResourceTypes`  | `xhr,fetch`                                                     | 逗号分隔的 resourceType 白名单，只有命中的请求才录制（响应/失败事件随请求一并过滤）；默认只录接口调用，静态资源/文档不录 |
| `redactHeaders`         | `cookie,authorization,proxy-authorization,x-api-key,set-cookie` | 逗号分隔的脱敏请求头名（大小写不敏感），命中头值记为 `<redacted>`                                                        |

## 构建与验证

```sh
pnpm install       # 安装依赖(devDependencies 与 peerDependencies 对齐)
pnpm typecheck     # tsc --noEmit
pnpm test          # vitest 单元测试(report 生成纯函数)
pnpm build         # tsdown 产物到 lib/(插件加载与 smoke 依赖产物)
pnpm smoke         # 真实 Edge 端到端冒烟(需本机已装浏览器, 产物在 reports/)
pnpm smoke:cdp     # CDP attach 模式端到端冒烟(需本机已装浏览器, 产物在 reports/)
pnpm smoke:mcp     # MCP 浏览器自动发现 attach 冒烟(模拟带调试端口的 MCP Chrome)
pnpm smoke:wait    # 等待期(前置剔除)冒烟: 验证 waitSeconds 等待期内不记录、等待期后正常记录
```

## 仓库已被收录在 awesome-dsh-plugin

本仓库已按 awesome-dsh-plugin/contributing 对齐:

- `package.json` 声明 `dsh.bundle.patch: ./cordis.patch.yml`(见仓库根 `cordis.patch.yml`, 其中 id 与插件内部注册名 `dsh-web-recorder` 一致)
- 官方 `@deepseek-ai/*` 包放在 `peerDependencies`: `dsh-tools` 的范围带显式预发布分支(harness 以 rc 版本发布, 无分支的宽范围会被 node-semver 静默排除), `cordis`/`schemastery` 同为 peer 由宿主提供
- 运行时默认产物输出到**当前工作目录（用户正在操作的文件夹）**下 `reports/recorder`，可用 `Config.outputDir` 显式指定
- 附带真实冒烟测试 `tests/smoke.mjs`, 非占位仓库

## 已知限制

- 只录制插件 attach / 自己启动的浏览器窗口（含其新标签页 / iframe），无法录制其他浏览器实例里的操作。要让 playwright MCP / browser-use 等自动化工具的操作被录到，需让它们驱动与记录仪相同的浏览器实例：浏览器带 `--remote-debugging-port` 时记录仪会自动 CDP attach（见「协同前提」），插件自启的窗口同样自带 CDP 端口、可反过来让驱动方连上来（见「零配置接入」）。
- playwright MCP / browser-use 都不是必需依赖（真人操作即可）。接管回退模式下 MCP 原有的浏览器会被关闭（它的页面已由插件窗口接手），此后既可由真人在插件窗口里操作，也可用返回的 `cdpUrl` 让 MCP 重新连到这个窗口。
- 插件自启窗口模式下，起始页的加载必然发生在录制开始之后，因此会连带录进一批页面初始化请求（登录态/字典/配置/埋点等），见「初始化请求噪音」一节；只有 attach 到「已加载完成」的页面才能避开。
- 请求头中的敏感头默认脱敏；请求体/响应体不做内容级脱敏（可能含 token 等业务数据），`events.jsonl` 应按敏感数据处理，不要外发。
- 跨域 iframe 的 UI 事件依赖 init script 注入，极少数强 CSP 页面可能注入失败（网络事件不受影响）。
- 点击接口请求后立即跳转页面时，浏览器会取消该响应体的抓取，此场景响应体可能缺失（事件仍在，只是无 body）。

## 欢迎 PR 和 Issue

插件还在持续打磨，任何形式的参与都欢迎：

- **提 Issue**：[反馈问题或想法](https://github.com/kakajun/dsh-web-recorder/issues/new)——录不到的事件、attach 失败的场景、`report.md` 里缺的字段，都可以直接开 issue。附上日志与截图会更快定位；**请勿粘贴 `events.jsonl` 原文**（含完整请求头/请求体）。
- **提 PR**：[fork 后提交合并请求](https://github.com/kakajun/dsh-web-recorder/pulls)——新增事件类型、改进报告呈现、补充冒烟用例或文档都欢迎。提交前建议跑 `pnpm typecheck && pnpm test`；改动了 `session.ts` 的事件采集逻辑，再跑一遍 `pnpm build && pnpm smoke`。
- 顺手点个 [Star](https://github.com/kakajun/dsh-web-recorder/stargazers) 也是支持。

想做但不确定合不合适的改动，欢迎先开 issue 聊一聊思路。
