- 删除 docs/plan 下 00–18 的计划、评审、验收、发布记录与升级证据,docs/spikes 与 spikes 验证代码
(保留 spikes/movieclaw-src 本地克隆),以及 4 份一次性审查 / 调研;全部可在 ceb66f8 查到
- MP 对照移到 docs/porting/moviepilot-v3-comparison.md;经验教训改为 docs/plan/lessons.md(补第 25 条)
- docs/plan/README.md:文档放哪、当前状态、待办
- 引用改写:代码注释指向在维护的文档,其余注明已删除与 git 位置;清掉原有 6 个失效链接
- README 开头改为当前状态与入口;CHANGELOG 补齐 0.3.8–0.3.14 与工程变更
Co-Authored-By: Claude Opus 5.5 <[email protected]>
320 lines
27 KiB
Markdown
320 lines
27 KiB
Markdown
# 原站视图: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` 的构建阶段与两处修复):
|
||
|
||
```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` 不变)
|
||
|
||
```yaml
|
||
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 共用)。
|
||
|
||
## 来源
|
||
|
||
- [W1] linuxserver 博客《Webtop 4.0 – Wayland is here》:https://www.linuxserver.io/blog/webtop-4-0-wayland-is-here-engage-the-reality-engine
|
||
- [W2] linuxserver/docker-webtop README 与 `readme-vars.yml`(commit a34075b,分支 `debian-i3` Dockerfile):https://github.com/linuxserver/docker-webtop
|
||
- [W3] Selkies 配置参考:https://docs.linuxserver.io/selkies/user-guide/configuration/
|
||
- [W4] 反向代理:https://docs.linuxserver.io/selkies/user-guide/reverse-proxy/
|
||
- [W5] 安全与加固:https://docs.linuxserver.io/selkies/user-guide/security/ ;Web 客户端:https://docs.linuxserver.io/selkies/user-guide/web-client/
|
||
- [W7] linuxserver/docker-baseimage-selkies(commit 094bd40:`Dockerfile`、`root/defaults/*`、`init-selkies-config`、`init-nginx`、`svc-de`):https://github.com/linuxserver/docker-baseimage-selkies
|
||
- [W8] selkies-project/selkies(commit 3291323:`addons/selkies-web-core/lib/input.js`、`lib/clipboard-sync.js`、`src/selkies/input_handler.py`、`stream_server.py`、`settings.py`):https://github.com/selkies-project/selkies
|
||
- [W9] 本机 `camofox-browser:1.17.0` 镜像内 `server.js`、`lib/config.js`、`plugins/vnc`、`node_modules/camoufox-js/dist/utils.js`;
|
||
`node_modules/.deno/@[email protected]` 的 `dist/types.d.ts` 与 `dist/api/*`
|