diff --git a/Services/CronjobService.cs b/Services/CronjobService.cs index 6b1cf86..398fea9 100644 --- a/Services/CronjobService.cs +++ b/Services/CronjobService.cs @@ -178,43 +178,9 @@ public class CronjobService : BackgroundService return; } - // API-Key des Cronjob-Owners für Notify-Agent auflösen - var notifyApiKey = await _db.GetAgentApiKeyRecord(job.UserId, notifyCtx.AgentId); - bool notifyUseCliFallback = false; - - if (notifyApiKey == null) - { - // Kein User-Key – Verhalten wie ExecuteCronjob/ChatEndpoint: - // - Anthropic (Provider leer) → Claude-CLI-Fallback (sofern berechtigt/verfügbar) - // - "openai" → abbrechen (kein Server-Key, kein CLI-Äquivalent) - // - nvidia/gemini/minimax/pollinations → durchlassen: Server-Key via ResolveProviderConfig - if (string.IsNullOrEmpty(notifyCtx.Provider)) - { - var notifyOwner = await _db.GetUser(job.UserId); - if ((notifyOwner != null && !notifyOwner.CanUseCli && !notifyOwner.IsAdmin) || !_anthropicService.IsCliAvailable) - { - _logger.LogWarning("Kein API-Key für Agent {AgentId} (Cronjob '{Name}' Notification) und CLI nicht verfügbar/berechtigt", notifyCtx.AgentId, job.Name); - return; - } - - notifyUseCliFallback = true; - } - else if (notifyCtx.Provider == "openai") - { - _logger.LogWarning("Kein API-Key für Agent {AgentId} (Cronjob '{Name}' Notification, OpenAI benötigt zwingend einen Key)", notifyCtx.AgentId, job.Name); - return; - } - // sonst: nvidia/gemini/minimax/pollinations → notifyCtx.ApiToken bleibt leer (Server-Key-Auflösung). - } - else - { - notifyCtx.ApiToken = notifyApiKey.ApiKey; - } - - // Kurze Zusammenfassung als User-Nachricht in die Notify-Session schreiben + // Cronjob-Ergebnis als Nachricht in die Notify-Session schreiben (für die Web-History). var summary = $"[Cronjob \"{job.Name}\" wurde in Session \"{job.SessionName}\" ausgeführt]\n\nAntwort:\n{Truncate(responseText, 2000)}"; - // In Notify-Session als Nachricht speichern await _db.SaveMessage(new MessageRecord { ChainId = notifyCtx.ChainId, @@ -224,31 +190,92 @@ public class CronjobService : BackgroundService Content = summary }); - // Nur die Benachrichtigung als History senden (nicht volle Session-History, - // da diese mit einer assistant-Nachricht enden könnte → prefill-Fehler) - var notifyHistory = new List - { - new Dictionary - { - ["role"] = "user", - ["content"] = new List { new { type = "text", text = summary } } - } - }; - string notifyResponse; - if (!string.IsNullOrEmpty(notifyCtx.Provider)) - notifyResponse = await _openAiService.ChatNonStreaming(notifyCtx, notifyHistory); - else if (notifyUseCliFallback) - notifyResponse = await _anthropicService.ChatNonStreamingViaCli(notifyCtx, notifyHistory); - else - notifyResponse = await _anthropicService.ChatNonStreaming(notifyCtx, notifyHistory); - await _db.UpdateSessionTimestamp(job.NotifySessionId); - // Telegram auf Notify-Session senden - if (!string.IsNullOrEmpty(notifyResponse)) + // ── Nachricht 1 (Pflicht): das echte Cronjob-Ergebnis ────────────────────────── + // Geht IMMER raus, unabhängig vom Notify-Agent / API-Key. Früher lief hier nur der + // Notify-Agent auf der Zusammenfassung; quittierte er bloß mit "👍", erreichte der + // eigentliche Inhalt Telegram nie. Das Ergebnis ist jetzt die garantierte Nachricht. + if (!string.IsNullOrEmpty(responseText)) { await _telegramService.SendNotification(job.NotifySessionId, - $"📋 Cronjob: {job.Name}\n📂 Session: {job.SessionName}\n\n{notifyResponse}"); + $"📋 Cronjob: {job.Name}\n📂 Session: {job.SessionName}\n\n{responseText}"); + } + + // ── Nachricht 2 (optional, best effort): die Reaktion des Notify-Agenten ──────── + // Der Notify-Agent reagiert auf das Ergebnis; seine Antwort geht als SEPARATE + // Telegram-Nachricht raus und wird (über ChatNonStreaming*) automatisch in die + // Web-History persistiert. Schlägt der Lauf fehl (kein Key / OpenAI ohne Key / + // CLI nicht verfügbar), wird die Reaktion still übersprungen – Nachricht 1 ist + // dann längst zugestellt, es geht also nie wieder Information verloren. + try + { + // API-Key des Cronjob-Owners für den Notify-Agenten auflösen (wie ChatEndpoint): + // - Anthropic (Provider leer) → Claude-CLI-Fallback (sofern berechtigt/verfügbar) + // - "openai" → überspringen (kein Server-Key, kein CLI-Äquivalent) + // - nvidia/gemini/minimax/pollinations → durchlassen: Server-Key via ResolveProviderConfig + var notifyApiKey = await _db.GetAgentApiKeyRecord(job.UserId, notifyCtx.AgentId); + bool notifyUseCliFallback = false; + bool canRunNotifyAgent = true; + + if (notifyApiKey == null) + { + if (string.IsNullOrEmpty(notifyCtx.Provider)) + { + var notifyOwner = await _db.GetUser(job.UserId); + if ((notifyOwner != null && !notifyOwner.CanUseCli && !notifyOwner.IsAdmin) || !_anthropicService.IsCliAvailable) + { + _logger.LogWarning("Notify-Agent-Reaktion für Cronjob '{Name}' übersprungen: kein API-Key und CLI nicht verfügbar/berechtigt", job.Name); + canRunNotifyAgent = false; + } + else + { + notifyUseCliFallback = true; + } + } + else if (notifyCtx.Provider == "openai") + { + _logger.LogWarning("Notify-Agent-Reaktion für Cronjob '{Name}' übersprungen: OpenAI benötigt zwingend einen Key", job.Name); + canRunNotifyAgent = false; + } + // sonst: nvidia/gemini/minimax/pollinations → notifyCtx.ApiToken bleibt leer (Server-Key-Auflösung). + } + else + { + notifyCtx.ApiToken = notifyApiKey.ApiKey; + } + + if (canRunNotifyAgent) + { + // Nur die Benachrichtigung als History senden (nicht volle Session-History, + // da diese mit einer assistant-Nachricht enden könnte → prefill-Fehler) + var notifyHistory = new List + { + new Dictionary + { + ["role"] = "user", + ["content"] = new List { new { type = "text", text = summary } } + } + }; + + string notifyResponse; + if (!string.IsNullOrEmpty(notifyCtx.Provider)) + notifyResponse = await _openAiService.ChatNonStreaming(notifyCtx, notifyHistory); + else if (notifyUseCliFallback) + notifyResponse = await _anthropicService.ChatNonStreamingViaCli(notifyCtx, notifyHistory); + else + notifyResponse = await _anthropicService.ChatNonStreaming(notifyCtx, notifyHistory); + + if (!string.IsNullOrEmpty(notifyResponse)) + { + await _telegramService.SendNotification(job.NotifySessionId, $"💬 {notifyResponse}"); + } + } + } + catch (Exception reactEx) + { + // Reaktion ist rein optional – das Ergebnis (Nachricht 1) ist bereits zugestellt. + _logger.LogWarning(reactEx, "Notify-Agent-Reaktion für Cronjob '{Name}' fehlgeschlagen (Ergebnis wurde dennoch gesendet)", job.Name); } } catch (Exception ex) diff --git a/Services/TelegramService.cs b/Services/TelegramService.cs index ebc00f0..bdb37e6 100644 --- a/Services/TelegramService.cs +++ b/Services/TelegramService.cs @@ -280,7 +280,10 @@ public class TelegramService if (bot == null || string.IsNullOrEmpty(bot.BotToken)) return; var chatId = long.Parse(session.TelegramChatId); - await SendMessage(chatId, bot.BotToken, text); + // SendLongMessage statt SendMessage: lange Ergebnisse (z.B. Cronjob-Antworten) werden in + // 4096-Zeichen-Chunks zerlegt. Sonst lehnt die Telegram-API zu lange Texte komplett ab + // (400 → der User bekäme gar nichts). + await SendLongMessage(chatId, bot.BotToken, text); } // ── Telegram Bot API ──