From 73c6d3825454973d7b61f71cd31413503a8abbaf Mon Sep 17 00:00:00 2001 From: Meleki Date: Fri, 27 Feb 2026 13:34:14 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20custom=20command=20execution=20via=20sys?= =?UTF-8?q?temd-run=20f=C3=BCr=20prozessunabh=C3=A4ngigkeit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Endpoints/ChatEndpoint.cs | 2 +- Endpoints/SessionEndpoint.cs | 70 +++++++++++++++++++++++++++++++- Models/SessionRecord.cs | 1 + Services/DatabaseService.cs | 9 ++-- deploy.sh | 4 +- wwwroot/css/chat.css | 33 +++++++++++++++ wwwroot/css/form.css | 38 +++++++++++++++++ wwwroot/index.html | 10 +++-- wwwroot/js/api.js | 8 ++++ wwwroot/js/pages/chat-page.js | 29 +++++++++++++ wwwroot/js/pages/session-page.js | 50 ++++++++++++++++++++++- 11 files changed, 242 insertions(+), 12 deletions(-) diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 3a69693..f4c96da 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -84,7 +84,7 @@ public static class ChatEndpoint promptParts.Add($"## Instructions\n{agent.Instructions}"); if (!string.IsNullOrWhiteSpace(session.Workspace)) - promptParts.Add($"## Workspace\n{session.Workspace}"); + promptParts.Add("## Tools\nDu hast Tools zur Verfügung (read_file, write_file, edit_file, list_files, delete_file, execute). Benutze sie um Dateien zu lesen, schreiben und bearbeiten. Beschreibe NICHT was du tun würdest - tu es einfach über die Tools."); if (!string.IsNullOrWhiteSpace(session.Context)) promptParts.Add($"## Kontext\n{session.Context}"); diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index 8b7ff38..0607047 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -19,6 +21,7 @@ public static class SessionEndpoint app.MapPut("/api/sessions/{sessionId}", HandleUpdate); app.MapDelete("/api/sessions/{sessionId}", HandleDelete); app.MapGet("/api/sessions/{sessionId}/system-prompt", HandleSystemPrompt); + app.MapPost("/api/sessions/{sessionId}/exec", HandleExec); } private static async Task HandleList(HttpContext ctx, DatabaseService db) @@ -54,8 +57,9 @@ public static class SessionEndpoint var agentId = body.TryGetProperty("agentId", out var a) ? a.GetString() : null; var workspace = body.TryGetProperty("workspace", out var w) ? w.GetString() : null; var context = body.TryGetProperty("context", out var ctxEl) ? ctxEl.GetString() : null; + var customCommands = body.TryGetProperty("customCommands", out var cc) && cc.ValueKind != JsonValueKind.Null ? cc.GetRawText() : null; - var session = await db.CreateSession(name, description, image, agentId, workspace, context); + var session = await db.CreateSession(name, description, image, agentId, workspace, context, customCommands); await ctx.Response.WriteAsJsonAsync(session); } @@ -122,7 +126,7 @@ public static class SessionEndpoint if (!string.IsNullOrWhiteSpace(agent.Instructions)) promptParts.Add($"## Instructions\n{agent.Instructions}"); if (!string.IsNullOrWhiteSpace(session.Workspace)) - promptParts.Add($"## Workspace\n{session.Workspace}"); + promptParts.Add("## Tools\nDu hast Tools zur Verfügung (read_file, write_file, edit_file, list_files, delete_file, execute). Benutze sie um Dateien zu lesen, schreiben und bearbeiten. Beschreibe NICHT was du tun würdest - tu es einfach über die Tools."); if (!string.IsNullOrWhiteSpace(session.Context)) promptParts.Add($"## Kontext\n{session.Context}"); @@ -131,4 +135,66 @@ public static class SessionEndpoint ctx.Response.ContentType = "text/plain; charset=utf-8"; await ctx.Response.WriteAsync(systemPrompt); } + + private static async Task HandleExec(HttpContext ctx, DatabaseService db) + { + var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); + if (string.IsNullOrEmpty(sessionId)) + { + ctx.Response.StatusCode = 400; + return; + } + + var session = await db.GetSession(sessionId); + if (session == null) + { + ctx.Response.StatusCode = 404; + return; + } + + var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); + var command = body.TryGetProperty("command", out var c) ? c.GetString() : null; + + if (string.IsNullOrWhiteSpace(command)) + { + ctx.Response.StatusCode = 400; + await ctx.Response.WriteAsJsonAsync(new { error = "Kein Befehl angegeben" }); + return; + } + + var workingDir = !string.IsNullOrWhiteSpace(session.Workspace) ? session.Workspace : "/tmp"; + + try + { + var scriptPath = $"/tmp/gott-exec-{Guid.NewGuid():N}.sh"; + await System.IO.File.WriteAllTextAsync(scriptPath, $"#!/bin/bash\ncd {workingDir}\n{command}\nrm -f {scriptPath}\n"); + + var psi = new ProcessStartInfo + { + FileName = "systemd-run", + Arguments = $"--scope --quiet bash {scriptPath}", + WorkingDirectory = workingDir, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + var process = Process.Start(psi); + if (process != null) + { + process.WaitForExit(5000); + } + + ctx.Response.StatusCode = 202; + await ctx.Response.WriteAsJsonAsync(new { status = "started", command = command }); + } + catch (Exception ex) + { + ctx.Response.StatusCode = 500; + await ctx.Response.WriteAsJsonAsync(new { error = ex.Message }); + } + } + + } diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index 1f19119..b5a94db 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -10,6 +10,7 @@ public class SessionRecord public string AgentId { get; set; } public string Workspace { get; set; } public string Context { get; set; } + public string CustomCommands { get; set; } public bool IsDeleted { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 37720e3..5c187ff 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -120,6 +120,7 @@ public class DatabaseService TryAddColumn(conn, "sessions", "workspace", "TEXT"); TryAddColumn(conn, "sessions", "context", "TEXT"); TryAddColumn(conn, "sessions", "is_deleted", "INTEGER NOT NULL DEFAULT 0"); + TryAddColumn(conn, "sessions", "custom_commands", "TEXT"); TryAddColumn(conn, "chains", "agent_id", "TEXT"); TryAddColumn(conn, "messages", "attachment_count", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "messages", "attachment_names", "TEXT"); @@ -182,7 +183,7 @@ public class DatabaseService // ── Sessions ── - public async Task CreateSession(string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null) + public async Task CreateSession(string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null) { using var conn = CreateConnection(); var now = DateTime.UtcNow.ToString("o"); @@ -195,12 +196,13 @@ public class DatabaseService AgentId = agentId, Workspace = workspace, Context = context, + CustomCommands = customCommands, CreatedAt = now, UpdatedAt = now }; await conn.ExecuteAsync(@" - INSERT INTO sessions (session_id, name, description, image, agent_id, workspace, context, created_at, updated_at) - VALUES (@SessionId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CreatedAt, @UpdatedAt) + INSERT INTO sessions (session_id, name, description, image, agent_id, workspace, context, custom_commands, created_at, updated_at) + VALUES (@SessionId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @CreatedAt, @UpdatedAt) ", session); return session; } @@ -241,6 +243,7 @@ public class DatabaseService if (body.TryGetProperty("agentId", out var a)) { sets.Add("agent_id = @agentId2"); p.Add("agentId2", a.GetString()); } if (body.TryGetProperty("workspace", out var w)) { sets.Add("workspace = @workspace"); p.Add("workspace", w.GetString()); } if (body.TryGetProperty("context", out var ctx)) { sets.Add("context = @context"); p.Add("context", ctx.GetString()); } + if (body.TryGetProperty("customCommands", out var cc)) { sets.Add("custom_commands = @customCommands"); p.Add("customCommands", cc.ValueKind == System.Text.Json.JsonValueKind.Null ? null : cc.GetRawText()); } await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p); } diff --git a/deploy.sh b/deploy.sh index 41c21e4..ab938c7 100755 --- a/deploy.sh +++ b/deploy.sh @@ -7,7 +7,7 @@ _hostname=$(hostname) if [ "$_hostname" = "stein2" ]; then cd /home/node/.openclaw/workspace/projects/gott systemctl stop gott -elif [ "$_hostname" = "meleki" ]; then +elif [ "$_hostname" = "meleki-container" ]; then cd ~/.openclaw/workspace/projects/gott else echo "$_hostname: deployment not allowed" @@ -21,7 +21,7 @@ cp wwwroot/index.html publish/wwwroot/index.html if [ "$_hostname" = "stein2" ]; then systemctl start gott -elif [ "$_hostname" = "meleki" ]; then +elif [ "$_hostname" = "meleki-container" ]; then ssh -o ConnectTimeout=5 meleki@192.168.100.200 "nohup sudo systemctl restart gott > /dev/null 2>&1 &" fi diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 52c0c19..99a770d 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -1,3 +1,36 @@ +/* Custom Command Buttons */ + +.custom-cmd-bar { + display: flex; + gap: var(--s2); + margin-left: var(--s3); + flex-wrap: wrap; + align-items: center; +} + +.custom-cmd-bar:empty { + display: none; +} + +.custom-cmd-btn { + padding: var(--s1) var(--s2); + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); + white-space: nowrap; + cursor: pointer; + font-size: 0.85em; +} + +.custom-cmd-btn:hover { + background: var(--f3-bg); + border-color: var(--f3-border); +} + +.custom-cmd-btn.running { + opacity: 0.5; + cursor: wait; +} + /* Header Menu */ .header-menu { diff --git a/wwwroot/css/form.css b/wwwroot/css/form.css index 0f50ca8..e85f097 100644 --- a/wwwroot/css/form.css +++ b/wwwroot/css/form.css @@ -52,6 +52,44 @@ margin-top: var(--s4); } +/* ── Custom Commands ── */ + +.custom-commands { + display: flex; + flex-direction: column; + gap: var(--s3); +} + +.custom-command-item { + display: flex; + gap: var(--s2); + align-items: start; +} + +.custom-command-fields { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--s2); +} + +.custom-command-item .remove-cmd-btn { + flex-shrink: 0; + color: #ff6b6b; + border-color: #ff6b6b; + padding: var(--s2) var(--s3); + align-self: center; +} + +.custom-command-item .remove-cmd-btn:hover { + background: #ff6b6b; + color: var(--f1-bg); +} + +.add-cmd-btn { + align-self: flex-start; +} + .danger-btn { color: #ff6b6b; border-color: #ff6b6b; diff --git a/wwwroot/index.html b/wwwroot/index.html index c6232ad..00fbf87 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -66,13 +66,16 @@ - -
+ + + +
+
@@ -106,7 +109,7 @@ - +
@@ -122,6 +125,7 @@

-

+