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

+11 -7
View File
@@ -53,7 +53,8 @@ public class AnthropicService
bool planMode = false,
CancellationToken workCt = default,
CancellationToken hardCt = default,
CancellationToken sseCt = default)
CancellationToken sseCt = default,
RemoteConnectionManager remoteMgr = null)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var usage = new TokenUsage();
@@ -90,7 +91,8 @@ public class AnthropicService
if (systemPrompt != null && systemPrompt.Count > 0)
payload["system"] = systemPrompt;
AddToolsToPayload(payload, workspace, agentId, useMemory, webSearchVersion, toolChoice, iterations, allowUserChoice, planMode);
var hasRemote = remoteMgr?.GetBySessionId(sessionId) != null;
AddToolsToPayload(payload, workspace, agentId, useMemory, webSearchVersion, toolChoice, iterations, allowUserChoice, planMode, includeRemote: hasRemote);
var client = CreateClient();
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
@@ -136,7 +138,7 @@ public class AnthropicService
return;
}
var (stopReason, assistantContent, executedTools, streamErrorInfo) = await ProcessStream(response, httpResp, usage, workspace, agentId, workCt, sseCt);
var (stopReason, assistantContent, executedTools, streamErrorInfo) = await ProcessStream(response, httpResp, usage, workspace, agentId, workCt, sseCt, remoteMgr, sessionId);
lastStopReason = stopReason;
promptTrimRetries = 0; // Erfolgreicher API-Call → Retry-Counter resetten
@@ -1153,7 +1155,9 @@ public class AnthropicService
string workspace,
string agentId = null,
CancellationToken workCt = default,
CancellationToken sseCt = default)
CancellationToken sseCt = default,
RemoteConnectionManager remoteMgr = null,
string sessionId = null)
{
var stream = await httpResp.Content.ReadAsStreamAsync(workCt);
// Bei Cancellation den Stream disposen, damit ReadLineAsync sofort abbricht.
@@ -1335,7 +1339,7 @@ public class AnthropicService
string toolResult;
bool isError = false;
try { toolResult = await _toolService.ExecuteTool(currentToolName, inputEl, workspace, _memoryService, agentId, workCt); }
try { toolResult = await _toolService.ExecuteTool(currentToolName, inputEl, workspace, _memoryService, agentId, workCt, remoteMgr: remoteMgr, sessionId: sessionId); }
catch (OperationCanceledException) { throw; }
catch (Exception ex) { toolResult = $"Fehler: {ex.Message}"; isError = true; }
@@ -1508,7 +1512,7 @@ public class AnthropicService
/// <summary>
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
/// </summary>
private void AddToolsToPayload(Dictionary<string, object> payload, string workspace, string agentId, bool useMemory, string webSearchVersion, string toolChoice, int iteration, bool allowUserChoice = false, bool planMode = false)
private void AddToolsToPayload(Dictionary<string, object> payload, string workspace, string agentId, bool useMemory, string webSearchVersion, string toolChoice, int iteration, bool allowUserChoice = false, bool planMode = false, bool includeRemote = false)
{
var hasWorkspace = !string.IsNullOrEmpty(workspace);
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
@@ -1520,7 +1524,7 @@ public class AnthropicService
var tools = new List<object>();
if (hasTools)
{
var allTools = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: allowUserChoice);
var allTools = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: allowUserChoice, includeRemote: includeRemote);
if (planMode)
allTools = _toolService.FilterReadOnlyTools(allTools);
tools.AddRange(allTools);