feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# Staging-Freigabe für irreversible Aktionen
|
||||
|
||||
Setzt A2 aus der [Roadmap](Roadmap.md) um (F-A1 + S4). Irreversible Aktionen werden
|
||||
**gestaged statt ausgeführt**: Vorschlag → Review im Hauptfenster → Freigabe/Ablehnung.
|
||||
Aufbauend auf dem Taskboard (A1, Fortsetzung nach Freigabe) und dem Audit-Log (A3,
|
||||
Approval-Records).
|
||||
|
||||
Sicherheitswirkung: Eine Prompt-Injection (K2) kann dann nur noch einen **Vorschlag**
|
||||
erzeugen, keine Ausführung.
|
||||
|
||||
## Das Gate wird zum Durchsetzungspunkt (S4)
|
||||
|
||||
Das bisher wirkungslose `PermissionGate` (es prüfte nur, ob ein Tool zugewiesen ist)
|
||||
wird zur zentralen Stelle: Policy-Prüfung, Staging-Entscheidung und Audit-Hook an
|
||||
**einer** Stelle (`AgentEngine.ExecuteToolCallAsync`), statt ad-hoc in jedem Tool. S4
|
||||
geht hier auf.
|
||||
|
||||
## Policy: `auto | approve | deny` pro Tool/Aktion
|
||||
|
||||
Je (Tool, Aktion) eine Entscheidung:
|
||||
|
||||
- **`auto`** — läuft wie bisher.
|
||||
- **`approve`** — wird gestaged; die Ausführung wartet auf eine menschliche Freigabe.
|
||||
- **`deny`** — wird gar nicht erst vorgeschlagen, sondern abgelehnt.
|
||||
|
||||
Auflösung vom Speziellen zum Allgemeinen: `Tool.Aktion` → `Tool` → Standard (`auto`).
|
||||
Die „Aktion" ist das `action`-Argument des Aufrufs (die meisten Tools haben es).
|
||||
|
||||
**Eingebaute Standardregeln** (nach Sichtung der Tools, überschreibbar per Konfiguration)
|
||||
— genau die in der Roadmap genannten irreversiblen Aktionen:
|
||||
|
||||
| Tool.Aktion | Standard |
|
||||
|---|---|
|
||||
| `Mail.send` | approve |
|
||||
| `Telegram.send_message` | approve |
|
||||
| `Database.insert`, `Database.upsert` | approve |
|
||||
| `FileRW.delete` | approve |
|
||||
| `FTP.upload`, `FTP.delete` | approve |
|
||||
| alles andere | auto |
|
||||
|
||||
Lesende Aktionen (`Mail.read_inbox`, `FileRW.read`, `Database.query`, …) bleiben `auto` —
|
||||
Staging soll schützen, nicht lähmen.
|
||||
|
||||
## Plan-Freeze
|
||||
|
||||
Freigegeben wird ein **eingefrorener, konkreter Aufruf**: Tool, Aktion und die **exakten
|
||||
Argumente** zum Zeitpunkt des Stagings. Ausgeführt wird genau das Eingefrorene (der
|
||||
gespeicherte Argument-JSON), nie eine nachträglich veränderte Fassung. Jede Änderung
|
||||
wäre eine neue Freigabe.
|
||||
|
||||
Das ist auch der Grund, warum der Agent den Aufruf **nicht** nach der Freigabe erneut
|
||||
formuliert (er könnte etwas anderes bauen) — der eingefrorene JSON wird direkt an das
|
||||
Tool gegeben.
|
||||
|
||||
## Fortsetzung nach Freigabe — kein pausierter Lauf
|
||||
|
||||
Die tragende Architektur-Vorgabe (aus der Roadmap-Einstufung): **Kein pausierter, im
|
||||
Speicher gehaltener Lauf.** Der Ablauf:
|
||||
|
||||
1. **Vorschlag.** Der Agent ruft eine `approve`-Aktion auf. Das Gate führt sie nicht aus,
|
||||
sondern legt einen **Pending**-Datensatz an (eingefrorener Aufruf) und gibt dem Agenten
|
||||
„Zur Freigabe vorgelegt (#id)" als Tool-Ergebnis zurück. Der Lauf endet **regulär** —
|
||||
der Agent schließt ab, nichts hängt im Speicher.
|
||||
2. **Review.** Ein Mensch sieht die offenen Vorschläge im Hauptfenster und entscheidet.
|
||||
3a. **Freigabe.** Der eingefrorene Aufruf wird **direkt** ausgeführt (standalone, nicht
|
||||
über einen Chat-Lauf). Das Ergebnis wird festgehalten, und ein **Folge-Task** (A1)
|
||||
weckt den Agenten: „Deine Aktion X wurde freigegeben und ausgeführt, Ergebnis: Y —
|
||||
mach weiter." Der Scanner stellt ihn zu (`ChatAsync`, bestehender Kontext).
|
||||
3b. **Ablehnung.** Ein Folge-Task weckt den Agenten mit der Ablehnung (samt Grund).
|
||||
|
||||
Echtes Suspend/Resume eines laufenden `ChatAsync` wäre Fable-Terrain — und ist mit dieser
|
||||
Vereinfachung unnötig.
|
||||
|
||||
## Approval-Records (A3)
|
||||
|
||||
Jede Entscheidung — Freigabe wie Ablehnung — wird an zwei Stellen verankert:
|
||||
|
||||
- Im **Staging-Datensatz** selbst: Status, `DecidedBy`, `DecidedAt`, Ergebnis-Verweis
|
||||
bzw. Ablehnungsgrund.
|
||||
- Als **Audit-Eintrag** (A3): Tool, Ausgang, `source: approval`, „Freigegeben von …" bzw.
|
||||
„Abgelehnt von …". Die Herkunft stempelt auch hier das System, nicht der Agent.
|
||||
|
||||
Der Vorschlag selbst wird beim Anlegen als Audit-Eintrag mit Status **`Staged`** notiert —
|
||||
so ist die ganze Kette (Vorschlag → Entscheidung → Ausführung) im Log nachvollziehbar.
|
||||
|
||||
## Nebenläufigkeit
|
||||
|
||||
Zwei Reviewer dürfen nicht denselben Vorschlag doppelt freigeben. Der Übergang
|
||||
`pending → approved/rejected` ist ein **atomares, bedingtes `UPDATE`** (dieselbe
|
||||
Claim-Technik wie beim Taskboard): Genau einer gewinnt, der zweite Klick läuft ins Leere.
|
||||
Erst nach gewonnenem Übergang wird der eingefrorene Aufruf ausgeführt.
|
||||
|
||||
## Verdrahtung
|
||||
|
||||
- `StagingGate` (Policy + Anlegen des Vorschlags) hängt optional an der Engine — ohne es
|
||||
läuft alles wie bisher (`auto`).
|
||||
- Der eingefrorene Aufruf wird über `IFrozenCallExecutor` (von der Engine implementiert)
|
||||
ausgeführt: gültiger Tool-Kontext, aber ohne LLM-Schleife.
|
||||
- `StagingService` (Freigabe/Ablehnung) nutzt Executor, Audit und das Taskboard für den
|
||||
Folge-Task. Es ist die API, die die Review-Oberfläche aufruft.
|
||||
|
||||
## Offen
|
||||
|
||||
- **Review-Oberfläche** im Hauptfenster (Liste der offenen Vorschläge, Freigeben/Ablehnen)
|
||||
— die Dienst-API steht bereit; die WinForms-Ansicht ist die verbleibende Integration.
|
||||
- **Output-Scrubbing** greift auch hier auf den gespeicherten Argument-JSON, sobald es
|
||||
steht (eigener Roadmap-Punkt).
|
||||
- **Orders** (Handelsaufträge) reihen sich später als weitere `approve`-Aktionen ein.
|
||||
Reference in New Issue
Block a user