- HANDOFF: 修正 git 说明(6 commits/artifacts 有意提交)、重排 §7 下一步(真机化为首要缺口)、 更新单测计数(50)、补 M4/tmux resize 代码地图 - CLAUDE.md: 单测计数 50、git 状态 - README: 全面重写——M0~M4 进展、实际包结构(TXCore/TXTransport/app,澄清计划包未成形)、 artifacts 有意提交、指向 HANDOFF/CLAUDE Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
16 KiB
16 KiB
terminalX 交接文档(HANDOFF)
目的:让没有上下文的新会话读完即可掌握项目全貌并继续工作。配合
../CLAUDE.md(操作规范/命令)与项目记忆(~/.claude/projects/-Users-kid-...-terminalX/memory/,本项目会话会自动注入)一起看。 最后更新:2026-07-24。
0. 一句话现状
一个原生 iPad SSH 终端已可用并真机验证:libghostty 渲染 + 键盘输入 + SSH 直连 + SSH-over-tsnet(Tailscale 用户态) + tmux -CC 原生 tab + 多 pane 分屏 + mosh(LAN + over-tsnet 均已端到端验证) + 断线自动重连 + mosh 挂起→恢复<3s(M3)。四大核心需求(libghostty/tailscale/tmux/mosh 均已验证)。仅在 iOS 模拟器验证(未上真机/未签名)。
mosh 状态(2026-07-24 全部验证通过):mosh(C++,blinksh/ios)+protobuf-lite 交叉编译成
MoshCore.xcframework(CommonCrypto 后端,含 arm64 模拟器 slice);tsnet 桥加 UDP relay(StartMoshRelay);MoshSession(pipe↔FILE*/pthread/SIGWINCH)+MoshConnectScanner(TXCore)+SSHTerminalModel编排(SSH 引导 mosh-server→解析 MOSH CONNECT→接管)。
- LAN 直连端到端验证(192.168.9.199):mosh-server 存活 104s≫60s 无客户端超时+子 shell+心跳=真连。
- mosh over tsnet 端到端验证(vohive-vm 100.69.201.101):app 日志
parsed MOSH CONNECT port=60002→relay up localPort=63542→activateMosh started;屏幕渲染远端 shell 且无 mosh 断连 overlay=relay UDP 双向流通。R1(tsnet UDP 数据报语义) 证伪。- 复现 tsnet:模拟器已持久化 tsnet 节点状态(
~/Library/.../tsnet-main),-txTsnetKey传任意非空串即可凭存量身份重连(节点未被 tailnet 清理时);节点被清理则需新 ephemeral key。
1. 项目愿景与硬约束
- 目标:iPad/iPhone 远程轻量运维 macOS/Linux + vibe coding。平台优先级 iPadOS > iOS > macOS(Apple Silicon)。
- iOS/iPadOS 只远程(沙盒禁 fork/exec,不能跑本地 shell);macOS 才支持本地 PTY。
- 分发目标 App Store(M5,已暂缓)。
- 四大核心:libghostty(终端引擎) · mosh · Tailscale tsnet(用户态,非 NetworkExtension) · tmux control-mode(-CC) hook。
2. 关键架构决策(已定,勿推翻)
- 终端引擎用 libghostty,经社区 GhosttyKit(
Lakr233/libghostty-spm,已 vendor 到vendor/libghostty-spm)。TerminalViewState是ObservableObject;喂字节入口InMemoryTerminalSession.receive(Data);SwiftUI 视图TerminalSurfaceView(context:)。 - Tailscale 走 tsnet 用户态(Go,gomobile bind →
TsnetBridge.xcframework),不用 NetworkExtension(避开 VPN entitlement + 50MB 内存墙)。 - SSH 用 libssh2 + mbedTLS(
CSSHCore.xcframework),支持传入外部 fd。 - SSH-over-tsnet = fd 桥:
tsnet.Dial()得 net.Conn(无真实 fd)→socketpair(AF_UNIX)+ 双向 io.Copy 泵 → 把另一端 fd 交给 libssh2。见vendor/tsnet-bridge/tsnetbridge/bridge.go: DialTCPFD。 - 一进程只一个 Go runtime、只一次 gomobile bind;C/C++ 库符号隐藏只导出 C API。
- 多账号(未实现):多 tsnet 节点并存;每 host 绑 egress profile(Direct/tailnet);禁止多 tailnet 并发盲探(重叠 100.x/同名会误连)。
3. 环境(决定构建方式,极重要)
- 带认证的 HTTPS 代理(
HTTPS_PROXY=...@...:38128)。github.comrelease blob 能 curl;go.dev / ghcr.io 被代理 abort;SwiftPM 不支持带认证代理 → 所有 SPM 依赖 vendor 本地化。Go 模块用goproxy.cn。 - Go 1.26.5 在
~/.local/go(不在默认 PATH;经 aliyun 镜像 curl 装),gomobile/gobind 在~/go/bin,go env -w GOPROXY=https://goproxy.cn GOSUMDB=off已固化。 - 工具链:Xcode 26.6 / Swift 6.3 / xcodegen 2.46 / zig 0.15.2 / cmake 4.4 / ninja 1.13 / git。
4. 里程碑状态
| 里程碑 | 状态 | 真机实证 |
|---|---|---|
| M0 SSH 直连终端 | ✅ 完成 | libghostty 渲染(SGR/CJK)、键盘输入、连接;白屏 bug 修复(OutputGate);会话状态机+自动重连(TXCore 单测总数见 §5=50) |
| M1 tsnet + SSH-over-tsnet | ✅ 完成 | app 内 tsnet 加入 tailnet(IP 100.74.60.108);对端枚举;SSH-over-tsnet 登录 Ubuntu shell(服务端 Last login from 100.x 证明走 tailnet) |
| M1.5 tmux -CC 原生 tab | ✅ MVP | 直连跑 tmux -CC attach → 渲染 fish/tmux 两原生 tab + 活动窗口内容 |
| dial 重试打磨 | ✅ | TsnetManager.dialFD 6 次退避重试,解决 tsnet 首连路径预热超时 |
| R5 Go+gvisor+C+++Swift 同进程 | ✅ retire | 全程真机无崩溃 |
| M2 mosh | ✅ 完成(LAN + tsnet 均验证) | libmoshios(C++)+protobuf-lite→MoshCore.xcframework;UDP relay over tsnet;LAN 直连 + mosh-over-tsnet 均端到端验证通过(R1 证伪) |
| M3 生命周期深化 | ✅ mosh 快恢复(真机深挂起待补) | scenePhase FSM + mosh 挂起→恢复<3s 已验证(模拟器:15/30s 冻结→前台,wake pulse→1.3s 收到服务器包,"已恢复");冷启动重连(SSP 序号+key 持久化)划入 M4/M5 |
| M4 安全 | ✅ 完成(真机 SE 硬件待补) | host key TOFU pin(首次信任+不符告警,指纹对齐 ssh-keygen,MITM 关闭)+ publickey 认证(P-256 sign_callback→SecKeyCreateSignature→DER→SSH mpint,免密登录 192.168.9.199 验证通过;SE 优先/未签名回退软件+文件,SE 生成在模拟器 hw=true 可达,真机 SE 硬件签名+keychain 持久化待补) |
| M5 合规提审 | ⏸️ 暂缓 | 用户指示暂缓 |
5. 代码地图与数据流
packages/TXCore/Sources/TXCore/Tmux/TmuxControlParser.swiftTmuxEvent.swiftTmuxIDs.swiftTmuxOutputDecoder.swiftTmuxLayout.swiftTmuxControlSequence.swift(DCS 检测)—— 纯解析。TXCore 全部单测共 50(40 swift-testing + 10 XCTest:tmux 解析/SessionMachine含mosh三相/MoshConnect/SSHWire),swift test --package-path packages/TXCore全绿。Session/SessionMachine.swift——reduce(event)->[Effect]会话状态机(指数退避重连/后台冻结/前台恢复)。M3 加 mosh 三相moshActive/moshParked/moshResuming+ 事件moshEstablished/moshHealthy/moshExited/resumeWatchdogFired+ 效果nudgeResume/scheduleResumeWatchdog/teardownMosh:mosh 接管即拆 SSH、后台冻结不重建、前台打唤醒脉冲等 SSP 续、两次脉冲无效兜底全量重建。
packages/TXTransport/Sources/TXTransport/Transport.swift(协议+SSHConfig+SSHError)、SSHSession.swift(libssh2;preconnectedFD支持外部 fd;事件循环 poll+非阻塞)。TXTransport/MoshSession.swift(M2):驱动mosh_main;pipe↔FILE* 桥(in/out)、Foundation.Thread 跑阻塞主循环+pthread_self()供pthread_kill(SIGWINCH)resize、忽略 SIGPIPE、setenv UTF-8 locale。CSSH/(C target:libssh2 头 + modulemap,供import CSSH)、CSSHCore(binaryTarget,library-only)。CMosh/(C target:moshiosbridge.h(mosh_main)+modulemap+占位 shim.c,供import CMosh)、MoshCore(binaryTarget,library-only);TXTransport 链libc++/libz(mosh 是 C++ 且 compressor 用 zlib;CommonCrypto 属 libSystem 自动解析)。TXCore/Mosh/MoshConnect.swift:MoshConnectScanner流式扫描MOSH CONNECT <port> <key>(注意 Swift 视\r\n为单 Character,行尾判定须用isNewline)。TXCore/SSH/(M4):SSHWire(string/mpint 编码)/ECDSAConv(DER→SSH 签名·P256 blob)/HostKey(opensshFingerprint·evaluate·HostTriple·KnownHostsStore 协议),SSHWireTests 10 单测(真实指纹向量+高位 DER)。
- M4 安全:
TXTransportSSHConfig.hostKeyVerifier(闭包)+SSHError.hostKeyMismatch+SSHSigner协议+Authentication.publicKeyCallback;SSHSession握手后libssh2_session_hostkey校验、sshSignCallback(@convention(c)+malloc 出参)接libssh2_userauth_publickey。app:KeychainKnownHostsStore(文件后端 TOFU pin)、SigningKeyProvider(SE 优先/软件回退+文件持久化,SecKeyCreateSignature)、mismatch alert(ContentView)。 - M3 快恢复链:
bridge.go加Node.WakeUp()(tsnetInjectEvent+MagicSock.Rebind/ReSTUN) 与MoshRelay.Rebind()(同端口重开 loopback、泵按 socket 生成容错);iosclient.cc加g_mosh_last_heard(收到服务器包即更新,导出mosh_last_heard_ms()) 并恢复 SIGCONT 全屏重绘;MoshSession.nudge()(SIGCONT) +lastHeardMs();SSHTerminalModel前台执行"唤醒脉冲三连"(WakeUp+Rebind+nudge)+healthy 轮询(探针增长→moshHealthy)+恢复看门狗+beginBackgroundTask。 apps/TerminalX/iOS/SSHTerminalModel.swift—— 核心编排:OutputGate(surface-ready 缓冲)、TransportHolder、TsnetManager(tsnet 节点 up/dial+重试)、TsnetError、TmuxRouter(raw/gateway 字节分流)、状态机驱动 startSession(直连/tsnet 分支)、autoConnectIfConfigured(启动参数)。ContentView.swift—— 分支:tmuxController != nil→TmuxTabbedView(tab 条+活动窗口);否则RawTerminalView;TsnetProbeView(纯自检);ConnectionForm。TmuxController.swift—— tmux 网关:消费解析事件;pane-per-surface(TmuxPaneSurface=session+state+gate/pane);TmuxWindow持 panes 字典 +visibleLayout(渲染)/fullLayout(diff);surfaceByPaneO(1) 路由 %output;applyLayoutreconcile(增删留);attach:refresh-client -C WxH→list-windows(带 layout)→每 panecapture-pane(awaitingCapture丢弃快照前 %output);输入绑 pane→send-keys -t %self;tap→select-pane。字节经SSHTerminalModel的TmuxByteChannel(AsyncStream 单消费者)保序入网关。TsnetProbe.swift—— M1 自检视图(加入 tailnet + 对端列表)。TerminalXApp.swift—— @main。
- 数据流(直连):transport.onBytes →
TmuxRouter.feed→ (raw)OutputGate.deliver→session.receive→libghostty;(检测到 DCS) →TmuxController.feed→解析→各 window。输入:libghostty→session write 闭包→transport.send(tmux 模式→send-keys)。 - 数据流(tsnet):
TsnetManager.ensureUp(auth key)→dialFD(socketpair fd)→SSHSession(preconnectedFD:)。
6. 已知问题 / TODO(继续工作的入口)
- M4 安全(已完成,剩真机 SE):host key TOFU pin(
SSHConfig.hostKeyVerifier→appKeychainKnownHostsStore文件后端) + publickey 认证(Authentication.publicKeyCallback(SSHSigner)→SSHSessionCsshSignCallback→SigningKeyProvider)。剩:真机验证 SE 硬件密钥生成+签名+keychain 持久化(未签名/模拟器回退软件+文件);libssh2_session_method_pref锁定 pinned hostkey 类型防算法漂移误报(fable 建议,未做);M4DBG NSLog 埋点可清理。 - tmux 多 pane 分屏 + 动态 resize:✅ 已做(pane-per-surface + layout rect 绝对定位渲染 + 按 pane %output 路由 + tap 切焦点/select-pane + attach
refresh-client -C设尺寸 + iPadOS 拖拽窗口/旋转 →onChange(geo.size)→containerResized(点×displayScale÷cell像素)→debounce→refresh-client -C→%layout-change→更新 frame,验证:容器 1032x1280@2x cell16x35→129x73,tmux 精确采纳)。剩余:完整 scrollback(仍靠 capture-pane 抓当前屏);pause 流控(%pause/%continue,防单 pane 刷屏);split/kill pane 手势;tmux<3.1 降级路径未做。 - tsnet 首连:已加 dial 重试;更优是等 peer online 再 dial(读 PeersJSON online 状态)。
- 签名/真机:
CODE_SIGNING_ALLOWED=NO,仅模拟器;上真机/TestFlight 需配置签名 + entitlements(tsnet 用户态无需 NE entitlement,利好审核)。 - git:main 分支已有 6 次提交(初始 + M2/M3/tmux多pane/tmux resize/M4a/M4b),工作区干净,未 push 远端(无 remote)。commit/push 只在用户明确要求时。
artifacts/*.xcframework(CSSHCore/TsnetBridge/MoshCore)+vendor/mosh+vendor/protobuf源码均有意提交(克隆即可构建,无需网络);构建中间产物(vendor/build/mosh-out、vendor/protobuf/build-*)已 gitignore。 - 多平台:iOS target 已跑;macOS target(本地 PTY,
TXPTY)未建。
7. 下一步建议(M0~M4 + tmux 多pane/resize 均已完成并验证;以下按价值)
- 真机化(最大缺口,多项验证只在模拟器):配置签名 + entitlements(tsnet 用户态无需 NE,利好审核)→ 上真机/TestFlight。真机上补验三件模拟器测不了的:①SE 硬件密钥生成+签名+keychain 持久化(M4b 现回退软件+文件;
SigningKeyProvider已备 SE 路径);②mosh 深挂起恢复(锁屏数分钟过 WG 180s 过期线 / socket defunct,验MoshRelay.Rebind+hop_port);③签名后 Keychain 可用(当前未签名 SecItem 全 -34018,host key pin 才回退到文件)。 - 产品化:主机列表持久化(签名后用 Keychain 存凭据/host 配置)、多账号多 tsnet 节点(架构决策 §2.6:每 host 绑 egress,禁多 tailnet 盲探)、软键盘运维工具栏(GhosttyKit 内建,接线即可)、多标签(非 tmux)。
- 打磨(可选,不阻塞):
- 清理诊断埋点:
SSHTerminalModel/TmuxController/iosclient.cc的NSLog("MOSHDBG/TXM3/TMUXDBG/M4DBG …")+ moshiosbridge.cc 的fwrite("Hello from the Bridge!")。 - mosh:
activateMosh写死 80x24 初值(靠首次 resize 纠正);mosh 接管后 idle SSH 未关;MoshSession.close()后 mosh 线程可能滞留;远端累积 detached mosh-server(每次mosh-server new)需清理策略。 - tmux:pause 流控(
%pause/%continue防单 pane 刷屏);split/kill pane 手势;外接键盘Cmd+Opt+方向导航 pane;完整 scrollback(现靠 capture-pane 抓当前屏);tmux<3.1 降级路径。 - M4:
libssh2_session_method_pref锁定 pinned hostkey 类型防算法漂移误报;host key pin 的 UI 管理(查看/删除已信任)。 - tsnet 首连:更优是等 peer online 再 dial(读 PeersJSON online 状态)。
- 清理诊断埋点:
- 冷启动 mosh 重连(大活):mosh 进程被杀后接原 detached 会话——受 SSP 序号防重放限制,需持久化 MOSH_KEY+port 且改 mosh 序列化客户端状态(Blink 式)。价值中等,成本高。
- macOS target:本地 PTY(
TXPTY)未建。 - M5 合规提审:用户指示暂缓。
8. 测试资源(用户提供,凭据勿写入仓库/勿硬编码,每次向用户索取)
- 一台局域网 macOS(有 tmux + mosh-server,如 MacBook-Air-M1;fish 为默认 shell,
mosh-server在/opt/homebrew/bin且交互 shell PATH 可见)、一台 tailnet 内 online 的 Ubuntu VM(vohive-vm)、一个 tailnet auth key(ephemeral,可能过期,用户会给新的)。用于直连 SSH / SSH-over-tsnet / tmux / M2 mosh 端到端验证。凭据每次向用户即时索取。 - tailnet 是
tail3b5057.ts.net,已见节点:vohive-vm(linux)、iphone-air、ipad-pro、macbook-air-m1、macbook-air。
9. 工作纪律(务必遵守)
- 绝不臆想工具结果:调用后等真实返回再推理,绝不编造 BUILD/日志/截图。曾因连续编造工具输出(含假 BUILD SUCCEEDED、错误归因 GhosttyKit)而误诊——真相全靠老实
Read完整 log。此规则已写入全局~/.claude/CLAUDE.md。 - 每步用真实证据验证:
swift test看真实计数、xcodebuild看真实 BUILD 结果、模拟器screenshot+Read看真实画面。 - 与用户用简体中文;commit/push 只在明确要求时。