Files
UHD_Media_Tool/docs/design/webtop-remote-browser.md
T

27 KiB
Raw Blame History

原站视图:Webtop 4.0(Wayland)远程桌面 + 同一 Camoufox 会话

对应 REQ v0.7 §4.4、§4.7.1–4.7.4(S18–S20);现状见 docs/design/browser-provider.md、docs/runbook/browser.md。 状态:方案 / 待原型验证(2026-09-29 调研,未起容器)。文中"已核实"指读过源码或文档;"待验证"必须在 P0 原型里实测,不能当结论。

0. 结论先行

  • 能做到同会话。camofox-browser 1.17.0 原生支持有头运行:CAMOFOX_INTERACTIVE=desktop 时不再自起 Xvfb, 以 headless:false 启动 Camoufox,并把进程环境(DISPLAY/WAYLAND_DISPLAY/MOZ_ENABLE_WAYLAND)原样传给 Firefox (server.js L1138–1180,camoufox-js launchOptions 缺省 env = process.env)。所以 把 camofox-browser 装进 Selkies Wayland 桌面容器里跑,桌面画面上的窗口就是 Agent 经 camofox API 驱动的那个 Firefox 进程,不是另开的浏览器。
  • 镜像:以 ghcr.io/linuxserver/baseimage-selkies:debiantrixie(Webtop 4.0 的底座)叠加 camofox-browser, 而不是 webtop:latest(Alpine/musl)或 webtop:debian-xfce(不支持 Wayland)。浏览器桌面单独一个容器,app 不动(§5.1)。
  • 中日文输入以用户本机输入法为主:Selkies 客户端接管 composition 事件,把上屏文本按 Unicode keysym 注入 (已核实源码);容器内 fcitx5 只作备选。中日文显示由 Camoufox 自带字体包(含 Noto Sans SC/JP/KR)负责。
  • 输入权限用 Selkies 的双口令做服务端强制:持有人工租约的人拿控制口令,其他人拿只读口令,由我们的反代注入。
  • 硬前提:产品必须经 HTTPS(或 localhost)访问,否则 WebCodecs 与剪贴板 API 都不可用 → 自动回退截图模式。
  • 视觉闭环(§7)可行:pi-ai 0.87.1 的工具结果支持图片块;Agent 截图继续走 camofox API(视口坐标),不抓桌面。

1. 调研:Webtop 4.0 / Selkies 架构(来源见 §10)

