fix(channelbridge): IM 入站附件引用写进落库的用户消息(安卓报的 P0)
根因:摄取结果只挂到「内存里的模型可见消息」(inbound.go 的 attachInboundAttachments), 落库时只传了 text、history.Message 没有 Document 字段 ⇒ 该列恒空 ⇒ 读侧 omitempty 导致 documents 键整个缺席 ⇒ 任何客户端只看到一条空消息。Web 侧不这样,是因为 gateway/session_persist.go 有 messageTextImageDocument 把 ContentTypeDocument 部件编码进该列 —— IM 路径缺的就是同一步。 修法:inboundAttachments.documentColumn() 复用 history.EncodeDocuments 同一口径写出该列; persistToMain/persistIsolated 增 document 参数并返回刚落库的消息;新增两段式第二段 persistTurnDocumentStatus 按 id 回写抽取结论(只补结论、按存储名匹配、匹配不到原样留), 与 gateway/session_document_status.go 逐字对齐。 契约零扩张:无新端点/帧/字段;0 附件 ⇒ 列空 ⇒ 键省略;形状即 web 的同一结构体。 测试 6 条(含元素 JSON 逐字断言、unreadable 带 reason、被拒不编造芯片、群聊隔离分支、 真循环端到端);变异两条(还原挂载 / 关闭结论回写)⇒ 各 4 条红。
This commit is contained in:
1 parent
f8f2e9c54c
commit
679cee04f7
7 files changed
+606
-19
No files matched your search
@@ -5,6 +5,11 @@ package channelbridge
|
||||
// 上传同一份实现),成功者挂成该消息的文档部件,让装配期 `resolveDocuments`
|
||||
// 照既有链路完成抽取/注入/清单/结论回写,**不需要新链路**。
|
||||
//
|
||||
// 同样的产物还必须是**用户消息落库时的 `document` 列**(documentColumn):只把部件
|
||||
// 挂在内存里的模型可见消息上,客户端读到的那条消息就是一片空白 —— 附件芯片只认
|
||||
// 库里的那一列。结论(status/chars/truncated/reason)随后由
|
||||
// persistTurnDocumentStatus 补进同一列(inbound_documents_persist.go)。
|
||||
//
|
||||
// 读不到的附件**绝不静默**(design/24 §4.4):模型会收到一行诚实提示,日志留一条
|
||||
// 带来源的记录(只记 name/mime/size,**绝不记正文**)。
|
||||
//
|
||||
@@ -26,6 +31,7 @@ import (
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/documents"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
@@ -81,6 +87,44 @@ func (a inboundAttachments) noticeText() string {
|
||||
return strings.Join(a.notices, "\n")
|
||||
}
|
||||
|
||||
// documentColumn 把本次摄取产出的文档部件编码成落库用的 `document` 列(design/24
|
||||
// §2.4 的引用数组)。它与 Web 上传走**同一个字段、同一个结构体**:history.DocumentRef
|
||||
// 同时就是历史 DTO 里 `documents` 数组的元素形状,因此两边客户端读到的附件芯片
|
||||
// 逐字一致(名字/MIME/大小取自服务端,客户端给的原始文件名与声明类型都不在其中)。
|
||||
//
|
||||
// 与网关侧 documentRefOf(session_persist.go)是同一份映射:那里未导出、且本包
|
||||
// 不许反向依赖网关,因此这里按同一口径重写一份 —— 两侧任一改动都必须同时改。
|
||||
//
|
||||
// 这里只落**引用**,不落结论:chars/truncated/status 是**本回合注入侧**的事实,
|
||||
// 要等回合跑起来(装配期解析)才存在,由 persistTurnDocumentStatus 在回合结束后
|
||||
// 补进同一列。没有附件时返回空串(与 EncodeDocuments 对空列表的口径一致:空串
|
||||
// ⇒ DTO 里该键省略)。
|
||||
func (a inboundAttachments) documentColumn() (string, error) {
|
||||
refs := make([]history.DocumentRef, 0, len(a.parts))
|
||||
for _, c := range a.parts {
|
||||
refs = append(refs, history.DocumentRef{Name: c.DocumentName, MIME: c.DocumentMIME, Size: c.DocumentSize})
|
||||
}
|
||||
return history.EncodeDocuments(refs)
|
||||
}
|
||||
|
||||
// inboundDocumentColumn 取本回合要写进用户消息 `document` 列的引用串。编码失败
|
||||
// (只可能是坏数据或超限)绝不静默:打一条带来源的 ERROR,并让这条消息按「没有
|
||||
// 附件」落库 —— 丢一条消息远比丢一个附件引用严重,而那条写不进去的引用也绝不
|
||||
// 假装存在(与 Web 侧 persistUserMessages 的同一取舍)。
|
||||
func (b *Bridge) inboundDocumentColumn(msg adapter.UnifiedMessage, attach inboundAttachments) string {
|
||||
if len(attach.parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
column, err := attach.documentColumn()
|
||||
if err != nil {
|
||||
b.log.Error("inbound attachment refs not persisted",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID,
|
||||
"documents", len(attach.parts), "error", err)
|
||||
return ""
|
||||
}
|
||||
return column
|
||||
}
|
||||
|
||||
// hasFileMedia 报告消息是否携带文件类媒体(决定「无文本消息」是否仍要跑流水线)。
|
||||
func hasFileMedia(msg adapter.UnifiedMessage) bool {
|
||||
for _, m := range msg.Media {
|
||||
|
||||
Reference in new issue
Block a user