远程 Host 与 Relay
远程控制通过 Relay 将已配对客户端连接到 Wuu Host。Host 运行 Agent 并访问工作区, Relay 转发端到端加密的 app-server 连接。配对授予的是 Host 控制权,不只是只读查看权限。
手机配对目前是开发预览:用 npm --prefix desktop run dev:remote 启动桌面,
可在「设置 → 手机访问」中直接配对原生 iOS App,无需账号。发布验收完成前,
正式构建暂不展示手机访问入口。App 目前需要从
原生客户端项目构建,尚未提供公开 App Store 分发。
桌面账号登录仍仅在开发模式开放。CLI 构建方法见开发指南。
选择桌面会话连接
Section titled “选择桌面会话连接”默认桌面的真实会话池读取可选环境变量 WUU_DESKTOP_SESSION_ROUTES 指定的文件。
未配置时保留本地 stdio 路径;明确选择远端或禁用后,配置错误、凭据缺失和远端离线
都不会退回本机进程。
{ "version": 1, "default": { "kind": "local" }, "contexts": { "/Users/example/remote-project-view": { "kind": "remote", "execution_host": "<已配对主机公钥>", "storage_host": "<存储主机标识>", "display_host": "<当前桌面标识>", "workdir": "/srv/project", "connector": { "kind": "keychain", "service": "wuu.remote.my-host" } } }}在桌面启动环境中设置 WUU_DESKTOP_SESSION_ROUTES=/absolute/routes.json。
contexts 的键是已注册的本机展示工作区。需要同时使用多台主机时,为其分配不同的
展示工作区,即使远端路径或会话 ID 相同。远端 workdir 原样发送给 app-server,
不会按本机文件系统解析;请求顶层 cwd 等于展示工作区时会映射为远端路径。
storage_host 和 display_host 只描述位置,不迁移数据或授予权限,存储权威仍由
所选主机及其存储提供者决定。这是会话 RPC 连接,不会替换所有本机文件、终端或电脑控制功能。接收主机需提供所选桌面
UI 提供者,例如配置 composition: {version: 3, profile: "desktop"};headless 组合
不会自动回退为本机 GUI 提供者。
会话池在 list/start/resume 返回和 thread 通知中填充 Thread.session_locator,
并在路由事件中填充 ServerEvent.session_locator,统一使用 SessionLocatorV1
(version、execution_host、session_id?、storage_host?、display_host?)。
附件流将 thread locator 放入 RuntimeContext.session_locator 后绑定。主机、存储主机
或会话 ID 不匹配时,在发送前拒绝;定位元数据不是授权凭据。
macOS 内置连接器按 service 读取现有通用密码 Keychain 条目,内容为已配对
RemoteClient 凭据 JSON(v、device_seed、host_pub、relay_url)。应由受信任
配对或凭据管理器提供,禁止把凭据 JSON 或设备私钥放入路由文件。连接器校验
execution_host 与凭据固定的 host_pub 一致,并复用现有加密 RemoteClient。
Relay URL 本身不是授权。用户完整配对控制凭据不能交给仅获受限 session/federation
委派的 agent;接收端认证网关独立检查资源授权和委派上限。
外部受信任连接器可以配置为
"connector": { "kind": "module", "path": "/absolute/connector.mjs" }。
此模块以受信任的主进程代码运行,导出
connectDesktopSession({ version: 1, route, hooks }),返回连接对象或 Promise。
连接器负责验证所选执行主机,并应用相应用户或委派权限。安装连接器不构成沙箱边界。
从 @wuu/plugin-sdk 导入 ConnectDesktopSessionV1 和
DesktopSessionConnectionV1;内置 RemoteClient 适配器也使用同一结构化契约。
SDK 同时导出 DesktopSessionConnectionHooksV1、DesktopSessionRPCV1、
DesktopSessionRouteConfigV1 和 DesktopSessionContextV1(桌面上下文加可选的公开
SessionLocatorV1)。不需要导入私有 client、protocol 或 desktop 模块,也无需复制接口
定义。isDesktopSessionConnectionV1 只做浅层加载检查,不负责认证或验证契约行为。
这是完整 app-server 连接边界,不是范围更窄的 session.transport 联邦派发服务。
- 连接对象:
start(): void、stop(): Promise<void>、waitAttached(timeoutMs?): Promise<void>、rpc(): DesktopSessionRPCV1 | null。 - RPC:
call(method, params?, timeoutMs?, workdir?, onResult?): Promise<unknown>和isClosed(): boolean。必须在后续通知及 Promise 完成前同步调用onResult; 回调抛错时拒绝本次调用。明确的服务端拒绝使用DesktopSessionResponseErrorV1, 包含kind: "app-server-response"和字符串message;接纳后的其他连接错误属于结果未知。 - 回调:
onNotification(method, params, workdir?)、onServerRequest({id, method, params?})(返回{result?, error?})、onAttach({session, resumed})、onDetach()、onState({running})。 running 条目可带thread_id和workdir。
小幅本地增强可以包装已有的受信任连接器,不必替换整个 GUI 或提供者栈。例如,以下模块 只增加本地运行会话计数日志,其余回调和连接对象保持不变:
import type { ConnectDesktopSessionV1 } from "@wuu/plugin-sdk";import { connectDesktopSession as connectBase } from "./existing-connector.js";
export const connectDesktopSession: ConnectDesktopSessionV1 = options => connectBase({ ...options, hooks: { ...options.hooks, onState(state) { console.debug("Running remote sessions:", state.running.length); options.hooks.onState(state); }, }, });existing-connector.js 是你自己的连接器,不是 Wuu 私有适配器。将现有 module 路由
指向该包装模块即可。本地增强是可选项;完整替换提供者
仍是独立的组合选择。包装模块必须保留认证、权限和生命周期行为,不创建新服务,也不改变数据归属。
连接器返回尚未启动的连接,由桌面调用 start。start 和 stop 必须幂等;遵守传入的附着和
RPC 超时时间(目前分别为 10 秒与 30 秒),超时不代表取消远端工作。
续接必须保留同一 RPC 对象及待响应请求;全新连接必须替换并关闭旧 RPC,拒绝其待响应请求。
stop 只断开 GUI,不发送 app-server shutdown,也不终止远端任务。桌面阻止直接远端
shutdown,并默认拒绝远端浏览器、本机电脑控制和未知反向请求。审批和用户输入反向请求
通过现有 GUI 应答路由传递;界面不支持的展示请求仍明确拒绝。
在源码中运行 node --test packages/plugin-sdk/test/desktop-session-package.test.mjs,
可仅用打包后的公开 SDK 编译合成独立适配器,不依赖仓库私有模块。此检查验证作者边界,
不验证真实网络部署或连接器的可信程度。
每次请求或事件路由都会重读配置,旧响应和通知不能写入新路由。已发送调用不会自动重放:
续接可完成原请求;全新连接、路由变化或响应超时则报告结果未知,应先检查会话再决定重试。
bindSessionRequest(context, sessionID) 捕获实际会话 owner 的 bindRequest(),供
附件流等关联操作使用,不会偷偷迁移到其他主机或连接代次。配置变更在下一次路由操作生效;
更换所选主机后应重新初始化 GUI。
Mac 隔离验收:构建 core 与桌面,用 esbuild(Node/ESM)将
desktop/scripts/fixtures/remoteSessionPeer.ts 打包为
desktop/out/e2e/remote-session/connector.mjs,再用 Electron 启动
desktop/scripts/remote-session-e2e.mjs。该脚本使用临时 profile、合成身份、真实加密通道、
真实 Go app-server 和本机合成模型。报告与截图保存在 desktop/out/e2e/remote-session/;
不验证互联网 Relay 部署、Keychain 配置或 agent 委派授权。
连接 SSH 工作区
Section titled “连接 SSH 工作区”实验性的无桌面宿主在远端 macOS 或 Linux 电脑上运行 Wuu、已安装的 Agent 引擎、 文件、Git 和终端,本地浏览器通过 OpenSSH 隧道显示共享 Wuu 工作台。 Codex、Claude Code、Pi、OpenCode 等受支持引擎需在远端安装并登录; 引擎发现与会话状态都属于该电脑。
在远端同一份 Wuu 源码中构建,需要 Node 22+ 和 go.mod 指定的 Go 版本:
make setupnpm --prefix desktop run build:remotenpm --prefix desktop run remote:serve -- --workdir /srv/my-project --port 8787 --pair用 tmux 或服务管理器运行最后一条命令,避免退出 SSH 时服务随之停止。
服务只监听 127.0.0.1,并打印一次性的浏览器配对链接。
构建产物包含匹配的 Go 核心和网页资源;也可用 --core 和 --web-root
指定路径。不要让桌面的远程 Host 与该服务同时使用同一个 Wuu home。
SSH 服务使用本地设备配对;如果其 Wuu home 已登录 Wuu 账号,服务会拒绝启动,
并保留现有账号凭据。可使用独立目录,例如
WUU_HOME="$HOME/.wuu-ssh" npm --prefix desktop run remote:serve -- --workdir /srv/my-project --pair,
或自行退出该 Wuu 账号后再启动。独立目录拥有自己的 Wuu 配置和会话历史,
请在开始任务前完成配置。
在本地从同一版本构建、安装 CLI,然后连接:
wuu remote ssh --port 8787 user@server打开远端服务打印的配对链接。以后访问 http://127.0.0.1:8787/
即可使用已保存的配对。使用期间保持 SSH 命令运行,Ctrl-C 关闭隧道。
SSH 配置别名、密钥、主机密钥校验和 ProxyJump 沿用本机 OpenSSH 配置;
可用 --ssh-port 或 --ssh-config 显式指定连接参数。Wuu 始终在前台独占这条隧道,
不会为目的地主机连接复用或留下 control master,即使本机配置默认启用了连接共享或
后台运行。跳板机仍遵循其自身的 SSH 配置;已有或显式后台运行的跳板机 master 由用户管理。
Linux 和 macOS 上,没有控制终端时,Wuu 会将 SSH 及其前台代理子进程放在独立进程组中, 取消连接也会清理仍在连接的跳板机进程。有交互终端时保留 shell 作业控制和 OpenSSH 提示,即使标准输入被重定向也可交互认证。请用 Ctrl-C 向整个前台作业发送信号。 在这种交互场景下,只向 Wuu 的 PID 发信号,不能保证清理仍在认证的代理进程; 服务自动化应在没有控制终端的环境运行,或停止整个进程组。
CLI 的连接提示不是应用健康检查。请等待远端服务打印 Wuu workspace ready 后再
打开工作台;这表示监听器已运行,Host 已完成注册。打开工作区时才会发现远端的
Agent 引擎。ExitOnForwardFailure 能发现转发配置失败,不能证明远端应用可用。
如果 SSH 报错退出,请按报错排查后重新运行同一命令。Wuu 不会自动重试 SSH 或
反复请求密码;隧道恢复后,浏览器会自动重连。
每台主机使用不同的本地端口,让浏览器身份和草稿保持独立。
例如将远端 8787 映射到本地 8788:远端启动参数使用
--port 8787 --browser-port 8788 --pair,本地运行
wuu remote ssh --port 8788 --remote-port 8787 user@server。
更换对外声明的浏览器端口后需要重新配对。
关闭浏览器或断开 SSH 后,Agent 任务仍在远端服务中运行。重新连接隧道后,
工作台恢复会话与缓存输出。恢复原连接时终端也继续保留;若建立了全新连接,
工作台会提示重新打开终端。停止或重启远端服务会停止任务和终端,
已保存的会话历史仍在磁盘上。重启时不加 --pair,即可接受已有设备,
同时不开启新设备配对。
如果启动时的工作区被移除或迁移,重连仍可通过共享的「对话」工作区发现项目, 然后重新选择需要的项目。
将 Host 作为服务运行
Section titled “将 Host 作为服务运行”Linux 使用 systemd 时,先按上面的命令交互配对,然后停止该 Host,再启动服务。
通过 command -v node 确认 Node 的绝对路径,并替换实际源码、工作区和 Wuu home 路径:
[Unit]Description=Wuu SSH workspace
[Service]Type=simpleWorkingDirectory=/srv/wuuEnvironment=WUU_HOME=/home/YOUR_USER/.wuu-sshExecStart=/usr/bin/node /srv/wuu/desktop/out-remote/host.mjs --workdir /srv/my-project --port 8787Restart=on-failureRestartSec=5KillMode=control-groupTimeoutStopSec=15UMask=0077
[Install]WantedBy=default.targetsystemctl --user daemon-reloadsystemctl --user enable --now wuu-workspacejournalctl --user -u wuu-workspace -f# 主动停止服务及其任务:systemctl --user stop wuu-workspace如果管理员未启用用户 lingering(loginctl enable-linger YOUR_USER),用户服务可能在
登出时停止。请遵循电脑的管理策略;Wuu 不会自行安装或启用服务。macOS 可用 tmux,
或使用相同 Node 命令的 launchd 服务。服务重启会恢复已保存的历史,但不是进程检查点:
运行中的回合与终端会停止。启动失败或收到 SIGINT/SIGTERM/SIGHUP 时,服务会清理其
启动的子进程;取消后不会再报告就绪。
不要在自动重启的服务中加入 --pair;仅在准备配对新浏览器时开启配对,
并避免把配对链接写入共享日志。
这一生命周期参考了 code-server 的预安装服务与回环 SSH 转发方式。OpenSSH 负责端口转发 和连接共享;Wuu 的已有会话连续性机制负责恢复连接。
当前是从源码构建的浏览器工作台预览。无桌面宿主不提供原生桌面窗口、 内嵌浏览器/电脑控制工具或自动安装远端软件。需要原生浏览器的引擎登录, 应先在远端完成。本机的 SSH 凭据和模型配置不会上传。
连接 iOS App
Section titled “连接 iOS App”- 保持电脑上的 Wuu 运行。在「设置 → 手机访问」中允许访问,显示配对码。
- 手机连接同一 Wi-Fi,或按下文配置双方可达的 Tailscale 地址或 Relay。在 App 的 「连接电脑」中扫描配对码,或粘贴链接后选择「连接」。
- 选择已有会话,或新建会话并选择工作目录;也可以输入电脑上已经存在的绝对目录路径。 消息和任务控制通过加密连接发送给该电脑。
首次发送前,打开「… → 会话设置」选择引擎,列表来自所连接电脑上已启用且可用的引擎。 已有会话会显示绑定的引擎;使用其他引擎需要新建会话。同一页面提供所选引擎的模型、推理配置 和主机声明的权限选项,修改需要电脑在线且任务空闲。iOS 会记住保存的模型、推理配置与 执行权限,包括确认后的「不受限」,用于同一电脑、同一引擎的新会话,重启后仍有效; 账号连接的偏好按账号隔离。已有会话保留自己的设置。已移除的模型回退到电脑默认设置, 引擎仍支持的权限选择会继续保留。引擎安装、登录与凭据仍在电脑上管理。
App 将固定的电脑身份和手机密钥保存在 iOS 钥匙串中。重启后恢复上次的电脑、目录与会话; 从后台返回时建立新的加密连接,不会自动重发已经发送的消息。新建、发送和停止任务要求电脑 在线。账号历史同步和通知需要使用账号连接。
回复进行中发送的后续消息,会排在输入栏上方的紧凑队列中。点击预览可读全文、移除消息, 或立即引导当前回复;尚未消费的引导也可以改回排队。电脑开始处理消息后,它会从队列移除。 上滑可自动读取更早的消息,点击操作摘要可查看详情。最终回复出现后,前面的过程文字和 工具调用会收成摘要。读取、查看图片的结果默认收在操作详情中,展开后才加载; 明确展示给用户的图片仍保留在消息流中。已发布图片的缩略图和原图按需通过加密连接读取, 打开原图时校验已保存的内容摘要。
原生 iOS 和 Android App 支持离线渲染 Mermaid 代码块,并随回复逐步更新。 流式语法暂时不完整时保留上一张有效图,结束后语法仍有误则显示源码。 可点击「查看源码」或「复制代码」,宽图与高图可在图表内滚动;图表颜色和字号跟随手机外观设置。
长按自己的消息气泡或附件,可复制、编辑、选择文本或分享。回复的复制和分叉 按钮会在本轮结束后显示。分叉时可选择当前目录,或基于当前提交创建新的 Git 工作树; 创建工作树要求当前目录属于 Git 仓库。新会话先加载近期历史,上滑可继续读取更早的消息。 编辑会保留原附件, 重新发送时替换这条消息及之后的对话;取消不会修改历史。编辑需要电脑在线、会话可写且空闲, 并已处理完待发消息。
使用 Tailscale 或反向代理时,给桌面进程设置 WUU_WEB_URL 为手机可达的 HTTP(S) origin,
并将 WUU_WEB_LISTEN 设置为本机监听地址(例如 Tailscale 地址和端口)。使用独立 Relay 时,
还需将 WUU_WEB_RELAY_URL 设置为它的 wss://…/v1/connect 地址。配对码会携带该地址,
双方都必须能够访问;安装 Tailscale 本身不会修改已经保存的 Relay 地址。更改对外地址后,
应重新生成配对码并配对。
在手机上移除电脑只会删除本机保存的身份。撤销手机的访问权限,需要在电脑的「手机访问」 设置中移除该设备。
准备 Relay
Section titled “准备 Relay”Host 和客户端需要能访问同一个以 /v1/connect 结尾的 WebSocket 地址。单机开发可运行:
wuu relay --addr 127.0.0.1:8787保持进程运行,使用 ws://127.0.0.1:8787/v1/connect。另一台设备不能通过它自己的回环
地址访问这台电脑。跨设备时,应部署双方都能访问的 Relay,并使用带 TLS 的 wss://。
服务支持 TLS 证书和私钥选项,也支持反向代理部署;消息内容加密不免除对路由元数据、
服务可用性和运维访问的保护。
账号服务和浏览器托管是需要单独配置的可选服务端能力。下面的基础 CLI 配对流程不要求
注册账号。服务端选项见
internal/remote/server/server.go。
启动并配对 Host
Section titled “启动并配对 Host”在工作区所在电脑上运行:
wuu remote init --relay ws://127.0.0.1:8787/v1/connect --name my-macwuu remote host --workdir /path/to/project --pair初始化会创建或更新 Host 身份并打印指纹。Host 主动连接 Relay,注册后打印配对 URI。
配对窗口默认持续 10 分钟,第一次成功配对后关闭。仅在需要时使用 --pair-timeout 30m
或 --pair-once=false。
remote host 接受 --provider、--model、--workspace-id 和 --relay 覆盖项。
Host 必须持续运行。同一个 Wuu home 只能由一个远程 Host 占用,启动另一个前应先关闭
已有 Host。
在开发客户端私下导入 URI:
wuu remote phone pair --uri 'wuu://pair?...' --name dev-clientwuu remote phone statuswuu remote phone send "总结当前工作区"wuu remote phone send --thread THREAD_ID "继续调查"wuu remote phone watchphone 是 CLI 开发客户端,也可以在另一台电脑上运行。send 默认创建新会话,提供
--thread 则继续指定会话,然后流式输出回合内容。watch 显示连接状态和缩略通知。
这些输出不遵循 wuu exec JSONL(英文)的协议。
状态与设备移除
Section titled “状态与设备移除”Host 身份和配对设备保存在 WUU_HOME/remote.json,客户端身份保存在
WUU_HOME/phone.json,默认 home 是 ~/.wuu。phone 子命令的 --store FILE 可以选择
另一份客户端身份。上述文件和配对 URI 包含敏感访问材料,不要附在问题报告中。
wuu remote status --jsonwuu remote devices --jsonwuu remote devices remove DEVICE_FINGERPRINT移除操作更新磁盘上的设备列表,不会更新运行中 Host 的内存状态。可靠撤销的顺序是先 停止 Host,再移除设备,最后在不开启配对窗口的情况下重启。不能认为执行移除就会断开 已有客户端。
配对失败时,检查双方是否使用同一个可达 Relay,以及配对窗口是否仍有效。
remote status 中存在已保存身份不代表 Host 在线,应结合运行中的 Host 日志和客户端
连接状态判断。
客户端断线本身不会停止 Host 上的 Agent。重新连接后先检查原会话,再决定是否重发任务。 事件重放有容量限制,不能当作无限的持久日志存储。
已配对客户端使用 Host 的 app-server 控制接口。应按控制端信任它,包括它请求修改配置 的能力。Agent 操作仍受当前生效的权限模式约束;配对不是 独立的逐设备只读策略。开发自定义客户端可参考 app-server 集成。