Tree:
4ec8932aef
along
along-test
main
memory-center-impl-v1
memory-design-v0.2
${ noResults }
6 Commits (4ec8932aef623c18635d32245347311a7279ccd8)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
4ec8932aef |
车载香薰 AI 对话:唤醒问候、按键插话、自动回连、退出聊天、前后台与状态同步(均只对香薰生效,恒玄耳机流程不变)
- 会话:服务端放弃回复(requestAccepted)时停播解门控,修掉互等 90s 的卡死;会话中再按唤醒键=打断重听; 唤醒后用应用音色随机回一句「我在」;退出(0x07 03)无应答不再干等 2s;设备上线复位 AI 状态, 防 App 被杀后设备停在 AI 模式、再唤醒 0 帧上行;Android 会话期间开前台服务,防切后台被冻结断 WS - 指令:extra_info.commands 为 JSON 字符串时也能解出 quit(新车载应用下发形状) - 连接:香薰断线/冷启动自动回连(带退避、OTA 与主动断开不回连);回连带上记住的 MAC; 设备断开后界面显示最后连接的那台(DeviceHub 落盘 lastUsedVendor) - iOS 经典蓝牙判断按 MAC 首字节±1/设备名匹配,连着就不再弹配对提示 - Android 播放器:车载会话(playbackOnly)回复间垫静音,防 A2DP 流挂起吞掉「我在」 - 聊天页:设备对话中底部按钮可「改用手机」;离开正显示香薰对话的页面时结束会话并通知设备 - 车载助手百炼应用 ID 改为 mm_127ddb82ba1244a99d2feaef0c1f Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
2 weeks ago |
|
|
7ce56f693d |
client: 通话翻译音频链路修复(iOS GATT 写入、后台保活、状态竞态)
iOS 上恒玄走的是 GATT over BR/EDR 而非 BLE,之前按 BLE 模型处理导致一连串问题。 本次改动都有真机数据支撑(2026-09-21 压测)。 GATT 写入(bluetooth_manager) - 写类型改为优先 .withoutResponse。ATT 是请求-响应协议,带应答写每包等 ACK, 追不上下行 50 包/秒,表现为音频越听越滞后。 - canSendWriteWithoutResponse 只作观测、不作门槛。它是 CoreBluetooth 为 BLE 设计的提示,在 BR/EDR GATT 上不反映真实能力(实测 100% 为 false 却零丢弃); 拿它当闸门会死锁:不写就不回调,不回调就永不翻真,实测 548 包只出去 11 个。 - 控制命令无条件直发、不排队。排到积压音频后面会让 AA 09/AA 06/queryCallState 全部发不出去,曾被误判成「固件换了命令号」。 - 不再对标准电池服务 0x180F 做任何 ATT 操作:它在 Echo-one 上是空壳(值恒为 0), 而挂在它上面的读/订阅会把整条 ATT 通道堵死。 后台存活(translation) - 通话翻译期间持有 BackgroundKeepalive。两路音频全走耳机 GATT,手机侧没有 AVAudioSession 的实际 I/O,UIBackgroundModes=audio 不成立,切后台几十秒后 上行被掐断:WebSocket 还连着,但服务端收不到语音,靠默认 VAD 每 30 秒强切一句, 吐出空译文或幻觉 —— 表现为「切后台后只剩译文、原文全没」。 - BackgroundKeepalive 不再无条件把音频类别改成 .playback,避免踩掉通话翻译 正在跑的 .playAndRecord 录音链路。 状态竞态(azure_speech / device_hub) - markReady 的就绪状态记在插件上、建桥时继承。建桥与 AST 异步初始化是两条独立 路径,初始化先完成时那句 optional chaining 打在 nil 上静默丢失,之后 ready 恒为 false、上行帧全积在环形缓冲里,表现为「打开通话翻译没反应,再开一次才好」。 - DeviceHub.inCall 改以会话快照为准。事件会随 worker 在断连时被 dispose 而丢失, 重连后 ever 又不回调当前值,且后续重复赋同值不触发,永远不会自愈。 可观测性 - 下行发送器与 GATT 写入的日志从 NSLog 改为 os_log(release/profile 包里 idevicesyslog 只收得到 os_log),并经 DeviceSession.diagnostics() 暴露给 Dart, 写入文件日志后可用 devicectl 直接拉取,不依赖 usbmuxd。 - 新增 inCall 变化、闸门拦截、通话状态翻转、前后台切换四处观测点。 其它 - DeviceSession 新增 diagnostics()(默认空表);SmartcarSession 因是 implements 而非 extends,同步补了实现。 - CLAUDE.md 记录本次全部实测结论,含 BB 8E 为通话状态权威、BB 02/03 是噪声 (已知问题,本次未改判定)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
3 weeks ago |
|
|
c14580d7db |
client: 设备默认图去背;AI 页编排跟随设备;香薰 AI 回复走蓝牙;OTA 状态如实显示
设备默认图 - default.png / default_icon.png 是 assets/images/devices 里仅有的两张**不透明带底图** (892×1152 竖版海报 + 浅蓝渐变底),而设备页展示区是 1.14:1 的横宽框, BoxFit.contain 后成了中间一条窄的蓝色卡片、左右大片留白,深色模式下尤其突兀。 改成去背透明(948×828 / 176×154),主体自己铺满框、周围就是页面背景,代码零适配。 - device_images.dart 记下这条约定,免得以后又丢一张竖版带底图进来。 AI 页按设备铺编排(连着香薰却显示全量功能) - _load() 的一次性迁移会改 enabledIds 却**不更新基线 _lastAppliedKey**, 尤其 _migrateDefaultLayout 会把 defaultEnabled 里缺的全部 append 回来 —— 香薰编排铺成 3 项,重启后被追加成 11 项,基线还停在 3 项, 于是 applyDeviceDefaults 判定「用户动过编排」,从此再也不铺,且怎么重连都恢复不了。 现在:当前编排若是自动铺的就整个跳过这些迁移;已经坏掉的装机由 _rebaseDeviceDefaultsBaseline 一次性把基线抬到现状(判据收紧到迁移留下的特征形态)。 - AgentDefaults.bind 补跑一次当前状态:GetX 的 ever 拿不到绑定那一刻已有的值, 而设备是被动回连的,完全可能赶在 bind 之前就绪。 - 断开设备后回退到 DeviceHub.lastKnownVendor:设备图和「设备管理」在断连后都显示 最近那台设备,AI 页却停在上一台铺的编排里,于是出现「图是耳机、功能是香薰」。 为此给 DeviceHub 加 lastKnownVendor,并把它与 lastKnownName 抽成同一个来源 —— 各算各的就还会自相矛盾。 - test/agent_module_defaults_test.dart 6 个用例守这条规则。 车载香薰唤醒 AI 后回复从手机扬声器出来 - 下行本来就设计成走经典蓝牙 A2DP,坏在共用的音频会话上:defaultToSpeaker 把输出 钉在内置扬声器,选项里只有 allowBluetooth(HFP)而没有 allowBluetoothA2dp (playAndRecord 下 A2DP 输出默认是关的)。而这条链路采音在设备上走 BLE opus, 手机压根不录音,用 playAndRecord 本身就是错配。 - EmaiAudioSession 分档:香薰走新的 playbackOnly(.playback 类别,输出跟随系统路由), EMAI 页「按住说话」仍是 playAndRecord(去掉 defaultToSpeaker 会退回听筒,更糟), 只补上 allowBluetoothA2dp。原生 PcmSpeakerPlayer 明确不碰 AVAudioSession,改这一处即可。 固件升级页 - 后台没给产品配过固件时,页面显示的是绿色的「已是最新」——这是假话,会让人以为 手上固件是最新的而不再去查。改成三态:找不到产品配置 / 服务端暂无固件版本 / 有新版本·已是最新。 新文案 otaNoServerVersion 已补齐 41 个语言文件(i18n_audit total_missing=0)。 - 产品找到但版本为空时打一条点名 warn,不再和「拉取失败」混在一起。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
714a05b6d4 |
修 iOS EMAI 没有语音播报、连续对话上行断供;接入 MCP 令牌
iOS 上助手一直不出声,和这次改连续对话无关,是播报本来就坏的。三轮真机对照 实测:just_audio 喂的是「长度未知的直播 WAV 流」,AVPlayer 只发一次 `Range: bytes=0-1` 的嗅探请求就判定资源不可播,回 -11850 且 processingState 直接落 idle,一次都没真正起播。声明长度、支持 range、把收到的字节全缓存下来 允许重放,三种都试过,结论一样。ExoPlayer 不发这个嗅探请求,所以安卓一直是 好的——这就是「安卓有声、苹果没声」的全部原因。 iOS 改走原生 AVAudioEngine 排队播 PCM(PcmSpeakerPlayer),安卓不动。 验证方式:一次性灌 3 秒音频,7ms 喂完但 3708ms 才播完,证明是实时渲染不是丢弃。 连续对话的上行也是断的,服务端一直回 ClientAudioTimeout。两个原因: - _ensureContinuousMic 被 Started 事件和 _enterContinuous 并发调用,两边都在 `_micRunning` 置位前通过守卫,同一个 recorder 被 startStream 两遍,第二次把 第一次那条流顶掉。控制器侧改成同步置位,MicPcmStreamer 内部再加一层 in-flight 去重。 - 开麦前没配好并激活音频会话。会话停在 soloAmbient 且未激活时,startStream 会**成功返回**、引擎也起得来,但输入 tap 一个 buffer 都不回调,客户端毫无报错。 麦克风和播放器统一到 EmaiAudioSession 一份会话配置:各配各的会在连续对话下 互相覆盖。iOS 固定 voiceChat——record 的 echoCancel 会打开 voice-processing 并 把 mode 改成 voiceChat 持有着,播放这边写回 default 就是两边互相掀桌子。 androidAudioAttributes 仍是 media,别改成 voiceCommunication(华为会静音)。 连续对话交互按要求改:进页面只建会话不开麦,短按开始、再按结束、可反复, 退出即停,10 秒无对话往来自动结束。计时依据必须是对话事件不是音频包—— 麦克风没人说话也在稳定出包,拿它续命这个超时永远不会触发。 另修 _enterContinuous 在 reconnect() 一返回就判 `!= ready`:ready 要等服务端回 Started(一次网络往返),几乎每次都命中,于是刚打开的连续对话被当场关掉, 而 Started 稍后到达又会把麦开起来,留下一个界面上看不见的常开麦克风。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
1 month ago |
|
|
4c5b6f0c65 |
拾忆:修分类筛选无效、加自动补传与提示节流;试修 iOS 播报
真机反馈的三条。 ## 1) 分类筛选点了没反应(我的 bug) 筛选条改的是 categoryFilter,但 eventsForScope **只按日期过滤,从没读过它** —— 值改了没人用。列表源头补上分类条件。 顺带:onDaySelected 里那句 `categoryFilter.value = null` 也去掉了。它是旧首页 折叠态横向条的语义(临时开关,切天清掉合理);拾忆页的筛选条是常驻的, 选了「花销」再点一天就被清空,看起来同样像按钮失灵。 ## 2) 本地未同步项每点必弹,且无从同步 原来只是拦下来提示一句,没给出路,连点几下还叠一串一模一样的条。 - AssistantDirectiveService.syncPending():把所有 synced=false 的本地闹钟补传, 由拾忆页刷新(进页面/下拉/切前台)驱动。这是自愈机制——服务端没部署、断网、 或恰好那一下超时,闹钟都会先落在本地,等能连上时自动补上去,不用用户做任何事。 遇到业务错误或网络异常就 break:后面的一样会失败,别把整串请求都打一遍。 加了重入保护。 - refreshFromServer 里**先补传再拉取**。反过来的话刚补传的这次拉不到, 要等下一次刷新才显示。 - 新增 ToastThrottle:同一 key 3 秒内只弹一次(比 snackbar 默认停留略长, 保证上一条没消失就不会再来一条)。memory 的三处提示都接上。 登出时清节流状态,免得上个账号的记录影响下一个。 - 点未同步项时除了提示,**顺手触发一次补传**——用户点它说明他在意这条, 正好借这个动作试一次同步,成功了下次点就能编辑。 ## 3) EMAI 回复没声音(试修,需真机验证) 日志里 PcmStreamPlayer 反复报 -11850 Operation Stopped,每来一包音频重试一次。 链路:手机这条路是「按住说话」,MicPcmStreamer 用 record 的 echoCancel/noiseSuppress,在 iOS 上插件会把 AVAudioSession 切到 voice-processing 并自己持有;松手时 _mic.stop() 只停引擎、**不还原会话**。随后回复音频到达, AVPlayer 在那个会话上起不来。 _ensureSession 原来只在首次 configure、**从头到尾没有 setActive**。改成: 配置仍只做一次(配置本身是静态的),但**每轮 begin 都显式 setActive(true)**, 把会话按本方配置拿回来;起播失败时复位 _sessionConfigured,下一轮重新走完整 configure+setActive——只补激活救不回被改过 category/mode 的会话。 ⚠️ 没动 category/mode/routing 那几个值。CLAUDE.md 记着这块「被改坏过三次」 (外放/听筒/A2DP 来回绕),这次只加激活时机,不碰路由语义。 ⚠️ 这条是根据日志推断的,**未经真机验证**。 ## 测试 dart analyze 零错误。flutter test 有 7 个失败,逐个核对过是既有失败: 6 个语言的 translations_test 报「与 enUS key 不一致」,实测这些语言相对 enUS 缺 14~188 个 key,**其中属于本次新增的是 0 个**;id_verify_dialog_test 同理。 改动前后失败数都是 7。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
1 month ago |
|
|
2d1ee0b783 |
apps/client 首次入库(Flutter 移动端)
从 /Users/yunyanzhineng/work/eaimar_client-main 搬入,未带原 .git,历史留在原仓库。 3215 个文件 / 约 118MB,其中约 40MB 是构建必需的预编译 SDK(azure_speech 的 MicrosoftCognitiveServicesSpeech.xcframework、device_jieli 的 JL_BLEKit 等)。 build/ (5.2G)、ios/Pods/ (311M)、android/.gradle/ (120M) 已由 .gitignore 挡掉。 本次入库的代码包含两项刚做完的改动: 1) 游客登录入口接上服务端开关。此前 _checkGuestLoginConfig() 恒定写死 true, 入口根本不读配置。现改为取服务端算好的结论,并把入口所在的 Builder 改成 Obx ——配置是异步拿到的,Builder 只渲染 onInit 时的初值 false,入口再也不会出现。 拿不到配置时按隐藏处理:默认显示会在送审期把不该露的入口露给审核员。 ⚠️ 已发布的老客户端关不掉(写死 true),只对本版及以后生效。 2) iOS 上架合规修复: - 移除 ATT/IDFA:删掉 Info.plist 的 NSUserTrackingUsageDescription、 SKAdNetworkItems、NSAdvertisingAttributionReportEndpoint,删掉 splash 里的 授权请求与 app_tracking_transparency 依赖。本 App 从不读取 IDFA,也没有任何 广告/归因 SDK,弹框纯粹劝退用户,还要在 App Store Connect 申报追踪数据类型。 - ios/Podfile 里那段 config.build_settings.delete('NSUserTrackingUsageDescription') 是无效代码(该 key 在 Info.plist 里,不是 build setting),换成注释说明。 - 通讯录符号(ITMS-90683 被拒原因)随之一并解决:permission_handler 走 SPM 后 按 Info.plist 有无对应 key 决定编不编,判定结果被 SwiftPM 按内容哈希缓存, 改完 plist 必须清 DerivedData 与 manifests 缓存才会重新求值。 另附 GOOGLE_PLAY_RELEASE_CHECKLIST.md:Google Play 上架阻塞项的实测梳理 (targetSdk 36 与 Billing 8 的 2026-08-31 期限、16KB 页对齐不合规的两个 .so、 支付回退逻辑的下架风险)。 ⚠️ android/app/sign/ 下的 eaimar.keystore 与 Eaimar.properties(含明文签名密码) 随本次提交入库,与本仓库"私有仓库下接受真实凭据"的既有取舍一致。 仓库若要公开,这两个必须先摘出去。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
1 month ago |