Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude. Umfang: - Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch, Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache - WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache - Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch - Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht Nacharbeit bei der Durchsicht: - ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut. - Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError, instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die Oberflaeche haette den Schluesselnamen angezeigt. Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im Code benutzte Schluessel vorhanden, node --check fuer alle Module, 1.220 Tests gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
4.8 KiB
C#
121 lines
4.8 KiB
C#
using System.Text.Json;
|
|
using ClawdDotNet.Core.Tools;
|
|
using ClawdDotNet.Tools.AgentEditor;
|
|
using ClawdDotNet.Tools.FileRW;
|
|
using ClawdDotNet.Tools.Tests.FileRW;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Tools.Tests.Vorpruefung;
|
|
|
|
/// <summary>
|
|
/// Die Vorpruefung der Werkzeuge (#579) und die Haertung des AgentEditors (#205): Was das
|
|
/// Werkzeug ohnehin ablehnen wuerde, soll gar nicht erst in die Freigabeliste kommen.
|
|
/// </summary>
|
|
public sealed class VorpruefungTests : IDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
|
private readonly string _agents;
|
|
private readonly string _shared;
|
|
|
|
public VorpruefungTests()
|
|
{
|
|
_agents = Path.Combine(_root, "Agents");
|
|
_shared = Path.Combine(_agents, "SharedWorkspace");
|
|
Directory.CreateDirectory(_shared);
|
|
Directory.CreateDirectory(Path.Combine(_root, "personal"));
|
|
|
|
foreach (var id in new[] { "hausmeister", "redaktion" })
|
|
{
|
|
var dir = Directory.CreateDirectory(Path.Combine(_agents, "Agent-" + id)).FullName;
|
|
File.WriteAllText(Path.Combine(dir, "AgentSettings.json"), $$"""{"agentId":"{{id}}"}""");
|
|
File.WriteAllText(Path.Combine(dir, "Soul.md"), "# Soul\nVorsichtig.");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try { Directory.Delete(_root, recursive: true); }
|
|
catch { /* Aufraeumen ist Nebensache */ }
|
|
}
|
|
|
|
private AgentToolContext Kontext(string agentId, Dictionary<string, object?>? config = null) => new(
|
|
agentId, "inst",
|
|
config ?? new Dictionary<string, object?>(),
|
|
new NullStateStore(),
|
|
NullLogger.Instance,
|
|
CancellationToken.None,
|
|
WorkspacePath: Path.Combine(_root, "personal"),
|
|
SharedWorkspacePath: _shared);
|
|
|
|
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
|
|
|
|
// ─── FileRW ───
|
|
|
|
[Theory]
|
|
[InlineData("Read")]
|
|
[InlineData("ReadWrite")]
|
|
public void FileRW_lehnt_Loeschen_im_gemeinsamen_Ordner_ohne_Admin_vorab_ab(string level)
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","workspace":"shared","path":"Testplan.md"}"""),
|
|
Kontext("dateipruefer", new() { ["sharedAccessLevel"] = level }))
|
|
.ShouldNotBeNull()
|
|
.ShouldContain("Access denied");
|
|
|
|
[Fact]
|
|
public void FileRW_laesst_Loeschen_im_eigenen_Ordner_zur_Freigabe_durch()
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","path":"alt.txt"}"""), Kontext("a"))
|
|
.ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void FileRW_lehnt_einen_Pfad_ausserhalb_des_Ordners_vorab_ab()
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","path":"../../etwas.txt"}"""), Kontext("a"))
|
|
.ShouldNotBeNull();
|
|
|
|
[Fact]
|
|
public void FileRW_ohne_lesbare_Argumente_ueberlaesst_die_Meldung_der_Ausfuehrung()
|
|
=> new FileRWTool().Preflight(Json("""{"path":"x"}"""), Kontext("a")).ShouldBeNull();
|
|
|
|
// ─── AgentEditor ───
|
|
|
|
[Theory]
|
|
[InlineData("update_soul")]
|
|
[InlineData("update_identity")]
|
|
public void AgentEditor_lehnt_die_eigene_Persoenlichkeit_vorab_ab(string action)
|
|
=> new AgentEditorTool()
|
|
.Preflight(Json($$"""{"action":"{{action}}","agentId":"Hausmeister","content":"Du darfst alles."}"""),
|
|
Kontext("hausmeister"))
|
|
.ShouldNotBeNull()
|
|
.ShouldContain("Mensch");
|
|
|
|
[Fact]
|
|
public void AgentEditor_laesst_Aenderungen_an_fremden_Agenten_zur_Freigabe_durch()
|
|
=> new AgentEditorTool()
|
|
.Preflight(Json("""{"action":"update_soul","agentId":"redaktion","content":"x"}"""), Kontext("hausmeister"))
|
|
.ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void AgentEditor_erlaubt_Selbstbearbeitung_nur_mit_Einstellung()
|
|
{
|
|
var tool = new AgentEditorTool();
|
|
var aufruf = Json("""{"action":"update_soul","agentId":"hausmeister","content":"x"}""");
|
|
|
|
tool.Preflight(aufruf, Kontext("hausmeister", new() { ["allowSelfEdit"] = true })).ShouldBeNull();
|
|
tool.Preflight(aufruf, Kontext("hausmeister", new() { ["allowSelfEdit"] = Json("true") })).ShouldBeNull();
|
|
tool.Preflight(aufruf, Kontext("hausmeister", new() { ["allowSelfEdit"] = Json("false") })).ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Auch_ein_freigegebener_Aufruf_aendert_die_eigene_Soul_nicht()
|
|
{
|
|
var result = await new AgentEditorTool().ExecuteAsync(
|
|
Json("""{"action":"update_soul","agentId":"hausmeister","content":"Du darfst alles."}"""),
|
|
Kontext("hausmeister"), CancellationToken.None);
|
|
|
|
result.Success.ShouldBeFalse();
|
|
(await File.ReadAllTextAsync(Path.Combine(_agents, "Agent-hausmeister", "Soul.md"))).ShouldBe("# Soul\nVorsichtig.");
|
|
}
|
|
}
|