Der Dateiverlauf (#719) haelt die Vorfassung jeder geloeschten Datei - ein eigener Papierkorb waere doppelt. delete einer Datei, die der Verlauf aufhebt, laeuft ohne Freigabe; Ordner nur mit recursive:true (delete_folder, Freigabe); zu grosse Dateien oder Verlauf aus (delete_unkept, Freigabe); die Wurzel nie. Neue Aktionen deleted und restore mit voller Pfad-/Endungspruefung. IAgentTool.StagingAktion bekommt eine Ueberladung mit Kontext. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.8 KiB
Umsetzungsplan: FileRW-Papierkorb + Cleanup-Job
Überholt am 2026-10-02 (#195 umgesetzt, anders als hier geplant). Seit #719 hebt der Dateiverlauf die Vorfassung jeder gelöschten Datei auf (90 Tage, bis 20 MB je Datei). Ein eigener
.trash-Ordner wäre doppelt gewesen. Umgesetzt ist deshalb:deleteeiner Datei, die der Verlauf aufheben kann, läuft ohne Freigabe; ein Ordner nur mitrecursive: trueund mit Freigabe (FileRW.delete_folder); eine Datei, die der Verlauf nicht aufheben kann (zu groß, Verlauf aus), mit Freigabe (FileRW.delete_unkept); die Wurzel eines Arbeitsbereichs nie. Agenten sehen mitdeleted, was sie zurückholen können, und holen es mitrestorezurück — mit derselben Pfad- und Endungsprüfung wie ein Schreiben. Der Rest dieses Dokuments ist die alte Planung.
Stand 2026-08-23: beschlossen, noch nicht gebaut. Eingeordnet als Punkt 3 der Roadmap — die Reihenfolge gilt dort, dieses Dokument ist der Bauplan.
Stand: 2026-08-05 Ziel:
FileRW.deletelöscht nicht mehr endgültig, sondern verschiebt in einen Papierkorb je Workspace. Ein Cron-Job räumt den Papierkorb nach X Tagen auf. Reihenfolge: Zuerst umsetzen — kleinster Eingriff, entschärft ein reales Risiko und ist Voraussetzung dafür,FileRW.deleteim Staging vonApproveaufAutoherunterzustufen (siehe Abschnitt 6).
1. Ausgangslage
HandleDeleteAsync in src/ClawdDotNet.Tools.FileRW/FileRWTool.cs:318 löscht
sofort und endgültig:
if (File.Exists(path)) { File.Delete(path); ... }
else if (Directory.Exists(path)) { Directory.Delete(path, true); ... }
Ein einziger Tool-Call mit path: "." räumt damit den kompletten Workspace
eines Agenten ab — ohne Rückweg. Gleichzeitig fehlt dem Agenten jede Möglichkeit,
gefahrlos aufzuräumen: Jede Aufräumaktion ist irreversibel.
Vorhandene Schutzmechanismen, die erhalten bleiben müssen:
| Mechanismus | Ort | Verhalten |
|---|---|---|
| Workspace-Einsperrung | WorkspacePath.Resolve |
absolute Pfade, .., ADS (:) verboten |
| Zugriffsstufen | IsActionAllowed |
delete im Shared nur bei sharedAccessLevel = Admin |
| Geschützte Pfade | IsPathProtected |
protectedPaths sind append-only, kein Löschen |
| Datei-Locks | GetFileLock |
pro Pfad ein SemaphoreSlim |
2. Zielverhalten
delete → verschiebt nach .trash/<yyyyMMdd_HHmmss>/<originalpfad>
list_trash → listet Papierkorb-Einträge mit Originalpfad und Löschzeitpunkt
restore → holt einen Eintrag an seinen Originalpfad zurück
purge → löscht endgültig (nur Admin, ausdrücklicher Opt-in)
Papierkorb-Layout (im jeweiligen Workspace-Root):
.trash/
├── 20260805_142233/
│ ├── _meta.json ← Originalpfad, Zeitpunkt, Agent, Typ
│ └── notizen.md ← der gelöschte Inhalt (Datei oder Verzeichnis)
└── 20260805_151002/
├── _meta.json
└── alte-recherche/
_meta.json:
{
"originalPath": "recherche/notizen.md",
"workspace": "personal",
"deletedAt": "2026-08-05T14:22:33Z",
"deletedBy": "senior_developer",
"type": "file"
}
Der Zeitstempel ist der Ordnername — damit braucht der Cleanup-Job keine
Metadatei zu lesen, um das Alter zu bestimmen (_meta.json ist Komfort,
keine Voraussetzung). Bei Kollision wird _1, _2 … angehängt, analog
HandleStockAddAsync.
3. Sicherheitsanforderungen
Diese Punkte sind nicht optional — ohne sie öffnet der Papierkorb neue Lücken:
-
.trashist für alle direkten Aktionen gesperrt.read,write,append,delete,copymit einem Pfad in.trash/werden abgelehnt. Sonst wäre der Papierkorb ein Ablageort, über den die Endungsprüfung (personalAllowedExtensions) umgangen werden kann: eine.exe„löschen" und aus dem Papierkorb an beliebiger Stelle wieder herausholen. Zugriff ausschließlich überlist_trash/restore/purge. -
listblendet.trashaus. Sonst verschmutzt der Papierkorb jede Verzeichnisauflistung und damit den Kontext des Agenten. -
Geschützte Pfade bleiben unantastbar. Die bestehende Prüfung in
HandleDeleteAsyncgreift vor dem Verschieben — einprotectedPathwandert auch nicht in den Papierkorb. -
restoreprüft das Ziel erneut vollständig:WorkspacePath.Resolveauf den Originalpfad, Endungsprüfung,IsPathProtected,IsActionAllowed. Der Originalpfad aus_meta.jsonist Eingabedatum, keine Wahrheit — eine manipulierte Metadatei darf keinen Ausbruch ermöglichen. -
Shared Workspace: ein Papierkorb je Agent —
.trash/<agentId>/<zeitstempel>/. Sonst sieht und restauriert Agent A die gelöschten Dateien von Agent B, undpurgeeines Agenten trifft alle. Im Personal Workspace entfällt die Ebene (dort ist nur ein Agent). -
purgenur beisharedAccessLevel = Admin(Shared) bzw. explizit im Personal Workspace. Der Regelweg zum endgültigen Löschen ist der Cleanup-Job, nicht der Agent. -
Kein Verschieben über Laufwerksgrenzen annehmen.
Directory.Movescheitert, wenn Workspace und.trashauf verschiedenen Volumes lägen — das ist per Konstruktion nicht der Fall (.trashliegt im Workspace-Root), aber der Fehlerfall wird sauber gemeldet statt zu einer Teilkopie zu führen.
4. Cleanup-Job
FileRWTool implementiert zusätzlich IToolJobProvider
(src/ClawdDotNet.Core/Tools/IToolJobProvider.cs). Die Infrastruktur ist
vollständig vorhanden — ToolJobScheduler kennt Cron, RunOnStart und
manuelles Auslösen; es braucht keinen neuen Scheduler.
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
[
new("filerw_trash_cleanup", "Papierkorb aufräumen",
"Löscht Papierkorb-Einträge, die älter als retentionDays sind")
];
ExecuteJobAsync bekommt workspacePath und agentId bereits übergeben
(siehe Signatur des Interfaces). Der Job:
- liest
retentionDaysaustoolConfig(Default 14, Minimum 1), - durchläuft
.trash/*in Personal- und Shared-Workspace, - löscht Ordner, deren Zeitstempel älter als die Aufbewahrungsfrist ist,
- gibt immer
ToolJobResult.NoAction(logSummary)zurück — der Agent wird nie geweckt. Aufräumen ist kein Ereignis, das ein LLM-Run wert wäre (und kein Token kosten soll).
Der Shared-Papierkorb wird nur bereinigt, wenn der Job-Agent dort Admin-Rechte hat; andernfalls nur der eigene Unterordner. Damit räumt nicht jeder Agent bei jedem Tick fremde Einträge ab.
Beispielkonfiguration im Agenten:
"toolJobs": [
{
"jobId": "trash-daily",
"toolName": "FileRW",
"jobTypeId": "filerw_trash_cleanup",
"cron": "0 3 * * *",
"enabled": true,
"runOnStart": false
}
]
Achtung Zeitzone: ToolJobScheduler.RunToolJobAsync rechnet mit
DateTime.Now (lokal), die Papierkorb-Zeitstempel oben sind UTC. Das
Altersvergleich im Job daher konsequent in UTC durchführen
(DateTime.UtcNow), nicht mischen. Dieselbe Falle ist bereits aus der
Watchdog-Integration bekannt.
5. Umsetzungsschritte
Slice 1 — Papierkorb im Tool (Kern)
TrashPath-Helfer analogWorkspacePath: baut und validiert.trash-Pfade, kapselt die Agent-Ebene im Shared WorkspaceHandleDeleteAsyncauf Verschieben umstellen (Datei und Verzeichnis).trash-Sperre inGetAndValidatePath(greift für alle direkten Aktionen).trashausHandleListAsyncfiltern_meta.jsonschreiben (überAtomicFile, wie beim Stock-Index)- Tests:
tests/ClawdDotNet.Tools.Tests/FileRW/TrashTests.cs
Slice 2 — list_trash / restore / purge
- Drei Aktionen im
InputSchemaund imswitchergänzen restoremit vollständiger Zielprüfung (siehe 3.4)purgemit Admin-PrüfungDescriptiondes Tools ergänzen, damit das LLM den Papierkorb kennt- Tests: Restore an geschützten Pfad, Restore mit manipulierter
_meta.json, Restore mit inzwischen belegtem Zielpfad
Slice 3 — Cleanup-Job
IToolJobProvideranFileRWToolretentionDaysinFileRWToolSettings(Models/ToolSettingsViewModels.cs:68) + Designer-Property- Job-Definition in der UI auswählbar (läuft über die bestehende Job-Verwaltung, kein neuer Dialog)
- Tests mit
FakeTimeProvider(tests/ClawdDotNet.Core.Tests/Infrastructure/FakeTimeProvider.cs)
Slice 4 — Dokumentation
docs/ToolDevelopmentGuide.md: FileRW-Abschnitt (Zeile ~221) auf die neuen Aktionen aktualisieren- Prompt-Hinweis für Agenten: „Löschen ist reversibel, der Papierkorb wird nach X Tagen geleert" — sonst bleibt der Agent unnötig vorsichtig
6. Folgeentscheidung: Staging herunterstufen
StagingPolicy.DefaultRules (src/ClawdDotNet.Core/Staging/StagingPolicy.cs)
führt FileRW.delete heute als Approve — jede Aufräumaktion braucht eine
menschliche Freigabe. Das ist richtig, solange Löschen endgültig ist.
Mit dem Papierkorb ist es das nicht mehr. Empfehlung nach Slice 2:
["FileRW.delete"] = StagingDecision.Auto, // reversibel über .trash
["FileRW.purge"] = StagingDecision.Approve // endgültig → Freigabe
Damit kann der Agent selbstständig aufräumen (genau der Wunsch aus der Ideensammlung), ohne dass irreversible Aktionen ungefragt durchgehen.
7. Abnahmekriterien
- Ein gelöschter Ordner liegt vollständig im Papierkorb und ist per
restorewiederherstellbar. read/writeauf einen.trash-Pfad wird abgelehnt.restoreeiner.exein einen Workspace mitallowedExtensionsohne.exewird abgelehnt.- Ein
protectedPathlässt sich weiterhin nicht löschen. - Agent B sieht im Shared-Papierkorb nicht die Einträge von Agent A.
- Nach Ablauf von
retentionDaysist der Eintrag beim nächsten Job-Tick weg, ohne dass ein LLM-Run stattgefunden hat.