项 事实
合成器 PIXELFLUX_WAYLAND=true 时 pixelflux(基于 Smithay)作采集合成器(wayland-1),其内嵌套 labwc 作应用合成器(wayland-0,带 Xwayland);false 走 X11(Xvfb+openbox)[W3][W7]
渲染 有 GPU 时 EGL/DMABUF 零拷贝;无 GPU 用 Pixman + Mesa llvmpipe[W1]
传输 缺省 WebSocket(SELKIES_MODE=websockets);WebRTC 为可选,才需要 STUN/TURN[W3]
编码 SELKIES_ENCODER 缺省 h264enc,h265enc,vp8enc,vp9enc,av1enc,jpeg;SELKIES_USE_CPU 强制软编;VAAPI/NVENC 需 /dev/dri 或 NVIDIA 运行时[W3]
性能 官方演示:N97 迷你机 1080p/60fps 多应用W1;纯 CPU 下 1080p 帧率未见官方数据,需 P0 实测
端口 3000 HTTP(只给反代)、3001 HTTPS 自签;Selkies 进程在容器内 localhost:8082,nginx 反代 SUBFOLDER 与 SUBFOLDERapi[W2][W7]
变体 webtop latest=Alpine XFCE;Wayland 支持的 Debian 变体只有 debian-i3(sway);debian-xfce/kde/mate 不支持 Wayland[W2]
底座 baseimage-selkies master 分支 = tag debiantrixie,Selkies 2.0.0、pixelflux 2.1.0,已装 fonts-noto-cjk、labwc、xwayland;HOME=/config、运行用户 abc(PUID/PGID)[W7]
常用变量 PUID/PGID/TZ、TITLE、SUBFOLDER(前后都带 /)、NO_DECOR、NO_FULL、DISABLE_IPV6、LC_ALL(写 LANG/LANGUAGE)、START_DOCKER(底座缺省 true,须设 false)、DRINODE/DRI_NODE、MAX_RES[W2][W3]
鉴权 底座 SELKIES_ENABLE_BASIC_AUTH=false;设 PASSWORD 会让 nginx 开 basic auth(只认一个口令);Selkies 自身 basic auth 支持第二口令 SELKIES_BASIC_AUTH_VIEWONLY_PASSWORD,用它登录的连接被服务端限为 viewer(无键鼠/剪贴板/命令)[W3][W8 stream_server.py/settings.py]
反代 转发 Upgrade/Connection,读写超时 ≥3600s,proxy_buffering off;可由反代注入上游 Authorization[W4]
加固 HARDEN_DESKTOP(禁 sudo/终端/xdg-open、关文件传输/命令/打印)、HARDEN_OPENBOX(labwc 下同样生效:去关闭按钮、禁右/中键菜单、禁 Alt-F4 等快捷键)[W5][W7 init-selkies-config]
自启动 Wayland 模式把 /defaults/autostart_wayland 复制为 ~/.config/labwc/autostart;s6 服务在 /etc/s6-overlay/s6-rc.d/;另支持 /custom-cont-init.d[W7]
iframe 容器 nginx 未设 X-Frame-Options/CSP(已核实 default.conf),可被同源 iframe 嵌入

剪贴板(读 clipboard-sync.js):文本双向;Chromium 在授予 clipboard-read 且页面聚焦时可无手势自动同步 (SELKIES_CLIPBOARD_SEAMLESS);Firefox/Safari 只能在用户手势内同步(Ctrl+V 的 paste 事件送入,复制出来需一次点击), 且 navigator.clipboard 只在安全上下文存在。服务端在 Wayland 下走 data-control 原生剪贴板,不 fork wl-copy。 iframe 里还需 allow="clipboard-read; clipboard-write"(Chromium 权限策略)。

IME(读 input.js、input_handler.py、tests/e2e/test_ime_composition.py):浏览器端监听 compositionstart/update/end,预编辑按码点差分发送,提交文本按 keysym 注入;服务端对布局缺失的 keysym (Unicode 平面 0x01000000|cp)临时改写合成器 keymap 绑定到空闲键码,嵌套合成器下改走 zwp_virtual_keyboard 文本批次。 源码注释明确:键码 >255 的绑定只有原生 Wayland 客户端看得见,Xwayland 客户端收不到 → Camoufox 必须以原生 Wayland 运行 (MOZ_ENABLE_WAYLAND=1)。Camoufox 152 的 libxul 含 Wayland 后端(libmozwayland.so、xdg_toplevel 等符号,已核实)。

容器内输入法(备选):labwc 新版本实现 input-method-v2/text-input-v3,Firefox Wayland 经 GTK text-input-v3 接 IME, 理论上 fcitx5(fcitx5-chinese-addons/fcitx5-rime + fcitx5-mozc)可用;需 XMODIFIERS=@im=fcitx,Wayland 下 不要设 GTK_IM_MODULE=fcitx(会绕过 text-input-v3)。与本机 IME 同时开会双重组字,缺省关闭(Ctrl+Space 切换)。待验证。

字体:camoufox-js 在 Linux 上给 Firefox 设 FONTCONFIG_PATH 指向自带 fontconfig/<os>,只加载自带 fonts/<os> (Linux 包含 NotoSansSC/JP/KR-Regular.otf,Windows 包含 simsun.ttc/msgothic.ttc)——网页与浏览器 UI 的中日文由它负责, 系统 fonts-noto-cjk 只影响 labwc 标题栏、fcitx5 候选框等。这是反指纹设计,不要往 Camoufox 里硬塞系统字体。

