[Bug] iOS 错过模型可见性变更后继续展示 Mac 已隐藏模型
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 问题描述
在 Cindy `v0.1.22` 中,Mac 端「设置 → 模型供应商」修改模型显示状态后,iOS 新建会话的模型列表可能没有同步最新可见性。用户反馈的复现现象是:Mac 端已将 CindyAI 的模型全部隐藏,tapsvx 仅显示 10 个模型,但 iOS 新建会话仍出现 Mac 端未显示的模型。
## 环境
- Cindy 版本:`v0.1.22`
- 控制端:iOS 新建会话
- 被控端:Mac,已通过 device-link 连接
- Mac 模型供应商设置:CindyAI 全部隐藏;tapsvx 显示 10 个模型
## 复现步骤
1. 在 Mac 端进入「设置 → 模型供应商」。
2. 将 CindyAI 的模型全部设置为隐藏,并确认 tapsvx 仅显示 10 个模型。
3. 让 iOS 在后台、弱网或断线期间错过供应商变更推送(`maker:provider:changed`),再回到前台或重新进入新建会话。
4. 打开 iOS 新建会话的模型选择器,观察模型列表。
## 实际行为
iOS 仍展示 Mac 端已经隐藏的模型,并且这些模型可能可以被继续选中创建会话。列表与 Mac 端的当前可见性设置不一致。
## 期望行为
iOS 新建会话应始终基于被控 Mac 的最新模型可见性快照;即使错过实时推送,在重新连接、回到前台或打开模型选择器时也应通过主动刷新或 revision 校验收敛,隐藏模型不应继续出现在可选列表中。
## 源码分析
当前源码的相关链路如下:
- `apps/mobile/src/device-link/deviceProvidersCache.ts:55-60` 命中 `deviceId` 缓存后直接返回,没有 TTL、revision 校验或强制刷新入口。
- `apps/mobile/src/device-link/useDeviceProviders.ts:60-68` 命中缓存后直接结束 effect,不会重新调用 `maker:listProviders`。
- `apps/mobile/src/device-link/DeviceLinkContext.tsx:663-678` 只有收到 `maker:provider:changed` 推送时才驱逐缓存并重拉供应商目录。
- `DeviceLinkContext` 的前台/重连 rehydrate 主要补齐会话、能力等快照,没有对供应商目录做同等的主动刷新;因此 iOS 在推送断档期间保留旧缓存时,重新进入选择器仍会读取旧的 `modelVisibilityOverrides`。
- `apps/mobile/src/session/providerModelSections.ts:6-10,116-133` 会按 `modelVisibilityOverrides` 过滤列表,但该快照一旦过期,过滤逻辑会稳定地产生错误结果。
这说明问题不在 Mac 端隐藏操作本身,而在 iOS 对供应商目录变更通知的缓存一致性依赖过强。
## 截图
Mac 端模型供应商设置:

iOS 新建会话模型列表:

## 与现有 Issue 的边界
自定义供应商把 `transcribe`、`image`、`embedding`、`elevenlabs`、`voyage` 等非 chat 模型导入模型列表的问题已由 [#101](https://github.com/makecindy/cindy/issues/101) 跟踪。本 Issue 只跟踪「Mac 已隐藏模型在 iOS 因缓存未刷新而重新出现」的跨端可见性一致性问题,不重复 #101 的能力护栏议题。
## 验收标准
- Mac 端修改模型可见性后,iOS 在线且收到推送时,模型选择器及时移除已隐藏模型。
- iOS 在后台、断线或弱网期间错过推送后,重新连接、回到前台或打开新建会话时能主动刷新或校验 revision,不再展示过期模型。
- 供应商目录刷新仍保持按 `deviceId` 隔离、inflight 去重和旧版本被控端兼容,不引入跨设备或跨账号缓存串用。
- 为上述推送命中、推送丢失后恢复、缓存过期/刷新及模型过滤补充自动化测试。
Contributor guide
Research direction
Start with apps/mobile/src/device-link/deviceProvidersCache.ts and useDeviceProviders.ts, then trace DeviceLinkContext.tsx rehydration and provider-change handling. Check providerModelSections.ts to understand how stale modelVisibilityOverrides affect filtering. Done means missed provider-change events converge after reconnect, foregrounding, or opening the selector, with automated coverage for refresh, deduplication, device isolation, and filtering.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, typescript
- Domain
- mobile, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100