Files
nofx/agent/preferences.go
lky-spec 3ca95b294d feat: port NOFXi agent module onto latest dev base (#1485)
* feat: integrate NOFXi agent into dev

* Enhance NOFXi agent workflow and diagnostics
2026-04-21 23:47:55 +08:00

162 lines
4.5 KiB
Go

package agent
import (
"encoding/json"
"fmt"
"hash/fnv"
"strings"
"time"
)
// PersistentPreference is a durable user instruction shown in the UI and
// injected into the agent context for future conversations.
type PersistentPreference struct {
ID string `json:"id"`
Text string `json:"text"`
CreatedAt string `json:"created_at,omitempty"`
}
func NewPersistentPreference(text string) (PersistentPreference, error) {
text = strings.TrimSpace(text)
if text == "" {
return PersistentPreference{}, fmt.Errorf("text required")
}
now := time.Now().UTC()
return PersistentPreference{
ID: now.Format("20060102150405.000000000"),
Text: text,
CreatedAt: now.Format(time.RFC3339),
}, nil
}
// SessionUserIDFromKey maps a stable user key (for example a UUID string from
// auth) to the int64 session id expected by the current agent implementation.
func SessionUserIDFromKey(userKey string) int64 {
if strings.TrimSpace(userKey) == "" {
return 1
}
h := fnv.New64a()
_, _ = h.Write([]byte(userKey))
sum := h.Sum64() & 0x7fffffffffffffff
if sum == 0 {
return 1
}
return int64(sum)
}
func PreferencesConfigKey(userID int64) string {
return fmt.Sprintf("agent_preferences_%d", userID)
}
func (a *Agent) getPersistentPreferences(userID int64) []PersistentPreference {
if a.store == nil {
return nil
}
raw, err := a.store.GetSystemConfig(PreferencesConfigKey(userID))
if err != nil || strings.TrimSpace(raw) == "" {
return nil
}
var prefs []PersistentPreference
if err := json.Unmarshal([]byte(raw), &prefs); err != nil {
a.logger.Warn("failed to parse persistent preferences", "error", err, "user_id", userID)
return nil
}
return prefs
}
func (a *Agent) savePersistentPreferences(userID int64, prefs []PersistentPreference) error {
if a.store == nil {
return fmt.Errorf("store unavailable")
}
data, err := json.Marshal(prefs)
if err != nil {
return err
}
return a.store.SetSystemConfig(PreferencesConfigKey(userID), string(data))
}
func (a *Agent) addPersistentPreference(userID int64, text string) ([]PersistentPreference, PersistentPreference, error) {
created, err := NewPersistentPreference(text)
if err != nil {
return nil, PersistentPreference{}, err
}
prefs := a.getPersistentPreferences(userID)
prefs = append([]PersistentPreference{created}, prefs...)
if len(prefs) > 20 {
prefs = prefs[:20]
}
if err := a.savePersistentPreferences(userID, prefs); err != nil {
return nil, PersistentPreference{}, err
}
return prefs, created, nil
}
func (a *Agent) updatePersistentPreference(userID int64, match, replacement string) ([]PersistentPreference, *PersistentPreference, error) {
match = strings.TrimSpace(match)
replacement = strings.TrimSpace(replacement)
if match == "" || replacement == "" {
return nil, nil, fmt.Errorf("match and replacement are required")
}
prefs := a.getPersistentPreferences(userID)
for i := range prefs {
if prefs[i].ID == match || strings.Contains(strings.ToLower(prefs[i].Text), strings.ToLower(match)) {
prefs[i].Text = replacement
if err := a.savePersistentPreferences(userID, prefs); err != nil {
return nil, nil, err
}
return prefs, &prefs[i], nil
}
}
return prefs, nil, fmt.Errorf("preference not found")
}
func (a *Agent) deletePersistentPreference(userID int64, match string) ([]PersistentPreference, *PersistentPreference, error) {
match = strings.TrimSpace(match)
if match == "" {
return nil, nil, fmt.Errorf("match required")
}
prefs := a.getPersistentPreferences(userID)
filtered := make([]PersistentPreference, 0, len(prefs))
var removed *PersistentPreference
for i := range prefs {
p := prefs[i]
if removed == nil && (p.ID == match || strings.Contains(strings.ToLower(p.Text), strings.ToLower(match))) {
cp := p
removed = &cp
continue
}
filtered = append(filtered, p)
}
if removed == nil {
return prefs, nil, fmt.Errorf("preference not found")
}
if err := a.savePersistentPreferences(userID, filtered); err != nil {
return nil, nil, err
}
return filtered, removed, nil
}
func (a *Agent) buildPersistentPreferencesContext(userID int64) string {
prefs := a.getPersistentPreferences(userID)
if len(prefs) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("[Persistent User Preferences - follow unless the user explicitly overrides them]\n")
for _, pref := range prefs {
if strings.TrimSpace(pref.Text) == "" {
continue
}
sb.WriteString("- ")
sb.WriteString(pref.Text)
sb.WriteString("\n")
}
return strings.TrimSpace(sb.String())
}