Files
aiagent/android/architecture_context.md
renjianbo beff3fac8d 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>
2026-06-29 01:17:21 +08:00

467 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 天工智能体 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*`)