3623 字
18 分钟
浏览器里的开发工作台:dsh 与 Paseo 的部署与日常

以前说“开发环境”,默认指一台电脑上的 IDE、终端和一堆 SDK。现在我的默认界面是一个浏览器标签页:真正干活的主机在家里常开,笔记本、平板、手机都只是显示器。需要写代码、跑测试、查服务时,打开一个网址就能接着上次的会话继续。

这套东西的核心是两个工具:dsh 和 Paseo。它们都跑在同一台常开主机上,都能在浏览器里使用,也都支持从别的设备接入。下面按“为什么这样部署、每天怎么分工、手机端能做什么”讲一遍。

1. 为什么把开发放在一台常开主机上#

最直接的好处是环境只有一份。语言运行时、包管理器、Docker、数据库客户端、各个 agent CLI 的登录状态,全都留在主机上。换一台笔记本,不需要重新装一遍 Node、Python、Go,也不需要重新登录一遍模型账号;浏览器打开就是同一个环境。

第二个好处是任务可以长跑。安装依赖、跑测试、等一个长任务、让 agent 慢慢改一个仓库,这些都不再依赖某台电脑是否合盖。主机一直开着,会话和进程就一直在;笔记本只是接入它的终端。

代价也很明确:所有入口都变成了网络服务。一旦可以从浏览器访问,就要回答认证、权限、暴露面和备份的问题。把 shell 和代码交给一个网页,和把静态博客放到公网完全不是一个量级。所以这套部署的第一原则不是“方便”,而是“默认不公开,只开必要的入口”。

2. dsh:带 Web UI 的 agent 运行时#

dsh 是 DeepSeek Harness 的命令行入口。它本身不只是一段聊天程序,而是一个可组合的 agent 运行时:profile 决定加载哪些插件,Web、ACP、headless 等入口共用同一套模型访问、工具和安全默认值。

日常用得最多的是 dsh --profile web。它会启动一个浏览器 GUI,包含聊天、模型与设置、会话历史;每个浏览器会话可以选择一个 workspace,agent 在里面读写文件、执行命令、搜索网页、调用子 agent。写入范围由权限模式控制,常用的 workspace-write 会把文件修改限制在工作区内,而不是整个家目录。

Web 入口有几道和安全直接相关的设计,值得在部署前理解:

  • 监听地址只接受一个具体地址,不接受 0.0.0.0 这类通配地址。这是好事,逼着部署者明确“这个界面到底给谁访问”。
  • 启动 URL 带一次性 token,浏览器用它换一个签名 cookie;后续每个 API 和 WebSocket 请求都要过认证。
  • Host/Origin 白名单用来防 DNS rebinding。--public-url 只影响打印/打开的地址和运行时上下文,不会授予信任;真正允许某个域名访问 API 的是 --trusted-host。
  • 反向代理终止 TLS 后,到主机这一段可以是明文 HTTP,但那段链路必须限制在私有网络或本机。

dsh 还有 acp profile:通过 stdio 给自动化客户端提供 agent 服务。它和 Web UI 不是同一个界面,但共用同一个应用 home。Paseo 接入 dsh 用的就是这一条。

3. Paseo:把多个 agent 放进同一个工作台#

如果说 dsh 更像“一个 agent 的完整工作台”,Paseo 更像“多个 agent 的调度台”。它由 daemon 和客户端组成:daemon 跑在主机上,客户端可以是桌面 App、手机 App、浏览器里的 Web UI,或者 CLI。

Paseo 的组织方式不是“聊天列表”,而是 project → workspace → session:

  • 一个 project 对应一个仓库或目录;
  • 一个 workspace 是一次任务的容器,可以同时放多个 agent 会话、终端和浏览器标签;
  • 需要并行时,workspace 可以基于 git worktree 创建独立目录和分支,几个 agent 各改各的,最后再看 diff、决定合并哪个。

这解决了一个很现实的问题:让两个 agent 同时改同一份工作区,冲突几乎不可避免。worktree 把“并行”变成互不干扰的目录,代价是要多一步合并和清理。

Paseo 不自己打包 agent,而是复用本机已经安装并登录的 CLI:Claude Code、Codex、OpenCode、Pi,以及 ACP 兼容的 agent。每个任务可以在创建时选 provider 和模型;常用的组合可以存成 agent profile。运行中的 agent 不能直接换 provider,想换内核就新建一个 agent,或者用交接的方式把上下文迁过去。

dsh 在这里是作为一个 ACP provider 出现的。Paseo 启动的是 dsh --profile acp,不是 dsh 的 Web UI;两者共用 ~/.dsh,所以多个 dsh agent 并行时要留意同一个 home 下的锁竞争。如果确实需要隔离,可以给 Paseo 单独指定一个 dsh home。

