Verlorene Updates erkennen statt warten: optimistische Pruefung beim Ersetzen, Konflikte im Audit und im Lagebild (#720)

FileLedger merkt je Agent die gelesene Fassung einer Datei in gemeinsamen
Bereichen. write, copy und Downloads (FTP, RocketChat) ersetzen eine Datei nur,
wenn sie seit dem Lesen unveraendert ist; eine fremde Datei nicht ungelesen.
append bleibt ungeprueft, macht aeltere Lesestaende aber ungueltig.
Abgelehnte Schreibvorgaenge: eigener Audit-Status Conflict, Tabelle
FileConflicts, Hotspots (>= 3 in 7 Tagen) in der Teamuebersicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-28 10:45:46 +02:00
co-authored by Claude Opus 5.5
parent 8a7212c240
commit 79a3a7854f
21 changed files with 818 additions and 11 deletions
@@ -255,9 +255,19 @@ public sealed class RocketChatTool : IAgentTool, IToolJobProvider
if (file is null)
return ToolResult.Fail(pathError!);
// #720: a download over a shared file replaces it — not over a change this agent has not seen.
var guarded = file.Label != WorkspaceLayout.Personal && context.Files is not null;
if (guarded && context.Files!.CheckReplace(context.AgentId, context.RunId, file.FullPath, file.Label, Core.Files.FileLedger.HashOf(file.FullPath)) is { } conflict)
{
await context.Files.RecordAsync(conflict, ct);
return ToolResult.Conflict(conflict.Message($"{file.Label}:{targetName}"));
}
Directory.CreateDirectory(Path.GetDirectoryName(file.FullPath)!);
var bytes = await client.DownloadToFileAsync(fileId, file.FullPath, config.MaxDownloadBytes, ct);
if (guarded)
context.Files!.NoteReplace(context.AgentId, context.RunId, file.FullPath, Core.Files.FileLedger.HashOf(file.FullPath));
return ToolResult.Ok(JsonSerializer.Serialize(new
{