Files
Rtty/KNOWLEDGE_GRAPH.md
CNWei f7a6c50c0b docs: 新增项目知识图谱与新会话自动加载指引
- KNOWLEDGE_GRAPH.md: 架构/实体关系/数据流/WebSocket 协议/设计不变量/构建测试/环境踩坑/路线图
- CLAUDE.md + AGENTS.md: claude 与 Codex 会话自动加载的对接入口
- 记录关键运维约束: 服务端不可在沙盒等受限环境运行(否则 claude 等 TUI 子进程无法写状态)
2026-08-02 21:27:43 +08:00

11 KiB
Raw Permalink Blame History

Rtty 知识图谱Knowledge Graph

面向新会话的快速对接文档。本文件记录项目的实体、关系、流程、协议与约束 项目总览与目录导航见 PROJECT_SUMMARY.md。 新会话请先读本文件 + PROJECT_SUMMARY.md即可直接对接代码库。


1. 项目身份

名称 Rttyrtty-server
定位 仿 dinotty 的分布式虚拟终端系统,只保留「终端同步」核心能力
技术栈 后端 RustAxum + portable-pty + alacritty_terminal前端 Flutter桌面 Windows + 移动 Android
仓库 https://gitea.ngmao.com/CNWei/Rtty.git,默认分支 developer
会话 ID 形如 session-0;也接受客户端自定义 ID按 ID 原子绑定)

2. 架构总览

flowchart LR
    subgraph Server["rtty-server (Rust)"]
        PTY["portable-pty<br/>Shell 子进程"]
        ENGINE["alacritty_terminal Term<br/>真相源:网格/历史/ANSI 解析"]
        WS["Axum WebSocket<br/>会话表 + 广播"]
        PTY -->|原始字节流| ENGINE -->|快照/重绘| WS
    end
    subgraph Desktop["desktop (Flutter Windows)"]
        DB["RttyPtyBackend<br/>WebSocket 桥接"]
        DA["flutter_alacritty 引擎<br/>渲染原始 ANSI"]
    end
    subgraph Mobile["mobile (Flutter Android)"]
        MC["RttyMobileClient<br/>语义快照消费"]
        MU["快照列表渲染<br/>颜色/光标/Keybar"]
    end
    WS -->|"二进制原始 ANSI<br/>client=desktop"| DB --> DA
    WS -->|"JSON 语义快照<br/>client=mobile"| MC --> MU

3. 核心实体与关系

