refactor: pluginize text cleaning and tool NoMemory control

- SDK: ToolDef.NoMemory field, PluginSDK.RegisterTextCleaner/TextCleaners
- Registry: aggregate text cleaners from plugins, expose CleanText()
- Memory: replace hardcoded QQ regex CleanTemplateText with dynamic CleanText/SetTextCleaner
- StageHost: add ToolDef(name) lookup
- eventloop: check ToolDef.NoMemory before emitMemoryCandidate
- context/Prune: replace hardcoded agentcli/terminal source filter with ToolsUsed NoMemory check
- agentcli/cmd: mark tools with NoMemory: true
- main.go: wire memory.SetTextCleaner(pluginReg.CleanText)
This commit is contained in:
root
2026-07-24 14:49:08 +08:00
parent 097c8e80b7
commit 3fc2151588
32 changed files with 2368 additions and 69 deletions

View File

@ -2,10 +2,31 @@ package memory
import (
"fmt"
"regexp"
"sort"
"testing"
)
func init() {
globalTextCleaner = func(text string) string {
reQQGroupSuffix := regexp.MustCompile(`通过id\d+使用qq_get_message工具获取消息正文。获取内容后使用 output_send\(channel="qq"\) 回复该群聊content 设为 JSON 字符串:\{[^}]*\}`)
reQQPrivateSuffix := regexp.MustCompile(`通过id\d+使用qq_get_message工具获取消息正文。获取内容后使用 output_send\(channel="qq"\) 回复对方content 设为 JSON 字符串:\{[^}]*\}`)
reQQOldReply := regexp.MustCompile(`通过id\d+使用qq_get_message工具获取消息正文。获取后必须使用[^。]+。`)
reQQOldForbid := regexp.MustCompile(`你只能通过qq_get_message先看消息然后直接用%!s\(MISSING\)send_private_msg回复中间的思考过程禁止调用任何其他工具\s*→\s*`)
reQQGeneral := regexp.MustCompile(`通过id\d+使用qq_get_message工具获取消息正文[。,][^。]*?(?:回复|发送消息)`)
reTimestamp := regexp.MustCompile(`\[\d{2}:\d{2}\]\s*`)
reMultiSpace := regexp.MustCompile(`\s+`)
text = reQQGroupSuffix.ReplaceAllString(text, "")
text = reQQPrivateSuffix.ReplaceAllString(text, "")
text = reQQOldReply.ReplaceAllString(text, "")
text = reQQOldForbid.ReplaceAllString(text, "")
text = reQQGeneral.ReplaceAllString(text, "")
text = reTimestamp.ReplaceAllString(text, "")
text = reMultiSpace.ReplaceAllString(text, " ")
return text
}
}
type cleanTestEvent struct {
idx int
source string
@ -173,7 +194,7 @@ func TestCleanVectorConsistency(t *testing.T) {
}
for _, tmpl := range templates {
cleaned := CleanTemplateText(tmpl)
cleaned := CleanText(tmpl)
t.Logf("template {%q} → {%q} (%d chars)", trimLen(tmpl, 60), cleaned, len(cleaned))
}
@ -286,11 +307,11 @@ func genStressEvents(n int) []cleanTestEvent {
func cleanEventText(source, input, response string) string {
switch {
case source == "agent" && response != "":
return CleanTemplateText(response)
return CleanText(response)
case source == "cold_storage":
return CleanTemplateText(input + " " + response)
return CleanText(input + " " + response)
default:
return CleanTemplateText(input)
return CleanText(input)
}
}