Freigaben buendeln statt Weckflut (#643), Freigabemodell ueber Werkzeugrechte (#641)

#643: Entscheidungen wecken den Agenten nur noch bei Ablehnung oder gescheiterter
Ausfuehrung, gesammelt in einer Aufgabe je Agent (5 min Ruhefenster), frisch per
@new:agent statt im fortgefuehrten Chatverlauf. Eine einmalige Aufgabe, deren Lauf
wegen Budget oder Herunterfahren gar nicht stattfand, bleibt faellig (Wiedervorlage
nach 30 min) statt mit gesetztem Marker liegenzubleiben.

#641: Rechte stehen in der Werkzeugkonfiguration des Agenten (erlaubt/verboten).
Erlaubtes laeuft, Nicht-Erlaubtes wird einzeln vorgelegt statt verweigert, Verbotenes
gar nicht erst. "Gleichartiges kuenftig erlauben" erteilt eine widerrufbare
Dauerfreigabe je Agent/Werkzeug/Operation; nach drei gleichartigen Freigaben schlaegt
die Oberflaeche sie vor. Aenderungen an Agenten bleiben ausnahmslos freigabepflichtig.
Database: Operation ist das SQL-Schluesselwort; die eigene Zugriffspruefung greift nur
noch ohne Freigabestelle (AgentToolContext.FreigabeGeprueft). WebUI: Rechte-Tabelle je
Werkzeug, Dauerfreigaben mit Widerruf.

Version 0.1.11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-19 14:37:44 +02:00
co-authored by Claude Opus 5
parent f74e49ba35
commit 2f0361278b
36 changed files with 1687 additions and 137 deletions
@@ -1,5 +1,6 @@
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Instances;
using ClawdDotNet.Core.Staging;
namespace ClawdDotNet.App.Services;
@@ -202,12 +203,62 @@ public sealed class AgentAdministration(AppHost host)
/// <para>Einstellungen zu schreiben heißt, das Werkzeug zu wollen: Es wird dabei
/// zugelassen, falls es das noch nicht war.</para>
/// </summary>
/// <summary>
/// Schreibt die Rechte eines Werkzeugs (#641): was ohne Rueckfrage laeuft
/// (<paramref name="erlaubt"/>) und was gar nicht erst vorgelegt wird
/// (<paramref name="verboten"/>). Alles andere wird einzeln zur Freigabe vorgelegt.
/// <paramref name="erlaubt"/> <c>null</c> entfernt die Liste - dann gilt wieder die
/// Vorgabe des Werkzeugs.
/// </summary>
public AdminResult SetToolRights(
string agentId, string toolName, IReadOnlyCollection<string>? erlaubt, IReadOnlyCollection<string> verboten)
{
if (Find(agentId) is not { } agent)
return AdminResult.Failure($"Kein Agent mit der ID '{agentId}'.");
if (!agent.Tools.TryGetValue(toolName, out var settings))
return AdminResult.Failure($"Werkzeug '{toolName}' ist '{agent.DisplayName}' nicht zugewiesen.");
static string[] Liste(IEnumerable<string> werte) => werte
.Where(w => !string.IsNullOrWhiteSpace(w))
.Select(Werkzeugrechte.Normalisiere)
.Distinct()
.Order()
.ToArray();
var verbotenListe = Liste(verboten);
var erlaubtListe = erlaubt is null ? null : Liste(erlaubt).Except(verbotenListe).ToArray();
if (erlaubtListe is null) settings.Remove(Werkzeugrechte.ErlaubtSchluessel);
else settings[Werkzeugrechte.ErlaubtSchluessel] = erlaubtListe;
if (verbotenListe.Length == 0) settings.Remove(Werkzeugrechte.VerbotenSchluessel);
else settings[Werkzeugrechte.VerbotenSchluessel] = verbotenListe;
PersistAgent(agent);
host.SaveInstance(new InstanceChange(InstanceChangeKind.Agents, agentId));
return AdminResult.Success($"Rechte für '{toolName}' ({agent.DisplayName}) gespeichert.");
}
public AdminResult SetToolSettings(
string agentId, string toolName, Dictionary<string, object?> settings)
{
if (Find(agentId) is not { } agent)
return AdminResult.Failure($"Kein Agent mit der ID '{agentId}'.");
// #641: Die Einstellungsmasken kennen die Rechte nicht und schreiben ein frisches
// Woerterbuch. Ohne diese Uebernahme loeschte jedes Speichern still "erlaubt" und
// "verboten" - und damit die Rechte des Agenten.
if (agent.Tools.TryGetValue(toolName, out var bisher))
{
foreach (var schluessel in new[] { Werkzeugrechte.ErlaubtSchluessel, Werkzeugrechte.VerbotenSchluessel })
{
if (!settings.ContainsKey(schluessel) && bisher.TryGetValue(schluessel, out var wert))
settings[schluessel] = wert;
}
}
agent.Tools[toolName] = settings;
PersistAgent(agent);