Files
UHD_Media_Tool/docs/design/browser-provider.md
T
rootandClaude Opus 5.5 effaa32b70 feat(browser): W4-C BrowserProvider contract and camofox-browser adapter skeleton
- contracts/browser.ts: provider kind, per-capability {supported, verified} flags,
  session/tab/control-lease rows, NavigationBudget, redacted BrowserSnapshot,
  DownloadArtifact with blob/secret refs, BrowserErrorKind
- adapters/browser: BrowserProvider interface, camofox client (health, capabilities
  static + /openapi.json probe, openSession/navigate/snapshot/screenshot; rest
  not_implemented for M2), none provider, redactSnapshot, profile serial lock,
  domain allowlist budget; Bearer access key via BrowserSecretResolver
- testkit/browser: mock camofox server (auth, fixtures with secrets, faults)
- db 0023: browser_provider/session/tab/control_lease with one-active-lease and
  one-live-session-per-profile partial unique indexes; types regenerated
- docs/design/browser-provider.md: contract, camofox OpenAPI 1.17.0 mapping,
  unverified items and M2 prototype checklist; Playwright is not a product browser

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 21:39:29 +08:00

11 KiB
Raw Blame History

BrowserProvider 合同与 camofox-browser 适配

对应 REQ v0.7 §4.4、§4.7.2–4.7.4、§12.1 浏览器实体、附录 S18–S20;计划 docs/plan/00-first-version-plan.md §3.2:v0.1 只落接口,浏览器接入与原站视图在 M2。

代码位置:

