E2E-Befund vm-test (2.0.16): Einzeldatei-Plugin-Einträge werden mit 'configured plugin path must be a directory' verworfen — V2 verlangt Verzeichnis-Packages. Layout: v1/explain-permissions.js (1.3.0, Fleet-Pin, SHA unverändert f02a153d…) + v2/index.js (2.0.0, getesteter Stand fe2e78c0…). Event-Namen empirisch korrigiert: permission.asked/replied.
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 und besteht aus dieser Doku plus je einer
Plugin-Fassung pro Major-API — es gibt bewusst keine zweiten Solo-Kopien:
| Pfad | Fassung | Status |
|---|---|---|
v1/explain-permissions.js |
1.3.0 (V1-Plugin-API, Einzeldatei) | eingefroren — aktiver Fleet-Pin (ADR-0010) |
v2/index.js |
2.0.0 (V2-Plugin-API, Verzeichnis-Package) | V2-only-Cut (az-fleet-7x8) — Pin aktiviert mit dem V2-Rollout |
opencode V2 (verifiziert 2.0.16, vm-test) lädt keine Einzeldatei-Plugin-
Einträge — file:///…/<datei>.js wird mit der Log-Warnung
configured plugin path must be a directory verworfen. v2/ ist deshalb
ein Verzeichnis-Package mit index.js als Entrypoint (kein package.json
erforderlich); ausgerollt wird das Verzeichnis als Ganzes.
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:
- 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). - Schickt eine Desktop-Notification, wenn der Freigabe-Dialog erscheint — mit der deutschen Erklärung des Modells, Fallback: der Roh-Befehl.
- Schreibt ein JSONL-Audit-Log: jeden Freigabe-Request (
ask), die abgegebene Erklärung (ask.explained) und die Entscheidung (replied). - Erzwingt optional (Default: aus), dass Erklärungen VOR dem Call kommen — sonst wirft der Call einen Fehler.
Installation
Lokale Kiste (Checkout + Symlink, V1)
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/v1/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)
v2/index.js exportiert Plugin.define({id: "az.explain-permissions", setup})
als Default-Export. Referenziert wird das Verzeichnis v2/ (umgenannt
nach Wunsch, z. B. explain-permissions-v2/) unter dem V2-Schlüssel
plugins mit absoluter file:///-URL:
{
"plugins": [{ "package": "file:///C:/ProgramData/opencode/plugins/explain-permissions-v2" }]
}
Alternativ läuft das Verzeichnis auch unverändert als Plugin-Package in
~/.config/opencode/plugins/ bzw. .opencode/plugins/ (dort automatisch
entdeckt, kein Config-Eintrag nötig). Benötigt opencode V2 (Core 2.0.x);
V1 (1.18.x) lädt es 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
# 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— mitversion: welche Plugin-Version wann geladen wurdeask— Freigabe-Request (Roh-Call, Muster, Session)ask.explained— die Erklärung, die das Modell vor dem Call abgegeben hatreplied— 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.replied im öffentlichen Event-Stream (ctx.event.subscribe), Payload {sessionID, requestID, reply} — feuert nur bei echter Client-Antwort, NICHT bei Non-Interactive-Auto-Reject (vm-test 2.0.16) |
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 viactx.permission.hook("evaluate")/ctx.session.hook("context")/ctx.tool.hook("execute.before"), Event-Streampermission.asked/permission.replied, Config-Lesung perfsmit V1+V2-Parser (JSONC-tolerant) statthooks.config, Action-Namen auf V2 (bash→shell, task→subagent; lsp/doom_loop verworfen). Layout: Verzeichnis-Packagev2/index.js— V2 (2.0.16) verwirft Einzeldatei- Einträge („configured plugin path must be a directory", vm-test-Evidenz). 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 vm-test (2.0.16): Plugin-Load (plugin-loaded 2.0.0), Config-Lesung Global+Managed+Projekt, System-Regel je Model-Request, evaluate-Hook allow+ask mit V1-Config-Normalisierung (bash→shell), ask-Audit + Notification-Pfad, Einzeldatei-Ablehnung reproduziert.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.transformwirkt nur modellseitig). Desktop-Notifications erhalten reinen Text ohne Markdown-Marker;EXPLAIN_FIRST_ERRORverweist 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_VERSIONim Dateikopf; Änderungen bekommen ein Tagexplain-permissions/v<version>in diesem Repo.