Files
Cyrene-For-Android/devdocs/02-前后端协同事项.md
T
Aska 5f24f75dba feat: 初始化 Android 客户端(M0 骨架 + M1 登录 + M2 基础模块)
M0:Gradle 9.7.1 / AGP 9.2.1 / Kotlin 2.4 / Miuix 0.9.3,minSdk 33,compileSdk 37。
M1:登录页(服务器地址可配、服务端/本地错误分流、EncryptedSharedPreferences、自动登录与静默重登、401 登出)。
M2 基础模块:消息内容解析器(<p>/<action>/<narration> + 流式增量)、WebSocket 客户端、REST 数据层与分页去重。
45 个单元测试全绿,真机验证通过(登录页 + 启动图标)。
devdocs/ 收录设计文档、前后端协同事项与细化方案。
2026-09-08 20:35:49 +08:00

208 lines
12 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.
# 02 — 前后端协同事项(Cyrene-For-Android ⇄ Cyrene v2)
> **用途**:Android 端「要做/想做」,但**后端当前不支持、或契约未明确**的事项统一登记在这里。主人(AskaEth)按此去后端做适配与开发。
> **状态机**:`待确认` → `已确认` → `已排期` → `已完成` / `不采纳`
> **依据**:`Cyrene_v2/Docs/api-reference.md`、`Docs/api-client.md`、`DevDocs/design/12,14,15,16,21`(调研中发现多处文档与实现不一致,见 §E2)
> **最后更新**:2026-09-08
---
## 登记规范(开发过程中持续维护)
**触发条件**——开发 / 联调中遇到以下任一情况,**当场**追加到本文并立即向主人反馈,不攒到最后:
1. 调用后端**直接失败**(501 / 404 / 500 / 字段缺失导致功能做不了);
2. **契约或设计文档写了、但实现没做**(例如 `mode` 不落库、`voice_input` 不落库)——即「理论上应该支持但没有」;
3. 后端行为与文档不符,导致客户端必须绕行。
**每条要写清**:
- **现象**:可复现的请求 / 响应或代码位置(`文件:行`);
- **客户端为什么要**:不补会怎样;
- **后端建议方案**:客户端期望的接口 / 字段 / 语义;
- **当前绕行**:客户端现在怎么扛过去(没有就写「无,功能阻塞」);
- **优先级**:阻塞 P0 / 影响体验 / P1 / P2 / 文档治理。
**流程**:`待确认` → 主人确认 → `已排期` → 后端完成 → `已完成`(注明后端版本 / commit);不做的标 `不采纳` 并写明理由。
**反馈节奏**:每轮开发结束时,在对话里列「本轮新增协同事项」(编号 + 一句话),主人直接照着修后端。
---
## A. 阻塞 P0 的(不解决就得改客户端设计)
### A1. 结构化流帧不带 `sessionId` → 多会话流式无法归属
- **现象**:`message.start/delta/end`、`thinking.*`、`tool_call.*`、`tool_execution.update` 的 payload 只有 `{index}` / `{id,index,delta}`,**没有 `sessionId`**(`model.StreamEvent` 无会话上下文)。
- **客户端为什么要**:WS 是全局一条连接,多会话同时流式时无法判断 delta 属于哪个会话,切会话会串台。
- **后端建议**:这些帧补 `sessionId`,或至少补 `turnId`(客户端用 turnId 反查会话)。
- **当前绕行**:客户端硬约束「同一时刻只有一个会话在流式」(已拍板接受)。
- **优先级**:中高(有绕行,不阻塞 P0)
### A2. `interrupt` 清空 pending → 「打断」模式连发丢消息
- **现象**:`interrupt {sessionId}` 取消在跑 turn **并清空 pending**,下行 `session_interrupted{dropped}`。
- **客户端为什么要**:S1.4 定了「追加队列 / 打断」两种模式;用户选「打断」时连发多条会被静默丢弃。
- **后端建议**:`interrupt` 增加 `scope`(`current` = 只取消当前 turn / `all` = 连 pending 一起清),默认维持现状。
- **当前绕行**:打断模式下 UI 限制一次只能发一条 + 明确提示。
- **优先级**:中
### A3. 消息 `mode` 不落库 → 渲染形态丢失
- **现象**:design/16 §5.1 写「消息体带 `mode`」,但 Web 路径(`session_persist.go`)与 IM 路径(`channelbridge/persist.go`)**都不写 `Mode`**,字段 `omitempty` → 实际恒缺。
- **客户端为什么要**:9 值渲染 mode(chat/code/home/work/docs/voice/vision/agents/settings)决定气泡/卡片形态,且「只有 chat 才解析 `<p>`」这条规则依赖它。
- **后端建议**:持久化时写入 `mode`(design/16 已约定,属实现缺失)。
- **当前绕行**:客户端一律回落 `chat`。
- **优先级**:中
### A4. 会话 `title` 恒空 → 列表没名字
- **现象**:`historySessionSummary.title` 有字段,但 `store.go` 恒写 `""`,无生成规则/接口。
- **后端建议**:后端生成(截首条用户消息),或提供 `PATCH /sessions/{id}` 让客户端改名。
- **当前绕行**:客户端本地生成「会话 N」+ 序号持久化。
- **优先级**:低
### A5. WS 断线无补发 / 无 resume cursor
- **现象**:无 `since` / `lastEventId`;`seq` 只用于排序,未规定可续传;重连仅有 `history` 回放主会话最近 50 条。
- **客户端为什么要**:断线期间超过 50 条就永久丢失,只能整页重拉。
- **后端建议**:`/ws/chat?since=<seq 或 createdAt>` 补发,或提供 `GET /sessions/{id}/messages?after=<ms>` 正序增量接口。
- **当前绕行**:重连后 REST 按 `before` 补差 + 按 `id` 去重。
- **优先级**:中
---
## B. 影响体验 / 客户端需绕行
### B1. 无图片上传端点 → 大图只能 base64 塞进 `chat.image`
- **现象**:无 `/upload`;`image` 只接受 base64 data URI 或 http(s) URL;WS 上行帧限 **4096 字节**。
- **客户端为什么要**:主人已定「发图先做着」,但没有上传端点就只能把压缩后的 base64 走 REST,大图会撑大请求体(WS 完全走不通)。
- **后端建议**:`POST /api/v1/upload`(multipart)→ 返回 URL;`chat.image` 收 URL。
- **当前绕行**:客户端压缩后再 base64,限制单图尺寸。
- **优先级**:中高(主人已要求做发图)
### B2. token 24h 且无 refresh 端点
- **现象**:`tokenTTL=24h`,无 refresh/renew;`rotate-jwt` 让全部 token 立即失效。
- **客户端为什么要**:主人选了「静默重登」,但静默重登需要长期保存账号密码。
- **后端建议**:提供 refresh token,或拉长 TTL + 支持「记住登录」的长期凭据。
- **当前绕行**:存密码做静默重登(安全上不理想)。
- **优先级**:中高
### B3. `GET /sessions` 信息太少(无未读、无最后一条预览)
- **现象**:只有 `{id,type,title?,createdAt}`。
- **客户端为什么要**:会话列表做不了预览和未读红点;多端同步时无法标记已读。
- **后端建议**:补 `lastMessage{content,role,createdAt}`、`unreadCount`,或提供已读游标接口。
- **优先级**:低(P0 按序号展示即可)
### B4. `platform` / `mood` / `mode` 取值未穷举
- **现象**:文档只给示例(`web`/`telegram`/`weixin-ilink`/`qq-obv11`/`kook`;`calm`;`normal`)。
- **客户端为什么要**:来源角标和情绪标签需要**稳定映射**(显示「来自微信」而不是原始字符串)。
- **后端建议**:给出权威枚举表 + 未知值回落约定。
- **当前绕行**:客户端维护映射表 + 未知值原样显示。
- **优先级**:低
### B5. 管理员端点 5 个返回 501
- **清单**:`POST /admin/users`、`POST /admin/iot/scenarios`、`POST /admin/iot/rules`、`POST /admin/plugins/install`、`POST /admin/rag/delete`。
- **客户端为什么要**:管理员页面要决定这些入口做不做、做了怎么降级。
- **当前绕行**:客户端硬编码「已知未实现」清单,入口置灰。
- **优先级**:低
### B6. `proactive_message` 只在 `api-reference.md` 有,`api-client.md` 摘要缺失
- **后端建议**:补进 `Docs/api-client.md`。
- **优先级**:低(文档治理)
---
## C. P1 语音相关
### C1. `voice_input` 轮次不落库、不过编排器
- **现象**:`runVoiceTurn` 直调 `runner.Run`,无 `history.AppendFull`、不经 `session_orchestrator` → **无 `message_persisted`、无 busy/pending、不跨端同步**;与 `api-client.md` §3.1「随语音轮次落库」不符。
- **客户端为什么要**:语音问过的事必须进历史、跨端可见,否则在 Web 上查不到。
- **后端建议**:把语音轮次接入编排器与持久化(与文本轮次同一路径),或在文档里明确「语音不留痕」。
- **当前绕行(客户端已定)**:**不用 `voice_input` 提交对话**,改为 `REST /audio/transcribe` 转写 → **WS `chat`**(落库 + 流式 + 编排)→ `REST /audio/speech` 播放。
- **优先级**:中
### C2. 语音降级到 silent 时客户端无法感知
- **现状**:cloud 档已接阿里百炼(STT `qwen-audio-3.0-asr-flash` / TTS `cosyvoice-v3-flash`,需 `DASHSCOPE_API_KEY`);`edge-tts`/`espeak` 未实现;`local` 默认不注入;`silent` 兜底返回空音频。
- **客户端为什么要**:没有 key 时语音会「静默成功但没声音」,客户端无法提示。
- **后端建议**:降级到 silent 时给出可识别信号(响应头 `X-Voice-Provider: silent` 或专门错误码)。
- **优先级**:中
### C3. `voice_output` 是否带 `sessionId` / `deviceId` 未规定
- **客户端为什么要**:多端同时说话时无法区分归属。
- **优先级**:低
---
## D. P2 / 路线缺失(客户端想做,后端尚无能力)
### D1. 移动端推送通道(系统通知)—— 最大缺口
- **现象**:主动推送只有 WS `proactive_message`(仅 owner/admin 连接收到),**App 不在前台就收不到**。
- **客户端为什么要**:design/21 §5 的 P2 明确要「主动推送 → 系统通知」,目前没有任何移动推送通道。
- **后端建议**:接 FCM / 个推 / 自建长连接保活,并在 WS 之外提供推送投递。
- **优先级**:P2,但属**路线级缺失**,建议早登记。
### D2. 消息搜索 / 会话搜索
- **现象**:无搜索接口(`/admin/memories/search` 是记忆,不是消息)。
- **优先级**:P2
### D3. 消息编辑 / 删除 / 重发
- **现象**:无接口。
- **优先级**:P2
### D4. 未读状态 / 已读游标
- 见 B3。
- **优先级**:P2
### D5. 会话改名 / 置顶 / 排序
- 见 A4;`GET /sessions` 次级按 `created_at ASC`,改名/置顶需要新接口。
- **优先级**:P2
---
## E. 文档与契约治理
### E1. `X-API-Version` 形同虚设
- **现象**:服务端不校验该头;CORS `Allow-Headers` 只有 `Authorization, Content-Type`(未放开 `X-API-Version`)。
- **建议**:要么真正校验,要么从契约里去掉,避免客户端误以为存在版本协商。
### E2. 文档与实现不一致(调研发现)
| 事项 | 文档说法 | 实现 |
|---|---|---|
| 真实 ASR/TTS | design/21 §4「未接通」 | 已接百炼并实测 |
| `voice_input.deviceId` | design/21「待补」 | 已有 |
| REST 语音端点 | design/21「待新增」 | 已有 |
| 语音轮次落库 | api-client「随轮次落库」 | 未落库(见 C1) |
| 消息 `mode` | design/16 §5.1「消息体带 mode」 | 不落库(见 A3) |
| 来源标识注入 | design/16 §4.6.1「文本前缀」 | 独立 system 消息 + 结构化 `SenderName` |
- **建议**:以 `api-reference.md` + 代码为准,`api-client.md` 随实现同步(AGENTS.md 已有此红线)。
### E3. `thinking` 可达性确认
- **现状**:WS 实时/回放已剥离,只有 `GET /sessions/{id}/messages`(owner/admin)返回。
- **客户端影响**:历史消息的思维链需要额外请求。
- **待确认**:这是有意为之(隐私)还是可优化?
---
## 附:状态一览
| 编号 | 事项 | 状态 | 优先级 |
|---|---|---|---|
| A1 | 流帧缺 `sessionId` | 待确认 | 中高 |
| A2 | `interrupt` 清 pending | 待确认 | 中 |
| A3 | `mode` 不落库 | 待确认 | 中 |
| A4 | `title` 恒空 | 待确认 | 低 |
| A5 | 无 resume cursor | 待确认 | 中 |
| B1 | 无图片上传端点 | 待确认 | 中高 |
| B2 | 无 token refresh | 待确认 | 中高 |
| B3 | `sessions` 信息少 | 待确认 | 低 |
| B4 | 枚举未穷举 | 待确认 | 低 |
| B5 | admin 501 清单 | 待确认 | 低 |
| B6 | `proactive_message` 文档缺失 | 待确认 | 低 |
| C1 | 语音轮次不落库 | 待确认 | 中 |
| C2 | 语音降级无信号 | 待确认 | 中 |
| C3 | `voice_output` 字段 | 待确认 | 低 |
| D1 | 无移动推送通道 | 待确认 | P2(路线缺失) |
| D2 | 无消息搜索 | 待确认 | P2 |
| D3 | 无编辑/删除/重发 | 待确认 | P2 |
| D4 | 无未读状态 | 待确认 | P2 |
| D5 | 会话改名/置顶 | 待确认 | P2 |
| E1 | `X-API-Version` 虚设 | 待确认 | 低 |
| E2 | 文档与实现不一致 | 待确认 | 中 |
| E3 | `thinking` 可达性 | 待确认 | 低 |