i18n: Englisch als Hauptsprache, Sprachpakete fuer Desktop und WebUI (#661)
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
69718122f3
commit
ad71682c2d
@@ -22,23 +22,23 @@ public sealed class MemoryTool : IAgentTool
|
||||
public string Name => "Memory";
|
||||
|
||||
public string Description => """
|
||||
Dein Langzeitgedächtnis — überdauert einzelne Läufe und Neustarts.
|
||||
Your long-term memory — persists across runs and restarts.
|
||||
|
||||
Nutze es, um Erkenntnisse festzuhalten, die beim nächsten Lauf noch zählen:
|
||||
getroffene Entscheidungen, gesicherte Fakten, offene Punkte, Beobachtungen
|
||||
über Zeit. Prüfe zu Beginn eines Laufs mit 'recall', was du bereits weißt,
|
||||
statt es erneut herzuleiten.
|
||||
Use it to retain insights that matter in future runs:
|
||||
decisions made, verified facts, open action items, observations
|
||||
over time. At the start of a run, check with 'recall' what you already know,
|
||||
instead of deriving it again.
|
||||
|
||||
Aktionen: remember, recall, forget, list_subjects
|
||||
Actions: remember, recall, forget, list_subjects
|
||||
|
||||
Zwei Bereiche:
|
||||
- scope='agent' (Standard): nur für dich sichtbar
|
||||
- scope='shared': für alle Agenten der Instanz sichtbar
|
||||
Two scopes:
|
||||
- scope='agent' (default): visible only to you
|
||||
- scope='shared': visible to all agents in this instance
|
||||
|
||||
Zum 'key': Vergib einen, wenn eine Angabe sich später ändern kann
|
||||
(z.B. key='kursziel_nvda'). Erneutes Merken unter demselben Schlüssel
|
||||
aktualisiert den Eintrag, statt einen zweiten anzulegen — so wächst dein
|
||||
Gedächtnis nicht zu.
|
||||
Regarding 'key': Specify one when an item might change later
|
||||
(e.g. key='target_price_nvda'). Remembering under the same key
|
||||
updates the existing entry instead of creating a second one — keeping
|
||||
your memory clean.
|
||||
""";
|
||||
|
||||
public JsonElement InputSchema { get; } = JsonDocument.Parse("""
|
||||
@@ -48,54 +48,54 @@ public sealed class MemoryTool : IAgentTool
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["remember", "recall", "forget", "list_subjects"],
|
||||
"description": "Die auszuführende Aktion."
|
||||
"description": "The action to perform."
|
||||
},
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"enum": ["agent", "shared"],
|
||||
"description": "Bereich. Standard ist 'agent' (nur für dich)."
|
||||
"description": "Scope. Default is 'agent' (visible only to you)."
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"description": "Worum es geht — z.B. 'NVDA', 'Kunde Meier', 'Projekt Alpha'. Pflicht bei remember."
|
||||
"description": "What this is about — e.g. 'NVDA', 'Customer Smith', 'Project Alpha'. Required for remember."
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Was du dir merken willst. Pflicht bei remember."
|
||||
"description": "What you want to remember. Required for remember."
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Optionaler eindeutiger Schlüssel. Erneutes Merken darunter aktualisiert den Eintrag."
|
||||
"description": "Optional unique key. Remembering under the same key updates the entry."
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["fact", "decision", "observation", "task", "contact", "other"],
|
||||
"description": "Art der Erinnerung. Standard 'observation'."
|
||||
"description": "Type of memory. Default 'observation'."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Schlagworte zum späteren Wiederfinden."
|
||||
"description": "Tags for later retrieval."
|
||||
},
|
||||
"importance": {
|
||||
"type": "integer",
|
||||
"description": "1 (nebensächlich) bis 5 (zentral). Standard 3. Steuert die Reihenfolge beim Abruf."
|
||||
"description": "1 (minor) to 5 (critical). Default 3. Controls ordering on recall."
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Suchtext für recall — wird gegen Betreff, Inhalt und Schlagworte geprüft."
|
||||
"description": "Search query for recall — matched against subject, content, and tags."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximale Anzahl Treffer bei recall. Standard 20."
|
||||
"description": "Maximum number of results for recall. Default 20."
|
||||
},
|
||||
"minImportance": {
|
||||
"type": "integer",
|
||||
"description": "Nur Erinnerungen ab dieser Wichtigkeit abrufen."
|
||||
"description": "Only retrieve memories with importance greater than or equal to this."
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "Id der zu löschenden Erinnerung (forget)."
|
||||
"description": "ID of memory to delete (forget)."
|
||||
}
|
||||
},
|
||||
"required": ["action"]
|
||||
@@ -106,11 +106,11 @@ public sealed class MemoryTool : IAgentTool
|
||||
JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
if (context.Memory is not { } repository)
|
||||
return ToolResult.Fail("Das Gedächtnis ist für diese Instanz nicht verfügbar.");
|
||||
return ToolResult.Fail("Memory is not available for this instance.");
|
||||
|
||||
var action = input.TryGetProperty("action", out var a) ? a.GetString() : null;
|
||||
if (string.IsNullOrWhiteSpace(action))
|
||||
return ToolResult.Fail("'action' ist erforderlich.");
|
||||
return ToolResult.Fail("'action' is required.");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -120,13 +120,13 @@ public sealed class MemoryTool : IAgentTool
|
||||
"recall" => await RecallAsync(input, repository, context, ct),
|
||||
"forget" => await ForgetAsync(input, repository, context, ct),
|
||||
"list_subjects" => await ListSubjectsAsync(input, repository, context, ct),
|
||||
_ => ToolResult.Fail($"Unbekannte Aktion: {action}")
|
||||
_ => ToolResult.Fail($"Unknown action: {action}")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Logger.LogError(ex, "Fehler im Memory-Tool bei Aktion {Action}", action);
|
||||
return ToolResult.Fail($"Fehler: {ex.Message}");
|
||||
context.Logger.LogError(ex, "Error in Memory tool for action {Action}", action);
|
||||
return ToolResult.Fail($"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,11 +137,11 @@ public sealed class MemoryTool : IAgentTool
|
||||
{
|
||||
var subject = GetString(input, "subject");
|
||||
if (string.IsNullOrWhiteSpace(subject))
|
||||
return ToolResult.Fail("'subject' ist erforderlich — worum geht es?");
|
||||
return ToolResult.Fail("'subject' is required — what is this about?");
|
||||
|
||||
var content = GetString(input, "content");
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return ToolResult.Fail("'content' ist erforderlich — was soll gemerkt werden?");
|
||||
return ToolResult.Fail("'content' is required — what should be remembered?");
|
||||
|
||||
var scope = GetScope(input);
|
||||
|
||||
@@ -160,12 +160,12 @@ public sealed class MemoryTool : IAgentTool
|
||||
|
||||
var saved = await repository.RememberAsync(entry, ct);
|
||||
|
||||
var scopeLabel = scope == MemoryScope.Shared ? "geteilt" : "privat";
|
||||
var keyHint = string.IsNullOrWhiteSpace(saved.Key) ? "" : $" | Schlüssel: {saved.Key}";
|
||||
var scopeLabel = scope == MemoryScope.Shared ? "shared" : "private";
|
||||
var keyHint = string.IsNullOrWhiteSpace(saved.Key) ? "" : $" | Key: {saved.Key}";
|
||||
|
||||
return ToolResult.Ok(
|
||||
$"Gemerkt (#{saved.Id}, {scopeLabel}){keyHint}\n" +
|
||||
$"Betreff: {saved.Subject} | Art: {saved.Category} | Wichtigkeit: {saved.Importance}" +
|
||||
$"Remembered (#{saved.Id}, {scopeLabel}){keyHint}\n" +
|
||||
$"Subject: {saved.Subject} | Category: {saved.Category} | Importance: {saved.Importance}" +
|
||||
await HinweiseAsync(saved, scope, repository, context, ct));
|
||||
}
|
||||
|
||||
@@ -193,16 +193,16 @@ public sealed class MemoryTool : IAgentTool
|
||||
var ohneSchluessel = gleicherBetreff.Count(e => string.IsNullOrWhiteSpace(e.Key));
|
||||
if (ohneSchluessel > 1)
|
||||
{
|
||||
sb.Append($"\nHinweis: Unter dem Betreff '{saved.Subject}' liegen jetzt {ohneSchluessel} Einträge " +
|
||||
"ohne Schlüssel. Ersetzt dieser Eintrag einen früheren, merke ihn mit 'key' – dann wird " +
|
||||
"er fortgeschrieben statt verdoppelt. Überholte Einträge entfernst du mit forget (id).");
|
||||
sb.Append($"\nNote: Under subject '{saved.Subject}' there are now {ohneSchluessel} entries " +
|
||||
"without a key. If this entry replaces an earlier one, remember it with 'key' – then it will be " +
|
||||
"updated instead of duplicated. Remove outdated entries with forget (id).");
|
||||
}
|
||||
}
|
||||
|
||||
if (saved.Content.Length > MaxResultChars / 2)
|
||||
{
|
||||
sb.Append($"\nHinweis: Der Eintrag ist {saved.Content.Length} Zeichen lang, ein Abruf zeigt höchstens " +
|
||||
$"{MaxResultChars} Zeichen. Halte ihn kürzer oder teile ihn auf, sonst wird er beim Abruf gekürzt.");
|
||||
sb.Append($"\nNote: The entry is {saved.Content.Length} characters long, recall displays at most " +
|
||||
$"{MaxResultChars} characters. Keep it shorter or split it up, otherwise it will be truncated on recall.");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
@@ -231,9 +231,9 @@ public sealed class MemoryTool : IAgentTool
|
||||
{
|
||||
var total = await repository.CountAsync(scope, context.AgentId, ct);
|
||||
return ToolResult.Ok(total == 0
|
||||
? "Noch keine Erinnerungen in diesem Bereich."
|
||||
: $"Keine Treffer. Der Bereich enthält {total} Erinnerung(en) — " +
|
||||
"versuche einen anderen Suchbegriff oder 'list_subjects' für einen Überblick.");
|
||||
? "No memories in this scope yet."
|
||||
: $"No matches. Scope contains {total} memory entry/entries — " +
|
||||
"try a different search term or 'list_subjects' for an overview.");
|
||||
}
|
||||
|
||||
return ToolResult.Ok(Render(entries, scope));
|
||||
@@ -246,16 +246,16 @@ public sealed class MemoryTool : IAgentTool
|
||||
{
|
||||
var removed = await repository.ForgetAsync(id, ct);
|
||||
return ToolResult.Ok(removed
|
||||
? $"Erinnerung #{id} gelöscht."
|
||||
: $"Keine Erinnerung mit der Id {id} gefunden.");
|
||||
? $"Memory #{id} deleted."
|
||||
: $"No memory found with ID {id}.");
|
||||
}
|
||||
|
||||
var subject = GetString(input, "subject");
|
||||
if (string.IsNullOrWhiteSpace(subject))
|
||||
return ToolResult.Fail("Für 'forget' wird entweder 'id' oder 'subject' benötigt.");
|
||||
return ToolResult.Fail("Either 'id' or 'subject' is required for 'forget'.");
|
||||
|
||||
var count = await repository.ForgetBySubjectAsync(GetScope(input), context.AgentId, subject, ct);
|
||||
return ToolResult.Ok($"{count} Erinnerung(en) zum Betreff '{subject}' gelöscht.");
|
||||
return ToolResult.Ok($"{count} memory entry/entries deleted for subject '{subject}'.");
|
||||
}
|
||||
|
||||
private static async Task<ToolResult> ListSubjectsAsync(
|
||||
@@ -266,10 +266,10 @@ public sealed class MemoryTool : IAgentTool
|
||||
scope, context.AgentId, GetInt(input, "limit") ?? 50, ct);
|
||||
|
||||
if (subjects.Count == 0)
|
||||
return ToolResult.Ok("Noch keine Erinnerungen in diesem Bereich.");
|
||||
return ToolResult.Ok("No memories in this scope yet.");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Betreffs im Bereich '{(scope == MemoryScope.Shared ? "shared" : "agent")}':");
|
||||
sb.AppendLine($"Subjects in scope '{(scope == MemoryScope.Shared ? "shared" : "agent")}':");
|
||||
foreach (var (subject, count) in subjects)
|
||||
sb.AppendLine($" • {subject} ({count})");
|
||||
|
||||
@@ -281,7 +281,7 @@ public sealed class MemoryTool : IAgentTool
|
||||
private static string Render(IReadOnlyList<MemoryEntry> entries, MemoryScope scope)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"{entries.Count} Erinnerung(en), wichtigste zuerst:");
|
||||
sb.AppendLine($"{entries.Count} memory entry/entries, most important first:");
|
||||
sb.AppendLine();
|
||||
|
||||
var shown = 0;
|
||||
@@ -304,8 +304,8 @@ public sealed class MemoryTool : IAgentTool
|
||||
|
||||
if (entries.Count > shown)
|
||||
{
|
||||
sb.AppendLine($"[… {entries.Count - shown} weitere Treffer ausgelassen. " +
|
||||
"Grenze die Suche ein, um sie zu sehen.]");
|
||||
sb.AppendLine($"[… {entries.Count - shown} more results omitted. " +
|
||||
"Narrow down search to view them.]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -323,15 +323,15 @@ public sealed class MemoryTool : IAgentTool
|
||||
private static string FormatEntry(MemoryEntry entry, MemoryScope scope, int? maxChars = null)
|
||||
{
|
||||
var header = new StringBuilder();
|
||||
header.Append($"#{entry.Id} [{entry.Subject}] {entry.Category}, Wichtigkeit {entry.Importance}");
|
||||
header.Append($"#{entry.Id} [{entry.Subject}] {entry.Category}, Importance {entry.Importance}");
|
||||
if (!string.IsNullOrWhiteSpace(entry.Key))
|
||||
header.Append($", Schlüssel: {entry.Key}");
|
||||
header.Append($", Key: {entry.Key}");
|
||||
|
||||
var meta = new List<string> { $"aktualisiert {entry.UpdatedAt:yyyy-MM-dd HH:mm} UTC" };
|
||||
var meta = new List<string> { $"updated {entry.UpdatedAt:yyyy-MM-dd HH:mm} UTC" };
|
||||
if (entry.Tags.Count > 0)
|
||||
meta.Add("Schlagworte: " + string.Join(", ", entry.Tags));
|
||||
meta.Add("Tags: " + string.Join(", ", entry.Tags));
|
||||
if (scope == MemoryScope.Shared && !string.IsNullOrWhiteSpace(entry.CreatedBy))
|
||||
meta.Add($"von {entry.CreatedBy}");
|
||||
meta.Add($"by {entry.CreatedBy}");
|
||||
var metaLine = $" ({string.Join(" | ", meta)})";
|
||||
|
||||
var content = entry.Content;
|
||||
@@ -352,8 +352,8 @@ public sealed class MemoryTool : IAgentTool
|
||||
{
|
||||
const int Mindestens = 400;
|
||||
|
||||
var marke = $"\n [… gekürzt: {{0}} von {content.Length} Zeichen ausgelassen – der Eintrag ist zu groß " +
|
||||
"für einen Abruf. Teile ihn auf oder halte ihn kürzer.]\n ";
|
||||
var marke = $"\n [… truncated: {{0}} of {content.Length} characters omitted – entry is too large " +
|
||||
"for recall. Split it up or keep it shorter.]\n ";
|
||||
|
||||
if (content.Length <= platz)
|
||||
return content;
|
||||
|
||||
Reference in New Issue
Block a user