From 8e0ae90dc62707fcc50e074719c50ad7330ad54c Mon Sep 17 00:00:00 2001 From: Meleki Date: Sat, 7 Mar 2026 12:20:44 +0100 Subject: [PATCH] =?UTF-8?q?Plan-Modus:=20System-Prompt-Bl=C3=B6cke=20konte?= =?UTF-8?q?xtabh=C3=A4ngig=20anpassen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SystemPromptBuilder.BuildBlocks/Build bekommt planMode-Parameter - Im Plan-Modus: Tools-Block zeigt nur Read-only-Tools (read_file, list_files, glob, grep) - Im Plan-Modus: KRITISCH, Verhalten, Erinnerung und Git-Blöcke werden übersprungen - Im Plan-Modus: Skills als 'nicht ausführbar' markiert - Plan-Modus-Block zentral im SystemPromptBuilder statt doppelt in AnthropicService + SessionEndpoint - ChatService reicht planMode an SystemPromptBuilder durch - SessionEndpoint reicht planMode Query-Parameter an BuildBlocks durch - System-Prompt-Modal zeigt korrekte Blöcke je nach Plan-Modus-Status --- Endpoints/SessionEndpoint.cs | 3 +- Services/AnthropicService.cs | 14 -------- Services/ChatService.cs | 2 +- Services/SystemPromptBuilder.cs | 61 ++++++++++++++++++++++++++------- wwwroot/js/api.js | 5 +-- wwwroot/js/ui.js | 2 +- 6 files changed, 55 insertions(+), 32 deletions(-) diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index a10f55e..9b469c8 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -214,7 +214,8 @@ public static class SessionEndpoint var resolvedWorkspace = WorkspaceHelper.ResolveWorkspace(session, user); if (string.IsNullOrEmpty(resolvedWorkspace) && agentSkills.Any()) resolvedWorkspace = "/tmp"; - var blocks = SystemPromptBuilder.BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, agentSkills, skillService, userPersona: user?.GetEffectivePersona(), resolvedWorkspace: resolvedWorkspace); + var planMode = ctx.Request.Query.ContainsKey("planMode") && ctx.Request.Query["planMode"] == "true"; + var blocks = SystemPromptBuilder.BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, agentSkills, skillService, userPersona: user?.GetEffectivePersona(), resolvedWorkspace: resolvedWorkspace, planMode: planMode); await ctx.Response.WriteAsJsonAsync(blocks.Select(b => new { b.Icon, b.Label, b.Content })); } diff --git a/Services/AnthropicService.cs b/Services/AnthropicService.cs index c84afb0..1b57550 100644 --- a/Services/AnthropicService.cs +++ b/Services/AnthropicService.cs @@ -59,20 +59,6 @@ public class AnthropicService int reviewRound = 0; string lastStopReason = ""; - // Plan-Modus: System-Prompt-Ergänzung - if (planMode && systemPrompt != null) - { - systemPrompt.Add(new { type = "text", text = @"## Plan-Modus aktiv -Der User hat den Plan-Modus aktiviert. Du DARFST KEINE Änderungen vornehmen: -- KEINE Dateien erstellen, bearbeiten oder löschen -- KEINE Shell-Befehle ausführen die Änderungen vornehmen (Commits, Installationen etc.) -- NUR lesen, analysieren, suchen und planen -- Nutze read_file, list_files, glob, grep und web_search um den Codebase zu verstehen -- Nutze ask_user_choice um Rückfragen zu stellen und Anforderungen zu klären -- Erstelle einen strukturierten Plan mit Aufgaben, Abhängigkeiten und Reihenfolge -- Der User wird den Plan-Modus deaktivieren wenn du mit der Umsetzung beginnen sollst" }); - } - var chain = new ChainRecord { ChainId = chainId, diff --git a/Services/ChatService.cs b/Services/ChatService.cs index 39d64de..385b04f 100644 --- a/Services/ChatService.cs +++ b/Services/ChatService.cs @@ -66,7 +66,7 @@ public class ChatService var systemPromptText = SystemPromptBuilder.Build( agent, session, markedMessages, _memoryService, agentMemoryDir, agentSkills, _skillService, userPersona: user?.GetEffectivePersona(), - resolvedWorkspace: workspace); + resolvedWorkspace: workspace, planMode: planMode); var toolChoice = agent.ToolChoice ?? "any"; var hasWorkspace = !string.IsNullOrEmpty(workspace); diff --git a/Services/SystemPromptBuilder.cs b/Services/SystemPromptBuilder.cs index 6d64dc1..5092733 100644 --- a/Services/SystemPromptBuilder.cs +++ b/Services/SystemPromptBuilder.cs @@ -13,7 +13,7 @@ public class PromptBlock public static class SystemPromptBuilder { - public static List BuildBlocks(AgentRecord agent, SessionRecord session, IEnumerable markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable skillSlugs = null, SkillService skillService = null, string userPersona = null, string resolvedWorkspace = null) + public static List BuildBlocks(AgentRecord agent, SessionRecord session, IEnumerable markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable skillSlugs = null, SkillService skillService = null, string userPersona = null, string resolvedWorkspace = null, bool planMode = false) { var blocks = new List(); @@ -50,7 +50,7 @@ public static class SystemPromptBuilder var effectiveWorkspace = resolvedWorkspace ?? session.Workspace; if (!string.IsNullOrWhiteSpace(effectiveWorkspace)) { - if (toolAny) + if (toolAny && !planMode) { blocks.Add(new PromptBlock { @@ -61,16 +61,32 @@ JEDE Datei-Operation und JEDER Shell-Befehl MUSS über einen Tool-Aufruf erfolge }); } - blocks.Add(new PromptBlock + if (planMode) { - Icon = "fa-wrench", - Label = "Session · Workspace", - Content = @$"## Tools + blocks.Add(new PromptBlock + { + Icon = "fa-wrench", + Label = "Session · Workspace (Plan-Modus)", + Content = @$"## Tools +Du hast Read-only-Tools zur Verfügung (read_file, list_files, glob, grep). Benutze sie um den Code zu analysieren und zu verstehen. + +Der Workspace-Pfad ist: `{effectiveWorkspace}` +Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder absolute Pfade verwenden, die mit diesem Pfad beginnen." + }); + } + else + { + blocks.Add(new PromptBlock + { + Icon = "fa-wrench", + Label = "Session · Workspace", + Content = @$"## Tools Du 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. Der Workspace-Pfad ist: `{effectiveWorkspace}` Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder absolute Pfade verwenden, die mit diesem Pfad beginnen." - }); + }); + } } // Rückfragen-Hinweis @@ -113,7 +129,10 @@ Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder a skillNames.Add(skill.Name ?? slug); } var skillLabel = "Agent · Skills: " + string.Join(", ", skillNames); - var skillSection = "## Skills\nDu hast folgende Skills zur Verfügung. Nutze sie über das `execute`-Tool wenn der User etwas anfragt, das ein Skill abdeckt.\n\n" + var skillIntro = planMode + ? "## Skills\nFolgende Skills sind verfügbar (im Plan-Modus nur zur Kenntnis – nicht ausführbar):\n\n" + : "## Skills\nDu hast folgende Skills zur Verfügung. Nutze sie über das `execute`-Tool wenn der User etwas anfragt, das ein Skill abdeckt.\n\n"; + var skillSection = skillIntro + string.Join("\n\n---\n\n", skillParts); blocks.Add(new PromptBlock { Icon = "fa-puzzle-piece", Label = skillLabel, Content = skillSection }); } @@ -148,8 +167,8 @@ Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder a blocks.Add(new PromptBlock { Icon = "fa-database", Label = "Agent · Memory (UseMemory)", Content = string.Join("\n", memoryLines) }); } - // Git-Commit-Verhalten: relevant sobald Workspace vorhanden (unabhängig von ToolChoice) - if (!string.IsNullOrWhiteSpace(effectiveWorkspace)) + // Git-Commit-Verhalten: relevant sobald Workspace vorhanden (nicht im Plan-Modus) + if (!string.IsNullOrWhiteSpace(effectiveWorkspace) && !planMode) { var autoCommit = (agent.AutoCommit ?? "").ToLowerInvariant(); if (autoCommit == "mention") @@ -182,7 +201,7 @@ Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder a // autoCommit == "" → kein Block, Thema wird nicht erwähnt } - if (toolAny && !string.IsNullOrWhiteSpace(effectiveWorkspace)) + if (toolAny && !string.IsNullOrWhiteSpace(effectiveWorkspace) && !planMode) { blocks.Add(new PromptBlock { @@ -208,6 +227,22 @@ Diese Regel hat höchste Priorität und gilt für JEDE Antwort: Wenn eine Datei } // kein extra Erinnerungsblock ohne toolAny – Zeilenreferenz-Hinweise kommen direkt über die Nachricht + // Plan-Modus-Block + if (planMode) + { + blocks.Add(new PromptBlock + { + Icon = "fa-map", + Label = "Plan-Modus aktiv", + Content = "## Plan-Modus\nDer User hat den Plan-Modus aktiviert. Du DARFST KEINE Änderungen vornehmen:\n" + + "- KEINE Dateien schreiben, editieren oder löschen\n" + + "- KEINE Shell-Befehle ausführen (außer read-only wie cat, ls, find, grep)\n" + + "- KEINE Commits erstellen\n\n" + + "Stattdessen: Analysiere, stelle Rückfragen und erstelle einen detaillierten Plan.\n" + + "Der User wird den Plan-Modus deaktivieren wenn du mit der Umsetzung beginnen sollst." + }); + } + var marked = markedMessages?.Where(m => !string.IsNullOrWhiteSpace(m.Content)).ToList(); if (marked != null && marked.Count > 0) { @@ -223,9 +258,9 @@ Diese Regel hat höchste Priorität und gilt für JEDE Antwort: Wenn eine Datei return blocks; } - public static string Build(AgentRecord agent, SessionRecord session, IEnumerable markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable skillSlugs = null, SkillService skillService = null, string userPersona = null, string resolvedWorkspace = null) + public static string Build(AgentRecord agent, SessionRecord session, IEnumerable markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, IEnumerable skillSlugs = null, SkillService skillService = null, string userPersona = null, string resolvedWorkspace = null, bool planMode = false) { - var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, skillSlugs, skillService, userPersona, resolvedWorkspace); + var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, skillSlugs, skillService, userPersona, resolvedWorkspace, planMode); return string.Join("\n\n", blocks.Select(b => b.Content)); } } diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index 6c82ed0..556820b 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -178,8 +178,9 @@ async function apiGetSystemPrompt(sessionId) { return resp.text(); } -async function apiGetSystemPromptBlocks(sessionId) { - const resp = await fetch(`/api/sessions/${sessionId}/system-prompt-blocks`); +async function apiGetSystemPromptBlocks(sessionId, planMode) { + const url = `/api/sessions/${sessionId}/system-prompt-blocks` + (planMode ? '?planMode=true' : ''); + const resp = await fetch(url); if (!resp.ok) throw new Error('System-Prompt konnte nicht geladen werden'); return resp.json(); } diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 966ce9b..19907ee 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -1338,7 +1338,7 @@ async function showSystemPromptModal(sessionId) { // Laden try { - const blocks = await apiGetSystemPromptBlocks(sessionId); + const blocks = await apiGetSystemPromptBlocks(sessionId, typeof _planModeActive !== 'undefined' && _planModeActive); body.innerHTML = ''; _renderSystemPromptBlocks(body, blocks); } catch (e) {