# Spike #1 决策：AI Ref 协议

> **⚠️ 已废弃（2026-05-04）**：本文档记录的两个 spike 决策都已被推翻，仅作为历史参考保留。
>
> - **Spike #1（自实现 data-ai-ref）**：已替换为 patchright 内置 `page.ariaSnapshot({mode:'ai'})` + `aria-ref=eN` 选择器引擎。完整继承 playwright 的 ARIA 树、ref 分配、iframe 递归、W3C accname 算法。见 `service/snapshot-ref.service.ts`。
> - **Spike #2（ghost-cursor + 手动 CDP）**：已删除。ghost-cursor 在 patchright 上 mousemove 不派发到 DOM；手动 `ctx.newCDPSession + cdp.send('Input.dispatchMouseEvent')` 会暴露 debugger 给反爬探测，违反 patchright 反检测保护。
>
> **当前实现**（见 `service/humanizer.service.ts`）：
> - 拟人化轨迹：手写 `page.mouse.move` 单步循环 + 二次 bezier 曲线 + 随机抖动
> - mousedown/mouseup：用 patchright 内置 `page.mouse.down/up`
> - **红线**：禁止 `ctx.newCDPSession + cdp.send`，所有交互走 patchright 内置 API
>
> 以下原文保留作为决策溯源。

---

**选定方案 A：自实现 data-ai-ref 注入**

## 理由
- 实现简单（一段 page.evaluate JS 即可）
- 不引入额外依赖
- 完全可控

## 实现要点
1. snapshot 命令时调用 `injectRefs(page)`，给所有可交互元素加 `data-ai-ref="eN"`
2. service 内存维护 `Map<sessionName, Map<ref, locator-info>>` 用于审计/调试
3. interaction 命令收到 ref 后用 `page.locator('[data-ai-ref="${ref}"]')` 定位
4. 页面跳转/重新 snapshot 后旧 ref 失效（DOM 变化）

## 不选 B/C 的理由
- B（selector）：AI 写 selector 容易错；ref 抽象更友好
- C（vendor playwright-cli）：playwright-cli 用 socket+state 同步，移植成本高

# Spike #2 决策：ghost-cursor 兼容

✅ ghost-cursor 在 patchright + neta-chromium 上工作正常。
- cursor.moveTo 走贝塞尔轨迹，patchright 没拦截 mouse 事件
- click 后 navigator.webdriver 仍 false（patchright 反检测维持）
- 类型不兼容需要 `createCursor(page as any)` cast

## 兼容层补丁（必须）

ghost-cursor 依赖 Puppeteer 内部 API，在 patchright（Playwright）上需要打补丁：

```typescript
const cdpSession = await ctx.newCDPSession(page);
const pageAny = page as any;
// ghost-cursor 用 page._client 发 CDP 命令
pageAny._client = cdpSession;
// ghost-cursor 在 catch 块中调用 page.browser().isConnected()
pageAny.browser = () => ({ isConnected: () => true });
const cursor = createCursor(pageAny);
```

## 点击方式

ghost-cursor 的 moveTo 通过 CDP `Input.dispatchMouseEvent` 移动鼠标。
点击必须也通过同一 CDP session 发送，不能混用 `page.mouse.down/up`（不同通道）：

```typescript
await cursor.moveTo({ x: targetX, y: targetY });
await cdpSession.send('Input.dispatchMouseEvent', {
  type: 'mousePressed', x: targetX, y: targetY, button: 'left', clickCount: 1,
});
await page.waitForTimeout(80);
await cdpSession.send('Input.dispatchMouseEvent', {
  type: 'mouseReleased', x: targetX, y: targetY, button: 'left', clickCount: 1,
});
```

humanizer.service.ts 直接 `import { createCursor } from 'ghost-cursor'` + 上述补丁即可。
