第 10 章 · 02 SSH Remote-SSH 与桌面全栈


文档摘要

第 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.

第 10 章 · 02 SSH Remote-SSH 与桌面全栈

本节摘要:本节精读 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/ 在远端启动 detached reasonix 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.png Skills 面板、Zustand/xterm/KaTeX/Mermaid 前端栈、8 套官方主题(images/desktop-theme-dark.jpg)。本节还涵盖 ACP VS Code 扩展(images/claude-desktop-layout-preview.png)。

内容来源:原项目源码 internal/remote/remote.gointernal/remote/forward/forward.gointernal/remote/sftpfs/sftpfs.gointernal/remote/bootstrap/bootstrap.godesktop/,go.mod github.com/pkg/sftp v1.13.10,docs/SPEC.md §Remote,配图 images/desktop-v2-layout-preview.pngimages/desktop-v2-skills-preview.pngimages/desktop-theme-dark.jpgimages/claude-desktop-layout-preview.png

⚠️ 注意:本节聚焦 Remote-SSH 的依赖分层契约与桌面全栈技术栈,不深入 SSH 协议底层和 React 组件实现细节。

学习目标

阅读完本节,你应当能够:

  1. 解释 internal/remote 的四个子模块(remote 主、forward、sftpfs、bootstrap)各管什么。
  2. 说清 SPEC §Remote 的依赖分层契约——remote 不 import cli/agent/serve,所有交互走回调。
  3. 描述 bootstrap 的安全细节——detached serve、随机 loopback 端口、文件 token 不进 argv、~/.reasonix/remote 状态复用。
  4. 区分 forward 的 Local(-L)和 Remote(-R)转发在重连时的不同保留策略。
  5. 讲清 sftpfs 为什么"隔离 github.com/pkg/sftp"——依赖边界管理。
  6. 说出桌面全栈技术栈(Wails + React 19 + Zustand/xterm/KaTeX/Mermaid)和 8 套官方主题。

一、Remote-SSH:把 agent 延伸到远端

很多开发场景里,代码不在本地——在云开发机、在跳板机后、在 Docker 容器里。Reasonix 的 Remote-SSH 模块让 agent 在远端跑,但操作体验和本地一致。

1.1 remote 包的总契约

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.

四条核心契约:

  1. agent 本身从不通过这个包运行——远端工作区由远端的 reasonix serve 进程驱动(bootstrap 启动),通过转发的 loopback 端口访问。remote 包只管 SSH 传输,不跑 agent。
  2. 前端无关——所有交互通过回调(HostKeyPrompt、SecretPrompt)和状态订阅,CLI/TUI/桌面消费同一 surface。
  3. 受监督的连接——keepalive + 指数退避重连,网络抖动自动恢复。
  4. host-key 验证——系统 known_hosts 只读 + Reasonix 管理的 TOFU(trust on first use)文件。

1.2 连接状态机

