Skip to content
    ↑↓ select↵ openesc close
    mightykatun

    Reviewer

    opencode-reviewer·v2.3.0·Notifications

    Safety reviews, report history, optional one-time auto-approval and Linux desktop notifications for OpenCode permissions

    GitHub stars

    1

    Monthly installs

    3,347

    1.4k in 7 days

    Composite score

    44.1

    Multi-signal model

    Last commit

    11 hours ago

    2026-10-20

    Install and configure

    opencode.json

    Writes to this project's opencode.json — applies to this repository only.

    opencode.json

    {
      "$schema": "https://opencode.ai/config.json",
      "plugin": ["opencode-reviewer@2.3.0"]
    }

    OpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.

    Reviews pending permissions in the OpenCode sidebar using a separate LLM. Reviews native shell commands, file edits, skill loading, external-directory access and tools/MCPs. Supports streaming explanations, saved report history and stats, auto-approval, and Linux desktop notifications (Gnome) with distinct sounds.

    Commands:

    • Review/outcome history panel: Alt+h
    • Per session reviewer instructions: Alt+i

    This plugin is meant to enable you to run sessions with less frequent input in a safer way. Also giving the reviewer context of specific files the agent might want to run/see/... The review prompts are freely configurable, so you can determine the reviewers behavior very accurately.

    ⚠️ Disclaimer

    This does not make running agents foolproof and safe! An actor able to run any/intrusive commands on your machine can always exploit an attack surface or create damage! It's meant to reduce risks, and enables you to get more insight into what your agent is doing.

    For OpenCode V2 on Linux.

    Install and config

    Reviews send request data, the latest user prompt, and selected file snapshots, diffs, skill instructions or tool arguments to your configured endpoint before permission approval. Shell snapshots can follow symlinks outside the project; denying the command afterward cannot undo that disclosure. Use an endpoint you trust, and a local reviewer or a sandbox without accessible secrets when working with untrusted repositories.

    Add this entry to the global ~/.config/opencode/opencode.json, or $XDG_CONFIG_HOME/opencode/opencode.json when XDG_CONFIG_HOME is set. OpenCode loads the npm package and its terminal component from this single entry. No reviewer entry is needed in cli.json.

    {
      "$schema": "https://opencode.ai/config.json",
      "plugins": [
        {
          "package": "opencode-reviewer@latest",
          "options": {
            "baseURL": "https://openrouter.ai/api/v1",
            "model": "your-model",
            "apiKey": "your-api-key",
            "apiKeyEnv": "OPENCODE_REVIEWER_API_KEY",
            "instructions": "/absolute/path/to/reviewer-prompts",
            "stream": false,
            "reviewBash": true,
            "reviewEdits": true,
            "reviewSkills": true,
            "reviewTools": true,
            "reviewExternalDirectories": true,
            "autoApprove": false,
            "fastMode": false,
            "extraCareful": true,
            "autoApproveDelaySeconds": 15,
            "notify": true,
            "notifySound": true,
            "staleReminderSeconds": 60,
            "notifications": {
              "attention": { "banner": true, "sound": true },
              "unsafe": { "banner": true, "sound": true },
              "question": { "banner": true, "sound": true },
              "approved": { "banner": true, "sound": false },
              "error": { "banner": true, "sound": true },
              "ended": { "banner": true, "sound": true }
            },
            "notificationSoundDirectory": "/absolute/path/to/notification-sounds",
            "formatRetries": 1,
            "maxOutputTokens": 4096,
            "timeoutMs": 30000,
            "maxFiles": 6,
            "maxEvidenceBytes": 131072
          }
        }
      ]
    }
    

    Only baseURL and model are required. Use any OpenAI-compatible Chat Completions endpoint: /chat/completions is appended to the base URL. Choose a model available at that endpoint.

    Replace apiKey, or remove it and set the variable named by apiKeyEnv before launching OpenCode. The inline key takes precedence. Omit both for an unauthenticated endpoint. Remove instructions to use the built-in prompts and notificationSoundDirectory to use the bundled sounds.

    Opencode docs

    OpenCode config and CLI plugins guides.

    Options reference

    These are options inside the plugin's options object in global opencode.json. Defaults apply only to omitted options. null is invalid. Boolean and integer options require JSON booleans and numbers, not quoted strings. Integer ranges are inclusive, fractions are invalid, and the maximum safe integer is 9,007,199,254,740,991. Unknown option names are rejected.

    Option Type Default Accepted values and behavior
    baseURL string Required HTTP(S) API base URL; /chat/completions is appended after removing trailing slashes. Embedded credentials, query strings and fragments are rejected.
    model string Required Nonempty model identifier accepted by the configured endpoint.
    apiKey string Unset Nonempty Bearer token. Takes precedence over apiKeyEnv.
    apiKeyEnv string Unset Environment-variable name matching [A-Za-z_][A-Za-z_0-9]*. Without an inline key, its value must be set and nonblank when a review runs. Omit both key options for no Authorization header.
    instructions string Unset (built-ins) Absolute, NUL-free directory path containing category and extra-careful prompt template overrides, not inline instructions. Missing named files use built-ins; supplied files must be readable, regular, non-symlink, nonempty UTF-8 text up to 64 KiB. Fixed contracts and the session-instructions precedence wrapper cannot be overridden.
    stream boolean false Request SSE and show provisional rating/text updates. Normal automatic approval waits for a completed, validated report; fast mode can approve on an early Safe rating. Approval rules are described below.
    reviewBash boolean true Review native shell permissions. The option keeps its existing name.
    reviewEdits boolean true Review native edit, write and patch tools, which use the edit permission action.
    reviewSkills boolean true Review native skill loading using the exact requested ID and full currently registered main instructions. Assumes the selected definition stays unchanged while permission is pending. Missing or ambiguous instructions remain manual.
    reviewTools boolean true Review other tools, including MCP and custom tools, using TOOL-REVIEW-PROMPT.md. Exact call linkage, arguments and permission scope are required. Descriptions and JSON Schemas are optional; missing evidence is explained to the LLM, which decides Safe/Unsafe. Safe reviews can use normal or fast automatic approval.
    reviewExternalDirectories boolean true Review directory access independently of operation-review switches. Directory approval can resume the operation without another prompt.
    autoApprove boolean false Allow Safe reviews once automatically. Normally waits for completion, validation and the countdown. Works throughout the current conversation, including subagent browsing and hidden/covered reports. Home, dashboards, unrelated conversations and native permission decision editing wait without cancellation.
    fastMode boolean false Requires autoApprove: true. Approve an eligible, front-of-queue request as soon as a Safe rating is parsed, bypassing the countdown. Respects stream; with stream: false, waits for the complete validated response. Finish the report and save it in the background after approval.
    extraCareful boolean true Include extra-careful guidance when autoApprove is enabled. Setting false omits that guidance without disabling automatic approval.
    autoApproveDelaySeconds integer 15 0–3600 seconds. Positive values include an extra 200 ms initial grace; 0 skips the wait but keeps the other approval checks. Ignored in fast mode.
    notify boolean true Master switch for desktop banners, sounds and reminders. false overrides all per-type controls.
    notifySound boolean true Master switch for notification audio. false silences every type and reminder without disabling enabled banners.
    staleReminderSeconds integer 60 0 through the maximum safe integer, in seconds. 0 disables reminders; otherwise eligible pending human interactions repeat at this interval, limited to the native front-of-queue blocker per conversation.
    notifications object All banners enabled; all sounds enabled except approved Keys: attention, unsafe, question, approved, error, ended. Each entry is an object with optional banner and sound booleans. Omitted types/fields retain their defaults; unknown types/fields are invalid. Controls also apply to reminders.
    notificationSoundDirectory string Unset (bundled sounds) Absolute, NUL-free path, at most 4096 string code units. Use the six notification type names as basenames: usable .wav first, then .mp3, then the corresponding bundled sound.
    formatRetries integer 1 0–100 additional assessment-format correction attempts. 0 disables format corrections, not independent transport recovery.
    maxOutputTokens integer 4096 1 through the maximum safe integer. Sent as max_tokens on every reviewer POST, including retries/corrections. Your provider/model enforces its supported output limit.
    timeoutMs integer 30000 1–3600000 milliseconds shared across evidence collection, HTTP requests, retries and corrections; not a fresh budget per POST.
    maxFiles integer 6 1–1000 distinct file candidates per shell/edit review, including unavailable candidates.
    maxEvidenceBytes integer 131072 1–16777216 bytes for command/source content, edit diffs or category-specific JSON evidence. Oversized source files and diffs use numbered relevant excerpts with a missing X more lines footer. Counts are exact when the bounded scan completes, otherwise marked unavailable. Other optional items may be omitted; oversized mandatory evidence fails analysis. Independently caps trimmed session instructions. This is not a cap on the full prompt or HTTP request.

    Notification channels are independent: banner: false, sound: true is sound-only, banner: true, sound: false is banner-only, both false disable that type. notify and notifySound take precedence. Invalid notification settings disable notification.

    My LLMs insist on writing a bunch of useless text to my README, so I collapsed them here in case anyone wants to inflict themselves (or more likely their agent) the pain of reading it.

    Usage The sidebar shows Safe, Unsafe, or Analysis unavailable. Explanations support Markdown and scrolling. If the sidebar is hidden, use OpenCode's Show sidebar command.

    Evidence lookup failures show Analysis unavailable and leave the native permission manual; they do not silently hide the error or start an approval.

    The reviewer panel appears only in the main conversation. Opening any subagent, including a nested one, hides the panel but does not stop normal or fast auto-approval within that conversation. A Safe report need not be displayed before approval. Command selectors, dialogs, report history, other session panels, sidebar hiding, terminal resizing and disabled mouse tracking do not cancel automation.

    Home, dashboard routes and unrelated conversations hold approval until you return to the request's conversation. A countdown already started keeps its original deadline; if it expires while waiting, approval can proceed immediately on return after fresh checks. A review that has never started a countdown starts its full delay when its conversation becomes eligible.

    The plugin doesn't enable mouse tracking or reveal a hidden sidebar unless you click an approval notification; it checks that the sidebar is absent from the main conversation before showing it. Hidden automatic reports are saved normally.

    Skill reviews use the full main instructions from the server's current registered skill definition, not a fresh SKILL.md read or an invocation-time snapshot. This assumes that the selected definition stays unchanged while its permission is pending. The exact skill ID, native call and permission scope must match. Missing, ambiguous or oversized main instructions remain manual. Supporting files and scripts are not inspected; their omission alone does not prevent review. Allowing a skill loads its instructions, not a blanket grant for later tool actions.

    Tool reviews use the exact effective tool ID, captured arguments and pending permission scope. Current server-side registered names, descriptions and input JSON Schemas are included when available. Missing, duplicate, oversized, unreadable or unsupported definitions are omitted with factual reasons sent to the LLM. Reviews still run when no registered tool data is available, with that absence stated explicitly. The LLM decides whether the remaining evidence justifies Safe or Unsafe; missing optional evidence does not itself force manual approval.

    This assumes any supplied definition stays unchanged while approval is pending and accurately describes behavior. The reviewer does not inspect executors or remote implementations, infer origin from names, or claim an invocation-time definition snapshot. Ambiguous call linkage still remains manual because the reviewer cannot identify the operation being approved. Saved reports use the tool category. All review categories default to enabled; autoApprove still defaults to false. reviewTools replaces reviewMcp and reviewCustomTools.

    Code Mode and its tool search stay enabled. Native tool calls can be reviewed when the reviewer can match them exactly to their permission requests. Nested calls without an exact match need manual approval. This assumes other installed plugins don't change what native tools do, replace or alias their executors, or rewrite commands after the reviewer has captured them.

    Subagent reviews include the latest user prompt from the main conversation. The V2 evidence collector can't get the exact task passed to the requesting subagent, so it leaves that out rather than guessing from older tasks, sibling requests or queued follow-ups. Missing optional context doesn't prevent review if the required operation evidence is verified. Context can't override the review rules.

    With streaming enabled, Evaluating and its spinner disappear when a rating arrives. The rating is provisional until the full response passes validation. A retry clears the preview and brings back the loading indicator. Normal auto-approval waits for the full validated report, then the configured countdown.

    With autoApprove: true and fastMode: true, a parsed Safe rating can approve the request before the streamed report finishes, without a countdown. The request must be first in OpenCode's permission queue and belong to the currently viewed conversation or its descendants. The report may be hidden or covered. Without streaming, fast mode waits for the full validated response, then skips the countdown.

    After confirmed fast approval, the live panel shows Auto-approved; finishing report… and stays ahead of newer requests until its report finishes. Hiding the panel, changing conversations or disabling review does not stop that already-approved assessment. Normal retries and the original deadline still apply. The panel closes when the assessment finishes; history saves continue in the background. Only a completed validated report is saved, including a final Unsafe rating after a retry. A failed remainder creates no report entry and cannot undo the earlier approval.

    Transient connection failures, HTTP 408/429/500/502/503/504 responses, and rejected assessment streams share up to two internal retries within timeoutMs, honoring server cooldowns. Stream protocol failures and output-token truncation regenerate the report from a fresh, identical request; previous previews are cleared and never joined to the next response. Socket failures after text starts, provider error/refusal events, tool calls, invalid JSON/UTF-8, incomplete SSE framing and resource-limit failures remain terminal. This recovery is separate from formatRetries. If token-limit errors persist, increase maxOutputTokens within your model's limits. Providers enforce this limit and may include reasoning tokens; the plugin's byte limits and overall timeout still apply. Retried requests can incur additional provider charges, and unreported usage remains unknown.

    During an auto-approval countdown, click the countdown to allow once immediately, or Cancel to leave the request manual. Only explicit stop actions, such as Cancel, disabling review, or native interruption/rejection, stop automation for that request. Returning, reopening the panel or re-enabling review won't restart a canceled countdown. Selecting native Allow always or Reject, or editing a rejection reason, holds approval without cancellation. Returning to Allow once resumes eligibility with the original countdown deadline. Ambiguous native choice state also waits. Temporary queue/cache/read failures wait and recover after fresh verification. Changed or unverifiable requests remain manual. If the plugin can't confirm the result of a sent approval attempt, it never retries that write. Cancel remains available during native preparation, until the approval is sent. Positive countdowns add a 200 ms initial grace and then update on remaining-time boundaries. A zero-second setting still approves without that grace.

    /reviewer-disable and /reviewer-enable control the current conversation and its descendants. The setting is saved for resume. Both commands are also in the command palette. Disabling stops unapproved reviews and countdowns while native permission controls remain available. Already-approved fast-mode reports finish in the background. The commands remain available when review configuration or the companion is unavailable; they save the preference without starting reviews.

    At startup, the reviewer waits for the connected server event stream before its first companion handshake. Connection waiting and retries for timed-out or not-yet-available handshakes share one 10-second budget. If it remains unavailable, history and notifications still work; reload the plugin to try again. Invalid handshake data is not retried.

    After a network disconnect, live review resumes automatically when the client and native event stream reconnect and a fresh companion handshake succeeds. Pending requests are read again without reusing old evidence. Canceled or uncertain approvals stay manual, and started countdowns keep their original deadline. A companion replaced without a connection change still requires a plugin reload.

    Session instructions

    From a main conversation, use Alt+I, /reviewer-instructions, or Reviewer: Session instructions in the palette. Enter adds a newline and Tab inserts a tab. Apply or Ctrl+S saves and closes; Clear only empties the draft. Close or Escape discards unapplied changes.

    These literal instructions go only to the judge, across every review category and all descendants of that conversation. Explicit session rules override ordinary and extra-careful review guidance, not evidence checks, approval safeguards, or the fixed response format. Other cases keep ordinary guidance.

    Rules persist locally until cleared, follow the conversation when moved, and are shared by this user's terminals. Forks start empty. Each newly started review captures the saved rules before evidence collection; changes do not alter existing reviews, retries or countdowns. The last successful Apply wins without replacing other open drafts. Report history does not store the rules.

    Outer whitespace is trimmed. Empty Apply clears saved rules. The UTF-8 limit is maxEvidenceBytes, independently of operation evidence; oversized text is never truncated. Unreadable or invalid saved rules silently fall back to ordinary guidance and open as an empty editor. Editing remains available with review disabled or the companion unavailable, provided maxEvidenceBytes is valid. The shortcut is configurable through keybinds["reviewer.instructions"] in global cli.json.

    Report history

    Use /reviewer-history or Reviewer: Report history in the command palette to open the newest saved report for the current conversation and its descendants. Alt+H toggles report history from the main conversation. Its shortcut can be remapped with keybinds["reviewer.history.toggle"] in global cli.json. Left/Right or the arrow buttons select older/newer entries; Up/Down and PageUp/PageDown scroll. Close or Escape closes history. Repeating the command returns to the newest report. History remains available with review disabled or invalid reviewer configuration. The history command preserves a hidden sidebar; clicking an approval notification reveals it and selects that permission's live or saved report. Keyboard browsing also works with mouse tracking off.

    Each entry shows the original report, available usage, model, provider and outcome: Auto approved, Manually approved, Cancelled, or Rejected. Cancelled records a linked interruption or removed request after automation was cancelled; it does not prove that the tool never ran. Ambiguous outcomes are omitted, including native Allow once replies whose submitting client cannot be identified. History keeps the latest completed report, which can differ from the report that caused approval in another window.

    Opening report history does not hold normal or fast auto-approval for eligible live requests. Browsing saved reports doesn't itself approve requests or replay notifications.

    Every completed, validated review is saved when its permission is approved manually, approved automatically, or rejected, including Unsafe reviews. Distinct reviews of the same permission are retained separately. Pending permissions, cancellation alone, partial previews and failed reviews do not save reports. Fast-mode reports that finish after approval are saved on completion. An approval whose submitting client cannot be identified is labeled Approved, without guessing manual or automatic attribution. They can contain private text and are kept under OpenCode's local state base: $XDG_STATE_HOME, or ~/.local/state when unset. Safely accessible history stays under opencode/opencode-reviewer/. If the host directory is unsuitable for private storage, the plugin uses its private, plugin-owned sibling opencode-reviewer-state/opencode-reviewer/ without changing host-directory permissions. An existing safe sibling database remains in use across restarts. Conversation enable/disable settings stay in the original OpenCode state directory. Saved modes require private ownership and a nonreplaceable path; an untrusted or unreadable mode suspends review rather than silently enabling it. Deleting a conversation currently leaves its saved history in place. Known live deletion events invalidate only volatile session state, not saved reports or totals. A missing list entry or bare 404 cannot prove deletion ownership. Windows using the same state directory share history, grouped by host directory and main conversation. Reports not yet saved can be lost on shutdown or during a prolonged storage failure.

    Usage totals

    Token and cost totals appear below completed reports when available. Open Reviewer: Statistics in the command palette for Lifetime and Conversation views. Use Tab or click a scope to switch. The dialog opens on Lifetime; Conversation uses the session selected when opened, resolving its root and all descendants, including subagents, within the current host-directory scope. Opening from a descendant shows the same combined conversation totals. Statistics remain available with review disabled or invalid reviewer configuration. OpenRouter costs use reported charges; other endpoints use available catalog estimates. Received usage counts even if a review fails or is interrupted. Unreported charges are missing from the totals.

    Both views show completed reviews, retries, tokens, cost, Safe/Unsafe percentages, confirmed auto-approvals, and average time to a rating and full report. Percentages use recorded completed reviews as their denominator. Each validated review counts once, even without usage data; a fresh review after re-enabling counts again. Retries count extra API attempts actually dispatched, including format corrections and transport recovery. Manual approvals are not auto-approvals. Timings run from evaluation start, including evidence and retries, to the accepted attempt's first rating and final validated response, excluding countdown time. Non-streaming reviews use the final response time for both measurements. Averages use running means and sample counts; stored review details also retain their individual timings. Both scopes persist in the existing history database across restart/resume. Conversation totals keep independent roots separate; Lifetime combines activity across conversations and host directories using the same state directory. Browsing and switching views do not add usage.

    Older retained history can provide a partial conversation baseline. Previously deleted details and usage predating history tracking cannot be attributed or reconstructed from legacy lifetime aggregates. Once recorded in conversation totals, deleting report details does not reduce those totals or Lifetime. Storage failures show unavailable, not zero.

    Tokens describe received provider usage, not just the visible explanation. One report may sum several reviewer POSTs, including format corrections and transport retries with received usage. Each POST can resend the prompt and evidence; corrections also include the failed response and feedback. Cached input remains part of the provider's input-token count. Lifetime and Conversation also retain received usage from unsuccessful or interrupted reviews, which do not count as completed reviews. Averages include measured reviews only.

    Reviews send the pending request, latest user prompt, project context, and relevant file snapshots, diffs, registered skill instructions, or tool arguments to your endpoint before approval. Shell file snapshots can follow symlinks outside the project. Missing evidence is noted in the report; ratings are advice based on the supplied evidence.

    Desktop notifications Transient banners have an **Opencode (Session name)** heading and a small status icon: green checkmark for approvals, orange exclamation mark for attention, red exclamation mark for Unsafe reviews, purple question mark for questions, red X for errors, and a neutral code mark for completed responses. GNOME controls the heading's font weight. The event message appears beneath it, even while the terminal is focused:
    • Session needs attention: manual permissions. Reviewed requests stay silent during identification, report generation and retries. Final Safe reports notify when auto-approval is disabled or canceled, or after failed approval is confirmed to remain pending. Waiting for conversation eligibility, native priority or an automatic countdown to start does not trigger attention. Disabled/unsupported reviews and terminal analysis failures can notify without a report.
    • Unsafe permission needs human approval: a completed, validated Unsafe review, with its own sound. Provisional streamed ratings never notify.
    • Agent has a question: a pending agent question at the front of the native input queue, with its own sound.
    • Reviewer approved a permission: a silent banner by default, sent after confirmed automatic approval, including fast mode while its report is still streaming. Positive and zero delays also notify on success; the countdown is silent. Opt in with notifications.approved.sound: true. Approval sounds are limited to one every two seconds, including delayed playback. Sounds from the same session wait for the previous player to finish, so successive alerts do not overlap.
    • Session error: an unrecovered session/provider failure, not a review failure.
    • Session ended: a completed root-agent response, not a question/permission pause or an explicit user interruption. Waits until the conversation's subagents, shells and terminals have finished, including work the main agent stopped waiting for.

    Permission attention, Unsafe and question notifications, including their reminders, apply only to the current native input blocker in each visited root conversation. Requests queued behind it stay silent and are re-evaluated from their current state when they become actionable. Questions wait behind pending permissions and earlier questions. A request resolved while queued never sends a delayed alert. A sequence of permissions that proceeds through automatic approval stays free of attention notifications. Losing the actionable position, restarting analysis or entering automatic approval cancels pending attention delivery and reminders.

    Each type's banner and sound can be controlled independently. For example, notifications.approved: { "banner": false, "sound": true } enables approval sounds without banners; notifications.question.sound: false keeps question banners silent. Set both to false to disable that type. notify: false overrides all types and disables reminders too; notifySound: false silences all types and their reminders. Sound-only delivery does not require a desktop banner or delivery acknowledgement. Invalid notification settings disable notification work without disabling reviews.

    Pending permissions and questions repeat after staleReminderSeconds, measured from actual initial dispatch, even if desktop delivery fails. Capacity waiting does not start the reminder clock or replace an undelivered initial alert. Reminders replace their previous banner and append (Reminder) to the event text, reusing the same sound, icon, session heading and click target. Only the native front-of-queue interaction per root conversation repeats: permissions precede questions, with the host's session/request ordering. Queued interactions stay silent. Handoff sends the next eligible request's initial notification, then waits a full interval before its first reminder. Independent visited conversations can each remind. The supported V2 host's root input queue includes the full descendant family, root first and then the host's public family order.

    Answering/dismissing a question in OpenCode, resolving a permission, deleting its session or closing the plugin stops its reminders. Closing/clicking a desktop banner, activating the terminal or viewing a conversation does not. Countdown and approval submission remain silent. Approval, error and completed-response notifications never repeat. Setting staleReminderSeconds: 0 disables all reminders.

    Notifications cover conversations visited in this terminal and their descendants. Existing pending requests at startup/resume are not replayed or reminded, but still participate in queue ordering. Clicking can select the originating GNOME Terminal tab and root conversation. Native input prompts cover the root and its full descendant family in the supported host. Open dialogs are left intact; other terminals still receive banners and sounds. Desktop policies control expiry/history and whether activation actually brings a window forward. Clicking Reviewer approved a permission opens that permission's unfinished fast-mode report, or its matching saved report if it has completed, revealing the sidebar if hidden. If a dialog is open, navigation waits until it closes and then checks whether the report is still live. A clicked live panel closes on completion like any fast-mode report. A pending save gets up to five seconds; if the report is unavailable or deleted, the click just opens its conversation rather than a different report. GNOME Terminal clicks use the desktop activation token to bring the correct tab forward across workspaces. GNOME may attribute the notification source to Terminal; the banner heading remains Opencode.

    Linux delivery uses notify-send, stdbuf, and gdbus; audio uses paplay or pw-play. On Ubuntu, libnotify-bin, coreutils, libglib2.0-bin, and pulseaudio-utils provide these utilities. The MP3 decoder and six default sounds are bundled; FFmpeg is not required. MP3 and mono/stereo PCM/float WAV files up to 4 MiB and 10 seconds are normalized toward -20 dBFS RMS with a -3 dBFS peak ceiling before playback. Custom sounds are loaded on first use; restart after replacing them. Audio is prepared before showing its banner and played from the normalized cache with a low-latency buffer; preparation never blocks permission approval. stdbuf makes delivery acknowledgements immediate instead of waiting for notify-send to flush its output when the banner closes. Delivery is bounded: up to 64 active tickets (48 routine), 256 waiting actionable episodes, and 128 waiting routine events. A full routine backlog drops new approval/error/completion events; already admitted events retain their exact targets. Actionable attention/Unsafe/question work has priority, rotating among roots; after eight actionable dispatches, waiting routine work gets a turn. Each root has only its current actionable episode, and waiting reminders coalesce. The process pool allows 24 children: 20 banners (16 routine), two players, one withdrawal and one activation. Audio preparation has two actual-operation slots; audio waiting is bounded to 64 requests. Cancellation and timeouts do not free physical slots until their operations settle. Queued actionable work is canceled when its request resolves or loses native queue ownership. Desktop/process failures remain best-effort and do not delay permission approval. Disable overlapping notification plugins to avoid duplicate alerts.

    Development

    Use Node.js 24.15.0+ within 24.x, or 22.22.2+ within 22.x, and npm. Run npm ci --ignore-scripts, then npm run check for typechecking, source tests, pure-helper tests and the build. npm run test:helpers runs the helper checks without building or starting OpenCode. Pushing a version tag runs verification and publication in one workflow: source tests, helper tests, reproducible builds and archive checks must pass before the exact verified archive is published. The manual Extended host checks workflow tests Node 22/24 and the real Linux TUI on the newest stable OpenCode V2. These timing-sensitive host fixtures are not an automatic release prerequisite. After building, run node scripts/test-runtime.mjs --profile v2-acceptance for that same profile. Use node scripts/test-runtime.mjs --list for the complete runtime inventory or node scripts/test-runtime.mjs --plan --profile v2-acceptance for a host-independent plan. Profiles select documented scenario/flag combinations; interactive desktop checks have separate prerequisites. See AGENTS.md for runtime tests and the tag-driven release process.

    Similar plugins