fix(gateway): Web 活跃客户端主动推送走 turnreply 规整(先规整再落库,推帧与落库逐字相同)

M19 复审抓到的真缺口:internal/services/gateway/proactive.go 的
SendProactive -> persistProactiveToMain 这条 Web 活跃客户端主动推送路由
完全绕过 turnreply.NormalizeParagraphs —— 同一条主动消息走 IM 是规整过的、
走 Web 实时推还是原样(半包 <p> 在客户端按「存在完整 <p> 就不再按空行回退」
的契约下整条塌成一坨)。

- 接线:SendProactive 在落库之前先 msg.Content = turnreply.NormalizeParagraphs(msg.Content),
  落库与 proactive_message 帧取同一个字符串 ⇒ 两端逐字相同;不在 gateway 另写实现。
- 新增 proactive_normalize_test.go:修前红(web 原样 / im 已规整),修后断言
  ① 落库是规整后文本 ② 推帧 == 落库行 ③ 两条路由同源同文本 ④ <br>/<action> 语义不动。
- 既有两条断言按新口径更新(纯散文现在也被包成一个 <p>,与其它三处一致)。
This commit is contained in:
AskaEth committed 2026-10-24 19:40:30 +08:00
1 parent 4ed5925296
commit 1d3965aa80
5 files changed
+173 -7

No files matched your search

+5 -2
View File
@@ -52,8 +52,11 @@ func TestHub_SendProactive_OwnerAndAdmin(t *testing.T) {
if !ok {
t.Fatalf("frame data type = %T, want map[string]any", ev.Data)
}
if data["content"] != "记得喝水哦" {
t.Fatalf("frame content = %q, want the proactive text", data["content"])
// 正文经共享规整器(turnreply.NormalizeParagraphs):纯散文也会被包成
// 一个 `<p>` —— 这条路由与 Web 回合 / IM 出站 / IM 侧主动推送**同源**,
// 未包裹的裸文本同样会包上(M25 修的就是本路由曾绕过它)。
if data["content"] != "<p>记得喝水哦</p>" {
t.Fatalf("frame content = %q, want the normalized proactive text", data["content"])
}
if data["sender"] != "Cyrene" {
t.Fatalf("frame sender = %q, want Cyrene", data["sender"])