Web UI 是 Paseo daemon 自带的,和 API 同源。它默认关闭,需要在配置里打开;打开后由 daemon 同时提供静态页面、API 和 WebSocket。页面本身不需要密码就能加载,但 API 和 WebSocket 在认证前不会返回任何 agent 数据,也不会执行命令。这种“页面能打开”和“已经登录”之间的区别,在排查时很容易误判。

4. 部署骨架:两条入口,同一套约束#

整体上是两台机器:一台在家常开的主机,一台只做公网边缘的小 VPS。公网请求走 Cloudflare 的 DNS、TLS、Tunnel 和 Access,到 VPS 的 Nginx,再经 WireGuard 私网进入家庭主机:

浏览器 / 手机 App
│ HTTPS
▼
Cloudflare(DNS / TLS / Access)
│ Tunnel
▼
VPS Nginx
│ WireGuard 私网
▼
家用主机上的 dsh / Paseo

同一个局域网里还有一条短路:浏览器用 mDNS 名字访问,先到只监听 LAN 地址的 lan-bridge,再回环到主机自己的私有地址。这条路径不经过 Cloudflare、VPS 和 WireGuard,延迟从数百毫秒降到毫秒级。它也是下一篇文章的主题,这里只需要知道:业务服务的监听地址没有变,变的是它前面多了一层局域网入口。

两个工具虽然形态不同,部署约束几乎一样:

约束做法
进程systemd user unit,Restart=always,日志进 journal
路径CLI 装在 nvm 下,用 current 软链,不写死版本目录
状态dsh 用 ~/.dsh,Paseo 用 PASEO_HOME;配置、会话、日志分开
密钥.env 权限 600,只给 unit 读;不进仓库、不进命令行
监听只绑私有地址,不绑 0.0.0.0
反向代理转发 WebSocket、关闭缓冲、放长超时、允许大 body、保留 Host 和 X-Forwarded-Proto
认证Cloudflare Access + 应用自身认证;Access 不是应用认证的替代品

dsh 的启动参数大致是这样:

Terminal window
dsh --profile web \
--host WG_IP --port PORT \
--public-url https://app.example.com \
--trusted-host app.example.com \
--trusted-host homebox.local \
--no-open

其中 WorkingDirectory 要指向一个专用 workspace,不能是家目录。否则 workspace-write 会把整个 home 当成可写范围,.env、SSH 私钥、其他仓库都会暴露在 agent 的写入边界里。这个坑不显眼,但一旦踩到,后面所有权限设计都白做。

Paseo 的关键在 config.json:daemon.listen 决定绑哪个地址,daemon.hostnames 决定接受哪些 Host,daemon.trustedProxies 决定信任哪些反代来源,features.webUi.enabled 打开自带 Web UI,daemon.relay.enabled 打开手机端 relay。密码从环境文件注入,hostnames 只写主机名(daemon 会去掉端口)。改动这些启动项后要重启 daemon,reload 不会热更新监听地址和 relay 这类字段。

部署时最容易遇到的三类故障:

  1. nvm 版本目录被删。nvm install 新补丁版本时会删掉旧版本目录,写死版本路径的 unit 会直接 203/EXEC。所以用 current 软链,并且 Node 也走稳定入口。
  2. systemd 不加载登录 shell。shell 里 which dsh 能找到,不代表 unit 里也能找到;PATH 要显式写,或者用 ~/.local/bin 下的稳定入口。
  3. 反向代理没转发 WebSocket。页面能打开,但一直显示连接中;dsh 和 Paseo 都依赖长连接,Nginx 必须处理 Upgrade,并且关闭响应缓冲。

5. 日常分工:什么时候用 dsh,什么时候用 Paseo#

用久了会发现,两者不是替代关系,而是两种节奏。

dsh 适合“一个任务、连续上下文”的深度工作。 比如读一个陌生仓库、定位一个跨文件的 bug、写一篇长文、把一组测试从失败跑到通过。它的会话是连续的,工具调用和文件修改都在同一个工作区里,适合需要反复推理和回看的场景。浏览器标签页一直开着,想到什么就接着问,不用重新解释背景。

Paseo 适合“多个任务并行、需要比较和审阅”的工作。 比如同时让两个 agent 用不同思路改一个模块,各自在 worktree 里跑;或者一个 agent 实现、另一个 agent 审查;又或者把终端、浏览器和 diff 放在同一个 workspace 里,边跑边看。它的价值不在于“多开几个聊天”,而在于把任务、目录、分支和审阅动作组织在一起。

两者可以组合:在 Paseo 里把 dsh 当成一个 ACP provider,用 Paseo 的 workspace/worktree/移动端能力,同时保留 dsh 自己的工具和权限模型。但要知道,这时运行的是 dsh 的 ACP 进程,不是 dsh 的 Web UI;两边的界面和进程不是同一个,只是共享同一个 home。

