Umsetzungsplaene fuer vier neue Agenten-Tools angelegt

Aus Richards Ideensammlung, nach Sichtung des vorhandenen Codes:

- FileRW-Papierkorb + Cleanup-Job: delete verschiebt nach .trash statt
  endgueltig zu loeschen, Aufraeumen ueber IToolJobProvider/ToolJobScheduler.
  Macht Loeschen reversibel und erlaubt damit, FileRW.delete im Staging von
  Approve auf Auto herunterzustufen.
- AgentInspector: lesende Aufsicht ueber andere Agenten. Beleg-basiert
  (Audit/Receipts/Taskboard) statt datei-basiert; harte Allowlist, damit
  AgentSettings.json mit den API-Keys nicht in einen LLM-Kontext geraet.
- AgentEditor-Haertung: Identity/Soul-Aenderungen ueber die vorhandene
  StagingPolicy freigabepflichtig machen, Selbstbearbeitung sperren,
  Audit + restore ergaenzen. Das Tool selbst existiert bereits.
- WebSearch: Suche als eigenes Tool, Lesen bleibt bei WebFetch hinter der
  Domain-Whitelist. Kein agent-reach (Klartext-Cookies, Fremdprozess).
  Trennung Rechercheagent / handelnder Agent gegen Prompt Injection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-05 13:13:09 +02:00
co-authored by Claude Opus 5
parent b51cc29667
commit 61a93ffa58
4 changed files with 768 additions and 0 deletions
@@ -0,0 +1,232 @@
# Umsetzungsplan: FileRW-Papierkorb + Cleanup-Job
> Stand: 2026-08-05
> Ziel: `FileRW.delete` lö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.delete` im Staging von
> `Approve` auf `Auto` herunterzustufen (siehe Abschnitt 6).
---
## 1. Ausgangslage
`HandleDeleteAsync` in `src/ClawdDotNet.Tools.FileRW/FileRWTool.cs:318` löscht
sofort und endgültig:
```csharp
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`:
```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:
1. **`.trash` ist für alle direkten Aktionen gesperrt.**
`read`, `write`, `append`, `delete`, `copy` mit 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 über `list_trash` / `restore` / `purge`.
2. **`list` blendet `.trash` aus.** Sonst verschmutzt der Papierkorb jede
Verzeichnisauflistung und damit den Kontext des Agenten.
3. **Geschützte Pfade bleiben unantastbar.** Die bestehende Prüfung in
`HandleDeleteAsync` greift **vor** dem Verschieben — ein `protectedPath`
wandert auch nicht in den Papierkorb.
4. **`restore` prüft das Ziel erneut vollständig**: `WorkspacePath.Resolve`
auf den Originalpfad, Endungsprüfung, `IsPathProtected`, `IsActionAllowed`.
Der Originalpfad aus `_meta.json` ist **Eingabedatum, keine Wahrheit**
eine manipulierte Metadatei darf keinen Ausbruch ermöglichen.
5. **Shared Workspace: ein Papierkorb je Agent**
`.trash/<agentId>/<zeitstempel>/`. Sonst sieht und restauriert Agent A die
gelöschten Dateien von Agent B, und `purge` eines Agenten trifft alle.
Im Personal Workspace entfällt die Ebene (dort ist nur ein Agent).
6. **`purge` nur bei `sharedAccessLevel = Admin`** (Shared) bzw. explizit im
Personal Workspace. Der Regelweg zum endgültigen Löschen ist der
Cleanup-Job, nicht der Agent.
7. **Kein Verschieben über Laufwerksgrenzen annehmen.** `Directory.Move`
scheitert, wenn Workspace und `.trash` auf verschiedenen Volumes lägen —
das ist per Konstruktion nicht der Fall (`.trash` liegt 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**.
```csharp
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:
1. liest `retentionDays` aus `toolConfig` (Default **14**, Minimum 1),
2. durchläuft `.trash/*` in Personal- **und** Shared-Workspace,
3. löscht Ordner, deren Zeitstempel älter als die Aufbewahrungsfrist ist,
4. 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:
```json
"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 analog `WorkspacePath`: baut und validiert
`.trash`-Pfade, kapselt die Agent-Ebene im Shared Workspace
- [ ] `HandleDeleteAsync` auf Verschieben umstellen (Datei **und** Verzeichnis)
- [ ] `.trash`-Sperre in `GetAndValidatePath` (greift für alle direkten Aktionen)
- [ ] `.trash` aus `HandleListAsync` filtern
- [ ] `_meta.json` schreiben (über `AtomicFile`, wie beim Stock-Index)
- [ ] Tests: `tests/ClawdDotNet.Tools.Tests/FileRW/TrashTests.cs`
### Slice 2 — list_trash / restore / purge
- [ ] Drei Aktionen im `InputSchema` und im `switch` ergänzen
- [ ] `restore` mit vollständiger Zielprüfung (siehe 3.4)
- [ ] `purge` mit Admin-Prüfung
- [ ] `Description` des 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
- [ ] `IToolJobProvider` an `FileRWTool`
- [ ] `retentionDays` in `FileRWToolSettings`
(`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:
```csharp
["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 `restore`
wiederherstellbar.
- `read`/`write` auf einen `.trash`-Pfad wird abgelehnt.
- `restore` einer `.exe` in einen Workspace mit `allowedExtensions` ohne `.exe`
wird abgelehnt.
- Ein `protectedPath` lässt sich weiterhin nicht löschen.
- Agent B sieht im Shared-Papierkorb nicht die Einträge von Agent A.
- Nach Ablauf von `retentionDays` ist der Eintrag beim nächsten Job-Tick weg,
ohne dass ein LLM-Run stattgefunden hat.