Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft. Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch. WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b umbenannten Parameter angeglichen. Dabei behoben: - 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue" gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day. - Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine CSS-Klasse gab - jetzt "success" wie laufende Instanzen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
2.8 KiB
C#
77 lines
2.8 KiB
C#
using System.Text.Json;
|
|
|
|
namespace ClawdDotNet.Core.Config;
|
|
|
|
public static class ConfigLoader
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
AllowTrailingCommas = true,
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
public static async Task<InstanceConfig> LoadAsync(string filePath, CancellationToken ct = default)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
throw new FileNotFoundException($"Config file not found: {filePath}");
|
|
|
|
await using var stream = File.OpenRead(filePath);
|
|
var config = await JsonSerializer.DeserializeAsync<InstanceConfig>(stream, JsonOptions, ct)
|
|
?? throw new InvalidOperationException($"Config file is empty or invalid: {filePath}");
|
|
|
|
Migrate(config);
|
|
// Make encrypted credentials readable for the runtime. Plain text from
|
|
// older configurations stays unchanged and is taken over when saving.
|
|
Security.ConfigSecrets.Unprotect(config);
|
|
Validate(config, filePath);
|
|
return config;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lifts old configurations to the current schema.
|
|
///
|
|
/// "maxTokens" used to be understood as a context limit, but counted cumulatively
|
|
/// across all steps — normal runs therefore aborted prematurely. The value is
|
|
/// not taken over, but replaced by the generous default for the cost budget;
|
|
/// context control is taken over by maxContextTokens.
|
|
/// </summary>
|
|
public static void Migrate(InstanceConfig config)
|
|
{
|
|
foreach (var agent in config.Agents)
|
|
Migrate(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Migration for an individually loaded agent (AgentSettings.json).
|
|
/// </summary>
|
|
public static void Migrate(AgentConfig agent)
|
|
{
|
|
var guard = agent.LoopGuard;
|
|
if (guard.LegacyMaxTokens is not { } legacy)
|
|
return;
|
|
|
|
// Only take over if deliberately configured more generously than the default.
|
|
if (legacy > guard.MaxCumulativeTokens)
|
|
guard.MaxCumulativeTokens = legacy;
|
|
|
|
guard.LegacyMaxTokens = null;
|
|
}
|
|
|
|
private static void Validate(InstanceConfig config, string filePath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(config.OpenRouterApiKey))
|
|
throw new InvalidOperationException($"'openRouterApiKey' is required in {filePath}");
|
|
|
|
var agentIds = new HashSet<string>();
|
|
foreach (var agent in config.Agents)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(agent.AgentId))
|
|
throw new InvalidOperationException($"Every agent must have an 'agentId' in {filePath}");
|
|
|
|
if (!agentIds.Add(agent.AgentId))
|
|
throw new InvalidOperationException($"Duplicate agentId '{agent.AgentId}' in {filePath}");
|
|
}
|
|
}
|
|
}
|