2. 调研:camofox 有头运行与同会话

  • 启动路径(已核实):Linux 且 interactiveMode !== 'desktop' 时每次启动浏览器新建 Xvfb(1280×720,VNC 插件改 1920×1080); desktop 模式跳过 Xvfb,headless = !useDesktopWindow = false。
  • 自带 VNC 插件(ENABLE_VNC,x11vnc+noVNC)能看同一个 Xvfb,但无中文 IME 注入、无剪贴板/HTTPS/鉴权整合、画质差, 只适合作对照,不作为方案。
  • 同一 Firefox 被 Playwright(Juggler)驱动 + 人在桌面点击:Juggler 通过协议合成事件,不抓 OS 输入,也不锁输入,二者共存; Playwright 还对每个页面做"焦点模拟",人工切窗口不影响 Agent。待验证:人工输入期间 Agent 的截图/快照不受干扰。
  • 窗口对应:camofox 一个 userId = 一个 BrowserContext(我们的站点/账号 profile),sessionKey = 标签组。有头 Firefox 下 Playwright 新页面多半各开窗口(待验证);labwc 规则会把所有窗口最大化。接管时需把目标页置前:插件调 page.bringToFront();Wayland 禁止客户端抢焦点时,退路是 wlrctl toplevel focus(labwc 支持 foreign-toplevel)。
  • 生命周期:BROWSER_IDLE_TIMEOUT_MS 到期浏览器整体退出(窗口消失),SESSION_TIMEOUT_MS 到期关闭 context(窗口消失)。 人工操作期间 camofox 看不到 API 请求 → 接管期间服务端必须心跳,否则人手里的窗口会被回收。
  • 风险点:用户点窗口关闭按钮 = 关掉 Playwright 页面(→ 我们对账为 lost);用户在地址栏输入任意网址 = 越出站点白名单。 前者用 HARDEN_OPENBOX 去掉关闭按钮;后者见 §6。

3. 与现有实现的对接

现有 桌面方案
原站视图 = 1.5s 截图轮询 + 坐标点击(site-origin-view.tsx,coords.ts) iframe 嵌 Selkies 客户端;截图模式保留为回退
接管 = control/acquire → 适配器拒自动化输入 → 写租约 不变;另外把本人的桌面连接升级为 controller 口令
人工输入走 HTTP 端点(click/type/scroll…,需持租约) 人工输入直接进桌面;HTTP 端点保留(截图模式用)
Agent:snapshot/navigate/interact/download 走 camofox API 不变。截图也走 camofox API,不抓桌面(坐标系不同,见 §7.3)
下载捕获:camofox downloads → 暂存核验 → 计划链路 不变(人工在桌面点的下载同样被 Playwright 捕获,待验证)
交还后 needsSnapshot=true,旧 eN 作废 不变;另外校验当前 URL 在白名单内,不在则标记并回到站点首页

