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:
@@ -16,5 +16,12 @@ public sealed record AgentToolContext(
|
||||
string? SharedWorkspacePath = null,
|
||||
IAgentMessageRouter? MessageRouter = null,
|
||||
IMemoryRepository? Memory = null,
|
||||
ITaskRepository? Tasks = null
|
||||
ITaskRepository? Tasks = null,
|
||||
|
||||
/// <summary>
|
||||
/// Wer an diesem Aufruf schon beteiligt ist. Werkzeuge, die einen anderen Agenten
|
||||
/// beauftragen, reichen die Kette weiter — sonst kann die Engine einen Zyklus
|
||||
/// nicht von einer neuen Delegation unterscheiden. Siehe <see cref="DelegationChain"/>.
|
||||
/// </summary>
|
||||
DelegationChain? Delegation = null
|
||||
);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace ClawdDotNet.Core.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Wer an diesem Aufruf schon beteiligt ist — die Kette vom ersten Agenten bis zum
|
||||
/// gerade laufenden.
|
||||
///
|
||||
/// <para><b>Warum es die Kette gibt (B8).</b> <c>send_message</c> ist ein synchroner
|
||||
/// Aufruf: A hält sein Gate, während es auf B wartet. Ruft B nun A, wartet B auf ein
|
||||
/// Gate, das A hält und erst freigibt, wenn B geantwortet hat. Das löste nur der
|
||||
/// Timeout des äußeren Laufs auf — bis dahin standen beide Agenten.</para>
|
||||
///
|
||||
/// <para><b>Die Zusagen</b>, auf denen alles Weitere aufsetzt:</para>
|
||||
/// <list type="number">
|
||||
/// <item>Ein Agent kommt in einer Kette höchstens einmal vor. Damit wartet niemand
|
||||
/// auf ein Gate, das er in derselben Kette schon hält — der Verklemmung ist die
|
||||
/// Grundlage entzogen, nicht nur die Wahrscheinlichkeit genommen.</item>
|
||||
/// <item>Eine Kette ist höchstens <c>MaxDelegationDepth</c> Schritte lang. Zyklen
|
||||
/// sind damit ausgeschlossen, eine lange Staffel A→B→C→D→… aber nicht; die
|
||||
/// kostet Zeit und Tokens, ohne dass jemand sie beauftragt hätte.</item>
|
||||
/// <item>Die Kette gehört zum <b>Aufruf</b>, nicht zum Agenten. Zwei unabhängige
|
||||
/// Läufe desselben Agenten behindern einander nicht.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>Unveränderlich: <see cref="Append"/> gibt eine neue Kette zurück. Ein Aufruf
|
||||
/// kann die Kette seines Aufrufers deshalb nicht verändern, und die Kette lässt sich
|
||||
/// gefahrlos über Fadengrenzen reichen.</para>
|
||||
/// </summary>
|
||||
public sealed class DelegationChain
|
||||
{
|
||||
private readonly string[] _agents;
|
||||
|
||||
private DelegationChain(string[] agents) => _agents = agents;
|
||||
|
||||
/// <summary>Der Anfang: ein Lauf, den niemand delegiert hat.</summary>
|
||||
public static DelegationChain Root(string agentId) => new([agentId]);
|
||||
|
||||
/// <summary>Die Agenten in der Reihenfolge, in der sie beauftragt wurden.</summary>
|
||||
public IReadOnlyList<string> Agents => _agents;
|
||||
|
||||
/// <summary>
|
||||
/// Die Zahl der Weitergaben — nicht die der Agenten. Ein Lauf ohne Delegation hat
|
||||
/// die Tiefe 0, A→B die Tiefe 1.
|
||||
/// </summary>
|
||||
public int Depth => _agents.Length - 1;
|
||||
|
||||
/// <summary>Ist dieser Agent in der Kette schon vertreten?</summary>
|
||||
public bool Contains(string agentId)
|
||||
=> Array.IndexOf(_agents, agentId) >= 0;
|
||||
|
||||
/// <summary>Die Kette mit einem weiteren Agenten am Ende.</summary>
|
||||
public DelegationChain Append(string agentId)
|
||||
=> new([.. _agents, agentId]);
|
||||
|
||||
/// <summary>Für Meldungen und Protokoll: <c>A -> B -> C</c>.</summary>
|
||||
public override string ToString() => string.Join(" -> ", _agents);
|
||||
}
|
||||
@@ -10,15 +10,32 @@ public interface IAgentMessageRouter
|
||||
{
|
||||
IReadOnlyList<AgentInfo> ListAgents(string callerAgentId);
|
||||
|
||||
/// <param name="instanceId">
|
||||
/// Die Instanz des aufrufenden Laufs — aus <see cref="AgentToolContext.InstanceId"/>
|
||||
/// durchgereicht. Leer heißt „nimm die Instanz, mit der die Engine eingerichtet
|
||||
/// wurde". Vorher galt immer Letzteres, auch wenn der Lauf mit einer anderen
|
||||
/// Instanz-Id angestoßen worden war (B13).
|
||||
/// </param>
|
||||
/// <param name="chain">
|
||||
/// Die Delegationskette des aufrufenden Laufs — aus
|
||||
/// <see cref="AgentToolContext.Delegation"/> durchgereicht. <c>null</c> heißt
|
||||
/// „von aussen angestossen", die Kette beginnt dann beim Absender. Ohne sie kann
|
||||
/// die Engine A→B→A nicht von einer neuen Delegation unterscheiden (B8).
|
||||
/// </param>
|
||||
Task<AgentMessageResult> SendMessageAsync(
|
||||
string fromAgentId,
|
||||
string toAgentId,
|
||||
string message,
|
||||
string? instanceId,
|
||||
DelegationChain? chain,
|
||||
CancellationToken ct);
|
||||
|
||||
/// <inheritdoc cref="SendMessageAsync"/>
|
||||
Task<AgentSpawnResult> SpawnAgentAsync(
|
||||
string fromAgentId,
|
||||
string targetAgentId,
|
||||
string taskMessage,
|
||||
string? instanceId,
|
||||
DelegationChain? chain,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user