From 4e65028d1da2f48fe6a0b6d2b4a672590814657c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9A=E7=AB=A0=E6=B5=A9?= Date: Tue, 15 Sep 2026 17:08:33 +0800 Subject: [PATCH] fix(dingtalk): strip leaked internal source handles --- internal/channels/dingtalk/display_sanitizer.go | 9 +++++++-- internal/channels/dingtalk/display_sanitizer_test.go | 12 +++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/channels/dingtalk/display_sanitizer.go b/internal/channels/dingtalk/display_sanitizer.go index 21161e1..5e5150f 100644 --- a/internal/channels/dingtalk/display_sanitizer.go +++ b/internal/channels/dingtalk/display_sanitizer.go @@ -8,7 +8,11 @@ import ( const displayReferenceMarkerPattern = `\[\[ref[\t ]+id[\t ]*=[\t ]*["']c[0-9]+["'][\t ]*\]\]` var ( - displayReferenceMarkerRE = regexp.MustCompile(displayReferenceMarkerPattern) + displayReferenceMarkerRE = regexp.MustCompile(displayReferenceMarkerPattern) + // bareInternalSourceHandleRE matches WeKnora request-local source aliases + // leaked by a model (for example [d21], [c12], or [w3]). It intentionally + // does not match ordinary Markdown links or arbitrary bracketed content. + bareInternalSourceHandleRE = regexp.MustCompile(`(?i)\[(?:[dcw][0-9]+)(?:[ ]*[,,][ ]*(?:[dcw][0-9]+))*\]`) leadingReferenceGroupColonRE = regexp.MustCompile(`(?m)^[ ]*(?:` + displayReferenceMarkerPattern + `[ ]*)+[::][ ]*`) spacedReferenceGroupRE = regexp.MustCompile(`[ ]+(?:` + displayReferenceMarkerPattern + `[ ]*)+`) spaceBeforeDisplayPunctuation = regexp.MustCompile(`[ ]+([,。;:!?、,.;:!?])`) @@ -19,12 +23,13 @@ func sanitizeDisplayAnswer(content string) string { if index := strings.LastIndex(content, "[[ref"); index >= 0 && !strings.Contains(content[index:], "]]") { content = content[:index] } - if !displayReferenceMarkerRE.MatchString(content) { + if !displayReferenceMarkerRE.MatchString(content) && !bareInternalSourceHandleRE.MatchString(content) { return content } content = leadingReferenceGroupColonRE.ReplaceAllString(content, "") content = spacedReferenceGroupRE.ReplaceAllString(content, " ") content = displayReferenceMarkerRE.ReplaceAllString(content, "") + content = bareInternalSourceHandleRE.ReplaceAllString(content, "") content = spaceBeforeDisplayPunctuation.ReplaceAllString(content, "$1") content = spaceAfterDisplayColon.ReplaceAllString(content, "$1") return content diff --git a/internal/channels/dingtalk/display_sanitizer_test.go b/internal/channels/dingtalk/display_sanitizer_test.go index b8acd90..ea762e4 100644 --- a/internal/channels/dingtalk/display_sanitizer_test.go +++ b/internal/channels/dingtalk/display_sanitizer_test.go @@ -34,10 +34,20 @@ func TestSanitizeDisplayAnswerRemovesInternalReferenceMarkers(t *testing.T) { want: "Feature A remains supported.", }, { - name: "unrelated double bracket syntax is preserved", + name: "unrelated double bracket syntax and ordinary link are preserved", input: "请保留 [[wiki|页面名称]] 和 [普通链接](https://example.com)。", want: "请保留 [[wiki|页面名称]] 和 [普通链接](https://example.com)。", }, + { + name: "bare internal source handles", + input: "Nova支持虚拟控制器离线仿真[d21],并可搭配VX500[w3]。", + want: "Nova支持虚拟控制器离线仿真,并可搭配VX500。", + }, + { + name: "grouped bare internal source handles", + input: "该结论由多个资料支持 [d21, c12,w3]。", + want: "该结论由多个资料支持。", + }, { name: "content without references remains byte for byte unchanged", input: "Protocol: value ; keep spacing\n indented",