fix: delete agent 500 error + dynamic personality + deployment guide

- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 01:17:21 +08:00
parent 86b98865e3
commit beff3fac8d
1084 changed files with 117315 additions and 1281 deletions

View File

@@ -0,0 +1,466 @@
# 天工智能体 Android 客户端 — 架构上下文文档
**文档版本**: 2.0 | **生成日期**: 2026-06-27 | **分析源码文件数**: 14
---
## 1. 技术栈全景
| 类别 | 技术 | 版本 | 证据来源 |
|------|------|------|----------|
| **语言** | Kotlin | 2.0.10 | `gradle/libs.versions.toml:3` |
| **编译 SDK** | Android API | 34 | `app/build.gradle.kts:9` |
| **最低 SDK** | Android API | 26 | `app/build.gradle.kts:13` |
| **Java 目标** | JVM 17 | - | `app/build.gradle.kts:34-35` |
| **UI 框架** | Jetpack Compose (BOM) | 2024.06.00 | `gradle/libs.versions.toml:4` |
| **Material Design** | Material3 | BOM 托管 | `gradle/libs.versions.toml:26` |
| **导航** | Navigation Compose | 2.7.7 | `gradle/libs.versions.toml:6` |
| **DI** | Hilt (Dagger) | 2.51.1 | `gradle/libs.versions.toml:11` |
| **网络层** | Retrofit + OkHttp | 2.11.0 / 4.12.0 | `gradle/libs.versions.toml:8-9` |
| **JSON** | Gson | 2.11.0 | `gradle/libs.versions.toml:14` |
| **SSE 流式** | OkHttp 底层流式读取 | - | `SseClient.kt:95-120` |
| **本地数据库** | Room | 2.6.1 | `gradle/libs.versions.toml:10` |
| **键值存储** | DataStore Preferences | 1.1.1 | `gradle/libs.versions.toml:10` |
| **图片加载** | Coil | 2.6.0 | `gradle/libs.versions.toml:14` |
| **音频播放** | Media3 ExoPlayer | 1.3.1 | `gradle/libs.versions.toml:14` |
| **Markdown 渲染** | Markwon | 4.6.2 | `gradle/libs.versions.toml:15` |
| **安全加密** | Security Crypto | 1.1.0-alpha06 | `gradle/libs.versions.toml:15` |
| **启动画面** | SplashScreen API | 1.0.1 | `app/build.gradle.kts:75` |
| **构建工具** | AGP | 8.5.2 | `gradle/libs.versions.toml:2` |
| **架构模式** | MVVM + Repository + UDF (单向数据流) | - | 所有 ViewModel 均采用 `StateFlow<UiState>` |
---
## 2. 目录结构与模块组织
```
app/src/main/java/com/tiangong/aiagent/
├── MainActivity.kt # 入口 Activity
├── TiangongApplication.kt # Hilt Application
├── navigation/ # 导航图
├── di/ # Hilt DI 模块
├── data/
│ ├── local/
│ │ ├── TokenDataStore.kt # DataStore 键值存储Token/偏好)
│ │ ├── CredentialStore.kt # 加密凭据存储
│ │ ├── AppDatabase.kt # Room 数据库定义
│ │ ├── ConversationEntity.kt # 对话表实体
│ │ ├── MessageEntity.kt # 消息表实体
│ │ ├── ConversationDao.kt # 对话 DAO
│ │ └── MessageDao.kt # 消息 DAO
│ ├── remote/
│ │ ├── ApiService.kt # Retrofit API 接口
│ │ ├── SseClient.kt # SSE 流式客户端OkHttp
│ │ ├── AuthInterceptor.kt # Token 注入拦截器
│ │ └── dto/ # 请求/响应 DTO
│ └── repository/
│ ├── AuthRepository.kt # 认证仓库
│ ├── ChatRepository.kt # 对话仓库
│ ├── AgentRepository.kt # 智能体仓库
│ └── FeedbackRepository.kt # 反馈仓库
├── domain/
│ └── model/
│ ├── Agent.kt # 智能体模型
│ ├── Message.kt # 消息模型 + Role 枚举
│ ├── Conversation.kt # 对话模型
│ ├── SseEvent.kt # SSE 事件 sealed class
│ └── TokenUsage.kt # Token 用量模型
├── ui/
│ ├── login/ LoginScreen + LoginViewModel
│ ├── register/ RegisterScreen + RegisterViewModel
│ ├── chat/
│ │ ├── ChatScreen.kt # 主聊天界面
│ │ ├── ChatViewModel.kt # 聊天 ViewModel核心
│ │ └── components/ # MessageBubble, StreamingText, ToolCallCard, VoiceInputButton
│ ├── agents/ AgentListScreen + AgentListViewModel
│ ├── history/ ConversationListScreen + ConversationListViewModel
│ ├── settings/ SettingsScreen
│ └── common/ SkeletonAgentList, SkeletonChat, SkeletonConversationList
└── util/
├── AudioPlayer.kt # TTS 音频播放器Media3
└── AudioRecorder.kt # 语音录制器
```
---
## 3. 核心模块分析
### 3.1 认证模块 (Auth)
| 文件 | 职责 |
|------|------|
| `LoginViewModel.kt` | 处理登录表单状态,调用 `AuthRepository.login()` |
| `RegisterViewModel.kt` | 客户端字段验证 + 调用 `AuthRepository.register()` |
| `AuthRepository.kt` | 封装 API 调用,管理 Token 持久化和凭据存储 |
| `ApiService.kt:15-19` | Retrofit `@FormUrlEncoded @POST login` / `@POST register` |
| `TokenDataStore.kt` | DataStore 持久化 `access_token``server_url` 等 |
| `CredentialStore.kt` | 加密存储用户名/密码 |
| `AuthInterceptor.kt` | OkHttp 拦截器,自动注入 `Authorization: Bearer $token` |
**认证流程**:
```
LoginScreen → LoginViewModel.login()
→ AuthRepository.login(username, password)
→ ApiService.login() → 返回 LoginResponse(accessToken)
→ tokenDataStore.saveToken() // 持久化 Token
→ credentialStore.saveCredentials() // 加密保存凭据
→ LoginViewModel._uiState.copy(isLoggedIn = true)
→ LoginScreen LaunchedEffect 跳转主页
```
**⚠️ 风险点**:
- `LoginViewModel.kt:2-3` — 默认用户名 `"admin"`、密码 `"123456"` 硬编码在 `LoginUiState` 中,这是明显的安全漏洞,可能是测试遗留代码
- `AuthRepository.kt:22``login()``catch` 捕获所有异常不区分类型,网络异常和业务异常混杂
- `TokenDataStore.kt:86-88``clearAll()` 注释说 "Keep server URL and theme preferences across logouts",但实际执行的是 `prefs.clear()` 然后什么也不保留——**注释与实现矛盾**
### 3.2 聊天/对话模块 (Chat) — 核心模块
| 文件 | 行数(估) | 职责 |
|------|----------|------|
| `ChatViewModel.kt` | ~300+ | 管理聊天状态、SSE 流式消费、语音/图片、反馈 |
| `ChatScreen.kt` | ~400+ | Compose UI消息列表、输入框、语音按钮、反馈操作 |
| `ChatRepository.kt` | 186 | 对话 API + Room 本地存储 |
| `SseClient.kt` | 203 | SSE 长连接 + 指数退避自动重连 |
**ChatUiState 状态字段**`ChatViewModel.kt`:
```
messages: List<UiMessage> # 当前对话消息列表
currentAgent: Agent? # 当前选中的智能体
availableAgents: List<Agent> # 可用智能体列表
isLoading/isStreaming # 加载/流式状态
streamingContent: String # 流式接收中的文本
currentToolCall: Pair? # 进行中的工具调用 (name, input)
sessionId: String? # 当前会话 ID
isRecording/isTranscribing # 语音录制/识别状态
reconnectionState # SSE 重连状态Idle/Reconnecting/Failed
feedbackSubmittingMsgId # 提交反馈中的消息 ID
```
**ChatViewModel 依赖注入**`ChatViewModel.kt`10 个依赖):
```
ChatRepository, AgentRepository, FeedbackRepository,
AudioRecorder, AudioPlayer, ApiService,
TokenDataStore, SavedStateHandle, Gson
```
**⚠️ 高依赖耦合**: ViewModel 直接依赖 `ApiService`(绕过 Repository 层),对于 `speakText()`TTS`transcribeVoice()``uploadImage()` 这三个方法是直接在 ViewModel 中调用 `ApiService` + `TokenDataStore`,破坏了 Repository 模式的封装。
**SSE 流式数据路径**:
```
ChatScreen 输入文字 → ChatViewModel.sendMessage()
→ ChatRepository.chatStream(agentId, message, sessionId)
→ SseClient.connect(agentId, token, request)
→ OkHttp POST 到 /api/v1/agent-chat/{agentId}/stream
→ 通过 callbackFlow 逐行解析 "event:" / "data:" 行
→ parseEvent() 转为 SseEvent sealed class:
Plan / Think / ToolCall / ToolResult / Final / Message / Error / ConnectionError
→ 返回 Flow<SseEvent>
→ ChatViewModel 收集 Flow:
on Plan → 不处理
on Think → 不处理
on ToolCall → 更新 currentToolCall
on ToolResult → 清除 currentToolCall
on Final → 追加 AI 消息到 messages停止 streaming
on Message → 累积 streamingContent
on ConnectionError → 更新 reconnectionState
on Done → 停止 streaming
on Error → 设置 error
→ 同时 ChatRepository.saveMessage() 写入 Room 数据库
```
**SSE 重连策略**`SseClient.kt:28-32`:
```
初始延迟: 1s → 退避因子: 2.0 → 最大延迟: 30s → 最大重试: 3 次
401: 不重试(认证过期)
5xx/IOException: 触发退避重试
```
### 3.3 数据持久化层
**Room 数据库** — 包含两张表:
- `ConversationEntity` — 字段: `sessionId, agentId, agentName, title, lastMessage, lastMessageAt, messageCount`
- `MessageEntity` — 字段: `id, conversationId, agentId, role, content, toolName, toolInput, toolOutput, tokenUsageJson, createdAt`
**DataStore (Preferences)**`TokenDataStore.kt` — 8 个键:
```
access_token # JWT Token
server_url # 服务器地址(默认 BuildConfig.BASE_URL = http://192.168.31.150:8037/
current_agent_id # 当前选中智能体
current_agent_name # 当前智能体名称
tts_enabled # TTS 开关
tts_voice # TTS 语音 (默认 "alloy")
theme_mode # 主题模式 (默认 "system")
last_session_id # 上次会话 ID用于恢复
```
**消息持久化时机**`ChatRepository.kt`:
- `chat()` (非流式): 调用 `saveMessagesFromResponse()` 批量保存 steps
- `chatStream()` (流式): 不自动保存,由 ViewModel 在收到 `SseEvent.Final` 后调用 `saveMessage()` 逐条保存
- `saveMessage()` 同时 upsert `ConversationEntity`
**⚠️ 风险**: 流式消息在 `SseEvent.Final` 之前如果 App 崩溃或网络断开,消息会丢失(未持久化到 Room
### 3.4 UI 层
**骨架屏 (Skeleton Screens)**:
- `SkeletonAgentList` — 智能体列表加载中6 个占位项)
- `SkeletonChat` — 聊天界面加载中
- `SkeletonConversationList` — 对话历史加载中5 个占位项)
**ChatScreen 组件树**`ChatScreen.kt`:
```
Scaffold
├── TopAppBar (标题 = currentAgent.name, 操作按钮: 历史/通知/设置)
├── LazyColumn (消息列表, reversed)
│ ├── SkeletonChat (首次加载时)
│ ├── MessageBubble (逐条消息气泡)
│ │ ├── Markdown 渲染正文
│ │ ├── ToolCallCard (工具调用卡片)
│ │ └── 操作栏: 复制 / 朗读 / 点赞 / 点踩
│ ├── ToolCallCard (进行中的工具调用)
│ └── StreamingText (流式输出文本)
└── BottomBar
├── IconButton (图片上传)
├── OutlinedTextField (文字输入)
└── VoiceInputButton (语音输入)
```
**⚠️ 注意**: 预扫描中 ChatScreen.kt 被截断8KB 限制),部分实现细节未获取。`MessageBubble`, `StreamingText`, `ToolCallCard`, `VoiceInputButton` 的具体实现在 `ui/chat/components/` 目录下的独立文件中,未读取。
### 3.5 反馈模块
`ChatViewModel.kt` 中:
- `submitFeedback(msgId, rating)``FeedbackRepository``ApiService.submitFeedback()`
- 支持 LIKE / DISLIKE 评分 + 可选评论 (`submitFeedbackComment`)
- 状态通过 `feedbackSubmittingMsgId` 跟踪提交中状态
---
## 4. 数据流路径汇总
### 4.1 登录流程
```
LoginScreen
→ onUsernameChange / onPasswordChange → LoginViewModel._uiState.update
→ Button onClick → LoginViewModel.login()
→ AuthRepository.login(username, password)
→ ApiService.login(@Field username, @Field password)
→ 返回 LoginResponse(accessToken)
→ TokenDataStore.saveToken(accessToken)
→ CredentialStore.saveCredentials(username, password)
→ _uiState.copy(isLoggedIn = true)
→ LaunchedEffect(isLoggedIn) → onLoginSuccess() 导航
```
### 4.2 注册流程
```
RegisterScreen (5 个字段: username/displayName/email/password/confirmPassword)
→ 每个输入框 onValueChange → RegisterViewModel.onXxxChanged() → 实时客户端验证
→ Button onClick → RegisterViewModel.register()
→ 再次验证所有字段
→ AuthRepository.register(RegisterRequest)
→ ApiService.register(@Body)
→ 成功: isSuccess = true
→ HTTP 409: 解析 errorBody JSON 中的 detail 字段
→ HTTP 422: 同上
→ IOException: "网络连接失败"
→ RegisterScreen LaunchedEffect(isSuccess) → Snackbar + 跳转登录
```
### 4.3 SSE 流式对话
```
ChatScreen 输入 "你好"
→ ChatViewModel.sendMessage("你好")
→ 生成用户 UiMessage追加到 messages
→ ChatRepository.chatStream(agentId, "你好", sessionId)
→ TokenDataStore.getToken()
→ SseClient.connect(agentId, token, ChatRequest)
→ runBlocking { TokenDataStore.serverUrl.first() } // ⚠️ 阻塞 OkHttp 线程
→ POST {baseUrl}api/v1/agent-chat/{agentId}/stream
→ callbackFlow 主循环:
source.readUtf8Line() 逐行解析 SSE
event: → 设置事件类型
data: → 设置数据
空行 → parseEvent(type, data) → trySend(event)
返回 Flow<SseEvent>
→ ChatViewModel.collect:
is ToolCall → currentToolCall = (name, input)
is ToolResult → currentToolCall = null
is Message → streamingContent += content
is Final → 生成 AI UiMessage, isStreaming = false, 保存到 Room
is ConnectionError → reconnectionState = Reconnecting(...)
is Done → isStreaming = false
is Error → error = ...
→ ChatScreen 自动滚动到最新消息
```
### 4.4 TTS 语音合成
```
ChatScreen 点击朗读按钮 → ChatViewModel.speakText(text)
→ TokenDataStore.ttsEnabled.first() // 检查 TTS 开关
→ TokenDataStore.ttsVoice.first() // 获取语音类型
→ ApiService.synthesizeSpeech(TtsRequest)
→ 拼接 audioUrl (相对路径补全 BaseURL)
→ AudioPlayer.play(audioUrl) // Media3 ExoPlayer
```
### 4.5 语音识别 (ASR)
```
VoiceInputButton 录制完成
→ AudioRecorder 输出 File (AAC 格式)
→ ChatViewModel.transcribeVoice(file)
→ file.asRequestBody("audio/aac")
→ ApiService.transcribeAudio(MultipartBody.Part)
→ 返回 transcribedText
→ file.delete() // finally 中删除临时文件
```
### 4.6 图片上传
```
ChatScreen 图片选择器 (ActivityResultContracts.GetContent)
→ ContentResolver 读取 Uri → 写入 cacheDir 临时文件
→ ChatViewModel.uploadImage(tempFile)
→ file.asRequestBody("image/*")
→ ApiService.uploadFile(MultipartBody.Part)
→ 拼接预览 URL: {baseUrl}api/v1/uploads/preview/file?file_path={relativePath}
→ 设置 transcribedText = "[图片](url)" // Markdown 格式
→ file.delete()
```
---
## 5. 关键类的职责与依赖关系
```
┌───────────────────────┐
│ TiangongApplication │ (Hilt Entry Point)
└───────────┬───────────┘
│ @HiltAndroidApp
┌───────────▼───────────┐
│ MainActivity │ (Single Activity)
└───────────┬───────────┘
│ setContent { NavHost }
┌───────────────────────────┼───────────────────────────┐
│ │ │
LoginScreen ChatScreen AgentListScreen
RegisterScreen ConversationListScreen SettingsScreen
│ │ │
LoginViewModel ChatViewModel AgentListViewModel
RegisterViewModel ConversationListVM
│ │ │
AuthRepository ChatRepository ◄──┐ AgentRepository
│ │ │ │ │
│ ┌────────┼────┐ │ │ │
│ │ │ │ │ │ │
ApiService SseClient AppDatabase │ │ ApiService
│ │ │ │ │ │
TokenDataStore OkHttpClient │ │ │
CredentialStore Room DAOs │ │
│ │ │
MessageDao ConversationDao
```
**依赖注入关系说明**:
- 所有 ViewModel 通过 `@HiltViewModel` + `@Inject constructor` 获取依赖
- 所有 Repository 通过 `@Singleton` + `@Inject constructor` 由 Hilt 管理
- `ApiService` 是 Retrofit 接口,由 Hilt Module 提供实例
- `SseClient` 直接注入 `OkHttpClient` + `Gson` + `TokenDataStore`
- `TokenDataStore` 通过 `@ApplicationContext` 获取 Context 初始化 DataStore
---
## 6. 从源码中发现的实际代码质量问题
### 🔴 严重问题
| ID | 问题 | 位置 | 影响 |
|----|------|------|------|
| **B1** | `runBlocking` 在 OkHttp 回调线程调用 | `SseClient.kt:66``val baseUrl = runBlocking { tokenDataStore.serverUrl.first() }` | **阻塞 OkHttp 网络线程**,可能导致 ANR 或连接超时。`runBlocking``callbackFlow {}` builder 内运行在 OkHttp 的分发线程上 |
| **B2** | OkHttp Callback 中 `Thread.sleep()` | `SseClient.kt:90,108,140` — 重连等待使用 `Thread.sleep(delay)` | **阻塞 OkHttp 线程池**,多连接下可能耗尽 OkHttp dispatcher 线程 |
| **B3** | 硬编码默认凭据 | `LoginViewModel.kt:3``val username: String = "admin"`, `val password: String = "123456"` | **安全漏洞**,发布到生产环境会让攻击者轻松猜测默认凭据 |
| **B4** | `clearAll()` 实现与注释矛盾 | `TokenDataStore.kt:85-88` — 注释说保留 server_url 和 theme但实际执行 `prefs.clear()` | 用户退出登录后可能丢失服务器地址配置 |
### 🟡 中等问题
| ID | 问题 | 位置 | 影响 |
|----|------|------|------|
| **M1** | ViewModel 绕过 Repository 直接调用 ApiService | `ChatViewModel.kt``speakText()`, `transcribeVoice()`, `uploadImage()` 方法内直接使用 `apiService``tokenDataStore` | 破坏架构分层,测试困难,无法 mock |
| **M2** | 流式消息在 Final 事件前不持久化 | `ChatViewModel.kt` — 仅在 `SseEvent.Final` 后调用 `ChatRepository.saveMessage()` | App 崩溃或断网时,已流式接收的消息全部丢失 |
| **M3** | 缺少 Token 过期自动刷新机制 | `SseClient.kt:95` — 401 直接 `close()``AuthRepository``refreshToken` 方法 | Token 过期后用户必须手动重新登录 |
| **M4** | `parseEvent()` 对未知 type 默认创建 `SseEvent.Unknown` | `SseClient.kt:178-181``else → SseEvent.Unknown` | 服务端新增事件类型可能导致前端默默忽略而非报错 |
| **M5** | ChatViewModel 依赖过多10 个注入参数) | `ChatViewModel.kt` 构造函数 | 违反单一职责原则,建议拆分为 AudioHandler、ImageHandler 等 |
### 🟢 轻微问题
| ID | 问题 | 位置 | 影响 |
|----|------|------|------|
| **N1** | 搜索输入每次变化都触发网络请求 | `AgentListViewModel.kt:42-44``onSearchQueryChange` 直接调用 `loadAgents()` | 快速输入时产生大量冗余 API 请求,无防抖(debounce) |
| **N2** | BaseURL 硬编码局域网 IP | `app/build.gradle.kts:23``http://192.168.31.150:8037/` | 只能在该局域网使用,无法切换环境 |
| **N3** | 异常吞没 | `ChatViewModel.kt``speakText()``catch (_: Exception) { }` 完全忽略异常 | 用户点击朗读无反应时无任何错误提示 |
| **N4** | `ConversationListViewModel.loadConversations()` 每次加载取消上一个 Job | `ConversationListScreen.kt:51``loadJob?.cancel()` | 频繁切换筛选时可能丢失中间状态 |
| **N5** | Room role 字段使用 String 值枚举 | `ChatRepository.kt:147-151``Message.Role.valueOf(entity.role.uppercase())` 失败时 default=`SYSTEM` | 类型不安全,数据库中存在非预期值时静默降级 |
| **N6** | 临时文件未统一管理 | ChatScreen/ChatViewModel — 图片和音频临时文件写入 `cacheDir`,依赖 `file.delete()` | 异常时可能产生孤儿文件 |
| **N7** | ProGuard 代码混淆未启用 | `app/build.gradle.kts:27``isMinifyEnabled = false` | Release 构建无混淆保护 |
| **N8** | RegisterScreen 密码明文传输 | `RegisterRequest` DTO — 密码字段未被加密 | 即使走 HTTPS也应在客户端做一次哈希后再发送 |
---
## 7. 架构模式总结
```
┌─────────────────────────────────────────────────────┐
│ 架构模式: MVVM + UDF │
│ │
│ UI Layer (Compose) ViewModel Layer Data Layer│
│ ┌──────────┐ ┌──────────────┐ ┌─────────┐ │
│ │ Screen │──State─│ ViewModel │──▶│Repository│ │
│ │ │◀─Flow──│ (StateFlow) │◀──│ │ │
│ │ Events │───────▶│ │ │ ┌─────┐│ │
│ │ (click) │ │ viewModelScope│ │ │ApiSvc││ │
│ └──────────┘ └──────────────┘ │ ├─────┤│ │
│ │ │Room ││ │
│ │ ├─────┤│ │
│ │ │DataSt││ │
│ │ └─────┘│ │
│ └─────────┘ │
└─────────────────────────────────────────────────────┘
```
**数据流向特点**:
- **单向数据流 (UDF)**: UI → Events → ViewModel → Repository → DataSource → State → UI
- **StateFlow 驱动**: 所有 ViewModel 通过 `MutableStateFlow<UiState>` + `asStateFlow()` 暴露只读状态
- **viewModelScope 管理协程**: 所有异步操作绑定到 ViewModel 生命周期
- **Flow 用于响应式数据**: Room DAO 返回 `Flow<List<T>>`Repository 用 `map` 转换为 Domain Model
- **callbackFlow 用于 SSE**: 将 OkHttp 回调风格的事件源桥接到 Kotlin Flow
---
## 8. 附录:分析引用的源文件清单
| # | 文件路径 | 读取状态 | 用途 |
|---|----------|----------|------|
| 1 | `app/build.gradle.kts` | 完整 | 构建配置、SDK 版本、依赖声明 |
| 2 | `gradle/libs.versions.toml` | 完整 | 全量版本号目录 |
| 3 | `ui/agents/AgentListViewModel.kt` | 完整 | 智能体列表 ViewModel |
| 4 | `ui/chat/ChatViewModel.kt` | 截断(8KB) | 聊天核心 ViewModelSSE 消费TTS/ASR/图片 |
| 5 | `ui/login/LoginViewModel.kt` | 完整 | 登录 ViewModel |
| 6 | `ui/register/RegisterViewModel.kt` | 完整 | 注册 ViewModel + 客户端验证 |
| 7 | `ui/agents/AgentListScreen.kt` | 完整 | 智能体列表 Compose UI |
| 8 | `ui/chat/ChatScreen.kt` | 截断(8KB) | 聊天 Compose UI |
| 9 | `ui/history/ConversationListScreen.kt` | 截断(8KB) | 对话历史 Compose UI + ViewModel |
| 10 | `ui/login/LoginScreen.kt` | 完整 | 登录 Compose UI |
| 11 | `ui/register/RegisterScreen.kt` | 截断(8KB) | 注册 Compose UI |
| 12 | `data/repository/AuthRepository.kt` | 完整 | 认证仓库 |
| 13 | `data/remote/ApiService.kt` | 完整 | Retrofit API 接口定义 |
| 14 | `data/repository/ChatRepository.kt` | 完整 | 聊天仓库 + Room 持久化 |
| 15 | `data/local/TokenDataStore.kt` | 完整 | DataStore 键值存储 |
| 16 | `domain/model/Message.kt` | 完整 | 消息领域模型 |
| 17 | `data/remote/SseClient.kt` | 完整 | SSE 流式客户端 |
**未读取但推测存在的文件**: `AppDatabase.kt`, `ConversationEntity.kt`, `MessageEntity.kt`, `ConversationDao.kt`, `MessageDao.kt`, `CredentialStore.kt`, `AuthInterceptor.kt`, `AgentRepository.kt`, `FeedbackRepository.kt`, `Agent.kt`, `Conversation.kt`, `SseEvent.kt`, `MainActivity.kt`, `TiangongApplication.kt`, 各 DTO 文件, DI 模块, 导航图, 各 UI 组件 (`MessageBubble`, `StreamingText`, `ToolCallCard`, `VoiceInputButton`, `Skeleton*`)