From 78bf7bd68bc5d98fc1e3fa863e58d03a0ff5ed42 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 15 Feb 2026 00:00:16 -0500 Subject: [PATCH] chore: auto-commit uncommitted changes --- config.yaml | 2 +- triage.go | 69 ++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/config.yaml b/config.yaml index 0589295..d8eb893 100644 --- a/config.yaml +++ b/config.yaml @@ -56,4 +56,4 @@ triage: base_url: https://api.fireworks.ai/inference/v1 api_key: ${FIREWORKS_API_KEY} model: accounts/fireworks/models/kimi-k2p5 - dashboard_url: http://localhost:9200/api/news + # dashboard_url removed — routing handled by OpenClaw via webhook diff --git a/triage.go b/triage.go index e809880..e8f2fc5 100644 --- a/triage.go +++ b/triage.go @@ -28,10 +28,20 @@ type ProviderConfig struct { // TriageResult is the expected LLM response type TriageResult struct { - Action string `json:"action"` // trash, archive, keep, escalate + Action string `json:"action"` // junk, pass Reason string `json:"reason"` } +// triageLogEntry is a structured log entry for triage decisions +type triageLogEntry struct { + Ts string `json:"ts"` + ID string `json:"id"` + From string `json:"from"` + Subject string `json:"subject"` + Action string `json:"action"` + Reason string `json:"reason"` +} + // triageEmail runs LLM triage on an email message. // Returns the triage result, or nil if triage should be skipped (fall through to webhook). func triageEmail(msg UnifiedMessage, cfg TriageConfig) *TriageResult { @@ -154,7 +164,7 @@ func callLLM(provider ProviderConfig, systemPrompt, userMessage string) (*Triage // Validate action switch result.Action { - case "trash", "archive", "keep", "escalate": + case "junk", "pass": // valid default: return nil, fmt.Errorf("unknown triage action: %q", result.Action) @@ -166,23 +176,13 @@ func callLLM(provider ProviderConfig, systemPrompt, userMessage string) (*Triage // executeTriageAction performs the action decided by the LLM func executeTriageAction(msg UnifiedMessage, result TriageResult) error { switch result.Action { - case "trash": + case "junk": if err := store.Delete(msg.ID); err != nil { - return fmt.Errorf("trash: %w", err) + return fmt.Errorf("junk delete: %w", err) } orch.RecordAction(msg.ID, "delete") - case "archive": - if err := store.Archive(msg.ID); err != nil { - return fmt.Errorf("archive: %w", err) - } - orch.RecordAction(msg.ID, "archive") - case "keep": - if err := store.MarkSeen(msg.ID); err != nil { - return fmt.Errorf("mark seen: %w", err) - } - orch.RecordAction(msg.ID, "seen") - case "escalate": - // Record as seen but don't action — webhook will fire + case "pass": + // Leave for webhook — record as seen so it shows in /messages/new for the agent source, sourceID, _ := parseMessageID(msg.ID) orch.RecordSeen(msg.ID, source, sourceID, "INBOX") } @@ -190,6 +190,40 @@ func executeTriageAction(msg UnifiedMessage, result TriageResult) error { return nil } +// logTriageDecision appends a structured log entry to the triage log file +func logTriageDecision(msg UnifiedMessage, result TriageResult) { + logPath := os.ExpandEnv("$HOME") + "/.message-center/triage-log.jsonl" + + sender := msg.From + if msg.FromName != "" { + sender = msg.FromName + " <" + msg.From + ">" + } + + entry := triageLogEntry{ + Ts: time.Now().UTC().Format(time.RFC3339), + ID: msg.ID, + From: sender, + Subject: msg.Subject, + Action: result.Action, + Reason: result.Reason, + } + + data, err := json.Marshal(entry) + if err != nil { + log.Printf("[triage] Failed to marshal log entry: %v", err) + return + } + + f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Printf("[triage] Failed to open triage log %s: %v", logPath, err) + return + } + defer f.Close() + + f.Write(append(data, '\n')) +} + // logTriageToDashboard posts a log entry to the alert dashboard func logTriageToDashboard(dashboardURL string, msg UnifiedMessage, result TriageResult) { if dashboardURL == "" { @@ -270,9 +304,10 @@ func triageNewEmails(accountName string, cfg TriageConfig) bool { continue } + logTriageDecision(msg, *result) logTriageToDashboard(cfg.DashboardURL, msg, *result) - if result.Action == "escalate" { + if result.Action == "pass" { shouldEscalate = true } }