Bugtracker-Durchgang: 13 Bugs behoben, Build ohne Warnungen
Alle Behebungen auf der Linux-Test-VM gegengeprueft (Ubuntu, .NET 10.0.111): Build 0 Warnungen / 0 Fehler, 637 Tests gruen, 1 uebersprungen (WindowsFact). Sicherheit und Zielplattform #373 PathBoundary.Canonicalize fiel offen aus. File.ResolveLinkTarget wirft fuer einen fehlenden Abschnitt FileNotFoundException; der catch(IOException) verliess daraufhin die GANZE Aufloesung mit dem unaufgeloesten Pfad - und der besteht jede Praefixpruefung. Der Symlink-Schutz war damit bei jedem Schreibvorgang wirkungslos. Jetzt ueber FileSystemInfo.LinkTarget, und TryCanonicalize meldet "nicht bestimmbar", statt fail-open durchzureichen. #374 WorkspacePath weist den Backslash rundheraus ab, wie den Doppelpunkt. Eine Schreibweise statt einer Sonderregel je Windows-Form. #375 Log-Verzeichnis: leer heisst "Anwendungseinstellung"; ein relativer Wert zaehlt gegen das Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis des Prozesses. Ein systemd-Dienst landet nicht mehr auf /Logs. Agentenkommunikation #206 Delegationskette gegen A->B->A (Test A13). Zyklusfrei per Bauart, dazu MaxDelegationDepth gegen die Staffel, die niemand beauftragt hat. #461 Zwei unabhaengige Ketten ueber Kreuz - beim Beheben von #206 gefunden und reproduziert. Ein delegierter Lauf wartet nur noch begrenzt auf ein fremdes Gate; ein Chat vom Menschen reiht sich weiter unbegrenzt ein. #234 Instanz-Id wird durchgereicht statt aus einem nur beilaeufig gesetzten Feld gelesen. Betrieb und Oberflaeche #376 Log-Ansicht chronologisch statt gruppiert je Moduldatei, mit Stacktraces an ihrer Meldung - auch ueber Taktgrenzen hinweg. #377 Info-Tabelle: feste Spaltenbreiten, damit ein langer Aenderungstext den anderen Spalten nicht den Platz nimmt. #378 Sicherung: Mindestabstand zwischen Ueberschrift und Schaltflaechenleiste. #379 Hellmodus: drei von vier gedaempften Textpinseln lagen unter 4,5:1. Die helle Palette ist als Ganzes verschoben, damit die Staffelung sichtbar bleibt statt auf einen Wert zusammenzufallen. Hygiene #233 stock_add: der Indexstand wird innerhalb der Sperre festgehalten. #235 Zeitumstellung: eine nicht existierende Ortszeit wird auf den ersten Zeitpunkt nach der Luecke verschoben statt uebersprungen (R4, R5). #236 45 Nullable-Warnungen und eine CA1416 - nicht unterdrueckt, sondern die Stellen so umgebaut, dass die Zusage nachvollziehbar wird. Mail meldet jetzt, WELCHER Zugangsschluessel fehlt, statt an einer KeyNotFoundException zu scheitern. #203 Kompaktiert wird vor dem API-Aufruf statt danach; die gemessenen Prompt-Groessen kalibrieren die Schaetzung je Modell. Neu: tests/ClawdDotNet.App.Tests - fuer #375 und #376 gab es keinen Ort fuer Tests. Dazu Regressionstests fuer jede Behebung; jeder wurde gegen den alten Stand geprueft, damit belegt ist, dass er den Fehler faengt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Ein Tool, das einen anderen Agenten beauftragt — der Nachbau dessen, was
|
||||
/// <c>AgentComm.send_message</c> und <c>AgentSpawn.spawn_agent</c> tun.
|
||||
///
|
||||
/// <para>Der Nachbau ist nötig, weil beide Werkzeuge in eigenen Projekten liegen, die
|
||||
/// <c>ClawdDotNet.Core.Tests</c> nicht referenziert. Entscheidend ist ohnehin nur die
|
||||
/// eine Zeile, in der sie sich hier gleichen: Die Delegationskette des eigenen Laufs
|
||||
/// wird weitergereicht. Ein Tool, das sie unterschlägt, macht aus einem Zyklus eine
|
||||
/// neue Beauftragung — genau der Fall, den B8 beschreibt.</para>
|
||||
///
|
||||
/// <para>Wen ein Agent beauftragt, steht in <paramref name="routes"/>: agentId → Ziel.
|
||||
/// Wer dort nicht vorkommt, delegiert nicht und antwortet schlicht.</para>
|
||||
/// </summary>
|
||||
internal sealed class DelegatingTool : IAgentTool
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, string> _routes;
|
||||
private readonly bool _spawn;
|
||||
private readonly TimeSpan _delayBeforeHandoff;
|
||||
|
||||
public string Name => "TestTool";
|
||||
public string Description => "Beauftragt einen anderen Agenten.";
|
||||
|
||||
public JsonElement InputSchema { get; } = JsonDocument.Parse("""
|
||||
{ "type": "object", "properties": { "message": { "type": "string" } } }
|
||||
""").RootElement.Clone();
|
||||
|
||||
/// <summary>Was zurückkam — Aufrufer, Ziel und das Ergebnis der Weitergabe.</summary>
|
||||
public ConcurrentBag<(string From, string To, bool Delivered, string? Error)> Handoffs { get; } = new();
|
||||
|
||||
/// <param name="delayBeforeHandoff">
|
||||
/// Wartezeit, bevor weitergegeben wird. Der Fake-Client antwortet synchron; ohne
|
||||
/// diese Pause läuft ein Chat vollständig durch, bevor der nächste überhaupt
|
||||
/// beginnt, und zwei Ketten können sich nicht überschneiden.
|
||||
/// </param>
|
||||
public DelegatingTool(
|
||||
IReadOnlyDictionary<string, string> routes,
|
||||
bool spawn = false,
|
||||
TimeSpan delayBeforeHandoff = default)
|
||||
{
|
||||
_routes = routes;
|
||||
_spawn = spawn;
|
||||
_delayBeforeHandoff = delayBeforeHandoff;
|
||||
}
|
||||
|
||||
public async Task<ToolResult> ExecuteAsync(
|
||||
JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
if (!_routes.TryGetValue(context.AgentId, out var target))
|
||||
return ToolResult.Ok("nichts zu delegieren");
|
||||
|
||||
if (_delayBeforeHandoff > TimeSpan.Zero)
|
||||
await Task.Delay(_delayBeforeHandoff, ct);
|
||||
|
||||
if (_spawn)
|
||||
{
|
||||
var spawned = await context.MessageRouter!.SpawnAgentAsync(
|
||||
context.AgentId, target, "Auftrag", context.InstanceId, context.Delegation, ct);
|
||||
|
||||
Handoffs.Add((context.AgentId, target, spawned.Started, spawned.Error));
|
||||
|
||||
return spawned.Started
|
||||
? ToolResult.Ok(spawned.FinalMessage ?? "")
|
||||
: ToolResult.Ok(JsonSerializer.Serialize(new { spawned = false, error = spawned.Error }));
|
||||
}
|
||||
|
||||
var result = await context.MessageRouter!.SendMessageAsync(
|
||||
context.AgentId, target, "Frage", context.InstanceId, context.Delegation, ct);
|
||||
|
||||
Handoffs.Add((context.AgentId, target, result.Delivered, result.Error));
|
||||
|
||||
// Wie das echte Tool: Eine Ablehnung ist kein Wurf, sondern ein Ergebnis, das
|
||||
// der Agent lesen und auf das er anders reagieren kann.
|
||||
return result.Delivered
|
||||
? ToolResult.Ok(result.Response ?? "")
|
||||
: ToolResult.Ok(JsonSerializer.Serialize(new { delivered = false, error = result.Error }));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user