T3 Code 源码拆解
客户端

15 · Electron 桌面端

主进程如何监督 Server、管理窗口与 IPC,并把 SSH、WSL、更新和预览能力交给共享 UI。

桌面端是一个宿主,不是另一套后端

Electron 应用包含至少三个执行域:

  1. Main process:窗口、协议、更新、secret storage、SSH/WSL、backend supervisor;
  2. Preload:将严格筛选的 IPC API 暴露给 renderer;
  3. Renderer:复用 apps/web 的 React 控制面,通过 hash router 和 platform adapter 使用桌面能力。

此外 main process 会启动一个或多个 T3 Server backend。Server 仍是 project/thread/Agent 的权威,main 只拥有“怎样运行并连接它”。

Electron 进程边界
typed WebSocket RPC narrow IPC remote endpoint Electron Main BrowserWindow DesktopBackendPool Embedded T3 Server SSH / WSL Launchers Updater Privileged Preview Manager Preload IPC bridge Web Renderer

启动阶段与 Electron 约束

main.ts 进入 DesktopApp Effect runtime。 DesktopEarlyElectronStartup / DesktopPreReadyPlatform 处理必须在 app.ready 前设置的协议、命令行和平台行为; 之后才创建窗口、菜单、IPC 和 backend。

Electron 有不少“调用时机就是 API 契约”的能力,例如 custom scheme privilege 必须在 ready 前注册。把早期阶段拆出并测试, 比在一个 app.whenReady().then(...) 中混合所有初始化更可靠。

DesktopBackendManager

DesktopBackendManager.ts 负责单个 backend 的配置、启动、readiness、状态流、重启和停止;DesktopBackendPool.ts 管理多个环境/后端。

一个 backend 不是 spawn 成功就 ready。Manager 需要:

  • 选择可执行入口、端口、userdata 与 auth 配置;
  • 捕获 stdout/stderr 与 exit;
  • 等待健康/readiness;
  • 对预期 shutdown 与 crash 区分;
  • 避免旧进程迟到事件覆盖新 generation 状态;
  • 在窗口仍活着时按策略重启;
  • App 退出时停止所有 backend。

这与客户端 connection supervisor 类似但不是同一个职责:BackendManager 管进程,supervisor 管到 endpoint 的网络 session。

Local environment auth

Renderer 即使和 main 在同一应用,也通过真实 RPC 连接 backend。DesktopLocalEnvironmentAuth.ts 为本地环境提供凭据/会话, 而不是因为“同机”就关闭鉴权。这样同一 backend 暴露给局域网/Tailscale 时不必切换成另一套安全模型。

敏感材料通过 Electron safe storage 或平台 secret backend 保存;Linux 有单独 fallback/实现。Preload 不应把长期 secret 原样暴露给 renderer, 只提供获得已授权 endpoint/session 所需的窄方法。

IPC 的边界

DesktopIpc.ts、 DesktopIpcHandlers.ts 和 channels.ts 定义 renderer 可调用的桌面操作。原则上:

  • 业务数据走 Server RPC;
  • 只有 Electron 特权能力走 IPC;
  • preload 验证/限制参数,不暴露 ipcRenderer 全对象;
  • handler 返回可序列化、可辨别的错误;
  • renderer 通过 platform capability 判断功能是否存在。

如果项目/线程也走 IPC,浏览器和桌面会形成两套协议;如果任意文件/命令执行都暴露给 renderer,XSS 会升级为本机代码执行。

SSH 与 WSL 执行环境

DesktopSshEnvironment.ts 能启动/发现远端 T3 Server,并把它作为 execution environment 加入连接目录。密码/host-key 等交互通过 DesktopSshPasswordPrompts.ts 桥接到受控 UI。DesktopWslBackend.ts / DesktopWslEnvironment.ts 处理 Windows 路径、发行版和进程边界。

这两种能力再次验证“environment identity 与 launch/access method 分离”:

  • SSH 是桌面替用户启动/连接远端 Server 的方式;
  • WSL 是 Windows 上的另一个执行域;
  • 一旦 endpoint 可用,renderer 仍使用同一个 client-runtime/RPC,不需要 SSH 专用聊天状态。

Custom protocol 与深链

ElectronProtocol.ts 和平台 URL handler 支持 t3code:// 等深链。处理流程需要:

  1. 单实例锁与第二实例参数转发;
  2. 在窗口未 ready 时暂存 URL;
  3. 验证 scheme/host/path,不把任意 URL 当命令;
  4. 路由到 connect/pair/thread 页面;
  5. macOS、Windows、Linux 启动参数差异。

深链经常包含配对信息,敏感 token 应放 URL fragment 或短期 code,并尽快兑换/清理。

更新状态机

Electron updater 不只是“检查版本”按钮。updates/updateMachine.ts 对 checking、available、downloading、ready、error、install 等状态建模, updateChannels.ts 区分 channel,releaseNotes.ts 解析展示内容。更新必须协调:

  • 当前 Agent/terminal 是否还在运行;
  • 下载失败是否可重试;
  • renderer 与内嵌 Server 版本是否兼容;
  • 安装重启前能否优雅关闭 backend;
  • 平台签名与安装器差异。

Privileged preview

桌面预览使用 Electron BrowserView/webContents 级能力,preview/Manager.ts、BrowserSession.ts、preload 与元素拾取 runtime 组成隔离边界。Web 页面只接收被挑选的 DOM metadata/截图/状态,不直接获得任意 guest page Node 能力。

对不可信本地开发站点,应禁用 Node integration、启用 context isolation,并严格限制 navigation/new-window 和 preload surface。

Shutdown

DesktopShutdown.ts / lifecycle 服务协调窗口、backend、preview、IPC 和更新。正确 shutdown 顺序要阻止新动作,再停止子服务, 最后退出 Electron;如果直接 app.quit() 后期待所有 finalizer 自行赶上,可能留下 Agent/PTY/SSH child process。

源码锚点

可迁移经验

  • Electron main 管“进程和平台”,Server 管“业务和副作用”;不要因同包发布就抹掉边界。
  • 后端进程 readiness 与网络连接 supervision 是两个状态机。
  • 同机连接仍鉴权,renderer 不应因本地身份获得隐式超级权限。
  • IPC 只暴露平台特权;可复用业务统一走网络契约。
  • SSH/WSL 是环境启动与访问方式,连接成功后复用同一客户端运行时。

On this page