- PiAgentRuntime 改用 pi-durable Harness:每个 run 一个 JSONL 存储目录 sessionDir/<runId>/ + root 对话; 系统提示 / 能力工具 / 白名单与预算钩子装在每次运行自己的扩展里;watchEvents 翻译成 AgentRunEvent - 挂起(工具内 close)立即封口,调用保持未完成,重开后 resume 重放;replayGate 用调用级 memo - 旧格式 <runId>.jsonl 打开时抛 LegacySessionError;skills 加载与 <skill> 块格式内置(源自 pi-agent-core,MIT) - tool_call_end 新增可选 durationMs;用量取 pi.usage 合计(整段会话总量) - Azure provider 改名 azure,迁移 0093 改存量;移除 pi-agent-core,锁 pi-durable/pi-ai/chord 1.1.0 - 文档:ADR-003、pi-providers、runbook、agent README、CHANGELOG Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
27 KiB
原站视图: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.jsL1138–1180,camoufox-jslaunchOptions缺省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. 越界防护(人工接管时)
- 插件对每个 profile 的 context 装
context.route白名单(站点域名 +browserDomains),越界请求直接 abort——对 Agent 与人工同样生效。 policies.json限制浏览器本身的危险入口(§5.4)。- 交还时服务端核对当前 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.tsL193–196),要加图片通道。 pi-durable 的GenerationTask.beforeRequest钩子(原 pi-agent-coretransformContext)可在每轮请求前把较早的截图替换为占位文本,控制 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-i3Dockerfile):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/*