Files
Cyrene_v2/internal/services/gateway/proactive_persist.go
T
AskaEth 84c124f5a9
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(backthink): 她主动找你终于看得见 —— 主动消息落主会话 + 前端消费 + 非前台浏览器通知
修主人报的硬故障「后台思考的主动推送一个都没有推出来」(根因两条,缺一不可):
- 后端 gateway/proactive.go 只广播 proactive_message 帧、**从不写会话历史** ⇒ 刷新补不回来
- 前端**零消费点** ⇒ 帧被静默丢弃(既无气泡也无通知)

后端:新增 proactive_persist.go(**先落库、再发帧**;web/assistant/mode=chat;未接线时空操作且不谎称已落库)
  载荷新增 id/createdAt(落库行权威 id);PushResult.Detail 三态如实分开报告「投递到几个连接」与「是否落库」
前端:handlers/proactive.ts(气泡**恒落** + 按落库 id 去重 + **非前台才补通知** + 拒绝授权/畸形载荷可见降级)
  + lib/notify/proactive.ts(纯逻辑 isBackground/decideProactiveNotice/有界去重表)
  + handlers/task.ts(另三个被丢的帧 task_done/automation_result/subagent_result)
  + ws/resync.ts + useWsResync.ts(**重连重跑本页引导读取**,首次连接不跑)
双份核查(带三层证据):现状不双份 —— 路由分叉「一次 Push 至多一个 sink 成功」,且 IM 路由本来就把主动消息镜像回主会话恰好一次
契约 v0.72;变异实测:只发帧不落库 / 写成 user 角色 / 去重失效 / 前台也弹 / IM 成功再打活跃 ⇒ 全部打红
2026-10-05 16:02:36 +08:00

52 lines
2.6 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 gateway
import (
"context"
"fmt"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
)
// 本文件承载主动推送的**落库**那一半(切片 P0「她主动找你」)。它从
// proactive.go 拆出来,好让「帧长什么样」与「记录怎么写」各自成篇
// (AGENTS §10 模块按职责拆源文件)。
//
// 为什么必须有这一半:修复前活跃客户端路由只把 `proactive_message` 帧
// 广播给在线连接(fire-and-forget),**一行都不写** ⇒ 刷新/重载后什么都补不回来,
// 界面既不显示气泡也补不出历史。主人的原话是「后台思考的主动推送一个都没推出来」。
// 主动消息是**她真实说过的一句话**,因此它必须与 IM 镜像路径走同一条去向:
// 主会话历史里的一条 assistant 消息。
// persistProactiveToMain 把一条主动消息写进主会话(`history.MainSessionID`),
// 并返回落库后的完整消息(含权威 id/时间戳),供帧载荷携带。
//
// 三条口径:
// - **角色恒为 assistant**:这是她单方面开口说的话,不是主人的输入 —— 塞成
// user 角色会污染「谁说了什么」,也会在历史里冒充主人发言(红线)。
// - **正文用调用方已经净化过的文本**(backthink 的 stripInternalMarkers 出口),
// 系统元数据(决策标记、渠道选择)绝不进正文,也绝不新增任何隐藏字段 ——
// 注入面因此与「气泡里看到的那句话」逐字相同。
// - **turn_id 留空**:主动推送不属于任何一路回合(她单方面开口,没有对应的
// 入站回合),空值诚实地表示这一点(与 channelbridge 的同一取舍)。
//
// 未接线历史存储时它是空操作并返回零值(`Persisted=false`)—— 与编排器 /
// IM 镜像路径同一取舍:没存储不是错误,但**绝不能谎称已落库**。写库失败返回
// 包装后的错误,由调用方让它可见(WARN + 返回给推送服务)。
func (h *hub) persistProactiveToMain(ctx context.Context, msg adapter.UnifiedMessage) (history.Message, error) {
if h.history == nil {
return history.Message{}, nil
}
full, err := h.history.AppendFull(ctx, history.Message{
SessionID: history.MainSessionID,
Role: history.RoleAssistant,
Content: msg.Content,
Platform: msg.Platform,
Mode: modeChat,
})
if err != nil {
return history.Message{}, fmt.Errorf("gateway: persist proactive to main session: %w", err)
}
return full, nil
}