4. 资源与网络安全

  • 资源(全部待 P0 实测,以下仅为估计口径):现 camofox 空闲约 200 MB、2 会话约 390 MB(runbook §7 实测); 桌面栈另加 labwc + pixelflux + Selkies(Python)+ nginx + pulseaudio 常驻,有客户端连接时再加编码 CPU。 建议缺省 mem_limit: 3g、shm_size: 2gb,分辨率锁 1280×900,SELKIES_FRAMERATE=8-30|locked,纯 CPU 优先 jpeg 或 h264enc(实测后定)。无人观看时是否停止编码要实测(决定"开着原站视图空跑"的成本)。
  • 网络:3000/3001/8082 不映射到宿主,只在 compose 内网;唯一入口是 app 的 /browser/desktop/* 反代。 WebSocket 模式下不需要 TURN(局域网/公网都走同一个 HTTPS 连接);不启用 WebRTC。
  • 剪贴板/文件边界:只开文本剪贴板(SELKIES_ENABLE_BINARY_CLIPBOARD=false|locked);文件传输、打印、音频、麦克风、 摄像头、手柄、分享链接、命令通道全部关闭并 |locked;HARDEN_DESKTOP=true 去掉终端与 sudo(底座缺省终端带免密 sudo)。
  • SELKIES_ALLOWED_ORIGINS 设为产品对外 Origin;不启用 Pelorus / PIXELFLUX_CU(无鉴权的 Computer Use 端口)。

5. 方案

5.1 合一方式:A(浏览器桌面独立容器)还是 B(塞进 app 镜像)

维度 A:Selkies 桌面 + 有头 camofox 一个容器,app 独立 B:Deno app + 桌面 + 浏览器一个镜像
同会话 成立:GUI 与 Firefox 同容器同 Wayland 会话 同样成立,无额外收益
资源 桌面栈只在浏览器容器;可 CAMOFOX_REPLICAS=0 整体关掉 app 常驻即背上 GUI 栈;不用浏览器也关不掉
生命周期 浏览器 OOM/崩溃/重启只影响浏览器;API、worker 继续(app 已按 502/410 降级) 容器 OOM 会把 API 与 worker 一起杀掉;s6 做 PID1 与 app 的信号/落盘时序互相牵制
安全面 Firefox 渲染不可信 PT 页面,逃逸后只拿到浏览器容器(无 DB 密钥、无媒体库挂载) 逃逸即拿到 SECRET_MASTER_KEY、数据库口令与媒体库写权限,直接违反 REQ §4.4"浏览器没有媒体库写权限"与 §4.7.2"浏览器后端独立运行"
镜像/构建 浏览器镜像约 3.6 GB 级别单独构建、少变;app 镜像保持 trixie-slim + Deno 小体积 app 镜像膨胀到数 GB,每次业务发版都重推
升级独立 Selkies/Camoufox/app 各自升级、各自回滚 任一升级都要整体回归

结论:支持协调者的 A。另有 A′(桌面容器与 camofox 容器分开,共享 Wayland socket 卷)技术上可行 (Unix socket 可传 fd),但要对齐 UID、XDG_RUNTIME_DIR、重启顺序,收益只有"桌面可单独关",不值得。

5.2 镜像:底座选 baseimage-selkies,而不是反过来

  • 不用 webtop:latest:Alpine/musl,Camoufox 发行包是 glibc 构建;不用 webtop:debian-xfce:不支持 Wayland; webtop:debian-i3 可用但带 sway/i3/Chromium,我们只需要"单应用 kiosk"。Webtop 4.0 本身就是这个底座 + 桌面环境。
  • 不反过来(camofox 镜像上装 Selkies):pixelflux/pcmflux/labwc 层、s6 服务、nginx 模板都由 linuxserver 维护,自行拼装维护成本高。
  • 草案 docker/browser-desktop/Dockerfile(沿用现有 docker/camofox/Dockerfile 的构建阶段与两处修复):
FROM node:22-trixie-slim AS camofox-build        # 与现有 Dockerfile 相同:拉 Camoufox 152.0.4-beta.28、npm ci、插件依赖
# …(略:照抄 docker/camofox/Dockerfile 的 camofox-browser 阶段,产物 /app 与 /root/.cache/camoufox)

FROM ghcr.io/linuxserver/baseimage-selkies:debiantrixie@sha256:<锁定>
RUN apt-get update && apt-get install -y --no-install-recommends \
      libdbus-glib-1-2 libxt6 libasound2 wlrctl \
      fcitx5 fcitx5-chinese-addons fcitx5-rime fcitx5-mozc fcitx5-frontend-gtk3 \
    && rm -rf /var/lib/apt/lists/*          # fcitx5 一行在 P0 验证不通过时删除
COPY --from=camofox-build /usr/local/bin/node /usr/local/bin/node
COPY --from=camofox-build /app /opt/camofox
COPY --from=camofox-build /root/.cache/camoufox /opt/camoufox
COPY root/ /                                  # s6 服务 svc-camofox、labwc 规则、policies.json、umt 插件
ENV CAMOUFOX_EXECUTABLE_PATH=/opt/camoufox/camoufox-bin CAMOUFOX_INSTALL_DIR=/opt/camoufox

5.3 compose(替换现有 camofox 服务,保留服务名,BROWSER_PROVIDER_URL 不变)

  camofox:
    build: { context: docker/browser-desktop, additional_contexts: { upstream: https://github.com/jo-inc/camofox-browser.git#v1.17.0 } }
    image: ${CAMOFOX_IMAGE:-umt-browser-desktop:1.17.0-s2.0.0}
    deploy: { replicas: ${CAMOFOX_REPLICAS:-1} }
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: ${TZ:-Asia/Shanghai}
      LC_ALL: zh_CN.UTF-8
      PIXELFLUX_WAYLAND: "true"
      START_DOCKER: "false"
      SUBFOLDER: /browser/desktop/
      TITLE: 原站视图
      HARDEN_DESKTOP: "true"
      HARDEN_OPENBOX: "true"
      NO_DECOR: "true"
      SELKIES_ENABLE_BASIC_AUTH: "true"          # 不设 PASSWORD:nginx 层只认单口令,会拦掉只读口令
      SELKIES_BASIC_AUTH_PASSWORD: ${BROWSER_DESKTOP_CTL_KEY:?}
      SELKIES_BASIC_AUTH_VIEWONLY_PASSWORD: ${BROWSER_DESKTOP_VIEW_KEY:?}
      SELKIES_ALLOWED_ORIGINS: ${UMT_PUBLIC_ORIGIN:?}
      SELKIES_MANUAL_RESOLUTION: "true|locked"
      SELKIES_MANUAL_WIDTH: "1280"
      SELKIES_MANUAL_HEIGHT: "900"
      SELKIES_SCALING_DPI: "96|locked"
      SELKIES_FRAMERATE: "8-30|locked"
      SELKIES_ENABLE_BINARY_CLIPBOARD: "false|locked"
      SELKIES_FILE_TRANSFERS: "none"
      # 音频/麦克风/摄像头/手柄/打印/分享/命令/侧栏应用与文件:SELKIES_*_ENABLED 或 SELKIES_UI_* 一律 "false|locked"(§4)
      CAMOFOX_INTERACTIVE: desktop
      CAMOFOX_PROFILE_DIR: /config/.camofox/profiles
      # 其余 CAMOFOX_* / SESSION_TIMEOUT_MS / BROWSER_IDLE_TIMEOUT_MS / MAX_SESSIONS 沿用现有 camofox 服务
    volumes: [ "umt-browser-desktop:/config" ]     # 旧卷 umt-camofox 的 profiles 一次性迁移并 chown PUID
    shm_size: 2gb
    mem_limit: ${CAMOFOX_MEM_LIMIT:-3g}
    stop_grace_period: 30s                           # s6 逐级 SIGTERM,camofox 落盘需要时间

截图-only 部署保留旧 docker/camofox/Dockerfile 与镜像(CAMOFOX_IMAGE=camofox-browser:1.17.0),app 按能力探测自动选模式。

5.4 启动脚本

  • svc-camofox(s6 longrun,依赖 svc-de):等 ${XDG_RUNTIME_DIR}/wayland-0 出现后以 abc 身份 exec node --max-old-space-size=… /opt/camofox/server.js(保持 exec,SIGTERM 直达 node),环境: WAYLAND_DISPLAY=wayland-0、MOZ_ENABLE_WAYLAND=1、XMODIFIERS=@im=fcitx、CAMOFOX_INTERACTIVE=desktop。 设 S6_KILL_GRACETIME/服务 timeout-finish ≥15s,保证持久化插件关机落盘(runbook §7 第 7 条的教训)。
  • labwc:~/.config/labwc/autostart 只放 fcitx5 -d --replace(备选 IME);rc.xml 增加 camoufox 的 serverDecoration="no" + Maximize 规则;不自启任何其它应用,桌面为空时前端显示"浏览器空闲,打开会话后出现"。
  • /opt/camoufox/distribution/policies.json:禁 DevTools、about:config、file://、扩展安装、密码保存提示。

5.5 camofox 插件 plugins/umt(随镜像,只加路由不改上游代码)

POST /umt/tabs/:tabId/focus(bringToFront,失败回退 wlrctl 按标题)、POST /umt/sessions/:userId/touch(接管期心跳, 刷新会话与浏览器空闲计时)、POST /umt/tabs/:tabId/mouse(坐标点击/按下/移动/抬起/滚轮,§7)、 GET /umt/tabs/:tabId/screenshot?scale=css&type=jpeg&mask=…(§7)、POST /umt/sessions/:userId/route-guard(context.route 域名白名单,§6)。 全部挂 camofox 的 auth 中间件(同一 CAMOFOX_ACCESS_KEY)。

5.6 server 反代与鉴权

  • 路由 /browser/desktop/*(HTTP 静态资源)与 /browser/desktop/api(WebSocket):要求控制台 cookie 会话 + 浏览器接管权限; 静态资源 fetch 透传到 http://camofox:3000/browser/desktop/…,补 Content-Security-Policy: frame-ancestors 'self'。
  • WebSocket:Deno.upgradeWebSocket 接客户端,再 new WebSocket(upstream, { headers: { Authorization } }) 连容器,双向转发 (Deno 2.9.7 的客户端 WebSocket 支持自定义头,已本机实测)。Authorization 由服务端决定:当前用户持有该 provider 下某会话的 active 人工租约 → 控制口令;否则 → 只读口令。两口令只在进程内(同 CAMOFOX_ACCESS_KEY 的处理)。
  • 租约变化(acquire/release/过期)时服务端主动关闭该用户的桌面 WS,Selkies 客户端自动重连并按新角色认证。 于是"自动化中"桌面只能看不能动(Selkies 服务端强制),"人工接管"才放开输入——与 §4.7.3"接管前停止自动化输入"对称。
  • 一个桌面承载所有站点会话:持有 A 站租约的人在桌面上理论上能切到 B 站窗口。缓解:接管时置前目标窗口、labwc 禁掉窗口切换快捷键; 严格隔离需每会话一个桌面实例(资源成本高,列为后续选项)。

5.7 前端原站视图

  • 能力新增 liveDesktop {supported, verified};supported && verified && window.isSecureContext 时默认桌面模式,否则截图模式,可手动切换。
  • <iframe src="/browser/desktop/" allow="clipboard-read; clipboard-write; fullscreen; keyboard-map">,顶栏沿用现有 控制者/站点/连接状态 + "接管 / 交还"按钮;接管时先调 focus 再重连 iframe。
  • 提示条:未接管时"只读观看,Agent 正在操作";接管后"用本机输入法直接输入中文/日文;Firefox 下复制粘贴需按 Ctrl+C/Ctrl+V"。 非 HTTPS 访问时说明"桌面模式需要 HTTPS"。

6. 越界防护(人工接管时)

  1. 插件对每个 profile 的 context 装 context.route 白名单(站点域名 + browserDomains),越界请求直接 abort——对 Agent 与人工同样生效。
  2. policies.json 限制浏览器本身的危险入口(§5.4)。
  3. 交还时服务端核对当前 URL,越界则记事件、导航回站点首页,Agent 在重新快照后继续。

7. 视觉自动化闭环(Agent 处理登录页 / 滑块 / Cloudflare 交互挑战)

7.1 已核实的前提

  • pi-ai 0.87.1:ToolResultMessage.content: (TextContent | ImageContent)[],ImageContent = {type:"image", data(base64), mimeType}。 各上游处理(读 dist/api/*):Anthropic 放进 tool_result 内容块;OpenAI Responses 转 input_image;OpenAI Chat Completions 另追加一条带 image_url 的 user 消息;Google Gemini 3+ 嵌在 functionResponse.parts,更早版本另起 user 轮;Bedrock 转 image 块; Mistral 视模型而定。全部以 model.input.includes("image") 为门,不支持视觉的模型得到占位文本。
  • 我们的 runtime 目前把能力输出 JSON.stringify 成单个 text 块(packages/agent/src/pi/runtime.ts L193–196),要加图片通道。 pi-agent-core 有 transformContext,可在每轮请求前把较早的截图替换为占位文本,控制 token 与缓存前缀。
  • camofox 1.17.0 没有坐标鼠标端点;适配器现在的坐标点击是 elementFromPoint → 选择器点击,对 canvas 滑块、 跨源 iframe(Turnstile 复选框)与拖拽无效。截图是 page.screenshot({type:'png'}),像素 = 视口 CSS 像素 × DPR,runbook 实测 DPR=1。

7.2 能力设计

能力 类别 说明
browser.screenshot read 调插件 scale=css JPEG(质量 70),用 Playwright mask 盖住 input[type=password] 与含 passkey 的元素;返回图片块 + {screenshotId, width, height, url(脱敏), title, refsCount};modelSafe=false 的页面(URL 含凭据等)拒绝给模型
browser.click_at execute {screenshotId, x, y, button?, clickCount?},坐标以截图像素计;服务端核对该截图仍是当前页(URL/导航序号未变,否则 409"先重新截图"),按记录的缩放换算成视口坐标,调插件 mouse(真实 page.mouse,跨 iframe 有效)
browser.type_at execute click_at 后以 keyboard 模式输入;登录字段仍只能用 credential 由服务端注入,模型不得看到或写出密码
browser.drag execute {screenshotId, from, to, durationMs},插件分段 mouse.move 生成带抖动的轨迹(滑块)
browser.solve_challenge execute(有界子流程) 见 §7.4

分辨率锁 1280×900 后视口约 1280×800,低于 Anthropic 1568 长边与 OpenAI 缩放阈值,上游不会再缩放,坐标可直接映射; 若视口更大,服务端先缩到长边 ≤1280 并记录比例。

7.3 坐标系:Agent 永远用 camofox 截图,不用桌面画面

桌面(Selkies)坐标 = 屏幕像素,含 labwc 与 Firefox 工具栏偏移,还受 Selkies 缩放影响;camofox 截图坐标 = 页面视口 CSS 像素, 与 page.mouse 同一坐标系。故 Agent 截图与点击只走 camofox API,Selkies 画面只给人看。锁 SELKIES_SCALING_DPI=96 保证 DPR=1, 截图用 scale=css 兜底,P0 验证"截图点 (x,y) = 页面 elementFromPoint(x,y)"。

7.4 solve_challenge 边界

  • 触发:快照判定 captcha/challenge 且站点策略 autoSolve=true 且当前模型支持图片;否则照旧转 waiting + 待处理 + 接管入口。
  • 允许自动的:图形字符验证码、滑块/拼图、Cloudflare Turnstile/托管挑战的复选框、普通登录表单(凭证服务端注入)。
  • 永不自动:二次认证/TOTP/短信/邮件验证码、本人设备确认、账户安全问题;密码错误一次即停(避免锁号)。
  • 预算:每次挑战 ≤8 步、≤2 次整体重试、≤90 秒;每站每日自动解题次数与 token 上限可配;超限即转人工。
  • 证据:每步截图(已遮蔽)+ 模型给出的动作与理由写入该会话的待处理事项/运行记录,供事后复核;截图不入模型以外的日志。
  • 失败/不确定 → waiting(challenge_unsolved),待处理事项带"在原站视图接管"链接;不在未知状态下继续点击。
  • 桌面方案下,人可在 iframe 里实时看到 Agent 解题过程,需要时直接接管——这是截图模式给不了的。

8. 验证清单(P0 原型 + 上线前)

# 项 通过标准
1 同会话 Agent navigate 打开详情页 → 桌面上可见同一页面;人工点击后 Agent snapshot 读到变化;tabId 不变
2 反向同会话 人工在桌面登录 → 交还 → Agent 直接访问受保护页不需再登录
3 中文显示 中/日文站点、标签页标题、地址栏、表单占位符无方块字
4 中文输入 Win 微软拼音 / macOS 拼音 / 搜狗:搜索框输入"流浪地球 2",含标点与候选翻页;Chrome 与 Firefox 客户端各一次
5 日文输入 macOS/Win 日文 IME:输入并变换"進撃の巨人",含片假名、全角空格
6 剪贴板 本机 → 远端(Ctrl+V 粘贴中文长文本);远端 → 本机(复制页面中文);HTTP 访问时明确提示不可用
7 输入权限 未接管时桌面键鼠无效(服务端拒绝);接管后生效;交还后立刻失效
8 重启保持 docker compose restart camofox 后登录仍在;SIGKILL 情形与现状一致(已知限制)
9 坐标一致 截图坐标点击命中预期元素;Turnstile 类跨源 iframe 与 canvas 滑块可点/可拖
10 越界 地址栏输入站外网址被拦;关闭按钮不存在;无终端、无 sudo
11 资源 记录空闲/1/2/4 会话内存,观看时 1280×900 的 CPU 与帧率,无人观看时 CPU
12 下载 人工在桌面点 .torrent 被 camofox 捕获、进暂存,不重复提交

9. 风险与回退

风险 影响 回退
Camoufox/Playwright 在原生 Wayland 下异常(窗口、截图、WebGL) 同会话不成立 改 Xwayland(MOZ_ENABLE_WAYLAND=0)+ 容器内 fcitx5(XIM)输入;仍不行则保留截图模式
本机 IME 注入丢字/重复 中日文输入不可用 启用容器内 fcitx5;或前端"输入文本"框经 camofox type 注入(现有能力)
纯 CPU 编码占用过高 宿主负载 降帧率/分辨率、改 jpeg;挂 /dev/dri 用 VAAPI
Wayland 下置前窗口失败 人看到别的站点窗口 wlrctl;不行则同一时刻只保留被接管会话的窗口可见
部署只有 HTTP 桌面模式不可用 自动截图模式,提示配置 HTTPS
Selkies/底座升级破坏行为 回归 镜像按 digest 锁定;核验表按"camofox 版本 + Selkies 版本"登记

截图模式代码与能力保持不删;桌面能力 liveDesktop.verified 只由真机核验置 true(同 runbook §6 规则)。

10. 实施拆分与估算(人日,不含等待构建)

阶段 内容 估算
P0 原型 临时构建镜像,验证 §8 的 1、3、4、5、6、9、11;不通过则按 §9 调整或止损 2
P1 镜像与 compose Dockerfile、s6 服务、labwc 规则、policies.json、卷迁移、runbook 1.5
P2 camofox 插件 focus / touch / mouse / screenshot(mask, css) / route-guard + 测试 1.5
P3 server /browser/desktop 反代、WS 转发与角色切换、心跳、能力 liveDesktop 2
P4 前端 iframe 模式、接管联动、提示、模式切换 1.5
P5 真机核验 §8 全部 + 四站登录;更新核验表与 runbook 1.5
V1 视觉通道 runtime 图片块 + transformContext 截图裁剪 + 能力 browser.screenshot 1.5
V2 坐标动作 click_at / type_at / drag + 截图有效期校验 1.5
V3 解题子流程 solve_challenge 策略、预算、证据、转人工;mock 滑块/复选框页测试 2.5

合计约 15.5 人日。P0 不通过不进入 P1;V 系列不依赖桌面,可与 P1–P4 并行(截图与鼠标插件在 P2 共用)。

来源