203 lines
6.0 KiB
Go
203 lines
6.0 KiB
Go
// tool/agent.go – Tool-Agent: Dispatcher für externe Tools (Email, ...)
|
||
package tool
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"my-brain-importer/internal/agents"
|
||
"my-brain-importer/internal/agents/tool/email"
|
||
"my-brain-importer/internal/brain"
|
||
"my-brain-importer/internal/config"
|
||
)
|
||
|
||
// Agent verteilt Tool-Anfragen an spezialisierte Sub-Agenten.
|
||
type Agent struct{}
|
||
|
||
func New() *Agent { return &Agent{} }
|
||
|
||
// Handle unterstützt: email
|
||
func (a *Agent) Handle(req agents.Request) agents.Response {
|
||
switch req.Action {
|
||
case agents.ActionEmail:
|
||
return a.handleEmail(req)
|
||
default:
|
||
return agents.Response{Text: "❌ Unbekannte Tool-Aktion. Verfügbar: email"}
|
||
}
|
||
}
|
||
|
||
func (a *Agent) handleEmail(req agents.Request) agents.Response {
|
||
subAction := agents.ActionEmailSummary
|
||
if len(req.Args) > 0 {
|
||
subAction = req.Args[0]
|
||
}
|
||
|
||
var (
|
||
result string
|
||
err error
|
||
)
|
||
|
||
switch subAction {
|
||
case agents.ActionEmailSummary:
|
||
result, err = email.Summarize()
|
||
case agents.ActionEmailUnread:
|
||
result, err = email.SummarizeUnread()
|
||
case agents.ActionEmailRemind:
|
||
result, err = email.ExtractReminders()
|
||
case agents.ActionEmailIngest:
|
||
return a.handleEmailIngest(req)
|
||
case agents.ActionEmailMove:
|
||
return a.handleEmailMove(req)
|
||
case agents.ActionEmailTriage:
|
||
return a.handleEmailTriage()
|
||
default:
|
||
return agents.Response{Text: fmt.Sprintf("❌ Unbekannte Email-Aktion `%s`. Verfügbar: summary, unread, remind, ingest, move, triage", subAction)}
|
||
}
|
||
|
||
if err != nil {
|
||
return agents.Response{Error: err, Text: fmt.Sprintf("❌ Email-Fehler: %v", err)}
|
||
}
|
||
return agents.Response{Text: "📧 **Email-Analyse:**\n\n" + result}
|
||
}
|
||
|
||
// handleEmailIngest importiert Emails aus einem IMAP-Ordner in Qdrant.
|
||
// Args[1] = Ordnername (Standard: "Archiv")
|
||
func (a *Agent) handleEmailIngest(req agents.Request) agents.Response {
|
||
folder := "Archiv"
|
||
if len(req.Args) > 1 && req.Args[1] != "" {
|
||
folder = req.Args[1]
|
||
}
|
||
|
||
accounts := config.AllEmailAccounts()
|
||
if len(accounts) == 0 {
|
||
return agents.Response{Text: "❌ Kein Email-Account konfiguriert."}
|
||
}
|
||
|
||
total := 0
|
||
var errs []string
|
||
for _, acc := range accounts {
|
||
n, err := brain.IngestEmailFolder(acc, folder, 500)
|
||
if err != nil {
|
||
errs = append(errs, fmt.Sprintf("%s: %v", acc.Name, err))
|
||
continue
|
||
}
|
||
total += n
|
||
}
|
||
|
||
if len(errs) > 0 && total == 0 {
|
||
return agents.Response{Text: fmt.Sprintf("❌ Email-Ingest fehlgeschlagen:\n%s", joinLines(errs))}
|
||
}
|
||
|
||
msg := fmt.Sprintf("✅ **Email-Ingest abgeschlossen:** %d Emails aus `%s` in die Wissensdatenbank importiert.", total, folder)
|
||
if len(errs) > 0 {
|
||
msg += "\n⚠️ Fehler bei einigen Accounts:\n" + joinLines(errs)
|
||
}
|
||
return agents.Response{Text: msg}
|
||
}
|
||
|
||
// handleEmailMove verschiebt alle ungelesenen Emails in einen konfigurierten Archivordner.
|
||
// Args[1] = Zielordner-Name (aus archive_folders in config oder Legacy: 2Jahre/5Jahre/Archiv)
|
||
func (a *Agent) handleEmailMove(req agents.Request) agents.Response {
|
||
if len(req.Args) < 2 || req.Args[1] == "" {
|
||
return agents.Response{Text: "❌ Zielordner fehlt. " + buildMoveFoldersHint()}
|
||
}
|
||
dest := req.Args[1]
|
||
|
||
imapFolder, ok := resolveArchiveFolder(dest)
|
||
if !ok {
|
||
return agents.Response{Text: fmt.Sprintf("❌ Unbekannter Ordner `%s`. %s", dest, buildMoveFoldersHint())}
|
||
}
|
||
|
||
accounts := config.AllEmailAccounts()
|
||
if len(accounts) == 0 {
|
||
return agents.Response{Text: "❌ Kein Email-Account konfiguriert."}
|
||
}
|
||
|
||
total := 0
|
||
var errs []string
|
||
for _, acc := range accounts {
|
||
n, err := email.MoveUnread(acc, imapFolder)
|
||
if err != nil {
|
||
errs = append(errs, fmt.Sprintf("%s: %v", acc.Name, err))
|
||
continue
|
||
}
|
||
total += n
|
||
}
|
||
|
||
if len(errs) > 0 && total == 0 {
|
||
return agents.Response{Text: fmt.Sprintf("❌ Verschieben fehlgeschlagen:\n%s", joinLines(errs))}
|
||
}
|
||
if total == 0 {
|
||
return agents.Response{Text: fmt.Sprintf("📭 Keine ungelesenen Emails zum Verschieben nach `%s`.", imapFolder)}
|
||
}
|
||
|
||
msg := fmt.Sprintf("✅ **%d Email(s)** nach `%s` verschoben.", total, imapFolder)
|
||
if len(errs) > 0 {
|
||
msg += "\n⚠️ Fehler:\n" + joinLines(errs)
|
||
}
|
||
return agents.Response{Text: msg}
|
||
}
|
||
|
||
// handleEmailTriage klassifiziert die letzten 10 Emails aller Accounts und verschiebt sie.
|
||
func (a *Agent) handleEmailTriage() agents.Response {
|
||
result, err := email.TriageRecentAllAccounts(10)
|
||
if err != nil {
|
||
return agents.Response{Error: err, Text: fmt.Sprintf("❌ Triage fehlgeschlagen: %v", err)}
|
||
}
|
||
return agents.Response{Text: "🗂️ **Email-Triage (letzte 10 Emails):**\n\n" + result}
|
||
}
|
||
|
||
// ResolveArchiveFolder ist die exportierte Version von resolveArchiveFolder für den Discord-Layer.
|
||
func ResolveArchiveFolder(name string) (imapFolder string, ok bool) {
|
||
return resolveArchiveFolder(name)
|
||
}
|
||
|
||
// resolveArchiveFolder sucht den IMAP-Ordnernamen für einen Anzeigenamen aus der Config.
|
||
// Fallback: Legacy-Hardcoding für 2Jahre/5Jahre/Archiv wenn keine archive_folders konfiguriert.
|
||
func resolveArchiveFolder(name string) (imapFolder string, ok bool) {
|
||
for _, acc := range config.AllEmailAccounts() {
|
||
for _, af := range acc.ArchiveFolders {
|
||
if strings.EqualFold(af.Name, name) || strings.EqualFold(af.IMAPFolder, name) {
|
||
return af.IMAPFolder, true
|
||
}
|
||
}
|
||
}
|
||
// Legacy-Fallback für Konfigurationen ohne archive_folders
|
||
legacy := map[string]string{
|
||
"2jahre": "2Jahre",
|
||
"5jahre": "5Jahre",
|
||
"archiv": "Archiv",
|
||
}
|
||
if canonical, found := legacy[strings.ToLower(name)]; found {
|
||
return canonical, true
|
||
}
|
||
return "", false
|
||
}
|
||
|
||
// buildMoveFoldersHint gibt eine Hinweis-Nachricht mit verfügbaren Archivordnern zurück.
|
||
func buildMoveFoldersHint() string {
|
||
seen := map[string]bool{}
|
||
var names []string
|
||
for _, acc := range config.AllEmailAccounts() {
|
||
for _, af := range acc.ArchiveFolders {
|
||
key := strings.ToLower(af.Name)
|
||
if !seen[key] {
|
||
seen[key] = true
|
||
names = append(names, fmt.Sprintf("`%s`", af.Name))
|
||
}
|
||
}
|
||
}
|
||
if len(names) == 0 {
|
||
return "Verfügbar: `2Jahre`, `5Jahre`, `Archiv`"
|
||
}
|
||
return fmt.Sprintf("Verfügbar: %s", strings.Join(names, ", "))
|
||
}
|
||
|
||
func joinLines(lines []string) string {
|
||
result := ""
|
||
for _, l := range lines {
|
||
result += "• " + l + "\n"
|
||
}
|
||
return result
|
||
}
|