Persistent, sanitized subagent permission context for the OpenCode TUI.
When a subagent (or a nested descendant) raises a permission request, the native OpenCode prompt may show little context: with concurrent subagents the requester can be unclear, and tool arguments are not always visible. This plugin renders a compact widget in the right sidebar (first widget, above other sidebar plugins) that shows, for every pending request visible from your root session:
Permission requests (2)
1 @explore · bash
rg "permission.ask" packages/opencode
2 @librarian · context7.query-docs
/anomalyco/opencode
Rows show the requester, the tool, and the request essence in plain form
(command text, skill name, matched pattern) — not JSON. The full sanitized
payload and the matched patterns are available in the details dialog
(Subagent permission requests: details in the command palette).
A second palette command, Subagent permission requests: manage, lists every
pending request in the viewed session tree (all types, including ones the
widget does not duplicate) and rejects the selected one. This is the cleanup
surface for orphaned requests — pending requests whose run is gone and whose
native dialog will never appear (for example, subagents spawned into other
worktrees and later aborted). Rejecting removes the request from the core.
The widget lists only requests the native dialog renders without their essence (the generic "Call tool X" fallback: skills, MCP tools, custom permission types). Types the native dialog already renders fully — bash commands, file edits, task spawns, web fetches — are not duplicated in the widget.
Stale rows are cleaned up without polling: when a run ends without replying
(abort, interrupt), the core removes its pending requests silently, so the
widget refetches on the session's idle status transition; requests from
deleted sessions are filtered out even if the core still lists them.
The panel is informational only. The native OpenCode permission dialog remains the only decision surface; the plugin never approves, denies, or rewrites any permission decision and never injects anything into the session.
Related upstream reports: #15332 (permission context), #13715 (nested-subagent routing).
- OpenCode 1.18.25 or newer with the typed TUI slot API.
- No extra runtime installs: the plugin resolves
solid-js/@opentui/*inside the OpenCode runtime.
OpenCode must be fully restarted after installing or changing plugin files or configuration — configuration and plugin files load at startup.
<project>/.opencode/tui.json (or ~/.config/opencode/tui.json for global):
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["/absolute/path/to/opencode-subagent-permissions/src/tui.tsx"]
}Relative paths are resolved against the config file that declares them; use absolute paths to share one checkout across projects.
opencode plugin add opencode-subagent-permissionsand add the package name to tui.json (plugin) if your OpenCode version
does not patch the config for you.
- The panel appears in the root/primary session view while at least one pending request targets that session tree, and disappears when the last request resolves.
- Requests are ordered oldest-first; a newly arriving request never replaces an existing one; resolving one request removes only that request.
- Invocation context is resolved in priority order:
- the session tool part for the request's
(messageID, callID)— the same source the native permission prompt uses, - sanitized
permission.metadata, - an explicit
Arguments unavailable from OpenCodemarker (never invented context).
- the session tool part for the request's
- The origin row shows the requesting session's agent (
@explore), falling back to the session title, falling back toUnknown subagent. - A command palette entry (
Subagent permission requests: details) opens a scrollable dialog with the full sanitized payload for every pending request.
Permission arguments can contain credentials. Everything shown in the panel and the details dialog passes through the sanitizer first:
- object keys containing (case-insensitive)
authorization,cookie,password,passwd,secret,token,api_key,apikey,private_key,client_secretare replaced with[REDACTED]; - bearer-token and private-key patterns inside strings are redacted;
- limits: max depth 6, 50 entries per level, 2,000 chars per string, 16 KiB per payload, 240 chars per compact summary;
- nothing is written to disk by default; files are never read to build previews.
The repository ships a fixture project for the full loop
(test/fixtures/project/):
cd test/fixtures/project- Start the TUI:
opencode(config already registers the TUI plugin and anintegration-subagentagent whose Bash permission isask). - Prompt the primary session to spawn the
integration-subagentsubagent with the task "run your integration command". - Before approving, verify the TUI shows the panel with the subagent's name,
bash, and theprintf SUBAGENT_PERMISSION_INTEGRATION_TESTcommand. - Verify the native permission prompt is still the only decision surface. Approve once — only that panel entry disappears. Repeat with rejection.
- Run two requests concurrently (two subagents) and resolve them in reverse order; each row must disappear independently.
- Repeat with an MCP server configured via
mcpconfig that exposes a tool with nested arguments and a fake secret value; verify the arguments are shown and the secret is redacted.
If OpenCode fails to deliver a nested-subagent request to the TUI at all, that is the known upstream core limitation (#13715); the plugin cannot display a request it never sees.
npm install # local devDependencies only (cache it wherever you like)
npm run typecheck # tsc --noEmit, includes the TSX against real @opentui types
npm test # vitest: unit + UI-logic + integration smokeThe integration smoke test spawns a real opencode serve and asserts the
/permission data surface the panel depends on. The full TUI scenario needs a
live model and is documented above.
- OpenCode versions without the typed TUI slot API cannot render the panel.
- Nested descendants may not deliver permission events in some affected OpenCode versions; the plugin shows what OpenCode actually delivers and labels anything missing.
- The plugin starts with empty in-memory state after reload; the panel rebuilds from the authoritative pending-permissions query on the next render, so stale requests are never shown as live.