docs: 新增项目知识图谱与新会话自动加载指引

- KNOWLEDGE_GRAPH.md: 架构/实体关系/数据流/WebSocket 协议/设计不变量/构建测试/环境踩坑/路线图
- CLAUDE.md + AGENTS.md: claude 与 Codex 会话自动加载的对接入口
- 记录关键运维约束: 服务端不可在沙盒等受限环境运行(否则 claude 等 TUI 子进程无法写状态)
This commit is contained in:
2026-08-02 21:27:43 +08:00
parent 7323364b9c
commit f7a6c50c0b
3 changed files with 256 additions and 0 deletions

218
KNOWLEDGE_GRAPH.md Normal file
View File

@@ -0,0 +1,218 @@
# Rtty 知识图谱Knowledge Graph
> 面向新会话的快速对接文档。本文件记录项目的**实体、关系、流程、协议与约束**
> 项目总览与目录导航见 [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md)。
> 新会话请先读本文件 + PROJECT_SUMMARY.md即可直接对接代码库。
---
## 1. 项目身份
| 项 | 值 |
|---|---|
| 名称 | Rttyrtty-server |
| 定位 | 仿 [dinotty](https://github.com/xichan96/dinotty) 的分布式虚拟终端系统,**只保留「终端同步」核心能力** |
| 技术栈 | 后端 RustAxum + portable-pty + alacritty_terminal前端 Flutter桌面 Windows + 移动 Android |
| 仓库 | `https://gitea.ngmao.com/CNWei/Rtty.git`,默认分支 `developer` |
| 会话 ID | 形如 `session-0`;也接受客户端自定义 ID按 ID 原子绑定) |
## 2. 架构总览
```mermaid
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 重绘;`SessionListener``Event::PtyWrite`(终端应答)写回 PTY |
| `Session` | `ws/handler.rs` | 一个会话 = PTY + 引擎 + broadcast 输出通道 + 控制权 + 客户端计数(`clients`/`mobile_clients` |
| `ClientKind` | `ws/handler.rs` | 按 `?client=` 参数区分:`desktop`(原始 ANSI 二进制) / `mobile`JSON 快照),默认 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 文本帧)
```jsonc
{"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 结构
```jsonc
{
"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. 构建 / 运行 / 测试
```bash
# 服务端(仓库根)
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.4`D:\flutter``PUB_CACHE=C:\Users\NianJiu\AppData\Local\Pub\Cache`flutter/git 命令需要提权(沙盒外)执行,因为要写 `D:\flutter\bin\cache``.git`
3. **adb**:手机 `3060945816001X7``adb install -r` 可能触发手机安装确认弹窗,重试一次即可。
4. **手机连接地址**:用 PC 局域网 IP`192.168.31.249:8080`),不要用 127.0.0.1。
5. **Android 明文 WebSocket**Manifest 已开 `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` 而非手拼) |