# explain-permissions — opencode-Plugin für laienverständliche Tool-Call-Erklärungen Dieses Verzeichnis ist die **kanonische Quelle** für das opencode-Plugin `explain-permissions`. Es lebt im Sammel-Repos [`opencode-plugins`](../README.md) und besteht aus genau einer Plugin-Datei (`explain-permissions.js`) plus dieser Doku. Wer das Plugin nutzt, bezieht es von hier — es gibt bewusst keine zweiten Solo-Kopien. Konsumenten: | Konsument | Bezug | |---|---| | Lokale Linux-Kiste (m3tam3re) | Repo-Checkout + Symlink in `~/.config/opencode/plugins/` | | Fleet (`az-fleet`) | später per Repo-Pin (anonym lesbar, kein Token für Pulls) | ## Zweck Jeden berechtigungspflichtigen Tool-Call laienverständlich erklären — für Menschen ohne Computer-Kenntnisse. Das Plugin: 1. **Injiziert die Freigabe-Regel in den System-Prompt** (Was / Welche Folgen / Wie riskant, auf Deutsch, vor jedem freigabepflichtigen Call) — inklusive der konkreten ask-/allow-Muster aus der opencode-Konfiguration. Seit v1.3.0 mit festem Ausgabeformat: Zitat-Block (`>`) mit fetten Labels, jeder Punkt auf eigener Zeile. TUI und OpenCode Desktop rendern das als abgesetzten Kasten mit farbigem Balken — die Erklärung hebt sich damit klar vom restlichen Chatverlauf ab, und die drei Punkte bleiben sauber untereinander lesbar. Desktop-Notifications bekommen reinen Text (Markdown-Marker werden entfernt). 2. **Schickt eine Desktop-Notification**, wenn der Freigabe-Dialog erscheint — mit der deutschen Erklärung des Modells, Fallback: der Roh-Befehl. 3. **Schreibt ein JSONL-Audit-Log**: jeden Freigabe-Request (`ask`), die abgegebene Erklärung (`ask.explained`) und die Entscheidung (`replied`). 4. **Erzwingt optional** (Default: aus), dass Erklärungen VOR dem Call kommen — sonst wirft der Call einen Fehler. ## Installation ### Lokale Kiste (Checkout + Symlink) ```bash git clone https://git.az-gruppe.com/AZ-Intec-GmbH/opencode-plugins.git ~/p/AZ/opencode-plugins ln -sf ~/p/AZ/opencode-plugins/explain-permissions/explain-permissions.js ~/.config/opencode/plugins/explain-permissions.js ``` Danach opencode neu starten (siehe unten). Wichtig: nur EINE Datei dieses Namens im Plugin-Ordner — sonst lädt opencode das Plugin doppelt. ### opencode V2 (ab v2.0.0 — V2-only) Die Datei exportiert `Plugin.define({id: "az.explain-permissions", setup})` als Default-Export und wird in `opencode.json(c)` unter dem V2-Schlüssel `plugins` referenziert (Einzel-Datei-Eintrag, absolute `file:///`-URL): ```jsonc { "plugins": [{ "package": "file:///C:/ProgramData/opencode/plugins/explain-permissions.js" }] } ``` Alternativ läuft die Datei auch unverändert als Datei-Plugin in `~/.config/opencode/plugins/` bzw. `.opencode/plugins/` (dort wird sie automatisch entdeckt, kein Config-Eintrag nötig). Benötigt opencode V2 (Core 2.0.x); V1 (1.18.x) lädt sie bewusst NICHT (V2-only-Cut) — die V1-Fleet bleibt auf der gepinnten v1.3.0. ### Fleet Pin auf ein Tag (z. B. `explain-permissions/v2.0.0`) oder `main`; anonymes `git clone` über HTTPS reicht, kein Token nötig. ## Konfiguration via Environment-Variablen | Variable | Wirkung | Default | |---|---|---| | `OPENCODE_EXPLAIN_NOTIFY=0` | Desktop-Notifications abschalten | an | | `OPENCODE_EXPLAIN_LOG=0` | Audit-Log abschalten | an | | `OPENCODE_EXPLAIN_INJECT=0` | System-Prompt-Regel abschalten | an | | `OPENCODE_EXPLAIN_ENFORCE=1` | Hartes Erzwingen der Erklärung aktivieren (Call ohne vorherige Erklärung → Fehler) | aus | | `OPENCODE_EXPLAIN_DEBUG=1` | Permission-Events ins Log schreiben | aus | | `OPENCODE_EXPLAIN_LOG_PATH=…` | Anderer Log-Pfad | plattformabhängig (siehe unten) | ## Notifications je Plattform (v1.2.0) | Plattform | Mechanismus | Anmerkung | |---|---|---| | Linux | `notify-send` (libnotify) | wie bisher | | macOS | `osascript display notification` | Boardmittel, keine Abhängigkeit | | Windows | PowerShell-Toast (WinRT) via `-EncodedCommand` | AppUserModelID = PowerShell-AUMID — **keine App-Registrierung nötig**; Titel/Text laufen über env-Variablen des Child-Prozesses (injektionssicher), XML-Escaping in PowerShell | Alle Zweige sind **fail-soft**: fehlendes Binary, headless Session oder Spawn-Fehler sind stille No-Ops — kein Crash, kein Log-Müll. Node und Bun behandeln ENOENT beim Spawn unterschiedlich (asynchrones `error`-Event vs. synchroner Wurf); das Plugin deckt beide Semantiken ab (try/catch plus error-Listener) und läuft damit in beiden Runtime-Welten. ## Log-Pfad-Entscheidung (v1.2.0) | Plattform | Default-Pfad | Begründung | |---|---|---| | Windows | `%LOCALAPPDATA%\opencode\explain-permissions.jsonl` | idiomatisch, roamt nicht, liegt neben anderen App-States; Fallback `~\AppData\Local\…`, falls `LOCALAPPDATA` nicht gesetzt | | Linux/macOS | `~/.local/state/opencode/explain-permissions.jsonl` | XDG-State-Konvention (unverändert seit v1.1.0) | `~/.local/state` würde zwar überall funktionieren (auch auf Windows), aber `%LOCALAPPDATA%` ist auf Windows der etablierte Ort für pro-Nutzer-State — Pilotnutzer-Support findet Dateien dort, wo Windows sie erwartet. ## Audit-Log ansehen ```bash # Linux/macOS tail -f ~/.local/state/opencode/explain-permissions.jsonl | jq . # Windows (PowerShell) Get-Content "$env:LOCALAPPDATA\opencode\explain-permissions.jsonl" -Wait ``` Relevante Events: - `plugin-loaded` — mit `version`: welche Plugin-Version wann geladen wurde - `ask` — Freigabe-Request (Roh-Call, Muster, Session) - `ask.explained` — die Erklärung, die das Modell vor dem Call abgegeben hat - `replied` — die Entscheidung (`once` / `always` / `reject`) ## Wichtig: Neustart **Plugin-Änderungen greifen erst nach Neustart von opencode** — eine neue Chat-Session im laufenden Prozess reicht nicht. Der Audit-Log-Eintrag `plugin-loaded` (mit Version) zeigt an, welche Version wann geladen wurde. Unter V2 gilt zusätzlich: Der Background-Service besitzt und cachet die Konfiguration — Config-/Plugin-Änderungen wirken erst nach `opencode service restart`; Änderungen an beobachteten Plugin-Verzeichnissen laden automatisch neu. ## V2-Migrationsmappe (v2.0.0, az-fleet-7x8) | V1 (1.18.x) | V2 (2.0.x) | |---|---| | `export const ExplainPermissionsPlugin = async ({client}) => …` (Hooks-Objekt) | `export default Plugin.define({id: "az.explain-permissions", async setup(ctx)})`, Hooks via `ctx.*.hook()` registriert | | Config-Schlüssel `"plugin": ["file:///…"]` | `"plugins": [{"package": "file:///…"}]` (Einzel-Datei-Einträge unterstützt) | | `hooks.config` (liest permission-Sektion) | entfallen — Config-Dateien direkt per `fs` gelesen (Managed-Verzeichnis, Global, Projekt, `OPENCODE_CONFIG`), **beide** Formate: V1-`permission`-Map + V2-`permissions`-Array; Action-Renames bash→shell, task→subagent, write/patch→edit; `lsp`/`doom_loop` sind in V2 tote Actions und werden verworfen | | `permission.ask`-Hook + `permission.asked/updated`-Events | `ctx.permission.hook("evaluate")` — feuert für allow UND ask nach Regel-Evaluation, vor Dialog/Ausführung (explizites deny ruft den Hook nicht); Effect `ask` → `recordAsk` + `explainAndNotify` | | `permission.replied`-Event | `permission.v2.replied` im öffentlichen Event-Stream (`ctx.event.subscribe`), Payload `{sessionID, requestID, reply}` | | `experimental.chat.system.transform` | `ctx.session.hook("context")` → `event.system.push({type: "text", text})` — Agent-Loop inkl. Tool-Continuations | | `tool.execute.before` (ENFORCE) | `ctx.tool.hook("execute.before")` — Event `{tool, sessionID, callID, input}` | | `client.session.messages({path:{id}})` | `ctx.session.context({sessionID})` — Tool-Parts tragen die Call-ID als `id` (matcht `source.id` der Permission-Evaluation) | | `plugin-loaded`-Audit-Eintrag beim Laden | in `setup()` (jetzt mit Plugin-ID, opencode-Version, Location) | Unverändert übernommen: Notification-Logik je Plattform (PowerShell-WinRT-AUMID, injektionssicher), JSONL-Audit-Log samt Env-Flags, fail-soft-Muster, das Erklär-Format (Zitat-Block) und die ENFORCE-Semantik (greift nur auf Calls, die durch einen Freigabe-Dialog gegangen sind). Der `@opencode/plugin`-Import ist bewusst dynamisch mit Fallback auf die Rohestform `{id, setup}`: Schlägt die Paketauflösung fehl (Einzel-Datei-Package außerhalb eines npm-Kontexts), bleibt der Fleet-Load fail-soft und das Plugin trotzdem aktiv, statt beim Laden der ganzen Datei zu werfen. ## Versionierung - `explain-permissions/v2.0.0` — V2-only-Port auf die V2-Plugin-API (Core 2.0.x, az-fleet-7x8): `Plugin.define`-Default-Export, Hooks via `ctx.permission.hook("evaluate")` / `ctx.session.hook("context")` / `ctx.tool.hook("execute.before")`, Event-Stream `permission.v2.asked` / `permission.v2.replied`, Config-Lesung per `fs` mit V1+V2-Parser (JSONC-tolerant) statt `hooks.config`, Action-Namen auf V2 (bash→shell, task→subagent; lsp/doom_loop verworfen). KEIN V1-Export mehr (V2-only-Cut, Entscheidung 25.09.) — V1-Fleet bleibt gepinnt auf v1.3.0. Verifikation: Mock-ctx-Tests 21/21 grün (Config-Parsing beider Formen, System-Regel-Injektion, evaluate/ask-Pfad, Hook↔Event-Dedup, replied-Audit, ENFORCE); E2E auf vm-test laut Issue az-fleet-7x8. - `explain-permissions/v1.3.0` — Erklärungs-Format überarbeitet: Zitat-Block (`>`) mit fetten Labels (`**WAS passiert:**` usw.), jeder Punkt auf eigener Zeile — bessere Lesbarkeit und visuelle Hervorhebung in TUI und Desktop (beide rendern Blockquotes mit farbigem Balken; verifiziert gegen opencode 1.18.30, das keinen Anzeige-Transform-Hook bietet — `experimental.chat.messages.transform` wirkt nur modellseitig). Desktop-Notifications erhalten reinen Text ohne Markdown-Marker; `EXPLAIN_FIRST_ERROR` verweist auf das neue Format. - `explain-permissions/v1.2.0` — Cross-Platform-Notifications (Linux notify-send / macOS osascript / Windows PowerShell-Toast mit PowerShell-AUMID, keine App-Registrierung), fail-soft Spawn in Node- und Bun-Runtimes, Windows-Log-Pfad `%LOCALAPPDATA%\opencode\` (Entscheidung siehe oben). - `explain-permissions/v1.1.0` — Baseline, exakt der Stand der lokalen Solo-Datei vom 17.09.2026 (Inhaltsgleichheit per SHA-256 verifiziert). - Fortlaufende Versionsnummer in `PLUGIN_VERSION` im Dateikopf; Änderungen bekommen ein Tag `explain-permissions/v` in diesem Repo.