Files
Cyrene_v2/internal/services/channelbridge/proactive_notify.go
T
AskaEth 28afa11aeb feat(channelbridge): 出站语音结论落 voice 列并挂出站 Media(M58)
- voice.Ref 增 SourceTTS 与 FallbackReason,Validate/IsZero/validSource 同步
- 出站成功:INSERT -> 补写 voice 列 -> notify(首帧即带 voice)
- 出站降级:source=tts + status=failed + fallbackReason,正文原样
- 首条气泡挂出站 Media(新开条目,不复制 target.Media)
- gateway historyVoice 投影 fallbackReason
- 新增 outbound_voice.go 与整链 e2e 测试(含变异打红验证时序)
2026-10-27 22:08:38 +08:00

41 lines
2.2 KiB
Go
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.
package channelbridge
import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
)
// 本文件承载 IM 主动推送那条**实时通知**接缝(切片 P0b「IM 路由的主动消息也要在非前台
// 补通知」)。它与 persist.go 的 notifyPersisted 并列:普通消息走那一条,主动推送走这
// 一条。两条都写主会话,因此「写」是同一份代码(persistMainWith),差别只在通知。
//
// 为什么必须分开而不是去猜:主人裁定「她说话该不该提醒我,与走哪条路由无关」,因此
// 主动推送必须自己声明「我是主动推送」。判据是**结构化**的(本次写入走的是哪条通知
// 通道),**绝不**读正文关键词、**绝不**让提示词/模型判断 —— 后者是项目红线
// (AGENTS §9:身份与判定靠元数据,不靠提示词)。
//
// 为什么不在落库行上找判据:主会话的 `platform`/`adapter_id` 区分不出主动推送与普通
// 回复 —— 主动推送写的是**解析后的真实平台**(proactive.go 的 target.platform,例如
// "weixin-ilink"),与普通回复行同一形状(普通回复在按平台名注册的适配器上
// adapter_id 也等于平台名)。因此结构化判据放在**落库那一刻的通知通道**上,随帧顶的
// `proactive:true` 到达客户端(见 gateway 的 BroadcastProactivePersisted)。
// SetOnProactivePersisted 装配(或替换)「一条主动消息刚落进主会话」之后所调用的通知
// 钩子。组合根把它接进 gateway 的 BroadcastProactivePersisted(帧顶带结构化判据的
// `message_persisted`)。它可以安全空调用,也可与在途推送安全并发。
func (b *Bridge) SetOnProactivePersisted(fn func(history.Message)) {
b.mu.Lock()
defer b.mu.Unlock()
b.onProactivePersisted = fn
}
// notifyProactive 在装配了主动通知钩子时把刚落库的主动消息交给它;未装配时是空操作
// (行为与本切片之前逐字相同:主动消息照旧落库、只是没有「主动」这条通知通道)。
func (b *Bridge) notifyProactive(msg history.Message) {
b.mu.RLock()
fn := b.onProactivePersisted
b.mu.RUnlock()
if fn != nil {
fn(msg)
}
}