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
+41 -1
View File
@@ -167,6 +167,9 @@ public sealed record ForecastTaskDto(
public sealed record TaskWhenDto(string Kind, string Value, string TimeZone);
/// <summary>Ein eingefrorener Aufruf, der auf Freigabe wartet.</summary>
/// <param name="PreviousApprovals">#641: Wie oft dieselbe Operation dieses Agenten zuletzt schon freigegeben wurde.</param>
/// <param name="CanAllowSimilar">#641: Ob "Gleichartiges kuenftig erlauben" angeboten werden darf.</param>
/// <param name="SuggestAllowSimilar">#641: Ob die Oberflaeche die Dauerfreigabe vorschlagen soll.</param>
public sealed record StagedCallDto(
long Id,
string RunId,
@@ -176,7 +179,44 @@ public sealed record StagedCallDto(
string Proposal,
string Status,
string ArgumentsJson,
DateTime CreatedAt);
DateTime CreatedAt,
int PreviousApprovals = 0,
bool CanAllowSimilar = false,
bool SuggestAllowSimilar = false);
/// <summary>#641: Eine Dauerfreigabe - "Gleichartiges kuenftig erlauben".</summary>
public sealed record StandingApprovalDto(
long Id,
string AgentId,
string Tool,
string Operation,
string GrantedBy,
DateTime GrantedAt,
long? SourceId,
DateTime? RevokedAt,
string? RevokedBy);
/// <summary>
/// #641: Die Rechte eines Agenten an einem Werkzeug, je Operation.
/// <c>Setting</c>: "erlaubt", "verboten" oder "freigabe" (einzeln vorlegen) - so wie es in der
/// Konfiguration steht. <c>Effective</c>: was tatsaechlich geschieht ("auto", "approve",
/// "deny") - einschliesslich Dauerfreigaben und Werkzeugvorgabe.
/// </summary>
public sealed record ToolRightsDto(
string Tool,
bool Configured,
IReadOnlyList<ToolOperationDto> Operations);
public sealed record ToolOperationDto(
string Operation,
string Setting,
string Effective,
string Reason,
bool AlwaysApproval,
long? StandingApprovalId);
/// <summary>#641: Neue Rechte. <c>Allowed</c> <c>null</c> = Liste entfernen, Vorgabe des Werkzeugs gilt.</summary>
public sealed record ToolRightsInputDto(IReadOnlyList<string>? Allowed, IReadOnlyList<string>? Denied);
/// <summary>Warum ein Vorschlag abgelehnt wurde.</summary>
public sealed record RejectDto(string Reason);
+12 -3
View File
@@ -139,8 +139,10 @@ internal static class Mapping
/// Zusammenfassung davon. Sie sind zugleich Fremdtext aus einem Modell; die Anzeige
/// setzt sie als Text ein, nie als HTML.</para>
/// </summary>
public static StagedCallDto ToDto(this StagedCall call)
=> new(
public static StagedCallDto ToDto(this StagedCall call, int bisherFreigegeben = 0, bool dauerfreigabenAktiv = false)
{
var erlaubbar = dauerfreigabenAktiv && StagingService.IstDauerhaftErlaubbar(call);
return new(
call.Id,
call.RunId,
call.AgentId,
@@ -149,7 +151,14 @@ internal static class Mapping
call.Proposal,
call.Status.ToString(),
call.ArgumentsJson,
call.CreatedAt);
call.CreatedAt,
bisherFreigegeben,
erlaubbar,
erlaubbar && bisherFreigegeben >= StagingService.VorschlagAb);
}
public static StandingApprovalDto ToDto(this Dauerfreigabe d)
=> new(d.Id, d.AgentId, d.Tool, d.Operation, d.ErteiltVon, d.ErteiltAm, d.AnlassId, d.WiderrufenAm, d.WiderrufenVon);
public static UsageDto ToDto(this RunUsage usage)
=> new(
@@ -0,0 +1,55 @@
using ClawdDotNet.Core.Staging;
using ClawdDotNet.Core.Tools;
namespace ClawdDotNet.Web.Contracts;
/// <summary>
/// #641: Was ein Agent mit einem Werkzeug darf - je Operation, so wie es in der
/// Konfiguration steht, und so, wie es tatsaechlich eingestuft wird. Beides zu zeigen ist
/// Absicht: Eine Dauerfreigabe oder die Vorgabe des Werkzeugs kann eine Operation laufen
/// lassen, die in der Konfiguration nicht als erlaubt steht.
/// </summary>
public static class WerkzeugrechteAnsicht
{
public static ToolRightsDto Erstelle(
IAgentTool tool, IReadOnlyDictionary<string, object?> settings, IReadOnlyList<Dauerfreigabe> dauerfreigaben)
{
var rechte = Werkzeugrechte.Aus(settings);
var policy = new StagingPolicy();
// Auch Operationen zeigen, die in der Konfiguration stehen, das Werkzeug aber nicht
// (mehr) aufzaehlt - sonst waere ein Tippfehler unsichtbar.
var operationen = tool.Operationen
.Select(Werkzeugrechte.Normalisiere)
.Concat(rechte.Erlaubt?.Where(o => o != Werkzeugrechte.Alles) ?? [])
.Concat(rechte.Verboten)
.Distinct()
.ToList();
var zeilen = operationen.Select(op =>
{
var dauer = dauerfreigaben.FirstOrDefault(d => d.Operation == op);
var einstufung = FreigabeRegeln.Einstufen(
tool.Name, op, rechte, tool.Vorgabe(op, settings), policy, dauerfreigabe: dauer is not null);
var einstellung = rechte.IstVerboten(op) ? "verboten"
: rechte.IstErlaubt(op) ? "erlaubt"
: "freigabe";
return new ToolOperationDto(
op,
einstellung,
einstufung.Entscheidung switch
{
StagingDecision.Auto => "auto",
StagingDecision.Deny => "deny",
_ => "approve"
},
einstufung.Grund,
FreigabeRegeln.IstImmerFreigabe(tool.Name, op),
dauer?.Id);
}).ToList();
return new ToolRightsDto(tool.Name, rechte.IstKonfiguriert, zeilen);
}
}