diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 8d6bfee..72af6fe 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -206,6 +206,28 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) return al.processSystemMessage(ctx, msg) } + // Explicit language command: /lang + if strings.HasPrefix(strings.TrimSpace(msg.Content), "/lang") { + parts := strings.Fields(strings.TrimSpace(msg.Content)) + if len(parts) < 2 { + preferred, last := al.sessions.GetLanguagePreferences(msg.SessionKey) + if preferred == "" { + preferred = "(auto)" + } + if last == "" { + last = "(none)" + } + return fmt.Sprintf("Usage: /lang \nCurrent preferred: %s\nLast detected: %s", preferred, last), nil + } + lang := normalizeLang(parts[1]) + if lang == "" { + return "Invalid language code.", nil + } + al.sessions.SetPreferredLanguage(msg.SessionKey, lang) + al.sessions.Save(al.sessions.GetOrCreate(msg.SessionKey)) + return fmt.Sprintf("Language preference updated to %s", lang), nil + } + // Update tool contexts if tool, ok := al.tools.Get("message"); ok { if mt, ok := tool.(*tools.MessageTool); ok { diff --git a/pkg/tools/memory_write.go b/pkg/tools/memory_write.go index 0a48fe5..7045563 100644 --- a/pkg/tools/memory_write.go +++ b/pkg/tools/memory_write.go @@ -38,6 +38,21 @@ func (t *MemoryWriteTool) Parameters() map[string]interface{} { "description": "Target memory kind: longterm or daily", "default": "daily", }, + "importance": map[string]interface{}{ + "type": "string", + "description": "low|medium|high. high is recommended for longterm", + "default": "medium", + }, + "source": map[string]interface{}{ + "type": "string", + "description": "Source/context for this memory, e.g. user, system, tool", + "default": "user", + }, + "tags": map[string]interface{}{ + "type": "array", + "items": map[string]interface{}{"type": "string"}, + "description": "Optional tags for filtering/search, e.g. preference,todo,decision", + }, "append": map[string]interface{}{ "type": "boolean", "description": "Append mode (default true)", @@ -61,18 +76,31 @@ func (t *MemoryWriteTool) Execute(ctx context.Context, args map[string]interface kind = "daily" } + importance, _ := args["importance"].(string) + importance = normalizeImportance(importance) + + source, _ := args["source"].(string) + source = strings.TrimSpace(source) + if source == "" { + source = "user" + } + + tags := parseTags(args["tags"]) + appendMode := true if v, ok := args["append"].(bool); ok { appendMode = v } + formatted := formatMemoryLine(content, importance, source, tags) + switch kind { case "longterm", "memory", "permanent": path := filepath.Join(t.workspace, "MEMORY.md") if appendMode { - return t.appendWithTimestamp(path, content) + return t.appendWithTimestamp(path, formatted) } - if err := os.WriteFile(path, []byte(content+"\n"), 0644); err != nil { + if err := os.WriteFile(path, []byte(formatted+"\n"), 0644); err != nil { return "", err } return fmt.Sprintf("Wrote long-term memory: %s", path), nil @@ -83,9 +111,9 @@ func (t *MemoryWriteTool) Execute(ctx context.Context, args map[string]interface } path := filepath.Join(memDir, time.Now().Format("2006-01-02")+".md") if appendMode { - return t.appendWithTimestamp(path, content) + return t.appendWithTimestamp(path, formatted) } - if err := os.WriteFile(path, []byte(content+"\n"), 0644); err != nil { + if err := os.WriteFile(path, []byte(formatted+"\n"), 0644); err != nil { return "", err } return fmt.Sprintf("Wrote daily memory: %s", path), nil @@ -109,3 +137,45 @@ func (t *MemoryWriteTool) appendWithTimestamp(path, content string) (string, err } return fmt.Sprintf("Appended memory to %s", path), nil } + +func normalizeImportance(v string) string { + s := strings.ToLower(strings.TrimSpace(v)) + switch s { + case "high", "medium", "low": + return s + default: + return "medium" + } +} + +func parseTags(raw interface{}) []string { + items, ok := raw.([]interface{}) + if !ok { + return nil + } + out := make([]string, 0, len(items)) + seen := map[string]struct{}{} + for _, it := range items { + s, _ := it.(string) + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" { + continue + } + if _, exists := seen[s]; exists { + continue + } + seen[s] = struct{}{} + out = append(out, s) + } + return out +} + +func formatMemoryLine(content, importance, source string, tags []string) string { + var meta []string + meta = append(meta, "importance="+importance) + meta = append(meta, "source="+source) + if len(tags) > 0 { + meta = append(meta, "tags="+strings.Join(tags, ",")) + } + return fmt.Sprintf("[%s] %s", strings.Join(meta, " | "), content) +} diff --git a/pkg/tools/memory_write_test.go b/pkg/tools/memory_write_test.go new file mode 100644 index 0000000..a6176aa --- /dev/null +++ b/pkg/tools/memory_write_test.go @@ -0,0 +1,28 @@ +package tools + +import ( + "strings" + "testing" +) + +func TestFormatMemoryLine(t *testing.T) { + got := formatMemoryLine("remember this", "high", "user", []string{"preference", "lang"}) + if got == "" { + t.Fatal("empty formatted line") + } + if want := "importance=high"; !strings.Contains(got, want) { + t.Fatalf("expected %q in %q", want, got) + } + if want := "source=user"; !strings.Contains(got, want) { + t.Fatalf("expected %q in %q", want, got) + } +} + +func TestNormalizeImportance(t *testing.T) { + if normalizeImportance("HIGH") != "high" { + t.Fatal("expected high") + } + if normalizeImportance("unknown") != "medium" { + t.Fatal("expected medium fallback") + } +}