返回列表

博客

深入 Mac 伴侣应用:轻量级会话管理器的架构设计

Mac 伴侣应用是 Tactic Remote 默默运转的基石——一个常驻菜单栏的实用工具,负责管理 tmux 会话、运行内嵌的 HTTP/WebSocket 服务器,并在 AI 工作期间保持 Mac 不休眠。

发布于 标签: architecture / mac / engineering
博客

继续查看该栏目下的产品与工程更新。

浏览此分类

Mac 伴侣应用是 Tactic Remote 中最不起眼的部分,但可以说是最重要的部分。它运行在菜单栏中,管理你机器上的每个 Claude Code 会话,为你的 iPhone 提供 API 服务,并承担着在休眠周期和网络变化中保持会话存活这一不起眼但至关重要的工作。本文将详细说明它的工作原理以及我们做出各项架构决策的原因。

设计约束

我们在开发初期确立了三个约束条件:

  1. 应用在空闲时必须几乎无感知。 开发者通常已经运行了几十个后台进程。一个在空闲状态下消耗明显 CPU 或内存的会话管理器是不可接受的。
  2. 必须支持多个并发会话。 许多开发者会同时运行两三个 Claude Code 任务——一个用于功能分支,一个用于测试,一个用于文档。
  3. 必须优雅地应对 Mac 休眠和网络变化。 开发者合上笔记本电脑,走到咖啡店,重新打开,一切应该在几秒钟内重新连接。

这些约束塑造了架构的每一层。

内嵌服务器模型

伴侣应用没有运行一个独立的服务器进程,而是直接内嵌了一个 HTTP 和 WebSocket 服务器。这是经过深思熟虑后做出的选择,我们放弃了附带一个独立守护进程的方案。

为什么选择内嵌? 独立的守护进程需要自己的生命周期管理——launchd 配置、进程监控、日志轮转和崩溃恢复。这些每一项都是潜在的故障点和维护负担。通过将服务器内嵌到应用本身,生命周期变得很简单:应用运行,服务器就运行。应用退出,一切干净地停止。

内嵌服务器绑定到一个可配置的端口(默认 48736),可选择仅绑定 localhost(用于本地网络模式,配合独立的网络代理层)或绑定所有接口。它提供用于会话管理和状态查询的 REST API,以及用于实时终端流传输和审批推送的 WebSocket 端点。

在空闲状态下,服务器保持打开的套接字但不执行任何工作。在我们的基准测试中,没有活跃会话时的伴侣应用大约消耗 18 MB 内存和可忽略不计的 CPU——与典型的菜单栏实用工具相当。

tmux 作为会话骨架

Tactic Remote 管理的每个 Claude Code 会话都在 tmux 会话内运行。我们选择 tmux 而非 screen 或直接 PTY 管理,原因如下:

会话持久性。 tmux 会话独立于创建它们的进程存活。如果伴侣应用崩溃并重启,tmux 会话及其 Claude Code 进程会继续不受干扰地运行。应用只需在启动时重新附加到现有会话即可。

无干扰捕获。 tmux 的 capture-pane 命令让我们可以在不注入输入或修改运行中进程的情况下读取终端内容。这至关重要——我们需要将终端输出流传输到 iPhone,同时不能有任何干扰 Claude Code 运行的风险。

命名会话。 每个 Claude Code 会话都有一个可预测的 tmux 会话名称(例如 claude-remote-0、claude-remote-1),这使得枚举、附加和以编程方式管理会话变得简单。

成熟且通用。 tmux 随 macOS 附带,或者可以通过 Homebrew 轻松安装。它拥有数十年的稳定性积累,并处理了我们否则必须自行解决的边缘情况(终端大小调整、Unicode、转义序列)。

伴侣应用以可配置的间隔(默认 200ms)轮询活跃的 tmux 会话以捕获最新的终端内容。当内容发生变化时,它通过 WebSocket 将差异推送到已连接的客户端。这种轮询方式是刻意选择的简洁方案——我们评估了基于文件系统的通知机制,发现在不同 macOS 版本上,它们不如在此间隔下进行直接轮询可靠。

会话生命周期管理

Tactic Remote 中的 Claude Code 会话经历一个明确的生命周期:

  1. 创建 — iPhone 发送"启动会话"请求。伴侣应用创建一个新的 tmux 会话,并在其中以指定的工作目录和任务描述启动 Claude Code。
  2. 活跃监控 — 终端内容被捕获并流传输。来自 Claude Code hook 系统的审批请求被拦截,并通过 WebSocket 和推送通知转发到 iPhone。
  3. 空闲检测 — 如果某个会话在可配置的时间段内没有终端输出变化,其轮询频率会降低以减少资源使用。
  4. 完成 — Claude Code 退出。伴侣应用检测到退出,捕获最终输出,并通知 iPhone。
  5. 清理 — tmux 会话在可配置的保留期(默认 30 分钟)内被保留,以便用户查看输出,然后自动清理。