层 路径
合同(zod) packages/contracts/src/browser.ts
Provider 接口与实现 packages/adapters/src/browser/**(provider.ts、camofox/、none.ts、redact.ts、profile-lock.ts、budget.ts)
mock camofox packages/testkit/src/browser/mock-camofox.ts
表结构 packages/db/migrations/0023_browser.ts

1. Playwright 不是产品浏览器能力

产品内所有浏览器动作(站点排障、原站视图、Agent 受控交互、下载捕获)只通过 BrowserProvider 访问浏览器服务,首个实现对接用户已经运行的 camofox-browser。仓库里的 Playwright 只用于前端截图/视觉测试,不作为产品能力、不在服务端启动、不作为 camofox 不可用时的回退。不得把"另起一个浏览器完成了操作"标为同会话协作(REQ §4.7.2)。

2. 合同要点

  • BrowserProviderKind:camofox | none。none 为默认,能力全关,动作报 unavailable,界面据此显示"浏览器不可用"。
  • BrowserCapabilities:sessions / tabs / navigate / snapshot / screenshot / interact / extract / downloadCapture / takeover / persistentProfile,每项是 { supported, verified, source, note? }:
    • supported:本适配器当前对该上游能提供(上游有端点但适配器尚未实现的也记 false,并注明 M2);
    • verified:仅在真实实例原型验证通过后由配置注入为 true,探测永远不会把它设为 true;
    • source:static(按文档声明)或 probed(从上游 /openapi.json 推导);
    • 另有 upstreamVersion、probedAt。界面只把 supported && verified 视为可用(REQ §4.7.4)。
  • BrowserSession:providerId, siteId, accountId, profileKey, controller, currentUrl, lastActivityAt, upstreamRef, recoveryState, closedAt, closeReason。controller ∈ automation | human | waiting | closed;非 automation 时适配器拒绝自动化动作(control_conflict)。
  • BrowserTab:上游标签页映射(upstreamTabId、url、status open|closed|lost、lastSnapshotAt)。
  • BrowserControlLease:sessionId, holderKind(automation|human), holder, reason, status, acquiredAt, expiresAt, releasedAt, releaseReason;每会话至多一条 active(DB 部分唯一索引)。
  • NavigationBudget:domainAllowlist[](含子域;空 = 全拒)、timeoutMs、maxRetries(只重试超时/网络/5xx/503)、maxBytes(快照截断、截图超限报 budget_exceeded)。导航前检查目标,导航后检查落地地址(重定向出白名单报 domain_blocked)。
  • BrowserSnapshot:可直接交给模型的结构 —— 脱敏后的可访问性文本(元素以 eN 引用)、脱敏 URL、表单/表格摘要、redactions 计数、分页 nextOffset。不含原始 HTML/脚本。
  • DownloadArtifact:id, sessionId, filename, mimeType, sizeBytes, bytes: BlobRef{kind: blob|secret, ref}, sha256, capturedAt。文件内容只以引用出现(种子可能含 passkey),交后端暂存核验后再走原有提交链路。
  • BrowserErrorKind:not_implemented, unavailable, auth, timeout, network, not_found, rate_limited, bad_request, upstream, protocol, domain_blocked, budget_exceeded, control_conflict, lock_timeout。

安全约束

  • 访问凭据(camofox CAMOFOX_ACCESS_KEY)只以 browser_provider.secret_ref 保存,每次请求经 BrowserSecretResolver 解出;日志只记方法/路径(不含 query)/状态/耗时;错误信息不含凭据。
  • 站点登录凭据经 TypeInput { kind: 'secret' } 由服务端解析后直接下发到输入框,不进入模型(M2)。
  • redactSnapshot:遮蔽密码框值(a11y 文本、HTML、structure JSON 三种形态)、Cookie 值(Cookie: 行与常见站点会话 Cookie 名)、URL 凭据参数(passkey/authkey/rsskey/token/…)、路径形式 passkey 与 URL userinfo。camofox 快照中密码框的真实呈现方式未核验,规则宁可多遮。
  • 截图暂不做像素级遮蔽:登录页或含凭据地址的截图标 modelSafe: false,只给人看不喂模型。
  • 同一 profileKey 的操作串行(ProfileLock,当前进程内实现;多进程部署需换 PG advisory lock 或基于 browser_control_lease 的持久化实现)。
  • 浏览器没有媒体库写权限;人工接管不是对其他站点、宿主终端或文件系统的授权。

3. camofox-browser OpenAPI 对照

来源:https://raw.githubusercontent.com/jo-inc/camofox-browser/master/openapi.json(info.version 1.17.0)与同仓 README,2026-09-27 取得,仅读文档,未连接真实实例;用户实际部署的仓库/fork/版本尚未确认。

上游模型:一个 Camoufox 实例 → userId 对应一个隔离 BrowserContext(cookies/storage)→ sessionKey 为标签组 → 标签页。本项目以 profileKey(站点/账号)作 userId,以 browser_session.id 作 sessionKey。认证:设置 CAMOFOX_ACCESS_KEY 后,除 /health、cookie 导入、/stop 外全部路由要求 Authorization: Bearer <key>;CAMOFOX_API_KEY 另管 cookie 导入与 traces。

合同方法 camofox 端点 状态
health GET /health(200 {ok, engine, browserConnected, browserRunning, activeTabs, activeSessions, consecutiveFailures};503 {ok:false, recovering}) 已实现(mock 验证)
capabilities GET /openapi.json(按 paths 推导) 已实现(mock 验证)
openSession POST /tabs {userId, sessionKey, url?} → {tabId, url, httpStatus, navigationOk};上游无显式建会话端点 已实现(mock 验证)
navigate POST /tabs/{tabId}/navigate {userId, url} → {ok, tabId, url, httpStatus, navigationOk} 已实现(mock 验证)
snapshot GET /tabs/{tabId}/snapshot?userId&format=text&offset → {url, snapshot, structure?, refsCount, truncated, hasMore, nextOffset} 已实现(mock 验证)
screenshot GET /tabs/{tabId}/screenshot?userId → {screenshot:{data(base64), mimeType}};兼容直接返回 image/* 已实现(mock 验证)
closeSession DELETE /sessions/{userId}(关闭该 userId 全部标签);或 DELETE /tabs/group/{listItemId} M2
listSessions GET /tabs?userId(只有标签页) M2,以 DB 为准
click / type `POST /tabs/{tabId}/click {ref selector}、/type {ref
extract POST /tabs/{tabId}/extract {schema}(属性带 x-ref,需先 snapshot) M2
waitForDownload GET /tabs/{tabId}/downloads?includeData&consume&maxBytes(README;OpenAPI 未列这些 query) M2
acquireControl / releaseControl 无;VNC 插件(ENABLE_VNC、noVNC 6080)提供整块虚拟显示,不在 OpenAPI 中 M2,原型验证

与设计的差异 / 待核验

  1. 上游没有会话概念的建立端点:会话在首个 POST /tabs 时隐式创建,由 SESSION_TIMEOUT_MS(默认 10 分钟,README 架构节写 30 分钟,自相矛盾)空闲回收;标签页 TAB_INACTIVITY_MS 默认 5 分钟回收,且达到上限时"最旧标签被自动回收"。本项目的会话/标签状态必须以 DB 为准并定期对账,不能假定上游会话长期存在。
  2. userId 允许字符集、长度未在 OpenAPI 定义(profileKeyFor 生成 umt-site-<uuid>-acct-<uuid>),待核验。
  3. 快照是可访问性树文本([button e1] Submit),密码框的值是否出现、以什么形式出现未核验;format=json 与 structure 的具体字段也未核验。
  4. 截图 OpenAPI 为 JSON base64;是否有版本直接返回二进制未核验(适配器两者兼容)。
  5. README 的 type 示例用 pressEnter,OpenAPI 用 submit。
  6. 接管:上游没有 API;VNC 插件是整块显示,能否定位到同一会话/标签、画面认证能否接产品认证,需原型;CAMOFOX_INTERACTIVE=desktop 只适用于本机桌面。
  7. 持久化:持久化插件默认保存 cookies + localStorage 到 ~/.camofox/profiles,sessionStorage 不保存,IndexedDB 需显式开启;"重启后仍登录"要逐站验证(REQ §4.4)。
  8. 下载捕获的 includeData 返回 base64,大文件内存占用与 maxBytes 行为待验证。

4. 表结构(0023)

  • browser_provider:name, kind, base_url, secret_ref, enabled, capabilities jsonb, verified_at, health_status, last_health_at, config;kind <> 'none' 时必须有 base_url。
  • browser_session:provider_id, site_id, account_id, profile_key, controller, current_url(脱敏), last_activity_at, upstream_ref, recovery_state, closed_at, close_reason;(provider_id, profile_key) 在未关闭会话中唯一;controller = 'closed' 与 closed_at 一致。
  • browser_tab:session_id, upstream_tab_id, url, title, status, last_snapshot_at, closed_at;(session_id, upstream_tab_id) 唯一。
  • browser_control_lease:session_id, holder_kind, holder, reason, status, acquired_at, expires_at, released_at, release_reason;session_id 在 status = 'active' 中唯一,expires_at > acquired_at。

5. M2 原型验证清单(REQ §4.7.4)

先固定用户现有 camofox-browser 的仓库、版本(/openapi.json 的 info.version)、启动方式与认证配置,再逐项验证,结果写入 browser_provider.capabilities 的 verified 与 verified_at。未通过项在界面标为不可用,不得用另一浏览器窗口代替后宣称通过。

# 项目 验证方法 通过标准
1 登录保持 观众/馒头/HHClub/CHD 各一账号:导入 Cookie 或经接管登录后,闲置超过 SESSION_TIMEOUT_MS 再访问 无需重新登录;失败时准确识别"登录过期"而非空列表
2 结构化快照 列表页/详情页/登录页 snapshot 元素引用稳定可点;密码框值、Cookie、passkey 经 redactSnapshot 后不可见;大页面分页正确
3 截图 列表/详情/登录页 返回格式与大小;登录页 modelSafe=false
4 列表/详情提取 快照 + extract(x-ref)对比现有 HTTP 适配器结果 标题/分类/副标题/促销/做种字段一致,原始字段不丢
5 下载捕获 详情页点击下载 .torrent downloads 能拿到文件与文件名;sha256 与直接 HTTP 下载一致;进入后端暂存核验,不直写媒体库
6 重启后会话 重启 camofox 容器/浏览器(BROWSER_IDLE_TIMEOUT_MS 触发与强制重启各一次) 持久化 profile 恢复登录;旧 tabId 失效被识别为 lost,不重放旧元素引用
7 人机同标签页连续操作 Agent 打开详情页 → 转人工(VNC)在同一标签页操作 → 交还 Agent 重新 snapshot 人工看到的是同一 Camoufox 会话与页面;交还后 Agent 读到人工操作后的状态;不重复提交下载
8 异常路径 登录过期、页面结构变化、需人工验证(验证码/2FA)、服务不可用(503/断连) 分别落为 auth / 提取失败标过期 / waiting 待处理 / unavailable,均不无限重试
9 性能与资源 四站并发 1/2/4 会话 记录启动延迟、实时查询延迟、内存、失败恢复时间,作为默认并发与空闲释放参数依据