type Status int const ( StatusIdle Status = iota // 创建了,Start 还没调 StatusConnecting // 首次 dial 进行中 StatusConnected // SSH 建立,转发已附加 // ... )

连接是一个受监督的状态机,从 Idle → Connecting → Connected,带断线重连和状态变更订阅。这让前端能实时显示连接状态(桌面的连接指示器就是消费这个状态)。

二、SPEC §Remote:严格的依赖分层

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.

2.1 依赖层级图

cli │ ▼ remote/bootstrap(远端 serve 启动) │ ▼ remote(SSH 传输主体) ├→ remote/forward(-L/-R 端口转发) ├→ remote/sftpfs(SFTP 文件层,隔离 pkg/sftp) ├→ config └→ netclient

2.2 三条硬约束

  1. remote 及其子包永远不 import cli/agent/serve——remote 是底层传输,不能反向依赖上层。这和第 2 章的 cli → {agent, plugin, config} → {tool, provider} 是同一套依赖方向无环哲学。
  2. 所有交互走回调——host-key 提示、secret 提示通过回调函数暴露(HostKeyPrompt、SecretPrompt),不直接调 CLI 输入或桌面对话框。这让同一套 remote 代码能被 CLI(终端提示)、TUI(对话框)、桌面(模态框)消费。
  3. 桌面模块消费同一 surface——桌面不是特殊路径,它和 CLI 一样调 remote 的回调接口,只是回调的实现不同(桌面弹对话框,CLI 走 stdin)。

这是 transport-agnostic 哲学(第 8 章)在远程模块的再一次体现——remote 包不依赖前端,前端依赖 remote。依赖方向单向,前端可换,remote 稳定。

💡 契约要点:SPEC §Remote 的依赖分层是"接口优先 + 依赖方向无环"(第 1 章/第 2 章)在远程场景的落地。remote 通过回调(HostKeyPrompt/SecretPrompt)和状态订阅把交互抽象出来,前端只实现回调不进入 remote 内部。这让 remote 包能同时服务 CLI/TUI/桌面三种前端,而不用为每个前端复制连接逻辑。

三、forward 子包:-L/-R 端口转发

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.

3.1 两种方向

type Direction int const ( Local Direction = iota // -L:本地监听,远端拨号 Remote // -R:远端监听,本地拨号 )
  • Local(-L):本地开监听端口,连接进来时通过 SSH 隧道让远端去拨号目标。典型用途——把远端的 reasonix serve(绑在远端 loopback)映射到本地端口,本地浏览器/客户端访问 localhost 就能连远端 serve。
  • Remote(-R):远端开监听端口,连接进来时通过 SSH 隧道让本地去拨号。典型用途——把本地服务暴露给远端。

3.2 重连时的不同保留策略

这是 forward 包最精妙的设计——两种方向在 SSH 重连时行为不同:

  • Local 转发的本地监听器跨重连保留——本地端口不关,这样转发的 serve URL 在网络中断期间也"活着",重连后立刻恢复工作。用户在浏览器里访问的 URL 不变。
  • Remote 转发每次重连重注册——因为 Remote 转发随 SSH 连接死亡(远端监听端口是 SSH 进程开的,连接断端口就没了),所以重连必须重新注册。

这个不对称设计来自 SSH 协议本身的特性——Remote 转发的生命周期绑在 SSH 连接上,Local 转发的本地监听器是本地资源可以独立保留。forward 包把这个差异封装好,调用方不用操心。

3.3 端口占用处理

addrinuse.go/addrinuse_windows.go 处理端口占用——本地监听端口被占时重试或换端口,平台差异有专门文件。这是工程鲁棒性的细节。

四、sftpfs 子包:隔离 pkg/sftp 依赖

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.

4.1 为什么"隔离"pkg/sftp

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 抽象。

为什么这么做?

  1. 依赖边界清晰——如果十个包都直接 import pkg/sftp,换 SFTP 库就要改十个包。隔离后只改 sftpfs 一个包。
  2. API 收敛——sftpfs 把 pkg/sftp 的低层 API 收敛成 Reasonix 风格的高层接口(目录列举、stat、capped reads、文本/二进制检测、原子写、mkdir/rename/remove)。
  3. 并发安全封装——一个 SSH 连接共享一个 *FS,底层 pkg/sftp client 并发安全,sftpfs 封装了这个并发模型。

4.2 文件操作的能力面

sftpfs 提供的能力:

  • directory listing(目录列举)
  • stat(文件元信息)
  • capped reads with text/binary detection(有上限的读,自动检测文本/二进制)
  • atomic writes(原子写——先写临时文件再 rename,避免半写状态)
  • mkdir / rename / remove(常规文件系统操作)

"capped reads"(有上限的读)很重要——远端文件可能很大(日志、数据集),sftpfs 限制单次读的规模,防止 agent 把超大文件整个拉下来撑爆上下文。这是第 6 章上下文维护哲学在文件层的延伸。

五、bootstrap 子包:detached serve 启动

internal/remote/bootstrap/bootstrap.go 在远端启动和管理一个 detached reasonix serve 进程。这是 Remote-SSH 的灵魂——agent 不通过 SSH 跑,而是在远端跑一个 serve,本地通过转发的端口连它。

5.1 bootstrap 的契约

// 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.

五步流程:

  1. 检测远端 OS/arch——agent_unix.go/agent_windows.go 平台分流。
  2. 定位或安装 reasonix——install.go/launch.go,远端没有就装一个。
  3. 启动 serve 绑随机 loopback 端口——只在 127.0.0.1 监听,不暴露公网。
  4. 文件 token(绝不进 argv)——token 写文件,serve 启动后从文件读。关键安全细节——token 不放在命令行参数(argv),因为任何能看进程列表的用户都能看到 argv。放文件里权限可控。
  5. 记录到 ~/.reasonix/remote——state.go 把 serve 的端口、token、PID 等记下来,重连时复用,不用重启 serve。

5.2 detached 的意义

"detached"意思是 serve 进程脱离 SSH 会话独立运行——即使 SSH 连接断,serve 继续跑。这样重连后能立刻复用同一个 serve(它还活着,状态还在),不必每次重连都重启 agent 会话。配合 forward 的 Local 转发保留策略,远端 serve 的访问 URL 在网络抖动期间也稳定。

5.3 状态文件复用

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(几十秒),之后连接秒连。

5.4 平台支持

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 的工程实现。

六、桌面全栈:Wails + React 19

第 8 章第 2 节已经讲了桌面的技术栈分层,这里补充更多细节。

6.1 整体布局

images/desktop-v2-layout-preview.png 展示了桌面 v2 的整体布局:

  • 侧边栏:会话列表、Bots 入口(IM bot 连接,第 10 章第 1 节)、项目切换。
  • 主区域:对话流(agent 推理 + 工具调用 + 审批卡)。
  • 底部:输入框、模式切换(Ask/Auto/YOLO)、planmode 开关。
  • Skills 面板:images/desktop-v2-skills-preview.png 展示 Skills/子代理 profile(第 9 章)的管理界面。

6.2 React 19 前端栈

桌面 React 19 前端用了几个关键库:

作用
Zustand 轻量状态管理,替代 Redux,适合中等复杂度
xterm.js 在 webview 里渲染真终端,内嵌终端跑命令
KaTeX 渲染数学公式,agent 输出的 LaTeX 美观显示
Mermaid 渲染流程图/时序图,agent 输出图表直接可视化

这些都是桌面专属的富交互能力,底层 agent 推理/工具/审批还是走 control.Controller。

6.3 8 套官方主题

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 一致。

6.4 桌面作为集成枢纽

桌面 Go 后端除了是前端,还承载多个集成:

  • bot_bridge.go 等:IM bot 运行时(第 10 章第 1 节),桌面是 bot 网关的宿主之一。
  • bot_connection_app.go:bot 连接管理 UI 后端。
  • background_runtime.go:后台运行时,即使主窗口关了 agent 也能继续跑。

这让桌面成为 Reasonix 的"集成枢纽"——不只是 UI,还是 IM bot、后台任务、远程连接的管理中心。这是桌面相对 TUI/SSE 的独特定位。

七、ACP VS Code 扩展

第 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 驱动:

  • 用户在 VS Code 里输入 slash 命令(/reviewer review the current diff)。
  • 扩展通过 ACP 把命令发给 Reasonix 后端。
  • Reasonix 启动子代理(第 9 章)执行,事件流通过 ACP 回传。
  • 扩展把事件渲染成 VS Code 原生 UI(代码 diff、诊断、聊天消息)。

ACP 的价值是协议标准化——任何支持 ACP 的编辑器(VS Code、Neovim、Zed 等)都能零成本接入 Reasonix,不用为每个编辑器写专门的集成。internal/acp 是协议适配层(第 8 章第 2 节),ProtocolVersion = 1 保持 wire contract 稳定。

本节要点回顾

  1. remote 总契约:四条——agent 不通过此包跑(远端 serve 驱动)、前端无关(回调 + 状态订阅)、受监督连接(keepalive + 指数退避)、host-key 验证(系统 known_hosts 只读 + TOFU)。
  2. SPEC §Remote 依赖分层:cli → remote/bootstrap → remote → {remote/forward, remote/sftpfs, config, netclient};remote 及子包永不 import cli/agent/serve;交互走回调(HostKeyPrompt/SecretPrompt)让桌面消费同一 surface——transport-agnostic 在远程模块的落地。
  3. forward 端口转发:Local(-L)本地监听远端拨号、Remote(-R)远端监听本地拨号;Local 转发本地监听器跨重连保留(URL 稳定),Remote 转发每次重连重注册(随 SSH 连接死亡);addrinuse 平台分流处理端口占用。
  4. sftpfs 隔离 pkg/sftp:quarantines github.com/pkg/sftp v1.13.10,其他包不直接 import;收敛 API(目录/stat/capped reads 文本二进制检测/原子写/mkdir/rename/remove);capped reads 防超大文件撑爆上下文;一连接一 *FS 并发安全。
  5. bootstrap detached serve:五步(检测 OS/arch → 定位或装 reasonix → 绑随机 loopback 端口 → 文件 token 不进 argv → 记 ~/.reasonix/remote);detached 脱离 SSH 独立跑,重连复用秒连;V1 支持 Linux/macOS 远端。
  6. 桌面全栈:Wails(Go 后端 + 系统 webview)+ React 19 前端;Zustand/xterm/KaTeX/Mermaid;布局见 desktop-v2-layout-preview.png、Skills 见 desktop-v2-skills-preview.png;8 套官方主题(crimson-horizon/cyan-stage/fortune-forge/noir-gold/rose-dawn/sage-breeze/spark-notebook/violet-starlight);桌面是集成枢纽(bot_bridge/background_runtime)。
  7. ACP VS Code 扩展:stdio JSON-RPC 适配,编辑器内嵌 agent;slash 命令经 ACP 发给 Reasonix 后端;ProtocolVersion=1 稳定;任何 ACP 编辑器零成本接入。

下一节是全书最后一节——Workers(Cloudflare Workers accounts/forum/crash-report)、GoReleaser 六平台交叉编译、SignPath Windows 代码签名、production_checklist 生产清单,以及全书 10 章契约驱动回顾与 Reasonix 核心哲学四点。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U