docs: 新增项目知识图谱与新会话自动加载指引
- KNOWLEDGE_GRAPH.md: 架构/实体关系/数据流/WebSocket 协议/设计不变量/构建测试/环境踩坑/路线图 - CLAUDE.md + AGENTS.md: claude 与 Codex 会话自动加载的对接入口 - 记录关键运维约束: 服务端不可在沙盒等受限环境运行(否则 claude 等 TUI 子进程无法写状态)
This commit is contained in:
18
AGENTS.md
Normal file
18
AGENTS.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Rtty 项目指引(Codex 会话自动加载)
|
||||
|
||||
Rtty 是一个仿 dinotty 的分布式虚拟终端系统,**只保留终端同步能力**:Rust 服务端
|
||||
(Axum + portable-pty + alacritty_terminal 真相源)通过 WebSocket 同步到
|
||||
Flutter 桌面端(原始 ANSI 渲染)与移动端(语义快照)。
|
||||
|
||||
**对接前必读**:
|
||||
- [KNOWLEDGE_GRAPH.md](./KNOWLEDGE_GRAPH.md) —— 知识图谱:架构、协议、数据流、不变量、踩坑记录
|
||||
- [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md) —— 项目总览与目录导航
|
||||
|
||||
**关键约束**:
|
||||
- 服务端 alacritty 引擎是唯一真相源;Raw/快照/重绘帧都在引擎锁内广播(严格有序)
|
||||
- 协议见 KNOWLEDGE_GRAPH.md §5;移动端颜色分段 `segments` 是扩展字段
|
||||
- 会话空闲 60s 回收;`RTTY_TOKEN` 可选鉴权;桌面端输入走二进制帧
|
||||
- 本机 flutter/git 命令需要提权(沙盒外)执行;`PUB_CACHE=C:\Users\NianJiu\AppData\Local\Pub\Cache`
|
||||
|
||||
**⚠️ 最重要的一条**:服务端不能在文件写入受限的环境(沙盒)里运行,否则子进程
|
||||
(如 claude)无法写入用户状态文件,表现为 TUI 应用确认后卡死/退出。
|
||||
20
CLAUDE.md
Normal file
20
CLAUDE.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Rtty 项目指引(Claude Code 会话自动加载)
|
||||
|
||||
Rtty 是一个仿 dinotty 的分布式虚拟终端系统,**只保留终端同步能力**:Rust 服务端
|
||||
(Axum + portable-pty + alacritty_terminal 真相源)通过 WebSocket 同步到
|
||||
Flutter 桌面端(原始 ANSI 渲染)与移动端(语义快照)。
|
||||
|
||||
**对接前必读**:
|
||||
- [KNOWLEDGE_GRAPH.md](./KNOWLEDGE_GRAPH.md) —— 知识图谱:架构、协议、数据流、不变量、踩坑记录
|
||||
- [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md) —— 项目总览与目录导航
|
||||
|
||||
**关键约束**:
|
||||
- 服务端 alacritty 引擎是唯一真相源;Raw/快照/重绘帧都在引擎锁内广播(严格有序)
|
||||
- 协议见 KNOWLEDGE_GRAPH.md §5;移动端颜色分段 `segments` 是扩展字段
|
||||
- 会话空闲 60s 回收;`RTTY_TOKEN` 可选鉴权;桌面端输入走二进制帧
|
||||
|
||||
**运行**:`cargo run`(监听 ws://0.0.0.0:8080);桌面端 `cd desktop && flutter run -d windows`;
|
||||
移动端 `cd mobile && flutter build apk --debug`。
|
||||
|
||||
**⚠️ 最重要的一条**:服务端不能在文件写入受限的环境(沙盒)里运行,否则子进程
|
||||
(如 claude)无法写入用户状态文件,表现为 TUI 应用确认后卡死/退出。
|
||||
218
KNOWLEDGE_GRAPH.md
Normal file
218
KNOWLEDGE_GRAPH.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Rtty 知识图谱(Knowledge Graph)
|
||||
|
||||
> 面向新会话的快速对接文档。本文件记录项目的**实体、关系、流程、协议与约束**;
|
||||
> 项目总览与目录导航见 [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md)。
|
||||
> 新会话请先读本文件 + PROJECT_SUMMARY.md,即可直接对接代码库。
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目身份
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 名称 | Rtty(rtty-server) |
|
||||
| 定位 | 仿 [dinotty](https://github.com/xichan96/dinotty) 的分布式虚拟终端系统,**只保留「终端同步」核心能力** |
|
||||
| 技术栈 | 后端 Rust(Axum + 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` | 连接面板 + 快照列表渲染(颜色/光标块/光标可见滚动)+ 输入框 + Keybar(ESC/TAB/CTRL+C/CTRL+L/方向键/ENTER) |
|
||||
|
||||
## 4. 数据流
|
||||
|
||||
### 4.1 输出路径(PTY → 客户端)
|
||||
|
||||
```
|
||||
Shell 输出
|
||||
→ PtySession.reader(spawn_blocking 独占)
|
||||
→ TerminalEngine.feed()(引擎锁内)
|
||||
→ 引擎锁内 broadcast:
|
||||
Raw(chunk) → desktop 客户端收二进制
|
||||
Snapshot(快照) → 仅当 mobile_clients>0 时生成 → mobile 客户端收 JSON
|
||||
→ desktop 订阅时/每次 resize:redraw_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 分钟)+ 读 EOF(Unix)→ 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 运行,手机可看可发消息;前提:服务端非沙盒)
|
||||
|
||||
### 已知限制
|
||||
- 移动端是"语义快照视图":文本+颜色,无完整终端渲染器;全屏 TUI(vim/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` 而非手拼) |
|
||||
Reference in New Issue
Block a user