管理多个并发会话意味着独立地复用这些生命周期。每个会话有自己的捕获循环、自己的 WebSocket 通道和自己的状态机。伴侣应用目前最多支持 8 个并发会话,这一限制是基于实际资源测试而非架构约束。

防止 Mac 休眠

这是不太显而易见的工程挑战之一。macOS 在检测到不活动时会积极进入休眠状态,而在 tmux 中运行的 Claude Code 任务不会产生 macOS 识别为"活动"的用户输入。

伴侣应用在至少有一个 Claude Code 会话活跃运行时,使用 IOPMAssertionCreateWithName API 来持有电源断言。断言类型为 kIOPMAssertPreventUserIdleSystemSleep,它可以防止系统休眠,同时仍允许显示器关闭。当所有会话完成或被终止时,断言会立即释放。

我们考虑过使用 caffeinate 作为外部进程,但出于与拒绝独立守护进程相同的理由放弃了它——它增加了生命周期复杂性。IOKit 断言 API 给予我们精确的控制,并能与应用自身的会话追踪干净地集成。

菜单栏集成

伴侣应用以菜单栏图标和一个简洁的下拉界面呈现。菜单显示:

  • 连接状态 — 服务器是否在运行、绑定到哪个端口、Cloudflare Tunnel 是否处于活跃状态。
  • 活跃会话 — 正在运行的 Claude Code 会话数量及简要状态指示。
  • 二维码访问 — 一键显示包含连接 URL 和认证令牌的二维码,用于 iPhone 配对。
  • 快捷操作 — 启动/停止服务器、打开设置、复制连接 URL。

我们刻意避免构建完整的窗口化界面。伴侣应用的职责是充当基础设施,而非交互目的地。开发者通过 iPhone 与 Tactic Remote 交互;Mac 应用只应在初始设置和偶尔的配置更改时才需要关注。

Cloudflare Tunnel 集成

启用 Cloudflare Tunnel 模式后,伴侣应用以子进程方式管理 cloudflared 进程。它使用配置好的隧道凭据启动 cloudflared,通过本地指标端点监控其健康状态,并在隧道断开时自动重启。

此集成处理了一个微妙的时序问题:隧道必须完全建立后,iPhone 才能通过它进行连接。伴侣应用会等待 cloudflared 报告连接健康(通常在启动后 1-3 秒)后,才将隧道 URL 标记为可用。在此期间,菜单栏图标会显示独特的"正在连接"状态。

如果隧道进程崩溃,伴侣应用会以指数退避方式(2 秒、4 秒、8 秒)尝试最多 3 次重启,然后将隧道模式标记为失败,并通知已连接的客户端回退到本地模式(如果可用)。

认证

每个 API 请求和 WebSocket 连接都需要 Bearer Token。该令牌在首次启动时生成,并存储在 macOS 钥匙串中。它包含在设置过程中显示的二维码中,因此配对只需扫描一次,无需手动复制令牌。

该令牌是一个 256 位随机值,使用 SecRandomCopyBytes 生成。我们选择静态令牌模型而非基于会话的认证,因为主要威胁模型是未经授权的网络访问,而非多用户访问控制。对于需要按用户认证的团队,我们建议在隧道前部署 Cloudflare Access。

我们考虑过但放弃的方案

完整的 Electron 应用。 仅内存开销就否决了这个方案——Electron 的基线是 80-150 MB,大约是我们原生 Swift 应用空闲时的 5-8 倍。

独立的 CLI 守护进程。 初期构建更简单,但对非技术用户来说更难管理,且无法集成 macOS 菜单栏规范。

直接 PTY 管理代替 tmux。 控制力更强,但我们将不得不重新实现会话持久性、终端模拟边缘情况以及进程生命周期管理——这些 tmux 已经可靠地处理了。

用 LaunchAgent 实现自启动。 我们实现了这个功能,但将其设为可选而非默认。开发者对登录时运行什么有强烈的偏好,我们尊重这一点。

性能概况

在典型负载下(2 个活跃会话,1 部已连接的 iPhone),伴侣应用的资源使用:

指标数值
内存 (RSS)22-35 MB
CPU(空闲,无输出变化)< 0.1%
CPU(活跃流传输,2 个会话)1-3%
磁盘 I/O可忽略不计(默认不写入磁盘日志)
网络(本地模式,活跃流传输)10-50 KB/s

这些数据在运行 macOS 14 的 MacBook Pro M2 上测量。Intel Mac 上的性能相当,在活跃流传输时 CPU 使用率略高。

展望未来

伴侣应用的架构设计旨在支持超出当前已发布功能的能力。会话管理层可以容纳会话共享(多部 iPhone 监控同一会话)、会话录制(捕获完整终端历史用于回放),以及随着 Claude Code 可扩展性 API 的演进与之更深度地集成。

有关设置说明,请参阅快速入门。有关 Mac 伴侣应用的故障排除,请参阅常见问题。

立即体验 Tactic Remote

用手机远程控制编程 Agent

连接 Mac、Windows 或 Linux 电脑上的 Claude Code、Codex 等 Agent,在 iPhone 或 iPad 上查看进展并发送下一条要求。