第 10 章 · 02 SSH Remote-SSH 与桌面全栈 本节摘要:本节精读 Reasonix 把 agent 从本地延伸到远程开发场景的两条主线。internal/remote 实现 SSH Remote-SSH——主机解析([remote] 配置 + /.ssh/config)、鉴权、host-key 验证(系统 knownhosts 只读 + Reasonix 管理的 TOFU 文件)、带 keepalive 和指数退避重连的受监督连接、共享 SFTP 访问、端口转发生命周期。三个子包各司其职: 管 -L/-R 端口转发(Local 转发的本地监听器跨重连保留,Remote 转发每次重连重注册), 隔离 github.
本节摘要:本节精读 Reasonix 把 agent 从本地延伸到远程开发场景的两条主线。internal/remote 实现 SSH Remote-SSH——主机解析([remote] 配置 + ~/.ssh/config)、鉴权、host-key 验证(系统 known_hosts 只读 + Reasonix 管理的 TOFU 文件)、带 keepalive 和指数退避重连的受监督连接、共享 SFTP 访问、端口转发生命周期。三个子包各司其职:
forward/管 -L/-R 端口转发(Local 转发的本地监听器跨重连保留,Remote 转发每次重连重注册),sftpfs/隔离 github.com/pkg/sftp 依赖(原子写、文本/二进制检测),bootstrap/在远端启动 detachedreasonix serve进程(绑定随机 loopback 端口、文件 token 绝不进 argv、记录到 ~/.reasonix/remote 供重连复用)。SPEC §Remote 规定 remote 不 import cli/agent/serve,所有交互走回调(HostKeyPrompt/SecretPrompt),桌面模块消费同一 surface。desktop Wails + React 19 桌面全栈——images/desktop-v2-layout-preview.png布局、images/desktop-v2-skills-preview.pngSkills 面板、Zustand/xterm/KaTeX/Mermaid 前端栈、8 套官方主题(images/desktop-theme-dark.jpg)。本节还涵盖 ACP VS Code 扩展(images/claude-desktop-layout-preview.png)。
内容来源:原项目源码
internal/remote/remote.go、internal/remote/forward/forward.go、internal/remote/sftpfs/sftpfs.go、internal/remote/bootstrap/bootstrap.go、desktop/,go.modgithub.com/pkg/sftp v1.13.10,docs/SPEC.md§Remote,配图images/desktop-v2-layout-preview.png、images/desktop-v2-skills-preview.png、images/desktop-theme-dark.jpg、images/claude-desktop-layout-preview.png。
⚠️ 注意:本节聚焦 Remote-SSH 的依赖分层契约与桌面全栈技术栈,不深入 SSH 协议底层和 React 组件实现细节。
阅读完本节,你应当能够:
很多开发场景里,代码不在本地——在云开发机、在跳板机后、在 Docker 容器里。Reasonix 的 Remote-SSH 模块让 agent 在远端跑,但操作体验和本地一致。
internal/remote/remote.go 的包注释把整个模块的契约讲清:
// Package remote is the SSH transport for Reasonix's remote module: host // resolution ([remote] config + ~/.ssh/config), authentication, host-key // verification (system known_hosts read-only + a Reasonix-managed TOFU file), // a supervised connection with keepalive and exponential-backoff reconnect, // shared SFTP access, and port-forward lifecycle. The agent itself never runs // through this package — remote workspaces are driven by a `reasonix serve` // process bootstrapped on the remote host (internal/remote/bootstrap) and // reached through a forwarded loopback port. // // The package is frontend-agnostic: all interactivity flows through callbacks // (HostKeyPrompt, SecretPrompt) and status subscriptions, so the CLI, chat // TUI, and the Wails desktop consume the same surface.
四条核心契约:
reasonix serve 进程驱动(bootstrap 启动),通过转发的 loopback 端口访问。remote 包只管 SSH 传输,不跑 agent。type Status int const ( StatusIdle Status = iota // 创建了,Start 还没调 StatusConnecting // 首次 dial 进行中 StatusConnected // SSH 建立,转发已附加 // ... )
连接是一个受监督的状态机,从 Idle → Connecting → Connected,带断线重连和状态变更订阅。这让前端能实时显示连接状态(桌面的连接指示器就是消费这个状态)。
SPEC §Remote 给 remote 模块规定了严格的依赖方向,这是第 2 章 Layout 依赖无环原则在远程场景的具体延伸:
The Remote-SSH module layers cli → remote/bootstrap → remote → {remote/forward, remote/sftpfs, config, netclient}; remote and its subpackages never import cli, agent, or serve, and all interactivity flows through callbacks (host-key / secret prompts) so the desktop module consumes the same surface.
cli │ ▼ remote/bootstrap(远端 serve 启动) │ ▼ remote(SSH 传输主体) ├→ remote/forward(-L/-R 端口转发) ├→ remote/sftpfs(SFTP 文件层,隔离 pkg/sftp) ├→ config └→ netclient
cli → {agent, plugin, config} → {tool, provider} 是同一套依赖方向无环哲学。这是 transport-agnostic 哲学(第 8 章)在远程模块的再一次体现——remote 包不依赖前端,前端依赖 remote。依赖方向单向,前端可换,remote 稳定。
💡 契约要点:SPEC §Remote 的依赖分层是"接口优先 + 依赖方向无环"(第 1 章/第 2 章)在远程场景的落地。remote 通过回调(HostKeyPrompt/SecretPrompt)和状态订阅把交互抽象出来,前端只实现回调不进入 remote 内部。这让 remote 包能同时服务 CLI/TUI/桌面三种前端,而不用为每个前端复制连接逻辑。
internal/remote/forward/forward.go 管理 SSH 端口转发规则,绑定到活跃连接:
// Package forward manages SSH port-forward rules bound to a live connection. // Local (-L) forwards keep their local listener open across reconnects so a // forwarded serve URL survives an outage; remote (-R) forwards are // re-registered on every re-attach because they die with the SSH connection.
type Direction int const ( Local Direction = iota // -L:本地监听,远端拨号 Remote // -R:远端监听,本地拨号 )
reasonix serve(绑在远端 loopback)映射到本地端口,本地浏览器/客户端访问 localhost 就能连远端 serve。这是 forward 包最精妙的设计——两种方向在 SSH 重连时行为不同:
这个不对称设计来自 SSH 协议本身的特性——Remote 转发的生命周期绑在 SSH 连接上,Local 转发的本地监听器是本地资源可以独立保留。forward 包把这个差异封装好,调用方不用操心。
addrinuse.go/addrinuse_windows.go 处理端口占用——本地监听端口被占时重试或换端口,平台差异有专门文件。这是工程鲁棒性的细节。
internal/remote/sftpfs/sftpfs.go 是 SFTP 文件层:
// Package sftpfs is the SFTP file layer for the remote module: directory // listing, stat, capped reads with text/binary detection, atomic writes, and // the usual mkdir/rename/remove. It quarantines the github.com/pkg/sftp // dependency — no other Reasonix package imports it directly. One *FS is shared // per SSH connection; the underlying pkg/sftp client is safe for concurrent // use.
It quarantines the github.com/pkg/sftp dependency — no other Reasonix package imports it directly.
这句"quarantines"(隔离)是关键。github.com/pkg/sftp v1.13.10 是个第三方依赖,Reasonix 不让任何其他包直接 import 它——只有 sftpfs 包 import。其他包要用 SFTP,通过 sftpfs 提供的 *FS 抽象。
为什么这么做?
*FS,底层 pkg/sftp client 并发安全,sftpfs 封装了这个并发模型。sftpfs 提供的能力:
"capped reads"(有上限的读)很重要——远端文件可能很大(日志、数据集),sftpfs 限制单次读的规模,防止 agent 把超大文件整个拉下来撑爆上下文。这是第 6 章上下文维护哲学在文件层的延伸。
internal/remote/bootstrap/bootstrap.go 在远端启动和管理一个 detached reasonix serve 进程。这是 Remote-SSH 的灵魂——agent 不通过 SSH 跑,而是在远端跑一个 serve,本地通过转发的端口连它。
// Package bootstrap starts and manages a detached `reasonix serve` process on // a remote host over an established SSH connection. It detects the remote // OS/arch, locates or installs reasonix, launches serve bound to a random // loopback port with a file-based token (never in argv), and records the // result under the remote ~/.reasonix/remote so a later reconnect can reuse // it. V1 targets Linux and macOS remotes.
五步流程:
agent_unix.go/agent_windows.go 平台分流。install.go/launch.go,远端没有就装一个。state.go 把 serve 的端口、token、PID 等记下来,重连时复用,不用重启 serve。"detached"意思是 serve 进程脱离 SSH 会话独立运行——即使 SSH 连接断,serve 继续跑。这样重连后能立刻复用同一个 serve(它还活着,状态还在),不必每次重连都重启 agent 会话。配合 forward 的 Local 转发保留策略,远端 serve 的访问 URL 在网络抖动期间也稳定。
records the result under the remote ~/.reasonix/remote so a later reconnect can reuse it.
bootstrap 把每次启动的 serve 信息(端口、token、远端工作目录等)记到 ~/.reasonix/remote。下次连接同一远端时,先读这个状态文件,如果对应的 serve 进程还活着(检查 PID),就直接复用,不再启动新的。这让 Remote-SSH 体验流畅——第一次连接可能要装 reasonix + 启动 serve(几十秒),之后连接秒连。
V1 targets Linux and macOS remotes.
V1 只支持 Linux 和 macOS 远端。Windows 远端不在 V1 范围——这是因为 detached 进程管理、文件 token 权限模型在 Windows 上实现差异大,V1 先覆盖最常见的 Linux/macOS 远端开发场景。
💡 契约要点:bootstrap 的安全设计有三个亮点——(1) serve 绑随机 loopback 端口,不暴露公网;(2) 文件 token 绝不进 argv,防进程列表泄露;(3) detached 进程 + ~/.reasonix/remote 状态复用,重连秒连不重启。这三个细节共同把"在远端跑一个 agent 服务"这件本来危险的事,做成了可控、可复用、不泄露 token 的工程实现。
第 8 章第 2 节已经讲了桌面的技术栈分层,这里补充更多细节。
images/desktop-v2-layout-preview.png 展示了桌面 v2 的整体布局:
images/desktop-v2-skills-preview.png 展示 Skills/子代理 profile(第 9 章)的管理界面。桌面 React 19 前端用了几个关键库:
| 库 | 作用 |
|---|---|
| Zustand | 轻量状态管理,替代 Redux,适合中等复杂度 |
| xterm.js | 在 webview 里渲染真终端,内嵌终端跑命令 |
| KaTeX | 渲染数学公式,agent 输出的 LaTeX 美观显示 |
| Mermaid | 渲染流程图/时序图,agent 输出图表直接可视化 |
这些都是桌面专属的富交互能力,底层 agent 推理/工具/审批还是走 control.Controller。
desktop/themes/official/ 下有 8 套官方主题:
official-crimson-horizon 绯红地平线 official-cyan-stage 青色舞台 official-fortune-forge 财富锻造 official-noir-gold 暗夜金 official-rose-dawn 玫瑰黎明 official-sage-breeze 鼠尾草微风 official-spark-notebook 火花笔记本 official-violet-starlight 紫罗兰星光
images/desktop-theme-dark.jpg 是暗色主题截图(类似 noir-gold)。8 套主题覆盖不同审美——深色/浅色、暖色/冷色、商务/创意。主题是桌面专属表现层(不进 Controller),用户可在设置里切换。
主题的存在呼应了第 8 章"判断行为是否跨前端共享"的标准——主题是 UI 表现(桌面专属),agent 行为是会话语义(跨前端共享)。TUI 有 CLI 主题,桌面有桌面主题,各自独立但底层 agent 一致。
桌面 Go 后端除了是前端,还承载多个集成:
这让桌面成为 Reasonix 的"集成枢纽"——不只是 UI,还是 IM bot、后台任务、远程连接的管理中心。这是桌面相对 TUI/SSE 的独特定位。
第 8 章第 2 节介绍了 ACP(Agent Client Protocol),这里补充它在 VS Code 扩展里的落地。
images/claude-desktop-layout-preview.png 展示了编辑器内嵌 agent 的布局——agent 面板挂在编辑器侧边,代码区还能正常编辑,agent 在旁边辅助。通过 ACP 协议(stdio JSON-RPC),VS Code 扩展把 Reasonix 当后端 agent 驱动:
/reviewer review the current diff)。ACP 的价值是协议标准化——任何支持 ACP 的编辑器(VS Code、Neovim、Zed 等)都能零成本接入 Reasonix,不用为每个编辑器写专门的集成。internal/acp 是协议适配层(第 8 章第 2 节),ProtocolVersion = 1 保持 wire contract 稳定。
cli → remote/bootstrap → remote → {remote/forward, remote/sftpfs, config, netclient};remote 及子包永不 import cli/agent/serve;交互走回调(HostKeyPrompt/SecretPrompt)让桌面消费同一 surface——transport-agnostic 在远程模块的落地。下一节是全书最后一节——Workers(Cloudflare Workers accounts/forum/crash-report)、GoReleaser 六平台交叉编译、SignPath Windows 代码签名、production_checklist 生产清单,以及全书 10 章契约驱动回顾与 Reasonix 核心哲学四点。