feat: Remote-Verbindungen – Windows-Workstations per WebSocket steuern

- RemoteConnectionManager: Singleton-Service verwaltet WebSocket-Verbindungen
  mit Command-Execution (Send/Receive mit Timeout + SemaphoreSlim-Locking)
- RemoteEndpoint: WebSocket-Endpoint /api/remote (auth-frei), REST-APIs für
  Connections/Assign/Unassign, PowerShell-Script-Auslieferung mit dynamischer URL
- remote.ps1: PowerShell-Client für Windows – verbindet per WebSocket,
  sendet Hostname/User/OS, empfängt und führt Befehle aus, Heartbeat
- remote_execute Tool: Neues Agent-Tool, nur verfügbar wenn Session eine
  zugeordnete Remote-Verbindung hat, führt PowerShell-Befehle remote aus
- Frontend: Menüpunkt 'Remote-Verbindungen' im Chat-Dropdown, Modal zeigt
  alle aktiven WebSocket-Verbindungen (Hostname, User, OS, Verbindungszeit),
  Verbinden/Trennen-Buttons pro Verbindung und Session
- Auth: /api/remote und /remote.ps1 von Authentifizierung ausgenommen

Architektur: Windows-Client → WebSocket → GOTT-Server → Agent-Tool
Session-gebunden: Eine Verbindung wird einer Session zugeordnet
This commit is contained in:
Meleki committed 2026-03-11 07:04:24 +01:00
1 parent 9f4746c2e7
commit 10da2bd964
14 files changed
+706 -18

No files matched your search

+47 -3
View File
@@ -34,7 +34,7 @@ public class ToolService
{
"read_file", "list_files", "glob", "grep", "execute", "todo_write",
"memory_read", "memory_write", "memory_list",
AskUserChoiceToolName
"remote_execute", AskUserChoiceToolName
};
public object[] FilterReadOnlyTools(object[] tools)
@@ -54,7 +54,7 @@ public class ToolService
return filtered.ToArray();
}
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true)
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false)
{
var allTools = new List<object>();
if (includeUserChoice)
@@ -63,6 +63,8 @@ public class ToolService
allTools.AddRange(GetFileTools());
if (includeMemory)
allTools.AddRange(GetMemoryToolDefinitions());
if (includeRemote)
allTools.Add(GetRemoteExecuteTool());
return allTools.ToArray();
}
@@ -262,6 +264,24 @@ public class ToolService
};
}
private object GetRemoteExecuteTool()
{
return new
{
name = "remote_execute",
description = "Führt einen PowerShell-Befehl auf der verbundenen Remote-Windows-Workstation aus. Gibt stdout und stderr zurück. Nutze dies für Diagnose und Administration des Remote-PCs (z.B. Druckerprobleme, Netzwerk, Services, Registry). Timeout: 180 Sekunden.",
input_schema = new
{
type = "object",
properties = new Dictionary<string, object>
{
["command"] = new { type = "string", description = "PowerShell-Befehl zum Ausführen auf dem Remote-PC" }
},
required = new[] { "command" }
}
};
}
private object[] GetMemoryToolDefinitions()
{
return new object[]
@@ -313,7 +333,7 @@ public class ToolService
};
}
public async Task<string> ExecuteTool(string name, JsonElement input, string workspace, MemoryService memoryService = null, string agentId = null, CancellationToken cancellationToken = default)
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)
{
switch (name)
{
@@ -597,6 +617,30 @@ public class ToolService
var files = memoryService.ListMemoryFiles(agentId, subPath);
return files.Count == 0 ? "(keine Memory-Dateien vorhanden)" : string.Join("\n", files);
}
case "remote_execute":
{
if (remoteMgr == null || string.IsNullOrEmpty(sessionId))
return "Fehler: Keine Remote-Verbindung verfügbar";
var conn = remoteMgr.GetBySessionId(sessionId);
if (conn == null)
return "Fehler: Keine Remote-Verbindung für diese Session zugeordnet";
var command = input.GetProperty("command").GetString() ?? "";
var result = await remoteMgr.ExecuteCommand(conn.Id, command, ExecuteTimeoutSeconds, cancellationToken);
var sb = new StringBuilder();
if (!string.IsNullOrEmpty(result.Output))
sb.Append(result.Output);
if (!string.IsNullOrEmpty(result.Error))
{
if (sb.Length > 0) sb.AppendLine();
sb.Append("[stderr] ").Append(result.Error);
}
if (result.ExitCode != 0)
{
if (sb.Length > 0) sb.AppendLine();
sb.Append($"[Exit Code: {result.ExitCode}]");
}
return sb.Length > 0 ? sb.ToString() : "(keine Ausgabe)";
}
default:
return $"Fehler: Unbekanntes Tool: {name}";
}