不用装桌面端:把整个 Hermes 工作区塞进一个浏览器标签页

你在同事的笔记本上,或者在一台装不了任何东西的公司机器上,或者只是 SSH 进了一台开发机——此刻你想要的不是聊天框,而是那个完整的工作区:会话列表、终端、文件树、预览面板。桌面端不让装,或者根本装不了;退回到光秃秃的 CLI 里靠手打重建上下文,又像戴着手套敲键盘。以前的办法是打开 dashboard,但那是另一个东西:独立进程、独立的视角,长得像,却完全不知道你家里那台机器上还开着的那个标签页。
这次的 Webapp 走了一条更简单的路:让 gateway 自己来托管 Desktop 工作区(#141864,2026-10-25 合并),延续 #93508 的方向——没有独立的 Webapp 服务器,中间也没有 relay 这一跳。
它到底是什么
一句话:你已经在跑的那个进程,把 Desktop 工作区托管起来了。
gateway(常驻后台的网关进程,CLI、桌面端和消息平台都连它)是这套东西的中心。Webapp 里的会话跑在这个 gateway 上,跑在你指定的那个 profile 里——就是同一个进程在回应你的终端、你的 Telegram 适配器和你的桌面窗口。同一个对话,同一个队列。不管从哪个界面看过去,状态只有一份。
这一条性质解释了后面几乎所有行为。标签页不是桌面会话的镜像,而是同一个后端的另一个客户端:在浏览器里起一个任务,切回终端,活儿就在那儿。没有东西需要对齐,因为没有东西被复制——也没有多余的东西要跑:没有 Webapp 服务器,没有第二个端口,没有必须先健康起来的 relay。
怎么开起来
开关在 gateway 所在 profile 的 config.yaml 里,默认关闭:
webapp:
enabled: false # true:gateway 提供 Webapp
host: 127.0.0.1 # 127.0.0.1 / ::1 / localhost;0.0.0.0 或 :: 让其他机器也能访问(需要登录)
port: 9120 # 固定端口,浏览器地址在 gateway 重启后保持不变
public_url: "" # 反向代理/隧道对外提供的 http(s) 地址(会启用登录)
enabled 是 gateway 启动时读取的,改完记得重启网关:hermes gateway restart。然后是真正每天要用的命令:
hermes webapp # 在浏览器里打开 Desktop 工作区
hermes webapp --status # 当前状态;没起来的话,给出原因
hermes webapp --no-open # 只打印链接,不启动浏览器
hermes webapp -p work # 指定 profile
hermes webapp 不只是“打开一个网址”。bundle 过期了它会先重新构建,gateway 没在跑它会先拉起来,最后才递给你一条一次性登录链接。
host 比看上去严格,很多人在这里白白耗掉十分钟。只有 127.0.0.1、::1、localhost 表示“只在本机”;0.0.0.0 和 :: 表示别的机器也能连——而这要求你配置了 dashboard 登录 provider,没配的话 gateway 只会继续服务原生客户端,hermes webapp --status 会把原因说清楚。其它任何值,包括看着人畜无害的 127.0.0.2,都会让 Webapp 保持关闭。
port 故意做成固定端口,浏览器地址在重启后不变,标签页可以一直挂着。如果这是个还没设端口的 standalone profile,桌面端的 Settings → Web Access 面板(PR #142220)会自动挑一个:在 9120 之上、没有被别的 profile 占用,然后写回 port;它永远不会覆盖你自己设的端口。到底该让哪个 profile 来提供这个服务,一台机器一个 gateway 里讨论的取舍值得先看一眼。
public_url 是给你已经架好反向代理或隧道的情况用的。填上它,即使 host 还是 loopback 也会启用 dashboard 登录;同时它成为 Webapp 唯一接受的外部 hostname——登录回调是基于它拼的,不是基于请求。它必须是 http:// 或 https://,不能带凭据、查询串和 fragment。这里有个值得点名的坑:dashboard.public_url 只管 hermes dashboard,对 Webapp 永远不生效。
三种“打不开”,各说明什么
| 你看到的现象 | 实际发生了什么 | 怎么办 |
|---|---|---|
浏览器一片空白,--status 给出拒绝原因 |
host 不是 Webapp 接受的值,比如 127.0.0.2 |
改成 127.0.0.1、::1、localhost,或用通配地址并配好 dashboard 登录 |
| CLI 和桌面端都正常,浏览器就是打不开 | 固定端口被占、登录门拒绝、或者浏览器凭据文件读写不了 | 腾出端口,再跑一次 hermes webapp |
| 昨天还好好的标签页,今天早上死了 | gateway 在端口被占的情况下重启过,浏览器凭据被换掉了 | 对已经开着的那个标签页再跑一次 hermes webapp |
第二行最让人意外,因为 gateway 根本不会崩。Webapp listener 起不来时,它保留原来的私有 loopback listener 继续服务原生客户端——CLI、TUI、桌面端全都不受影响——同时把浏览器凭据换掉。换掉是有道理的:端口被占,说明可能有另一个程序正坐在上面,把旧凭据交给它,比一个死掉的标签页糟糕得多。端口空出来之后,正是 hermes webapp 让已经开着的标签页重新连上。第三行是同一机制的另一面:单纯重启不必刷新浏览器,但重启发生在端口被占期间就必须重开。
纯 HTTP 会悄悄拿走什么
上面这些在 http:// 下都能正常工作,工作区却可能少了零件——而且这些报错在不久前还毫无信息量。
浏览器只在安全上下文里提供 crypto.subtle。地址是纯 HTTP 时,bot 画面(bot screen / noVNC)会报 Cannot read properties of undefined (reading 'digest'),这句话什么也没告诉你。现在它直接说清楚:需要 HTTPS,或者到 127.0.0.1 的 SSH 隧道。实时语音同理:非安全上下文里没有 navigator.mediaDevices,getUserMedia 是 undefined,提示现在就是一句“麦克风不支持”,而不是从客户端深处抛上来的一串堆栈(#144722)。
启动器和文档现在把纯 HTTP 的代价列全了:剪贴板读取、唤醒词、bot 画面、实时语音。其余功能看着一切正常,所以这件事最好在动手排查之前就知道。地址栏里既不是 127.0.0.1 也不是 HTTPS,就默认这四项没有——前面挂一个带 TLS 的反向代理并设好 public_url,或者把端口打隧道。
文件属于会话的沙箱,不属于你的笔记本
浏览器标签页会让文件树显得很“本地”,而这正是 #144741 作为安全修复堵上的坑:文件和媒体请求现在按会话自己的沙箱解析。跑在 Docker 沙箱里的会话,文件在容器里;SSH 环境里的会话,文件在远端 workspace 里。拿宿主机的 cwd 去糊弄,不是显示问题,而是把另一台机器、另一套权限模型下的目录树交了出去。如果你本来就把会话跑在容器里,Docker 沙箱里的 setuid 剥离讲的那条边界,就是文件树现在遵守的同一条。
同批的其它改动大多是你用起来才感觉得到的:大 HTML 预览能正常打开了,SSH profile 上可以暂存预览文本,浏览器端的 Export profile 也有了说明(#144718);浏览器终端能正确重绘,exit 有用了(#144719);输入队列满时标签页会干净地关掉(#144665);群聊预览的下载并发做了限制(#144557)。预览面板本身也长出了不少浏览器控制能力,从预览面板驱动浏览器里写得更细。
还有一个会绊倒集成工作的路由细节:Webapp 的专属路由——暂存上传、文件票据——只存在于 Webapp 上。独立的 dashboard 和 hermes serve 对这些路由返回 404。
一次性链接,到底谁能拿到
hermes webapp 打印出来的链接 60 秒有效、只能用一次。浏览器拿它换到登录态,放在该标签页的 sessionStorage 里,所以浏览器历史里不会留下任何凭据形状的东西,截图也不怕。
按设计只有你自己的操作系统用户能拿到这条链接,而且 gateway 是通过私有控制 socket 回答这个请求的,不走网络。各平台对“只有你”的实现并不一样:
- Linux —— gateway 会校验换取链接的连接属于当前用户。
- Windows —— 不校验。链接只出现在浏览器启动器的命令行里,能读到它的只有同一用户、SYSTEM 和提权管理员。
- macOS、BSD、illumos、WSL —— 也不校验。命令改为把链接写进 gateway home 下的
webapp/launch-*.html页面,目录0700、文件0600,带<meta refresh>、default-src 'none'的 CSP 和no-referrer;用浏览器打开这一页,之前的 launch 页面会被清掉,随后打印链接。
登录(sign-in)是有条件的:host 是 0.0.0.0/::,或者设了 public_url 时才启用,用 dashboard 账号登录。原生客户端永远不登录——CLI、TUI、桌面端的 listener 从不要求登录,配置里写什么都一样——而一次性链接只能打开“不需要登录”的 Webapp。桌面端怎么在没有密码提示的情况下把会话交给浏览器,登录交接的流程里有完整路径。
去找开关之前
先把发布状态说清楚:#141864 和这批修复合并在 2026-10-25 和 2026-10-26,都晚于 2026-10-23 发布的 v0.22.5。它们只在 main 上,还没有进任何正式版本。所以你装的版本里如果没有 hermes webapp,原因就是这个,不是装坏了。想用就跟 main,或者等下一个版本;这篇写的是 main 上的东西,等你读到的时候它可能已经进版本了。
另外两件事。跟踪 issue #144648 仍然开着,标题就写着它的性质:post-merge follow-ups——说明上面这批是已经清掉的部分,不是“这块做完了”的结论。还有 Windows 上 gateway 独占这个固定端口,所以“9120 上已经有别的东西”是个值得先排除的可能,别一上来就怀疑配置。
说句实在的:它用一个配置块加一条命令,替掉了第二台服务器和一跳 relay。它做不到的是把非安全上下文变成安全上下文——那部分仍然要靠你的 TLS 终结或者 SSH 隧道。