feat: send_to_session tool – agent-to-agent messaging

- Neues Tool send_to_session: Agent kann Nachrichten in andere Sessions schicken
- Ziel-Session per ID oder Name (case-insensitive) auffindbar
- User-Message wird nicht in DB gespeichert (wie Cronjob) – keine Bubble im Frontend
- Nachricht wird als '[Nachricht von Agent "X"]' kontextualisiert injiziert
- Sicherheitscheck: Ziel-Session muss demselben User gehören, keine eigene Session
- Zirkuläre Abhängigkeit ChatService ↔ AnthropicService via SetChatService() gelöst
- tool-ui.js: Display-Name für send_to_session
This commit is contained in:
Meleki committed 2026-03-11 12:56:36 +01:00
1 parent 20d891c9ef
commit c18b94cd4e
5 files changed
+99 -11

No files matched your search

+72 -2
View File
@@ -54,7 +54,7 @@ public class ToolService
return filtered.ToArray();
}
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false, bool includeMemorySearch = false)
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false, bool includeMemorySearch = false, bool includeSendToSession = false)
{
var allTools = new List<object>();
if (includeUserChoice)
@@ -67,9 +67,30 @@ public class ToolService
allTools.Add(GetMemorySearchTool());
if (includeRemote)
allTools.Add(GetRemoteExecuteTool());
if (includeSendToSession)
allTools.Add(GetSendToSessionTool());
return allTools.ToArray();
}
private object GetSendToSessionTool()
{
return new
{
name = "send_to_session",
description = "Schreibt eine Nachricht in eine andere Session und löst dort eine Antwort des Agents aus. Nützlich um Nachrichten, Grüße oder Aufgaben an einen anderen Agenten in einer anderen Session zu übermitteln.",
input_schema = new
{
type = "object",
properties = new Dictionary<string, object>
{
["session_id_or_name"] = new { type = "string", description = "Die ID oder der Name der Ziel-Session" },
["message"] = new { type = "string", description = "Die Nachricht die in die Ziel-Session geschrieben wird" }
},
required = new[] { "session_id_or_name", "message" }
}
};
}
private object GetAskUserChoiceTool()
{
return new
@@ -356,7 +377,7 @@ public class ToolService
};
}
public async Task<string> ExecuteTool(string name, JsonElement input, string workspace, MemoryService memoryService = null, string agentId = null, CancellationToken cancellationToken = default, RemoteConnectionManager remoteMgr = null, string sessionId = null, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
public async Task<string> ExecuteTool(string name, JsonElement input, string workspace, MemoryService memoryService = null, string agentId = null, CancellationToken cancellationToken = default, RemoteConnectionManager remoteMgr = null, string sessionId = null, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null, DatabaseService db = null, ChatService chatService = null, AnthropicService anthropicService = null, string userId = null)
{
switch (name)
{
@@ -706,6 +727,55 @@ public class ToolService
}
return sb.Length > 0 ? sb.ToString() : "(keine Ausgabe)";
}
case "send_to_session":
{
if (db == null || chatService == null || anthropicService == null)
return "Fehler: send_to_session nicht verfügbar";
var sessionIdOrName = input.GetProperty("session_id_or_name").GetString() ?? "";
var message = input.GetProperty("message").GetString() ?? "";
if (string.IsNullOrEmpty(sessionIdOrName))
return "Fehler: session_id_or_name fehlt";
if (string.IsNullOrEmpty(message))
return "Fehler: message fehlt";
// Ziel-Session per ID oder Name suchen
var targetSession = await db.GetSession(sessionIdOrName);
if (targetSession == null && !string.IsNullOrEmpty(userId))
{
// Fallback: per Name suchen (case-insensitive)
var allSessions = await db.GetSessionsWithStats(userId);
targetSession = allSessions
.FirstOrDefault(s => string.Equals(s.Session.Name, sessionIdOrName, StringComparison.OrdinalIgnoreCase))
?.Session;
}
if (targetSession == null)
return $"Fehler: Session '{sessionIdOrName}' nicht gefunden";
if (!string.IsNullOrEmpty(userId) && targetSession.UserId != userId)
return "Fehler: Zugriff auf diese Session nicht erlaubt";
var targetSessionId = targetSession.SessionId;
if (targetSessionId == sessionId)
return "Fehler: Kann keine Nachricht an die eigene Session senden";
// Context für Ziel-Session vorbereiten (analog zu CronjobService)
var ctx = await chatService.PrepareContext(targetSessionId, targetSession.UserId);
if (ctx == null)
return $"Fehler: Session '{targetSession.Name}' ist nicht konfiguriert (kein Agent zugewiesen?)";
// History aufbauen – User-Message NICHT in DB speichern (wie Cronjob)
// Stattdessen als kontextualisierte Nachricht in die History injizieren
var history = await chatService.BuildHistoryFromDb(targetSessionId, promptBlocks: ctx.PromptBlocks);
var senderAgent = agentId != null ? await db.GetAgent(agentId) : null;
var senderName = senderAgent?.Name ?? agentId ?? "Unbekannter Agent";
var senderInfo = $"[Nachricht von Agent \"{senderName}\"]: {message}";
var userContent = new List<object> { new { type = "text", text = senderInfo } };
history.Add(new Dictionary<string, object> { ["role"] = "user", ["content"] = userContent });
await anthropicService.ChatNonStreaming(ctx, history);
await db.UpdateSessionTimestamp(targetSessionId);
return $"Nachricht wurde erfolgreich an Session \"{targetSession.Name}\" gesendet. Der Agent dort hat geantwortet.";
}
default:
return $"Fehler: Unbekanntes Tool: {name}";
}