3.1 服务端(src/

实体 文件 职责
AppState main.rs 全局状态:sessions: DashMap<id, Arc<Session>> + 配置
ServerConfig config.rs 环境变量配置:RTTY_HOST/PORT/TOKEN/SHELL/COLS/ROWS/MAX_SCROLLBACK/MAX_CLIENTS/IDLE_TIMEOUT
PtySession terminal/pty.rs portable-pty 封装reader 独占交出、writer 多客户端共享(Arc<Mutex<Box<dyn Write>>>、resize/kill/try_wait
TerminalEngine terminal/engine.rs alacritty Term 真相源;feed() 逐字节喂解析器;snapshot() 生成移动端快照;redraw_ansi() 生成全屏 ANSI 重绘;SessionListenerEvent::PtyWrite(终端应答)写回 PTY
Session ws/handler.rs 一个会话 = PTY + 引擎 + broadcast 输出通道 + 控制权 + 客户端计数(clients/mobile_clients
ClientKind ws/handler.rs ?client= 参数区分:desktop(原始 ANSI 二进制) / mobileJSON 快照),默认 desktop

3.2 桌面端(desktop/lib/

实体 文件 职责
RttyPtyBackend src/rtty_pty_backend.dart 实现 flutter_alacritty 的 PtyBackend二进制输入、指数退避自动重连、30s 心跳、token、onReady(id, cols, rows, resumed)
TerminalScreen src/terminal_screen.dart TerminalEngine + TerminalView 双向接线、resize 防抖80ms
ConnectionBar src/connection_bar.dart HOST/PORT/TOKEN/SESSION 输入 + 状态灯 + SID · RESUMED/NEW 徽章
RttyTerminalConfig src/terminal_config.dart 配色/字体映射到 TerminalConfig

3.3 移动端(mobile/lib/

实体 文件 职责
RttyMobileClient src/rtty_mobile_client.dart ?client=mobile 连接、快照/分段解析、自动重连、心跳、sendInput/sendResize/claimControl
TerminalPage main.dart 连接面板 + 快照列表渲染(颜色/光标块/光标可见滚动)+ 输入框 + KeybarESC/TAB/CTRL+C/CTRL+L/方向键/ENTER

4. 数据流

4.1 输出路径PTY → 客户端)

Shell 输出
  → PtySession.readerspawn_blocking 独占)
  → TerminalEngine.feed()(引擎锁内)
  → 引擎锁内 broadcast
      Raw(chunk)      → desktop 客户端收二进制
      Snapshot(快照)   → 仅当 mobile_clients>0 时生成 → mobile 客户端收 JSON
  → desktop 订阅时/每次 resizeredraw_ansi() 注入 broadcast全屏重绘

顺序不变量Raw / Snapshot / 重绘帧全部在引擎锁内发出,保证与实时输出严格有序(桌面重连恢复依赖此不变量)。

4.2 输入路径(客户端 → PTY

desktop: 引擎输出字节 → backend.write() → WebSocket 二进制帧 → 服务端 Message::Binary → pty.write()
mobile:  输入框/Keybar → sendInput() → JSON {type:"input",data} → 服务端 → pty.write()

4.3 会话生命周期

连接(?session=X)
  → 鉴权RTTY_TOKEN 时校验 ?token=
  → get_or_create_session按 ID 原子「查-建-插」;已存在 → resumed=true否则新建 resumed=false
  → Ready{id,cols,rows,resumed} →mobile 发初始快照)→ 订阅广播 →desktop 注入全屏重绘)
  → 持续双向收发
断开/退出
  → clients-- ;为 0 时启动 idle_timeout默认 60s清理
  → 兜底退出监控100ms 轮询 try_wait上限 5 分钟)+ 读 EOFUnix→ cleanup_session幂等

5. WebSocket 协议

端点:ws://host:port/ws?client=desktop|mobile&session=<id>&token=<token>

5.1 客户端 → 服务端JSON 文本帧)

{"type":"input","data":"ls\r"}      // 输入,写入 PTY
{"type":"resize","cols":120,"rows":32}
{"type":"claim_control"}             // 声明控制权(无控制者时授予)
{"type":"ping"}                      // 心跳 → 服务端回 Pong

桌面端也可用二进制帧直接发原始输入字节(无损)。

5.2 服务端 → 客户端

type 说明
ready {id, cols, rows, resumed}resumed=true 表示恢复既有会话
mobile_snapshot {data: MobileSnapshot},仅发给 mobile
control_response {granted, holder}
pong 心跳应答
error {message};鉴权失败等(客户端据此停止自动重连)
session_closed 会话结束(客户端停止自动重连)

5.3 MobileSnapshot 结构

{
  "cursor_x": 0, "cursor_y": 0,
  "cols": 120, "rows": 32,
  "display_offset": 0, "scrollback_lines": 0,
  "lines": ["..."],                       // 纯文本行(去尾空白)
  "segments": [[{                          // 着色分段,与 lines 一一对应
    "text": "...", "fg": null, "bg": null,
    "bold": false, "italic": false, "underline": false
  }]]
}

颜色编码:null = 默认色;"n:k" = 命名色 0-15"r,g,b" = 具体 RGB服务端已把 256 色索引按标准色表转成 RGB