几条实际用下来的经验:

  • 一个任务一个 workspace。不要把不相关的任务塞进同一个目录,否则 agent 的文件修改会互相污染。
  • 并行必须用 worktree。同一份工作区里跑两个会写文件的 agent,冲突只是时间问题。
  • 常用组合存成 profile。provider、模型、权限模式固定下来,减少每次创建 agent 时的选择成本。
  • 给长任务写清验收标准。比如“测试通过、只改这两个文件、不要动依赖”,比“帮我优化一下”有效得多。
  • 合并前一定看 diff。agent 跑得快,不代表改得对;worktree 的意义就是让审阅发生在合并之前。

一天的使用大概是:早上打开 Paseo,看昨晚跑完的任务和待审的 diff;需要连续推理的活开一个 dsh 会话;下午在外面用手机看进度、补一句指令;晚上回到桌面收尾、合并、清理 worktree。

6. 手机端:把 Paseo 当作远程遥控器#

手机端用的是 Paseo 的原生 App。它不要求家里有公网 IP,也不需要在路由器上开端口:daemon 主动出站连接 relay,手机和 daemon 在 relay 那里会合。整条链路是端到端加密的,relay 只能看到 IP、时序、报文大小和会话 ID,读不到内容,也无法伪造或重放。

配对靠二维码或配对链接。这个二维码里包含 daemon 的公钥,是整条链路的信任锚,安全等级等同密码:谁拿到它,谁就能尝试连接你的 daemon。所以不要截图发到群里,也不要把配对链接贴进公开文档。怀疑泄漏时重新配对,旧连接就失效了。

relay 是独立于 Web 入口的另一条通道:

relay(手机 App)Web(浏览器)
方向daemon 出站连 relay入站,经 Cloudflare、VPS、WireGuard
认证配对二维码 + daemon 密码Access + daemon 密码
加密端到端(X25519 + NaCl box)边缘 TLS
入站端口不需要由 VPS Nginx 提供

注意 relay 不经过 Cloudflare Access。它的安全边界是“配对 + daemon 密码 + 端到端加密”,不是 Access。所以 daemon 密码不能省;手机丢失时,除了改 daemon 密码,还要把已配对设备当成凭据来评估影响。

手机上适合做什么?看任务进度、发下一条指令、批准或纠正方向、把任务从一台机器转到另一台。不适合做什么?读几百行 diff、在多个终端之间切换、处理复杂的合并冲突。手机是遥控器,不是主工作台。

7. 安全边界与故障点#

这套东西把“能执行 shell 的界面”放到了网络上,安全边界必须说清楚:

  • Access 是边缘门,应用认证是第二层。 两者都在的时候是双层;局域网入口会绕过 Access,只剩应用认证。所以每个服务自己的密码、2FA、Host 白名单都不能省。
  • agent 的权限要单独设计。 能读写文件、执行命令的 agent,等于一个受限的远程 shell;workspace-write、工作区边界、密钥存放位置,都是这套部署的一部分。
  • 不要把密钥放进工作区。 agent 能读工作区里的所有东西;.env、私钥、token 应该放在工作区之外,由 systemd 或应用自己读取。
  • 备份要覆盖状态,不只是代码。 dsh 的 ~/.dsh 里有会话、profile 和凭据;Paseo 的 PASEO_HOME 里有配置、会话和 worktree。代码在 git 里,状态不一定在。
  • 升级要一个一个来。 这些 CLI 更新很快,同时升 dsh、Paseo 和 provider,出问题很难定位;先看 release notes,升完跑一个最小任务验证。

可观测性方面,systemctl --user status 只能说明进程在,不说明服务可用。至少要加端口探测和一次真实请求;dsh 和 Paseo 都有 Web 入口,打开页面、发一条消息、看 WebSocket 是否连上,比看 active 更可靠。

8. 小结#

这套工作流真正稳定的部分,不是某个模型或某个 CLI,而是三件事:

  1. 一台常开的主机,环境只有一份;
  2. 浏览器和手机两个入口,分别覆盖深度工作和移动场景;
  3. 明确的监听地址、认证层和权限边界,让“方便”没有以“暴露”为代价。

工具会继续换。dsh 和 Paseo 也可能被更好的东西替代,但只要这三件事还在,换工具只是换一个进程和一组配置,不会把整个工作方式推倒重来。

相关阅读#

浏览器里的开发工作台:dsh 与 Paseo 的部署与日常
https://blog.xqcherry.top/posts/operations/dsh-paseo-browser-workbench/
作者
xqcherry
发布于
2026-11-07
许可协议
CC BY-NC-SA 4.0