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:
1 parent
9f4746c2e7
commit
10da2bd964
14 files changed
+706
-18
No files matched your search
@@ -629,6 +629,31 @@ async function apiImportOpenclawSession(openclawSessionId, sessionId) {
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// ── Remote API ──
|
||||
|
||||
async function apiGetRemoteConnections() {
|
||||
const resp = await fetch('/api/remote/connections');
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiAssignRemote(connectionId, sessionId) {
|
||||
const resp = await fetch('/api/remote/assign', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ connectionId, sessionId })
|
||||
});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiUnassignRemote(connectionId) {
|
||||
const resp = await fetch('/api/remote/unassign', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ connectionId })
|
||||
});
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// ── Backup API ──
|
||||
|
||||
async function apiBackupDb() {
|
||||
|
||||
@@ -292,6 +292,104 @@ async function showOpenClawImportModal(sessionId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Remote-Verbindungen Modal ──
|
||||
|
||||
async function showRemoteModal(sessionId) {
|
||||
const { body, close } = _createModal('Remote-Verbindungen');
|
||||
|
||||
body.innerHTML = '<div class="sp-loading">Laden...</div>';
|
||||
|
||||
async function renderConnections() {
|
||||
try {
|
||||
const connections = await apiGetRemoteConnections();
|
||||
body.innerHTML = '';
|
||||
|
||||
if (!connections || connections.length === 0) {
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'sp-loading';
|
||||
empty.innerHTML = 'Keine Remote-Verbindungen aktiv.<br><br>' +
|
||||
'<span style="font-size:var(--font-size);opacity:var(--semi-transparent)">Auf dem Windows-PC ausführen:<br>' +
|
||||
'<code>irm ' + esc(location.origin) + '/remote.ps1 | iex</code></span>';
|
||||
body.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
var hint = document.createElement('div');
|
||||
hint.style.cssText = 'font-size:var(--font-size);opacity:var(--semi-transparent);margin-bottom:var(--box-spacing)';
|
||||
hint.textContent = 'Verbindung auswählen um Remote-Befehle in dieser Session zu ermöglichen:';
|
||||
body.appendChild(hint);
|
||||
|
||||
for (var i = 0; i < connections.length; i++) {
|
||||
var conn = connections[i];
|
||||
var card = document.createElement('div');
|
||||
card.className = 'sp-block';
|
||||
card.style.cursor = 'pointer';
|
||||
|
||||
var isAssigned = conn.assignedSessionId === sessionId;
|
||||
var isAssignedOther = conn.assignedSessionId && conn.assignedSessionId !== sessionId;
|
||||
|
||||
var label = document.createElement('div');
|
||||
label.className = 'sp-block-label';
|
||||
var statusIcon = isAssigned ? 'fa-check-circle' : (isAssignedOther ? 'fa-link' : 'fa-desktop');
|
||||
var statusColor = isAssigned ? 'color:var(--primary)' : '';
|
||||
label.innerHTML = '<i class="fa ' + statusIcon + '" style="' + statusColor + '"></i> ' +
|
||||
esc(conn.username || '?') + '@' + esc(conn.hostname || '?');
|
||||
card.appendChild(label);
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.className = 'sp-block-content';
|
||||
content.style.cssText = 'font-size:var(--font-size);display:flex;align-items:center;gap:var(--box-spacing)';
|
||||
|
||||
var info = document.createElement('span');
|
||||
info.style.cssText = 'flex:1;opacity:var(--semi-transparent)';
|
||||
var connDate = new Date(conn.connectedAt);
|
||||
info.textContent = conn.os + ' • Verbunden seit ' + connDate.toLocaleTimeString('de');
|
||||
content.appendChild(info);
|
||||
|
||||
var btn = document.createElement('button');
|
||||
if (isAssigned) {
|
||||
btn.textContent = 'Trennen';
|
||||
btn.style.cssText = 'color:var(--dangerous)';
|
||||
} else if (isAssignedOther) {
|
||||
btn.textContent = 'Andere Session';
|
||||
btn.disabled = true;
|
||||
btn.style.cssText = 'opacity:var(--semi-transparent)';
|
||||
} else {
|
||||
btn.textContent = 'Verbinden';
|
||||
btn.style.cssText = 'color:var(--primary)';
|
||||
}
|
||||
content.appendChild(btn);
|
||||
|
||||
card.appendChild(content);
|
||||
body.appendChild(card);
|
||||
|
||||
// Event-Handler
|
||||
(function(c, assigned) {
|
||||
btn.addEventListener('click', async function(e) {
|
||||
e.stopPropagation();
|
||||
btn.disabled = true;
|
||||
try {
|
||||
if (assigned) {
|
||||
await apiUnassignRemote(c.id);
|
||||
} else {
|
||||
await apiAssignRemote(c.id, sessionId);
|
||||
}
|
||||
await renderConnections();
|
||||
} catch(err) {
|
||||
showToast('Fehler: ' + err.message, 'error');
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
})(conn, isAssigned);
|
||||
}
|
||||
} catch (e) {
|
||||
body.innerHTML = '<div class="sp-loading" style="color:var(--dangerous)">Fehler beim Laden: ' + esc(e.message) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
await renderConnections();
|
||||
}
|
||||
|
||||
// Event-Delegation für Krea-Thumbnails im Chat
|
||||
document.addEventListener('click', function(e) {
|
||||
var ref = e.target.closest('.krea-ref');
|
||||
|
||||
@@ -169,6 +169,13 @@ registerPage('chat', {
|
||||
});
|
||||
}
|
||||
|
||||
// Remote-Verbindungen
|
||||
const remoteBtn = document.getElementById('remoteBtn');
|
||||
if (remoteBtn) remoteBtn.addEventListener('click', () => {
|
||||
if (chatMenuDropdown) chatMenuDropdown.hidden = true;
|
||||
showRemoteModal(sessionId);
|
||||
});
|
||||
|
||||
// System-Prompt anzeigen
|
||||
const systemPromptBtn = document.getElementById('systemPromptBtn');
|
||||
if (systemPromptBtn) systemPromptBtn.addEventListener('click', () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// ── Tool Call Visibility ──
|
||||
|
||||
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write'];
|
||||
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write', 'remote_execute'];
|
||||
const TOOL_DISPLAY_NAMES = {
|
||||
write_file: 'Datei schreiben',
|
||||
edit_file: 'Datei bearbeiten',
|
||||
delete_file: 'Datei löschen',
|
||||
memory_write: 'Erinnerungen festhalten',
|
||||
web_search: 'Web-Suche'
|
||||
web_search: 'Web-Suche',
|
||||
remote_execute: 'Remote-Befehl'
|
||||
};
|
||||
|
||||
function isToolVisible(toolName) {
|
||||
|
||||
Reference in new issue
Block a user