6. 关键设计决策与不变量

  1. 服务端 alacritty 引擎是唯一真相源:断线重连恢复、移动端快照、桌面端重绘全部源于引擎网格。
  2. 桌面端重连恢复:订阅时注入 redraw_ansi()ESC[2J ESC[H + 逐行 SGR + 光标归位);重连后画面完整,无需客户端重拉。
  3. 惰性快照:仅当存在 mobile 订阅者(mobile_clients>0)才生成快照,纯桌面场景零额外开销。
  4. 二进制输入:桌面端输入走二进制帧,避免 UTF-8 有损转换。
  5. 控制权强制:有控制者时非控制者输入被拒;claim_control 仅无控制者时授予。
  6. 会话 ID 绑定:请求的 ID 不存在时按该 ID 新建(原子 entry存在则复用resumed=true杜绝并发重复创建。
  7. 最小鉴权RTTY_TOKEN 全局令牌(可选);未设置时仅适合内网。
  8. 快照为文本+颜色语义,不是像素级画面:适合查看与轻量交互,不适合全屏 TUI 深度操作vim/claude 建议用桌面端)。

7. 构建 / 运行 / 测试

# 服务端(仓库根)
cargo run                          # 监听 ws://0.0.0.0:8080
$env:RTTY_TOKEN="xxx"              # 可选:启用鉴权

# 桌面端
cd desktop && flutter run -d windows
flutter build windows --release

# 移动端
cd mobile && flutter build apk --debug
adb install -r build/app/outputs/flutter-apk/app-debug.apk

# 测试
cargo test                          # 服务端单测
flutter test                        # 桌面端(服务端离线时集成测试自动跳过)
flutter test                        # 移动端 widget 测试

8. 环境与操作要点(重要,踩坑记录)

  1. ⚠️ 服务端必须在无文件写入限制的环境运行:若在沙盒/受限环境启动服务端其子进程claude 等 TUI 程序)将无法写用户状态文件(如 ~/.claude.json表现为claude 信任确认页勾选后卡死/退出、后续画面消失。修复:在沙盒外启动服务端(本机当前正确运行方式:提权执行后启动 target/debug/rtty-server.exe)。
  2. 本机 Flutter 环境Flutter 3.44.4D:\flutterPUB_CACHE=C:\Users\NianJiu\AppData\Local\Pub\Cacheflutter/git 命令需要提权(沙盒外)执行,因为要写 D:\flutter\bin\cache.git
  3. adb:手机 3060945816001X7adb install -r 可能触发手机安装确认弹窗,重试一次即可。
  4. 手机连接地址:用 PC 局域网 IP192.168.31.249:8080),不要用 127.0.0.1。
  5. Android 明文 WebSocketManifest 已开 usesCleartextTraffic + INTERNET 权限。
  6. 会话空闲清理:默认 60s 无客户端即回收会话PTY 被杀);测试/抓快照要保证有客户端在线。

9. 当前状态与路线图

已实现

  • 服务端 VTE 真相源alacritty_terminal、会话持久化 + resumed 语义、多端解耦广播
  • 桌面端全屏重绘恢复 + 自动重连 + 二进制输入 + token + 心跳
  • 移动端最小验证 App快照渲染颜色/光标、自动重连、Keybar含 ENTER、token
  • 最小鉴权(RTTY_TOKEN、Ping/Pong、并发会话创建竞态修复
  • claude 端到端可用claude 在服务端 PTY 运行,手机可看可发消息;前提:服务端非沙盒)

已知限制

  • 移动端是"语义快照视图":文本+颜色,无完整终端渲染器;全屏 TUIvim/claude 深度操作)体验有限
  • 移动端无文本换行Word Wrap、无 Keybar 自定义、无粘贴支持
  • 鉴权仅全局令牌,无细粒度权限/会话绑定

规划

  • 移动端 TUI 透传模式(全屏快照渲染 + 虚拟键盘)
  • 智能换行 / 卡片化命令交互 / Keybar 自定义
  • 分布式横向扩展、服务端细粒度鉴权

10. 常见问题速查

现象 原因/解法
手机连不上 用 LAN IP确认服务端在跑Test-NetConnection ip -Port 8080
手机显示空白 会话被空闲回收60s→ 重新连接;或确认 ?client=mobile 已带上
claude 勾选确认后画面消失 服务端运行在受限环境 → 改为沙盒外启动
桌面端重连后画面空白 检查服务端为最新构建(含 redraw_ansi);旧二进制无重绘功能
flutter 命令卡住 本机 flutter 需提权运行;设置 PUB_CACHE 指向 NianJiu 的 Pub 缓存
输入乱码/被吞 桌面端应走二进制帧;\r 进 JSON 必须转义(用 ConvertTo-Json 而非手拼)