mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 16:15:24 +00:00
* feat(channelmemory): add passive extraction tuning * feat(ui): expose passive memory tuning controls * docs(memory): document passive extraction tuning --------- Co-authored-by: Collective Developer <man@collective.dev>
174 lines
4.9 KiB
Go
174 lines
4.9 KiB
Go
package channelmemory
|
|
|
|
import (
|
|
"encoding/json"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseConfigDefaultsDisabled(t *testing.T) {
|
|
cfg := ParseConfig(nil)
|
|
if cfg.Enabled {
|
|
t.Fatal("default passive memory must be disabled")
|
|
}
|
|
if !cfg.ReviewMode {
|
|
t.Fatal("default passive memory must require review")
|
|
}
|
|
if !slices.Equal(cfg.AllowedTypes, DefaultAllowedTypes) {
|
|
t.Fatalf("allowed types = %v, want %v", cfg.AllowedTypes, DefaultAllowedTypes)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigNormalizesBoundsAndTypes(t *testing.T) {
|
|
raw := json.RawMessage(`{
|
|
"passive_memory": {
|
|
"enabled": true,
|
|
"review_mode": true,
|
|
"interval_minutes": 2,
|
|
"message_cap": 5000,
|
|
"retention_hours": 0,
|
|
"allowed_types": ["people", "people", "unknown", "todos"],
|
|
"exclude_users": ["u1", ""],
|
|
"exclude_patterns": ["secret", "["],
|
|
"exclude_history_keys": ["group-1", ""],
|
|
"custom_prompt": " Avoid duplicate facts across candidate items. ",
|
|
"group_custom_prompts": {
|
|
"group-1": " Prefer Project Orion context. ",
|
|
" ": "ignored",
|
|
"group-2": " ",
|
|
"group-3": "Keep owner names stable."
|
|
},
|
|
"min_messages": 1,
|
|
"group_only": false
|
|
}
|
|
}`)
|
|
cfg := ParseConfig(raw)
|
|
if !cfg.Enabled || !cfg.ReviewMode {
|
|
t.Fatalf("unexpected enabled/review flags: %+v", cfg)
|
|
}
|
|
if cfg.IntervalMinutes != 15 {
|
|
t.Fatalf("interval = %d, want lower bound 15", cfg.IntervalMinutes)
|
|
}
|
|
if cfg.MessageCap != 1000 {
|
|
t.Fatalf("message cap = %d, want upper bound 1000", cfg.MessageCap)
|
|
}
|
|
if cfg.RetentionHours != 168 {
|
|
t.Fatalf("retention = %d, want fallback 168", cfg.RetentionHours)
|
|
}
|
|
if !slices.Equal(cfg.AllowedTypes, []string{"people", "todos"}) {
|
|
t.Fatalf("allowed types = %v", cfg.AllowedTypes)
|
|
}
|
|
if !slices.Equal(cfg.ExcludeUsers, []string{"u1"}) {
|
|
t.Fatalf("exclude users = %v", cfg.ExcludeUsers)
|
|
}
|
|
if !slices.Equal(cfg.ExcludePatterns, []string{"secret"}) {
|
|
t.Fatalf("exclude patterns = %v", cfg.ExcludePatterns)
|
|
}
|
|
if !slices.Equal(cfg.ExcludeHistoryKeys, []string{"group-1"}) {
|
|
t.Fatalf("exclude history keys = %v", cfg.ExcludeHistoryKeys)
|
|
}
|
|
if cfg.CustomPrompt != "Avoid duplicate facts across candidate items." {
|
|
t.Fatalf("custom prompt = %q", cfg.CustomPrompt)
|
|
}
|
|
if !mapsEqual(cfg.GroupCustomPrompts, map[string]string{
|
|
"group-1": "Prefer Project Orion context.",
|
|
"group-3": "Keep owner names stable.",
|
|
}) {
|
|
t.Fatalf("group custom prompts = %#v", cfg.GroupCustomPrompts)
|
|
}
|
|
if cfg.MinMessages != 2 {
|
|
t.Fatalf("min messages = %d, want lower bound 2", cfg.MinMessages)
|
|
}
|
|
if !cfg.GroupOnly {
|
|
t.Fatal("group_only must normalize to true")
|
|
}
|
|
}
|
|
|
|
func TestMergeIntoInstanceConfigPreservesSiblingFields(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
cfg.Enabled = true
|
|
raw := MergeIntoInstanceConfig(json.RawMessage(`{"foo":"bar"}`), cfg)
|
|
var root map[string]any
|
|
if err := json.Unmarshal(raw, &root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if root["foo"] != "bar" {
|
|
t.Fatalf("sibling field missing: %s", raw)
|
|
}
|
|
if _, ok := root["passive_memory"]; !ok {
|
|
t.Fatalf("passive_memory missing: %s", raw)
|
|
}
|
|
}
|
|
|
|
func TestApplyConfigPatchPreservesUnspecifiedReviewMode(t *testing.T) {
|
|
base := DefaultConfig()
|
|
base.ReviewMode = true
|
|
base.CustomPrompt = "Keep existing dedupe guidance."
|
|
base.GroupCustomPrompts = map[string]string{"group-1": "Keep group prompt."}
|
|
|
|
cfg, err := ApplyConfigPatch(base, []byte(`{"enabled":true}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !cfg.Enabled {
|
|
t.Fatal("enabled patch was not applied")
|
|
}
|
|
if !cfg.ReviewMode {
|
|
t.Fatal("partial patch must preserve review_mode")
|
|
}
|
|
if cfg.CustomPrompt != "Keep existing dedupe guidance." {
|
|
t.Fatalf("custom prompt = %q", cfg.CustomPrompt)
|
|
}
|
|
if cfg.GroupCustomPrompts["group-1"] != "Keep group prompt." {
|
|
t.Fatalf("group custom prompt missing: %#v", cfg.GroupCustomPrompts)
|
|
}
|
|
}
|
|
|
|
func TestApplyInstanceConfigPatchPreservesSiblingRootConfig(t *testing.T) {
|
|
raw := json.RawMessage(`{
|
|
"discord": {"require_mention": true},
|
|
"passive_memory": {
|
|
"enabled": true,
|
|
"review_mode": true,
|
|
"custom_prompt": "Avoid duplicates."
|
|
}
|
|
}`)
|
|
|
|
updated, cfg, err := ApplyInstanceConfigPatch(raw, []byte(`{
|
|
"custom_prompt":" Prefer one Project Orion fact. ",
|
|
"group_custom_prompts": {"group-1": " Keep product context. "}
|
|
}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.CustomPrompt != "Prefer one Project Orion fact." {
|
|
t.Fatalf("custom prompt = %q", cfg.CustomPrompt)
|
|
}
|
|
if cfg.GroupCustomPrompts["group-1"] != "Keep product context." {
|
|
t.Fatalf("group custom prompt = %#v", cfg.GroupCustomPrompts)
|
|
}
|
|
|
|
var root map[string]json.RawMessage
|
|
if err := json.Unmarshal(updated, &root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := root["discord"]; !ok {
|
|
t.Fatalf("sibling discord config missing: %s", updated)
|
|
}
|
|
if _, ok := root["passive_memory"]; !ok {
|
|
t.Fatalf("passive_memory missing: %s", updated)
|
|
}
|
|
}
|
|
|
|
func mapsEqual(a, b map[string]string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for key, value := range b {
|
|
if a[key] != value {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|