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>
115 lines
4.8 KiB
C#
115 lines
4.8 KiB
C#
using System.Text.Json;
|
|
using ClawdDotNet.Core.Memory;
|
|
using ClawdDotNet.Core.Storage;
|
|
using ClawdDotNet.Core.Tools;
|
|
using ClawdDotNet.Tools.Memory;
|
|
using ClawdDotNet.Tools.Tests.FileRW;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Tools.Tests.Memory;
|
|
|
|
/// <summary>
|
|
/// #607: Der Nachrichten-Agent merkte sich bei jedem Lauf die ganze, wachsende Linkliste ohne
|
|
/// Schluessel. Sobald der neueste Eintrag allein ueber 8.000 Zeichen lag, lieferte recall keinen
|
|
/// einzigen Inhalt mehr - der Agent haette jede Meldung erneut fuer neu gehalten.
|
|
/// </summary>
|
|
public sealed class MemoryToolTests : IDisposable
|
|
{
|
|
private readonly string _dir = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
|
private readonly SqliteMemoryRepository _repository;
|
|
private readonly MemoryTool _tool = new();
|
|
|
|
public MemoryToolTests()
|
|
{
|
|
Directory.CreateDirectory(_dir);
|
|
_repository = new SqliteMemoryRepository(new SqliteStorage(Path.Combine(_dir, "state.db")));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
|
|
try { Directory.Delete(_dir, recursive: true); }
|
|
catch { /* Aufraeumen ist Nebensache */ }
|
|
}
|
|
|
|
private Task<ToolResult> Aufruf(object eingabe) => _tool.ExecuteAsync(
|
|
JsonSerializer.SerializeToElement(eingabe),
|
|
new AgentToolContext("nachrichten", "inst", new Dictionary<string, object?>(), new NullStateStore(),
|
|
NullLogger.Instance, CancellationToken.None, Memory: _repository),
|
|
CancellationToken.None);
|
|
|
|
private static string Linkliste(int anzahl)
|
|
=> string.Join("\n", Enumerable.Range(1, anzahl).Select(i => $"https://www.tagesschau.de/inland/meldung-{i:D4}.html"));
|
|
|
|
[Fact]
|
|
public async Task Ein_einzelner_uebergrosser_Eintrag_wird_gekuerzt_statt_weggelassen()
|
|
{
|
|
var liste = Linkliste(250); // ~11.500 Zeichen
|
|
(await Aufruf(new { action = "remember", subject = "gemeldet", content = liste })).Success.ShouldBeTrue();
|
|
|
|
var abruf = await Aufruf(new { action = "recall", query = "gemeldet" });
|
|
|
|
abruf.Success.ShouldBeTrue();
|
|
abruf.Content.ShouldContain("meldung-0001.html", customMessage: "der Anfang bleibt sichtbar");
|
|
abruf.Content.ShouldContain("meldung-0250.html", customMessage: "das Ende - die neuesten Links - bleibt sichtbar");
|
|
abruf.Content.ShouldContain("truncated");
|
|
abruf.Content.ShouldNotContain("more results omitted");
|
|
abruf.Content.Length.ShouldBeLessThanOrEqualTo(8_000);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Ist_der_neueste_von_mehreren_Eintraegen_zu_gross_bleibt_er_teilweise_sichtbar()
|
|
{
|
|
await Aufruf(new { action = "remember", subject = "gemeldet", content = Linkliste(100) });
|
|
await Task.Delay(20);
|
|
await Aufruf(new { action = "remember", subject = "gemeldet", content = Linkliste(220) });
|
|
|
|
var abruf = await Aufruf(new { action = "recall", query = "gemeldet" });
|
|
|
|
abruf.Content.ShouldContain("meldung-0220.html");
|
|
abruf.Content.ShouldContain("1 more results omitted");
|
|
abruf.Content.Length.ShouldBeLessThanOrEqualTo(8_000);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Was_passt_bleibt_ungekuerzt()
|
|
{
|
|
await Aufruf(new { action = "remember", subject = "gemeldet", content = Linkliste(20) });
|
|
|
|
var abruf = await Aufruf(new { action = "recall", query = "gemeldet" });
|
|
|
|
abruf.Content.ShouldNotContain("truncated");
|
|
abruf.Content.ShouldContain("meldung-0010.html");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Erneutes_Merken_ohne_Schluessel_unter_demselben_Betreff_weist_auf_key_hin()
|
|
{
|
|
var erster = await Aufruf(new { action = "remember", subject = "gemeldet", content = Linkliste(3) });
|
|
var zweiter = await Aufruf(new { action = "remember", subject = "gemeldet", content = Linkliste(5) });
|
|
|
|
erster.Content.ShouldNotContain("Note:");
|
|
zweiter.Content.ShouldContain("2 entries without a key");
|
|
zweiter.Content.ShouldContain("key");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Mit_Schluessel_wird_fortgeschrieben_und_nicht_gewarnt()
|
|
{
|
|
await Aufruf(new { action = "remember", subject = "gemeldet", key = "gemeldete-links", content = Linkliste(3) });
|
|
var zweiter = await Aufruf(new { action = "remember", subject = "gemeldet", key = "gemeldete-links", content = Linkliste(5) });
|
|
|
|
zweiter.Content.ShouldNotContain("Note:");
|
|
(await _repository.CountAsync(MemoryScope.Agent, "nachrichten", CancellationToken.None)).ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Ein_Eintrag_nahe_der_Abrufgrenze_wird_beim_Merken_angemahnt()
|
|
{
|
|
var ergebnis = await Aufruf(new { action = "remember", subject = "gemeldet", key = "links", content = Linkliste(120) });
|
|
|
|
ergebnis.Content.ShouldContain("characters long");
|
|
}
|
|
}
|