修主人报的硬故障「后台思考的主动推送一个都没有推出来」(根因两条,缺一不可): - 后端 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 成功再打活跃 ⇒ 全部打红
52 lines
2.6 KiB
Go
52 lines
2.6 KiB
Go
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
|
||
}
|